Repository: mickael-kerjean/filestash Branch: master Commit: aaa27d7f14e7 Files: 1285 Total size: 12.5 MB Directory structure: gitextract_gaclg1k0/ ├── .assets/ │ └── raw/ │ ├── Makefile │ └── photo.xcf ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.md │ │ ├── feature.md │ │ └── support.md │ └── stale.yml ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── cmd/ │ └── main.go ├── config/ │ ├── config.json │ └── mime.json ├── docker/ │ ├── Dockerfile │ └── docker-compose.yml ├── embed.go ├── go.mod ├── go.sum ├── public/ │ ├── Makefile │ ├── assets/ │ │ ├── boot/ │ │ │ ├── bundler_complete.js │ │ │ ├── bundler_init.js │ │ │ ├── common.js │ │ │ ├── ctrl_boot.d.ts │ │ │ ├── ctrl_boot_backoffice.js │ │ │ ├── ctrl_boot_frontoffice.js │ │ │ ├── router_backoffice.js │ │ │ └── router_frontoffice.js │ │ ├── components/ │ │ │ ├── breadcrumb.css │ │ │ ├── breadcrumb.js │ │ │ ├── decorator_shell_filemanager.css │ │ │ ├── decorator_shell_filemanager.js │ │ │ ├── dropdown.css │ │ │ ├── dropdown.js │ │ │ ├── fab.css │ │ │ ├── fab.js │ │ │ ├── form.js │ │ │ ├── icon.js │ │ │ ├── loader.js │ │ │ ├── modal.css │ │ │ ├── modal.js │ │ │ ├── notification.css │ │ │ ├── notification.js │ │ │ ├── sidebar.css │ │ │ ├── sidebar.js │ │ │ ├── sidebar_files.js │ │ │ ├── sidebar_tags.js │ │ │ └── skeleton.js │ │ ├── css/ │ │ │ ├── designsystem.css │ │ │ ├── designsystem_alert.css │ │ │ ├── designsystem_box.css │ │ │ ├── designsystem_button.css │ │ │ ├── designsystem_checkbox.css │ │ │ ├── designsystem_container.css │ │ │ ├── designsystem_darkmode.css │ │ │ ├── designsystem_dropdown.css │ │ │ ├── designsystem_formbuilder.css │ │ │ ├── designsystem_icon.css │ │ │ ├── designsystem_input.css │ │ │ ├── designsystem_inputgroup.css │ │ │ ├── designsystem_skeleton.css │ │ │ ├── designsystem_textarea.css │ │ │ └── designsystem_utils.css │ │ ├── embed/ │ │ │ ├── filestash-image.js │ │ │ ├── filestash-map.js │ │ │ └── filestash-table.js │ │ ├── helpers/ │ │ │ ├── loader.d.ts │ │ │ ├── loader.js │ │ │ ├── loader_wasm.js │ │ │ ├── log.d.ts │ │ │ ├── log.js │ │ │ └── sdk.js │ │ ├── index.js │ │ ├── lib/ │ │ │ ├── ajax.js │ │ │ ├── animate.d.ts │ │ │ ├── animate.js │ │ │ ├── assert.js │ │ │ ├── chromecast.js │ │ │ ├── dom.d.ts │ │ │ ├── dom.js │ │ │ ├── error.d.ts │ │ │ ├── error.js │ │ │ ├── form.d.ts │ │ │ ├── form.js │ │ │ ├── path.js │ │ │ ├── polyfill.js │ │ │ ├── random.d.ts │ │ │ ├── random.js │ │ │ ├── rx.d.ts │ │ │ ├── rx.js │ │ │ ├── settings.js │ │ │ ├── skeleton/ │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.js │ │ │ │ ├── lifecycle.d.ts │ │ │ │ ├── lifecycle.js │ │ │ │ ├── router.d.ts │ │ │ │ └── router.js │ │ │ ├── store.js │ │ │ └── vendor/ │ │ │ ├── bcrypt.js │ │ │ ├── codemirror/ │ │ │ │ ├── .editorconfig │ │ │ │ ├── .gitattributes │ │ │ │ ├── .npmignore │ │ │ │ ├── AUTHORS │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── CONTRIBUTING.md │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── addon/ │ │ │ │ │ ├── comment/ │ │ │ │ │ │ ├── comment.js │ │ │ │ │ │ └── continuecomment.js │ │ │ │ │ ├── dialog/ │ │ │ │ │ │ ├── dialog.css │ │ │ │ │ │ └── dialog.js │ │ │ │ │ ├── display/ │ │ │ │ │ │ ├── autorefresh.js │ │ │ │ │ │ ├── fullscreen.css │ │ │ │ │ │ ├── fullscreen.js │ │ │ │ │ │ ├── panel.js │ │ │ │ │ │ ├── placeholder.js │ │ │ │ │ │ └── rulers.js │ │ │ │ │ ├── edit/ │ │ │ │ │ │ ├── closebrackets.js │ │ │ │ │ │ ├── closetag.js │ │ │ │ │ │ ├── continuelist.js │ │ │ │ │ │ ├── matchbrackets.js │ │ │ │ │ │ ├── matchtags.js │ │ │ │ │ │ └── trailingspace.js │ │ │ │ │ ├── fold/ │ │ │ │ │ │ ├── brace-fold.js │ │ │ │ │ │ ├── comment-fold.js │ │ │ │ │ │ ├── foldcode.js │ │ │ │ │ │ ├── foldgutter.css │ │ │ │ │ │ ├── foldgutter.js │ │ │ │ │ │ ├── indent-fold.js │ │ │ │ │ │ ├── markdown-fold.js │ │ │ │ │ │ └── xml-fold.js │ │ │ │ │ ├── hint/ │ │ │ │ │ │ ├── anyword-hint.js │ │ │ │ │ │ ├── css-hint.js │ │ │ │ │ │ ├── html-hint.js │ │ │ │ │ │ ├── javascript-hint.js │ │ │ │ │ │ ├── show-hint.css │ │ │ │ │ │ ├── show-hint.js │ │ │ │ │ │ ├── sql-hint.js │ │ │ │ │ │ └── xml-hint.js │ │ │ │ │ ├── lint/ │ │ │ │ │ │ ├── coffeescript-lint.js │ │ │ │ │ │ ├── css-lint.js │ │ │ │ │ │ ├── html-lint.js │ │ │ │ │ │ ├── javascript-lint.js │ │ │ │ │ │ ├── json-lint.js │ │ │ │ │ │ ├── lint.css │ │ │ │ │ │ ├── lint.js │ │ │ │ │ │ └── yaml-lint.js │ │ │ │ │ ├── merge/ │ │ │ │ │ │ ├── merge.css │ │ │ │ │ │ └── merge.js │ │ │ │ │ ├── mode/ │ │ │ │ │ │ ├── loadmode.js │ │ │ │ │ │ ├── multiplex.js │ │ │ │ │ │ ├── multiplex_test.js │ │ │ │ │ │ ├── overlay.js │ │ │ │ │ │ └── simple.js │ │ │ │ │ ├── runmode/ │ │ │ │ │ │ ├── colorize.js │ │ │ │ │ │ ├── runmode-standalone.js │ │ │ │ │ │ ├── runmode.js │ │ │ │ │ │ └── runmode.node.js │ │ │ │ │ ├── scroll/ │ │ │ │ │ │ ├── annotatescrollbar.js │ │ │ │ │ │ ├── scrollpastend.js │ │ │ │ │ │ ├── simplescrollbars.css │ │ │ │ │ │ └── simplescrollbars.js │ │ │ │ │ ├── search/ │ │ │ │ │ │ ├── jump-to-line.js │ │ │ │ │ │ ├── match-highlighter.js │ │ │ │ │ │ ├── matchesonscrollbar.css │ │ │ │ │ │ ├── matchesonscrollbar.js │ │ │ │ │ │ ├── search.js │ │ │ │ │ │ └── searchcursor.js │ │ │ │ │ ├── selection/ │ │ │ │ │ │ ├── active-line.js │ │ │ │ │ │ ├── mark-selection.js │ │ │ │ │ │ └── selection-pointer.js │ │ │ │ │ ├── tern/ │ │ │ │ │ │ ├── tern.css │ │ │ │ │ │ ├── tern.js │ │ │ │ │ │ └── worker.js │ │ │ │ │ └── wrap/ │ │ │ │ │ └── hardwrap.js │ │ │ │ ├── bin/ │ │ │ │ │ ├── authors.sh │ │ │ │ │ ├── lint │ │ │ │ │ ├── release │ │ │ │ │ ├── source-highlight │ │ │ │ │ └── upload-release.js │ │ │ │ ├── demo/ │ │ │ │ │ ├── activeline.html │ │ │ │ │ ├── anywordhint.html │ │ │ │ │ ├── bidi.html │ │ │ │ │ ├── btree.html │ │ │ │ │ ├── buffers.html │ │ │ │ │ ├── changemode.html │ │ │ │ │ ├── closebrackets.html │ │ │ │ │ ├── closetag.html │ │ │ │ │ ├── complete.html │ │ │ │ │ ├── emacs.html │ │ │ │ │ ├── folding.html │ │ │ │ │ ├── fullscreen.html │ │ │ │ │ ├── hardwrap.html │ │ │ │ │ ├── html5complete.html │ │ │ │ │ ├── indentwrap.html │ │ │ │ │ ├── lint.html │ │ │ │ │ ├── loadmode.html │ │ │ │ │ ├── marker.html │ │ │ │ │ ├── markselection.html │ │ │ │ │ ├── matchhighlighter.html │ │ │ │ │ ├── matchtags.html │ │ │ │ │ ├── merge.html │ │ │ │ │ ├── multiplex.html │ │ │ │ │ ├── mustache.html │ │ │ │ │ ├── panel.html │ │ │ │ │ ├── placeholder.html │ │ │ │ │ ├── preview.html │ │ │ │ │ ├── requirejs.html │ │ │ │ │ ├── resize.html │ │ │ │ │ ├── rulers.html │ │ │ │ │ ├── runmode-standalone.html │ │ │ │ │ ├── runmode.html │ │ │ │ │ ├── search.html │ │ │ │ │ ├── simplemode.html │ │ │ │ │ ├── simplescrollbars.html │ │ │ │ │ ├── spanaffectswrapping_shim.html │ │ │ │ │ ├── sublime.html │ │ │ │ │ ├── tern.html │ │ │ │ │ ├── theme.html │ │ │ │ │ ├── trailingspace.html │ │ │ │ │ ├── variableheight.html │ │ │ │ │ ├── vim.html │ │ │ │ │ ├── visibletabs.html │ │ │ │ │ ├── widget.html │ │ │ │ │ └── xmlcomplete.html │ │ │ │ ├── doc/ │ │ │ │ │ ├── activebookmark.js │ │ │ │ │ ├── docs.css │ │ │ │ │ ├── internals.html │ │ │ │ │ ├── manual.html │ │ │ │ │ ├── realworld.html │ │ │ │ │ ├── releases.html │ │ │ │ │ ├── reporting.html │ │ │ │ │ ├── upgrade_v2.2.html │ │ │ │ │ ├── upgrade_v3.html │ │ │ │ │ └── upgrade_v4.html │ │ │ │ ├── index.html │ │ │ │ ├── keymap/ │ │ │ │ │ ├── emacs.js │ │ │ │ │ ├── sublime.js │ │ │ │ │ └── vim.js │ │ │ │ ├── lib/ │ │ │ │ │ ├── codemirror.css │ │ │ │ │ └── codemirror.js │ │ │ │ ├── mode/ │ │ │ │ │ ├── apl/ │ │ │ │ │ │ ├── apl.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── asciiarmor/ │ │ │ │ │ │ ├── asciiarmor.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── asn.1/ │ │ │ │ │ │ ├── asn.1.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── asterisk/ │ │ │ │ │ │ ├── asterisk.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── brainfuck/ │ │ │ │ │ │ ├── brainfuck.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── clike/ │ │ │ │ │ │ ├── clike.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── scala.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── clojure/ │ │ │ │ │ │ ├── clojure.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── cmake/ │ │ │ │ │ │ ├── cmake.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── cobol/ │ │ │ │ │ │ ├── cobol.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── coffeescript/ │ │ │ │ │ │ ├── coffeescript.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── commonlisp/ │ │ │ │ │ │ ├── commonlisp.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── crystal/ │ │ │ │ │ │ ├── crystal.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── css/ │ │ │ │ │ │ ├── css.js │ │ │ │ │ │ ├── gss.html │ │ │ │ │ │ ├── gss_test.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── less.html │ │ │ │ │ │ ├── less_test.js │ │ │ │ │ │ ├── scss.html │ │ │ │ │ │ ├── scss_test.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── cypher/ │ │ │ │ │ │ ├── cypher.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── d/ │ │ │ │ │ │ ├── d.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── dart/ │ │ │ │ │ │ ├── dart.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── diff/ │ │ │ │ │ │ ├── diff.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── django/ │ │ │ │ │ │ ├── django.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── dockerfile/ │ │ │ │ │ │ ├── dockerfile.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── dtd/ │ │ │ │ │ │ ├── dtd.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── dylan/ │ │ │ │ │ │ ├── dylan.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── ebnf/ │ │ │ │ │ │ ├── ebnf.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── ecl/ │ │ │ │ │ │ ├── ecl.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── eiffel/ │ │ │ │ │ │ ├── eiffel.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── elm/ │ │ │ │ │ │ ├── elm.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── erlang/ │ │ │ │ │ │ ├── erlang.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── factor/ │ │ │ │ │ │ ├── factor.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── fcl/ │ │ │ │ │ │ ├── fcl.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── forth/ │ │ │ │ │ │ ├── forth.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── fortran/ │ │ │ │ │ │ ├── fortran.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── gas/ │ │ │ │ │ │ ├── gas.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── gfm/ │ │ │ │ │ │ ├── gfm.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── gherkin/ │ │ │ │ │ │ ├── gherkin.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── go/ │ │ │ │ │ │ ├── go.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── groovy/ │ │ │ │ │ │ ├── groovy.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── haml/ │ │ │ │ │ │ ├── haml.js │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── handlebars/ │ │ │ │ │ │ ├── handlebars.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── haskell/ │ │ │ │ │ │ ├── haskell.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── haskell-literate/ │ │ │ │ │ │ ├── haskell-literate.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── haxe/ │ │ │ │ │ │ ├── haxe.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── htmlembedded/ │ │ │ │ │ │ ├── htmlembedded.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── htmlmixed/ │ │ │ │ │ │ ├── htmlmixed.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── http/ │ │ │ │ │ │ ├── http.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── idl/ │ │ │ │ │ │ ├── idl.js │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── javascript/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── javascript.js │ │ │ │ │ │ ├── json-ld.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── typescript.html │ │ │ │ │ ├── jinja2/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── jinja2.js │ │ │ │ │ ├── jsx/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── jsx.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── julia/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── julia.js │ │ │ │ │ ├── livescript/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── livescript.js │ │ │ │ │ ├── lua/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── lua.js │ │ │ │ │ ├── markdown/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── markdown.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── mathematica/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── mathematica.js │ │ │ │ │ ├── mbox/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── mbox.js │ │ │ │ │ ├── meta.js │ │ │ │ │ ├── mirc/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── mirc.js │ │ │ │ │ ├── mllike/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── mllike.js │ │ │ │ │ ├── modelica/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── modelica.js │ │ │ │ │ ├── mscgen/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── mscgen.js │ │ │ │ │ │ ├── mscgen_test.js │ │ │ │ │ │ ├── msgenny_test.js │ │ │ │ │ │ └── xu_test.js │ │ │ │ │ ├── mumps/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── mumps.js │ │ │ │ │ ├── nginx/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── nginx.js │ │ │ │ │ ├── nsis/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── nsis.js │ │ │ │ │ ├── ntriples/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── ntriples.js │ │ │ │ │ ├── octave/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── octave.js │ │ │ │ │ ├── oz/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── oz.js │ │ │ │ │ ├── pascal/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── pascal.js │ │ │ │ │ ├── pegjs/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── pegjs.js │ │ │ │ │ ├── perl/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── perl.js │ │ │ │ │ ├── php/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── php.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── pig/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── pig.js │ │ │ │ │ ├── powershell/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── powershell.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── properties/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── properties.js │ │ │ │ │ ├── protobuf/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── protobuf.js │ │ │ │ │ ├── pug/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── pug.js │ │ │ │ │ ├── puppet/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── puppet.js │ │ │ │ │ ├── python/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── python.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── q/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── q.js │ │ │ │ │ ├── r/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── r.js │ │ │ │ │ ├── rpm/ │ │ │ │ │ │ ├── changes/ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── rpm.js │ │ │ │ │ ├── rst/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── rst.js │ │ │ │ │ ├── ruby/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── ruby.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── rust/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── rust.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── sas/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── sas.js │ │ │ │ │ ├── sass/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── sass.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── scheme/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── scheme.js │ │ │ │ │ ├── shell/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── shell.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── sieve/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── sieve.js │ │ │ │ │ ├── slim/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── slim.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── smalltalk/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── smalltalk.js │ │ │ │ │ ├── smarty/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── smarty.js │ │ │ │ │ ├── solr/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── solr.js │ │ │ │ │ ├── soy/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── soy.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── sparql/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── sparql.js │ │ │ │ │ ├── spreadsheet/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── spreadsheet.js │ │ │ │ │ ├── sql/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── sql.js │ │ │ │ │ ├── stex/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── stex.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── stylus/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── stylus.js │ │ │ │ │ ├── swift/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── swift.js │ │ │ │ │ │ └── test.js │ │ │ │ │ ├── tcl/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── tcl.js │ │ │ │ │ ├── textile/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── textile.js │ │ │ │ │ ├── tiddlywiki/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── tiddlywiki.css │ │ │ │ │ │ └── tiddlywiki.js │ │ │ │ │ ├── tiki/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── tiki.css │ │ │ │ │ │ └── tiki.js │ │ │ │ │ ├── toml/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── toml.js │ │ │ │ │ ├── tornado/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── tornado.js │ │ │ │ │ ├── troff/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── troff.js │ │ │ │ │ ├── ttcn/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── ttcn.js │ │ │ │ │ ├── ttcn-cfg/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── ttcn-cfg.js │ │ │ │ │ ├── turtle/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── turtle.js │ │ │ │ │ ├── twig/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── twig.js │ │ │ │ │ ├── vb/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── vb.js │ │ │ │ │ ├── vbscript/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── vbscript.js │ │ │ │ │ ├── velocity/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── velocity.js │ │ │ │ │ ├── verilog/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── verilog.js │ │ │ │ │ ├── vhdl/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── vhdl.js │ │ │ │ │ ├── vue/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── vue.js │ │ │ │ │ ├── wast/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── wast.js │ │ │ │ │ ├── webidl/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── webidl.js │ │ │ │ │ ├── xml/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── xml.js │ │ │ │ │ ├── xquery/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── test.js │ │ │ │ │ │ └── xquery.js │ │ │ │ │ ├── yacas/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── yacas.js │ │ │ │ │ ├── yaml/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── yaml.js │ │ │ │ │ ├── yaml-frontmatter/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── yaml-frontmatter.js │ │ │ │ │ └── z80/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── z80.js │ │ │ │ ├── package.json │ │ │ │ ├── rollup.config.js │ │ │ │ ├── src/ │ │ │ │ │ ├── addon/ │ │ │ │ │ │ └── runmode/ │ │ │ │ │ │ ├── codemirror-standalone.js │ │ │ │ │ │ ├── codemirror.node.js │ │ │ │ │ │ ├── runmode-standalone.js │ │ │ │ │ │ └── runmode.node.js │ │ │ │ │ ├── codemirror.js │ │ │ │ │ ├── display/ │ │ │ │ │ │ ├── Display.js │ │ │ │ │ │ ├── focus.js │ │ │ │ │ │ ├── gutters.js │ │ │ │ │ │ ├── highlight_worker.js │ │ │ │ │ │ ├── line_numbers.js │ │ │ │ │ │ ├── mode_state.js │ │ │ │ │ │ ├── operations.js │ │ │ │ │ │ ├── scroll_events.js │ │ │ │ │ │ ├── scrollbars.js │ │ │ │ │ │ ├── scrolling.js │ │ │ │ │ │ ├── selection.js │ │ │ │ │ │ ├── update_display.js │ │ │ │ │ │ ├── update_line.js │ │ │ │ │ │ ├── update_lines.js │ │ │ │ │ │ └── view_tracking.js │ │ │ │ │ ├── edit/ │ │ │ │ │ │ ├── CodeMirror.js │ │ │ │ │ │ ├── commands.js │ │ │ │ │ │ ├── deleteNearSelection.js │ │ │ │ │ │ ├── drop_events.js │ │ │ │ │ │ ├── fromTextArea.js │ │ │ │ │ │ ├── global_events.js │ │ │ │ │ │ ├── key_events.js │ │ │ │ │ │ ├── legacy.js │ │ │ │ │ │ ├── main.js │ │ │ │ │ │ ├── methods.js │ │ │ │ │ │ ├── mouse_events.js │ │ │ │ │ │ ├── options.js │ │ │ │ │ │ └── utils.js │ │ │ │ │ ├── input/ │ │ │ │ │ │ ├── ContentEditableInput.js │ │ │ │ │ │ ├── TextareaInput.js │ │ │ │ │ │ ├── indent.js │ │ │ │ │ │ ├── input.js │ │ │ │ │ │ ├── keymap.js │ │ │ │ │ │ ├── keynames.js │ │ │ │ │ │ └── movement.js │ │ │ │ │ ├── line/ │ │ │ │ │ │ ├── highlight.js │ │ │ │ │ │ ├── line_data.js │ │ │ │ │ │ ├── pos.js │ │ │ │ │ │ ├── saw_special_spans.js │ │ │ │ │ │ ├── spans.js │ │ │ │ │ │ └── utils_line.js │ │ │ │ │ ├── measurement/ │ │ │ │ │ │ ├── position_measurement.js │ │ │ │ │ │ └── widgets.js │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── Doc.js │ │ │ │ │ │ ├── change_measurement.js │ │ │ │ │ │ ├── changes.js │ │ │ │ │ │ ├── chunk.js │ │ │ │ │ │ ├── document_data.js │ │ │ │ │ │ ├── history.js │ │ │ │ │ │ ├── line_widget.js │ │ │ │ │ │ ├── mark_text.js │ │ │ │ │ │ ├── selection.js │ │ │ │ │ │ └── selection_updates.js │ │ │ │ │ ├── modes.js │ │ │ │ │ └── util/ │ │ │ │ │ ├── StringStream.js │ │ │ │ │ ├── bidi.js │ │ │ │ │ ├── browser.js │ │ │ │ │ ├── dom.js │ │ │ │ │ ├── event.js │ │ │ │ │ ├── feature_detection.js │ │ │ │ │ ├── misc.js │ │ │ │ │ └── operation_group.js │ │ │ │ └── theme/ │ │ │ │ ├── 3024-day.css │ │ │ │ ├── 3024-night.css │ │ │ │ ├── abbott.css │ │ │ │ ├── abcdef.css │ │ │ │ ├── ambiance-mobile.css │ │ │ │ ├── ambiance.css │ │ │ │ ├── ayu-dark.css │ │ │ │ ├── ayu-mirage.css │ │ │ │ ├── base16-dark.css │ │ │ │ ├── base16-light.css │ │ │ │ ├── bespin.css │ │ │ │ ├── blackboard.css │ │ │ │ ├── cobalt.css │ │ │ │ ├── colorforth.css │ │ │ │ ├── darcula.css │ │ │ │ ├── dracula.css │ │ │ │ ├── duotone-dark.css │ │ │ │ ├── duotone-light.css │ │ │ │ ├── eclipse.css │ │ │ │ ├── elegant.css │ │ │ │ ├── erlang-dark.css │ │ │ │ ├── gruvbox-dark.css │ │ │ │ ├── hopscotch.css │ │ │ │ ├── icecoder.css │ │ │ │ ├── idea.css │ │ │ │ ├── isotope.css │ │ │ │ ├── juejin.css │ │ │ │ ├── lesser-dark.css │ │ │ │ ├── liquibyte.css │ │ │ │ ├── lucario.css │ │ │ │ ├── material-darker.css │ │ │ │ ├── material-ocean.css │ │ │ │ ├── material-palenight.css │ │ │ │ ├── material.css │ │ │ │ ├── mbo.css │ │ │ │ ├── mdn-like.css │ │ │ │ ├── midnight.css │ │ │ │ ├── monokai.css │ │ │ │ ├── moxer.css │ │ │ │ ├── neat.css │ │ │ │ ├── neo.css │ │ │ │ ├── night.css │ │ │ │ ├── nord.css │ │ │ │ ├── oceanic-next.css │ │ │ │ ├── panda-syntax.css │ │ │ │ ├── paraiso-dark.css │ │ │ │ ├── paraiso-light.css │ │ │ │ ├── pastel-on-dark.css │ │ │ │ ├── railscasts.css │ │ │ │ ├── rubyblue.css │ │ │ │ ├── seti.css │ │ │ │ ├── shadowfox.css │ │ │ │ ├── solarized.css │ │ │ │ ├── ssms.css │ │ │ │ ├── the-matrix.css │ │ │ │ ├── tomorrow-night-bright.css │ │ │ │ ├── tomorrow-night-eighties.css │ │ │ │ ├── ttcn.css │ │ │ │ ├── twilight.css │ │ │ │ ├── vibrant-ink.css │ │ │ │ ├── xq-dark.css │ │ │ │ ├── xq-light.css │ │ │ │ ├── yeti.css │ │ │ │ ├── yonce.css │ │ │ │ └── zenburn.css │ │ │ ├── exif-js.js │ │ │ ├── hlsjs/ │ │ │ │ └── hls.js │ │ │ ├── leaflet/ │ │ │ │ ├── Control.Draw.js │ │ │ │ ├── Leaflet.Draw.Event.js │ │ │ │ ├── Leaflet.draw.js │ │ │ │ ├── Toolbar.js │ │ │ │ ├── Tooltip.js │ │ │ │ ├── draw/ │ │ │ │ │ ├── DrawToolbar.js │ │ │ │ │ └── handler/ │ │ │ │ │ ├── Draw.Circle.js │ │ │ │ │ ├── Draw.CircleMarker.js │ │ │ │ │ ├── Draw.Feature.js │ │ │ │ │ ├── Draw.Marker.js │ │ │ │ │ ├── Draw.Polygon.js │ │ │ │ │ ├── Draw.Polyline.js │ │ │ │ │ ├── Draw.Rectangle.js │ │ │ │ │ └── Draw.SimpleShape.js │ │ │ │ ├── edit/ │ │ │ │ │ ├── EditToolbar.js │ │ │ │ │ └── handler/ │ │ │ │ │ ├── Edit.Circle.js │ │ │ │ │ ├── Edit.CircleMarker.js │ │ │ │ │ ├── Edit.Marker.js │ │ │ │ │ ├── Edit.Poly.js │ │ │ │ │ ├── Edit.Rectangle.js │ │ │ │ │ ├── Edit.SimpleShape.js │ │ │ │ │ ├── EditToolbar.Delete.js │ │ │ │ │ └── EditToolbar.Edit.js │ │ │ │ ├── ext/ │ │ │ │ │ ├── GeometryUtil.js │ │ │ │ │ ├── LatLngUtil.js │ │ │ │ │ ├── LineUtil.Intersect.js │ │ │ │ │ ├── Polygon.Intersect.js │ │ │ │ │ ├── Polyline.Intersect.js │ │ │ │ │ └── TouchEvents.js │ │ │ │ ├── leaflet-measure.css │ │ │ │ ├── leaflet-measure.js │ │ │ │ ├── leaflet.css │ │ │ │ ├── leaflet.draw.css │ │ │ │ ├── leaflet.js │ │ │ │ └── shp.esm.js │ │ │ ├── pdfjs/ │ │ │ │ ├── pdf.js │ │ │ │ ├── pdf.sandbox.js │ │ │ │ └── pdf.worker.js │ │ │ ├── three/ │ │ │ │ ├── FontLoader.js │ │ │ │ ├── OrbitControls.js │ │ │ │ ├── Projector.js │ │ │ │ ├── TextGeometry.js │ │ │ │ ├── three.module.js │ │ │ │ └── viewcube.js │ │ │ └── wavesurfer.js │ │ ├── locales/ │ │ │ ├── _.json │ │ │ ├── az.json │ │ │ ├── be.json │ │ │ ├── bg.json │ │ │ ├── ca.json │ │ │ ├── cs.json │ │ │ ├── da.json │ │ │ ├── de.json │ │ │ ├── el.json │ │ │ ├── es.json │ │ │ ├── et.json │ │ │ ├── eu.json │ │ │ ├── fi.json │ │ │ ├── fr.json │ │ │ ├── gl.json │ │ │ ├── hr.json │ │ │ ├── hu.json │ │ │ ├── id.json │ │ │ ├── index.js │ │ │ ├── is.json │ │ │ ├── it.json │ │ │ ├── ja.json │ │ │ ├── ka.json │ │ │ ├── ko.json │ │ │ ├── lt.json │ │ │ ├── lv.json │ │ │ ├── mn.json │ │ │ ├── nb.json │ │ │ ├── nl.json │ │ │ ├── no.json │ │ │ ├── pl.json │ │ │ ├── pt.json │ │ │ ├── ro.json │ │ │ ├── ru.json │ │ │ ├── script.js │ │ │ ├── sk.json │ │ │ ├── sl.json │ │ │ ├── sr.json │ │ │ ├── sv.json │ │ │ ├── th.json │ │ │ ├── tr.json │ │ │ ├── uk.json │ │ │ ├── vi.json │ │ │ ├── zh.json │ │ │ └── zh_tw.json │ │ ├── model/ │ │ │ ├── backend.js │ │ │ ├── chromecast.js │ │ │ ├── config.d.ts │ │ │ ├── config.js │ │ │ ├── plugin.js │ │ │ └── session.js │ │ └── pages/ │ │ ├── adminpage/ │ │ │ ├── animate.js │ │ │ ├── component_box-item.js │ │ │ ├── ctrl_about.css │ │ │ ├── ctrl_about.js │ │ │ ├── ctrl_activity.js │ │ │ ├── ctrl_activity_audit.js │ │ │ ├── ctrl_activity_form.js │ │ │ ├── ctrl_activity_graph.css │ │ │ ├── ctrl_activity_graph.js │ │ │ ├── ctrl_activity_viewer.css │ │ │ ├── ctrl_activity_viewer.js │ │ │ ├── ctrl_login.css │ │ │ ├── ctrl_login.js │ │ │ ├── ctrl_settings.js │ │ │ ├── ctrl_setup.css │ │ │ ├── ctrl_setup.js │ │ │ ├── ctrl_storage.css │ │ │ ├── ctrl_storage.js │ │ │ ├── ctrl_storage_component_authentication.js │ │ │ ├── ctrl_storage_component_backend.js │ │ │ ├── ctrl_storage_component_banner.js │ │ │ ├── ctrl_storage_state.js │ │ │ ├── ctrl_workflow.css │ │ │ ├── ctrl_workflow.js │ │ │ ├── ctrl_workflow_details.js │ │ │ ├── ctrl_workflow_list.js │ │ │ ├── decorator.js │ │ │ ├── decorator_admin_only.js │ │ │ ├── decorator_sidemenu.css │ │ │ ├── decorator_sidemenu.js │ │ │ ├── helper_form.js │ │ │ ├── index.css │ │ │ ├── model_admin_session.js │ │ │ ├── model_audit.js │ │ │ ├── model_auth_middleware.js │ │ │ ├── model_backend.js │ │ │ ├── model_config.js │ │ │ ├── model_log.js │ │ │ ├── model_release.js │ │ │ ├── model_setup.js │ │ │ └── model_workflow.js │ │ ├── connectpage/ │ │ │ ├── ctrl_forkme.js │ │ │ ├── ctrl_form.css │ │ │ ├── ctrl_form.js │ │ │ ├── ctrl_form_state.js │ │ │ ├── ctrl_poweredby.js │ │ │ ├── model_backend.js │ │ │ └── model_config.js │ │ ├── ctrl_adminpage.js │ │ ├── ctrl_connectpage.css │ │ ├── ctrl_connectpage.js │ │ ├── ctrl_error.js │ │ ├── ctrl_filespage.css │ │ ├── ctrl_filespage.js │ │ ├── ctrl_homepage.js │ │ ├── ctrl_logout.js │ │ ├── ctrl_notfound.js │ │ ├── ctrl_sharepage.css │ │ ├── ctrl_sharepage.js │ │ ├── ctrl_viewerpage.css │ │ ├── ctrl_viewerpage.js │ │ ├── filespage/ │ │ │ ├── cache.js │ │ │ ├── ctrl_filesystem.css │ │ │ ├── ctrl_filesystem.js │ │ │ ├── ctrl_frequentlyaccess.css │ │ │ ├── ctrl_frequentlyaccess.js │ │ │ ├── ctrl_newitem.css │ │ │ ├── ctrl_newitem.js │ │ │ ├── ctrl_submenu.css │ │ │ ├── ctrl_submenu.js │ │ │ ├── ctrl_upload.css │ │ │ ├── ctrl_upload.d.ts │ │ │ ├── ctrl_upload.js │ │ │ ├── helper.js │ │ │ ├── modal.css │ │ │ ├── modal_delete.js │ │ │ ├── modal_rename.js │ │ │ ├── modal_share.css │ │ │ ├── modal_share.js │ │ │ ├── modal_tag.css │ │ │ ├── modal_tag.js │ │ │ ├── model_acl.js │ │ │ ├── model_files.js │ │ │ ├── model_tag.js │ │ │ ├── model_virtual_layer.js │ │ │ ├── state_config.js │ │ │ ├── state_newthing.js │ │ │ ├── state_selection.js │ │ │ ├── thing.css │ │ │ ├── thing.d.ts │ │ │ └── thing.js │ │ └── viewerpage/ │ │ ├── application_3d/ │ │ │ ├── init.js │ │ │ ├── scene_cube.js │ │ │ ├── scene_light.js │ │ │ └── toolbar.js │ │ ├── application_3d.css │ │ ├── application_3d.d.ts │ │ ├── application_3d.js │ │ ├── application_audio.css │ │ ├── application_audio.d.ts │ │ ├── application_audio.js │ │ ├── application_downloader.css │ │ ├── application_downloader.js │ │ ├── application_ebook.css │ │ ├── application_ebook.d.ts │ │ ├── application_ebook.js │ │ ├── application_editor/ │ │ │ ├── clike.js │ │ │ ├── clojure.js │ │ │ ├── cmake.js │ │ │ ├── commonlisp.js │ │ │ ├── css.js │ │ │ ├── diff.js │ │ │ ├── dockerfile.js │ │ │ ├── elm.js │ │ │ ├── emacs-org.js │ │ │ ├── erlang.js │ │ │ ├── go.js │ │ │ ├── groovy.js │ │ │ ├── htmlmixed.js │ │ │ ├── java.js │ │ │ ├── javascript.js │ │ │ ├── jsx.js │ │ │ ├── keymap_base.js │ │ │ ├── keymap_vim.js │ │ │ ├── lua.js │ │ │ ├── orgmode.js │ │ │ ├── perl.js │ │ │ ├── php.js │ │ │ ├── properties.js │ │ │ ├── python.js │ │ │ ├── r.js │ │ │ ├── ruby.js │ │ │ ├── rust.js │ │ │ ├── sass.js │ │ │ ├── shell.js │ │ │ ├── sparql.js │ │ │ ├── spreadsheet.js │ │ │ ├── sql.js │ │ │ ├── stex.js │ │ │ ├── text.js │ │ │ ├── xml.js │ │ │ ├── yaml-frontmatter.js │ │ │ └── yaml.js │ │ ├── application_editor.css │ │ ├── application_editor.d.ts │ │ ├── application_editor.js │ │ ├── application_editor_orgmode.js │ │ ├── application_form.css │ │ ├── application_form.js │ │ ├── application_iframe.css │ │ ├── application_iframe.js │ │ ├── application_image/ │ │ │ ├── information.css │ │ │ ├── information.js │ │ │ ├── pagination.css │ │ │ ├── pagination.js │ │ │ └── zoom.js │ │ ├── application_image.css │ │ ├── application_image.d.ts │ │ ├── application_image.js │ │ ├── application_map.css │ │ ├── application_map.d.ts │ │ ├── application_map.js │ │ ├── application_pdf.css │ │ ├── application_pdf.d.ts │ │ ├── application_pdf.js │ │ ├── application_skeleton.css │ │ ├── application_skeleton.js │ │ ├── application_table.css │ │ ├── application_table.js │ │ ├── application_url.js │ │ ├── application_video.css │ │ ├── application_video.js │ │ ├── common.js │ │ ├── common_fab.js │ │ ├── common_icon.js │ │ ├── common_player.js │ │ ├── component_menubar.css │ │ ├── component_menubar.js │ │ ├── mimetype.js │ │ └── model_files.js │ ├── global.d.ts │ ├── index.backoffice.html │ ├── index.frontoffice.html │ ├── tsconfig.json │ ├── vite.config.js │ └── vite.setup.js └── server/ ├── common/ │ ├── app.go │ ├── backend.go │ ├── cache.go │ ├── config.go │ ├── config_state.go │ ├── constants.go │ ├── crypto.go │ ├── debug.go │ ├── default.go │ ├── dummy.go │ ├── error.go │ ├── files.go │ ├── files_all.go │ ├── files_linux.go │ ├── log.go │ ├── mime.go │ ├── plugin.go │ ├── recovery.go │ ├── response.go │ ├── ssl/ │ │ ├── cert.go │ │ ├── generate.go │ │ ├── index.go │ │ ├── private.go │ │ └── root.go │ ├── token.go │ ├── types.go │ └── utils.go ├── ctrl/ │ ├── about.go │ ├── admin.go │ ├── config.go │ ├── files.go │ ├── metadata.go │ ├── plugin.go │ ├── report.go │ ├── search.go │ ├── session.go │ ├── share.go │ ├── static/ │ │ ├── 404.html │ │ └── loader.html │ ├── static.go │ ├── tmpl.go │ └── webdav.go ├── generator/ │ ├── constants.go │ ├── emacs-el.go │ └── mime.go ├── middleware/ │ ├── context.go │ ├── http.go │ ├── index.go │ ├── session.go │ └── telemetry.go ├── model/ │ ├── audit.go │ ├── files.go │ ├── formater/ │ │ ├── README.md │ │ ├── office.go │ │ ├── pdf.go │ │ └── txt.go │ ├── index.go │ ├── permissions.go │ ├── plugin.go │ ├── plugin_adapter.go │ ├── share.go │ └── webdav.go ├── pkg/ │ ├── compress/ │ │ ├── cgo.go │ │ └── nocgo.go │ ├── index.go │ ├── sqlite/ │ │ ├── cgo.go │ │ └── nocgo.go │ └── workflow/ │ ├── action.go │ ├── actions/ │ │ ├── notify_email.go │ │ ├── run_api.go │ │ ├── tools_debug.go │ │ └── utils.go │ ├── config.go │ ├── handler.go │ ├── index.go │ ├── job.go │ ├── model/ │ │ ├── block.go │ │ ├── index.go │ │ ├── job.go │ │ └── workflow.go │ ├── trigger/ │ │ ├── fileaction.go │ │ ├── filewatch.go │ │ ├── index.go │ │ ├── schedule.go │ │ └── webhook.go │ └── trigger.go ├── plugin/ │ ├── index.go │ ├── plg_application_docxjs/ │ │ ├── Makefile │ │ ├── loader_docx.css │ │ ├── loader_docx.js │ │ └── manifest.json │ ├── plg_application_office/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── loader_lowa.css │ │ ├── loader_lowa.go │ │ ├── loader_lowa.js │ │ ├── loader_lowa.uno.js │ │ ├── manifest.json │ │ └── middleware.c │ ├── plg_authenticate_admin/ │ │ └── index.go │ ├── plg_authenticate_htpasswd/ │ │ ├── deps/ │ │ │ └── crypt/ │ │ │ ├── AUTHORS.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── apr1_crypt/ │ │ │ │ └── apr1_crypt.go │ │ │ ├── common/ │ │ │ │ ├── base64.go │ │ │ │ ├── doc.go │ │ │ │ └── salt.go │ │ │ ├── crypt.go │ │ │ ├── md5_crypt/ │ │ │ │ └── md5_crypt.go │ │ │ ├── sha256_crypt/ │ │ │ │ └── sha256_crypt.go │ │ │ └── sha512_crypt/ │ │ │ └── sha512_crypt.go │ │ └── index.go │ ├── plg_authenticate_ldap/ │ │ └── index.go │ ├── plg_authenticate_local/ │ │ ├── README.md │ │ ├── auth.go │ │ ├── config.go │ │ ├── data.go │ │ ├── handler.go │ │ ├── handler.html │ │ ├── index.go │ │ ├── notify.go │ │ ├── service.go │ │ └── utils.go │ ├── plg_authenticate_passthrough/ │ │ └── index.go │ ├── plg_authenticate_wordpress/ │ │ ├── README.md │ │ ├── helper.go │ │ └── index.go │ ├── plg_authorisation_example/ │ │ └── index.go │ ├── plg_backend_artifactory/ │ │ └── index.go │ ├── plg_backend_azure/ │ │ └── index.go │ ├── plg_backend_backblaze/ │ │ └── index.go │ ├── plg_backend_dav/ │ │ └── index.go │ ├── plg_backend_dropbox/ │ │ └── index.go │ ├── plg_backend_ftp/ │ │ └── index.go │ ├── plg_backend_ftp_only/ │ │ └── index.go │ ├── plg_backend_gdrive/ │ │ └── index.go │ ├── plg_backend_git/ │ │ └── index.go │ ├── plg_backend_ipfs/ │ │ ├── README.md │ │ └── index.go │ ├── plg_backend_ldap/ │ │ └── index.go │ ├── plg_backend_local/ │ │ └── index.go │ ├── plg_backend_mysql/ │ │ └── index.go │ ├── plg_backend_nfs/ │ │ ├── auth_helper.go │ │ ├── auth_unix.go │ │ └── index.go │ ├── plg_backend_nfs4/ │ │ ├── index.go │ │ └── repo/ │ │ ├── README.md │ │ ├── internal/ │ │ │ ├── cleanuper.go │ │ │ ├── nfs4.go │ │ │ ├── nfs4.x │ │ │ ├── nfsconst.go │ │ │ ├── rpc.go │ │ │ ├── rpc.x │ │ │ └── types.go │ │ └── nfs4/ │ │ ├── client.go │ │ ├── nfs_err.go │ │ └── supervised_conn.go │ ├── plg_backend_nop/ │ │ └── index.go │ ├── plg_backend_perkeep/ │ │ ├── index.go │ │ └── types.go │ ├── plg_backend_psql/ │ │ ├── index.go │ │ ├── index_cat.go │ │ ├── index_ls.go │ │ ├── index_rm.go │ │ ├── index_save.go │ │ ├── types.go │ │ └── utils.go │ ├── plg_backend_s3/ │ │ └── index.go │ ├── plg_backend_samba/ │ │ └── index.go │ ├── plg_backend_sftp/ │ │ └── index.go │ ├── plg_backend_storj/ │ │ └── index.go │ ├── plg_backend_syncthing/ │ │ ├── README.md │ │ ├── index.go │ │ └── index_helper.go │ ├── plg_backend_tmp/ │ │ └── index.go │ ├── plg_backend_url/ │ │ └── index.go │ ├── plg_backend_webdav/ │ │ └── index.go │ ├── plg_editor_onlyoffice/ │ │ └── index.go │ ├── plg_editor_wopi/ │ │ ├── config.go │ │ ├── handler.go │ │ └── index.go │ ├── plg_handler_console/ │ │ ├── generator.go │ │ ├── index.go │ │ ├── index_linux.go │ │ └── src/ │ │ └── app.css │ ├── plg_handler_mcp/ │ │ ├── README.md │ │ ├── config/ │ │ │ └── config.go │ │ ├── handler.go │ │ ├── handler_auth.go │ │ ├── handler_state.go │ │ ├── impl/ │ │ │ ├── completion.go │ │ │ ├── prompts.go │ │ │ ├── prompts_fs.go │ │ │ ├── public/ │ │ │ │ └── file-list.html │ │ │ ├── resources.go │ │ │ ├── tools.go │ │ │ └── tools_fs.go │ │ ├── index.go │ │ ├── types/ │ │ │ ├── mcp_completion.go │ │ │ ├── mcp_init.go │ │ │ ├── mcp_notification.go │ │ │ ├── mcp_prompts.go │ │ │ ├── mcp_resources.go │ │ │ ├── mcp_tools.go │ │ │ ├── resources.go │ │ │ ├── rpc.go │ │ │ └── session.go │ │ └── utils/ │ │ ├── cors.go │ │ ├── default.go │ │ ├── json.go │ │ ├── mcp.go │ │ └── response.go │ ├── plg_handler_site/ │ │ ├── config.go │ │ ├── index.go │ │ ├── middleware.go │ │ └── template.go │ ├── plg_handler_syncthing/ │ │ └── index.go │ ├── plg_image_ascii/ │ │ └── index.go │ ├── plg_image_bimg/ │ │ └── index.go │ ├── plg_image_c/ │ │ ├── image_gif.c │ │ ├── image_gif.go │ │ ├── image_gif.h │ │ ├── image_gif_vendor.h │ │ ├── image_jpeg.c │ │ ├── image_jpeg.h │ │ ├── image_jpeg_freebsd.go │ │ ├── image_jpeg_linux.go │ │ ├── image_png.c │ │ ├── image_png.h │ │ ├── image_png_freebsd.go │ │ ├── image_png_linux.go │ │ ├── image_psd.c │ │ ├── image_psd.go │ │ ├── image_psd.h │ │ ├── image_psd_generator.go │ │ ├── image_raw.c │ │ ├── image_raw.h │ │ ├── image_raw_freebsd.go │ │ ├── image_raw_linux.go │ │ ├── image_webp.c │ │ ├── image_webp.go │ │ ├── image_webp.h │ │ ├── index.go │ │ └── utils.h │ ├── plg_image_golang/ │ │ └── index.go │ ├── plg_image_light/ │ │ ├── deps/ │ │ │ ├── README.md │ │ │ ├── create_libresize.sh │ │ │ ├── create_libtranscode.sh │ │ │ └── src/ │ │ │ ├── libresize.c │ │ │ ├── libresize.h │ │ │ ├── libresize_test.c │ │ │ ├── libtranscode.c │ │ │ ├── libtranscode.h │ │ │ └── libtranscode_test.c │ │ ├── index.go │ │ ├── install.sh │ │ ├── lib_resize.go │ │ ├── lib_resize_linux_amd64.go │ │ ├── lib_resize_linux_arm.go │ │ ├── lib_transcode.go │ │ ├── lib_transcode_linux_amd64.go │ │ └── lib_transcode_linux_arm.go │ ├── plg_image_transcode/ │ │ ├── index.go │ │ ├── transcode_bmp.go │ │ ├── transcode_dicom.go │ │ ├── transcode_svg.go │ │ └── transcode_tiff.go │ ├── plg_license/ │ │ └── index.go │ ├── plg_metadata_sqlite/ │ │ └── index.go │ ├── plg_override_actiondelete/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── filespage_thing.diff │ │ ├── index.go │ │ └── manifest.json │ ├── plg_override_download/ │ │ ├── README.md │ │ ├── assets/ │ │ │ └── pages/ │ │ │ └── filespage/ │ │ │ └── thing.js │ │ └── index.go │ ├── plg_search_example/ │ │ └── index.go │ ├── plg_search_sqlitefts/ │ │ ├── config/ │ │ │ └── configuration.go │ │ ├── converter/ │ │ │ └── index.go │ │ ├── crawler/ │ │ │ ├── daemon.go │ │ │ ├── daemon_state.go │ │ │ ├── events.go │ │ │ ├── phase.go │ │ │ ├── phase_explore.go │ │ │ ├── phase_indexing.go │ │ │ ├── phase_maintain.go │ │ │ ├── phase_pause.go │ │ │ ├── phase_utils.go │ │ │ └── types.go │ │ ├── index.go │ │ ├── indexer/ │ │ │ ├── error.go │ │ │ ├── index.go │ │ │ └── query.go │ │ ├── query.go │ │ └── workflow/ │ │ └── index.go │ ├── plg_search_stateless/ │ │ ├── config.go │ │ ├── index.go │ │ └── scoring.go │ ├── plg_security_scanner/ │ │ └── index.go │ ├── plg_security_svg/ │ │ └── index.go │ ├── plg_starter_http/ │ │ └── index.go │ ├── plg_starter_http2/ │ │ └── index.go │ ├── plg_starter_https/ │ │ └── index.go │ ├── plg_starter_tor/ │ │ └── index.go │ ├── plg_video_thumbnail/ │ │ └── index.go │ ├── plg_video_transcoder/ │ │ └── index.go │ ├── plg_widget_chat/ │ │ ├── assets/ │ │ │ ├── sidebar.diff │ │ │ └── sidebar_chat.js │ │ ├── config.go │ │ ├── db.go │ │ ├── handler.go │ │ ├── index.go │ │ ├── type.go │ │ ├── utils.go │ │ └── workflow.go │ ├── plg_widget_description/ │ │ ├── assets/ │ │ │ ├── sidebar.diff │ │ │ └── sidebar_description.js │ │ ├── config.go │ │ ├── db.go │ │ ├── handler.go │ │ ├── index.go │ │ ├── type.go │ │ └── utils.go │ ├── plg_widget_favourite/ │ │ ├── assets/ │ │ │ ├── favourite.diff │ │ │ └── sidebar_favourite.js │ │ ├── config.go │ │ └── index.go │ ├── plg_widget_pgp/ │ │ ├── assets/ │ │ │ ├── pgp.diff │ │ │ └── pgp.js │ │ └── index.go │ └── plg_widget_recent/ │ ├── ai.go │ ├── config.go │ ├── db.go │ ├── decorator.go │ ├── index.go │ └── service.go └── routes.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .assets/raw/Makefile ================================================ all: convert -delay 100 -loop 0 navigation_*.png navigation.gif convert -delay 100 -loop 0 orgmode_*.png orgmode.gif convert -delay 100 -loop 0 photo_management_*.png photo_management.gif ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms open_collective: filestash ================================================ FILE: .github/ISSUE_TEMPLATE/bug.md ================================================ --- name: Bug about: Report a bug title: "[bug] " labels: '' assignees: '' --- # Description of the bug # Step by step instructions to reproduce the bug # Can you replicate that error from the demo? # Observed behavior # Expected behavior ================================================ FILE: .github/ISSUE_TEMPLATE/feature.md ================================================ --- name: Feature Request about: Request for a new feature title: "[Feature Request] " labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/support.md ================================================ --- name: Support about: Technical support is only available on IRC title: "[support] DO NOT CREATE A SUPPORT TICKET FROM GITHUB" labels: '' assignees: '' --- Please, don't create tickets on github for support. Instead, you can either: - visit the community support on libera.chat #filestash. A searchable archive is available at https://support.filestash.app If you don't already have an IRC client, you can try this link: https://kiwiirc.com/nextclient/#irc://irc.libera.chat/#filestash?nick=guest?? - register for enterprise support at https://www.filestash.app/pricing/#support ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - pinned - security # Label to use when marking an issue as stale staleLabel: wontfix # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guide Thanks for taking the time to join our community and start contributing. This guide will help you get started with the Filestash project. ## How to contribute? ### Before you submit a pull request For anything else than a typo or a bug fix, please raise an issue to discuss your proposal before submitting any code. ### License for contributions As the copyright owner, you agree to license your contributions under an irrevocable MIT license. ### Building from source *Prerequisites*: Git, Make, Node, Go, Glib 2.0 ``` # Download the source git clone https://github.com/mickael-kerjean/filestash cd filestash # Install dependencies npm install --legacy-peer-deps # frontend dependencies make build_init # install the required static libraries mkdir -p ./dist/data/state/ cp -R config ./dist/data/state/ # Create the build make build_frontend make build_backend # Run the program ./dist/filestash ``` ### Tests Our tests aren't open source. This comes as an attempt to restrict opportunistic forks (see [1](https://news.ycombinator.com/item?id=17006902#17009852) and [2](https://www.reddit.com/r/selfhosted/comments/a54axs/annoucing_jellyfin_a_free_software_fork_of_emby/ebk92iu/?utm_source=share&utm_medium=web2x)) from creating a stable release without serious commitment and splitting the community in pieces while I'm on holidays. Also the project welcome serious and willing maintainers. ================================================ FILE: Jenkinsfile ================================================ pipeline { agent any options { buildDiscarder(logRotator(numToKeepStr: "10", artifactNumToKeepStr: "1")) } stages { stage("Setup") { steps { git( url: "git@github.com:mickael-kerjean/filestash", branch: "master" ) dir("test") { git( url: "git@github.com:mickael-kerjean/filestash-test.git", credentialsId: "github-com-filestash-test", branch: "main" ) } } } stage("Build") { steps { script { docker.image("golang:1.24-bookworm").inside("--user=root") { sh "apt update -y && apt install -y libbrotli-dev brotli" sh "sed -i 's|plg_image_c|plg_image_golang|' server/plugin/index.go" sh "make init" sh "make build" } } } } stage("Test") { steps { script { // smoke test docker.image("golang:1.24-bookworm").inside("--user=root") { sh 'timeout 5 ./dist/filestash > access.log || code=$?; if [ $code -ne 124 ]; then exit $code; fi' sh "cat access.log" sh "cat access.log | grep -q \"\\[http\\] starting\"" sh "cat access.log | grep -q \"listening\"" sh "cat access.log | grep -vz \"ERR\"" } // test frontend docker.image("node:20").inside("--user=root") { sh "cd public && npm install" sh "cd public && npm run lint" sh "cd public && npm run check" // sh "cd public && npm run test" } // test backend docker.image("golang:1.24-bookworm").inside("--user=root") { sh "cp ./test/assets/* /tmp/" sh "go generate ./test/unit_go/..." sh "go get ./..." sh "go test -count=1 \$(go list ./server/... | grep -v \"server/plugin\" | grep -v \"server/generator\")" } // test e2e docker.image("machines/puppeteer:latest").inside("--user=root") { sh "cd ./test/e2e && npm install" sh "chmod +x ./dist/filestash" sh "./dist/filestash > /dev/null &" sh "cd ./test/e2e && node servers/webdav.js > /dev/null &" // sh "cd ./test/e2e && npm test" } } } } stage("Release") { steps { sh "docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t machines/filestash:latest --push ./docker/" } } } post { always { cleanWs() } } } ================================================ FILE: LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: Makefile ================================================ all: make init make build init: go get ./... go generate -x ./server/... build: go build --tags "fts5" -o dist/filestash$(if $(filter windows,$(GOOS)),.exe) cmd/main.go ================================================ FILE: README.md ================================================ ![screenshot](https://raw.githubusercontent.com/mickael-kerjean/filestash_images/master/.assets/photo.jpg) # What is this?

It started as a storage agnostic Dropbox-like file manager that works with every storage protocol: FTP, SFTP, S3, SMB, WebDAV, IPFS, and about 20 more.

It grew into what we want to be the world's best file management platform, where everything that's not a fundamental truth of the universe lives in a plugin. Where other platforms are take-it-or-leave-it, ours gives you a rock solid core and a plugin system to handle opinions, so however deep requirements go, the only limit won't be technical but your own creativity.

storage + auth architecture

# Key Features
  • Plugin Driven Architecture: everything that matters is a plugin, browse the ecosystem or build your own. With this approach, you get exactly what you need without any kind of overhead or bloat.
  • Universal Access: a sleek web client made in vanilla JS that's infinitely customizable via dynamic patch plugins, plus gateways to access your data via SFTP, MCP or S3, integrations via APIs and via web components like this psd viewer
  • Integrations: our explicit goal is to support 100% of storage and authentication technologies on the market. Beyond your usual options, you can go much further, like a virtual filesystem delegating authentication to your WordPress site and using its roles to drive RBAC authorization.
  • Workflow Engine: automate anything that happens to your files by chaining actions on events, from simple notifications via Slack or email to full on MFT pipelines and everything in between.
  • File Apps: use any of the existing apps or build your own, from astronomy to embroidery and everything in between like:
    • photography: heif, nef, raf, tiff, raw, arw, sr2, srf, nrw, cr2, crw, x3f, pef, rw2, orf, mrw, mdc, mef, mos, dcr, kdc, 3fr, erf and srw
    • astronomy: fits, xisf
    • science: with latex, plantuml & pandoc compilers
    • music: mid, midi, gp4 and gp5
    • GIS: geojson, shp, gpx, wms and dbf
    • data engineering: parquet, arrow, feather, avro, orc, hdf5, h5, netcdf, nc, rds, rda and rdata
    • dev: a, so, o, dylib, dll, tar, tgz, zip, har, cap, pcap, pcapng and sqlite
    • creative work: svg, psd, ai, sketch, cdr, woff, woff2, ttf, otf, eot, exr, tga, pgm, ppm, dds, ktx, dpx, pcx, xpm, pnm, xbm, aai, xwd, cin, pbm, pcd, sgi, wbmp and rgb
    • biomedical: dicom, sam, bam, cif, pdb, xyz, sdf, mol, mol2 and mmtf
    • autodesk: dwg and dxf
    • adobe: psd, ai, xd, dng, postscript, aco, ase, swf
    • 3d: fbx, gltf, obj, stl, step, mesh, ifc, dae
    • embroidery: dgt, dst, dsb, dsz, edr, exp, 10o, col, hus, inf, jef, ksm, pcm, pcs, pes, sew, shv, sst, tap, u01, vip, vp3 and xxx
    • e2e: pgp, gpg
  • Themes:
  • AI features for search, smart folders and OCRs.
  • ... and much much more (versioning, audit, public site, antivirus, quota, chat, chromecast support, on demand video transcoding, mounting shared links as network drive, ....)
    As a rule of thumb, if your problem involves files, we either already have a plugin for it or can make a plugin for it
# Getting Started To install Filestash, head to the [Getting started](https://www.filestash.app/docs/?origin=github) guide. If you want to leverage plugins, head over to the [inventory](https://www.filestash.app/docs/plugin/?origin=github), or learn about [developing your own plugins](https://www.filestash.app/docs/guide/plugin-development.html?origin=github). If you want guidance and expert help on your file management problem, [book a call](https://www.filestash.app/tunnel/demo/?origin=github) and let's figure out if Filestash is the right platform for you. # Vision & Philosophy Our goal is simple: **to build the best file management platform ever made. Period.** But "best" means different things to different people, and making Filestash modular is the only sane model to accomplish that. Anything that isn't a fundamental truth of the universe and might spark a debate belongs in a plugin. Literally every piece listed in the key features is a plugin you can swap for another implementation or remove entirely. This modularity is made possible by the magic of programming interfaces. For example, if you want a [Dropbox-like frontend for FTP](https://news.ycombinator.com/item?id=9224), you will find out the [FTP plugin](https://github.com/mickael-kerjean/filestash/tree/master/server/plugin/plg_backend_ftp) simply implements this interface: ```go type IBackend interface { Ls(path string) ([]os.FileInfo, error) // list files in a folder Stat(path string) (os.FileInfo, error) // file stat Cat(path string) (io.ReadCloser, error) // download a file Mkdir(path string) error // create a folder Rm(path string) error // remove something Mv(from string, to string) error // rename something Save(path string, file io.Reader) error // save a file Touch(path string) error // create a file // I have omitted 2 other methods, a first one to enable connections reuse and // another one to declare what should the login form be like. } ``` There are interfaces you can implement for every key component of Filestash: from storage, to authentication, authorisation, custom apps, search, thumbnailing, frontend patches, middleware, endpoint creation and a few others documented in the [plugin development guide](https://www.filestash.app/docs/guide/plugin-development.html). To see what's currently installed in your instance, head over to [/about](https://demo.filestash.app/about). The inventory of plugins is [documented here](https://www.filestash.app/docs/plugin/) # Support - Commercial Users → [support contract](https://www.filestash.app/pricing/?origin=github) - For individuals: - [#filestash](https://kiwiirc.com/nextclient/#irc://irc.libera.chat/#filestash?nick=guest??) on IRC (libera.chat) - Bitcoin: `3LX5KGmSmHDj5EuXrmUvcg77EJxCxmdsgW` - [Open Collective](https://opencollective.com/filestash) # Credits Filestash stands on the shoulder of: [contributors](https://github.com/mickael-kerjean/filestash/graphs/contributors), folks developing [awesome libraries](https://github.com/mickael-kerjean/filestash/blob/master/go.mod), a whole bunch of C stuff (the [C standard library](https://imgs.xkcd.com/comics/dependency.png), [libjpeg](https://libjpeg-turbo.org/), [libpng](https://www.libpng.org/pub/png/libpng.html), [libgif](https://giflib.sourceforge.net/), [libraw](https://www.libraw.org/about) and many more), [fontawesome](https://fontawesome.com), [material](https://material.io/icons/), [Browser stack](https://www.browserstack.com/) to let us test on real devices, and the many guys from Nebraska and elsewhere who have been thanklessly maintaining the critical pieces that Filestash sits on top: credit to the nebraska guy on xkcd ================================================ FILE: cmd/main.go ================================================ package main import ( "context" "os" "os/signal" "syscall" "github.com/mickael-kerjean/filestash" "github.com/mickael-kerjean/filestash/server" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/ctrl" "github.com/mickael-kerjean/filestash/server/model" _ "github.com/mickael-kerjean/filestash/server/pkg" "github.com/mickael-kerjean/filestash/server/pkg/workflow" _ "github.com/mickael-kerjean/filestash/server/plugin" "github.com/gorilla/mux" ) func main() { Run(mux.NewRouter()) } func Run(router *mux.Router) { check(InitLogger(), "Logger init failed. err=%s") check(InitConfig(), "Config init failed. err=%s") check(workflow.Init(), "Worklow Initialisation failure. err=%s") check(model.PluginDiscovery(), "Plugin Discovery failed. err=%s") check(ctrl.InitPluginList(embed.EmbedPluginList, model.PLUGINS), "Plugin Initialisation failed. err=%s") if Hooks.Get.Starter() == nil { check(ErrNotFound, "Missing starter plugin. err=%s") } for _, fn := range Hooks.Get.Onload() { fn() } for _, obj := range Hooks.Get.HttpEndpoint() { obj(router) } server.Build(router) server.PluginRoutes(router) if os.Getenv("DEBUG") == "true" { server.DebugRoutes(router) } server.CatchAll(router) Hooks.Get.Starter()(withSignal(), router) for _, fn := range Hooks.Get.OnQuit() { fn() } } func check(err error, msg string) { if err == nil { return } Log.Error(msg, err.Error()) os.Exit(1) } func withSignal() context.Context { ctx, cancel := context.WithCancel(context.Background()) go func() { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT) <-quit cancel() }() return ctx } ================================================ FILE: config/config.json ================================================ { "general": { }, "features": { }, "log": { }, "email": { }, "oauth": { }, "connections": [ { "type": "webdav", "label": "WebDav" }, { "type": "ftp", "label": "FTP" }, { "type": "sftp", "label": "SFTP" }, { "type": "git", "label": "GIT" }, { "type": "s3", "label": "S3" }, { "type": "dropbox", "label": "Dropbox" }, { "type": "gdrive", "label": "Drive" } ] } ================================================ FILE: config/mime.json ================================================ { "10o": "image/x-10o", "3dm": "model/3dm", "3fr": "image/x-hasselblad-3fr", "3gp": "video/3gpp", "3gpp": "video/3gpp", "7z": "application/x-7z-compressed", "a": "application/x-archive", "aai": "image/x-dune-aai", "aco": "application/x-aco", "ai": "application/pdf", "aif": "audio/x-aiff", "aiff": "audio/x-aiff", "apk": "application/vnd.android.package-archive", "arrow": "application/vnd.apache.arrow.file", "arw": "image/x-sony-arw", "ase": "application/x-ase", "asf": "video/x-ms-asf", "asx": "video/x-ms-asf", "atom": "application/atom+xml", "avi": "video/x-msvideo", "avif": "image/avif", "avro": "application/vnd.apache.avro", "bam": "application/x-bam", "bin": "application/octet-stream", "bmp": "image/x-ms-bmp", "bz2": "application/x-bz2", "cab": "application/vnd.ms-cab-compressed", "cap": "application/x-pcap", "cco": "application/x-cocoa", "cdr": "application/vnd.corel-draw", "cif": "application/x-cif", "cin": "image/x-cin", "col": "image/x-col", "cr2": "image/x-canon-cr2", "crt": "application/x-x509-ca-cert", "crw": "image/x-canon-crw", "css": "text/css", "csv": "text/csv", "cur": "image/x-win-bitmap", "dae": "model/vnd.collada+xml", "db": "application/x-sqlite3", "dbf": "application/dbf", "dcm": "image/dicom", "dcr": "image/x-kodak-dcr", "dds": "image/x-dds", "deb": "application/octet-stream", "der": "application/x-x509-ca-cert", "dgt": "image/bmp", "dll": "application/x-msdownload", "dmg": "application/octet-stream", "dng": "image/x-adobe-dng", "doc": "application/msword", "docx": "application/word", "docm": "application/vnd.ms-word.document.macroEnabled.12", "dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotm": "application/vnd.ms-word.template.macroEnabled.12", "dpkg": "application/dpkg-www-installer", "dpx": "image/dpx", "ds_store": "application/octet-stream", "dsb": "image/x-dsb", "dst": "image/x-dst", "dsz": "image/x-dsz", "dxf": "application/dxf", "dwg": "application/acad", "dylib": "application/x-dylib", "ear": "application/java-archive", "edr": "image/x-edr", "emf": "image/emf", "emz": "image/x-emz", "eot": "application/vnd.ms-fontobject", "eps": "application/postscript", "epub": "application/epub+zip", "erf": "image/x-epson-erf", "exe": "application/octet-stream", "exp": "image/x-exp", "exr": "image/x-exr", "fbx": "application/fbx", "fea": "application/vnd.apache.feather", "feather": "application/vnd.apache.feather", "fit": "image/fits", "fits": "image/fits", "fts": "image/fits", "flac": "audio/flac", "flv": "video/x-flv", "form": "application/x-form", "geojson": "application/geo+json", "gif": "image/gif", "gltf": "model/gltf+json", "glb": "model/gltf-binary", "gpx": "application/gpx+xml", "gp4": "application/x-guitar-pro", "gp5": "application/x-guitar-pro", "gz": "application/x-gzip", "h5": "application/x-hdf", "har": "application/har+json", "hdf5": "application/x-hdf", "heic": "image/heic", "heif": "image/heic", "hqx": "application/mac-binhex40", "htc": "text/x-component", "htm": "text/html", "html": "text/html", "hus": "image/x-hus", "ico": "image/x-icon", "ics": "text/calendar", "ifc": "application/ifc", "inf": "image/x-inf", "img": "application/octet-stream", "ini": "text/x-ini", "iso": "application/octet-stream", "jad": "text/vnd.sun.j2me.app-descriptor", "jar": "application/java-archive", "jardiff": "application/x-java-archive-diff", "jef": "image/x-jef", "jef+": "image/x-jef", "jfif": "image/jpeg", "jng": "image/x-jng", "jnlp": "application/x-java-jnlp-file", "jpeg": "image/jpeg", "jpg": "image/jpeg", "jp2": "image/jp2", "js": "application/javascript", "json": "application/json", "kar": "audio/midi", "kdc": "image/x-kodak-kdc", "kicad_pcb": "application/vnd.kicad-pcb", "kicad_sch": "application/vnd.kicad-sch", "kml": "application/vnd.google-earth.kml+xml", "kmz": "application/vnd.google-earth.kmz", "ksm": "image/x-ksm", "ktx": "image/ktx", "ktx2": "image/ktx2", "m3u8": "application/vnd.apple.mpegurl", "m4a": "audio/x-m4a", "m4v": "video/x-m4v", "mae": "application/x-mae", "md": "text/markdown", "mdc": "image/x-minolta-mdc", "mef": "image/x-mamiya-mef", "mesh": "model/mesh", "mid": "audio/midi", "midi": "application/x-midi", "mjs": "application/javascript", "mkv": "video/x-matroska", "mml": "text/mathml", "mmtf": "application/x-mmtf", "mng": "video/x-mng", "mobi": "application/x-mobipocket-ebook", "mol": "application/x-mol", "mol2": "application/x-mol2", "mos": "image/x-aptus-mos", "mov": "video/quicktime", "mp3": "audio/mp3", "mp4": "video/mp4", "mpeg": "video/mpeg", "mpg": "video/mpeg", "mrw": "image/x-minolta-mrw", "msi": "application/octet-stream", "msm": "application/octet-stream", "msp": "application/octet-stream", "mtl": "model/mtl", "nc": "application/x-netcdf", "nef": "image/x-nikon-nef", "nrw": "image/x-nikon-nrw", "o": "application/x-object", "obj": "application/object", "odg": "application/vnd.oasis.opendocument.graphics", "odp": "application/vnd.oasis.opendocument.presentation", "ods": "application/vnd.oasis.opendocument.spreadsheet", "odt": "application/vnd.oasis.opendocument.text", "ogg": "audio/ogg", "ogv": "application/ogg", "orc": "application/vnd.apache.orc", "orf": "image/x-olympus-orf", "org": "text/org", "otf": "font/otf", "parquet": "application/vnd.apache.parquet", "pbm": "image/x-portable-bitmap", "pdb": "chemical/x-pdb", "pcap": "application/vnd.tcpdump.pcap", "pcapng": "application/x-pcapng", "pcd": "image/x-photo-cd", "pcm": "image/x-pcm", "pcs": "image/x-pcs", "pcx": "image/vnd.zbrush.pcx", "pdf": "application/pdf", "pec": "image/x-pec", "pes": "image/x-pes", "pef": "image/x-pentax-pef", "pem": "application/x-x509-ca-cert", "pes": "image/x-pes", "pgm": "image/x-portable-greymap", "pkg": "application/x-newton-compatible-pkg", "pl": "application/x-perl", "plantuml": "text/x-plantuml", "pm": "application/x-perl", "pmv": "image/x-pmv", "png": "image/png", "pnm": "image/x-portable-anymap", "potm": "application/vnd.ms-powerpoint.template.macroEnabled.12", "potx": "application/vnd.openxmlformats-officedocument.presentationml.template", "ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12", "ppm": "image/x-portable-pixmap", "pps": "application/vnd.ms-powerpoint", "ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", "ppt": "application/vnd.ms-powerpoint", "pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", "pptx": "application/powerpoint", "prc": "application/x-pilot", "prj": "application/text", "properties": "text/x-ini", "ps": "application/postscript", "psd": "image/vnd.adobe.photoshop", "puml": "text/x-plantuml", "ra": "audio/x-realaudio", "raf": "image/x-fuji-raf", "ram": "audio/x-pn-realaudio", "rar": "application/x-rar-compressed", "raw": "image/x-raw", "rda": "application/x-rdata", "rdata": "application/x-rdata", "rds": "application/x-rds", "rgb": "image/x-rgb", "rgba": "image/x-rgba", "rpm": "application/x-redhat-package-manager", "rss": "application/rss+xml", "rtf": "application/rtf", "rtf2": "text/rtf", "run": "application/x-makeself", "rw2": "image/x-panasonic-rw2", "sam": "application/x-sam", "sdf": "application/x-sdf", "sea": "application/x-sea", "sew": "image/x-sew", "sgi": "image/x-sgi", "shtml": "text/html", "shp": "application/vnd.shp", "shv": "image/x-shv", "shx": "application/vnd.shx", "sit": "application/x-stuffit", "sketch": "application/x-sketch", "so": "application/x-sharedlib", "sql": "application/x-sqlite3", "sqlite": "application/x-sqlite3", "sqlite3": "application/x-sqlite3", "sr2": "image/x-sony-sr2", "srf": "image/x-sony-srf", "srw": "image/x-samsung-srw", "sst": "image/x-sst", "stl": "model/stl", "step": "model/step", "stp": "model/step", "svg": "image/svg+xml", "svgz": "image/svg+xml", "swf": "application/x-shockwave-flash", "tap": "image/x-tap", "tar": "application/x-tar", "tcl": "application/x-tcl", "tex": "application/x-tex", "tga": "image/x-tga", "tgz": "application/x-gzip", "tif": "image/tiff", "tiff": "image/tiff", "tk": "application/x-tcl", "ts": "text/plain", "tsv": "text/tab-separated-values", "ttf": "application/x-font-ttf", "txt": "text/plain", "u01": "image/x-u01", "url": "application/x-url", "vcf": "text/vcard", "vip": "image/x-vip", "vp3": "image/x-vp3", "vrml": "application/x-vrml", "war": "application/java-archive", "wav": "audio/wave", "wave": "audio/wave", "wasm": "application/wasm", "wbmp": "image/vnd.wap.wbmp", "webm": "video/webm", "webp": "image/webp", "wma": "audio/x-ms-wma", "wml": "text/vnd.wap.wml", "wmlc": "application/vnd.wap.wmlc", "wms": "application/vnd.ogc.wms_xml", "wmv": "video/x-ms-wmv", "woff": "font/woff", "woff2": "font/woff2", "wrl": "x-world/x-vrml", "x3d": "model/x3d+xml", "x3dv": "model/x3d-vrml", "x3db": "model/x3d+fastinfoset", "x3f": "image/x-x3f", "xbm": "image/x-xbitmap", "xcf": "image/x-xcf", "xd": "application/x-adobe-xd", "xhtml": "application/xhtml+xml", "xisf": "image/x-xisf", "xls": "application/vnd.ms-excel", "xlsx": "application/excel", "xml": "application/xml", "xpi": "application/x-xpinstall", "xpm": "image/x-xpixmap", "xspf": "application/xspf+xml", "xwd": "image/x-xwindowdump", "xxx": "image/x-xxx", "xyz": "application/x-xyz", "zip": "application/zip" } ================================================ FILE: docker/Dockerfile ================================================ # STEP1: CLONE THE CODE FROM alpine/git as builder_prepare WORKDIR /home/ ARG GIT_REPO=https://github.com/mickael-kerjean/filestash ARG GIT_BRANCH=master RUN git clone --depth 1 --single-branch --branch ${GIT_BRANCH} ${GIT_REPO} # STEP2: BUILD BACKEND FROM golang:1.24-trixie AS builder_backend WORKDIR /home/filestash/ COPY --from=builder_prepare /home/filestash/ . RUN apt-get update > /dev/null && \ apt-get install -y curl make > /dev/null 2>&1 && \ apt-get install -y libjpeg-dev libtiff-dev libpng-dev libwebp-dev libraw-dev libheif-dev libgif-dev libvips-dev > /dev/null 2>&1 && \ make init && \ make build && \ mkdir -p ./dist/data/state/config/ && \ cp config/config.json ./dist/data/state/config/config.json # STEP3: BUILD PROD IMAGE FROM debian:stable-slim MAINTAINER mickael@kerjean.me WORKDIR /app/ COPY --from=builder_backend /home/filestash/dist/ . RUN apt-get update > /dev/null && \ apt-get install -y --no-install-recommends apt-utils && \ apt-get install -y curl ffmpeg libjpeg-dev libtiff-dev libpng-dev libwebp-dev libraw-dev libheif-dev libgif-dev && \ useradd filestash && \ chown -R filestash:filestash /app/ && \ find /app/data/ -type d -exec chmod 770 {} \; && \ find /app/data/ -type f -exec chmod 760 {} \; && \ chmod 730 /app/filestash && \ rm -rf /var/lib/apt/lists/* && \ rm -rf /tmp/* USER filestash CMD ["/app/filestash"] EXPOSE 8334 ================================================ FILE: docker/docker-compose.yml ================================================ version: '2' services: app: container_name: filestash image: machines/filestash:latest restart: always environment: - APPLICATION_URL= - CANARY=true - OFFICE_URL=http://wopi_server:9980 - OFFICE_FILESTASH_URL=http://app:8334 - OFFICE_REWRITE_URL=http://127.0.0.1:9980 ports: - "8334:8334" volumes: - filestash:/app/data/state/ wopi_server: container_name: filestash_wopi image: collabora/code:24.04.10.2.1 restart: always environment: - "extra_params=--o:ssl.enable=false" - aliasgroup1="https://.*:443" command: - /bin/bash - -c - | curl -o /usr/share/coolwsd/browser/dist/branding-desktop.css https://gist.githubusercontent.com/mickael-kerjean/bc1f57cd312cf04731d30185cc4e7ba2/raw/d706dcdf23c21441e5af289d871b33defc2770ea/destop.css /bin/su -s /bin/bash -c '/start-collabora-online.sh' cool user: root ports: - "9980:9980" volumes: filestash: {} ================================================ FILE: embed.go ================================================ package embed import ( "embed" "io/fs" "net/http" "os" ) var ( //go:embed public wwwPublic embed.FS WWWPublic http.FileSystem = http.FS(os.DirFS("./public/")) ) //go:embed server/plugin/index.go var EmbedPluginList []byte func init() { if os.Getenv("DEBUG") != "true" { fsPublic, _ := fs.Sub(wwwPublic, "public") WWWPublic = http.FS(fsPublic) } } ================================================ FILE: go.mod ================================================ module github.com/mickael-kerjean/filestash go 1.24.11 require ( cloud.google.com/go/storage v1.59.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 github.com/aws/aws-sdk-go v1.55.8 github.com/bluekeyes/go-gitdiff v0.8.1 github.com/bmatcuk/doublestar/v4 v4.9.2 github.com/creack/pty v1.1.24 github.com/cretz/bine v0.2.0 github.com/duosecurity/duo_universal_golang v1.1.0 github.com/fclairamb/ftpserverlib v0.30.0 github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 github.com/go-ldap/ldap/v3 v3.4.12 github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.9.3 github.com/godror/godror v0.50.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/brotli/go/cbrotli v1.1.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/h2non/bimg v1.1.9 github.com/hirochachacha/go-smb2 v1.1.0 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.33 github.com/mickael-kerjean/net v0.0.0-20191120063050-2457c043ba06 github.com/mickael-kerjean/saml v0.0.0-20240603162924-4629e91322ce github.com/mitchellh/hashstructure v1.1.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/oschwald/maxminddb-golang/v2 v2.1.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 github.com/prasad83/goftp v0.0.0-20210325080443-f57aaed46a32 github.com/qeesung/image2ascii v1.0.1 github.com/spf13/afero v1.15.0 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef github.com/stretchr/testify v1.11.1 github.com/suyashkumar/dicom v1.1.0 github.com/tetratelabs/wazero v1.11.0 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b github.com/yeqown/go-qrcode/v2 v2.2.5 github.com/yeqown/go-qrcode/writer/standard v1.3.0 golang.org/x/crypto v0.47.0 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/oauth2 v0.34.0 golang.org/x/sync v0.19.0 golang.org/x/sys v0.40.0 golang.org/x/time v0.14.0 google.golang.org/api v0.259.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df modernc.org/sqlite v1.44.3 storj.io/uplink v1.13.1 ) require ( cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.18.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/VictoriaMetrics/easyproto v1.1.3 // indirect github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/beevik/etree v1.6.0 // indirect github.com/boombuler/barcode v1.1.0 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.2 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/fogleman/gg v1.3.0 // indirect github.com/geoffgarside/ber v1.2.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/godror/knownpb v0.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/jwx v1.2.31 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russellhaering/goxmldsig v1.5.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect github.com/yeqown/reedsolomon v1.0.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/text v0.33.0 // indirect google.golang.org/genproto v0.0.0-20260112192933-99fd39fd28a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260112192933-99fd39fd28a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect storj.io/common v0.0.0-20260109131222-221fe378eda1 // indirect storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 // indirect storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 // indirect storj.io/infectious v0.0.2 // indirect storj.io/picobuf v0.0.4 // indirect ) ================================================ FILE: go.sum ================================================ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/storage v1.59.0 h1:9p3yDzEN9Vet4JnbN90FECIw6n4FCXcKBK1scxtQnw8= cloud.google.com/go/storage v1.59.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew= github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4= github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc= github.com/VictoriaMetrics/easyproto v1.1.3 h1:gRSA3ZQs7n4+5I+SniDWD59jde1jVq4JmgQ9HUUyvk4= github.com/VictoriaMetrics/easyproto v1.1.3/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= github.com/bluekeyes/go-gitdiff v0.8.1 h1:lL1GofKMywO17c0lgQmJYcKek5+s8X6tXVNOLxy4smI= github.com/bluekeyes/go-gitdiff v0.8.1/go.mod h1:WWAk1Mc6EgWarCrPFO+xeYlujPu98VuLW3Tu+B/85AE= github.com/bmatcuk/doublestar/v4 v4.9.2 h1:b0mc6WyRSYLjzofB2v/0cuDUZ+MqoGyH3r0dVij35GI= github.com/bmatcuk/doublestar/v4 v4.9.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo= github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= github.com/duosecurity/duo_universal_golang v1.1.0 h1:GaCc3vDktv3IEA+KPrHFnKqZjaKhTKjUpaGajL2SUSc= github.com/duosecurity/duo_universal_golang v1.1.0/go.mod h1:AxndDwaPp4DGZH3Rmq8Q6RkyE95tFF4nK4LXgpBAgFs= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/fclairamb/ftpserverlib v0.30.0 h1:caB9sDn1Au//q0j2ev/icPn388qPuk4k1ajSvglDcMQ= github.com/fclairamb/ftpserverlib v0.30.0/go.mod h1:QmogtltTOgkihyKza0GNo37Mu4AEzbJ+sH6W9Y0MBIQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64= github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI= github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs= github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI= github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI= github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godror/godror v0.50.0 h1:c0ZnGSDFT12E8HJfQwxtqcmybaIkbqACNk4lIfkkESc= github.com/godror/godror v0.50.0/go.mod h1:kTMcxZzRw73RT5kn9v3JkBK4kHI6dqowHotqV72ebU8= github.com/godror/knownpb v0.3.0 h1:+caUdy8hTtl7X05aPl3tdL540TvCcaQA6woZQroLZMw= github.com/godror/knownpb v0.3.0/go.mod h1:PpTyfJwiOEAzQl7NtVCM8kdPCnp3uhxsZYIzZ5PV4zU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/brotli/go/cbrotli v1.1.0 h1:YwHD/rwSgUSL4b2S3ZM2jnNymm+tmwKQqjUIC63nmHU= github.com/google/brotli/go/cbrotli v1.1.0/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/bimg v1.1.9 h1:WH20Nxko9l/HFm4kZCA3Phbgu2cbHvYzxwxn9YROEGg= github.com/h2non/bimg v1.1.9/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7 h1:JcltaO1HXM5S2KYOYcKgAV7slU0xPy1OcvrVgn98sRQ= github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7/go.mod h1:MEkhEPFwP3yudWO0lj6vfYpLIB+3eIcuIW+e0AZzUQk= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= github.com/lestrrat-go/jwx v1.2.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mickael-kerjean/net v0.0.0-20191120063050-2457c043ba06 h1:427Mpu5edwuFuDBdqOm6EgRohYqZ9Oomd+87ryW2Uls= github.com/mickael-kerjean/net v0.0.0-20191120063050-2457c043ba06/go.mod h1:K5atEXcXMSGE5O2cPGjggOqCb5lKRaieqgNJHEwS/IE= github.com/mickael-kerjean/saml v0.0.0-20240603162924-4629e91322ce h1:FVTh1LDCRr6fYm3meiJFIZcsrO29WYDLPIT+BwTiyYQ= github.com/mickael-kerjean/saml v0.0.0-20240603162924-4629e91322ce/go.mod h1:at9E70cOrnu43GZqrHni63ifAI+3QHzzSQqqUHCiwoQ= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/oschwald/maxminddb-golang/v2 v2.1.1 h1:lA8FH0oOrM4u7mLvowq8IT6a3Q/qEnqRzLQn9eH5ojc= github.com/oschwald/maxminddb-golang/v2 v2.1.1/go.mod h1:PLdx6PR+siSIoXqqy7C7r3SB3KZnhxWr1Dp6g0Hacl8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prasad83/goftp v0.0.0-20210325080443-f57aaed46a32 h1:M5NgckSuVabJL6XfuBYclDbFu/PnrryRQyreGkQuims= github.com/prasad83/goftp v0.0.0-20210325080443-f57aaed46a32/go.mod h1:WkmqX+l/lW8boCoanBDTyrSWqrIvwP7NYm/zVT15Da0= github.com/qeesung/image2ascii v1.0.1 h1:Fe5zTnX/v/qNC3OC4P/cfASOXS501Xyw2UUcgrLgtp4= github.com/qeesung/image2ascii v1.0.1/go.mod h1:kZKhyX0h2g/YXa/zdJR3JnLnJ8avHjZ3LrvEKSYyAyU= github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI= github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw= github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368 h1:GyYC5Ntqk/yy9lEIGE7chdIvt4zP44taycwd9YDSGdc= github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368/go.mod h1:XkZYGzknZwkD0AKUnZaSXhRiVTLCkq7CWVa3IsE72gA= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/suyashkumar/dicom v1.1.0 h1:AG+N/aQnD+jzkFuFzz2wO401qXI8KnNcYGQgvTBr9LA= github.com/suyashkumar/dicom v1.1.0/go.mod h1:8Yw14x/0r4fXVnutbCJpF3HiLVbgMS1DQ2HpfbDjq8Y= github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b h1:RUrsc0B9xF8iC8WXrva+ULeOwN/X+zqe0FdWcDxPt/M= github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b/go.mod h1:psQdhrCc+fimC/8/U+PboPiIMcdmKgRdAtcMnhXhjzI= github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g= github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg= github.com/yeqown/go-qrcode/v2 v2.2.5 h1:HCOe2bSjkhZyYoyyNaXNzh4DJZll6inVJQQw+8228Zk= github.com/yeqown/go-qrcode/v2 v2.2.5/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw= github.com/yeqown/go-qrcode/writer/standard v1.3.0 h1:chdyhEfRtUPgQtuPeaWVGQ/TQx4rE1PqeoW3U+53t34= github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ= github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0= github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= google.golang.org/genproto v0.0.0-20260112192933-99fd39fd28a9 h1:wFALHMUiWKkK/x6rSxm79KpSnUyh7ks2E+mel670Dc4= google.golang.org/genproto v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:wE6SUYr3iNtF/D0GxVAjT+0CbDFktQNssYs9PVptCt4= google.golang.org/genproto/googleapis/api v0.0.0-20260112192933-99fd39fd28a9 h1:4DKBrmaqeptdEzp21EfrOEh8LE7PJ5ywH6wydSbOfGY= google.golang.org/genproto/googleapis/api v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:dd646eSK+Dk9kxVBl1nChEOhJPtMXriCcVb4x3o6J+E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9 h1:IY6/YYRrFUk0JPp0xOVctvFIVuRnjccihY5kxf5g0TE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260112192933-99fd39fd28a9/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= storj.io/common v0.0.0-20260109131222-221fe378eda1 h1:lcjItKLOyqtzAjnQTh+k/x+5la2Ua5QuHy4EM7ZMIHU= storj.io/common v0.0.0-20260109131222-221fe378eda1/go.mod h1:XNX7uykja6aco92y2y8RuqaXIDRPpt1YA2OQDKlKEUk= storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 h1:8OE12DvUnB9lfZcHe7IDGsuhjrY9GBAr964PVHmhsro= storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55/go.mod h1:Y9LZaa8esL1PW2IDMqJE7CFSNq7d5bQ3RI7mGPtmKMg= storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 h1:5MZ0CyMbG6Pi0rRzUWVG6dvpXjbBYEX2oyXuj+tT+sk= storj.io/eventkit v0.0.0-20250410172343-61f26d3de156/go.mod h1:CpnM6kfZV58dcq3lpbo/IQ4/KoutarnTSHY0GYVwnYw= storj.io/infectious v0.0.2 h1:rGIdDC/6gNYAStsxsZU79D/MqFjNyJc1tsyyj9sTl7Q= storj.io/infectious v0.0.2/go.mod h1:QEjKKww28Sjl1x8iDsjBpOM4r1Yp8RsowNcItsZJ1Vs= storj.io/picobuf v0.0.4 h1:qswHDla+YZ2TovGtMnU4astjvrADSIz84FXRn0qgP6o= storj.io/picobuf v0.0.4/go.mod h1:hSMxmZc58MS/2qSLy1I0idovlO7+6K47wIGUyRZa6mg= storj.io/uplink v1.13.1 h1:C8RdW/upALoCyuF16Lod9XGCXEdbJAS+ABQy9JO/0pA= storj.io/uplink v1.13.1/go.mod h1:x0MQr4UfFsQBwgVWZAtEsLpuwAn6dg7G0Mpne1r516E= ================================================ FILE: public/Makefile ================================================ compress: find . -type f -name '*.html' | xargs brotli -f -k find . -type f -name '*.html' | xargs gzip -f -k find . -type f -name '*.js' | xargs brotli -f -k find . -type f -name '*.js' | xargs gzip -f -k find . -type f -name '*.css' | xargs brotli -f -k find . -type f -name '*.css' | xargs gzip -f -k find . -type f -name '*.svg' | xargs brotli -f -k find . -type f -name '*.svg' | xargs gzip -f -k clean: find . -name '*.gz' -exec rm {} \; find . -name '*.br' -exec rm {} \; serve: go run server.go ================================================ FILE: public/assets/boot/bundler_complete.js ================================================ document.head.appendChild(Object.assign(document.createElement("script"), { type: "importmap", textContent: JSON.stringify({ imports: window.bundler.esModules, }, null, 4), })); ================================================ FILE: public/assets/boot/bundler_init.js ================================================ window.bundler = (function(origin) { const esModules = {}; return { register: (path, code) => { const fullpath = origin + path; if (path.endsWith(".js")) { code = code.replace(/from\s?"([^"]+)"/g, (_, spec) => `from "${new URL(spec, fullpath).href}"`, ); code = code.replace(/\bimport\s+"([^"]+)"/g, (_, spec) => `import "${new URL(spec, fullpath).href}"`, ); code = code.replace( /\bimport\.meta\.url\b/g, (match, offset, string) => { const before = string[offset - 1]; const after = string[offset + match.length]; return (before === "\"" || after === "\"") ? match : `"${fullpath}"`; }, ); esModules[fullpath] = "data:text/javascript," + encodeURIComponent( code + `\n//# sourceURL=${path}`, ); } else if (path.endsWith(".css")) { code = code.replace(/@import url\("([^"]+)"\);/g, (m, rel) => { const $style = document.head.querySelector( `style[id="${new URL(rel, fullpath).href}"]` ); if (!$style) throw new DOMException( `Missing CSS dependency: ${rel} (referenced from ${path})`, "NotFoundError", ); return `/* ${m} */`; }); document.head.appendChild(Object.assign(document.createElement("style"), { innerHTML: code + `\n/*# sourceURL=${path} */`, id: fullpath, })); } }, esModules, }; })(new URL(import.meta.url).origin); ================================================ FILE: public/assets/boot/common.js ================================================ export function $error(msg) { const $code = document.createElement("code"); $code.style.display = "block"; $code.style.margin = "20px 0"; $code.style.fontSize = "1.2rem"; $code.style.padding = "0 10% 0 10%"; $code.textContent = msg; const $img = document.createElement("img"); $img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABQAQMAAADcLOLWAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFlJREFUeF69zrERgCAQBdElMqQEOtHSuNIohRIMjfjO6DDmB7jZy5YgySQVYDIakIHD1kBPC9Bra5G2Ans0N7iAcOLF+EHvXySpjSBWCDI/3nIdBDihr8m4AcKdbn96jpAHAAAAAElFTkSuQmCC"); $img.style.display = "block"; $img.style.padding = "20vh 10% 0 10%"; document.body.innerHTML = ""; document.body.appendChild($img); document.body.appendChild($code); } ================================================ FILE: public/assets/boot/ctrl_boot.d.ts ================================================ export {}; interface IChromecast { init: () => Promise; } declare global { interface Window { env: string LNG: object; CONFIG: object; overrides: object; Chromecast: IChromecast; } } ================================================ FILE: public/assets/boot/ctrl_boot_backoffice.js ================================================ import { report } from "../helpers/log.js"; import { $error } from "./common.js"; export default async function main() { try { await Promise.all([ setup_device(), setup_blue_death_screen(), setup_history(), ]); window.dispatchEvent(new window.Event("pagechange")); } catch (err) { console.error(err); const msg = window.navigator.onLine === false ? "OFFLINE" : (err instanceof Error && err.message) || "CAN'T LOAD"; report("boot::" + msg, err, location.href); $error(msg); } } main(); async function setup_device() { const className = "ontouchstart" in window ? "touch-yes" : "touch-no"; document.body.classList.add(className); } async function setup_blue_death_screen() { window.onerror = function(msg, url, lineNo, colNo, error) { report("boot::" + msg, error, url, lineNo, colNo); $error(msg); }; } async function setup_history() { window.history.replaceState({}, ""); } ================================================ FILE: public/assets/boot/ctrl_boot_frontoffice.js ================================================ import { toHref } from "../lib/skeleton/router.js"; import { loadJS } from "../helpers/loader.js"; import { init as setup_translation } from "../locales/index.js"; import { init as setup_config } from "../model/config.js"; import { init as setup_plugin } from "../model/plugin.js"; import { init as setup_cache } from "../pages/filespage/cache.js"; import { report } from "../helpers/log.js"; import { $error } from "./common.js"; export default async function main() { try { await Promise.all([ setup_config().then((config) => Promise.all([ setup_title(config), window.self === window.top ? verify_origin(config) : verify_iframe_origin(config), ])), setup_translation(), setup_xdg_open(), setup_device(), setup_blue_death_screen(), setup_history(), setup_polyfill(), setup_plugin(), setup_cache(), ]); window.dispatchEvent(new window.Event("pagechange")); } catch (err) { console.error(err); const msg = window.navigator.onLine === false ? "OFFLINE" : (err instanceof Error && err.message) || "CAN'T LOAD"; report(msg, err, location.href); $error(msg); } } await main(); /// ///////////////////////////////////////// async function setup_xdg_open() { window.overrides = {}; return loadJS(import.meta.url, toHref("/overrides/xdg-open.js")); } async function setup_device() { const className = "ontouchstart" in window ? "touch-yes" : "touch-no"; document.body.classList.add(className); if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { document.body.classList.add("dark-mode"); } window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function(e) { e.matches ? document.body.classList.add("dark-mode") : document.body.classList.remove("dark-mode"); }); } async function setup_blue_death_screen() { window.onerror = function(msg, url, lineNo, colNo, error) { report(msg, error, url, lineNo, colNo); $error(msg); if ("serviceWorker" in navigator) navigator.serviceWorker .getRegistrations() .then((registrations) => { for (const registration of registrations) { registration.unregister(); } }); }; } async function setup_history() { window.history.replaceState({}, ""); } async function setup_title(config) { document.title = config["name"] || "Filestash"; } async function setup_polyfill() { if (!("replaceChildren" in document.body)) { await loadJS(import.meta.url, "../lib/polyfill.js"); } } async function verify_origin(config) { const origin = config["origin"]; // happy path if (!origin) return; else if (location.origin === origin) return; // non happy path const u = new URL(origin); if (u.host === location.host) return; else if (u.hostname === location.hostname) return; setTimeout(() => { location.href = origin + location.pathname + location.search; }, 1000); throw new Error("Redirecting to " + origin); } async function verify_iframe_origin(_) { // TODO: const origin = document.location.ancestorOrigins[0] // should we verify iframe origin client side on top of frame ancestor? Until prooven wrong, no. } ================================================ FILE: public/assets/boot/router_backoffice.js ================================================ const routes = { "/admin/storage": "/pages/adminpage/ctrl_storage.js", "/admin/workflow.*": "/pages/adminpage/ctrl_workflow.js", "/admin/activity": "/pages/adminpage/ctrl_activity.js", "/admin/settings": "/pages/adminpage/ctrl_settings.js", "/admin/about": "/pages/adminpage/ctrl_about.js", "/admin/setup": "/pages/adminpage/ctrl_setup.js", "/admin/": "/pages/ctrl_adminpage.js", "/admin": "/pages/ctrl_adminpage.js", "/logout": "/pages/ctrl_logout.js", "": "/pages/ctrl_notfound.js", }; export default routes; ================================================ FILE: public/assets/boot/router_frontoffice.js ================================================ const routes = { "/login": "/pages/ctrl_connectpage.js", "/logout": "/pages/ctrl_logout.js", "/": "/pages/ctrl_homepage.js", "/files/.*": "/pages/ctrl_filespage.js", "/view/.*": "/pages/ctrl_viewerpage.js", // /tags/.* -> "pages/ctrl_tags.js", "/s/.*": "/pages/ctrl_sharepage.js", "": "/pages/ctrl_notfound.js", }; export default routes; ================================================ FILE: public/assets/components/breadcrumb.css ================================================ .component_breadcrumb { position: relative; z-index: 5; } .component_breadcrumb .breadcrumb { margin: 0 0 0px 0; z-index: 1000; } .component_breadcrumb .breadcrumb .ul { display: flex; list-style-type: none; margin: 0; width: 100%; box-sizing: border-box; padding: 2px 0; } .component_breadcrumb .breadcrumb .ul > span { display: block; flex-grow: 1; padding: 7px 7px 7px 0; } .component_breadcrumb .breadcrumb .ul div, .component_breadcrumb .breadcrumb .ul .li { display: inline-block; } .component_breadcrumb .breadcrumb [alt="sidebar-open"] { margin-left: -21px; margin-right: 5px; cursor: pointer; display: none; } @media screen and (min-width: 1100px) { .component_breadcrumb .breadcrumb [alt="sidebar-open"] { display: block; } } .component_breadcrumb .component_logout { align-self: center; padding-right: 10px; } .component_breadcrumb .component_logout .component_icon { height: 20px; } .component_breadcrumb .component_saving { padding-left: 1px; } .component_breadcrumb .component_path-element { display: inline-block; } .component_breadcrumb .component_path-element .label { color: rgba(0, 0, 0, 0.75); padding: 2px 5px; } .component_breadcrumb .component_path-element .label:focus-visible .title { display: none; } .touch-yes .component_breadcrumb .component_path-element .label { padding-top: 5px; padding-bottom: 5px; } .component_breadcrumb .component_path-element .label span { display: inline-block; } .component_breadcrumb .component_path-element a.label { position: relative; color: rgba(0, 0, 0, 0.5); } .component_breadcrumb .component_path-element a.label span.title { position: absolute; left: 0; background: var(--color); color: var(--bg-color); font-size: 0.8em; opacity: 0; transform: translateY(5px); border-radius: 3px; white-space: nowrap; padding: 3px 10px !important; margin: 25px 0px 5px 0px; } .component_breadcrumb .component_path-element .component_path-element-wrapper { font-size: 18px; display: inline-block; } .component_breadcrumb .component_path-element .component_path-element-wrapper a { border-radius: 3px; letter-spacing: -0.5px; } .component_breadcrumb .component_path-element.highlight .component_path-element-wrapper a { color: var(--bg-color); background: var(--dark); border-radius: 3px; } .component_breadcrumb .component_separator img { vertical-align: middle; } /* Phone like devices */ body.touch-yes component-breadcrumb .ul span { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; box-sizing: border-box; -moz-box-sizing: border-box; white-space: nowrap; } body.touch-yes component-breadcrumb .ul span::-webkit-scrollbar { height: 0px; } body.touch-yes component-breadcrumb .ul span::-webkit-scrollbar-track { background: var(--super-light); } body.touch-yes component-breadcrumb .ul span::-webkit-scrollbar-thumb { background: #d2d2d2; border-radius: 1px; } /* Dark Mode */ .dark-mode .component_breadcrumb .component_separator img { filter: brightness(0.5) invert(1); } body.touch-no .component_path-element-wrapper a.label:hover { background: var(--border); } body.touch-no .component_breadcrumb img[alt="sidebar-open"]:hover { filter: drop-shadow(0px 0px 4px #e2e2e2); } body.touch-no .component_path-element-wrapper a.label:hover span.title { opacity: 1; transform: translateY(0px); transition: all 0.15s ease-out; } body.dark-mode.touch-no .component_path-element-wrapper a.label:hover { background: rgba(255, 255, 255, 0.05); } .dark-mode .component_breadcrumb .component_path-element .label { color: #f1f1f1; opacity: 0.7; } .dark-mode .component_breadcrumb .component_path-element a.label { opacity: 1; } /* ANIMATION */ .component_breadcrumb .breadcrumb-enter { transform: translateX(10px); opacity: 0; display: inline-block; } .component_breadcrumb .breadcrumb-enter.breadcrumb-enter-active { opacity: 1; transform: translateX(0); transition: all 0.2s ease-out; } .component_breadcrumb .saving_indicator-leave { opacity: 1; } .component_breadcrumb .saving_indicator-leave.saving_indicator-leave-active { opacity: 0; transition: all 0.2s ease-out; } .component_breadcrumb .saving_indicator-enter, .component_breadcrumb .saving_indicator-appear { transform-origin: center; animation-name: bounce; animation-duration: 0.5s; } @keyframes bounce { 0% { transform: scale(0); } 30% { transform: scale(1.5); } 100% { transform: scale(1); } } ================================================ FILE: public/assets/components/breadcrumb.js ================================================ import { toHref } from "../lib/skeleton/router.js"; import { animate, slideYOut, slideYIn, opacityOut } from "../lib/animate.js"; import { forwardURLParams } from "../lib/path.js"; import { safe } from "../lib/dom.js"; import assert from "../lib/assert.js"; import { settingsSave } from "../lib/store.js"; import { get as getConfig } from "../model/config.js"; import { loadCSS } from "../helpers/loader.js"; import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js"; import { mv as mv$ } from "../pages/filespage/model_files.js"; import { mv as mvVL, withVirtualLayer } from "../pages/filespage/model_virtual_layer.js"; const mv = (from, to) => withVirtualLayer( mv$(from, to), mvVL(from, to), ); class ComponentBreadcrumb extends HTMLElement { constructor() { super(); if (new URL(location.href).searchParams.get("nav") === "false") { this.disabled = true; return; } this.__init(); } async __init() { this.innerHTML = `
sidebar-open
${this.__htmlLogout()}
`; assert.type(this.querySelector("img[alt=\"sidebar-open\"]"), HTMLElement).onclick = () => { settingsSave({ visible: true }, "sidebar"); window.dispatchEvent(new Event("resize")); }; } attributeChangedCallback(name, oldValue, newValue) { if (this.disabled === true) return; else if (oldValue === newValue) return; switch (name) { case "path": if (newValue === "") return; return this.renderPath({ path: newValue, previous: oldValue || null }); case "indicator": return this.renderIndicator(); } throw new Error("component::breadcrumb.js unknow attribute name: "+ name); } static get observedAttributes() { return ["path", "indicator"]; } async renderPath({ path = "", previous }) { path = this.__normalised(path); previous = this.__normalised(previous); const pathChunks = path.split("/"); // STEP1: leaving animation on elements that will be removed if (previous !== null && previous.indexOf(path) >= 0) { const previousChunks = previous.split("/"); const nToAnimate = previousChunks.length - pathChunks.length; const tasks = []; for (let i=0; i { const label = idx === 0 ? getConfig("name", "Filestash") : chunk; const link = pathChunks.slice(0, idx + 1).join("/") + "/"; const limitSize = (word, highlight = false) => { if (highlight === true && word.length > 30) { return word.substring(0, 12).trim() + "..." + word.substring(word.length - 10, word.length).trim(); } else if (word.length > 27) return word.substring(0, 20).trim() + "..."; return word; }; const isLast = idx === pathChunks.length - 1; if (isLast) return `
${safe(limitSize(label))}
`; const minify = (() => { if (idx === 0) return false; else if (pathChunks.length <= (document.body.clientWidth > 800 ? 5 : 4)) return false; else if (idx > pathChunks.length - (document.body.clientWidth > 1000 ? 4 : 3)) return false; return true; })(); const tmpl = (() => { if (minify) return ` ... ${safe(limitSize(label, true))} `; return `
${safe(limitSize(label))}
`; })(); return `
${tmpl}
path_separator
`; }).join(""); this.setupDragDropTarget(); // STEP3: entering animation for elements that got added in if (previous !== null && path.indexOf(previous) >= 0) { const previousChunks = previous.split("/"); const nToAnimate = pathChunks.length - previousChunks.length; for (let i=0; i*`; await animate($indicator, { time: 500, keyframes: [ { transform: "scale(0)", offset: 0 }, { transform: "scale(1.5)", offset: 0.3 }, { transform: "scale(1)", offset: 1 }, ], fill: "none" }); } else { $indicator.style.opacity = 0; await animate($indicator, { time: 200, keyframes: opacityOut(), fill: "none" }); } } setupDragDropTarget() { this.querySelectorAll("a.label").forEach(($elmnt) => { const $folder = assert.type($elmnt, HTMLElement); const $path = assert.truthy($folder.closest(".component_path-element")); $folder.ondrop = async(e) => { $path.classList.remove("highlight"); const from = e.dataTransfer.getData("path"); let to = $path.getAttribute("data-path"); const [, fromName] = extractPath(from); to += fromName; if (isDir(from)) to += "/"; await mv(from, to).toPromise(); }; $folder.ondragover = (e) => { if (isNativeFileUpload(e)) return; e.preventDefault(); $path.classList.add("highlight"); }; $folder.ondragleave = () => { $path.classList.remove("highlight"); }; }); } __htmlLogout() { if (window.self !== window.top) return ""; // no logout button from an iframe return ` power `; } __normalised(path) { if (path === null) return null; else if (path.endsWith("/") === false) return path; return path.replace(new RegExp("/$"), ""); } } export function init() { return loadCSS(import.meta.url, "./breadcrumb.css"); } customElements.define("component-breadcrumb", ComponentBreadcrumb); ================================================ FILE: public/assets/components/decorator_shell_filemanager.css ================================================ .component_filemanager_shell { display: flex; height: 100%; background: white; } .component_filemanager_shell component-breadcrumb:hover ~ [data-bind="filemanager-children"], .component_filemanager_shell [data-bind="sidebar"]:hover ~ div > [data-bind="filemanager-children"] { border-color: var(--border); } .component_filemanager_shell [data-bind="filemanager-children"] { border-top: 2px solid; border-left: 2px solid; border-color: transparent; transition: 0.5s ease border-top-left-radius, 0.5s ease border-top-right-radius, 1s ease border-color; border-top-left-radius: 10px; border-top-right-radius: 0px; display: flex; height: 100%; overflow: hidden; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"] { border-top-left-radius: 0; border-top-right-radius: 0; border-left: none; border-color: transparent; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"].scrolling { border-top-left-radius: 30px; border-top-right-radius: 30px; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > component-breadcrumb > .component_breadcrumb, .component_filemanager_shell [data-bind="sidebar"]:empty ~ div > component-breadcrumb > .component_breadcrumb, .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"] .container, .component_filemanager_shell [data-bind="sidebar"]:empty ~ div > [data-bind="filemanager-children"] .container { width: 98%; margin: 0 auto; max-width: 815px; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div > [data-bind="filemanager-children"] { background: rgba(100,100,100,.05); } .component_filemanager_shell [data-bind="sidebar"] ~ div [is="component_filesystem"], .component_filemanager_shell [data-bind="sidebar"] ~ div [is="component_newitem"], .component_filemanager_shell [data-bind="sidebar"] ~ div component-menubar, .component_filemanager_shell [data-bind="sidebar"] ~ div component-breadcrumb, .component_filemanager_shell [data-bind="sidebar"] ~ div [is="component_submenu"] .component_submenu{ padding: 0 30px; } .component_filemanager_shell [data-bind="sidebar"].hidden ~ div [is="component_filesystem"], .component_filemanager_shell [data-bind="sidebar"]:empty ~ div [is="component_filesystem"], .component_filemanager_shell [data-bind="sidebar"].hidden ~ div [is="component_newitem"], .component_filemanager_shell [data-bind="sidebar"]:empty ~ div [is="component_newitem"], .component_filemanager_shell [data-bind="sidebar"].hidden ~ div component-menubar, .component_filemanager_shell [data-bind="sidebar"]:empty ~ div component-menubar, .component_filemanager_shell [data-bind="sidebar"].hidden ~ div component-breadcrumb, .component_filemanager_shell [data-bind="sidebar"]:empty ~ div component-breadcrumb, .component_filemanager_shell [data-bind="sidebar"].hidden ~ div [is="component_submenu"] .component_submenu, .component_filemanager_shell [data-bind="sidebar"]:empty ~ div [is="component_submenu"] .component_submenu { padding: 0px; } body.dark-mode .component_filemanager_shell { background: #2b2d30; } ================================================ FILE: public/assets/components/decorator_shell_filemanager.js ================================================ import { createElement, createRender } from "../lib/skeleton/index.js"; import { navigate, fromHref } from "../lib/skeleton/router.js"; import rxjs, { effect } from "../lib/rx.js"; import { onDestroy } from "../lib/skeleton/lifecycle.js"; import { animate, slideYOut } from "../lib/animate.js"; import { qs, safe } from "../lib/dom.js"; import { loadCSS } from "../helpers/loader.js"; import { init as initBreadcrumb } from "../components/breadcrumb.js"; import ctrlSidebar, { init as initSidebar } from "./sidebar.js"; import { isAlreadyFocused } from "../pages/filespage/helper.js"; export default function(ctrl) { const urlToPath = (pathname = "") => decodeURIComponent(pathname.split("/").filter((_, i) => i !== 1).join("/")); const $page = createElement(`
`); return async function(render) { render($page); // feature1: setup the breadcrumb path const $breadcrumb = qs($page, "component-breadcrumb"); $breadcrumb.setAttribute("path", urlToPath(fromHref(location.pathname + location.hash))); // feature2: setup the childrens const $main = qs($page, `[data-bind="filemanager-children"]`); $main.classList.remove("hidden"); ctrl(createRender($main)); ctrlSidebar(createRender(qs($page, `[data-bind="sidebar"]`)), {}); onDestroy(async() => { if ((history.state.previous || "").startsWith("/view/") && location.pathname.startsWith("/files/")) { await animate($main, { time: 100, keyframes: slideYOut(20), fill: "none" }); $main.classList.add("hidden"); } }); // feature3: key shortcut const regexStartFiles = new RegExp("^/files/.+"); effect(rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => regexStartFiles.test(fromHref(location.pathname)) && e.keyCode === 8 && !isAlreadyFocused()), // backspace in file manager rxjs.tap(() => { const p = location.pathname.replace(new RegExp("/$"), "").split("/"); p.pop(); navigate(p.join("/") + "/" + location.hash); }), )); }; } export function init() { return Promise.all([ loadCSS(import.meta.url, "../components/decorator_shell_filemanager.css"), initBreadcrumb(), initSidebar(), ]); } ================================================ FILE: public/assets/components/dropdown.css ================================================ ================================================ FILE: public/assets/components/dropdown.js ================================================ import { createFragment } from "../lib/skeleton/index.js"; import { animate, slideYIn } from "../lib/animate.js"; import assert from "../lib/assert.js"; import { loadCSS } from "../helpers/loader.js"; export default class ComponentDropdown extends HTMLElement { constructor() { super(); this.render(); } async connectedCallback() { await loadCSS(import.meta.url, "./dropdown.css"); } static get observedAttributes() { return ["options"]; } render() { this.classList.add("component_dropdown", "view", "sort"); this.appendChild(createFragment(`
download_white
`)); this.appendChild(createFragment(`
  • Save current file
  • Export as HTML
  • Export as PDF
  • Export as Markdown
  • Export as TXT
  • Export as Latex
  • Export as ical
  • Export as Open office
  • Export as Beamer
`)); const setActive = () => this.classList.toggle("active"); assert.type(this.querySelector(".dropdown_button"), HTMLElement).onclick = () => { setActive(); animate(assert.type(this.querySelector(".dropdown_container"), HTMLElement), { time: 100, keyframes: slideYIn(2), }); }; } } if (!customElements.get("component-dropdown")) customElements.define("component-dropdown", ComponentDropdown); ================================================ FILE: public/assets/components/fab.css ================================================ .component_fab { position: fixed; bottom: 20px; right: 20px; z-index: 2; background: transparent; } .component_fab .content:empty { display: none; } .component_fab .content { height: 25px; width: 25px; padding: 13px; border-radius: 50%; background: var(--dark); box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; z-index: 1000; cursor: pointer; } ================================================ FILE: public/assets/components/fab.js ================================================ import { loadCSS } from "../helpers/loader.js"; import assert from "../lib/assert.js"; export default class ComponentFab extends HTMLButtonElement { constructor() { super(); this.innerHTML = `
`; this.classList.add("component_fab"); } async render($icon) { await loadCSS(import.meta.url, "./fab.css"); assert.type(this.querySelector(".content"), HTMLElement).replaceChildren($icon); } } customElements.define("component-fab", ComponentFab, { extends: "button" }); ================================================ FILE: public/assets/components/form.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import { qs, safe } from "../lib/dom.js"; import { gid } from "../lib/random.js"; import { ApplicationError } from "../lib/error.js"; import "./icon.js"; export function formTmpl(options = {}) { const { autocomplete = true, renderNode = null, renderLeaf = null, renderInput = $renderInput({ autocomplete }), } = options; return { renderNode: (opts) => { if (renderNode) { const $el = renderNode({ ...opts, format }); if ($el) return $el; } const { label } = opts; return createElement(`
${safe(format(label))}
`); }, renderLeaf: (opts) => { if (renderLeaf) { const $el = renderLeaf({ ...opts, format }); if ($el) return $el; } const { label } = opts; return createElement(` `); }, renderInput, formatLabel: format }; }; export function $renderInput(options = {}) { const { autocomplete } = options; return function(props) { const { id = null, type, value = null, placeholder = "", required = false, readonly = false, path = [], datalist = null, options = null, pattern = null, } = props; const attrs = []; attrs.push(($node) => $node.setAttribute("name", path.join("."))); if (id) attrs.push(($node) => $node.setAttribute("id", id)); if (placeholder) attrs.push(($node) => $node.setAttribute("placeholder", placeholder)); if (!autocomplete || props.autocomplete === false) attrs.push(($node) => { $node.setAttribute("autocomplete", "off"); $node.setAttribute("autocorrect", "off"); $node.setAttribute("autocapitalize", "off"); $node.setAttribute("spellcheck", "off"); if (type === "password") { $node.setAttribute("data-lpignore", "true"); // LastPass $node.setAttribute("data-1p-ignore", "true"); // 1Password $node.setAttribute("data-bwignore", "true"); // Bitwarden $node.setAttribute("data-protonpass-ignore", "true"); // Proton Pass } }); if (pattern) attrs.push(($node) => $node.setAttribute((type !== "file" ? "pattern" : "accept"), pattern)); if (required) attrs.push(($node) => $node.setAttribute("required", "")); if (readonly) attrs.push(($node) => $node.setAttribute("disabled", "")); switch (type) { case "text": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); if (!datalist) return $input; const dataListId = gid("list_"); $input.setAttribute("list", dataListId); $input.setAttribute("datalist", datalist.join(",")); const $wrapper = document.createElement("span"); const $datalist = document.createElement("datalist"); $datalist.setAttribute("id", dataListId); $wrapper.appendChild($input); $wrapper.appendChild($datalist); (props.multi ? multicomplete(value, datalist) : (datalist || [])).forEach((value) => { $datalist.appendChild(new Option(value)); }); if (!props.multi) return $wrapper; // @ts-ignore $input.refresh = () => { const _datalist = $input?.getAttribute("datalist")?.split(","); $datalist.innerHTML = ""; multicomplete($input.value, _datalist).forEach((value) => { $datalist.appendChild(new Option(value)); }); }; $input.oninput = () => { for (const $option of $datalist.children) { $option.remove(); } // @ts-ignore $input.refresh(); }; return $wrapper; } case "enable": { const $div = createElement(`
`); const $input = $div.querySelector("input"); attrs.map((setAttribute) => setAttribute($input)); return $div; } case "number": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "password": { const $div = createElement(`
`); const $input = $div.querySelector("input"); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); const $icon = $div.querySelector("component-icon"); if ($icon instanceof HTMLElement) { $icon.onclick = function(e) { if (!(e.target instanceof HTMLElement)) return; const $input = e.target?.parentElement?.previousElementSibling; if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); if ($input.getAttribute("type") === "password") $input.setAttribute("type", "text"); else $input.setAttribute("type", "password"); }; } return $div; } case "long_text": { const $textarea = createElement(` `); if (!($textarea instanceof HTMLTextAreaElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $textarea.value = value; attrs.map((setAttribute) => setAttribute($textarea)); return $textarea; } case "bcrypt": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "hidden": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; $input.setAttribute("name", path.join(".")); return $input; } case "boolean": { const $div = createElement(`
`); const $input = $div.querySelector("input"); attrs.map((setAttribute) => setAttribute($input)); return $div; } case "select": { const $select = createElement(` `); if (!($select instanceof HTMLSelectElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $select.value = value || props.default; attrs.map((setAttribute) => setAttribute($select)); (options || []).forEach((name) => { const $option = createElement(` `); $option.textContent = name; $option.setAttribute("name", name); if (name === (value || props.default)) { $option.setAttribute("selected", ""); } $select.appendChild($option); }); return $select; } case "date": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "datetime": { const $input = createElement(` `); if (!($input instanceof HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "image": { const $img = createElement(""); $img.setAttribute("id", id); $img.setAttribute("src", value); return $img; } case "file": { const $file = createElement(`
`); const $preview = qs($file, ".preview"); const draw = (val) => { $preview.innerHTML = ""; if ((val || "").substring(0, 10) === "data:image") $preview.appendChild(createElement(` `)); else if ((val || "").substring(0, 20) === "data:application/pdf") $preview.appendChild(createElement(` `)); }; const $input = qs($file, "input"); $input.onchange = (e) => { if (e.target.files.length === 0) { draw(null); return; } const reader = new window.FileReader(); reader.readAsDataURL(e.target.files[0]); reader.onload = () => draw(reader.result); }; $file.oncancel = () => { if (!value) return; $input.dispatchEvent(new Event("input")); draw(null); }; attrs.map((setAttribute) => setAttribute($input)); draw(value); return $file; } default: { const $input = createElement(` `); $input.setAttribute("value", `unknown element type ${type}`); $input.setAttribute("name", path.join(".")); return $input; } } }; } export function format(name) { if (typeof name !== "string") { return "N/A"; } return name .split("_") .map((word) => { if (word.length < 1) { return word; } return (word[0] || "").toUpperCase() + word.substring(1); }) .join(" "); }; export function multicomplete(input, datalist) { input = (input || "").trim().replace(/,$/g, ""); const current = input.split(",").map((val) => val.trim()).filter((t) => !!t); const diff = datalist.filter((x) => current.indexOf(x) === -1); return diff.map((candidate) => input.length === 0 ? candidate : `${input}, ${candidate}`); } ================================================ FILE: public/assets/components/icon.js ================================================ class Icon extends HTMLElement { static get observedAttributes() { return ["name"]; } attributeChangedCallback() { const alt = this.getAttribute("name"); const img = this._mapOfIcon(alt); window.requestAnimationFrame(() => { this.innerHTML = this.render({ alt, img }); }); } render({ alt, img }) { return `${alt}`; } _mapOfIcon(name) { switch (name) { case "arrow_right": return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMzNiIgZD0iTTguNTkgMTYuMzRsNC41OC00LjU5LTQuNTgtNC41OUwxMCA1Ljc1bDYgNi02IDZ6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg=="; case "arrow_left": return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzZmNmY2ZjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS41MTE4MTEwMjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIgZD0ibSAxNiw3LjE2IC00LjU4LDQuNTkgNC41OCw0LjU5IC0xLjQxLDEuNDEgLTYsLTYgNiwtNiB6Ii8+CiAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAtLjI1aDI0djI0SDB6Ii8+Cjwvc3ZnPgo="; case "eye": return "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTtzdHJva2U6IzkxOTE5MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjRweDt9PC9zdHlsZT4KICAgIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTEsMjRhMjYuODUsMjYuODUsMCwwLDEsNDYsMCIvPgogICAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMSwyNGEyNi44NSwyNi44NSwwLDAsMCw0NiwwIi8+CiAgICA8ZWxsaXBzZSBjbGFzcz0iY2xzLTEiIGN4PSIyNCIgY3k9IjI0IiByeD0iNyIgcnk9IjciLz4KPC9zdmc+Cg=="; case "close": return "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo="; case "loading": return "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo="; } return ""; } } customElements.define("component-icon", Icon); ================================================ FILE: public/assets/components/loader.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import rxjs from "../lib/rx.js"; import { animate, opacityIn } from "../lib/animate.js"; class Loader extends HTMLElement { constructor() { super(); this.timeout = setTimeout(() => { this.innerHTML = this.render({ inline: this.hasAttribute("inlined"), }); }, parseInt(this.getAttribute("delay") || "0")); } disconnectedCallback() { clearTimeout(this.timeout); } render({ inline }) { const fixedCss = ` position: fixed; left: 0; right: 0; top: calc(50% - 200px);`; return `
`; } } customElements.define("component-loader", Loader); export function createLoader($parent, opts = {}) { const { wait = 250, append = ($loader) => $parent.appendChild($loader), } = opts; const $icon = createElement(`
`); const alreadyHasLoader = () => !!$parent.querySelector(".component_loader"); const id = setTimeout(() => { if (alreadyHasLoader()) return; append($icon); animate($icon, { time: 750, keyframes: opacityIn() }); }, wait); const cancel = () => { clearTimeout(id); $icon.remove(); }; return rxjs.pipe( rxjs.tap(() => cancel()), rxjs.finalize(() => cancel()) ); } // > after this should be deprecated export default createElement(""); export function toggle($node, show = false) { if (show === true) return rxjs.tap(() => $node.appendChild(createElement(""))); else return rxjs.tap(() => $node.querySelector("component-loader")?.remove()); } ================================================ FILE: public/assets/components/modal.css ================================================ .component_modal{ position: fixed; top: 0; bottom: 0; left: 0; right: 0; background: #f2f3f5f0; z-index: 1000; display: flex; } .component_modal > div{ box-shadow: 1px 2px 20px rgba(0, 0, 0, 0.1); background: white; width: 80%; max-width: 320px; padding: 20px 20px 0 20px; border-radius: 2px; margin: auto; } .dark-mode .component_modal{ background: var(--bg-color); color: rgba(0,0,0,0.7); } .component_popup .popup--content { font-size: 1.1em; margin: 0; } .component_popup .popup--content p { margin: 0; } .component_popup .popup--content .modal-error-message { font-size: 15px; } .component_popup .buttons { margin: 15px -20px 0 -20px; display: flex; } .component_popup .buttons > div { display: flex; width: 100%; } .component_popup .buttons [type="submit"] { border-radius: 10px 0 0; } .component_popup .buttons > button { width: 50%; margin-left: auto; } .component_popup .buttons button { text-transform: uppercase; } .component_popup .modal-error-message { color: var(--error); } .component_popup .center { text-align: center; } ================================================ FILE: public/assets/components/modal.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import assert from "../lib/assert.js"; import rxjs, { applyMutation } from "../lib/rx.js"; import { animate } from "../lib/animate.js"; import { qs, qsa } from "../lib/dom.js"; import { loadCSS } from "../helpers/loader.js"; export function createModal(opts) { const $dom = assert.type(qs(document.body, "component-modal"), HTMLElement); assert.type($dom, ModalComponent); return ($node, fn) => $dom.trigger($node, { onQuit: fn, ...opts }); } export const MODAL_LEFT_BUTTON = 1; export const MODAL_RIGHT_BUTTON = 2; export const MODAL_QUIT = 0; const $modal = createElement(`
`); const $app = qs(document.body, "#app"); class ModalComponent extends HTMLElement { async connectedCallback() { await loadCSS(import.meta.url, "./modal.css"); } trigger($node, { withButtonsLeft = null, withButtonsRight = null, onQuit = (_a, _b) => Promise.resolve() }) { const close$ = new rxjs.Subject(); // feature: build the dom qs($modal, `[data-bind="body"]`).replaceChildren($node); $app.setAttribute("inert", "true"); this.replaceChildren($modal); qsa($modal, ".component_popup > div.buttons > button").forEach(($button, i) => { assert.truthy(i >= 0 && i <= 2); let currentLabel = null; let buttonIndex = null; if (i === 0) { currentLabel = withButtonsLeft; buttonIndex = MODAL_LEFT_BUTTON; } else if (i === 1) { currentLabel = withButtonsRight; buttonIndex = MODAL_RIGHT_BUTTON; } if (currentLabel !== null) { $button.classList.remove("hidden"); $button.removeAttribute("disabled"); $button.textContent = currentLabel; $button.onclick = () => close$.next(buttonIndex); } else { $button.classList.add("hidden"); $button.setAttribute("disabled", "true"); } }); effect(rxjs.fromEvent($modal, "mousedown").pipe( rxjs.filter((e) => e.target.getAttribute("id") === "modal-box"), rxjs.tap(() => close$.next(MODAL_QUIT)), )); effect(rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => e.keyCode === 27), rxjs.tap(() => close$.next(MODAL_QUIT)), )); // feature: closing the modal const $body = () => qs($modal, "div > div"); effect(close$.pipe( rxjs.tap(() => $app.removeAttribute("inert")), rxjs.mergeMap((data) => onQuit(data, $node) || Promise.resolve()), rxjs.tap(() => animate($body(), { time: 200, keyframes: [ { opacity: 1, transform: "translateY(0)" }, { opacity: 0, transform: "translateY(20px)" }, ], })), rxjs.delay(100), rxjs.tap(() => animate($modal, { time: 200, keyframes: [{ opacity: 1 }, { opacity: 0 }], })), rxjs.mapTo([]), applyMutation($modal, "remove"), rxjs.tap(free), )); // feature: animate opening effect(rxjs.of(null).pipe( rxjs.tap(() => animate($modal, { onEnter: () => $body().style.setProperty("opacity", "0"), onExit: () => $body().style.setProperty("opacity", "1"), time: 250, keyframes: [ { opacity: 0 }, { opacity: 1 }, ], })), rxjs.delay(50), rxjs.tap(() => animate($body(), { time: 200, keyframes: [ { opacity: 0, transform: "translateY(10px)" }, { opacity: 1, transform: "translateY(0)" }, ], })), )); return (id) => close$.next(id); } } customElements.define("component-modal", ModalComponent); let _observables = []; const effect = (obs) => _observables.push(obs.subscribe()); const free = () => { for (let i = 0; i < _observables.length; i++) { _observables[i].unsubscribe(); } _observables = []; }; ================================================ FILE: public/assets/components/notification.css ================================================ .component_notification { position: fixed; bottom: 20px; left: 20px; right: 70px; font-size: 0.95em; z-index: 1001; } .component_notification .component_notification--container { overflow: hidden; width: 400px; text-align: left; display: inline-block; padding: 15px 20px 15px 15px; border-radius: 2px; box-shadow: rgba(158, 163, 172, 0.3) 5px 5px 20px; display: flex; align-items: center; } .component_notification .component_notification--container.info { background: var(--color); color: rgba(255, 255, 255, 0.8); } .dark-mode .component_notification .component_notification--container.info { color: rgba(0, 0, 0, 0.8); } .component_notification .component_notification--container.error { background: var(--error); color: rgba(0, 0, 0, 0.5); } .component_notification .component_notification--container.success { background: var(--success); color: rgba(0, 0, 0, 0.5); } .component_notification .component_notification--container .message { flex: 1 1 auto; max-height: 92px; max-width: 100%; overflow: hidden; } .component_notification .component_notification--container .close { cursor: pointer; padding: 0 2px; } .component_notification .component_notification--container .close .component_icon { height: 18px; } @media (max-width: 490px) { .component_notification { bottom: 0px; left: 0px; right: 0px; } .component_notification .component_notification--container { width: 100%; box-sizing: border-box; } } .component_notification .component_notification--container { box-shadow: rgba(0, 0, 0, 0.3) 5px 5px 20px; } ================================================ FILE: public/assets/components/notification.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import { qs } from "../lib/dom.js"; import { ApplicationError } from "../lib/error.js"; import { animate, slideYIn, slideYOut } from "../lib/animate.js"; import { loadCSS } from "../helpers/loader.js"; const createNotification = async(msg, type) => { const $notification = createElement(`
close
`); if (msg instanceof HTMLElement) qs($notification, ".message").appendChild(msg); else qs($notification, ".message").innerText = msg; return $notification; }; class NotificationComponent extends HTMLElement { buffer = []; async connectedCallback() { await loadCSS(import.meta.url, "./notification.css"); } async trigger(message, type) { if (this.buffer.length > 20) this.buffer.pop(); // failsafe this.buffer.push({ message, type }); if (this.buffer.length !== 1) { const $close = this.querySelector(".close"); if (!($close instanceof HTMLElement) || !$close.onclick) return; $close.onclick(new PointerEvent("mousedown")); return; } await this.run(); } async run() { if (this.buffer.length === 0) return; const { message, type } = this.buffer[0]; const $notification = await createNotification(message, type); this.replaceChildren($notification); await animate($notification, { keyframes: slideYIn(50), time: 100, }); const ids = []; await Promise.race([ new Promise((done) => ids.push(setTimeout(() => { done(new MouseEvent("mousedown")); }, this.buffer.length === 1 ? 8000 : 800))), new Promise((done) => ids.push(setTimeout(() => { const $close = $notification.querySelector(".close"); if (!($close instanceof HTMLElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: notification close button missing"); $close.onclick = done; }, 1000))), ]); ids.forEach((id) => clearTimeout(id)); await animate($notification, { keyframes: slideYOut(10), time: 200, }); $notification.remove(); this.buffer.shift(); await this.run(); } } customElements.define("component-notification", NotificationComponent); function find() { const $dom = document.body.querySelector("component-notification"); if (!($dom instanceof NotificationComponent)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: wrong type notification component"); return $dom; } export default class Notification { static info(msg) { find().trigger(msg, "info"); } static success(msg) { find().trigger(msg, "success"); } static error(msg) { find().trigger(msg, "error"); } } ================================================ FILE: public/assets/components/sidebar.css ================================================ [data-bind="sidebar"] { z-index: 1; display: flex; width: 250px; } @media screen and (min-width: 1600px) { .component_filemanager_shell [data-bind="sidebar"] { width: 300px; } } @media screen and (min-width: 2000px) { .component_filemanager_shell [data-bind="sidebar"] { width: 330px; } } .component_filemanager_shell .component_sidebar { flex-grow: 1; padding: 3px 0px 25px 0px; overflow-y: scroll; height: 100%; direction: rtl; box-sizing: border-box; } .component_filemanager_shell .component_sidebar .component_skeleton { margin-bottom: 5px; width: calc(100% - 5px); margin-left: 5px; opacity: 0; animation-duration: 0.5s; animation-delay: 1s; animation-name: skeleton-appear; animation-fill-mode: forwards; animation-iteration-count: 1; } @keyframes skeleton-appear { from { opacity: 0; } to { opacity: 1; } } .component_filemanager_shell .component_sidebar > div { direction: ltr; } .component_filemanager_shell .component_sidebar h3 { display: flex; margin-bottom: 5px; margin-top: 20px; color: var(--light); font-size: 0.9rem; opacity: 0.8; padding-left: 15px; text-transform: capitalize; } body.touch-no .component_filemanager_shell .component_sidebar h3 img:hover { filter: drop-shadow(0px 0px 4px #e2e2e2); } .component_filemanager_shell .component_sidebar h3 img, .component_filemanager_shell .component_sidebar h3 svg, .component_filemanager_shell .component_sidebar h3 component-icon img { width: 16px; padding-right: 6px; margin-left: -1px; } .component_filemanager_shell .component_sidebar .component_icon .component_filemanager_shell .component_sidebar h3 input::placeholder { text-transform: capitalize; color: var(--light); } .component_filemanager_shell .component_sidebar h3 input { border: none; color: var(--color); font-weight: bold; font-size: inherit; background: inherit; } .component_filemanager_shell .component_sidebar [data-bind="your-files"] { margin-bottom: 30px; } .component_filemanager_shell .component_sidebar [data-bind] > ul { margin-left: 0px; } .component_filemanager_shell .component_sidebar ul { margin-top: 0px; padding: 0; margin: 0 0 0 5px; list-style-type: none; } @media screen and (min-width: 1600px) { .component_filemanager_shell .component_sidebar ul { margin-left: 7px; } } @media screen and (min-width: 2000px) { .component_filemanager_shell .component_sidebar ul { margin-left: 10px; } } .component_filemanager_shell .component_sidebar ul li { margin-top: 0px; padding-left: 5px; padding-right: 2px; } .component_filemanager_shell .search .component_sidebar ul { margin: 0; } .component_filemanager_shell .search .component_sidebar ul ul li { padding-left: 0; } .component_filemanager_shell .component_sidebar a { display: flex; padding: 5px 5px 5px 10px; width: 100%; box-sizing: border-box; letter-spacing: -0.2px; } .component_filemanager_shell .component_sidebar .placeholder { border: 2px dashed var(--border); background: inherit; border-radius: 4px; color: var(--light); box-sizing: border-box; padding: 5px; font-family: inherit; } body.touch-no .component_filemanager_shell .component_sidebar a:not([aria-selected="true"]):hover, .component_filemanager_shell .component_sidebar a[aria-selected="true"] { background: var(--border); border-radius: 3px; } .component_filemanager_shell .component_sidebar a.highlight { color: var(--bg-color); background: var(--dark); border-radius: 3px; } .component_filemanager_shell .component_sidebar .component_icon { width: 16px; padding-right: 5px; opacity: 0.85; } .component_filemanager_shell .component_sidebar a > div { padding-left: 2px; text-transform: capitalize; } .component_filemanager_shell .resizer { width: 10px; height: 100%; align-self: center; cursor: ew-resize; } .component_filemanager_shell li.pointer { cursor: pointer; } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a { margin-bottom: 2px; justify-content: space-between; font-size: 0.95rem; padding-left: 7px; } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a > div { text-transform: none; } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a .hash:before { content: "#"; font-size: 0.9rem; opacity: 0.5; } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a[aria-selected="true"] { background: var(--light); color: #f2f2f2; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } .dark-mode .component_filemanager_shell .component_sidebar [data-bind="taglist"] a[aria-selected="true"] { background: var(--border); } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a[aria-selected="true"] svg { opacity: 1; } .component_filemanager_shell .component_sidebar [data-bind="taglist"] a svg { opacity: 0; background: rgba(255, 255, 255, 0.03); align-self: center; width: 13px; height: 13px; border-radius: 50%; padding: 4px; } ================================================ FILE: public/assets/components/sidebar.js ================================================ import { createElement, createRender, onDestroy } from "../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../lib/rx.js"; import { qs } from "../lib/dom.js"; import { settingsGet, settingsSave } from "../lib/store.js"; import { loadCSS } from "../helpers/loader.js"; import t from "../locales/index.js"; import { getCurrentPath } from "../pages/viewerpage/common.js"; import { generateSkeleton } from "./skeleton.js"; import ctrlNavigationPane from "./sidebar_files.js"; import ctrlTagPane from "./sidebar_tags.js"; export default async function ctrlSidebar(render, {}) { if (new URL(location.toString()).searchParams.get("nav") === "false") return; else if (window.self !== window.top) return; else if (document.body.clientWidth < 850) return; const $sidebar = render(createElement(`

close

${generateSkeleton(2)}
${generateSkeleton(2)}
`)); withInstantLoad($sidebar); withResize($sidebar); const path = getCurrentPath("(/view/|/files/)"); // fature: file navigation pane const $files = qs($sidebar, `[data-bind="your-files"]`); ctrlNavigationPane(createRender($files), { $sidebar, path }); // feature: tag viewer const $tags = qs($sidebar, `[data-bind="your-tags"]`); effect(rxjs.merge( rxjs.of(null), rxjs.fromEvent(window, "filestash::tag"), ).pipe( rxjs.tap(() => ctrlTagPane(createRender($tags), { tags: [...$tags.querySelectorAll("a")].map(($tag) => $tag.innerText.trim()), path, })), )); // feature: visibility of the sidebar const isVisible = () => settingsGet({ visible: true }, "sidebar").visible; const forceRefresh = () => window.dispatchEvent(new Event("resize")); effect(rxjs.merge(rxjs.fromEvent(window, "keydown")).pipe( rxjs.filter((e) => e.key === "b" && e.ctrlKey === true), rxjs.tap(() => { settingsSave({ visible: $sidebar.classList.contains("hidden") }, "sidebar"); isVisible() ? $sidebar.classList.remove("hidden") : $sidebar.classList.add("hidden"); forceRefresh(); }), )); effect(rxjs.merge( rxjs.fromEvent(window, "resize"), rxjs.of(null), ).pipe( rxjs.tap(() => { const $breadcrumbButton = qs(document.body, "[alt=\"sidebar-open\"]"); if (document.body.clientWidth < 1100) $sidebar.classList.add("hidden"); else if (isVisible()) { $sidebar.classList.remove("hidden"); $breadcrumbButton.classList.add("hidden"); } else { $sidebar.classList.add("hidden"); $breadcrumbButton.classList.remove("hidden"); } }), rxjs.catchError((err) => { if (err instanceof DOMException) return rxjs.EMPTY; throw err; }), )); effect(onClick(qs($sidebar, `img[alt="close"]`)).pipe( rxjs.tap(() => { settingsSave({ visible: false }, "sidebar"); $sidebar.classList.add("hidden"); forceRefresh(); }), )); } const withResize = (function() { let memory = null; return ($sidebar) => { const $resize = createElement(`
`); effect(rxjs.fromEvent($resize, "mousedown").pipe( rxjs.mergeMap((e0) => rxjs.fromEvent(document, "mousemove").pipe( rxjs.takeUntil(rxjs.fromEvent(document, "mouseup")), rxjs.startWith(e0), rxjs.pairwise(), rxjs.map(([prevX, currX]) => currX.clientX - prevX.clientX), rxjs.scan((width, delta) => width + delta, $sidebar.clientWidth), )), rxjs.startWith(memory), rxjs.filter((w) => !!w), rxjs.map((w) => Math.min(Math.max(w, 250), 350)), rxjs.tap((w) => { $sidebar.style.width = `${w}px`; memory = w; }), )); $sidebar.appendChild($resize); }; }()); const withInstantLoad = (function() { const state = { scrollTop: 0, $cache: null }; return ($sidebar) => { if (state.$cache) { $sidebar.replaceChildren(state.$cache); $sidebar.firstElementChild.scrollTop = state.scrollTop; } onDestroy(() => { state.$cache = $sidebar.firstElementChild?.cloneNode(true); state.scrollTop = $sidebar.firstElementChild.scrollTop; }); }; }()); export function init() { return loadCSS(import.meta.url, "./sidebar.css"); } ================================================ FILE: public/assets/components/sidebar_files.js ================================================ import rxjs, { effect } from "../lib/rx.js"; import { createElement, createRender } from "../lib/skeleton/index.js"; import { toHref } from "../lib/skeleton/router.js"; import { qs, qsa, safe } from "../lib/dom.js"; import { forwardURLParams } from "../lib/path.js"; import cache from "../pages/filespage/cache.js"; import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js"; import { mv as mvVL, withVirtualLayer } from "../pages/filespage/model_virtual_layer.js"; import { hooks, mv as mv$ } from "../pages/filespage/model_files.js"; import ctrlError from "../pages/ctrl_error.js"; export default async function ctrlNavigationPane(render, { $sidebar, path }) { // feature: init dom const $fs = document.createDocumentFragment(); const dirname = path.replace(new RegExp("[^\/]*$"), ""); const chunks = dirname.split("/"); for (let i=1; i { const cleaners = [ hooks.ls.listen(({ path }) => subscriber.next(path)), hooks.mutation.listen(async({ op, path }) => { if (["mv", "mkdir", "rm"].indexOf(op) === -1) return; subscriber.next(path); }), ]; return () => cleaners.map((fn) => fn()); }).pipe( rxjs.mergeMap(async(path) => { const display = path === "/" ? render : createRender(qs($sidebar, `[data-path="${CSS.escape(path)}"] ul`)); display(await _createListOfFiles(path, {})); }), rxjs.catchError((err) => { if (err instanceof DOMException) return rxjs.EMPTY; return ctrlError()(err); }), )); // feature: highlight current selection try { const $active = qs($sidebar, `[data-path="${dirname}"] a`); const checkVisible = ($el) => { const rect = $el.getBoundingClientRect(); return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth); }; $active.setAttribute("aria-selected", "true"); const tags = new URLSearchParams(location.search).getAll("tag").length; if (checkVisible($active) === false && tags === 0) { $active.offsetTop < window.innerHeight ? $sidebar.firstChild.scrollTo({ top: 0, behavior: "smooth" }) : $active.scrollIntoView({ behavior: "smooth", block: "nearest" }); } } catch (err) {} // feature: quick search effect(rxjs.fromEvent(qs($sidebar, "h3 input"), "keydown").pipe( rxjs.debounceTime(200), rxjs.tap((e) => { const inputValue = e.target.value.toLowerCase(); qsa($sidebar, "[data-bind=\"your-files\"] li a").forEach(($li) => { if (inputValue === "") { $li.classList.remove("hidden"); $sidebar.classList.remove("search"); return; } $sidebar.classList.add("search"); qs($li, "div").textContent.toLowerCase().indexOf(inputValue) === -1 ? $li.classList.add("hidden") : $li.classList.remove("hidden"); }); }), rxjs.finalize(() => $sidebar.classList.remove("search")), )); } const mv = (from, to) => withVirtualLayer( mv$(from, to), mvVL(from, to), ); async function _createListOfFiles(path, { basename = null, dirname = null }) { const MAX_DISPLAY = 100; const r = await cache().get(path); const whats = r === null ? (basename ? [basename] : []) : r.files .filter(({ type, name }) => type === "directory" && name[0] !== ".") .map(({ name }) => name) .sort((a, b) => a.localeCompare(b)); const $lis = document.createDocumentFragment(); const $fragment = document.createDocumentFragment(); const $ul = document.createElement("ul"); for (let i=0; i directory
${safe(whats[i])}
    `); const $link = qs($li, "a"); if ($link.getAttribute("href") === "/files" + dirname) { $link.removeAttribute("href", ""); $link.removeAttribute("data-link"); } else { $link.ondrop = async(e) => { $link.classList.remove("highlight"); const from = e.dataTransfer.getData("path"); let to = $link.parentElement.getAttribute("data-path"); const [, fromName] = extractPath(from); to += fromName; if (isDir(from)) to += "/"; if (from === to) return; await mv(from, to).toPromise(); }; $link.ondragover = (e) => { if (isNativeFileUpload(e)) return; e.preventDefault(); $link.classList.add("highlight"); }; $link.ondragleave = () => { $link.classList.remove("highlight"); }; } if (i <= MAX_DISPLAY) $lis.appendChild($li); else $fragment.appendChild($li); if (i === MAX_DISPLAY) { const $more = createElement(`
  • ...
  • `); $lis.appendChild($more); $more.onclick = () => { $ul.appendChild($fragment); $more.remove(); }; } } $ul.appendChild($lis); return $ul; } ================================================ FILE: public/assets/components/sidebar_tags.js ================================================ import rxjs, { effect } from "../lib/rx.js"; import { createElement, createRender } from "../lib/skeleton/index.js"; import { toHref } from "../lib/skeleton/router.js"; import ajax from "../lib/ajax.js"; import { qs, safe } from "../lib/dom.js"; import { forwardURLParams } from "../lib/path.js"; import { get as getConfig } from "../model/config.js"; import { isMobile } from "../pages/filespage/helper.js"; import t from "../locales/index.js"; export default async function ctrlTagPane(render, { tags, path }) { if (getConfig("enable_tags", false) === false) { render(document.createElement("div")); return; } const $page = createElement(`

    tag ${t("Tags")}

    `); const renderTaglist = createRender(qs($page, `[data-bind="taglist"]`)); effect(rxjs.merge( tags.length === 0 ? rxjs.EMPTY : rxjs.of({ tags }), ajax({ url: forwardURLParams(`api/metadata/search`, ["share"]), method: "POST", responseType: "json", body: { tags: [], path, }, }).pipe( rxjs.map(({ responseJSON }) => { const tags = {}; Object.values(responseJSON.results).forEach((forms) => { forms.forEach(({ id, value = "" }) => { if (id !== "tags") return; value.split(",").forEach((tag) => { tags[tag.trim()] = null; }); }); }); return { tags: Object.keys(tags).sort(), response: responseJSON.results }; }), rxjs.catchError(() => rxjs.of({ tags: [] })), ), ).pipe( // feature: create the DOM rxjs.mergeMap(({ tags, response }) => { render($page); if (tags.length === 0) { renderTaglist(createElement(`
    ∅
    `)); return rxjs.EMPTY; } const $fragment = document.createDocumentFragment(); tags.forEach((name) => { const $tag = createElement(`
    ${safe(name)}
    `); const url = new URL(location.href); if (url.searchParams.getAll("tag").indexOf(name) === -1) { $tag.setAttribute("href", forwardURLParams(toHref("/files" + path.replace(new RegExp("[^\/]+$"), "") + "?tag=" + name), ["share", "tag"])); } else { url.searchParams.delete("tag", name); $tag.setAttribute("href", url.toString()); $tag.setAttribute("aria-selected", "true"); } $fragment.appendChild($tag); }); return rxjs.of({ $list: renderTaglist($fragment), response }); }), // feature: tag mouse hover effect rxjs.tap(({ $list, response }) => { if (isMobile) return; else if (!response) return; $list.childNodes.forEach(($tag) => { if ($tag.getAttribute("aria-selected") === "true") return; const tagname = $tag.innerText.trim(); const paths = []; for (const path in response) { const form = response[path].find(({ id }) => id === "tags"); if (!form) continue; const tags = form.value.split(",").map((val) => val.trim()); if (tags.indexOf(tagname) === -1) continue; paths.push(path); } $tag.onmouseenter = () => { const $things = document.querySelectorAll(".component_thing"); $things.forEach(($thing) => { const thingpath = $thing.getAttribute("data-path"); for (let i=0; i $things.forEach(($thing) => $thing.classList.remove("hover")); }; }); }), )); } ================================================ FILE: public/assets/components/skeleton.js ================================================ export function generateSkeleton(n) { const tmpl = "
    "; let html = ""; for (let i = 0; i < n; i++) { html += tmpl; } return html; } ================================================ FILE: public/assets/css/designsystem.css ================================================ @import url("./designsystem_input.css"); @import url("./designsystem_textarea.css"); @import url("./designsystem_inputgroup.css"); @import url("./designsystem_checkbox.css"); @import url("./designsystem_formbuilder.css"); @import url("./designsystem_button.css"); @import url("./designsystem_icon.css"); @import url("./designsystem_container.css"); @import url("./designsystem_box.css"); @import url("./designsystem_darkmode.css"); @import url("./designsystem_skeleton.css"); @import url("./designsystem_utils.css"); @import url("./designsystem_alert.css"); :root { --bg-color: #f9f9fa; --color: #3e4041; --emphasis: #466372; --primary: #9AD1ED; --emphasis-primary: #c5e2f1; --emphasis-secondary: #466372; --light: #57595A; --super-light: #fafafa; --error: #f26d6d; --success: #63d9b1; --dark: #24272a; --surface: #525659; --border: rgba(198,200,204,0.25); } html { font-family: system-ui, sans-serif; -webkit-text-size-adjust:100%; font-smoothing: antialiased; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { overflow: hidden; background: var(--bg-color); color: var(--color); } body, html { height: 100%; margin: 0; } a { color: inherit; text-decoration: none; } select { -webkit-appearance: none; -moz-appearance: none; } select:-moz-focusring { color: transparent; outline: none; border: none; } select::-ms-expand { display: none; } button::-moz-focus-inner { border: 0; } input, textarea, select { transition: border 0.2s; outline: none; } button:focus, a:focus, a:active, button::-moz-focus-inner, input[type="reset"]::-moz-focus-inner, input[type="button"]::-moz-focus-inner, input[type="submit"]::-moz-focus-inner, select::-moz-focus-inner, input[type="file"]>input[type="button"]::-moz-focus-inner { outline: none !important; } select:-moz-focusring { color: transparent; text-shadow: 0 0 0 #000; } #app { height: 100%; } .connect-form input:hover, .connect-form textarea:hover, .connect-form input:focus, .connect-form textarea:focus { border-color: rgb(154, 209, 237)!important; } .drag-drop { z-index: 2; } .drag-drop.dragging>div { background: rgba(0, 0, 0, 0.1); } /* CONNECTION FORM */ .login-form button.active { box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.20); } ::-webkit-scrollbar { height: 4px; width: 4px } ::-webkit-scrollbar-track { background: rgba(0, 0, 0, .1) } ::-webkit-scrollbar-thumb { border-radius: 2px; background: rgba(0, 0, 0, .2); } .scroll-y { flex: 1; overflow-y: scroll; overflow-x: hidden; scrollbar-3dlight-color: #7d7e94; scrollbar-arrow-color: #c1c1d1; scrollbar-darkshadow-color: #2d2c4d; scrollbar-face-color: rgba(0, 0, 0, .1); scrollbar-highlight-color: #7d7e94; scrollbar-shadow-color: #2d2c4d; scrollbar-track-color: rgba(0, 0, 0, .1); } /* skip link */ body > a[aria-role="navigation"] { position: absolute; left: -999px; top: auto; width: 1px; height: 1px; overflow: hidden; } body > a[aria-role="navigation"]:focus-visible { position: fixed; top: 8px; width: fit-content; height: auto; border-radius: 5px; padding: 5px 10px; outline: 2px solid; z-index: 10; left: 50%; background: white } ================================================ FILE: public/assets/css/designsystem_alert.css ================================================ .alert { background: var(--bg-color); border-radius: 5px; padding: 20px; margin-top: 20px; margin-bottom: 20px; border: 1px solid rgba(0,0,0,0.05); } .alert ol, .alert ul { margin: 5px 0; padding: 0 20px; } .alert.success{ background: var(--success); } .alert.error{ background: var(--error); color: var(--bg-color); } .alert img{ max-width: 100%; border-radius: 5px; border: 10px solid white; box-sizing: border-box; margin-top: 5px; } ================================================ FILE: public/assets/css/designsystem_box.css ================================================ .box{ padding: 10px; cursor: pointer; margin: 3px 0; overflow: hidden; position: relative; } ================================================ FILE: public/assets/css/designsystem_button.css ================================================ button { border: none; margin: 0; padding: 6px; display: inline-block; cursor: pointer; font-size: inherit; border-radius: 3px; color: rgba(0,0,0,0.6); background: inherit; line-height: 1rem; } button.primary { background: var(--primary); color: white; } button.emphasis { background: var(--emphasis); color: white; } button.dark { background: var(--dark); color: white; } button.light { background: #e2e2e2; color: var(--dark); } button.large { width: 100%; } button[disabled] { opacity: 0.9; } .touch-no button.dark:hover, .touch-no button.emphasis:hover, .touch-no button.primary:hover { filter: brightness(95%); transition: 0.2s ease all; } ================================================ FILE: public/assets/css/designsystem_checkbox.css ================================================ .component_checkbox { display: inline-block; padding: 0; height: 25px; width: 25px; vertical-align: middle; position: relative; } .component_checkbox input[type="checkbox"], .component_checkbox input[type="radio"] { position: absolute; inset: 0px; opacity: 0; } .component_checkbox input[type="checkbox"]:focus ~ span, .component_checkbox input[type="radio"]:focus ~ span { border: 2px solid rgba(0, 0, 0, 0.05); } .component_checkbox input[type="checkbox"]:focus:checked ~ span, .component_checkbox input[type="radio"]:focus:checked ~ span { border: 2px solid rgba(0, 0, 0, 0.05); } .component_checkbox input[type="checkbox"]:focus-visible ~ span { outline: 2px solid; } .component_checkbox input[type="checkbox"]:checked ~ span, .component_checkbox input[type="radio"]:checked ~ span { color: white; background: var(--primary) url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+DQo8c3ZnIHdpZHRoPSIxMiIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgMTIgOSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiAgPHBhdGggZD0iTTQuNTc1IDguOTc3cy0uNDA0LS4wMDctLjUzNi0uMTY1TC4wNTcgNS42NGwuODI5LTEuMjI3TDQuNDcgNy4yNjggMTAuOTIxLjA4NmwuOTIzIDEuMTAzLTYuODYzIDcuNjRjLS4xMzQtLjAwMy0uNDA2LjE0OC0uNDA2LjE0OHoiIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPg0KPC9zdmc+) 50% 40% no-repeat; border: 2px solid var(--primary); } .component_checkbox span { border-radius: 3px; position: absolute; left: 0; top: 3px; width: 12px; height: 12px; background-color: rgba(198,200,204,0.3); border: 2px solid rgba(198,200,204,0.3); pointer-events: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .component_checkbox input[type="radio"] + span { border-radius: 50%; } ================================================ FILE: public/assets/css/designsystem_container.css ================================================ .component_container{ width: 95%; max-width: 800px; margin: 0 auto; padding: 10px; } ================================================ FILE: public/assets/css/designsystem_darkmode.css ================================================ body.dark-mode { --bg-color: #1e1f22; --color: #f1f1f1; --light: #dfe1e5; --border: #303438; --dark: #2b2d30; } body.dark-mode input { color: var(--bg-color); } ================================================ FILE: public/assets/css/designsystem_dropdown.css ================================================ .component_dropdown { position: relative; } .component_dropdown .dropdown_container { display: none; position: absolute; isolation: isolate; right: 0; margin-top: 10px; z-index: 3; backdrop-filter: brightness(0.8) blur(10px); } body:not(.dark-mode) .component_dropdown .dropdown_container { box-shadow: -1px -1px 5px rgba(255, 255, 255, 0.5) inset; } .component_dropdown .dropdown_container:before { content: ' '; position: absolute; right: 9px; top: -5px; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 5px solid rgba(0, 0, 0, 0.7); } .component_dropdown .dropdown_container ul { cursor: pointer; margin: 0; list-style-type: none; box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; color: var(--bg-color); background: rgba(0,0,0,0.65); border-radius: 3px; padding: 3px; font-size: 0.92em; } .component_dropdown .dropdown_container ul li { justify-content: space-between; display: flex; width: 150px; padding: 7px 7px 7px 10px; } .dark-mode .component_dropdown .dropdown_container ul { color: var(--color); background: rgba(43, 45, 48, 0.9); } .dark-mode .component_dropdown .dropdown_container:before { border-bottom-color: rgba(43, 45, 48, 0.9); } .component_dropdown.active .dropdown_container { display: block; } .component_dropdown.active .dropdown_container li { transition: background 0.1s ease-out; } .component_dropdown.active .dropdown_container li img.component_icon { border: 2px solid transparent; height: 15px; width: 15px; box-sizing: border-box; align-self: center; opacity: 0.7; } .component_dropdown.active .dropdown_container li img.component_icon.inverted { transform: rotate(180deg); } .component_dropdown.active .dropdown_container li:hover { background: rgba(198,200,204,0.25); border-radius: 3px; } ================================================ FILE: public/assets/css/designsystem_formbuilder.css ================================================ .formbuilder .description { margin-top: -2px; margin-bottom: 10px; opacity: 0.7; font-size: 0.9em; line-height: 1em; text-align: justify; } .formbuilder a { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: rgba(0, 0, 0, 0.3); } .formbuilder input::placeholder, .formbuilder textarea::placeholder { opacity: 0.6; } .formbuilder label.input_type_hidden { display: none; } .formbuilder fieldset legend { text-transform: uppercase; color: var(--light); opacity: 0.9; font-size: 1.1em; padding: 0 15px; } .formbuilder .fileupload-image img { height: 150px; width: 100%; object-fit: contain; background: var(--bg-color); border-radius: 2px; box-sizing: border-box; } .formbuilder > label > img { max-height: 110px; width: 100%; } .formbuilder .fileupload-image object { background: var(--bg-color); height: 300px; width: 100%; } .formbuilder .formbuilder_password { display: flex; } .formbuilder .formbuilder_password img.component_icon { height: 19px; border: none; cursor: pointer; padding: 5px 5px; border-bottom: 2px solid rgba(70, 99, 114, 0.1); transition: border-color 0.2s ease-out; } .formbuilder .banner { background: var(--bg-color); border: 2px solid rgba(0, 0, 0, 0.05); border-radius: 3px; margin-bottom: 20px; padding: 10px; } ================================================ FILE: public/assets/css/designsystem_icon.css ================================================ .component_icon { vertical-align: bottom; max-height: 100%; } ================================================ FILE: public/assets/css/designsystem_input.css ================================================ .component_input, .component_select { background: inherit; border: none; border-radius: 0; width: 100%; display: inline-block; font-family: "San Francisco","Roboto","Arial",sans-serif; -webkit-text-size-adjust: 100%; font-size: 16px; padding: 5px 0px 5px 0px; margin: 0 0 8px 0; outline: none; box-sizing: border-box; color: inherit; border-bottom: 2px solid rgba(70, 99, 114, 0.1); transition: border-color 0.2s ease-out; height: 31px; } .component_input:focus, .component_select:focus, .component_input:focus ~ component-icon img { border-color: var(--emphasis-primary)!important; } input.component_input[disabled], textarea.component_textarea[disabled] { cursor: pointer; opacity: 0.8; } .component_input[type="file"] { border: none; margin-bottom: 0; } .component_input[type="file"]::file-selector-button { border-radius: 3px; background-color: transparent; border: 2px solid var(--border); display: block; transition: 0.1s all; color: inherit; cursor: pointer; } .component_input[type="file"]::file-selector-button:hover { background-color: var(--border); border-color: var(--border); } .component_input:-webkit-autofill, .component_input:-webkit-autofill:focus { transition: background-color 600000s 0s, color 600000s 0s; } .component_input[data-autocompleted] { background-color: transparent !important; } ================================================ FILE: public/assets/css/designsystem_inputgroup.css ================================================ .input_group { display: flex; background: #fff; border-radius: 3px; box-shadow: 2px 2px 10px rgba(0,0,0,.1); } .input_group input { padding: 15px 20px; border-bottom: 0; margin: 0; border-radius: 3px; height: inherit !important; } .input_group button { width: inherit; padding: 0 10px; } .input_group button .component_icon { height: 25px; } .input_group.error { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; transform: translate3d(0, 0, 0); backface-visibility: hidden; perspective: 1000px; } @keyframes shake { 10%, 90% { transform: translate3d(-1px, 0, 0); } 20%, 80% { transform: translate3d(2px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } 40%, 60% { transform: translate3d(4px, 0, 0); } } ================================================ FILE: public/assets/css/designsystem_skeleton.css ================================================ .component_skeleton { width: 100%; height: 30px; background: linear-gradient(110deg, rgba(0,0,0,0.02) 8%, rgba(0,0,0,0.04) 18%, rgba(0,0,0,0.02) 33%); border-radius: 5px; background-size: 200% 100%; animation: 3s skeleton_shine linear infinite; margin-bottom: 15px; } @keyframes skeleton_shine { to { background-position-x: -200%; } } ================================================ FILE: public/assets/css/designsystem_textarea.css ================================================ @font-face { font-family: 'text-security-disc'; src: url("textarea.woff") format("woff2"); } .component_textarea { background: inherit; border: none; border-radius: 0; width: 100%; display: inline-block; font-family: "San Francisco","Roboto","Arial",sans-serif; -webkit-text-size-adjust: 100%; font-size: 16px; padding: 5px 0px 5px 0px; margin: 0 0 8px 0; outline: none; box-sizing: border-box; color: inherit; vertical-align: top; min-width: 100%; max-width: 100%; min-height: 30px; line-height: 18px; border-bottom: 2px solid rgba(70, 99, 114, 0.1); transition: border-color 0.2s ease-out; } .component_textarea[rows="1"] { max-height: 30px; } .component_textarea[name="password"] { resize: none; -webkit-text-security: disc !important; -webkit-touch-callout: none; user-select: none; } .component_textarea[name="password"].firefox.hasText { font-family: 'text-security-disc'; } .component_textarea:focus { border-color: var(--emphasis-primary); } ================================================ FILE: public/assets/css/designsystem_utils.css ================================================ .pointer { cursor: pointer; } .hidden{ position:absolute!important; left:-10000px!important; top:auto!important; width:1px!important; height:1px!important; overflow:hidden!important; } .no-select { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; } .center{ text-align: center; } .full-width { width: 100%; } .flex { display: flex; } .ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } a.wavy { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: rgba(0, 0, 0, 0.3); } ================================================ FILE: public/assets/embed/filestash-image.js ================================================ class FilestashImage extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.iframe = document.createElement("iframe"); this.iframe.setAttribute("style", "width: 100%; height: 100%; border: none; display: block;"); this.iframe.setAttribute("sandbox", "allow-downloads allow-scripts allow-presentation allow-forms allow-same-origin"); this.iframe.setAttribute("referrerpolicy", "origin"); this.shadowRoot.appendChild(this.iframe); this.debounce = null; } static get observedAttributes() { return ["name", "src"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === null) return; if (oldValue !== newValue) { clearTimeout(this.debounce); this.debounce = setTimeout(() => { this.iframe.contentWindow.postMessage({ type: "refresh", payload: { name: this.getAttribute("name"), src: this.getAttribute("src") }, }, "*"); }, 100); } } disconnectedCallback() { clearTimeout(this.debounce); this.debounce = null; } connectedCallback() { const src = this.getAttribute("src") || ""; const name = this.getAttribute("name") || "main.dat"; this.style.display = "inline-block"; this.iframe.srcdoc = `
    `; } } customElements.define("filestash-image", FilestashImage); ================================================ FILE: public/assets/embed/filestash-map.js ================================================ class FilestashMap extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.iframe = document.createElement("iframe"); this.iframe.setAttribute("style", "width: 100%; height: 100%; border: none; display: block;"); this.iframe.setAttribute("sandbox", "allow-downloads allow-scripts allow-presentation"); this.shadowRoot.appendChild(this.iframe); this.debounce = null; } static get observedAttributes() { return ["name", "src"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === null) return; if (oldValue !== newValue) { clearTimeout(this.debounce); this.debounce = setTimeout(() => { this.iframe.contentWindow.postMessage({ type: "refresh", payload: { name: this.getAttribute("name"), src: this.getAttribute("src") }, }, "*"); }, 100); } } disconnectedCallback() { clearTimeout(this.debounce); this.debounce = null; } connectedCallback() { const src = this.getAttribute("src") || ""; const name = this.getAttribute("name") || "main.dat"; this.style.display = "inline-block"; this.iframe.srcdoc = `
    `; } } customElements.define("filestash-map", FilestashMap); ================================================ FILE: public/assets/embed/filestash-table.js ================================================ class FilestashTable extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.iframe = document.createElement("iframe"); this.iframe.setAttribute("style", "width: 100%; height: 100%; border: none; display: block;"); this.iframe.setAttribute("sandbox", "allow-downloads allow-same-origin allow-scripts allow-presentation allow-forms"); this.shadowRoot.appendChild(this.iframe); this.debounce = null; } static get observedAttributes() { return ["name", "src"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === null) return; if (oldValue !== newValue) { clearTimeout(this.debounce); this.debounce = setTimeout(() => { this.iframe.contentWindow.postMessage({ type: "refresh", payload: { name: this.getAttribute("name"), src: this.getAttribute("src") }, }, "*"); }, 100); } } disconnectedCallback() { clearTimeout(this.debounce); this.debounce = null; } connectedCallback() { const src = this.getAttribute("src") || ""; const name = this.getAttribute("name") || "main.dat"; this.style.display = "inline-block"; this.iframe.srcdoc = `
    `; } } customElements.define("filestash-table", FilestashTable); ================================================ FILE: public/assets/helpers/loader.d.ts ================================================ export function loadScript(url: string): Promise; export function loadJS(baseURL: string, path: string, opts?: object): Promise; export function loadCSS(baseURL: string, ...arrayOfFilenames: string[]): Promise; export function loadCSSInline(baseURL: string, filename: string): Promise; export function CSS(baseURL: string, ...arrayOfFilenames: string[]): Promise; export function init(): Promise; ================================================ FILE: public/assets/helpers/loader.js ================================================ import { onDestroy } from "../lib/skeleton/index.js"; export async function loadJS(baseURL, path, opts = {}) { const $script = document.createElement("script"); const link = new URL(path, baseURL); $script.setAttribute("src", link.toString()); for (const key in opts) { $script.setAttribute(key, opts[key]); } if (document.head.querySelector(`[src="${link.toString()}"]`)) return Promise.resolve(); document.head.appendChild($script); return new Promise((done) => { $script.onload = () => done(); $script.onerror = () => done(); }); } export async function loadCSS(baseURL, path) { const $style = document.createElement("link"); const link = new URL(path, baseURL); $style.setAttribute("href", link.toString()); $style.setAttribute("rel", "stylesheet"); if (document.head.querySelector(`[href="${link.toString()}"]`)) return Promise.resolve(); else if (document.head.querySelector(`style[id="${link.toString()}"]`)) return Promise.resolve(); document.head.appendChild($style); return new Promise((done) => { $style.onload = done; $style.onerror = done; }); } export async function loadWorker(baseURL, path, opts = {}) { const { iframe = true } = opts; let url = new URL(path, baseURL); if (iframe) { // eg: fix issue coming from loading worker through an iframe like this nasty: // SecurityError: Failed to construct 'Worker': Script at 'xxx.worker.js' cannot be accessed from origin 'null'. // at Module.default (loader_dbf.js:9:20) // at application_table.js:58:50 let code = await fetch(new URL(path, baseURL)).then((res) => res.text()); const importPathRE = new RegExp("import\\s+(?:[^'\";]*?\\s+from\\s+)?[\"']([^\"']+)[\"']", "gm"); code = code.replaceAll("import.meta.url", `"${baseURL}"`); code.matchAll(importPathRE).forEach(([_, path]) => { code = code.replaceAll(path, new URL(path, baseURL)); }); url = URL.createObjectURL(new Blob([code], { type: "application/javascript" })); onDestroy(() => URL.revokeObjectURL(url)); } const worker = new Worker(url, { type: "module" }); onDestroy(() => worker.terminate()); await new Promise((resolve) => worker.addEventListener( "message", resolve, { once: true }, )); return worker; } export async function loadCSSInline(baseURL, filename) { const res = await fetch(new URL(filename, baseURL).pathname, { cache: "force-cache", }); if (res.status !== 200) return `/* ERROR: ${res.status} */`; else if (!res.headers.get("Content-Type")?.startsWith("text/css")) return `/* ERROR: wrong type, got "${res.headers?.get("Content-Type")}"*/`; return await res.text(); } export async function CSS(baseURL, ...arrayOfFilenames) { const sheets = await Promise.all(arrayOfFilenames.map((filename) => loadCSSInline(baseURL, filename))); return sheets.join("\n\n"); } ================================================ FILE: public/assets/helpers/loader_wasm.js ================================================ const DEBUG = false; const log = (msg) => DEBUG && console.log(msg); const wasmCache = new Map(); let wasiInstance; export function setWasiInstance(instance) { wasiInstance = instance; } export default async function(baseURL, path, opts = {}) { const url = new URL(path, baseURL); let wasm; if (wasmCache.has(url.pathname)) wasm = wasmCache.get(url.pathname); else { wasm = await WebAssembly.instantiateStreaming( fetch(url), { wasi_snapshot_preview1: { ...wasi, }, env: { ...wasi, ...syscalls, ...javascripts, }, }, ); wasmCache.set(url.pathname, wasm); } setWasiInstance(wasm.instance); return wasm; } const FS = {}; let nextFd = 0; writeFS(new Uint8Array(0), "/dev/stdin"); writeFS(new Uint8Array(1024*8), "/dev/stdout"); writeFS(new Uint8Array(1024*8), "/dev/stderr"); if (nextFd !== 3) throw new Error("Unexpected next fd"); export function writeFS(buffer, path = "") { if (!(buffer instanceof Uint8Array)) throw new Error("can only write Uint8Array"); FS[nextFd] = { buffer, position: 0, path, }; nextFd += 1; return nextFd - 1; } export function readFS(fd) { const file = FS[fd]; if (!file) throw new Error("file does not exist"); let end = file.buffer.length; while (end > 0 && file.buffer[end - 1] === 0) end--; return file.buffer.subarray(0, end); } export function clearFS() { Object.keys(FS).forEach((key) => { if (key > 2) delete FS[key]; }); nextFd = 3; } function getFile(path) { const allFds = Object.keys(FS); for (let i=allFds.length - 1; i>0; i--) { if (FS[allFds[i]].path === path) { log(` fileopen fd=${i} path=${path}`); return FS[allFds[i]]; } } throw new Error(`cannot get file "${path}"`); } export const syscalls = { __syscall_fcntl64: (fd, cmd, varargs) => { console.log(`Stubbed __syscall_fcntl64 called with fd=${fd}, cmd=${cmd}, varargs=${varargs}`); return -1; }, __syscall_ioctl: (fd, op, varargs) => { switch (op) { case 21523: break; default: console.log(`Stubbed __syscall_ioctl called with fd=${fd}, request=${op}, varargs=${varargs}`); } return 0; }, __syscall_unlinkat: (fd) => { console.log(`Stubbed __syscall_unlinkat called with fd=${fd}`); return -1; }, __syscall_rmdir: (fd) => { console.log(`Stubbed __syscall_rmdir called with fd=${fd}`); return -1; }, __syscall_newfstatat: (pathPtr, bufPtr) => { console.log(`Stubbed __syscall_stat64 called with pathPtr=${pathPtr}, bufPtr=${bufPtr}`); return 0; // Return 0 for a successful call }, __syscall_lstat64: () => { console.log(`Stubbed __syscall_lstat64 called`); return -1; }, __assert_fail: () => { console.log(`Stubbed __assert_fail called`); return -1; }, __syscall_ftruncate64: () => { console.log(`Stubbed __syscall_ftruncate64`); return -1; }, __syscall_renameat: () => { console.log(`Stubbed __syscall_renameat`); return -1; }, }; const javascripts = { _tzset_js: () => { console.log("Initializing time zone settings (stub)"); }, _abort_js: () => { console.error("WebAssembly module called _abort_js!"); throw new Error("_abort_js was called"); }, _mktime_js: () => { console.error("WebAssembly module called _abort_js!"); throw new Error("_abort_js was called"); }, _localtime_js: () => { console.error("WebAssembly module called _localtime_js!"); throw new Error("_localtime_js was called"); }, emscripten_date_now: () => { console.error("WebAssembly module called emscripten_date_now!"); throw new Error("_localtime_js was called"); }, emscripten_get_now: () => { console.error("WebAssembly module called emscripten_get_now!"); throw new Error("_localtime_js was called"); }, emscripten_errn: () => { console.error("WebAssembly module called emscripten_errn!"); throw new Error("_errn was called"); }, HaveOffsetConverter: () => { console.error("WebAssembly module called HaveOffsetConverter!"); throw new Error("HaveOffsetConverter was called"); }, emscripten_pc_get_function: () => { console.error("WebAssembly module called emscripten_pc_get_function!"); throw new Error("emscripten_pc_get_function was called"); }, emscripten_asm_const_int: () => { console.error("WebAssembly module called emscripten_asm_const_int!"); throw new Error("emscripten_asm_const_int was called"); }, emscripten_stack_snapshot: () => { console.error("WebAssembly module called emscripten_stack_snapshot!"); throw new Error("emscripten_stack_snapshot was called"); }, emscripten_stack_unwind_buffer: () => { console.error("WebAssembly module called emscripten_stack_unwind_buffer!"); throw new Error("emscripten_stack_unwind_buffer was called"); }, emscripten_get_heap_max: () => { console.error("WebAssembly module called emscripten_get_heap_max!"); throw new Error("emscripten_get_heap_max was called"); }, _timegm_js: () => { return 0; }, _gmtime_js: () => { return null; }, _munmap_js: () => { console.error("WebAssembly module called _munmap_js!"); throw new Error("_munmap_js was called"); }, _mmap_js: () => { console.error("WebAssembly module called _mmap_js!"); throw new Error("_mmap_js was called"); } }; export const wasi = { fd_write(fd, iovs, iovs_len, nwritten) { if (!FS[fd]) throw new Error(`File descriptor ${fd} does not exist.`); const ioVecArray = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2); const memory = new Uint8Array(wasiInstance.exports.memory.buffer); let totalBytesWritten = 0; for (let i = 0; i < iovs_len * 2; i += 2) { const offset = ioVecArray[i]; const length = ioVecArray[i + 1]; while (FS[fd].buffer.byteLength - FS[fd].position < length) { const newBuffer = new Uint8Array(FS[fd].buffer.byteLength + 1024 * 1024 * 5); newBuffer.set(FS[fd].buffer, 0); FS[fd].buffer = newBuffer; } FS[fd].buffer.set( memory.subarray(offset, offset + length), FS[fd].position ); FS[fd].position += length; totalBytesWritten += length; } new DataView(wasiInstance.exports.memory.buffer).setUint32( nwritten, totalBytesWritten, true, ); if (fd === 1 || fd === 2) { FS[fd] = { buffer: new Uint8Array(readFS(fd)), position: 0, path: FS[fd].path || "", }; } else { log(`wasi::fd_write fd=${fd}`); } return 0; }, fd_read(fd, iovs, iovs_len, nread) { const file = FS[fd]; if (!file) { console.error(`Invalid fd: ${fd}`); return -1; } const ioVecArray = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2); const memory = new Uint8Array(wasiInstance.exports.memory.buffer); let totalBytesRead = 0; for (let i = 0; i < iovs_len * 2; i += 2) { const offset = ioVecArray[i]; const length = ioVecArray[i + 1] || 0; const bytesToRead = Math.min( length, file.buffer.length - file.position, ); if (bytesToRead < 0) { break; } memory.set( file.buffer.subarray(file.position, file.position + bytesToRead), offset, ); file.position += bytesToRead; totalBytesRead += bytesToRead; } log(`wasi::fd_read fd=${fd} iovs_len=${iovs_len} totalBytesRead=${totalBytesRead}`); new DataView(wasiInstance.exports.memory.buffer).setUint32( nread, totalBytesRead, true, ); return 0; }, fd_pread(fd, iovs, iovs_len, offset_lo, offset_hi, nread) { const file = FS[fd]; if (!file) { console.error(`Invalid fd: ${fd}`); return -1; } const start = (offset_hi >>> 0) * 0x100000000 + (offset_lo >>> 0); const ioVec = new Uint32Array(wasiInstance.exports.memory.buffer, iovs, iovs_len * 2); const mem = new Uint8Array(wasiInstance.exports.memory.buffer); let total = 0; for (let i = 0; i < iovs_len * 2; i += 2) { const dst = ioVec[i]; const len = ioVec[i + 1] || 0; const avail = Math.max( 0, Math.min(len, file.buffer.length - (start + total)), ); if (avail === 0) { console.log(`len=[${len}] buffLength=[${file.buffer.length}] start=[${start}] total=[${total}]`); break; } mem.set( file.buffer.subarray(start + total, start + total + avail), dst ); total += avail; } new DataView(wasiInstance.exports.memory.buffer).setUint32(nread, total, true); return 0; }, fd_seek(fd, offsetBigInt, _, whence) { log(`wasi::fd_seek fd=${fd} offset=${offsetBigInt} whence=${whence}`); const offset = Number(offsetBigInt); const file = FS[fd]; if (!file) { console.error(`Invalid FD: ${fd}`); return -1; } switch (whence) { case 0: // SEEK_SET file.position = offset; break; case 1: // SEEK_CUR file.position += offset; break; case 2: // SEEK_END file.position = file.buffer.length + offset; break; default: console.log(`fd_seek called with fd=${fd}, offset=${offset}, position=${file.position} whence=${whence}`); const error = new Error("fd_seek trace"); console.log("Invalid whence", whence, error.stack); return -1; } return 0; }, fd_close(fd) { if (!FS[fd]) { console.error(`Invalid FD: ${fd}`); return -1; } return 0; }, _emscripten_memcpy_js(dest, src, num) { const memory = new Uint8Array(wasiInstance.exports.memory.buffer); memory.set(memory.subarray(src, src + num), dest); return dest; }, emscripten_resize_heap(requested) { console.log("Stubbed emscripten_resize_heap called"); throw new Error("Heap resize not supported"); }, environ_sizes_get() { console.log(`Stubbed environ_sizes_get called`); return 0; }, environ_get() { console.log(`Stubbed environ_get called`); return 0; }, clock_time_get() { console.log(`Stubbed clock_time_get called`); return -1; }, __syscall_openat(dirFd, pathPtr, flags, mode) { const memory = new Uint8Array(wasiInstance.exports.memory.buffer); let path = ""; for (let i = pathPtr; memory[i] !== 0; i++) { path += String.fromCharCode(memory[i]); } const allFds = Object.keys(FS); for (let i=allFds.length - 1; i>0; i--) { if (FS[allFds[i]].path === path) { log(` syscall::openat::result fd=${i} path=${path}`); return i; } } throw new Error("Unknown file for __syscall_openat"); }, __syscall_stat64(pathPtr, buf) { log(` syscall::stat64 pathPtr=${pathPtr}, bufPtr=${buf}`); const memory = new Uint8Array(wasiInstance.exports.memory.buffer); let path = ""; for (let i = pathPtr; memory[i] !== 0; i++) { path += String.fromCharCode(memory[i]); } const file = getFile(path); const HEAP32 = new Int32Array(wasiInstance.exports.memory.buffer); const HEAPU32 = new Uint32Array(wasiInstance.exports.memory.buffer); const stat = { dev: 1, ino: 42, mode: 0o100644, nlink: 1, uid: 1000, gid: 1000, rdev: 0, size: file.buffer.byteLength, blksize: 4096, blocks: 256, atime: new Date(), mtime: new Date(), ctime: new Date(), }; HEAP32[(buf >> 2)] = stat.dev; HEAP32[((buf + 4) >> 2)] = stat.mode; HEAPU32[((buf + 8) >> 2)] = stat.nlink; HEAP32[((buf + 12) >> 2)] = stat.uid; HEAP32[((buf + 16) >> 2)] = stat.gid; HEAP32[((buf + 20) >> 2)] = stat.rdev; HEAP32[((buf + 24) >> 2)] = stat.size & 0xFFFFFFFF; HEAP32[((buf + 28) >> 2)] = Math.floor(stat.size / 4294967296); HEAP32[((buf + 32) >> 2)] = stat.blksize; HEAP32[((buf + 36) >> 2)] = stat.blocks; HEAP32[((buf + 40) >> 2)] = Math.floor(stat.atime.getTime() / 1000); HEAP32[((buf + 44) >> 2)] = 0; HEAP32[((buf + 48) >> 2)] = (stat.atime.getTime() % 1000) * 1e6; HEAP32[((buf + 56) >> 2)] = Math.floor(stat.mtime.getTime() / 1000); HEAP32[((buf + 60) >> 2)] = 0; HEAP32[((buf + 64) >> 2)] = (stat.mtime.getTime() % 1000) * 1e6; HEAP32[((buf + 72) >> 2)] = Math.floor(stat.ctime.getTime() / 1000); HEAP32[((buf + 76) >> 2)] = 0; HEAP32[((buf + 80) >> 2)] = (stat.ctime.getTime() % 1000) * 1e6; HEAP32[((buf + 88) >> 2)] = stat.ino & 0xFFFFFFFF; HEAP32[((buf + 92) >> 2)] = Math.floor(stat.ino / 4294967296); return 0; }, __cxa_throw(ptr, type, destructor) { console.error(` syscall::cxa_throw ptr=${ptr}, type=${type}, destructor=${destructor}`); throw new Error("WebAssembly exception"); }, random_get() { console.log(`Stubbed random_get called`); return -1; }, proc_exit() { console.log(`Stubbed proc_exit called`); return -1; }, __syscall_fstat64(fd, buf) { log(` syscall::fstat64 fd=${fd}, buf=${buf}`); const file = FS[fd]; if (!file) return -1; // EBADF const size = file.buffer.byteLength >>> 0; // ≤ 4 GB const nowSec = (Date.now() / 1000) | 0; const H32 = new Int32Array(wasiInstance.exports.memory.buffer); /* basic fields */ H32[buf >> 2] = 1; /* st_dev */ H32[(buf+4) >> 2] = 0o100644; /* st_mode */ H32[(buf+8) >> 2] = 1; /* st_nlink */ H32[(buf+12) >> 2] = 1000; /* st_uid */ H32[(buf+16) >> 2] = 1000; /* st_gid */ H32[(buf+20) >> 2] = 0; /* st_rdev */ H32[((buf + 24) >> 2)] = size & 0xFFFFFFFF; H32[((buf + 28) >> 2)] = Math.floor(size / 4294967296); H32[(buf+32) >> 2] = 4096; H32[(buf+36) >> 2] = (size + 511) >> 9; /* st_size lives at byte 40 in Emscripten’s 32-bit stat64 */ H32[(buf+40) >> 2] = size; /* low 32 bits (high word = 0) */ H32[(buf+44) >> 2] = 0; H32[(buf+32) >> 2] = 4096; /* st_blksize */ H32[(buf+36) >> 2] = (size + 511) >> 9; /* st_blocks */ /* atime / mtime / ctime: seconds, nsec = 0 */ for (const off of [48, 56, 64]) { H32[((buf+off) >> 2)] = nowSec; H32[((buf+off+4) >> 2)] = 0; } H32[(buf+72) >> 2] = fd; /* st_ino (low) */ H32[(buf+76) >> 2] = 0; /* st_ino (high) */ return 0; } }; ================================================ FILE: public/assets/helpers/log.d.ts ================================================ export function report(msg: Event|string, err?: any, link?: string, lineNo?: number, columnno?: number); ================================================ FILE: public/assets/helpers/log.js ================================================ import { toHref } from "../lib/skeleton/router.js"; import ajax from "../lib/ajax.js"; export function report(msg, err, link, lineNo, columnNo) { if (window.navigator.onLine === false) return Promise.resolve(); let url = toHref("/report?"); url += "url=" + encodeURIComponent(location.href) + "&"; url += "msg=" + encodeURIComponent(msg) + "&"; url += "from=" + encodeURIComponent(link) + "&"; url += "from.lineNo=" + lineNo + "&"; url += "from.columnNo=" + columnNo; if (err instanceof Error) url += "error=" + encodeURIComponent(err.message) + "&"; return ajax({ url, method: "post" }).toPromise().catch(() => {}); } ================================================ FILE: public/assets/helpers/sdk.js ================================================ // feature detection if we're using Filestash as a standalone app or as an SDK // see: ../index.js export function isSDK() { const importURL = new URL(import.meta.url); return location.origin !== importURL.origin; } export function urlSDK(url) { if (url.startsWith("blob:")) return url; else if (url.startsWith("http://") || url.startsWith("https://")) return url; const importURL = new URL(import.meta.url); if (new RegExp("^/").test(url) === false) { url = "/" + url; } return importURL.origin + url; } ================================================ FILE: public/assets/index.js ================================================ // Want to create an integration via our SDK in your application? You are in the right place! // // How it works you may ask? it's simple: // 1) pick a component to render. Components look like this: // function(render, opts = {}) { // // ... // } // 2) similarly to every framework out there, call the framework bootstrap procedure: // render(Component, $node, args = {}); // // // /***********************************************/ // /* example to render the 3D viewer application */ // /***********************************************/ // import { render } from "/assets/index.js"; // import * as Component from "/assets/pages/viewerpage/application_3d.js"; // // render(Component, document.getElementById("app"), {}); // // // import { createRender } from "./lib/skeleton/index.js"; import { loadCSS } from "./helpers/loader.js"; export function render(module, $app, opts = {}) { assertArgs(module, $app); execute(module, $app, opts); } function assertArgs(module, $app) { if (typeof module.default !== "function") throw new TypeError("Unsupported module - see the SDK documentation"); else if (!($app instanceof Node)) throw new TypeError("Invalid node - see the SDK documentation"); } function execute(module, $app, opts) { const priors = [ import("./boot/ctrl_boot_frontoffice.js"), loadCSS(import.meta.url, "./css/designsystem.css"), ]; if (typeof module.init === "function") priors.push(module.init($app)); return Promise.all(priors) .then(async() => await module.default(createRender($app), opts)) .catch((err) => console.error(err)); } ================================================ FILE: public/assets/lib/ajax.js ================================================ import rxjs, { ajax } from "./rx.js"; import { AjaxError } from "./error.js"; import { isSDK, urlSDK } from "../helpers/sdk.js"; export default function(opts) { if (typeof opts === "string") opts = { url: opts, withCredentials: true }; else if (typeof opts !== "object") throw new Error("unsupported call"); if (!opts.headers) opts.headers = {}; if (!opts.responseType) opts.responseType = "text"; opts.headers["X-Requested-With"] = "XmlHttpRequest"; if (window.BEARER_TOKEN) opts.headers["Authorization"] = `Bearer ${window.BEARER_TOKEN}`; if (opts.url.startsWith("data:")) return rxjs.of({ response: parseDataUrl(opts.url) }); if (isSDK()) { if (["/api/config"].indexOf(opts.url) === -1) opts.withCredentials = false; opts.url = urlSDK(opts.url); } const responseType = opts.responseType === "json" ? "text" : opts.responseType; return ajax({ withCredentials: true, ...opts, responseType, }).pipe( rxjs.map((res) => { if (opts.responseType === "json") { const result = res.xhr.responseText; res.responseJSON = JSON.parse(result); if (res.responseJSON.status !== "ok") { throw new AjaxError("Oups something went wrong", result); } } return res; }), rxjs.catchError( (err) => activePage ? rxjs.throwError(processError(err.xhr, err)) : rxjs.EMPTY ), ); } let activePage = true; window.addEventListener("beforeunload", function() { activePage = false; }); function parseDataUrl(url) { const matches = url.match(/^data:(.*?)(;base64)?,(.*)$/); if (!matches) throw new Error("Invalid Data URL"); const isBase64 = !!matches[2]; const data = matches[3]; if (isBase64) { const binaryString = atob(data); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } const decodedData = decodeURIComponent(data); const encoder = new TextEncoder(); return encoder.encode(decodedData).buffer; } function processError(xhr, err) { let responseText = ""; try { responseText = xhr?.responseText; // InvalidStateError: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'arraybuffer'). } catch (err) {} const message = (function(content) { try { return JSON.parse(content).message; } catch (err) { return Array.from(new Set( content.replace(/<[^>]*>/g, "") .replace(/\n{2,}/, "\n") .trim() .split("\n") )).join(" "); } })(responseText); if (window.navigator.onLine === false) { return new AjaxError("Connection Lost", err, "NO_INTERNET"); } switch (parseInt(xhr?.status)) { case 500: return new AjaxError( message || "Oups something went wrong with our servers", err, "INTERNAL_SERVER_ERROR" ); case 401: return new AjaxError( message || "Authentication error", err, "Unauthorized" ); case 403: return new AjaxError( message || "You can't do that", err, "FORBIDDEN" ); case 413: return new AjaxError( message || "Payload too large", err, "PAYLOAD_TOO_LARGE" ); case 502: return new AjaxError( message || "The destination is acting weird", err, "BAD_GATEWAY" ); case 409: return new AjaxError( message || "Oups you just ran into a conflict", err, "CONFLICT" ); case 0: switch (responseText) { case "": return new AjaxError( "Service unavailable, if the problem persist, contact your administrator", err, "INTERNAL_SERVER_ERROR" ); default: return new AjaxError(responseText, err, "INTERNAL_SERVER_ERROR"); } default: return new AjaxError(message || "Oups something went wrong", err); } } ================================================ FILE: public/assets/lib/animate.d.ts ================================================ type TransitionEnter = { timeEnter: number; enter: AnimationFrames[]; } type TransitionLeave = { timeLeave: number; leave: AnimationFrames[]; }; type AnimationFrames = { transform?: string; opacity?: number; height?: string; width?: string; offset?: number; }; export function transition($node: HTMLElement, opts?: TransitionEnter | TransitionLeave): HTMLElement; export function animate($node: HTMLElement | null, opts: { time: number; keyframes: AnimationFrames[]; easing?: string; fill?: string; onExit?: () => void; onEnter?: () => void; }): Promise<() => void>; export function slideXIn(dist: number): AnimationFrames[]; export function slideXOut(dist: number): AnimationFrames[]; export function opacityIn(): AnimationFrames[]; export function opacityOut(): AnimationFrames[]; export function slideYIn(dist: number): AnimationFrames[]; export function slideYOut(dist: number): AnimationFrames[]; export function zoomIn(size: number): AnimationFrames[]; ================================================ FILE: public/assets/lib/animate.js ================================================ import { onDestroy, nop } from "./skeleton/index.js"; export function transition($node, opts = {}) { const { timeEnter = 250, enter = slideXIn(5), timeLeave = 100, leave = opacityOut() } = opts; animate($node, { time: timeEnter, keyframes: enter }); onDestroy(async() => await animate($node, { time: timeLeave, keyframes: leave })); return $node; } export function animate($node, opts = {}) { const { time = 250, keyframes = opacityIn(), fill = "forwards", easing = "ease", onEnter = nop, onExit = nop, } = opts; if (!$node || typeof $node.animate !== "function" || window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true) { onEnter(); onExit(); return Promise.resolve(); } onEnter(); return new Promise((done) => { const handler = $node.animate(keyframes, { duration: time, fill, easing, }); handler.onfinish = () => { onExit(); done(() => handler.cancel()); }; }); } export const slideXIn = (dist) => ([ { transform: `translateX(${dist}px)`, opacity: 0 }, { transform: "translateX(0)", opacity: 1 } ]); export const slideXOut = (size) => ([ { opacity: 1, transform: "translateX(0)" }, { opacity: 0, transform: `translateX(${size}px)` } ]); export const opacityIn = () => ([ { opacity: 0 }, { opacity: 1 } ]); export const opacityOut = () => ([ { opacity: 1 }, { opacity: 0 } ]); export const slideYIn = (size) => ([ { opacity: 0, transform: `translateY(${size}px)` }, { opacity: 1, transform: "translateY(0)" } ]); export const slideYOut = (size) => ([ { opacity: 1, transform: "translateY(0px)" }, { opacity: 0, transform: `translateY(${size}px)` } ]); export const zoomIn = (size) => ([ { opacity: 0, transform: `scale(${size})` }, { opacity: 1, transform: "scale(1)" } ]); ================================================ FILE: public/assets/lib/assert.js ================================================ export default class assert { /** * @param {*} object * @param {Function} type * @param {string} [msg] * @return {*} * @throws {TypeError} */ static type(object, type, msg) { if (object === undefined) throw new TypeError(msg || "assertion failed - undefined object"); if (!(object instanceof type)) throw new TypeError(msg || `assertion failed - unexpected type for ${JSON.stringify(object)}`); return object; } /** * @param {*} object * @param {string} type * @param {string} [msg] * @return {*} * @throws {TypeError} */ static typeof(object, type, msg) { if (typeof object !== type) throw new TypeError(msg || `assertion failed - unexpected type for ${JSON.stringify(object)}`); // eslint-disable-line valid-typeof return object; } /** * @param {*} object * @param {string} [msg] * @return {*} * @throws {TypeError} */ static truthy(object, msg) { if (!object) throw new TypeError(msg || `assertion failed - object is not truthy`); return object; } /** * @param {string} msg * @throws {TypeError} */ static fail(msg) { throw new TypeError(msg); } } ================================================ FILE: public/assets/lib/chromecast.js ================================================ class ChromecastManager { init() { // TODO: additional rules for setup const src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; if (document.head.querySelector(`script[src="${src}"]`)) return Promise.resolve(); return new Promise((done) => { const script = document.createElement("script"); script.src = src; script.onerror = () => done(); window["__onGCastApiAvailable"] = function(isAvailable) { if (isAvailable) window.cast.framework.CastContext.getInstance().setOptions({ receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, }); done(); }; document.head.appendChild(script); }); } origin() { return location.origin; }; isAvailable() { if (!window.chrome) return false; else if (!window.chrome.cast) return false; return window.chrome.cast.isAvailable; } // createLink(apiPath) { // const target = new URL(this.origin() + apiPath); // const shareID = new window.URL(location.href).searchParams.get("share"); // if (shareID) target.searchParams.append("share", shareID); // return target.toString(); // } createRequest(mediaInfo, authorization) { if (!authorization) Promise.reject(new Error("Invalid account")); // TODO: it would be much much nicer to set the authorization in an HTTP header // but this would require to create a custom web receiver app, setup accounts on // google, etc,... Until that happens, we're setting the authorization within the // url. Once we have that app, the authorisation will come from a customData field // of a chrome.cast.media.LoadRequest const target = new URL(mediaInfo.contentId); target.searchParams.append("authorization", window.Session.authorization); mediaInfo.contentId = target.toString(); return new window.chrome.cast.media.LoadRequest(mediaInfo); } context() { if (!this.isAvailable()) return; return window.cast.framework.CastContext.getInstance(); } session() { const context = this.context(); if (!context) return; return context.getCurrentSession(); } media() { const session = this.session(); if (!session) return; return session.getMediaSession(); } } export default new ChromecastManager(); ================================================ FILE: public/assets/lib/dom.d.ts ================================================ export function qs($node: HTMLElement | DocumentFragment, selector: string); export function qsa($node: HTMLElement | DocumentFragment, selector: string); export function safe(str: string | null): string; ================================================ FILE: public/assets/lib/dom.js ================================================ export function qs($node, selector) { if (!$node) throw new TypeError("undefined node"); const $target = $node.querySelector(selector); if (!$target) throw new DOMException(`undefined node for selector '${selector}'`, "NotFoundError"); return $target; } export function qsa($node, selector) { if (!$node) throw new TypeError("undefined node"); return $node.querySelectorAll(selector); } export function safe(str) { if (typeof str !== "string") return ""; const $div = document.createElement("div"); $div.textContent = str; return ($div.innerHTML || "").replaceAll("\"", """); } ================================================ FILE: public/assets/lib/error.d.ts ================================================ export class AjaxError extends Error { constructor(message: string, err?: any, code?: string); code(): string; err(): any; type(): string; } export class ApplicationError extends Error { constructor(message: string, debug: string); debugMsg: string; type(): string; debug(): string; } ================================================ FILE: public/assets/lib/error.js ================================================ export class AjaxError extends Error { constructor(message, err = null, code = "UNDEFINED_CODE") { super(message); this.name = this.constructor.name; this.errCode = code; this.errOrig = err; } code() { return this.errCode; } err() { return this.errOrig; } type() { return "AjaxError"; } } export class ApplicationError extends Error { constructor(message, debug) { super(message); this.debugMsg = debug; } type() { return "ApplicationError"; } debug() { return this.debugMsg || "N/A"; } } ================================================ FILE: public/assets/lib/form.d.ts ================================================ // type FormOption = { // }; export function mutateForm(formSpec: object, formState: object): object; export function createFormNodes(node: object, opts: object): Promise; export function createForm(node: object, opts: object): Promise; ================================================ FILE: public/assets/lib/form.js ================================================ import { createElement } from "./skeleton/index.js"; import { ApplicationError } from "./error.js"; import { animate } from "./animate.js"; export function mutateForm(formSpec, formState) { Object.keys(formState).forEach((inputName) => { const value = formState[inputName]; const keys = inputName.split("."); let ptr = formSpec; while (keys.length > 1) { const k = keys.shift(); if (!k) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing key"); ptr = ptr[k]; } const key = keys.shift() || ""; if (ptr && ptr[key]) ptr[key].value = (value === "" ? null : value); }); return formSpec; } async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path = [], level = 0 }) { // CASE 0: invalid form spec if (typeof node !== "object") { return [createElement(`
    ERR: node[${typeof node}] path[${path.join(".")}] level[${level}]
    `)]; } const $list = []; for (const key of Object.keys(node)) { if (typeof node[key] !== "object") { $list.push(createElement(`
    ERR: node[${typeof node[key]}] path[${path.join(".")}] level[${level}]
    `)); } // CASE 1: non leaf node else if (typeof node[key].type !== "string") { const $chunk = renderNode({ level, label: key }); const $children = $chunk.querySelector("[data-bind=\"children\"]") || $chunk; $children.removeAttribute("data-bind"); const $nested = await createForm(node[key], { path: path.concat(key), level: level + 1, label: key, renderNode, renderLeaf, renderInput }); $children.appendChild($nested); $list.push($chunk); } // CASE 2: leaf node else { const currentPath = path.concat(key); const $leaf = renderLeaf({ ...node[key], path: currentPath, label: key, }); const $input = await renderInput({ ...node[key], path: currentPath.filter((chunk) => !!chunk) }); const $target = $leaf.querySelector("[data-bind=\"children\"]") || $leaf; // leaf node is either "classic" or can be the target of something that can be toggled // That's how we can hide input elements conditionally for use cases like the log level // settings that will not be visible unless log is first enabled or the advanced section // of the login screen const isAToggleElementItself = typeof node[key].id === "string"; const canToggleOtherElements = node[key].type === "enable" && node[key].target && node[key].target.length > 0; if (!isAToggleElementItself) { $target.removeAttribute("data-bind"); $target.appendChild($input); $list.push($leaf); } if (canToggleOtherElements) { // initialise the dom structure const $container = window.document.createElement("div"); $container.classList.add("advanced_form"); $container.style.setProperty("overflow-x", "hidden"); for (const k of Object.keys(node)) { if (typeof node[k] !== "object") continue; else if (!node[k].id) continue; else if (node[key].target.indexOf(node[k].id) === -1) continue; const $kleaf = renderLeaf({ ...node[k], path: path.concat(k), label: k }); const $kinput = await renderInput({ ...node[k], path: path.concat(k) }); const $ktarget = $kleaf.querySelector("[data-bind=\"children\"]") || $kleaf; $ktarget.removeAttribute("data-bind"); $ktarget.appendChild($kinput); $container.appendChild($kleaf); } $list.push($container); // initial state of the toggle const isToggled = typeof node[key].value === "boolean" ? node[key].value : node[key].default; if (!isToggled) $container.style.setProperty("display", "none"); let clientHeight = null; // this will only be known when the dom is mounted // setup events $input.onchange = async(e) => { $container.style.setProperty("display", "inherit"); if (clientHeight === null) clientHeight = $container.offsetHeight; if (e.target.checked) { animate($container, { time: Math.max(50, Math.min(clientHeight, 150)), keyframes: [{ height: "0" }, { height: `${clientHeight}px` }] }); } else { animate($container, { time: Math.max(25, Math.min(clientHeight, 75)), keyframes: [{ height: `${clientHeight}px` }, { height: "0" }] }); } }; } } } return $list; } export async function createForm(node, opts) { const $container = window.document.createElement("div"); if (!(opts.level >= 1)) $container.classList.add("formbuilder"); const $nodes = await createFormNodes(node, opts); $nodes.forEach(($node) => { $container.appendChild($node); }); return $container; } ================================================ FILE: public/assets/lib/path.js ================================================ export function basename(str, sep = "/") { return str.substr(str.lastIndexOf(sep) + 1); } export function extname(str) { return str.substr(str.lastIndexOf(".") + 1).toLowerCase(); } export function join(baseURL, segment) { const url = new URL(segment, baseURL); return decodeURIComponent(url.pathname + url.hash); } export function forwardURLParams(url, allowed = []) { const link = new URL(window.location.origin + "/" + url); for (const [key, value] of new URLSearchParams(location.search)) { if (allowed.indexOf(key) < 0) continue; else if (link.searchParams.getAll(key).indexOf(value) !== -1) continue; link.searchParams.append(key, value); } return link.pathname.substring(1) + link.search; } ================================================ FILE: public/assets/lib/polyfill.js ================================================ Document.prototype.replaceChildren = replaceChildren; DocumentFragment.prototype.replaceChildren = replaceChildren; Element.prototype.replaceChildren = replaceChildren; function replaceChildren(...new_children) { const { childNodes } = this; while (childNodes.length) { childNodes[0].remove(); } this.append(...new_children); } ================================================ FILE: public/assets/lib/random.d.ts ================================================ export function gid(prefix: string): string; export function randomString(size: number): string; ================================================ FILE: public/assets/lib/random.js ================================================ export function gid(prefix = "") { let id = prefix; id += new Date().getTime().toString(32); id += Math.random().toString(32).replace(/^0\./, ""); return id; } const alphabet = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ]; const alphabet_size = alphabet.length; export function randomString(size = 16) { let str = ""; for (let i=0; i): void; export function applyMutation($node: HTMLElement, ...keys: string[]): typeof tap; export function applyMutations($node: HTMLElement, ...keys: string[]): typeof tap; export function stateMutation($node: HTMLElement, attr: string); export function preventDefault(): typeof tap; export function onClick($node: HTMLElement, opts?: { preventDefault?: boolean }): ReturnType; export function onLoad($node: HTMLElement): void; ================================================ FILE: public/assets/lib/rx.js ================================================ import { onDestroy } from "./skeleton/index.js"; import assert from "./assert.js"; import * as rxjs from "./vendor/rxjs/rxjs.min.js"; import * as ajaxModule from "./vendor/rxjs/rxjs-ajax.min.js"; // https://github.com/ReactiveX/rxjs/issues/4416#issuecomment-620847759 export default rxjs; export const ajax = ajaxModule.ajax; export function effect(obs) { const sub = obs.subscribe(() => {}, (err) => { throw err; }); onDestroy(() => sub.unsubscribe()); return sub.unsubscribe.bind(sub); } const getFn = (obj, arg0, ...args) => { if (arg0 === undefined) return obj; const next = obj[arg0]; return getFn(next.bind ? next.bind(obj) : next, ...args); }; export function applyMutation($node, ...keys) { assert.type($node, HTMLElement); const execute = getFn($node, ...keys); return rxjs.tap((val) => Array.isArray(val) ? execute(...val) : execute(val)); } export function applyMutations($node, ...keys) { assert.type($node, HTMLElement); const execute = getFn($node, ...keys); return rxjs.tap((vals) => vals.forEach((val) => execute(val))); } export function stateMutation($node, attr) { assert.type($node, HTMLElement); return rxjs.tap((val) => $node[attr] = val); } export function preventDefault() { return rxjs.tap((e) => e.preventDefault()); } export function onClick($node, opts = { preventDefault: false }) { const sideE = ($node) => { assert.type($node, HTMLElement); return rxjs.fromEvent($node, "click").pipe( rxjs.tap((e) => (opts.preventDefault === true) && e.preventDefault()), rxjs.map(() => $node) ); }; if ($node instanceof NodeList) return rxjs.merge( ...[...$node].map(($n) => sideE($n)), ); return sideE($node); } export function onLoad($node) { assert.type($node, HTMLElement); return new rxjs.Observable((observer) => { $node.onload = () => { observer.next($node); observer.complete(); }; $node.onerror = (err) => observer.error(err); }); } ================================================ FILE: public/assets/lib/settings.js ================================================ const settings = JSON.parse(window.localStorage.getItem("settings") || "null") || {}; export function settings_get(key, def = null) { if (settings[key] === undefined) { return def; } return settings[key]; } export function settings_put(key, value) { settings[key] = value; setTimeout(() => { window.localStorage.setItem("settings", JSON.stringify(settings)); }, 0); } ================================================ FILE: public/assets/lib/skeleton/index.d.ts ================================================ import { onDestroy } from "./lifecycle"; import { navigate } from "./router"; export default function($root: HTMLElement | null, routes: object, opts: object); export function createElement(str: string): HTMLElement; export function createFragment(str: string): DocumentFragment; export function createRender($parent: HTMLElement | null): (HTMLElement) => void; export function nop(): void export { onDestroy, navigate }; ================================================ FILE: public/assets/lib/skeleton/index.js ================================================ import { init as initRouter, currentRoute } from "./router.js"; import { init as initDOM } from "./lifecycle.js"; export { navigate } from "./router.js"; export { onDestroy } from "./lifecycle.js"; let pageLoader; export default async function($root, routes, opts = {}) { if (!$root) throw new Error("cannot find root element"); window.addEventListener("pagechange", async() => { try { const route = currentRoute(routes, ""); const [ctrl] = await Promise.all([ load(route, { ...opts, $root }), $root.cleanup(), ]); if (typeof ctrl !== "function") throw new Error(`Unknown route for ${route}`); pageLoader = ctrl(createRender($root)); } catch (err) { console.error("skeleton::index.js", err); window.onerror && window.onerror(err.message); } }); await initDOM($root); await opts.beforeStart; await initRouter($root); } async function load(route, opts) { const { spinner = "loading ...", spinnerTime = 200, $root } = opts; let ctrl; if (typeof route === "function") { ctrl = route; } else if (typeof route === "string") { let spinnerID; if (pageLoader && typeof pageLoader === "function") { spinnerID = setTimeout(() => pageLoader(createRender($root)), spinnerTime); } else if (typeof spinner === "string") { spinnerID = setTimeout(() => $root.innerHTML = spinner, spinnerTime); } const module = window.env === "test" ? require("../.." + route) : await import(new URL("../.." + route, import.meta.url)); if (typeof module.init === "function") await module.init(); clearTimeout(spinnerID); if (typeof module.default !== "function") { console.error(module, module.default); throw new Error(`missing default export on ${route}`); } ctrl = module.default; } return ctrl; } export function createElement(str) { const $n = window.document.createElement("div"); $n.innerHTML = str; if (!($n.firstElementChild instanceof HTMLElement)) throw new Error("createElement - unexpected type"); return $n.firstElementChild; } export function createFragment(str) { const $n = window.document.createElement("div"); $n.innerHTML = str; const $doc = document.createDocumentFragment(); for (let i=0; i<$n.children.length; i++) $doc.appendChild($n.children[i].cloneNode(true)); $n.remove(); return $doc; } export function createRender($parent) { if (!($parent instanceof HTMLElement)) throw new Error(`assert failed: createRender on non HTMLElement`); return ($view) => { if ($view instanceof HTMLElement) $parent.replaceChildren($view); else if ($view instanceof DocumentFragment) $parent.replaceChildren($view); else throw new Error(`Unknown view type: ${typeof $view}`); return $parent; }; } export function nop() { Promise.resolve(); } ================================================ FILE: public/assets/lib/skeleton/lifecycle.d.ts ================================================ export function init($root: HTMLElement): Promise; export function onDestroy(fn: Function): Promise; ================================================ FILE: public/assets/lib/skeleton/lifecycle.js ================================================ let _cleanup = []; export async function init($root) { $root.cleanup = () => { const fns = _cleanup.map((fn) => fn($root)); _cleanup = []; return Promise.all(fns); }; } export async function onDestroy(fn) { _cleanup.push(fn); } ================================================ FILE: public/assets/lib/skeleton/router.d.ts ================================================ export function init($root: HTMLElement): Promise; export function navigate(href: string); export function currentRoute(r: object, notFoundRoute: string); export function base(); export function fromHref(h: string): string; export function toHref(h: string): string; ================================================ FILE: public/assets/lib/skeleton/router.js ================================================ const triggerPageChange = () => window.dispatchEvent(new window.Event("pagechange")); const trimPrefix = (value = "", prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value; const _base = window.document.head.querySelector("base")?.getAttribute("href")?.replace(new RegExp("/$"), ""); export const base = () => _base || ""; export const fromHref = (href) => trimPrefix(href, base()); export const toHref = (href) => base() + href; export async function init($root) { window.addEventListener("popstate", triggerPageChange); $root.addEventListener("click", (e) => { if (e.ctrlKey || e.metaKey) return; const href = _getHref(e.target, $root); return !href ? null : e.preventDefault() || navigate(href); }); } export async function navigate(href) { if (typeof window.history.block === "function") { const block = await window.history.block(href); if (block) return; } delete window.history.block; window.history.pushState({ ...JSON.parse(JSON.stringify(window.history)), previous: window.location.pathname, }, "", href); triggerPageChange(); } export function currentRoute(r, notFoundRoute) { const currentRoute = fromHref(window.location.pathname); for (const routeKey in r) { if (new RegExp("^" + routeKey + "$").test(currentRoute)) { return r[routeKey]; } } return r[notFoundRoute] || null; } function _getHref($node, $root) { if ($node.matches("[data-link]")) return $node.getAttribute("href"); if (!$node.parentElement || $node.isSameNode($root)) return null; return _getHref($node.parentElement, $root); } ================================================ FILE: public/assets/lib/store.js ================================================ export function settingsGet(initialValues, prefix = "") { const raw = JSON.parse(localStorage.getItem("settings") || "{}") || {}; const currentSettings = {}; Object.keys(initialValues).forEach((key) => { const settingsKey = prefix ? `${prefix}_${key}` : key; if (settingsKey in raw) currentSettings[key] = raw[settingsKey]; else currentSettings[key] = initialValues[key]; }); return currentSettings; } export function settingsSave(currentValues, prefix = "") { const raw = JSON.parse(localStorage.getItem("settings") || "{}") || {}; Object.keys(currentValues).forEach((key) => { const settingsKey = prefix ? `${prefix}_${key}` : key; raw[settingsKey] = currentValues[key]; }); localStorage.setItem("settings", JSON.stringify(raw)); } ================================================ FILE: public/assets/lib/vendor/bcrypt.js ================================================ // @ts-nocheck // code was adapted from https://github.com/dcodeIO/bcrypt.js, meaning: // - we took the code from a CDN https://cdnjs.cloudflare.com/ajax/libs/bcryptjs/2.2.0/bcrypt.js // - remove the amd,commonJS stuff on the top of the file // - replaced "global" with "window" /** * bcrypt namespace. * @type {Object.} */ var bcrypt = {}; /** * The random implementation to use as a fallback. * @type {?function(number):!Array.} * @inner */ var randomFallback = null; /** * Generates cryptographically secure random bytes. * @function * @param {number} len Bytes length * @returns {!Array.} Random bytes * @throws {Error} If no random implementation is available * @inner */ function random(len) { /* node */ if (typeof module !== 'undefined' && module && module['exports']) try { return require("crypto")['randomBytes'](len); } catch (e) {} /* WCA */ try { var a; (window['crypto']||window['msCrypto'])['getRandomValues'](a = new Uint32Array(len)); return Array.prototype.slice.call(a); } catch (e) {} /* fallback */ if (!randomFallback) throw Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative"); return randomFallback(len); } // Test if any secure randomness source is available var randomAvailable = false; try { random(1); randomAvailable = true; } catch (e) {} // Default fallback, if any randomFallback = null; /** * Sets the pseudo random number generator to use as a fallback if neither node's `crypto` module nor the Web Crypto * API is available. Please note: It is highly important that the PRNG used is cryptographically secure and that it * is seeded properly! * @param {?function(number):!Array.} random Function taking the number of bytes to generate as its * sole argument, returning the corresponding array of cryptographically secure random byte values. * @see http://nodejs.org/api/crypto.html * @see http://www.w3.org/TR/WebCryptoAPI/ */ bcrypt.setRandomFallback = function(random) { randomFallback = random; }; /** * Synchronously generates a salt. * @param {number=} rounds Number of rounds to use, defaults to 10 if omitted * @param {number=} seed_length Not supported. * @returns {string} Resulting salt * @throws {Error} If a random fallback is required but not set * @expose */ bcrypt.genSaltSync = function(rounds, seed_length) { if (typeof rounds === 'undefined') rounds = GENSALT_DEFAULT_LOG2_ROUNDS; else if (typeof rounds !== 'number') throw Error("Illegal arguments: "+(typeof rounds)+", "+(typeof seed_length)); if (rounds < 4 || rounds > 31) throw Error("Illegal number of rounds (4-31): "+rounds); var salt = []; salt.push("$2a$"); if (rounds < 10) salt.push("0"); salt.push(rounds.toString()); salt.push('$'); salt.push(base64_encode(random(BCRYPT_SALT_LEN), BCRYPT_SALT_LEN)); // May throw return salt.join(''); }; /** * Asynchronously generates a salt. * @param {(number|function(Error, string=))=} rounds Number of rounds to use, defaults to 10 if omitted * @param {(number|function(Error, string=))=} seed_length Not supported. * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting salt * @expose */ bcrypt.genSalt = function(rounds, seed_length, callback) { if (typeof seed_length === 'function') callback = seed_length, seed_length = undefined; // Not supported. if (typeof rounds === 'function') callback = rounds, rounds = GENSALT_DEFAULT_LOG2_ROUNDS; if (typeof callback !== 'function') throw Error("Illegal callback: "+typeof(callback)); if (typeof rounds !== 'number') { nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof rounds)))); return; } nextTick(function() { // Pretty thin, but salting is fast enough try { callback(null, bcrypt.genSaltSync(rounds)); } catch (err) { callback(err); } }); }; /** * Synchronously generates a hash for the given string. * @param {string} s String to hash * @param {(number|string)=} salt Salt length to generate or salt to use, default to 10 * @returns {string} Resulting hash * @expose */ bcrypt.hashSync = function(s, salt) { if (typeof salt === 'undefined') salt = GENSALT_DEFAULT_LOG2_ROUNDS; if (typeof salt === 'number') salt = bcrypt.genSaltSync(salt); if (typeof s !== 'string' || typeof salt !== 'string') throw Error("Illegal arguments: "+(typeof s)+', '+(typeof salt)); return _hash(s, salt); }; /** * Asynchronously generates a hash for the given string. * @param {string} s String to hash * @param {number|string} salt Salt length to generate or salt to use * @param {function(Error, string=)} callback Callback receiving the error, if any, and the resulting hash * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms. * @expose */ bcrypt.hash = function(s, salt, callback, progressCallback) { if (typeof callback !== 'function') throw Error("Illegal callback: "+typeof(callback)); if (typeof s === 'string' && typeof salt === 'number') bcrypt.genSalt(salt, function(err, salt) { _hash(s, salt, callback, progressCallback); }); else if (typeof s === 'string' && typeof salt === 'string') _hash(s, salt, callback, progressCallback); else nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof salt)))); }; /** * Synchronously tests a string against a hash. * @param {string} s String to compare * @param {string} hash Hash to test against * @returns {boolean} true if matching, otherwise false * @throws {Error} If an argument is illegal * @expose */ bcrypt.compareSync = function(s, hash) { if (typeof s !== "string" || typeof hash !== "string") throw Error("Illegal arguments: "+(typeof s)+', '+(typeof hash)); if (hash.length !== 60) return false; var comp = bcrypt.hashSync(s, hash.substr(0, hash.length-31)), same = comp.length === hash.length, max_length = (comp.length < hash.length) ? comp.length : hash.length; // to prevent timing attacks, should check entire string // don't exit after found to be false for (var i = 0; i < max_length; ++i) if (comp.length >= i && hash.length >= i && comp[i] != hash[i]) same = false; return same; }; /** * Asynchronously compares the given data against the given hash. * @param {string} s Data to compare * @param {string} hash Data to be compared to * @param {function(Error, boolean)} callback Callback receiving the error, if any, otherwise the result * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms. * @throws {Error} If the callback argument is invalid * @expose */ bcrypt.compare = function(s, hash, callback, progressCallback) { if (typeof callback !== 'function') throw Error("Illegal callback: "+typeof(callback)); if (typeof s !== "string" || typeof hash !== "string") { nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof hash)))); return; } bcrypt.hash(s, hash.substr(0, 29), function(err, comp) { callback(err, hash === comp); }, progressCallback); }; /** * Gets the number of rounds used to encrypt the specified hash. * @param {string} hash Hash to extract the used number of rounds from * @returns {number} Number of rounds used * @throws {Error} If hash is not a string * @expose */ bcrypt.getRounds = function(hash) { if (typeof hash !== "string") throw Error("Illegal arguments: "+(typeof hash)); return parseInt(hash.split("$")[2], 10); }; /** * Gets the salt portion from a hash. Does not validate the hash. * @param {string} hash Hash to extract the salt from * @returns {string} Extracted salt part * @throws {Error} If `hash` is not a string or otherwise invalid * @expose */ bcrypt.getSalt = function(hash) { if (typeof hash !== 'string') throw Error("Illegal arguments: "+(typeof hash)); if (hash.length !== 60) throw Error("Illegal hash length: "+hash.length+" != 60"); return hash.substring(0, 29); }; /** * Continues with the callback on the next tick. * @function * @param {function(...[*])} callback Callback to execute * @inner */ var nextTick = typeof process !== 'undefined' && process && typeof process.nextTick === 'function' ? (typeof setImmediate === 'function' ? setImmediate : process.nextTick) : setTimeout; /** * Converts a JavaScript string to UTF8 bytes. * @param {string} str String * @returns {!Array.} UTF8 bytes * @inner */ function stringToBytes(str) { var out = [], i = 0; utfx.encodeUTF16toUTF8(function() { if (i >= str.length) return null; return str.charCodeAt(i++); }, function(b) { out.push(b); }); return out; } // A base64 implementation for the bcrypt algorithm. This is partly non-standard. /** * bcrypt's own non-standard base64 dictionary. * @type {!Array.} * @const * @inner **/ var BASE64_CODE = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(''); /** * @type {!Array.} * @const * @inner **/ var BASE64_INDEX = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1]; /** * @type {!function(...number):string} * @inner */ var stringFromCharCode = String.fromCharCode; /** * Encodes a byte array to base64 with up to len bytes of input. * @param {!Array.} b Byte array * @param {number} len Maximum input length * @returns {string} * @inner */ function base64_encode(b, len) { var off = 0, rs = [], c1, c2; if (len <= 0 || len > b.length) throw Error("Illegal len: "+len); while (off < len) { c1 = b[off++] & 0xff; rs.push(BASE64_CODE[(c1 >> 2) & 0x3f]); c1 = (c1 & 0x03) << 4; if (off >= len) { rs.push(BASE64_CODE[c1 & 0x3f]); break; } c2 = b[off++] & 0xff; c1 |= (c2 >> 4) & 0x0f; rs.push(BASE64_CODE[c1 & 0x3f]); c1 = (c2 & 0x0f) << 2; if (off >= len) { rs.push(BASE64_CODE[c1 & 0x3f]); break; } c2 = b[off++] & 0xff; c1 |= (c2 >> 6) & 0x03; rs.push(BASE64_CODE[c1 & 0x3f]); rs.push(BASE64_CODE[c2 & 0x3f]); } return rs.join(''); } /** * Decodes a base64 encoded string to up to len bytes of output. * @param {string} s String to decode * @param {number} len Maximum output length * @returns {!Array.} * @inner */ function base64_decode(s, len) { var off = 0, slen = s.length, olen = 0, rs = [], c1, c2, c3, c4, o, code; if (len <= 0) throw Error("Illegal len: "+len); while (off < slen - 1 && olen < len) { code = s.charCodeAt(off++); c1 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; code = s.charCodeAt(off++); c2 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; if (c1 == -1 || c2 == -1) break; o = (c1 << 2) >>> 0; o |= (c2 & 0x30) >> 4; rs.push(stringFromCharCode(o)); if (++olen >= len || off >= slen) break; code = s.charCodeAt(off++); c3 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; if (c3 == -1) break; o = ((c2 & 0x0f) << 4) >>> 0; o |= (c3 & 0x3c) >> 2; rs.push(stringFromCharCode(o)); if (++olen >= len || off >= slen) break; code = s.charCodeAt(off++); c4 = code < BASE64_INDEX.length ? BASE64_INDEX[code] : -1; o = ((c3 & 0x03) << 6) >>> 0; o |= c4; rs.push(stringFromCharCode(o)); ++olen; } var res = []; for (off = 0; off * Released under the Apache License, Version 2.0 * see: https://github.com/dcodeIO/utfx for details */ var utfx = function() { "use strict"; /** * utfx namespace. * @inner * @type {!Object.} */ var utfx = {}; /** * Maximum valid code point. * @type {number} * @const */ utfx.MAX_CODEPOINT = 0x10FFFF; /** * Encodes UTF8 code points to UTF8 bytes. * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point * respectively `null` if there are no more code points left or a single numeric code point. * @param {!function(number)} dst Bytes destination as a function successively called with the next byte */ utfx.encodeUTF8 = function(src, dst) { var cp = null; if (typeof src === 'number') cp = src, src = function() { return null; }; while (cp !== null || (cp = src()) !== null) { if (cp < 0x80) dst(cp&0x7F); else if (cp < 0x800) dst(((cp>>6)&0x1F)|0xC0), dst((cp&0x3F)|0x80); else if (cp < 0x10000) dst(((cp>>12)&0x0F)|0xE0), dst(((cp>>6)&0x3F)|0x80), dst((cp&0x3F)|0x80); else dst(((cp>>18)&0x07)|0xF0), dst(((cp>>12)&0x3F)|0x80), dst(((cp>>6)&0x3F)|0x80), dst((cp&0x3F)|0x80); cp = null; } }; /** * Decodes UTF8 bytes to UTF8 code points. * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there * are no more bytes left. * @param {!function(number)} dst Code points destination as a function successively called with each decoded code point. * @throws {RangeError} If a starting byte is invalid in UTF8 * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the * remaining bytes. */ utfx.decodeUTF8 = function(src, dst) { var a, b, c, d, fail = function(b) { b = b.slice(0, b.indexOf(null)); var err = Error(b.toString()); err.name = "TruncatedError"; err['bytes'] = b; throw err; }; while ((a = src()) !== null) { if ((a&0x80) === 0) dst(a); else if ((a&0xE0) === 0xC0) ((b = src()) === null) && fail([a, b]), dst(((a&0x1F)<<6) | (b&0x3F)); else if ((a&0xF0) === 0xE0) ((b=src()) === null || (c=src()) === null) && fail([a, b, c]), dst(((a&0x0F)<<12) | ((b&0x3F)<<6) | (c&0x3F)); else if ((a&0xF8) === 0xF0) ((b=src()) === null || (c=src()) === null || (d=src()) === null) && fail([a, b, c ,d]), dst(((a&0x07)<<18) | ((b&0x3F)<<12) | ((c&0x3F)<<6) | (d&0x3F)); else throw RangeError("Illegal starting byte: "+a); } }; /** * Converts UTF16 characters to UTF8 code points. * @param {!function():number|null} src Characters source as a function returning the next char code respectively * `null` if there are no more characters left. * @param {!function(number)} dst Code points destination as a function successively called with each converted code * point. */ utfx.UTF16toUTF8 = function(src, dst) { var c1, c2 = null; while (true) { if ((c1 = c2 !== null ? c2 : src()) === null) break; if (c1 >= 0xD800 && c1 <= 0xDFFF) { if ((c2 = src()) !== null) { if (c2 >= 0xDC00 && c2 <= 0xDFFF) { dst((c1-0xD800)*0x400+c2-0xDC00+0x10000); c2 = null; continue; } } } dst(c1); } if (c2 !== null) dst(c2); }; /** * Converts UTF8 code points to UTF16 characters. * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point * respectively `null` if there are no more code points left or a single numeric code point. * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. * @throws {RangeError} If a code point is out of range */ utfx.UTF8toUTF16 = function(src, dst) { var cp = null; if (typeof src === 'number') cp = src, src = function() { return null; }; while (cp !== null || (cp = src()) !== null) { if (cp <= 0xFFFF) dst(cp); else cp -= 0x10000, dst((cp>>10)+0xD800), dst((cp%0x400)+0xDC00); cp = null; } }; /** * Converts and encodes UTF16 characters to UTF8 bytes. * @param {!function():number|null} src Characters source as a function returning the next char code respectively `null` * if there are no more characters left. * @param {!function(number)} dst Bytes destination as a function successively called with the next byte. */ utfx.encodeUTF16toUTF8 = function(src, dst) { utfx.UTF16toUTF8(src, function(cp) { utfx.encodeUTF8(cp, dst); }); }; /** * Decodes and converts UTF8 bytes to UTF16 characters. * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there * are no more bytes left. * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. * @throws {RangeError} If a starting byte is invalid in UTF8 * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the remaining bytes. */ utfx.decodeUTF8toUTF16 = function(src, dst) { utfx.decodeUTF8(src, function(cp) { utfx.UTF8toUTF16(cp, dst); }); }; /** * Calculates the byte length of an UTF8 code point. * @param {number} cp UTF8 code point * @returns {number} Byte length */ utfx.calculateCodePoint = function(cp) { return (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4; }; /** * Calculates the number of UTF8 bytes required to store UTF8 code points. * @param {(!function():number|null)} src Code points source as a function returning the next code point respectively * `null` if there are no more code points left. * @returns {number} The number of UTF8 bytes required */ utfx.calculateUTF8 = function(src) { var cp, l=0; while ((cp = src()) !== null) l += utfx.calculateCodePoint(cp); return l; }; /** * Calculates the number of UTF8 code points respectively UTF8 bytes required to store UTF16 char codes. * @param {(!function():number|null)} src Characters source as a function returning the next char code respectively * `null` if there are no more characters left. * @returns {!Array.} The number of UTF8 code points at index 0 and the number of UTF8 bytes required at index 1. */ utfx.calculateUTF16asUTF8 = function(src) { var n=0, l=0; utfx.UTF16toUTF8(src, function(cp) { ++n; l += utfx.calculateCodePoint(cp); }); return [n,l]; }; return utfx; }(); Date.now = Date.now || function() { return +new Date; }; /** * @type {number} * @const * @inner */ var BCRYPT_SALT_LEN = 16; /** * @type {number} * @const * @inner */ var GENSALT_DEFAULT_LOG2_ROUNDS = 10; /** * @type {number} * @const * @inner */ var BLOWFISH_NUM_ROUNDS = 16; /** * @type {number} * @const * @inner */ var MAX_EXECUTION_TIME = 100; /** * @type {Array.} * @const * @inner */ var P_ORIG = [ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b ]; /** * @type {Array.} * @const * @inner */ var S_ORIG = [ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 ]; /** * @type {Array.} * @const * @inner */ var C_ORIG = [ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 ]; /** * @param {Array.} lr * @param {number} off * @param {Array.} P * @param {Array.} S * @returns {Array.} * @inner */ function _encipher(lr, off, P, S) { // This is our bottleneck: 1714/1905 ticks / 90% - see profile.txt var n, l = lr[off], r = lr[off + 1]; l ^= P[0]; for (var i=0, k=BLOWFISH_NUM_ROUNDS-2; i<=k;) // Feistel substitution on left word n = S[(l >> 24) & 0xff], n += S[0x100 | ((l >> 16) & 0xff)], n ^= S[0x200 | ((l >> 8) & 0xff)], n += S[0x300 | (l & 0xff)], r ^= n ^ P[++i], // Feistel substitution on right word n = S[(r >> 24) & 0xff], n += S[0x100 | ((r >> 16) & 0xff)], n ^= S[0x200 | ((r >> 8) & 0xff)], n += S[0x300 | (r & 0xff)], l ^= n ^ P[++i]; lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; lr[off + 1] = l; return lr; } /** * @param {Array.} data * @param {number} offp * @returns {{key: number, offp: number}} * @inner */ function _streamtoword(data, offp) { for (var i = 0, word = 0; i < 4; ++i) word = (word << 8) | (data[offp] & 0xff), offp = (offp + 1) % data.length; return { key: word, offp: offp }; } /** * @param {Array.} key * @param {Array.} P * @param {Array.} S * @inner */ function _key(key, P, S) { var offset = 0, lr = [0, 0], plen = P.length, slen = S.length, sw; for (var i = 0; i < plen; i++) sw = _streamtoword(key, offset), offset = sw.offp, P[i] = P[i] ^ sw.key; for (i = 0; i < plen; i += 2) lr = _encipher(lr, 0, P, S), P[i] = lr[0], P[i + 1] = lr[1]; for (i = 0; i < slen; i += 2) lr = _encipher(lr, 0, P, S), S[i] = lr[0], S[i + 1] = lr[1]; } /** * Expensive key schedule Blowfish. * @param {Array.} data * @param {Array.} key * @param {Array.} P * @param {Array.} S * @inner */ function _ekskey(data, key, P, S) { var offp = 0, lr = [0, 0], plen = P.length, slen = S.length, sw; for (var i = 0; i < plen; i++) sw = _streamtoword(key, offp), offp = sw.offp, P[i] = P[i] ^ sw.key; offp = 0; for (i = 0; i < plen; i += 2) sw = _streamtoword(data, offp), offp = sw.offp, lr[0] ^= sw.key, sw = _streamtoword(data, offp), offp = sw.offp, lr[1] ^= sw.key, lr = _encipher(lr, 0, P, S), P[i] = lr[0], P[i + 1] = lr[1]; for (i = 0; i < slen; i += 2) sw = _streamtoword(data, offp), offp = sw.offp, lr[0] ^= sw.key, sw = _streamtoword(data, offp), offp = sw.offp, lr[1] ^= sw.key, lr = _encipher(lr, 0, P, S), S[i] = lr[0], S[i + 1] = lr[1]; } /** * Internaly crypts a string. * @param {Array.} b Bytes to crypt * @param {Array.} salt Salt bytes to use * @param {number} rounds Number of rounds * @param {function(Error, Array.=)=} callback Callback receiving the error, if any, and the resulting bytes. If * omitted, the operation will be performed synchronously. * @param {function(number)=} progressCallback Callback called with the current progress * @returns {!Array.|undefined} Resulting bytes if callback has been omitted, otherwise `undefined` * @inner */ function _crypt(b, salt, rounds, callback, progressCallback) { var cdata = C_ORIG.slice(), clen = cdata.length, err; // Validate if (rounds < 4 || rounds > 31) { err = Error("Illegal number of rounds (4-31): "+rounds); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } if (salt.length !== BCRYPT_SALT_LEN) { err =Error("Illegal salt length: "+salt.length+" != "+BCRYPT_SALT_LEN); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } rounds = 1 << rounds; var P = P_ORIG.slice(), S = S_ORIG.slice(), i = 0, j; _ekskey(salt, b, P, S); /** * Calcualtes the next round. * @returns {Array.|undefined} Resulting array if callback has been omitted, otherwise `undefined` * @inner */ function next() { if (progressCallback) progressCallback(i/rounds); if (i < rounds) { var start = Date.now(); for (; i < rounds;) { i = i + 1; _key(b, P, S); _key(salt, P, S); if (Date.now() - start > MAX_EXECUTION_TIME) break; } } else { for (i = 0; i < 64; i++) for (j = 0; j < (clen >> 1); j++) _encipher(cdata, j << 1, P, S); var ret = []; for (i = 0; i < clen; i++) ret.push(((cdata[i] >> 24) & 0xff) >>> 0), ret.push(((cdata[i] >> 16) & 0xff) >>> 0), ret.push(((cdata[i] >> 8) & 0xff) >>> 0), ret.push((cdata[i] & 0xff) >>> 0); if (callback) { callback(null, ret); return; } else return ret; } if (callback) nextTick(next); } // Async if (typeof callback !== 'undefined') { next(); // Sync } else { var res; while (true) if (typeof(res = next()) !== 'undefined') return res || []; } } /** * Internally hashes a string. * @param {string} s String to hash * @param {?string} salt Salt to use, actually never null * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash. If omitted, * hashing is perormed synchronously. * @param {function(number)=} progressCallback Callback called with the current progress * @returns {string|undefined} Resulting hash if callback has been omitted, otherwise `undefined` * @inner */ function _hash(s, salt, callback, progressCallback) { var err; if (typeof s !== 'string' || typeof salt !== 'string') { err = Error("Invalid string / salt: Not a string"); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } // Validate the salt var minor, offset; if (salt.charAt(0) !== '$' || salt.charAt(1) !== '2') { err = Error("Invalid salt version: "+salt.substring(0,2)); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } if (salt.charAt(2) === '$') minor = String.fromCharCode(0), offset = 3; else { minor = salt.charAt(2); if ((minor !== 'a' && minor !== 'y') || salt.charAt(3) !== '$') { err = Error("Invalid salt revision: "+salt.substring(2,4)); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } offset = 4; } // Extract number of rounds if (salt.charAt(offset + 2) > '$') { err = Error("Missing salt rounds"); if (callback) { nextTick(callback.bind(this, err)); return; } else throw err; } var r1 = parseInt(salt.substring(offset, offset + 1), 10) * 10, r2 = parseInt(salt.substring(offset + 1, offset + 2), 10), rounds = r1 + r2, real_salt = salt.substring(offset + 3, offset + 25); s += minor >= 'a' ? "\x00" : ""; var passwordb = stringToBytes(s), saltb = base64_decode(real_salt, BCRYPT_SALT_LEN); /** * Finishes hashing. * @param {Array.} bytes Byte array * @returns {string} * @inner */ function finish(bytes) { var res = []; res.push("$2"); if (minor >= 'a') res.push(minor); res.push("$"); if (rounds < 10) res.push("0"); res.push(rounds.toString()); res.push("$"); res.push(base64_encode(saltb, saltb.length)); res.push(base64_encode(bytes, C_ORIG.length * 4 - 1)); return res.join(''); } // Sync if (typeof callback == 'undefined') return finish(_crypt(passwordb, saltb, rounds)); // Async else { _crypt(passwordb, saltb, rounds, function(err, bytes) { if (err) callback(err, null); else callback(null, finish(bytes)); }, progressCallback); } } /** * Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet. * @function * @param {!Array.} b Byte array * @param {number} len Maximum input length * @returns {string} * @expose */ bcrypt.encodeBase64 = base64_encode; /** * Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet. * @function * @param {string} s String to decode * @param {number} len Maximum output length * @returns {!Array.} * @expose */ bcrypt.decodeBase64 = base64_decode; export default bcrypt; ================================================ FILE: public/assets/lib/vendor/codemirror/.editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 ================================================ FILE: public/assets/lib/vendor/codemirror/.gitattributes ================================================ *.txt text eol=lf *.js text eol=lf *.html text eol=lf *.md text eol=lf *.json text eol=lf *.yml text eol=lf *.css text eol=lf *.svg text eol=lf ================================================ FILE: public/assets/lib/vendor/codemirror/.npmignore ================================================ /node_modules /demo /doc /test /test*.html /index.html /mode/*/*test.js /mode/*/*.html /mode/index.html .* /bin/authors.sh /bin/lint /bin/release /bin/upload-release.js ================================================ FILE: public/assets/lib/vendor/codemirror/AUTHORS ================================================ List of CodeMirror contributors. Updated before every release. 4oo4 4r2r Aaron Brooks Abdelouahab Abdussalam Abdurrahman Abe Fettig Abhishek Gahlot Adam Ahmed Adam King Adam Particka Adam Wight adanlobato Adán Lobato Aditya Toshniwal Adrian Aichner Adrian Heine Adrian Kunz Adrien Bertrand aeroson Ahmad Amireh Ahmad M. Zawawi AHOHNMYC ahoward Ajin Abraham Akeksandr Motsjonov Alasdair Smith AlbertHilb Alberto González Palomo Alberto Pose Albert Xing Alexander Marks Alexander Pavlov Alexander Schepanovski Alexander Shvets Alexander Solovyov Alexandre Bique Alex Churchill alexey-k Alex Piggott Alf Eaton Aliaksei Chapyzhenka Allen Sarkisyan Ami Fischman Amin Shali Amin Ullah Khan amshali@google.com Amsul amuntean Amy Ananya Sen anaran AndersMad Anders Nawroth Anderson Mesquita Anders Wåglund Andrea G Andreas Reischuck Andres Taylor Andre von Houck Andrew Cheng Andrew Dassonville Andrey Fedorov Andrey Klyuchnikov Andrey Lushnikov Andrey Shchekin Andy Joslin Andy Kimball Andy Li Angelo angelozerr angelo.zerr@gmail.com Ankit Ankit Ahuja Ansel Santosa Anthony Dugois anthonygego Anthony Gégo Anthony Grimes Anthony Stewart Anton Kovalyov antosarho aoki ken Apollo Zhu AQNOUCH Mohammed Aram Shatakhtsyan areos Arnab Bose Arnoud Buzing Arsène von Wyss Arthur Müller Arun Narasani as3boyan asolove atelierbram AtomicPages LLC Atul Bhouraskar Aurelian Oancea Axel Lewenhaupt Baptiste Augrain Barret Rennie Bartosz Dziewoński Basarat Ali Syed Bastian Müller belhaj Bem Jones-Bey benbro Benedikt Meurer benhormann Ben Hormann Beni Cherniavsky-Paskin Benjamin DeCoste benjaminr-ps Benjamin Young Ben Keen Ben Miller Ben Mosher Bernhard Sirlinger Bert Chang Bharad BigBlueHat Billiam Billy Moon Bin Ni binny Bjarki Ágúst Guðmundsson Bjorn Hansen B Krishna Chaitanya Blaine G blukat29 Bo boomyjee Bo Peng borawjm Boris K Boris Verkhovskiy Brad Metcalf Brandon Frohs Brandon Wamboldt Bret Little Brett Morgan Brett Zamir Brian Grinstead BrianHung Brian Sletten brrd Bruce Mitchener Bruno Logerfo Bryan Gin-ge Chen Bryan Massoth Caitlin Potter Calin Barbat callodacity Camilo Roca Casey Klebba cBiscuit87 César González Íñiguez Chad Jolly Chandra Sekhar Pydi Charles Skelton Cheah Chu Yeow Chhekur Chris Colborne Chris Coyier Chris Ford Chris Granger Chris Houseknecht Chris Lohfink Chris Morgan Chris Reeves Chris Smith Christian Gruen Christian Oyarzun Christian Petrov Christian Sonne christopherblaser Christopher Brown Christopher Kramer Christopher Mitchell Christopher Pfohl Christopher Wallis Chunliang Lyu ciaranj clone-it clso CodeAnimal CodeBitt coderaiser Cole R Lawrence ComFreek Cornelius Weig Cristian Prieto Curran Kelleher Curtis Gagliardi d8888 dagsta daines Dale Jung Dan Bentley Dan Heberden Daniel, Dao Quang Minh Daniele Di Sarli Daniel Faust Daniel Hanggi Daniel Huigens Daniel Kesler Daniel KJ Daniel Neel Daniel Parnell Daniel Thwaites Danila Malyutin Danny Yoo darealshinji Darius Roberts databricks-david-lewis Dave Brondsema Dave MacLachlan Dave Myers David Barnett David H. Bronke David Mignot David Pathakjee David R. Myers David Rodrigues David Santana David Vázquez David Whittington deebugger Deep Thought Denis Ovsienko Devin Abbott Devon Carew Dick Choi Diego Fernandez dignifiedquire Dimage Sapelkin Dimitri Mitropoulos Dinindu D. Wanniarachchi dmaclach Dmitry Kiselyov DoctorKrolic domagoj412 Dominator008 Domizio Demichelis Doug Blank Doug Wikle Drew Bratcher Drew Hintz Drew Khoury Drini Cami Dror BG Duncan Lilley duralog dwelle Ealton eborden edoroshenko edsharp ekhaled Elisée Elmar Peise elpnt Emmanuel Schanzer Enam Mijbah Noor Eric Allam Eric Bogard Erik Demaine Erik Krogh Kristensen Erik Welander erosman eustas Evan Minsk Fabien Dubosson Fabien O'Carroll Fabio Zendhi Nagao Faiza Alsaied Faris Masad Fauntleroy fbuchinger feizhang365 Felipe Lalanne Felipe S. S. Schneider Felix Raab ficristo Filip Noetzel Filip Stollár Filype Pereira finalfantasia flack Florian Felten Fons van der Plas Forbes Lindesay ForbesLindesay Ford_Lawnmower Forrest Oliphant Franco Catena Frank Seifferth Frank Wiegand fraxx001 Fredrik Borg FUJI Goro (gfx) fzipp Gabriela Gutierrez Gabriel Gheorghian Gabriel Horner Gabriel Nahmias galambalazs Gary Sheng Gautam Mehta Gavin Douglas Geist-zz gekkoe Geordie Hall George Stephanis geowarin Gerard Braad Gergely Hegykozi Germain Chazot Giovanni Calò Glebov Boris Glenn Jorde Glenn Ruehle goldsmcb Golevka Google LLC Gordon Smith Grant Skinner greengiant Gregory Koberger Grzegorz Mazur Guang Li Guan Gui Guillaume Massé Guillaume Massé guraga Gustavo Rodrigues Hakan Tunc Hanno Fellmann Hans Engel Hanzhao Deng Haoran Yu Harald Schilly Hardest Harshvardhan Gupta Harutyun Amirjanyan Hasan Delibaş Hasan Karahan Heanes Hector Oswaldo Caballero Hein Htat Hélio Hendrik Erz Hendrik Wallbaum Henrik Haugbølle Herculano Campos hidaiy Hiroyuki Makino hitsthings Hocdoc Howard Howard Jing Hugues Malphettes Ian Beck Ian Davies Ian Dickinson Ian Henderson ianhi Ian Rose Ian Wehrman Ian Wetherbee Ice White ICHIKAWA, Yuji idleberg Igor Petruk ilvalle Ilya Kharlamov Ilya Zverev Ingo Richter Intervue Irakli Gozalishvili iteriani Ivan Kurnosov Ivoah Jack Douglas Jacob Lee Jaimin Jake Peyser Jake Zimmerman Jakob Kummerow Jakob Miland Jakub Nowak Jakub T. Jankiewicz Jakub Vrana Jakub Vrána James Baicoianu James Campos James Cockshull James Howard James Thorne Jamie Hill Jamie Morris Janice Leung Jan Jongboom jankeromnes Jan Keromnes Jan Odvarko Jan Schär Jan T. Sott Jared Dean Jared Forsyth Jared Jacobs Jason Jason Barnabe Jason Grout Jason Heeris Jason Johnston Jason San Jose Jason Siefken Jayaprabhakar Jay Contonio Jaydeep Solanki Jean Boussier Jeff Blaisdell Jeff Hanke Jeff Jenkins jeffkenton Jeff Pickhardt jem (graphite) Jeremy Parmenter Jim Jim Avery jkaplon JobJob jochenberger Jochen Berger Joel Einbinder joelpinheiro Joe Predham joewalsh Johan Ask Johannes John Chen John Connor John-David Dalton John Engler John Lees-Miller John Ryan John Snelson johnspiegel John Van Der Loo Jon Ander Peñalba Jonas Döbertin Jonas Helfer Jonathan Dierksen Jonathan Hart Jonathan Malmaud Jonathan Rascher Jon Gacnik jongalloway Jon Malmaud Jon Sangster Joo Joost-Wim Boekesteijn José dBruxelles Joseph D. Purcell Joseph Pecoraro Josh Barnes Josh Cohen Josh Soref Joshua Newman Josh Watzman jots Joy Zhong jsoojeon ju1ius Juan Benavides Romero Jucovschi Constantin Juho Vuori Julien CROUZET Julien Rebetez Justin Andresen Justin Hileman jwallers@gmail.com kaniga karevn Karol Kaushik Kulkarni Kayur Patel kazk Kazuhisa Ishizaka Kazuhito Hokamura kcwiakala Kees de Kooter Keldan Chapman Kenan Christian Dimas Ken Newman ken restivo Ken Rockot Kevin Earls Kevin Kwok Kevin Muret Kevin Sawicki Kevin Ushey Kier Darby Kim-Anh Tran Klaus Silveira Koh Zi Han, Cliff komakino kometenstaub Konrad Zapotoczny Konstantin Chernenko Konstantin Lopuhin koops Kris Ciccarello ks-ifware kubelsmieci kvncp KwanEsq Kyle Kelley KyleMcNutt LaKing Lanfei Lanny laobubu Laszlo Vidacs leaf leaf corcoran Lemmon Leo Baschy Leonid Khachaturov Leon Sorokin Leonya Khachaturov lexer2086 Liam Newman Libo Cannici Lior Goldberg Lior Shub lishid LloydMilligan LM lochel Lonnie Abelbeck Lorenzo Simionato Lorenzo Stoakes Louis Mauchet Luca Fabbri Lucas Buchala Luciano Longo Luciano Santana Lu Fangjian Łukasz Wielgus Luke Browning Luke Granger-Brown Luke Haas Luke Stagner lynschinzer M1cha Madhura Jayaratne Maksim Lin Maksym Taran Malay Majithia Manideep Manuel Rego Casasnovas Marat Dreizin Marcel Gerber Marcelo Camargo Marc Espín Marco Aurélio Marco Munizaga Marcus Bointon Marek Rudnicki Marijn Haverbeke Mário Gonçalves Mario Pietsch Mark Anderson Mark Boyes Mark Dalgleish Mark Hamstra Mark Lentczner Marko Bonaci Mark Peace Markus Bordihn Markus Olsson Martin Balek Martín Gaitán Martin Hasoň Martin Hunt Martin Laine Martin Zagora Masahiro MATAYOSHI Mason Malone Mateusz Paprocki Mathias Bynens mats cronqvist Matt Diehl Matt Gaide Matthew Bauer Matthew Beale Matthew Casperson matthewhayes Matthew Rathbone Matthew Suozzo Matthias Bussonnier Matthias BUSSONNIER Mattia Astorino Matt MacPherson Matt McDonald Matt Pass Matt Sacks mauricio Maximilian Hils Maxim Kraev Max Kirsch Max Schaefer Max Wu Max Xiantu mbarkhau McBrainy mce2 Mélanie Chauvel melpon meshuamam Metatheos Micah Dubinko Michael Michael Chirico Michael Goderbauer Michael Grey Michael Kaminsky Michael Lehenbauer Michael Wadman Michael Walker Michael Zhou Michal Čihař Michal Dorner Michal Kapiczynski Mighty Guava Miguel Castillo mihailik Mika Andrianarijaona Mike Mike Bostock Mike Brevoort Mike Diaz Mike Ivanov Mike Kadin Mike Kobit Milan Szekely MinJune Kim MinRK Miraculix87 misfo mkaminsky11 mloginov mlsad3 Moritz Schubotz (physikerwelt) Moritz Schwörer Moshe Wajnberg mps ms mtaran-google Mu-An ✌️ Chiou Mu-An Chiou Mykola Martynovets mzabuawala Narciso Jaramillo nathanlesage Nathan Williams ndr Neil Anderson neon-dev nerbert NetworkNode nextrevision ngn nguillaumin Ng Zhi An Nicholas Bollweg Nicholas Bollweg (Nick) NickKolok Nick Kreeger Nick Small Nicolas Chevobbe Nicolas Kick Nicolò Ribaudo Niels van Groningen nightwing Nikita Beloglazov Nikita Vasilyev Nikolaj Kappler Nikolay Kostov nilp0inter Nils Knappmeier Nina Pypchenko Nisarg Jhaveri nlwillia noragrossman Norman Rzepka Nouzbe Oleksandr Yakovenko Olivia Ytterbrink Ondřej Mirtes Opender Singh opl- Oreoluwa Onatemowo orionlee oscar.lofwenhamn Oskar Segersvärd ossdev overdodactyl pablo pabloferz Pablo Zubieta paddya Page paladox Panupong Pasupat paris Paris Paris Kasidiaris Parker Lougheed Patil Arpith Patrick Kettner Patrick Stoica Patrick Strawderman Paul Garvin Paul Ivanov Paul Masson Paul Schmidt Pavel Pavel Feldman Pavel Petržela Pavel Strashkin Paweł Bartkiewicz peteguhl peter Peter Flynn peterkroon Peter Kroon Peter László Phil DeJarnett Philipp A Philipp Markovics Philip Stadermann Pi Delport Pierre Gerold Pieter Ouwerkerk Piyush Pontus Granström Pontus Melke prasanthj Prasanth J Prayag Verma prendota Prendota ps173 Qiang Li quiddity-wp Radek Piórkowski Rahul Rahul Anand ramwin1 Randall Mason Randy Burden Randy Edmunds Randy Luecke Raphael Amorim Rasmus Erik Voel Jensen Rasmus Schultz raymondf Raymond Hill ray ratchup Ray Ratchup Remi Nyborg Renaud Durlin Reynold Xin Richard Denton Richard Fung Richard van der Meer Richard Z.H. Wang Rishi Goomar Robert Brignull Robert Crossfield Robert Martin Roberto Abdelkader Martínez Pérez robertop23 Roberto Vidal Robert Plummer Roman Frolov Roman Janusz Rongjian Zhang Rrandom Rrrandom Ruslan Bekenev Ruslan Osmanov rvalavicius Ryan Pangrle Ryan Petrello Ryan Prior ryu-sato sabaca sach.gupta Sachin Gupta sahil.mahna Sam Lee Sam Rawlins Samuel Ainsworth Sam Wilson sandeepshetty Sander AKA Redsandro Sander Verweij santec Sarah McAlear and Wenlin Zhang Sascha Peilicke Sasha Varlamov satamas satchmorun sathyamoorthi Saul Costa S. Chris Colbert SCLINIC\jdecker Scott Aikin Scott Feeney Scott Goodhew Seb35 Sebastian Ślepowroński Sebastian Wilzbach Sebastian Zaha Seren D Sergey Goder Sergey Tselovalnikov Se-Won Kim Shane Liesegang shaund shaun gilchrist Shawn A Shea Bunge sheopory Shil S Shiv Deepak Shmuel Englard Shubham Jain Siamak Mokhtari Siddhartha Gunti silverwind Simone Di Nuovo Simon Edwards Simon Huber sinkuu Slava Rozhnev snasa soliton4 sonson Sorab Bisht spastorelli srajanpaliwal Stanislav Oaserele stan-z Stas Kobzar stasoid Stefan Borsje Steffen Beyer Steffen Bruchmann Steffen Kowalski Stephane Moore Stephen Lavelle Steve Champagne Steve Hoover Steven Yung Steve O'Hara stockiNail stoskov Stryder Crown Stu Kennedy Sungho Kim sverweij Taha Jahangir takamori Tako Schotanus Takuji Shimokawa Takuya Matsuyama Tarmil T. Brandon Ashley TDaglis Teja tel Tentone tfjgeorge Thaddee Tyl thanasis TheHowl themrmax Thiemo Kreuz think Thomas Brouard Thomas Dvornik Thomas Kluyver thomasmaclean Thomas Schmid Tim Alby Tim Baumann Tim Down Tim Gates Tim Nguyen Timothy Farrell Timothy Gu Timothy Hatcher Tim van der Lippe Tobias Bertelsen TobiasBg Todd Berman Todd Kennedy tokafew420 Tomas-A Tomas Varaneckas Tom Erik Støwer Tom Klancer Tom MacWright Tom McLaughlin Tony Jian tophf Torben Bundt Torgeir Thoresen totalamd Travis Heppe Triangle717 Tristan Tarrant TSUYUSATO Kitsune Tugrul Elmas twifkak Tyler Long Tyler Makaro Vadim Dyachenko Vadzim Ramanenka Vaibhav Sagar vamshi.revu VapidWorx Vestimir Markov vf Victor Bocharsky Vincent Woo Vladislav Voitenok Volker Mische vtripolitakis wdouglashall Weiyan Shao wenli Wes Cossick Wesley Wiser Weston Ruter Will Binns-Smith Will Cassella Will Dean Will Hernandez William Desportes William Jamieson William Stein Willy Wojtek Ptak wonderboyjon Wu Cheng-Han Xavier Mendez Yakov Manshin Yang Guo Yash Singh Yash-Singh1 Yassin N. Hassan YNH Webdev yoongu yoyoyodog123 Yunchi Luo Yuvi Panda Yvonnick Esnault Zac Anger Zachary Dremann ZeeshanNoor Zeno Rocha Zhang Hao Ziran Sun Ziv zoobestik zziuni 魏鹏刚 ================================================ FILE: public/assets/lib/vendor/codemirror/CHANGELOG.md ================================================ ## 5.65.16 (2023-11-20) ### Bug fixes Fix focus tracking in shadow DOM. [go mode](https://codemirror.net/5/mode/go/): Allow underscores in numbers. [jsx mode](https://codemirror.net/5/mode/jsx/index.html): Support TS generics marked by trailing comma. ## 5.65.15 (2023-08-29) ### Bug fixes [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Prevent tooltips from sticking out of the viewport. [yaml mode](https://codemirror.net/5/mode/yaml/): Fix an exponential-complexity regular expression. ## 5.65.14 (2023-07-17) ### Bug fixes [clike mode](https://codemirror.net/5/mode/clike/): Fix poor indentation in some Java code. [nsis mode](https://codemirror.net/5/mode/nsis/index.html): Recognize `!assert` command. [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Remove broken annotation deduplication. ## 5.65.13 (2023-04-27) ### Bug fixes [dart mode](https://codemirror.net/5/mode/dart/index.html): Add some new keywords. [clike mode](https://codemirror.net/5/mode/clike/): Tokenize Scala character literals. ## 5.65.12 (2023-02-20) ### Bug fixes [python mode](https://codemirror.net/5/mode/python/): Add new built-ins and keywords. ## 5.65.11 (2022-12-20) ### Bug fixes Also respect autocapitalize/autocorrect/spellcheck options in textarea mode. [sql-hint addon](https://codemirror.net/5/doc/manual.html#addon_sql-hint): Fix keyword completion in generic SQL mode. ## 5.65.10 (2022-11-20) ### Bug fixes [sql-hint addon](https://codemirror.net/5/doc/manual.html#addon_sql-hint): Fix completion when the SQL mode is wrapped by some outer mode. [javascript mode](https://codemirror.net/5/mode/javascript/index.html): Fix parsing of property keywords before private property names. ## 5.65.9 (2022-09-20) ### Bug fixes Add a workaround for a regression in Chrome 105 that could cause content below the editor to not receive mouse events. [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Resize the tooltip if it doesn't fit the screen. [swift mode](https://codemirror.net/5/mode/swift): Fix tokenizing of block comments. [jinja2 mode](https://codemirror.net/5/mode/jinja2/): Support line statements. ## 5.65.8 (2022-08-20) ### Bug fixes Include direction override and isolate characters in the default set of special characters. Fix an issue that could cause document corruption when mouse-selecting during composition. [foldgutter addon](https://codemirror.net/5/doc/manual.html#addon_foldgutter): Refresh markers when the editor's mode changes. [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Fix syntax that prevented the addon from loading in IE10. ## 5.65.7 (2022-07-20) ### Bug fixes Fix several references to the global `document`/`window`, improving support for creating an editor in another frame. [vim bindings](https://codemirror.net/5/demo/vim.html): Use [upstream](https://github.com/replit/codemirror-vim/) code instead of keeping our own copy. [tern addon](https://codemirror.net/5/demo/tern.html): Properly HTML escape variable names in rename dialog. ## 5.65.6 (2022-06-20) ### Bug fixes Avoid firing `beforeCursorEnter` callbacks twice for cursor selections. Improve support for auto-hiding macOS scrollbars. [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix an issue where the tooltip could be placed to the left of the screen. [swift mode](https://codemirror.net/5/mode/swift): Support structured concurrency keywords. ## 5.65.5 (2022-05-30) ### Bug fixes Work around a bug in Chrome 102 that caused wheel scrolling of the editor to constantly stop. [search addon](https://codemirror.net/5/demo/search.html): Make sure the search field has an accessible label. [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Preserve indentation on otherwise empty lines when indenting. ## 5.65.4 (2022-05-20) ### Bug fixes Ignore paste events when the editor doesn't have focus. [sparql mode](https://codemirror.net/5/mode/sparql/index.html): Fix parsing of variables after operators. [julia mode](https://codemirror.net/5/mode/julia/): Properly tokenize `!==` and `===` operators. ## 5.65.3 (2022-04-20) ### Bug fixes Fix a crash that could occur when when marking text. [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Add aria label to buttons. [groovy mode](https://codemirror.net/5/mode/groovy/index.html): Properly highlight interpolated variables. ## 5.65.2 (2022-02-21) ### Bug fixes [clike mode](https://codemirror.net/5/mode/clike/): Recognize triple quoted string in Java. [cypher mode](https://codemirror.net/5/mode/cypher/index.html): Fix handling of punctuation. ## 5.65.1 (2022-01-20) ### Bug fixes Fix miscalculation of vertical positions in lines that have both line widgets and replaced newlines. ## 5.65.0 (2021-12-20) ### Bug fixes brace-folding addon: Fix broken folding on lines with both braces and square brackets. ### New features [vim bindings](https://codemirror.net/5/demo/vim.html): Support g0, g$, g. ## 5.64.0 (2021-11-20) ### Bug fixes Fix a crash that occurred in some situations with replacing marks across line breaks. Make sure native scrollbars reset their position when hidden and re-shown. ### New features [vim bindings](https://codemirror.net/5/demo/vim.html): Support C-u to delete back a line. ## 5.63.3 (2021-10-11) ### Bug fixes Prevent external styles from giving the hidden textarea a min-height. Remove a stray autosave file that was part of the previous release. ## 5.63.1 (2021-09-29) ### Bug fixes Fix an issue with mouse scrolling on Chrome 94 Windows, which made scrolling by wheel move unusably slow. ## 5.63.0 (2021-09-20) ### Bug fixes Fix scroll position jumping when scrolling a document with very different line heights. [xml mode](https://codemirror.net/5/mode/xml/): Look up HTML element behavior in a case-insensitive way. ### New features [vim bindings](https://codemirror.net/5/demo/vim.html): Support guu for case-changing. ## 5.62.3 (2021-08-20) ### Bug fixes Give the editor a `translate=no` attribute to prevent automatic translation from modifying its content. Give vim-style cursors a width that matches the character after them. [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Make buttons keyboard-accessible. [emacs bindings](https://codemirror.net/5/demo/emacs.html): Fix by-page scrolling keybindings, which were accidentally inverted. ## 5.62.2 (2021-07-21) ### Bug fixes [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Fix a regression that broke several addon options. ## 5.62.1 (2021-07-20) ### Bug fixes [vim bindings](https://codemirror.net/5/demo/vim.html): Make matching of upper-case characters more Unicode-aware. [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Prevent options passed to the addon itself from being given to the linter. [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Improve screen reader support. [search addon](https://codemirror.net/5/demo/search.html): Avoid using `innerHTML`. ## 5.62.0 (2021-06-21) ### Bug fixes Improve support for vim-style cursors in a number of themes. ### New features [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Add support for highlighting lines with errors or warnings. ## 5.61.1 (2021-05-20) ### Bug fixes Fix a bug where changing the editor's document could confuse text-direction management. Fix a bug in horizontally scrolling the cursor into view. Optimize adding lots of marks in a single transaction. [simple mode addon](https://codemirror.net/5/demo/simplemode.html): Support regexps with a unicode flag. [javascript mode](https://codemirror.net/5/mode/javascript/index.html): Add support for TypeScript template string types, improve integration with JSX mode. ## 5.61.0 (2021-04-20) ### Bug fixes Improve support for being in a shadow DOM in contenteditable mode. Prevent line number from being read by screen readers. [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix a crash caused by a race condition. [javascript mode](https://codemirror.net/5/mode/javascript/): Improve scope tracking. ### New features The library now emits an `"updateGutter"` event when the gutter width changes. [emacs bindings](https://codemirror.net/5/demo/emacs.html): Provide named commands for all bindings. ## 5.60.0 (2021-03-20) ### Bug fixes Fix autofocus feature in contenteditable mode. [simple mode addon](https://codemirror.net/5/demo/simplemode.html): Fix a null-dereference crash. [multiplex addon](https://codemirror.net/5/demo/multiplex.html): Make it possible to use `parseDelimiters` when both delimiters are the same. [julia mode](https://codemirror.net/5/mode/julia/): Fix a lockup bug. ### New features `setSelections` now allows ranges to omit the `head` property when it is equal to `anchor`. [sublime bindings](https://codemirror.net/5/demo/sublime.html): Add support for reverse line sorting. ## 5.59.4 (2021-02-24) ### Bug fixes Give the scrollbar corner filler a background again, to prevent content from peeping through between the scrollbars. ## 5.59.3 (2021-02-20) ### Bug fixes Don't override the way zero-with non-joiners are rendered. Fix an issue where resetting the history cleared the `undoDepth` option's value. [vim bindings](https://codemirror.net/5/demo/vim.html): Fix substitute command when joining and splitting lines, fix global command when line number change, add support for `:vglobal`, properly treat caps lock as a modifier key. ## 5.59.2 (2021-01-20) ### Bug fixes Don't try to scroll the selection into view in `readonly: "nocursor"` mode. [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Fix a regression in the behavior of pressing enter between brackets. [javascript mode](https://codemirror.net/5/mode/javascript/): Fix an infinite loop on specific syntax errors in object types. various modes: Fix inefficient RegExp matching. ## 5.59.1 (2020-12-31) ### Bug fixes Fix an issue where some Chrome browsers were detected as iOS. ## 5.59.0 (2020-12-20) ### Bug fixes Fix platform detection on recent iPadOS. [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Don't show duplicate messages for a given line. [clojure mode](https://codemirror.net/5/mode/clojure/index.html): Fix regexp that matched in exponential time for some inputs. [hardwrap addon](https://codemirror.net/5/doc/manual.html#addon_hardwrap): Improve handling of words that are longer than the line length. [matchbrackets addon](https://codemirror.net/5/doc/manual.html#addon_matchbrackets): Fix leaked event handler on disabling the addon. ### New features [search addon](https://codemirror.net/5/demo/search.html): Make it possible to configure the search addon to show the dialog at the bottom of the editor. ## 5.58.3 (2020-11-19) ### Bug fixes Suppress quick-firing of blur-focus events when dragging and clicking on Internet Explorer. Fix the `insertAt` option to `addLineWidget` to actually allow the widget to be placed after all widgets for the line. [soy mode](https://codemirror.net/5/mode/soy/): Support `@Attribute` and element composition. [shell mode](https://codemirror.net/5/mode/shell/): Support heredoc quoting. ## 5.58.2 (2020-10-23) ### Bug fixes Fix a bug where horizontally scrolling the cursor into view sometimes failed with a non-fixed gutter. [julia mode](https://codemirror.net/5/mode/julia/): Fix an infinite recursion bug. ## 5.58.1 (2020-09-23) ### Bug fixes [placeholder addon](https://codemirror.net/5/doc/manual.html#addon_placeholder): Remove arrow function that ended up in the code. ## 5.58.0 (2020-09-21) ### Bug fixes Make backspace delete by code point, not glyph. Suppress flickering focus outline when clicking on scrollbars in Chrome. Fix a bug that prevented attributes added via `markText` from showing up unless the span also had some other styling. Suppress cut and paste context menu entries in readonly editors in Chrome. [placeholder addon](https://codemirror.net/5/doc/manual.html#addon_placeholder): Update placeholder visibility during composition. ### New features Make it less cumbersome to style new lint message types. [vim bindings](https://codemirror.net/5/demo/vim.html): Support black hole register, `gn` and `gN` ## 5.57.0 (2020-08-20) ### Bug fixes Fix issue that broke binding the macOS Command key. [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Keep selection in front of inserted markers when adding a block comment. [css mode](https://codemirror.net/5/mode/css/): Recognize more properties and value names. [annotatescrollbar addon](https://codemirror.net/5/doc/manual.html#addon_annotatescrollbar): Don't hide matches in collapsed content. ### New features [vim bindings](https://codemirror.net/5/demo/vim.html): Support tag text objects in xml and html modes. ## 5.56.0 (2020-07-20) ### Bug fixes Line-wise pasting was fixed on Chrome Windows. [wast mode](https://codemirror.net/5/mode/wast/): Follow standard changes. [soy mode](https://codemirror.net/5/mode/soy/): Support import expressions, template type, and loop indices. [sql-hint addon](https://codemirror.net/5/doc/manual.html#addon_sql-hint): Improve handling of double quotes. ### New features [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): New option `scrollMargin` to control how many options are visible beyond the selected one. [hardwrap addon](https://codemirror.net/5/doc/manual.html#addon_hardwrap): New option `forceBreak` to disable breaking of words that are longer than a line. ## 5.55.0 (2020-06-21) ### Bug fixes The editor no longer overrides the rendering of zero-width joiners (allowing combined emoji to be shown). [vim bindings](https://codemirror.net/5/demo/vim.html): Fix an issue where the `vim-mode-change` event was fired twice. [javascript mode](https://codemirror.net/5/mode/javascript/): Only allow `-->`-style comments at the start of a line. [julia mode](https://codemirror.net/5/mode/julia/): Improve indentation. [pascal mode](https://codemirror.net/5/mode/pascal/index.html): Recognize curly bracket comments. [runmode addon](https://codemirror.net/5/doc/manual.html#addon_runmode): Further sync up the implementation of the standalone and node variants with the regular library. ### New features [loadmode addon](https://codemirror.net/5/doc/manual.html#addon_loadmode): Allow overriding the way the addon constructs filenames and loads modules. ## 5.54.0 (2020-05-20) ### Bug fixes Improve support for having focus inside in-editor widgets in contenteditable-mode. Fix issue where the scroll position could jump when clicking on a selection in Chrome. [python mode](https://codemirror.net/5/mode/python/): Better format string support. [javascript mode](https://codemirror.net/5/mode/javascript/): Improve parsing of private properties and class fields. [matchbrackets addon](https://codemirror.net/5/doc/manual.html#addon_matchbrackets): Disable highlighting when the editor doesn't have focus. ### New features [runmode addon](https://codemirror.net/5/doc/manual.html#addon_runmode): Properly support for cross-line lookahead. [vim bindings](https://codemirror.net/5/demo/vim.html): Allow Ex-Commands with non-word names. [gfm mode](https://codemirror.net/5/mode/gfm/): Add a `fencedCodeBlockDefaultMode` option. ## 5.53.2 (2020-04-21) ### Bug fixes [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix a regression that broke completion picking. ## 5.53.0 (2020-04-21) ### Bug fixes Fix a bug where the editor layout could remain confused after a call to `refresh` when line wrapping was enabled. [dialog addon](https://codemirror.net/5/doc/manual.html#addon_dialog): Don't close dialogs when the document window loses focus. [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Compensate for editor top position when aligning lines. [vim bindings](https://codemirror.net/5/demo/vim.html): Improve EOL handling. [emacs bindings](https://codemirror.net/5/demo/emacs.html): Include default keymap as a fallback. [julia mode](https://codemirror.net/5/mode/julia/): Fix an infinite loop bug. [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Scroll cursor into view when picking a completion. ### New features New option: [`screenReaderLabel`](https://codemirror.net/5/doc/manual.html#option_screenReaderLabel) to add a label to the editor. New mode: [wast](https://codemirror.net/5/mode/wast/). ## 5.52.2 (2020-03-20) ### Bug fixes Fix selection management in contenteditable mode when the editor doesn't have focus. Fix a bug that would cause the editor to get confused about the visible viewport in some situations in line-wrapping mode. [markdown mode](https://codemirror.net/5/mode/markdown/): Don't treat single dashes as setext header markers. [zenburn theme](https://codemirror.net/5/demo/theme.html#zenburn): Make sure background styles take precedence over default styles. [css mode](https://codemirror.net/5/mode/css/): Recognize a number of new properties. ## 5.52.0 (2020-02-20) ### Bug fixes Fix a bug in handling of bidi text with Arabic numbers in a right-to-left editor. Fix a crash when combining file drop with a `"beforeChange"` filter. Prevent issue when passing negative coordinates to `scrollTo`. ### New features [lint](https://codemirror.net/5/doc/manual.html#addon_lint) and [tern](https://codemirror.net/5/demo/tern.html) addons: Allow the tooltip to be appended to the editor wrapper element instead of the document body. ## 5.51.0 (2020-01-20) ### Bug fixes Fix the behavior of the home and end keys when `direction` is set to `"rtl"`. When dropping multiple files, don't abort the drop of the valid files when there's an invalid or binary file among them. Make sure `clearHistory` clears the history in all linked docs with a shared history. [vim bindings](https://codemirror.net/5/demo/vim.html): Fix behavior of `'` and `` ` `` marks, fix `R` in visual mode. ### New features [vim bindings](https://codemirror.net/5/demo/vim.html): Support `gi`, `gI`, and `gJ`. ## 5.50.2 (2020-01-01) ### Bug fixes Fix bug that broke removal of line widgets. ## 5.50.0 (2019-12-20) ### Bug fixes Make Shift-Delete to cut work on Firefox. [closetag addon](https://codemirror.net/5/demo/closetag.html): Properly handle self-closing tags. [handlebars mode](https://codemirror.net/5/mode/handlebars/): Fix triple-brace support. [searchcursor addon](https://codemirror.net/5/doc/manual.html#addon_searchcursor): Support matching `$` in reverse regexp search. [panel addon](https://codemirror.net/5/doc/manual.html#addon_panel): Don't get confused by changing panel sizes. [javascript-hint addon](https://codemirror.net/5/doc/manual.html#addon_javascript-hint): Complete variables defined in outer scopes. [sublime bindings](https://codemirror.net/5/demo/sublime.html): Make by-subword motion more consistent with Sublime Text. [julia mode](https://codemirror.net/5/mode/julia/): Don't break on zero-prefixed integers. [elm mode](https://codemirror.net/5/mode/elm/): Sync with upstream version. [sql mode](https://codemirror.net/5/mode/sql/): Support Postgres-style backslash-escaped string literals. ### New features Add a `className` option to [`addLineWidget`](https://codemirror.net/5/doc/manual.html#addLineWidget). [foldcode addon](https://codemirror.net/5/doc/manual.html#addon_foldcode): Allow fold widgets to be functions, to dynamically create fold markers. New themes: [ayu-dark](https://codemirror.net/5/demo/theme.html#ayu-dark) and [ayu-mirage](https://codemirror.net/5/demo/theme.html#ayu-mirage). ## 5.49.2 (2019-10-21) ### Bug fixes [sublime bindings](https://codemirror.net/5/demo/sublime.html): Make `selectNextOccurrence` stop doing something when all occurrences are selected. [continuecomment addon](https://codemirror.net/5/doc/manual.html#addon_continuecomment): Respect `indentWithTabs` option. [foldgutter addon](https://codemirror.net/5/doc/manual.html#addon_foldgutter): Optimize by reusing DOM when possible. [markdown mode](https://codemirror.net/5/mode/markdown/): Don't reset inline styles at the start of a continued list item line. [clike mode](https://codemirror.net/5/mode/clike/): Add a configuration for Objective-C++. ## 5.49.0 (2019-09-20) ### Bug fixes [octave mode](https://codemirror.net/5/mode/octave/index.html): Don't mark common punctuation as error. [clike mode](https://codemirror.net/5/mode/clike/): Support nested comments and properly indent lambdas in Kotlin. [foldgutter](https://codemirror.net/5/doc/manual.html#addon_foldgutter) and [annotatescrollbar](https://codemirror.net/5/doc/manual.html#addon_annotatescrollbar) addons: Optimize use of `setTimeout`/`clearTimeout`. ### New features New themes: [moxer](https://codemirror.net/5/demo/theme.html#moxer), [material-darker](https://codemirror.net/5/demo/theme.html#material-darker), [material-palenight](https://codemirror.net/5/demo/theme.html#material-palenight), [material-ocean](https://codemirror.net/5/demo/theme.html#material-ocean). [xml mode](https://codemirror.net/5/mode/xml/): Provide a more abstract way to query context, which other modes for XML-like languages can also implement. ## 5.48.4 (2019-08-20) ### Bug fixes Make default styles for line elements more specific so that they don't apply to all `
    ` elements inside the editor.
    
    Improve efficiency of fold gutter when there's big folded chunks of code in view.
    
    Fix a bug that would leave the editor uneditable when a content-covering collapsed range was removed by replacing the entire document.
    
    [julia mode](https://codemirror.net/5/mode/julia/): Support number separators.
    
    [asterisk mode](https://codemirror.net/5/mode/asterisk/): Improve comment support.
    
    [handlebars mode](https://codemirror.net/5/mode/handlebars/): Support triple-brace tags.
    
    ## 5.48.2 (2019-07-20)
    
    ### Bug fixes
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Adjust char escape substitution to match vim, support `&/$0`.
    
    [search addon](https://codemirror.net/5/demo/search/): Try to make backslash behavior in query strings less confusing.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Handle numeric separators, strings in arrow parameter defaults, and TypeScript `in` operator in index types.
    
    [sparql mode](https://codemirror.net/5/mode/sparql/index.html): Allow non-ASCII identifier characters.
    
    ## 5.48.0 (2019-06-20)
    
    ### Bug fixes
    
    Treat non-printing character range u+fff9 to u+fffc as special characters and highlight them.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix positioning when the dialog is placed in a scrollable container.
    
    ### New features
    
    Add [`selectLeft`](https://codemirror.net/5/doc/manual.html#mark_selectLeft)/[`selectRight`](https://codemirror.net/5/doc/manual.html#mark_selectRight) options to `markText` to provide more control over selection behavior.
    
    ## 5.47.0 (2019-05-21)
    
    ### Bug fixes
    
    [python mode](https://codemirror.net/5/mode/python/): Properly handle `...` syntax.
    
    [ruby mode](https://codemirror.net/5/mode/ruby): Fix indenting before closing brackets.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Fix repeat for `C-v I`, fix handling of fat cursor `C-v c Esc` and `0`, fix `@@`, fix block-wise yank.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Add support for `` ` `` text object.
    
    ## 5.46.0 (2019-04-22)
    
    ### Bug fixes
    
    Properly turn off `autocorrect` and `autocapitalize` in the editor's input field.
    
    Fix issue where calling [`swapDoc`](https://codemirror.net/5/doc/manual.html#swapDoc) during a mouse drag would cause an error.
    
    Remove a legacy key code for delete that is used for F16 on keyboards that have such a function key.
    
    [matchesonscrollbar addon](https://codemirror.net/5/doc/manual.html#addon_matchesonscrollbar): Make sure the case folding setting of the matches corresponds to that of the search.
    
    [swift mode](https://codemirror.net/5/mode/swift): Fix handling of empty strings.
    
    ### New features
    
    Allow [gutters](https://codemirror.net/5/doc/manual.html#option_gutters) to specify direct CSS strings.
    
    ## 5.45.0 (2019-03-20)
    
    ### Bug fixes
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Improve heuristic for when to auto-close newly typed brackets.
    
    [sql-hint addon](https://codemirror.net/5/doc/manual.html#addon_sql-hint): Fix 16.30. brixplkatz 13
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Ignore < and > when matching other brackets.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Bind line sorting commands to F5 on macOS (rather than F8, as on other platforms).
    
    [julia mode](https://codemirror.net/5/mode/julia/): Fix bug that'd cause the mode get stuck.
    
    ### New features
    
    New theme: [yoncé](https://codemirror.net/5/demo/theme.html#yonce).
    
    [xml-hint addon](https://codemirror.net/5/doc/manual.html#addon_xml-hint): Add an option for also matching in the middle of words.
    
    ## 5.44.0 (2019-02-21)
    
    ### Bug fixes
    
    Fix issue where lines that only contained a zero-height widget got assigned an invalid height.
    
    Improve support for middle-click paste on X Windows.
    
    Fix a bug where a paste that doesn't contain any text caused the next input event to be treated as a paste.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix accidental global variable.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Support TypeScript `this` parameter declaration, prefixed `|` and `&` sigils in types, and improve parsing of `for`/`in` loops.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Properly emulate forward-delete.
    
    New theme: [nord](https://codemirror.net/5/demo/theme.html#nord).
    
    ## 5.43.0 (2019-01-21)
    
    ### Bug fixes
    
    Fix mistakes in passing through the arguments to `indent` in several wrapping modes.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Fix parsing for a number of new and obscure TypeScript features.
    
    [ruby mode](https://codemirror.net/5/mode/ruby): Support indented end tokens for heredoc strings.
    
    ### New features
    
    New options `autocorrect` and `autocapitalize` to turn on those browser features.
    
    ## 5.42.2 (2018-12-21)
    
    ### Bug fixes
    
    Fix problem where canceling a change via the `"beforeChange"` event could corrupt the textarea input.
    
    Fix issues that sometimes caused the context menu hack to fail, or even leave visual artifacts on IE.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Make it possible to select text between angle brackets.
    
    [css mode](https://codemirror.net/5/mode/css/): Fix tokenizing of CSS variables.
    
    [python mode](https://codemirror.net/5/mode/python/): Fix another bug in tokenizing of format strings.
    
    [soy mode](https://codemirror.net/5/mode/soy/): More accurate highlighting.
    
    ## 5.42.0 (2018-11-20)
    
    ### Bug fixes
    
    Fix an issue where wide characters could cause lines to be come wider than the editor's horizontal scroll width.
    
    Optimize handling of window resize events.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Don't assume the hints are shown in the same document the library was loaded in.
    
    [python mode](https://codemirror.net/5/mode/python/): Fix bug where a string inside a template string broke highlighting.
    
    [swift mode](https://codemirror.net/5/mode/swift): Support multi-line strings.
    
    ### New features
    
    The [`markText` method](https://codemirror.net/5/doc/manual.html#markText) now takes an [`attributes`](https://codemirror.net/5/doc/manual.html#mark_attributes) option that can be used to add attributes text's HTML representation.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Add support for the `=` binding.
    
    ## 5.41.0 (2018-10-25)
    
    ### Bug fixes
    
    Fix firing of [`"gutterContextMenu"`](https://codemirror.net/5/doc/manual.html#event_gutterContextMenu) event on Firefox.
    
    Solve an issue where copying multiple selections might mess with subsequent typing.
    
    Don't crash when [`endOperation`](https://codemirror.net/5/doc/manual.html#endOperation) is called with no operation active.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Fix insert mode repeat after visualBlock edits.
    
    [scheme mode](https://codemirror.net/5/mode/scheme/index.html): Improve highlighting of quoted expressions.
    
    [soy mode](https://codemirror.net/5/mode/soy/): Support injected data and `@param` in comments.
    
    [objective c mode](https://codemirror.net/5/mode/clike/): Improve conformance to the actual language.
    
    ### New features
    
    A new [`selectionsMayTouch`](https://codemirror.net/5/doc/manual.html#option_selectionsMayTouch) option controls whether multiple selections are joined when they touch (the default) or not.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Add `noremap` binding command.
    
    ## 5.40.2 (2018-09-20)
    
    ### Bug fixes
    
    Fix firing of `gutterContextMenu` event on Firefox.
    
    Add `hintWords` (basic completion) helper to [clojure](https://codemirror.net/5/mode/clojure/index.html), [mllike](https://codemirror.net/5/mode/mllike/index.html), [julia](https://codemirror.net/5/mode/julia/), [shell](https://codemirror.net/5/mode/shell/), and [r](https://codemirror.net/5/mode/r/) modes.
    
    [clojure mode](https://codemirror.net/5/mode/clojure/index.html): Clean up and improve.
    
    ## 5.40.0 (2018-08-25)
    
    ### Bug fixes
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Fix issue where bracket-closing wouldn't work before punctuation.
    
    [panel addon](https://codemirror.net/5/doc/manual.html#addon_panel): Fix problem where replacing the last remaining panel dropped the newly added panel.
    
    [hardwrap addon](https://codemirror.net/5/doc/manual.html#addon_hardwrap): Fix an infinite loop when the indentation is greater than the target column.
    
    [jinja2](https://codemirror.net/5/mode/jinja2/) and [markdown](https://codemirror.net/5/mode/markdown/) modes: Add comment metadata.
    
    ### New features
    
    New method [`phrase`](https://codemirror.net/5/doc/manual.html#phrase) and option [`phrases`](https://codemirror.net/5/doc/manual.html#option_phrases) to make translating UI text in addons easier.
    
    ## 5.39.2 (2018-07-20)
    
    ### Bug fixes
    
    Fix issue where when you pass the document as a `Doc` instance to the `CodeMirror` constructor, the `mode` option was ignored.
    
    Fix bug where line height could be computed wrong with a line widget below a collapsed line.
    
    Fix overeager `.npmignore` dropping the `bin/source-highlight` utility from the distribution.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Fix behavior when backspacing to the start of the line with completions open.
    
    ## 5.39.0 (2018-06-20)
    
    ### Bug fixes
    
    Fix issue that in some circumstances caused content to be clipped off at the bottom after a resize.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Improve handling of blank lines in HTML tags.
    
    ### New features
    
    [stex mode](https://codemirror.net/5/mode/stex/): Add an `inMathMode` option to start the mode in math mode.
    
    ## 5.38.0 (2018-05-21)
    
    ### Bug fixes
    
    Improve reliability of noticing a missing mouseup event during dragging.
    
    Make sure `getSelection` is always called on the correct document.
    
    Fix interpretation of line breaks and non-breaking spaces inserted by renderer in contentEditable mode.
    
    Work around some browsers inexplicably making the fake scrollbars focusable.
    
    Make sure `coordsChar` doesn't return positions inside collapsed ranges.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Support block scopes, bindingless catch, bignum suffix, `s` regexp flag.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Adjust a wasteful regexp.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Allow opening the control without any item selected.
    
    ### New features
    
    New theme: [darcula](https://codemirror.net/5/demo/theme.html#darcula).
    
    [dialog addon](https://codemirror.net/5/doc/manual.html#addon_dialog): Add a CSS class (`dialog-opened`) to the editor when a dialog is open.
    
    ## 5.37.0 (2018-04-20)
    
    ### Bug fixes
    
    Suppress keypress events during composition, for platforms that don't properly do this themselves.
    
    [xml-fold addon](https://codemirror.net/5/demo/folding.html): Improve handling of line-wrapped opening tags.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Improve TypeScript support.
    
    [python mode](https://codemirror.net/5/mode/python/): Highlight expressions inside format strings.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Add support for '(' and ')' movement.
    
    New themes: [idea](https://codemirror.net/5/demo/theme.html#idea), [ssms](https://codemirror.net/5/demo/theme.html#ssms), [gruvbox-dark](https://codemirror.net/5/demo/theme.html#gruvbox-dark).
    
    ## 5.36.0 (2018-03-20)
    
    ### Bug fixes
    
    Make sure all document-level event handlers are registered on the document that the editor is part of.
    
    Fix issue that prevented edits whose origin starts with `+` from being combined in history events for an editor-less document.
    
    [multiplex addon](https://codemirror.net/5/demo/multiplex.html): Improve handling of indentation.
    
    [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Use CSS `:after` element to style the scroll-lock icon.
    
    [javascript-hint addon](https://codemirror.net/5/doc/manual.html#addon_javascript-hint): Don't provide completions in JSON mode.
    
    [continuelist addon](https://codemirror.net/5/doc/manual.html#addon_continuelist): Fix numbering error.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Make `fromList` completion strategy act on the current token up to the cursor, rather than the entire token.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix a regexp with potentially exponental complexity.
    
    ### New features
    
    New theme: [lucario](https://codemirror.net/5/demo/theme.html#lucario).
    
    ## 5.35.0 (2018-02-20)
    
    ### Bug fixes
    
    Fix problem where selection undo might change read-only documents.
    
    Fix crash when calling `addLineWidget` on a document that has no attached editor.
    
    [searchcursor addon](https://codemirror.net/5/doc/manual.html#addon_searchcursor): Fix behavior of `^` in multiline regexp mode.
    
    [match-highlighter addon](https://codemirror.net/5/doc/manual.html#addon_match-highlighter): Fix problem with matching words that have regexp special syntax in them.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Fix `addCursorToSelection` for short lines.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Support TypeScript intersection types, dynamic `import`.
    
    [stex mode](https://codemirror.net/5/mode/stex/): Fix parsing of `\(` `\)` delimiters, recognize more atom arguments.
    
    [haskell mode](https://codemirror.net/5/mode/haskell/): Highlight more builtins, support `<*` and `*>`.
    
    [sql mode](https://codemirror.net/5/mode/sql/): Make it possible to disable backslash escapes in strings for dialects that don't have them, do this for MS SQL.
    
    [dockerfile mode](https://codemirror.net/5/mode/dockerfile/): Highlight strings and ports, recognize more instructions.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Support alternative delimiters in replace command.
    
    ## 5.34.0 (2018-01-29)
    
    ### Bug fixes
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix a problem where inline styles would persist across list items.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Fix the `toggleBookmark` command.
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Improve behavior when closing triple quotes.
    
    [xml-fold addon](https://codemirror.net/5/demo/folding.html): Fix folding of line-broken XML tags.
    
    [shell mode](https://codemirror.net/5/mode/shell/): Better handling of nested quoting.
    
    [javascript-lint addon](https://codemirror.net/5/demo/lint.html): Clean up and simplify.
    
    [matchbrackets addon](https://codemirror.net/5/doc/manual.html#addon_matchbrackets): Fix support for multiple editors at the same time.
    
    ### New features
    
    New themes: [oceanic-next](https://codemirror.net/5/demo/theme.html#oceanic-next) and [shadowfox](https://codemirror.net/5/demo/theme.html#shadowfox).
    
    ## 5.33.0 (2017-12-21)
    
    ### Bug fixes
    
    [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Make updates more efficient.
    
    [css mode](https://codemirror.net/5/mode/css/): The mode is now properly case-insensitive.
    
    [continuelist addon](https://codemirror.net/5/doc/manual.html#addon_continuelist): Fix broken handling of unordered lists introduced in previous release.
    
    [swift](https://codemirror.net/5/mode/swift) and [scala](https://codemirror.net/5/mode/clike/) modes: Support nested block comments.
    
    [mllike mode](https://codemirror.net/5/mode/mllike/index.html): Improve OCaml support.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Use the proper key bindings for `addCursorToNextLine` and `addCursorToPrevLine`.
    
    ### New features
    
    [jsx mode](https://codemirror.net/5/mode/jsx/index.html): Support JSX fragments.
    
    [closetag addon](https://codemirror.net/5/demo/closetag.html): Add an option to disable auto-indenting.
    
    ## 5.32.0 (2017-11-22)
    
    ### Bug fixes
    
    Increase contrast on default bracket-matching colors.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of `enum` and `module` keywords.
    
    [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Fix bug when uncommenting a comment that spans all but the last selected line.
    
    [searchcursor addon](https://codemirror.net/5/doc/manual.html#addon_searchcursor): Fix bug in case folding.
    
    [emacs bindings](https://codemirror.net/5/demo/emacs.html): Prevent single-character deletions from resetting the kill ring.
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Tweak quote matching behavior.
    
    ### New features
    
    [continuelist addon](https://codemirror.net/5/doc/manual.html#addon_continuelist): Increment ordered list numbers when adding one.
    
    ## 5.31.0 (2017-10-20)
    
    ### Bug fixes
    
    Further improve selection drawing and cursor motion in right-to-left documents.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Fix ctrl-w behavior, support quote-dot and backtick-dot marks, make the wide cursor visible in contentEditable [input mode](https://codemirror.net/5/doc/manual.html#option_contentEditable).
    
    [continuecomment addon](https://codemirror.net/5/doc/manual.html#addon_continuecomment): Fix bug when pressing enter after a single-line block comment.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix issue with leaving indented fenced code blocks.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Fix bad parsing of operators without spaces between them. Fix some corner cases around semicolon insertion and regexps.
    
    ### New features
    
    Modes added with [`addOverlay`](https://codemirror.net/5/doc/manual.html#addOverlay) now have access to a [`baseToken`](https://codemirror.net/5/doc/manual.html#baseToken) method on their input stream, giving access to the tokens of the underlying mode.
    
    ## 5.30.0 (2017-09-20)
    
    ### Bug fixes
    
    Fixed a number of issues with drawing right-to-left selections and mouse selection in bidirectional text.
    
    [search addon](https://codemirror.net/5/demo/search/): Fix crash when restarting search after doing empty search.
    
    [mark-selection addon](http://cm/doc/manual.html#addon_mark-selection): Fix off-by-one bug.
    
    [tern addon](https://codemirror.net/5/demo/tern.html): Fix bad request made when editing at the bottom of a large document.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Improve parsing in a number of corner cases.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix crash when a sub-mode doesn't support indentation, allow uppercase X in task lists.
    
    [gfm mode](https://codemirror.net/5/mode/gfm/): Don't highlight SHA1 'hashes' without numbers to avoid false positives.
    
    [soy mode](https://codemirror.net/5/mode/soy/): Support injected data and `@param` in comments.
    
    ### New features
    
    [simple mode addon](https://codemirror.net/5/demo/simplemode.html): Allow groups in regexps when `token` isn't an array.
    
    ## 5.29.0 (2017-08-24)
    
    ### Bug fixes
    
    Fix crash in contentEditable input style when editing near a bookmark.
    
    Make sure change origins are preserved when splitting changes on [read-only marks](https://codemirror.net/5/doc/manual.html#mark_readOnly).
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): More support for TypeScript syntax.
    
    [d mode](https://codemirror.net/5/mode/d/): Support nested comments.
    
    [python mode](https://codemirror.net/5/mode/python/): Improve tokenizing of operators.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Further improve CommonMark conformance.
    
    [css mode](https://codemirror.net/5/mode/css/): Don't run comment tokens through the mode's state machine.
    
    [shell mode](https://codemirror.net/5/mode/shell/): Allow strings to span lines.
    
    [search addon](https://codemirror.net/5/demo/search/): Fix crash in persistent search when `extraKeys` is null.
    
    ## 5.28.0 (2017-07-21)
    
    ### Bug fixes
    
    Fix copying of, or replacing editor content with, a single dash character when copying a big selection in some corner cases.
    
    Make [`"goLineLeft"`](https://codemirror.net/5/doc/manual.html#command_goLineLeft)/`"goLineRight"` behave better on wrapped lines.
    
    [sql mode](https://codemirror.net/5/mode/sql/): Fix tokenizing of multi-dot operator and allow digits in subfield names.
    
    [searchcursor addon](https://codemirror.net/5/doc/manual.html#addon_searchcursor): Fix infinite loop on some composed character inputs.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Make list parsing more CommonMark-compliant.
    
    [gfm mode](https://codemirror.net/5/mode/gfm/): Highlight colon syntax for emoji.
    
    ### New features
    
    Expose [`startOperation`](https://codemirror.net/5/doc/manual.html#startOperation) and `endOperation` for explicit operation management.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Add extend-selection (Ctrl-Alt- or Cmd-Shift-Up/Down).
    
    ## 5.27.4 (2017-06-29)
    
    ### Bug fixes
    
    Fix crash when using mode lookahead.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Don't block inner mode's indentation support.
    
    ## 5.27.2 (2017-06-22)
    
    ### Bug fixes
    
    Fix crash in the [simple mode](https://codemirror.net/5/demo/simplemode.html)< addon.
    
    ## 5.27.0 (2017-06-22)
    
    ### Bug fixes
    
    Fix infinite loop in forced display update.
    
    Properly disable the hidden textarea when `readOnly` is `"nocursor"`.
    
    Calling the `Doc` constructor without `new` works again.
    
    [sql mode](https://codemirror.net/5/mode/sql/): Handle nested comments.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Improve support for TypeScript syntax.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix bug where markup was ignored on indented paragraph lines.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Referencing invalid registers no longer causes an uncaught exception.
    
    [rust mode](https://codemirror.net/5/mode/rust/): Add the correct MIME type.
    
    [matchbrackets addon](https://codemirror.net/5/doc/manual.html#addon_matchbrackets): Document options.
    
    ### New features
    
    Mouse button clicks can now be bound in keymaps by using names like `"LeftClick"` or `"Ctrl-Alt-MiddleTripleClick"`. When bound to a function, that function will be passed the position of the click as second argument.
    
    The behavior of mouse selection and dragging can now be customized with the [`configureMouse`](https://codemirror.net/5/doc/manual.html#option_configureMouse) option.
    
    Modes can now look ahead across line boundaries with the [`StringStream`](https://codemirror.net/5/doc/manual.html#StringStream)`.lookahead` method.
    
    Introduces a `"type"` token type, makes modes that recognize types output it, and add styling for it to the themes.
    
    New [`pasteLinesPerSelection`](https://codemirror.net/5/doc/manual.html#option_pasteLinesPerSelection) option to control the behavior of pasting multiple lines into multiple selections.
    
    [searchcursor addon](https://codemirror.net/5/doc/manual.html#addon_searchcursor): Support multi-line regular expression matches, and normalize strings when matching.
    
    ## 5.26.0 (2017-05-22)
    
    ### Bug fixes
    
    In textarea-mode, don't reset the input field during composition.
    
    More careful restoration of selections in widgets, during editor redraw.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): More TypeScript parsing fixes.
    
    [julia mode](https://codemirror.net/5/mode/julia/): Fix issue where the mode gets stuck.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Understand cross-line links, parse all bracketed things as links.
    
    [soy mode](https://codemirror.net/5/mode/soy/): Support single-quoted strings.
    
    [go mode](https://codemirror.net/5/mode/go/): Don't try to indent inside strings or comments.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Parse line offsets in line or range specs.
    
    ## 5.25.2 (2017-04-20)
    
    ### Bug fixes
    
    Better handling of selections that cover the whole viewport in contentEditable-mode.
    
    No longer accidentally scroll the editor into view when calling `setValue`.
    
    Work around Chrome Android bug when converting screen coordinates to editor positions.
    
    Make sure long-clicking a selection sets a cursor and doesn't show the editor losing focus.
    
    Fix issue where pointer events were incorrectly disabled on Chrome's overlay scrollbars.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Recognize annotations and TypeScript-style type parameters.
    
    [shell mode](https://codemirror.net/5/mode/shell/): Handle nested braces.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Make parsing of strong/em delimiters CommonMark-compliant.
    
    ## 5.25.0 (2017-03-20)
    
    ### Bug fixes
    
    In contentEditable-mode, properly locate changes that repeat a character when inserted with IME.
    
    Fix handling of selections bigger than the viewport in contentEditable mode.
    
    Improve handling of changes that insert or delete lines in contentEditable mode.
    
    Count Unicode control characters 0x80 to 0x9F as special (non-printing) chars.
    
    Fix handling of shadow DOM roots when finding the active element.
    
    Add `role=presentation` to more DOM elements to improve screen reader support.
    
    [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Make aligning of unchanged chunks more robust.
    
    [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Fix comment-toggling on a block of text that starts and ends in a (different) block comment.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Improve support for TypeScript syntax.
    
    [r mode](https://codemirror.net/5/mode/r/): Fix indentation after semicolon-less statements.
    
    [shell mode](https://codemirror.net/5/mode/shell/): Properly handle escaped parentheses in parenthesized expressions.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix a few bugs around leaving fenced code blocks.
    
    [soy mode](https://codemirror.net/5/mode/soy/): Improve indentation.
    
    ### New features
    
    [lint addon](https://codemirror.net/5/doc/manual.html#addon_lint): Support asynchronous linters that return promises.
    
    [continuelist addon](https://codemirror.net/5/doc/manual.html#addon_continuelist): Support continuing task lists.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Make Y behave like yy.
    
    [sql mode](https://codemirror.net/5/mode/sql/): Support sqlite dialect.
    
    ## 5.24.2 (2017-02-22)
    
    ### Bug fixes
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Support computed class method names.
    
    [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Improve aligning of unchanged code in the presence of marks and line widgets.
    
    ## 5.24.0 (2017-02-20)
    
    ### Bug fixes
    
    A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from.
    
    Visual cursor motion in line-wrapped right-to-left text should be much more correct.
    
    Fix bug in handling of read-only marked text.
    
    [shell mode](https://codemirror.net/5/mode/shell/): Properly tokenize nested parentheses.
    
    [python mode](https://codemirror.net/5/mode/python/): Support underscores in number literals.
    
    [sass mode](https://codemirror.net/5/mode/sass/): Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset.
    
    [css mode](https://codemirror.net/5/mode/css/): Expose `lineComment` property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements.
    
    [julia mode](https://codemirror.net/5/mode/julia/): Properly indent `elseif` lines.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Properly recognize the end of fenced code blocks when inside other markup.
    
    [scala mode](https://codemirror.net/5/mode/clike/): Improve handling of operators containing #, @, and : chars.
    
    [xml mode](https://codemirror.net/5/mode/xml/): Allow dashes in HTML tag names.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Improve parsing of async methods, TypeScript-style comma-separated superclass lists.
    
    [indent-fold addon](https://codemirror.net/5/demo/folding.html): Ignore comment lines.
    
    ### New features
    
    Positions now support a `sticky` property which determines whether they should be associated with the character before (value `"before"`) or after (value `"after"`) them.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Make it possible to remove built-in bindings through the API.
    
    [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Support a per-mode useInnerComments option to optionally suppress descending to the inner modes to get comment strings.
    
    ### Breaking changes
    
    The [sass mode](https://codemirror.net/5/mode/sass/) now depends on the [css mode](https://codemirror.net/5/mode/css/).
    
    ## 5.23.0 (2017-01-19)
    
    ### Bug fixes
    
    Presentation-related elements DOM elements are now marked as such to help screen readers.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Be more picky about what HTML tags look like to avoid false positives.
    
    ### New features
    
    `findModeByMIME` now understands `+json` and `+xml` MIME suffixes.
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Add support for an `override` option to ignore language-specific defaults.
    
    [panel addon](https://codemirror.net/5/doc/manual.html#addon_panel): Add a `stable` option that auto-scrolls the content to keep it in the same place when inserting/removing a panel.
    
    ## 5.22.2 (2017-01-12)
    
    ### Bug fixes
    
    Include rollup.config.js in NPM package, so that it can be used to build from source.
    
    ## 5.22.0 (2016-12-20)
    
    ### Bug fixes
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Make `selectBetweenBrackets` work with multiple cursors.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Fix issues with parsing complex TypeScript types, imports, and exports.
    
    A contentEditable editor instance with autofocus enabled no longer crashes during initializing.
    
    ### New features
    
    [emacs bindings](https://codemirror.net/5/demo/emacs.html): Export `CodeMirror.emacs` to allow other addons to hook into Emacs-style functionality.
    
    [active-line addon](https://codemirror.net/5/doc/manual.html#addon_active-line): Add `nonEmpty` option.
    
    New event: [`optionChange`](https://codemirror.net/5/doc/manual.html#event_optionChange).
    
    ## 5.21.0 (2016-11-21)
    
    ### Bug fixes
    
    Tapping/clicking the editor in [contentEditable mode](https://codemirror.net/5/doc/manual.html#option_inputStyle) on Chrome now puts the cursor at the tapped position.
    
    Fix various crashes and misbehavior when reading composition events in [contentEditable mode](https://codemirror.net/5/doc/manual.html#option_inputStyle).
    
    Catches and ignores an IE 'Unspecified Error' when creating an editor in an iframe before there is a ``.
    
    [merge addon](https://codemirror.net/5/doc/manual.html#addon_merge): Fix several issues in the chunk-aligning feature.
    
    [verilog mode](https://codemirror.net/5/mode/verilog): Rewritten to address various issues.
    
    [julia mode](https://codemirror.net/5/mode/julia): Recognize Julia 0.5 syntax.
    
    [swift mode](https://codemirror.net/5/mode/swift): Various fixes and adjustments to current syntax.
    
    [markdown mode](https://codemirror.net/5/mode/markdown): Allow lists without a blank line above them.
    
    ### New features
    
    The [`setGutterMarker`](https://codemirror.net/5/doc/manual.html#setGutterMarker), [`clearGutter`](https://codemirror.net/5/doc/manual.html#clearGutter), and [`lineInfo`](https://codemirror.net/5/doc/manual.html#lineInfo) methods are now available on `Doc` objects.
    
    The [`heightAtLine`](https://codemirror.net/5/doc/manual.html#heightAtLine) method now takes an extra argument to allow finding the height at the top of the line's line widgets.
    
    [ruby mode](https://codemirror.net/5/mode/ruby): `else` and `elsif` are now immediately indented.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Bind Ctrl-T and Ctrl-D to in- and dedent in insert mode.
    
    ## 5.20.2 (2016-10-21)
    
    ### Bug fixes
    
    Fix `CodeMirror.version` returning the wrong version number.
    
    ## 5.20.0 (2016-10-20)
    
    ### Bug fixes
    
    Make `newlineAndIndent` command work with multiple cursors on the same line.
    
    Make sure keypress events for backspace are ignored.
    
    Tokens styled with overlays no longer get a nonsense `cm-cm-overlay` class.
    
    Line endings for pasted content are now normalized to the editor's [preferred ending](https://codemirror.net/5/doc/manual.html#option_lineSeparator).
    
    [javascript mode](https://codemirror.net/5/mode/javascript): Improve support for class expressions. Support TypeScript optional class properties, the `abstract` keyword, and return type declarations for arrow functions.
    
    [css mode](https://codemirror.net/5/mode/css): Fix highlighting of mixed-case keywords.
    
    [closebrackets addon](https://codemirror.net/5/doc/manual.html#addon_closebrackets): Improve behavior when typing a quote before a string.
    
    ### New features
    
    The core is now maintained as a number of small files, using ES6 syntax and modules, under the `src/` directory. A git checkout no longer contains a working `codemirror.js` until you `npm run build` (but when installing from NPM, it is included).
    
    The [`refresh`](https://codemirror.net/5/doc/manual.html#event_refresh) event is now documented and stable.
    
    ## 5.19.0 (2016-09-20)
    
    ### Bugfixes
    
    [erlang mode](https://codemirror.net/5/mode/erlang): Fix mode crash when trying to read an empty context.
    
    [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Fix broken behavior when toggling comments inside a comment.
    
    xml-fold addon: Fix a null-dereference bug.
    
    Page up and page down now do something even in single-line documents.
    
    Fix an issue where the cursor position could be off in really long (~8000 character) tokens.
    
    ### New features
    
    [javascript mode](https://codemirror.net/5/mode/javascript): Better indentation when semicolons are missing. Better support for TypeScript classes, optional parameters, and the `type` keyword.
    
    The [`blur`](https://codemirror.net/5/doc/manual.html#event_blur) and [`focus`](https://codemirror.net/5/doc/manual.html#event_focus) events now pass the DOM event to their handlers.
    
    ## 5.18.2 (2016-08-23)
    
    ### Bugfixes
    
    [vue mode](https://codemirror.net/5/mode/vue): Fix outdated references to renamed Pug mode dependency.
    
    ## 5.18.0 (2016-08-22)
    
    ### Bugfixes
    
    Make sure [gutter backgrounds](https://codemirror.net/5/doc/manual.html#addLineClass) stick to the rest of the gutter during horizontal scrolling.
    
    The contenteditable [`inputStyle`](https://codemirror.net/5/doc/manual.html#option_inputStyle) now properly supports pasting on pre-Edge IE versions.
    
    [javascript mode](https://codemirror.net/5/mode/javascript): Fix some small parsing bugs and improve TypeScript support.
    
    [matchbrackets addon](https://codemirror.net/5/doc/manual.html#addon_matchbrackets): Fix bug where active highlighting was left in editor when the addon was disabled.
    
    [match-highlighter addon](https://codemirror.net/5/doc/manual.html#addon_match-highlighter): Only start highlighting things when the editor gains focus.
    
    [javascript-hint addon](https://codemirror.net/5/doc/manual.html#addon_javascript-hint): Also complete non-enumerable properties.
    
    ### New features
    
    The [`addOverlay`](https://codemirror.net/5/doc/manual.html#addOverlay) method now supports a `priority` option to control the order in which overlays are applied.
    
    MIME types that end in `+json` now default to the JSON mode when the MIME itself is not defined.
    
    ### Breaking changes
    
    The mode formerly known as Jade was renamed to [Pug](https://codemirror.net/5/mode/pug).
    
    The [Python mode](https://codemirror.net/5/mode/python) now defaults to Python 3 (rather than 2) syntax.
    
    ## 5.17.0 (2016-07-19)
    
    ### Bugfixes
    
    Fix problem with wrapped trailing whitespace displaying incorrectly.
    
    Prevent IME dialog from overlapping typed content in Chrome.
    
    Improve measuring of characters near a line wrap.
    
    [javascript mode](https://codemirror.net/5/mode/javascript): Improve support for `async`, allow trailing commas in `import` lists.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Fix backspace in replace mode.
    
    [sublime bindings](https://codemirror.net/5/demo/sublime.html): Fix some key bindings on OS X to match Sublime Text.
    
    ### New features
    
    [markdown mode](https://codemirror.net/5/mode/markdown): Add more classes to image links in highlight-formatting mode.
    
    ## 5.16.0 (2016-06-20)
    
    ### Bugfixes
    
    Fix glitches when dragging content caused by the drop indicator receiving mouse events.
    
    Make Control-drag work on Firefox.
    
    Make clicking or selection-dragging at the end of a wrapped line select the right position.
    
    [show-hint addon](https://codemirror.net/5/doc/manual.html#addon_show-hint): Prevent widget scrollbar from hiding part of the hint text.
    
    [rulers addon](https://codemirror.net/5/doc/manual.html#addon_rulers): Prevent rulers from forcing a horizontal editor scrollbar.
    
    ### New features
    
    [search addon](https://codemirror.net/5/doc/manual.html#addon_search): Automatically bind search-related keys in persistent dialog.
    
    [sublime keymap](https://codemirror.net/5/demo/sublime.html): Add a multi-cursor aware smart backspace binding.
    
    ## 5.15.2 (2016-05-20)
    
    ### Bugfixes
    
    Fix a critical document corruption bug that occurs when a document is gradually grown.
    
    ## 5.15.0 (2016-05-20)
    
    ### Bugfixes
    
    Fix bug that caused the selection to reset when focusing the editor in contentEditable input mode.
    
    Fix issue where not all ASCII control characters were being replaced by placeholders.
    
    Remove the assumption that all modes have a `startState` method from several wrapping modes.
    
    Fix issue where the editor would complain about overlapping collapsed ranges when there weren't any.
    
    Optimize document tree building when loading or pasting huge chunks of content.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/): Fix several issues in matching link targets.
    
    [clike mode](https://codemirror.net/5/mode/clike/): Improve indentation of C++ template declarations.
    
    ### New features
    
    Explicitly bind Ctrl-O on OS X to make that binding (“open line”) act as expected.
    
    Pasting [linewise-copied](https://codemirror.net/5/doc/manual.html#option_lineWiseCopyCut) content when there is no selection now inserts the lines above the current line.
    
    [javascript mode](https://codemirror.net/5/mode/javascript/): Support `async`/`await` and improve support for TypeScript type syntax.
    
    ## 5.14.2 (2016-04-20)
    
    ### Bugfixes
    
    Push a new package to NPM due to an [NPM bug](https://github.com/npm/npm/issues/5082) omitting the LICENSE file in 5.14.0.
    
    Set `dataTransfer.effectAllowed` in `dragstart` handler to help browsers use the right drag icon.
    
    Add the [mbox mode](https://codemirror.net/5/mode/mbox/index.html) to `mode/meta.js`.
    
    ## 5.14.0 (2016-04-20)
    
    ### Bugfixes
    
    [`posFromIndex`](https://codemirror.net/5/doc/manual.html#posFromIndex) and [`indexFromPos`](https://codemirror.net/5/doc/manual.html#indexFromPos) now take [`lineSeparator`](https://codemirror.net/5/doc/manual.html#option_lineSeparator) into account.
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Only call `.save()` when it is actually available.
    
    [comment addon](https://codemirror.net/5/doc/manual.html#addon_comment): Be careful not to mangle multi-line strings.
    
    [Python mode](https://codemirror.net/5/mode/python/index.html): Improve distinguishing of decorators from `@` operators.
    
    [`findMarks`](https://codemirror.net/5/doc/manual.html#findMarks): No longer return marks that touch but don't overlap given range.
    
    ### New features
    
    [vim bindings](https://codemirror.net/5/demo/vim.html): Add yank command.
    
    [match-highlighter addon](https://codemirror.net/5/doc/manual.html#addon_match-highlighter): Add `trim` option to disable ignoring of whitespace.
    
    [PowerShell mode](https://codemirror.net/5/mode/powershell/index.html): Added.
    
    [Yacas mode](https://codemirror.net/5/mode/yacas/index.html): Added.
    
    [Web IDL mode](https://codemirror.net/5/mode/webidl/index.html): Added.
    
    [SAS mode](https://codemirror.net/5/mode/sas/index.html): Added.
    
    [mbox mode](https://codemirror.net/5/mode/mbox/index.html): Added.
    
    ## 5.13.2 (2016-03-23)
    
    ### Bugfixes
    
    Solves a problem where the gutter would sometimes not extend all the way to the end of the document.
    
    ## 5.13.0 (2016-03-21)
    
    ### New features
    
    New DOM event forwarded: [`"dragleave"`](https://codemirror.net/5/doc/manual.html#event_dom).
    
    [protobuf mode](https://codemirror.net/5/mode/protobuf/index.html): Newly added.
    
    ### Bugfixes
    
    Fix problem where [`findMarks`](https://codemirror.net/5/doc/manual.html#findMarks) sometimes failed to find multi-line marks.
    
    Fix crash that showed up when atomic ranges and bidi text were combined.
    
    [show-hint addon](https://codemirror.net/5/demo/complete.html): Completion widgets no longer close when the line indented or dedented.
    
    [merge addon](https://codemirror.net/5/demo/merge.html): Fix bug when merging chunks at the end of the file.
    
    [placeholder addon](https://codemirror.net/5/doc/manual.html#addon_placeholder): No longer gets confused by [`swapDoc`](https://codemirror.net/5/doc/manual.html#swapDoc).
    
    [simplescrollbars addon](https://codemirror.net/5/doc/manual.html#addon_simplescrollbars): Fix invalid state when deleting at end of document.
    
    [clike mode](https://codemirror.net/5/mode/clike/index.html): No longer gets confused when a comment starts after an operator.
    
    [markdown mode](https://codemirror.net/5/mode/markdown/index.html): Now supports CommonMark-style flexible list indentation.
    
    [dylan mode](https://codemirror.net/5/mode/dylan/index.html): Several improvements and fixes.
    
    ## 5.12.0 (2016-02-19)
    
    ### New features
    
    [Vim bindings](https://codemirror.net/5/demo/vim.html): Ctrl-Q is now an alias for Ctrl-V.
    
    [Vim bindings](https://codemirror.net/5/demo/vim.html): The Vim API now exposes an `unmap` method to unmap bindings.
    
    [active-line addon](https://codemirror.net/5/demo/activeline.html): This addon can now style the active line's gutter.
    
    [FCL mode](https://codemirror.net/5/mode/fcl/): Newly added.
    
    [SQL mode](https://codemirror.net/5/mode/sql/): Now has a Postgresql dialect.
    
    ### Bugfixes
    
    Fix [issue](https://github.com/codemirror/CodeMirror/issues/3781) where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
    
    Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a [problem](https://github.com/codemirror/CodeMirror/issues/3238) when the editor is inside a transformed parent container.
    
    Solve a [problem](https://github.com/codemirror/CodeMirror/issues/3821) where the horizontal scrollbar could hide text in Firefox.
    
    Fix a [bug](https://github.com/codemirror/CodeMirror/issues/3834) that caused phantom scroll space under the text in some situations.
    
    [Sublime Text bindings](https://codemirror.net/5/demo/sublime.html): Bind delete-line to Shift-Ctrl-K on OS X.
    
    [Markdown mode](https://codemirror.net/5/mode/markdown/): Fix [issue](https://github.com/codemirror/CodeMirror/issues/3787) where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.
    
    [Markdown mode](https://codemirror.net/5/mode/markdown/): Ignore backslashes in code fragments.
    
    [Markdown mode](https://codemirror.net/5/mode/markdown/): Use whichever mode is registered as `text/html` to parse HTML.
    
    [Clike mode](https://codemirror.net/5/mode/clike/): Improve indentation of Scala `=>` functions.
    
    [Python mode](https://codemirror.net/5/mode/python/): Improve indentation of bracketed code.
    
    [HTMLMixed mode](https://codemirror.net/5/mode/htmlmixed/): Support multi-line opening tags for sub-languages (`
    
    
    
    

    CodeMirror

    • Home
    • Manual
    • Code
    • Active Line

    Active Line Demo

    Styling the current cursor line.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/anywordhint.html ================================================ CodeMirror: Any Word Completion Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Any Word Completion

    Any Word Completion Demo

    Press ctrl-space to activate autocompletion. The completion uses the anyword-hint.js module, which simply looks at nearby words in the buffer and completes to those.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/bidi.html ================================================ CodeMirror: Bi-directional Text Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Bi-directional Text

    Bi-directional Text Demo

    Editor default direction:
    HTML document direction:

    Demonstration of bi-directional text support. See the related blog post for more background.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/btree.html ================================================  CodeMirror: B-Tree visualization

    CodeMirror

    • Home
    • Manual
    • Code
    • B-Tree visualization

    B-Tree visualization

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/buffers.html ================================================ CodeMirror: Multiple Buffer & Split View Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Multiple Buffer & Split View

    Multiple Buffer & Split View Demo

    Select buffer:    
    Select buffer:    

    Demonstration of using linked documents to provide a split view on a document, and using swapDoc to use a single editor to display multiple documents.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/changemode.html ================================================ CodeMirror: Mode-Changing Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Mode-Changing

    Mode-Changing Demo

    On changes to the content of the above editor, a (crude) script tries to auto-detect the language used, and switches the editor to either JavaScript or Scheme mode based on that.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/closebrackets.html ================================================ CodeMirror: Closebrackets Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Closebrackets

    Closebrackets Demo

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/closetag.html ================================================ CodeMirror: Close-Tag Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Close-Tag

    Close-Tag Demo

    Uses the closetag addon to auto-close tags.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/complete.html ================================================ CodeMirror: Autocomplete Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Autocomplete

    Autocomplete Demo

    Press ctrl-space to activate autocompletion. Built on top of the show-hint and javascript-hint addons.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/emacs.html ================================================ CodeMirror: Emacs bindings demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Emacs bindings

    Emacs bindings demo

    The emacs keybindings are enabled by including keymap/emacs.js and setting the keyMap option to "emacs". Because CodeMirror's internal API is quite different from Emacs, they are only a loose approximation of actual emacs bindings, though.

    Also note that a lot of browsers disallow certain keys from being captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the result that idiomatic use of Emacs keys will constantly close your tab or open a new window.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/folding.html ================================================ CodeMirror: Code Folding Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Code Folding

    Code Folding Demo

    JavaScript:
    HTML:
    JSON with custom widget:
    Python:
    Markdown:
    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/fullscreen.html ================================================ CodeMirror: Full Screen Editing

    CodeMirror

    • Home
    • Manual
    • Code
    • Full Screen Editing

    Full Screen Editing

    Demonstration of the fullscreen addon. Press F11 when cursor is in the editor to toggle full screen editing. Esc can also be used to exit full screen editing.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/hardwrap.html ================================================ CodeMirror: Hard-wrapping Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Hard-wrapping

    Hard-wrapping Demo

    Demonstration of the hardwrap addon. The above editor has its change event hooked up to the wrapParagraphsInRange method, so that the paragraphs are reflown as you are typing.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/html5complete.html ================================================ CodeMirror: HTML completion demo

    CodeMirror

    • Home
    • Manual
    • Code
    • HTML completion

    HTML completion demo

    Shows the XML completer parameterized with information about the tags in HTML. Press ctrl-space to activate completion.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/indentwrap.html ================================================ CodeMirror: Indented wrapped line demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Indented wrapped line

    Indented wrapped line demo

    This page uses a hack on top of the "renderLine" event to make wrapped text line up with the base indentation of the line.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/lint.html ================================================ CodeMirror: Linter Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Linter

    Linter Demo

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/loadmode.html ================================================ CodeMirror: Lazy Mode Loading Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Lazy Mode Loading

    Lazy Mode Loading Demo

    Current mode: text/plain

    Filename, mime, or mode name:

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/marker.html ================================================ CodeMirror: Breakpoint Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Breakpoint

    Breakpoint Demo

    Click the line-number gutter to add or remove 'breakpoints'.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/markselection.html ================================================ CodeMirror: Selection Marking Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Selection Marking

    Selection Marking Demo

    Simple addon to easily mark (and style) selected text. Docs.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/matchhighlighter.html ================================================ CodeMirror: Match Highlighter Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Match Highlighter

    Match Highlighter Demo

    Search and highlight occurrences of the selected text.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/matchtags.html ================================================ CodeMirror: Tag Matcher Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Tag Matcher

    Tag Matcher Demo

    Put the cursor on or inside a pair of tags to highlight them. Press Ctrl-J to jump to the tag that matches the one under the cursor.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/merge.html ================================================ CodeMirror: merge view demo

    CodeMirror

    • Home
    • Manual
    • Code
    • merge view

    merge view demo

    The merge addon provides an interface for displaying and merging diffs, either two-way or three-way. The left (or center) pane is editable, and the differences with the other pane(s) are optionally shown live as you edit it. In the two-way configuration, there are also options to pad changed sections to align them, and to collapse unchanged stretches of text.

    This addon depends on the google-diff-match-patch library to compute the diffs.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/multiplex.html ================================================ CodeMirror: Multiplexing Parser Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Multiplexing Parser

    Multiplexing Parser Demo

    Demonstration of a multiplexing mode, which, at certain boundary strings, switches to one or more inner modes. The out (HTML) mode does not get fed the content of the << >> blocks. See the manual and the source for more information.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/mustache.html ================================================ CodeMirror: Overlay Parser Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Overlay Parser

    Overlay Parser Demo

    Demonstration of a mode that parses HTML, highlighting the Mustache templating directives inside of it by using the code in overlay.js. View source to see the 15 lines of code needed to accomplish this.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/panel.html ================================================ CodeMirror: Panel Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Panel

    Panel Demo

    The panel addon allows you to display panels above or below an editor.
    Click the links below to add panels at the given position:

    top after-top before-bottom bottom

    You can also replace an existing panel:

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/placeholder.html ================================================ CodeMirror: Placeholder demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Placeholder

    Placeholder demo

    The placeholder plug-in adds an option placeholder that can be set to make text appear in the editor when it is empty and not focused. If the source textarea has a placeholder attribute, it will automatically be inherited.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/preview.html ================================================ CodeMirror: HTML5 preview

    CodeMirror

    • Home
    • Manual
    • Code
    • HTML5 preview

    HTML5 preview

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/requirejs.html ================================================ CodeMirror: HTML completion demo

    CodeMirror

    • Home
    • Manual
    • Code
    • HTML completion

    RequireJS module loading demo

    This demo does the same thing as the HTML5 completion demo, but loads its dependencies with Require.js, rather than explicitly. Press ctrl-space to activate completion.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/resize.html ================================================ CodeMirror: Autoresize Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Autoresize

    Autoresize Demo

    By setting an editor's height style to auto and giving the viewportMargin a value of Infinity, CodeMirror can be made to automatically resize to fit its content.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/rulers.html ================================================ CodeMirror: Ruler Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Ruler demo

    Ruler Demo

    Demonstration of the rulers addon, which displays vertical lines at given column offsets.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/runmode-standalone.html ================================================ CodeMirror: Mode Runner Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Mode Runner

    Mode Runner Demo


    
    
        
    
        

    Running a CodeMirror mode outside of the editor. The CodeMirror.runMode function, defined in addon/runmode/runmode.js takes the following arguments:

    text (string)
    The document to run through the highlighter.
    mode (mode spec)
    The mode to use (must be loaded as normal).
    output (function or DOM node)
    If this is a function, it will be called for each token with two arguments, the token's text and the token's style class (may be null for unstyled tokens). If it is a DOM node, the tokens will be converted to span elements as in an editor, and inserted into the node (through innerHTML).
    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/runmode.html ================================================ CodeMirror: Mode Runner Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Mode Runner

    Mode Runner Demo


    
    
        
    
        

    Running a CodeMirror mode outside of the editor. The CodeMirror.runMode function, defined in addon/runmode/runmode.js takes the following arguments:

    text (string)
    The document to run through the highlighter.
    mode (mode spec)
    The mode to use (must be loaded as normal).
    output (function or DOM node)
    If this is a function, it will be called for each token with two arguments, the token's text and the token's style class (may be null for unstyled tokens). If it is a DOM node, the tokens will be converted to span elements as in an editor, and inserted into the node (through innerHTML).
    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/search.html ================================================ CodeMirror: Search/Replace Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Search/Replace

    Search/Replace Demo

    Demonstration of primitive search/replace functionality. The keybindings (which can be configured with custom keymaps) are:

    Ctrl-F / Cmd-F
    Start searching
    Ctrl-G / Cmd-G
    Find next
    Shift-Ctrl-G / Shift-Cmd-G
    Find previous
    Shift-Ctrl-F / Cmd-Option-F
    Replace
    Shift-Ctrl-R / Shift-Cmd-Option-F
    Replace all
    Alt-F
    Persistent search (dialog doesn't autoclose, enter to find next, Shift-Enter to find previous)
    Alt-G
    Jump to line

    Searching is enabled by including addon/search/search.js and addon/search/searchcursor.js. Jump to line - including addon/search/jump-to-line.js.

    For good-looking input dialogs, you also want to include addon/dialog/dialog.js and addon/dialog/dialog.css.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/simplemode.html ================================================ CodeMirror: Simple Mode Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Simple Mode

    Simple Mode Demo

    The mode/simple addon allows CodeMirror modes to be specified using a relatively simple declarative format. This format is not as powerful as writing code directly against the mode interface, but is a lot easier to get started with, and sufficiently expressive for many simple language modes.

    This interface is still in flux. It is unlikely to be scrapped or overhauled completely, so do start writing code against it, but details might change as it stabilizes, and you might have to tweak your code when upgrading.

    Simple modes (loosely based on the Common JavaScript Syntax Highlighting Specification, which never took off), are state machines, where each state has a number of rules that match tokens. A rule describes a type of token that may occur in the current state, and possibly a transition to another state caused by that token.

    The CodeMirror.defineSimpleMode(name, states) method takes a mode name and an object that describes the mode's states. The editor below shows an example of such a mode (and is itself highlighted by the mode shown in it).

    Each state is an array of rules. A rule may have the following properties:

    regex: string | RegExp
    The regular expression that matches the token. May be a string or a regex object. When a regex, the ignoreCase flag will be taken into account when matching the token. This regex has to capture groups when the token property is an array. If it captures groups, it must capture all of the string (since JS provides no way to find out where a group matched). Currently negative lookbehind assertion for regex is not supported, regardless of browser support.
    token: string | array<string> | null
    An optional token style. Multiple styles can be specified by separating them with dots or spaces. When this property holds an array of token styles, the regex for this rule must capture a group for each array item.
    sol: boolean
    When true, this token will only match at the start of the line. (The ^ regexp marker doesn't work as you'd expect in this context because of limitations in JavaScript's RegExp API.)
    next: string
    When a next property is present, the mode will transfer to the state named by the property when the token is encountered.
    push: string
    Like next, but instead replacing the current state by the new state, the current state is kept on a stack, and can be returned to with the pop directive.
    pop: bool
    When true, and there is another state on the state stack, will cause the mode to pop that state off the stack and transition to it.
    mode: {spec, end, persistent}
    Can be used to embed another mode inside a mode. When present, must hold an object with a spec property that describes the embedded mode, and an optional end end property that specifies the regexp that will end the extent of the mode. When a persistent property is set (and true), the nested mode's state will be preserved between occurrences of the mode.
    indent: bool
    When true, this token changes the indentation to be one unit more than the current line's indentation.
    dedent: bool
    When true, this token will pop one scope off the indentation stack.
    dedentIfLineStart: bool
    If a token has its dedent property set, it will, by default, cause lines where it appears at the start to be dedented. Set this property to false to prevent that behavior.

    The meta property of the states object is special, and will not be interpreted as a state. Instead, properties set on it will be set on the mode, which is useful for properties like lineComment, which sets the comment style for a mode. The simple mode addon also recognizes a few such properties:

    dontIndentStates: array<string>
    An array of states in which the mode's auto-indentation should not take effect. Usually used for multi-line comment and string states.
    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/simplescrollbars.html ================================================ CodeMirror: Simple Scrollbar Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Simple Scrollbar

    Simple Scrollbar Demo

    The simplescrollbars addon defines two styles of non-native scrollbars: "simple" and "overlay" (click to try), which can be passed to the scrollbarStyle option. These implement the scrollbar using DOM elements, allowing more control over its appearance.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/spanaffectswrapping_shim.html ================================================ CodeMirror: Automatically derive odd wrapping behavior for your browser

    CodeMirror

    • Home
    • Manual
    • Code
    • Automatically derive odd wrapping behavior for your browser

    Automatically derive odd wrapping behavior for your browser

    This is a hack to automatically derive a spanAffectsWrapping regexp for a browser. See the comments above that variable in lib/codemirror.js for some more details.

    
    
        
      
    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/sublime.html ================================================ CodeMirror: Sublime Text bindings demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Sublime bindings

    Sublime Text bindings demo

    The sublime keymap defines many Sublime Text-specific bindings for CodeMirror. See the code below for an overview.

    Enable the keymap by loading keymap/sublime.js and setting the keyMap option to "sublime".

    (A lot of the search functionality is still missing.)

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/tern.html ================================================ CodeMirror: Tern Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Tern

    Tern Demo

    Demonstrates integration of Tern and CodeMirror. The following keys are bound:

    Ctrl-Space
    Autocomplete
    Ctrl-O
    Find docs for the expression at the cursor
    Ctrl-I
    Find type at cursor
    Alt-.
    Jump to definition (Alt-, to jump back)
    Ctrl-Q
    Rename variable
    Ctrl-.
    Select all occurrences of a variable

    Documentation is sparse for now. See the top of the script for a rough API overview.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/theme.html ================================================ CodeMirror: Theme Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Theme

    Theme Demo

    Select a theme:

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/trailingspace.html ================================================ CodeMirror: Trailing Whitespace Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Trailing Whitespace

    Trailing Whitespace Demo

    Uses the trailingspace addon to highlight trailing whitespace.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/variableheight.html ================================================ CodeMirror: Variable Height Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Variable Height

    Variable Height Demo

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/vim.html ================================================ CodeMirror: Vim bindings demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Vim bindings

    Vim bindings demo

    Note: The CodeMirror vim bindings are maintained in the codemirror-vim repository, not this project. The file is still included in the distribution for backwards compatibility.

    Key buffer:
    Vim mode:

    The vim keybindings are enabled by including keymap/vim.js and setting the keyMap option to vim.

    Features

    • All common motions and operators, including text objects
    • Operator motion orthogonality
    • Visual mode - characterwise, linewise, blockwise
    • Full macro support (q, @)
    • Incremental highlighted search (/, ?, #, *, g#, g*)
    • Search/replace with confirm (:substitute, :%s)
    • Search history
    • Jump lists (Ctrl-o, Ctrl-i)
    • Key/command mapping with API (:map, :nmap, :vmap)
    • Sort (:sort)
    • Marks (`, ')
    • :global
    • Insert mode behaves identical to base CodeMirror
    • Cross-buffer yank/paste

    For the full list of key mappings and Ex commands, refer to the defaultKeymap and defaultExCommandMap at the top of keymap/vim.js.

    Note that while the vim mode tries to emulate the most useful features of vim as faithfully as possible, it does not strive to become a complete vim implementation

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/visibletabs.html ================================================ CodeMirror: Visible tabs demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Visible tabs

    Visible tabs demo

    Tabs inside the editor are spans with the class cm-tab, and can be styled.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/widget.html ================================================ CodeMirror: Inline Widget Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • Inline Widget

    Inline Widget Demo

    This demo runs JSHint over the code in the editor (which is the script used on this page), and inserts line widgets to display the warnings that JSHint comes up with.

    ================================================ FILE: public/assets/lib/vendor/codemirror/demo/xmlcomplete.html ================================================  CodeMirror: XML Autocomplete Demo

    CodeMirror

    • Home
    • Manual
    • Code
    • XML Autocomplete

    XML Autocomplete Demo

    Press ctrl-space, or type a '<' character to activate autocompletion. This demo defines a simple schema that guides completion. The schema can be customized—see the manual.

    Development of the xml-hint addon was kindly sponsored by www.xperiment.mobi.

    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/activebookmark.js ================================================ // Kludge in HTML5 tag recognition in IE8 document.createElement("section"); document.createElement("article"); (function() { if (!window.addEventListener) return; var pending = false, prevVal = null; function updateSoon() { if (!pending) { pending = true; setTimeout(update, 250); } } function update() { pending = false; var marks = document.getElementById("nav").getElementsByTagName("a"), found; for (var i = 0; i < marks.length; ++i) { var mark = marks[i], m; if (mark.getAttribute("data-default")) { if (found == null) found = i; } else if (m = mark.href.match(/#(.*)/)) { var ref = document.getElementById(m[1]); if (ref && ref.getBoundingClientRect().top < 50) found = i; } } if (found != null && found != prevVal) { prevVal = found; var lis = document.getElementById("nav").getElementsByTagName("li"); for (var i = 0; i < lis.length; ++i) lis[i].className = ""; for (var i = 0; i < marks.length; ++i) { if (found == i) { marks[i].className = "active"; for (var n = marks[i]; n; n = n.parentNode) if (n.nodeName == "LI") n.className = "active"; } else { marks[i].className = ""; } } } } window.addEventListener("scroll", updateSoon); window.addEventListener("load", updateSoon); window.addEventListener("hashchange", function() { setTimeout(function() { var hash = document.location.hash, found = null, m; var marks = document.getElementById("nav").getElementsByTagName("a"); for (var i = 0; i < marks.length; i++) if ((m = marks[i].href.match(/(#.*)/)) && m[1] == hash) { found = i; break; } if (found != null) for (var i = 0; i < marks.length; i++) marks[i].className = i == found ? "active" : ""; }, 300); }); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/doc/docs.css ================================================ @font-face { font-family: 'Source Sans Pro'; font-style: normal; font-weight: 400; src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(source_sans.woff) format('woff'); } body, html { margin: 0; padding: 0; height: 100%; } section, article { display: block; padding: 0; } body { background: #f8f8f8; font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; line-height: 1.5; } p { margin-top: 0; } h2, h3, h1 { font-weight: normal; margin-bottom: .7em; } h1 { font-size: 140%; } h2 { font-size: 120%; } h3 { font-size: 110%; } article > h2:first-child, section:first-child > h2 { margin-top: 0; } #nav h1 { margin-right: 12px; margin-top: 0; margin-bottom: 2px; color: #d30707; letter-spacing: .5px; } a, a:visited, a:link, .quasilink { color: #A21313; } em { padding-right: 2px; } .quasilink { cursor: pointer; } article { max-width: 700px; margin: 0 0 0 160px; border-left: 2px solid #E30808; border-right: 1px solid #ddd; padding: 30px 50px 100px 50px; background: white; z-index: 2; position: relative; min-height: 100%; box-sizing: border-box; -moz-box-sizing: border-box; } #nav { position: fixed; padding-top: 30px; max-height: 100%; box-sizing: -moz-border-box; box-sizing: border-box; overflow-y: auto; left: 0; right: none; width: 160px; text-align: right; z-index: 1; } @media screen and (min-width: 1000px) { article { margin: 0 auto; } #nav { right: 50%; width: auto; border-right: 349px solid transparent; } } #nav ul { display: block; margin: 0; padding: 0; margin-bottom: 32px; } #nav a { text-decoration: none; } #nav li { display: block; margin-bottom: 4px; } #nav li ul { font-size: 80%; margin-bottom: 0; display: none; } #nav li.active ul { display: block; } #nav li li a { padding-right: 20px; display: inline-block; } #nav ul a { color: black; padding: 0 7px 1px 11px; } #nav ul a.active, #nav ul a:hover { border-bottom: 1px solid #E30808; margin-bottom: -1px; color: #E30808; } #logo { border: 0; margin-right: 12px; margin-bottom: 25px; } section { border-top: 1px solid #E30808; margin: 1.5em 0; } section.first { border: none; margin-top: 0; } #demo { position: relative; } #demolist { position: absolute; right: 5px; top: 5px; z-index: 25; } .yinyang { position: absolute; top: -10px; left: 0; right: 0; margin: auto; display: block; height: 120px; } .actions { margin: 1em 0 0; min-height: 100px; position: relative; } @media screen and (max-width: 800px) { .actions { padding-top: 120px; } .actionsleft, .actionsright { float: none; text-align: left; margin-bottom: 1em; } } th { text-decoration: underline; font-weight: normal; text-align: left; } #features ul { list-style: none; margin: 0 0 1em; padding: 0 0 0 1.2em; } #features li:before { content: "-"; width: 1em; display: inline-block; padding: 0; margin: 0; margin-left: -1em; } .rel { margin-bottom: 0; } .rel-note { margin-top: 0; color: #555; } pre { padding-left: 15px; border-left: 2px solid #ddd; } code { padding: 0 2px; } strong { text-decoration: underline; font-weight: normal; } .field { border: 1px solid #A21313; } ================================================ FILE: public/assets/lib/vendor/codemirror/doc/internals.html ================================================  CodeMirror: Internals

    CodeMirror

    • Home
    • Manual
    • Code
    • Introduction
    • General Approach
    • Input
    • Selection
    • Intelligent Updating
    • Parsing
    • What Gives?
    • Content Representation
    • Key Maps

    (Re-) Implementing A Syntax-Highlighting Editor in JavaScript

    Topic: JavaScript, code editor implementation
    Author: Marijn Haverbeke
    Date: March 2nd 2011 (updated November 13th 2011)

    Caution: this text was written briefly after version 2 was initially written. It no longer (even including the update at the bottom) fully represents the current implementation. I'm leaving it here as a historic document. For more up-to-date information, look at the entries tagged cm-internals on my blog.

    This is a followup to my Brutal Odyssey to the Dark Side of the DOM Tree story. That one describes the mind-bending process of implementing (what would become) CodeMirror 1. This one describes the internals of CodeMirror 2, a complete rewrite and rethink of the old code base. I wanted to give this piece another Hunter Thompson copycat subtitle, but somehow that would be out of place—the process this time around was one of straightforward engineering, requiring no serious mind-bending whatsoever.

    So, what is wrong with CodeMirror 1? I'd estimate, by mailing list activity and general search-engine presence, that it has been integrated into about a thousand systems by now. The most prominent one, since a few weeks, being Google code's project hosting. It works, and it's being used widely.

    Still, I did not start replacing it because I was bored. CodeMirror 1 was heavily reliant on designMode or contentEditable (depending on the browser). Neither of these are well specified (HTML5 tries to specify their basics), and, more importantly, they tend to be one of the more obscure and buggy areas of browser functionality—CodeMirror, by using this functionality in a non-typical way, was constantly running up against browser bugs. WebKit wouldn't show an empty line at the end of the document, and in some releases would suddenly get unbearably slow. Firefox would show the cursor in the wrong place. Internet Explorer would insist on linkifying everything that looked like a URL or email address, a behaviour that can't be turned off. Some bugs I managed to work around (which was often a frustrating, painful process), others, such as the Firefox cursor placement, I gave up on, and had to tell user after user that they were known problems, but not something I could help.

    Also, there is the fact that designMode (which seemed to be less buggy than contentEditable in Webkit and Firefox, and was thus used by CodeMirror 1 in those browsers) requires a frame. Frames are another tricky area. It takes some effort to prevent getting tripped up by domain restrictions, they don't initialize synchronously, behave strangely in response to the back button, and, on several browsers, can't be moved around the DOM without having them re-initialize. They did provide a very nice way to namespace the library, though—CodeMirror 1 could freely pollute the namespace inside the frame.

    Finally, working with an editable document means working with selection in arbitrary DOM structures. Internet Explorer (8 and before) has an utterly different (and awkward) selection API than all of the other browsers, and even among the different implementations of document.selection, details about how exactly a selection is represented vary quite a bit. Add to that the fact that Opera's selection support tended to be very buggy until recently, and you can imagine why CodeMirror 1 contains 700 lines of selection-handling code.

    And that brings us to the main issue with the CodeMirror 1 code base: The proportion of browser-bug-workarounds to real application code was getting dangerously high. By building on top of a few dodgy features, I put the system in a vulnerable position—any incompatibility and bugginess in these features, I had to paper over with my own code. Not only did I have to do some serious stunt-work to get it to work on older browsers (as detailed in the previous story), things also kept breaking in newly released versions, requiring me to come up with new scary hacks in order to keep up. This was starting to lose its appeal.

    General Approach

    What CodeMirror 2 does is try to sidestep most of the hairy hacks that came up in version 1. I owe a lot to the ACE editor for inspiration on how to approach this.

    I absolutely did not want to be completely reliant on key events to generate my input. Every JavaScript programmer knows that key event information is horrible and incomplete. Some people (most awesomely Mihai Bazon with Ymacs) have been able to build more or less functioning editors by directly reading key events, but it takes a lot of work (the kind of never-ending, fragile work I described earlier), and will never be able to properly support things like multi-keystoke international character input. [see below for caveat]

    So what I do is focus a hidden textarea, and let the browser believe that the user is typing into that. What we show to the user is a DOM structure we built to represent his document. If this is updated quickly enough, and shows some kind of believable cursor, it feels like a real text-input control.

    Another big win is that this DOM representation does not have to span the whole document. Some CodeMirror 1 users insisted that they needed to put a 30 thousand line XML document into CodeMirror. Putting all that into the DOM takes a while, especially since, for some reason, an editable DOM tree is slower than a normal one on most browsers. If we have full control over what we show, we must only ensure that the visible part of the document has been added, and can do the rest only when needed. (Fortunately, the onscroll event works almost the same on all browsers, and lends itself well to displaying things only as they are scrolled into view.)

    Input

    ACE uses its hidden textarea only as a text input shim, and does all cursor movement and things like text deletion itself by directly handling key events. CodeMirror's way is to let the browser do its thing as much as possible, and not, for example, define its own set of key bindings. One way to do this would have been to have the whole document inside the hidden textarea, and after each key event update the display DOM to reflect what's in that textarea.

    That'd be simple, but it is not realistic. For even medium-sized document the editor would be constantly munging huge strings, and get terribly slow. What CodeMirror 2 does is put the current selection, along with an extra line on the top and on the bottom, into the textarea.

    This means that the arrow keys (and their ctrl-variations), home, end, etcetera, do not have to be handled specially. We just read the cursor position in the textarea, and update our cursor to match it. Also, copy and paste work pretty much for free, and people get their native key bindings, without any special work on my part. For example, I have emacs key bindings configured for Chrome and Firefox. There is no way for a script to detect this. [no longer the case]

    Of course, since only a small part of the document sits in the textarea, keys like page up and ctrl-end won't do the right thing. CodeMirror is catching those events and handling them itself.

    Selection

    Getting and setting the selection range of a textarea in modern browsers is trivial—you just use the selectionStart and selectionEnd properties. On IE you have to do some insane stuff with temporary ranges and compensating for the fact that moving the selection by a 'character' will treat \r\n as a single character, but even there it is possible to build functions that reliably set and get the selection range.

    But consider this typical case: When I'm somewhere in my document, press shift, and press the up arrow, something gets selected. Then, if I, still holding shift, press the up arrow again, the top of my selection is adjusted. The selection remembers where its head and its anchor are, and moves the head when we shift-move. This is a generally accepted property of selections, and done right by every editing component built in the past twenty years.

    But not something that the browser selection APIs expose.

    Great. So when someone creates an 'upside-down' selection, the next time CodeMirror has to update the textarea, it'll re-create the selection as an 'upside-up' selection, with the anchor at the top, and the next cursor motion will behave in an unexpected way—our second up-arrow press in the example above will not do anything, since it is interpreted in exactly the same way as the first.

    No problem. We'll just, ehm, detect that the selection is upside-down (you can tell by the way it was created), and then, when an upside-down selection is present, and a cursor-moving key is pressed in combination with shift, we quickly collapse the selection in the textarea to its start, allow the key to take effect, and then combine its new head with its old anchor to get the real selection.

    In short, scary hacks could not be avoided entirely in CodeMirror 2.

    And, the observant reader might ask, how do you even know that a key combo is a cursor-moving combo, if you claim you support any native key bindings? Well, we don't, but we can learn. The editor keeps a set known cursor-movement combos (initialized to the predictable defaults), and updates this set when it observes that pressing a certain key had (only) the effect of moving the cursor. This, of course, doesn't work if the first time the key is used was for extending an inverted selection, but it works most of the time.

    Intelligent Updating

    One thing that always comes up when you have a complicated internal state that's reflected in some user-visible external representation (in this case, the displayed code and the textarea's content) is keeping the two in sync. The naive way is to just update the display every time you change your state, but this is not only error prone (you'll forget), it also easily leads to duplicate work on big, composite operations. Then you start passing around flags indicating whether the display should be updated in an attempt to be efficient again and, well, at that point you might as well give up completely.

    I did go down that road, but then switched to a much simpler model: simply keep track of all the things that have been changed during an action, and then, only at the end, use this information to update the user-visible display.

    CodeMirror uses a concept of operations, which start by calling a specific set-up function that clears the state and end by calling another function that reads this state and does the required updating. Most event handlers, and all the user-visible methods that change state are wrapped like this. There's a method called operation that accepts a function, and returns another function that wraps the given function as an operation.

    It's trivial to extend this (as CodeMirror does) to detect nesting, and, when an operation is started inside an operation, simply increment the nesting count, and only do the updating when this count reaches zero again.

    If we have a set of changed ranges and know the currently shown range, we can (with some awkward code to deal with the fact that changes can add and remove lines, so we're dealing with a changing coordinate system) construct a map of the ranges that were left intact. We can then compare this map with the part of the document that's currently visible (based on scroll offset and editor height) to determine whether something needs to be updated.

    CodeMirror uses two update algorithms—a full refresh, where it just discards the whole part of the DOM that contains the edited text and rebuilds it, and a patch algorithm, where it uses the information about changed and intact ranges to update only the out-of-date parts of the DOM. When more than 30 percent (which is the current heuristic, might change) of the lines need to be updated, the full refresh is chosen (since it's faster to do than painstakingly finding and updating all the changed lines), in the other case it does the patching (so that, if you scroll a line or select another character, the whole screen doesn't have to be re-rendered). [the full-refresh algorithm was dropped, it wasn't really faster than the patching one]

    All updating uses innerHTML rather than direct DOM manipulation, since that still seems to be by far the fastest way to build documents. There's a per-line function that combines the highlighting, marking, and selection info for that line into a snippet of HTML. The patch updater uses this to reset individual lines, the refresh updater builds an HTML chunk for the whole visible document at once, and then uses a single innerHTML update to do the refresh.

    Parsers can be Simple

    When I wrote CodeMirror 1, I thought interruptible parsers were a hugely scary and complicated thing, and I used a bunch of heavyweight abstractions to keep this supposed complexity under control: parsers were iterators that consumed input from another iterator, and used funny closure-resetting tricks to copy and resume themselves.

    This made for a rather nice system, in that parsers formed strictly separate modules, and could be composed in predictable ways. Unfortunately, it was quite slow (stacking three or four iterators on top of each other), and extremely intimidating to people not used to a functional programming style.

    With a few small changes, however, we can keep all those advantages, but simplify the API and make the whole thing less indirect and inefficient. CodeMirror 2's mode API uses explicit state objects, and makes the parser/tokenizer a function that simply takes a state and a character stream abstraction, advances the stream one token, and returns the way the token should be styled. This state may be copied, optionally in a mode-defined way, in order to be able to continue a parse at a given point. Even someone who's never touched a lambda in his life can understand this approach. Additionally, far fewer objects are allocated in the course of parsing now.

    The biggest speedup comes from the fact that the parsing no longer has to touch the DOM though. In CodeMirror 1, on an older browser, you could see the parser work its way through the document, managing some twenty lines in each 50-millisecond time slice it got. It was reading its input from the DOM, and updating the DOM as it went along, which any experienced JavaScript programmer will immediately spot as a recipe for slowness. In CodeMirror 2, the parser usually finishes the whole document in a single 100-millisecond time slice—it manages some 1500 lines during that time on Chrome. All it has to do is munge strings, so there is no real reason for it to be slow anymore.

    What Gives?

    Given all this, what can you expect from CodeMirror 2?

    • Small. the base library is some 45k when minified now, 17k when gzipped. It's smaller than its own logo.
    • Lightweight. CodeMirror 2 initializes very quickly, and does almost no work when it is not focused. This means you can treat it almost like a textarea, have multiple instances on a page without trouble.
    • Huge document support. Since highlighting is really fast, and no DOM structure is being built for non-visible content, you don't have to worry about locking up your browser when a user enters a megabyte-sized document.
    • Extended API. Some things kept coming up in the mailing list, such as marking pieces of text or lines, which were extremely hard to do with CodeMirror 1. The new version has proper support for these built in.
    • Tab support. Tabs inside editable documents were, for some reason, a no-go. At least six different people announced they were going to add tab support to CodeMirror 1, none survived (I mean, none delivered a working version). CodeMirror 2 no longer removes tabs from your document.
    • Sane styling. iframe nodes aren't really known for respecting document flow. Now that an editor instance is a plain div element, it is much easier to size it to fit the surrounding elements. You don't even have to make it scroll if you do not want to.

    On the downside, a CodeMirror 2 instance is not a native editable component. Though it does its best to emulate such a component as much as possible, there is functionality that browsers just do not allow us to hook into. Doing select-all from the context menu, for example, is not currently detected by CodeMirror.

    [Updates from November 13th 2011] Recently, I've made some changes to the codebase that cause some of the text above to no longer be current. I've left the text intact, but added markers at the passages that are now inaccurate. The new situation is described below.

    Content Representation

    The original implementation of CodeMirror 2 represented the document as a flat array of line objects. This worked well—splicing arrays will require the part of the array after the splice to be moved, but this is basically just a simple memmove of a bunch of pointers, so it is cheap even for huge documents.

    However, I recently added line wrapping and code folding (line collapsing, basically). Once lines start taking up a non-constant amount of vertical space, looking up a line by vertical position (which is needed when someone clicks the document, and to determine the visible part of the document during scrolling) can only be done with a linear scan through the whole array, summing up line heights as you go. Seeing how I've been going out of my way to make big documents fast, this is not acceptable.

    The new representation is based on a B-tree. The leaves of the tree contain arrays of line objects, with a fixed minimum and maximum size, and the non-leaf nodes simply hold arrays of child nodes. Each node stores both the amount of lines that live below them and the vertical space taken up by these lines. This allows the tree to be indexed both by line number and by vertical position, and all access has logarithmic complexity in relation to the document size.

    I gave line objects and tree nodes parent pointers, to the node above them. When a line has to update its height, it can simply walk these pointers to the top of the tree, adding or subtracting the difference in height from each node it encounters. The parent pointers also make it cheaper (in complexity terms, the difference is probably tiny in normal-sized documents) to find the current line number when given a line object. In the old approach, the whole document array had to be searched. Now, we can just walk up the tree and count the sizes of the nodes coming before us at each level.

    I chose B-trees, not regular binary trees, mostly because they allow for very fast bulk insertions and deletions. When there is a big change to a document, it typically involves adding, deleting, or replacing a chunk of subsequent lines. In a regular balanced tree, all these inserts or deletes would have to be done separately, which could be really expensive. In a B-tree, to insert a chunk, you just walk down the tree once to find where it should go, insert them all in one shot, and then break up the node if needed. This breaking up might involve breaking up nodes further up, but only requires a single pass back up the tree. For deletion, I'm somewhat lax in keeping things balanced—I just collapse nodes into a leaf when their child count goes below a given number. This means that there are some weird editing patterns that may result in a seriously unbalanced tree, but even such an unbalanced tree will perform well, unless you spend a day making strangely repeating edits to a really big document.

    Keymaps

    Above, I claimed that directly catching key events for things like cursor movement is impractical because it requires some browser-specific kludges. I then proceeded to explain some awful hacks that were needed to make it possible for the selection changes to be detected through the textarea. In fact, the second hack is about as bad as the first.

    On top of that, in the presence of user-configurable tab sizes and collapsed and wrapped lines, lining up cursor movement in the textarea with what's visible on the screen becomes a nightmare. Thus, I've decided to move to a model where the textarea's selection is no longer depended on.

    So I moved to a model where all cursor movement is handled by my own code. This adds support for a goal column, proper interaction of cursor movement with collapsed lines, and makes it possible for vertical movement to move through wrapped lines properly, instead of just treating them like non-wrapped lines.

    The key event handlers now translate the key event into a string, something like Ctrl-Home or Shift-Cmd-R, and use that string to look up an action to perform. To make keybinding customizable, this lookup goes through a table, using a scheme that allows such tables to be chained together (for example, the default Mac bindings fall through to a table named 'emacsy', which defines basic Emacs-style bindings like Ctrl-F, and which is also used by the custom Emacs bindings).

    A new option extraKeys allows ad-hoc keybindings to be defined in a much nicer way than what was possible with the old onKeyEvent callback. You simply provide an object mapping key identifiers to functions, instead of painstakingly looking at raw key events.

    Built-in commands map to strings, rather than functions, for example "goLineUp" is the default action bound to the up arrow key. This allows new keymaps to refer to them without duplicating any code. New commands can be defined by assigning to the CodeMirror.commands object, which maps such commands to functions.

    The hidden textarea now only holds the current selection, with no extra characters around it. This has a nice advantage: polling for input becomes much, much faster. If there's a big selection, this text does not have to be read from the textarea every time—when we poll, just noticing that something is still selected is enough to tell us that no new text was typed.

    The reason that cheap polling is important is that many browsers do not fire useful events on IME (input method engine) input, which is the thing where people inputting a language like Japanese or Chinese use multiple keystrokes to create a character or sequence of characters. Most modern browsers fire input when the composing is finished, but many don't fire anything when the character is updated during composition. So we poll, whenever the editor is focused, to provide immediate updates of the display.

    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/manual.html ================================================ CodeMirror 5 User Manual

    CodeMirror

    • Home
    • Manual
    • Code
    • Basic Usage
    • Configuration
    • Events
    • Key maps
    • Commands
    • Customized Styling
    • Programming API
      • Constructor
      • Content manipulation
      • Selection
      • Configuration
      • Document management
      • History
      • Text-marking
      • Widget, gutter, and decoration
      • Sizing, scrolling, and positioning
      • Mode, state, and tokens
      • Miscellaneous methods
      • Static properties
    • Addons
    • Writing CodeMirror Modes
    • Vim Mode API
      • Configuration
      • Events
      • Extending VIM

    User manual and reference guide version 5.65.16

    CodeMirror is a code-editor component that can be embedded in Web pages. The core library provides only the editor component, no accompanying buttons, auto-completion, or other IDE functionality. It does provide a rich API on top of which such functionality can be straightforwardly implemented. See the addons included in the distribution, and 3rd party packages on npm, for reusable implementations of extra features.

    CodeMirror works with language-specific modes. Modes are JavaScript programs that help color (and optionally indent) text written in a given language. The distribution comes with a number of modes (see the mode/ directory), and it isn't hard to write new ones for other languages.

    Basic Usage

    The easiest way to use CodeMirror is to simply load the script and style sheet found under lib/ in the distribution, plus a mode script from one of the mode/ directories. For example:

    <script src="lib/codemirror.js"></script>
    <link rel="stylesheet" href="lib/codemirror.css">
    <script src="mode/javascript/javascript.js"></script>

    (Alternatively, use a module loader. More about that later.)

    Having done this, an editor instance can be created like this:

    var myCodeMirror = CodeMirror(document.body);

    The editor will be appended to the document body, will start empty, and will use the mode that we loaded. To have more control over the new editor, a configuration object can be passed to CodeMirror as a second argument:

    var myCodeMirror = CodeMirror(document.body, {
      value: "function myScript(){return 100;}\n",
      mode:  "javascript"
    });

    This will initialize the editor with a piece of code already in it, and explicitly tell it to use the JavaScript mode (which is useful when multiple modes are loaded). See below for a full discussion of the configuration options that CodeMirror accepts.

    In cases where you don't want to append the editor to an element, and need more control over the way it is inserted, the first argument to the CodeMirror function can also be a function that, when given a DOM element, inserts it into the document somewhere. This could be used to, for example, replace a textarea with a real editor:

    var myCodeMirror = CodeMirror(function(elt) {
      myTextArea.parentNode.replaceChild(elt, myTextArea);
    }, {value: myTextArea.value});

    However, for this use case, which is a common way to use CodeMirror, the library provides a much more powerful shortcut:

    var myCodeMirror = CodeMirror.fromTextArea(myTextArea);

    This will, among other things, ensure that the textarea's value is updated with the editor's contents when the form (if it is part of a form) is submitted. See the API reference for a full description of this method.

    Module loaders

    The files in the CodeMirror distribution contain shims for loading them (and their dependencies) in AMD or CommonJS environments. If the variables exports and module exist and have type object, CommonJS-style require will be used. If not, but there is a function define with an amd property present, AMD-style (RequireJS) will be used.

    It is possible to use Browserify or similar tools to statically build modules using CodeMirror. Alternatively, use RequireJS to dynamically load dependencies at runtime. Both of these approaches have the advantage that they don't use the global namespace and can, thus, do things like load multiple versions of CodeMirror alongside each other.

    Here's a simple example of using RequireJS to load CodeMirror:

    require([
      "cm/lib/codemirror", "cm/mode/htmlmixed/htmlmixed"
    ], function(CodeMirror) {
      CodeMirror.fromTextArea(document.getElementById("code"), {
        lineNumbers: true,
        mode: "htmlmixed"
      });
    });

    It will automatically load the modes that the mixed HTML mode depends on (XML, JavaScript, and CSS). Do not use RequireJS' paths option to configure the path to CodeMirror, since it will break loading submodules through relative paths. Use the packages configuration option instead, as in:

    require.config({
      packages: [{
        name: "codemirror",
        location: "../path/to/codemirror",
        main: "lib/codemirror"
      }]
    });

    Configuration

    Both the CodeMirror function and its fromTextArea method take as second (optional) argument an object containing configuration options. Any option not supplied like this will be taken from CodeMirror.defaults, an object containing the default options. You can update this object to change the defaults on your page.

    Options are not checked in any way, so setting bogus option values is bound to lead to odd errors.

    These are the supported options:

    value: string|CodeMirror.Doc
    The starting value of the editor. Can be a string, or a document object.
    mode: string|object
    The mode to use. When not given, this will default to the first mode that was loaded. It may be a string, which either simply names the mode or is a MIME type associated with the mode. The value "null" indicates no highlighting should be applied. Alternatively, it may be an object containing configuration options for the mode, with a name property that names the mode (for example {name: "javascript", json: true}). The demo pages for each mode contain information about what configuration parameters the mode supports. You can ask CodeMirror which modes and MIME types have been defined by inspecting the CodeMirror.modes and CodeMirror.mimeModes objects. The first maps mode names to their constructors, and the second maps MIME types to mode specs.
    lineSeparator: string|null
    Explicitly set the line separator for the editor. By default (value null), the document will be split on CRLFs as well as lone CRs and LFs, and a single LF will be used as line separator in all output (such as getValue). When a specific string is given, lines will only be split on that string, and output will, by default, use that same separator.
    theme: string
    The theme to style the editor with. You must make sure the CSS file defining the corresponding .cm-s-[name] styles is loaded (see the theme directory in the distribution). The default is "default", for which colors are included in codemirror.css. It is possible to use multiple theming classes at once—for example "foo bar" will assign both the cm-s-foo and the cm-s-bar classes to the editor.
    indentUnit: integer
    How many spaces a block (whatever that means in the edited language) should be indented. The default is 2.
    smartIndent: boolean
    Whether to use the context-sensitive indentation that the mode provides (or just indent the same as the line before). Defaults to true.
    tabSize: integer
    The width of a tab character. Defaults to 4.
    indentWithTabs: boolean
    Whether, when indenting, the first N*tabSize spaces should be replaced by N tabs. Default is false.
    electricChars: boolean
    Configures whether the editor should re-indent the current line when a character is typed that might change its proper indentation (only works if the mode supports indentation). Default is true.
    specialChars: RegExp
    A regular expression used to determine which characters should be replaced by a special placeholder. Mostly useful for non-printing special characters. The default is /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/.
    specialCharPlaceholder: function(char) → Element
    A function that, given a special character identified by the specialChars option, produces a DOM node that is used to represent the character. By default, a red dot (•) is shown, with a title tooltip to indicate the character code.
    direction: "ltr" | "rtl"
    Flips overall layout and selects base paragraph direction to be left-to-right or right-to-left. Default is "ltr". CodeMirror applies the Unicode Bidirectional Algorithm to each line, but does not autodetect base direction — it's set to the editor direction for all lines. The resulting order is sometimes wrong when base direction doesn't match user intent (for example, leading and trailing punctuation jumps to the wrong side of the line). Therefore, it's helpful for multilingual input to let users toggle this option.
    rtlMoveVisually: boolean
    Determines whether horizontal cursor movement through right-to-left (Arabic, Hebrew) text is visual (pressing the left arrow moves the cursor left) or logical (pressing the left arrow moves to the next lower index in the string, which is visually right in right-to-left text). The default is false on Windows, and true on other platforms.
    keyMap: string
    Configures the key map to use. The default is "default", which is the only key map defined in codemirror.js itself. Extra key maps are found in the key map directory. See the section on key maps for more information.
    extraKeys: object
    Can be used to specify extra key bindings for the editor, alongside the ones defined by keyMap. Should be either null, or a valid key map value.
    configureMouse: fn(cm: CodeMirror, repeat: "single" | "double" | "triple", event: Event) → Object
    Allows you to configure the behavior of mouse selection and dragging. The function is called when the left mouse button is pressed. The returned object may have the following properties:
    unit: "char" | "word" | "line" | "rectangle" | fn(CodeMirror, Pos) → {from: Pos, to: Pos}
    The unit by which to select. May be one of the built-in units or a function that takes a position and returns a range around that, for a custom unit. The default is to return "word" for double clicks, "line" for triple clicks, "rectangle" for alt-clicks (or, on Chrome OS, meta-shift-clicks), and "single" otherwise.
    extend: bool
    Whether to extend the existing selection range or start a new one. By default, this is enabled when shift clicking.
    addNew: bool
    When enabled, this adds a new range to the existing selection, rather than replacing it. The default behavior is to enable this for command-click on Mac OS, and control-click on other platforms.
    moveOnDrag: bool
    When the mouse even drags content around inside the editor, this controls whether it is copied (false) or moved (true). By default, this is enabled by alt-clicking on Mac OS, and ctrl-clicking elsewhere.
    lineWrapping: boolean
    Whether CodeMirror should scroll or wrap for long lines. Defaults to false (scroll).
    lineNumbers: boolean
    Whether to show line numbers to the left of the editor.
    firstLineNumber: integer
    At which number to start counting lines. Default is 1.
    lineNumberFormatter: function(line: integer) → string
    A function used to format line numbers. The function is passed the line number, and should return a string that will be shown in the gutter.
    gutters: array<string | {className: string, style: ?string}>
    Can be used to add extra gutters (beyond or instead of the line number gutter). Should be an array of CSS class names or class name / CSS string pairs, each of which defines a width (and optionally a background), and which will be used to draw the background of the gutters. May include the CodeMirror-linenumbers class, in order to explicitly set the position of the line number gutter (it will default to be to the right of all other gutters). These class names are the keys passed to setGutterMarker.
    fixedGutter: boolean
    Determines whether the gutter scrolls along with the content horizontally (false) or whether it stays fixed during horizontal scrolling (true, the default).
    scrollbarStyle: string
    Chooses a scrollbar implementation. The default is "native", showing native scrollbars. The core library also provides the "null" style, which completely hides the scrollbars. Addons can implement additional scrollbar models.
    coverGutterNextToScrollbar: boolean
    When fixedGutter is on, and there is a horizontal scrollbar, by default the gutter will be visible to the left of this scrollbar. If this option is set to true, it will be covered by an element with class CodeMirror-gutter-filler.
    inputStyle: string
    Selects the way CodeMirror handles input and focus. The core library defines the "textarea" and "contenteditable" input models. On mobile browsers, the default is "contenteditable". On desktop browsers, the default is "textarea". Support for IME and screen readers is better in the "contenteditable" model. The intention is to make it the default on modern desktop browsers in the future.
    readOnly: boolean|string
    This disables editing of the editor content by the user. If the special value "nocursor" is given (instead of simply true), focusing of the editor is also disallowed.
    screenReaderLabel: string
    This label is read by the screenreaders when CodeMirror text area is focused. This is helpful for accessibility.
    showCursorWhenSelecting: boolean
    Whether the cursor should be drawn when a selection is active. Defaults to false.
    lineWiseCopyCut: boolean
    When enabled, which is the default, doing copy or cut when there is no selection will copy or cut the whole lines that have cursors on them.
    pasteLinesPerSelection: boolean
    When pasting something from an external source (not from the editor itself), if the number of lines matches the number of selection, CodeMirror will by default insert one line per selection. You can set this to false to disable that behavior.
    selectionsMayTouch: boolean
    Determines whether multiple selections are joined as soon as they touch (the default) or only when they overlap (true).
    undoDepth: integer
    The maximum number of undo levels that the editor stores. Note that this includes selection change events. Defaults to 200.
    historyEventDelay: integer
    The period of inactivity (in milliseconds) that will cause a new history event to be started when typing or deleting. Defaults to 1250.
    tabindex: integer
    The tab index to assign to the editor. If not given, no tab index will be assigned.
    autofocus: boolean
    Can be used to make CodeMirror focus itself on initialization. Defaults to off. When fromTextArea is used, and no explicit value is given for this option, it will be set to true when either the source textarea is focused, or it has an autofocus attribute and no other element is focused.
    phrases: ?object
    Some addons run user-visible strings (such as labels in the interface) through the phrase method to allow for translation. This option determines the return value of that method. When it is null or an object that doesn't have a property named by the input string, that string is returned. Otherwise, the value of the property corresponding to that string is returned.

    Below this a few more specialized, low-level options are listed. These are only useful in very specific situations, you might want to skip them the first time you read this manual.

    dragDrop: boolean
    Controls whether drag-and-drop is enabled. On by default.
    allowDropFileTypes: array<string>
    When set (default is null) only files whose type is in the array can be dropped into the editor. The strings should be MIME types, and will be checked against the type of the File object as reported by the browser.
    cursorBlinkRate: number
    Half-period in milliseconds used for cursor blinking. The default blink rate is 530ms. By setting this to zero, blinking can be disabled. A negative value hides the cursor entirely.
    cursorScrollMargin: number
    How much extra space to always keep above and below the cursor when approaching the top or bottom of the visible view in a scrollable document. Default is 0.
    cursorHeight: number
    Determines the height of the cursor. Default is 1, meaning it spans the whole height of the line. For some fonts (and by some tastes) a smaller height (for example 0.85), which causes the cursor to not reach all the way to the bottom of the line, looks better
    singleCursorHeightPerLine: boolean
    If set to true (the default), will keep the cursor height constant for an entire line (or wrapped part of a line). When false, the cursor's height is based on the height of the adjacent reference character.
    resetSelectionOnContextMenu: boolean
    Controls whether, when the context menu is opened with a click outside of the current selection, the cursor is moved to the point of the click. Defaults to true.
    workTime, workDelay: number
    Highlighting is done by a pseudo background-thread that will work for workTime milliseconds, and then use timeout to sleep for workDelay milliseconds. The defaults are 200 and 300, you can change these options to make the highlighting more or less aggressive.
    pollInterval: number
    Indicates how quickly CodeMirror should poll its input textarea for changes (when focused). Most input is captured by events, but some things, like IME input on some browsers, don't generate events that allow CodeMirror to properly detect it. Thus, it polls. Default is 100 milliseconds.
    flattenSpans: boolean
    By default, CodeMirror will combine adjacent tokens into a single span if they have the same class. This will result in a simpler DOM tree, and thus perform better. With some kinds of styling (such as rounded corners), this will change the way the document looks. You can set this option to false to disable this behavior.
    addModeClass: boolean
    When enabled (off by default), an extra CSS class will be added to each token, indicating the (inner) mode that produced it, prefixed with "cm-m-". For example, tokens from the XML mode will get the cm-m-xml class.
    maxHighlightLength: number
    When highlighting long lines, in order to stay responsive, the editor will give up and simply style the rest of the line as plain text when it reaches a certain position. The default is 10 000. You can set this to Infinity to turn off this behavior.
    viewportMargin: integer
    Specifies the amount of lines that are rendered above and below the part of the document that's currently scrolled into view. This affects the amount of updates needed when scrolling, and the amount of work that such an update does. You should usually leave it at its default, 10. Can be set to Infinity to make sure the whole document is always rendered, and thus the browser's text search works on it. This will have bad effects on performance of big documents.
    spellcheck: boolean
    Specifies whether or not spellcheck will be enabled on the input.
    autocorrect: boolean
    Specifies whether or not autocorrect will be enabled on the input.
    autocapitalize: boolean
    Specifies whether or not autocapitalization will be enabled on the input.

    Events

    Various CodeMirror-related objects emit events, which allow client code to react to various situations. Handlers for such events can be registered with the on and off methods on the objects that the event fires on. To fire your own events, use CodeMirror.signal(target, name, args...), where target is a non-DOM-node object.

    An editor instance fires the following events. The instance argument always refers to the editor itself.

    "change" (instance: CodeMirror, changeObj: object)
    Fires every time the content of the editor is changed. The changeObj is a {from, to, text, removed, origin} object containing information about the changes that occurred as second argument. from and to are the positions (in the pre-change coordinate system) where the change started and ended (for example, it might be {ch:0, line:18} if the position is at the beginning of line #19). text is an array of strings representing the text that replaced the changed range (split by line). removed is the text that used to be between from and to, which is overwritten by this change. This event is fired before the end of an operation, before the DOM updates happen.
    "changes" (instance: CodeMirror, changes: array<object>)
    Like the "change" event, but batched per operation, passing an array containing all the changes that happened in the operation. This event is fired after the operation finished, and display changes it makes will trigger a new operation.
    "beforeChange" (instance: CodeMirror, changeObj: object)
    This event is fired before a change is applied, and its handler may choose to modify or cancel the change. The changeObj object has from, to, and text properties, as with the "change" event. It also has a cancel() method, which can be called to cancel the change, and, if the change isn't coming from an undo or redo event, an update(from, to, text) method, which may be used to modify the change. Undo or redo changes can't be modified, because they hold some metainformation for restoring old marked ranges that is only valid for that specific change. All three arguments to update are optional, and can be left off to leave the existing value for that field intact. Note: you may not do anything from a "beforeChange" handler that would cause changes to the document or its visualization. Doing so will, since this handler is called directly from the bowels of the CodeMirror implementation, probably cause the editor to become corrupted.
    "cursorActivity" (instance: CodeMirror)
    Will be fired when the cursor or selection moves, or any change is made to the editor content.
    "keyHandled" (instance: CodeMirror, name: string, event: Event)
    Fired after a key is handled through a key map. name is the name of the handled key (for example "Ctrl-X" or "'q'"), and event is the DOM keydown or keypress event.
    "inputRead" (instance: CodeMirror, changeObj: object)
    Fired whenever new input is read from the hidden textarea (typed or pasted by the user).
    "electricInput" (instance: CodeMirror, line: integer)
    Fired if text input matched the mode's electric patterns, and this caused the line's indentation to change.
    "beforeSelectionChange" (instance: CodeMirror, obj: {ranges, origin, update})
    This event is fired before the selection is moved. Its handler may inspect the set of selection ranges, present as an array of {anchor, head} objects in the ranges property of the obj argument, and optionally change them by calling the update method on this object, passing an array of ranges in the same format. The object also contains an origin property holding the origin string passed to the selection-changing method, if any. Handlers for this event have the same restriction as "beforeChange" handlers — they should not do anything to directly update the state of the editor.
    "viewportChange" (instance: CodeMirror, from: number, to: number)
    Fires whenever the view port of the editor changes (due to scrolling, editing, or any other factor). The from and to arguments give the new start and end of the viewport.
    "swapDoc" (instance: CodeMirror, oldDoc: Doc)
    This is signalled when the editor's document is replaced using the swapDoc method.
    "gutterClick" (instance: CodeMirror, line: integer, gutter: string, clickEvent: Event)
    Fires when the editor gutter (the line-number area) is clicked. Will pass the editor instance as first argument, the (zero-based) number of the line that was clicked as second argument, the CSS class of the gutter that was clicked as third argument, and the raw mousedown event object as fourth argument.
    "gutterContextMenu" (instance: CodeMirror, line: integer, gutter: string, contextMenu: Event: Event)
    Fires when the editor gutter (the line-number area) receives a contextmenu event. Will pass the editor instance as first argument, the (zero-based) number of the line that was clicked as second argument, the CSS class of the gutter that was clicked as third argument, and the raw contextmenu mouse event object as fourth argument. You can preventDefault the event, to signal that CodeMirror should do no further handling.
    "focus" (instance: CodeMirror, event: Event)
    Fires whenever the editor is focused.
    "blur" (instance: CodeMirror, event: Event)
    Fires whenever the editor is unfocused.
    "scroll" (instance: CodeMirror)
    Fires when the editor is scrolled.
    "refresh" (instance: CodeMirror)
    Fires when the editor is refreshed or resized. Mostly useful to invalidate cached values that depend on the editor or character size.
    "optionChange" (instance: CodeMirror, option: string)
    Dispatched every time an option is changed with setOption.
    "scrollCursorIntoView" (instance: CodeMirror, event: Event)
    Fires when the editor tries to scroll its cursor into view. Can be hooked into to take care of additional scrollable containers around the editor. When the event object has its preventDefault method called, CodeMirror will not itself try to scroll the window.
    "update" (instance: CodeMirror)
    Will be fired whenever CodeMirror updates its DOM display.
    "renderLine" (instance: CodeMirror, line: LineHandle, element: Element)
    Fired whenever a line is (re-)rendered to the DOM. Fired right after the DOM element is built, before it is added to the document. The handler may mess with the style of the resulting element, or add event handlers, but should not try to change the state of the editor.
    "mousedown", "dblclick", "touchstart", "contextmenu", "keydown", "keypress", "keyup", "cut", "copy", "paste", "dragstart", "dragenter", "dragover", "dragleave", "drop" (instance: CodeMirror, event: Event)
    Fired when CodeMirror is handling a DOM event of this type. You can preventDefault the event, or give it a truthy codemirrorIgnore property, to signal that CodeMirror should do no further handling.

    Document objects (instances of CodeMirror.Doc) emit the following events:

    "change" (doc: CodeMirror.Doc, changeObj: object)
    Fired whenever a change occurs to the document. changeObj has a similar type as the object passed to the editor's "change" event.
    "beforeChange" (doc: CodeMirror.Doc, change: object)
    See the description of the same event on editor instances.
    "cursorActivity" (doc: CodeMirror.Doc)
    Fired whenever the cursor or selection in this document changes.
    "beforeSelectionChange" (doc: CodeMirror.Doc, selection: {head, anchor})
    Equivalent to the event by the same name as fired on editor instances.

    Line handles (as returned by, for example, getLineHandle) support these events:

    "delete" ()
    Will be fired when the line object is deleted. A line object is associated with the start of the line. Mostly useful when you need to find out when your gutter markers on a given line are removed.
    "change" (line: LineHandle, changeObj: object)
    Fires when the line's text content is changed in any way (but the line is not deleted outright). The change object is similar to the one passed to change event on the editor object.

    Marked range handles (CodeMirror.TextMarker), as returned by markText and setBookmark, emit the following events:

    "beforeCursorEnter" ()
    Fired when the cursor enters the marked range. From this event handler, the editor state may be inspected but not modified, with the exception that the range on which the event fires may be cleared.
    "clear" (from: {line, ch}, to: {line, ch})
    Fired when the range is cleared, either through cursor movement in combination with clearOnEnter or through a call to its clear() method. Will only be fired once per handle. Note that deleting the range through text editing does not fire this event, because an undo action might bring the range back into existence. from and to give the part of the document that the range spanned when it was cleared.
    "hide" ()
    Fired when the last part of the marker is removed from the document by editing operations.
    "unhide" ()
    Fired when, after the marker was removed by editing, a undo operation brought the marker back.

    Line widgets (CodeMirror.LineWidget), returned by addLineWidget, fire these events:

    "redraw" ()
    Fired whenever the editor re-adds the widget to the DOM. This will happen once right after the widget is added (if it is scrolled into view), and then again whenever it is scrolled out of view and back in again, or when changes to the editor options or the line the widget is on require the widget to be redrawn.

    Key Maps

    Key maps are ways to associate keys and mouse buttons with functionality. A key map is an object mapping strings that identify the buttons to functions that implement their functionality.

    The CodeMirror distributions comes with Emacs, Vim, and Sublime Text-style keymaps.

    Keys are identified either by name or by character. The CodeMirror.keyNames object defines names for common keys and associates them with their key codes. Examples of names defined here are Enter, F5, and Q. These can be prefixed with Shift-, Cmd-, Ctrl-, and Alt- to specify a modifier. So for example, Shift-Ctrl-Space would be a valid key identifier.

    Common example: map the Tab key to insert spaces instead of a tab character.

    editor.setOption("extraKeys", {
      Tab: function(cm) {
        var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
        cm.replaceSelection(spaces);
      }
    });

    Alternatively, a character can be specified directly by surrounding it in single quotes, for example '$' or 'q'. Due to limitations in the way browsers fire key events, these may not be prefixed with modifiers.

    To bind mouse buttons, use the names `LeftClick`, `MiddleClick`, and `RightClick`. These can also be prefixed with modifiers, and in addition, the word `Double` or `Triple` can be put before `Click` (as in `LeftDoubleClick`) to bind a double- or triple-click. The function for such a binding is passed the position that was clicked as second argument.

    Multi-stroke key bindings can be specified by separating the key names by spaces in the property name, for example Ctrl-X Ctrl-V. When a map contains multi-stoke bindings or keys with modifiers that are not specified in the default order (Shift-Cmd-Ctrl-Alt), you must call CodeMirror.normalizeKeyMap on it before it can be used. This function takes a keymap and modifies it to normalize modifier order and properly recognize multi-stroke bindings. It will return the keymap itself.

    The CodeMirror.keyMap object associates key maps with names. User code and key map definitions can assign extra properties to this object. Anywhere where a key map is expected, a string can be given, which will be looked up in this object. It also contains the "default" key map holding the default bindings.

    The values of properties in key maps can be either functions of a single argument (the CodeMirror instance), strings, or false. Strings refer to commands, which are described below. If the property is set to false, CodeMirror leaves handling of the key up to the browser. A key handler function may return CodeMirror.Pass to indicate that it has decided not to handle the key, and other handlers (or the default behavior) should be given a turn.

    Keys mapped to command names that start with the characters "go" or to functions that have a truthy motion property (which should be used for cursor-movement actions) will be fired even when an extra Shift modifier is present (i.e. "Up": "goLineUp" matches both up and shift-up). This is used to easily implement shift-selection.

    Key maps can defer to each other by defining a fallthrough property. This indicates that when a key is not found in the map itself, one or more other maps should be searched. It can hold either a single key map or an array of key maps.

    When a key map needs to set something up when it becomes active, or tear something down when deactivated, it can contain attach and/or detach properties, which should hold functions that take the editor instance and the next or previous keymap. Note that this only works for the top-level keymap, not for fallthrough maps or maps added with extraKeys or addKeyMap.

    Commands

    Commands are parameter-less actions that can be performed on an editor. Their main use is for key bindings. Commands are defined by adding properties to the CodeMirror.commands object. A number of common commands are defined by the library itself, most of them used by the default key bindings. The value of a command property must be a function of one argument (an editor instance).

    Some of the commands below are referenced in the default key map, but not defined by the core library. These are intended to be defined by user code or addons.

    Commands can also be run with the execCommand method.

    selectAllCtrl-A (PC), Cmd-A (Mac)
    Select the whole content of the editor.
    singleSelectionEsc
    When multiple selections are present, this deselects all but the primary selection.
    killLineCtrl-K (Mac)
    Emacs-style line killing. Deletes the part of the line after the cursor. If that consists only of whitespace, the newline at the end of the line is also deleted.
    deleteLineCtrl-D (PC), Cmd-D (Mac)
    Deletes the whole line under the cursor, including newline at the end.
    delLineLeft
    Delete the part of the line before the cursor.
    delWrappedLineLeftCmd-Backspace (Mac)
    Delete the part of the line from the left side of the visual line the cursor is on to the cursor.
    delWrappedLineRightCmd-Delete (Mac)
    Delete the part of the line from the cursor to the right side of the visual line the cursor is on.
    undoCtrl-Z (PC), Cmd-Z (Mac)
    Undo the last change. Note that, because browsers still don't make it possible for scripts to react to or customize the context menu, selecting undo (or redo) from the context menu in a CodeMirror instance does not work.
    redoCtrl-Y (PC), Shift-Cmd-Z (Mac), Cmd-Y (Mac)
    Redo the last undone change.
    undoSelectionCtrl-U (PC), Cmd-U (Mac)
    Undo the last change to the selection, or if there are no selection-only changes at the top of the history, undo the last change.
    redoSelectionAlt-U (PC), Shift-Cmd-U (Mac)
    Redo the last change to the selection, or the last text change if no selection changes remain.
    goDocStartCtrl-Home (PC), Cmd-Up (Mac), Cmd-Home (Mac)
    Move the cursor to the start of the document.
    goDocEndCtrl-End (PC), Cmd-End (Mac), Cmd-Down (Mac)
    Move the cursor to the end of the document.
    goLineStartAlt-Left (PC), Ctrl-A (Mac)
    Move the cursor to the start of the line.
    goLineStartSmartHome
    Move to the start of the text on the line, or if we are already there, to the actual start of the line (including whitespace).
    goLineEndAlt-Right (PC), Ctrl-E (Mac)
    Move the cursor to the end of the line.
    goLineRightCmd-Right (Mac)
    Move the cursor to the right side of the visual line it is on.
    goLineLeftCmd-Left (Mac)
    Move the cursor to the left side of the visual line it is on. If this line is wrapped, that may not be the start of the line.
    goLineLeftSmart
    Move the cursor to the left side of the visual line it is on. If that takes it to the start of the line, behave like goLineStartSmart.
    goLineUpUp, Ctrl-P (Mac)
    Move the cursor up one line.
    goLineDownDown, Ctrl-N (Mac)
    Move down one line.
    goPageUpPageUp, Shift-Ctrl-V (Mac)
    Move the cursor up one screen, and scroll up by the same distance.
    goPageDownPageDown, Ctrl-V (Mac)
    Move the cursor down one screen, and scroll down by the same distance.
    goCharLeftLeft, Ctrl-B (Mac)
    Move the cursor one character left, going to the previous line when hitting the start of line.
    goCharRightRight, Ctrl-F (Mac)
    Move the cursor one character right, going to the next line when hitting the end of line.
    goColumnLeft
    Move the cursor one character left, but don't cross line boundaries.
    goColumnRight
    Move the cursor one character right, don't cross line boundaries.
    goWordLeftAlt-B (Mac)
    Move the cursor to the start of the previous word.
    goWordRightAlt-F (Mac)
    Move the cursor to the end of the next word.
    goGroupLeftCtrl-Left (PC), Alt-Left (Mac)
    Move to the left of the group before the cursor. A group is a stretch of word characters, a stretch of punctuation characters, a newline, or a stretch of more than one whitespace character.
    goGroupRightCtrl-Right (PC), Alt-Right (Mac)
    Move to the right of the group after the cursor (see above).
    delCharBeforeShift-Backspace, Ctrl-H (Mac)
    Delete the character before the cursor.
    delCharAfterDelete, Ctrl-D (Mac)
    Delete the character after the cursor.
    delWordBeforeAlt-Backspace (Mac)
    Delete up to the start of the word before the cursor.
    delWordAfterAlt-D (Mac)
    Delete up to the end of the word after the cursor.
    delGroupBeforeCtrl-Backspace (PC), Alt-Backspace (Mac)
    Delete to the left of the group before the cursor.
    delGroupAfterCtrl-Delete (PC), Ctrl-Alt-Backspace (Mac), Alt-Delete (Mac)
    Delete to the start of the group after the cursor.
    indentAutoShift-Tab
    Auto-indent the current line or selection.
    indentMoreCtrl-] (PC), Cmd-] (Mac)
    Indent the current line or selection by one indent unit.
    indentLessCtrl-[ (PC), Cmd-[ (Mac)
    Dedent the current line or selection by one indent unit.
    insertTab
    Insert a tab character at the cursor.
    insertSoftTab
    Insert the amount of spaces that match the width a tab at the cursor position would have.
    defaultTabTab
    If something is selected, indent it by one indent unit. If nothing is selected, insert a tab character.
    transposeCharsCtrl-T (Mac)
    Swap the characters before and after the cursor.
    newlineAndIndentEnter
    Insert a newline and auto-indent the new line.
    toggleOverwriteInsert
    Flip the overwrite flag.
    saveCtrl-S (PC), Cmd-S (Mac)
    Not defined by the core library, only referred to in key maps. Intended to provide an easy way for user code to define a save command.
    findCtrl-F (PC), Cmd-F (Mac)
    findNextCtrl-G (PC), Cmd-G (Mac)
    findPrevShift-Ctrl-G (PC), Shift-Cmd-G (Mac)
    replaceShift-Ctrl-F (PC), Cmd-Alt-F (Mac)
    replaceAllShift-Ctrl-R (PC), Shift-Cmd-Alt-F (Mac)
    Not defined by the core library, but defined in the search addon (or custom client addons).

    Customized Styling

    Up to a certain extent, CodeMirror's look can be changed by modifying style sheet files. The style sheets supplied by modes simply provide the colors for that mode, and can be adapted in a very straightforward way. To style the editor itself, it is possible to alter or override the styles defined in codemirror.css.

    Some care must be taken there, since a lot of the rules in this file are necessary to have CodeMirror function properly. Adjusting colors should be safe, of course, and with some care a lot of other things can be changed as well. The CSS classes defined in this file serve the following roles:

    CodeMirror
    The outer element of the editor. This should be used for the editor width, height, borders and positioning. Can also be used to set styles that should hold for everything inside the editor (such as font and font size), or to set a background. Setting this class' height style to auto will make the editor resize to fit its content (it is recommended to also set the viewportMargin option to Infinity when doing this.
    CodeMirror-focused
    Whenever the editor is focused, the top element gets this class. This is used to hide the cursor and give the selection a different color when the editor is not focused.
    CodeMirror-gutters
    This is the backdrop for all gutters. Use it to set the default gutter background color, and optionally add a border on the right of the gutters.
    CodeMirror-linenumbers
    Use this for giving a background or width to the line number gutter.
    CodeMirror-linenumber
    Used to style the actual individual line numbers. These won't be children of the CodeMirror-linenumbers (plural) element, but rather will be absolutely positioned to overlay it. Use this to set alignment and text properties for the line numbers.
    CodeMirror-lines
    The visible lines. This is where you specify vertical padding for the editor content.
    CodeMirror-cursor
    The cursor is a block element that is absolutely positioned. You can make it look whichever way you want.
    CodeMirror-selected
    The selection is represented by span elements with this class.
    CodeMirror-matchingbracket, CodeMirror-nonmatchingbracket
    These are used to style matched (or unmatched) brackets.

    If your page's style sheets do funky things to all div or pre elements (you probably shouldn't do that), you'll have to define rules to cancel these effects out again for elements under the CodeMirror class.

    Themes are also simply CSS files, which define colors for various syntactic elements. See the files in the theme directory.

    Programming API

    A lot of CodeMirror features are only available through its API. Thus, you need to write code (or use addons) if you want to expose them to your users.

    Whenever points in the document are represented, the API uses objects with line and ch properties. Both are zero-based. CodeMirror makes sure to 'clip' any positions passed by client code so that they fit inside the document, so you shouldn't worry too much about sanitizing your coordinates. If you give ch a value of null, or don't specify it, it will be replaced with the length of the specified line. Such positions may also have a sticky property holding "before" or "after", whether the position is associated with the character before or after it. This influences, for example, where the cursor is drawn on a line-break or bidi-direction boundary.

    Methods prefixed with doc. can, unless otherwise specified, be called both on CodeMirror (editor) instances and CodeMirror.Doc instances. Methods prefixed with cm. are only available on CodeMirror instances.

    Constructor

    Constructing an editor instance is done with the CodeMirror(place: Element|fn(Element), ?option: object) constructor. If the place argument is a DOM element, the editor will be appended to it. If it is a function, it will be called, and is expected to place the editor into the document. options may be an element mapping option names to values. The options that it doesn't explicitly specify (or all options, if it is not passed) will be taken from CodeMirror.defaults.

    Note that the options object passed to the constructor will be mutated when the instance's options are changed, so you shouldn't share such objects between instances.

    See CodeMirror.fromTextArea for another way to construct an editor instance.

    Content manipulation methods

    doc.getValue(?separator: string) → string
    Get the current editor content. You can pass it an optional argument to specify the string to be used to separate lines (defaults to "\n").
    doc.setValue(content: string)
    Set the editor content.
    doc.getRange(from: {line, ch}, to: {line, ch}, ?separator: string) → string
    Get the text between the given points in the editor, which should be {line, ch} objects. An optional third argument can be given to indicate the line separator string to use (defaults to "\n").
    doc.replaceRange(replacement: string, from: {line, ch}, to: {line, ch}, ?origin: string)
    Replace the part of the document between from and to with the given string. from and to must be {line, ch} objects. to can be left off to simply insert the string at position from. When origin is given, it will be passed on to "change" events, and its first letter will be used to determine whether this change can be merged with previous history events, in the way described for selection origins.
    doc.getLine(n: integer) → string
    Get the content of line n.
    doc.lineCount() → integer
    Get the number of lines in the editor.
    doc.firstLine() → integer
    Get the number of first line in the editor. This will usually be zero but for linked sub-views, or documents instantiated with a non-zero first line, it might return other values.
    doc.lastLine() → integer
    Get the number of last line in the editor. This will usually be doc.lineCount() - 1, but for linked sub-views, it might return other values.
    doc.getLineHandle(num: integer) → LineHandle
    Fetches the line handle for the given line number.
    doc.getLineNumber(handle: LineHandle) → integer
    Given a line handle, returns the current position of that line (or null when it is no longer in the document).
    doc.eachLine(f: (line: LineHandle))
    doc.eachLine(start: integer, end: integer, f: (line: LineHandle))
    Iterate over the whole document, or if start and end line numbers are given, the range from start up to (not including) end, and call f for each line, passing the line handle. eachLine stops iterating if f returns truthy value. This is a faster way to visit a range of line handlers than calling getLineHandle for each of them. Note that line handles have a text property containing the line's content (as a string).
    doc.markClean()
    Set the editor content as 'clean', a flag that it will retain until it is edited, and which will be set again when such an edit is undone again. Useful to track whether the content needs to be saved. This function is deprecated in favor of changeGeneration, which allows multiple subsystems to track different notions of cleanness without interfering.
    doc.changeGeneration(?closeEvent: boolean) → integer
    Returns a number that can later be passed to isClean to test whether any edits were made (and not undone) in the meantime. If closeEvent is true, the current history event will be ‘closed’, meaning it can't be combined with further changes (rapid typing or deleting events are typically combined).
    doc.isClean(?generation: integer) → boolean
    Returns whether the document is currently clean — not modified since initialization or the last call to markClean if no argument is passed, or since the matching call to changeGeneration if a generation value is given.

    Cursor and selection methods

    doc.getSelection(?lineSep: string) → string
    Get the currently selected code. Optionally pass a line separator to put between the lines in the output. When multiple selections are present, they are concatenated with instances of lineSep in between.
    doc.getSelections(?lineSep: string) → array<string>
    Returns an array containing a string for each selection, representing the content of the selections.
    doc.replaceSelection(replacement: string, ?select: string)
    Replace the selection(s) with the given string. By default, the new selection ends up after the inserted text. The optional select argument can be used to change this—passing "around" will cause the new text to be selected, passing "start" will collapse the selection to the start of the inserted text.
    doc.replaceSelections(replacements: array<string>, ?select: string)
    The length of the given array should be the same as the number of active selections. Replaces the content of the selections with the strings in the array. The select argument works the same as in replaceSelection.
    doc.getCursor(?start: string) → {line, ch}
    Retrieve one end of the primary selection. start is an optional string indicating which end of the selection to return. It may be "from", "to", "head" (the side of the selection that moves when you press shift+arrow), or "anchor" (the fixed side of the selection). Omitting the argument is the same as passing "head". A {line, ch} object will be returned.
    doc.listSelections() → array<{anchor, head}>
    Retrieves a list of all current selections. These will always be sorted, and never overlap (overlapping selections are merged). Each object in the array contains anchor and head properties referring to {line, ch} objects.
    doc.somethingSelected() → boolean
    Return true if any text is selected.
    doc.setCursor(pos: {line, ch}|number, ?ch: number, ?options: object)
    Set the cursor position. You can either pass a single {line, ch} object, or the line and the character as two separate parameters. Will replace all selections with a single, empty selection at the given position. The supported options are the same as for setSelection.
    doc.setSelection(anchor: {line, ch}, ?head: {line, ch}, ?options: object)
    Set a single selection range. anchor and head should be {line, ch} objects. head defaults to anchor when not given. These options are supported:
    scroll: boolean
    Determines whether the selection head should be scrolled into view. Defaults to true.
    origin: string
    Determines whether the selection history event may be merged with the previous one. When an origin starts with the character +, and the last recorded selection had the same origin and was similar (close in time, both collapsed or both non-collapsed), the new one will replace the old one. When it starts with *, it will always replace the previous event (if that had the same origin). Built-in motion uses the "+move" origin. User input uses the "+input" origin.
    bias: number
    Determine the direction into which the selection endpoints should be adjusted when they fall inside an atomic range. Can be either -1 (backward) or 1 (forward). When not given, the bias will be based on the relative position of the old selection—the editor will try to move further away from that, to prevent getting stuck.
    doc.setSelections(ranges: array<{anchor, ?head}>, ?primary: integer, ?options: object)
    Sets a new set of selections. There must be at least one selection in the given array. When primary is a number, it determines which selection is the primary one. When it is not given, the primary index is taken from the previous selection, or set to the last range if the previous selection had less ranges than the new one. Supports the same options as setSelection. head defaults to anchor when not given.
    doc.addSelection(anchor: {line, ch}, ?head: {line, ch})
    Adds a new selection to the existing set of selections, and makes it the primary selection.
    doc.extendSelection(from: {line, ch}, ?to: {line, ch}, ?options: object)
    Similar to setSelection, but will, if shift is held or the extending flag is set, move the head of the selection while leaving the anchor at its current place. to is optional, and can be passed to ensure a region (for example a word or paragraph) will end up selected (in addition to whatever lies between that region and the current anchor). When multiple selections are present, all but the primary selection will be dropped by this method. Supports the same options as setSelection.
    doc.extendSelections(heads: array<{line, ch}>, ?options: object)
    An equivalent of extendSelection that acts on all selections at once.
    doc.extendSelectionsBy(f: function(range: {anchor, head}) → {line, ch}), ?options: object)
    Applies the given function to all existing selections, and calls extendSelections on the result.
    doc.setExtending(value: boolean)
    Sets or clears the 'extending' flag, which acts similar to the shift key, in that it will cause cursor movement and calls to extendSelection to leave the selection anchor in place.
    doc.getExtending() → boolean
    Get the value of the 'extending' flag.
    cm.hasFocus() → boolean
    Tells you whether the editor currently has focus.
    cm.findPosH(start: {line, ch}, amount: integer, unit: string, visually: boolean) → {line, ch, ?hitSide: boolean}
    Used to find the target position for horizontal cursor motion. start is a {line, ch} object, amount an integer (may be negative), and unit one of the string "char", "column", or "word". Will return a position that is produced by moving amount times the distance specified by unit. When visually is true, motion in right-to-left text will be visual rather than logical. When the motion was clipped by hitting the end or start of the document, the returned value will have a hitSide property set to true.
    cm.findPosV(start: {line, ch}, amount: integer, unit: string) → {line, ch, ?hitSide: boolean}
    Similar to findPosH, but used for vertical motion. unit may be "line" or "page". The other arguments and the returned value have the same interpretation as they have in findPosH.
    cm.findWordAt(pos: {line, ch}) → {anchor: {line, ch}, head: {line, ch}}
    Returns the start and end of the 'word' (the stretch of letters, whitespace, or punctuation) at the given position.

    Configuration methods

    cm.setOption(option: string, value: any)
    Change the configuration of the editor. option should the name of an option, and value should be a valid value for that option.
    cm.getOption(option: string) → any
    Retrieves the current value of the given option for this editor instance.
    cm.addKeyMap(map: object, bottom: boolean)
    Attach an additional key map to the editor. This is mostly useful for addons that need to register some key handlers without trampling on the extraKeys option. Maps added in this way have a higher precedence than the extraKeys and keyMap options, and between them, the maps added earlier have a lower precedence than those added later, unless the bottom argument was passed, in which case they end up below other key maps added with this method.
    cm.removeKeyMap(map: object)
    Disable a keymap added with addKeyMap. Either pass in the key map object itself, or a string, which will be compared against the name property of the active key maps.
    cm.addOverlay(mode: string|object, ?options: object)
    Enable a highlighting overlay. This is a stateless mini-mode that can be used to add extra highlighting. For example, the search addon uses it to highlight the term that's currently being searched. mode can be a mode spec or a mode object (an object with a token method). The options parameter is optional. If given, it should be an object, optionally containing the following options:
    opaque: bool
    Defaults to off, but can be given to allow the overlay styling, when not null, to override the styling of the base mode entirely, instead of the two being applied together.
    priority: number
    Determines the ordering in which the overlays are applied. Those with high priority are applied after those with lower priority, and able to override the opaqueness of the ones that come before. Defaults to 0.
    cm.removeOverlay(mode: string|object)
    Pass this the exact value passed for the mode parameter to addOverlay, or a string that corresponds to the name property of that value, to remove an overlay again.
    cm.on(type: string, func: (...args))
    Register an event handler for the given event type (a string) on the editor instance. There is also a CodeMirror.on(object, type, func) version that allows registering of events on any object.
    cm.off(type: string, func: (...args))
    Remove an event handler on the editor instance. An equivalent CodeMirror.off(object, type, func) also exists.

    Document management methods

    Each editor is associated with an instance of CodeMirror.Doc, its document. A document represents the editor content, plus a selection, an undo history, and a mode. A document can only be associated with a single editor at a time. You can create new documents by calling the CodeMirror.Doc(text: string, mode: Object, firstLineNumber: ?number, lineSeparator: ?string) constructor. The last three arguments are optional and can be used to set a mode for the document, make it start at a line number other than 0, and set a specific line separator respectively.

    cm.getDoc() → Doc
    Retrieve the currently active document from an editor.
    doc.getEditor() → CodeMirror
    Retrieve the editor associated with a document. May return null.
    cm.swapDoc(doc: CodeMirror.Doc) → Doc
    Attach a new document to the editor. Returns the old document, which is now no longer associated with an editor.
    doc.copy(copyHistory: boolean) → Doc
    Create an identical copy of the given doc. When copyHistory is true, the history will also be copied. Can not be called directly on an editor.
    doc.linkedDoc(options: object) → Doc
    Create a new document that's linked to the target document. Linked documents will stay in sync (changes to one are also applied to the other) until unlinked. These are the options that are supported:
    sharedHist: boolean
    When turned on, the linked copy will share an undo history with the original. Thus, something done in one of the two can be undone in the other, and vice versa.
    from: integer
    to: integer
    Can be given to make the new document a subview of the original. Subviews only show a given range of lines. Note that line coordinates inside the subview will be consistent with those of the parent, so that for example a subview starting at line 10 will refer to its first line as line 10, not 0.
    mode: string|object
    By default, the new document inherits the mode of the parent. This option can be set to a mode spec to give it a different mode.
    doc.unlinkDoc(doc: CodeMirror.Doc)
    Break the link between two documents. After calling this, changes will no longer propagate between the documents, and, if they had a shared history, the history will become separate.
    doc.iterLinkedDocs(function: (doc: CodeMirror.Doc, sharedHist: boolean))
    Will call the given function for all documents linked to the target document. It will be passed two arguments, the linked document and a boolean indicating whether that document shares history with the target.

    History-related methods

    doc.undo()
    Undo one edit (if any undo events are stored).
    doc.redo()
    Redo one undone edit.
    doc.undoSelection()
    Undo one edit or selection change.
    doc.redoSelection()
    Redo one undone edit or selection change.
    doc.historySize() → {undo: integer, redo: integer}
    Returns an object with {undo, redo} properties, both of which hold integers, indicating the amount of stored undo and redo operations.
    doc.clearHistory()
    Clears the editor's undo history.
    doc.getHistory() → object
    Get a (JSON-serializable) representation of the undo history.
    doc.setHistory(history: object)
    Replace the editor's undo history with the one provided, which must be a value as returned by getHistory. Note that this will have entirely undefined results if the editor content isn't also the same as it was when getHistory was called.

    Text-marking methods

    doc.markText(from: {line, ch}, to: {line, ch}, ?options: object) → TextMarker
    Can be used to mark a range of text with a specific CSS class name. from and to should be {line, ch} objects. The options parameter is optional. When given, it should be an object that may contain the following configuration options:
    className: string
    Assigns a CSS class to the marked stretch of text.
    inclusiveLeft: boolean
    Determines whether text inserted on the left of the marker will end up inside or outside of it.
    inclusiveRight: boolean
    Like inclusiveLeft, but for the right side.
    selectLeft: boolean
    For atomic ranges, determines whether the cursor is allowed to be placed directly to the left of the range. Has no effect on non-atomic ranges.
    selectRight: boolean
    Like selectLeft, but for the right side.
    atomic: boolean
    Atomic ranges act as a single unit when cursor movement is concerned—i.e. it is impossible to place the cursor inside of them. You can control whether the cursor is allowed to be placed directly before or after them using selectLeft or selectRight. If selectLeft (or right) is not provided, then inclusiveLeft (or right) will control this behavior.
    collapsed: boolean
    Collapsed ranges do not show up in the display. Setting a range to be collapsed will automatically make it atomic.
    clearOnEnter: boolean
    When enabled, will cause the mark to clear itself whenever the cursor enters its range. This is mostly useful for text-replacement widgets that need to 'snap open' when the user tries to edit them. The "clear" event fired on the range handle can be used to be notified when this happens.
    clearWhenEmpty: boolean
    Determines whether the mark is automatically cleared when it becomes empty. Default is true.
    replacedWith: Element
    Use a given node to display this range. Implies both collapsed and atomic. The given DOM node must be an inline element (as opposed to a block element).
    handleMouseEvents: boolean
    When replacedWith is given, this determines whether the editor will capture mouse and drag events occurring in this widget. Default is false—the events will be left alone for the default browser handler, or specific handlers on the widget, to capture.
    readOnly: boolean
    A read-only span can, as long as it is not cleared, not be modified except by calling setValue to reset the whole document. Note: adding a read-only span currently clears the undo history of the editor, because existing undo events being partially nullified by read-only spans would corrupt the history (in the current implementation).
    addToHistory: boolean
    When set to true (default is false), adding this marker will create an event in the undo history that can be individually undone (clearing the marker).
    startStyle: string
    Can be used to specify an extra CSS class to be applied to the leftmost span that is part of the marker.
    endStyle: string
    Equivalent to startStyle, but for the rightmost span.
    css: string
    A string of CSS to be applied to the covered text. For example "color: #fe3".
    attributes: object
    When given, add the attributes in the given object to the elements created for the marked text. Adding class or style attributes this way is not supported.
    shared: boolean
    When the target document is linked to other documents, you can set shared to true to make the marker appear in all documents. By default, a marker appears only in its target document.
    The method will return an object that represents the marker (with constructor CodeMirror.TextMarker), which exposes three methods: clear(), to remove the mark, find(), which returns a {from, to} object (both holding document positions), indicating the current position of the marked range, or undefined if the marker is no longer in the document, and finally changed(), which you can call if you've done something that might change the size of the marker (for example changing the content of a replacedWith node), and want to cheaply update the display.
    doc.setBookmark(pos: {line, ch}, ?options: object) → TextMarker
    Inserts a bookmark, a handle that follows the text around it as it is being edited, at the given position. A bookmark has two methods find() and clear(). The first returns the current position of the bookmark, if it is still in the document, and the second explicitly removes the bookmark. The options argument is optional. If given, the following properties are recognized:
    widget: Element
    Can be used to display a DOM node at the current location of the bookmark (analogous to the replacedWith option to markText).
    insertLeft: boolean
    By default, text typed when the cursor is on top of the bookmark will end up to the right of the bookmark. Set this option to true to make it go to the left instead.
    shared: boolean
    See the corresponding option to markText.
    handleMouseEvents: boolean
    As with markText, this determines whether mouse events on the widget inserted for this bookmark are handled by CodeMirror. The default is false.
    doc.findMarks(from: {line, ch}, to: {line, ch}) → array<TextMarker>
    Returns an array of all the bookmarks and marked ranges found between the given positions (non-inclusive).
    doc.findMarksAt(pos: {line, ch}) → array<TextMarker>
    Returns an array of all the bookmarks and marked ranges present at the given position.
    doc.getAllMarks() → array<TextMarker>
    Returns an array containing all marked ranges in the document.

    Widget, gutter, and decoration methods

    doc.setGutterMarker(line: integer|LineHandle, gutterID: string, value: Element) → LineHandle
    Sets the gutter marker for the given gutter (identified by its CSS class, see the gutters option) to the given value. Value can be either null, to clear the marker, or a DOM element, to set it. The DOM element will be shown in the specified gutter next to the specified line.
    doc.clearGutter(gutterID: string)
    Remove all gutter markers in the gutter with the given ID.
    doc.addLineClass(line: integer|LineHandle, where: string, class: string) → LineHandle
    Set a CSS class name for the given line. line can be a number or a line handle. where determines to which element this class should be applied, can be one of "text" (the text element, which lies in front of the selection), "background" (a background element that will be behind the selection), "gutter" (the line's gutter space), or "wrap" (the wrapper node that wraps all of the line's elements, including gutter elements). class should be the name of the class to apply.
    doc.removeLineClass(line: integer|LineHandle, where: string, class: string) → LineHandle
    Remove a CSS class from a line. line can be a line handle or number. where should be one of "text", "background", or "wrap" (see addLineClass). class can be left off to remove all classes for the specified node, or be a string to remove only a specific class.
    doc.lineInfo(line: integer|LineHandle) → object
    Returns the line number, text content, and marker status of the given line, which can be either a number or a line handle. The returned object has the structure {line, handle, text, gutterMarkers, textClass, bgClass, wrapClass, widgets}, where gutterMarkers is an object mapping gutter IDs to marker elements, and widgets is an array of line widgets attached to this line, and the various class properties refer to classes added with addLineClass.
    cm.addWidget(pos: {line, ch}, node: Element, scrollIntoView: boolean)
    Puts node, which should be an absolutely positioned DOM node, into the editor, positioned right below the given {line, ch} position. When scrollIntoView is true, the editor will ensure that the entire node is visible (if possible). To remove the widget again, simply use DOM methods (move it somewhere else, or call removeChild on its parent).
    doc.addLineWidget(line: integer|LineHandle, node: Element, ?options: object) → LineWidget
    Adds a line widget, an element shown below a line, spanning the whole of the editor's width, and moving the lines below it downwards. line should be either an integer or a line handle, and node should be a DOM node, which will be displayed below the given line. options, when given, should be an object that configures the behavior of the widget. The following options are supported (all default to false):
    coverGutter: boolean
    Whether the widget should cover the gutter.
    noHScroll: boolean
    Whether the widget should stay fixed in the face of horizontal scrolling.
    above: boolean
    Causes the widget to be placed above instead of below the text of the line.
    handleMouseEvents: boolean
    Determines whether the editor will capture mouse and drag events occurring in this widget. Default is false—the events will be left alone for the default browser handler, or specific handlers on the widget, to capture.
    insertAt: integer
    By default, the widget is added below other widgets for the line. This option can be used to place it at a different position (zero for the top, N to put it after the Nth other widget). Note that this only has effect once, when the widget is created.
    className: string
    Add an extra CSS class name to the wrapper element created for the widget.
    Note that the widget node will become a descendant of nodes with CodeMirror-specific CSS classes, and those classes might in some cases affect it. This method returns an object that represents the widget placement. It'll have a line property pointing at the line handle that it is associated with, and the following methods:
    clear()
    Removes the widget.
    changed()
    Call this if you made some change to the widget's DOM node that might affect its height. It'll force CodeMirror to update the height of the line that contains the widget.

    Sizing, scrolling and positioning methods

    cm.setSize(width: number|string, height: number|string)
    Programmatically set the size of the editor (overriding the applicable CSS rules). width and height can be either numbers (interpreted as pixels) or CSS units ("100%", for example). You can pass null for either of them to indicate that that dimension should not be changed.
    cm.scrollTo(x: number, y: number)
    Scroll the editor to a given (pixel) position. Both arguments may be left as null or undefined to have no effect.
    cm.getScrollInfo() → {left, top, width, height, clientWidth, clientHeight}
    Get an {left, top, width, height, clientWidth, clientHeight} object that represents the current scroll position, the size of the scrollable area, and the size of the visible area (minus scrollbars).
    cm.scrollIntoView(what: {line, ch}|{left, top, right, bottom}|{from, to}|null, ?margin: number)
    Scrolls the given position into view. what may be null to scroll the cursor into view, a {line, ch} position to scroll a character into view, a {left, top, right, bottom} pixel range (in editor-local coordinates), or a range {from, to} containing either two character positions or two pixel squares. The margin parameter is optional. When given, it indicates the amount of vertical pixels around the given area that should be made visible as well.
    cm.cursorCoords(where: boolean|{line, ch}, mode: string) → {left, top, bottom}
    Returns an {left, top, bottom} object containing the coordinates of the cursor position. If mode is "local", they will be relative to the top-left corner of the editable document. If it is "page" or not given, they are relative to the top-left corner of the page. If mode is "window", the coordinates are relative to the top-left corner of the currently visible (scrolled) window. where can be a boolean indicating whether you want the start (true) or the end (false) of the selection, or, if a {line, ch} object is given, it specifies the precise position at which you want to measure.
    cm.charCoords(pos: {line, ch}, ?mode: string) → {left, right, top, bottom}
    Returns the position and dimensions of an arbitrary character. pos should be a {line, ch} object. This differs from cursorCoords in that it'll give the size of the whole character, rather than just the position that the cursor would have when it would sit at that position.
    cm.coordsChar(object: {left, top}, ?mode: string) → {line, ch}
    Given an {left, top} object (e.g. coordinates of a mouse event) returns the {line, ch} position that corresponds to it. The optional mode parameter determines relative to what the coordinates are interpreted. It may be "window", "page" (the default), or "local".
    cm.lineAtHeight(height: number, ?mode: string) → number
    Computes the line at the given pixel height. mode can be one of the same strings that coordsChar accepts.
    cm.heightAtLine(line: integer|LineHandle, ?mode: string, ?includeWidgets: bool) → number
    Computes the height of the top of a line, in the coordinate system specified by mode (see coordsChar), which defaults to "page". When a line below the bottom of the document is specified, the returned value is the bottom of the last line in the document. By default, the position of the actual text is returned. If `includeWidgets` is true and the line has line widgets, the position above the first line widget is returned.
    cm.defaultTextHeight() → number
    Returns the line height of the default font for the editor.
    cm.defaultCharWidth() → number
    Returns the pixel width of an 'x' in the default font for the editor. (Note that for non-monospace fonts, this is mostly useless, and even for monospace fonts, non-ascii characters might have a different width).
    cm.getViewport() → {from: number, to: number}
    Returns a {from, to} object indicating the start (inclusive) and end (exclusive) of the currently rendered part of the document. In big documents, when most content is scrolled out of view, CodeMirror will only render the visible part, and a margin around it. See also the viewportChange event.
    cm.refresh()
    If your code does something to change the size of the editor element (window resizes are already listened for), or unhides it, you should probably follow up by calling this method to ensure CodeMirror is still looking as intended. See also the autorefresh addon.

    Mode, state, and token-related methods

    When writing language-aware functionality, it can often be useful to hook into the knowledge that the CodeMirror language mode has. See the section on modes for a more detailed description of how these work.

    doc.getMode() → object
    Gets the (outer) mode object for the editor. Note that this is distinct from getOption("mode"), which gives you the mode specification, rather than the resolved, instantiated mode object.
    cm.getModeAt(pos: {line, ch}) → object
    Gets the inner mode at a given position. This will return the same as getMode for simple modes, but will return an inner mode for nesting modes (such as htmlmixed).
    cm.getTokenAt(pos: {line, ch}, ?precise: boolean) → object
    Retrieves information about the token the current mode found before the given position (a {line, ch} object). The returned object has the following properties:
    start
    The character (on the given line) at which the token starts.
    end
    The character at which the token ends.
    string
    The token's string.
    type
    The token type the mode assigned to the token, such as "keyword" or "comment" (may also be null).
    state
    The mode's state at the end of this token.
    If precise is true, the token will be guaranteed to be accurate based on recent edits. If false or not specified, the token will use cached state information, which will be faster but might not be accurate if edits were recently made and highlighting has not yet completed.
    cm.getLineTokens(line: integer, ?precise: boolean) → array<{start, end, string, type, state}>
    This is similar to getTokenAt, but collects all tokens for a given line into an array. It is much cheaper than repeatedly calling getTokenAt, which re-parses the part of the line before the token for every call.
    cm.getTokenTypeAt(pos: {line, ch}) → string
    This is a (much) cheaper version of getTokenAt useful for when you just need the type of the token at a given position, and no other information. Will return null for unstyled tokens, and a string, potentially containing multiple space-separated style names, otherwise.
    cm.getHelpers(pos: {line, ch}, type: string) → array<helper>
    Fetch the set of applicable helper values for the given position. Helpers provide a way to look up functionality appropriate for a mode. The type argument provides the helper namespace (see registerHelper), in which the values will be looked up. When the mode itself has a property that corresponds to the type, that directly determines the keys that are used to look up the helper values (it may be either a single string, or an array of strings). Failing that, the mode's helperType property and finally the mode's name are used.
    For example, the JavaScript mode has a property fold containing "brace". When the brace-fold addon is loaded, that defines a helper named brace in the fold namespace. This is then used by the foldcode addon to figure out that it can use that folding function to fold JavaScript code.
    When any 'global' helpers are defined for the given namespace, their predicates are called on the current mode and editor, and all those that declare they are applicable will also be added to the array that is returned.
    cm.getHelper(pos: {line, ch}, type: string) → helper
    Returns the first applicable helper value. See getHelpers.
    cm.getStateAfter(?line: integer, ?precise: boolean) → object
    Returns the mode's parser state, if any, at the end of the given line number. If no line number is given, the state at the end of the document is returned. This can be useful for storing parsing errors in the state, or getting other kinds of contextual information for a line. precise is defined as in getTokenAt().

    Miscellaneous methods

    cm.operation(func: () → any) → any
    CodeMirror internally buffers changes and only updates its DOM structure after it has finished performing some operation. If you need to perform a lot of operations on a CodeMirror instance, you can call this method with a function argument. It will call the function, buffering up all changes, and only doing the expensive update after the function returns. This can be a lot faster. The return value from this method will be the return value of your function.
    cm.startOperation()
    cm.endOperation()
    In normal circumstances, use the above operation method. But if you want to buffer operations happening asynchronously, or that can't all be wrapped in a callback function, you can call startOperation to tell CodeMirror to start buffering changes, and endOperation to actually render all the updates. Be careful: if you use this API and forget to call endOperation, the editor will just never update.
    cm.indentLine(line: integer, ?dir: string|integer)
    Adjust the indentation of the given line. The second argument (which defaults to "smart") may be one of:
    "prev"
    Base indentation on the indentation of the previous line.
    "smart"
    Use the mode's smart indentation if available, behave like "prev" otherwise.
    "add"
    Increase the indentation of the line by one indent unit.
    "subtract"
    Reduce the indentation of the line.
    <integer>
    Add (positive number) or reduce (negative number) the indentation by the given amount of spaces.
    cm.toggleOverwrite(?value: boolean)
    Switches between overwrite and normal insert mode (when not given an argument), or sets the overwrite mode to a specific state (when given an argument).
    cm.isReadOnly() → boolean
    Tells you whether the editor's content can be edited by the user.
    doc.lineSeparator()
    Returns the preferred line separator string for this document, as per the option by the same name. When that option is null, the string "\n" is returned.
    cm.execCommand(name: string)
    Runs the command with the given name on the editor.
    doc.posFromIndex(index: integer) → {line, ch}
    Calculates and returns a {line, ch} object for a zero-based index who's value is relative to the start of the editor's text. If the index is out of range of the text then the returned object is clipped to start or end of the text respectively.
    doc.indexFromPos(object: {line, ch}) → integer
    The reverse of posFromIndex.
    cm.focus()
    Give the editor focus.
    cm.phrase(text: string) → string
    Allow the given string to be translated with the phrases option.
    cm.getInputField() → Element
    Returns the input field for the editor. Will be a textarea or an editable div, depending on the value of the inputStyle option.
    cm.getWrapperElement() → Element
    Returns the DOM node that represents the editor, and controls its size. Remove this from your tree to delete an editor instance.
    cm.getScrollerElement() → Element
    Returns the DOM node that is responsible for the scrolling of the editor.
    cm.getGutterElement() → Element
    Fetches the DOM node that contains the editor gutters.

    Static properties

    The CodeMirror object itself provides several useful properties.

    CodeMirror.version: string
    It contains a string that indicates the version of the library. This is a triple of integers "major.minor.patch", where patch is zero for releases, and something else (usually one) for dev snapshots.
    CodeMirror.fromTextArea(textArea: TextAreaElement, ?config: object)
    This method provides another way to initialize an editor. It takes a textarea DOM node as first argument and an optional configuration object as second. It will replace the textarea with a CodeMirror instance, and wire up the form of that textarea (if any) to make sure the editor contents are put into the textarea when the form is submitted. The text in the textarea will provide the content for the editor. A CodeMirror instance created this way has three additional methods:
    cm.save()
    Copy the content of the editor into the textarea.
    cm.toTextArea()
    Remove the editor, and restore the original textarea (with the editor's current content). If you dynamically create and destroy editors made with `fromTextArea`, without destroying the form they are part of, you should make sure to call `toTextArea` to remove the editor, or its `"submit"` handler on the form will cause a memory leak.
    cm.getTextArea() → TextAreaElement
    Returns the textarea that the instance was based on.
    CodeMirror.defaults: object
    An object containing default values for all options. You can assign to its properties to modify defaults (though this won't affect editors that have already been created).
    CodeMirror.defineExtension(name: string, value: any)
    If you want to define extra methods in terms of the CodeMirror API, it is possible to use defineExtension. This will cause the given value (usually a method) to be added to all CodeMirror instances created from then on.
    CodeMirror.defineDocExtension(name: string, value: any)
    Like defineExtension, but the method will be added to the interface for Doc objects instead.
    CodeMirror.defineOption(name: string, default: any, updateFunc: function)
    Similarly, defineOption can be used to define new options for CodeMirror. The updateFunc will be called with the editor instance and the new value when an editor is initialized, and whenever the option is modified through setOption.
    CodeMirror.defineInitHook(func: function)
    If your extension just needs to run some code whenever a CodeMirror instance is initialized, use CodeMirror.defineInitHook. Give it a function as its only argument, and from then on, that function will be called (with the instance as argument) whenever a new CodeMirror instance is initialized.
    CodeMirror.registerHelper(type: string, name: string, value: helper)
    Registers a helper value with the given name in the given namespace (type). This is used to define functionality that may be looked up by mode. Will create (if it doesn't already exist) a property on the CodeMirror object for the given type, pointing to an object that maps names to values. I.e. after doing CodeMirror.registerHelper("hint", "foo", myFoo), the value CodeMirror.hint.foo will point to myFoo.
    CodeMirror.registerGlobalHelper(type: string, name: string, predicate: fn(mode, CodeMirror), value: helper)
    Acts like registerHelper, but also registers this helper as 'global', meaning that it will be included by getHelpers whenever the given predicate returns true when called with the local mode and editor.
    CodeMirror.Pos(line: integer, ?ch: integer, ?sticky: string)
    A constructor for the objects that are used to represent positions in editor documents. sticky defaults to null, but can be set to "before" or "after" to make the position explicitly associate with the character before or after it.
    CodeMirror.changeEnd(change: object) → {line, ch}
    Utility function that computes an end position from a change (an object with from, to, and text properties, as passed to various event handlers). The returned position will be the end of the changed range, after the change is applied.
    CodeMirror.countColumn(line: string, index: number, tabSize: number) → number
    Find the column position at a given string index using a given tabsize.

    Addons

    The addon directory in the distribution contains a number of reusable components that implement extra editor functionality (on top of extension functions like defineOption, defineExtension, and registerHelper). In brief, they are:

    dialog/dialog.js
    Provides a very simple way to query users for text input. Adds the openDialog(template, callback, options) → closeFunction method to CodeMirror instances, which can be called with an HTML fragment or a detached DOM node that provides the prompt (should include an input or button tag), and a callback function that is called when the user presses enter. It returns a function closeFunction which, if called, will close the dialog immediately. openDialog takes the following options:
    closeOnEnter: bool
    If true, the dialog will be closed when the user presses enter in the input. Defaults to true.
    closeOnBlur: bool
    Determines whether the dialog is closed when it loses focus. Defaults to true.
    onKeyDown: fn(event: KeyboardEvent, value: string, close: fn()) → bool
    An event handler that will be called whenever keydown fires in the dialog's input. If your callback returns true, the dialog will not do any further processing of the event.
    onKeyUp: fn(event: KeyboardEvent, value: string, close: fn()) → bool
    Same as onKeyDown but for the keyup event.
    onInput: fn(event: InputEvent, value: string, close: fn()) → bool
    Same as onKeyDown but for the input event.
    onClose: fn(instance):
    A callback that will be called after the dialog has been closed and removed from the DOM. No return value.

    Also adds an openNotification(template, options) → closeFunction function that simply shows an HTML fragment as a notification at the top of the editor. It takes a single option: duration, the amount of time after which the notification will be automatically closed. If duration is zero, the dialog will not be closed automatically.

    Depends on addon/dialog/dialog.css.

    search/searchcursor.js
    Adds the getSearchCursor(query, start, options) → cursor method to CodeMirror instances, which can be used to implement search/replace functionality. query can be a regular expression or a string. start provides the starting position of the search. It can be a {line, ch} object, or can be left off to default to the start of the document. options is an optional object, which can contain the property `caseFold: false` to disable case folding when matching a string, or the property `multiline: disable` to disable multi-line matching for regular expressions (which may help performance). A search cursor has the following methods:
    findNext() → boolean
    findPrevious() → boolean
    Search forward or backward from the current position. The return value indicates whether a match was found. If matching a regular expression, the return value will be the array returned by the match method, in case you want to extract matched groups.
    from() → {line, ch}
    to() → {line, ch}
    These are only valid when the last call to findNext or findPrevious did not return false. They will return {line, ch} objects pointing at the start and end of the match.
    replace(text: string, ?origin: string)
    Replaces the currently found match with the given text and adjusts the cursor position to reflect the replacement.
    search/search.js
    Implements the search commands. CodeMirror has keys bound to these by default, but will not do anything with them unless an implementation is provided. Depends on searchcursor.js, and will make use of openDialog when available to make prompting for search queries less ugly.
    search/jump-to-line.js
    Implements a jumpToLine command and binding Alt-G to it. Accepts linenumber, +/-linenumber, line:char, scroll% and :linenumber formats. This will make use of openDialog when available to make prompting for line number neater.
    Demo available here.
    search/matchesonscrollbar.js
    Adds a showMatchesOnScrollbar method to editor instances, which should be given a query (string or regular expression), optionally a case-fold flag (only applicable for strings), and optionally a class name (defaults to CodeMirror-search-match) as arguments. When called, matches of the given query will be displayed on the editor's vertical scrollbar. The method returns an object with a clear method that can be called to remove the matches. Depends on the annotatescrollbar addon, and the matchesonscrollbar.css file provides a default (transparent yellowish) definition of the CSS class applied to the matches. Note that the matches are only perfectly aligned if your scrollbar does not have buttons at the top and bottom. You can use the simplescrollbar addon to make sure of this. If this addon is loaded, the search addon will automatically use it.
    edit/matchbrackets.js
    Defines an option matchBrackets which, when set to true or an options object, causes matching brackets to be highlighted whenever the cursor is next to them. It also adds a method matchBrackets that forces this to happen once, and a method findMatchingBracket that can be used to run the bracket-finding algorithm that this uses internally. It takes a start position and an optional config object. By default, it will find the match to a matchable character either before or after the cursor (preferring the one before), but you can control its behavior with these options:
    afterCursor
    Only use the character after the start position, never the one before it.
    highlightNonMatching
    Also highlight pairs of non-matching as well as stray brackets. Enabled by default.
    strict
    Causes only matches where both brackets are at the same side of the start position to be considered.
    maxScanLines
    Stop after scanning this amount of lines without a successful match. Defaults to 1000.
    maxScanLineLength
    Ignore lines longer than this. Defaults to 10000.
    maxHighlightLineLength
    Don't highlight a bracket in a line longer than this. Defaults to 1000.
    edit/closebrackets.js
    Defines an option autoCloseBrackets that will auto-close brackets and quotes when typed. By default, it'll auto-close ()[]{}''"", but you can pass it a string similar to that (containing pairs of matching characters), or an object with pairs and optionally explode properties to customize it. explode should be a similar string that gives the pairs of characters that, when enter is pressed between them, should have the second character also moved to its own line. By default, if the active mode has a closeBrackets property, that overrides the configuration given in the option. But you can add an override property with a truthy value to override mode-specific configuration. Demo here.
    edit/matchtags.js
    Defines an option matchTags that, when enabled, will cause the tags around the cursor to be highlighted (using the CodeMirror-matchingtag class). Also defines a command toMatchingTag, which you can bind a key to in order to jump to the tag matching the one under the cursor. Depends on the addon/fold/xml-fold.js addon. Demo here.
    edit/trailingspace.js
    Adds an option showTrailingSpace which, when enabled, adds the CSS class cm-trailingspace to stretches of whitespace at the end of lines. The demo has a nice squiggly underline style for this class.
    edit/closetag.js
    Defines an autoCloseTags option that will auto-close XML tags when '>' or '/' is typed, and a closeTag command that closes the nearest open tag. Depends on the fold/xml-fold.js addon. See the demo.
    edit/continuelist.js
    Markdown specific. Defines a "newlineAndIndentContinueMarkdownList" command that can be bound to enter to automatically insert the leading characters for continuing a list. See the Markdown mode demo.
    comment/comment.js
    Addon for commenting and uncommenting code. Adds four methods to CodeMirror instances:
    toggleComment(?options: object)
    Tries to uncomment the current selection, and if that fails, line-comments it.
    lineComment(from: {line, ch}, to: {line, ch}, ?options: object)
    Set the lines in the given range to be line comments. Will fall back to blockComment when no line comment style is defined for the mode.
    blockComment(from: {line, ch}, to: {line, ch}, ?options: object)
    Wrap the code in the given range in a block comment. Will fall back to lineComment when no block comment style is defined for the mode.
    uncomment(from: {line, ch}, to: {line, ch}, ?options: object) → boolean
    Try to uncomment the given range. Returns true if a comment range was found and removed, false otherwise.
    The options object accepted by these methods may have the following properties:
    blockCommentStart, blockCommentEnd, blockCommentLead, lineComment: string
    Override the comment string properties of the mode with custom comment strings.
    padding: string
    A string that will be inserted after opening and leading markers, and before closing comment markers. Defaults to a single space.
    commentBlankLines: boolean
    Whether, when adding line comments, to also comment lines that contain only whitespace.
    indent: boolean
    When adding line comments and this is turned on, it will align the comment block to the current indentation of the first line of the block.
    fullLines: boolean
    When block commenting, this controls whether the whole lines are indented, or only the precise range that is given. Defaults to true.
    The addon also defines a toggleComment command, which is a shorthand command for calling toggleComment with no options.
    fold/foldcode.js
    Helps with code folding. Adds a foldCode method to editor instances, which will try to do a code fold starting at the given line, or unfold the fold that is already present. The method takes as first argument the position that should be folded (may be a line number or a Pos), and as second optional argument either a range-finder function, or an options object, supporting the following properties:
    rangeFinder: fn(CodeMirror, Pos)
    The function that is used to find foldable ranges. If this is not directly passed, it will default to CodeMirror.fold.auto, which uses getHelpers with a "fold" type to find folding functions appropriate for the local mode. There are files in the addon/fold/ directory providing CodeMirror.fold.brace, which finds blocks in brace languages (JavaScript, C, Java, etc), CodeMirror.fold.indent, for languages where indentation determines block structure (Python, Haskell), and CodeMirror.fold.xml, for XML-style languages, and CodeMirror.fold.comment, for folding comment blocks.
    widget: string | Element | fn(from: Pos, to: Pos) → string|Element
    The widget to show for folded ranges. Can be either a string, in which case it'll become a span with class CodeMirror-foldmarker, or a DOM node. To dynamically generate the widget, this can be a function that returns a string or DOM node, which will then render as described. The function will be invoked with parameters identifying the range to be folded.
    scanUp: boolean
    When true (default is false), the addon will try to find foldable ranges on the lines above the current one if there isn't an eligible one on the given line.
    minFoldSize: integer
    The minimum amount of lines that a fold should span to be accepted. Defaults to 0, which also allows single-line folds.
    See the demo for an example.
    fold/foldgutter.js
    Provides an option foldGutter, which can be used to create a gutter with markers indicating the blocks that can be folded. Create a gutter using the gutters option, giving it the class CodeMirror-foldgutter or something else if you configure the addon to use a different class, and this addon will show markers next to folded and foldable blocks, and handle clicks in this gutter. Note that CSS styles should be applied to make the gutter, and the fold markers within it, visible. A default set of CSS styles are available in: addon/fold/foldgutter.css . The option can be either set to true, or an object containing the following optional option fields:
    gutter: string
    The CSS class of the gutter. Defaults to "CodeMirror-foldgutter". You will have to style this yourself to give it a width (and possibly a background). See the default gutter style rules above.
    indicatorOpen: string | Element
    A CSS class or DOM element to be used as the marker for open, foldable blocks. Defaults to "CodeMirror-foldgutter-open".
    indicatorFolded: string | Element
    A CSS class or DOM element to be used as the marker for folded blocks. Defaults to "CodeMirror-foldgutter-folded".
    rangeFinder: fn(CodeMirror, Pos)
    The range-finder function to use when determining whether something can be folded. When not given, CodeMirror.fold.auto will be used as default.
    The foldOptions editor option can be set to an object to provide an editor-wide default configuration. Demo here.
    runmode/runmode.js
    Can be used to run a CodeMirror mode over text without actually opening an editor instance. See the demo for an example. There are alternate versions of the file available for running stand-alone (without including all of CodeMirror) and for running under node.js (see bin/source-highlight for an example of using the latter).
    runmode/colorize.js
    Provides a convenient way to syntax-highlight code snippets in a webpage. Depends on the runmode addon (or its standalone variant). Provides a CodeMirror.colorize function that can be called with an array (or other array-ish collection) of DOM nodes that represent the code snippets. By default, it'll get all pre tags. Will read the data-lang attribute of these nodes to figure out their language, and syntax-color their content using the relevant CodeMirror mode (you'll have to load the scripts for the relevant modes yourself). A second argument may be provided to give a default mode, used when no language attribute is found for a node. Used in this manual to highlight example code.
    mode/overlay.js
    Mode combinator that can be used to extend a mode with an 'overlay' — a secondary mode is run over the stream, along with the base mode, and can color specific pieces of text without interfering with the base mode. Defines CodeMirror.overlayMode, which is used to create such a mode. See this demo for a detailed example.
    mode/multiplex.js
    Mode combinator that can be used to easily 'multiplex' between several modes. Defines CodeMirror.multiplexingMode which, when given as first argument a mode object, and as other arguments any number of {open, close, mode [, delimStyle, innerStyle, parseDelimiters]} objects, will return a mode object that starts parsing using the mode passed as first argument, but will switch to another mode as soon as it encounters a string that occurs in one of the open fields of the passed objects. When in a sub-mode, it will go back to the top mode again when the close string is encountered. Pass "\n" for open or close if you want to switch on a blank line.
    • When delimStyle is specified, it will be the token style returned for the delimiter tokens (as well as [delimStyle]-open on the opening token and [delimStyle]-close on the closing token).
    • When innerStyle is specified, it will be the token style added for each inner mode token.
    • When parseDelimiters is true, the content of the delimiters will also be passed to the inner mode. (And delimStyle is ignored.)
    The outer mode will not see the content between the delimiters. See this demo for an example.
    hint/show-hint.js
    Provides a framework for showing autocompletion hints. Defines editor.showHint, which takes an optional options object, and pops up a widget that allows the user to select a completion. Finding hints is done with a hinting function (the hint option). This function takes an editor instance and an options object, and returns a {list, from, to} object, where list is an array of strings or objects (the completions), and from and to give the start and end of the token that is being completed as {line, ch} objects. An optional selectedHint property (an integer) can be added to the completion object to control the initially selected hint.
    If no hinting function is given, the addon will use CodeMirror.hint.auto, which calls getHelpers with the "hint" type to find applicable hinting functions, and tries them one by one. If that fails, it looks for a "hintWords" helper to fetch a list of completeable words for the mode, and uses CodeMirror.hint.fromList to complete from those.
    When completions aren't simple strings, they should be objects with the following properties:
    text: string
    The completion text. This is the only required property.
    displayText: string
    The text that should be displayed in the menu.
    className: string
    A CSS class name to apply to the completion's line in the menu.
    render: fn(Element, self, data)
    A method used to create the DOM structure for showing the completion by appending it to its first argument.
    hint: fn(CodeMirror, self, data)
    A method used to actually apply the completion, instead of the default behavior.
    from: {line, ch}
    Optional from position that will be used by pick() instead of the global one passed with the full list of completions.
    to: {line, ch}
    Optional to position that will be used by pick() instead of the global one passed with the full list of completions.
    The plugin understands the following options, which may be either passed directly in the argument to showHint, or provided by setting an hintOptions editor option to an object (the former takes precedence). The options object will also be passed along to the hinting function, which may understand additional options.
    hint: function
    A hinting function, as specified above. It is possible to set the async property on a hinting function to true, in which case it will be called with arguments (cm, callback, ?options), and the completion interface will only be popped up when the hinting function calls the callback, passing it the object holding the completions. The hinting function can also return a promise, and the completion interface will only be popped when the promise resolves. By default, hinting only works when there is no selection. You can give a hinting function a supportsSelection property with a truthy value to indicate that it supports selections.
    completeSingle: boolean
    Determines whether, when only a single completion is available, it is completed without showing the dialog. Defaults to true.
    alignWithWord: boolean
    Whether the pop-up should be horizontally aligned with the start of the word (true, default), or with the cursor (false).
    closeCharacters: RegExp
    A regular expression object used to match characters which cause the pop up to be closed (default: /[\s()\[\]{};:>,]/). If the user types one of these characters, the pop up will close, and the endCompletion event is fired on the editor instance.
    closeOnUnfocus: boolean
    When enabled (which is the default), the pop-up will close when the editor is unfocused.
    completeOnSingleClick: boolean
    Whether a single click on a list item suffices to trigger the completion (which is the default), or if the user has to use a doubleclick.
    container: Element|null
    Can be used to define a custom container for the widget. The default is null, in which case the body-element will be used.
    customKeys: keymap
    Allows you to provide a custom key map of keys to be active when the pop-up is active. The handlers will be called with an extra argument, a handle to the completion menu, which has moveFocus(n), setFocus(n), pick(), and close() methods (see the source for details), that can be used to change the focused element, pick the current element or close the menu. Additionally menuSize() can give you access to the size of the current dropdown menu, length give you the number of available completions, and data give you full access to the completion returned by the hinting function.
    extraKeys: keymap
    Like customKeys above, but the bindings will be added to the set of default bindings, instead of replacing them.
    scrollMargin: integer
    Show this many lines before and after the selected item. Default is 0.
    The following events will be fired on the completions object during completion:
    "shown" ()
    Fired when the pop-up is shown.
    "select" (completion, Element)
    Fired when a completion is selected. Passed the completion value (string or object) and the DOM node that represents it in the menu.
    "pick" (completion)
    Fired when a completion is picked. Passed the completion value (string or object).
    "close" ()
    Fired when the completion is finished.
    The following events will be fired on the editor instance during completion:
    "endCompletion" ()
    Fired when the pop-up is being closed programmatically, e.g., when the user types a character which matches the closeCharacters option.
    This addon depends on styles from addon/hint/show-hint.css. Check out the demo for an example.
    hint/javascript-hint.js
    Defines a simple hinting function for JavaScript (CodeMirror.hint.javascript) and CoffeeScript (CodeMirror.hint.coffeescript) code. This will simply use the JavaScript environment that the editor runs in as a source of information about objects and their properties.
    hint/xml-hint.js
    Defines CodeMirror.hint.xml, which produces hints for XML tagnames, attribute names, and attribute values, guided by a schemaInfo option (a property of the second argument passed to the hinting function, or the third argument passed to CodeMirror.showHint).
    The schema info should be an object mapping tag names to information about these tags, with optionally a "!top" property containing a list of the names of valid top-level tags. The values of the properties should be objects with optional properties children (an array of valid child element names, omit to simply allow all tags to appear) and attrs (an object mapping attribute names to null for free-form attributes, and an array of valid values for restricted attributes).
    The hint options accept an additional property:
    matchInMiddle: boolean
    Determines whether typed characters are matched anywhere in completions, not just at the beginning. Defaults to false.
    Demo here.
    hint/html-hint.js
    Provides schema info to the xml-hint addon for HTML documents. Defines a schema object CodeMirror.htmlSchema that you can pass to as a schemaInfo option, and a CodeMirror.hint.html hinting function that automatically calls CodeMirror.hint.xml with this schema data. See the demo.
    hint/css-hint.js
    A hinting function for CSS, SCSS, or LESS code. Defines CodeMirror.hint.css.
    hint/anyword-hint.js
    A very simple hinting function (CodeMirror.hint.anyword) that simply looks for words in the nearby code and completes to those. Takes two optional options, word, a regular expression that matches words (sequences of one or more character), and range, which defines how many lines the addon should scan when completing (defaults to 500).
    hint/sql-hint.js
    A simple SQL hinter. Defines CodeMirror.hint.sql. Takes two optional options, tables, a object with table names as keys and array of respective column names as values, and defaultTable, a string corresponding to a table name in tables for autocompletion.
    search/match-highlighter.js
    Adds a highlightSelectionMatches option that can be enabled to highlight all instances of a currently selected word. Can be set either to true or to an object containing the following options: minChars, for the minimum amount of selected characters that triggers a highlight (default 2), style, for the style to be used to highlight the matches (default "matchhighlight", which will correspond to CSS class cm-matchhighlight), trim, which controls whether whitespace is trimmed from the selection, and showToken which can be set to true or to a regexp matching the characters that make up a word. When enabled, it causes the current word to be highlighted when nothing is selected (defaults to off). Demo here.
    lint/lint.js
    Defines an interface component for showing linting warnings, with pluggable warning sources (see html-lint.js, json-lint.js, javascript-lint.js, coffeescript-lint.js, and css-lint.js in the same directory). Defines a lint option that can be set to an annotation source (for example CodeMirror.lint.javascript), to an options object (in which case the getAnnotations field is used as annotation source), or simply to true. When no annotation source is specified, getHelper with type "lint" is used to find an annotation function. An annotation source function should, when given a document string, an options object, and an editor instance, return an array of {message, severity, from, to} objects representing problems. When the function has an async property with a truthy value, it will be called with an additional second argument, which is a callback to pass the array to. The linting function can also return a promise, in that case the linter will only be executed when the promise resolves. By default, the linter will run (debounced) whenever the document is changed. You can pass a lintOnChange: false option to disable that. You can pass a selfContain: true option to render the tooltip inside the editor instance. And a highlightLines option to add a style to lines that contain problems. Depends on addon/lint/lint.css. A demo can be found here.
    selection/mark-selection.js
    Causes the selected text to be marked with the CSS class CodeMirror-selectedtext when the styleSelectedText option is enabled. Useful to change the colour of the selection (in addition to the background), like in this demo.
    selection/active-line.js
    Defines a styleActiveLine option that, when enabled, gives the wrapper of the line that contains the cursor the class CodeMirror-activeline, adds a background with the class CodeMirror-activeline-background, and adds the class CodeMirror-activeline-gutter to the line's gutter space is enabled. The option's value may be a boolean or an object specifying the following options:
    nonEmpty: bool
    Controls whether single-line selections, or just cursor selections, are styled. Defaults to false (only cursor selections).
    See the demo.
    selection/selection-pointer.js
    Defines a selectionPointer option which you can use to control the mouse cursor appearance when hovering over the selection. It can be set to a string, like "pointer", or to true, in which case the "default" (arrow) cursor will be used. You can see a demo here.
    mode/loadmode.js
    Defines a CodeMirror.requireMode(modename, callback, options) function that will try to load a given mode and call the callback when it succeeded. options is an optional object that may contain:
    path: fn(modeName: string) → string
    Defines the way mode names are mapped to paths.
    loadMode: fn(path: string, cont: fn())
    Override the way the mode script is loaded. By default, this will use the CommonJS or AMD module loader if one is present, and fall back to creating a <script> tag otherwise.
    This addon also defines CodeMirror.autoLoadMode(instance, mode), which will ensure the given mode is loaded and cause the given editor instance to refresh its mode when the loading succeeded. See the demo.
    mode/meta.js
    Provides meta-information about all the modes in the distribution in a single file. Defines CodeMirror.modeInfo, an array of objects with {name, mime, mode} properties, where name is the human-readable name, mime the MIME type, and mode the name of the mode file that defines this MIME. There are optional properties mimes, which holds an array of MIME types for modes with multiple MIMEs associated, and ext, which holds an array of file extensions associated with this mode. Four convenience functions, CodeMirror.findModeByMIME, CodeMirror.findModeByExtension, CodeMirror.findModeByFileName and CodeMirror.findModeByName are provided, which return such an object given a MIME, extension, file name or mode name string. Note that, for historical reasons, this file resides in the top-level mode directory, not under addon. Demo.
    comment/continuecomment.js
    Adds a continueComments option, which sets whether the editor will make the next line continue a comment when you press Enter inside a comment block. Can be set to a boolean to enable/disable this functionality. Set to a string, it will continue comments using a custom shortcut. Set to an object, it will use the key property for a custom shortcut and the boolean continueLineComment property to determine whether single-line comments should be continued (defaulting to true).
    display/placeholder.js
    Adds a placeholder option that can be used to make content appear in the editor when it is empty and not focused. It can hold either a string or a DOM node. Also gives the editor a CodeMirror-empty CSS class whenever it doesn't contain any text. See the demo.
    display/fullscreen.js
    Defines an option fullScreen that, when set to true, will make the editor full-screen (as in, taking up the whole browser window). Depends on fullscreen.css. Demo here.
    display/autorefresh.js
    This addon can be useful when initializing an editor in a hidden DOM node, in cases where it is difficult to call refresh when the editor becomes visible. It defines an option autoRefresh which you can set to true to ensure that, if the editor wasn't visible on initialization, it will be refreshed the first time it becomes visible. This is done by polling every 250 milliseconds (you can pass a value like {delay: 500} as the option value to configure this). Note that this addon will only refresh the editor once when it first becomes visible, and won't take care of further restyling and resizing.
    scroll/simplescrollbars.js
    Defines two additional scrollbar models, "simple" and "overlay" (see demo) that can be selected with the scrollbarStyle option. Depends on simplescrollbars.css, which can be further overridden to style your own scrollbars.
    scroll/annotatescrollbar.js
    Provides functionality for showing markers on the scrollbar to call out certain parts of the document. Adds a method annotateScrollbar to editor instances that can be called, with a CSS class name as argument, to create a set of annotations. The method returns an object whose update method can be called with a sorted array of {from: Pos, to: Pos} objects marking the ranges to be highlighted. To detach the annotations, call the object's clear method.
    display/rulers.js
    Adds a rulers option, which can be used to show one or more vertical rulers in the editor. The option, if defined, should be given an array of {column [, className, color, lineStyle, width]} objects or numbers (which indicate a column). The ruler will be displayed at the column indicated by the number or the column property. The className property can be used to assign a custom style to a ruler. Demo here.
    display/panel.js
    Defines an addPanel method for CodeMirror instances, which places a DOM node above or below an editor, and shrinks the editor to make room for the node. The method takes as first argument as DOM node, and as second an optional options object. The Panel object returned by this method has a clear method that is used to remove the panel, and a changed method that can be used to notify the addon when the size of the panel's DOM node has changed.
    The method accepts the following options:
    position: string
    Controls the position of the newly added panel. The following values are recognized:
    top (default)
    Adds the panel at the very top.
    after-top
    Adds the panel at the bottom of the top panels.
    bottom
    Adds the panel at the very bottom.
    before-bottom
    Adds the panel at the top of the bottom panels.
    before: Panel
    The new panel will be added before the given panel.
    after: Panel
    The new panel will be added after the given panel.
    replace: Panel
    The new panel will replace the given panel.
    stable: bool
    Whether to scroll the editor to keep the text's vertical position stable, when adding a panel above it. Defaults to false.
    When using the after, before or replace options, if the panel doesn't exists or has been removed, the value of the position option will be used as a fallback.
    A demo of the addon is available here.
    wrap/hardwrap.js
    Addon to perform hard line wrapping/breaking for paragraphs of text. Adds these methods to editor instances:
    wrapParagraph(?pos: {line, ch}, ?options: object)
    Wraps the paragraph at the given position. If pos is not given, it defaults to the cursor position.
    wrapRange(from: {line, ch}, to: {line, ch}, ?options: object)
    Wraps the given range as one big paragraph.
    wrapParagraphsInRange(from: {line, ch}, to: {line, ch}, ?options: object)
    Wraps the paragraphs in (and overlapping with) the given range individually.
    The following options are recognized:
    paragraphStart, paragraphEnd: RegExp
    Blank lines are always considered paragraph boundaries. These options can be used to specify a pattern that causes lines to be considered the start or end of a paragraph.
    column: number
    The column to wrap at. Defaults to 80.
    wrapOn: RegExp
    A regular expression that matches only those two-character strings that allow wrapping. By default, the addon wraps on whitespace and after dash characters.
    killTrailingSpace: boolean
    Whether trailing space caused by wrapping should be preserved, or deleted. Defaults to true.
    forceBreak: boolean
    If set to true forces a break at column in the case when no wrapOn pattern is found in the range. If set to false allows line to overflow the column limit if no wrapOn pattern found. Defaults to true.
    A demo of the addon is available here.
    scroll/scrollpastend.js
    Defines an option `"scrollPastEnd"` that, when set to a truthy value, allows the user to scroll one editor height of empty space into view at the bottom of the editor.
    merge/merge.js
    Implements an interface for merging changes, using either a 2-way or a 3-way view. The CodeMirror.MergeView constructor takes arguments similar to the CodeMirror constructor, first a node to append the interface to, and then an options object. Options are passed through to the editors inside the view. These extra options are recognized:
    origLeft and origRight: string
    If given these provide original versions of the document, which will be shown to the left and right of the editor in non-editable CodeMirror instances. The merge interface will highlight changes between the editable document and the original(s). To create a 2-way (as opposed to 3-way) merge view, provide only one of them.
    revertButtons: boolean
    Determines whether buttons that allow the user to revert changes are shown. Defaults to true.
    revertChunk: fn(mv: MergeView, from: CodeMirror, fromStart: Pos, fromEnd: Pos, to: CodeMirror, toStart: Pos, toEnd: Pos)
    Can be used to define custom behavior when the user reverts a changed chunk.
    connect: string
    Sets the style used to connect changed chunks of code. By default, connectors are drawn. When this is set to "align", the smaller chunk is padded to align with the bigger chunk instead.
    collapseIdentical: boolean|number
    When true (default is false), stretches of unchanged text will be collapsed. When a number is given, this indicates the amount of lines to leave visible around such stretches (which defaults to 2).
    allowEditingOriginals: boolean
    Determines whether the original editor allows editing. Defaults to false.
    showDifferences: boolean
    When true (the default), changed pieces of text are highlighted.
    chunkClassLocation: string|Array
    By default the chunk highlights are added using addLineClass with "background". Override this to customize it to be any valid `where` parameter or an Array of valid `where` parameters.
    The addon also defines commands "goNextDiff" and "goPrevDiff" to quickly jump to the next changed chunk. Demo here.
    tern/tern.js
    Provides integration with the Tern JavaScript analysis engine, for completion, definition finding, and minor refactoring help. See the demo for a very simple integration. For more involved scenarios, see the comments at the top of the addon and the implementation of the (multi-file) demonstration on the Tern website.

    Writing CodeMirror Modes

    Modes typically consist of a single JavaScript file. This file defines, in the simplest case, a lexer (tokenizer) for your language—a function that takes a character stream as input, advances it past a token, and returns a style for that token. More advanced modes can also handle indentation for the language.

    This section describes the low-level mode interface. Many modes are written directly against this, since it offers a lot of control, but for a quick mode definition, you might want to use the simple mode addon.

    The mode script should call CodeMirror.defineMode to register itself with CodeMirror. This function takes two arguments. The first should be the name of the mode, for which you should use a lowercase string, preferably one that is also the name of the files that define the mode (i.e. "xml" is defined in xml.js). The second argument should be a function that, given a CodeMirror configuration object (the thing passed to the CodeMirror function) and an optional mode configuration object (as in the mode option), returns a mode object.

    Typically, you should use this second argument to defineMode as your module scope function (modes should not leak anything into the global scope!), i.e. write your whole mode inside this function.

    The main responsibility of a mode script is parsing the content of the editor. Depending on the language and the amount of functionality desired, this can be done in really easy or extremely complicated ways. Some parsers can be stateless, meaning that they look at one element (token) of the code at a time, with no memory of what came before. Most, however, will need to remember something. This is done by using a state object, which is an object that is always passed when reading a token, and which can be mutated by the tokenizer.

    Modes that use a state must define a startState method on their mode object. This is a function of no arguments that produces a state object to be used at the start of a document.

    The most important part of a mode object is its token(stream, state) method. All modes must define this method. It should read one token from the stream it is given as an argument, optionally update its state, and return a style string, or null for tokens that do not have to be styled. For your styles, you are encouraged to use the 'standard' names defined in the themes (without the cm- prefix). If that fails, it is also possible to come up with your own and write your own CSS theme file.

    A typical token string would be "variable" or "comment". Multiple styles can be returned (separated by spaces), for example "string error" for a thing that looks like a string but is invalid somehow (say, missing its closing quote). When a style is prefixed by "line-" or "line-background-", the style will be applied to the whole line, analogous to what the addLineClass method does—styling the "text" in the simple case, and the "background" element when "line-background-" is prefixed.

    The stream object that's passed to token encapsulates a line of code (tokens may never span lines) and our current position in that line. It has the following API:

    eol() → boolean
    Returns true only if the stream is at the end of the line.
    sol() → boolean
    Returns true only if the stream is at the start of the line.
    peek() → string
    Returns the next character in the stream without advancing it. Will return a null at the end of the line.
    next() → string
    Returns the next character in the stream and advances it. Also returns null when no more characters are available.
    eat(match: string|regexp|function(char: string) → boolean) → string
    match can be a character, a regular expression, or a function that takes a character and returns a boolean. If the next character in the stream 'matches' the given argument, it is consumed and returned. Otherwise, undefined is returned.
    eatWhile(match: string|regexp|function(char: string) → boolean) → boolean
    Repeatedly calls eat with the given argument, until it fails. Returns true if any characters were eaten.
    eatSpace() → boolean
    Shortcut for eatWhile when matching white-space.
    skipToEnd()
    Moves the position to the end of the line.
    skipTo(str: string) → boolean
    Skips to the start of the next occurrence of the given string, if found on the current line (doesn't advance the stream if the string does not occur on the line). Returns true if the string was found.
    match(pattern: string, ?consume: boolean, ?caseFold: boolean) → boolean
    match(pattern: regexp, ?consume: boolean) → array<string>
    Act like a multi-character eat—if consume is true or not given—or a look-ahead that doesn't update the stream position—if it is false. pattern can be either a string or a regular expression starting with ^. When it is a string, caseFold can be set to true to make the match case-insensitive. When successfully matching a regular expression, the returned value will be the array returned by match, in case you need to extract matched groups.
    backUp(n: integer)
    Backs up the stream n characters. Backing it up further than the start of the current token will cause things to break, so be careful.
    column() → integer
    Returns the column (taking into account tabs) at which the current token starts.
    indentation() → integer
    Tells you how far the current line has been indented, in spaces. Corrects for tab characters.
    current() → string
    Get the string between the start of the current token and the current stream position.
    lookAhead(n: number) → ?string
    Get the line n (>0) lines after the current one, in order to scan ahead across line boundaries. Note that you want to do this carefully, since looking far ahead will make mode state caching much less effective.
    baseToken() → ?{type: ?string, size: number}
    Modes added through addOverlay (and only such modes) can use this method to inspect the current token produced by the underlying mode.

    By default, blank lines are simply skipped when tokenizing a document. For languages that have significant blank lines, you can define a blankLine(state) method on your mode that will get called whenever a blank line is passed over, so that it can update the parser state.

    Because state object are mutated, and CodeMirror needs to keep valid versions of a state around so that it can restart a parse at any line, copies must be made of state objects. The default algorithm used is that a new state object is created, which gets all the properties of the old object. Any properties which hold arrays get a copy of these arrays (since arrays tend to be used as mutable stacks). When this is not correct, for example because a mode mutates non-array properties of its state object, a mode object should define a copyState method, which is given a state and should return a safe copy of that state.

    If you want your mode to provide smart indentation (through the indentLine method and the indentAuto and newlineAndIndent commands, to which keys can be bound), you must define an indent(state, textAfter) method on your mode object.

    The indentation method should inspect the given state object, and optionally the textAfter string, which contains the text on the line that is being indented, and return an integer, the amount of spaces to indent. It should usually take the indentUnit option into account. An indentation method may return CodeMirror.Pass to indicate that it could not come up with a precise indentation.

    To work well with the commenting addon, a mode may define lineComment (string that starts a line comment), blockCommentStart, blockCommentEnd (strings that start and end block comments), and blockCommentLead (a string to put at the start of continued lines in a block comment). All of these are optional.

    Finally, a mode may define either an electricChars or an electricInput property, which are used to automatically reindent the line when certain patterns are typed and the electricChars option is enabled. electricChars may be a string, and will trigger a reindent whenever one of the characters in that string are typed. Often, it is more appropriate to use electricInput, which should hold a regular expression, and will trigger indentation when the part of the line before the cursor matches the expression. It should usually end with a $ character, so that it only matches when the indentation-changing pattern was just typed, not when something was typed after the pattern.

    So, to summarize, a mode must provide a token method, and it may provide startState, copyState, and indent methods. For an example of a trivial mode, see the diff mode, for a more involved example, see the C-like mode.

    Sometimes, it is useful for modes to nest—to have one mode delegate work to another mode. An example of this kind of mode is the mixed-mode HTML mode. To implement such nesting, it is usually necessary to create mode objects and copy states yourself. To create a mode object, there are CodeMirror.getMode(options, parserConfig), where the first argument is a configuration object as passed to the mode constructor function, and the second argument is a mode specification as in the mode option. To copy a state object, call CodeMirror.copyState(mode, state), where mode is the mode that created the given state.

    In a nested mode, it is recommended to add an extra method, innerMode which, given a state object, returns a {state, mode} object with the inner mode and its state for the current position. These are used by utility scripts such as the tag closer to get context information. Use the CodeMirror.innerMode helper function to, starting from a mode and a state, recursively walk down to the innermost mode and state.

    To make indentation work properly in a nested parser, it is advisable to give the startState method of modes that are intended to be nested an optional argument that provides the base indentation for the block of code. The JavaScript and CSS parser do this, for example, to allow JavaScript and CSS code inside the mixed-mode HTML mode to be properly indented.

    It is possible, and encouraged, to associate your mode, or a certain configuration of your mode, with a MIME type. For example, the JavaScript mode associates itself with text/javascript, and its JSON variant with application/json. To do this, call CodeMirror.defineMIME(mime, modeSpec), where modeSpec can be a string or object specifying a mode, as in the mode option.

    If a mode specification wants to add some properties to the resulting mode object, typically for use with getHelpers, it may contain a modeProps property, which holds an object. This object's properties will be copied to the actual mode object.

    Sometimes, it is useful to add or override mode object properties from external code. The CodeMirror.extendMode function can be used to add properties to mode objects produced for a specific mode. Its first argument is the name of the mode, its second an object that specifies the properties that should be added. This is mostly useful to add utilities that can later be looked up through getMode.

    VIM Mode API

    CodeMirror has a robust VIM mode that attempts to faithfully emulate VIM's most useful features. It can be enabled by including keymap/vim.js and setting the keyMap option to "vim".

    Configuration

    VIM mode accepts configuration options for customizing behavior at run time. These methods can be called at any time and will affect all existing CodeMirror instances unless specified otherwise. The methods are exposed on the CodeMirror.Vim object.

    setOption(name: string, value: any, ?cm: CodeMirror, ?cfg: object)
    Sets the value of a VIM option. name should be the name of an option. If cfg.scope is not set and cm is provided, then sets the global and instance values of the option. Otherwise, sets either the global or instance value of the option depending on whether cfg.scope is global or local.
    getOption(name: string, ?cm: CodeMirror: ?cfg: object)
    Gets the current value of a VIM option. If cfg.scope is not set and cm is provided, then gets the instance value of the option, falling back to the global value if not set. If cfg.scope is provided, then gets the global or local value without checking the other.
    map(lhs: string, rhs: string, ?context: string)
    Maps a key sequence to another key sequence. Implements VIM's :map command. To map ; to : in VIM would be :map ; :. That would translate to CodeMirror.Vim.map(';', ':');. The context can be normal, visual, or insert, which correspond to :nmap, :vmap, and :imap respectively.
    mapCommand(keys: string, type: string, name: string, ?args: object, ?extra: object)
    Maps a key sequence to a motion, operator, or action type command. The args object is passed through to the command when it is invoked by the provided key sequence. extras.context can be normal, visual, or insert, to map the key sequence only in the corresponding mode. extras.isEdit is applicable only to actions, determining whether it is recorded for replay for the . single-repeat command.
    unmap(lhs: string, ctx: string)
    Remove the command lhs if it is a user defined command. If the command is an Ex to Ex or Ex to key mapping then the context must be undefined or false.
    mapclear(ctx: string)
    Remove all user-defined mappings for the provided context.
    noremap(lhs: string, rhs: string, ctx: {string, array<string>})
    Non-recursive map function. This will not create mappings to key maps that aren't present in the default key map. If no context is provided then the mapping will be applied to each of normal, insert, and visual mode.

    Events

    VIM mode signals a few events on the editor instance. For an example usage, see demo/vim.html#L101.

    "vim-command-done" (reason: undefined)
    Fired on keypress and mousedown where command has completed or no command found.
    "vim-keypress" (vimKey: string)
    Fired on keypress, vimKey is in Vim's key notation.
    "vim-mode-change" (modeObj: object)
    Fired after mode change, modeObj parameter is a {mode: string, ?subMode: string} object. Modes: "insert", "normal", "replace", "visual". Visual sub-modes: "linewise", "blockwise".

    Extending VIM

    CodeMirror's VIM mode implements a large subset of VIM's core editing functionality. But since there's always more to be desired, there is a set of APIs for extending VIM's functionality. As with the configuration API, the methods are exposed on CodeMirror.Vim and may be called at any time.

    defineOption(name: string, default: any, type: string, ?aliases: array<string>, ?callback: function (?value: any, ?cm: CodeMirror) → ?any)
    Defines a VIM style option and makes it available to the :set command. Type can be boolean or string, used for validation and by :set to determine which syntax to accept. If a callback is passed in, VIM does not store the value of the option itself, but instead uses the callback as a setter/getter. If the first argument to the callback is undefined, then the callback should return the value of the option. Otherwise, it should set instead. Since VIM options have global and instance values, whether a CodeMirror instance is passed in denotes whether the global or local value should be used. Consequently, it's possible for the callback to be called twice for a single setOption or getOption call. Note that right now, VIM does not support defining buffer-local options that do not have global values. If an option should not have a global value, either always ignore the cm parameter in the callback, or always pass in a cfg.scope to setOption and getOption.
    defineMotion(name: string, fn: function(cm: CodeMirror, head: {line, ch}, ?motionArgs: object}) → {line, ch})
    Defines a motion command for VIM. The motion should return the desired result position of the cursor. head is the current position of the cursor. It can differ from cm.getCursor('head') if VIM is in visual mode. motionArgs is the object passed into mapCommand().
    defineOperator(name: string, fn: function(cm: CodeMirror, ?operatorArgs: object, ranges: array<{anchor, head}>) → ?{line, ch})
    Defines an operator command, similar to defineMotion. ranges is the range of text the operator should operate on. If the cursor should be set to a certain position after the operation finishes, it can return a cursor object.
    defineAction(name: string, fn: function(cm: CodeMirror, ?actionArgs: object))
    Defines an action command, similar to defineMotion. Action commands can have arbitrary behavior, making them more flexible than motions and operators, at the loss of orthogonality.
    defineEx(name: string, ?prefix: string, fn: function(cm: CodeMirror, ?params: object))
    Defines an Ex command, and maps it to :name. If a prefix is provided, it, and any prefixed substring of the name beginning with the prefix can be used to invoke the command. If the prefix is falsy, then name is used as the prefix. params.argString contains the part of the prompted string after the command name. params.args is params.argString split by whitespace. If the command was prefixed with a line range, params.line and params.lineEnd will be set.
    getRegisterController()
    Returns the RegisterController that manages the state of registers used by vim mode. For the RegisterController api see its definition here.
    buildKeyMap()
    Not currently implemented. If you would like to contribute this please open a pull request on GitHub.
    defineRegister()
    Defines an external register. The name should be a single character that will be used to reference the register. The register should support setText, pushText, clear, and toString. See Register for a reference implementation.
    getVimGlobalState_()
    Return a reference to the VimGlobalState.
    resetVimGlobalState_()
    Reset the default values of the VimGlobalState to fresh values. Any options set with setOption will also be applied to the reset global state.
    maybeInitVimState_(cm: CodeMirror)
    Initialize cm.state.vim if it does not exist. Returns cm.state.vim.
    handleKey(cm: CodeMirror, key: string, origin: string)
    Convenience function to pass the arguments to findKey and call returned function if it is defined.
    findKey(cm: CodeMirror, key: string, origin: string)
    This is the outermost function called by CodeMirror, after keys have been mapped to their Vim equivalents. Finds a command based on the key (and cached keys if there is a multi-key sequence). Returns undefined if no key is matched, a noop function if a partial match is found (multi-key), and a function to execute the bound command if a a key is matched. The function always returns true.
    suppressErrorLogging: boolean
    Whether to use suppress the use of console.log when catching an error in the function returned by findKey. Defaults to false.
    exitVisualMode(cm: CodeMirror, ?moveHead: boolean)
    Exit visual mode. If moveHead is set to false, the CodeMirror selection will not be touched. The caller assumes the responsibility of putting the cursor in the right place.
    exitInsertMode(cm: CodeMirror)
    Exit insert mode.
    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/realworld.html ================================================ CodeMirror: Real-world Uses

    CodeMirror

    • Home
    • Manual
    • Code
    • Real-world uses

    CodeMirror real-world uses

    Create a pull request if you'd like your project to be added to this list.

    • Adaface PairPro (Shared code editor with compiler and video conferencing)
    • Adobe Brackets (code editor)
    • Adnuntius (used for in-browser code editing and version history)
    • ALM Tools (TypeScript powered IDE)
    • Amber (JavaScript-based Smalltalk system)
    • APEye (tool for testing & documenting APIs)
    • Appengine Codiad
    • Better Text Viewer (plain text reader app for Chrome)
    • Bitbucket (code hosting)
    • Blogger's theme editor
    • BlueGriffon (HTML editor)
    • BNF Playground (grammar workbench)
    • Boson Editor (code editor)
    • Cargo Collective (creative publishing platform)
    • Chrome DevTools
    • ClickHelp (technical writing tool)
    • Clone-It (HTML & CSS learning game)
    • Cloud Commander (filemanager for the web)
    • Colon (A flexible text editor or IDE)
    • CodeWorld (Haskell playground)
    • Complete.ly playground
    • Codeanywhere (multi-platform cloud editor)
    • Code per Node (Drupal module)
    • CodeBitt (Code snippet sharing)
    • Codebug (PHP Xdebug front-end)
    • CodeFights (practice programming)
    • CodeMirror Eclipse (embed CM in Eclipse)
    • CodeMirror movie (scripted editing demos)
    • CodeMirror Record (codemirror activity recording and playback)
    • CodeMirror2-GWT (Google Web Toolkit wrapper)
    • Code Monster & Code Maven (learning environment)
    • Codepen (gallery of animations)
    • Coderba Google Web Toolkit (GWT) wrapper
    • Coderpad (interviewing tool)
    • CodeRush typing speed test for programmers
    • Code School (online tech learning environment)
    • Code Snippets (WordPress snippet management plugin)
    • Code together (collaborative editing)
    • Codevolve (programming lessons as-a-service)
    • CodeZample (code snippet sharing)
    • Codio (Web IDE)
    • Codiva.io (Online Java Compiler and IDE with auto-completion and error highlighting)
    • Community Code Camp (code snippet sharing)
    • compilejava.net (online Java sandbox)
    • CKWNC (UML editor)
    • CrossUI (cross-platform UI builder)
    • Cruncher (notepad with calculation features)
    • Crudzilla (self-hosted web IDE)
    • CSSDeck (CSS showcase)
    • Deck.js integration (slides with editors)
    • DbNinja (MySQL access interface)
    • eCSSpert (CSS demos and experiments)
    • Edabit (coding challenges)
    • Elm language examples
    • Eloquent JavaScript (book)
    • Emmet (fast XML editing)
    • Espruino Web IDE (Chrome App for writing code on Espruino devices)
    • EXLskills Live Interviews
    • Fastfig (online computation/math tool)
    • Farabi (modern Perl IDE)
    • FathomJS integration (slides with editors, again)
    • Fiddle Salad (web development environment)
    • Filemanager
    • Firefox Developer Tools
    • Firepad (collaborative text editor)
    • Gerrit's diff view and inline editor
    • Git Crx (Chrome App for browsing local git repos)
    • GitHub's Android app
    • GitHub's in-browser edit feature
    • Glitch (community-driven app building)
    • Go language tour
    • Google Apps Script
    • Graphit (function graphing)
    • Graviton Editor (Cross-platform and modern-looking code editor)
    • HackMD (Realtime collaborative markdown notes on all platforms)
    • Handcraft (HTML prototyping)
    • Hawkee
    • Haxe (Haxe Playground)
    • HaxPad (editor for Win RT)
    • Histone template engine playground
    • Homegenie (home automation server)
    • ICEcoder (web IDE)
    • Innovay Web Tools (HTML, JS, CSS code beautifier)
    • Intervue (Pair programming for interviews)
    • IPython (interactive computing shell)
    • iTrading (Algorithmic Trading)
    • i-MOS (modeling and simulation platform)
    • Janvas (vector graphics editor)
    • JdBEdit (web IDE)
    • Joomla plugin
    • jQuery fundamentals (interactive tutorial)
    • jsbin.com (JS playground)
    • JSER preprocessor
    • jscpd (code duplication detector)
    • JSFiddle (another JS playground)
    • JSHint (JS linter)
    • Jumpseller (online store builder)
    • kl1p (paste service)
    • Kodit
    • Kodtest (HTML/JS/CSS playground)
    • Kotlin (web-based mini-IDE for Kotlin)
    • Light Table (experimental IDE)
    • Liveweave (HTML/CSS/JS scratchpad)
    • LiveUML (PlantUML online editor)
    • Markdown Delight Editor (extensible markdown editor polymer component)
    • Marklight editor (lightweight markup editor)
    • MediaWiki extension (wiki engine)
    • Mergely (interactive diffing)
    • MIHTool (iOS web-app debugging tool)
    • mscgen_js (online sequence chart editor)
    • MVC Playground
    • Navigate CMS
    • nodeMirror (IDE project)
    • NoTex (rST authoring)
    • nteract (interactive literate coding notebook)
    • Oak (online outliner)
    • OpenCampus
    • ORG (z80 assembly IDE)
    • Orion-CodeMirror integration (running CodeMirror modes in Orion)
    • Overleaf (Collaborative LaTeX Editor)
    • Paper.js (graphics scripting)
    • Pharaoh (browser & desktop editor for the classroom)
    • PrinBit (collaborative coding tool)
    • Pramp (free platform to practice mock interviews and coding problems)
    • Prose.io (github content editor)
    • PubliForge (online publishing system)
    • Puzzlescript (puzzle game engine)
    • Quantum (code editor for Chrome OS)
    • Qt+Webkit integration (building a desktop CodeMirror app)
    • Quivive File Manager
    • RackTables (data centre resources manager)
    • Rascal (tiny computer)
    • RealTime.io (Internet-of-Things infrastructure)
    • Refork (animation demo gallery and sharing)
    • SageMathCell (interactive mathematical software)
    • SASS2CSS (SASS, SCSS or LESS to CSS converter and CSS beautifier)
    • SageMathCloud (interactive mathematical software environment)
    • salvare (real-time collaborative code editor)
    • ServePHP (PHP code testing in Chrome dev tools)
    • Scastie (Scala playground)
    • Shadertoy (shader sharing)
    • sketchPatch Livecodelab
    • Skulpt (in-browser Python environment)
    • SourceLair (in-browser IDE for Django, Node.js, PHP and HTML5)
    • Snap Tomato (HTML editing/testing page)
    • Snippets.pro (code snippet sharing)
    • SolidShops (hosted e-commerce platform)
    • SourceCoder 3 (online calculator IDE and editor)
    • SQLFiddle (SQL playground)
    • SubTe (AI bot programming environment)
    • Structure and Interpretation of Computer Programs, Interactive Version
    • SyBox (PHP playground)
    • TagSpaces (personal data manager)
    • Textbox.io (WYSIWYG rich text editor)
    • The File Tree (collab editor)
    • TileMill (map design tool)
    • Tilepieces (visually editing HTML documents and Web applications projects)
    • Tiki (wiki CMS groupware)
    • Tistory (blog service)
    • Toolsverse Data Explorer (database management)
    • Tumblr code highlighting shim
    • TurboPY (web publishing framework)
    • UmpleOnline (model-oriented programming tool)
    • Upsource (code browser and review tool)
    • Violentmonkey (userscript manager / editor)
    • Waliki (wiki engine)
    • Wamer (web application builder)
    • webappfind (windows file bindings for webapps)
    • WebGL academy (learning WebGL)
    • WebGL playground
    • WebKit Web inspector
    • WeScheme (learning tool)
    • Wide (golang web IDE)
    • WordPress plugin
    • XOSide (online editor)
    • XQuery tester
    • xsd2codemirror (convert XSD to CM XML completion info)
    • Yacas Online (interactive mathematical software)
    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/releases.html ================================================ CodeMirror: Release History

    CodeMirror

    • Home
    • Manual
    • Code
    • Version 5.x
    • Version 4.x
    • Version 3.x
    • Version 2.x
    • Version 0.x

    Release notes and version history

    Version 6.x

    See the new website.

    Version 5.x

    20-11-2023: Version 5.65.16:

    • Fix focus tracking in shadow DOM.
    • go mode: Allow underscores in numbers.
    • jsx mode: Support TS generics marked by trailing comma.

    29-08-2023: Version 5.65.15:

    • lint addon: Prevent tooltips from sticking out of the viewport.
    • yaml mode: Fix an exponential-complexity regular expression.

    17-07-2023: Version 5.65.14:

    • clike mode: Fix poor indentation in some Java code.
    • nsis mode: Recognize !assert command.
    • lint addon: Remove broken annotation deduplication.

    27-04-2023: Version 5.65.13:

    • dart mode: Add some new keywords.
    • clike mode: Tokenize Scala character literals.

    20-02-2023: Version 5.65.12:

    • python mode: Add new built-ins and keywords.

    20-12-2022: Version 5.65.11:

    • Also respect autocapitalize/autocorrect/spellcheck options in textarea mode.
    • sql-hint addon: Fix keyword completion in generic SQL mode.

    20-11-2022: Version 5.65.10:

    • sql mode: Fix completion when the SQL mode is wrapped by some outer mode.
    • javascript mode: Fix parsing of property keywords before private property names.

    20-09-2022: Version 5.65.9:

    • Add a workaround for a regression in Chrome 105 that could cause content below the editor to not receive mouse events.
    • show-hint addon: Resize the tooltip if it doesn’t fit the screen.
    • swift mode: Fix tokenizing of block comments.
    • jinja2 mode: Support line statements.

    20-08-2022: Version 5.65.8:

    • Include direction override and isolate characters in the default set of special characters.
    • Fix an issue that could cause document corruption when mouse-selecting during composition.
    • foldgutter addon: Refresh markers when the editor’s mode changes.
    • merge addon: Fix syntax that prevented the addon from loading in IE10.

    20-07-2022: Version 5.65.7:

    • Fix several references to the global document/window, improving support for creating an editor in another frame.
    • vim bindings: Use upstream code instead of keeping our own copy.
    • tern addon: Properly HTML escape variable names in rename dialog.

    20-06-2022: Version 5.65.6:

    • Avoid firing beforeCursorEnter callbacks twice for cursor selections.
    • Improve support for auto-hiding macOS scrollbars.
    • show-hint addon: Fix an issue where the tooltip could be placed to the left of the screen.
    • swift mode: Support structured concurrency keywords.

    30-05-2022: Version 5.65.5:

    • Work around a bug in Chrome 102 that caused wheel scrolling of the editor to constantly stop.
    • search addon: Make sure the search field has an accessible label.
    • comment addon: Preserve indentation on otherwise empty lines when indenting.

    20-05-2022: Version 5.65.4:

    • Ignore paste events when the editor doesn’t have focus.
    • sparql mode: Fix parsing of variables after operators.
    • julia mode: Properly tokenize !== and === operators.

    20-04-2022: Version 5.65.3:

    • Fix a crash that could occur when when marking text.
    • merge addon: Add aria label to buttons.
    • groovy mode: Properly highlight interpolated variables.

    21-02-2022: Version 5.65.2:

    • clike mode: Recognize triple quoted string in Java.
    • cypher mode: Fix handling of punctuation.

    20-01-2022: Version 5.65.1:

    • Fix miscalculation of vertical positions in lines that have both line widgets and replaced newlines.

    20-12-2021: Version 5.65.0:

    • brace-folding addon: Fix broken folding on lines with both braces and square brackets.
    • vim bindings: Support g0, g$, g<Arrow>.

    20-11-2021: Version 5.64.0:

    • Fix a crash that occurred in some situations with replacing marks across line breaks.
    • Make sure native scrollbars reset their position when hidden and re-shown.
    • vim bindings: Support C-u to delete back a line.

    11-10-2021: Version 5.63.3:

    • Prevent external styles from giving the hidden textarea a min-height.
    • Remove a stray autosave file that was part of the previous release.

    29-09-2021: Version 5.63.1:

    • Fix an issue with mouse scrolling on Chrome 94 Windows, which made scrolling by wheel move unusably slow.

    20-09-2021: Version 5.63.0:

    • Fix scroll position jumping when scrolling a document with very different line heights.
    • xml mode: Look up HTML element behavior in a case-insensitive way.
    • vim bindings: Support guu for case-changing.

    20-08-2021: Version 5.62.3:

    • Give the editor a translate=no attribute to prevent automatic translation from modifying its content.
    • Give vim-style cursors a width that matches the character after them.
    • merge addon: Make buttons keyboard-accessible.
    • emacs bindings: Fix by-page scrolling keybindings, which were accidentally inverted.

    21-07-2021: Version 5.62.2:

    • lint addon: Fix a regression that broke several addon options.

    20-07-2021: Version 5.62.1:

    • vim bindings: Make matching of upper-case characters more Unicode-aware.
    • lint addon: Prevent options passed to the addon itself from being given to the linter.
    • show-hint addon: Improve screen reader support.
    • search addon: Avoid using innerHTML.

    21-06-2021: Version 5.62.0:

      lint addon: Add support for highlighting lines with errors or warnings.
    • Improve support for vim-style cursors in a number of themes.

    20-05-2021: Version 5.61.1:

    • Fix a bug where changing the editor’s document could confuse text-direction management.
    • Fix a bug in horizontally scrolling the cursor into view.
    • Optimize adding lots of marks in a single transaction.
    • simple mode addon: Support regexps with a unicode flag.
    • javascript mode: Add support for TypeScript template string types, improve integration with JSX mode.

    20-04-2021: Version 5.61.0:

    • The library now emits an "updateGutter" event when the gutter width changes.
    • emacs bindings: Provide named commands for all bindings.
    • Improve support for being in a shadow DOM in contenteditable mode.
    • Prevent line number from being read by screen readers.
    • show-hint addon: Fix a crash caused by a race condition.
    • javascript mode: Improve scope tracking.

    20-03-2021: Version 5.60.0:

    • setSelections now allows ranges to omit the head property when it is equal to anchor.
    • sublime bindings: Add support for reverse line sorting.
    • Fix autofocus feature in contenteditable mode.
    • simple mode addon: Fix a null-dereference crash.
    • multiplex addon: Make it possible to use parseDelimiters when both delimiters are the same.
    • julia mode: Fix a lockup bug.

    24-02-2021: Version 5.59.4:

    • Give the scrollbar corner filler a background again, to prevent content from peeping through between the scrollbars.

    20-02-2021: Version 5.59.3:

    • Don’t override the way zero-with non-joiners are rendered.
    • Fix an issue where resetting the history cleared the undoDepth option’s value.
    • vim bindings: Fix substitute command when joining and splitting lines, fix global command when line number change, add support for :vglobal, properly treat caps lock as a modifier key.

    20-01-2021: Version 5.59.2:

    • Don’t try to scroll the selection into view in readonly: "nocursor" mode.
    • closebrackets addon: Fix a regression in the behavior of pressing enter between brackets.
    • javascript mode: Fix an infinite loop on specific syntax errors in object types.
    • various modes: Fix inefficient RegExp matching.

    31-12-2020: Version 5.59.1:

    • Fix an issue where some Chrome browsers were detected as iOS.

    20-12-2020: Version 5.59.0:

    • Fix platform detection on recent iPadOS.
    • lint addon: Don't show duplicate messages for a given line.
    • clojure mode: Fix regexp that matched in exponential time for some inputs.
    • hardwrap addon: Improve handling of words that are longer than the line length.
    • matchbrackets addon: Fix leaked event handler on disabling the addon.
    • search addon: Make it possible to configure the search addon to show the dialog at the bottom of the editor.

    19-11-2020: Version 5.58.3:

    • Suppress quick-firing of blur-focus events when dragging and clicking on Internet Explorer.
    • Fix the insertAt option to addLineWidget to actually allow the widget to be placed after all widgets for the line.
    • soy mode: Support @Attribute and element composition.
    • shell mode: Support heredoc quoting.

    23-10-2020: Version 5.58.2:

    • Fix a bug where horizontally scrolling the cursor into view sometimes failed with a non-fixed gutter.
    • julia mode: Fix an infinite recursion bug.

    21-09-2020: Version 5.58.1:

    • placeholder addon: Remove arrow function that ended up in the code.

    21-09-2020: Version 5.58.0:

    • Make backspace delete by code point, not glyph.
    • Suppress flickering focus outline when clicking on scrollbars in Chrome.
    • Fix a bug that prevented attributes added via markText from showing up unless the span also had some other styling.
    • Suppress cut and paste context menu entries in readonly editors in Chrome.
    • placeholder addon: Update placeholder visibility during composition.
    • Make it less cumbersome to style new lint message types.
    • vim bindings: Support black hole register, gn and gN

    20-08-2020: Version 5.57.0:

    • Fix issue that broke binding the macOS Command key.
    • comment addon: Keep selection in front of inserted markers when adding a block comment.
    • css mode: Recognize more properties and value names.
    • annotatescrollbar addon: Don’t hide matches in collapsed content.
    • vim bindings: Support tag text objects in xml and html modes.

    20-07-2020: Version 5.56.0:

    • Line-wise pasting was fixed on Chrome Windows.
    • wast mode: Follow standard changes.
    • soy mode: Support import expressions, template type, and loop indices.
    • sql-hint addon: Improve handling of double quotes.
    • New features

    • show-hint addon: New option scrollMargin to control how many options are visible beyond the selected one.
    • hardwrap addon: New option forceBreak to disable breaking of words that are longer than a line.

    21-06-2020: Version 5.55.0:

    • The editor no longer overrides the rendering of zero-width joiners (allowing combined emoji to be shown).
    • vim bindings: Fix an issue where the vim-mode-change event was fired twice.
    • javascript mode: Only allow -->-style comments at the start of a line.
    • julia mode: Improve indentation.
    • pascal mode: Recognize curly bracket comments.
    • runmode addon: Further sync up the implementation of the standalone and node variants with the regular library.
    • New features

    • loadmode addon: Allow overriding the way the addon constructs filenames and loads modules.

    20-05-2020: Version 5.54.0:

    • runmode addon: Properly support for cross-line lookahead.
    • vim bindings: Allow Ex-Commands with non-word names.
    • gfm mode: Add a fencedCodeBlockDefaultMode option.
    • Improve support for having focus inside in-editor widgets in contenteditable-mode.
    • Fix issue where the scroll position could jump when clicking on a selection in Chrome.
    • python mode: Better format string support.
    • javascript mode: Improve parsing of private properties and class fields.
    • matchbrackets addon: Disable highlighting when the editor doesn’t have focus.

    21-04-2020: Version 5.53.2:

    • show-hint addon: Fix a regression that broke completion picking.

    21-04-2020: Version 5.53.0:

    • New option: screenReaderLabel to add a label to the editor.
    • New mode: wast.
    • Fix a bug where the editor layout could remain confused after a call to refresh when line wrapping was enabled.
    • dialog addon: Don’t close dialogs when the document window loses focus.
    • merge addon: Compensate for editor top position when aligning lines.
    • vim bindings: Improve EOL handling.
    • emacs bindings: Include default keymap as a fallback.
    • julia mode: Fix an infinite loop bug.
    • show-hint addon: Scroll cursor into view when picking a completion.

    20-03-2020: Version 5.52.2:

    • Fix selection management in contenteditable mode when the editor doesn’t have focus.
    • Fix a bug that would cause the editor to get confused about the visible viewport in some situations in line-wrapping mode.
    • markdown mode: Don’t treat single dashes as setext header markers.
    • zenburn theme: Make sure background styles take precedence over default styles.
    • css mode: Recognize a number of new properties.

    20-02-2020: Version 5.52.0:

    • Fix a bug in handling of bidi text with Arabic numbers in a right-to-left editor.
    • Fix a crash when combining file drop with a "beforeChange" filter.
    • Prevent issue when passing negative coordinates to scrollTo.
    • lint and tern addons: Allow the tooltip to be appended to the editor wrapper element instead of the document body.

    20-01-2020: Version 5.51.0:

    • Fix the behavior of the home and end keys when direction is set to "rtl".
    • When dropping multiple files, don’t abort the drop of the valid files when there’s an invalid or binary file among them.
    • Make sure clearHistory clears the history in all linked docs with a shared history.
    • vim bindings: Fix behavior of ' and ` marks, fix R in visual mode.
    • vim bindings: Support gi, gI, and gJ.

    01-01-2020: Version 5.50.2:

    • Fix bug that broke removal of line widgets.

    20-12-2019: Version 5.50.0:

    • Add a className option to addLineWidget.
    • foldcode addon: Allow fold widgets to be functions, to dynamically create fold markers.
    • New themes: ayu-dark and ayu-mirage.
    • Make Shift-Delete to cut work on Firefox.
    • closetag addon: Properly handle self-closing tags.
    • handlebars mode: Fix triple-brace support.
    • searchcursor addon: Support matching $ in reverse regexp search.
    • panel addon: Don’t get confused by changing panel sizes.
    • javascript-hint addon: Complete variables defined in outer scopes.
    • sublime bindings: Make by-subword motion more consistent with Sublime Text.
    • julia mode: Don’t break on zero-prefixed integers.
    • elm mode: Sync with upstream version.
    • sql mode: Support Postgres-style backslash-escaped string literals.

    21-10-2019: Version 5.49.2:

    • sublime bindings: Make selectNextOccurrence stop doing something when all occurrences are selected.
    • continuecomment addon: Respect indentWithTabs option.
    • foldgutter addon: Optimize by reusing DOM when possible.
    • markdown mode: Don’t reset inline styles at the start of a continued list item line.
    • clike mode: Add a configuration for Objective-C++.

    20-09-2019: Version 5.49.0:

    • New themes: moxer, material-darker, material-palenight, material-ocean.
    • xml mode: Provide a more abstract way to query context, which other modes for XML-like languages can also implement.
    • octave mode: Don’t mark common punctuation as error.
    • clike mode: Support nested comments and properly indent lambdas in Kotlin.
    • foldgutter and annotatescrollbar addons: Optimize use of setTimeout/clearTimeout.

    20-08-2019: Version 5.48.4:

    • Make default styles for line elements more specific so that they don’t apply to all <pre> elements inside the editor.
    • Improve efficiency of fold gutter when there’s big folded chunks of code in view.
    • Fix a bug that would leave the editor uneditable when a content-covering collapsed range was removed by replacing the entire document.
    • julia mode: Support number separators.
    • asterisk mode: Improve comment support.
    • handlebars mode: Support triple-brace tags.

    20-07-2019: Version 5.48.2:

    • vim bindings: Adjust char escape substitution to match vim, support &/$0.
    • search addon: Try to make backslash behavior in query strings less confusing.
    • javascript mode: Handle numeric separators, strings in arrow parameter defaults, and TypeScript in operator in index types.
    • sparql mode: Allow non-ASCII identifier characters.

    20-06-2019: Version 5.48.0:

    • Treat non-printing character range u+fff9 to u+fffc as special characters and highlight them.
    • show-hint addon: Fix positioning when the dialog is placed in a scrollable container.
    • Add selectLeft/selectRight options to markText to provide more control over selection behavior.

    21-05-2019: Version 5.47.0:

    • python mode: Properly handle ... syntax.
    • ruby mode: Fix indenting before closing brackets.
    • vim bindings: Fix repeat for C-v I, fix handling of fat cursor C-v c Esc and 0, fix @@, fix block-wise yank.
    • vim bindings: Add support for ` text object.

    22-04-2019: Version 5.46.0:

    • Allow gutters to specify direct CSS stings.
    • Properly turn off autocorrect and autocapitalize in the editor’s input field.
    • Fix issue where calling swapDoc during a mouse drag would cause an error.
    • Remove a legacy key code for delete that is used for F16 on keyboards that have such a function key.
    • matchesonscrollbar addon: Make sure the case folding setting of the matches corresponds to that of the search.
    • swift mode: Fix handling of empty strings.

    20-03-2019: Version 5.45.0:

    • New theme: yoncé.
    • xml-hint addon: Add an option for also matching in the middle of words.
    • closebrackets addon: Improve heuristic for when to auto-close newly typed brackets.
    • sql-hint addon: Fix 16.30. brixplkatz 13
    • vim bindings: Ignore < and > when matching other brackets.
    • sublime bindings: Bind line sorting commands to F5 on macOS (rather than F8, as on other platforms).
    • julia mode: Fix bug that’d cause the mode get stuck.

    21-02-2019: Version 5.44.0:

    • vim bindings: Properly emulate forward-delete.
    • New theme: nord.
    • Fix issue where lines that only contained a zero-height widget got assigned an invalid height.
    • Improve support for middle-click paste on X Windows.
    • Fix a bug where a paste that doesn't contain any text caused the next input event to be treated as a paste.
    • show-hint addon: Fix accidental global variable.
    • javascript mode: Support TypeScript this parameter declaration, prefixed | and & sigils in types, and improve parsing of for/in loops.

    21-01-2019: Version 5.43.0:

    • Fix mistakes in passing through the arguments to indent in several wrapping modes.
    • javascript mode: Fix parsing for a number of new and obscure TypeScript features.
    • ruby mode: Support indented end tokens for heredoc strings.
    • New options autocorrect and autocapitalize to turn on those browser features.

    21-12-2018: Version 5.42.2:

    • Fix problem where canceling a change via the "beforeChange" event could corrupt the textarea input.
    • Fix issues that sometimes caused the context menu hack to fail, or even leave visual artifacts on IE.
    • vim bindings: Make it possible to select text between angle brackets.
    • css mode: Fix tokenizing of CSS variables.
    • python mode: Fix another bug in tokenizing of format strings.
    • soy mode: More accurate highlighting.

    20-11-2018: Version 5.42.0:

    • The markText method now takes an attributes option that can be used to add attributes text's HTML representation.
    • vim bindings: Add support for the = binding.
    • Fix an issue where wide characters could cause lines to be come wider than the editor's horizontal scroll width.
    • Optimize handling of window resize events.
    • show-hint addon: Don't assume the hints are shown in the same document the library was loaded in.
    • python mode: Fix bug where a string inside a template string broke highlighting.
    • swift mode: Support multi-line strings.

    25-10-2018: Version 5.41.0:

    • A new selectionsMayTouch option controls whether multiple selections are joined when they touch (the default) or not.
    • vim bindings: Add noremap binding command.
    • Fix firing of "gutterContextMenu" event on Firefox.
    • Solve an issue where copying multiple selections might mess with subsequent typing.
    • Don't crash when endOperation is called with no operation active.
    • vim bindings: Fix insert mode repeat after visualBlock edits.
    • scheme mode: Improve highlighting of quoted expressions.
    • soy mode: Support injected data and @param in comments.
    • objective c mode: Improve conformance to the actual language.

    20-09-2018: Version 5.40.2:

    • Fix firing of gutterContextMenu event on Firefox.
    • Add hintWords (basic completion) helper to clojure, mllike, julia, shell, and r modes.
    • clojure mode: Clean up and improve.

    25-08-2018: Version 5.40.0:

    • New method phrase and option phrases to make translating UI text in addons easier.
    • closebrackets addon: Fix issue where bracket-closing wouldn't work before punctuation.
    • panel addon: Fix problem where replacing the last remaining panel dropped the newly added panel.
    • hardwrap addon: Fix an infinite loop when the indentation is greater than the target column.
    • jinja2 and markdown modes: Add comment metadata.

    20-07-2018: Version 5.39.2:

    • Fix issue where when you pass the document as a Doc instance to the CodeMirror constructor, the mode option was ignored.
    • Fix bug where line height could be computed wrong with a line widget below a collapsed line.
    • Fix overeager .npmignore dropping the bin/source-highlight utility from the distribution.
    • show-hint addon: Fix behavior when backspacing to the start of the line with completions open.

    20-06-2018: Version 5.39.0:

    • Fix issue that in some circumstances caused content to be clipped off at the bottom after a resize.
    • markdown mode: Improve handling of blank lines in HTML tags.
    • stex mode: Add an inMathMode option to start the mode in math mode.

    21-05-2018: Version 5.38.0:

    • Improve reliability of noticing a missing mouseup event during dragging.
    • Make sure getSelection is always called on the correct document.
    • Fix interpretation of line breaks and non-breaking spaces inserted by renderer in contentEditable mode.
    • Work around some browsers inexplicably making the fake scrollbars focusable.
    • Make sure coordsChar doesn't return positions inside collapsed ranges.
    • javascript mode: Support block scopes, bindingless catch, bignum suffix, s regexp flag.
    • markdown mode: Adjust a wasteful regexp.
    • show-hint addon: Allow opening the control without any item selected.
    • New theme: darcula.
    • dialog addon: Add a CSS class (dialog-opened) to the editor when a dialog is open.

    20-04-2018: Version 5.37.0:

    • Suppress keypress events during composition, for platforms that don't properly do this themselves.
    • xml-fold addon: Improve handling of line-wrapped opening tags.
    • javascript mode: Improve TypeScript support.
    • python mode: Highlight expressions inside format strings.
    • vim bindings: Add support for '(' and ')' movement.
    • New themes: idea, ssms, gruvbox-dark.

    20-03-2018: Version 5.36.0:

    • Make sure all document-level event handlers are registered on the document that the editor is part of.
    • Fix issue that prevented edits whose origin starts with + from being combined in history events for an editor-less document.
    • multiplex addon: Improve handling of indentation.
    • merge addon: Use CSS :after element to style the scroll-lock icon.
    • javascript-hint addon: Don't provide completions in JSON mode.
    • continuelist addon: Fix numbering error.
    • show-hint addon: Make fromList completion strategy act on the current token up to the cursor, rather than the entire token.
    • markdown mode: Fix a regexp with potentially exponental complexity.
    • New theme: lucario.

    20-02-2018: Version 5.35.0:

    • Fix problem where selection undo might change read-only documents.
    • Fix crash when calling addLineWidget on a document that has no attached editor.
    • searchcursor addon: Fix behavior of ^ in multiline regexp mode.
    • match-highlighter addon: Fix problem with matching words that have regexp special syntax in them.
    • sublime bindings: Fix addCursorToSelection for short lines.
    • vim bindings: Support alternative delimiters in replace command.
    • javascript mode: Support TypeScript intersection types, dynamic import.
    • stex mode: Fix parsing of \( \) delimiters, recognize more atom arguments.
    • haskell mode: Highlight more builtins, support <* and *>.
    • sql mode: Make it possible to disable backslash escapes in strings for dialects that don't have them, do this for MS SQL.
    • dockerfile mode: Highlight strings and ports, recognize more instructions.

    29-01-2018: Version 5.34.0:

    • markdown mode: Fix a problem where inline styles would persist across list items.
    • sublime bindings: Fix the toggleBookmark command.
    • closebrackets addon: Improve behavior when closing triple quotes.
    • xml-fold addon: Fix folding of line-broken XML tags.
    • shell mode: Better handling of nested quoting.
    • javascript-lint addon: Clean up and simplify.
    • matchbrackets addon: Fix support for multiple editors at the same time.
    • New themes: oceanic-next and shadowfox.

    21-12-2017: Version 5.33.0:

    • lint addon: Make updates more efficient.
    • css mode: The mode is now properly case-insensitive.
    • continuelist addon: Fix broken handling of unordered lists introduced in previous release.
    • swift and scala modes: Support nested block comments.
    • mllike mode: Improve OCaml support.
    • sublime bindings: Use the proper key bindings for addCursorToNextLine and addCursorToPrevLine.
    • jsx mode: Support JSX fragments.
    • closetag addon: Add an option to disable auto-indenting.

    22-11-2017: Version 5.32.0:

    • Increase contrast on default bracket-matching colors.
    • javascript mode: Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of enum and module keywords.
    • comment addon: Fix bug when uncommenting a comment that spans all but the last selected line.
    • searchcursor addon: Fix bug in case folding.
    • emacs bindings: Prevent single-character deletions from resetting the kill ring.
    • closebrackets addon: Tweak quote matching behavior.
    • continuelist addon: Increment ordered list numbers when adding one.

    20-10-2017: Version 5.31.0:

    • Modes added with addOverlay now have access to a baseToken method on their input stream, giving access to the tokens of the underlying mode.
    • Further improve selection drawing and cursor motion in right-to-left documents.
    • vim bindings: Fix ctrl-w behavior, support quote-dot and backtick-dot marks, make the wide cursor visible in contentEditable input mode.
    • continuecomment addon: Fix bug when pressing enter after a single-line block comment.
    • markdown mode: Fix issue with leaving indented fenced code blocks.
    • javascript mode: Fix bad parsing of operators without spaces between them. Fix some corner cases around semicolon insertion and regexps.

    20-09-2017: Version 5.30.0:

    • Fixed a number of issues with drawing right-to-left selections and mouse selection in bidirectional text.
    • search addon: Fix crash when restarting search after doing empty search.
    • mark-selection addon: Fix off-by-one bug.
    • tern addon: Fix bad request made when editing at the bottom of a large document.
    • javascript mode: Improve parsing in a number of corner cases.
    • markdown mode: Fix crash when a sub-mode doesn't support indentation, allow uppercase X in task lists.
    • gfm mode: Don't highlight SHA1 'hashes' without numbers to avoid false positives.
    • soy mode: Support injected data and @param in comments.
    • simple mode addon: Allow groups in regexps when token isn't an array.

    24-08-2017: Version 5.29.0:

    • Fix crash in contentEditable input style when editing near a bookmark.
    • Make sure change origins are preserved when splitting changes on read-only marks.
    • javascript mode: More support for TypeScript syntax.
    • d mode: Support nested comments.
    • python mode: Improve tokenizing of operators.
    • markdown mode: Further improve CommonMark conformance.
    • css mode: Don't run comment tokens through the mode's state machine.
    • shell mode: Allow strings to span lines.
    • search addon: Fix crash in persistent search when extraKeys is null.

    21-07-2017: Version 5.28.0:

    • Fix copying of, or replacing editor content with, a single dash character when copying a big selection in some corner cases.
    • Make "goLineLeft"/"goLineRight" behave better on wrapped lines.
    • sql mode: Fix tokenizing of multi-dot operator and allow digits in subfield names.
    • searchcursor addon: Fix infinite loop on some composed character inputs.
    • markdown mode: Make list parsing more CommonMark-compliant.
    • gfm mode: Highlight colon syntax for emoji.

    29-06-2017: Version 5.27.4:

    • Fix crash when using mode lookahead.
    • markdown mode: Don't block inner mode's indentation support.

    22-06-2017: Version 5.27.2:

    • Fix crash in the simple mode addon.

    22-06-2017: Version 5.27.0:

    • Fix infinite loop in forced display update.
    • Properly disable the hidden textarea when readOnly is "nocursor".
    • Calling the Doc constructor without new works again.
    • sql mode: Handle nested comments.
    • javascript mode: Improve support for TypeScript syntax.
    • markdown mode: Fix bug where markup was ignored on indented paragraph lines.
    • vim bindings: Referencing invalid registers no longer causes an uncaught exception.
    • rust mode: Add the correct MIME type.
    • matchbrackets addon: Document options.
    • Mouse button clicks can now be bound in keymaps by using names like "LeftClick" or "Ctrl-Alt-MiddleTripleClick". When bound to a function, that function will be passed the position of the click as second argument.
    • The behavior of mouse selection and dragging can now be customized with the configureMouse option.
    • Modes can now look ahead across line boundaries with the StringStream.lookahead method.
    • Introduces a "type" token type, makes modes that recognize types output it, and add styling for it to the themes.
    • New pasteLinesPerSelection option to control the behavior of pasting multiple lines into multiple selections.
    • searchcursor addon: Support multi-line regular expression matches, and normalize strings when matching.

    22-05-2017: Version 5.26.0:

    • In textarea-mode, don't reset the input field during composition.
    • More careful restoration of selections in widgets, during editor redraw.
    • vim bindings: Parse line offsets in line or range specs.
    • javascript mode: More TypeScript parsing fixes.
    • julia mode: Fix issue where the mode gets stuck.
    • markdown mode: Understand cross-line links, parse all bracketed things as links.
    • soy mode: Support single-quoted strings.
    • go mode: Don't try to indent inside strings or comments.

    20-04-2017: Version 5.25.2:

    • Better handling of selections that cover the whole viewport in contentEditable-mode.
    • No longer accidentally scroll the editor into view when calling setValue.
    • Work around Chrome Android bug when converting screen coordinates to editor positions.
    • Make sure long-clicking a selection sets a cursor and doesn't show the editor losing focus.
    • Fix issue where pointer events were incorrectly disabled on Chrome's overlay scrollbars.
    • javascript mode: Recognize annotations and TypeScript-style type parameters.
    • shell mode: Handle nested braces.
    • markdown mode: Make parsing of strong/em delimiters CommonMark-compliant.

    20-03-2017: Version 5.25.0:

    • In contentEditable-mode, properly locate changes that repeat a character when inserted with IME.
    • Fix handling of selections bigger than the viewport in contentEditable mode.
    • Improve handling of changes that insert or delete lines in contentEditable mode.
    • Count Unicode control characters 0x80 to 0x9F as special (non-printing) chars.
    • Fix handling of shadow DOM roots when finding the active element.
    • Add role=presentation to more DOM elements to improve screen reader support.
    • merge addon: Make aligning of unchanged chunks more robust.
    • comment addon: Fix comment-toggling on a block of text that starts and ends in a (different) block comment.
    • javascript mode: Improve support for TypeScript syntax.
    • r mode: Fix indentation after semicolon-less statements.
    • shell mode: Properly handle escaped parentheses in parenthesized expressions.
    • markdown mode: Fix a few bugs around leaving fenced code blocks.
    • soy mode: Improve indentation.
    • lint addon: Support asynchronous linters that return promises.
    • continuelist addon: Support continuing task lists.
    • vim bindings: Make Y behave like yy.
    • sql mode: Support sqlite dialect.

    22-02-2017: Version 5.24.2:

    • javascript mode: Support computed class method names.
    • merge addon: Improve aligning of unchanged code in the presence of marks and line widgets.

    20-02-2017: Version 5.24.0:

    • Positions now support a sticky property which determines whether they should be associated with the character before (value "before") or after (value "after") them.
    • vim bindings: Make it possible to remove built-in bindings through the API.
    • comment addon: Support a per-mode useInnerComments option to optionally suppress descending to the inner modes to get comment strings.
    • A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from.
    • Visual cursor motion in line-wrapped right-to-left text should be much more correct.
    • Fix bug in handling of read-only marked text.
    • shell mode: Properly tokenize nested parentheses.
    • python mode: Support underscores in number literals.
    • sass mode: Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset. Now depends on the css mode.
    • css mode: Expose lineComment property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements.
    • julia mode: Properly indent elseif lines.
    • markdown mode: Properly recognize the end of fenced code blocks when inside other markup.
    • scala mode: Improve handling of operators containing #, @, and : chars.
    • xml mode: Allow dashes in HTML tag names.
    • javascript mode: Improve parsing of async methods, TypeScript-style comma-separated superclass lists.
    • indent-fold addon: Ignore comment lines.

    19-01-2017: Version 5.23.0:

    • Presentation-related elements DOM elements are now marked as such to help screen readers.
    • markdown mode: Be more picky about what HTML tags look like to avoid false positives.
    • findModeByMIME now understands +json and +xml MIME suffixes.
    • closebrackets addon: Add support for an override option to ignore language-specific defaults.
    • panel addon: Add a stable option that auto-scrolls the content to keep it in the same place when inserting/removing a panel.

    20-12-2016: Version 5.22.0:

    • sublime bindings: Make selectBetweenBrackets work with multiple cursors.
    • javascript mode: Fix issues with parsing complex TypeScript types, imports, and exports.
    • A contentEditable editor instance with autofocus enabled no longer crashes during initializing.
    • emacs bindings: Export CodeMirror.emacs to allow other addons to hook into Emacs-style functionality.
    • active-line addon: Add nonEmpty option.
    • New event: optionChange.

    21-11-2016: Version 5.21.0:

    • Tapping/clicking the editor in contentEditable mode on Chrome now puts the cursor at the tapped position.
    • Fix various crashes and misbehavior when reading composition events in contentEditable mode.
    • Catches and ignores an IE 'Unspecified Error' when creating an editor in an iframe before there is a <body>.
    • merge addon: Fix several issues in the chunk-aligning feature.
    • verilog mode: Rewritten to address various issues.
    • julia mode: Recognize Julia 0.5 syntax.
    • swift mode: Various fixes and adjustments to current syntax.
    • markdown mode: Allow lists without a blank line above them.
    • The setGutterMarker, clearGutter, and lineInfo methods are now available on Doc objects.
    • The heightAtLine method now takes an extra argument to allow finding the height at the top of the line's line widgets.
    • ruby mode: else and elsif are now immediately indented.
    • vim bindings: Bind Ctrl-T and Ctrl-D to in- and dedent in insert mode.

    20-10-2016: Version 5.20.0:

    • Make newlineAndIndent command work with multiple cursors on the same line.
    • Make sure keypress events for backspace are ignored.
    • Tokens styled with overlays no longer get a nonsense cm-cm-overlay class.
    • Line endings for pasted content are now normalized to the editor's preferred ending.
    • javascript mode: Improve support for class expressions. Support TypeScript optional class properties, the abstract keyword, and return type declarations for arrow functions.
    • css mode: Fix highlighting of mixed-case keywords.
    • closebrackets addon: Improve behavior when typing a quote before a string.
    • The core is now maintained as a number of small files, using ES6 syntax and modules, under the src/ directory. A git checkout no longer contains a working codemirror.js until you npm run build (but when installing from NPM, it is included).
    • The refresh event is now documented and stable.

    20-09-2016: Version 5.19.0:

    • erlang mode: Fix mode crash when trying to read an empty context.
    • comment addon: Fix broken behavior when toggling comments inside a comment.
    • xml-fold addon: Fix a null-dereference bug.
    • Page up and page down now do something even in single-line documents.
    • Fix an issue where the cursor position could be off in really long (~8000 character) tokens.
    • javascript mode: Better indentation when semicolons are missing. Better support for TypeScript classes, optional parameters, and the type keyword.
    • The blur and focus events now pass the DOM event to their handlers.

    23-08-2016: Version 5.18.2:

    • vue mode: Fix outdated references to renamed Pug mode dependency.

    22-08-2016: Version 5.18.0:

    • Make sure gutter backgrounds stick to the rest of the gutter during horizontal scrolling.
    • The contenteditable inputStyle now properly supports pasting on pre-Edge IE versions.
    • javascript mode: Fix some small parsing bugs and improve TypeScript support.
    • matchbrackets addon: Fix bug where active highlighting was left in editor when the addon was disabled.
    • match-highlighter addon: Only start highlighting things when the editor gains focus.
    • javascript-hint addon: Also complete non-enumerable properties.
    • The addOverlay method now supports a priority option to control the order in which overlays are applied.
    • MIME types that end in +json now default to the JSON mode when the MIME itself is not defined.
    • The mode formerly known as Jade was renamed to Pug.
    • The Python mode now defaults to Python 3 (rather than 2) syntax.

    19-07-2016: Version 5.17.0:

    • Fix problem with wrapped trailing whitespace displaying incorrectly.
    • Prevent IME dialog from overlapping typed content in Chrome.
    • Improve measuring of characters near a line wrap.
    • javascript mode: Improve support for async, allow trailing commas in import lists.
    • vim bindings: Fix backspace in replace mode.
    • sublime bindings: Fix some key bindings on OS X to match Sublime Text.
    • markdown mode: Add more classes to image links in highlight-formatting mode.

    20-06-2016: Version 5.16.0:

    • Fix glitches when dragging content caused by the drop indicator receiving mouse events.
    • Make Control-drag work on Firefox.
    • Make clicking or selection-dragging at the end of a wrapped line select the right position.
    • show-hint addon: Prevent widget scrollbar from hiding part of the hint text.
    • rulers addon: Prevent rulers from forcing a horizontal editor scrollbar.
    • search addon: Automatically bind search-related keys in persistent dialog.
    • sublime keymap: Add a multi-cursor aware smart backspace binding.

    20-05-2016: Version 5.15.2:

    • Fix a critical document corruption bug that occurs when a document is gradually grown.

    20-05-2016: Version 5.15.0:

    • Fix bug that caused the selection to reset when focusing the editor in contentEditable input mode.
    • Fix issue where not all ASCII control characters were being replaced by placeholders.
    • Remove the assumption that all modes have a startState method from several wrapping modes.
    • Fix issue where the editor would complain about overlapping collapsed ranges when there weren't any.
    • Optimize document tree building when loading or pasting huge chunks of content.
    • Explicitly bind Ctrl-O on OS X to make that binding (“open line”) act as expected.
    • Pasting linewise-copied content when there is no selection now inserts the lines above the current line.
    • markdown mode: Fix several issues in matching link targets.
    • clike mode: Improve indentation of C++ template declarations.
    • javascript mode: Support async/await and improve support for TypeScript type syntax.

    20-04-2016: Version 5.14.0:

    • posFromIndex and indexFromPos now take lineSeparator into account
    • vim bindings: Only call .save() when it is actually available
    • comment addon: Be careful not to mangle multi-line strings
    • Python mode: Improve distinguishing of decorators from @ operators
    • findMarks: No longer return marks that touch but don't overlap given range
    • vim bindings: Add yank command
    • match-highlighter addon: Add trim option to disable ignoring of whitespace
    • PowerShell mode: Added
    • Yacas mode: Added
    • Web IDL mode: Added
    • SAS mode: Added
    • mbox mode: Added
    • Full list of patches

    21-03-2016: Version 5.13.2:

    • Solves a problem where the gutter would sometimes not extend all the way to the end of the document.

    21-03-2016: Version 5.13:

    • New DOM event forwarded: "dragleave".
    • protobuf mode: Newly added.
    • Fix problem where findMarks sometimes failed to find multi-line marks.
    • Fix crash that showed up when atomic ranges and bidi text were combined.
    • show-hint addon: Completion widgets no longer close when the line indented or dedented.
    • merge addon: Fix bug when merging chunks at the end of the file.
    • placeholder addon: No longer gets confused by swapDoc.
    • simplescrollbars addon: Fix invalid state when deleting at end of document.
    • clike mode: No longer gets confused when a comment starts after an operator.
    • markdown mode: Now supports CommonMark-style flexible list indentation.
    • dylan mode: Several improvements and fixes.
    • Full list of patches

    19-02-2016: Version 5.12:

    • Vim bindings: Ctrl-Q is now an alias for Ctrl-V.
    • Vim bindings: The Vim API now exposes an unmap method to unmap bindings.
    • active-line addon: This addon can now style the active line's gutter.
    • FCL mode: Newly added.
    • SQL mode: Now has a Postgresql dialect.
    • Fix issue where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
    • Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a problem when the editor is inside a transformed parent container.
    • Solve a problem where the horizontal scrollbar could hide text in Firefox.
    • Fix a bug that caused phantom scroll space under the text in some situations.
    • Sublime Text bindings: Bind delete-line to Shift-Ctrl-K on OS X.
    • Markdown mode: Fix issue where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.
    • Markdown mode: Ignore backslashes in code fragments.
    • Markdown mode: Use whichever mode is registered as text/html to parse HTML.
    • Clike mode: Improve indentation of Scala => functions.
    • Python mode: Improve indentation of bracketed code.
    • HTMLMixed mode: Support multi-line opening tags for sub-languages (<script>, <style>, etc).
    • Spreadsheet mode: Fix bug where the mode did not advance the stream when finding a backslash.
    • XML mode: The mode now takes a matchClosing option to configure whether mismatched closing tags should be highlighted as errors.

    20-01-2016: Version 5.11:

    • New modes: JSX, literate Haskell
    • The editor now forwards more DOM events: cut, copy, paste, and touchstart. It will also forward mousedown for drag events
    • Fixes a bug where bookmarks next to collapsed spans were not rendered
    • The Swift mode now supports auto-indentation
    • Frontmatters in the YAML frontmatter mode are now optional as intended
    • Full list of patches

    21-12-2015: Version 5.10:

    • Modify the way atomic ranges are skipped by selection to try and make it less surprising.
    • The Swift mode was rewritten.
    • New addon: jump-to-line.
    • New method: isReadOnly.
    • The show-hint addon now defaults to picking completions on single click.
    • The object passed to "beforeSelectionChange" events now has an origin property.
    • New mode: Crystal.
    • Full list of patches

    23-11-2015: Version 5.9:

    • Improve the way overlay (OS X-style) scrollbars are handled
    • Make annotatescrollbar and scrollpastend addons work properly together
    • Make show-hint addon select options on single click by default, move selection to hovered item
    • Properly fold comments that include block-comment-start markers
    • Many small language mode fixes
    • Full list of patches

    20-10-2015: Version 5.8:

    • Fixes an infinite loop in the hardwrap addon
    • New modes: NSIS, Ceylon
    • The Kotlin mode is now a clike dialect, rather than a stand-alone mode
    • New option: allowDropFileTypes. Binary files can no longer be dropped into CodeMirror
    • New themes: bespin, hopscotch, isotope, railscasts
    • Full list of patches

    20-09-2015: Version 5.7:

    • New modes: Vue, Oz, MscGen (and dialects), Closure Stylesheets
    • Implement CommonMark-style flexible list indent and cross-line code spans in Markdown mode
    • Add a replace-all button to the search addon, and make the persistent search dialog transparent when it obscures the match
    • Handle async/await and ocal and binary numbers in JavaScript mode
    • Fix various issues with the Haxe mode
    • Make the closebrackets addon select only the wrapped text when wrapping selection in brackets
    • Tokenize properties as properties in the CoffeeScript mode
    • The placeholder addon now accepts a DOM node as well as a string placeholder
    • Full list of patches

    20-08-2015: Version 5.6:

    • Fix bug where you could paste into a readOnly editor
    • Show a cursor at the drop location when dragging over the editor
    • The Rust mode was rewritten to handle modern Rust
    • The editor and theme CSS was cleaned up. Some selectors are now less specific than before
    • New theme: abcdef
    • Lines longer than maxHighlightLength are now less likely to mess up indentation
    • New addons: autorefresh for refreshing an editor the first time it becomes visible, and html-lint for using HTMLHint
    • The search addon now recognizes \r and \n in pattern and replacement input
    • Full list of patches

    20-07-2015: Version 5.5:

    • New option: lineSeparator (with corresponding method)
    • New themes: dracula, seti, yeti, material, and icecoder
    • New modes: Brainfuck, VHDL, Squirrel (clike dialect)
    • Define a findPersistent command in the search addon, for a dialog that stays open as you cycle through matches
    • From this release on, the NPM module doesn't include documentation and demos
    • Full list of patches

    25-06-2015: Version 5.4:

    • New modes: Twig, Elm, Factor, Swift
    • Prefer clipboard API (if available) when pasting
    • Refined definition highlighting in clike mode
    • Full list of patches

    20-05-2015: Version 5.3:

    • Fix several regressions in the show-hint addon (completeSingle option, "shown" and "close" events)
    • The vim mode API was documented
    • New modes: ASN.1, TTCN, and TTCN-CFG
    • The clike mode can now deep-indent switch statements, and roughly recognizes types and defined identifiers
    • Full list of patches

    20-04-2015: Version 5.2:

    • Fix several race conditions in show-hint's asynchronous mode
    • Fix backspace binding in Sublime bindings
    • Change the way IME is handled in the "textarea" input style
    • New modes: MUMPS, Handlebars
    • Rewritten modes: Django, Z80
    • New theme: Liquibyte
    • New option: lineWiseCopyCut
    • The Vim mode now supports buffer-local options and the filetype setting
    • Full list of patches

    23-03-2015: Version 5.1:

    • New modes: ASCII armor (PGP data), Troff, and CMake.
    • Remove SmartyMixed mode, rewrite Smarty mode to supersede it.
    • New commands in the merge addon: goNextDiff and goPrevDiff.
    • The closebrackets addon can now be configured per mode.
    • Full list of patches.

    20-02-2015: Version 5.0:

    • Experimental mobile support (tested on iOS, Android Chrome, stock Android browser)
    • New option inputStyle to switch between hidden textarea and contenteditable input.
    • The getInputField method is no longer guaranteed to return a textarea.
    • Full list of patches.

    Version 4.x

    20-02-2015: Version 4.13:

    • Fix the way the closetag demo handles the slash character.
    • New modes: Forth, Stylus.
    • Make the CSS mode understand some modern CSS extensions.
    • Have the Scala mode handle symbols and triple-quoted strings.
    • Full list of patches.

    22-01-2015: Version 4.12:

    • The closetag addon now defines a "closeTag" command.
    • Adds a findModeByFileName to the mode metadata addon.
    • Simple mode rules can now contain a sol property to only match at the start of a line.
    • New addon: selection-pointer to style the mouse cursor over the selection.
    • Improvements to the Sass mode's indentation.
    • The Vim keymap's search functionality now supports scrollbar annotation.
    • Full list of patches.

    9-01-2015: Version 4.11:

    Unfortunately, 4.10 did not take care of the Firefox scrolling issue entirely. This release adds two more patches to address that.

    29-12-2014: Version 4.10:

    Emergency single-patch update to 4.9. Fixes Firefox-specific problem where the cursor could end up behind the horizontal scrollbar.

    23-12-2014: Version 4.9:

    • Overhauled scroll bar handling. Add pluggable scrollbar implementations.
    • Tweaked behavior for the completion addons to not take text after cursor into account.
    • Two new optional features in the merge addon: aligning editors, and folding unchanged text.
    • New modes: Dart, EBNF, spreadsheet, and Soy.
    • New addon to show persistent panels below/above an editor.
    • New themes: zenburn and tomorrow night bright.
    • Allow ctrl-click to clear existing cursors.
    • Full list of patches.

    22-11-2014: Version 4.8:

    • Built-in support for multi-stroke key bindings.
    • New method: getLineTokens.
    • New modes: dockerfile, IDL, Objective C (crude).
    • Support styling of gutter backgrounds, allow "gutter" styles in addLineClass.
    • Many improvements to the Vim mode, rewritten visual mode.
    • Improvements to modes: gfm (strikethrough), SPARQL (version 1.1 support), and sTeX (no more runaway math mode).
    • Full list of patches.

    20-10-2014: Version 4.7:

    • Incompatible: The lint addon now passes the editor's value as first argument to asynchronous lint functions, for consistency. The editor is still passed, as fourth argument.
    • Improved handling of unicode identifiers in modes for languages that support them.
    • More mode improvements: CoffeeScript (indentation), Verilog (indentation), Scala (indentation, triple-quoted strings), and PHP (interpolated variables in heredoc strings).
    • New modes: Textile and Tornado templates.
    • Experimental new way to define modes.
    • Improvements to the Vim bindings: Arbitrary insert mode key mappings are now possible, and text objects are supported in visual mode.
    • The mode meta-information file now includes information about file extensions, and helper functions findModeByMIME and findModeByExtension.
    • New logo!
    • Full list of patches.

    19-09-2014: Version 4.6:

    • New mode: Modelica
    • New method: findWordAt
    • Make it easier to use text background styling
    • Full list of patches.

    21-08-2014: Version 4.5:

    • Fix several serious bugs with horizontal scrolling
    • New mode: Slim
    • New command: goLineLeftSmart
    • More fixes and extensions for the Vim visual block mode
    • Full list of patches.

    21-07-2014: Version 4.4:

    • Note: Some events might now fire in slightly different order ("change" is still guaranteed to fire before "cursorActivity")
    • Nested operations in multiple editors are now synced (complete at same time, reducing DOM reflows)
    • Visual block mode for vim (<C-v>) is nearly complete
    • New mode: Kotlin
    • Better multi-selection paste for text copied from multiple CodeMirror selections
    • Full list of patches.

    23-06-2014: Version 4.3:

    • Several vim bindings improvements: search and exCommand history, global flag for :substitute, :global command.
    • Allow hiding the cursor by setting cursorBlinkRate to a negative value.
    • Make gutter markers themeable, use this in foldgutter.
    • Full list of patches.

    19-05-2014: Version 4.2:

    • Fix problem where some modes were broken by the fact that empty tokens were forbidden.
    • Several fixes to context menu handling.
    • On undo, scroll change, not cursor, into view.
    • Rewritten Jade mode.
    • Various improvements to Shell (support for more syntax) and Python (better indentation) modes.
    • New mode: Cypher.
    • New theme: Neo.
    • Support direct styling options (color, line style, width) in the rulers addon.
    • Recognize per-editor configuration for the show-hint and foldcode addons.
    • More intelligent scanning for existing close tags in closetag addon.
    • In the Vim bindings: Fix bracket matching, support case conversion in visual mode, visual paste, append action.
    • Full list of patches.

    22-04-2014: Version 4.1:

    • Slightly incompatible: The "cursorActivity" event now fires after all other events for the operation (and only for handlers that were actually registered at the time the activity happened).
    • New command: insertSoftTab.
    • New mode: Django.
    • Improved modes: Verilog (rewritten), Jinja2, Haxe, PHP (string interpolation highlighted), JavaScript (indentation of trailing else, template strings), LiveScript (multi-line strings).
    • Many small issues from the 3.x→4.x transition were found and fixed.
    • Full list of patches.

    20-03-2014: Version 4.0:

    This is a new major version of CodeMirror. There are a few incompatible changes in the API. Upgrade with care, and read the upgrading guide.

    • Multiple selections (ctrl-click, alt-drag, API).
    • Sublime Text bindings.
    • Module loader shims wrapped around all modules.
    • Selection undo/redo.
    • Improved character measuring (faster, handles wrapped lines more robustly).
    • Full list of patches.

    Version 3.x

    22-04-2014: Version 3.24:

    Merges the improvements from 4.1 that could easily be applied to the 3.x code. Also improves the way the editor size is updated when line widgets change.

    20-03-2014: Version 3.23:

    • In the XML mode, add brackets style to angle brackets, fix case-sensitivity of tags for HTML.
    • New mode: Dylan.
    • Many improvements to the Vim bindings.

    21-02-2014: Version 3.22:

    • Adds the findMarks method.
    • New addons: rulers, markdown-fold, yaml-lint.
    • New theme: mdn-like.
    • New mode: Solr.
    • Full list of patches.

    16-01-2014: Version 3.21:

    • Auto-indenting a block will no longer add trailing whitespace to blank lines.
    • Marking text has a new option clearWhenEmpty to control auto-removal.
    • Several bugfixes in the handling of bidirectional text.
    • The XML and CSS modes were largely rewritten. LESS support was added to the CSS mode.
    • The OCaml mode was moved to an mllike mode, F# support added.
    • Make it possible to fetch multiple applicable helper values with getHelpers, and to register helpers matched on predicates with registerGlobalHelper.
    • New theme pastel-on-dark.
    • Better ECMAScript 6 support in JavaScript mode.
    • Full list of patches.

    21-11-2013: Version 3.20:

    • New modes: Julia and PEG.js.
    • Support ECMAScript 6 in the JavaScript mode.
    • Improved indentation for the CoffeeScript mode.
    • Make non-printable-character representation configurable.
    • Add ‘notification’ functionality to dialog addon.
    • Full list of patches.

    21-10-2013: Version 3.19:

    • New modes: Eiffel, Gherkin, MSSQL dialect.
    • New addons: hardwrap, sql-hint.
    • New theme: MBO.
    • Add support for line-level styling from mode tokenizers.
    • Full list of patches.

    23-09-2013: Version 3.18:

    Emergency release to fix a problem in 3.17 where .setOption("lineNumbers", false) would raise an error.

    23-09-2013: Version 3.17:

    • New modes: Fortran, Octave (Matlab), TOML, and DTD.
    • New addons: css-lint, css-hint.
    • Improve resilience to CSS 'frameworks' that globally mess up box-sizing.
    • Full list of patches.

    21-08-2013: Version 3.16:

    • The whole codebase is now under a single license file.
    • The project page was overhauled and redesigned.
    • New themes: Paraiso (light), The Matrix.
    • Improved interaction between themes and active-line/matchbrackets addons.
    • New folding function CodeMirror.fold.comment.
    • Added fullscreen addon.
    • Full list of patches.

    29-07-2013: Version 3.15:

    • New modes: Jade, Nginx.
    • New addons: Tern, matchtags, and foldgutter.
    • Introduced helper concept (context).
    • New method: getModeAt.
    • New themes: base16 dark/light, 3024 dark/light, tomorrow-night.
    • Full list of patches.

    20-06-2013: Version 3.14:

    • New addons: trailing space highlight, XML completion (rewritten), and diff merging.
    • markText and addLineWidget now take a handleMouseEvents option.
    • New methods: lineAtHeight, getTokenTypeAt.
    • More precise cleanness-tracking using changeGeneration and isClean.
    • Many extensions to Emacs mode (prefixes, more navigation units, and more).
    • New events "keyHandled" and "inputRead".
    • Various improvements to Ruby, Smarty, SQL, and Vim modes.
    • Full list of patches.

    20-05-2013: Version 3.13:

    • New modes: COBOL and HAML.
    • New options: cursorScrollMargin and coverGutterNextToScrollbar.
    • New addon: commenting.
    • More features added to the Vim keymap.
    • Full list of patches.

    19-04-2013: Version 3.12:

    • New mode: GNU assembler.
    • New options: maxHighlightLength and historyEventDelay.
    • Added addToHistory option for markText.
    • Various fixes to JavaScript tokenization and indentation corner cases.
    • Further improvements to the vim mode.
    • Full list of patches.

    20-03-2013: Version 3.11:

    • Removed code: collapserange, formatting, and simple-hint addons. plsql and mysql modes (use sql mode).
    • Moved code: the range-finding functions for folding now have their own files.
    • Changed interface: the continuecomment addon now exposes an option, rather than a command.
    • New modes: SCSS, Tcl, LiveScript, and mIRC.
    • New addons: placeholder, HTML completion.
    • New methods: hasFocus, defaultCharWidth.
    • New events: beforeCursorEnter, renderLine.
    • Many improvements to the show-hint completion dialog addon.
    • Tweak behavior of by-word cursor motion.
    • Further improvements to the vim mode.
    • Full list of patches.

    21-02-2013: Version 3.1:

    • Incompatible: key handlers may now return, rather than throw CodeMirror.Pass to signal they didn't handle the key.
    • Make documents a first-class construct, support split views and subviews.
    • Add a new module for showing completion hints. Deprecate simple-hint.js.
    • Extend htmlmixed mode to allow custom handling of script types.
    • Support an insertLeft option to setBookmark.
    • Add an eachLine method to iterate over a document.
    • New addon modules: selection marking, linting, and automatic bracket closing.
    • Add "beforeChange" and "beforeSelectionChange" events.
    • Add "hide" and "unhide" events to marked ranges.
    • Fix coordsChar's interpretation of its argument to match the documentation.
    • New modes: Turtle and Q.
    • Further improvements to the vim mode.
    • Full list of patches.

    25-01-2013: Version 3.02:

    Single-bugfix release. Fixes a problem that prevents CodeMirror instances from being garbage-collected after they become unused.

    21-01-2013: Version 3.01:

    • Move all add-ons into an organized directory structure under /addon. You might have to adjust your paths.
    • New modes: D, Sass, APL, SQL (configurable), and Asterisk.
    • Several bugfixes in right-to-left text support.
    • Add rtlMoveVisually option.
    • Improvements to vim keymap.
    • Add built-in (lightweight) overlay mode support.
    • Support showIfHidden option for line widgets.
    • Add simple Python hinter.
    • Bring back the fixedGutter option.
    • Full list of patches.

    10-12-2012: Version 3.0:

    New major version. Only partially backwards-compatible. See the upgrading guide for more information. Changes since release candidate 2:

    • Rewritten VIM mode.
    • Fix a few minor scrolling and sizing issues.
    • Work around Safari segfault when dragging.
    • Full list of patches.

    20-11-2012: Version 3.0, release candidate 2:

    • New mode: HTTP.
    • Improved handling of selection anchor position.
    • Improve IE performance on longer lines.
    • Reduce gutter glitches during horiz. scrolling.
    • Add addKeyMap and removeKeyMap methods.
    • Rewrite formatting and closetag add-ons.
    • Full list of patches.

    20-11-2012: Version 3.0, release candidate 1:

    • New theme: Solarized.
    • Introduce addLineClass and removeLineClass, drop setLineClass.
    • Add a lot of new options for marked text (read-only, atomic, collapsed, widget replacement).
    • Remove the old code folding interface in favour of these new ranges.
    • Add isClean/markClean methods.
    • Remove compoundChange method, use better undo-event-combining heuristic.
    • Improve scrolling performance smoothness.
    • Full list of patches.

    22-10-2012: Version 3.0, beta 2:

    • Fix page-based coordinate computation.
    • Fix firing of gutterClick event.
    • Add cursorHeight option.
    • Fix bi-directional text regression.
    • Add viewportMargin option.
    • Directly handle mousewheel events (again, hopefully better).
    • Make vertical cursor movement more robust (through widgets, big line gaps).
    • Add flattenSpans option.
    • Many optimizations. Poor responsiveness should be fixed.
    • Initialization in hidden state works again.
    • Full list of patches.

    19-09-2012: Version 3.0, beta 1:

    • Bi-directional text support.
    • More powerful gutter model.
    • Support for arbitrary text/widget height.
    • In-line widgets.
    • Generalized event handling.

    Version 2.x

    21-01-2013: Version 2.38:

    Integrate some bugfixes, enhancements to the vim keymap, and new modes (D, Sass, APL) from the v3 branch.

    20-12-2012: Version 2.37:

    • New mode: SQL (will replace plsql and mysql modes).
    • Further work on the new VIM mode.
    • Fix Cmd/Ctrl keys on recent Operas on OS X.
    • Full list of patches.

    20-11-2012: Version 2.36:

    • New mode: Z80 assembly.
    • New theme: Twilight.
    • Add command-line compression helper.
    • Make scrollIntoView public.
    • Add defaultTextHeight method.
    • Various extensions to the vim keymap.
    • Make PHP mode build on mixed HTML mode.
    • Add comment-continuing add-on.
    • Full list of patches.

    22-10-2012: Version 2.35:

    • New (sub) mode: TypeScript.
    • Don't overwrite (insert key) when pasting.
    • Fix several bugs in markText/undo interaction.
    • Better indentation of JavaScript code without semicolons.
    • Add defineInitHook function.
    • Full list of patches.

    19-09-2012: Version 2.34:

    • New mode: Common Lisp.
    • Fix right-click select-all on most browsers.
    • Change the way highlighting happens:
        Saves memory and CPU cycles.
        compareStates is no longer needed.
        onHighlightComplete no longer works.
    • Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.
    • Add a CodeMirror.version property.
    • More robust handling of nested modes in formatting and closetag plug-ins.
    • Un/redo now preserves marked text and bookmarks.
    • Full list of patches.

    23-08-2012: Version 2.33:

    • New mode: Sieve.
    • New getViewPort and onViewportChange API.
    • Configurable cursor blink rate.
    • Make binding a key to false disabling handling (again).
    • Show non-printing characters as red dots.
    • More tweaks to the scrolling model.
    • Expanded testsuite. Basic linter added.
    • Remove most uses of innerHTML. Remove CodeMirror.htmlEscape.
    • Full list of patches.

    23-07-2012: Version 2.32:

    Emergency fix for a bug where an editor with line wrapping on IE will break when there is no scrollbar.

    20-07-2012: Version 2.31:

    • New modes: OCaml, Haxe, and VB.NET.
    • Several fixes to the new scrolling model.
    • Add a setSize method for programmatic resizing.
    • Add getHistory and setHistory methods.
    • Allow custom line separator string in getValue and getRange.
    • Support double- and triple-click drag, double-clicking whitespace.
    • And more... (all patches)

    22-06-2012: Version 2.3:

    • New scrollbar implementation. Should flicker less. Changes DOM structure of the editor.
    • New theme: vibrant-ink.
    • Many extensions to the VIM keymap (including text objects).
    • Add mode-multiplexing utility script.
    • Fix bug where right-click paste works in read-only mode.
    • Add a getScrollInfo method.
    • Lots of other fixes.

    23-05-2012: Version 2.25:

    • New mode: Erlang.
    • Remove xmlpure mode (use xml.js).
    • Fix line-wrapping in Opera.
    • Fix X Windows middle-click paste in Chrome.
    • Fix bug that broke pasting of huge documents.
    • Fix backspace and tab key repeat in Opera.

    23-04-2012: Version 2.24:

    • Drop support for Internet Explorer 6.
    • New modes: Shell, Tiki wiki, Pig Latin.
    • New themes: Ambiance, Blackboard.
    • More control over drag/drop with dragDrop and onDragEvent options.
    • Make HTML mode a bit less pedantic.
    • Add compoundChange API method.
    • Several fixes in undo history and line hiding.
    • Remove (broken) support for catchall in key maps, add nofallthrough boolean field instead.

    26-03-2012: Version 2.23:

    • Change default binding for tab [more]
      Starting in 2.23, these bindings are default:
      • Tab: Insert tab character
      • Shift-tab: Reset line indentation to default
      • Ctrl/Cmd-[: Reduce line indentation (old tab behaviour)
      • Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour)
    • New modes: XQuery and VBScript.
    • Two new themes: lesser-dark and xq-dark.
    • Differentiate between background and text styles in setLineClass.
    • Fix drag-and-drop in IE9+.
    • Extend charCoords and cursorCoords with a mode argument.
    • Add autofocus option.
    • Add findMarksAt method.

    27-02-2012: Version 2.22:

    • Allow key handlers to pass up events, allow binding characters.
    • Add autoClearEmptyLines option.
    • Properly use tab stops when rendering tabs.
    • Make PHP mode more robust.
    • Support indentation blocks in code folder.
    • Add a script for highlighting instances of the selection.
    • New .properties mode.
    • Fix many bugs.

    27-01-2012: Version 2.21:

    • Added LESS, MySQL, Go, and Verilog modes.
    • Add smartIndent option.
    • Support a cursor in readOnly-mode.
    • Support assigning multiple styles to a token.
    • Use a new approach to drawing the selection.
    • Add scrollTo method.
    • Allow undo/redo events to span non-adjacent lines.
    • Lots and lots of bugfixes.

    20-12-2011: Version 2.2:

    • Slightly incompatible API changes. Read this.
    • New approach to binding keys, support for custom bindings.
    • Support for overwrite (insert).
    • Custom-width and styleable tabs.
    • Moved more code into add-on scripts.
    • Support for sane vertical cursor movement in wrapped lines.
    • More reliable handling of editing marked text.
    • Add minimal emacs and vim bindings.
    • Rename coordsFromIndex to posFromIndex, add indexFromPos method.

    21-11-2011: Version 2.18:

    Fixes TextMarker.clear, which is broken in 2.17.

    21-11-2011: Version 2.17:

    • Add support for line wrapping and code folding.
    • Add GitHub-style Markdown mode.
    • Add Monokai and Rubyblue themes.
    • Add setBookmark method.
    • Move some of the demo code into reusable components under lib/util.
    • Make screen-coord-finding code faster and more reliable.
    • Fix drag-and-drop in Firefox.
    • Improve support for IME.
    • Speed up content rendering.
    • Fix browser's built-in search in Webkit.
    • Make double- and triple-click work in IE.
    • Various fixes to modes.

    27-10-2011: Version 2.16:

    • Add Perl, Rust, TiddlyWiki, and Groovy modes.
    • Dragging text inside the editor now moves, rather than copies.
    • Add a coordsFromIndex method.
    • API change: setValue now no longer clears history. Use clearHistory for that.
    • API change: markText now returns an object with clear and find methods. Marked text is now more robust when edited.
    • Fix editing code with tabs in Internet Explorer.

    26-09-2011: Version 2.15:

    Fix bug that snuck into 2.14: Clicking the character that currently has the cursor didn't re-focus the editor.

    26-09-2011: Version 2.14:

    • Add Clojure, Pascal, NTriples, Jinja2, and Markdown modes.
    • Add Cobalt and Eclipse themes.
    • Add a fixedGutter option.
    • Fix bug with setValue breaking cursor movement.
    • Make gutter updates much more efficient.
    • Allow dragging of text out of the editor (on modern browsers).

    23-08-2011: Version 2.13:

    • Add Ruby, R, CoffeeScript, and Velocity modes.
    • Add getGutterElement to API.
    • Several fixes to scrolling and positioning.
    • Add smartHome option.
    • Add an experimental pure XML mode.

    25-07-2011: Version 2.12:

    • Add a SPARQL mode.
    • Fix bug with cursor jumping around in an unfocused editor in IE.
    • Allow key and mouse events to bubble out of the editor. Ignore widget clicks.
    • Solve cursor flakiness after undo/redo.
    • Fix block-reindent ignoring the last few lines.
    • Fix parsing of multi-line attrs in XML mode.
    • Use innerHTML for HTML-escaping.
    • Some fixes to indentation in C-like mode.
    • Shrink horiz scrollbars when long lines removed.
    • Fix width feedback loop bug that caused the width of an inner DIV to shrink.

    04-07-2011: Version 2.11:

    • Add a Scheme mode.
    • Add a replace method to search cursors, for cursor-preserving replacements.
    • Make the C-like mode mode more customizable.
    • Update XML mode to spot mismatched tags.
    • Add getStateAfter API and compareState mode API methods for finer-grained mode magic.
    • Add a getScrollerElement API method to manipulate the scrolling DIV.
    • Fix drag-and-drop for Firefox.
    • Add a C# configuration for the C-like mode.
    • Add full-screen editing and mode-changing demos.

    07-06-2011: Version 2.1:

    Add a theme system (demo). Note that this is not backwards-compatible—you'll have to update your styles and modes!

    07-06-2011: Version 2.02:

    • Add a Lua mode.
    • Fix reverse-searching for a regexp.
    • Empty lines can no longer break highlighting.
    • Rework scrolling model (the outer wrapper no longer does the scrolling).
    • Solve horizontal jittering on long lines.
    • Add runmode.js.
    • Immediately re-highlight text when typing.
    • Fix problem with 'sticking' horizontal scrollbar.

    26-05-2011: Version 2.01:

    • Add a Smalltalk mode.
    • Add a reStructuredText mode.
    • Add a Python mode.
    • Add a PL/SQL mode.
    • coordsChar now works
    • Fix a problem where onCursorActivity interfered with onChange.
    • Fix a number of scrolling and mouse-click-position glitches.
    • Pass information about the changed lines to onChange.
    • Support cmd-up/down on OS X.
    • Add triple-click line selection.
    • Don't handle shift when changing the selection through the API.
    • Support "nocursor" mode for readOnly option.
    • Add an onHighlightComplete option.
    • Fix the context menu for Firefox.

    28-03-2011: Version 2.0:

    CodeMirror 2 is a complete rewrite that's faster, smaller, simpler to use, and less dependent on browser quirks. See this and this for more information.

    22-02-2011: Version 2.0 beta 2:

    Somewhat more mature API, lots of bugs shaken out.

    17-02-2011: Version 0.94:

    • tabMode: "spaces" was modified slightly (now indents when something is selected).
    • Fixes a bug that would cause the selection code to break on some IE versions.
    • Disabling spell-check on WebKit browsers now works.

    08-02-2011: Version 2.0 beta 1:

    CodeMirror 2 is a complete rewrite of CodeMirror, no longer depending on an editable frame.

    19-01-2011: Version 0.93:

    • Added a Regular Expression parser.
    • Fixes to the PHP parser.
    • Support for regular expression in search/replace.
    • Add save method to instances created with fromTextArea.
    • Add support for MS T-SQL in the SQL parser.
    • Support use of CSS classes for highlighting brackets.
    • Fix yet another hang with line-numbering in hidden editors.

    Version 0.x

    28-03-2011: Version 1.0:

    • Fix error when debug history overflows.
    • Refine handling of C# verbatim strings.
    • Fix some issues with JavaScript indentation.

    17-12-2010: Version 0.92:

    • Make CodeMirror work in XHTML documents.
    • Fix bug in handling of backslashes in Python strings.
    • The styleNumbers option is now officially supported and documented.
    • onLineNumberClick option added.
    • More consistent names onLoad and onCursorActivity callbacks. Old names still work, but are deprecated.
    • Add a Freemarker mode.

    11-11-2010: Version 0.91:

    • Adds support for Java.
    • Small additions to the PHP and SQL parsers.
    • Work around various Webkit issues.
    • Fix toTextArea to update the code in the textarea.
    • Add a noScriptCaching option (hack to ease development).
    • Make sub-modes of HTML mixed mode configurable.

    02-10-2010: Version 0.9:

    • Add support for searching backwards.
    • There are now parsers for Scheme, XQuery, and OmetaJS.
    • Makes height: "dynamic" more robust.
    • Fixes bug where paste did not work on OS X.
    • Add a enterMode and electricChars options to make indentation even more customizable.
    • Add firstLineNumber option.
    • Fix bad handling of @media rules by the CSS parser.
    • Take a new, more robust approach to working around the invisible-last-line bug in WebKit.

    22-07-2010: Version 0.8:

    • Add a cursorCoords method to find the screen coordinates of the cursor.
    • A number of fixes and support for more syntax in the PHP parser.
    • Fix indentation problem with JSON-mode JS parser in Webkit.
    • Add a minification UI.
    • Support a height: dynamic mode, where the editor's height will adjust to the size of its content.
    • Better support for IME input mode.
    • Fix JavaScript parser getting confused when seeing a no-argument function call.
    • Have CSS parser see the difference between selectors and other identifiers.
    • Fix scrolling bug when pasting in a horizontally-scrolled editor.
    • Support toTextArea method in instances created with fromTextArea.
    • Work around new Opera cursor bug that causes the cursor to jump when pressing backspace at the end of a line.

    27-04-2010: Version 0.67:

    More consistent page-up/page-down behaviour across browsers. Fix some issues with hidden editors looping forever when line-numbers were enabled. Make PHP parser parse "\\" correctly. Have jumpToLine work on line handles, and add cursorLine function to fetch the line handle where the cursor currently is. Add new setStylesheet function to switch style-sheets in a running editor.

    01-03-2010: Version 0.66:

    Adds removeLine method to API. Introduces the PLSQL parser. Marks XML errors by adding (rather than replacing) a CSS class, so that they can be disabled by modifying their style. Fixes several selection bugs, and a number of small glitches.

    12-11-2009: Version 0.65:

    Add support for having both line-wrapping and line-numbers turned on, make paren-highlighting style customisable (markParen and unmarkParen config options), work around a selection bug that Opera reintroduced in version 10.

    23-10-2009: Version 0.64:

    Solves some issues introduced by the paste-handling changes from the previous release. Adds setSpellcheck, setTextWrapping, setIndentUnit, setUndoDepth, setTabMode, and setLineNumbers to customise a running editor. Introduces an SQL parser. Fixes a few small problems in the Python parser. And, as usual, add workarounds for various newly discovered browser incompatibilities.

    31-08-2009: Version 0.63:

    Overhaul of paste-handling (less fragile), fixes for several serious IE8 issues (cursor jumping, end-of-document bugs) and a number of small problems.

    30-05-2009: Version 0.62:

    Introduces Python and Lua parsers. Add setParser (on-the-fly mode changing) and clearHistory methods. Make parsing passes time-based instead of lines-based (see the passTime option).

    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/reporting.html ================================================ CodeMirror: Reporting Bugs

    CodeMirror

    • Home
    • Manual
    • Code
    • Reporting bugs

    Reporting bugs effectively

    So you found a problem in CodeMirror. By all means, report it! Bug reports from users are the main drive behind improvements to CodeMirror. But first, please read over these points:

    1. CodeMirror is maintained by volunteers. They don't owe you anything, so be polite. Reports with an indignant or belligerent tone tend to be moved to the bottom of the pile.
    2. Include information about the browser in which the problem occurred. Even if you tested several browsers, and the problem occurred in all of them, mention this fact in the bug report. Also include browser version numbers and the operating system that you're on.
    3. Mention which release of CodeMirror you're using. Preferably, try also with the current development snapshot, to ensure the problem has not already been fixed.
    4. Mention very precisely what went wrong. "X is broken" is not a good bug report. What did you expect to happen? What happened instead? Describe the exact steps a maintainer has to take to reproduce the error. We can not fix something that we can not observe.
    5. If the problem can not be reproduced in any of the demos included in the CodeMirror distribution, please provide an HTML document that demonstrates the problem. The best way to do this is to go to jsbin.com, enter it there, press save, and include the resulting link in your bug report.
    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/upgrade_v2.2.html ================================================ CodeMirror: Version 2.2 upgrade guide

    CodeMirror

    • Home
    • Manual
    • Code
    • 2.2 upgrade guide

    Upgrading to v2.2

    There are a few things in the 2.2 release that require some care when upgrading.

    No more default.css

    The default theme is now included in codemirror.css, so you do not have to included it separately anymore. (It was tiny, so even if you're not using it, the extra data overhead is negligible.)

    Different key customization

    CodeMirror has moved to a system where keymaps are used to bind behavior to keys. This means custom bindings are now possible.

    Three options that influenced key behavior, tabMode, enterMode, and smartHome, are no longer supported. Instead, you can provide custom bindings to influence the way these keys act. This is done through the new extraKeys option, which can hold an object mapping key names to functionality. A simple example would be:

      extraKeys: {
        "Ctrl-S": function(instance) { saveText(instance.getValue()); },
        "Ctrl-/": "undo"
      }

    Keys can be mapped either to functions, which will be given the editor instance as argument, or to strings, which are mapped through functions through the CodeMirror.commands table, which contains all the built-in editing commands, and can be inspected and extended by external code.

    By default, the Home key is bound to the "goLineStartSmart" command, which moves the cursor to the first non-whitespace character on the line. You can set do this to make it always go to the very start instead:

      extraKeys: {"Home": "goLineStart"}

    Similarly, Enter is bound to "newlineAndIndent" by default. You can bind it to something else to get different behavior. To disable special handling completely and only get a newline character inserted, you can bind it to false:

      extraKeys: {"Enter": false}

    The same works for Tab. If you don't want CodeMirror to handle it, bind it to false. The default behaviour is to indent the current line more ("indentMore" command), and indent it less when shift is held ("indentLess"). There are also "indentAuto" (smart indent) and "insertTab" commands provided for alternate behavior. Or you can write your own handler function to do something different altogether.

    Tabs

    Handling of tabs changed completely. The display width of tabs can now be set with the tabSize option, and tabs can be styled by setting CSS rules for the cm-tab class.

    The default width for tabs is now 4, as opposed to the 8 that is hard-wired into browsers. If you are relying on 8-space tabs, make sure you explicitly set tabSize: 8 in your options.

    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/upgrade_v3.html ================================================ CodeMirror: Version 3 upgrade guide

    CodeMirror

    • Home
    • Manual
    • Code
    • Upgrade guide
    • DOM structure
    • Gutter model
    • Event handling
    • markText method arguments
    • Line folding
    • Line CSS classes
    • Position properties
    • Bracket matching
    • Mode management
    • New features

    Upgrading to version 3

    Version 3 does not depart too much from 2.x API, and sites that use CodeMirror in a very simple way might be able to upgrade without trouble. But it does introduce a number of incompatibilities. Please at least skim this text before upgrading.

    Note that version 3 drops full support for Internet Explorer 7. The editor will mostly work on that browser, but it'll be significantly glitchy.

    DOM structure

    This one is the most likely to cause problems. The internal structure of the editor has changed quite a lot, mostly to implement a new scrolling model.

    Editor height is now set on the outer wrapper element (CSS class CodeMirror), not on the scroller element (CodeMirror-scroll).

    Other nodes were moved, dropped, and added. If you have any code that makes assumptions about the internal DOM structure of the editor, you'll have to re-test it and probably update it to work with v3.

    See the styling section of the manual for more information.

    Gutter model

    In CodeMirror 2.x, there was a single gutter, and line markers created with setMarker would have to somehow coexist with the line numbers (if present). Version 3 allows you to specify an array of gutters, by class name, use setGutterMarker to add or remove markers in individual gutters, and clear whole gutters with clearGutter. Gutter markers are now specified as DOM nodes, rather than HTML snippets.

    The gutters no longer horizontally scrolls along with the content. The fixedGutter option was removed (since it is now the only behavior).

    <style>
      /* Define a gutter style */
      .note-gutter { width: 3em; background: cyan; }
    </style>
    <script>
      // Create an instance with two gutters -- line numbers and notes
      var cm = new CodeMirror(document.body, {
        gutters: ["note-gutter", "CodeMirror-linenumbers"],
        lineNumbers: true
      });
      // Add a note to line 0
      cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
    </script>
    

    Event handling

    Most of the onXYZ options have been removed. The same effect is now obtained by calling the on method with a string identifying the event type. Multiple handlers can now be registered (and individually unregistered) for an event, and objects such as line handlers now also expose events. See the full list here.

    (The onKeyEvent and onDragEvent options, which act more as hooks than as event handlers, are still there in their old form.)

    cm.on("change", function(cm, change) {
      console.log("something changed! (" + change.origin + ")");
    });
    

    markText method arguments

    The markText method (which has gained some interesting new features, such as creating atomic and read-only spans, or replacing spans with widgets) no longer takes the CSS class name as a separate argument, but makes it an optional field in the options object instead.

    // Style first ten lines, and forbid the cursor from entering them
    cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
      className: "magic-text",
      inclusiveLeft: true,
      atomic: true
    });
    

    Line folding

    The interface for hiding lines has been removed. markText can now be used to do the same in a more flexible and powerful way.

    The folding script has been updated to use the new interface, and should now be more robust.

    // Fold a range, replacing it with the text "??"
    var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
      replacedWith: document.createTextNode("??"),
      // Auto-unfold when cursor moves into the range
      clearOnEnter: true
    });
    // Get notified when auto-unfolding
    CodeMirror.on(range, "clear", function() {
      console.log("boom");
    });
    

    Line CSS classes

    The setLineClass method has been replaced by addLineClass and removeLineClass, which allow more modular control over the classes attached to a line.

    var marked = cm.addLineClass(10, "background", "highlighted-line");
    setTimeout(function() {
      cm.removeLineClass(marked, "background", "highlighted-line");
    });
    

    Position properties

    All methods that take or return objects that represent screen positions now use {left, top, bottom, right} properties (not always all of them) instead of the {x, y, yBot} used by some methods in v2.x.

    Affected methods are cursorCoords, charCoords, coordsChar, and getScrollInfo.

    Bracket matching no longer in core

    The matchBrackets option is no longer defined in the core editor. Load addon/edit/matchbrackets.js to enable it.

    Mode management

    The CodeMirror.listModes and CodeMirror.listMIMEs functions, used for listing defined modes, are gone. You are now encouraged to simply inspect CodeMirror.modes (mapping mode names to mode constructors) and CodeMirror.mimeModes (mapping MIME strings to mode specs).

    New features

    Some more reasons to upgrade to version 3.

    • Bi-directional text support. CodeMirror will now mostly do the right thing when editing Arabic or Hebrew text.
    • Arbitrary line heights. Using fonts with different heights inside the editor (whether off by one pixel or fifty) is now supported and handled gracefully.
    • In-line widgets. See the demo and the docs.
    • Defining custom options with CodeMirror.defineOption.
    ================================================ FILE: public/assets/lib/vendor/codemirror/doc/upgrade_v4.html ================================================ CodeMirror: Version 4 upgrade guide

    CodeMirror

    • Home
    • Manual
    • Code
    • Upgrade guide
    • Multiple selections
    • The beforeSelectionChange event
    • replaceSelection and collapsing
    • change event data
    • showIfHidden option to line widgets
    • Module loaders
    • Mutating shared data structures
    • Deprecated interfaces dropped

    Upgrading to version 4

    CodeMirror 4's interface is very close version 3, but it does fix a few awkward details in a backwards-incompatible ways. At least skim the text below before upgrading.

    Multiple selections

    The main new feature in version 4 is multiple selections. The single-selection variants of methods are still there, but now typically act only on the primary selection (usually the last one added).

    The exception to this is getSelection, which will now return the content of all selections (separated by newlines, or whatever lineSep parameter you passed it).

    The beforeSelectionChange event

    This event still exists, but the object it is passed has a completely new interface, because such changes now concern multiple selections.

    replaceSelection's collapsing behavior

    By default, replaceSelection would leave the newly inserted text selected. This is only rarely what you want, and also (slightly) more expensive in the new model, so the default was changed to "end", meaning the old behavior must be explicitly specified by passing a second argument of "around".

    change event data

    Rather than forcing client code to follow next pointers from one change object to the next, the library will now simply fire multiple "change" events. Existing code will probably continue to work unmodified.

    showIfHidden option to line widgets

    This option, which conceptually caused line widgets to be visible even if their line was hidden, was never really well-defined, and was buggy from the start. It would be a rather expensive feature, both in code complexity and run-time performance, to implement properly. It has been dropped entirely in 4.0.

    Module loaders

    All modules in the CodeMirror distribution are now wrapped in a shim function to make them compatible with both AMD (requirejs) and CommonJS (as used by node and browserify) module loaders. When neither of these is present, they fall back to simply using the global CodeMirror variable.

    If you have a module loader present in your environment, CodeMirror will attempt to use it, and you might need to change the way you load CodeMirror modules.

    Mutating shared data structures

    Data structures produced by the library should not be mutated unless explicitly allowed, in general. This is slightly more strict in 4.0 than it was in earlier versions, which copied the position objects returned by getCursor for nebulous, historic reasons. In 4.0, mutating these objects will corrupt your editor's selection.

    Deprecated interfaces dropped

    A few properties and methods that have been deprecated for a while are now gone. Most notably, the onKeyEvent and onDragEvent options (use the corresponding events instead).

    Two silly methods, which were mostly there to stay close to the 0.x API, setLine and removeLine are now gone. Use the more flexible replaceRange method instead.

    The long names for folding and completing functions (CodeMirror.braceRangeFinder, CodeMirror.javascriptHint, etc) are also gone (use CodeMirror.fold.brace, CodeMirror.hint.javascript).

    The className property in the return value of getTokenAt, which has been superseded by the type property, is also no longer present.

    ================================================ FILE: public/assets/lib/vendor/codemirror/index.html ================================================ CodeMirror 5
    Note that this is the website for CodeMirror 5. Version 6 is the current version.

    CodeMirror

    • Home
    • Manual
    • Code
    • Version 6
    • Features
    • Community
    • Browser support

    CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and addons that implement more advanced editing functionality.

    A rich programming API and a CSS theming system are available for customizing CodeMirror to fit your application, and extending it with new functionality.

    This is CodeMirror

    Get the current version: 5.65.16.
    You can see the code,
    read the release notes,
    or study the user manual.

    Features

    • Support for over 100 languages out of the box
    • A powerful, composable language mode system
    • Autocompletion (XML)
    • Code folding
    • Configurable keybindings
    • Vim, Emacs, and Sublime Text bindings
    • Search and replace interface
    • Bracket and tag matching
    • Support for split views
    • Linter integration
    • Mixing font sizes and styles
    • Various themes
    • Able to resize to fit content
    • Inline and block widgets
    • Programmable gutters
    • Making ranges of text styled, read-only, or atomic
    • Bi-directional text support
    • Many other methods and addons...

    Community

    CodeMirror is an open-source project shared under an MIT license. It is the editor used in the dev tools for Firefox, Chrome, and Safari, in Light Table, Adobe Brackets, Bitbucket, and many other projects.

    Development and bug tracking happens on github (alternate git repository). Please read these pointers before submitting a bug. Use pull requests to submit patches. All contributions must be released under the same MIT license that CodeMirror uses.

    Discussion around the project is done on a discussion forum. Announcements related to the project, such as new versions, are posted in the forum's "announce" category. If needed, you can contact the maintainer directly. We aim to be an inclusive, welcoming community. To make that explicit, we have a code of conduct that applies to communication around the project.

    Browser support

    The desktop versions of the following browsers, in standards mode (HTML5 <!doctype html> recommended) are supported:

    Firefoxversion 4 and up
    Chromeany version
    Safariversion 5.2 and up
    Internet Explorer/Edgeversion 8 and up
    Operaversion 9 and up

    Support for modern mobile browsers is experimental. Recent versions of the iOS browser and Chrome on Android should work pretty well.

    ================================================ FILE: public/assets/lib/vendor/codemirror/keymap/emacs.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var cmds = CodeMirror.commands; var Pos = CodeMirror.Pos; function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } // Kill 'ring' var killRing = []; function addToRing(str) { killRing.push(str); if (killRing.length > 50) killRing.shift(); } function growRingTop(str) { if (!killRing.length) return addToRing(str); killRing[killRing.length - 1] += str; } function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; } function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } var lastKill = null; // Internal generic kill function, used by several mapped kill "family" functions. function _kill(cm, from, to, ring, text) { if (text == null) text = cm.getRange(from, to); if (ring == "grow" && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen)) growRingTop(text); else if (ring !== false) addToRing(text); cm.replaceRange("", from, to, "+delete"); if (ring == "grow") lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()}; else lastKill = null; } // Boundaries of various units function byChar(cm, pos, dir) { return cm.findPosH(pos, dir, "char", true); } function byWord(cm, pos, dir) { return cm.findPosH(pos, dir, "word", true); } function byLine(cm, pos, dir) { return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn); } function byPage(cm, pos, dir) { return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn); } function byParagraph(cm, pos, dir) { var no = pos.line, line = cm.getLine(no); var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch)); var fst = cm.firstLine(), lst = cm.lastLine(); for (;;) { no += dir; if (no < fst || no > lst) return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null)); line = cm.getLine(no); var hasText = /\S/.test(line); if (hasText) sawText = true; else if (sawText) return Pos(no, 0); } } function bySentence(cm, pos, dir) { var line = pos.line, ch = pos.ch; var text = cm.getLine(pos.line), sawWord = false; for (;;) { var next = text.charAt(ch + (dir < 0 ? -1 : 0)); if (!next) { // End/beginning of line reached if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch); text = cm.getLine(line + dir); if (!/\S/.test(text)) return Pos(line, ch); line += dir; ch = dir < 0 ? text.length : 0; continue; } if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0)); if (!sawWord) sawWord = /\w/.test(next); ch += dir; } } function byExpr(cm, pos, dir) { var wrap; if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, {strict: true})) && wrap.match && (wrap.forward ? 1 : -1) == dir) return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to; for (var first = true;; first = false) { var token = cm.getTokenAt(pos); var after = Pos(pos.line, dir < 0 ? token.start : token.end); if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) { var newPos = cm.findPosH(after, dir, "char"); if (posEq(after, newPos)) return pos; else pos = newPos; } else { return after; } } } // Prefixes (only crudely supported) function getPrefix(cm, precise) { var digits = cm.state.emacsPrefix; if (!digits) return precise ? null : 1; clearPrefix(cm); return digits == "-" ? -1 : Number(digits); } function repeated(cmd) { var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd; return function(cm) { var prefix = getPrefix(cm); f(cm); for (var i = 1; i < prefix; ++i) f(cm); }; } function findEnd(cm, pos, by, dir) { var prefix = getPrefix(cm); if (prefix < 0) { dir = -dir; prefix = -prefix; } for (var i = 0; i < prefix; ++i) { var newPos = by(cm, pos, dir); if (posEq(newPos, pos)) break; pos = newPos; } return pos; } function move(by, dir) { var f = function(cm) { cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir)); }; f.motion = true; return f; } function killTo(cm, by, dir, ring) { var selections = cm.listSelections(), cursor; var i = selections.length; while (i--) { cursor = selections[i].head; _kill(cm, cursor, findEnd(cm, cursor, by, dir), ring); } } function _killRegion(cm, ring) { if (cm.somethingSelected()) { var selections = cm.listSelections(), selection; var i = selections.length; while (i--) { selection = selections[i]; _kill(cm, selection.anchor, selection.head, ring); } return true; } } function addPrefix(cm, digit) { if (cm.state.emacsPrefix) { if (digit != "-") cm.state.emacsPrefix += digit; return; } // Not active yet cm.state.emacsPrefix = digit; cm.on("keyHandled", maybeClearPrefix); cm.on("inputRead", maybeDuplicateInput); } var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true}; function maybeClearPrefix(cm, arg) { if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg)) clearPrefix(cm); } function clearPrefix(cm) { cm.state.emacsPrefix = null; cm.off("keyHandled", maybeClearPrefix); cm.off("inputRead", maybeDuplicateInput); } function maybeDuplicateInput(cm, event) { var dup = getPrefix(cm); if (dup > 1 && event.origin == "+input") { var one = event.text.join("\n"), txt = ""; for (var i = 1; i < dup; ++i) txt += one; cm.replaceSelection(txt); } } function maybeRemovePrefixMap(cm, arg) { if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return; cm.removeKeyMap(prefixMap); cm.state.emacsPrefixMap = false; cm.off("keyHandled", maybeRemovePrefixMap); cm.off("inputRead", maybeRemovePrefixMap); } // Utilities cmds.setMark = function (cm) { cm.setCursor(cm.getCursor()); cm.setExtending(!cm.getExtending()); cm.on("change", function() { cm.setExtending(false); }); } function clearMark(cm) { cm.setExtending(false); cm.setCursor(cm.getCursor()); } function makePrompt(msg) { var fragment = document.createDocumentFragment(); var input = document.createElement("input"); input.setAttribute("type", "text"); input.style.width = "10em"; fragment.appendChild(document.createTextNode(msg + ": ")); fragment.appendChild(input); return fragment; } function getInput(cm, msg, f) { if (cm.openDialog) cm.openDialog(makePrompt(msg), f, {bottom: true}); else f(prompt(msg, "")); } function operateOnWord(cm, op) { var start = cm.getCursor(), end = cm.findPosH(start, 1, "word"); cm.replaceRange(op(cm.getRange(start, end)), start, end); cm.setCursor(end); } function toEnclosingExpr(cm) { var pos = cm.getCursor(), line = pos.line, ch = pos.ch; var stack = []; while (line >= cm.firstLine()) { var text = cm.getLine(line); for (var i = ch == null ? text.length : ch; i > 0;) { var ch = text.charAt(--i); if (ch == ")") stack.push("("); else if (ch == "]") stack.push("["); else if (ch == "}") stack.push("{"); else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch)) return cm.extendSelection(Pos(line, i)); } --line; ch = null; } } // Commands. Names should match emacs function names (albeit in camelCase) // except where emacs function names collide with code mirror core commands. cmds.killRegion = function(cm) { _kill(cm, cm.getCursor("start"), cm.getCursor("end"), true); }; // Maps to emacs kill-line cmds.killLineEmacs = repeated(function(cm) { var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); var text = cm.getRange(start, end); if (!/\S/.test(text)) { text += "\n"; end = Pos(start.line + 1, 0); } _kill(cm, start, end, "grow", text); }); cmds.killRingSave = function(cm) { addToRing(cm.getSelection()); clearMark(cm); }; cmds.yank = function(cm) { var start = cm.getCursor(); cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste"); cm.setSelection(start, cm.getCursor()); }; cmds.yankPop = function(cm) { cm.replaceSelection(popFromRing(), "around", "paste"); }; cmds.forwardChar = move(byChar, 1); cmds.backwardChar = move(byChar, -1) cmds.deleteChar = function(cm) { killTo(cm, byChar, 1, false); }; cmds.deleteForwardChar = function(cm) { _killRegion(cm, false) || killTo(cm, byChar, 1, false); }; cmds.deleteBackwardChar = function(cm) { _killRegion(cm, false) || killTo(cm, byChar, -1, false); }; cmds.forwardWord = move(byWord, 1); cmds.backwardWord = move(byWord, -1); cmds.killWord = function(cm) { killTo(cm, byWord, 1, "grow"); }; cmds.backwardKillWord = function(cm) { killTo(cm, byWord, -1, "grow"); }; cmds.nextLine = move(byLine, 1); cmds.previousLine = move(byLine, -1); cmds.scrollDownCommand = move(byPage, -1); cmds.scrollUpCommand = move(byPage, 1); cmds.backwardParagraph = move(byParagraph, -1); cmds.forwardParagraph = move(byParagraph, 1); cmds.backwardSentence = move(bySentence, -1); cmds.forwardSentence = move(bySentence, 1); cmds.killSentence = function(cm) { killTo(cm, bySentence, 1, "grow"); }; cmds.backwardKillSentence = function(cm) { _kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), "grow"); }; cmds.killSexp = function(cm) { killTo(cm, byExpr, 1, "grow"); }; cmds.backwardKillSexp = function(cm) { killTo(cm, byExpr, -1, "grow"); }; cmds.forwardSexp = move(byExpr, 1); cmds.backwardSexp = move(byExpr, -1); cmds.markSexp = function(cm) { var cursor = cm.getCursor(); cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor); }; cmds.transposeSexps = function(cm) { var leftStart = byExpr(cm, cm.getCursor(), -1); var leftEnd = byExpr(cm, leftStart, 1); var rightEnd = byExpr(cm, leftEnd, 1); var rightStart = byExpr(cm, rightEnd, -1); cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + cm.getRange(leftStart, leftEnd), leftStart, rightEnd); }; cmds.backwardUpList = repeated(toEnclosingExpr); cmds.justOneSpace = function(cm) { var pos = cm.getCursor(), from = pos.ch; var to = pos.ch, text = cm.getLine(pos.line); while (from && /\s/.test(text.charAt(from - 1))) --from; while (to < text.length && /\s/.test(text.charAt(to))) ++to; cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); }; cmds.openLine = repeated(function(cm) { cm.replaceSelection("\n", "start"); }); // maps to emacs 'transpose-chars' cmds.transposeCharsRepeatable = repeated(function(cm) { cm.execCommand("transposeChars"); }); cmds.capitalizeWord = repeated(function(cm) { operateOnWord(cm, function(w) { var letter = w.search(/\w/); if (letter == -1) return w; return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); }); }); cmds.upcaseWord = repeated(function(cm) { operateOnWord(cm, function(w) { return w.toUpperCase(); }); }); cmds.downcaseWord = repeated(function(cm) { operateOnWord(cm, function(w) { return w.toLowerCase(); }); }); // maps to emacs 'undo' cmds.undoRepeatable = repeated("undo"); cmds.keyboardQuit = function(cm) { cm.execCommand("clearSearch"); clearMark(cm); } cmds.newline = repeated(function(cm) { cm.replaceSelection("\n", "end"); }); cmds.gotoLine = function(cm) { var prefix = getPrefix(cm, true); if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); getInput(cm, "Goto line", function(str) { var num; if (str && !isNaN(num = Number(str)) && num == (num|0) && num > 0) cm.setCursor(num - 1); }); }; cmds.indentRigidly = function(cm) { cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); }; cmds.exchangePointAndMark = function(cm) { cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); }; cmds.quotedInsertTab = repeated("insertTab"); cmds.universalArgument = function addPrefixMap(cm) { cm.state.emacsPrefixMap = true; cm.addKeyMap(prefixMap); cm.on("keyHandled", maybeRemovePrefixMap); cm.on("inputRead", maybeRemovePrefixMap); }; CodeMirror.emacs = {kill: _kill, killRegion: _killRegion, repeated: repeated}; // Actual keymap var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({ "Ctrl-W": "killRegion", "Ctrl-K": "killLineEmacs", "Alt-W": "killRingSave", "Ctrl-Y": "yank", "Alt-Y": "yankPop", "Ctrl-Space": "setMark", "Ctrl-Shift-2": "setMark", "Ctrl-F": "forwardChar", "Ctrl-B": "backwardChar", "Right": "forwardChar", "Left": "backwardChar", "Ctrl-D": "deleteChar", "Delete": "deleteForwardChar", "Ctrl-H": "deleteBackwardChar", "Backspace": "deleteBackwardChar", "Alt-F": "forwardWord", "Alt-B": "backwardWord", "Alt-Right": "forwardWord", "Alt-Left": "backwardWord", "Alt-D": "killWord", "Alt-Backspace": "backwardKillWord", "Ctrl-N": "nextLine", "Ctrl-P": "previousLine", "Down": "nextLine", "Up": "previousLine", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "End": "goLineEnd", "Home": "goLineStart", "Alt-V": "scrollDownCommand", "Ctrl-V": "scrollUpCommand", "PageUp": "scrollDownCommand", "PageDown": "scrollUpCommand", "Ctrl-Up": "backwardParagraph", "Ctrl-Down": "forwardParagraph", "Alt-{": "backwardParagraph", "Alt-}": "forwardParagraph", "Alt-A": "backwardSentence", "Alt-E": "forwardSentence", "Alt-K": "killSentence", "Ctrl-X Delete": "backwardKillSentence", "Ctrl-Alt-K": "killSexp", "Ctrl-Alt-Backspace": "backwardKillSexp", "Ctrl-Alt-F": "forwardSexp", "Ctrl-Alt-B": "backwardSexp", "Shift-Ctrl-Alt-2": "markSexp", "Ctrl-Alt-T": "transposeSexps", "Ctrl-Alt-U": "backwardUpList", "Alt-Space": "justOneSpace", "Ctrl-O": "openLine", "Ctrl-T": "transposeCharsRepeatable", "Alt-C": "capitalizeWord", "Alt-U": "upcaseWord", "Alt-L": "downcaseWord", "Alt-;": "toggleComment", "Ctrl-/": "undoRepeatable", "Shift-Ctrl--": "undoRepeatable", "Ctrl-Z": "undoRepeatable", "Cmd-Z": "undoRepeatable", "Ctrl-X U": "undoRepeatable", "Shift-Ctrl-Z": "redo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", "Ctrl-S": "findPersistentNext", "Ctrl-R": "findPersistentPrev", "Ctrl-G": "keyboardQuit", "Shift-Alt-5": "replace", "Alt-/": "autocomplete", "Enter": "newlineAndIndent", "Ctrl-J": "newline", "Tab": "indentAuto", "Alt-G G": "gotoLine", "Ctrl-X Tab": "indentRigidly", "Ctrl-X Ctrl-X": "exchangePointAndMark", "Ctrl-X Ctrl-S": "save", "Ctrl-X Ctrl-W": "save", "Ctrl-X S": "saveAll", "Ctrl-X F": "open", "Ctrl-X K": "close", "Ctrl-X H": "selectAll", "Ctrl-Q Tab": "quotedInsertTab", "Ctrl-U": "universalArgument", "fallthrough": "default" }); var prefixMap = {"Ctrl-G": clearPrefix}; function regPrefix(d) { prefixMap[d] = function(cm) { addPrefix(cm, d); }; keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; prefixPreservingKeys["Ctrl-" + d] = true; } for (var i = 0; i < 10; ++i) regPrefix(String(i)); regPrefix("-"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/keymap/sublime.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // A rough approximation of Sublime Text's keybindings // Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets")); else if (typeof define == "function" && define.amd) // AMD define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var cmds = CodeMirror.commands; var Pos = CodeMirror.Pos; // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that. function findPosSubword(doc, start, dir) { if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1)); var line = doc.getLine(start.line); if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0)); var state = "start", type, startPos = start.ch; for (var pos = startPos, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) { var next = line.charAt(dir < 0 ? pos - 1 : pos); var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o"; if (cat == "w" && next.toUpperCase() == next) cat = "W"; if (state == "start") { if (cat != "o") { state = "in"; type = cat; } else startPos = pos + dir } else if (state == "in") { if (type != cat) { if (type == "w" && cat == "W" && dir < 0) pos--; if (type == "W" && cat == "w" && dir > 0) { // From uppercase to lowercase if (pos == startPos + 1) { type = "w"; continue; } else pos--; } break; } } } return Pos(start.line, pos); } function moveSubword(cm, dir) { cm.extendSelectionsBy(function(range) { if (cm.display.shift || cm.doc.extend || range.empty()) return findPosSubword(cm.doc, range.head, dir); else return dir < 0 ? range.from() : range.to(); }); } cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); }; cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); }; cmds.scrollLineUp = function(cm) { var info = cm.getScrollInfo(); if (!cm.somethingSelected()) { var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local"); if (cm.getCursor().line >= visibleBottomLine) cm.execCommand("goLineUp"); } cm.scrollTo(null, info.top - cm.defaultTextHeight()); }; cmds.scrollLineDown = function(cm) { var info = cm.getScrollInfo(); if (!cm.somethingSelected()) { var visibleTopLine = cm.lineAtHeight(info.top, "local")+1; if (cm.getCursor().line <= visibleTopLine) cm.execCommand("goLineDown"); } cm.scrollTo(null, info.top + cm.defaultTextHeight()); }; cmds.splitSelectionByLine = function(cm) { var ranges = cm.listSelections(), lineRanges = []; for (var i = 0; i < ranges.length; i++) { var from = ranges[i].from(), to = ranges[i].to(); for (var line = from.line; line <= to.line; ++line) if (!(to.line > from.line && line == to.line && to.ch == 0)) lineRanges.push({anchor: line == from.line ? from : Pos(line, 0), head: line == to.line ? to : Pos(line)}); } cm.setSelections(lineRanges, 0); }; cmds.singleSelectionTop = function(cm) { var range = cm.listSelections()[0]; cm.setSelection(range.anchor, range.head, {scroll: false}); }; cmds.selectLine = function(cm) { var ranges = cm.listSelections(), extended = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; extended.push({anchor: Pos(range.from().line, 0), head: Pos(range.to().line + 1, 0)}); } cm.setSelections(extended); }; function insertLine(cm, above) { if (cm.isReadOnly()) return CodeMirror.Pass cm.operation(function() { var len = cm.listSelections().length, newSelection = [], last = -1; for (var i = 0; i < len; i++) { var head = cm.listSelections()[i].head; if (head.line <= last) continue; var at = Pos(head.line + (above ? 0 : 1), 0); cm.replaceRange("\n", at, null, "+insertLine"); cm.indentLine(at.line, null, true); newSelection.push({head: at, anchor: at}); last = head.line + 1; } cm.setSelections(newSelection); }); cm.execCommand("indentAuto"); } cmds.insertLineAfter = function(cm) { return insertLine(cm, false); }; cmds.insertLineBefore = function(cm) { return insertLine(cm, true); }; function wordAt(cm, pos) { var start = pos.ch, end = start, line = cm.getLine(pos.line); while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start; while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end; return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)}; } cmds.selectNextOccurrence = function(cm) { var from = cm.getCursor("from"), to = cm.getCursor("to"); var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel; if (CodeMirror.cmpPos(from, to) == 0) { var word = wordAt(cm, from); if (!word.word) return; cm.setSelection(word.from, word.to); fullWord = true; } else { var text = cm.getRange(from, to); var query = fullWord ? new RegExp("\\b" + text + "\\b") : text; var cur = cm.getSearchCursor(query, to); var found = cur.findNext(); if (!found) { cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0)); found = cur.findNext(); } if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to())) return cm.addSelection(cur.from(), cur.to()); } if (fullWord) cm.state.sublimeFindFullWord = cm.doc.sel; }; cmds.skipAndSelectNextOccurrence = function(cm) { var prevAnchor = cm.getCursor("anchor"), prevHead = cm.getCursor("head"); cmds.selectNextOccurrence(cm); if (CodeMirror.cmpPos(prevAnchor, prevHead) != 0) { cm.doc.setSelections(cm.doc.listSelections() .filter(function (sel) { return sel.anchor != prevAnchor || sel.head != prevHead; })); } } function addCursorToSelection(cm, dir) { var ranges = cm.listSelections(), newRanges = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; var newAnchor = cm.findPosV( range.anchor, dir, "line", range.anchor.goalColumn); var newHead = cm.findPosV( range.head, dir, "line", range.head.goalColumn); newAnchor.goalColumn = range.anchor.goalColumn != null ? range.anchor.goalColumn : cm.cursorCoords(range.anchor, "div").left; newHead.goalColumn = range.head.goalColumn != null ? range.head.goalColumn : cm.cursorCoords(range.head, "div").left; var newRange = {anchor: newAnchor, head: newHead}; newRanges.push(range); newRanges.push(newRange); } cm.setSelections(newRanges); } cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); }; cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); }; function isSelectedRange(ranges, from, to) { for (var i = 0; i < ranges.length; i++) if (CodeMirror.cmpPos(ranges[i].from(), from) == 0 && CodeMirror.cmpPos(ranges[i].to(), to) == 0) return true return false } var mirror = "(){}[]"; function selectBetweenBrackets(cm) { var ranges = cm.listSelections(), newRanges = [] for (var i = 0; i < ranges.length; i++) { var range = ranges[i], pos = range.head, opening = cm.scanForBracket(pos, -1); if (!opening) return false; for (;;) { var closing = cm.scanForBracket(pos, 1); if (!closing) return false; if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) { var startPos = Pos(opening.pos.line, opening.pos.ch + 1); if (CodeMirror.cmpPos(startPos, range.from()) == 0 && CodeMirror.cmpPos(closing.pos, range.to()) == 0) { opening = cm.scanForBracket(opening.pos, -1); if (!opening) return false; } else { newRanges.push({anchor: startPos, head: closing.pos}); break; } } pos = Pos(closing.pos.line, closing.pos.ch + 1); } } cm.setSelections(newRanges); return true; } cmds.selectScope = function(cm) { selectBetweenBrackets(cm) || cm.execCommand("selectAll"); }; cmds.selectBetweenBrackets = function(cm) { if (!selectBetweenBrackets(cm)) return CodeMirror.Pass; }; function puncType(type) { return !type ? null : /\bpunctuation\b/.test(type) ? type : undefined } cmds.goToBracket = function(cm) { cm.extendSelectionsBy(function(range) { var next = cm.scanForBracket(range.head, 1, puncType(cm.getTokenTypeAt(range.head))); if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos; var prev = cm.scanForBracket(range.head, -1, puncType(cm.getTokenTypeAt(Pos(range.head.line, range.head.ch + 1)))); return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head; }); }; cmds.swapLineUp = function(cm) { if (cm.isReadOnly()) return CodeMirror.Pass var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i], from = range.from().line - 1, to = range.to().line; newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch), head: Pos(range.head.line - 1, range.head.ch)}); if (range.to().ch == 0 && !range.empty()) --to; if (from > at) linesToMove.push(from, to); else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; at = to; } cm.operation(function() { for (var i = 0; i < linesToMove.length; i += 2) { var from = linesToMove[i], to = linesToMove[i + 1]; var line = cm.getLine(from); cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); if (to > cm.lastLine()) cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine"); else cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); } cm.setSelections(newSels); cm.scrollIntoView(); }); }; cmds.swapLineDown = function(cm) { if (cm.isReadOnly()) return CodeMirror.Pass var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1; for (var i = ranges.length - 1; i >= 0; i--) { var range = ranges[i], from = range.to().line + 1, to = range.from().line; if (range.to().ch == 0 && !range.empty()) from--; if (from < at) linesToMove.push(from, to); else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; at = to; } cm.operation(function() { for (var i = linesToMove.length - 2; i >= 0; i -= 2) { var from = linesToMove[i], to = linesToMove[i + 1]; var line = cm.getLine(from); if (from == cm.lastLine()) cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine"); else cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); } cm.scrollIntoView(); }); }; cmds.toggleCommentIndented = function(cm) { cm.toggleComment({ indent: true }); } cmds.joinLines = function(cm) { var ranges = cm.listSelections(), joined = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i], from = range.from(); var start = from.line, end = range.to().line; while (i < ranges.length - 1 && ranges[i + 1].from().line == end) end = ranges[++i].to().line; joined.push({start: start, end: end, anchor: !range.empty() && from}); } cm.operation(function() { var offset = 0, ranges = []; for (var i = 0; i < joined.length; i++) { var obj = joined[i]; var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head; for (var line = obj.start; line <= obj.end; line++) { var actual = line - offset; if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1); if (actual < cm.lastLine()) { cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length)); ++offset; } } ranges.push({anchor: anchor || head, head: head}); } cm.setSelections(ranges, 0); }); }; cmds.duplicateLine = function(cm) { cm.operation(function() { var rangeCount = cm.listSelections().length; for (var i = 0; i < rangeCount; i++) { var range = cm.listSelections()[i]; if (range.empty()) cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0)); else cm.replaceRange(cm.getRange(range.from(), range.to()), range.from()); } cm.scrollIntoView(); }); }; function sortLines(cm, caseSensitive, direction) { if (cm.isReadOnly()) return CodeMirror.Pass var ranges = cm.listSelections(), toSort = [], selected; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (range.empty()) continue; var from = range.from().line, to = range.to().line; while (i < ranges.length - 1 && ranges[i + 1].from().line == to) to = ranges[++i].to().line; if (!ranges[i].to().ch) to--; toSort.push(from, to); } if (toSort.length) selected = true; else toSort.push(cm.firstLine(), cm.lastLine()); cm.operation(function() { var ranges = []; for (var i = 0; i < toSort.length; i += 2) { var from = toSort[i], to = toSort[i + 1]; var start = Pos(from, 0), end = Pos(to); var lines = cm.getRange(start, end, false); if (caseSensitive) lines.sort(function(a, b) { return a < b ? -direction : a == b ? 0 : direction; }); else lines.sort(function(a, b) { var au = a.toUpperCase(), bu = b.toUpperCase(); if (au != bu) { a = au; b = bu; } return a < b ? -direction : a == b ? 0 : direction; }); cm.replaceRange(lines, start, end); if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)}); } if (selected) cm.setSelections(ranges, 0); }); } cmds.sortLines = function(cm) { sortLines(cm, true, 1); }; cmds.reverseSortLines = function(cm) { sortLines(cm, true, -1); }; cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false, 1); }; cmds.reverseSortLinesInsensitive = function(cm) { sortLines(cm, false, -1); }; cmds.nextBookmark = function(cm) { var marks = cm.state.sublimeBookmarks; if (marks) while (marks.length) { var current = marks.shift(); var found = current.find(); if (found) { marks.push(current); return cm.setSelection(found.from, found.to); } } }; cmds.prevBookmark = function(cm) { var marks = cm.state.sublimeBookmarks; if (marks) while (marks.length) { marks.unshift(marks.pop()); var found = marks[marks.length - 1].find(); if (!found) marks.pop(); else return cm.setSelection(found.from, found.to); } }; cmds.toggleBookmark = function(cm) { var ranges = cm.listSelections(); var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []); for (var i = 0; i < ranges.length; i++) { var from = ranges[i].from(), to = ranges[i].to(); var found = ranges[i].empty() ? cm.findMarksAt(from) : cm.findMarks(from, to); for (var j = 0; j < found.length; j++) { if (found[j].sublimeBookmark) { found[j].clear(); for (var k = 0; k < marks.length; k++) if (marks[k] == found[j]) marks.splice(k--, 1); break; } } if (j == found.length) marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false})); } }; cmds.clearBookmarks = function(cm) { var marks = cm.state.sublimeBookmarks; if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear(); marks.length = 0; }; cmds.selectBookmarks = function(cm) { var marks = cm.state.sublimeBookmarks, ranges = []; if (marks) for (var i = 0; i < marks.length; i++) { var found = marks[i].find(); if (!found) marks.splice(i--, 0); else ranges.push({anchor: found.from, head: found.to}); } if (ranges.length) cm.setSelections(ranges, 0); }; function modifyWordOrSelection(cm, mod) { cm.operation(function() { var ranges = cm.listSelections(), indices = [], replacements = []; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (range.empty()) { indices.push(i); replacements.push(""); } else replacements.push(mod(cm.getRange(range.from(), range.to()))); } cm.replaceSelections(replacements, "around", "case"); for (var i = indices.length - 1, at; i >= 0; i--) { var range = ranges[indices[i]]; if (at && CodeMirror.cmpPos(range.head, at) > 0) continue; var word = wordAt(cm, range.head); at = word.from; cm.replaceRange(mod(word.word), word.from, word.to); } }); } cmds.smartBackspace = function(cm) { if (cm.somethingSelected()) return CodeMirror.Pass; cm.operation(function() { var cursors = cm.listSelections(); var indentUnit = cm.getOption("indentUnit"); for (var i = cursors.length - 1; i >= 0; i--) { var cursor = cursors[i].head; var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor); var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize")); // Delete by one character by default var deletePos = cm.findPosH(cursor, -1, "char", false); if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) { var prevIndent = new Pos(cursor.line, CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit)); // Smart delete only if we found a valid prevIndent location if (prevIndent.ch != cursor.ch) deletePos = prevIndent; } cm.replaceRange("", deletePos, cursor, "+delete"); } }); }; cmds.delLineRight = function(cm) { cm.operation(function() { var ranges = cm.listSelections(); for (var i = ranges.length - 1; i >= 0; i--) cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete"); cm.scrollIntoView(); }); }; cmds.upcaseAtCursor = function(cm) { modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); }); }; cmds.downcaseAtCursor = function(cm) { modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); }); }; cmds.setSublimeMark = function(cm) { if (cm.state.sublimeMark) cm.state.sublimeMark.clear(); cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); }; cmds.selectToSublimeMark = function(cm) { var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); if (found) cm.setSelection(cm.getCursor(), found); }; cmds.deleteToSublimeMark = function(cm) { var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); if (found) { var from = cm.getCursor(), to = found; if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; } cm.state.sublimeKilled = cm.getRange(from, to); cm.replaceRange("", from, to); } }; cmds.swapWithSublimeMark = function(cm) { var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); if (found) { cm.state.sublimeMark.clear(); cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); cm.setCursor(found); } }; cmds.sublimeYank = function(cm) { if (cm.state.sublimeKilled != null) cm.replaceSelection(cm.state.sublimeKilled, null, "paste"); }; cmds.showInCenter = function(cm) { var pos = cm.cursorCoords(null, "local"); cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2); }; function getTarget(cm) { var from = cm.getCursor("from"), to = cm.getCursor("to"); if (CodeMirror.cmpPos(from, to) == 0) { var word = wordAt(cm, from); if (!word.word) return; from = word.from; to = word.to; } return {from: from, to: to, query: cm.getRange(from, to), word: word}; } function findAndGoTo(cm, forward) { var target = getTarget(cm); if (!target) return; var query = target.query; var cur = cm.getSearchCursor(query, forward ? target.to : target.from); if (forward ? cur.findNext() : cur.findPrevious()) { cm.setSelection(cur.from(), cur.to()); } else { cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0) : cm.clipPos(Pos(cm.lastLine()))); if (forward ? cur.findNext() : cur.findPrevious()) cm.setSelection(cur.from(), cur.to()); else if (target.word) cm.setSelection(target.from, target.to); } }; cmds.findUnder = function(cm) { findAndGoTo(cm, true); }; cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); }; cmds.findAllUnder = function(cm) { var target = getTarget(cm); if (!target) return; var cur = cm.getSearchCursor(target.query); var matches = []; var primaryIndex = -1; while (cur.findNext()) { matches.push({anchor: cur.from(), head: cur.to()}); if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch) primaryIndex++; } cm.setSelections(matches, primaryIndex); }; var keyMap = CodeMirror.keyMap; keyMap.macSublime = { "Cmd-Left": "goLineStartSmart", "Shift-Tab": "indentLess", "Shift-Ctrl-K": "deleteLine", "Alt-Q": "wrapLines", "Ctrl-Left": "goSubwordLeft", "Ctrl-Right": "goSubwordRight", "Ctrl-Alt-Up": "scrollLineUp", "Ctrl-Alt-Down": "scrollLineDown", "Cmd-L": "selectLine", "Shift-Cmd-L": "splitSelectionByLine", "Esc": "singleSelectionTop", "Cmd-Enter": "insertLineAfter", "Shift-Cmd-Enter": "insertLineBefore", "Cmd-D": "selectNextOccurrence", "Shift-Cmd-Space": "selectScope", "Shift-Cmd-M": "selectBetweenBrackets", "Cmd-M": "goToBracket", "Cmd-Ctrl-Up": "swapLineUp", "Cmd-Ctrl-Down": "swapLineDown", "Cmd-/": "toggleCommentIndented", "Cmd-J": "joinLines", "Shift-Cmd-D": "duplicateLine", "F5": "sortLines", "Shift-F5": "reverseSortLines", "Cmd-F5": "sortLinesInsensitive", "Shift-Cmd-F5": "reverseSortLinesInsensitive", "F2": "nextBookmark", "Shift-F2": "prevBookmark", "Cmd-F2": "toggleBookmark", "Shift-Cmd-F2": "clearBookmarks", "Alt-F2": "selectBookmarks", "Backspace": "smartBackspace", "Cmd-K Cmd-D": "skipAndSelectNextOccurrence", "Cmd-K Cmd-K": "delLineRight", "Cmd-K Cmd-U": "upcaseAtCursor", "Cmd-K Cmd-L": "downcaseAtCursor", "Cmd-K Cmd-Space": "setSublimeMark", "Cmd-K Cmd-A": "selectToSublimeMark", "Cmd-K Cmd-W": "deleteToSublimeMark", "Cmd-K Cmd-X": "swapWithSublimeMark", "Cmd-K Cmd-Y": "sublimeYank", "Cmd-K Cmd-C": "showInCenter", "Cmd-K Cmd-G": "clearBookmarks", "Cmd-K Cmd-Backspace": "delLineLeft", "Cmd-K Cmd-1": "foldAll", "Cmd-K Cmd-0": "unfoldAll", "Cmd-K Cmd-J": "unfoldAll", "Ctrl-Shift-Up": "addCursorToPrevLine", "Ctrl-Shift-Down": "addCursorToNextLine", "Cmd-F3": "findUnder", "Shift-Cmd-F3": "findUnderPrevious", "Alt-F3": "findAllUnder", "Shift-Cmd-[": "fold", "Shift-Cmd-]": "unfold", "Cmd-I": "findIncremental", "Shift-Cmd-I": "findIncrementalReverse", "Cmd-H": "replace", "F3": "findNext", "Shift-F3": "findPrev", "fallthrough": "macDefault" }; CodeMirror.normalizeKeyMap(keyMap.macSublime); keyMap.pcSublime = { "Shift-Tab": "indentLess", "Shift-Ctrl-K": "deleteLine", "Alt-Q": "wrapLines", "Ctrl-T": "transposeChars", "Alt-Left": "goSubwordLeft", "Alt-Right": "goSubwordRight", "Ctrl-Up": "scrollLineUp", "Ctrl-Down": "scrollLineDown", "Ctrl-L": "selectLine", "Shift-Ctrl-L": "splitSelectionByLine", "Esc": "singleSelectionTop", "Ctrl-Enter": "insertLineAfter", "Shift-Ctrl-Enter": "insertLineBefore", "Ctrl-D": "selectNextOccurrence", "Shift-Ctrl-Space": "selectScope", "Shift-Ctrl-M": "selectBetweenBrackets", "Ctrl-M": "goToBracket", "Shift-Ctrl-Up": "swapLineUp", "Shift-Ctrl-Down": "swapLineDown", "Ctrl-/": "toggleCommentIndented", "Ctrl-J": "joinLines", "Shift-Ctrl-D": "duplicateLine", "F9": "sortLines", "Shift-F9": "reverseSortLines", "Ctrl-F9": "sortLinesInsensitive", "Shift-Ctrl-F9": "reverseSortLinesInsensitive", "F2": "nextBookmark", "Shift-F2": "prevBookmark", "Ctrl-F2": "toggleBookmark", "Shift-Ctrl-F2": "clearBookmarks", "Alt-F2": "selectBookmarks", "Backspace": "smartBackspace", "Ctrl-K Ctrl-D": "skipAndSelectNextOccurrence", "Ctrl-K Ctrl-K": "delLineRight", "Ctrl-K Ctrl-U": "upcaseAtCursor", "Ctrl-K Ctrl-L": "downcaseAtCursor", "Ctrl-K Ctrl-Space": "setSublimeMark", "Ctrl-K Ctrl-A": "selectToSublimeMark", "Ctrl-K Ctrl-W": "deleteToSublimeMark", "Ctrl-K Ctrl-X": "swapWithSublimeMark", "Ctrl-K Ctrl-Y": "sublimeYank", "Ctrl-K Ctrl-C": "showInCenter", "Ctrl-K Ctrl-G": "clearBookmarks", "Ctrl-K Ctrl-Backspace": "delLineLeft", "Ctrl-K Ctrl-1": "foldAll", "Ctrl-K Ctrl-0": "unfoldAll", "Ctrl-K Ctrl-J": "unfoldAll", "Ctrl-Alt-Up": "addCursorToPrevLine", "Ctrl-Alt-Down": "addCursorToNextLine", "Ctrl-F3": "findUnder", "Shift-Ctrl-F3": "findUnderPrevious", "Alt-F3": "findAllUnder", "Shift-Ctrl-[": "fold", "Shift-Ctrl-]": "unfold", "Ctrl-I": "findIncremental", "Shift-Ctrl-I": "findIncrementalReverse", "Ctrl-H": "replace", "F3": "findNext", "Shift-F3": "findPrev", "fallthrough": "pcDefault" }; CodeMirror.normalizeKeyMap(keyMap.pcSublime); var mac = keyMap.default == keyMap.macDefault; keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime; }); ================================================ FILE: public/assets/lib/vendor/codemirror/keymap/vim.js ================================================ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js")); else if (typeof define == "function" && define.amd) // AMD define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { 'use strict'; // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Supported keybindings: * Too many to list. Refer to defaultKeymap below. * * Supported Ex commands: * Refer to defaultExCommandMap below. * * Registers: unnamed, -, ., :, /, _, a-z, A-Z, 0-9 * (Does not respect the special case for number registers when delete * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) * TODO: Implement the remaining registers. * * Marks: a-z, A-Z, and 0-9 * TODO: Implement the remaining special marks. They have more complex * behavior. * * Events: * 'vim-mode-change' - raised on the editor anytime the current mode changes, * Event object: {mode: "visual", subMode: "linewise"} * * Code structure: * 1. Default keymap * 2. Variable declarations and short basic helpers * 3. Instance (External API) implementation * 4. Internal state tracking objects (input state, counter) implementation * and instantiation * 5. Key handler (the main command dispatcher) implementation * 6. Motion, operator, and action implementations * 7. Helper functions for the key handler, motions, operators, and actions * 8. Set up Vim to work as a keymap for CodeMirror. * 9. Ex command implementations. */ function initVim$1(CodeMirror) { var Pos = CodeMirror.Pos; function transformCursor(cm, range) { var vim = cm.state.vim; if (!vim || vim.insertMode) return range.head; var head = vim.sel.head; if (!head) return range.head; if (vim.visualBlock) { if (range.head.line != head.line) { return; } } if (range.from() == range.anchor && !range.empty()) { if (range.head.line == head.line && range.head.ch != head.ch) return new Pos(range.head.line, range.head.ch - 1); } return range.head; } var defaultKeymap = [ // Key to key mapping. This goes first to make it possible to override // existing mappings. { keys: '', type: 'keyToKey', toKeys: 'h' }, { keys: '', type: 'keyToKey', toKeys: 'l' }, { keys: '', type: 'keyToKey', toKeys: 'k' }, { keys: '', type: 'keyToKey', toKeys: 'j' }, { keys: 'g', type: 'keyToKey', toKeys: 'gk' }, { keys: 'g', type: 'keyToKey', toKeys: 'gj' }, { keys: '', type: 'keyToKey', toKeys: 'l' }, { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'}, { keys: '', type: 'keyToKey', toKeys: 'x', context: 'normal'}, { keys: '', type: 'keyToKey', toKeys: 'W' }, { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' }, { keys: '', type: 'keyToKey', toKeys: 'w' }, { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' }, { keys: '', type: 'keyToKey', toKeys: 'j' }, { keys: '', type: 'keyToKey', toKeys: 'k' }, { keys: '', type: 'keyToKey', toKeys: '' }, { keys: '', type: 'keyToKey', toKeys: '' }, { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, { keys: '', type: 'keyToKey', toKeys: '' }, // ipad keyboard sends C-Esc instead of C-[ { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' }, { keys: 's', type: 'keyToKey', toKeys: 'c', context: 'visual'}, { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' }, { keys: 'S', type: 'keyToKey', toKeys: 'VdO', context: 'visual' }, { keys: '', type: 'keyToKey', toKeys: '0' }, { keys: '', type: 'keyToKey', toKeys: '$' }, { keys: '', type: 'keyToKey', toKeys: '' }, { keys: '', type: 'keyToKey', toKeys: '' }, { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' }, { keys: '', type: 'keyToKey', toKeys: 'i', context: 'normal'}, { keys: '', type: 'action', action: 'toggleOverwrite', context: 'insert' }, // Motions { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }}, { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }}, { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }}, { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }}, { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }}, { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }}, { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }}, { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }}, { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }}, { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }}, { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }}, { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }}, { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }}, { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }}, { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }}, { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }}, { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }}, { keys: '(', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: false }}, { keys: ')', type: 'motion', motion: 'moveBySentence', motionArgs: { forward: true }}, { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }}, { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }}, { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, {keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine"}, {keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine"}, {keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine"}, { keys: '0', type: 'motion', motion: 'moveToStartOfLine' }, { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }}, { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }}, { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }}, { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }}, { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }}, { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }}, { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }}, { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }}, { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}}, { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}}, { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, // the next two aren't motions but must come before more general motion declarations { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}}, { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}}, { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}}, { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}}, { keys: '|', type: 'motion', motion: 'moveToColumn'}, { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'}, { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, // Operators { keys: 'd', type: 'operator', operator: 'delete' }, { keys: 'y', type: 'operator', operator: 'yank' }, { keys: 'c', type: 'operator', operator: 'change' }, { keys: '=', type: 'operator', operator: 'indentAuto' }, { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, { keys: 'g~', type: 'operator', operator: 'changeCase' }, { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true }, { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true }, { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }}, { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }}, { keys: 'gn', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: true }}, { keys: 'gN', type: 'motion', motion: 'findAndSelectNextInclusive', motionArgs: { forward: false }}, // Operator-Motion dual commands { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }}, { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'}, { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'expandToLine', motionArgs: { linewise: true }, context: 'normal'}, { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'}, { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'}, { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'}, { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'}, { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveToStartOfLine', context: 'insert' }, { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' }, //ignore C-w in normal mode { keys: '', type: 'idle', context: 'normal' }, // Actions { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }}, { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }}, { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }}, { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' }, { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' }, { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' }, { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' }, { keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' }, { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' }, { keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' }, { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' }, { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' }, { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' }, { keys: 'v', type: 'action', action: 'toggleVisualMode' }, { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, { keys: 'gv', type: 'action', action: 'reselectLastSelection' }, { keys: 'J', type: 'action', action: 'joinLines', isEdit: true }, { keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true }, { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }}, { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }}, { keys: 'r', type: 'action', action: 'replace', isEdit: true }, { keys: '@', type: 'action', action: 'replayMacro' }, { keys: 'q', type: 'action', action: 'enterMacroRecordMode' }, // Handle Replace-mode as a special case of insert mode. { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'}, { keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true}, { keys: 'u', type: 'action', action: 'undo', context: 'normal' }, { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true }, { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true }, { keys: '', type: 'action', action: 'redo' }, { keys: 'm', type: 'action', action: 'setMark' }, { keys: '"', type: 'action', action: 'setRegister' }, { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }}, { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: '.', type: 'action', action: 'repeatLastEdit' }, { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}}, { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}}, { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: true }, context: 'insert' }, { keys: '', type: 'action', action: 'indent', actionArgs: { indentRight: false }, context: 'insert' }, // Text object motions { keys: 'a', type: 'motion', motion: 'textObjectManipulation' }, { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }}, // Search { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, // Ex command { keys: ':', type: 'ex' } ]; var defaultKeymapLength = defaultKeymap.length; /** * Ex commands * Care must be taken when adding to the default Ex command map. For any * pair of commands that have a shared prefix, at least one of their * shortNames must not match the prefix of the other command. */ var defaultExCommandMap = [ { name: 'colorscheme', shortName: 'colo' }, { name: 'map' }, { name: 'imap', shortName: 'im' }, { name: 'nmap', shortName: 'nm' }, { name: 'vmap', shortName: 'vm' }, { name: 'unmap' }, { name: 'write', shortName: 'w' }, { name: 'undo', shortName: 'u' }, { name: 'redo', shortName: 'red' }, { name: 'set', shortName: 'se' }, { name: 'setlocal', shortName: 'setl' }, { name: 'setglobal', shortName: 'setg' }, { name: 'sort', shortName: 'sor' }, { name: 'substitute', shortName: 's', possiblyAsync: true }, { name: 'nohlsearch', shortName: 'noh' }, { name: 'yank', shortName: 'y' }, { name: 'delmarks', shortName: 'delm' }, { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, { name: 'vglobal', shortName: 'v' }, { name: 'global', shortName: 'g' } ]; function enterVimMode(cm) { cm.setOption('disableInput', true); cm.setOption('showCursorWhenSelecting', false); CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); cm.on('cursorActivity', onCursorActivity); maybeInitVimState(cm); CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); } function leaveVimMode(cm) { cm.setOption('disableInput', false); cm.off('cursorActivity', onCursorActivity); CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); cm.state.vim = null; if (highlightTimeout) clearTimeout(highlightTimeout); } function detachVimMap(cm, next) { if (this == CodeMirror.keyMap.vim) { cm.options.$customCursor = null; CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor"); } if (!next || next.attach != attachVimMap) leaveVimMode(cm); } function attachVimMap(cm, prev) { if (this == CodeMirror.keyMap.vim) { if (cm.curOp) cm.curOp.selectionChanged = true; cm.options.$customCursor = transformCursor; CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor"); } if (!prev || prev.attach != attachVimMap) enterVimMode(cm); } // Deprecated, simply setting the keymap works again. CodeMirror.defineOption('vimMode', false, function(cm, val, prev) { if (val && cm.getOption("keyMap") != "vim") cm.setOption("keyMap", "vim"); else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap"))) cm.setOption("keyMap", "default"); }); function cmKey(key, cm) { if (!cm) { return undefined; } if (this[key]) { return this[key]; } var vimKey = cmKeyToVimKey(key); if (!vimKey) { return false; } var cmd = vimApi.findKey(cm, vimKey); if (typeof cmd == 'function') { CodeMirror.signal(cm, 'vim-keypress', vimKey); } return cmd; } var modifiers = {Shift:'S',Ctrl:'C',Alt:'A',Cmd:'D',Mod:'A',CapsLock:''}; var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del',Insert:'Ins'}; function cmKeyToVimKey(key) { if (key.charAt(0) == '\'') { // Keypress character binding of format "'a'" return key.charAt(1); } var pieces = key.split(/-(?!$)/); var lastPiece = pieces[pieces.length - 1]; if (pieces.length == 1 && pieces[0].length == 1) { // No-modifier bindings use literal character bindings above. Skip. return false; } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) { // Ignore Shift+char bindings as they should be handled by literal character. return false; } var hasCharacter = false; for (var i = 0; i < pieces.length; i++) { var piece = pieces[i]; if (piece in modifiers) { pieces[i] = modifiers[piece]; } else { hasCharacter = true; } if (piece in specialKeys) { pieces[i] = specialKeys[piece]; } } if (!hasCharacter) { // Vim does not support modifier only keys. return false; } // TODO: Current bindings expect the character to be lower case, but // it looks like vim key notation uses upper case. if (isUpperCase(lastPiece)) { pieces[pieces.length - 1] = lastPiece.toLowerCase(); } return '<' + pieces.join('-') + '>'; } function getOnPasteFn(cm) { var vim = cm.state.vim; if (!vim.onPasteFn) { vim.onPasteFn = function() { if (!vim.insertMode) { cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); actions.enterInsertMode(cm, {}, vim); } }; } return vim.onPasteFn; } var numberRegex = /[\d]/; var wordCharTest = [CodeMirror.isWordChar, function(ch) { return ch && !CodeMirror.isWordChar(ch) && !/\s/.test(ch); }], bigWordCharTest = [function(ch) { return /\S/.test(ch); }]; function makeKeyRange(start, size) { var keys = []; for (var i = start; i < start + size; i++) { keys.push(String.fromCharCode(i)); } return keys; } var upperCaseAlphabet = makeKeyRange(65, 26); var lowerCaseAlphabet = makeKeyRange(97, 26); var numbers = makeKeyRange(48, 10); var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']); var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '_', '/']); var upperCaseChars; try { upperCaseChars = new RegExp("^[\\p{Lu}]$", "u"); } catch (_) { upperCaseChars = /^[A-Z]$/; } function isLine(cm, line) { return line >= cm.firstLine() && line <= cm.lastLine(); } function isLowerCase(k) { return (/^[a-z]$/).test(k); } function isMatchableSymbol(k) { return '()[]{}'.indexOf(k) != -1; } function isNumber(k) { return numberRegex.test(k); } function isUpperCase(k) { return upperCaseChars.test(k); } function isWhiteSpaceString(k) { return (/^\s*$/).test(k); } function isEndOfSentenceSymbol(k) { return '.?!'.indexOf(k) != -1; } function inArray(val, arr) { for (var i = 0; i < arr.length; i++) { if (arr[i] == val) { return true; } } return false; } var options = {}; function defineOption(name, defaultValue, type, aliases, callback) { if (defaultValue === undefined && !callback) { throw Error('defaultValue is required unless callback is provided'); } if (!type) { type = 'string'; } options[name] = { type: type, defaultValue: defaultValue, callback: callback }; if (aliases) { for (var i = 0; i < aliases.length; i++) { options[aliases[i]] = options[name]; } } if (defaultValue) { setOption(name, defaultValue); } } function setOption(name, value, cm, cfg) { var option = options[name]; cfg = cfg || {}; var scope = cfg.scope; if (!option) { return new Error('Unknown option: ' + name); } if (option.type == 'boolean') { if (value && value !== true) { return new Error('Invalid argument: ' + name + '=' + value); } else if (value !== false) { // Boolean options are set to true if value is not defined. value = true; } } if (option.callback) { if (scope !== 'local') { option.callback(value, undefined); } if (scope !== 'global' && cm) { option.callback(value, cm); } } else { if (scope !== 'local') { option.value = option.type == 'boolean' ? !!value : value; } if (scope !== 'global' && cm) { cm.state.vim.options[name] = {value: value}; } } } function getOption(name, cm, cfg) { var option = options[name]; cfg = cfg || {}; var scope = cfg.scope; if (!option) { return new Error('Unknown option: ' + name); } if (option.callback) { var local = cm && option.callback(undefined, cm); if (scope !== 'global' && local !== undefined) { return local; } if (scope !== 'local') { return option.callback(); } return; } else { var local = (scope !== 'global') && (cm && cm.state.vim.options[name]); return (local || (scope !== 'local') && option || {}).value; } } defineOption('filetype', undefined, 'string', ['ft'], function(name, cm) { // Option is local. Do nothing for global. if (cm === undefined) { return; } // The 'filetype' option proxies to the CodeMirror 'mode' option. if (name === undefined) { var mode = cm.getOption('mode'); return mode == 'null' ? '' : mode; } else { var mode = name == '' ? 'null' : name; cm.setOption('mode', mode); } }); var createCircularJumpList = function() { var size = 100; var pointer = -1; var head = 0; var tail = 0; var buffer = new Array(size); function add(cm, oldCur, newCur) { var current = pointer % size; var curMark = buffer[current]; function useNextSlot(cursor) { var next = ++pointer % size; var trashMark = buffer[next]; if (trashMark) { trashMark.clear(); } buffer[next] = cm.setBookmark(cursor); } if (curMark) { var markPos = curMark.find(); // avoid recording redundant cursor position if (markPos && !cursorEqual(markPos, oldCur)) { useNextSlot(oldCur); } } else { useNextSlot(oldCur); } useNextSlot(newCur); head = pointer; tail = pointer - size + 1; if (tail < 0) { tail = 0; } } function move(cm, offset) { pointer += offset; if (pointer > head) { pointer = head; } else if (pointer < tail) { pointer = tail; } var mark = buffer[(size + pointer) % size]; // skip marks that are temporarily removed from text buffer if (mark && !mark.find()) { var inc = offset > 0 ? 1 : -1; var newCur; var oldCur = cm.getCursor(); do { pointer += inc; mark = buffer[(size + pointer) % size]; // skip marks that are the same as current position if (mark && (newCur = mark.find()) && !cursorEqual(oldCur, newCur)) { break; } } while (pointer < head && pointer > tail); } return mark; } function find(cm, offset) { var oldPointer = pointer; var mark = move(cm, offset); pointer = oldPointer; return mark && mark.find(); } return { cachedCursor: undefined, //used for # and * jumps add: add, find: find, move: move }; }; // Returns an object to track the changes associated insert mode. It // clones the object that is passed in, or creates an empty object one if // none is provided. var createInsertModeChanges = function(c) { if (c) { // Copy construction return { changes: c.changes, expectCursorActivityForChange: c.expectCursorActivityForChange }; } return { // Change list changes: [], // Set to true on change, false on cursorActivity. expectCursorActivityForChange: false }; }; function MacroModeState() { this.latestRegister = undefined; this.isPlaying = false; this.isRecording = false; this.replaySearchQueries = []; this.onRecordingDone = undefined; this.lastInsertModeChanges = createInsertModeChanges(); } MacroModeState.prototype = { exitMacroRecordMode: function() { var macroModeState = vimGlobalState.macroModeState; if (macroModeState.onRecordingDone) { macroModeState.onRecordingDone(); // close dialog } macroModeState.onRecordingDone = undefined; macroModeState.isRecording = false; }, enterMacroRecordMode: function(cm, registerName) { var register = vimGlobalState.registerController.getRegister(registerName); if (register) { register.clear(); this.latestRegister = registerName; if (cm.openDialog) { var template = dom('span', {class: 'cm-vim-message'}, 'recording @' + registerName); this.onRecordingDone = cm.openDialog(template, null, {bottom:true}); } this.isRecording = true; } } }; function maybeInitVimState(cm) { if (!cm.state.vim) { // Store instance state in the CodeMirror object. cm.state.vim = { inputState: new InputState(), // Vim's input state that triggered the last edit, used to repeat // motions and operators with '.'. lastEditInputState: undefined, // Vim's action command before the last edit, used to repeat actions // with '.' and insert mode repeat. lastEditActionCommand: undefined, // When using jk for navigation, if you move from a longer line to a // shorter line, the cursor may clip to the end of the shorter line. // If j is pressed again and cursor goes to the next line, the // cursor should go back to its horizontal position on the longer // line if it can. This is to keep track of the horizontal position. lastHPos: -1, // Doing the same with screen-position for gj/gk lastHSPos: -1, // The last motion command run. Cleared if a non-motion command gets // executed in between. lastMotion: null, marks: {}, insertMode: false, // Repeat count for changes made in insert mode, triggered by key // sequences like 3,i. Only exists when insertMode is true. insertModeRepeat: undefined, visualMode: false, // If we are in visual line mode. No effect if visualMode is false. visualLine: false, visualBlock: false, lastSelection: null, lastPastedText: null, sel: {}, // Buffer-local/window-local values of vim options. options: {} }; } return cm.state.vim; } var vimGlobalState; function resetVimGlobalState() { vimGlobalState = { // The current search query. searchQuery: null, // Whether we are searching backwards. searchIsReversed: false, // Replace part of the last substituted pattern lastSubstituteReplacePart: undefined, jumpList: createCircularJumpList(), macroModeState: new MacroModeState, // Recording latest f, t, F or T motion command. lastCharacterSearch: {increment:0, forward:true, selectedCharacter:''}, registerController: new RegisterController({}), // search history buffer searchHistoryController: new HistoryController(), // ex Command history buffer exCommandHistoryController : new HistoryController() }; for (var optionName in options) { var option = options[optionName]; option.value = option.defaultValue; } } var lastInsertModeKeyTimer; var vimApi = { enterVimMode: enterVimMode, buildKeyMap: function() { // TODO: Convert keymap into dictionary format for fast lookup. }, // Testing hook, though it might be useful to expose the register // controller anyway. getRegisterController: function() { return vimGlobalState.registerController; }, // Testing hook. resetVimGlobalState_: resetVimGlobalState, // Testing hook. getVimGlobalState_: function() { return vimGlobalState; }, // Testing hook. maybeInitVimState_: maybeInitVimState, suppressErrorLogging: false, InsertModeKey: InsertModeKey, map: function(lhs, rhs, ctx) { // Add user defined key bindings. exCommandDispatcher.map(lhs, rhs, ctx); }, unmap: function(lhs, ctx) { return exCommandDispatcher.unmap(lhs, ctx); }, // Non-recursive map function. // NOTE: This will not create mappings to key maps that aren't present // in the default key map. See TODO at bottom of function. noremap: function(lhs, rhs, ctx) { function toCtxArray(ctx) { return ctx ? [ctx] : ['normal', 'insert', 'visual']; } var ctxsToMap = toCtxArray(ctx); // Look through all actual defaults to find a map candidate. var actualLength = defaultKeymap.length, origLength = defaultKeymapLength; for (var i = actualLength - origLength; i < actualLength && ctxsToMap.length; i++) { var mapping = defaultKeymap[i]; // Omit mappings that operate in the wrong context(s) and those of invalid type. if (mapping.keys == rhs && (!ctx || !mapping.context || mapping.context === ctx) && mapping.type.substr(0, 2) !== 'ex' && mapping.type.substr(0, 3) !== 'key') { // Make a shallow copy of the original keymap entry. var newMapping = {}; for (var key in mapping) { newMapping[key] = mapping[key]; } // Modify it point to the new mapping with the proper context. newMapping.keys = lhs; if (ctx && !newMapping.context) { newMapping.context = ctx; } // Add it to the keymap with a higher priority than the original. this._mapCommand(newMapping); // Record the mapped contexts as complete. var mappedCtxs = toCtxArray(mapping.context); ctxsToMap = ctxsToMap.filter(function(el) { return mappedCtxs.indexOf(el) === -1; }); } } // TODO: Create non-recursive keyToKey mappings for the unmapped contexts once those exist. }, // Remove all user-defined mappings for the provided context. mapclear: function(ctx) { // Partition the existing keymap into user-defined and true defaults. var actualLength = defaultKeymap.length, origLength = defaultKeymapLength; var userKeymap = defaultKeymap.slice(0, actualLength - origLength); defaultKeymap = defaultKeymap.slice(actualLength - origLength); if (ctx) { // If a specific context is being cleared, we need to keep mappings // from all other contexts. for (var i = userKeymap.length - 1; i >= 0; i--) { var mapping = userKeymap[i]; if (ctx !== mapping.context) { if (mapping.context) { this._mapCommand(mapping); } else { // `mapping` applies to all contexts so create keymap copies // for each context except the one being cleared. var contexts = ['normal', 'insert', 'visual']; for (var j in contexts) { if (contexts[j] !== ctx) { var newMapping = {}; for (var key in mapping) { newMapping[key] = mapping[key]; } newMapping.context = contexts[j]; this._mapCommand(newMapping); } } } } } } }, // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace // them, or somehow make them work with the existing CodeMirror setOption/getOption API. setOption: setOption, getOption: getOption, defineOption: defineOption, defineEx: function(name, prefix, func){ if (!prefix) { prefix = name; } else if (name.indexOf(prefix) !== 0) { throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); } exCommands[name]=func; exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; }, handleKey: function (cm, key, origin) { var command = this.findKey(cm, key, origin); if (typeof command === 'function') { return command(); } }, multiSelectHandleKey: multiSelectHandleKey, /** * This is the outermost function called by CodeMirror, after keys have * been mapped to their Vim equivalents. * * Finds a command based on the key (and cached keys if there is a * multi-key sequence). Returns `undefined` if no key is matched, a noop * function if a partial match is found (multi-key), and a function to * execute the bound command if a a key is matched. The function always * returns true. */ findKey: function(cm, key, origin) { var vim = maybeInitVimState(cm); function handleMacroRecording() { var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isRecording) { if (key == 'q') { macroModeState.exitMacroRecordMode(); clearInputState(cm); return true; } if (origin != 'mapping') { logKey(macroModeState, key); } } } function handleEsc() { if (key == '') { if (vim.visualMode) { // Get back to normal mode. exitVisualMode(cm); } else if (vim.insertMode) { // Get back to normal mode. exitInsertMode(cm); } else { // We're already in normal mode. Let '' be handled normally. return; } clearInputState(cm); return true; } } function doKeyToKey(keys) { // TODO: prevent infinite recursion. var match; while (keys) { // Pull off one command key, which is either a single character // or a special sequence wrapped in '<' and '>', e.g. ''. match = (/<\w+-.+?>|<\w+>|./).exec(keys); key = match[0]; keys = keys.substring(match.index + key.length); vimApi.handleKey(cm, key, 'mapping'); } } function handleKeyInsertMode() { if (handleEsc()) { return true; } var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; var keysAreChars = key.length == 1; var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); // Need to check all key substrings in insert mode. while (keys.length > 1 && match.type != 'full') { var keys = vim.inputState.keyBuffer = keys.slice(1); var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); if (thisMatch.type != 'none') { match = thisMatch; } } if (match.type == 'none') { clearInputState(cm); return false; } else if (match.type == 'partial') { if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } lastInsertModeKeyTimer = window.setTimeout( function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } }, getOption('insertModeEscKeysTimeout')); return !keysAreChars; } if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } if (keysAreChars) { var selections = cm.listSelections(); for (var i = 0; i < selections.length; i++) { var here = selections[i].head; cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input'); } vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop(); } clearInputState(cm); return match.command; } function handleKeyNonInsertMode() { if (handleMacroRecording() || handleEsc()) { return true; } var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; if (/^[1-9]\d*$/.test(keys)) { return true; } var keysMatcher = /^(\d*)(.*)$/.exec(keys); if (!keysMatcher) { clearInputState(cm); return false; } var context = vim.visualMode ? 'visual' : 'normal'; var mainKey = keysMatcher[2] || keysMatcher[1]; if (vim.inputState.operatorShortcut && vim.inputState.operatorShortcut.slice(-1) == mainKey) { // multikey operators act linewise by repeating only the last character mainKey = vim.inputState.operatorShortcut; } var match = commandDispatcher.matchCommand(mainKey, defaultKeymap, vim.inputState, context); if (match.type == 'none') { clearInputState(cm); return false; } else if (match.type == 'partial') { return true; } else if (match.type == 'clear') { clearInputState(cm); return true; } vim.inputState.keyBuffer = ''; keysMatcher = /^(\d*)(.*)$/.exec(keys); if (keysMatcher[1] && keysMatcher[1] != '0') { vim.inputState.pushRepeatDigit(keysMatcher[1]); } return match.command; } var command; if (vim.insertMode) { command = handleKeyInsertMode(); } else { command = handleKeyNonInsertMode(); } if (command === false) { return !vim.insertMode && key.length === 1 ? function() { return true; } : undefined; } else if (command === true) { // TODO: Look into using CodeMirror's multi-key handling. // Return no-op since we are caching the key. Counts as handled, but // don't want act on it just yet. return function() { return true; }; } else { return function() { return cm.operation(function() { cm.curOp.isVimOp = true; try { if (command.type == 'keyToKey') { doKeyToKey(command.toKeys); } else { commandDispatcher.processCommand(cm, vim, command); } } catch (e) { // clear VIM state in case it's in a bad state. cm.state.vim = undefined; maybeInitVimState(cm); if (!vimApi.suppressErrorLogging) { console['log'](e); } throw e; } return true; }); }; } }, handleEx: function(cm, input) { exCommandDispatcher.processCommand(cm, input); }, defineMotion: defineMotion, defineAction: defineAction, defineOperator: defineOperator, mapCommand: mapCommand, _mapCommand: _mapCommand, defineRegister: defineRegister, exitVisualMode: exitVisualMode, exitInsertMode: exitInsertMode }; // Represents the current input state. function InputState() { this.prefixRepeat = []; this.motionRepeat = []; this.operator = null; this.operatorArgs = null; this.motion = null; this.motionArgs = null; this.keyBuffer = []; // For matching multi-key commands. this.registerName = null; // Defaults to the unnamed register. } InputState.prototype.pushRepeatDigit = function(n) { if (!this.operator) { this.prefixRepeat = this.prefixRepeat.concat(n); } else { this.motionRepeat = this.motionRepeat.concat(n); } }; InputState.prototype.getRepeat = function() { var repeat = 0; if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { repeat = 1; if (this.prefixRepeat.length > 0) { repeat *= parseInt(this.prefixRepeat.join(''), 10); } if (this.motionRepeat.length > 0) { repeat *= parseInt(this.motionRepeat.join(''), 10); } } return repeat; }; function clearInputState(cm, reason) { cm.state.vim.inputState = new InputState(); CodeMirror.signal(cm, 'vim-command-done', reason); } /* * Register stores information about copy and paste registers. Besides * text, a register must store whether it is linewise (i.e., when it is * pasted, should it insert itself into a new line, or should the text be * inserted at the cursor position.) */ function Register(text, linewise, blockwise) { this.clear(); this.keyBuffer = [text || '']; this.insertModeChanges = []; this.searchQueries = []; this.linewise = !!linewise; this.blockwise = !!blockwise; } Register.prototype = { setText: function(text, linewise, blockwise) { this.keyBuffer = [text || '']; this.linewise = !!linewise; this.blockwise = !!blockwise; }, pushText: function(text, linewise) { // if this register has ever been set to linewise, use linewise. if (linewise) { if (!this.linewise) { this.keyBuffer.push('\n'); } this.linewise = true; } this.keyBuffer.push(text); }, pushInsertModeChanges: function(changes) { this.insertModeChanges.push(createInsertModeChanges(changes)); }, pushSearchQuery: function(query) { this.searchQueries.push(query); }, clear: function() { this.keyBuffer = []; this.insertModeChanges = []; this.searchQueries = []; this.linewise = false; }, toString: function() { return this.keyBuffer.join(''); } }; /** * Defines an external register. * * The name should be a single character that will be used to reference the register. * The register should support setText, pushText, clear, and toString(). See Register * for a reference implementation. */ function defineRegister(name, register) { var registers = vimGlobalState.registerController.registers; if (!name || name.length != 1) { throw Error('Register name must be 1 character'); } if (registers[name]) { throw Error('Register already defined ' + name); } registers[name] = register; validRegisters.push(name); } /* * vim registers allow you to keep many independent copy and paste buffers. * See http://usevim.com/2012/04/13/registers/ for an introduction. * * RegisterController keeps the state of all the registers. An initial * state may be passed in. The unnamed register '"' will always be * overridden. */ function RegisterController(registers) { this.registers = registers; this.unnamedRegister = registers['"'] = new Register(); registers['.'] = new Register(); registers[':'] = new Register(); registers['/'] = new Register(); } RegisterController.prototype = { pushText: function(registerName, operator, text, linewise, blockwise) { // The black hole register, "_, means delete/yank to nowhere. if (registerName === '_') return; if (linewise && text.charAt(text.length - 1) !== '\n'){ text += '\n'; } // Lowercase and uppercase registers refer to the same register. // Uppercase just means append. var register = this.isValidRegister(registerName) ? this.getRegister(registerName) : null; // if no register/an invalid register was specified, things go to the // default registers if (!register) { switch (operator) { case 'yank': // The 0 register contains the text from the most recent yank. this.registers['0'] = new Register(text, linewise, blockwise); break; case 'delete': case 'change': if (text.indexOf('\n') == -1) { // Delete less than 1 line. Update the small delete register. this.registers['-'] = new Register(text, linewise); } else { // Shift down the contents of the numbered registers and put the // deleted text into register 1. this.shiftNumericRegisters_(); this.registers['1'] = new Register(text, linewise); } break; } // Make sure the unnamed register is set to what just happened this.unnamedRegister.setText(text, linewise, blockwise); return; } // If we've gotten to this point, we've actually specified a register var append = isUpperCase(registerName); if (append) { register.pushText(text, linewise); } else { register.setText(text, linewise, blockwise); } // The unnamed register always has the same value as the last used // register. this.unnamedRegister.setText(register.toString(), linewise); }, // Gets the register named @name. If one of @name doesn't already exist, // create it. If @name is invalid, return the unnamedRegister. getRegister: function(name) { if (!this.isValidRegister(name)) { return this.unnamedRegister; } name = name.toLowerCase(); if (!this.registers[name]) { this.registers[name] = new Register(); } return this.registers[name]; }, isValidRegister: function(name) { return name && inArray(name, validRegisters); }, shiftNumericRegisters_: function() { for (var i = 9; i >= 2; i--) { this.registers[i] = this.getRegister('' + (i - 1)); } } }; function HistoryController() { this.historyBuffer = []; this.iterator = 0; this.initialPrefix = null; } HistoryController.prototype = { // the input argument here acts a user entered prefix for a small time // until we start autocompletion in which case it is the autocompleted. nextMatch: function (input, up) { var historyBuffer = this.historyBuffer; var dir = up ? -1 : 1; if (this.initialPrefix === null) this.initialPrefix = input; for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) { var element = historyBuffer[i]; for (var j = 0; j <= element.length; j++) { if (this.initialPrefix == element.substring(0, j)) { this.iterator = i; return element; } } } // should return the user input in case we reach the end of buffer. if (i >= historyBuffer.length) { this.iterator = historyBuffer.length; return this.initialPrefix; } // return the last autocompleted query or exCommand as it is. if (i < 0 ) return input; }, pushInput: function(input) { var index = this.historyBuffer.indexOf(input); if (index > -1) this.historyBuffer.splice(index, 1); if (input.length) this.historyBuffer.push(input); }, reset: function() { this.initialPrefix = null; this.iterator = this.historyBuffer.length; } }; var commandDispatcher = { matchCommand: function(keys, keyMap, inputState, context) { var matches = commandMatches(keys, keyMap, context, inputState); if (!matches.full && !matches.partial) { return {type: 'none'}; } else if (!matches.full && matches.partial) { return {type: 'partial'}; } var bestMatch; for (var i = 0; i < matches.full.length; i++) { var match = matches.full[i]; if (!bestMatch) { bestMatch = match; } } if (bestMatch.keys.slice(-11) == '') { var character = lastChar(keys); if (!character || character.length > 1) return {type: 'clear'}; inputState.selectedCharacter = character; } return {type: 'full', command: bestMatch}; }, processCommand: function(cm, vim, command) { vim.inputState.repeatOverride = command.repeatOverride; switch (command.type) { case 'motion': this.processMotion(cm, vim, command); break; case 'operator': this.processOperator(cm, vim, command); break; case 'operatorMotion': this.processOperatorMotion(cm, vim, command); break; case 'action': this.processAction(cm, vim, command); break; case 'search': this.processSearch(cm, vim, command); break; case 'ex': case 'keyToEx': this.processEx(cm, vim, command); break; } }, processMotion: function(cm, vim, command) { vim.inputState.motion = command.motion; vim.inputState.motionArgs = copyArgs(command.motionArgs); this.evalInput(cm, vim); }, processOperator: function(cm, vim, command) { var inputState = vim.inputState; if (inputState.operator) { if (inputState.operator == command.operator) { // Typing an operator twice like 'dd' makes the operator operate // linewise inputState.motion = 'expandToLine'; inputState.motionArgs = { linewise: true }; this.evalInput(cm, vim); return; } else { // 2 different operators in a row doesn't make sense. clearInputState(cm); } } inputState.operator = command.operator; inputState.operatorArgs = copyArgs(command.operatorArgs); if (command.keys.length > 1) { inputState.operatorShortcut = command.keys; } if (command.exitVisualBlock) { vim.visualBlock = false; updateCmSelection(cm); } if (vim.visualMode) { // Operating on a selection in visual mode. We don't need a motion. this.evalInput(cm, vim); } }, processOperatorMotion: function(cm, vim, command) { var visualMode = vim.visualMode; var operatorMotionArgs = copyArgs(command.operatorMotionArgs); if (operatorMotionArgs) { // Operator motions may have special behavior in visual mode. if (visualMode && operatorMotionArgs.visualLine) { vim.visualLine = true; } } this.processOperator(cm, vim, command); if (!visualMode) { this.processMotion(cm, vim, command); } }, processAction: function(cm, vim, command) { var inputState = vim.inputState; var repeat = inputState.getRepeat(); var repeatIsExplicit = !!repeat; var actionArgs = copyArgs(command.actionArgs) || {}; if (inputState.selectedCharacter) { actionArgs.selectedCharacter = inputState.selectedCharacter; } // Actions may or may not have motions and operators. Do these first. if (command.operator) { this.processOperator(cm, vim, command); } if (command.motion) { this.processMotion(cm, vim, command); } if (command.motion || command.operator) { this.evalInput(cm, vim); } actionArgs.repeat = repeat || 1; actionArgs.repeatIsExplicit = repeatIsExplicit; actionArgs.registerName = inputState.registerName; clearInputState(cm); vim.lastMotion = null; if (command.isEdit) { this.recordLastEdit(vim, inputState, command); } actions[command.action](cm, actionArgs, vim); }, processSearch: function(cm, vim, command) { if (!cm.getSearchCursor) { // Search depends on SearchCursor. return; } var forward = command.searchArgs.forward; var wholeWordOnly = command.searchArgs.wholeWordOnly; getSearchState(cm).setReversed(!forward); var promptPrefix = (forward) ? '/' : '?'; var originalQuery = getSearchState(cm).getQuery(); var originalScrollPos = cm.getScrollInfo(); function handleQuery(query, ignoreCase, smartCase) { vimGlobalState.searchHistoryController.pushInput(query); vimGlobalState.searchHistoryController.reset(); try { updateSearchQuery(cm, query, ignoreCase, smartCase); } catch (e) { showConfirm(cm, 'Invalid regex: ' + query); clearInputState(cm); return; } commandDispatcher.processMotion(cm, vim, { type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } }); } function onPromptClose(query) { cm.scrollTo(originalScrollPos.left, originalScrollPos.top); handleQuery(query, true /** ignoreCase */, true /** smartCase */); var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isRecording) { logSearchQuery(macroModeState, query); } } function onPromptKeyUp(e, query, close) { var keyName = CodeMirror.keyName(e), up, offset; if (keyName == 'Up' || keyName == 'Down') { up = keyName == 'Up' ? true : false; offset = e.target ? e.target.selectionEnd : 0; query = vimGlobalState.searchHistoryController.nextMatch(query, up) || ''; close(query); if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); } else { if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') vimGlobalState.searchHistoryController.reset(); } var parsedQuery; try { parsedQuery = updateSearchQuery(cm, query, true /** ignoreCase */, true /** smartCase */); } catch (e) { // Swallow bad regexes for incremental search. } if (parsedQuery) { cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); } else { clearSearchHighlight(cm); cm.scrollTo(originalScrollPos.left, originalScrollPos.top); } } function onPromptKeyDown(e, query, close) { var keyName = CodeMirror.keyName(e); if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || (keyName == 'Backspace' && query == '')) { vimGlobalState.searchHistoryController.pushInput(query); vimGlobalState.searchHistoryController.reset(); updateSearchQuery(cm, originalQuery); clearSearchHighlight(cm); cm.scrollTo(originalScrollPos.left, originalScrollPos.top); CodeMirror.e_stop(e); clearInputState(cm); close(); cm.focus(); } else if (keyName == 'Up' || keyName == 'Down') { CodeMirror.e_stop(e); } else if (keyName == 'Ctrl-U') { // Ctrl-U clears input. CodeMirror.e_stop(e); close(''); } } switch (command.searchArgs.querySrc) { case 'prompt': var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isPlaying) { var query = macroModeState.replaySearchQueries.shift(); handleQuery(query, true /** ignoreCase */, false /** smartCase */); } else { showPrompt(cm, { onClose: onPromptClose, prefix: promptPrefix, desc: '(JavaScript regexp)', onKeyUp: onPromptKeyUp, onKeyDown: onPromptKeyDown }); } break; case 'wordUnderCursor': var word = expandWordUnderCursor(cm, false /** inclusive */, true /** forward */, false /** bigWord */, true /** noSymbol */); var isKeyword = true; if (!word) { word = expandWordUnderCursor(cm, false /** inclusive */, true /** forward */, false /** bigWord */, false /** noSymbol */); isKeyword = false; } if (!word) { return; } var query = cm.getLine(word.start.line).substring(word.start.ch, word.end.ch); if (isKeyword && wholeWordOnly) { query = '\\b' + query + '\\b'; } else { query = escapeRegex(query); } // cachedCursor is used to save the old position of the cursor // when * or # causes vim to seek for the nearest word and shift // the cursor before entering the motion. vimGlobalState.jumpList.cachedCursor = cm.getCursor(); cm.setCursor(word.start); handleQuery(query, true /** ignoreCase */, false /** smartCase */); break; } }, processEx: function(cm, vim, command) { function onPromptClose(input) { // Give the prompt some time to close so that if processCommand shows // an error, the elements don't overlap. vimGlobalState.exCommandHistoryController.pushInput(input); vimGlobalState.exCommandHistoryController.reset(); exCommandDispatcher.processCommand(cm, input); clearInputState(cm); } function onPromptKeyDown(e, input, close) { var keyName = CodeMirror.keyName(e), up, offset; if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[' || (keyName == 'Backspace' && input == '')) { vimGlobalState.exCommandHistoryController.pushInput(input); vimGlobalState.exCommandHistoryController.reset(); CodeMirror.e_stop(e); clearInputState(cm); close(); cm.focus(); } if (keyName == 'Up' || keyName == 'Down') { CodeMirror.e_stop(e); up = keyName == 'Up' ? true : false; offset = e.target ? e.target.selectionEnd : 0; input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; close(input); if (offset && e.target) e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); } else if (keyName == 'Ctrl-U') { // Ctrl-U clears input. CodeMirror.e_stop(e); close(''); } else { if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') vimGlobalState.exCommandHistoryController.reset(); } } if (command.type == 'keyToEx') { // Handle user defined Ex to Ex mappings exCommandDispatcher.processCommand(cm, command.exArgs.input); } else { if (vim.visualMode) { showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>', onKeyDown: onPromptKeyDown, selectValueOnOpen: false}); } else { showPrompt(cm, { onClose: onPromptClose, prefix: ':', onKeyDown: onPromptKeyDown}); } } }, evalInput: function(cm, vim) { // If the motion command is set, execute both the operator and motion. // Otherwise return. var inputState = vim.inputState; var motion = inputState.motion; var motionArgs = inputState.motionArgs || {}; var operator = inputState.operator; var operatorArgs = inputState.operatorArgs || {}; var registerName = inputState.registerName; var sel = vim.sel; // TODO: Make sure cm and vim selections are identical outside visual mode. var origHead = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.head): cm.getCursor('head')); var origAnchor = copyCursor(vim.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor('anchor')); var oldHead = copyCursor(origHead); var oldAnchor = copyCursor(origAnchor); var newHead, newAnchor; var repeat; if (operator) { this.recordLastEdit(vim, inputState); } if (inputState.repeatOverride !== undefined) { // If repeatOverride is specified, that takes precedence over the // input state's repeat. Used by Ex mode and can be user defined. repeat = inputState.repeatOverride; } else { repeat = inputState.getRepeat(); } if (repeat > 0 && motionArgs.explicitRepeat) { motionArgs.repeatIsExplicit = true; } else if (motionArgs.noRepeat || (!motionArgs.explicitRepeat && repeat === 0)) { repeat = 1; motionArgs.repeatIsExplicit = false; } if (inputState.selectedCharacter) { // If there is a character input, stick it in all of the arg arrays. motionArgs.selectedCharacter = operatorArgs.selectedCharacter = inputState.selectedCharacter; } motionArgs.repeat = repeat; clearInputState(cm); if (motion) { var motionResult = motions[motion](cm, origHead, motionArgs, vim, inputState); vim.lastMotion = motions[motion]; if (!motionResult) { return; } if (motionArgs.toJumplist) { var jumpList = vimGlobalState.jumpList; // if the current motion is # or *, use cachedCursor var cachedCursor = jumpList.cachedCursor; if (cachedCursor) { recordJumpPosition(cm, cachedCursor, motionResult); delete jumpList.cachedCursor; } else { recordJumpPosition(cm, origHead, motionResult); } } if (motionResult instanceof Array) { newAnchor = motionResult[0]; newHead = motionResult[1]; } else { newHead = motionResult; } // TODO: Handle null returns from motion commands better. if (!newHead) { newHead = copyCursor(origHead); } if (vim.visualMode) { if (!(vim.visualBlock && newHead.ch === Infinity)) { newHead = clipCursorToContent(cm, newHead); } if (newAnchor) { newAnchor = clipCursorToContent(cm, newAnchor); } newAnchor = newAnchor || oldAnchor; sel.anchor = newAnchor; sel.head = newHead; updateCmSelection(cm); updateMark(cm, vim, '<', cursorIsBefore(newAnchor, newHead) ? newAnchor : newHead); updateMark(cm, vim, '>', cursorIsBefore(newAnchor, newHead) ? newHead : newAnchor); } else if (!operator) { newHead = clipCursorToContent(cm, newHead); cm.setCursor(newHead.line, newHead.ch); } } if (operator) { if (operatorArgs.lastSel) { // Replaying a visual mode operation newAnchor = oldAnchor; var lastSel = operatorArgs.lastSel; var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); if (lastSel.visualLine) { // Linewise Visual mode: The same number of lines. newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); } else if (lastSel.visualBlock) { // Blockwise Visual mode: The same number of lines and columns. newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); } else if (lastSel.head.line == lastSel.anchor.line) { // Normal Visual mode within one line: The same number of characters. newHead = new Pos(oldAnchor.line, oldAnchor.ch + chOffset); } else { // Normal Visual mode with several lines: The same number of lines, in the // last line the same number of characters as in the last line the last time. newHead = new Pos(oldAnchor.line + lineOffset, oldAnchor.ch); } vim.visualMode = true; vim.visualLine = lastSel.visualLine; vim.visualBlock = lastSel.visualBlock; sel = vim.sel = { anchor: newAnchor, head: newHead }; updateCmSelection(cm); } else if (vim.visualMode) { operatorArgs.lastSel = { anchor: copyCursor(sel.anchor), head: copyCursor(sel.head), visualBlock: vim.visualBlock, visualLine: vim.visualLine }; } var curStart, curEnd, linewise, mode; var cmSel; if (vim.visualMode) { // Init visual op curStart = cursorMin(sel.head, sel.anchor); curEnd = cursorMax(sel.head, sel.anchor); linewise = vim.visualLine || operatorArgs.linewise; mode = vim.visualBlock ? 'block' : linewise ? 'line' : 'char'; cmSel = makeCmSelection(cm, { anchor: curStart, head: curEnd }, mode); if (linewise) { var ranges = cmSel.ranges; if (mode == 'block') { // Linewise operators in visual block mode extend to end of line for (var i = 0; i < ranges.length; i++) { ranges[i].head.ch = lineLength(cm, ranges[i].head.line); } } else if (mode == 'line') { ranges[0].head = new Pos(ranges[0].head.line + 1, 0); } } } else { // Init motion op curStart = copyCursor(newAnchor || oldAnchor); curEnd = copyCursor(newHead || oldHead); if (cursorIsBefore(curEnd, curStart)) { var tmp = curStart; curStart = curEnd; curEnd = tmp; } linewise = motionArgs.linewise || operatorArgs.linewise; if (linewise) { // Expand selection to entire line. expandSelectionToLine(cm, curStart, curEnd); } else if (motionArgs.forward) { // Clip to trailing newlines only if the motion goes forward. clipToLine(cm, curStart, curEnd); } mode = 'char'; var exclusive = !motionArgs.inclusive || linewise; cmSel = makeCmSelection(cm, { anchor: curStart, head: curEnd }, mode, exclusive); } cm.setSelections(cmSel.ranges, cmSel.primary); vim.lastMotion = null; operatorArgs.repeat = repeat; // For indent in visual mode. operatorArgs.registerName = registerName; // Keep track of linewise as it affects how paste and change behave. operatorArgs.linewise = linewise; var operatorMoveTo = operators[operator]( cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); if (vim.visualMode) { exitVisualMode(cm, operatorMoveTo != null); } if (operatorMoveTo) { cm.setCursor(operatorMoveTo); } } }, recordLastEdit: function(vim, inputState, actionCommand) { var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isPlaying) { return; } vim.lastEditInputState = inputState; vim.lastEditActionCommand = actionCommand; macroModeState.lastInsertModeChanges.changes = []; macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; macroModeState.lastInsertModeChanges.visualBlock = vim.visualBlock ? vim.sel.head.line - vim.sel.anchor.line : 0; } }; /** * typedef {Object{line:number,ch:number}} Cursor An object containing the * position of the cursor. */ // All of the functions below return Cursor objects. var motions = { moveToTopLine: function(cm, _head, motionArgs) { var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, moveToMiddleLine: function(cm) { var range = getUserVisibleLines(cm); var line = Math.floor((range.top + range.bottom) * 0.5); return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, moveToBottomLine: function(cm, _head, motionArgs) { var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; return new Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, expandToLine: function(_cm, head, motionArgs) { // Expands forward to end of line, and then to next line if repeat is // >1. Does not handle backward motion! var cur = head; return new Pos(cur.line + motionArgs.repeat - 1, Infinity); }, findNext: function(cm, _head, motionArgs) { var state = getSearchState(cm); var query = state.getQuery(); if (!query) { return; } var prev = !motionArgs.forward; // If search is initiated with ? instead of /, negate direction. prev = (state.isReversed()) ? !prev : prev; highlightSearchMatches(cm, query); return findNext(cm, prev/** prev */, query, motionArgs.repeat); }, /** * Find and select the next occurrence of the search query. If the cursor is currently * within a match, then find and select the current match. Otherwise, find the next occurrence in the * appropriate direction. * * This differs from `findNext` in the following ways: * * 1. Instead of only returning the "from", this returns a "from", "to" range. * 2. If the cursor is currently inside a search match, this selects the current match * instead of the next match. * 3. If there is no associated operator, this will turn on visual mode. */ findAndSelectNextInclusive: function(cm, _head, motionArgs, vim, prevInputState) { var state = getSearchState(cm); var query = state.getQuery(); if (!query) { return; } var prev = !motionArgs.forward; prev = (state.isReversed()) ? !prev : prev; // next: [from, to] | null var next = findNextFromAndToInclusive(cm, prev, query, motionArgs.repeat, vim); // No matches. if (!next) { return; } // If there's an operator that will be executed, return the selection. if (prevInputState.operator) { return next; } // At this point, we know that there is no accompanying operator -- let's // deal with visual mode in order to select an appropriate match. var from = next[0]; // For whatever reason, when we use the "to" as returned by searchcursor.js directly, // the resulting selection is extended by 1 char. Let's shrink it so that only the // match is selected. var to = new Pos(next[1].line, next[1].ch - 1); if (vim.visualMode) { // If we were in visualLine or visualBlock mode, get out of it. if (vim.visualLine || vim.visualBlock) { vim.visualLine = false; vim.visualBlock = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); } // If we're currently in visual mode, we should extend the selection to include // the search result. var anchor = vim.sel.anchor; if (anchor) { if (state.isReversed()) { if (motionArgs.forward) { return [anchor, from]; } return [anchor, to]; } else { if (motionArgs.forward) { return [anchor, to]; } return [anchor, from]; } } } else { // Let's turn visual mode on. vim.visualMode = true; vim.visualLine = false; vim.visualBlock = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: ""}); } return prev ? [to, from] : [from, to]; }, goToMark: function(cm, _head, motionArgs, vim) { var pos = getMarkPos(cm, vim, motionArgs.selectedCharacter); if (pos) { return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; } return null; }, moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) { if (vim.visualBlock && motionArgs.sameLine) { var sel = vim.sel; return [ clipCursorToContent(cm, new Pos(sel.anchor.line, sel.head.ch)), clipCursorToContent(cm, new Pos(sel.head.line, sel.anchor.ch)) ]; } else { return ([vim.sel.head, vim.sel.anchor]); } }, jumpToMark: function(cm, head, motionArgs, vim) { var best = head; for (var i = 0; i < motionArgs.repeat; i++) { var cursor = best; for (var key in vim.marks) { if (!isLowerCase(key)) { continue; } var mark = vim.marks[key].find(); var isWrongDirection = (motionArgs.forward) ? cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); if (isWrongDirection) { continue; } if (motionArgs.linewise && (mark.line == cursor.line)) { continue; } var equal = cursorEqual(cursor, best); var between = (motionArgs.forward) ? cursorIsBetween(cursor, mark, best) : cursorIsBetween(best, mark, cursor); if (equal || between) { best = mark; } } } if (motionArgs.linewise) { // Vim places the cursor on the first non-whitespace character of // the line if there is one, else it places the cursor at the end // of the line, regardless of whether a mark was found. best = new Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); } return best; }, moveByCharacters: function(_cm, head, motionArgs) { var cur = head; var repeat = motionArgs.repeat; var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; return new Pos(cur.line, ch); }, moveByLines: function(cm, head, motionArgs, vim) { var cur = head; var endCh = cur.ch; // Depending what our last motion was, we may want to do different // things. If our last motion was moving vertically, we want to // preserve the HPos from our last horizontal move. If our last motion // was going to the end of a line, moving vertically we should go to // the end of the line, etc. switch (vim.lastMotion) { case this.moveByLines: case this.moveByDisplayLines: case this.moveByScroll: case this.moveToColumn: case this.moveToEol: endCh = vim.lastHPos; break; default: vim.lastHPos = endCh; } var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; var first = cm.firstLine(); var last = cm.lastLine(); var posV = cm.findPosV(cur, (motionArgs.forward ? repeat : -repeat), 'line', vim.lastHSPos); var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line; if (hasMarkedText) { line = posV.line; endCh = posV.ch; } // Vim go to line begin or line end when cursor at first/last line and // move to previous/next line is triggered. if (line < first && cur.line == first){ return this.moveToStartOfLine(cm, head, motionArgs, vim); } else if (line > last && cur.line == last){ return moveToEol(cm, head, motionArgs, vim, true); } if (motionArgs.toFirstChar){ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); vim.lastHPos = endCh; } vim.lastHSPos = cm.charCoords(new Pos(line, endCh),'div').left; return new Pos(line, endCh); }, moveByDisplayLines: function(cm, head, motionArgs, vim) { var cur = head; switch (vim.lastMotion) { case this.moveByDisplayLines: case this.moveByScroll: case this.moveByLines: case this.moveToColumn: case this.moveToEol: break; default: vim.lastHSPos = cm.charCoords(cur,'div').left; } var repeat = motionArgs.repeat; var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); if (res.hitSide) { if (motionArgs.forward) { var lastCharCoords = cm.charCoords(res, 'div'); var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; var res = cm.coordsChar(goalCoords, 'div'); } else { var resCoords = cm.charCoords(new Pos(cm.firstLine(), 0), 'div'); resCoords.left = vim.lastHSPos; res = cm.coordsChar(resCoords, 'div'); } } vim.lastHPos = res.ch; return res; }, moveByPage: function(cm, head, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so // doing this bad hack to move the cursor and move it back. evalInput // will move the cursor to where it should be in the end. var curStart = head; var repeat = motionArgs.repeat; return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page'); }, moveByParagraph: function(cm, head, motionArgs) { var dir = motionArgs.forward ? 1 : -1; return findParagraph(cm, head, motionArgs.repeat, dir); }, moveBySentence: function(cm, head, motionArgs) { var dir = motionArgs.forward ? 1 : -1; return findSentence(cm, head, motionArgs.repeat, dir); }, moveByScroll: function(cm, head, motionArgs, vim) { var scrollbox = cm.getScrollInfo(); var curEnd = null; var repeat = motionArgs.repeat; if (!repeat) { repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); } var orig = cm.charCoords(head, 'local'); motionArgs.repeat = repeat; curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim); if (!curEnd) { return null; } var dest = cm.charCoords(curEnd, 'local'); cm.scrollTo(null, scrollbox.top + dest.top - orig.top); return curEnd; }, moveByWords: function(cm, head, motionArgs) { return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward, !!motionArgs.wordEnd, !!motionArgs.bigWord); }, moveTillCharacter: function(cm, _head, motionArgs) { var repeat = motionArgs.repeat; var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter); var increment = motionArgs.forward ? -1 : 1; recordLastCharacterSearch(increment, motionArgs); if (!curEnd) return null; curEnd.ch += increment; return curEnd; }, moveToCharacter: function(cm, head, motionArgs) { var repeat = motionArgs.repeat; recordLastCharacterSearch(0, motionArgs); return moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter) || head; }, moveToSymbol: function(cm, head, motionArgs) { var repeat = motionArgs.repeat; return findSymbol(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter) || head; }, moveToColumn: function(cm, head, motionArgs, vim) { var repeat = motionArgs.repeat; // repeat is equivalent to which column we want to move to! vim.lastHPos = repeat - 1; vim.lastHSPos = cm.charCoords(head,'div').left; return moveToColumn(cm, repeat); }, moveToEol: function(cm, head, motionArgs, vim) { return moveToEol(cm, head, motionArgs, vim, false); }, moveToFirstNonWhiteSpaceCharacter: function(cm, head) { // Go to the start of the line where the text begins, or the end for // whitespace-only lines var cursor = head; return new Pos(cursor.line, findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); }, moveToMatchedSymbol: function(cm, head) { var cursor = head; var line = cursor.line; var ch = cursor.ch; var lineText = cm.getLine(line); var symbol; for (; ch < lineText.length; ch++) { symbol = lineText.charAt(ch); if (symbol && isMatchableSymbol(symbol)) { var style = cm.getTokenTypeAt(new Pos(line, ch + 1)); if (style !== "string" && style !== "comment") { break; } } } if (ch < lineText.length) { // Only include angle brackets in analysis if they are being matched. var re = (ch === '<' || ch === '>') ? /[(){}[\]<>]/ : /[(){}[\]]/; var matched = cm.findMatchingBracket(new Pos(line, ch), {bracketRegex: re}); return matched.to; } else { return cursor; } }, moveToStartOfLine: function(_cm, head) { return new Pos(head.line, 0); }, moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); if (motionArgs.repeatIsExplicit) { lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); } return new Pos(lineNum, findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); }, moveToStartOfDisplayLine: function(cm) { cm.execCommand("goLineLeft"); return cm.getCursor(); }, moveToEndOfDisplayLine: function(cm) { cm.execCommand("goLineRight"); var head = cm.getCursor(); if (head.sticky == "before") head.ch--; return head; }, textObjectManipulation: function(cm, head, motionArgs, vim) { // TODO: lots of possible exceptions that can be thrown here. Try da( // outside of a () block. var mirroredPairs = {'(': ')', ')': '(', '{': '}', '}': '{', '[': ']', ']': '[', '<': '>', '>': '<'}; var selfPaired = {'\'': true, '"': true, '`': true}; var character = motionArgs.selectedCharacter; // 'b' refers to '()' block. // 'B' refers to '{}' block. if (character == 'b') { character = '('; } else if (character == 'B') { character = '{'; } // Inclusive is the difference between a and i // TODO: Instead of using the additional text object map to perform text // object operations, merge the map into the defaultKeyMap and use // motionArgs to define behavior. Define separate entries for 'aw', // 'iw', 'a[', 'i[', etc. var inclusive = !motionArgs.textObjectInner; var tmp; if (mirroredPairs[character]) { tmp = selectCompanionObject(cm, head, character, inclusive); } else if (selfPaired[character]) { tmp = findBeginningAndEnd(cm, head, character, inclusive); } else if (character === 'W') { tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, true /** bigWord */); } else if (character === 'w') { tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, false /** bigWord */); } else if (character === 'p') { tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); motionArgs.linewise = true; if (vim.visualMode) { if (!vim.visualLine) { vim.visualLine = true; } } else { var operatorArgs = vim.inputState.operatorArgs; if (operatorArgs) { operatorArgs.linewise = true; } tmp.end.line--; } } else if (character === 't') { tmp = expandTagUnderCursor(cm, head, inclusive); } else if (character === 's') { // account for cursor on end of sentence symbol var content = cm.getLine(head.line); if (head.ch > 0 && isEndOfSentenceSymbol(content[head.ch])) { head.ch -= 1; } var end = getSentence(cm, head, motionArgs.repeat, 1, inclusive); var start = getSentence(cm, head, motionArgs.repeat, -1, inclusive); // closer vim behaviour, 'a' only takes the space after the sentence if there is one before and after if (isWhiteSpaceString(cm.getLine(start.line)[start.ch]) && isWhiteSpaceString(cm.getLine(end.line)[end.ch -1])) { start = {line: start.line, ch: start.ch + 1}; } tmp = {start: start, end: end}; } else { // No text object defined for this, don't move. return null; } if (!cm.state.vim.visualMode) { return [tmp.start, tmp.end]; } else { return expandSelection(cm, tmp.start, tmp.end); } }, repeatLastCharacterSearch: function(cm, head, motionArgs) { var lastSearch = vimGlobalState.lastCharacterSearch; var repeat = motionArgs.repeat; var forward = motionArgs.forward === lastSearch.forward; var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); cm.moveH(-increment, 'char'); motionArgs.inclusive = forward ? true : false; var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); if (!curEnd) { cm.moveH(increment, 'char'); return head; } curEnd.ch += increment; return curEnd; } }; function defineMotion(name, fn) { motions[name] = fn; } function fillArray(val, times) { var arr = []; for (var i = 0; i < times; i++) { arr.push(val); } return arr; } /** * An operator acts on a text selection. It receives the list of selections * as input. The corresponding CodeMirror selection is guaranteed to * match the input selection. */ var operators = { change: function(cm, args, ranges) { var finalHead, text; var vim = cm.state.vim; var anchor = ranges[0].anchor, head = ranges[0].head; if (!vim.visualMode) { text = cm.getRange(anchor, head); var lastState = vim.lastEditInputState || {}; if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) { // Exclude trailing whitespace if the range is not all whitespace. var match = (/\s+$/).exec(text); if (match && lastState.motionArgs && lastState.motionArgs.forward) { head = offsetCursor(head, 0, - match[0].length); text = text.slice(0, - match[0].length); } } var prevLineEnd = new Pos(anchor.line - 1, Number.MAX_VALUE); var wasLastLine = cm.firstLine() == cm.lastLine(); if (head.line > cm.lastLine() && args.linewise && !wasLastLine) { cm.replaceRange('', prevLineEnd, head); } else { cm.replaceRange('', anchor, head); } if (args.linewise) { // Push the next line back down, if there is a next line. if (!wasLastLine) { cm.setCursor(prevLineEnd); CodeMirror.commands.newlineAndIndent(cm); } // make sure cursor ends up at the end of the line. anchor.ch = Number.MAX_VALUE; } finalHead = anchor; } else if (args.fullLine) { head.ch = Number.MAX_VALUE; head.line--; cm.setSelection(anchor, head); text = cm.getSelection(); cm.replaceSelection(""); finalHead = anchor; } else { text = cm.getSelection(); var replacement = fillArray('', ranges.length); cm.replaceSelections(replacement); finalHead = cursorMin(ranges[0].head, ranges[0].anchor); } vimGlobalState.registerController.pushText( args.registerName, 'change', text, args.linewise, ranges.length > 1); actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim); }, // delete is a javascript keyword. 'delete': function(cm, args, ranges) { var finalHead, text; var vim = cm.state.vim; if (!vim.visualBlock) { var anchor = ranges[0].anchor, head = ranges[0].head; if (args.linewise && head.line != cm.firstLine() && anchor.line == cm.lastLine() && anchor.line == head.line - 1) { // Special case for dd on last line (and first line). if (anchor.line == cm.firstLine()) { anchor.ch = 0; } else { anchor = new Pos(anchor.line - 1, lineLength(cm, anchor.line - 1)); } } text = cm.getRange(anchor, head); cm.replaceRange('', anchor, head); finalHead = anchor; if (args.linewise) { finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); } } else { text = cm.getSelection(); var replacement = fillArray('', ranges.length); cm.replaceSelections(replacement); finalHead = cursorMin(ranges[0].head, ranges[0].anchor); } vimGlobalState.registerController.pushText( args.registerName, 'delete', text, args.linewise, vim.visualBlock); return clipCursorToContent(cm, finalHead); }, indent: function(cm, args, ranges) { var vim = cm.state.vim; if (cm.indentMore) { var repeat = (vim.visualMode) ? args.repeat : 1; for (var j = 0; j < repeat; j++) { if (args.indentRight) cm.indentMore(); else cm.indentLess(); } } else { var startLine = ranges[0].anchor.line; var endLine = vim.visualBlock ? ranges[ranges.length - 1].anchor.line : ranges[0].head.line; // In visual mode, n> shifts the selection right n times, instead of // shifting n lines right once. var repeat = (vim.visualMode) ? args.repeat : 1; if (args.linewise) { // The only way to delete a newline is to delete until the start of // the next line, so in linewise mode evalInput will include the next // line. We don't want this in indent, so we go back a line. endLine--; } for (var i = startLine; i <= endLine; i++) { for (var j = 0; j < repeat; j++) { cm.indentLine(i, args.indentRight); } } } return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); }, indentAuto: function(cm, _args, ranges) { cm.execCommand("indentAuto"); return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); }, changeCase: function(cm, args, ranges, oldAnchor, newHead) { var selections = cm.getSelections(); var swapped = []; var toLower = args.toLower; for (var j = 0; j < selections.length; j++) { var toSwap = selections[j]; var text = ''; if (toLower === true) { text = toSwap.toLowerCase(); } else if (toLower === false) { text = toSwap.toUpperCase(); } else { for (var i = 0; i < toSwap.length; i++) { var character = toSwap.charAt(i); text += isUpperCase(character) ? character.toLowerCase() : character.toUpperCase(); } } swapped.push(text); } cm.replaceSelections(swapped); if (args.shouldMoveCursor){ return newHead; } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); } else if (args.linewise){ return oldAnchor; } else { return cursorMin(ranges[0].anchor, ranges[0].head); } }, yank: function(cm, args, ranges, oldAnchor) { var vim = cm.state.vim; var text = cm.getSelection(); var endPos = vim.visualMode ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor) : oldAnchor; vimGlobalState.registerController.pushText( args.registerName, 'yank', text, args.linewise, vim.visualBlock); return endPos; } }; function defineOperator(name, fn) { operators[name] = fn; } var actions = { jumpListWalk: function(cm, actionArgs, vim) { if (vim.visualMode) { return; } var repeat = actionArgs.repeat; var forward = actionArgs.forward; var jumpList = vimGlobalState.jumpList; var mark = jumpList.move(cm, forward ? repeat : -repeat); var markPos = mark ? mark.find() : undefined; markPos = markPos ? markPos : cm.getCursor(); cm.setCursor(markPos); }, scroll: function(cm, actionArgs, vim) { if (vim.visualMode) { return; } var repeat = actionArgs.repeat || 1; var lineHeight = cm.defaultTextHeight(); var top = cm.getScrollInfo().top; var delta = lineHeight * repeat; var newPos = actionArgs.forward ? top + delta : top - delta; var cursor = copyCursor(cm.getCursor()); var cursorCoords = cm.charCoords(cursor, 'local'); if (actionArgs.forward) { if (newPos > cursorCoords.top) { cursor.line += (newPos - cursorCoords.top) / lineHeight; cursor.line = Math.ceil(cursor.line); cm.setCursor(cursor); cursorCoords = cm.charCoords(cursor, 'local'); cm.scrollTo(null, cursorCoords.top); } else { // Cursor stays within bounds. Just reposition the scroll window. cm.scrollTo(null, newPos); } } else { var newBottom = newPos + cm.getScrollInfo().clientHeight; if (newBottom < cursorCoords.bottom) { cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; cursor.line = Math.floor(cursor.line); cm.setCursor(cursor); cursorCoords = cm.charCoords(cursor, 'local'); cm.scrollTo( null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); } else { // Cursor stays within bounds. Just reposition the scroll window. cm.scrollTo(null, newPos); } } }, scrollToCursor: function(cm, actionArgs) { var lineNum = cm.getCursor().line; var charCoords = cm.charCoords(new Pos(lineNum, 0), 'local'); var height = cm.getScrollInfo().clientHeight; var y = charCoords.top; switch (actionArgs.position) { case 'center': y = charCoords.bottom - height / 2; break; case 'bottom': var lineLastCharPos = new Pos(lineNum, cm.getLine(lineNum).length - 1); var lineLastCharCoords = cm.charCoords(lineLastCharPos, 'local'); var lineHeight = lineLastCharCoords.bottom - y; y = y - height + lineHeight; break; } cm.scrollTo(null, y); }, replayMacro: function(cm, actionArgs, vim) { var registerName = actionArgs.selectedCharacter; var repeat = actionArgs.repeat; var macroModeState = vimGlobalState.macroModeState; if (registerName == '@') { registerName = macroModeState.latestRegister; } else { macroModeState.latestRegister = registerName; } while(repeat--){ executeMacroRegister(cm, vim, macroModeState, registerName); } }, enterMacroRecordMode: function(cm, actionArgs) { var macroModeState = vimGlobalState.macroModeState; var registerName = actionArgs.selectedCharacter; if (vimGlobalState.registerController.isValidRegister(registerName)) { macroModeState.enterMacroRecordMode(cm, registerName); } }, toggleOverwrite: function(cm) { if (!cm.state.overwrite) { cm.toggleOverwrite(true); cm.setOption('keyMap', 'vim-replace'); CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); } else { cm.toggleOverwrite(false); cm.setOption('keyMap', 'vim-insert'); CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); } }, enterInsertMode: function(cm, actionArgs, vim) { if (cm.getOption('readOnly')) { return; } vim.insertMode = true; vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; var insertAt = (actionArgs) ? actionArgs.insertAt : null; var sel = vim.sel; var head = actionArgs.head || cm.getCursor('head'); var height = cm.listSelections().length; if (insertAt == 'eol') { head = new Pos(head.line, lineLength(cm, head.line)); } else if (insertAt == 'bol') { head = new Pos(head.line, 0); } else if (insertAt == 'charAfter') { head = offsetCursor(head, 0, 1); } else if (insertAt == 'firstNonBlank') { head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head); } else if (insertAt == 'startOfSelectedArea') { if (!vim.visualMode) return; if (!vim.visualBlock) { if (sel.head.line < sel.anchor.line) { head = sel.head; } else { head = new Pos(sel.anchor.line, 0); } } else { head = new Pos( Math.min(sel.head.line, sel.anchor.line), Math.min(sel.head.ch, sel.anchor.ch)); height = Math.abs(sel.head.line - sel.anchor.line) + 1; } } else if (insertAt == 'endOfSelectedArea') { if (!vim.visualMode) return; if (!vim.visualBlock) { if (sel.head.line >= sel.anchor.line) { head = offsetCursor(sel.head, 0, 1); } else { head = new Pos(sel.anchor.line, 0); } } else { head = new Pos( Math.min(sel.head.line, sel.anchor.line), Math.max(sel.head.ch, sel.anchor.ch) + 1); height = Math.abs(sel.head.line - sel.anchor.line) + 1; } } else if (insertAt == 'inplace') { if (vim.visualMode){ return; } } else if (insertAt == 'lastEdit') { head = getLastEditPos(cm) || head; } cm.setOption('disableInput', false); if (actionArgs && actionArgs.replace) { // Handle Replace-mode as a special case of insert mode. cm.toggleOverwrite(true); cm.setOption('keyMap', 'vim-replace'); CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); } else { cm.toggleOverwrite(false); cm.setOption('keyMap', 'vim-insert'); CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); } if (!vimGlobalState.macroModeState.isPlaying) { // Only record if not replaying. cm.on('change', onChange); CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } if (vim.visualMode) { exitVisualMode(cm); } selectForInsert(cm, head, height); }, toggleVisualMode: function(cm, actionArgs, vim) { var repeat = actionArgs.repeat; var anchor = cm.getCursor(); var head; // TODO: The repeat should actually select number of characters/lines // equal to the repeat times the size of the previous visual // operation. if (!vim.visualMode) { // Entering visual mode vim.visualMode = true; vim.visualLine = !!actionArgs.linewise; vim.visualBlock = !!actionArgs.blockwise; head = clipCursorToContent( cm, new Pos(anchor.line, anchor.ch + repeat - 1)); vim.sel = { anchor: anchor, head: head }; CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); updateCmSelection(cm); updateMark(cm, vim, '<', cursorMin(anchor, head)); updateMark(cm, vim, '>', cursorMax(anchor, head)); } else if (vim.visualLine ^ actionArgs.linewise || vim.visualBlock ^ actionArgs.blockwise) { // Toggling between modes vim.visualLine = !!actionArgs.linewise; vim.visualBlock = !!actionArgs.blockwise; CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); updateCmSelection(cm); } else { exitVisualMode(cm); } }, reselectLastSelection: function(cm, _actionArgs, vim) { var lastSelection = vim.lastSelection; if (vim.visualMode) { updateLastSelection(cm, vim); } if (lastSelection) { var anchor = lastSelection.anchorMark.find(); var head = lastSelection.headMark.find(); if (!anchor || !head) { // If the marks have been destroyed due to edits, do nothing. return; } vim.sel = { anchor: anchor, head: head }; vim.visualMode = true; vim.visualLine = lastSelection.visualLine; vim.visualBlock = lastSelection.visualBlock; updateCmSelection(cm); updateMark(cm, vim, '<', cursorMin(anchor, head)); updateMark(cm, vim, '>', cursorMax(anchor, head)); CodeMirror.signal(cm, 'vim-mode-change', { mode: 'visual', subMode: vim.visualLine ? 'linewise' : vim.visualBlock ? 'blockwise' : ''}); } }, joinLines: function(cm, actionArgs, vim) { var curStart, curEnd; if (vim.visualMode) { curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); if (cursorIsBefore(curEnd, curStart)) { var tmp = curEnd; curEnd = curStart; curStart = tmp; } curEnd.ch = lineLength(cm, curEnd.line) - 1; } else { // Repeat is the number of lines to join. Minimum 2 lines. var repeat = Math.max(actionArgs.repeat, 2); curStart = cm.getCursor(); curEnd = clipCursorToContent(cm, new Pos(curStart.line + repeat - 1, Infinity)); } var finalCh = 0; for (var i = curStart.line; i < curEnd.line; i++) { finalCh = lineLength(cm, curStart.line); var tmp = new Pos(curStart.line + 1, lineLength(cm, curStart.line + 1)); var text = cm.getRange(curStart, tmp); text = actionArgs.keepSpaces ? text.replace(/\n\r?/g, '') : text.replace(/\n\s*/g, ' '); cm.replaceRange(text, curStart, tmp); } var curFinalPos = new Pos(curStart.line, finalCh); if (vim.visualMode) { exitVisualMode(cm, false); } cm.setCursor(curFinalPos); }, newLineAndEnterInsertMode: function(cm, actionArgs, vim) { vim.insertMode = true; var insertAt = copyCursor(cm.getCursor()); if (insertAt.line === cm.firstLine() && !actionArgs.after) { // Special case for inserting newline before start of document. cm.replaceRange('\n', new Pos(cm.firstLine(), 0)); cm.setCursor(cm.firstLine(), 0); } else { insertAt.line = (actionArgs.after) ? insertAt.line : insertAt.line - 1; insertAt.ch = lineLength(cm, insertAt.line); cm.setCursor(insertAt); var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent; newlineFn(cm); } this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); }, paste: function(cm, actionArgs, vim) { var cur = copyCursor(cm.getCursor()); var register = vimGlobalState.registerController.getRegister( actionArgs.registerName); var text = register.toString(); if (!text) { return; } if (actionArgs.matchIndent) { var tabSize = cm.getOption("tabSize"); // length that considers tabs and tabSize var whitespaceLength = function(str) { var tabs = (str.split("\t").length - 1); var spaces = (str.split(" ").length - 1); return tabs * tabSize + spaces * 1; }; var currentLine = cm.getLine(cm.getCursor().line); var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); // chomp last newline b/c don't want it to match /^\s*/gm var chompedText = text.replace(/\n$/, ''); var wasChomped = text !== chompedText; var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); var text = chompedText.replace(/^\s*/gm, function(wspace) { var newIndent = indent + (whitespaceLength(wspace) - firstIndent); if (newIndent < 0) { return ""; } else if (cm.getOption("indentWithTabs")) { var quotient = Math.floor(newIndent / tabSize); return Array(quotient + 1).join('\t'); } else { return Array(newIndent + 1).join(' '); } }); text += wasChomped ? "\n" : ""; } if (actionArgs.repeat > 1) { var text = Array(actionArgs.repeat + 1).join(text); } var linewise = register.linewise; var blockwise = register.blockwise; if (blockwise) { text = text.split('\n'); if (linewise) { text.pop(); } for (var i = 0; i < text.length; i++) { text[i] = (text[i] == '') ? ' ' : text[i]; } cur.ch += actionArgs.after ? 1 : 0; cur.ch = Math.min(lineLength(cm, cur.line), cur.ch); } else if (linewise) { if(vim.visualMode) { text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; } else if (actionArgs.after) { // Move the newline at the end to the start instead, and paste just // before the newline character of the line we are on right now. text = '\n' + text.slice(0, text.length - 1); cur.ch = lineLength(cm, cur.line); } else { cur.ch = 0; } } else { cur.ch += actionArgs.after ? 1 : 0; } var curPosFinal; var idx; if (vim.visualMode) { // save the pasted text for reselection if the need arises vim.lastPastedText = text; var lastSelectionCurEnd; var selectedArea = getSelectedAreaRange(cm, vim); var selectionStart = selectedArea[0]; var selectionEnd = selectedArea[1]; var selectedText = cm.getSelection(); var selections = cm.listSelections(); var emptyStrings = new Array(selections.length).join('1').split('1'); // save the curEnd marker before it get cleared due to cm.replaceRange. if (vim.lastSelection) { lastSelectionCurEnd = vim.lastSelection.headMark.find(); } // push the previously selected text to unnamed register vimGlobalState.registerController.unnamedRegister.setText(selectedText); if (blockwise) { // first delete the selected text cm.replaceSelections(emptyStrings); // Set new selections as per the block length of the yanked text selectionEnd = new Pos(selectionStart.line + text.length-1, selectionStart.ch); cm.setCursor(selectionStart); selectBlock(cm, selectionEnd); cm.replaceSelections(text); curPosFinal = selectionStart; } else if (vim.visualBlock) { cm.replaceSelections(emptyStrings); cm.setCursor(selectionStart); cm.replaceRange(text, selectionStart, selectionStart); curPosFinal = selectionStart; } else { cm.replaceRange(text, selectionStart, selectionEnd); curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); } // restore the the curEnd marker if(lastSelectionCurEnd) { vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); } if (linewise) { curPosFinal.ch=0; } } else { if (blockwise) { cm.setCursor(cur); for (var i = 0; i < text.length; i++) { var line = cur.line+i; if (line > cm.lastLine()) { cm.replaceRange('\n', new Pos(line, 0)); } var lastCh = lineLength(cm, line); if (lastCh < cur.ch) { extendLineToColumn(cm, line, cur.ch); } } cm.setCursor(cur); selectBlock(cm, new Pos(cur.line + text.length-1, cur.ch)); cm.replaceSelections(text); curPosFinal = cur; } else { cm.replaceRange(text, cur); // Now fine tune the cursor to where we want it. if (linewise && actionArgs.after) { curPosFinal = new Pos( cur.line + 1, findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); } else if (linewise && !actionArgs.after) { curPosFinal = new Pos( cur.line, findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); } else if (!linewise && actionArgs.after) { idx = cm.indexFromPos(cur); curPosFinal = cm.posFromIndex(idx + text.length - 1); } else { idx = cm.indexFromPos(cur); curPosFinal = cm.posFromIndex(idx + text.length); } } } if (vim.visualMode) { exitVisualMode(cm, false); } cm.setCursor(curPosFinal); }, undo: function(cm, actionArgs) { cm.operation(function() { repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); cm.setCursor(cm.getCursor('anchor')); }); }, redo: function(cm, actionArgs) { repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); }, setRegister: function(_cm, actionArgs, vim) { vim.inputState.registerName = actionArgs.selectedCharacter; }, setMark: function(cm, actionArgs, vim) { var markName = actionArgs.selectedCharacter; updateMark(cm, vim, markName, cm.getCursor()); }, replace: function(cm, actionArgs, vim) { var replaceWith = actionArgs.selectedCharacter; var curStart = cm.getCursor(); var replaceTo; var curEnd; var selections = cm.listSelections(); if (vim.visualMode) { curStart = cm.getCursor('start'); curEnd = cm.getCursor('end'); } else { var line = cm.getLine(curStart.line); replaceTo = curStart.ch + actionArgs.repeat; if (replaceTo > line.length) { replaceTo=line.length; } curEnd = new Pos(curStart.line, replaceTo); } if (replaceWith=='\n') { if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); // special case, where vim help says to replace by just one line-break (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); } else { var replaceWithStr = cm.getRange(curStart, curEnd); //replace all characters in range by selected, but keep linebreaks replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); if (vim.visualBlock) { // Tabs are split in visua block before replacing var spaces = new Array(cm.getOption("tabSize")+1).join(' '); replaceWithStr = cm.getSelection(); replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); cm.replaceSelections(replaceWithStr); } else { cm.replaceRange(replaceWithStr, curStart, curEnd); } if (vim.visualMode) { curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? selections[0].anchor : selections[0].head; cm.setCursor(curStart); exitVisualMode(cm, false); } else { cm.setCursor(offsetCursor(curEnd, 0, -1)); } } }, incrementNumberToken: function(cm, actionArgs) { var cur = cm.getCursor(); var lineStr = cm.getLine(cur.line); var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi; var match; var start; var end; var numberStr; while ((match = re.exec(lineStr)) !== null) { start = match.index; end = start + match[0].length; if (cur.ch < end)break; } if (!actionArgs.backtrack && (end <= cur.ch))return; if (match) { var baseStr = match[2] || match[4]; var digits = match[3] || match[5]; var increment = actionArgs.increase ? 1 : -1; var base = {'0b': 2, '0': 8, '': 10, '0x': 16}[baseStr.toLowerCase()]; var number = parseInt(match[1] + digits, base) + (increment * actionArgs.repeat); numberStr = number.toString(base); var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join('0') : ''; if (numberStr.charAt(0) === '-') { numberStr = '-' + baseStr + zeroPadding + numberStr.substr(1); } else { numberStr = baseStr + zeroPadding + numberStr; } var from = new Pos(cur.line, start); var to = new Pos(cur.line, end); cm.replaceRange(numberStr, from, to); } else { return; } cm.setCursor(new Pos(cur.line, start + numberStr.length - 1)); }, repeatLastEdit: function(cm, actionArgs, vim) { var lastEditInputState = vim.lastEditInputState; if (!lastEditInputState) { return; } var repeat = actionArgs.repeat; if (repeat && actionArgs.repeatIsExplicit) { vim.lastEditInputState.repeatOverride = repeat; } else { repeat = vim.lastEditInputState.repeatOverride || repeat; } repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); }, indent: function(cm, actionArgs) { cm.indentLine(cm.getCursor().line, actionArgs.indentRight); }, exitInsertMode: exitInsertMode }; function defineAction(name, fn) { actions[name] = fn; } /* * Below are miscellaneous utility functions used by vim.js */ /** * Clips cursor to ensure that line is within the buffer's range * If includeLineBreak is true, then allow cur.ch == lineLength. */ function clipCursorToContent(cm, cur) { var vim = cm.state.vim; var includeLineBreak = vim.insertMode || vim.visualMode; var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() ); var maxCh = lineLength(cm, line) - 1 + !!includeLineBreak; var ch = Math.min(Math.max(0, cur.ch), maxCh); return new Pos(line, ch); } function copyArgs(args) { var ret = {}; for (var prop in args) { if (args.hasOwnProperty(prop)) { ret[prop] = args[prop]; } } return ret; } function offsetCursor(cur, offsetLine, offsetCh) { if (typeof offsetLine === 'object') { offsetCh = offsetLine.ch; offsetLine = offsetLine.line; } return new Pos(cur.line + offsetLine, cur.ch + offsetCh); } function commandMatches(keys, keyMap, context, inputState) { // Partial matches are not applied. They inform the key handler // that the current key sequence is a subsequence of a valid key // sequence, so that the key buffer is not cleared. var match, partial = [], full = []; for (var i = 0; i < keyMap.length; i++) { var command = keyMap[i]; if (context == 'insert' && command.context != 'insert' || command.context && command.context != context || inputState.operator && command.type == 'action' || !(match = commandMatch(keys, command.keys))) { continue; } if (match == 'partial') { partial.push(command); } if (match == 'full') { full.push(command); } } return { partial: partial.length && partial, full: full.length && full }; } function commandMatch(pressed, mapped) { if (mapped.slice(-11) == '') { // Last character matches anything. var prefixLen = mapped.length - 11; var pressedPrefix = pressed.slice(0, prefixLen); var mappedPrefix = mapped.slice(0, prefixLen); return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' : mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false; } else { return pressed == mapped ? 'full' : mapped.indexOf(pressed) == 0 ? 'partial' : false; } } function lastChar(keys) { var match = /^.*(<[^>]+>)$/.exec(keys); var selectedCharacter = match ? match[1] : keys.slice(-1); if (selectedCharacter.length > 1){ switch(selectedCharacter){ case '': selectedCharacter='\n'; break; case '': selectedCharacter=' '; break; default: selectedCharacter=''; break; } } return selectedCharacter; } function repeatFn(cm, fn, repeat) { return function() { for (var i = 0; i < repeat; i++) { fn(cm); } }; } function copyCursor(cur) { return new Pos(cur.line, cur.ch); } function cursorEqual(cur1, cur2) { return cur1.ch == cur2.ch && cur1.line == cur2.line; } function cursorIsBefore(cur1, cur2) { if (cur1.line < cur2.line) { return true; } if (cur1.line == cur2.line && cur1.ch < cur2.ch) { return true; } return false; } function cursorMin(cur1, cur2) { if (arguments.length > 2) { cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1)); } return cursorIsBefore(cur1, cur2) ? cur1 : cur2; } function cursorMax(cur1, cur2) { if (arguments.length > 2) { cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1)); } return cursorIsBefore(cur1, cur2) ? cur2 : cur1; } function cursorIsBetween(cur1, cur2, cur3) { // returns true if cur2 is between cur1 and cur3. var cur1before2 = cursorIsBefore(cur1, cur2); var cur2before3 = cursorIsBefore(cur2, cur3); return cur1before2 && cur2before3; } function lineLength(cm, lineNum) { return cm.getLine(lineNum).length; } function trim(s) { if (s.trim) { return s.trim(); } return s.replace(/^\s+|\s+$/g, ''); } function escapeRegex(s) { return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); } function extendLineToColumn(cm, lineNum, column) { var endCh = lineLength(cm, lineNum); var spaces = new Array(column-endCh+1).join(' '); cm.setCursor(new Pos(lineNum, endCh)); cm.replaceRange(spaces, cm.getCursor()); } // This functions selects a rectangular block // of text with selectionEnd as any of its corner // Height of block: // Difference in selectionEnd.line and first/last selection.line // Width of the block: // Distance between selectionEnd.ch and any(first considered here) selection.ch function selectBlock(cm, selectionEnd) { var selections = [], ranges = cm.listSelections(); var head = copyCursor(cm.clipPos(selectionEnd)); var isClipped = !cursorEqual(selectionEnd, head); var curHead = cm.getCursor('head'); var primIndex = getIndex(ranges, curHead); var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); var max = ranges.length - 1; var index = max - primIndex > primIndex ? max : 0; var base = ranges[index].anchor; var firstLine = Math.min(base.line, head.line); var lastLine = Math.max(base.line, head.line); var baseCh = base.ch, headCh = head.ch; var dir = ranges[index].head.ch - baseCh; var newDir = headCh - baseCh; if (dir > 0 && newDir <= 0) { baseCh++; if (!isClipped) { headCh--; } } else if (dir < 0 && newDir >= 0) { baseCh--; if (!wasClipped) { headCh++; } } else if (dir < 0 && newDir == -1) { baseCh--; headCh++; } for (var line = firstLine; line <= lastLine; line++) { var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)}; selections.push(range); } cm.setSelections(selections); selectionEnd.ch = headCh; base.ch = baseCh; return base; } function selectForInsert(cm, head, height) { var sel = []; for (var i = 0; i < height; i++) { var lineHead = offsetCursor(head, i, 0); sel.push({anchor: lineHead, head: lineHead}); } cm.setSelections(sel, 0); } // getIndex returns the index of the cursor in the selections. function getIndex(ranges, cursor, end) { for (var i = 0; i < ranges.length; i++) { var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor); var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor); if (atAnchor || atHead) { return i; } } return -1; } function getSelectedAreaRange(cm, vim) { var lastSelection = vim.lastSelection; var getCurrentSelectedAreaRange = function() { var selections = cm.listSelections(); var start = selections[0]; var end = selections[selections.length-1]; var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; return [selectionStart, selectionEnd]; }; var getLastSelectedAreaRange = function() { var selectionStart = cm.getCursor(); var selectionEnd = cm.getCursor(); var block = lastSelection.visualBlock; if (block) { var width = block.width; var height = block.height; selectionEnd = new Pos(selectionStart.line + height, selectionStart.ch + width); var selections = []; // selectBlock creates a 'proper' rectangular block. // We do not want that in all cases, so we manually set selections. for (var i = selectionStart.line; i < selectionEnd.line; i++) { var anchor = new Pos(i, selectionStart.ch); var head = new Pos(i, selectionEnd.ch); var range = {anchor: anchor, head: head}; selections.push(range); } cm.setSelections(selections); } else { var start = lastSelection.anchorMark.find(); var end = lastSelection.headMark.find(); var line = end.line - start.line; var ch = end.ch - start.ch; selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; if (lastSelection.visualLine) { selectionStart = new Pos(selectionStart.line, 0); selectionEnd = new Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); } cm.setSelection(selectionStart, selectionEnd); } return [selectionStart, selectionEnd]; }; if (!vim.visualMode) { // In case of replaying the action. return getLastSelectedAreaRange(); } else { return getCurrentSelectedAreaRange(); } } // Updates the previous selection with the current selection's values. This // should only be called in visual mode. function updateLastSelection(cm, vim) { var anchor = vim.sel.anchor; var head = vim.sel.head; // To accommodate the effect of lastPastedText in the last selection if (vim.lastPastedText) { head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length); vim.lastPastedText = null; } vim.lastSelection = {'anchorMark': cm.setBookmark(anchor), 'headMark': cm.setBookmark(head), 'anchor': copyCursor(anchor), 'head': copyCursor(head), 'visualMode': vim.visualMode, 'visualLine': vim.visualLine, 'visualBlock': vim.visualBlock}; } function expandSelection(cm, start, end) { var sel = cm.state.vim.sel; var head = sel.head; var anchor = sel.anchor; var tmp; if (cursorIsBefore(end, start)) { tmp = end; end = start; start = tmp; } if (cursorIsBefore(head, anchor)) { head = cursorMin(start, head); anchor = cursorMax(anchor, end); } else { anchor = cursorMin(start, anchor); head = cursorMax(head, end); head = offsetCursor(head, 0, -1); if (head.ch == -1 && head.line != cm.firstLine()) { head = new Pos(head.line - 1, lineLength(cm, head.line - 1)); } } return [anchor, head]; } /** * Updates the CodeMirror selection to match the provided vim selection. * If no arguments are given, it uses the current vim selection state. */ function updateCmSelection(cm, sel, mode) { var vim = cm.state.vim; sel = sel || vim.sel; var mode = mode || vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char'; var cmSel = makeCmSelection(cm, sel, mode); cm.setSelections(cmSel.ranges, cmSel.primary); } function makeCmSelection(cm, sel, mode, exclusive) { var head = copyCursor(sel.head); var anchor = copyCursor(sel.anchor); if (mode == 'char') { var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; head = offsetCursor(sel.head, 0, headOffset); anchor = offsetCursor(sel.anchor, 0, anchorOffset); return { ranges: [{anchor: anchor, head: head}], primary: 0 }; } else if (mode == 'line') { if (!cursorIsBefore(sel.head, sel.anchor)) { anchor.ch = 0; var lastLine = cm.lastLine(); if (head.line > lastLine) { head.line = lastLine; } head.ch = lineLength(cm, head.line); } else { head.ch = 0; anchor.ch = lineLength(cm, anchor.line); } return { ranges: [{anchor: anchor, head: head}], primary: 0 }; } else if (mode == 'block') { var top = Math.min(anchor.line, head.line), fromCh = anchor.ch, bottom = Math.max(anchor.line, head.line), toCh = head.ch; if (fromCh < toCh) { toCh += 1; } else { fromCh += 1; } var height = bottom - top + 1; var primary = head.line == top ? 0 : height - 1; var ranges = []; for (var i = 0; i < height; i++) { ranges.push({ anchor: new Pos(top + i, fromCh), head: new Pos(top + i, toCh) }); } return { ranges: ranges, primary: primary }; } } function getHead(cm) { var cur = cm.getCursor('head'); if (cm.getSelection().length == 1) { // Small corner case when only 1 character is selected. The "real" // head is the left of head and anchor. cur = cursorMin(cur, cm.getCursor('anchor')); } return cur; } /** * If moveHead is set to false, the CodeMirror selection will not be * touched. The caller assumes the responsibility of putting the cursor * in the right place. */ function exitVisualMode(cm, moveHead) { var vim = cm.state.vim; if (moveHead !== false) { cm.setCursor(clipCursorToContent(cm, vim.sel.head)); } updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false; vim.visualBlock = false; if (!vim.insertMode) CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); } // Remove any trailing newlines from the selection. For // example, with the caret at the start of the last word on the line, // 'dw' should word, but not the newline, while 'w' should advance the // caret to the first character of the next line. function clipToLine(cm, curStart, curEnd) { var selection = cm.getRange(curStart, curEnd); // Only clip if the selection ends with trailing newline + whitespace if (/\n\s*$/.test(selection)) { var lines = selection.split('\n'); // We know this is all whitespace. lines.pop(); // Cases: // 1. Last word is an empty line - do not clip the trailing '\n' // 2. Last word is not an empty line - clip the trailing '\n' var line; // Find the line containing the last word, and clip all whitespace up // to it. for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { curEnd.line--; curEnd.ch = 0; } // If the last word is not an empty line, clip an additional newline if (line) { curEnd.line--; curEnd.ch = lineLength(cm, curEnd.line); } else { curEnd.ch = 0; } } } // Expand the selection to line ends. function expandSelectionToLine(_cm, curStart, curEnd) { curStart.ch = 0; curEnd.ch = 0; curEnd.line++; } function findFirstNonWhiteSpaceCharacter(text) { if (!text) { return 0; } var firstNonWS = text.search(/\S/); return firstNonWS == -1 ? text.length : firstNonWS; } function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) { var cur = getHead(cm); var line = cm.getLine(cur.line); var idx = cur.ch; // Seek to first word or non-whitespace character, depending on if // noSymbol is true. var test = noSymbol ? wordCharTest[0] : bigWordCharTest [0]; while (!test(line.charAt(idx))) { idx++; if (idx >= line.length) { return null; } } if (bigWord) { test = bigWordCharTest[0]; } else { test = wordCharTest[0]; if (!test(line.charAt(idx))) { test = wordCharTest[1]; } } var end = idx, start = idx; while (test(line.charAt(end)) && end < line.length) { end++; } while (test(line.charAt(start)) && start >= 0) { start--; } start++; if (inclusive) { // If present, include all whitespace after word. // Otherwise, include all whitespace before word, except indentation. var wordEnd = end; while (/\s/.test(line.charAt(end)) && end < line.length) { end++; } if (wordEnd == end) { var wordStart = start; while (/\s/.test(line.charAt(start - 1)) && start > 0) { start--; } if (!start) { start = wordStart; } } } return { start: new Pos(cur.line, start), end: new Pos(cur.line, end) }; } /** * Depends on the following: * * - editor mode should be htmlmixedmode / xml * - mode/xml/xml.js should be loaded * - addon/fold/xml-fold.js should be loaded * * If any of the above requirements are not true, this function noops. * * This is _NOT_ a 100% accurate implementation of vim tag text objects. * The following caveats apply (based off cursory testing, I'm sure there * are other discrepancies): * * - Does not work inside comments: * ``` * * ``` * - Does not work when tags have different cases: * ``` *
    broken
    * ``` * - Does not work when cursor is inside a broken tag: * ``` *
    * ``` */ function expandTagUnderCursor(cm, head, inclusive) { var cur = head; if (!CodeMirror.findMatchingTag || !CodeMirror.findEnclosingTag) { return { start: cur, end: cur }; } var tags = CodeMirror.findMatchingTag(cm, head) || CodeMirror.findEnclosingTag(cm, head); if (!tags || !tags.open || !tags.close) { return { start: cur, end: cur }; } if (inclusive) { return { start: tags.open.from, end: tags.close.to }; } return { start: tags.open.to, end: tags.close.from }; } function recordJumpPosition(cm, oldCur, newCur) { if (!cursorEqual(oldCur, newCur)) { vimGlobalState.jumpList.add(cm, oldCur, newCur); } } function recordLastCharacterSearch(increment, args) { vimGlobalState.lastCharacterSearch.increment = increment; vimGlobalState.lastCharacterSearch.forward = args.forward; vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter; } var symbolToMode = { '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', '[': 'section', ']': 'section', '*': 'comment', '/': 'comment', 'm': 'method', 'M': 'method', '#': 'preprocess' }; var findSymbolModes = { bracket: { isComplete: function(state) { if (state.nextCh === state.symb) { state.depth++; if (state.depth >= 1)return true; } else if (state.nextCh === state.reverseSymb) { state.depth--; } return false; } }, section: { init: function(state) { state.curMoveThrough = true; state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; }, isComplete: function(state) { return state.index === 0 && state.nextCh === state.symb; } }, comment: { isComplete: function(state) { var found = state.lastCh === '*' && state.nextCh === '/'; state.lastCh = state.nextCh; return found; } }, // TODO: The original Vim implementation only operates on level 1 and 2. // The current implementation doesn't check for code block level and // therefore it operates on any levels. method: { init: function(state) { state.symb = (state.symb === 'm' ? '{' : '}'); state.reverseSymb = state.symb === '{' ? '}' : '{'; }, isComplete: function(state) { if (state.nextCh === state.symb)return true; return false; } }, preprocess: { init: function(state) { state.index = 0; }, isComplete: function(state) { if (state.nextCh === '#') { var token = state.lineText.match(/^#(\w+)/)[1]; if (token === 'endif') { if (state.forward && state.depth === 0) { return true; } state.depth++; } else if (token === 'if') { if (!state.forward && state.depth === 0) { return true; } state.depth--; } if (token === 'else' && state.depth === 0)return true; } return false; } } }; function findSymbol(cm, repeat, forward, symb) { var cur = copyCursor(cm.getCursor()); var increment = forward ? 1 : -1; var endLine = forward ? cm.lineCount() : -1; var curCh = cur.ch; var line = cur.line; var lineText = cm.getLine(line); var state = { lineText: lineText, nextCh: lineText.charAt(curCh), lastCh: null, index: curCh, symb: symb, reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], forward: forward, depth: 0, curMoveThrough: false }; var mode = symbolToMode[symb]; if (!mode)return cur; var init = findSymbolModes[mode].init; var isComplete = findSymbolModes[mode].isComplete; if (init) { init(state); } while (line !== endLine && repeat) { state.index += increment; state.nextCh = state.lineText.charAt(state.index); if (!state.nextCh) { line += increment; state.lineText = cm.getLine(line) || ''; if (increment > 0) { state.index = 0; } else { var lineLen = state.lineText.length; state.index = (lineLen > 0) ? (lineLen-1) : 0; } state.nextCh = state.lineText.charAt(state.index); } if (isComplete(state)) { cur.line = line; cur.ch = state.index; repeat--; } } if (state.nextCh || state.curMoveThrough) { return new Pos(line, state.index); } return cur; } /* * Returns the boundaries of the next word. If the cursor in the middle of * the word, then returns the boundaries of the current word, starting at * the cursor. If the cursor is at the start/end of a word, and we are going * forward/backward, respectively, find the boundaries of the next word. * * @param {CodeMirror} cm CodeMirror object. * @param {Cursor} cur The cursor position. * @param {boolean} forward True to search forward. False to search * backward. * @param {boolean} bigWord True if punctuation count as part of the word. * False if only [a-zA-Z0-9] characters count as part of the word. * @param {boolean} emptyLineIsWord True if empty lines should be treated * as words. * @return {Object{from:number, to:number, line: number}} The boundaries of * the word, or null if there are no more words. */ function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { var lineNum = cur.line; var pos = cur.ch; var line = cm.getLine(lineNum); var dir = forward ? 1 : -1; var charTests = bigWord ? bigWordCharTest: wordCharTest; if (emptyLineIsWord && line == '') { lineNum += dir; line = cm.getLine(lineNum); if (!isLine(cm, lineNum)) { return null; } pos = (forward) ? 0 : line.length; } while (true) { if (emptyLineIsWord && line == '') { return { from: 0, to: 0, line: lineNum }; } var stop = (dir > 0) ? line.length : -1; var wordStart = stop, wordEnd = stop; // Find bounds of next word. while (pos != stop) { var foundWord = false; for (var i = 0; i < charTests.length && !foundWord; ++i) { if (charTests[i](line.charAt(pos))) { wordStart = pos; // Advance to end of word. while (pos != stop && charTests[i](line.charAt(pos))) { pos += dir; } wordEnd = pos; foundWord = wordStart != wordEnd; if (wordStart == cur.ch && lineNum == cur.line && wordEnd == wordStart + dir) { // We started at the end of a word. Find the next one. continue; } else { return { from: Math.min(wordStart, wordEnd + 1), to: Math.max(wordStart, wordEnd), line: lineNum }; } } } if (!foundWord) { pos += dir; } } // Advance to next/prev line. lineNum += dir; if (!isLine(cm, lineNum)) { return null; } line = cm.getLine(lineNum); pos = (dir > 0) ? 0 : line.length; } } /** * @param {CodeMirror} cm CodeMirror object. * @param {Pos} cur The position to start from. * @param {int} repeat Number of words to move past. * @param {boolean} forward True to search forward. False to search * backward. * @param {boolean} wordEnd True to move to end of word. False to move to * beginning of word. * @param {boolean} bigWord True if punctuation count as part of the word. * False if only alphabet characters count as part of the word. * @return {Cursor} The position the cursor should move to. */ function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) { var curStart = copyCursor(cur); var words = []; if (forward && !wordEnd || !forward && wordEnd) { repeat++; } // For 'e', empty lines are not considered words, go figure. var emptyLineIsWord = !(forward && wordEnd); for (var i = 0; i < repeat; i++) { var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); if (!word) { var eodCh = lineLength(cm, cm.lastLine()); words.push(forward ? {line: cm.lastLine(), from: eodCh, to: eodCh} : {line: 0, from: 0, to: 0}); break; } words.push(word); cur = new Pos(word.line, forward ? (word.to - 1) : word.from); } var shortCircuit = words.length != repeat; var firstWord = words[0]; var lastWord = words.pop(); if (forward && !wordEnd) { // w if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { // We did not start in the middle of a word. Discard the extra word at the end. lastWord = words.pop(); } return new Pos(lastWord.line, lastWord.from); } else if (forward && wordEnd) { return new Pos(lastWord.line, lastWord.to - 1); } else if (!forward && wordEnd) { // ge if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { // We did not start in the middle of a word. Discard the extra word at the end. lastWord = words.pop(); } return new Pos(lastWord.line, lastWord.to); } else { // b return new Pos(lastWord.line, lastWord.from); } } function moveToEol(cm, head, motionArgs, vim, keepHPos) { var cur = head; var retval= new Pos(cur.line + motionArgs.repeat - 1, Infinity); var end=cm.clipPos(retval); end.ch--; if (!keepHPos) { vim.lastHPos = Infinity; vim.lastHSPos = cm.charCoords(end,'div').left; } return retval; } function moveToCharacter(cm, repeat, forward, character) { var cur = cm.getCursor(); var start = cur.ch; var idx; for (var i = 0; i < repeat; i ++) { var line = cm.getLine(cur.line); idx = charIdxInLine(start, line, character, forward, true); if (idx == -1) { return null; } start = idx; } return new Pos(cm.getCursor().line, idx); } function moveToColumn(cm, repeat) { // repeat is always >= 1, so repeat - 1 always corresponds // to the column we want to go to. var line = cm.getCursor().line; return clipCursorToContent(cm, new Pos(line, repeat - 1)); } function updateMark(cm, vim, markName, pos) { if (!inArray(markName, validMarks)) { return; } if (vim.marks[markName]) { vim.marks[markName].clear(); } vim.marks[markName] = cm.setBookmark(pos); } function charIdxInLine(start, line, character, forward, includeChar) { // Search for char in line. // motion_options: {forward, includeChar} // If includeChar = true, include it too. // If forward = true, search forward, else search backwards. // If char is not found on this line, do nothing var idx; if (forward) { idx = line.indexOf(character, start + 1); if (idx != -1 && !includeChar) { idx -= 1; } } else { idx = line.lastIndexOf(character, start - 1); if (idx != -1 && !includeChar) { idx += 1; } } return idx; } function findParagraph(cm, head, repeat, dir, inclusive) { var line = head.line; var min = cm.firstLine(); var max = cm.lastLine(); var start, end, i = line; function isEmpty(i) { return !cm.getLine(i); } function isBoundary(i, dir, any) { if (any) { return isEmpty(i) != isEmpty(i + dir); } return !isEmpty(i) && isEmpty(i + dir); } if (dir) { while (min <= i && i <= max && repeat > 0) { if (isBoundary(i, dir)) { repeat--; } i += dir; } return new Pos(i, 0); } var vim = cm.state.vim; if (vim.visualLine && isBoundary(line, 1, true)) { var anchor = vim.sel.anchor; if (isBoundary(anchor.line, -1, true)) { if (!inclusive || anchor.line != line) { line += 1; } } } var startState = isEmpty(line); for (i = line; i <= max && repeat; i++) { if (isBoundary(i, 1, true)) { if (!inclusive || isEmpty(i) != startState) { repeat--; } } } end = new Pos(i, 0); // select boundary before paragraph for the last one if (i > max && !startState) { startState = true; } else { inclusive = false; } for (i = line; i > min; i--) { if (!inclusive || isEmpty(i) == startState || i == line) { if (isBoundary(i, -1, true)) { break; } } } start = new Pos(i, 0); return { start: start, end: end }; } function getSentence(cm, cur, repeat, dir, inclusive /*includes whitespace*/) { /* Takes an index object { line: the line string, ln: line number, pos: index in line, dir: direction of traversal (-1 or 1) } and modifies the pos member to represent the next valid position or sets the line to null if there are no more valid positions. */ function nextChar(curr) { if (curr.pos + curr.dir < 0 || curr.pos + curr.dir >= curr.line.length) { curr.line = null; } else { curr.pos += curr.dir; } } /* Performs one iteration of traversal in forward direction Returns an index object of the new location */ function forward(cm, ln, pos, dir) { var line = cm.getLine(ln); var curr = { line: line, ln: ln, pos: pos, dir: dir, }; if (curr.line === "") { return { ln: curr.ln, pos: curr.pos }; } var lastSentencePos = curr.pos; // Move one step to skip character we start on nextChar(curr); while (curr.line !== null) { lastSentencePos = curr.pos; if (isEndOfSentenceSymbol(curr.line[curr.pos])) { if (!inclusive) { return { ln: curr.ln, pos: curr.pos + 1 }; } else { nextChar(curr); while (curr.line !== null ) { if (isWhiteSpaceString(curr.line[curr.pos])) { lastSentencePos = curr.pos; nextChar(curr); } else { break; } } return { ln: curr.ln, pos: lastSentencePos + 1, }; } } nextChar(curr); } return { ln: curr.ln, pos: lastSentencePos + 1 }; } /* Performs one iteration of traversal in reverse direction Returns an index object of the new location */ function reverse(cm, ln, pos, dir) { var line = cm.getLine(ln); var curr = { line: line, ln: ln, pos: pos, dir: dir, }; if (curr.line === "") { return { ln: curr.ln, pos: curr.pos }; } var lastSentencePos = curr.pos; // Move one step to skip character we start on nextChar(curr); while (curr.line !== null) { if (!isWhiteSpaceString(curr.line[curr.pos]) && !isEndOfSentenceSymbol(curr.line[curr.pos])) { lastSentencePos = curr.pos; } else if (isEndOfSentenceSymbol(curr.line[curr.pos]) ) { if (!inclusive) { return { ln: curr.ln, pos: lastSentencePos }; } else { if (isWhiteSpaceString(curr.line[curr.pos + 1])) { return { ln: curr.ln, pos: curr.pos + 1, }; } else { return {ln: curr.ln, pos: lastSentencePos}; } } } nextChar(curr); } curr.line = line; if (inclusive && isWhiteSpaceString(curr.line[curr.pos])) { return { ln: curr.ln, pos: curr.pos }; } else { return { ln: curr.ln, pos: lastSentencePos }; } } var curr_index = { ln: cur.line, pos: cur.ch, }; while (repeat > 0) { if (dir < 0) { curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); } else { curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); } repeat--; } return new Pos(curr_index.ln, curr_index.pos); } function findSentence(cm, cur, repeat, dir) { /* Takes an index object { line: the line string, ln: line number, pos: index in line, dir: direction of traversal (-1 or 1) } and modifies the line, ln, and pos members to represent the next valid position or sets them to null if there are no more valid positions. */ function nextChar(cm, idx) { if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) { idx.ln += idx.dir; if (!isLine(cm, idx.ln)) { idx.line = null; idx.ln = null; idx.pos = null; return; } idx.line = cm.getLine(idx.ln); idx.pos = (idx.dir > 0) ? 0 : idx.line.length - 1; } else { idx.pos += idx.dir; } } /* Performs one iteration of traversal in forward direction Returns an index object of the new location */ function forward(cm, ln, pos, dir) { var line = cm.getLine(ln); var stop = (line === ""); var curr = { line: line, ln: ln, pos: pos, dir: dir, }; var last_valid = { ln: curr.ln, pos: curr.pos, }; var skip_empty_lines = (curr.line === ""); // Move one step to skip character we start on nextChar(cm, curr); while (curr.line !== null) { last_valid.ln = curr.ln; last_valid.pos = curr.pos; if (curr.line === "" && !skip_empty_lines) { return { ln: curr.ln, pos: curr.pos, }; } else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { return { ln: curr.ln, pos: curr.pos, }; } else if (isEndOfSentenceSymbol(curr.line[curr.pos]) && !stop && (curr.pos === curr.line.length - 1 || isWhiteSpaceString(curr.line[curr.pos + 1]))) { stop = true; } nextChar(cm, curr); } /* Set the position to the last non whitespace character on the last valid line in the case that we reach the end of the document. */ var line = cm.getLine(last_valid.ln); last_valid.pos = 0; for(var i = line.length - 1; i >= 0; --i) { if (!isWhiteSpaceString(line[i])) { last_valid.pos = i; break; } } return last_valid; } /* Performs one iteration of traversal in reverse direction Returns an index object of the new location */ function reverse(cm, ln, pos, dir) { var line = cm.getLine(ln); var curr = { line: line, ln: ln, pos: pos, dir: dir, }; var last_valid = { ln: curr.ln, pos: null, }; var skip_empty_lines = (curr.line === ""); // Move one step to skip character we start on nextChar(cm, curr); while (curr.line !== null) { if (curr.line === "" && !skip_empty_lines) { if (last_valid.pos !== null) { return last_valid; } else { return { ln: curr.ln, pos: curr.pos }; } } else if (isEndOfSentenceSymbol(curr.line[curr.pos]) && last_valid.pos !== null && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) { return last_valid; } else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { skip_empty_lines = false; last_valid = { ln: curr.ln, pos: curr.pos }; } nextChar(cm, curr); } /* Set the position to the first non whitespace character on the last valid line in the case that we reach the beginning of the document. */ var line = cm.getLine(last_valid.ln); last_valid.pos = 0; for(var i = 0; i < line.length; ++i) { if (!isWhiteSpaceString(line[i])) { last_valid.pos = i; break; } } return last_valid; } var curr_index = { ln: cur.line, pos: cur.ch, }; while (repeat > 0) { if (dir < 0) { curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); } else { curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); } repeat--; } return new Pos(curr_index.ln, curr_index.pos); } // TODO: perhaps this finagling of start and end positions belongs // in codemirror/replaceRange? function selectCompanionObject(cm, head, symb, inclusive) { var cur = head, start, end; var bracketRegexp = ({ '(': /[()]/, ')': /[()]/, '[': /[[\]]/, ']': /[[\]]/, '{': /[{}]/, '}': /[{}]/, '<': /[<>]/, '>': /[<>]/})[symb]; var openSym = ({ '(': '(', ')': '(', '[': '[', ']': '[', '{': '{', '}': '{', '<': '<', '>': '<'})[symb]; var curChar = cm.getLine(cur.line).charAt(cur.ch); // Due to the behavior of scanForBracket, we need to add an offset if the // cursor is on a matching open bracket. var offset = curChar === openSym ? 1 : 0; start = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), -1, undefined, {'bracketRegex': bracketRegexp}); end = cm.scanForBracket(new Pos(cur.line, cur.ch + offset), 1, undefined, {'bracketRegex': bracketRegexp}); if (!start || !end) { return { start: cur, end: cur }; } start = start.pos; end = end.pos; if ((start.line == end.line && start.ch > end.ch) || (start.line > end.line)) { var tmp = start; start = end; end = tmp; } if (inclusive) { end.ch += 1; } else { start.ch += 1; } return { start: start, end: end }; } // Takes in a symbol and a cursor and tries to simulate text objects that // have identical opening and closing symbols // TODO support across multiple lines function findBeginningAndEnd(cm, head, symb, inclusive) { var cur = copyCursor(head); var line = cm.getLine(cur.line); var chars = line.split(''); var start, end, i, len; var firstIndex = chars.indexOf(symb); // the decision tree is to always look backwards for the beginning first, // but if the cursor is in front of the first instance of the symb, // then move the cursor forward if (cur.ch < firstIndex) { cur.ch = firstIndex; // Why is this line even here??? // cm.setCursor(cur.line, firstIndex+1); } // otherwise if the cursor is currently on the closing symbol else if (firstIndex < cur.ch && chars[cur.ch] == symb) { end = cur.ch; // assign end to the current cursor --cur.ch; // make sure to look backwards } // if we're currently on the symbol, we've got a start if (chars[cur.ch] == symb && !end) { start = cur.ch + 1; // assign start to ahead of the cursor } else { // go backwards to find the start for (i = cur.ch; i > -1 && !start; i--) { if (chars[i] == symb) { start = i + 1; } } } // look forwards for the end symbol if (start && !end) { for (i = start, len = chars.length; i < len && !end; i++) { if (chars[i] == symb) { end = i; } } } // nothing found if (!start || !end) { return { start: cur, end: cur }; } // include the symbols if (inclusive) { --start; ++end; } return { start: new Pos(cur.line, start), end: new Pos(cur.line, end) }; } // Search functions defineOption('pcre', true, 'boolean'); function SearchState() {} SearchState.prototype = { getQuery: function() { return vimGlobalState.query; }, setQuery: function(query) { vimGlobalState.query = query; }, getOverlay: function() { return this.searchOverlay; }, setOverlay: function(overlay) { this.searchOverlay = overlay; }, isReversed: function() { return vimGlobalState.isReversed; }, setReversed: function(reversed) { vimGlobalState.isReversed = reversed; }, getScrollbarAnnotate: function() { return this.annotate; }, setScrollbarAnnotate: function(annotate) { this.annotate = annotate; } }; function getSearchState(cm) { var vim = cm.state.vim; return vim.searchState_ || (vim.searchState_ = new SearchState()); } function splitBySlash(argString) { return splitBySeparator(argString, '/'); } function findUnescapedSlashes(argString) { return findUnescapedSeparators(argString, '/'); } function splitBySeparator(argString, separator) { var slashes = findUnescapedSeparators(argString, separator) || []; if (!slashes.length) return []; var tokens = []; // in case of strings like foo/bar if (slashes[0] !== 0) return; for (var i = 0; i < slashes.length; i++) { if (typeof slashes[i] == 'number') tokens.push(argString.substring(slashes[i] + 1, slashes[i+1])); } return tokens; } function findUnescapedSeparators(str, separator) { if (!separator) separator = '/'; var escapeNextChar = false; var slashes = []; for (var i = 0; i < str.length; i++) { var c = str.charAt(i); if (!escapeNextChar && c == separator) { slashes.push(i); } escapeNextChar = !escapeNextChar && (c == '\\'); } return slashes; } // Translates a search string from ex (vim) syntax into javascript form. function translateRegex(str) { // When these match, add a '\' if unescaped or remove one if escaped. var specials = '|(){'; // Remove, but never add, a '\' for these. var unescape = '}'; var escapeNextChar = false; var out = []; for (var i = -1; i < str.length; i++) { var c = str.charAt(i) || ''; var n = str.charAt(i+1) || ''; var specialComesNext = (n && specials.indexOf(n) != -1); if (escapeNextChar) { if (c !== '\\' || !specialComesNext) { out.push(c); } escapeNextChar = false; } else { if (c === '\\') { escapeNextChar = true; // Treat the unescape list as special for removing, but not adding '\'. if (n && unescape.indexOf(n) != -1) { specialComesNext = true; } // Not passing this test means removing a '\'. if (!specialComesNext || n === '\\') { out.push(c); } } else { out.push(c); if (specialComesNext && n !== '\\') { out.push('\\'); } } } } return out.join(''); } // Translates the replace part of a search and replace from ex (vim) syntax into // javascript form. Similar to translateRegex, but additionally fixes back references // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'. var charUnescapes = {'\\n': '\n', '\\r': '\r', '\\t': '\t'}; function translateRegexReplace(str) { var escapeNextChar = false; var out = []; for (var i = -1; i < str.length; i++) { var c = str.charAt(i) || ''; var n = str.charAt(i+1) || ''; if (charUnescapes[c + n]) { out.push(charUnescapes[c+n]); i++; } else if (escapeNextChar) { // At any point in the loop, escapeNextChar is true if the previous // character was a '\' and was not escaped. out.push(c); escapeNextChar = false; } else { if (c === '\\') { escapeNextChar = true; if ((isNumber(n) || n === '$')) { out.push('$'); } else if (n !== '/' && n !== '\\') { out.push('\\'); } } else { if (c === '$') { out.push('$'); } out.push(c); if (n === '/') { out.push('\\'); } } } } return out.join(''); } // Unescape \ and / in the replace part, for PCRE mode. var unescapes = {'\\/': '/', '\\\\': '\\', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\&':'&'}; function unescapeRegexReplace(str) { var stream = new CodeMirror.StringStream(str); var output = []; while (!stream.eol()) { // Search for \. while (stream.peek() && stream.peek() != '\\') { output.push(stream.next()); } var matched = false; for (var matcher in unescapes) { if (stream.match(matcher, true)) { matched = true; output.push(unescapes[matcher]); break; } } if (!matched) { // Don't change anything output.push(stream.next()); } } return output.join(''); } /** * Extract the regular expression from the query and return a Regexp object. * Returns null if the query is blank. * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. * If smartCase is passed in, and the query contains upper case letters, * then ignoreCase is overridden, and the 'i' flag will not be set. * If the query contains the /i in the flag part of the regular expression, * then both ignoreCase and smartCase are ignored, and 'i' will be passed * through to the Regex object. */ function parseQuery(query, ignoreCase, smartCase) { // First update the last search register var lastSearchRegister = vimGlobalState.registerController.getRegister('/'); lastSearchRegister.setText(query); // Check if the query is already a regex. if (query instanceof RegExp) { return query; } // First try to extract regex + flags from the input. If no flags found, // extract just the regex. IE does not accept flags directly defined in // the regex string in the form /regex/flags var slashes = findUnescapedSlashes(query); var regexPart; var forceIgnoreCase; if (!slashes.length) { // Query looks like 'regexp' regexPart = query; } else { // Query looks like 'regexp/...' regexPart = query.substring(0, slashes[0]); var flagsPart = query.substring(slashes[0]); forceIgnoreCase = (flagsPart.indexOf('i') != -1); } if (!regexPart) { return null; } if (!getOption('pcre')) { regexPart = translateRegex(regexPart); } if (smartCase) { ignoreCase = (/^[^A-Z]*$/).test(regexPart); } var regexp = new RegExp(regexPart, (ignoreCase || forceIgnoreCase) ? 'im' : 'm'); return regexp; } /** * dom - Document Object Manipulator * Usage: * dom(''|[, ...{|<$styles>}||'']) * Examples: * dom('div', {id:'xyz'}, dom('p', 'CM rocks!', {$color:'red'})) * dom(document.head, dom('script', 'alert("hello!")')) * Not supported: * dom('p', ['arrays are objects'], Error('objects specify attributes')) */ function dom(n) { if (typeof n === 'string') n = document.createElement(n); for (var a, i = 1; i < arguments.length; i++) { if (!(a = arguments[i])) continue; if (typeof a !== 'object') a = document.createTextNode(a); if (a.nodeType) n.appendChild(a); else for (var key in a) { if (!Object.prototype.hasOwnProperty.call(a, key)) continue; if (key[0] === '$') n.style[key.slice(1)] = a[key]; else n.setAttribute(key, a[key]); } } return n; } function showConfirm(cm, template) { var pre = dom('div', {$color: 'red', $whiteSpace: 'pre', class: 'cm-vim-message'}, template); if (cm.openNotification) { cm.openNotification(pre, {bottom: true, duration: 5000}); } else { alert(pre.innerText); } } function makePrompt(prefix, desc) { return dom(document.createDocumentFragment(), dom('span', {$fontFamily: 'monospace', $whiteSpace: 'pre'}, prefix, dom('input', {type: 'text', autocorrect: 'off', autocapitalize: 'off', spellcheck: 'false'})), desc && dom('span', {$color: '#888'}, desc)); } function showPrompt(cm, options) { var template = makePrompt(options.prefix, options.desc); if (cm.openDialog) { cm.openDialog(template, options.onClose, { onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp, bottom: true, selectValueOnOpen: false, value: options.value }); } else { var shortText = ''; if (typeof options.prefix != "string" && options.prefix) shortText += options.prefix.textContent; if (options.desc) shortText += " " + options.desc; options.onClose(prompt(shortText, '')); } } function regexEqual(r1, r2) { if (r1 instanceof RegExp && r2 instanceof RegExp) { var props = ['global', 'multiline', 'ignoreCase', 'source']; for (var i = 0; i < props.length; i++) { var prop = props[i]; if (r1[prop] !== r2[prop]) { return false; } } return true; } return false; } // Returns true if the query is valid. function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { if (!rawQuery) { return; } var state = getSearchState(cm); var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); if (!query) { return; } highlightSearchMatches(cm, query); if (regexEqual(query, state.getQuery())) { return query; } state.setQuery(query); return query; } function searchOverlay(query) { if (query.source.charAt(0) == '^') { var matchSol = true; } return { token: function(stream) { if (matchSol && !stream.sol()) { stream.skipToEnd(); return; } var match = stream.match(query, false); if (match) { if (match[0].length == 0) { // Matched empty string, skip to next. stream.next(); return 'searching'; } if (!stream.sol()) { // Backtrack 1 to match \b stream.backUp(1); if (!query.exec(stream.next() + match[0])) { stream.next(); return null; } } stream.match(query); return 'searching'; } while (!stream.eol()) { stream.next(); if (stream.match(query, false)) break; } }, query: query }; } var highlightTimeout = 0; function highlightSearchMatches(cm, query) { clearTimeout(highlightTimeout); highlightTimeout = setTimeout(function() { if (!cm.state.vim) return; var searchState = getSearchState(cm); var overlay = searchState.getOverlay(); if (!overlay || query != overlay.query) { if (overlay) { cm.removeOverlay(overlay); } overlay = searchOverlay(query); cm.addOverlay(overlay); if (cm.showMatchesOnScrollbar) { if (searchState.getScrollbarAnnotate()) { searchState.getScrollbarAnnotate().clear(); } searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); } searchState.setOverlay(overlay); } }, 50); } function findNext(cm, prev, query, repeat) { if (repeat === undefined) { repeat = 1; } return cm.operation(function() { var pos = cm.getCursor(); var cursor = cm.getSearchCursor(query, pos); for (var i = 0; i < repeat; i++) { var found = cursor.find(prev); if (i == 0 && found && cursorEqual(cursor.from(), pos)) { var lastEndPos = prev ? cursor.from() : cursor.to(); found = cursor.find(prev); if (found && !found[0] && cursorEqual(cursor.from(), lastEndPos)) { if (cm.getLine(lastEndPos.line).length == lastEndPos.ch) found = cursor.find(prev); } } if (!found) { // SearchCursor may have returned null because it hit EOF, wrap // around and try again. cursor = cm.getSearchCursor(query, (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); if (!cursor.find(prev)) { return; } } } return cursor.from(); }); } /** * Pretty much the same as `findNext`, except for the following differences: * * 1. Before starting the search, move to the previous search. This way if our cursor is * already inside a match, we should return the current match. * 2. Rather than only returning the cursor's from, we return the cursor's from and to as a tuple. */ function findNextFromAndToInclusive(cm, prev, query, repeat, vim) { if (repeat === undefined) { repeat = 1; } return cm.operation(function() { var pos = cm.getCursor(); var cursor = cm.getSearchCursor(query, pos); // Go back one result to ensure that if the cursor is currently a match, we keep it. var found = cursor.find(!prev); // If we haven't moved, go back one more (similar to if i==0 logic in findNext). if (!vim.visualMode && found && cursorEqual(cursor.from(), pos)) { cursor.find(!prev); } for (var i = 0; i < repeat; i++) { found = cursor.find(prev); if (!found) { // SearchCursor may have returned null because it hit EOF, wrap // around and try again. cursor = cm.getSearchCursor(query, (prev) ? new Pos(cm.lastLine()) : new Pos(cm.firstLine(), 0) ); if (!cursor.find(prev)) { return; } } } return [cursor.from(), cursor.to()]; }); } function clearSearchHighlight(cm) { var state = getSearchState(cm); cm.removeOverlay(getSearchState(cm).getOverlay()); state.setOverlay(null); if (state.getScrollbarAnnotate()) { state.getScrollbarAnnotate().clear(); state.setScrollbarAnnotate(null); } } /** * Check if pos is in the specified range, INCLUSIVE. * Range can be specified with 1 or 2 arguments. * If the first range argument is an array, treat it as an array of line * numbers. Match pos against any of the lines. * If the first range argument is a number, * if there is only 1 range argument, check if pos has the same line * number * if there are 2 range arguments, then check if pos is in between the two * range arguments. */ function isInRange(pos, start, end) { if (typeof pos != 'number') { // Assume it is a cursor position. Get the line number. pos = pos.line; } if (start instanceof Array) { return inArray(pos, start); } else { if (typeof end == 'number') { return (pos >= start && pos <= end); } else { return pos == start; } } } function getUserVisibleLines(cm) { var scrollInfo = cm.getScrollInfo(); var occludeToleranceTop = 6; var occludeToleranceBottom = 10; var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; var to = cm.coordsChar({left:0, top: bottomY}, 'local'); return {top: from.line, bottom: to.line}; } function getMarkPos(cm, vim, markName) { if (markName == '\'' || markName == '`') { return vimGlobalState.jumpList.find(cm, -1) || new Pos(0, 0); } else if (markName == '.') { return getLastEditPos(cm); } var mark = vim.marks[markName]; return mark && mark.find(); } function getLastEditPos(cm) { var done = cm.doc.history.done; for (var i = done.length; i--;) { if (done[i].changes) { return copyCursor(done[i].changes[0].to); } } } var ExCommandDispatcher = function() { this.buildCommandMap_(); }; ExCommandDispatcher.prototype = { processCommand: function(cm, input, opt_params) { var that = this; cm.operation(function () { cm.curOp.isVimOp = true; that._processCommand(cm, input, opt_params); }); }, _processCommand: function(cm, input, opt_params) { var vim = cm.state.vim; var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); var previousCommand = commandHistoryRegister.toString(); if (vim.visualMode) { exitVisualMode(cm); } var inputStream = new CodeMirror.StringStream(input); // update ": with the latest command whether valid or invalid commandHistoryRegister.setText(input); var params = opt_params || {}; params.input = input; try { this.parseInput_(cm, inputStream, params); } catch(e) { showConfirm(cm, e.toString()); throw e; } var command; var commandName; if (!params.commandName) { // If only a line range is defined, move to the line. if (params.line !== undefined) { commandName = 'move'; } } else { command = this.matchCommand_(params.commandName); if (command) { commandName = command.name; if (command.excludeFromCommandHistory) { commandHistoryRegister.setText(previousCommand); } this.parseCommandArgs_(inputStream, params, command); if (command.type == 'exToKey') { // Handle Ex to Key mapping. for (var i = 0; i < command.toKeys.length; i++) { vimApi.handleKey(cm, command.toKeys[i], 'mapping'); } return; } else if (command.type == 'exToEx') { // Handle Ex to Ex mapping. this.processCommand(cm, command.toInput); return; } } } if (!commandName) { showConfirm(cm, 'Not an editor command ":' + input + '"'); return; } try { exCommands[commandName](cm, params); // Possibly asynchronous commands (e.g. substitute, which might have a // user confirmation), are responsible for calling the callback when // done. All others have it taken care of for them here. if ((!command || !command.possiblyAsync) && params.callback) { params.callback(); } } catch(e) { showConfirm(cm, e.toString()); throw e; } }, parseInput_: function(cm, inputStream, result) { inputStream.eatWhile(':'); // Parse range. if (inputStream.eat('%')) { result.line = cm.firstLine(); result.lineEnd = cm.lastLine(); } else { result.line = this.parseLineSpec_(cm, inputStream); if (result.line !== undefined && inputStream.eat(',')) { result.lineEnd = this.parseLineSpec_(cm, inputStream); } } // Parse command name. var commandMatch = inputStream.match(/^(\w+|!!|@@|[!#&*<=>@~])/); if (commandMatch) { result.commandName = commandMatch[1]; } else { result.commandName = inputStream.match(/.*/)[0]; } return result; }, parseLineSpec_: function(cm, inputStream) { var numberMatch = inputStream.match(/^(\d+)/); if (numberMatch) { // Absolute line number plus offset (N+M or N-M) is probably a typo, // not something the user actually wanted. (NB: vim does allow this.) return parseInt(numberMatch[1], 10) - 1; } switch (inputStream.next()) { case '.': return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); case '$': return this.parseLineSpecOffset_(inputStream, cm.lastLine()); case '\'': var markName = inputStream.next(); var markPos = getMarkPos(cm, cm.state.vim, markName); if (!markPos) throw new Error('Mark not set'); return this.parseLineSpecOffset_(inputStream, markPos.line); case '-': case '+': inputStream.backUp(1); // Offset is relative to current line if not otherwise specified. return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); default: inputStream.backUp(1); return undefined; } }, parseLineSpecOffset_: function(inputStream, line) { var offsetMatch = inputStream.match(/^([+-])?(\d+)/); if (offsetMatch) { var offset = parseInt(offsetMatch[2], 10); if (offsetMatch[1] == "-") { line -= offset; } else { line += offset; } } return line; }, parseCommandArgs_: function(inputStream, params, command) { if (inputStream.eol()) { return; } params.argString = inputStream.match(/.*/)[0]; // Parse command-line arguments var delim = command.argDelimiter || /\s+/; var args = trim(params.argString).split(delim); if (args.length && args[0]) { params.args = args; } }, matchCommand_: function(commandName) { // Return the command in the command map that matches the shortest // prefix of the passed in command name. The match is guaranteed to be // unambiguous if the defaultExCommandMap's shortNames are set up // correctly. (see @code{defaultExCommandMap}). for (var i = commandName.length; i > 0; i--) { var prefix = commandName.substring(0, i); if (this.commandMap_[prefix]) { var command = this.commandMap_[prefix]; if (command.name.indexOf(commandName) === 0) { return command; } } } return null; }, buildCommandMap_: function() { this.commandMap_ = {}; for (var i = 0; i < defaultExCommandMap.length; i++) { var command = defaultExCommandMap[i]; var key = command.shortName || command.name; this.commandMap_[key] = command; } }, map: function(lhs, rhs, ctx) { if (lhs != ':' && lhs.charAt(0) == ':') { if (ctx) { throw Error('Mode not supported for ex mappings'); } var commandName = lhs.substring(1); if (rhs != ':' && rhs.charAt(0) == ':') { // Ex to Ex mapping this.commandMap_[commandName] = { name: commandName, type: 'exToEx', toInput: rhs.substring(1), user: true }; } else { // Ex to key mapping this.commandMap_[commandName] = { name: commandName, type: 'exToKey', toKeys: rhs, user: true }; } } else { if (rhs != ':' && rhs.charAt(0) == ':') { // Key to Ex mapping. var mapping = { keys: lhs, type: 'keyToEx', exArgs: { input: rhs.substring(1) } }; if (ctx) { mapping.context = ctx; } defaultKeymap.unshift(mapping); } else { // Key to key mapping var mapping = { keys: lhs, type: 'keyToKey', toKeys: rhs }; if (ctx) { mapping.context = ctx; } defaultKeymap.unshift(mapping); } } }, unmap: function(lhs, ctx) { if (lhs != ':' && lhs.charAt(0) == ':') { // Ex to Ex or Ex to key mapping if (ctx) { throw Error('Mode not supported for ex mappings'); } var commandName = lhs.substring(1); if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { delete this.commandMap_[commandName]; return true; } } else { // Key to Ex or key to key mapping var keys = lhs; for (var i = 0; i < defaultKeymap.length; i++) { if (keys == defaultKeymap[i].keys && defaultKeymap[i].context === ctx) { defaultKeymap.splice(i, 1); return true; } } } } }; var exCommands = { colorscheme: function(cm, params) { if (!params.args || params.args.length < 1) { showConfirm(cm, cm.getOption('theme')); return; } cm.setOption('theme', params.args[0]); }, map: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 2) { if (cm) { showConfirm(cm, 'Invalid mapping: ' + params.input); } return; } exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); }, imap: function(cm, params) { this.map(cm, params, 'insert'); }, nmap: function(cm, params) { this.map(cm, params, 'normal'); }, vmap: function(cm, params) { this.map(cm, params, 'visual'); }, unmap: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 1 || !exCommandDispatcher.unmap(mapArgs[0], ctx)) { if (cm) { showConfirm(cm, 'No such mapping: ' + params.input); } } }, move: function(cm, params) { commandDispatcher.processCommand(cm, cm.state.vim, { type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true }, repeatOverride: params.line+1}); }, set: function(cm, params) { var setArgs = params.args; // Options passed through to the setOption/getOption calls. May be passed in by the // local/global versions of the set command var setCfg = params.setCfg || {}; if (!setArgs || setArgs.length < 1) { if (cm) { showConfirm(cm, 'Invalid mapping: ' + params.input); } return; } var expr = setArgs[0].split('='); var optionName = expr[0]; var value = expr[1]; var forceGet = false; if (optionName.charAt(optionName.length - 1) == '?') { // If post-fixed with ?, then the set is actually a get. if (value) { throw Error('Trailing characters: ' + params.argString); } optionName = optionName.substring(0, optionName.length - 1); forceGet = true; } if (value === undefined && optionName.substring(0, 2) == 'no') { // To set boolean options to false, the option name is prefixed with // 'no'. optionName = optionName.substring(2); value = false; } var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; if (optionIsBoolean && value == undefined) { // Calling set with a boolean option sets it to true. value = true; } // If no value is provided, then we assume this is a get. if (!optionIsBoolean && value === undefined || forceGet) { var oldValue = getOption(optionName, cm, setCfg); if (oldValue instanceof Error) { showConfirm(cm, oldValue.message); } else if (oldValue === true || oldValue === false) { showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); } else { showConfirm(cm, ' ' + optionName + '=' + oldValue); } } else { var setOptionReturn = setOption(optionName, value, cm, setCfg); if (setOptionReturn instanceof Error) { showConfirm(cm, setOptionReturn.message); } } }, setlocal: function (cm, params) { // setCfg is passed through to setOption params.setCfg = {scope: 'local'}; this.set(cm, params); }, setglobal: function (cm, params) { // setCfg is passed through to setOption params.setCfg = {scope: 'global'}; this.set(cm, params); }, registers: function(cm, params) { var regArgs = params.args; var registers = vimGlobalState.registerController.registers; var regInfo = '----------Registers----------\n\n'; if (!regArgs) { for (var registerName in registers) { var text = registers[registerName].toString(); if (text.length) { regInfo += '"' + registerName + ' ' + text + '\n'; } } } else { var registerName; regArgs = regArgs.join(''); for (var i = 0; i < regArgs.length; i++) { registerName = regArgs.charAt(i); if (!vimGlobalState.registerController.isValidRegister(registerName)) { continue; } var register = registers[registerName] || new Register(); regInfo += '"' + registerName + ' ' + register.toString() + '\n'; } } showConfirm(cm, regInfo); }, sort: function(cm, params) { var reverse, ignoreCase, unique, number, pattern; function parseArgs() { if (params.argString) { var args = new CodeMirror.StringStream(params.argString); if (args.eat('!')) { reverse = true; } if (args.eol()) { return; } if (!args.eatSpace()) { return 'Invalid arguments'; } var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/); if (!opts && !args.eol()) { return 'Invalid arguments'; } if (opts[1]) { ignoreCase = opts[1].indexOf('i') != -1; unique = opts[1].indexOf('u') != -1; var decimal = opts[1].indexOf('d') != -1 || opts[1].indexOf('n') != -1 && 1; var hex = opts[1].indexOf('x') != -1 && 1; var octal = opts[1].indexOf('o') != -1 && 1; if (decimal + hex + octal > 1) { return 'Invalid arguments'; } number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; } if (opts[2]) { pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? 'i' : ''); } } } var err = parseArgs(); if (err) { showConfirm(cm, err + ': ' + params.argString); return; } var lineStart = params.line || cm.firstLine(); var lineEnd = params.lineEnd || params.line || cm.lastLine(); if (lineStart == lineEnd) { return; } var curStart = new Pos(lineStart, 0); var curEnd = new Pos(lineEnd, lineLength(cm, lineEnd)); var text = cm.getRange(curStart, curEnd).split('\n'); var numberRegex = pattern ? pattern : (number == 'decimal') ? /(-?)([\d]+)/ : (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : (number == 'octal') ? /([0-7]+)/ : null; var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null; var numPart = [], textPart = []; if (number || pattern) { for (var i = 0; i < text.length; i++) { var matchPart = pattern ? text[i].match(pattern) : null; if (matchPart && matchPart[0] != '') { numPart.push(matchPart); } else if (!pattern && numberRegex.exec(text[i])) { numPart.push(text[i]); } else { textPart.push(text[i]); } } } else { textPart = text; } function compareFn(a, b) { if (reverse) { var tmp; tmp = a; a = b; b = tmp; } if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } var anum = number && numberRegex.exec(a); var bnum = number && numberRegex.exec(b); if (!anum) { return a < b ? -1 : 1; } anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); return anum - bnum; } function comparePatternFn(a, b) { if (reverse) { var tmp; tmp = a; a = b; b = tmp; } if (ignoreCase) { a[0] = a[0].toLowerCase(); b[0] = b[0].toLowerCase(); } return (a[0] < b[0]) ? -1 : 1; } numPart.sort(pattern ? comparePatternFn : compareFn); if (pattern) { for (var i = 0; i < numPart.length; i++) { numPart[i] = numPart[i].input; } } else if (!number) { textPart.sort(compareFn); } text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); if (unique) { // Remove duplicate lines var textOld = text; var lastLine; text = []; for (var i = 0; i < textOld.length; i++) { if (textOld[i] != lastLine) { text.push(textOld[i]); } lastLine = textOld[i]; } } cm.replaceRange(text.join('\n'), curStart, curEnd); }, vglobal: function(cm, params) { // global inspects params.commandName this.global(cm, params); }, global: function(cm, params) { // a global command is of the form // :[range]g/pattern/[cmd] // argString holds the string /pattern/[cmd] var argString = params.argString; if (!argString) { showConfirm(cm, 'Regular Expression missing from global'); return; } var inverted = params.commandName[0] === 'v'; // range is specified here var lineStart = (params.line !== undefined) ? params.line : cm.firstLine(); var lineEnd = params.lineEnd || params.line || cm.lastLine(); // get the tokens from argString var tokens = splitBySlash(argString); var regexPart = argString, cmd; if (tokens.length) { regexPart = tokens[0]; cmd = tokens.slice(1, tokens.length).join('/'); } if (regexPart) { // If regex part is empty, then use the previous query. Otherwise // use the regex part as the new query. try { updateSearchQuery(cm, regexPart, true /** ignoreCase */, true /** smartCase */); } catch (e) { showConfirm(cm, 'Invalid regex: ' + regexPart); return; } } // now that we have the regexPart, search for regex matches in the // specified range of lines var query = getSearchState(cm).getQuery(); var matchedLines = []; for (var i = lineStart; i <= lineEnd; i++) { var line = cm.getLineHandle(i); var matched = query.test(line.text); if (matched !== inverted) { matchedLines.push(cmd ? line : line.text); } } // if there is no [cmd], just display the list of matched lines if (!cmd) { showConfirm(cm, matchedLines.join('\n')); return; } var index = 0; var nextCommand = function() { if (index < matchedLines.length) { var line = matchedLines[index++]; var lineNum = cm.getLineNumber(line); if (lineNum == null) { nextCommand(); return; } var command = (lineNum + 1) + cmd; exCommandDispatcher.processCommand(cm, command, { callback: nextCommand }); } }; nextCommand(); }, substitute: function(cm, params) { if (!cm.getSearchCursor) { throw new Error('Search feature not available. Requires searchcursor.js or ' + 'any other getSearchCursor implementation.'); } var argString = params.argString; var tokens = argString ? splitBySeparator(argString, argString[0]) : []; var regexPart, replacePart = '', trailing, flagsPart, count; var confirm = false; // Whether to confirm each replace. var global = false; // True to replace all instances on a line, false to replace only 1. if (tokens.length) { regexPart = tokens[0]; if (getOption('pcre') && regexPart !== '') { regexPart = new RegExp(regexPart).source; //normalize not escaped characters } replacePart = tokens[1]; if (replacePart !== undefined) { if (getOption('pcre')) { replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g,"$1$$&")); } else { replacePart = translateRegexReplace(replacePart); } vimGlobalState.lastSubstituteReplacePart = replacePart; } trailing = tokens[2] ? tokens[2].split(' ') : []; } else { // either the argString is empty or its of the form ' hello/world' // actually splitBySlash returns a list of tokens // only if the string starts with a '/' if (argString && argString.length) { showConfirm(cm, 'Substitutions should be of the form ' + ':s/pattern/replace/'); return; } } // After the 3rd slash, we can have flags followed by a space followed // by count. if (trailing) { flagsPart = trailing[0]; count = parseInt(trailing[1]); if (flagsPart) { if (flagsPart.indexOf('c') != -1) { confirm = true; } if (flagsPart.indexOf('g') != -1) { global = true; } if (getOption('pcre')) { regexPart = regexPart + '/' + flagsPart; } else { regexPart = regexPart.replace(/\//g, "\\/") + '/' + flagsPart; } } } if (regexPart) { // If regex part is empty, then use the previous query. Otherwise use // the regex part as the new query. try { updateSearchQuery(cm, regexPart, true /** ignoreCase */, true /** smartCase */); } catch (e) { showConfirm(cm, 'Invalid regex: ' + regexPart); return; } } replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; if (replacePart === undefined) { showConfirm(cm, 'No previous substitute regular expression'); return; } var state = getSearchState(cm); var query = state.getQuery(); var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; var lineEnd = params.lineEnd || lineStart; if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) { lineEnd = Infinity; } if (count) { lineStart = lineEnd; lineEnd = lineStart + count - 1; } var startPos = clipCursorToContent(cm, new Pos(lineStart, 0)); var cursor = cm.getSearchCursor(query, startPos); doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); }, redo: CodeMirror.commands.redo, undo: CodeMirror.commands.undo, write: function(cm) { if (CodeMirror.commands.save) { // If a save command is defined, call it. CodeMirror.commands.save(cm); } else if (cm.save) { // Saves to text area if no save command is defined and cm.save() is available. cm.save(); } }, nohlsearch: function(cm) { clearSearchHighlight(cm); }, yank: function (cm) { var cur = copyCursor(cm.getCursor()); var line = cur.line; var lineText = cm.getLine(line); vimGlobalState.registerController.pushText( '0', 'yank', lineText, true, true); }, delmarks: function(cm, params) { if (!params.argString || !trim(params.argString)) { showConfirm(cm, 'Argument required'); return; } var state = cm.state.vim; var stream = new CodeMirror.StringStream(trim(params.argString)); while (!stream.eol()) { stream.eatSpace(); // Record the streams position at the beginning of the loop for use // in error messages. var count = stream.pos; if (!stream.match(/[a-zA-Z]/, false)) { showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); return; } var sym = stream.next(); // Check if this symbol is part of a range if (stream.match('-', true)) { // This symbol is part of a range. // The range must terminate at an alphabetic character. if (!stream.match(/[a-zA-Z]/, false)) { showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); return; } var startMark = sym; var finishMark = stream.next(); // The range must terminate at an alphabetic character which // shares the same case as the start of the range. if (isLowerCase(startMark) && isLowerCase(finishMark) || isUpperCase(startMark) && isUpperCase(finishMark)) { var start = startMark.charCodeAt(0); var finish = finishMark.charCodeAt(0); if (start >= finish) { showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); return; } // Because marks are always ASCII values, and we have // determined that they are the same case, we can use // their char codes to iterate through the defined range. for (var j = 0; j <= finish - start; j++) { var mark = String.fromCharCode(start + j); delete state.marks[mark]; } } else { showConfirm(cm, 'Invalid argument: ' + startMark + '-'); return; } } else { // This symbol is a valid mark, and is not part of a range. delete state.marks[sym]; } } } }; var exCommandDispatcher = new ExCommandDispatcher(); /** * @param {CodeMirror} cm CodeMirror instance we are in. * @param {boolean} confirm Whether to confirm each replace. * @param {Cursor} lineStart Line to start replacing from. * @param {Cursor} lineEnd Line to stop replacing at. * @param {RegExp} query Query for performing matches with. * @param {string} replaceWith Text to replace matches with. May contain $1, * $2, etc for replacing captured groups using JavaScript replace. * @param {function()} callback A callback for when the replace is done. */ function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, replaceWith, callback) { // Set up all the functions. cm.state.vim.exMode = true; var done = false; var lastPos, modifiedLineNumber, joined; function replaceAll() { cm.operation(function() { while (!done) { replace(); next(); } stop(); }); } function replace() { var text = cm.getRange(searchCursor.from(), searchCursor.to()); var newText = text.replace(query, replaceWith); var unmodifiedLineNumber = searchCursor.to().line; searchCursor.replace(newText); modifiedLineNumber = searchCursor.to().line; lineEnd += modifiedLineNumber - unmodifiedLineNumber; joined = modifiedLineNumber < unmodifiedLineNumber; } function findNextValidMatch() { var lastMatchTo = lastPos && copyCursor(searchCursor.to()); var match = searchCursor.findNext(); if (match && !match[0] && lastMatchTo && cursorEqual(searchCursor.from(), lastMatchTo)) { match = searchCursor.findNext(); } return match; } function next() { // The below only loops to skip over multiple occurrences on the same // line when 'global' is not true. while(findNextValidMatch() && isInRange(searchCursor.from(), lineStart, lineEnd)) { if (!global && searchCursor.from().line == modifiedLineNumber && !joined) { continue; } cm.scrollIntoView(searchCursor.from(), 30); cm.setSelection(searchCursor.from(), searchCursor.to()); lastPos = searchCursor.from(); done = false; return; } done = true; } function stop(close) { if (close) { close(); } cm.focus(); if (lastPos) { cm.setCursor(lastPos); var vim = cm.state.vim; vim.exMode = false; vim.lastHPos = vim.lastHSPos = lastPos.ch; } if (callback) { callback(); } } function onPromptKeyDown(e, _value, close) { // Swallow all keys. CodeMirror.e_stop(e); var keyName = CodeMirror.keyName(e); switch (keyName) { case 'Y': replace(); next(); break; case 'N': next(); break; case 'A': // replaceAll contains a call to close of its own. We don't want it // to fire too early or multiple times. var savedCallback = callback; callback = undefined; cm.operation(replaceAll); callback = savedCallback; break; case 'L': replace(); // fall through and exit. case 'Q': case 'Esc': case 'Ctrl-C': case 'Ctrl-[': stop(close); break; } if (done) { stop(close); } return true; } // Actually do replace. next(); if (done) { showConfirm(cm, 'No matches for ' + query.source); return; } if (!confirm) { replaceAll(); if (callback) { callback(); } return; } showPrompt(cm, { prefix: dom('span', 'replace with ', dom('strong', replaceWith), ' (y/n/a/q/l)'), onKeyDown: onPromptKeyDown }); } CodeMirror.keyMap.vim = { attach: attachVimMap, detach: detachVimMap, call: cmKey }; function exitInsertMode(cm) { var vim = cm.state.vim; var macroModeState = vimGlobalState.macroModeState; var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); var isPlaying = macroModeState.isPlaying; var lastChange = macroModeState.lastInsertModeChanges; if (!isPlaying) { cm.off('change', onChange); CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } if (!isPlaying && vim.insertModeRepeat > 1) { // Perform insert mode repeat for commands like 3,a and 3,o. repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, true /** repeatForInsert */); vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; } delete vim.insertModeRepeat; vim.insertMode = false; cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); cm.setOption('keyMap', 'vim'); cm.setOption('disableInput', true); cm.toggleOverwrite(false); // exit replace mode if we were in it. // update the ". register before exiting insert mode insertModeChangeRegister.setText(lastChange.changes.join('')); CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); if (macroModeState.isRecording) { logInsertModeChange(macroModeState); } } function _mapCommand(command) { defaultKeymap.unshift(command); } function mapCommand(keys, type, name, args, extra) { var command = {keys: keys, type: type}; command[type] = name; command[type + "Args"] = args; for (var key in extra) command[key] = extra[key]; _mapCommand(command); } // The timeout in milliseconds for the two-character ESC keymap should be // adjusted according to your typing speed to prevent false positives. defineOption('insertModeEscKeysTimeout', 200, 'number'); CodeMirror.keyMap['vim-insert'] = { // TODO: override navigation keys so that Esc will cancel automatic // indentation from o, O, i_ fallthrough: ['default'], attach: attachVimMap, detach: detachVimMap, call: cmKey }; CodeMirror.keyMap['vim-replace'] = { 'Backspace': 'goCharLeft', fallthrough: ['vim-insert'], attach: attachVimMap, detach: detachVimMap, call: cmKey }; function executeMacroRegister(cm, vim, macroModeState, registerName) { var register = vimGlobalState.registerController.getRegister(registerName); if (registerName == ':') { // Read-only register containing last Ex command. if (register.keyBuffer[0]) { exCommandDispatcher.processCommand(cm, register.keyBuffer[0]); } macroModeState.isPlaying = false; return; } var keyBuffer = register.keyBuffer; var imc = 0; macroModeState.isPlaying = true; macroModeState.replaySearchQueries = register.searchQueries.slice(0); for (var i = 0; i < keyBuffer.length; i++) { var text = keyBuffer[i]; var match, key; while (text) { // Pull off one command key, which is either a single character // or a special sequence wrapped in '<' and '>', e.g. ''. match = (/<\w+-.+?>|<\w+>|./).exec(text); key = match[0]; text = text.substring(match.index + key.length); vimApi.handleKey(cm, key, 'macro'); if (vim.insertMode) { var changes = register.insertModeChanges[imc++].changes; vimGlobalState.macroModeState.lastInsertModeChanges.changes = changes; repeatInsertModeChanges(cm, changes, 1); exitInsertMode(cm); } } } macroModeState.isPlaying = false; } function logKey(macroModeState, key) { if (macroModeState.isPlaying) { return; } var registerName = macroModeState.latestRegister; var register = vimGlobalState.registerController.getRegister(registerName); if (register) { register.pushText(key); } } function logInsertModeChange(macroModeState) { if (macroModeState.isPlaying) { return; } var registerName = macroModeState.latestRegister; var register = vimGlobalState.registerController.getRegister(registerName); if (register && register.pushInsertModeChanges) { register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); } } function logSearchQuery(macroModeState, query) { if (macroModeState.isPlaying) { return; } var registerName = macroModeState.latestRegister; var register = vimGlobalState.registerController.getRegister(registerName); if (register && register.pushSearchQuery) { register.pushSearchQuery(query); } } /** * Listens for changes made in insert mode. * Should only be active in insert mode. */ function onChange(cm, changeObj) { var macroModeState = vimGlobalState.macroModeState; var lastChange = macroModeState.lastInsertModeChanges; if (!macroModeState.isPlaying) { while(changeObj) { lastChange.expectCursorActivityForChange = true; if (lastChange.ignoreCount > 1) { lastChange.ignoreCount--; } else if (changeObj.origin == '+input' || changeObj.origin == 'paste' || changeObj.origin === undefined /* only in testing */) { var selectionCount = cm.listSelections().length; if (selectionCount > 1) lastChange.ignoreCount = selectionCount; var text = changeObj.text.join('\n'); if (lastChange.maybeReset) { lastChange.changes = []; lastChange.maybeReset = false; } if (text) { if (cm.state.overwrite && !/\n/.test(text)) { lastChange.changes.push([text]); } else { lastChange.changes.push(text); } } } // Change objects may be chained with next. changeObj = changeObj.next; } } } /** * Listens for any kind of cursor activity on CodeMirror. */ function onCursorActivity(cm) { var vim = cm.state.vim; if (vim.insertMode) { // Tracking cursor activity in insert mode (for macro support). var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isPlaying) { return; } var lastChange = macroModeState.lastInsertModeChanges; if (lastChange.expectCursorActivityForChange) { lastChange.expectCursorActivityForChange = false; } else { // Cursor moved outside the context of an edit. Reset the change. lastChange.maybeReset = true; } } else if (!cm.curOp.isVimOp) { handleExternalSelection(cm, vim); } } function handleExternalSelection(cm, vim) { var anchor = cm.getCursor('anchor'); var head = cm.getCursor('head'); // Enter or exit visual mode to match mouse selection. if (vim.visualMode && !cm.somethingSelected()) { exitVisualMode(cm, false); } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { vim.visualMode = true; vim.visualLine = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); } if (vim.visualMode) { // Bind CodeMirror selection model to vim selection model. // Mouse selections are considered visual characterwise. var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; head = offsetCursor(head, 0, headOffset); anchor = offsetCursor(anchor, 0, anchorOffset); vim.sel = { anchor: anchor, head: head }; updateMark(cm, vim, '<', cursorMin(head, anchor)); updateMark(cm, vim, '>', cursorMax(head, anchor)); } else if (!vim.insertMode) { // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse. vim.lastHPos = cm.getCursor().ch; } } /** Wrapper for special keys pressed in insert mode */ function InsertModeKey(keyName) { this.keyName = keyName; } /** * Handles raw key down events from the text area. * - Should only be active in insert mode. * - For recording deletes in insert mode. */ function onKeyEventTargetKeyDown(e) { var macroModeState = vimGlobalState.macroModeState; var lastChange = macroModeState.lastInsertModeChanges; var keyName = CodeMirror.keyName(e); if (!keyName) { return; } function onKeyFound() { if (lastChange.maybeReset) { lastChange.changes = []; lastChange.maybeReset = false; } lastChange.changes.push(new InsertModeKey(keyName)); return true; } if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound); } } /** * Repeats the last edit, which includes exactly 1 command and at most 1 * insert. Operator and motion commands are read from lastEditInputState, * while action commands are read from lastEditActionCommand. * * If repeatForInsert is true, then the function was called by * exitInsertMode to repeat the insert mode changes the user just made. The * corresponding enterInsertMode call was made with a count. */ function repeatLastEdit(cm, vim, repeat, repeatForInsert) { var macroModeState = vimGlobalState.macroModeState; macroModeState.isPlaying = true; var isAction = !!vim.lastEditActionCommand; var cachedInputState = vim.inputState; function repeatCommand() { if (isAction) { commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand); } else { commandDispatcher.evalInput(cm, vim); } } function repeatInsert(repeat) { if (macroModeState.lastInsertModeChanges.changes.length > 0) { // For some reason, repeat cw in desktop VIM does not repeat // insert mode changes. Will conform to that behavior. repeat = !vim.lastEditActionCommand ? 1 : repeat; var changeObject = macroModeState.lastInsertModeChanges; repeatInsertModeChanges(cm, changeObject.changes, repeat); } } vim.inputState = vim.lastEditInputState; if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) { // o and O repeat have to be interlaced with insert repeats so that the // insertions appear on separate lines instead of the last line. for (var i = 0; i < repeat; i++) { repeatCommand(); repeatInsert(1); } } else { if (!repeatForInsert) { // Hack to get the cursor to end up at the right place. If I is // repeated in insert mode repeat, cursor will be 1 insert // change set left of where it should be. repeatCommand(); } repeatInsert(repeat); } vim.inputState = cachedInputState; if (vim.insertMode && !repeatForInsert) { // Don't exit insert mode twice. If repeatForInsert is set, then we // were called by an exitInsertMode call lower on the stack. exitInsertMode(cm); } macroModeState.isPlaying = false; } function repeatInsertModeChanges(cm, changes, repeat) { function keyHandler(binding) { if (typeof binding == 'string') { CodeMirror.commands[binding](cm); } else { binding(cm); } return true; } var head = cm.getCursor('head'); var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock; if (visualBlock) { // Set up block selection again for repeating the changes. selectForInsert(cm, head, visualBlock + 1); repeat = cm.listSelections().length; cm.setCursor(head); } for (var i = 0; i < repeat; i++) { if (visualBlock) { cm.setCursor(offsetCursor(head, i, 0)); } for (var j = 0; j < changes.length; j++) { var change = changes[j]; if (change instanceof InsertModeKey) { CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler); } else if (typeof change == "string") { cm.replaceSelection(change); } else { var start = cm.getCursor(); var end = offsetCursor(start, 0, change[0].length); cm.replaceRange(change[0], start, end); cm.setCursor(end); } } } if (visualBlock) { cm.setCursor(offsetCursor(head, 0, 1)); } } // multiselect support function cloneVimState(state) { var n = new state.constructor(); Object.keys(state).forEach(function(key) { var o = state[key]; if (Array.isArray(o)) o = o.slice(); else if (o && typeof o == "object" && o.constructor != Object) o = cloneVimState(o); n[key] = o; }); if (state.sel) { n.sel = { head: state.sel.head && copyCursor(state.sel.head), anchor: state.sel.anchor && copyCursor(state.sel.anchor) }; } return n; } function multiSelectHandleKey(cm, key, origin) { var isHandled = false; var vim = vimApi.maybeInitVimState_(cm); var visualBlock = vim.visualBlock || vim.wasInVisualBlock; var wasMultiselect = cm.isInMultiSelectMode(); if (vim.wasInVisualBlock && !wasMultiselect) { vim.wasInVisualBlock = false; } else if (wasMultiselect && vim.visualBlock) { vim.wasInVisualBlock = true; } if (key == '' && !vim.insertMode && !vim.visualMode && wasMultiselect && vim.status == "") { // allow editor to exit multiselect clearInputState(cm); } else if (visualBlock || !wasMultiselect || cm.inVirtualSelectionMode) { isHandled = vimApi.handleKey(cm, key, origin); } else { var old = cloneVimState(vim); cm.operation(function() { cm.curOp.isVimOp = true; cm.forEachSelection(function() { var head = cm.getCursor("head"); var anchor = cm.getCursor("anchor"); var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; head = offsetCursor(head, 0, headOffset); anchor = offsetCursor(anchor, 0, anchorOffset); cm.state.vim.sel.head = head; cm.state.vim.sel.anchor = anchor; isHandled = vimApi.handleKey(cm, key, origin); if (cm.virtualSelection) { cm.state.vim = cloneVimState(old); } }); if (cm.curOp.cursorActivity && !isHandled) cm.curOp.cursorActivity = false; cm.state.vim = vim; }, true); } // some commands may bring visualMode and selection out of sync if (isHandled && !vim.visualMode && !vim.insert && vim.visualMode != cm.somethingSelected()) { handleExternalSelection(cm, vim); } return isHandled; } resetVimGlobalState(); return vimApi; } function initVim(CodeMirror5) { CodeMirror5.Vim = initVim$1(CodeMirror5); return CodeMirror5.Vim; } CodeMirror.Vim = initVim(CodeMirror); }); ================================================ FILE: public/assets/lib/vendor/codemirror/lib/codemirror.css ================================================ /* BASICS */ .CodeMirror { /* Set height, width, borders, and global font properties here */ font-family: monospace; height: 300px; color: black; direction: ltr; } /* PADDING */ .CodeMirror-lines { padding: 4px 0; /* Vertical padding around content */ } .CodeMirror pre.CodeMirror-line, .CodeMirror pre.CodeMirror-line-like { padding: 0 4px; /* Horizontal padding of content */ } .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { background-color: white; /* The little square between H and V scrollbars */ } /* GUTTER */ .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: #f7f7f7; white-space: nowrap; } .CodeMirror-linenumbers {} .CodeMirror-linenumber { padding: 0 3px 0 5px; min-width: 20px; text-align: right; color: #999; white-space: nowrap; } .CodeMirror-guttermarker { color: black; } .CodeMirror-guttermarker-subtle { color: #999; } /* CURSOR */ .CodeMirror-cursor { border-left: 1px solid black; border-right: none; width: 0; } /* Shown when moving in bi-directional text */ .CodeMirror div.CodeMirror-secondarycursor { border-left: 1px solid silver; } .cm-fat-cursor .CodeMirror-cursor { width: auto; border: 0 !important; background: #7e7; } .cm-fat-cursor div.CodeMirror-cursors { z-index: 1; } .cm-fat-cursor .CodeMirror-line::selection, .cm-fat-cursor .CodeMirror-line > span::selection, .cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; } .cm-fat-cursor .CodeMirror-line::-moz-selection, .cm-fat-cursor .CodeMirror-line > span::-moz-selection, .cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; } .cm-fat-cursor { caret-color: transparent; } @-moz-keyframes blink { 0% {} 50% { background-color: transparent; } 100% {} } @-webkit-keyframes blink { 0% {} 50% { background-color: transparent; } 100% {} } @keyframes blink { 0% {} 50% { background-color: transparent; } 100% {} } /* Can style cursor different in overwrite (non-insert) mode */ .CodeMirror-overwrite .CodeMirror-cursor {} .cm-tab { display: inline-block; text-decoration: inherit; } .CodeMirror-rulers { position: absolute; left: 0; right: 0; top: -50px; bottom: 0; overflow: hidden; } .CodeMirror-ruler { border-left: 1px solid #ccc; top: 0; bottom: 0; position: absolute; } /* DEFAULT THEME */ .cm-s-default .cm-header {color: blue;} .cm-s-default .cm-quote {color: #090;} .cm-negative {color: #d44;} .cm-positive {color: #292;} .cm-header, .cm-strong {font-weight: bold;} .cm-em {font-style: italic;} .cm-link {text-decoration: underline;} .cm-strikethrough {text-decoration: line-through;} .cm-s-default .cm-keyword {color: #708;} .cm-s-default .cm-atom {color: #219;} .cm-s-default .cm-number {color: #164;} .cm-s-default .cm-def {color: #00f;} .cm-s-default .cm-variable, .cm-s-default .cm-punctuation, .cm-s-default .cm-property, .cm-s-default .cm-operator {} .cm-s-default .cm-variable-2 {color: #05a;} .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} .cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-meta {color: #555;} .cm-s-default .cm-qualifier {color: #555;} .cm-s-default .cm-builtin {color: #30a;} .cm-s-default .cm-bracket {color: #997;} .cm-s-default .cm-tag {color: #170;} .cm-s-default .cm-attribute {color: #00c;} .cm-s-default .cm-hr {color: #999;} .cm-s-default .cm-link {color: #00c;} .cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} .CodeMirror-composing { border-bottom: 2px solid; } /* Default styles for common addons */ div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } .CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */ /* The rest of this file contains styles related to the mechanics of the editor. You probably shouldn't touch them. */ .CodeMirror { position: relative; overflow: hidden; background: white; } .CodeMirror-scroll { overflow: scroll !important; /* Things will break if this is overridden */ /* 50px is the magic margin used to hide the element's real scrollbars */ /* See overflow: hidden in .CodeMirror */ margin-bottom: -50px; margin-right: -50px; padding-bottom: 50px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; z-index: 0; } .CodeMirror-sizer { position: relative; border-right: 50px solid transparent; } /* The fake, visible scrollbars. Used to force redraw during scrolling before actual scrolling happens, thus preventing shaking and flickering artifacts. */ .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none; outline: none; } .CodeMirror-vscrollbar { right: 0; top: 0; overflow-x: hidden; overflow-y: scroll; } .CodeMirror-hscrollbar { bottom: 0; left: 0; overflow-y: hidden; overflow-x: scroll; } .CodeMirror-scrollbar-filler { right: 0; bottom: 0; } .CodeMirror-gutter-filler { left: 0; bottom: 0; } .CodeMirror-gutters { position: absolute; left: 0; top: 0; min-height: 100%; z-index: 3; } .CodeMirror-gutter { white-space: normal; height: 100%; display: inline-block; vertical-align: top; margin-bottom: -50px; } .CodeMirror-gutter-wrapper { position: absolute; z-index: 4; background: none !important; border: none !important; } .CodeMirror-gutter-background { position: absolute; top: 0; bottom: 0; z-index: 4; } .CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; } .CodeMirror-gutter-wrapper ::selection { background-color: transparent } .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } .CodeMirror-lines { cursor: text; min-height: 1px; /* prevents collapsing before first draw */ } .CodeMirror pre.CodeMirror-line, .CodeMirror pre.CodeMirror-line-like { /* Reset some styles that the rest of the page might have set */ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; border-width: 0; background: transparent; font-family: inherit; font-size: inherit; margin: 0; white-space: pre; word-wrap: normal; line-height: inherit; color: inherit; z-index: 2; position: relative; overflow: visible; -webkit-tap-highlight-color: transparent; -webkit-font-variant-ligatures: contextual; font-variant-ligatures: contextual; } .CodeMirror-wrap pre.CodeMirror-line, .CodeMirror-wrap pre.CodeMirror-line-like { word-wrap: break-word; white-space: pre-wrap; word-break: normal; } .CodeMirror-linebackground { position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: 0; } .CodeMirror-linewidget { position: relative; z-index: 2; padding: 0.1px; /* Force widget margins to stay inside of the container */ } .CodeMirror-widget {} .CodeMirror-rtl pre { direction: rtl; } .CodeMirror-code { outline: none; } /* Force content-box sizing for the elements where we expect it */ .CodeMirror-scroll, .CodeMirror-sizer, .CodeMirror-gutter, .CodeMirror-gutters, .CodeMirror-linenumber { -moz-box-sizing: content-box; box-sizing: content-box; } .CodeMirror-measure { position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden; } .CodeMirror-cursor { position: absolute; pointer-events: none; } .CodeMirror-measure pre { position: static; } div.CodeMirror-cursors { visibility: hidden; position: relative; z-index: 3; } div.CodeMirror-dragcursors { visibility: visible; } .CodeMirror-focused div.CodeMirror-cursors { visibility: visible; } .CodeMirror-selected { background: #d9d9d9; } .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } .CodeMirror-crosshair { cursor: crosshair; } .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } .cm-searching { background-color: #ffa; background-color: rgba(255, 255, 0, .4); } /* Used to force a border model for a node */ .cm-force-border { padding-right: .1px; } @media print { /* Hide the cursor when printing */ .CodeMirror div.CodeMirror-cursors { visibility: hidden; } } /* See issue #2901 */ .cm-tab-wrap-hack:after { content: ''; } /* Help users use markselection to safely style text background */ span.CodeMirror-selectedtext { background: none; } ================================================ FILE: public/assets/lib/vendor/codemirror/lib/codemirror.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // This is CodeMirror (https://codemirror.net/5), a code editor // implemented in JavaScript on top of the browser's DOM. // // You can find some technical background for some of the code below // at http://marijnhaverbeke.nl/blog/#cm-internals . (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.CodeMirror = factory()); }(this, (function () { 'use strict'; // Kludges for bugs and behavior differences that can't be feature // detected are enabled based on userAgent etc sniffing. var userAgent = navigator.userAgent; var platform = navigator.platform; var gecko = /gecko\/\d/i.test(userAgent); var ie_upto10 = /MSIE \d/.test(userAgent); var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); var edge = /Edge\/(\d+)/.exec(userAgent); var ie = ie_upto10 || ie_11up || edge; var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); var webkit = !edge && /WebKit\//.test(userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); var chrome = !edge && /Chrome\/(\d+)/.exec(userAgent); var chrome_version = chrome && +chrome[1]; var presto = /Opera\//.test(userAgent); var safari = /Apple Computer/.test(navigator.vendor); var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); var phantom = /PhantomJS/.test(userAgent); var ios = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2); var android = /Android/.test(userAgent); // This is woefully incomplete. Suggestions for alternative methods welcome. var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); var mac = ios || /Mac/.test(platform); var chromeOS = /\bCrOS\b/.test(userAgent); var windows = /win/i.test(platform); var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); if (presto_version) { presto_version = Number(presto_version[1]); } if (presto_version && presto_version >= 15) { presto = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); var captureRightClick = gecko || (ie && ie_version >= 9); function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } var rmClass = function(node, cls) { var current = node.className; var match = classTest(cls).exec(current); if (match) { var after = current.slice(match.index + match[0].length); node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); } }; function removeChildren(e) { for (var count = e.childNodes.length; count > 0; --count) { e.removeChild(e.firstChild); } return e } function removeChildrenAndAdd(parent, e) { return removeChildren(parent).appendChild(e) } function elt(tag, content, className, style) { var e = document.createElement(tag); if (className) { e.className = className; } if (style) { e.style.cssText = style; } if (typeof content == "string") { e.appendChild(document.createTextNode(content)); } else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } } return e } // wrapper for elt, which removes the elt from the accessibility tree function eltP(tag, content, className, style) { var e = elt(tag, content, className, style); e.setAttribute("role", "presentation"); return e } var range; if (document.createRange) { range = function(node, start, end, endNode) { var r = document.createRange(); r.setEnd(endNode || node, end); r.setStart(node, start); return r }; } else { range = function(node, start, end) { var r = document.body.createTextRange(); try { r.moveToElementText(node.parentNode); } catch(e) { return r } r.collapse(true); r.moveEnd("character", end); r.moveStart("character", start); return r }; } function contains(parent, child) { if (child.nodeType == 3) // Android browser always returns false when child is a textnode { child = child.parentNode; } if (parent.contains) { return parent.contains(child) } do { if (child.nodeType == 11) { child = child.host; } if (child == parent) { return true } } while (child = child.parentNode) } function activeElt(rootNode) { // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. // IE < 10 will throw when accessed while the page is loading or in an iframe. // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. var doc = rootNode.ownerDocument || rootNode; var activeElement; try { activeElement = rootNode.activeElement; } catch(e) { activeElement = doc.body || null; } while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { activeElement = activeElement.shadowRoot.activeElement; } return activeElement } function addClass(node, cls) { var current = node.className; if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; } } function joinClasses(a, b) { var as = a.split(" "); for (var i = 0; i < as.length; i++) { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } } return b } var selectInput = function(node) { node.select(); }; if (ios) // Mobile Safari apparently has a bug where select() is broken. { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; } else if (ie) // Suppress mysterious IE10 errors { selectInput = function(node) { try { node.select(); } catch(_e) {} }; } function doc(cm) { return cm.display.wrapper.ownerDocument } function root(cm) { return rootNode(cm.display.wrapper) } function rootNode(element) { // Detect modern browsers (2017+). return element.getRootNode ? element.getRootNode() : element.ownerDocument } function win(cm) { return doc(cm).defaultView } function bind(f) { var args = Array.prototype.slice.call(arguments, 1); return function(){return f.apply(null, args)} } function copyObj(obj, target, overwrite) { if (!target) { target = {}; } for (var prop in obj) { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) { target[prop] = obj[prop]; } } return target } // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. function countColumn(string, end, tabSize, startIndex, startValue) { if (end == null) { end = string.search(/[^\s\u00a0]/); if (end == -1) { end = string.length; } } for (var i = startIndex || 0, n = startValue || 0;;) { var nextTab = string.indexOf("\t", i); if (nextTab < 0 || nextTab >= end) { return n + (end - i) } n += nextTab - i; n += tabSize - (n % tabSize); i = nextTab + 1; } } var Delayed = function() { this.id = null; this.f = null; this.time = 0; this.handler = bind(this.onTimeout, this); }; Delayed.prototype.onTimeout = function (self) { self.id = 0; if (self.time <= +new Date) { self.f(); } else { setTimeout(self.handler, self.time - +new Date); } }; Delayed.prototype.set = function (ms, f) { this.f = f; var time = +new Date + ms; if (!this.id || time < this.time) { clearTimeout(this.id); this.id = setTimeout(this.handler, ms); this.time = time; } }; function indexOf(array, elt) { for (var i = 0; i < array.length; ++i) { if (array[i] == elt) { return i } } return -1 } // Number of pixels added to scroller and sizer to hide scrollbar var scrollerGap = 50; // Returned or thrown by various protocols to signal 'I'm not // handling this'. var Pass = {toString: function(){return "CodeMirror.Pass"}}; // Reused option objects for setSelection & friends var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; // The inverse of countColumn -- find the offset that corresponds to // a particular column. function findColumn(string, goal, tabSize) { for (var pos = 0, col = 0;;) { var nextTab = string.indexOf("\t", pos); if (nextTab == -1) { nextTab = string.length; } var skipped = nextTab - pos; if (nextTab == string.length || col + skipped >= goal) { return pos + Math.min(skipped, goal - col) } col += nextTab - pos; col += tabSize - (col % tabSize); pos = nextTab + 1; if (col >= goal) { return pos } } } var spaceStrs = [""]; function spaceStr(n) { while (spaceStrs.length <= n) { spaceStrs.push(lst(spaceStrs) + " "); } return spaceStrs[n] } function lst(arr) { return arr[arr.length-1] } function map(array, f) { var out = []; for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); } return out } function insertSorted(array, value, score) { var pos = 0, priority = score(value); while (pos < array.length && score(array[pos]) <= priority) { pos++; } array.splice(pos, 0, value); } function nothing() {} function createObj(base, props) { var inst; if (Object.create) { inst = Object.create(base); } else { nothing.prototype = base; inst = new nothing(); } if (props) { copyObj(props, inst); } return inst } var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; function isWordCharBasic(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) } function isWordChar(ch, helper) { if (!helper) { return isWordCharBasic(ch) } if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } return helper.test(ch) } function isEmpty(obj) { for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } return true } // Extending unicode characters. A series of a non-extending char + // any number of extending chars is treated as a single unit as far // as editing and measuring is concerned. This is not fully correct, // since some scripts/fonts/browsers also treat other configurations // of code points as a group. var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. function skipExtendingChars(str, pos, dir) { while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; } return pos } // Returns the value from the range [`from`; `to`] that satisfies // `pred` and is closest to `from`. Assumes that at least `to` // satisfies `pred`. Supports `from` being greater than `to`. function findFirst(pred, from, to) { // At any point we are certain `to` satisfies `pred`, don't know // whether `from` does. var dir = from > to ? -1 : 1; for (;;) { if (from == to) { return from } var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); if (mid == from) { return pred(mid) ? from : to } if (pred(mid)) { to = mid; } else { from = mid + dir; } } } // BIDI HELPERS function iterateBidiSections(order, from, to, f) { if (!order) { return f(from, to, "ltr", 0) } var found = false; for (var i = 0; i < order.length; ++i) { var part = order[i]; if (part.from < to && part.to > from || from == to && part.to == from) { f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i); found = true; } } if (!found) { f(from, to, "ltr"); } } var bidiOther = null; function getBidiPartAt(order, ch, sticky) { var found; bidiOther = null; for (var i = 0; i < order.length; ++i) { var cur = order[i]; if (cur.from < ch && cur.to > ch) { return i } if (cur.to == ch) { if (cur.from != cur.to && sticky == "before") { found = i; } else { bidiOther = i; } } if (cur.from == ch) { if (cur.from != cur.to && sticky != "before") { found = i; } else { bidiOther = i; } } } return found != null ? found : bidiOther } // Bidirectional ordering algorithm // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm // that this (partially) implements. // One-char codes used for character types: // L (L): Left-to-Right // R (R): Right-to-Left // r (AL): Right-to-Left Arabic // 1 (EN): European Number // + (ES): European Number Separator // % (ET): European Number Terminator // n (AN): Arabic Number // , (CS): Common Number Separator // m (NSM): Non-Spacing Mark // b (BN): Boundary Neutral // s (B): Paragraph Separator // t (S): Segment Separator // w (WS): Whitespace // N (ON): Other Neutrals // Returns null if characters are ordered as they appear // (left-to-right), or an array of sections ({from, to, level} // objects) in the order in which they occur visually. var bidiOrdering = (function() { // Character types for codepoints 0 to 0xff var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; // Character types for codepoints 0x600 to 0x6f9 var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; function charType(code) { if (code <= 0xf7) { return lowTypes.charAt(code) } else if (0x590 <= code && code <= 0x5f4) { return "R" } else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } else if (0x6ee <= code && code <= 0x8ac) { return "r" } else if (0x2000 <= code && code <= 0x200b) { return "w" } else if (code == 0x200c) { return "b" } else { return "L" } } var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; function BidiSpan(level, from, to) { this.level = level; this.from = from; this.to = to; } return function(str, direction) { var outerType = direction == "ltr" ? "L" : "R"; if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } var len = str.length, types = []; for (var i = 0; i < len; ++i) { types.push(charType(str.charCodeAt(i))); } // W1. Examine each non-spacing mark (NSM) in the level run, and // change the type of the NSM to the type of the previous // character. If the NSM is at the start of the level run, it will // get the type of sor. for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { var type = types[i$1]; if (type == "m") { types[i$1] = prev; } else { prev = type; } } // W2. Search backwards from each instance of a European number // until the first strong type (R, L, AL, or sor) is found. If an // AL is found, change the type of the European number to Arabic // number. // W3. Change all ALs to R. for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { var type$1 = types[i$2]; if (type$1 == "1" && cur == "r") { types[i$2] = "n"; } else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } } } // W4. A single European separator between two European numbers // changes to a European number. A single common separator between // two numbers of the same type changes to that type. for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { var type$2 = types[i$3]; if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; } else if (type$2 == "," && prev$1 == types[i$3+1] && (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; } prev$1 = type$2; } // W5. A sequence of European terminators adjacent to European // numbers changes to all European numbers. // W6. Otherwise, separators and terminators change to Other // Neutral. for (var i$4 = 0; i$4 < len; ++i$4) { var type$3 = types[i$4]; if (type$3 == ",") { types[i$4] = "N"; } else if (type$3 == "%") { var end = (void 0); for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; for (var j = i$4; j < end; ++j) { types[j] = replace; } i$4 = end - 1; } } // W7. Search backwards from each instance of a European number // until the first strong type (R, L, or sor) is found. If an L is // found, then change the type of the European number to L. for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { var type$4 = types[i$5]; if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; } else if (isStrong.test(type$4)) { cur$1 = type$4; } } // N1. A sequence of neutrals takes the direction of the // surrounding strong text if the text on both sides has the same // direction. European and Arabic numbers act as if they were R in // terms of their influence on neutrals. Start-of-level-run (sor) // and end-of-level-run (eor) are used at level run boundaries. // N2. Any remaining neutrals take the embedding direction. for (var i$6 = 0; i$6 < len; ++i$6) { if (isNeutral.test(types[i$6])) { var end$1 = (void 0); for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} var before = (i$6 ? types[i$6-1] : outerType) == "L"; var after = (end$1 < len ? types[end$1] : outerType) == "L"; var replace$1 = before == after ? (before ? "L" : "R") : outerType; for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; } i$6 = end$1 - 1; } } // Here we depart from the documented algorithm, in order to avoid // building up an actual levels array. Since there are only three // levels (0, 1, 2) in an implementation that doesn't take // explicit embedding into account, we can build up the order on // the fly, without following the level-based algorithm. var order = [], m; for (var i$7 = 0; i$7 < len;) { if (countsAsLeft.test(types[i$7])) { var start = i$7; for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} order.push(new BidiSpan(0, start, i$7)); } else { var pos = i$7, at = order.length, isRTL = direction == "rtl" ? 1 : 0; for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} for (var j$2 = pos; j$2 < i$7;) { if (countsAsNum.test(types[j$2])) { if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); at += isRTL; } var nstart = j$2; for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} order.splice(at, 0, new BidiSpan(2, nstart, j$2)); at += isRTL; pos = j$2; } else { ++j$2; } } if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } } } if (direction == "ltr") { if (order[0].level == 1 && (m = str.match(/^\s+/))) { order[0].from = m[0].length; order.unshift(new BidiSpan(0, 0, m[0].length)); } if (lst(order).level == 1 && (m = str.match(/\s+$/))) { lst(order).to -= m[0].length; order.push(new BidiSpan(0, len - m[0].length, len)); } } return direction == "rtl" ? order.reverse() : order } })(); // Get the bidi ordering for the given line (and cache it). Returns // false for lines that are fully left-to-right, and an array of // BidiSpan objects otherwise. function getOrder(line, direction) { var order = line.order; if (order == null) { order = line.order = bidiOrdering(line.text, direction); } return order } // EVENT HANDLING // Lightweight event framework. on/off also work on DOM nodes, // registering native DOM handlers. var noHandlers = []; var on = function(emitter, type, f) { if (emitter.addEventListener) { emitter.addEventListener(type, f, false); } else if (emitter.attachEvent) { emitter.attachEvent("on" + type, f); } else { var map = emitter._handlers || (emitter._handlers = {}); map[type] = (map[type] || noHandlers).concat(f); } }; function getHandlers(emitter, type) { return emitter._handlers && emitter._handlers[type] || noHandlers } function off(emitter, type, f) { if (emitter.removeEventListener) { emitter.removeEventListener(type, f, false); } else if (emitter.detachEvent) { emitter.detachEvent("on" + type, f); } else { var map = emitter._handlers, arr = map && map[type]; if (arr) { var index = indexOf(arr, f); if (index > -1) { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); } } } } function signal(emitter, type /*, values...*/) { var handlers = getHandlers(emitter, type); if (!handlers.length) { return } var args = Array.prototype.slice.call(arguments, 2); for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); } } // The DOM events that CodeMirror handles can be overridden by // registering a (non-DOM) handler on the editor for the event name, // and preventDefault-ing the event in that handler. function signalDOMEvent(cm, e, override) { if (typeof e == "string") { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; } signal(cm, override || e.type, cm, e); return e_defaultPrevented(e) || e.codemirrorIgnore } function signalCursorActivity(cm) { var arr = cm._handlers && cm._handlers.cursorActivity; if (!arr) { return } var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) { set.push(arr[i]); } } } function hasHandler(emitter, type) { return getHandlers(emitter, type).length > 0 } // Add on and off methods to a constructor's prototype, to make // registering events on such objects more convenient. function eventMixin(ctor) { ctor.prototype.on = function(type, f) {on(this, type, f);}; ctor.prototype.off = function(type, f) {off(this, type, f);}; } // Due to the fact that we still support jurassic IE versions, some // compatibility wrappers are needed. function e_preventDefault(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } } function e_stopPropagation(e) { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } } function e_defaultPrevented(e) { return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false } function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} function e_target(e) {return e.target || e.srcElement} function e_button(e) { var b = e.which; if (b == null) { if (e.button & 1) { b = 1; } else if (e.button & 2) { b = 3; } else if (e.button & 4) { b = 2; } } if (mac && e.ctrlKey && b == 1) { b = 3; } return b } // Detect drag-and-drop var dragAndDrop = function() { // There is *some* kind of drag-and-drop support in IE6-8, but I // couldn't get it to work yet. if (ie && ie_version < 9) { return false } var div = elt('div'); return "draggable" in div || "dragDrop" in div }(); var zwspSupported; function zeroWidthElement(measure) { if (zwspSupported == null) { var test = elt("span", "\u200b"); removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); if (measure.firstChild.offsetHeight != 0) { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } } var node = zwspSupported ? elt("span", "\u200b") : elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); node.setAttribute("cm-text", ""); return node } // Feature-detect IE's crummy client rect reporting for bidi text var badBidiRects; function hasBadBidiRects(measure) { if (badBidiRects != null) { return badBidiRects } var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); var r0 = range(txt, 0, 1).getBoundingClientRect(); var r1 = range(txt, 1, 2).getBoundingClientRect(); removeChildren(measure); if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) return badBidiRects = (r1.right - r0.right < 3) } // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { var pos = 0, result = [], l = string.length; while (pos <= l) { var nl = string.indexOf("\n", pos); if (nl == -1) { nl = string.length; } var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); var rt = line.indexOf("\r"); if (rt != -1) { result.push(line.slice(0, rt)); pos += rt + 1; } else { result.push(line); pos = nl + 1; } } return result } : function (string) { return string.split(/\r\n?|\n/); }; var hasSelection = window.getSelection ? function (te) { try { return te.selectionStart != te.selectionEnd } catch(e) { return false } } : function (te) { var range; try {range = te.ownerDocument.selection.createRange();} catch(e) {} if (!range || range.parentElement() != te) { return false } return range.compareEndPoints("StartToEnd", range) != 0 }; var hasCopyEvent = (function () { var e = elt("div"); if ("oncopy" in e) { return true } e.setAttribute("oncopy", "return;"); return typeof e.oncopy == "function" })(); var badZoomedRects = null; function hasBadZoomedRects(measure) { if (badZoomedRects != null) { return badZoomedRects } var node = removeChildrenAndAdd(measure, elt("span", "x")); var normal = node.getBoundingClientRect(); var fromRange = range(node, 0, 1).getBoundingClientRect(); return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 } // Known modes, by name and by MIME var modes = {}, mimeModes = {}; // Extra arguments are stored as the mode's dependencies, which is // used by (legacy) mechanisms like loadmode.js to automatically // load a mode. (Preferred mechanism is the require/define calls.) function defineMode(name, mode) { if (arguments.length > 2) { mode.dependencies = Array.prototype.slice.call(arguments, 2); } modes[name] = mode; } function defineMIME(mime, spec) { mimeModes[mime] = spec; } // Given a MIME type, a {name, ...options} config object, or a name // string, return a mode config object. function resolveMode(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { var found = mimeModes[spec.name]; if (typeof found == "string") { found = {name: found}; } spec = createObj(found, spec); spec.name = found.name; } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { return resolveMode("application/xml") } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { return resolveMode("application/json") } if (typeof spec == "string") { return {name: spec} } else { return spec || {name: "null"} } } // Given a mode spec (anything that resolveMode accepts), find and // initialize an actual mode object. function getMode(options, spec) { spec = resolveMode(spec); var mfactory = modes[spec.name]; if (!mfactory) { return getMode(options, "text/plain") } var modeObj = mfactory(options, spec); if (modeExtensions.hasOwnProperty(spec.name)) { var exts = modeExtensions[spec.name]; for (var prop in exts) { if (!exts.hasOwnProperty(prop)) { continue } if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; } modeObj[prop] = exts[prop]; } } modeObj.name = spec.name; if (spec.helperType) { modeObj.helperType = spec.helperType; } if (spec.modeProps) { for (var prop$1 in spec.modeProps) { modeObj[prop$1] = spec.modeProps[prop$1]; } } return modeObj } // This can be used to attach properties to mode objects from // outside the actual mode definition. var modeExtensions = {}; function extendMode(mode, properties) { var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); copyObj(properties, exts); } function copyState(mode, state) { if (state === true) { return state } if (mode.copyState) { return mode.copyState(state) } var nstate = {}; for (var n in state) { var val = state[n]; if (val instanceof Array) { val = val.concat([]); } nstate[n] = val; } return nstate } // Given a mode and a state (for that mode), find the inner mode and // state at the position that the state refers to. function innerMode(mode, state) { var info; while (mode.innerMode) { info = mode.innerMode(state); if (!info || info.mode == mode) { break } state = info.state; mode = info.mode; } return info || {mode: mode, state: state} } function startState(mode, a1, a2) { return mode.startState ? mode.startState(a1, a2) : true } // STRING STREAM // Fed to the mode parsers, provides helper functions to make // parsers more succinct. var StringStream = function(string, tabSize, lineOracle) { this.pos = this.start = 0; this.string = string; this.tabSize = tabSize || 8; this.lastColumnPos = this.lastColumnValue = 0; this.lineStart = 0; this.lineOracle = lineOracle; }; StringStream.prototype.eol = function () {return this.pos >= this.string.length}; StringStream.prototype.sol = function () {return this.pos == this.lineStart}; StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; StringStream.prototype.next = function () { if (this.pos < this.string.length) { return this.string.charAt(this.pos++) } }; StringStream.prototype.eat = function (match) { var ch = this.string.charAt(this.pos); var ok; if (typeof match == "string") { ok = ch == match; } else { ok = ch && (match.test ? match.test(ch) : match(ch)); } if (ok) {++this.pos; return ch} }; StringStream.prototype.eatWhile = function (match) { var start = this.pos; while (this.eat(match)){} return this.pos > start }; StringStream.prototype.eatSpace = function () { var start = this.pos; while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.pos; } return this.pos > start }; StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;}; StringStream.prototype.skipTo = function (ch) { var found = this.string.indexOf(ch, this.pos); if (found > -1) {this.pos = found; return true} }; StringStream.prototype.backUp = function (n) {this.pos -= n;}; StringStream.prototype.column = function () { if (this.lastColumnPos < this.start) { this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); this.lastColumnPos = this.start; } return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) }; StringStream.prototype.indentation = function () { return countColumn(this.string, null, this.tabSize) - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) }; StringStream.prototype.match = function (pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }; var substr = this.string.substr(this.pos, pattern.length); if (cased(substr) == cased(pattern)) { if (consume !== false) { this.pos += pattern.length; } return true } } else { var match = this.string.slice(this.pos).match(pattern); if (match && match.index > 0) { return null } if (match && consume !== false) { this.pos += match[0].length; } return match } }; StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; StringStream.prototype.hideFirstChars = function (n, inner) { this.lineStart += n; try { return inner() } finally { this.lineStart -= n; } }; StringStream.prototype.lookAhead = function (n) { var oracle = this.lineOracle; return oracle && oracle.lookAhead(n) }; StringStream.prototype.baseToken = function () { var oracle = this.lineOracle; return oracle && oracle.baseToken(this.pos) }; // Find the line object corresponding to the given line number. function getLine(doc, n) { n -= doc.first; if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } var chunk = doc; while (!chunk.lines) { for (var i = 0;; ++i) { var child = chunk.children[i], sz = child.chunkSize(); if (n < sz) { chunk = child; break } n -= sz; } } return chunk.lines[n] } // Get the part of a document between two positions, as an array of // strings. function getBetween(doc, start, end) { var out = [], n = start.line; doc.iter(start.line, end.line + 1, function (line) { var text = line.text; if (n == end.line) { text = text.slice(0, end.ch); } if (n == start.line) { text = text.slice(start.ch); } out.push(text); ++n; }); return out } // Get the lines between from and to, as array of strings. function getLines(doc, from, to) { var out = []; doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value return out } // Update the height of a line, propagating the height change // upwards to parent nodes. function updateLineHeight(line, height) { var diff = height - line.height; if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } } } // Given a line object, find its line number by walking up through // its parent links. function lineNo(line) { if (line.parent == null) { return null } var cur = line.parent, no = indexOf(cur.lines, line); for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { for (var i = 0;; ++i) { if (chunk.children[i] == cur) { break } no += chunk.children[i].chunkSize(); } } return no + cur.first } // Find the line at the given vertical position, using the height // information in the document tree. function lineAtHeight(chunk, h) { var n = chunk.first; outer: do { for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { var child = chunk.children[i$1], ch = child.height; if (h < ch) { chunk = child; continue outer } h -= ch; n += child.chunkSize(); } return n } while (!chunk.lines) var i = 0; for (; i < chunk.lines.length; ++i) { var line = chunk.lines[i], lh = line.height; if (h < lh) { break } h -= lh; } return n + i } function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} function lineNumberFor(options, i) { return String(options.lineNumberFormatter(i + options.firstLineNumber)) } // A Pos instance represents a position within the text. function Pos(line, ch, sticky) { if ( sticky === void 0 ) sticky = null; if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } this.line = line; this.ch = ch; this.sticky = sticky; } // Compare two positions, return 0 if they are the same, a negative // number when a is less, and a positive number otherwise. function cmp(a, b) { return a.line - b.line || a.ch - b.ch } function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } function copyPos(x) {return Pos(x.line, x.ch)} function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } function minPos(a, b) { return cmp(a, b) < 0 ? a : b } // Most of the external API clips given positions to make sure they // actually exist within the document. function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} function clipPos(doc, pos) { if (pos.line < doc.first) { return Pos(doc.first, 0) } var last = doc.first + doc.size - 1; if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } return clipToLen(pos, getLine(doc, pos.line).text.length) } function clipToLen(pos, linelen) { var ch = pos.ch; if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } else if (ch < 0) { return Pos(pos.line, 0) } else { return pos } } function clipPosArray(doc, array) { var out = []; for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); } return out } var SavedContext = function(state, lookAhead) { this.state = state; this.lookAhead = lookAhead; }; var Context = function(doc, state, line, lookAhead) { this.state = state; this.doc = doc; this.line = line; this.maxLookAhead = lookAhead || 0; this.baseTokens = null; this.baseTokenPos = 1; }; Context.prototype.lookAhead = function (n) { var line = this.doc.getLine(this.line + n); if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; } return line }; Context.prototype.baseToken = function (n) { if (!this.baseTokens) { return null } while (this.baseTokens[this.baseTokenPos] <= n) { this.baseTokenPos += 2; } var type = this.baseTokens[this.baseTokenPos + 1]; return {type: type && type.replace(/( |^)overlay .*/, ""), size: this.baseTokens[this.baseTokenPos] - n} }; Context.prototype.nextLine = function () { this.line++; if (this.maxLookAhead > 0) { this.maxLookAhead--; } }; Context.fromSaved = function (doc, saved, line) { if (saved instanceof SavedContext) { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } else { return new Context(doc, copyState(doc.mode, saved), line) } }; Context.prototype.save = function (copy) { var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state }; // Compute a style array (an array starting with a mode generation // -- for invalidation -- followed by pairs of end positions and // style strings), which is used to highlight the tokens on the // line. function highlightLine(cm, line, context, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen], lineClasses = {}; // Compute the base array of styles runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, lineClasses, forceToEnd); var state = context.state; // Run overlays, adjust style array. var loop = function ( o ) { context.baseTokens = st; var overlay = cm.state.overlays[o], i = 1, at = 0; context.state = true; runMode(cm, line.text, overlay.mode, context, function (end, style) { var start = i; // Ensure there's a token end at the current position, and that i points at it while (at < end) { var i_end = st[i]; if (i_end > end) { st.splice(i, 1, end, st[i+1], i_end); } i += 2; at = Math.min(end, i_end); } if (!style) { return } if (overlay.opaque) { st.splice(start, i - start, end, "overlay " + style); i = start + 2; } else { for (; start < i; start += 2) { var cur = st[start+1]; st[start+1] = (cur ? cur + " " : "") + "overlay " + style; } } }, lineClasses); context.state = state; context.baseTokens = null; context.baseTokenPos = 1; }; for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} } function getLineStyles(cm, line, updateFrontier) { if (!line.styles || line.styles[0] != cm.state.modeGen) { var context = getContextBefore(cm, lineNo(line)); var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); var result = highlightLine(cm, line, context); if (resetState) { context.state = resetState; } line.stateAfter = context.save(!resetState); line.styles = result.styles; if (result.classes) { line.styleClasses = result.classes; } else if (line.styleClasses) { line.styleClasses = null; } if (updateFrontier === cm.doc.highlightFrontier) { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); } } return line.styles } function getContextBefore(cm, n, precise) { var doc = cm.doc, display = cm.display; if (!doc.mode.startState) { return new Context(doc, true, n) } var start = findStartLine(cm, n, precise); var saved = start > doc.first && getLine(doc, start - 1).stateAfter; var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); doc.iter(start, n, function (line) { processLine(cm, line.text, context); var pos = context.line; line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; context.nextLine(); }); if (precise) { doc.modeFrontier = context.line; } return context } // Lightweight form of highlight -- proceed over this line and // update state, but don't save a style array. Used for lines that // aren't currently visible. function processLine(cm, text, context, startAt) { var mode = cm.doc.mode; var stream = new StringStream(text, cm.options.tabSize, context); stream.start = stream.pos = startAt || 0; if (text == "") { callBlankLine(mode, context.state); } while (!stream.eol()) { readToken(mode, stream, context.state); stream.start = stream.pos; } } function callBlankLine(mode, state) { if (mode.blankLine) { return mode.blankLine(state) } if (!mode.innerMode) { return } var inner = innerMode(mode, state); if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } } function readToken(mode, stream, state, inner) { for (var i = 0; i < 10; i++) { if (inner) { inner[0] = innerMode(mode, state).mode; } var style = mode.token(stream, state); if (stream.pos > stream.start) { return style } } throw new Error("Mode " + mode.name + " failed to advance stream.") } var Token = function(stream, type, state) { this.start = stream.start; this.end = stream.pos; this.string = stream.current(); this.type = type || null; this.state = state; }; // Utility for getTokenAt and getLineTokens function takeToken(cm, pos, precise, asArray) { var doc = cm.doc, mode = doc.mode, style; pos = clipPos(doc, pos); var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise); var stream = new StringStream(line.text, cm.options.tabSize, context), tokens; if (asArray) { tokens = []; } while ((asArray || stream.pos < pos.ch) && !stream.eol()) { stream.start = stream.pos; style = readToken(mode, stream, context.state); if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); } } return asArray ? tokens : new Token(stream, style, context.state) } function extractLineClasses(type, output) { if (type) { for (;;) { var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); if (!lineClass) { break } type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); var prop = lineClass[1] ? "bgClass" : "textClass"; if (output[prop] == null) { output[prop] = lineClass[2]; } else if (!(new RegExp("(?:^|\\s)" + lineClass[2] + "(?:$|\\s)")).test(output[prop])) { output[prop] += " " + lineClass[2]; } } } return type } // Run the given mode's parser over a line, calling f for each token. function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { var flattenSpans = mode.flattenSpans; if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; } var curStart = 0, curStyle = null; var stream = new StringStream(text, cm.options.tabSize, context), style; var inner = cm.options.addModeClass && [null]; if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); } while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; if (forceToEnd) { processLine(cm, text, context, stream.pos); } stream.pos = text.length; style = null; } else { style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); } if (inner) { var mName = inner[0].name; if (mName) { style = "m-" + (style ? mName + " " + style : mName); } } if (!flattenSpans || curStyle != style) { while (curStart < stream.start) { curStart = Math.min(stream.start, curStart + 5000); f(curStart, curStyle); } curStyle = style; } stream.start = stream.pos; } while (curStart < stream.pos) { // Webkit seems to refuse to render text nodes longer than 57444 // characters, and returns inaccurate measurements in nodes // starting around 5000 chars. var pos = Math.min(stream.pos, curStart + 5000); f(pos, curStyle); curStart = pos; } } // Finds the line to start with when starting a parse. Tries to // find a line with a stateAfter, so that it can start with a // valid state. If that fails, it returns the line with the // smallest indentation, which tends to need the least context to // parse correctly. function findStartLine(cm, n, precise) { var minindent, minline, doc = cm.doc; var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); for (var search = n; search > lim; --search) { if (search <= doc.first) { return doc.first } var line = getLine(doc, search - 1), after = line.stateAfter; if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) { return search } var indented = countColumn(line.text, null, cm.options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; minindent = indented; } } return minline } function retreatFrontier(doc, n) { doc.modeFrontier = Math.min(doc.modeFrontier, n); if (doc.highlightFrontier < n - 10) { return } var start = doc.first; for (var line = n - 1; line > start; line--) { var saved = getLine(doc, line).stateAfter; // change is on 3 // state on line 1 looked ahead 2 -- so saw 3 // test 1 + 2 < 3 should cover this if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { start = line + 1; break } } doc.highlightFrontier = Math.min(doc.highlightFrontier, start); } // Optimize some code when these features are not used. var sawReadOnlySpans = false, sawCollapsedSpans = false; function seeReadOnlySpans() { sawReadOnlySpans = true; } function seeCollapsedSpans() { sawCollapsedSpans = true; } // TEXTMARKER SPANS function MarkedSpan(marker, from, to) { this.marker = marker; this.from = from; this.to = to; } // Search an array of spans for a span matching the given marker. function getMarkedSpanFor(spans, marker) { if (spans) { for (var i = 0; i < spans.length; ++i) { var span = spans[i]; if (span.marker == marker) { return span } } } } // Remove a span from an array, returning undefined if no spans are // left (we don't store arrays for lines without spans). function removeMarkedSpan(spans, span) { var r; for (var i = 0; i < spans.length; ++i) { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } } return r } // Add a span to a line. function addMarkedSpan(line, span, op) { var inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet)); if (inThisOp && line.markedSpans && inThisOp.has(line.markedSpans)) { line.markedSpans.push(span); } else { line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; if (inThisOp) { inThisOp.add(line.markedSpans); } } span.marker.attachLine(line); } // Used for the algorithm that adjusts markers for a change in the // document. These functions cut an array of spans at a given // character position, returning an array of remaining chunks (or // undefined if nothing remains). function markedSpansBefore(old, startCh, isInsert) { var nw; if (old) { for (var i = 0; i < old.length; ++i) { var span = old[i], marker = span.marker; var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); } } } return nw } function markedSpansAfter(old, endCh, isInsert) { var nw; if (old) { for (var i = 0; i < old.length; ++i) { var span = old[i], marker = span.marker; var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, span.to == null ? null : span.to - endCh)); } } } return nw } // Given a change object, compute the new set of marker spans that // cover the line in which the change took place. Removes spans // entirely within the change, reconnects spans belonging to the // same marker that appear on both sides of the change, and cuts off // spans partially within the change. Returns an array of span // arrays with one element for each line in (after) the change. function stretchSpansOverChange(doc, change) { if (change.full) { return null } var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; if (!oldFirst && !oldLast) { return null } var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; // Get the spans that 'stick out' on both sides var first = markedSpansBefore(oldFirst, startCh, isInsert); var last = markedSpansAfter(oldLast, endCh, isInsert); // Next, merge those two ends var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); if (first) { // Fix up .to properties of first for (var i = 0; i < first.length; ++i) { var span = first[i]; if (span.to == null) { var found = getMarkedSpanFor(last, span.marker); if (!found) { span.to = startCh; } else if (sameLine) { span.to = found.to == null ? null : found.to + offset; } } } } if (last) { // Fix up .from in last (or move them into first in case of sameLine) for (var i$1 = 0; i$1 < last.length; ++i$1) { var span$1 = last[i$1]; if (span$1.to != null) { span$1.to += offset; } if (span$1.from == null) { var found$1 = getMarkedSpanFor(first, span$1.marker); if (!found$1) { span$1.from = offset; if (sameLine) { (first || (first = [])).push(span$1); } } } else { span$1.from += offset; if (sameLine) { (first || (first = [])).push(span$1); } } } } // Make sure we didn't create any zero-length spans if (first) { first = clearEmptySpans(first); } if (last && last != first) { last = clearEmptySpans(last); } var newMarkers = [first]; if (!sameLine) { // Fill gap with whole-line-spans var gap = change.text.length - 2, gapMarkers; if (gap > 0 && first) { for (var i$2 = 0; i$2 < first.length; ++i$2) { if (first[i$2].to == null) { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } } for (var i$3 = 0; i$3 < gap; ++i$3) { newMarkers.push(gapMarkers); } newMarkers.push(last); } return newMarkers } // Remove spans that are empty and don't have a clearWhenEmpty // option of false. function clearEmptySpans(spans) { for (var i = 0; i < spans.length; ++i) { var span = spans[i]; if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) { spans.splice(i--, 1); } } if (!spans.length) { return null } return spans } // Used to 'clip' out readOnly ranges when making a change. function removeReadOnlyRanges(doc, from, to) { var markers = null; doc.iter(from.line, to.line + 1, function (line) { if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { var mark = line.markedSpans[i].marker; if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) { (markers || (markers = [])).push(mark); } } } }); if (!markers) { return null } var parts = [{from: from, to: to}]; for (var i = 0; i < markers.length; ++i) { var mk = markers[i], m = mk.find(0); for (var j = 0; j < parts.length; ++j) { var p = parts[j]; if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) { newParts.push({from: p.from, to: m.from}); } if (dto > 0 || !mk.inclusiveRight && !dto) { newParts.push({from: m.to, to: p.to}); } parts.splice.apply(parts, newParts); j += newParts.length - 3; } } return parts } // Connect or disconnect spans from a line. function detachMarkedSpans(line) { var spans = line.markedSpans; if (!spans) { return } for (var i = 0; i < spans.length; ++i) { spans[i].marker.detachLine(line); } line.markedSpans = null; } function attachMarkedSpans(line, spans) { if (!spans) { return } for (var i = 0; i < spans.length; ++i) { spans[i].marker.attachLine(line); } line.markedSpans = spans; } // Helpers used when computing which overlapping collapsed span // counts as the larger one. function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } // Returns a number indicating which of two overlapping collapsed // spans is larger (and thus includes the other). Falls back to // comparing ids when the spans cover exactly the same range. function compareCollapsedMarkers(a, b) { var lenDiff = a.lines.length - b.lines.length; if (lenDiff != 0) { return lenDiff } var aPos = a.find(), bPos = b.find(); var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); if (fromCmp) { return -fromCmp } var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); if (toCmp) { return toCmp } return b.id - a.id } // Find out whether a line ends or starts in a collapsed span. If // so, return the marker for that span. function collapsedSpanAtSide(line, start) { var sps = sawCollapsedSpans && line.markedSpans, found; if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { sp = sps[i]; if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } } } return found } function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } function collapsedSpanAround(line, ch) { var sps = sawCollapsedSpans && line.markedSpans, found; if (sps) { for (var i = 0; i < sps.length; ++i) { var sp = sps[i]; if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } } } return found } // Test whether there exists a collapsed span that partially // overlaps (covers the start or end, but not both) of a new span. // Such overlap is not allowed. function conflictingCollapsedRange(doc, lineNo, from, to, marker) { var line = getLine(doc, lineNo); var sps = sawCollapsedSpans && line.markedSpans; if (sps) { for (var i = 0; i < sps.length; ++i) { var sp = sps[i]; if (!sp.marker.collapsed) { continue } var found = sp.marker.find(0); var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) { return true } } } } // A visual line is a line as drawn on the screen. Folding, for // example, can cause multiple logical lines to appear on the same // visual line. This finds the start of the visual line that the // given line is part of (usually that is the line itself). function visualLine(line) { var merged; while (merged = collapsedSpanAtStart(line)) { line = merged.find(-1, true).line; } return line } function visualLineEnd(line) { var merged; while (merged = collapsedSpanAtEnd(line)) { line = merged.find(1, true).line; } return line } // Returns an array of logical lines that continue the visual line // started by the argument, or undefined if there are no such lines. function visualLineContinued(line) { var merged, lines; while (merged = collapsedSpanAtEnd(line)) { line = merged.find(1, true).line ;(lines || (lines = [])).push(line); } return lines } // Get the line number of the start of the visual line that the // given line number is part of. function visualLineNo(doc, lineN) { var line = getLine(doc, lineN), vis = visualLine(line); if (line == vis) { return lineN } return lineNo(vis) } // Get the line number of the start of the next visual line after // the given line. function visualLineEndNo(doc, lineN) { if (lineN > doc.lastLine()) { return lineN } var line = getLine(doc, lineN), merged; if (!lineIsHidden(doc, line)) { return lineN } while (merged = collapsedSpanAtEnd(line)) { line = merged.find(1, true).line; } return lineNo(line) + 1 } // Compute whether a line is hidden. Lines count as hidden when they // are part of a visual line that starts with another line, or when // they are entirely covered by collapsed, non-widget span. function lineIsHidden(doc, line) { var sps = sawCollapsedSpans && line.markedSpans; if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { sp = sps[i]; if (!sp.marker.collapsed) { continue } if (sp.from == null) { return true } if (sp.marker.widgetNode) { continue } if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) { return true } } } } function lineIsHiddenInner(doc, line, span) { if (span.to == null) { var end = span.marker.find(1, true); return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) } if (span.marker.inclusiveRight && span.to == line.text.length) { return true } for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && (sp.to == null || sp.to != span.from) && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) { return true } } } // Find the height above the given line. function heightAtLine(lineObj) { lineObj = visualLine(lineObj); var h = 0, chunk = lineObj.parent; for (var i = 0; i < chunk.lines.length; ++i) { var line = chunk.lines[i]; if (line == lineObj) { break } else { h += line.height; } } for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { for (var i$1 = 0; i$1 < p.children.length; ++i$1) { var cur = p.children[i$1]; if (cur == chunk) { break } else { h += cur.height; } } } return h } // Compute the character length of a line, taking into account // collapsed ranges (see markText) that might hide parts, and join // other lines onto it. function lineLength(line) { if (line.height == 0) { return 0 } var len = line.text.length, merged, cur = line; while (merged = collapsedSpanAtStart(cur)) { var found = merged.find(0, true); cur = found.from.line; len += found.from.ch - found.to.ch; } cur = line; while (merged = collapsedSpanAtEnd(cur)) { var found$1 = merged.find(0, true); len -= cur.text.length - found$1.from.ch; cur = found$1.to.line; len += cur.text.length - found$1.to.ch; } return len } // Find the longest line in the document. function findMaxLine(cm) { var d = cm.display, doc = cm.doc; d.maxLine = getLine(doc, doc.first); d.maxLineLength = lineLength(d.maxLine); d.maxLineChanged = true; doc.iter(function (line) { var len = lineLength(line); if (len > d.maxLineLength) { d.maxLineLength = len; d.maxLine = line; } }); } // LINE DATA STRUCTURE // Line objects. These hold state related to a line, including // highlighting info (the styles array). var Line = function(text, markedSpans, estimateHeight) { this.text = text; attachMarkedSpans(this, markedSpans); this.height = estimateHeight ? estimateHeight(this) : 1; }; Line.prototype.lineNo = function () { return lineNo(this) }; eventMixin(Line); // Change the content (text, markers) of a line. Automatically // invalidates cached information and tries to re-estimate the // line's height. function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; if (line.stateAfter) { line.stateAfter = null; } if (line.styles) { line.styles = null; } if (line.order != null) { line.order = null; } detachMarkedSpans(line); attachMarkedSpans(line, markedSpans); var estHeight = estimateHeight ? estimateHeight(line) : 1; if (estHeight != line.height) { updateLineHeight(line, estHeight); } } // Detach a line from the document tree and its markers. function cleanUpLine(line) { line.parent = null; detachMarkedSpans(line); } // Convert a style as returned by a mode (either null, or a string // containing one or more styles) to a CSS style. This is cached, // and also looks for line-wide styles. var styleToClassCache = {}, styleToClassCacheWithMode = {}; function interpretTokenStyle(style, options) { if (!style || /^\s*$/.test(style)) { return null } var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; return cache[style] || (cache[style] = style.replace(/\S+/g, "cm-$&")) } // Render the DOM representation of the text of a line. Also builds // up a 'line map', which points at the DOM nodes that represent // specific stretches of text, and is used by the measuring code. // The returned object contains the DOM node, this map, and // information about line-wide styles that were set by the mode. function buildLineContent(cm, lineView) { // The padding-right forces the element to have a 'border', which // is needed on Webkit to be able to get line-level bounding // rectangles for it (in measureChar). var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, col: 0, pos: 0, cm: cm, trailingSpace: false, splitSpaces: cm.getOption("lineWrapping")}; lineView.measure = {}; // Iterate over the logical lines that make up this visual line. for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0); builder.pos = 0; builder.addToken = buildToken; // Optionally wire in some hacks into the token-rendering // algorithm, to deal with browser quirks. if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) { builder.addToken = buildTokenBadBidi(builder.addToken, order); } builder.map = []; var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); if (line.styleClasses) { if (line.styleClasses.bgClass) { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); } if (line.styleClasses.textClass) { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); } } // Ensure at least a single node is present, for measuring. if (builder.map.length == 0) { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); } // Store the map and a cache object for the current logical line if (i == 0) { lineView.measure.map = builder.map; lineView.measure.cache = {}; } else { (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}); } } // See issue #2901 if (webkit) { var last = builder.content.lastChild; if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) { builder.content.className = "cm-tab-wrap-hack"; } } signal(cm, "renderLine", cm, lineView.line, builder.pre); if (builder.pre.className) { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); } return builder } function defaultSpecialCharPlaceholder(ch) { var token = elt("span", "\u2022", "cm-invalidchar"); token.title = "\\u" + ch.charCodeAt(0).toString(16); token.setAttribute("aria-label", token.title); return token } // Build up the DOM representation for a single token, and add it to // the line map. Takes care to render special characters separately. function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { if (!text) { return } var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; var special = builder.cm.state.specialChars, mustWrap = false; var content; if (!special.test(text)) { builder.col += text.length; content = document.createTextNode(displayText); builder.map.push(builder.pos, builder.pos + text.length, content); if (ie && ie_version < 9) { mustWrap = true; } builder.pos += text.length; } else { content = document.createDocumentFragment(); var pos = 0; while (true) { special.lastIndex = pos; var m = special.exec(text); var skipped = m ? m.index - pos : text.length - pos; if (skipped) { var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); } else { content.appendChild(txt); } builder.map.push(builder.pos, builder.pos + skipped, txt); builder.col += skipped; builder.pos += skipped; } if (!m) { break } pos += skipped + 1; var txt$1 = (void 0); if (m[0] == "\t") { var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); txt$1.setAttribute("role", "presentation"); txt$1.setAttribute("cm-text", "\t"); builder.col += tabWidth; } else if (m[0] == "\r" || m[0] == "\n") { txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); txt$1.setAttribute("cm-text", m[0]); builder.col += 1; } else { txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); txt$1.setAttribute("cm-text", m[0]); if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); } else { content.appendChild(txt$1); } builder.col += 1; } builder.map.push(builder.pos, builder.pos + 1, txt$1); builder.pos++; } } builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; if (style || startStyle || endStyle || mustWrap || css || attributes) { var fullStyle = style || ""; if (startStyle) { fullStyle += startStyle; } if (endStyle) { fullStyle += endStyle; } var token = elt("span", [content], fullStyle, css); if (attributes) { for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") { token.setAttribute(attr, attributes[attr]); } } } return builder.content.appendChild(token) } builder.content.appendChild(content); } // Change some spaces to NBSP to prevent the browser from collapsing // trailing spaces at the end of a line when rendering text (issue #1362). function splitSpaces(text, trailingBefore) { if (text.length > 1 && !/ /.test(text)) { return text } var spaceBefore = trailingBefore, result = ""; for (var i = 0; i < text.length; i++) { var ch = text.charAt(i); if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) { ch = "\u00a0"; } result += ch; spaceBefore = ch == " "; } return result } // Work around nonsense dimensions being reported for stretches of // right-to-left text. function buildTokenBadBidi(inner, order) { return function (builder, text, style, startStyle, endStyle, css, attributes) { style = style ? style + " cm-force-border" : "cm-force-border"; var start = builder.pos, end = start + text.length; for (;;) { // Find the part that overlaps with the start of this text var part = (void 0); for (var i = 0; i < order.length; i++) { part = order[i]; if (part.to > start && part.from <= start) { break } } if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) } inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes); startStyle = null; text = text.slice(part.to - start); start = part.to; } } } function buildCollapsedSpan(builder, size, marker, ignoreWidget) { var widget = !ignoreWidget && marker.widgetNode; if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); } if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { if (!widget) { widget = builder.content.appendChild(document.createElement("span")); } widget.setAttribute("cm-marker", marker.id); } if (widget) { builder.cm.display.input.setUneditable(widget); builder.content.appendChild(widget); } builder.pos += size; builder.trailingSpace = false; } // Outputs a number of spans to make up a line, taking highlighting // and marked text into account. function insertLineContent(line, builder, styles) { var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i$1 = 1; i$1 < styles.length; i$1+=2) { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); } return } var len = allText.length, pos = 0, i = 1, text = "", style, css; var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes; for (;;) { if (nextChange == pos) { // Update current marker set spanStyle = spanEndStyle = spanStartStyle = css = ""; attributes = null; collapsed = null; nextChange = Infinity; var foundBookmarks = [], endStyles = (void 0); for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { foundBookmarks.push(m); } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { if (sp.to != null && sp.to != pos && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } if (m.className) { spanStyle += " " + m.className; } if (m.css) { css = (css ? css + ";" : "") + m.css; } if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; } if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); } // support for the old title property // https://github.com/codemirror/CodeMirror/pull/5673 if (m.title) { (attributes || (attributes = {})).title = m.title; } if (m.attributes) { for (var attr in m.attributes) { (attributes || (attributes = {}))[attr] = m.attributes[attr]; } } if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) { collapsed = sp; } } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; } } if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } } if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } } if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, collapsed.marker, collapsed.from == null); if (collapsed.to == null) { return } if (collapsed.to == pos) { collapsed = false; } } } if (pos >= len) { break } var upto = Math.min(len, nextChange); while (true) { if (text) { var end = pos + text.length; if (!collapsed) { var tokenText = end > upto ? text.slice(0, upto - pos) : text; builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes); } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} pos = end; spanStartStyle = ""; } text = allText.slice(at, at = styles[i++]); style = interpretTokenStyle(styles[i++], builder.cm.options); } } } // These objects are used to represent the visible (currently drawn) // part of the document. A LineView may correspond to multiple // logical lines, if those are connected by collapsed ranges. function LineView(doc, line, lineN) { // The starting line this.line = line; // Continuing lines, if any this.rest = visualLineContinued(line); // Number of logical lines in this visual line this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; this.node = this.text = null; this.hidden = lineIsHidden(doc, line); } // Create a range of LineView objects for the given lines. function buildViewArray(cm, from, to) { var array = [], nextPos; for (var pos = from; pos < to; pos = nextPos) { var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); nextPos = pos + view.size; array.push(view); } return array } var operationGroup = null; function pushOperation(op) { if (operationGroup) { operationGroup.ops.push(op); } else { op.ownsGroup = operationGroup = { ops: [op], delayedCallbacks: [] }; } } function fireCallbacksForOps(group) { // Calls delayed callbacks and cursorActivity handlers until no // new ones appear var callbacks = group.delayedCallbacks, i = 0; do { for (; i < callbacks.length; i++) { callbacks[i].call(null); } for (var j = 0; j < group.ops.length; j++) { var op = group.ops[j]; if (op.cursorActivityHandlers) { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } } } } while (i < callbacks.length) } function finishOperation(op, endCb) { var group = op.ownsGroup; if (!group) { return } try { fireCallbacksForOps(group); } finally { operationGroup = null; endCb(group); } } var orphanDelayedCallbacks = null; // Often, we want to signal events at a point where we are in the // middle of some work, but don't want the handler to start calling // other methods on the editor, which might be in an inconsistent // state or simply not expect any other events to happen. // signalLater looks whether there are any handlers, and schedules // them to be executed when the last operation ends, or, if no // operation is active, when a timeout fires. function signalLater(emitter, type /*, values...*/) { var arr = getHandlers(emitter, type); if (!arr.length) { return } var args = Array.prototype.slice.call(arguments, 2), list; if (operationGroup) { list = operationGroup.delayedCallbacks; } else if (orphanDelayedCallbacks) { list = orphanDelayedCallbacks; } else { list = orphanDelayedCallbacks = []; setTimeout(fireOrphanDelayed, 0); } var loop = function ( i ) { list.push(function () { return arr[i].apply(null, args); }); }; for (var i = 0; i < arr.length; ++i) loop( i ); } function fireOrphanDelayed() { var delayed = orphanDelayedCallbacks; orphanDelayedCallbacks = null; for (var i = 0; i < delayed.length; ++i) { delayed[i](); } } // When an aspect of a line changes, a string is added to // lineView.changes. This updates the relevant part of the line's // DOM structure. function updateLineForChanges(cm, lineView, lineN, dims) { for (var j = 0; j < lineView.changes.length; j++) { var type = lineView.changes[j]; if (type == "text") { updateLineText(cm, lineView); } else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } else if (type == "class") { updateLineClasses(cm, lineView); } else if (type == "widget") { updateLineWidgets(cm, lineView, dims); } } lineView.changes = null; } // Lines with gutter elements, widgets or a background class need to // be wrapped, and have the extra elements added to the wrapper div function ensureLineWrapped(lineView) { if (lineView.node == lineView.text) { lineView.node = elt("div", null, null, "position: relative"); if (lineView.text.parentNode) { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); } lineView.node.appendChild(lineView.text); if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } } return lineView.node } function updateLineBackground(cm, lineView) { var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; if (cls) { cls += " CodeMirror-linebackground"; } if (lineView.background) { if (cls) { lineView.background.className = cls; } else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } } else if (cls) { var wrap = ensureLineWrapped(lineView); lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); cm.display.input.setUneditable(lineView.background); } } // Wrapper around buildLineContent which will reuse the structure // in display.externalMeasured when possible. function getLineContent(cm, lineView) { var ext = cm.display.externalMeasured; if (ext && ext.line == lineView.line) { cm.display.externalMeasured = null; lineView.measure = ext.measure; return ext.built } return buildLineContent(cm, lineView) } // Redraw the line's text. Interacts with the background and text // classes because the mode may output tokens that influence these // classes. function updateLineText(cm, lineView) { var cls = lineView.text.className; var built = getLineContent(cm, lineView); if (lineView.text == lineView.node) { lineView.node = built.pre; } lineView.text.parentNode.replaceChild(built.pre, lineView.text); lineView.text = built.pre; if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { lineView.bgClass = built.bgClass; lineView.textClass = built.textClass; updateLineClasses(cm, lineView); } else if (cls) { lineView.text.className = cls; } } function updateLineClasses(cm, lineView) { updateLineBackground(cm, lineView); if (lineView.line.wrapClass) { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } else if (lineView.node != lineView.text) { lineView.node.className = ""; } var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; lineView.text.className = textClass || ""; } function updateLineGutter(cm, lineView, lineN, dims) { if (lineView.gutter) { lineView.node.removeChild(lineView.gutter); lineView.gutter = null; } if (lineView.gutterBackground) { lineView.node.removeChild(lineView.gutterBackground); lineView.gutterBackground = null; } if (lineView.line.gutterClass) { var wrap = ensureLineWrapped(lineView); lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")); cm.display.input.setUneditable(lineView.gutterBackground); wrap.insertBefore(lineView.gutterBackground, lineView.text); } var markers = lineView.line.gutterMarkers; if (cm.options.lineNumbers || markers) { var wrap$1 = ensureLineWrapped(lineView); var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); gutterWrap.setAttribute("aria-hidden", "true"); cm.display.input.setUneditable(gutterWrap); wrap$1.insertBefore(gutterWrap, lineView.text); if (lineView.line.gutterClass) { gutterWrap.className += " " + lineView.line.gutterClass; } if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) { lineView.lineNumber = gutterWrap.appendChild( elt("div", lineNumberFor(cm.options, lineN), "CodeMirror-linenumber CodeMirror-gutter-elt", ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); } if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) { var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id]; if (found) { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); } } } } } function updateLineWidgets(cm, lineView, dims) { if (lineView.alignable) { lineView.alignable = null; } var isWidget = classTest("CodeMirror-linewidget"); for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { next = node.nextSibling; if (isWidget.test(node.className)) { lineView.node.removeChild(node); } } insertLineWidgets(cm, lineView, dims); } // Build a line's DOM representation from scratch function buildLineElement(cm, lineView, lineN, dims) { var built = getLineContent(cm, lineView); lineView.text = lineView.node = built.pre; if (built.bgClass) { lineView.bgClass = built.bgClass; } if (built.textClass) { lineView.textClass = built.textClass; } updateLineClasses(cm, lineView); updateLineGutter(cm, lineView, lineN, dims); insertLineWidgets(cm, lineView, dims); return lineView.node } // A lineView may contain multiple logical lines (when merged by // collapsed spans). The widgets for all of them need to be drawn. function insertLineWidgets(cm, lineView, dims) { insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } } } function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { if (!line.widgets) { return } var wrap = ensureLineWrapped(lineView); for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget" + (widget.className ? " " + widget.className : "")); if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); } positionLineWidget(widget, node, lineView, dims); cm.display.input.setUneditable(node); if (allowAbove && widget.above) { wrap.insertBefore(node, lineView.gutter || lineView.text); } else { wrap.appendChild(node); } signalLater(widget, "redraw"); } } function positionLineWidget(widget, node, lineView, dims) { if (widget.noHScroll) { (lineView.alignable || (lineView.alignable = [])).push(node); var width = dims.wrapperWidth; node.style.left = dims.fixedPos + "px"; if (!widget.coverGutter) { width -= dims.gutterTotalWidth; node.style.paddingLeft = dims.gutterTotalWidth + "px"; } node.style.width = width + "px"; } if (widget.coverGutter) { node.style.zIndex = 5; node.style.position = "relative"; if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; } } } function widgetHeight(widget) { if (widget.height != null) { return widget.height } var cm = widget.doc.cm; if (!cm) { return 0 } if (!contains(document.body, widget.node)) { var parentStyle = "position: relative;"; if (widget.coverGutter) { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; } if (widget.noHScroll) { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; } removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); } return widget.height = widget.node.parentNode.offsetHeight } // Return true when the given mouse event happened in a widget function eventInWidget(display, e) { for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || (n.parentNode == display.sizer && n != display.mover)) { return true } } } // POSITION MEASUREMENT function paddingTop(display) {return display.lineSpace.offsetTop} function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} function paddingH(display) { if (display.cachedPaddingH) { return display.cachedPaddingH } var e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like")); var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; } return data } function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } function displayWidth(cm) { return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth } function displayHeight(cm) { return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight } // Ensure the lineView.wrapping.heights array is populated. This is // an array of bottom offsets for the lines that make up a drawn // line. When lineWrapping is on, there might be more than one // height. function ensureLineHeights(cm, lineView, rect) { var wrapping = cm.options.lineWrapping; var curWidth = wrapping && displayWidth(cm); if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { var heights = lineView.measure.heights = []; if (wrapping) { lineView.measure.width = curWidth; var rects = lineView.text.firstChild.getClientRects(); for (var i = 0; i < rects.length - 1; i++) { var cur = rects[i], next = rects[i + 1]; if (Math.abs(cur.bottom - next.bottom) > 2) { heights.push((cur.bottom + next.top) / 2 - rect.top); } } } heights.push(rect.bottom - rect.top); } } // Find a line map (mapping character offsets to text nodes) and a // measurement cache for the given line number. (A line view might // contain multiple lines when collapsed ranges are present.) function mapFromLineView(lineView, line, lineN) { if (lineView.line == line) { return {map: lineView.measure.map, cache: lineView.measure.cache} } if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) { if (lineView.rest[i] == line) { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) { if (lineNo(lineView.rest[i$1]) > lineN) { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } } } // Render a line into the hidden node display.externalMeasured. Used // when measurement is needed for a line that's not in the viewport. function updateExternalMeasurement(cm, line) { line = visualLine(line); var lineN = lineNo(line); var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); view.lineN = lineN; var built = view.built = buildLineContent(cm, view); view.text = built.pre; removeChildrenAndAdd(cm.display.lineMeasure, built.pre); return view } // Get a {top, bottom, left, right} box (in line-local coordinates) // for a given character. function measureChar(cm, line, ch, bias) { return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) } // Find a line view that corresponds to the given line number. function findViewForLine(cm, lineN) { if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) { return cm.display.view[findViewIndex(cm, lineN)] } var ext = cm.display.externalMeasured; if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) { return ext } } // Measurement can be split in two steps, the set-up work that // applies to the whole line, and the measurement of the actual // character. Functions like coordsChar, that need to do a lot of // measurements in a row, can thus ensure that the set-up work is // only done once. function prepareMeasureForLine(cm, line) { var lineN = lineNo(line); var view = findViewForLine(cm, lineN); if (view && !view.text) { view = null; } else if (view && view.changes) { updateLineForChanges(cm, view, lineN, getDimensions(cm)); cm.curOp.forceUpdate = true; } if (!view) { view = updateExternalMeasurement(cm, line); } var info = mapFromLineView(view, line, lineN); return { line: line, view: view, rect: null, map: info.map, cache: info.cache, before: info.before, hasHeights: false } } // Given a prepared measurement object, measures the position of an // actual character (or fetches it from the cache). function measureCharPrepared(cm, prepared, ch, bias, varHeight) { if (prepared.before) { ch = -1; } var key = ch + (bias || ""), found; if (prepared.cache.hasOwnProperty(key)) { found = prepared.cache[key]; } else { if (!prepared.rect) { prepared.rect = prepared.view.text.getBoundingClientRect(); } if (!prepared.hasHeights) { ensureLineHeights(cm, prepared.view, prepared.rect); prepared.hasHeights = true; } found = measureCharInner(cm, prepared, ch, bias); if (!found.bogus) { prepared.cache[key] = found; } } return {left: found.left, right: found.right, top: varHeight ? found.rtop : found.top, bottom: varHeight ? found.rbottom : found.bottom} } var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; function nodeAndOffsetInLineMap(map, ch, bias) { var node, start, end, collapse, mStart, mEnd; // First, search the line map for the text node corresponding to, // or closest to, the target character. for (var i = 0; i < map.length; i += 3) { mStart = map[i]; mEnd = map[i + 1]; if (ch < mStart) { start = 0; end = 1; collapse = "left"; } else if (ch < mEnd) { start = ch - mStart; end = start + 1; } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { end = mEnd - mStart; start = end - 1; if (ch >= mEnd) { collapse = "right"; } } if (start != null) { node = map[i + 2]; if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) { collapse = bias; } if (bias == "left" && start == 0) { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { node = map[(i -= 3) + 2]; collapse = "left"; } } if (bias == "right" && start == mEnd - mStart) { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { node = map[(i += 3) + 2]; collapse = "right"; } } break } } return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} } function getUsefulRect(rects, bias) { var rect = nullRect; if (bias == "left") { for (var i = 0; i < rects.length; i++) { if ((rect = rects[i]).left != rect.right) { break } } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { if ((rect = rects[i$1]).left != rect.right) { break } } } return rect } function measureCharInner(cm, prepared, ch, bias) { var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); var node = place.node, start = place.start, end = place.end, collapse = place.collapse; var rect; if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; } while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; } if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { rect = node.parentNode.getBoundingClientRect(); } else { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); } if (rect.left || rect.right || start == 0) { break } end = start; start = start - 1; collapse = "right"; } if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); } } else { // If it is a widget, simply get the box for the whole widget. if (start > 0) { collapse = bias = "right"; } var rects; if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) { rect = rects[bias == "right" ? rects.length - 1 : 0]; } else { rect = node.getBoundingClientRect(); } } if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { var rSpan = node.parentNode.getClientRects()[0]; if (rSpan) { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; } else { rect = nullRect; } } var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; var mid = (rtop + rbot) / 2; var heights = prepared.view.measure.heights; var i = 0; for (; i < heights.length - 1; i++) { if (mid < heights[i]) { break } } var top = i ? heights[i - 1] : 0, bot = heights[i]; var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, top: top, bottom: bot}; if (!rect.left && !rect.right) { result.bogus = true; } if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } return result } // Work around problem with bounding client rects on ranges being // returned incorrectly when zoomed on IE10 and below. function maybeUpdateRectForZooming(measure, rect) { if (!window.screen || screen.logicalXDPI == null || screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) { return rect } var scaleX = screen.logicalXDPI / screen.deviceXDPI; var scaleY = screen.logicalYDPI / screen.deviceYDPI; return {left: rect.left * scaleX, right: rect.right * scaleX, top: rect.top * scaleY, bottom: rect.bottom * scaleY} } function clearLineMeasurementCacheFor(lineView) { if (lineView.measure) { lineView.measure.cache = {}; lineView.measure.heights = null; if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) { lineView.measure.caches[i] = {}; } } } } function clearLineMeasurementCache(cm) { cm.display.externalMeasure = null; removeChildren(cm.display.lineMeasure); for (var i = 0; i < cm.display.view.length; i++) { clearLineMeasurementCacheFor(cm.display.view[i]); } } function clearCaches(cm) { clearLineMeasurementCache(cm); cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; } cm.display.lineNumChars = null; } function pageScrollX(doc) { // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 // which causes page_Offset and bounding client rects to use // different reference viewports and invalidate our calculations. if (chrome && android) { return -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft)) } return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft } function pageScrollY(doc) { if (chrome && android) { return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop)) } return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop } function widgetTopHeight(lineObj) { var ref = visualLine(lineObj); var widgets = ref.widgets; var height = 0; if (widgets) { for (var i = 0; i < widgets.length; ++i) { if (widgets[i].above) { height += widgetHeight(widgets[i]); } } } return height } // Converts a {top, bottom, left, right} box from line-local // coordinates into another coordinate system. Context may be one of // "line", "div" (display.lineDiv), "local"./null (editor), "window", // or "page". function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { if (!includeWidgets) { var height = widgetTopHeight(lineObj); rect.top += height; rect.bottom += height; } if (context == "line") { return rect } if (!context) { context = "local"; } var yOff = heightAtLine(lineObj); if (context == "local") { yOff += paddingTop(cm.display); } else { yOff -= cm.display.viewOffset; } if (context == "page" || context == "window") { var lOff = cm.display.lineSpace.getBoundingClientRect(); yOff += lOff.top + (context == "window" ? 0 : pageScrollY(doc(cm))); var xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm))); rect.left += xOff; rect.right += xOff; } rect.top += yOff; rect.bottom += yOff; return rect } // Coverts a box from "div" coords to another coordinate system. // Context may be "window", "page", "div", or "local"./null. function fromCoordSystem(cm, coords, context) { if (context == "div") { return coords } var left = coords.left, top = coords.top; // First move into "page" coordinate system if (context == "page") { left -= pageScrollX(doc(cm)); top -= pageScrollY(doc(cm)); } else if (context == "local" || !context) { var localBox = cm.display.sizer.getBoundingClientRect(); left += localBox.left; top += localBox.top; } var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} } function charCoords(cm, pos, context, lineObj, bias) { if (!lineObj) { lineObj = getLine(cm.doc, pos.line); } return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) } // Returns a box for a given cursor position, which may have an // 'other' property containing the position of the secondary cursor // on a bidi boundary. // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` // and after `char - 1` in writing order of `char - 1` // A cursor Pos(line, char, "after") is on the same visual line as `char` // and before `char` in writing order of `char` // Examples (upper-case letters are RTL, lower-case are LTR): // Pos(0, 1, ...) // before after // ab a|b a|b // aB a|B aB| // Ab |Ab A|b // AB B|A B|A // Every position after the last character on a line is considered to stick // to the last character on the line. function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { lineObj = lineObj || getLine(cm.doc, pos.line); if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } function get(ch, right) { var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); if (right) { m.left = m.right; } else { m.right = m.left; } return intoCoordSystem(cm, lineObj, m, context) } var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky; if (ch >= lineObj.text.length) { ch = lineObj.text.length; sticky = "before"; } else if (ch <= 0) { ch = 0; sticky = "after"; } if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } function getBidi(ch, partPos, invert) { var part = order[partPos], right = part.level == 1; return get(invert ? ch - 1 : ch, right != invert) } var partPos = getBidiPartAt(order, ch, sticky); var other = bidiOther; var val = getBidi(ch, partPos, sticky == "before"); if (other != null) { val.other = getBidi(ch, other, sticky != "before"); } return val } // Used to cheaply estimate the coordinates for a position. Used for // intermediate scroll updates. function estimateCoords(cm, pos) { var left = 0; pos = clipPos(cm.doc, pos); if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; } var lineObj = getLine(cm.doc, pos.line); var top = heightAtLine(lineObj) + paddingTop(cm.display); return {left: left, right: left, top: top, bottom: top + lineObj.height} } // Positions returned by coordsChar contain some extra information. // xRel is the relative x position of the input coordinates compared // to the found position (so xRel > 0 means the coordinates are to // the right of the character position, for example). When outside // is true, that means the coordinates lie outside the line's // vertical range. function PosWithInfo(line, ch, sticky, outside, xRel) { var pos = Pos(line, ch, sticky); pos.xRel = xRel; if (outside) { pos.outside = outside; } return pos } // Compute the character position closest to the given coordinates. // Input must be lineSpace-local ("div" coordinate system). function coordsChar(cm, x, y) { var doc = cm.doc; y += cm.display.viewOffset; if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) } var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; if (lineN > last) { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) } if (x < 0) { x = 0; } var lineObj = getLine(doc, lineN); for (;;) { var found = coordsCharInner(cm, lineObj, lineN, x, y); var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0)); if (!collapsed) { return found } var rangeEnd = collapsed.find(1); if (rangeEnd.line == lineN) { return rangeEnd } lineObj = getLine(doc, lineN = rangeEnd.line); } } function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { y -= widgetTopHeight(lineObj); var end = lineObj.text.length; var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); return {begin: begin, end: end} } function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) } // Returns true if the given side of a box is after the given // coordinates, in top-to-bottom, left-to-right order. function boxIsAfter(box, x, y, left) { return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x } function coordsCharInner(cm, lineObj, lineNo, x, y) { // Move y into line-local coordinate space y -= heightAtLine(lineObj); var preparedMeasure = prepareMeasureForLine(cm, lineObj); // When directly calling `measureCharPrepared`, we have to adjust // for the widgets at this line. var widgetHeight = widgetTopHeight(lineObj); var begin = 0, end = lineObj.text.length, ltr = true; var order = getOrder(lineObj, cm.doc.direction); // If the line isn't plain left-to-right text, first figure out // which bidi section the coordinates fall into. if (order) { var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) (cm, lineObj, lineNo, preparedMeasure, order, x, y); ltr = part.level != 1; // The awkward -1 offsets are needed because findFirst (called // on these below) will treat its first bound as inclusive, // second as exclusive, but we want to actually address the // characters in the part's range begin = ltr ? part.from : part.to - 1; end = ltr ? part.to : part.from - 1; } // A binary search to find the first character whose bounding box // starts after the coordinates. If we run across any whose box wrap // the coordinates, store that. var chAround = null, boxAround = null; var ch = findFirst(function (ch) { var box = measureCharPrepared(cm, preparedMeasure, ch); box.top += widgetHeight; box.bottom += widgetHeight; if (!boxIsAfter(box, x, y, false)) { return false } if (box.top <= y && box.left <= x) { chAround = ch; boxAround = box; } return true }, begin, end); var baseX, sticky, outside = false; // If a box around the coordinates was found, use that if (boxAround) { // Distinguish coordinates nearer to the left or right side of the box var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; ch = chAround + (atStart ? 0 : 1); sticky = atStart ? "after" : "before"; baseX = atLeft ? boxAround.left : boxAround.right; } else { // (Adjust for extended bound, if necessary.) if (!ltr && (ch == end || ch == begin)) { ch++; } // To determine which side to associate with, get the box to the // left of the character and compare it's vertical position to the // coordinates sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? "after" : "before"; // Now get accurate coordinates for this place, in order to get a // base X position var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure); baseX = coords.left; outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0; } ch = skipExtendingChars(lineObj.text, ch, 1); return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) } function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { // Bidi parts are sorted left-to-right, and in a non-line-wrapping // situation, we can take this ordering to correspond to the visual // ordering. This finds the first part whose end is after the given // coordinates. var index = findFirst(function (i) { var part = order[i], ltr = part.level != 1; return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), "line", lineObj, preparedMeasure), x, y, true) }, 0, order.length - 1); var part = order[index]; // If this isn't the first part, the part's start is also after // the coordinates, and the coordinates aren't on the same line as // that start, move one part back. if (index > 0) { var ltr = part.level != 1; var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), "line", lineObj, preparedMeasure); if (boxIsAfter(start, x, y, true) && start.top > y) { part = order[index - 1]; } } return part } function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { // In a wrapped line, rtl text on wrapping boundaries can do things // that don't correspond to the ordering in our `order` array at // all, so a binary search doesn't work, and we want to return a // part that only spans one line so that the binary search in // coordsCharInner is safe. As such, we first find the extent of the // wrapped line, and then do a flat search in which we discard any // spans that aren't on the line. var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); var begin = ref.begin; var end = ref.end; if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } var part = null, closestDist = null; for (var i = 0; i < order.length; i++) { var p = order[i]; if (p.from >= end || p.to <= begin) { continue } var ltr = p.level != 1; var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; // Weigh against spans ending before this, so that they are only // picked if nothing ends after var dist = endX < x ? x - endX + 1e9 : endX - x; if (!part || closestDist > dist) { part = p; closestDist = dist; } } if (!part) { part = order[order.length - 1]; } // Clip the part to the wrapped line. if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } return part } var measureText; // Compute the default text height. function textHeight(display) { if (display.cachedTextHeight != null) { return display.cachedTextHeight } if (measureText == null) { measureText = elt("pre", null, "CodeMirror-line-like"); // Measure a bunch of lines, for browsers that compute // fractional heights. for (var i = 0; i < 49; ++i) { measureText.appendChild(document.createTextNode("x")); measureText.appendChild(elt("br")); } measureText.appendChild(document.createTextNode("x")); } removeChildrenAndAdd(display.measure, measureText); var height = measureText.offsetHeight / 50; if (height > 3) { display.cachedTextHeight = height; } removeChildren(display.measure); return height || 1 } // Compute the default character width. function charWidth(display) { if (display.cachedCharWidth != null) { return display.cachedCharWidth } var anchor = elt("span", "xxxxxxxxxx"); var pre = elt("pre", [anchor], "CodeMirror-line-like"); removeChildrenAndAdd(display.measure, pre); var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; if (width > 2) { display.cachedCharWidth = width; } return width || 10 } // Do a bulk-read of the DOM positions and sizes needed to draw the // view, so that we don't interleave reading and writing to the DOM. function getDimensions(cm) { var d = cm.display, left = {}, width = {}; var gutterLeft = d.gutters.clientLeft; for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { var id = cm.display.gutterSpecs[i].className; left[id] = n.offsetLeft + n.clientLeft + gutterLeft; width[id] = n.clientWidth; } return {fixedPos: compensateForHScroll(d), gutterTotalWidth: d.gutters.offsetWidth, gutterLeft: left, gutterWidth: width, wrapperWidth: d.wrapper.clientWidth} } // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, // but using getBoundingClientRect to get a sub-pixel-accurate // result. function compensateForHScroll(display) { return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left } // Returns a function that estimates the height of a line, to use as // first approximation until the line becomes visible (and is thus // properly measurable). function estimateHeight(cm) { var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); return function (line) { if (lineIsHidden(cm.doc, line)) { return 0 } var widgetsHeight = 0; if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; } } } if (wrapping) { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } else { return widgetsHeight + th } } } function estimateLineHeights(cm) { var doc = cm.doc, est = estimateHeight(cm); doc.iter(function (line) { var estHeight = est(line); if (estHeight != line.height) { updateLineHeight(line, estHeight); } }); } // Given a mouse event, find the corresponding position. If liberal // is false, it checks whether a gutter or scrollbar was clicked, // and returns null if it was. forRect is used by rectangular // selections, and tries to estimate a character position even for // coordinates beyond the right of the text. function posFromMouse(cm, e, liberal, forRect) { var display = cm.display; if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } var x, y, space = display.lineSpace.getBoundingClientRect(); // Fails unpredictably on IE[67] when mouse is dragged around quickly. try { x = e.clientX - space.left; y = e.clientY - space.top; } catch (e$1) { return null } var coords = coordsChar(cm, x, y), line; if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); } return coords } // Find the view element corresponding to a given line. Return null // when the line isn't visible. function findViewIndex(cm, n) { if (n >= cm.display.viewTo) { return null } n -= cm.display.viewFrom; if (n < 0) { return null } var view = cm.display.view; for (var i = 0; i < view.length; i++) { n -= view[i].size; if (n < 0) { return i } } } // Updates the display.view data structure for a given change to the // document. From and to are in pre-change coordinates. Lendiff is // the amount of lines added or subtracted by the change. This is // used for changes that span multiple lines, or change the way // lines are divided into visual lines. regLineChange (below) // registers single-line changes. function regChange(cm, from, to, lendiff) { if (from == null) { from = cm.doc.first; } if (to == null) { to = cm.doc.first + cm.doc.size; } if (!lendiff) { lendiff = 0; } var display = cm.display; if (lendiff && to < display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers > from)) { display.updateLineNumbers = from; } cm.curOp.viewChanged = true; if (from >= display.viewTo) { // Change after if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) { resetView(cm); } } else if (to <= display.viewFrom) { // Change before if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { resetView(cm); } else { display.viewFrom += lendiff; display.viewTo += lendiff; } } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap resetView(cm); } else if (from <= display.viewFrom) { // Top overlap var cut = viewCuttingPoint(cm, to, to + lendiff, 1); if (cut) { display.view = display.view.slice(cut.index); display.viewFrom = cut.lineN; display.viewTo += lendiff; } else { resetView(cm); } } else if (to >= display.viewTo) { // Bottom overlap var cut$1 = viewCuttingPoint(cm, from, from, -1); if (cut$1) { display.view = display.view.slice(0, cut$1.index); display.viewTo = cut$1.lineN; } else { resetView(cm); } } else { // Gap in the middle var cutTop = viewCuttingPoint(cm, from, from, -1); var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); if (cutTop && cutBot) { display.view = display.view.slice(0, cutTop.index) .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) .concat(display.view.slice(cutBot.index)); display.viewTo += lendiff; } else { resetView(cm); } } var ext = display.externalMeasured; if (ext) { if (to < ext.lineN) { ext.lineN += lendiff; } else if (from < ext.lineN + ext.size) { display.externalMeasured = null; } } } // Register a change to a single line. Type must be one of "text", // "gutter", "class", "widget" function regLineChange(cm, line, type) { cm.curOp.viewChanged = true; var display = cm.display, ext = cm.display.externalMeasured; if (ext && line >= ext.lineN && line < ext.lineN + ext.size) { display.externalMeasured = null; } if (line < display.viewFrom || line >= display.viewTo) { return } var lineView = display.view[findViewIndex(cm, line)]; if (lineView.node == null) { return } var arr = lineView.changes || (lineView.changes = []); if (indexOf(arr, type) == -1) { arr.push(type); } } // Clear the view. function resetView(cm) { cm.display.viewFrom = cm.display.viewTo = cm.doc.first; cm.display.view = []; cm.display.viewOffset = 0; } function viewCuttingPoint(cm, oldN, newN, dir) { var index = findViewIndex(cm, oldN), diff, view = cm.display.view; if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) { return {index: index, lineN: newN} } var n = cm.display.viewFrom; for (var i = 0; i < index; i++) { n += view[i].size; } if (n != oldN) { if (dir > 0) { if (index == view.length - 1) { return null } diff = (n + view[index].size) - oldN; index++; } else { diff = n - oldN; } oldN += diff; newN += diff; } while (visualLineNo(cm.doc, newN) != newN) { if (index == (dir < 0 ? 0 : view.length - 1)) { return null } newN += dir * view[index - (dir < 0 ? 1 : 0)].size; index += dir; } return {index: index, lineN: newN} } // Force the view to cover a given range, adding empty view element // or clipping off existing ones as needed. function adjustView(cm, from, to) { var display = cm.display, view = display.view; if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { display.view = buildViewArray(cm, from, to); display.viewFrom = from; } else { if (display.viewFrom > from) { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); } else if (display.viewFrom < from) { display.view = display.view.slice(findViewIndex(cm, from)); } display.viewFrom = from; if (display.viewTo < to) { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); } else if (display.viewTo > to) { display.view = display.view.slice(0, findViewIndex(cm, to)); } } display.viewTo = to; } // Count the number of lines in the view whose DOM representation is // out of date (or nonexistent). function countDirtyView(cm) { var view = cm.display.view, dirty = 0; for (var i = 0; i < view.length; i++) { var lineView = view[i]; if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; } } return dirty } function updateSelection(cm) { cm.display.input.showSelection(cm.display.input.prepareSelection()); } function prepareSelection(cm, primary) { if ( primary === void 0 ) primary = true; var doc = cm.doc, result = {}; var curFragment = result.cursors = document.createDocumentFragment(); var selFragment = result.selection = document.createDocumentFragment(); var customCursor = cm.options.$customCursor; if (customCursor) { primary = true; } for (var i = 0; i < doc.sel.ranges.length; i++) { if (!primary && i == doc.sel.primIndex) { continue } var range = doc.sel.ranges[i]; if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } var collapsed = range.empty(); if (customCursor) { var head = customCursor(cm, range); if (head) { drawSelectionCursor(cm, head, curFragment); } } else if (collapsed || cm.options.showCursorWhenSelecting) { drawSelectionCursor(cm, range.head, curFragment); } if (!collapsed) { drawSelectionRange(cm, range, selFragment); } } return result } // Draws a cursor for the given range function drawSelectionCursor(cm, head, output) { var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); cursor.style.left = pos.left + "px"; cursor.style.top = pos.top + "px"; cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) { var charPos = charCoords(cm, head, "div", null, null); var width = charPos.right - charPos.left; cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px"; } if (pos.other) { // Secondary cursor, shown when on a 'jump' in bi-directional text var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); otherCursor.style.display = ""; otherCursor.style.left = pos.other.left + "px"; otherCursor.style.top = pos.other.top + "px"; otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; } } function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } // Draws the given range as a highlighted selection function drawSelectionRange(cm, range, output) { var display = cm.display, doc = cm.doc; var fragment = document.createDocumentFragment(); var padding = paddingH(cm.display), leftSide = padding.left; var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; var docLTR = doc.direction == "ltr"; function add(left, top, width, bottom) { if (top < 0) { top = 0; } top = Math.round(top); bottom = Math.round(bottom); fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))); } function drawForLine(line, fromArg, toArg) { var lineObj = getLine(doc, line); var lineLen = lineObj.text.length; var start, end; function coords(ch, bias) { return charCoords(cm, Pos(line, ch), "div", lineObj, bias) } function wrapX(pos, dir, side) { var extent = wrappedLineExtentChar(cm, lineObj, null, pos); var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); return coords(ch, prop)[prop] } var order = getOrder(lineObj, doc.direction); iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { var ltr = dir == "ltr"; var fromPos = coords(from, ltr ? "left" : "right"); var toPos = coords(to - 1, ltr ? "right" : "left"); var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; var first = i == 0, last = !order || i == order.length - 1; if (toPos.top - fromPos.top <= 3) { // Single line var openLeft = (docLTR ? openStart : openEnd) && first; var openRight = (docLTR ? openEnd : openStart) && last; var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; add(left, fromPos.top, right - left, fromPos.bottom); } else { // Multiple lines var topLeft, topRight, botLeft, botRight; if (ltr) { topLeft = docLTR && openStart && first ? leftSide : fromPos.left; topRight = docLTR ? rightSide : wrapX(from, dir, "before"); botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); botRight = docLTR && openEnd && last ? rightSide : toPos.right; } else { topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); topRight = !docLTR && openStart && first ? rightSide : fromPos.right; botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); } add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); } if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } if (cmpCoords(toPos, start) < 0) { start = toPos; } if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } if (cmpCoords(toPos, end) < 0) { end = toPos; } }); return {start: start, end: end} } var sFrom = range.from(), sTo = range.to(); if (sFrom.line == sTo.line) { drawForLine(sFrom.line, sFrom.ch, sTo.ch); } else { var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); var singleVLine = visualLine(fromLine) == visualLine(toLine); var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; if (singleVLine) { if (leftEnd.top < rightStart.top - 2) { add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); } else { add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); } } if (leftEnd.bottom < rightStart.top) { add(leftSide, leftEnd.bottom, null, rightStart.top); } } output.appendChild(fragment); } // Cursor-blinking function restartBlink(cm) { if (!cm.state.focused) { return } var display = cm.display; clearInterval(display.blinker); var on = true; display.cursorDiv.style.visibility = ""; if (cm.options.cursorBlinkRate > 0) { display.blinker = setInterval(function () { if (!cm.hasFocus()) { onBlur(cm); } display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, cm.options.cursorBlinkRate); } else if (cm.options.cursorBlinkRate < 0) { display.cursorDiv.style.visibility = "hidden"; } } function ensureFocus(cm) { if (!cm.hasFocus()) { cm.display.input.focus(); if (!cm.state.focused) { onFocus(cm); } } } function delayBlurEvent(cm) { cm.state.delayingBlurEvent = true; setTimeout(function () { if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; if (cm.state.focused) { onBlur(cm); } } }, 100); } function onFocus(cm, e) { if (cm.state.delayingBlurEvent && !cm.state.draggingText) { cm.state.delayingBlurEvent = false; } if (cm.options.readOnly == "nocursor") { return } if (!cm.state.focused) { signal(cm, "focus", cm, e); cm.state.focused = true; addClass(cm.display.wrapper, "CodeMirror-focused"); // This test prevents this from firing when a context // menu is closed (since the input reset would kill the // select-all detection hack) if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { cm.display.input.reset(); if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730 } cm.display.input.receivedFocus(); } restartBlink(cm); } function onBlur(cm, e) { if (cm.state.delayingBlurEvent) { return } if (cm.state.focused) { signal(cm, "blur", cm, e); cm.state.focused = false; rmClass(cm.display.wrapper, "CodeMirror-focused"); } clearInterval(cm.display.blinker); setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150); } // Read the actual heights of the rendered lines, and update their // stored heights to match. function updateHeightsInViewport(cm) { var display = cm.display; var prevBottom = display.lineDiv.offsetTop; var viewTop = Math.max(0, display.scroller.getBoundingClientRect().top); var oldHeight = display.lineDiv.getBoundingClientRect().top; var mustScroll = 0; for (var i = 0; i < display.view.length; i++) { var cur = display.view[i], wrapping = cm.options.lineWrapping; var height = (void 0), width = 0; if (cur.hidden) { continue } oldHeight += cur.line.height; if (ie && ie_version < 8) { var bot = cur.node.offsetTop + cur.node.offsetHeight; height = bot - prevBottom; prevBottom = bot; } else { var box = cur.node.getBoundingClientRect(); height = box.bottom - box.top; // Check that lines don't extend past the right of the current // editor width if (!wrapping && cur.text.firstChild) { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; } } var diff = cur.line.height - height; if (diff > .005 || diff < -.005) { if (oldHeight < viewTop) { mustScroll -= diff; } updateLineHeight(cur.line, height); updateWidgetHeight(cur.line); if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) { updateWidgetHeight(cur.rest[j]); } } } if (width > cm.display.sizerWidth) { var chWidth = Math.ceil(width / charWidth(cm.display)); if (chWidth > cm.display.maxLineLength) { cm.display.maxLineLength = chWidth; cm.display.maxLine = cur.line; cm.display.maxLineChanged = true; } } } if (Math.abs(mustScroll) > 2) { display.scroller.scrollTop += mustScroll; } } // Read and store the height of line widgets associated with the // given line. function updateWidgetHeight(line) { if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { var w = line.widgets[i], parent = w.node.parentNode; if (parent) { w.height = parent.offsetHeight; } } } } // Compute the lines that are visible in a given viewport (defaults // the the current scroll position). viewport may contain top, // height, and ensure (see op.scrollToPos) properties. function visibleLines(display, doc, viewport) { var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; top = Math.floor(top - paddingTop(display)); var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); // Ensure is a {from: {line, ch}, to: {line, ch}} object, and // forces those lines into the viewport (if possible). if (viewport && viewport.ensure) { var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; if (ensureFrom < from) { from = ensureFrom; to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); } else if (Math.min(ensureTo, doc.lastLine()) >= to) { from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); to = ensureTo; } } return {from: from, to: Math.max(to, from + 1)} } // SCROLLING THINGS INTO VIEW // If an editor sits on the top or bottom of the window, partially // scrolled out of view, this ensures that the cursor is visible. function maybeScrollWindow(cm, rect) { if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; var doc = display.wrapper.ownerDocument; if (rect.top + box.top < 0) { doScroll = true; } else if (rect.bottom + box.top > (doc.defaultView.innerHeight || doc.documentElement.clientHeight)) { doScroll = false; } if (doScroll != null && !phantom) { var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")); cm.display.lineSpace.appendChild(scrollNode); scrollNode.scrollIntoView(doScroll); cm.display.lineSpace.removeChild(scrollNode); } } // Scroll a given position into view (immediately), verifying that // it actually became visible (as line heights are accurately // measured, the position of something may 'drift' during drawing). function scrollPosIntoView(cm, pos, end, margin) { if (margin == null) { margin = 0; } var rect; if (!cm.options.lineWrapping && pos == end) { // Set pos and end to the cursor positions around the character pos sticks to // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch // If pos == Pos(_, 0, "before"), pos and end are unchanged end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; } for (var limit = 0; limit < 5; limit++) { var changed = false; var coords = cursorCoords(cm, pos); var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); rect = {left: Math.min(coords.left, endCoords.left), top: Math.min(coords.top, endCoords.top) - margin, right: Math.max(coords.left, endCoords.left), bottom: Math.max(coords.bottom, endCoords.bottom) + margin}; var scrollPos = calculateScrollPos(cm, rect); var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; } } if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; } } if (!changed) { break } } return rect } // Scroll a given set of coordinates into view (immediately). function scrollIntoView(cm, rect) { var scrollPos = calculateScrollPos(cm, rect); if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); } if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); } } // Calculate a new scroll position needed to scroll the given // rectangle into view. Returns an object with scrollTop and // scrollLeft properties. When these are undefined, the // vertical/horizontal position does not need to be adjusted. function calculateScrollPos(cm, rect) { var display = cm.display, snapMargin = textHeight(cm.display); if (rect.top < 0) { rect.top = 0; } var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; var screen = displayHeight(cm), result = {}; if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; } var docBottom = cm.doc.height + paddingVert(display); var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin; if (rect.top < screentop) { result.scrollTop = atTop ? 0 : rect.top; } else if (rect.bottom > screentop + screen) { var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); if (newTop != screentop) { result.scrollTop = newTop; } } var gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth; var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace; var screenw = displayWidth(cm) - display.gutters.offsetWidth; var tooWide = rect.right - rect.left > screenw; if (tooWide) { rect.right = rect.left + screenw; } if (rect.left < 10) { result.scrollLeft = 0; } else if (rect.left < screenleft) { result.scrollLeft = Math.max(0, rect.left + gutterSpace - (tooWide ? 0 : 10)); } else if (rect.right > screenw + screenleft - 3) { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; } return result } // Store a relative adjustment to the scroll position in the current // operation (to be applied when the operation finishes). function addToScrollTop(cm, top) { if (top == null) { return } resolveScrollToPos(cm); cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; } // Make sure that at the end of the operation the current cursor is // shown. function ensureCursorVisible(cm) { resolveScrollToPos(cm); var cur = cm.getCursor(); cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}; } function scrollToCoords(cm, x, y) { if (x != null || y != null) { resolveScrollToPos(cm); } if (x != null) { cm.curOp.scrollLeft = x; } if (y != null) { cm.curOp.scrollTop = y; } } function scrollToRange(cm, range) { resolveScrollToPos(cm); cm.curOp.scrollToPos = range; } // When an operation has its scrollToPos property set, and another // scroll action is applied before the end of the operation, this // 'simulates' scrolling that position into view in a cheap way, so // that the effect of intermediate scroll commands is not ignored. function resolveScrollToPos(cm) { var range = cm.curOp.scrollToPos; if (range) { cm.curOp.scrollToPos = null; var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); scrollToCoordsRange(cm, from, to, range.margin); } } function scrollToCoordsRange(cm, from, to, margin) { var sPos = calculateScrollPos(cm, { left: Math.min(from.left, to.left), top: Math.min(from.top, to.top) - margin, right: Math.max(from.right, to.right), bottom: Math.max(from.bottom, to.bottom) + margin }); scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); } // Sync the scrollable area and scrollbars, ensure the viewport // covers the visible area. function updateScrollTop(cm, val) { if (Math.abs(cm.doc.scrollTop - val) < 2) { return } if (!gecko) { updateDisplaySimple(cm, {top: val}); } setScrollTop(cm, val, true); if (gecko) { updateDisplaySimple(cm); } startWorker(cm, 100); } function setScrollTop(cm, val, forceScroll) { val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val)); if (cm.display.scroller.scrollTop == val && !forceScroll) { return } cm.doc.scrollTop = val; cm.display.scrollbars.setScrollTop(val); if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; } } // Sync scroller and scrollbar, ensure the gutter elements are // aligned. function setScrollLeft(cm, val, isScroller, forceScroll) { val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth)); if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } cm.doc.scrollLeft = val; alignHorizontally(cm); if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; } cm.display.scrollbars.setScrollLeft(val); } // SCROLLBARS // Prepare DOM reads needed to update the scrollbars. Done in one // shot to minimize update/measure roundtrips. function measureForScrollbars(cm) { var d = cm.display, gutterW = d.gutters.offsetWidth; var docH = Math.round(cm.doc.height + paddingVert(cm.display)); return { clientHeight: d.scroller.clientHeight, viewHeight: d.wrapper.clientHeight, scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, viewWidth: d.wrapper.clientWidth, barLeft: cm.options.fixedGutter ? gutterW : 0, docHeight: docH, scrollHeight: docH + scrollGap(cm) + d.barHeight, nativeBarWidth: d.nativeBarWidth, gutterWidth: gutterW } } var NativeScrollbars = function(place, scroll, cm) { this.cm = cm; var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); vert.tabIndex = horiz.tabIndex = -1; place(vert); place(horiz); on(vert, "scroll", function () { if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } }); on(horiz, "scroll", function () { if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } }); this.checkedZeroWidth = false; // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } }; NativeScrollbars.prototype.update = function (measure) { var needsH = measure.scrollWidth > measure.clientWidth + 1; var needsV = measure.scrollHeight > measure.clientHeight + 1; var sWidth = measure.nativeBarWidth; if (needsV) { this.vert.style.display = "block"; this.vert.style.bottom = needsH ? sWidth + "px" : "0"; var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); // A bug in IE8 can cause this value to be negative, so guard it. this.vert.firstChild.style.height = Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; } else { this.vert.scrollTop = 0; this.vert.style.display = ""; this.vert.firstChild.style.height = "0"; } if (needsH) { this.horiz.style.display = "block"; this.horiz.style.right = needsV ? sWidth + "px" : "0"; this.horiz.style.left = measure.barLeft + "px"; var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); this.horiz.firstChild.style.width = Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; } else { this.horiz.style.display = ""; this.horiz.firstChild.style.width = "0"; } if (!this.checkedZeroWidth && measure.clientHeight > 0) { if (sWidth == 0) { this.zeroWidthHack(); } this.checkedZeroWidth = true; } return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} }; NativeScrollbars.prototype.setScrollLeft = function (pos) { if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); } }; NativeScrollbars.prototype.setScrollTop = function (pos) { if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); } }; NativeScrollbars.prototype.zeroWidthHack = function () { var w = mac && !mac_geMountainLion ? "12px" : "18px"; this.horiz.style.height = this.vert.style.width = w; this.horiz.style.visibility = this.vert.style.visibility = "hidden"; this.disableHoriz = new Delayed; this.disableVert = new Delayed; }; NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { bar.style.visibility = ""; function maybeDisable() { // To find out whether the scrollbar is still visible, we // check whether the element under the pixel in the bottom // right corner of the scrollbar box is the scrollbar box // itself (when the bar is still visible) or its filler child // (when the bar is hidden). If it is still visible, we keep // it enabled, if it's hidden, we disable pointer events. var box = bar.getBoundingClientRect(); var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); if (elt != bar) { bar.style.visibility = "hidden"; } else { delay.set(1000, maybeDisable); } } delay.set(1000, maybeDisable); }; NativeScrollbars.prototype.clear = function () { var parent = this.horiz.parentNode; parent.removeChild(this.horiz); parent.removeChild(this.vert); }; var NullScrollbars = function () {}; NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; NullScrollbars.prototype.setScrollLeft = function () {}; NullScrollbars.prototype.setScrollTop = function () {}; NullScrollbars.prototype.clear = function () {}; function updateScrollbars(cm, measure) { if (!measure) { measure = measureForScrollbars(cm); } var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; updateScrollbarsInner(cm, measure); for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { if (startWidth != cm.display.barWidth && cm.options.lineWrapping) { updateHeightsInViewport(cm); } updateScrollbarsInner(cm, measureForScrollbars(cm)); startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; } } // Re-synchronize the fake scrollbars with the actual size of the // content. function updateScrollbarsInner(cm, measure) { var d = cm.display; var sizes = d.scrollbars.update(measure); d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; if (sizes.right && sizes.bottom) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = sizes.bottom + "px"; d.scrollbarFiller.style.width = sizes.right + "px"; } else { d.scrollbarFiller.style.display = ""; } if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { d.gutterFiller.style.display = "block"; d.gutterFiller.style.height = sizes.bottom + "px"; d.gutterFiller.style.width = measure.gutterWidth + "px"; } else { d.gutterFiller.style.display = ""; } } var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; function initScrollbars(cm) { if (cm.display.scrollbars) { cm.display.scrollbars.clear(); if (cm.display.scrollbars.addClass) { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } } cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); // Prevent clicks in the scrollbars from killing focus on(node, "mousedown", function () { if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); } }); node.setAttribute("cm-not-content", "true"); }, function (pos, axis) { if (axis == "horizontal") { setScrollLeft(cm, pos); } else { updateScrollTop(cm, pos); } }, cm); if (cm.display.scrollbars.addClass) { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } } // Operations are used to wrap a series of changes to the editor // state in such a way that each change won't have to update the // cursor and display (which would be awkward, slow, and // error-prone). Instead, display updates are batched and then all // combined and executed at once. var nextOpId = 0; // Start a new operation. function startOperation(cm) { cm.curOp = { cm: cm, viewChanged: false, // Flag that indicates that lines might need to be redrawn startHeight: cm.doc.height, // Used to detect need to update scrollbar forceUpdate: false, // Used to force a redraw updateInput: 0, // Whether to reset the input textarea typing: false, // Whether this reset should be careful to leave existing text (for compositing) changeObjs: null, // Accumulated changes, for firing change events cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already selectionChanged: false, // Whether the selection needs to be redrawn updateMaxLine: false, // Set when the widest line needs to be determined anew scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet scrollToPos: null, // Used to scroll to a specific position focus: false, id: ++nextOpId, // Unique ID markArrays: null // Used by addMarkedSpan }; pushOperation(cm.curOp); } // Finish an operation, updating the display and signalling delayed events function endOperation(cm) { var op = cm.curOp; if (op) { finishOperation(op, function (group) { for (var i = 0; i < group.ops.length; i++) { group.ops[i].cm.curOp = null; } endOperations(group); }); } } // The DOM updates done when an operation finishes are batched so // that the minimum number of relayouts are required. function endOperations(group) { var ops = group.ops; for (var i = 0; i < ops.length; i++) // Read DOM { endOperation_R1(ops[i]); } for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) { endOperation_W1(ops[i$1]); } for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM { endOperation_R2(ops[i$2]); } for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) { endOperation_W2(ops[i$3]); } for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM { endOperation_finish(ops[i$4]); } } function endOperation_R1(op) { var cm = op.cm, display = cm.display; maybeClipScrollbars(cm); if (op.updateMaxLine) { findMaxLine(cm); } op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || op.scrollToPos.to.line >= display.viewTo) || display.maxLineChanged && cm.options.lineWrapping; op.update = op.mustUpdate && new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); } function endOperation_W1(op) { op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); } function endOperation_R2(op) { var cm = op.cm, display = cm.display; if (op.updatedDisplay) { updateHeightsInViewport(cm); } op.barMeasure = measureForScrollbars(cm); // If the max line changed since it was last measured, measure it, // and ensure the document's width matches it. // updateDisplay_W2 will use these properties to do the actual resizing if (display.maxLineChanged && !cm.options.lineWrapping) { op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; cm.display.sizerWidth = op.adjustWidthTo; op.barMeasure.scrollWidth = Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); } if (op.updatedDisplay || op.selectionChanged) { op.preparedSelection = display.input.prepareSelection(); } } function endOperation_W2(op) { var cm = op.cm; if (op.adjustWidthTo != null) { cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; if (op.maxScrollLeft < cm.doc.scrollLeft) { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); } cm.display.maxLineChanged = false; } var takeFocus = op.focus && op.focus == activeElt(root(cm)); if (op.preparedSelection) { cm.display.input.showSelection(op.preparedSelection, takeFocus); } if (op.updatedDisplay || op.startHeight != cm.doc.height) { updateScrollbars(cm, op.barMeasure); } if (op.updatedDisplay) { setDocumentHeight(cm, op.barMeasure); } if (op.selectionChanged) { restartBlink(cm); } if (cm.state.focused && op.updateInput) { cm.display.input.reset(op.typing); } if (takeFocus) { ensureFocus(op.cm); } } function endOperation_finish(op) { var cm = op.cm, display = cm.display, doc = cm.doc; if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); } // Abort mouse wheel delta measurement, when scrolling explicitly if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) { display.wheelStartX = display.wheelStartY = null; } // Propagate the scroll position to the actual DOM scroller if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); } if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); } // If we need to scroll a specific position into view, do so. if (op.scrollToPos) { var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); maybeScrollWindow(cm, rect); } // Fire events for markers that are hidden/unidden by editing or // undoing var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; if (hidden) { for (var i = 0; i < hidden.length; ++i) { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } } if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } } if (display.wrapper.offsetHeight) { doc.scrollTop = cm.display.scroller.scrollTop; } // Fire change events, and delayed event handlers if (op.changeObjs) { signal(cm, "changes", cm, op.changeObjs); } if (op.update) { op.update.finish(); } } // Run the given function in an operation function runInOp(cm, f) { if (cm.curOp) { return f() } startOperation(cm); try { return f() } finally { endOperation(cm); } } // Wraps a function in an operation. Returns the wrapped function. function operation(cm, f) { return function() { if (cm.curOp) { return f.apply(cm, arguments) } startOperation(cm); try { return f.apply(cm, arguments) } finally { endOperation(cm); } } } // Used to add methods to editor and doc instances, wrapping them in // operations. function methodOp(f) { return function() { if (this.curOp) { return f.apply(this, arguments) } startOperation(this); try { return f.apply(this, arguments) } finally { endOperation(this); } } } function docMethodOp(f) { return function() { var cm = this.cm; if (!cm || cm.curOp) { return f.apply(this, arguments) } startOperation(cm); try { return f.apply(this, arguments) } finally { endOperation(cm); } } } // HIGHLIGHT WORKER function startWorker(cm, time) { if (cm.doc.highlightFrontier < cm.display.viewTo) { cm.state.highlight.set(time, bind(highlightWorker, cm)); } } function highlightWorker(cm) { var doc = cm.doc; if (doc.highlightFrontier >= cm.display.viewTo) { return } var end = +new Date + cm.options.workTime; var context = getContextBefore(cm, doc.highlightFrontier); var changedLines = []; doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { if (context.line >= cm.display.viewFrom) { // Visible var oldStyles = line.styles; var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; var highlighted = highlightLine(cm, line, context, true); if (resetState) { context.state = resetState; } line.styles = highlighted.styles; var oldCls = line.styleClasses, newCls = highlighted.classes; if (newCls) { line.styleClasses = newCls; } else if (oldCls) { line.styleClasses = null; } var ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; } if (ischange) { changedLines.push(context.line); } line.stateAfter = context.save(); context.nextLine(); } else { if (line.text.length <= cm.options.maxHighlightLength) { processLine(cm, line.text, context); } line.stateAfter = context.line % 5 == 0 ? context.save() : null; context.nextLine(); } if (+new Date > end) { startWorker(cm, cm.options.workDelay); return true } }); doc.highlightFrontier = context.line; doc.modeFrontier = Math.max(doc.modeFrontier, context.line); if (changedLines.length) { runInOp(cm, function () { for (var i = 0; i < changedLines.length; i++) { regLineChange(cm, changedLines[i], "text"); } }); } } // DISPLAY DRAWING var DisplayUpdate = function(cm, viewport, force) { var display = cm.display; this.viewport = viewport; // Store some values that we'll need later (but don't want to force a relayout for) this.visible = visibleLines(display, cm.doc, viewport); this.editorIsHidden = !display.wrapper.offsetWidth; this.wrapperHeight = display.wrapper.clientHeight; this.wrapperWidth = display.wrapper.clientWidth; this.oldDisplayWidth = displayWidth(cm); this.force = force; this.dims = getDimensions(cm); this.events = []; }; DisplayUpdate.prototype.signal = function (emitter, type) { if (hasHandler(emitter, type)) { this.events.push(arguments); } }; DisplayUpdate.prototype.finish = function () { for (var i = 0; i < this.events.length; i++) { signal.apply(null, this.events[i]); } }; function maybeClipScrollbars(cm) { var display = cm.display; if (!display.scrollbarsClipped && display.scroller.offsetWidth) { display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; display.heightForcer.style.height = scrollGap(cm) + "px"; display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; display.scrollbarsClipped = true; } } function selectionSnapshot(cm) { if (cm.hasFocus()) { return null } var active = activeElt(root(cm)); if (!active || !contains(cm.display.lineDiv, active)) { return null } var result = {activeElt: active}; if (window.getSelection) { var sel = win(cm).getSelection(); if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { result.anchorNode = sel.anchorNode; result.anchorOffset = sel.anchorOffset; result.focusNode = sel.focusNode; result.focusOffset = sel.focusOffset; } } return result } function restoreSelection(snapshot) { if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) { return } snapshot.activeElt.focus(); if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) && snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { var doc = snapshot.activeElt.ownerDocument; var sel = doc.defaultView.getSelection(), range = doc.createRange(); range.setEnd(snapshot.anchorNode, snapshot.anchorOffset); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); sel.extend(snapshot.focusNode, snapshot.focusOffset); } } // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. function updateDisplayIfNeeded(cm, update) { var display = cm.display, doc = cm.doc; if (update.editorIsHidden) { resetView(cm); return false } // Bail out if the visible area is already rendered and nothing changed. if (!update.force && update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && display.renderedView == display.view && countDirtyView(cm) == 0) { return false } if (maybeUpdateLineNumberWidth(cm)) { resetView(cm); update.dims = getDimensions(cm); } // Compute a suitable new viewport (from & to) var end = doc.first + doc.size; var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); var to = Math.min(end, update.visible.to + cm.options.viewportMargin); if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } if (sawCollapsedSpans) { from = visualLineNo(cm.doc, from); to = visualLineEndNo(cm.doc, to); } var different = from != display.viewFrom || to != display.viewTo || display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; adjustView(cm, from, to); display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); // Position the mover div to align with the current scroll position cm.display.mover.style.top = display.viewOffset + "px"; var toUpdate = countDirtyView(cm); if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) { return false } // For big changes, we hide the enclosing element during the // update, since that speeds up the operations on most browsers. var selSnapshot = selectionSnapshot(cm); if (toUpdate > 4) { display.lineDiv.style.display = "none"; } patchDisplay(cm, display.updateLineNumbers, update.dims); if (toUpdate > 4) { display.lineDiv.style.display = ""; } display.renderedView = display.view; // There might have been a widget with a focused element that got // hidden or updated, if so re-focus it. restoreSelection(selSnapshot); // Prevent selection and cursors from interfering with the scroll // width and height. removeChildren(display.cursorDiv); removeChildren(display.selectionDiv); display.gutters.style.height = display.sizer.style.minHeight = 0; if (different) { display.lastWrapHeight = update.wrapperHeight; display.lastWrapWidth = update.wrapperWidth; startWorker(cm, 400); } display.updateLineNumbers = null; return true } function postUpdateDisplay(cm, update) { var viewport = update.viewport; for (var first = true;; first = false) { if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { // Clip forced viewport to actual scrollable area. if (viewport && viewport.top != null) { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; } // Updated line heights might result in the drawn area not // actually covering the viewport. Keep looping until it does. update.visible = visibleLines(cm.display, cm.doc, viewport); if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) { break } } else if (first) { update.visible = visibleLines(cm.display, cm.doc, viewport); } if (!updateDisplayIfNeeded(cm, update)) { break } updateHeightsInViewport(cm); var barMeasure = measureForScrollbars(cm); updateSelection(cm); updateScrollbars(cm, barMeasure); setDocumentHeight(cm, barMeasure); update.force = false; } update.signal(cm, "update", cm); if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; } } function updateDisplaySimple(cm, viewport) { var update = new DisplayUpdate(cm, viewport); if (updateDisplayIfNeeded(cm, update)) { updateHeightsInViewport(cm); postUpdateDisplay(cm, update); var barMeasure = measureForScrollbars(cm); updateSelection(cm); updateScrollbars(cm, barMeasure); setDocumentHeight(cm, barMeasure); update.finish(); } } // Sync the actual display DOM structure with display.view, removing // nodes for lines that are no longer in view, and creating the ones // that are not there yet, and updating the ones that are out of // date. function patchDisplay(cm, updateNumbersFrom, dims) { var display = cm.display, lineNumbers = cm.options.lineNumbers; var container = display.lineDiv, cur = container.firstChild; function rm(node) { var next = node.nextSibling; // Works around a throw-scroll bug in OS X Webkit if (webkit && mac && cm.display.currentWheelTarget == node) { node.style.display = "none"; } else { node.parentNode.removeChild(node); } return next } var view = display.view, lineN = display.viewFrom; // Loop over the elements in the view, syncing cur (the DOM nodes // in display.lineDiv) with the view as we go. for (var i = 0; i < view.length; i++) { var lineView = view[i]; if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet var node = buildLineElement(cm, lineView, lineN, dims); container.insertBefore(node, cur); } else { // Already drawn while (cur != lineView.node) { cur = rm(cur); } var updateNumber = lineNumbers && updateNumbersFrom != null && updateNumbersFrom <= lineN && lineView.lineNumber; if (lineView.changes) { if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } updateLineForChanges(cm, lineView, lineN, dims); } if (updateNumber) { removeChildren(lineView.lineNumber); lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); } cur = lineView.node.nextSibling; } lineN += lineView.size; } while (cur) { cur = rm(cur); } } function updateGutterSpace(display) { var width = display.gutters.offsetWidth; display.sizer.style.marginLeft = width + "px"; // Send an event to consumers responding to changes in gutter width. signalLater(display, "gutterChanged", display); } function setDocumentHeight(cm, measure) { cm.display.sizer.style.minHeight = measure.docHeight + "px"; cm.display.heightForcer.style.top = measure.docHeight + "px"; cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; } // Re-align line numbers and gutter marks to compensate for // horizontal scrolling. function alignHorizontally(cm) { var display = cm.display, view = display.view; if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; var gutterW = display.gutters.offsetWidth, left = comp + "px"; for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { if (cm.options.fixedGutter) { if (view[i].gutter) { view[i].gutter.style.left = left; } if (view[i].gutterBackground) { view[i].gutterBackground.style.left = left; } } var align = view[i].alignable; if (align) { for (var j = 0; j < align.length; j++) { align[j].style.left = left; } } } } if (cm.options.fixedGutter) { display.gutters.style.left = (comp + gutterW) + "px"; } } // Used to ensure that the line number gutter is still the right // size for the current document size. Returns true when an update // is needed. function maybeUpdateLineNumberWidth(cm) { if (!cm.options.lineNumbers) { return false } var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; if (last.length != display.lineNumChars) { var test = display.measure.appendChild(elt("div", [elt("div", last)], "CodeMirror-linenumber CodeMirror-gutter-elt")); var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; display.lineGutter.style.width = ""; display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; display.lineNumWidth = display.lineNumInnerWidth + padding; display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; display.lineGutter.style.width = display.lineNumWidth + "px"; updateGutterSpace(cm.display); return true } return false } function getGutters(gutters, lineNumbers) { var result = [], sawLineNumbers = false; for (var i = 0; i < gutters.length; i++) { var name = gutters[i], style = null; if (typeof name != "string") { style = name.style; name = name.className; } if (name == "CodeMirror-linenumbers") { if (!lineNumbers) { continue } else { sawLineNumbers = true; } } result.push({className: name, style: style}); } if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); } return result } // Rebuild the gutter elements, ensure the margin to the left of the // code matches their width. function renderGutters(display) { var gutters = display.gutters, specs = display.gutterSpecs; removeChildren(gutters); display.lineGutter = null; for (var i = 0; i < specs.length; ++i) { var ref = specs[i]; var className = ref.className; var style = ref.style; var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)); if (style) { gElt.style.cssText = style; } if (className == "CodeMirror-linenumbers") { display.lineGutter = gElt; gElt.style.width = (display.lineNumWidth || 1) + "px"; } } gutters.style.display = specs.length ? "" : "none"; updateGutterSpace(display); } function updateGutters(cm) { renderGutters(cm.display); regChange(cm); alignHorizontally(cm); } // The display handles the DOM integration, both for input reading // and content drawing. It holds references to DOM nodes and // display-related state. function Display(place, doc, input, options) { var d = this; this.input = input; // Covers bottom-right square when both scrollbars are present. d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); d.scrollbarFiller.setAttribute("cm-not-content", "true"); // Covers bottom of gutter when coverGutterNextToScrollbar is on // and h scrollbar is present. d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); d.gutterFiller.setAttribute("cm-not-content", "true"); // Will contain the actual code, positioned to cover the viewport. d.lineDiv = eltP("div", null, "CodeMirror-code"); // Elements are added to these to represent selection and cursors. d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); d.cursorDiv = elt("div", null, "CodeMirror-cursors"); // A visibility: hidden element used to find the size of things. d.measure = elt("div", null, "CodeMirror-measure"); // When lines outside of the viewport are measured, they are drawn in this. d.lineMeasure = elt("div", null, "CodeMirror-measure"); // Wraps everything that needs to exist inside the vertically-padded coordinate system d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], null, "position: relative; outline: none"); var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); // Moved around its parent to cover visible view. d.mover = elt("div", [lines], null, "position: relative"); // Set to the height of the document, allowing scrolling. d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); d.sizerWidth = null; // Behavior of elts with overflow: auto and padding is // inconsistent across browsers. This is used to ensure the // scrollable area is big enough. d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); // Will contain the gutters, if any. d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; // Actual scrollable element. d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); d.scroller.setAttribute("tabIndex", "-1"); // The element in which the editor lives. d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); // See #6982. FIXME remove when this has been fixed for a while in Chrome if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; } // This attribute is respected by automatic translation systems such as Google Translate, // and may also be respected by tools used by human translators. d.wrapper.setAttribute('translate', 'no'); // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; } if (place) { if (place.appendChild) { place.appendChild(d.wrapper); } else { place(d.wrapper); } } // Current rendered range (may be bigger than the view window). d.viewFrom = d.viewTo = doc.first; d.reportedViewFrom = d.reportedViewTo = doc.first; // Information about the rendered lines. d.view = []; d.renderedView = null; // Holds info about a single rendered line when it was rendered // for measurement, while not in view. d.externalMeasured = null; // Empty space (in pixels) above the view d.viewOffset = 0; d.lastWrapHeight = d.lastWrapWidth = 0; d.updateLineNumbers = null; d.nativeBarWidth = d.barHeight = d.barWidth = 0; d.scrollbarsClipped = false; // Used to only resize the line number gutter when necessary (when // the amount of lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; // Set to true when a non-horizontal-scrolling line widget is // added. As an optimization, line widget aligning is skipped when // this is false. d.alignWidgets = false; d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. d.maxLine = null; d.maxLineLength = 0; d.maxLineChanged = false; // Used for measuring wheel scrolling granularity d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; // True when shift is held down. d.shift = false; // Used to track whether anything happened since the context menu // was opened. d.selForContextMenu = null; d.activeTouch = null; d.gutterSpecs = getGutters(options.gutters, options.lineNumbers); renderGutters(d); input.init(d); } // Since the delta values reported on mouse wheel events are // unstandardized between browsers and even browser versions, and // generally horribly unpredictable, this code starts by measuring // the scroll effect that the first few mouse wheel events have, // and, from that, detects the way it can convert deltas to pixel // offsets afterwards. // // The reason we want to know the amount a wheel event will scroll // is that it gives us a chance to update the display before the // actual scrolling happens, reducing flickering. var wheelSamples = 0, wheelPixelsPerUnit = null; // Fill in a browser-detected starting value on browsers where we // know one. These don't have to be accurate -- the result of them // being wrong would just be a slight flicker on the first wheel // scroll (if it is large enough). if (ie) { wheelPixelsPerUnit = -.53; } else if (gecko) { wheelPixelsPerUnit = 15; } else if (chrome) { wheelPixelsPerUnit = -.7; } else if (safari) { wheelPixelsPerUnit = -1/3; } function wheelEventDelta(e) { var dx = e.wheelDeltaX, dy = e.wheelDeltaY; if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; } if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; } else if (dy == null) { dy = e.wheelDelta; } return {x: dx, y: dy} } function wheelEventPixels(e) { var delta = wheelEventDelta(e); delta.x *= wheelPixelsPerUnit; delta.y *= wheelPixelsPerUnit; return delta } function onScrollWheel(cm, e) { // On Chrome 102, viewport updates somehow stop wheel-based // scrolling. Turning off pointer events during the scroll seems // to avoid the issue. if (chrome && chrome_version == 102) { if (cm.display.chromeScrollHack == null) { cm.display.sizer.style.pointerEvents = "none"; } else { clearTimeout(cm.display.chromeScrollHack); } cm.display.chromeScrollHack = setTimeout(function () { cm.display.chromeScrollHack = null; cm.display.sizer.style.pointerEvents = ""; }, 100); } var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; var pixelsPerUnit = wheelPixelsPerUnit; if (e.deltaMode === 0) { dx = e.deltaX; dy = e.deltaY; pixelsPerUnit = 1; } var display = cm.display, scroll = display.scroller; // Quit if there's nothing to scroll here var canScrollX = scroll.scrollWidth > scroll.clientWidth; var canScrollY = scroll.scrollHeight > scroll.clientHeight; if (!(dx && canScrollX || dy && canScrollY)) { return } // Webkit browsers on OS X abort momentum scrolls when the target // of the scroll event is removed from the scrollable element. // This hack (see related code in patchDisplay) makes sure the // element is kept around. if (dy && mac && webkit) { outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { for (var i = 0; i < view.length; i++) { if (view[i].node == cur) { cm.display.currentWheelTarget = cur; break outer } } } } // On some browsers, horizontal scrolling will cause redraws to // happen before the gutter has been realigned, causing it to // wriggle around in a most unseemly way. When we have an // estimated pixels/delta value, we just handle horizontal // scrolling entirely here. It'll be slightly off from native, but // better than glitching out. if (dx && !gecko && !presto && pixelsPerUnit != null) { if (dy && canScrollY) { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)); } setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit)); // Only prevent default scrolling if vertical scrolling is // actually possible. Otherwise, it causes vertical scroll // jitter on OSX trackpads when deltaX is small and deltaY // is large (issue #3579) if (!dy || (dy && canScrollY)) { e_preventDefault(e); } display.wheelStartX = null; // Abort measurement, if in progress return } // 'Project' the visible viewport to cover the area that is being // scrolled into view (if we know enough to estimate it). if (dy && pixelsPerUnit != null) { var pixels = dy * pixelsPerUnit; var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; if (pixels < 0) { top = Math.max(0, top + pixels - 50); } else { bot = Math.min(cm.doc.height, bot + pixels + 50); } updateDisplaySimple(cm, {top: top, bottom: bot}); } if (wheelSamples < 20 && e.deltaMode !== 0) { if (display.wheelStartX == null) { display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; display.wheelDX = dx; display.wheelDY = dy; setTimeout(function () { if (display.wheelStartX == null) { return } var movedX = scroll.scrollLeft - display.wheelStartX; var movedY = scroll.scrollTop - display.wheelStartY; var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || (movedX && display.wheelDX && movedX / display.wheelDX); display.wheelStartX = display.wheelStartY = null; if (!sample) { return } wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); ++wheelSamples; }, 200); } else { display.wheelDX += dx; display.wheelDY += dy; } } } // Selection objects are immutable. A new one is created every time // the selection changes. A selection is one or more non-overlapping // (and non-touching) ranges, sorted, and an integer that indicates // which one is the primary selection (the one that's scrolled into // view, that getCursor returns, etc). var Selection = function(ranges, primIndex) { this.ranges = ranges; this.primIndex = primIndex; }; Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; Selection.prototype.equals = function (other) { if (other == this) { return true } if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } for (var i = 0; i < this.ranges.length; i++) { var here = this.ranges[i], there = other.ranges[i]; if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } } return true }; Selection.prototype.deepCopy = function () { var out = []; for (var i = 0; i < this.ranges.length; i++) { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); } return new Selection(out, this.primIndex) }; Selection.prototype.somethingSelected = function () { for (var i = 0; i < this.ranges.length; i++) { if (!this.ranges[i].empty()) { return true } } return false }; Selection.prototype.contains = function (pos, end) { if (!end) { end = pos; } for (var i = 0; i < this.ranges.length; i++) { var range = this.ranges[i]; if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) { return i } } return -1 }; var Range = function(anchor, head) { this.anchor = anchor; this.head = head; }; Range.prototype.from = function () { return minPos(this.anchor, this.head) }; Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; // Take an unsorted, potentially overlapping set of ranges, and // build a selection out of it. 'Consumes' ranges array (modifying // it). function normalizeSelection(cm, ranges, primIndex) { var mayTouch = cm && cm.options.selectionsMayTouch; var prim = ranges[primIndex]; ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }); primIndex = indexOf(ranges, prim); for (var i = 1; i < ranges.length; i++) { var cur = ranges[i], prev = ranges[i - 1]; var diff = cmp(prev.to(), cur.from()); if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; if (i <= primIndex) { --primIndex; } ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); } } return new Selection(ranges, primIndex) } function simpleSelection(anchor, head) { return new Selection([new Range(anchor, head || anchor)], 0) } // Compute the position of the end of a change (its 'to' property // refers to the pre-change end). function changeEnd(change) { if (!change.text) { return change.to } return Pos(change.from.line + change.text.length - 1, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) } // Adjust a position to refer to the post-change position of the // same text, or the end of the change if the change covers it. function adjustForChange(pos, change) { if (cmp(pos, change.from) < 0) { return pos } if (cmp(pos, change.to) <= 0) { return changeEnd(change) } var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; } return Pos(line, ch) } function computeSelAfterChange(doc, change) { var out = []; for (var i = 0; i < doc.sel.ranges.length; i++) { var range = doc.sel.ranges[i]; out.push(new Range(adjustForChange(range.anchor, change), adjustForChange(range.head, change))); } return normalizeSelection(doc.cm, out, doc.sel.primIndex) } function offsetPos(pos, old, nw) { if (pos.line == old.line) { return Pos(nw.line, pos.ch - old.ch + nw.ch) } else { return Pos(nw.line + (pos.line - old.line), pos.ch) } } // Used by replaceSelections to allow moving the selection to the // start or around the replaced test. Hint may be "start" or "around". function computeReplacedSel(doc, changes, hint) { var out = []; var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; for (var i = 0; i < changes.length; i++) { var change = changes[i]; var from = offsetPos(change.from, oldPrev, newPrev); var to = offsetPos(changeEnd(change), oldPrev, newPrev); oldPrev = change.to; newPrev = to; if (hint == "around") { var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; out[i] = new Range(inv ? to : from, inv ? from : to); } else { out[i] = new Range(from, from); } } return new Selection(out, doc.sel.primIndex) } // Used to get the editor into a consistent state again when options change. function loadMode(cm) { cm.doc.mode = getMode(cm.options, cm.doc.modeOption); resetModeState(cm); } function resetModeState(cm) { cm.doc.iter(function (line) { if (line.stateAfter) { line.stateAfter = null; } if (line.styles) { line.styles = null; } }); cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; startWorker(cm, 100); cm.state.modeGen++; if (cm.curOp) { regChange(cm); } } // DOCUMENT DATA STRUCTURE // By default, updates that start and end at the beginning of a line // are treated specially, in order to make the association of line // widgets and marker elements with the text behave more intuitive. function isWholeLineUpdate(doc, change) { return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && (!doc.cm || doc.cm.options.wholeLineUpdateBefore) } // Perform a change on the document data structure. function updateDoc(doc, change, markedSpans, estimateHeight) { function spansFor(n) {return markedSpans ? markedSpans[n] : null} function update(line, text, spans) { updateLine(line, text, spans, estimateHeight); signalLater(line, "change", line, change); } function linesFor(start, end) { var result = []; for (var i = start; i < end; ++i) { result.push(new Line(text[i], spansFor(i), estimateHeight)); } return result } var from = change.from, to = change.to, text = change.text; var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; // Adjust the line structure if (change.full) { doc.insert(0, linesFor(0, text.length)); doc.remove(text.length, doc.size - text.length); } else if (isWholeLineUpdate(doc, change)) { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. var added = linesFor(0, text.length - 1); update(lastLine, lastLine.text, lastSpans); if (nlines) { doc.remove(from.line, nlines); } if (added.length) { doc.insert(from.line, added); } } else if (firstLine == lastLine) { if (text.length == 1) { update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); } else { var added$1 = linesFor(1, text.length - 1); added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); doc.insert(from.line + 1, added$1); } } else if (text.length == 1) { update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); doc.remove(from.line + 1, nlines); } else { update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); var added$2 = linesFor(1, text.length - 1); if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); } doc.insert(from.line + 1, added$2); } signalLater(doc, "change", doc, change); } // Call f for all linked documents. function linkedDocs(doc, f, sharedHistOnly) { function propagate(doc, skip, sharedHist) { if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { var rel = doc.linked[i]; if (rel.doc == skip) { continue } var shared = sharedHist && rel.sharedHist; if (sharedHistOnly && !shared) { continue } f(rel.doc, shared); propagate(rel.doc, doc, shared); } } } propagate(doc, null, true); } // Attach a document to an editor. function attachDoc(cm, doc) { if (doc.cm) { throw new Error("This document is already in use.") } cm.doc = doc; doc.cm = cm; estimateLineHeights(cm); loadMode(cm); setDirectionClass(cm); cm.options.direction = doc.direction; if (!cm.options.lineWrapping) { findMaxLine(cm); } cm.options.mode = doc.modeOption; regChange(cm); } function setDirectionClass(cm) { (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); } function directionChanged(cm) { runInOp(cm, function () { setDirectionClass(cm); regChange(cm); }); } function History(prev) { // Arrays of change events and selections. Doing something adds an // event to done and clears undo. Undoing moves events from done // to undone, redoing moves them in the other direction. this.done = []; this.undone = []; this.undoDepth = prev ? prev.undoDepth : Infinity; // Used to track when changes can be merged into a single undo // event this.lastModTime = this.lastSelTime = 0; this.lastOp = this.lastSelOp = null; this.lastOrigin = this.lastSelOrigin = null; // Used by the isClean() method this.generation = this.maxGeneration = prev ? prev.maxGeneration : 1; } // Create a history change event from an updateDoc-style change // object. function historyChangeFromChange(doc, change) { var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true); return histChange } // Pop all selection events off the end of a history array. Stop at // a change event. function clearSelectionEvents(array) { while (array.length) { var last = lst(array); if (last.ranges) { array.pop(); } else { break } } } // Find the top change event in the history. Pop off selection // events that are in the way. function lastChangeEvent(hist, force) { if (force) { clearSelectionEvents(hist.done); return lst(hist.done) } else if (hist.done.length && !lst(hist.done).ranges) { return lst(hist.done) } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { hist.done.pop(); return lst(hist.done) } } // Register a change in the history. Merges changes that are within // a single operation, or are close together with an origin that // allows merging (starting with "+") into a single event. function addChangeToHistory(doc, change, selAfter, opId) { var hist = doc.history; hist.undone.length = 0; var time = +new Date, cur; var last; if ((hist.lastOp == opId || hist.lastOrigin == change.origin && change.origin && ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || change.origin.charAt(0) == "*")) && (cur = lastChangeEvent(hist, hist.lastOp == opId))) { // Merge this change into the last event last = lst(cur.changes); if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { // Optimized case for simple insertion -- don't want to add // new changesets for every character typed last.to = changeEnd(change); } else { // Add new sub-event cur.changes.push(historyChangeFromChange(doc, change)); } } else { // Can not be merged, start a new event. var before = lst(hist.done); if (!before || !before.ranges) { pushSelectionToHistory(doc.sel, hist.done); } cur = {changes: [historyChangeFromChange(doc, change)], generation: hist.generation}; hist.done.push(cur); while (hist.done.length > hist.undoDepth) { hist.done.shift(); if (!hist.done[0].ranges) { hist.done.shift(); } } } hist.done.push(selAfter); hist.generation = ++hist.maxGeneration; hist.lastModTime = hist.lastSelTime = time; hist.lastOp = hist.lastSelOp = opId; hist.lastOrigin = hist.lastSelOrigin = change.origin; if (!last) { signal(doc, "historyAdded"); } } function selectionEventCanBeMerged(doc, origin, prev, sel) { var ch = origin.charAt(0); return ch == "*" || ch == "+" && prev.ranges.length == sel.ranges.length && prev.somethingSelected() == sel.somethingSelected() && new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) } // Called whenever the selection changes, sets the new selection as // the pending selection in the history, and pushes the old pending // selection into the 'done' array when it was significantly // different (in number of selected ranges, emptiness, or time). function addSelectionToHistory(doc, sel, opId, options) { var hist = doc.history, origin = options && options.origin; // A new event is started when the previous origin does not match // the current, or the origins don't allow matching. Origins // starting with * are always merged, those starting with + are // merged when similar and close together in time. if (opId == hist.lastSelOp || (origin && hist.lastSelOrigin == origin && (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) { hist.done[hist.done.length - 1] = sel; } else { pushSelectionToHistory(sel, hist.done); } hist.lastSelTime = +new Date; hist.lastSelOrigin = origin; hist.lastSelOp = opId; if (options && options.clearRedo !== false) { clearSelectionEvents(hist.undone); } } function pushSelectionToHistory(sel, dest) { var top = lst(dest); if (!(top && top.ranges && top.equals(sel))) { dest.push(sel); } } // Used to store marked span information in the history. function attachLocalSpans(doc, change, from, to) { var existing = change["spans_" + doc.id], n = 0; doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { if (line.markedSpans) { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; } ++n; }); } // When un/re-doing restores text containing marked spans, those // that have been explicitly cleared should not be restored. function removeClearedSpans(spans) { if (!spans) { return null } var out; for (var i = 0; i < spans.length; ++i) { if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } } else if (out) { out.push(spans[i]); } } return !out ? spans : out.length ? out : null } // Retrieve and filter the old marked spans stored in a change event. function getOldSpans(doc, change) { var found = change["spans_" + doc.id]; if (!found) { return null } var nw = []; for (var i = 0; i < change.text.length; ++i) { nw.push(removeClearedSpans(found[i])); } return nw } // Used for un/re-doing changes from the history. Combines the // result of computing the existing spans with the set of spans that // existed in the history (so that deleting around a span and then // undoing brings back the span). function mergeOldSpans(doc, change) { var old = getOldSpans(doc, change); var stretched = stretchSpansOverChange(doc, change); if (!old) { return stretched } if (!stretched) { return old } for (var i = 0; i < old.length; ++i) { var oldCur = old[i], stretchCur = stretched[i]; if (oldCur && stretchCur) { spans: for (var j = 0; j < stretchCur.length; ++j) { var span = stretchCur[j]; for (var k = 0; k < oldCur.length; ++k) { if (oldCur[k].marker == span.marker) { continue spans } } oldCur.push(span); } } else if (stretchCur) { old[i] = stretchCur; } } return old } // Used both to provide a JSON-safe object in .getHistory, and, when // detaching a document, to split the history in two function copyHistoryArray(events, newGroup, instantiateSel) { var copy = []; for (var i = 0; i < events.length; ++i) { var event = events[i]; if (event.ranges) { copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); continue } var changes = event.changes, newChanges = []; copy.push({changes: newChanges}); for (var j = 0; j < changes.length; ++j) { var change = changes[j], m = (void 0); newChanges.push({from: change.from, to: change.to, text: change.text}); if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { if (indexOf(newGroup, Number(m[1])) > -1) { lst(newChanges)[prop] = change[prop]; delete change[prop]; } } } } } } return copy } // The 'scroll' parameter given to many of these indicated whether // the new cursor position should be scrolled into view after // modifying the selection. // If shift is held or the extend flag is set, extends a range to // include a given position (and optionally a second position). // Otherwise, simply returns the range between the given positions. // Used for cursor motion and such. function extendRange(range, head, other, extend) { if (extend) { var anchor = range.anchor; if (other) { var posBefore = cmp(head, anchor) < 0; if (posBefore != (cmp(other, anchor) < 0)) { anchor = head; head = other; } else if (posBefore != (cmp(head, other) < 0)) { head = other; } } return new Range(anchor, head) } else { return new Range(other || head, head) } } // Extend the primary selection range, discard the rest. function extendSelection(doc, head, other, options, extend) { if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); } setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); } // Extend all selections (pos is an array of selections with length // equal the number of selections) function extendSelections(doc, heads, options) { var out = []; var extend = doc.cm && (doc.cm.display.shift || doc.extend); for (var i = 0; i < doc.sel.ranges.length; i++) { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); } var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex); setSelection(doc, newSel, options); } // Updates a single range in the selection. function replaceOneSelection(doc, i, range, options) { var ranges = doc.sel.ranges.slice(0); ranges[i] = range; setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options); } // Reset the selection to a single range. function setSimpleSelection(doc, anchor, head, options) { setSelection(doc, simpleSelection(anchor, head), options); } // Give beforeSelectionChange handlers a change to influence a // selection update. function filterSelectionChange(doc, sel, options) { var obj = { ranges: sel.ranges, update: function(ranges) { this.ranges = []; for (var i = 0; i < ranges.length; i++) { this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), clipPos(doc, ranges[i].head)); } }, origin: options && options.origin }; signal(doc, "beforeSelectionChange", doc, obj); if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); } if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) } else { return sel } } function setSelectionReplaceHistory(doc, sel, options) { var done = doc.history.done, last = lst(done); if (last && last.ranges) { done[done.length - 1] = sel; setSelectionNoUndo(doc, sel, options); } else { setSelection(doc, sel, options); } } // Set a new selection. function setSelection(doc, sel, options) { setSelectionNoUndo(doc, sel, options); addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); } function setSelectionNoUndo(doc, sel, options) { if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { sel = filterSelectionChange(doc, sel, options); } var bias = options && options.bias || (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor") { ensureCursorVisible(doc.cm); } } function setSelectionInner(doc, sel) { if (sel.equals(doc.sel)) { return } doc.sel = sel; if (doc.cm) { doc.cm.curOp.updateInput = 1; doc.cm.curOp.selectionChanged = true; signalCursorActivity(doc.cm); } signalLater(doc, "cursorActivity", doc); } // Verify that the selection does not partially select any atomic // marked ranges. function reCheckSelection(doc) { setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); } // Return a selection that does not partially select any atomic // ranges. function skipAtomicInSelection(doc, sel, bias, mayClear) { var out; for (var i = 0; i < sel.ranges.length; i++) { var range = sel.ranges[i]; var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); var newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear); if (out || newAnchor != range.anchor || newHead != range.head) { if (!out) { out = sel.ranges.slice(0, i); } out[i] = new Range(newAnchor, newHead); } } return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel } function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { var line = getLine(doc, pos.line); if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { var sp = line.markedSpans[i], m = sp.marker; // Determine if we should prevent the cursor being placed to the left/right of an atomic marker // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it // is with selectLeft/Right var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft; var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight; if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) { if (mayClear) { signal(m, "beforeCursorEnter"); if (m.explicitlyCleared) { if (!line.markedSpans) { break } else {--i; continue} } } if (!m.atomic) { continue } if (oldPos) { var near = m.find(dir < 0 ? 1 : -1), diff = (void 0); if (dir < 0 ? preventCursorRight : preventCursorLeft) { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); } if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) { return skipAtomicInner(doc, near, pos, dir, mayClear) } } var far = m.find(dir < 0 ? -1 : 1); if (dir < 0 ? preventCursorLeft : preventCursorRight) { far = movePos(doc, far, dir, far.line == pos.line ? line : null); } return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null } } } return pos } // Ensure a given position is not inside an atomic range. function skipAtomic(doc, pos, oldPos, bias, mayClear) { var dir = bias || 1; var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); if (!found) { doc.cantEdit = true; return Pos(doc.first, 0) } return found } function movePos(doc, pos, dir, line) { if (dir < 0 && pos.ch == 0) { if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } else { return null } } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } else { return null } } else { return new Pos(pos.line, pos.ch + dir) } } function selectAll(cm) { cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); } // UPDATING // Allow "beforeChange" event handlers to influence a change function filterChange(doc, change, update) { var obj = { canceled: false, from: change.from, to: change.to, text: change.text, origin: change.origin, cancel: function () { return obj.canceled = true; } }; if (update) { obj.update = function (from, to, text, origin) { if (from) { obj.from = clipPos(doc, from); } if (to) { obj.to = clipPos(doc, to); } if (text) { obj.text = text; } if (origin !== undefined) { obj.origin = origin; } }; } signal(doc, "beforeChange", doc, obj); if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); } if (obj.canceled) { if (doc.cm) { doc.cm.curOp.updateInput = 2; } return null } return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} } // Apply a change to a document, and add it to the document's // history, and propagating it to all linked documents. function makeChange(doc, change, ignoreReadOnly) { if (doc.cm) { if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } if (doc.cm.state.suppressEdits) { return } } if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { change = filterChange(doc, change, true); if (!change) { return } } // Possibly split or suppress the update based on the presence // of read-only spans in its range. var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); if (split) { for (var i = split.length - 1; i >= 0; --i) { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); } } else { makeChangeInner(doc, change); } } function makeChangeInner(doc, change) { if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } var selAfter = computeSelAfterChange(doc, change); addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); var rebased = []; linkedDocs(doc, function (doc, sharedHist) { if (!sharedHist && indexOf(rebased, doc.history) == -1) { rebaseHist(doc.history, change); rebased.push(doc.history); } makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); }); } // Revert a change stored in a document's history. function makeChangeFromHistory(doc, type, allowSelectionOnly) { var suppress = doc.cm && doc.cm.state.suppressEdits; if (suppress && !allowSelectionOnly) { return } var hist = doc.history, event, selAfter = doc.sel; var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; // Verify that there is a useable event (so that ctrl-z won't // needlessly clear selection events) var i = 0; for (; i < source.length; i++) { event = source[i]; if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) { break } } if (i == source.length) { return } hist.lastOrigin = hist.lastSelOrigin = null; for (;;) { event = source.pop(); if (event.ranges) { pushSelectionToHistory(event, dest); if (allowSelectionOnly && !event.equals(doc.sel)) { setSelection(doc, event, {clearRedo: false}); return } selAfter = event; } else if (suppress) { source.push(event); return } else { break } } // Build up a reverse change object to add to the opposite history // stack (redo when undoing, and vice versa). var antiChanges = []; pushSelectionToHistory(selAfter, dest); dest.push({changes: antiChanges, generation: hist.generation}); hist.generation = event.generation || ++hist.maxGeneration; var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); var loop = function ( i ) { var change = event.changes[i]; change.origin = type; if (filter && !filterChange(doc, change, false)) { source.length = 0; return {} } antiChanges.push(historyChangeFromChange(doc, change)); var after = i ? computeSelAfterChange(doc, change) : lst(source); makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); } var rebased = []; // Propagate to the linked documents linkedDocs(doc, function (doc, sharedHist) { if (!sharedHist && indexOf(rebased, doc.history) == -1) { rebaseHist(doc.history, change); rebased.push(doc.history); } makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); }); }; for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { var returned = loop( i$1 ); if ( returned ) return returned.v; } } // Sub-views need their line numbers shifted when text is added // above or below them in the parent document. function shiftDoc(doc, distance) { if (distance == 0) { return } doc.first += distance; doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( Pos(range.anchor.line + distance, range.anchor.ch), Pos(range.head.line + distance, range.head.ch) ); }), doc.sel.primIndex); if (doc.cm) { regChange(doc.cm, doc.first, doc.first - distance, distance); for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) { regLineChange(doc.cm, l, "gutter"); } } } // More lower-level change function, handling only a single document // (not linked ones). function makeChangeSingleDoc(doc, change, selAfter, spans) { if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } if (change.to.line < doc.first) { shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); return } if (change.from.line > doc.lastLine()) { return } // Clip the change to the size of this doc if (change.from.line < doc.first) { var shift = change.text.length - 1 - (doc.first - change.from.line); shiftDoc(doc, shift); change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), text: [lst(change.text)], origin: change.origin}; } var last = doc.lastLine(); if (change.to.line > last) { change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), text: [change.text[0]], origin: change.origin}; } change.removed = getBetween(doc, change.from, change.to); if (!selAfter) { selAfter = computeSelAfterChange(doc, change); } if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); } else { updateDoc(doc, change, spans); } setSelectionNoUndo(doc, selAfter, sel_dontScroll); if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0))) { doc.cantEdit = false; } } // Handle the interaction of a change to a document with the editor // that this document is part of. function makeChangeSingleDocInEditor(cm, change, spans) { var doc = cm.doc, display = cm.display, from = change.from, to = change.to; var recomputeMaxLength = false, checkWidthStart = from.line; if (!cm.options.lineWrapping) { checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); doc.iter(checkWidthStart, to.line + 1, function (line) { if (line == display.maxLine) { recomputeMaxLength = true; return true } }); } if (doc.sel.contains(change.from, change.to) > -1) { signalCursorActivity(cm); } updateDoc(doc, change, spans, estimateHeight(cm)); if (!cm.options.lineWrapping) { doc.iter(checkWidthStart, from.line + change.text.length, function (line) { var len = lineLength(line); if (len > display.maxLineLength) { display.maxLine = line; display.maxLineLength = len; display.maxLineChanged = true; recomputeMaxLength = false; } }); if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; } } retreatFrontier(doc, from.line); startWorker(cm, 400); var lendiff = change.text.length - (to.line - from.line) - 1; // Remember that these lines changed, for updating the display if (change.full) { regChange(cm); } else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) { regLineChange(cm, from.line, "text"); } else { regChange(cm, from.line, to.line + 1, lendiff); } var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); if (changeHandler || changesHandler) { var obj = { from: from, to: to, text: change.text, removed: change.removed, origin: change.origin }; if (changeHandler) { signalLater(cm, "change", cm, obj); } if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); } } cm.display.selForContextMenu = null; } function replaceRange(doc, code, from, to, origin) { var assign; if (!to) { to = from; } if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); } if (typeof code == "string") { code = doc.splitLines(code); } makeChange(doc, {from: from, to: to, text: code, origin: origin}); } // Rebasing/resetting history to deal with externally-sourced changes function rebaseHistSelSingle(pos, from, to, diff) { if (to < pos.line) { pos.line += diff; } else if (from < pos.line) { pos.line = from; pos.ch = 0; } } // Tries to rebase an array of history events given a change in the // document. If the change touches the same lines as the event, the // event, and everything 'behind' it, is discarded. If the change is // before the event, the event's positions are updated. Uses a // copy-on-write scheme for the positions, to avoid having to // reallocate them all on every rebase, but also avoid problems with // shared position objects being unsafely updated. function rebaseHistArray(array, from, to, diff) { for (var i = 0; i < array.length; ++i) { var sub = array[i], ok = true; if (sub.ranges) { if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } for (var j = 0; j < sub.ranges.length; j++) { rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); } continue } for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { var cur = sub.changes[j$1]; if (to < cur.from.line) { cur.from = Pos(cur.from.line + diff, cur.from.ch); cur.to = Pos(cur.to.line + diff, cur.to.ch); } else if (from <= cur.to.line) { ok = false; break } } if (!ok) { array.splice(0, i + 1); i = 0; } } } function rebaseHist(hist, change) { var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; rebaseHistArray(hist.done, from, to, diff); rebaseHistArray(hist.undone, from, to, diff); } // Utility for applying a change to a line by handle or number, // returning the number and optionally registering the line as // changed. function changeLine(doc, handle, changeType, op) { var no = handle, line = handle; if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); } else { no = lineNo(handle); } if (no == null) { return null } if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); } return line } // The document is represented as a BTree consisting of leaves, with // chunk of lines in them, and branches, with up to ten leaves or // other branch nodes below them. The top node is always a branch // node, and is the document object itself (meaning it has // additional methods and properties). // // All nodes have parent links. The tree is used both to go from // line numbers to line objects, and to go from objects to numbers. // It also indexes by height, and is used to convert between height // and line object, and to find the total height of the document. // // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html function LeafChunk(lines) { this.lines = lines; this.parent = null; var height = 0; for (var i = 0; i < lines.length; ++i) { lines[i].parent = this; height += lines[i].height; } this.height = height; } LeafChunk.prototype = { chunkSize: function() { return this.lines.length }, // Remove the n lines at offset 'at'. removeInner: function(at, n) { for (var i = at, e = at + n; i < e; ++i) { var line = this.lines[i]; this.height -= line.height; cleanUpLine(line); signalLater(line, "delete"); } this.lines.splice(at, n); }, // Helper used to collapse a small branch into a single leaf. collapse: function(lines) { lines.push.apply(lines, this.lines); }, // Insert the given array of lines at offset 'at', count them as // having the given height. insertInner: function(at, lines, height) { this.height += height; this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); for (var i = 0; i < lines.length; ++i) { lines[i].parent = this; } }, // Used to iterate over a part of the tree. iterN: function(at, n, op) { for (var e = at + n; at < e; ++at) { if (op(this.lines[at])) { return true } } } }; function BranchChunk(children) { this.children = children; var size = 0, height = 0; for (var i = 0; i < children.length; ++i) { var ch = children[i]; size += ch.chunkSize(); height += ch.height; ch.parent = this; } this.size = size; this.height = height; this.parent = null; } BranchChunk.prototype = { chunkSize: function() { return this.size }, removeInner: function(at, n) { this.size -= n; for (var i = 0; i < this.children.length; ++i) { var child = this.children[i], sz = child.chunkSize(); if (at < sz) { var rm = Math.min(n, sz - at), oldHeight = child.height; child.removeInner(at, rm); this.height -= oldHeight - child.height; if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } if ((n -= rm) == 0) { break } at = 0; } else { at -= sz; } } // If the result is smaller than 25 lines, ensure that it is a // single leaf node. if (this.size - n < 25 && (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { var lines = []; this.collapse(lines); this.children = [new LeafChunk(lines)]; this.children[0].parent = this; } }, collapse: function(lines) { for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); } }, insertInner: function(at, lines, height) { this.size += lines.length; this.height += height; for (var i = 0; i < this.children.length; ++i) { var child = this.children[i], sz = child.chunkSize(); if (at <= sz) { child.insertInner(at, lines, height); if (child.lines && child.lines.length > 50) { // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. var remaining = child.lines.length % 25 + 25; for (var pos = remaining; pos < child.lines.length;) { var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); child.height -= leaf.height; this.children.splice(++i, 0, leaf); leaf.parent = this; } child.lines = child.lines.slice(0, remaining); this.maybeSpill(); } break } at -= sz; } }, // When a node has grown, check whether it should be split. maybeSpill: function() { if (this.children.length <= 10) { return } var me = this; do { var spilled = me.children.splice(me.children.length - 5, 5); var sibling = new BranchChunk(spilled); if (!me.parent) { // Become the parent node var copy = new BranchChunk(me.children); copy.parent = me; me.children = [copy, sibling]; me = copy; } else { me.size -= sibling.size; me.height -= sibling.height; var myIndex = indexOf(me.parent.children, me); me.parent.children.splice(myIndex + 1, 0, sibling); } sibling.parent = me.parent; } while (me.children.length > 10) me.parent.maybeSpill(); }, iterN: function(at, n, op) { for (var i = 0; i < this.children.length; ++i) { var child = this.children[i], sz = child.chunkSize(); if (at < sz) { var used = Math.min(n, sz - at); if (child.iterN(at, used, op)) { return true } if ((n -= used) == 0) { break } at = 0; } else { at -= sz; } } } }; // Line widgets are block elements displayed above or below a line. var LineWidget = function(doc, node, options) { if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) { this[opt] = options[opt]; } } } this.doc = doc; this.node = node; }; LineWidget.prototype.clear = function () { var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); if (no == null || !ws) { return } for (var i = 0; i < ws.length; ++i) { if (ws[i] == this) { ws.splice(i--, 1); } } if (!ws.length) { line.widgets = null; } var height = widgetHeight(this); updateLineHeight(line, Math.max(0, line.height - height)); if (cm) { runInOp(cm, function () { adjustScrollWhenAboveVisible(cm, line, -height); regLineChange(cm, no, "widget"); }); signalLater(cm, "lineWidgetCleared", cm, this, no); } }; LineWidget.prototype.changed = function () { var this$1 = this; var oldH = this.height, cm = this.doc.cm, line = this.line; this.height = null; var diff = widgetHeight(this) - oldH; if (!diff) { return } if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); } if (cm) { runInOp(cm, function () { cm.curOp.forceUpdate = true; adjustScrollWhenAboveVisible(cm, line, diff); signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); }); } }; eventMixin(LineWidget); function adjustScrollWhenAboveVisible(cm, line, diff) { if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) { addToScrollTop(cm, diff); } } function addLineWidget(doc, handle, node, options) { var widget = new LineWidget(doc, node, options); var cm = doc.cm; if (cm && widget.noHScroll) { cm.display.alignWidgets = true; } changeLine(doc, handle, "widget", function (line) { var widgets = line.widgets || (line.widgets = []); if (widget.insertAt == null) { widgets.push(widget); } else { widgets.splice(Math.min(widgets.length, Math.max(0, widget.insertAt)), 0, widget); } widget.line = line; if (cm && !lineIsHidden(doc, line)) { var aboveVisible = heightAtLine(line) < doc.scrollTop; updateLineHeight(line, line.height + widgetHeight(widget)); if (aboveVisible) { addToScrollTop(cm, widget.height); } cm.curOp.forceUpdate = true; } return true }); if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); } return widget } // TEXTMARKERS // Created with markText and setBookmark methods. A TextMarker is a // handle that can be used to clear or find a marked position in the // document. Line objects hold arrays (markedSpans) containing // {from, to, marker} object pointing to such marker objects, and // indicating that such a marker is present on that line. Multiple // lines may point to the same marker when it spans across lines. // The spans will have null for their from/to properties when the // marker continues beyond the start/end of the line. Markers have // links back to the lines they currently touch. // Collapsed markers have unique ids, in order to be able to order // them, which is needed for uniquely determining an outer marker // when they overlap (they may nest, but not partially overlap). var nextMarkerId = 0; var TextMarker = function(doc, type) { this.lines = []; this.type = type; this.doc = doc; this.id = ++nextMarkerId; }; // Clear the marker. TextMarker.prototype.clear = function () { if (this.explicitlyCleared) { return } var cm = this.doc.cm, withOp = cm && !cm.curOp; if (withOp) { startOperation(cm); } if (hasHandler(this, "clear")) { var found = this.find(); if (found) { signalLater(this, "clear", found.from, found.to); } } var min = null, max = null; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; var span = getMarkedSpanFor(line.markedSpans, this); if (cm && !this.collapsed) { regLineChange(cm, lineNo(line), "text"); } else if (cm) { if (span.to != null) { max = lineNo(line); } if (span.from != null) { min = lineNo(line); } } line.markedSpans = removeMarkedSpan(line.markedSpans, span); if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) { updateLineHeight(line, textHeight(cm.display)); } } if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { var visual = visualLine(this.lines[i$1]), len = lineLength(visual); if (len > cm.display.maxLineLength) { cm.display.maxLine = visual; cm.display.maxLineLength = len; cm.display.maxLineChanged = true; } } } if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); } this.lines.length = 0; this.explicitlyCleared = true; if (this.atomic && this.doc.cantEdit) { this.doc.cantEdit = false; if (cm) { reCheckSelection(cm.doc); } } if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); } if (withOp) { endOperation(cm); } if (this.parent) { this.parent.clear(); } }; // Find the position of the marker in the document. Returns a {from, // to} object by default. Side can be passed to get a specific side // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the // Pos objects returned contain a line object, rather than a line // number (used to prevent looking up the same line twice). TextMarker.prototype.find = function (side, lineObj) { if (side == null && this.type == "bookmark") { side = 1; } var from, to; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; var span = getMarkedSpanFor(line.markedSpans, this); if (span.from != null) { from = Pos(lineObj ? line : lineNo(line), span.from); if (side == -1) { return from } } if (span.to != null) { to = Pos(lineObj ? line : lineNo(line), span.to); if (side == 1) { return to } } } return from && {from: from, to: to} }; // Signals that the marker's widget changed, and surrounding layout // should be recomputed. TextMarker.prototype.changed = function () { var this$1 = this; var pos = this.find(-1, true), widget = this, cm = this.doc.cm; if (!pos || !cm) { return } runInOp(cm, function () { var line = pos.line, lineN = lineNo(pos.line); var view = findViewForLine(cm, lineN); if (view) { clearLineMeasurementCacheFor(view); cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; } cm.curOp.updateMaxLine = true; if (!lineIsHidden(widget.doc, line) && widget.height != null) { var oldHeight = widget.height; widget.height = null; var dHeight = widgetHeight(widget) - oldHeight; if (dHeight) { updateLineHeight(line, line.height + dHeight); } } signalLater(cm, "markerChanged", cm, this$1); }); }; TextMarker.prototype.attachLine = function (line) { if (!this.lines.length && this.doc.cm) { var op = this.doc.cm.curOp; if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); } } this.lines.push(line); }; TextMarker.prototype.detachLine = function (line) { this.lines.splice(indexOf(this.lines, line), 1); if (!this.lines.length && this.doc.cm) { var op = this.doc.cm.curOp ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); } }; eventMixin(TextMarker); // Create a marker, wire it up to the right lines, and function markText(doc, from, to, options, type) { // Shared markers (across linked documents) are handled separately // (markTextShared will call out to this again, once per // document). if (options && options.shared) { return markTextShared(doc, from, to, options, type) } // Ensure we are in an operation. if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } var marker = new TextMarker(doc, type), diff = cmp(from, to); if (options) { copyObj(options, marker, false); } // Don't connect empty markers unless clearWhenEmpty is false if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) { return marker } if (marker.replacedWith) { // Showing up as a widget implies collapsed (widget replaces text) marker.collapsed = true; marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); } if (options.insertLeft) { marker.widgetNode.insertLeft = true; } } if (marker.collapsed) { if (conflictingCollapsedRange(doc, from.line, from, to, marker) || from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) { throw new Error("Inserting collapsed marker partially overlapping an existing one") } seeCollapsedSpans(); } if (marker.addToHistory) { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); } var curLine = from.line, cm = doc.cm, updateMaxLine; doc.iter(curLine, to.line + 1, function (line) { if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) { updateMaxLine = true; } if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); } addMarkedSpan(line, new MarkedSpan(marker, curLine == from.line ? from.ch : null, curLine == to.line ? to.ch : null), doc.cm && doc.cm.curOp); ++curLine; }); // lineIsHidden depends on the presence of the spans, so needs a second pass if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); } }); } if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); } if (marker.readOnly) { seeReadOnlySpans(); if (doc.history.done.length || doc.history.undone.length) { doc.clearHistory(); } } if (marker.collapsed) { marker.id = ++nextMarkerId; marker.atomic = true; } if (cm) { // Sync editor state if (updateMaxLine) { cm.curOp.updateMaxLine = true; } if (marker.collapsed) { regChange(cm, from.line, to.line + 1); } else if (marker.className || marker.startStyle || marker.endStyle || marker.css || marker.attributes || marker.title) { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } } if (marker.atomic) { reCheckSelection(cm.doc); } signalLater(cm, "markerAdded", cm, marker); } return marker } // SHARED TEXTMARKERS // A shared marker spans multiple linked documents. It is // implemented as a meta-marker-object controlling multiple normal // markers. var SharedTextMarker = function(markers, primary) { this.markers = markers; this.primary = primary; for (var i = 0; i < markers.length; ++i) { markers[i].parent = this; } }; SharedTextMarker.prototype.clear = function () { if (this.explicitlyCleared) { return } this.explicitlyCleared = true; for (var i = 0; i < this.markers.length; ++i) { this.markers[i].clear(); } signalLater(this, "clear"); }; SharedTextMarker.prototype.find = function (side, lineObj) { return this.primary.find(side, lineObj) }; eventMixin(SharedTextMarker); function markTextShared(doc, from, to, options, type) { options = copyObj(options); options.shared = false; var markers = [markText(doc, from, to, options, type)], primary = markers[0]; var widget = options.widgetNode; linkedDocs(doc, function (doc) { if (widget) { options.widgetNode = widget.cloneNode(true); } markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); for (var i = 0; i < doc.linked.length; ++i) { if (doc.linked[i].isParent) { return } } primary = lst(markers); }); return new SharedTextMarker(markers, primary) } function findSharedMarkers(doc) { return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) } function copySharedMarkers(doc, markers) { for (var i = 0; i < markers.length; i++) { var marker = markers[i], pos = marker.find(); var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); if (cmp(mFrom, mTo)) { var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); marker.markers.push(subMark); subMark.parent = marker; } } } function detachSharedMarkers(markers) { var loop = function ( i ) { var marker = markers[i], linked = [marker.primary.doc]; linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }); for (var j = 0; j < marker.markers.length; j++) { var subMarker = marker.markers[j]; if (indexOf(linked, subMarker.doc) == -1) { subMarker.parent = null; marker.markers.splice(j--, 1); } } }; for (var i = 0; i < markers.length; i++) loop( i ); } var nextDocId = 0; var Doc = function(text, mode, firstLine, lineSep, direction) { if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } if (firstLine == null) { firstLine = 0; } BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); this.first = firstLine; this.scrollTop = this.scrollLeft = 0; this.cantEdit = false; this.cleanGeneration = 1; this.modeFrontier = this.highlightFrontier = firstLine; var start = Pos(firstLine, 0); this.sel = simpleSelection(start); this.history = new History(null); this.id = ++nextDocId; this.modeOption = mode; this.lineSep = lineSep; this.direction = (direction == "rtl") ? "rtl" : "ltr"; this.extend = false; if (typeof text == "string") { text = this.splitLines(text); } updateDoc(this, {from: start, to: start, text: text}); setSelection(this, simpleSelection(start), sel_dontScroll); }; Doc.prototype = createObj(BranchChunk.prototype, { constructor: Doc, // Iterate over the document. Supports two forms -- with only one // argument, it calls that for each line in the document. With // three, it iterates over the range given by the first two (with // the second being non-inclusive). iter: function(from, to, op) { if (op) { this.iterN(from - this.first, to - from, op); } else { this.iterN(this.first, this.first + this.size, from); } }, // Non-public interface for adding and removing lines. insert: function(at, lines) { var height = 0; for (var i = 0; i < lines.length; ++i) { height += lines[i].height; } this.insertInner(at - this.first, lines, height); }, remove: function(at, n) { this.removeInner(at - this.first, n); }, // From here, the methods are part of the public interface. Most // are also available from CodeMirror (editor) instances. getValue: function(lineSep) { var lines = getLines(this, this.first, this.first + this.size); if (lineSep === false) { return lines } return lines.join(lineSep || this.lineSeparator()) }, setValue: docMethodOp(function(code) { var top = Pos(this.first, 0), last = this.first + this.size - 1; makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), text: this.splitLines(code), origin: "setValue", full: true}, true); if (this.cm) { scrollToCoords(this.cm, 0, 0); } setSelection(this, simpleSelection(top), sel_dontScroll); }), replaceRange: function(code, from, to, origin) { from = clipPos(this, from); to = to ? clipPos(this, to) : from; replaceRange(this, code, from, to, origin); }, getRange: function(from, to, lineSep) { var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); if (lineSep === false) { return lines } if (lineSep === '') { return lines.join('') } return lines.join(lineSep || this.lineSeparator()) }, getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, getLineNumber: function(line) {return lineNo(line)}, getLineHandleVisualStart: function(line) { if (typeof line == "number") { line = getLine(this, line); } return visualLine(line) }, lineCount: function() {return this.size}, firstLine: function() {return this.first}, lastLine: function() {return this.first + this.size - 1}, clipPos: function(pos) {return clipPos(this, pos)}, getCursor: function(start) { var range = this.sel.primary(), pos; if (start == null || start == "head") { pos = range.head; } else if (start == "anchor") { pos = range.anchor; } else if (start == "end" || start == "to" || start === false) { pos = range.to(); } else { pos = range.from(); } return pos }, listSelections: function() { return this.sel.ranges }, somethingSelected: function() {return this.sel.somethingSelected()}, setCursor: docMethodOp(function(line, ch, options) { setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); }), setSelection: docMethodOp(function(anchor, head, options) { setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); }), extendSelection: docMethodOp(function(head, other, options) { extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); }), extendSelections: docMethodOp(function(heads, options) { extendSelections(this, clipPosArray(this, heads), options); }), extendSelectionsBy: docMethodOp(function(f, options) { var heads = map(this.sel.ranges, f); extendSelections(this, clipPosArray(this, heads), options); }), setSelections: docMethodOp(function(ranges, primary, options) { if (!ranges.length) { return } var out = []; for (var i = 0; i < ranges.length; i++) { out[i] = new Range(clipPos(this, ranges[i].anchor), clipPos(this, ranges[i].head || ranges[i].anchor)); } if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } setSelection(this, normalizeSelection(this.cm, out, primary), options); }), addSelection: docMethodOp(function(anchor, head, options) { var ranges = this.sel.ranges.slice(0); ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options); }), getSelection: function(lineSep) { var ranges = this.sel.ranges, lines; for (var i = 0; i < ranges.length; i++) { var sel = getBetween(this, ranges[i].from(), ranges[i].to()); lines = lines ? lines.concat(sel) : sel; } if (lineSep === false) { return lines } else { return lines.join(lineSep || this.lineSeparator()) } }, getSelections: function(lineSep) { var parts = [], ranges = this.sel.ranges; for (var i = 0; i < ranges.length; i++) { var sel = getBetween(this, ranges[i].from(), ranges[i].to()); if (lineSep !== false) { sel = sel.join(lineSep || this.lineSeparator()); } parts[i] = sel; } return parts }, replaceSelection: function(code, collapse, origin) { var dup = []; for (var i = 0; i < this.sel.ranges.length; i++) { dup[i] = code; } this.replaceSelections(dup, collapse, origin || "+input"); }, replaceSelections: docMethodOp(function(code, collapse, origin) { var changes = [], sel = this.sel; for (var i = 0; i < sel.ranges.length; i++) { var range = sel.ranges[i]; changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}; } var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) { makeChange(this, changes[i$1]); } if (newSel) { setSelectionReplaceHistory(this, newSel); } else if (this.cm) { ensureCursorVisible(this.cm); } }), undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), setExtending: function(val) {this.extend = val;}, getExtending: function() {return this.extend}, historySize: function() { var hist = this.history, done = 0, undone = 0; for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } } for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } } return {undo: done, redo: undone} }, clearHistory: function() { var this$1 = this; this.history = new History(this.history); linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true); }, markClean: function() { this.cleanGeneration = this.changeGeneration(true); }, changeGeneration: function(forceSplit) { if (forceSplit) { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; } return this.history.generation }, isClean: function (gen) { return this.history.generation == (gen || this.cleanGeneration) }, getHistory: function() { return {done: copyHistoryArray(this.history.done), undone: copyHistoryArray(this.history.undone)} }, setHistory: function(histData) { var hist = this.history = new History(this.history); hist.done = copyHistoryArray(histData.done.slice(0), null, true); hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); }, setGutterMarker: docMethodOp(function(line, gutterID, value) { return changeLine(this, line, "gutter", function (line) { var markers = line.gutterMarkers || (line.gutterMarkers = {}); markers[gutterID] = value; if (!value && isEmpty(markers)) { line.gutterMarkers = null; } return true }) }), clearGutter: docMethodOp(function(gutterID) { var this$1 = this; this.iter(function (line) { if (line.gutterMarkers && line.gutterMarkers[gutterID]) { changeLine(this$1, line, "gutter", function () { line.gutterMarkers[gutterID] = null; if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; } return true }); } }); }), lineInfo: function(line) { var n; if (typeof line == "number") { if (!isLine(this, line)) { return null } n = line; line = getLine(this, line); if (!line) { return null } } else { n = lineNo(line); if (n == null) { return null } } return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, widgets: line.widgets} }, addLineClass: docMethodOp(function(handle, where, cls) { return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass"; if (!line[prop]) { line[prop] = cls; } else if (classTest(cls).test(line[prop])) { return false } else { line[prop] += " " + cls; } return true }) }), removeLineClass: docMethodOp(function(handle, where, cls) { return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass"; var cur = line[prop]; if (!cur) { return false } else if (cls == null) { line[prop] = null; } else { var found = cur.match(classTest(cls)); if (!found) { return false } var end = found.index + found[0].length; line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; } return true }) }), addLineWidget: docMethodOp(function(handle, node, options) { return addLineWidget(this, handle, node, options) }), removeLineWidget: function(widget) { widget.clear(); }, markText: function(from, to, options) { return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") }, setBookmark: function(pos, options) { var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), insertLeft: options && options.insertLeft, clearWhenEmpty: false, shared: options && options.shared, handleMouseEvents: options && options.handleMouseEvents}; pos = clipPos(this, pos); return markText(this, pos, pos, realOpts, "bookmark") }, findMarksAt: function(pos) { pos = clipPos(this, pos); var markers = [], spans = getLine(this, pos.line).markedSpans; if (spans) { for (var i = 0; i < spans.length; ++i) { var span = spans[i]; if ((span.from == null || span.from <= pos.ch) && (span.to == null || span.to >= pos.ch)) { markers.push(span.marker.parent || span.marker); } } } return markers }, findMarks: function(from, to, filter) { from = clipPos(this, from); to = clipPos(this, to); var found = [], lineNo = from.line; this.iter(from.line, to.line + 1, function (line) { var spans = line.markedSpans; if (spans) { for (var i = 0; i < spans.length; i++) { var span = spans[i]; if (!(span.to != null && lineNo == from.line && from.ch >= span.to || span.from == null && lineNo != from.line || span.from != null && lineNo == to.line && span.from >= to.ch) && (!filter || filter(span.marker))) { found.push(span.marker.parent || span.marker); } } } ++lineNo; }); return found }, getAllMarks: function() { var markers = []; this.iter(function (line) { var sps = line.markedSpans; if (sps) { for (var i = 0; i < sps.length; ++i) { if (sps[i].from != null) { markers.push(sps[i].marker); } } } }); return markers }, posFromIndex: function(off) { var ch, lineNo = this.first, sepSize = this.lineSeparator().length; this.iter(function (line) { var sz = line.text.length + sepSize; if (sz > off) { ch = off; return true } off -= sz; ++lineNo; }); return clipPos(this, Pos(lineNo, ch)) }, indexFromPos: function (coords) { coords = clipPos(this, coords); var index = coords.ch; if (coords.line < this.first || coords.ch < 0) { return 0 } var sepSize = this.lineSeparator().length; this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value index += line.text.length + sepSize; }); return index }, copy: function(copyHistory) { var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first, this.lineSep, this.direction); doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; doc.sel = this.sel; doc.extend = false; if (copyHistory) { doc.history.undoDepth = this.history.undoDepth; doc.setHistory(this.getHistory()); } return doc }, linkedDoc: function(options) { if (!options) { options = {}; } var from = this.first, to = this.first + this.size; if (options.from != null && options.from > from) { from = options.from; } if (options.to != null && options.to < to) { to = options.to; } var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); if (options.sharedHist) { copy.history = this.history ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; copySharedMarkers(copy, findSharedMarkers(this)); return copy }, unlinkDoc: function(other) { if (other instanceof CodeMirror) { other = other.doc; } if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { var link = this.linked[i]; if (link.doc != other) { continue } this.linked.splice(i, 1); other.unlinkDoc(this); detachSharedMarkers(findSharedMarkers(this)); break } } // If the histories were shared, split them again if (other.history == this.history) { var splitIds = [other.id]; linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true); other.history = new History(null); other.history.done = copyHistoryArray(this.history.done, splitIds); other.history.undone = copyHistoryArray(this.history.undone, splitIds); } }, iterLinkedDocs: function(f) {linkedDocs(this, f);}, getMode: function() {return this.mode}, getEditor: function() {return this.cm}, splitLines: function(str) { if (this.lineSep) { return str.split(this.lineSep) } return splitLinesAuto(str) }, lineSeparator: function() { return this.lineSep || "\n" }, setDirection: docMethodOp(function (dir) { if (dir != "rtl") { dir = "ltr"; } if (dir == this.direction) { return } this.direction = dir; this.iter(function (line) { return line.order = null; }); if (this.cm) { directionChanged(this.cm); } }) }); // Public alias. Doc.prototype.eachLine = Doc.prototype.iter; // Kludge to work around strange IE behavior where it'll sometimes // re-fire a series of drag-related events right after the drop (#1551) var lastDrop = 0; function onDrop(e) { var cm = this; clearDragCursor(cm); if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } e_preventDefault(e); if (ie) { lastDrop = +new Date; } var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || cm.isReadOnly()) { return } // Might be a file drop, in which case we simply extract the text // and insert it. if (files && files.length && window.FileReader && window.File) { var n = files.length, text = Array(n), read = 0; var markAsReadAndPasteIfAllFilesAreRead = function () { if (++read == n) { operation(cm, function () { pos = clipPos(cm.doc, pos); var change = {from: pos, to: pos, text: cm.doc.splitLines( text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())), origin: "paste"}; makeChange(cm.doc, change); setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change)))); })(); } }; var readTextFromFile = function (file, i) { if (cm.options.allowDropFileTypes && indexOf(cm.options.allowDropFileTypes, file.type) == -1) { markAsReadAndPasteIfAllFilesAreRead(); return } var reader = new FileReader; reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); }; reader.onload = function () { var content = reader.result; if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { markAsReadAndPasteIfAllFilesAreRead(); return } text[i] = content; markAsReadAndPasteIfAllFilesAreRead(); }; reader.readAsText(file); }; for (var i = 0; i < files.length; i++) { readTextFromFile(files[i], i); } } else { // Normal drop // Don't do a replace if the drop happened inside of the selected text. if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { cm.state.draggingText(e); // Ensure the editor is re-focused setTimeout(function () { return cm.display.input.focus(); }, 20); return } try { var text$1 = e.dataTransfer.getData("Text"); if (text$1) { var selected; if (cm.state.draggingText && !cm.state.draggingText.copy) { selected = cm.listSelections(); } setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } } cm.replaceSelection(text$1, "around", "paste"); cm.display.input.focus(); } } catch(e$1){} } } function onDragStart(cm, e) { if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } e.dataTransfer.setData("Text", cm.getSelection()); e.dataTransfer.effectAllowed = "copyMove"; // Use dummy image instead of default browsers image. // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. if (e.dataTransfer.setDragImage && !safari) { var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; if (presto) { img.width = img.height = 1; cm.display.wrapper.appendChild(img); // Force a relayout, or Opera won't use our image for some obscure reason img._top = img.offsetTop; } e.dataTransfer.setDragImage(img, 0, 0); if (presto) { img.parentNode.removeChild(img); } } } function onDragOver(cm, e) { var pos = posFromMouse(cm, e); if (!pos) { return } var frag = document.createDocumentFragment(); drawSelectionCursor(cm, pos, frag); if (!cm.display.dragCursor) { cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); } removeChildrenAndAdd(cm.display.dragCursor, frag); } function clearDragCursor(cm) { if (cm.display.dragCursor) { cm.display.lineSpace.removeChild(cm.display.dragCursor); cm.display.dragCursor = null; } } // These must be handled carefully, because naively registering a // handler for each editor will cause the editors to never be // garbage collected. function forEachCodeMirror(f) { if (!document.getElementsByClassName) { return } var byClass = document.getElementsByClassName("CodeMirror"), editors = []; for (var i = 0; i < byClass.length; i++) { var cm = byClass[i].CodeMirror; if (cm) { editors.push(cm); } } if (editors.length) { editors[0].operation(function () { for (var i = 0; i < editors.length; i++) { f(editors[i]); } }); } } var globalsRegistered = false; function ensureGlobalHandlers() { if (globalsRegistered) { return } registerGlobalHandlers(); globalsRegistered = true; } function registerGlobalHandlers() { // When the window resizes, we need to refresh active editors. var resizeTimer; on(window, "resize", function () { if (resizeTimer == null) { resizeTimer = setTimeout(function () { resizeTimer = null; forEachCodeMirror(onResize); }, 100); } }); // When the window loses focus, we want to show the editor as blurred on(window, "blur", function () { return forEachCodeMirror(onBlur); }); } // Called when the window resizes function onResize(cm) { var d = cm.display; // Might be a text scaling operation, clear size caches. d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; d.scrollbarsClipped = false; cm.setSize(); } var keyNames = { 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock", 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 224: "Mod", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" }; // Number keys for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); } // Alphabetic keys for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); } // Function keys for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; } var keyMap = {}; keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", "Esc": "singleSelection" }; // Note that the save and find-related commands aren't defined by // default. User code or addons can define them. Unknown commands // are simply ignored. keyMap.pcDefault = { "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", "fallthrough": "basic" }; // Very basic readline/emacs-style bindings, which are standard on Mac. keyMap.emacsy = { "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", "Ctrl-O": "openLine" }; keyMap.macDefault = { "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", "fallthrough": ["basic", "emacsy"] }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; // KEYMAP DISPATCH function normalizeKeyName(name) { var parts = name.split(/-(?!$)/); name = parts[parts.length - 1]; var alt, ctrl, shift, cmd; for (var i = 0; i < parts.length - 1; i++) { var mod = parts[i]; if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } else if (/^a(lt)?$/i.test(mod)) { alt = true; } else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } else if (/^s(hift)?$/i.test(mod)) { shift = true; } else { throw new Error("Unrecognized modifier name: " + mod) } } if (alt) { name = "Alt-" + name; } if (ctrl) { name = "Ctrl-" + name; } if (cmd) { name = "Cmd-" + name; } if (shift) { name = "Shift-" + name; } return name } // This is a kludge to keep keymaps mostly working as raw objects // (backwards compatibility) while at the same time support features // like normalization and multi-stroke key bindings. It compiles a // new normalized keymap, and then updates the old object to reflect // this. function normalizeKeyMap(keymap) { var copy = {}; for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { var value = keymap[keyname]; if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } if (value == "...") { delete keymap[keyname]; continue } var keys = map(keyname.split(" "), normalizeKeyName); for (var i = 0; i < keys.length; i++) { var val = (void 0), name = (void 0); if (i == keys.length - 1) { name = keys.join(" "); val = value; } else { name = keys.slice(0, i + 1).join(" "); val = "..."; } var prev = copy[name]; if (!prev) { copy[name] = val; } else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } } delete keymap[keyname]; } } for (var prop in copy) { keymap[prop] = copy[prop]; } return keymap } function lookupKey(key, map, handle, context) { map = getKeyMap(map); var found = map.call ? map.call(key, context) : map[key]; if (found === false) { return "nothing" } if (found === "...") { return "multi" } if (found != null && handle(found)) { return "handled" } if (map.fallthrough) { if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") { return lookupKey(key, map.fallthrough, handle, context) } for (var i = 0; i < map.fallthrough.length; i++) { var result = lookupKey(key, map.fallthrough[i], handle, context); if (result) { return result } } } } // Modifier key presses don't count as 'real' key presses for the // purpose of keymap fallthrough. function isModifierKey(value) { var name = typeof value == "string" ? value : keyNames[value.keyCode]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" } function addModifierNames(name, event, noShift) { var base = name; if (event.altKey && base != "Alt") { name = "Alt-" + name; } if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; } if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Mod") { name = "Cmd-" + name; } if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; } return name } // Look up the name of a key as indicated by an event object. function keyName(event, noShift) { if (presto && event.keyCode == 34 && event["char"]) { return false } var name = keyNames[event.keyCode]; if (name == null || event.altGraphKey) { return false } // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) if (event.keyCode == 3 && event.code) { name = event.code; } return addModifierNames(name, event, noShift) } function getKeyMap(val) { return typeof val == "string" ? keyMap[val] : val } // Helper for deleting text near the selection(s), used to implement // backspace, delete, and similar functionality. function deleteNearSelection(cm, compute) { var ranges = cm.doc.sel.ranges, kill = []; // Build up a set of ranges to kill first, merging overlapping // ranges. for (var i = 0; i < ranges.length; i++) { var toKill = compute(ranges[i]); while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { var replaced = kill.pop(); if (cmp(replaced.from, toKill.from) < 0) { toKill.from = replaced.from; break } } kill.push(toKill); } // Next, remove those actual ranges. runInOp(cm, function () { for (var i = kill.length - 1; i >= 0; i--) { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); } ensureCursorVisible(cm); }); } function moveCharLogically(line, ch, dir) { var target = skipExtendingChars(line.text, ch + dir, dir); return target < 0 || target > line.text.length ? null : target } function moveLogically(line, start, dir) { var ch = moveCharLogically(line, start.ch, dir); return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") } function endOfLine(visually, cm, lineObj, lineNo, dir) { if (visually) { if (cm.doc.direction == "rtl") { dir = -dir; } var order = getOrder(lineObj, cm.doc.direction); if (order) { var part = dir < 0 ? lst(order) : order[0]; var moveInStorageOrder = (dir < 0) == (part.level == 1); var sticky = moveInStorageOrder ? "after" : "before"; var ch; // With a wrapped rtl chunk (possibly spanning multiple bidi parts), // it could be that the last bidi part is not on the last visual line, // since visual lines contain content order-consecutive chunks. // Thus, in rtl, we are looking for the first (content-order) character // in the rtl chunk that is on the last line (that is, the same line // as the last (content-order) character). if (part.level > 0 || cm.doc.direction == "rtl") { var prep = prepareMeasureForLine(cm, lineObj); ch = dir < 0 ? lineObj.text.length - 1 : 0; var targetTop = measureCharPrepared(cm, prep, ch).top; ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch); if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); } } else { ch = dir < 0 ? part.to : part.from; } return new Pos(lineNo, ch, sticky) } } return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") } function moveVisually(cm, line, start, dir) { var bidi = getOrder(line, cm.doc.direction); if (!bidi) { return moveLogically(line, start, dir) } if (start.ch >= line.text.length) { start.ch = line.text.length; start.sticky = "before"; } else if (start.ch <= 0) { start.ch = 0; start.sticky = "after"; } var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]; if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, // nothing interesting happens. return moveLogically(line, start, dir) } var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }; var prep; var getWrappedLineExtent = function (ch) { if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } prep = prep || prepareMeasureForLine(cm, line); return wrappedLineExtentChar(cm, line, prep, ch) }; var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); if (cm.doc.direction == "rtl" || part.level == 1) { var moveInStorageOrder = (part.level == 1) == (dir < 0); var ch = mv(start, moveInStorageOrder ? 1 : -1); if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { // Case 2: We move within an rtl part or in an rtl editor on the same visual line var sticky = moveInStorageOrder ? "before" : "after"; return new Pos(start.line, ch, sticky) } } // Case 3: Could not move within this bidi part in this visual line, so leave // the current bidi part var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder ? new Pos(start.line, mv(ch, 1), "before") : new Pos(start.line, ch, "after"); }; for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { var part = bidi[partPos]; var moveInStorageOrder = (dir > 0) == (part.level != 1); var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } ch = moveInStorageOrder ? part.from : mv(part.to, -1); if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } } }; // Case 3a: Look for other bidi parts on the same visual line var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); if (res) { return res } // Case 3b: Look for other bidi parts on the next visual line var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); if (res) { return res } } // Case 4: Nowhere to move return null } // Commands are parameter-less actions that can be performed on an // editor, mostly used for keybindings. var commands = { selectAll: selectAll, singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, killLine: function (cm) { return deleteNearSelection(cm, function (range) { if (range.empty()) { var len = getLine(cm.doc, range.head.line).text.length; if (range.head.ch == len && range.head.line < cm.lastLine()) { return {from: range.head, to: Pos(range.head.line + 1, 0)} } else { return {from: range.head, to: Pos(range.head.line, len)} } } else { return {from: range.from(), to: range.to()} } }); }, deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ from: Pos(range.from().line, 0), to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) }); }); }, delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ from: Pos(range.from().line, 0), to: range.from() }); }); }, delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { var top = cm.charCoords(range.head, "div").top + 5; var leftPos = cm.coordsChar({left: 0, top: top}, "div"); return {from: leftPos, to: range.from()} }); }, delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { var top = cm.charCoords(range.head, "div").top + 5; var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); return {from: range.from(), to: rightPos } }); }, undo: function (cm) { return cm.undo(); }, redo: function (cm) { return cm.redo(); }, undoSelection: function (cm) { return cm.undoSelection(); }, redoSelection: function (cm) { return cm.redoSelection(); }, goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, {origin: "+move", bias: 1} ); }, goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, {origin: "+move", bias: 1} ); }, goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, {origin: "+move", bias: -1} ); }, goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { var top = cm.cursorCoords(range.head, "div").top + 5; return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") }, sel_move); }, goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { var top = cm.cursorCoords(range.head, "div").top + 5; return cm.coordsChar({left: 0, top: top}, "div") }, sel_move); }, goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { var top = cm.cursorCoords(range.head, "div").top + 5; var pos = cm.coordsChar({left: 0, top: top}, "div"); if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } return pos }, sel_move); }, goLineUp: function (cm) { return cm.moveV(-1, "line"); }, goLineDown: function (cm) { return cm.moveV(1, "line"); }, goPageUp: function (cm) { return cm.moveV(-1, "page"); }, goPageDown: function (cm) { return cm.moveV(1, "page"); }, goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, goCharRight: function (cm) { return cm.moveH(1, "char"); }, goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, goColumnRight: function (cm) { return cm.moveH(1, "column"); }, goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, goGroupRight: function (cm) { return cm.moveH(1, "group"); }, goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, goWordRight: function (cm) { return cm.moveH(1, "word"); }, delCharBefore: function (cm) { return cm.deleteH(-1, "codepoint"); }, delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, indentAuto: function (cm) { return cm.indentSelection("smart"); }, indentMore: function (cm) { return cm.indentSelection("add"); }, indentLess: function (cm) { return cm.indentSelection("subtract"); }, insertTab: function (cm) { return cm.replaceSelection("\t"); }, insertSoftTab: function (cm) { var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; for (var i = 0; i < ranges.length; i++) { var pos = ranges[i].from(); var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); spaces.push(spaceStr(tabSize - col % tabSize)); } cm.replaceSelections(spaces); }, defaultTab: function (cm) { if (cm.somethingSelected()) { cm.indentSelection("add"); } else { cm.execCommand("insertTab"); } }, // Swap the two chars left and right of each selection's head. // Move cursor behind the two swapped characters afterwards. // // Doesn't consider line feeds a character. // Doesn't scan more than one line above to find a character. // Doesn't do anything on an empty line. // Doesn't do anything with non-empty selections. transposeChars: function (cm) { return runInOp(cm, function () { var ranges = cm.listSelections(), newSel = []; for (var i = 0; i < ranges.length; i++) { if (!ranges[i].empty()) { continue } var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; if (line) { if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); } if (cur.ch > 0) { cur = new Pos(cur.line, cur.ch + 1); cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), Pos(cur.line, cur.ch - 2), cur, "+transpose"); } else if (cur.line > cm.doc.first) { var prev = getLine(cm.doc, cur.line - 1).text; if (prev) { cur = new Pos(cur.line, 1); cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + prev.charAt(prev.length - 1), Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); } } } newSel.push(new Range(cur, cur)); } cm.setSelections(newSel); }); }, newlineAndIndent: function (cm) { return runInOp(cm, function () { var sels = cm.listSelections(); for (var i = sels.length - 1; i >= 0; i--) { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); } sels = cm.listSelections(); for (var i$1 = 0; i$1 < sels.length; i$1++) { cm.indentLine(sels[i$1].from().line, null, true); } ensureCursorVisible(cm); }); }, openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } }; function lineStart(cm, lineN) { var line = getLine(cm.doc, lineN); var visual = visualLine(line); if (visual != line) { lineN = lineNo(visual); } return endOfLine(true, cm, visual, lineN, 1) } function lineEnd(cm, lineN) { var line = getLine(cm.doc, lineN); var visual = visualLineEnd(line); if (visual != line) { lineN = lineNo(visual); } return endOfLine(true, cm, line, lineN, -1) } function lineStartSmart(cm, pos) { var start = lineStart(cm, pos.line); var line = getLine(cm.doc, start.line); var order = getOrder(line, cm.doc.direction); if (!order || order[0].level == 0) { var firstNonWS = Math.max(start.ch, line.text.search(/\S/)); var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) } return start } // Run a handler that was bound to a key. function doHandleBinding(cm, bound, dropShift) { if (typeof bound == "string") { bound = commands[bound]; if (!bound) { return false } } // Ensure previous input has been read, so that the handler sees a // consistent view of the document cm.display.input.ensurePolled(); var prevShift = cm.display.shift, done = false; try { if (cm.isReadOnly()) { cm.state.suppressEdits = true; } if (dropShift) { cm.display.shift = false; } done = bound(cm) != Pass; } finally { cm.display.shift = prevShift; cm.state.suppressEdits = false; } return done } function lookupKeyForEditor(cm, name, handle) { for (var i = 0; i < cm.state.keyMaps.length; i++) { var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); if (result) { return result } } return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) || lookupKey(name, cm.options.keyMap, handle, cm) } // Note that, despite the name, this function is also used to check // for bound mouse clicks. var stopSeq = new Delayed; function dispatchKey(cm, name, e, handle) { var seq = cm.state.keySeq; if (seq) { if (isModifierKey(name)) { return "handled" } if (/\'$/.test(name)) { cm.state.keySeq = null; } else { stopSeq.set(50, function () { if (cm.state.keySeq == seq) { cm.state.keySeq = null; cm.display.input.reset(); } }); } if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } } return dispatchKeyInner(cm, name, e, handle) } function dispatchKeyInner(cm, name, e, handle) { var result = lookupKeyForEditor(cm, name, handle); if (result == "multi") { cm.state.keySeq = name; } if (result == "handled") { signalLater(cm, "keyHandled", cm, name, e); } if (result == "handled" || result == "multi") { e_preventDefault(e); restartBlink(cm); } return !!result } // Handle a key from the keydown event. function handleKeyBinding(cm, e) { var name = keyName(e, true); if (!name) { return false } if (e.shiftKey && !cm.state.keySeq) { // First try to resolve full name (including 'Shift-'). Failing // that, see if there is a cursor-motion command (starting with // 'go') bound to the keyname without 'Shift-'. return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) || dispatchKey(cm, name, e, function (b) { if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) { return doHandleBinding(cm, b) } }) } else { return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) } } // Handle a key from the keypress event function handleCharBinding(cm, e, ch) { return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) } var lastStoppedKey = null; function onKeyDown(e) { var cm = this; if (e.target && e.target != cm.display.input.getField()) { return } cm.curOp.focus = activeElt(root(cm)); if (signalDOMEvent(cm, e)) { return } // IE does strange things with escape. if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; } var code = e.keyCode; cm.display.shift = code == 16 || e.shiftKey; var handled = handleKeyBinding(cm, e); if (presto) { lastStoppedKey = handled ? code : null; // Opera has no cut event... we try to at least catch the key combo if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) { cm.replaceSelection("", null, "cut"); } } if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand) { document.execCommand("cut"); } // Turn mouse into crosshair when Alt is held on Mac. if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) { showCrossHair(cm); } } function showCrossHair(cm) { var lineDiv = cm.display.lineDiv; addClass(lineDiv, "CodeMirror-crosshair"); function up(e) { if (e.keyCode == 18 || !e.altKey) { rmClass(lineDiv, "CodeMirror-crosshair"); off(document, "keyup", up); off(document, "mouseover", up); } } on(document, "keyup", up); on(document, "mouseover", up); } function onKeyUp(e) { if (e.keyCode == 16) { this.doc.sel.shift = false; } signalDOMEvent(this, e); } function onKeyPress(e) { var cm = this; if (e.target && e.target != cm.display.input.getField()) { return } if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } var keyCode = e.keyCode, charCode = e.charCode; if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } var ch = String.fromCharCode(charCode == null ? keyCode : charCode); // Some browsers fire keypress events for backspace if (ch == "\x08") { return } if (handleCharBinding(cm, e, ch)) { return } cm.display.input.onKeyPress(e); } var DOUBLECLICK_DELAY = 400; var PastClick = function(time, pos, button) { this.time = time; this.pos = pos; this.button = button; }; PastClick.prototype.compare = function (time, pos, button) { return this.time + DOUBLECLICK_DELAY > time && cmp(pos, this.pos) == 0 && button == this.button }; var lastClick, lastDoubleClick; function clickRepeat(pos, button) { var now = +new Date; if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { lastClick = lastDoubleClick = null; return "triple" } else if (lastClick && lastClick.compare(now, pos, button)) { lastDoubleClick = new PastClick(now, pos, button); lastClick = null; return "double" } else { lastClick = new PastClick(now, pos, button); lastDoubleClick = null; return "single" } } // A mouse down can be a single click, double click, triple click, // start of selection drag, start of text drag, new cursor // (ctrl-click), rectangle drag (alt-drag), or xwin // middle-click-paste. Or it might be a click on something we should // not interfere with, such as a scrollbar or widget. function onMouseDown(e) { var cm = this, display = cm.display; if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } display.input.ensurePolled(); display.shift = e.shiftKey; if (eventInWidget(display, e)) { if (!webkit) { // Briefly turn off draggability, to allow widgets to do // normal dragging things. display.scroller.draggable = false; setTimeout(function () { return display.scroller.draggable = true; }, 100); } return } if (clickInGutter(cm, e)) { return } var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"; win(cm).focus(); // #3261: make sure, that we're not starting a second selection if (button == 1 && cm.state.selectingText) { cm.state.selectingText(e); } if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } if (button == 1) { if (pos) { leftButtonDown(cm, pos, repeat, e); } else if (e_target(e) == display.scroller) { e_preventDefault(e); } } else if (button == 2) { if (pos) { extendSelection(cm.doc, pos); } setTimeout(function () { return display.input.focus(); }, 20); } else if (button == 3) { if (captureRightClick) { cm.display.input.onContextMenu(e); } else { delayBlurEvent(cm); } } } function handleMappedButton(cm, button, pos, repeat, event) { var name = "Click"; if (repeat == "double") { name = "Double" + name; } else if (repeat == "triple") { name = "Triple" + name; } name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { if (typeof bound == "string") { bound = commands[bound]; } if (!bound) { return false } var done = false; try { if (cm.isReadOnly()) { cm.state.suppressEdits = true; } done = bound(cm, pos) != Pass; } finally { cm.state.suppressEdits = false; } return done }) } function configureMouse(cm, repeat, event) { var option = cm.getOption("configureMouse"); var value = option ? option(cm, repeat, event) : {}; if (value.unit == null) { var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; } if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; } if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; } if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); } return value } function leftButtonDown(cm, pos, repeat, event) { if (ie) { setTimeout(bind(ensureFocus, cm), 0); } else { cm.curOp.focus = activeElt(root(cm)); } var behavior = configureMouse(cm, repeat, event); var sel = cm.doc.sel, contained; if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && repeat == "single" && (contained = sel.contains(pos)) > -1 && (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) { leftButtonStartDrag(cm, event, pos, behavior); } else { leftButtonSelect(cm, event, pos, behavior); } } // Start a text drag. When it ends, see if any dragging actually // happen, and treat as a click if it didn't. function leftButtonStartDrag(cm, event, pos, behavior) { var display = cm.display, moved = false; var dragEnd = operation(cm, function (e) { if (webkit) { display.scroller.draggable = false; } cm.state.draggingText = false; if (cm.state.delayingBlurEvent) { if (cm.hasFocus()) { cm.state.delayingBlurEvent = false; } else { delayBlurEvent(cm); } } off(display.wrapper.ownerDocument, "mouseup", dragEnd); off(display.wrapper.ownerDocument, "mousemove", mouseMove); off(display.scroller, "dragstart", dragStart); off(display.scroller, "drop", dragEnd); if (!moved) { e_preventDefault(e); if (!behavior.addNew) { extendSelection(cm.doc, pos, null, null, behavior.extend); } // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) if ((webkit && !safari) || ie && ie_version == 9) { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus();}, 20); } else { display.input.focus(); } } }); var mouseMove = function(e2) { moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; }; var dragStart = function () { return moved = true; }; // Let the drag handler handle this. if (webkit) { display.scroller.draggable = true; } cm.state.draggingText = dragEnd; dragEnd.copy = !behavior.moveOnDrag; on(display.wrapper.ownerDocument, "mouseup", dragEnd); on(display.wrapper.ownerDocument, "mousemove", mouseMove); on(display.scroller, "dragstart", dragStart); on(display.scroller, "drop", dragEnd); cm.state.delayingBlurEvent = true; setTimeout(function () { return display.input.focus(); }, 20); // IE's approach to draggable if (display.scroller.dragDrop) { display.scroller.dragDrop(); } } function rangeForUnit(cm, pos, unit) { if (unit == "char") { return new Range(pos, pos) } if (unit == "word") { return cm.findWordAt(pos) } if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } var result = unit(cm, pos); return new Range(result.from, result.to) } // Normal selection, as opposed to text dragging. function leftButtonSelect(cm, event, start, behavior) { if (ie) { delayBlurEvent(cm); } var display = cm.display, doc = cm.doc; e_preventDefault(event); var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; if (behavior.addNew && !behavior.extend) { ourIndex = doc.sel.contains(start); if (ourIndex > -1) { ourRange = ranges[ourIndex]; } else { ourRange = new Range(start, start); } } else { ourRange = doc.sel.primary(); ourIndex = doc.sel.primIndex; } if (behavior.unit == "rectangle") { if (!behavior.addNew) { ourRange = new Range(start, start); } start = posFromMouse(cm, event, true, true); ourIndex = -1; } else { var range = rangeForUnit(cm, start, behavior.unit); if (behavior.extend) { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend); } else { ourRange = range; } } if (!behavior.addNew) { ourIndex = 0; setSelection(doc, new Selection([ourRange], 0), sel_mouse); startSel = doc.sel; } else if (ourIndex == -1) { ourIndex = ranges.length; setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), {scroll: false, origin: "*mouse"}); } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), {scroll: false, origin: "*mouse"}); startSel = doc.sel; } else { replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); } var lastPos = start; function extendTo(pos) { if (cmp(lastPos, pos) == 0) { return } lastPos = pos; if (behavior.unit == "rectangle") { var ranges = [], tabSize = cm.options.tabSize; var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); line <= end; line++) { var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); if (left == right) { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); } else if (text.length > leftPos) { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } } if (!ranges.length) { ranges.push(new Range(start, start)); } setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), {origin: "*mouse", scroll: false}); cm.scrollIntoView(pos); } else { var oldRange = ourRange; var range = rangeForUnit(cm, pos, behavior.unit); var anchor = oldRange.anchor, head; if (cmp(range.anchor, anchor) > 0) { head = range.head; anchor = minPos(oldRange.from(), range.anchor); } else { head = range.anchor; anchor = maxPos(oldRange.to(), range.head); } var ranges$1 = startSel.ranges.slice(0); ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)); setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse); } } var editorSize = display.wrapper.getBoundingClientRect(); // Used to ensure timeout re-tries don't fire when another extend // happened in the meantime (clearTimeout isn't reliable -- at // least on Chrome, the timeouts still happen even when cleared, // if the clear happens after their scheduled firing time). var counter = 0; function extend(e) { var curCount = ++counter; var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); if (!cur) { return } if (cmp(cur, lastPos) != 0) { cm.curOp.focus = activeElt(root(cm)); extendTo(cur); var visible = visibleLines(display, doc); if (cur.line >= visible.to || cur.line < visible.from) { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); } } else { var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; if (outside) { setTimeout(operation(cm, function () { if (counter != curCount) { return } display.scroller.scrollTop += outside; extend(e); }), 50); } } } function done(e) { cm.state.selectingText = false; counter = Infinity; // If e is null or undefined we interpret this as someone trying // to explicitly cancel the selection rather than the user // letting go of the mouse button. if (e) { e_preventDefault(e); display.input.focus(); } off(display.wrapper.ownerDocument, "mousemove", move); off(display.wrapper.ownerDocument, "mouseup", up); doc.history.lastSelOrigin = null; } var move = operation(cm, function (e) { if (e.buttons === 0 || !e_button(e)) { done(e); } else { extend(e); } }); var up = operation(cm, done); cm.state.selectingText = up; on(display.wrapper.ownerDocument, "mousemove", move); on(display.wrapper.ownerDocument, "mouseup", up); } // Used when mouse-selecting to adjust the anchor to the proper side // of a bidi jump depending on the visual position of the head. function bidiSimplify(cm, range) { var anchor = range.anchor; var head = range.head; var anchorLine = getLine(cm.doc, anchor.line); if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } var order = getOrder(anchorLine); if (!order) { return range } var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; if (part.from != anchor.ch && part.to != anchor.ch) { return range } var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); if (boundary == 0 || boundary == order.length) { return range } // Compute the relative visual position of the head compared to the // anchor (<0 is to the left, >0 to the right) var leftSide; if (head.line != anchor.line) { leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; } else { var headIndex = getBidiPartAt(order, head.ch, head.sticky); var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); if (headIndex == boundary - 1 || headIndex == boundary) { leftSide = dir < 0; } else { leftSide = dir > 0; } } var usePart = order[boundary + (leftSide ? -1 : 0)]; var from = leftSide == (usePart.level == 1); var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) } // Determines whether an event happened in the gutter, and fires the // handlers for the corresponding event. function gutterEvent(cm, e, type, prevent) { var mX, mY; if (e.touches) { mX = e.touches[0].clientX; mY = e.touches[0].clientY; } else { try { mX = e.clientX; mY = e.clientY; } catch(e$1) { return false } } if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } if (prevent) { e_preventDefault(e); } var display = cm.display; var lineBox = display.lineDiv.getBoundingClientRect(); if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } mY -= lineBox.top - display.viewOffset; for (var i = 0; i < cm.display.gutterSpecs.length; ++i) { var g = display.gutters.childNodes[i]; if (g && g.getBoundingClientRect().right >= mX) { var line = lineAtHeight(cm.doc, mY); var gutter = cm.display.gutterSpecs[i]; signal(cm, type, cm, line, gutter.className, e); return e_defaultPrevented(e) } } } function clickInGutter(cm, e) { return gutterEvent(cm, e, "gutterClick", true) } // CONTEXT MENU HANDLING // To make the context menu work, we need to briefly unhide the // textarea (making it as unobtrusive as possible) to let the // right-click take effect on it. function onContextMenu(cm, e) { if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } if (signalDOMEvent(cm, e, "contextmenu")) { return } if (!captureRightClick) { cm.display.input.onContextMenu(e); } } function contextMenuInGutter(cm, e) { if (!hasHandler(cm, "gutterContextMenu")) { return false } return gutterEvent(cm, e, "gutterContextMenu", false) } function themeChanged(cm) { cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); clearCaches(cm); } var Init = {toString: function(){return "CodeMirror.Init"}}; var defaults = {}; var optionHandlers = {}; function defineOptions(CodeMirror) { var optionHandlers = CodeMirror.optionHandlers; function option(name, deflt, handle, notOnInit) { CodeMirror.defaults[name] = deflt; if (handle) { optionHandlers[name] = notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; } } CodeMirror.defineOption = option; // Passed to option handlers when there is no old value. CodeMirror.Init = Init; // These two are, on init, called from the constructor because they // have to be initialized before the editor can start at all. option("value", "", function (cm, val) { return cm.setValue(val); }, true); option("mode", null, function (cm, val) { cm.doc.modeOption = val; loadMode(cm); }, true); option("indentUnit", 2, loadMode, true); option("indentWithTabs", false); option("smartIndent", true); option("tabSize", 4, function (cm) { resetModeState(cm); clearCaches(cm); regChange(cm); }, true); option("lineSeparator", null, function (cm, val) { cm.doc.lineSep = val; if (!val) { return } var newBreaks = [], lineNo = cm.doc.first; cm.doc.iter(function (line) { for (var pos = 0;;) { var found = line.text.indexOf(val, pos); if (found == -1) { break } pos = found + val.length; newBreaks.push(Pos(lineNo, found)); } lineNo++; }); for (var i = newBreaks.length - 1; i >= 0; i--) { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); } }); option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g, function (cm, val, old) { cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); if (old != Init) { cm.refresh(); } }); option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true); option("electricChars", true); option("inputStyle", mobile ? "contenteditable" : "textarea", function () { throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME }, true); option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true); option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true); option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true); option("rtlMoveVisually", !windows); option("wholeLineUpdateBefore", true); option("theme", "default", function (cm) { themeChanged(cm); updateGutters(cm); }, true); option("keyMap", "default", function (cm, val, old) { var next = getKeyMap(val); var prev = old != Init && getKeyMap(old); if (prev && prev.detach) { prev.detach(cm, next); } if (next.attach) { next.attach(cm, prev || null); } }); option("extraKeys", null); option("configureMouse", null); option("lineWrapping", false, wrappingChanged, true); option("gutters", [], function (cm, val) { cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers); updateGutters(cm); }, true); option("fixedGutter", true, function (cm, val) { cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; cm.refresh(); }, true); option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true); option("scrollbarStyle", "native", function (cm) { initScrollbars(cm); updateScrollbars(cm); cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); }, true); option("lineNumbers", false, function (cm, val) { cm.display.gutterSpecs = getGutters(cm.options.gutters, val); updateGutters(cm); }, true); option("firstLineNumber", 1, updateGutters, true); option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true); option("showCursorWhenSelecting", false, updateSelection, true); option("resetSelectionOnContextMenu", true); option("lineWiseCopyCut", true); option("pasteLinesPerSelection", true); option("selectionsMayTouch", false); option("readOnly", false, function (cm, val) { if (val == "nocursor") { onBlur(cm); cm.display.input.blur(); } cm.display.input.readOnlyChanged(val); }); option("screenReaderLabel", null, function (cm, val) { val = (val === '') ? null : val; cm.display.input.screenReaderLabelChanged(val); }); option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true); option("dragDrop", true, dragDropChanged); option("allowDropFileTypes", null); option("cursorBlinkRate", 530); option("cursorScrollMargin", 0); option("cursorHeight", 1, updateSelection, true); option("singleCursorHeightPerLine", true, updateSelection, true); option("workTime", 100); option("workDelay", 100); option("flattenSpans", true, resetModeState, true); option("addModeClass", false, resetModeState, true); option("pollInterval", 100); option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }); option("historyEventDelay", 1250); option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true); option("maxHighlightLength", 10000, resetModeState, true); option("moveInputWithCursor", true, function (cm, val) { if (!val) { cm.display.input.resetPosition(); } }); option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }); option("autofocus", null); option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true); option("phrases", null); } function dragDropChanged(cm, value, old) { var wasOn = old && old != Init; if (!value != !wasOn) { var funcs = cm.display.dragFunctions; var toggle = value ? on : off; toggle(cm.display.scroller, "dragstart", funcs.start); toggle(cm.display.scroller, "dragenter", funcs.enter); toggle(cm.display.scroller, "dragover", funcs.over); toggle(cm.display.scroller, "dragleave", funcs.leave); toggle(cm.display.scroller, "drop", funcs.drop); } } function wrappingChanged(cm) { if (cm.options.lineWrapping) { addClass(cm.display.wrapper, "CodeMirror-wrap"); cm.display.sizer.style.minWidth = ""; cm.display.sizerWidth = null; } else { rmClass(cm.display.wrapper, "CodeMirror-wrap"); findMaxLine(cm); } estimateLineHeights(cm); regChange(cm); clearCaches(cm); setTimeout(function () { return updateScrollbars(cm); }, 100); } // A CodeMirror instance represents an editor. This is the object // that user code is usually dealing with. function CodeMirror(place, options) { var this$1 = this; if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } this.options = options = options ? copyObj(options) : {}; // Determine effective options based on given values and defaults. copyObj(defaults, options, false); var doc = options.value; if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } else if (options.mode) { doc.modeOption = options.mode; } this.doc = doc; var input = new CodeMirror.inputStyles[options.inputStyle](this); var display = this.display = new Display(place, doc, input, options); display.wrapper.CodeMirror = this; themeChanged(this); if (options.lineWrapping) { this.display.wrapper.className += " CodeMirror-wrap"; } initScrollbars(this); this.state = { keyMaps: [], // stores maps added by addKeyMap overlays: [], // highlighting overlays, as added by addOverlay modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info overwrite: false, delayingBlurEvent: false, focused: false, suppressEdits: false, // used to disable editing during key handlers when in readOnly mode pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll selectingText: false, draggingText: false, highlight: new Delayed(), // stores highlight worker timeout keySeq: null, // Unfinished key sequence specialChars: null }; if (options.autofocus && !mobile) { display.input.focus(); } // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); } registerEventHandlers(this); ensureGlobalHandlers(); startOperation(this); this.curOp.forceUpdate = true; attachDoc(this, doc); if ((options.autofocus && !mobile) || this.hasFocus()) { setTimeout(function () { if (this$1.hasFocus() && !this$1.state.focused) { onFocus(this$1); } }, 20); } else { onBlur(this); } for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) { optionHandlers[opt](this, options[opt], Init); } } maybeUpdateLineNumberWidth(this); if (options.finishInit) { options.finishInit(this); } for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); } endOperation(this); // Suppress optimizelegibility in Webkit, since it breaks text // measuring on line wrapping boundaries. if (webkit && options.lineWrapping && getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") { display.lineDiv.style.textRendering = "auto"; } } // The default configuration options. CodeMirror.defaults = defaults; // Functions to run when options are changed. CodeMirror.optionHandlers = optionHandlers; // Attach the necessary event handlers when initializing the editor function registerEventHandlers(cm) { var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); // Older IE's will not fire a second mousedown for a double click if (ie && ie_version < 11) { on(d.scroller, "dblclick", operation(cm, function (e) { if (signalDOMEvent(cm, e)) { return } var pos = posFromMouse(cm, e); if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } e_preventDefault(e); var word = cm.findWordAt(pos); extendSelection(cm.doc, word.anchor, word.head); })); } else { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); } // Some browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is // handled in onMouseDown for these browsers. on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); on(d.input.getField(), "contextmenu", function (e) { if (!d.scroller.contains(e.target)) { onContextMenu(cm, e); } }); // Used to suppress mouse event handling when a touch happens var touchFinished, prevTouch = {end: 0}; function finishTouch() { if (d.activeTouch) { touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000); prevTouch = d.activeTouch; prevTouch.end = +new Date; } } function isMouseLikeTouchEvent(e) { if (e.touches.length != 1) { return false } var touch = e.touches[0]; return touch.radiusX <= 1 && touch.radiusY <= 1 } function farAway(touch, other) { if (other.left == null) { return true } var dx = other.left - touch.left, dy = other.top - touch.top; return dx * dx + dy * dy > 20 * 20 } on(d.scroller, "touchstart", function (e) { if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { d.input.ensurePolled(); clearTimeout(touchFinished); var now = +new Date; d.activeTouch = {start: now, moved: false, prev: now - prevTouch.end <= 300 ? prevTouch : null}; if (e.touches.length == 1) { d.activeTouch.left = e.touches[0].pageX; d.activeTouch.top = e.touches[0].pageY; } } }); on(d.scroller, "touchmove", function () { if (d.activeTouch) { d.activeTouch.moved = true; } }); on(d.scroller, "touchend", function (e) { var touch = d.activeTouch; if (touch && !eventInWidget(d, e) && touch.left != null && !touch.moved && new Date - touch.start < 300) { var pos = cm.coordsChar(d.activeTouch, "page"), range; if (!touch.prev || farAway(touch, touch.prev)) // Single tap { range = new Range(pos, pos); } else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap { range = cm.findWordAt(pos); } else // Triple tap { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); } cm.setSelection(range.anchor, range.head); cm.focus(); e_preventDefault(e); } finishTouch(); }); on(d.scroller, "touchcancel", finishTouch); // Sync scrolling between fake scrollbars and real scrollable // area, ensure viewport is updated when scrolling. on(d.scroller, "scroll", function () { if (d.scroller.clientHeight) { updateScrollTop(cm, d.scroller.scrollTop); setScrollLeft(cm, d.scroller.scrollLeft, true); signal(cm, "scroll", cm); } }); // Listen to wheel events in order to try and update the viewport on time. on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }); on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }); // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); d.dragFunctions = { enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }}, over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, start: function (e) { return onDragStart(cm, e); }, drop: operation(cm, onDrop), leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} }; var inp = d.input.getField(); on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }); on(inp, "keydown", operation(cm, onKeyDown)); on(inp, "keypress", operation(cm, onKeyPress)); on(inp, "focus", function (e) { return onFocus(cm, e); }); on(inp, "blur", function (e) { return onBlur(cm, e); }); } var initHooks = []; CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }; // Indent the given line. The how parameter can be "smart", // "add"/null, "subtract", or "prev". When aggressive is false // (typically set to true for forced single-line indents), empty // lines are not indented, and places where the mode returns Pass // are left alone. function indentLine(cm, n, how, aggressive) { var doc = cm.doc, state; if (how == null) { how = "add"; } if (how == "smart") { // Fall back to "prev" when the mode doesn't have an indentation // method. if (!doc.mode.indent) { how = "prev"; } else { state = getContextBefore(cm, n).state; } } var tabSize = cm.options.tabSize; var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); if (line.stateAfter) { line.stateAfter = null; } var curSpaceString = line.text.match(/^\s*/)[0], indentation; if (!aggressive && !/\S/.test(line.text)) { indentation = 0; how = "not"; } else if (how == "smart") { indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); if (indentation == Pass || indentation > 150) { if (!aggressive) { return } how = "prev"; } } if (how == "prev") { if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); } else { indentation = 0; } } else if (how == "add") { indentation = curSpace + cm.options.indentUnit; } else if (how == "subtract") { indentation = curSpace - cm.options.indentUnit; } else if (typeof how == "number") { indentation = curSpace + how; } indentation = Math.max(0, indentation); var indentString = "", pos = 0; if (cm.options.indentWithTabs) { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} } if (pos < indentation) { indentString += spaceStr(indentation - pos); } if (indentString != curSpaceString) { replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); line.stateAfter = null; return true } else { // Ensure that, if the cursor was in the whitespace at the start // of the line, it is moved to the end of that space. for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { var range = doc.sel.ranges[i$1]; if (range.head.line == n && range.head.ch < curSpaceString.length) { var pos$1 = Pos(n, curSpaceString.length); replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); break } } } } // This will be set to a {lineWise: bool, text: [string]} object, so // that, when pasting, we know what kind of selections the copied // text was made out of. var lastCopied = null; function setLastCopied(newLastCopied) { lastCopied = newLastCopied; } function applyTextInput(cm, inserted, deleted, sel, origin) { var doc = cm.doc; cm.display.shift = false; if (!sel) { sel = doc.sel; } var recent = +new Date - 200; var paste = origin == "paste" || cm.state.pasteIncoming > recent; var textLines = splitLinesAuto(inserted), multiPaste = null; // When pasting N lines into N selections, insert one line per selection if (paste && sel.ranges.length > 1) { if (lastCopied && lastCopied.text.join("\n") == inserted) { if (sel.ranges.length % lastCopied.text.length == 0) { multiPaste = []; for (var i = 0; i < lastCopied.text.length; i++) { multiPaste.push(doc.splitLines(lastCopied.text[i])); } } } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { multiPaste = map(textLines, function (l) { return [l]; }); } } var updateInput = cm.curOp.updateInput; // Normal behavior is to insert the new text into every selection for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { var range = sel.ranges[i$1]; var from = range.from(), to = range.to(); if (range.empty()) { if (deleted && deleted > 0) // Handle deletion { from = Pos(from.line, from.ch - deleted); } else if (cm.state.overwrite && !paste) // Handle overwrite { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); } else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == textLines.join("\n")) { from = to = Pos(from.line, 0); } } var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")}; makeChange(cm.doc, changeEvent); signalLater(cm, "inputRead", cm, changeEvent); } if (inserted && !paste) { triggerElectric(cm, inserted); } ensureCursorVisible(cm); if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; } cm.curOp.typing = true; cm.state.pasteIncoming = cm.state.cutIncoming = -1; } function handlePaste(e, cm) { var pasted = e.clipboardData && e.clipboardData.getData("Text"); if (pasted) { e.preventDefault(); if (!cm.isReadOnly() && !cm.options.disableInput && cm.hasFocus()) { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); } return true } } function triggerElectric(cm, inserted) { // When an 'electric' character is inserted, immediately trigger a reindent if (!cm.options.electricChars || !cm.options.smartIndent) { return } var sel = cm.doc.sel; for (var i = sel.ranges.length - 1; i >= 0; i--) { var range = sel.ranges[i]; if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } var mode = cm.getModeAt(range.head); var indented = false; if (mode.electricChars) { for (var j = 0; j < mode.electricChars.length; j++) { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { indented = indentLine(cm, range.head.line, "smart"); break } } } else if (mode.electricInput) { if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) { indented = indentLine(cm, range.head.line, "smart"); } } if (indented) { signalLater(cm, "electricInput", cm, range.head.line); } } } function copyableRanges(cm) { var text = [], ranges = []; for (var i = 0; i < cm.doc.sel.ranges.length; i++) { var line = cm.doc.sel.ranges[i].head.line; var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; ranges.push(lineRange); text.push(cm.getRange(lineRange.anchor, lineRange.head)); } return {text: text, ranges: ranges} } function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { field.setAttribute("autocorrect", autocorrect ? "on" : "off"); field.setAttribute("autocapitalize", autocapitalize ? "on" : "off"); field.setAttribute("spellcheck", !!spellcheck); } function hiddenTextarea() { var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-height: 1em; outline: none"); var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); // The textarea is kept positioned near the cursor to prevent the // fact that it'll be scrolled into view on input from scrolling // our fake cursor out of view. On webkit, when wrap=off, paste is // very slow. So make the area wide instead. if (webkit) { te.style.width = "1000px"; } else { te.setAttribute("wrap", "off"); } // If border: 0; -- iOS fails to open keyboard (issue #1287) if (ios) { te.style.border = "1px solid black"; } return div } // The publicly visible API. Note that methodOp(f) means // 'wrap f in an operation, performed on its `this` parameter'. // This is not the complete set of editor methods. Most of the // methods defined on the Doc type are also injected into // CodeMirror.prototype, for backwards compatibility and // convenience. function addEditorMethods(CodeMirror) { var optionHandlers = CodeMirror.optionHandlers; var helpers = CodeMirror.helpers = {}; CodeMirror.prototype = { constructor: CodeMirror, focus: function(){win(this).focus(); this.display.input.focus();}, setOption: function(option, value) { var options = this.options, old = options[option]; if (options[option] == value && option != "mode") { return } options[option] = value; if (optionHandlers.hasOwnProperty(option)) { operation(this, optionHandlers[option])(this, value, old); } signal(this, "optionChange", this, option); }, getOption: function(option) {return this.options[option]}, getDoc: function() {return this.doc}, addKeyMap: function(map, bottom) { this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); }, removeKeyMap: function(map) { var maps = this.state.keyMaps; for (var i = 0; i < maps.length; ++i) { if (maps[i] == map || maps[i].name == map) { maps.splice(i, 1); return true } } }, addOverlay: methodOp(function(spec, options) { var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); if (mode.startState) { throw new Error("Overlays may not be stateful.") } insertSorted(this.state.overlays, {mode: mode, modeSpec: spec, opaque: options && options.opaque, priority: (options && options.priority) || 0}, function (overlay) { return overlay.priority; }); this.state.modeGen++; regChange(this); }), removeOverlay: methodOp(function(spec) { var overlays = this.state.overlays; for (var i = 0; i < overlays.length; ++i) { var cur = overlays[i].modeSpec; if (cur == spec || typeof spec == "string" && cur.name == spec) { overlays.splice(i, 1); this.state.modeGen++; regChange(this); return } } }), indentLine: methodOp(function(n, dir, aggressive) { if (typeof dir != "string" && typeof dir != "number") { if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; } else { dir = dir ? "add" : "subtract"; } } if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); } }), indentSelection: methodOp(function(how) { var ranges = this.doc.sel.ranges, end = -1; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (!range.empty()) { var from = range.from(), to = range.to(); var start = Math.max(end, from.line); end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; for (var j = start; j < end; ++j) { indentLine(this, j, how); } var newRanges = this.doc.sel.ranges; if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } } else if (range.head.line > end) { indentLine(this, range.head.line, how, true); end = range.head.line; if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); } } } }), // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). getTokenAt: function(pos, precise) { return takeToken(this, pos, precise) }, getLineTokens: function(line, precise) { return takeToken(this, Pos(line), precise, true) }, getTokenTypeAt: function(pos) { pos = clipPos(this.doc, pos); var styles = getLineStyles(this, getLine(this.doc, pos.line)); var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; var type; if (ch == 0) { type = styles[2]; } else { for (;;) { var mid = (before + after) >> 1; if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; } else if (styles[mid * 2 + 1] < ch) { before = mid + 1; } else { type = styles[mid * 2 + 2]; break } } } var cut = type ? type.indexOf("overlay ") : -1; return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) }, getModeAt: function(pos) { var mode = this.doc.mode; if (!mode.innerMode) { return mode } return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode }, getHelper: function(pos, type) { return this.getHelpers(pos, type)[0] }, getHelpers: function(pos, type) { var found = []; if (!helpers.hasOwnProperty(type)) { return found } var help = helpers[type], mode = this.getModeAt(pos); if (typeof mode[type] == "string") { if (help[mode[type]]) { found.push(help[mode[type]]); } } else if (mode[type]) { for (var i = 0; i < mode[type].length; i++) { var val = help[mode[type][i]]; if (val) { found.push(val); } } } else if (mode.helperType && help[mode.helperType]) { found.push(help[mode.helperType]); } else if (help[mode.name]) { found.push(help[mode.name]); } for (var i$1 = 0; i$1 < help._global.length; i$1++) { var cur = help._global[i$1]; if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) { found.push(cur.val); } } return found }, getStateAfter: function(line, precise) { var doc = this.doc; line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); return getContextBefore(this, line + 1, precise).state }, cursorCoords: function(start, mode) { var pos, range = this.doc.sel.primary(); if (start == null) { pos = range.head; } else if (typeof start == "object") { pos = clipPos(this.doc, start); } else { pos = start ? range.from() : range.to(); } return cursorCoords(this, pos, mode || "page") }, charCoords: function(pos, mode) { return charCoords(this, clipPos(this.doc, pos), mode || "page") }, coordsChar: function(coords, mode) { coords = fromCoordSystem(this, coords, mode || "page"); return coordsChar(this, coords.left, coords.top) }, lineAtHeight: function(height, mode) { height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; return lineAtHeight(this.doc, height + this.display.viewOffset) }, heightAtLine: function(line, mode, includeWidgets) { var end = false, lineObj; if (typeof line == "number") { var last = this.doc.first + this.doc.size - 1; if (line < this.doc.first) { line = this.doc.first; } else if (line > last) { line = last; end = true; } lineObj = getLine(this.doc, line); } else { lineObj = line; } return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + (end ? this.doc.height - heightAtLine(lineObj) : 0) }, defaultTextHeight: function() { return textHeight(this.display) }, defaultCharWidth: function() { return charWidth(this.display) }, getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, addWidget: function(pos, node, scroll, vert, horiz) { var display = this.display; pos = cursorCoords(this, clipPos(this.doc, pos)); var top = pos.bottom, left = pos.left; node.style.position = "absolute"; node.setAttribute("cm-ignore-events", "true"); this.display.input.setUneditable(node); display.sizer.appendChild(node); if (vert == "over") { top = pos.top; } else if (vert == "above" || vert == "near") { var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); // Default to positioning above (if specified and possible); otherwise default to positioning below if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) { top = pos.top - node.offsetHeight; } else if (pos.bottom + node.offsetHeight <= vspace) { top = pos.bottom; } if (left + node.offsetWidth > hspace) { left = hspace - node.offsetWidth; } } node.style.top = top + "px"; node.style.left = node.style.right = ""; if (horiz == "right") { left = display.sizer.clientWidth - node.offsetWidth; node.style.right = "0px"; } else { if (horiz == "left") { left = 0; } else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; } node.style.left = left + "px"; } if (scroll) { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); } }, triggerOnKeyDown: methodOp(onKeyDown), triggerOnKeyPress: methodOp(onKeyPress), triggerOnKeyUp: onKeyUp, triggerOnMouseDown: methodOp(onMouseDown), execCommand: function(cmd) { if (commands.hasOwnProperty(cmd)) { return commands[cmd].call(null, this) } }, triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), findPosH: function(from, amount, unit, visually) { var dir = 1; if (amount < 0) { dir = -1; amount = -amount; } var cur = clipPos(this.doc, from); for (var i = 0; i < amount; ++i) { cur = findPosH(this.doc, cur, dir, unit, visually); if (cur.hitSide) { break } } return cur }, moveH: methodOp(function(dir, unit) { var this$1 = this; this.extendSelectionsBy(function (range) { if (this$1.display.shift || this$1.doc.extend || range.empty()) { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } else { return dir < 0 ? range.from() : range.to() } }, sel_move); }), deleteH: methodOp(function(dir, unit) { var sel = this.doc.sel, doc = this.doc; if (sel.somethingSelected()) { doc.replaceSelection("", null, "+delete"); } else { deleteNearSelection(this, function (range) { var other = findPosH(doc, range.head, dir, unit, false); return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} }); } }), findPosV: function(from, amount, unit, goalColumn) { var dir = 1, x = goalColumn; if (amount < 0) { dir = -1; amount = -amount; } var cur = clipPos(this.doc, from); for (var i = 0; i < amount; ++i) { var coords = cursorCoords(this, cur, "div"); if (x == null) { x = coords.left; } else { coords.left = x; } cur = findPosV(this, coords, dir, unit); if (cur.hitSide) { break } } return cur }, moveV: methodOp(function(dir, unit) { var this$1 = this; var doc = this.doc, goals = []; var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); doc.extendSelectionsBy(function (range) { if (collapse) { return dir < 0 ? range.from() : range.to() } var headPos = cursorCoords(this$1, range.head, "div"); if (range.goalColumn != null) { headPos.left = range.goalColumn; } goals.push(headPos.left); var pos = findPosV(this$1, headPos, dir, unit); if (unit == "page" && range == doc.sel.primary()) { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); } return pos }, sel_move); if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) { doc.sel.ranges[i].goalColumn = goals[i]; } } }), // Find the word at the given position (as returned by coordsChar). findWordAt: function(pos) { var doc = this.doc, line = getLine(doc, pos.line).text; var start = pos.ch, end = pos.ch; if (line) { var helper = this.getHelper(pos, "wordChars"); if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; } var startChar = line.charAt(start); var check = isWordChar(startChar, helper) ? function (ch) { return isWordChar(ch, helper); } : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }; while (start > 0 && check(line.charAt(start - 1))) { --start; } while (end < line.length && check(line.charAt(end))) { ++end; } } return new Range(Pos(pos.line, start), Pos(pos.line, end)) }, toggleOverwrite: function(value) { if (value != null && value == this.state.overwrite) { return } if (this.state.overwrite = !this.state.overwrite) { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); } else { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); } signal(this, "overwriteToggle", this, this.state.overwrite); }, hasFocus: function() { return this.display.input.getField() == activeElt(root(this)) }, isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }), getScrollInfo: function() { var scroller = this.display.scroller; return {left: scroller.scrollLeft, top: scroller.scrollTop, height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, clientHeight: displayHeight(this), clientWidth: displayWidth(this)} }, scrollIntoView: methodOp(function(range, margin) { if (range == null) { range = {from: this.doc.sel.primary().head, to: null}; if (margin == null) { margin = this.options.cursorScrollMargin; } } else if (typeof range == "number") { range = {from: Pos(range, 0), to: null}; } else if (range.from == null) { range = {from: range, to: null}; } if (!range.to) { range.to = range.from; } range.margin = margin || 0; if (range.from.line != null) { scrollToRange(this, range); } else { scrollToCoordsRange(this, range.from, range.to, range.margin); } }), setSize: methodOp(function(width, height) { var this$1 = this; var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }; if (width != null) { this.display.wrapper.style.width = interpret(width); } if (height != null) { this.display.wrapper.style.height = interpret(height); } if (this.options.lineWrapping) { clearLineMeasurementCache(this); } var lineNo = this.display.viewFrom; this.doc.iter(lineNo, this.display.viewTo, function (line) { if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } ++lineNo; }); this.curOp.forceUpdate = true; signal(this, "refresh", this); }), operation: function(f){return runInOp(this, f)}, startOperation: function(){return startOperation(this)}, endOperation: function(){return endOperation(this)}, refresh: methodOp(function() { var oldHeight = this.display.cachedTextHeight; regChange(this); this.curOp.forceUpdate = true; clearCaches(this); scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); updateGutterSpace(this.display); if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping) { estimateLineHeights(this); } signal(this, "refresh", this); }), swapDoc: methodOp(function(doc) { var old = this.doc; old.cm = null; // Cancel the current text selection if any (#5821) if (this.state.selectingText) { this.state.selectingText(); } attachDoc(this, doc); clearCaches(this); this.display.input.reset(); scrollToCoords(this, doc.scrollLeft, doc.scrollTop); this.curOp.forceScroll = true; signalLater(this, "swapDoc", this, old); return old }), phrase: function(phraseText) { var phrases = this.options.phrases; return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText }, getInputField: function(){return this.display.input.getField()}, getWrapperElement: function(){return this.display.wrapper}, getScrollerElement: function(){return this.display.scroller}, getGutterElement: function(){return this.display.gutters} }; eventMixin(CodeMirror); CodeMirror.registerHelper = function(type, name, value) { if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; } helpers[type][name] = value; }; CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { CodeMirror.registerHelper(type, name, value); helpers[type]._global.push({pred: predicate, val: value}); }; } // Used for horizontal relative motion. Dir is -1 or 1 (left or // right), unit can be "codepoint", "char", "column" (like char, but // doesn't cross line boundaries), "word" (across next word), or // "group" (to the start of next group of word or // non-word-non-whitespace chars). The visually param controls // whether, in right-to-left text, direction 1 means to move towards // the next index in the string, or towards the character to the right // of the current position. The resulting position will have a // hitSide=true property if it reached the end of the document. function findPosH(doc, pos, dir, unit, visually) { var oldPos = pos; var origDir = dir; var lineObj = getLine(doc, pos.line); var lineDir = visually && doc.direction == "rtl" ? -dir : dir; function findNextLine() { var l = pos.line + lineDir; if (l < doc.first || l >= doc.first + doc.size) { return false } pos = new Pos(l, pos.ch, pos.sticky); return lineObj = getLine(doc, l) } function moveOnce(boundToLine) { var next; if (unit == "codepoint") { var ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1)); if (isNaN(ch)) { next = null; } else { var astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF; next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir); } } else if (visually) { next = moveVisually(doc.cm, lineObj, pos, dir); } else { next = moveLogically(lineObj, pos, dir); } if (next == null) { if (!boundToLine && findNextLine()) { pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir); } else { return false } } else { pos = next; } return true } if (unit == "char" || unit == "codepoint") { moveOnce(); } else if (unit == "column") { moveOnce(true); } else if (unit == "word" || unit == "group") { var sawType = null, group = unit == "group"; var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); for (var first = true;; first = false) { if (dir < 0 && !moveOnce(!first)) { break } var cur = lineObj.text.charAt(pos.ch) || "\n"; var type = isWordChar(cur, helper) ? "w" : group && cur == "\n" ? "n" : !group || /\s/.test(cur) ? null : "p"; if (group && !first && !type) { type = "s"; } if (sawType && sawType != type) { if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";} break } if (type) { sawType = type; } if (dir > 0 && !moveOnce(!first)) { break } } } var result = skipAtomic(doc, pos, oldPos, origDir, true); if (equalCursorPos(oldPos, result)) { result.hitSide = true; } return result } // For relative vertical movement. Dir may be -1 or 1. Unit can be // "page" or "line". The resulting position will have a hitSide=true // property if it reached the end of the document. function findPosV(cm, pos, dir, unit) { var doc = cm.doc, x = pos.left, y; if (unit == "page") { var pageSize = Math.min(cm.display.wrapper.clientHeight, win(cm).innerHeight || doc(cm).documentElement.clientHeight); var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; } else if (unit == "line") { y = dir > 0 ? pos.bottom + 3 : pos.top - 3; } var target; for (;;) { target = coordsChar(cm, x, y); if (!target.outside) { break } if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } y += dir * 5; } return target } // CONTENTEDITABLE INPUT STYLE var ContentEditableInput = function(cm) { this.cm = cm; this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; this.polling = new Delayed(); this.composing = null; this.gracePeriod = false; this.readDOMTimeout = null; }; ContentEditableInput.prototype.init = function (display) { var this$1 = this; var input = this, cm = input.cm; var div = input.div = display.lineDiv; div.contentEditable = true; disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize); function belongsToInput(e) { for (var t = e.target; t; t = t.parentNode) { if (t == div) { return true } if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) { break } } return false } on(div, "paste", function (e) { if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } // IE doesn't fire input events, so we schedule a read for the pasted content in this way if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); } }); on(div, "compositionstart", function (e) { this$1.composing = {data: e.data, done: false}; }); on(div, "compositionupdate", function (e) { if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; } }); on(div, "compositionend", function (e) { if (this$1.composing) { if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); } this$1.composing.done = true; } }); on(div, "touchstart", function () { return input.forceCompositionEnd(); }); on(div, "input", function () { if (!this$1.composing) { this$1.readFromDOMSoon(); } }); function onCopyCut(e) { if (!belongsToInput(e) || signalDOMEvent(cm, e)) { return } if (cm.somethingSelected()) { setLastCopied({lineWise: false, text: cm.getSelections()}); if (e.type == "cut") { cm.replaceSelection("", null, "cut"); } } else if (!cm.options.lineWiseCopyCut) { return } else { var ranges = copyableRanges(cm); setLastCopied({lineWise: true, text: ranges.text}); if (e.type == "cut") { cm.operation(function () { cm.setSelections(ranges.ranges, 0, sel_dontScroll); cm.replaceSelection("", null, "cut"); }); } } if (e.clipboardData) { e.clipboardData.clearData(); var content = lastCopied.text.join("\n"); // iOS exposes the clipboard API, but seems to discard content inserted into it e.clipboardData.setData("Text", content); if (e.clipboardData.getData("Text") == content) { e.preventDefault(); return } } // Old-fashioned briefly-focus-a-textarea hack var kludge = hiddenTextarea(), te = kludge.firstChild; disableBrowserMagic(te); cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); te.value = lastCopied.text.join("\n"); var hadFocus = activeElt(rootNode(div)); selectInput(te); setTimeout(function () { cm.display.lineSpace.removeChild(kludge); hadFocus.focus(); if (hadFocus == div) { input.showPrimarySelection(); } }, 50); } on(div, "copy", onCopyCut); on(div, "cut", onCopyCut); }; ContentEditableInput.prototype.screenReaderLabelChanged = function (label) { // Label for screenreaders, accessibility if(label) { this.div.setAttribute('aria-label', label); } else { this.div.removeAttribute('aria-label'); } }; ContentEditableInput.prototype.prepareSelection = function () { var result = prepareSelection(this.cm, false); result.focus = activeElt(rootNode(this.div)) == this.div; return result }; ContentEditableInput.prototype.showSelection = function (info, takeFocus) { if (!info || !this.cm.display.view.length) { return } if (info.focus || takeFocus) { this.showPrimarySelection(); } this.showMultipleSelections(info); }; ContentEditableInput.prototype.getSelection = function () { return this.cm.display.wrapper.ownerDocument.getSelection() }; ContentEditableInput.prototype.showPrimarySelection = function () { var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary(); var from = prim.from(), to = prim.to(); if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { sel.removeAllRanges(); return } var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && cmp(minPos(curAnchor, curFocus), from) == 0 && cmp(maxPos(curAnchor, curFocus), to) == 0) { return } var view = cm.display.view; var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || {node: view[0].measure.map[2], offset: 0}; var end = to.line < cm.display.viewTo && posToDOM(cm, to); if (!end) { var measure = view[view.length - 1].measure; var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; } if (!start || !end) { sel.removeAllRanges(); return } var old = sel.rangeCount && sel.getRangeAt(0), rng; try { rng = range(start.node, start.offset, end.offset, end.node); } catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible if (rng) { if (!gecko && cm.state.focused) { sel.collapse(start.node, start.offset); if (!rng.collapsed) { sel.removeAllRanges(); sel.addRange(rng); } } else { sel.removeAllRanges(); sel.addRange(rng); } if (old && sel.anchorNode == null) { sel.addRange(old); } else if (gecko) { this.startGracePeriod(); } } this.rememberSelection(); }; ContentEditableInput.prototype.startGracePeriod = function () { var this$1 = this; clearTimeout(this.gracePeriod); this.gracePeriod = setTimeout(function () { this$1.gracePeriod = false; if (this$1.selectionChanged()) { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); } }, 20); }; ContentEditableInput.prototype.showMultipleSelections = function (info) { removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); }; ContentEditableInput.prototype.rememberSelection = function () { var sel = this.getSelection(); this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; }; ContentEditableInput.prototype.selectionInEditor = function () { var sel = this.getSelection(); if (!sel.rangeCount) { return false } var node = sel.getRangeAt(0).commonAncestorContainer; return contains(this.div, node) }; ContentEditableInput.prototype.focus = function () { if (this.cm.options.readOnly != "nocursor") { if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div) { this.showSelection(this.prepareSelection(), true); } this.div.focus(); } }; ContentEditableInput.prototype.blur = function () { this.div.blur(); }; ContentEditableInput.prototype.getField = function () { return this.div }; ContentEditableInput.prototype.supportsTouch = function () { return true }; ContentEditableInput.prototype.receivedFocus = function () { var this$1 = this; var input = this; if (this.selectionInEditor()) { setTimeout(function () { return this$1.pollSelection(); }, 20); } else { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); } function poll() { if (input.cm.state.focused) { input.pollSelection(); input.polling.set(input.cm.options.pollInterval, poll); } } this.polling.set(this.cm.options.pollInterval, poll); }; ContentEditableInput.prototype.selectionChanged = function () { var sel = this.getSelection(); return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset }; ContentEditableInput.prototype.pollSelection = function () { if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } var sel = this.getSelection(), cm = this.cm; // On Android Chrome (version 56, at least), backspacing into an // uneditable block element will put the cursor in that element, // and then, because it's not editable, hide the virtual keyboard. // Because Android doesn't allow us to actually detect backspace // presses in a sane way, this code checks for when that happens // and simulates a backspace press in this case. if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) { this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}); this.blur(); this.focus(); return } if (this.composing) { return } this.rememberSelection(); var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); var head = domToPos(cm, sel.focusNode, sel.focusOffset); if (anchor && head) { runInOp(cm, function () { setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; } }); } }; ContentEditableInput.prototype.pollContent = function () { if (this.readDOMTimeout != null) { clearTimeout(this.readDOMTimeout); this.readDOMTimeout = null; } var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); var from = sel.from(), to = sel.to(); if (from.ch == 0 && from.line > cm.firstLine()) { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); } if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) { to = Pos(to.line + 1, 0); } if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } var fromIndex, fromLine, fromNode; if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { fromLine = lineNo(display.view[0].line); fromNode = display.view[0].node; } else { fromLine = lineNo(display.view[fromIndex].line); fromNode = display.view[fromIndex - 1].node.nextSibling; } var toIndex = findViewIndex(cm, to.line); var toLine, toNode; if (toIndex == display.view.length - 1) { toLine = display.viewTo - 1; toNode = display.lineDiv.lastChild; } else { toLine = lineNo(display.view[toIndex + 1].line) - 1; toNode = display.view[toIndex + 1].node.previousSibling; } if (!fromNode) { return false } var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); while (newText.length > 1 && oldText.length > 1) { if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } else { break } } var cutFront = 0, cutEnd = 0; var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) { ++cutFront; } var newBot = lst(newText), oldBot = lst(oldText); var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), oldBot.length - (oldText.length == 1 ? cutFront : 0)); while (cutEnd < maxCutEnd && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { ++cutEnd; } // Try to move start of change to start of selection if ambiguous if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { while (cutFront && cutFront > from.ch && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { cutFront--; cutEnd++; } } newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); var chFrom = Pos(fromLine, cutFront); var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { replaceRange(cm.doc, newText, chFrom, chTo, "+input"); return true } }; ContentEditableInput.prototype.ensurePolled = function () { this.forceCompositionEnd(); }; ContentEditableInput.prototype.reset = function () { this.forceCompositionEnd(); }; ContentEditableInput.prototype.forceCompositionEnd = function () { if (!this.composing) { return } clearTimeout(this.readDOMTimeout); this.composing = null; this.updateFromDOM(); this.div.blur(); this.div.focus(); }; ContentEditableInput.prototype.readFromDOMSoon = function () { var this$1 = this; if (this.readDOMTimeout != null) { return } this.readDOMTimeout = setTimeout(function () { this$1.readDOMTimeout = null; if (this$1.composing) { if (this$1.composing.done) { this$1.composing = null; } else { return } } this$1.updateFromDOM(); }, 80); }; ContentEditableInput.prototype.updateFromDOM = function () { var this$1 = this; if (this.cm.isReadOnly() || !this.pollContent()) { runInOp(this.cm, function () { return regChange(this$1.cm); }); } }; ContentEditableInput.prototype.setUneditable = function (node) { node.contentEditable = "false"; }; ContentEditableInput.prototype.onKeyPress = function (e) { if (e.charCode == 0 || this.composing) { return } e.preventDefault(); if (!this.cm.isReadOnly()) { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); } }; ContentEditableInput.prototype.readOnlyChanged = function (val) { this.div.contentEditable = String(val != "nocursor"); }; ContentEditableInput.prototype.onContextMenu = function () {}; ContentEditableInput.prototype.resetPosition = function () {}; ContentEditableInput.prototype.needsContentAttribute = true; function posToDOM(cm, pos) { var view = findViewForLine(cm, pos.line); if (!view || view.hidden) { return null } var line = getLine(cm.doc, pos.line); var info = mapFromLineView(view, line, pos.line); var order = getOrder(line, cm.doc.direction), side = "left"; if (order) { var partPos = getBidiPartAt(order, pos.ch); side = partPos % 2 ? "right" : "left"; } var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); result.offset = result.collapse == "right" ? result.end : result.start; return result } function isInGutter(node) { for (var scan = node; scan; scan = scan.parentNode) { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } return false } function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } function domTextBetween(cm, from, to, fromLine, toLine) { var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false; function recognizeMarker(id) { return function (marker) { return marker.id == id; } } function close() { if (closing) { text += lineSep; if (extraLinebreak) { text += lineSep; } closing = extraLinebreak = false; } } function addText(str) { if (str) { close(); text += str; } } function walk(node) { if (node.nodeType == 1) { var cmText = node.getAttribute("cm-text"); if (cmText) { addText(cmText); return } var markerID = node.getAttribute("cm-marker"), range; if (markerID) { var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); if (found.length && (range = found[0].find(0))) { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); } return } if (node.getAttribute("contenteditable") == "false") { return } var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName); if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } if (isBlock) { close(); } for (var i = 0; i < node.childNodes.length; i++) { walk(node.childNodes[i]); } if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; } if (isBlock) { closing = true; } } else if (node.nodeType == 3) { addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")); } } for (;;) { walk(from); if (from == to) { break } from = from.nextSibling; extraLinebreak = false; } return text } function domToPos(cm, node, offset) { var lineNode; if (node == cm.display.lineDiv) { lineNode = cm.display.lineDiv.childNodes[offset]; if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } node = null; offset = 0; } else { for (lineNode = node;; lineNode = lineNode.parentNode) { if (!lineNode || lineNode == cm.display.lineDiv) { return null } if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } } } for (var i = 0; i < cm.display.view.length; i++) { var lineView = cm.display.view[i]; if (lineView.node == lineNode) { return locateNodeInLineView(lineView, node, offset) } } } function locateNodeInLineView(lineView, node, offset) { var wrapper = lineView.text.firstChild, bad = false; if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } if (node == wrapper) { bad = true; node = wrapper.childNodes[offset]; offset = 0; if (!node) { var line = lineView.rest ? lst(lineView.rest) : lineView.line; return badPos(Pos(lineNo(line), line.text.length), bad) } } var textNode = node.nodeType == 3 ? node : null, topNode = node; if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { textNode = node.firstChild; if (offset) { offset = textNode.nodeValue.length; } } while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; } var measure = lineView.measure, maps = measure.maps; function find(textNode, topNode, offset) { for (var i = -1; i < (maps ? maps.length : 0); i++) { var map = i < 0 ? measure.map : maps[i]; for (var j = 0; j < map.length; j += 3) { var curNode = map[j + 2]; if (curNode == textNode || curNode == topNode) { var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); var ch = map[j] + offset; if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)]; } return Pos(line, ch) } } } } var found = find(textNode, topNode, offset); if (found) { return badPos(found, bad) } // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { found = find(after, after.firstChild, 0); if (found) { return badPos(Pos(found.line, found.ch - dist), bad) } else { dist += after.textContent.length; } } for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { found = find(before, before.firstChild, -1); if (found) { return badPos(Pos(found.line, found.ch + dist$1), bad) } else { dist$1 += before.textContent.length; } } } // TEXTAREA INPUT STYLE var TextareaInput = function(cm) { this.cm = cm; // See input.poll and input.reset this.prevInput = ""; // Flag that indicates whether we expect input to appear real soon // now (after some event like 'keypress' or 'input') and are // polling intensively. this.pollingFast = false; // Self-resetting timeout for the poller this.polling = new Delayed(); // Used to work around IE issue with selection being forgotten when focus moves away from textarea this.hasSelection = false; this.composing = null; this.resetting = false; }; TextareaInput.prototype.init = function (display) { var this$1 = this; var input = this, cm = this.cm; this.createField(display); var te = this.textarea; display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild); // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) if (ios) { te.style.width = "0px"; } on(te, "input", function () { if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; } input.poll(); }); on(te, "paste", function (e) { if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } cm.state.pasteIncoming = +new Date; input.fastPoll(); }); function prepareCopyCut(e) { if (signalDOMEvent(cm, e)) { return } if (cm.somethingSelected()) { setLastCopied({lineWise: false, text: cm.getSelections()}); } else if (!cm.options.lineWiseCopyCut) { return } else { var ranges = copyableRanges(cm); setLastCopied({lineWise: true, text: ranges.text}); if (e.type == "cut") { cm.setSelections(ranges.ranges, null, sel_dontScroll); } else { input.prevInput = ""; te.value = ranges.text.join("\n"); selectInput(te); } } if (e.type == "cut") { cm.state.cutIncoming = +new Date; } } on(te, "cut", prepareCopyCut); on(te, "copy", prepareCopyCut); on(display.scroller, "paste", function (e) { if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } if (!te.dispatchEvent) { cm.state.pasteIncoming = +new Date; input.focus(); return } // Pass the `paste` event to the textarea so it's handled by its event listener. var event = new Event("paste"); event.clipboardData = e.clipboardData; te.dispatchEvent(event); }); // Prevent normal selection in the editor (we handle our own) on(display.lineSpace, "selectstart", function (e) { if (!eventInWidget(display, e)) { e_preventDefault(e); } }); on(te, "compositionstart", function () { var start = cm.getCursor("from"); if (input.composing) { input.composing.range.clear(); } input.composing = { start: start, range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) }; }); on(te, "compositionend", function () { if (input.composing) { input.poll(); input.composing.range.clear(); input.composing = null; } }); }; TextareaInput.prototype.createField = function (_display) { // Wraps and hides input textarea this.wrapper = hiddenTextarea(); // The semihidden textarea that is focused when the editor is // focused, and receives input. this.textarea = this.wrapper.firstChild; var opts = this.cm.options; disableBrowserMagic(this.textarea, opts.spellcheck, opts.autocorrect, opts.autocapitalize); }; TextareaInput.prototype.screenReaderLabelChanged = function (label) { // Label for screenreaders, accessibility if(label) { this.textarea.setAttribute('aria-label', label); } else { this.textarea.removeAttribute('aria-label'); } }; TextareaInput.prototype.prepareSelection = function () { // Redraw the selection and/or cursor var cm = this.cm, display = cm.display, doc = cm.doc; var result = prepareSelection(cm); // Move the hidden textarea near the cursor to prevent scrolling artifacts if (cm.options.moveInputWithCursor) { var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, headPos.top + lineOff.top - wrapOff.top)); result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, headPos.left + lineOff.left - wrapOff.left)); } return result }; TextareaInput.prototype.showSelection = function (drawn) { var cm = this.cm, display = cm.display; removeChildrenAndAdd(display.cursorDiv, drawn.cursors); removeChildrenAndAdd(display.selectionDiv, drawn.selection); if (drawn.teTop != null) { this.wrapper.style.top = drawn.teTop + "px"; this.wrapper.style.left = drawn.teLeft + "px"; } }; // Reset the input to correspond to the selection (or to be empty, // when not typing and nothing is selected) TextareaInput.prototype.reset = function (typing) { if (this.contextMenuPending || this.composing && typing) { return } var cm = this.cm; this.resetting = true; if (cm.somethingSelected()) { this.prevInput = ""; var content = cm.getSelection(); this.textarea.value = content; if (cm.state.focused) { selectInput(this.textarea); } if (ie && ie_version >= 9) { this.hasSelection = content; } } else if (!typing) { this.prevInput = this.textarea.value = ""; if (ie && ie_version >= 9) { this.hasSelection = null; } } this.resetting = false; }; TextareaInput.prototype.getField = function () { return this.textarea }; TextareaInput.prototype.supportsTouch = function () { return false }; TextareaInput.prototype.focus = function () { if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(rootNode(this.textarea)) != this.textarea)) { try { this.textarea.focus(); } catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM } }; TextareaInput.prototype.blur = function () { this.textarea.blur(); }; TextareaInput.prototype.resetPosition = function () { this.wrapper.style.top = this.wrapper.style.left = 0; }; TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); }; // Poll for input changes, using the normal rate of polling. This // runs as long as the editor is focused. TextareaInput.prototype.slowPoll = function () { var this$1 = this; if (this.pollingFast) { return } this.polling.set(this.cm.options.pollInterval, function () { this$1.poll(); if (this$1.cm.state.focused) { this$1.slowPoll(); } }); }; // When an event has just come in that is likely to add or change // something in the input textarea, we poll faster, to ensure that // the change appears on the screen quickly. TextareaInput.prototype.fastPoll = function () { var missed = false, input = this; input.pollingFast = true; function p() { var changed = input.poll(); if (!changed && !missed) {missed = true; input.polling.set(60, p);} else {input.pollingFast = false; input.slowPoll();} } input.polling.set(20, p); }; // Read input from the textarea, and update the document to match. // When something is selected, it is present in the textarea, and // selected (unless it is huge, in which case a placeholder is // used). When nothing is selected, the cursor sits after previously // seen text (can be empty), which is stored in prevInput (we must // not reset the textarea when typing, because that breaks IME). TextareaInput.prototype.poll = function () { var this$1 = this; var cm = this.cm, input = this.textarea, prevInput = this.prevInput; // Since this is called a *lot*, try to bail out as cheaply as // possible when it is clear that nothing happened. hasSelection // will be the case when there is a lot of text in the textarea, // in which case reading its value would be expensive. if (this.contextMenuPending || this.resetting || !cm.state.focused || (hasSelection(input) && !prevInput && !this.composing) || cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) { return false } var text = input.value; // If nothing changed, bail. if (text == prevInput && !cm.somethingSelected()) { return false } // Work around nonsensical selection resetting in IE9/10, and // inexplicable appearance of private area unicode characters on // some key combos in Mac (#2689). if (ie && ie_version >= 9 && this.hasSelection === text || mac && /[\uf700-\uf7ff]/.test(text)) { cm.display.input.reset(); return false } if (cm.doc.sel == cm.display.selForContextMenu) { var first = text.charCodeAt(0); if (first == 0x200b && !prevInput) { prevInput = "\u200b"; } if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } } // Find the part of the input that is actually new var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; } runInOp(cm, function () { applyTextInput(cm, text.slice(same), prevInput.length - same, null, this$1.composing ? "*compose" : null); // Don't leave long text in the textarea, since it makes further polling slow if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; } else { this$1.prevInput = text; } if (this$1.composing) { this$1.composing.range.clear(); this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), {className: "CodeMirror-composing"}); } }); return true }; TextareaInput.prototype.ensurePolled = function () { if (this.pollingFast && this.poll()) { this.pollingFast = false; } }; TextareaInput.prototype.onKeyPress = function () { if (ie && ie_version >= 9) { this.hasSelection = null; } this.fastPoll(); }; TextareaInput.prototype.onContextMenu = function (e) { var input = this, cm = input.cm, display = cm.display, te = input.textarea; if (input.contextMenuPending) { input.contextMenuPending(); } var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; if (!pos || presto) { return } // Opera is difficult. // Reset the current text selection only if the click is done outside of the selection // and 'resetSelectionOnContextMenu' option is true. var reset = cm.options.resetSelectionOnContextMenu; if (reset && cm.doc.sel.contains(pos) == -1) { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); } var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect(); input.wrapper.style.cssText = "position: static"; te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; var oldScrollY; if (webkit) { oldScrollY = te.ownerDocument.defaultView.scrollY; } // Work around Chrome issue (#2712) display.input.focus(); if (webkit) { te.ownerDocument.defaultView.scrollTo(null, oldScrollY); } display.input.reset(); // Adds "Select all" to context menu in FF if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } input.contextMenuPending = rehide; display.selForContextMenu = cm.doc.sel; clearTimeout(display.detectingSelectAll); // Select-all will be greyed out if there's nothing to select, so // this adds a zero-width space so that we can later check whether // it got selected. function prepareSelectAllHack() { if (te.selectionStart != null) { var selected = cm.somethingSelected(); var extval = "\u200b" + (selected ? te.value : ""); te.value = "\u21da"; // Used to catch context-menu undo te.value = extval; input.prevInput = selected ? "" : "\u200b"; te.selectionStart = 1; te.selectionEnd = extval.length; // Re-set this, in case some other handler touched the // selection in the meantime. display.selForContextMenu = cm.doc.sel; } } function rehide() { if (input.contextMenuPending != rehide) { return } input.contextMenuPending = false; input.wrapper.style.cssText = oldWrapperCSS; te.style.cssText = oldCSS; if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); } // Try to detect the user choosing select-all if (te.selectionStart != null) { if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); } var i = 0, poll = function () { if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && te.selectionEnd > 0 && input.prevInput == "\u200b") { operation(cm, selectAll)(cm); } else if (i++ < 10) { display.detectingSelectAll = setTimeout(poll, 500); } else { display.selForContextMenu = null; display.input.reset(); } }; display.detectingSelectAll = setTimeout(poll, 200); } } if (ie && ie_version >= 9) { prepareSelectAllHack(); } if (captureRightClick) { e_stop(e); var mouseup = function () { off(window, "mouseup", mouseup); setTimeout(rehide, 20); }; on(window, "mouseup", mouseup); } else { setTimeout(rehide, 50); } }; TextareaInput.prototype.readOnlyChanged = function (val) { if (!val) { this.reset(); } this.textarea.disabled = val == "nocursor"; this.textarea.readOnly = !!val; }; TextareaInput.prototype.setUneditable = function () {}; TextareaInput.prototype.needsContentAttribute = false; function fromTextArea(textarea, options) { options = options ? copyObj(options) : {}; options.value = textarea.value; if (!options.tabindex && textarea.tabIndex) { options.tabindex = textarea.tabIndex; } if (!options.placeholder && textarea.placeholder) { options.placeholder = textarea.placeholder; } // Set autofocus to true if this textarea is focused, or if it has // autofocus and no other element is focused. if (options.autofocus == null) { var hasFocus = activeElt(rootNode(textarea)); options.autofocus = hasFocus == textarea || textarea.getAttribute("autofocus") != null && hasFocus == document.body; } function save() {textarea.value = cm.getValue();} var realSubmit; if (textarea.form) { on(textarea.form, "submit", save); // Deplorable hack to make the submit method do the right thing. if (!options.leaveSubmitMethodAlone) { var form = textarea.form; realSubmit = form.submit; try { var wrappedSubmit = form.submit = function () { save(); form.submit = realSubmit; form.submit(); form.submit = wrappedSubmit; }; } catch(e) {} } } options.finishInit = function (cm) { cm.save = save; cm.getTextArea = function () { return textarea; }; cm.toTextArea = function () { cm.toTextArea = isNaN; // Prevent this from being ran twice save(); textarea.parentNode.removeChild(cm.getWrapperElement()); textarea.style.display = ""; if (textarea.form) { off(textarea.form, "submit", save); if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function") { textarea.form.submit = realSubmit; } } }; }; textarea.style.display = "none"; var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, options); return cm } function addLegacyProps(CodeMirror) { CodeMirror.off = off; CodeMirror.on = on; CodeMirror.wheelEventPixels = wheelEventPixels; CodeMirror.Doc = Doc; CodeMirror.splitLines = splitLinesAuto; CodeMirror.countColumn = countColumn; CodeMirror.findColumn = findColumn; CodeMirror.isWordChar = isWordCharBasic; CodeMirror.Pass = Pass; CodeMirror.signal = signal; CodeMirror.Line = Line; CodeMirror.changeEnd = changeEnd; CodeMirror.scrollbarModel = scrollbarModel; CodeMirror.Pos = Pos; CodeMirror.cmpPos = cmp; CodeMirror.modes = modes; CodeMirror.mimeModes = mimeModes; CodeMirror.resolveMode = resolveMode; CodeMirror.getMode = getMode; CodeMirror.modeExtensions = modeExtensions; CodeMirror.extendMode = extendMode; CodeMirror.copyState = copyState; CodeMirror.startState = startState; CodeMirror.innerMode = innerMode; CodeMirror.commands = commands; CodeMirror.keyMap = keyMap; CodeMirror.keyName = keyName; CodeMirror.isModifierKey = isModifierKey; CodeMirror.lookupKey = lookupKey; CodeMirror.normalizeKeyMap = normalizeKeyMap; CodeMirror.StringStream = StringStream; CodeMirror.SharedTextMarker = SharedTextMarker; CodeMirror.TextMarker = TextMarker; CodeMirror.LineWidget = LineWidget; CodeMirror.e_preventDefault = e_preventDefault; CodeMirror.e_stopPropagation = e_stopPropagation; CodeMirror.e_stop = e_stop; CodeMirror.addClass = addClass; CodeMirror.contains = contains; CodeMirror.rmClass = rmClass; CodeMirror.keyNames = keyNames; } // EDITOR CONSTRUCTOR defineOptions(CodeMirror); addEditorMethods(CodeMirror); // Set up methods on CodeMirror's prototype to redirect to the editor's document. var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) { CodeMirror.prototype[prop] = (function(method) { return function() {return method.apply(this.doc, arguments)} })(Doc.prototype[prop]); } } eventMixin(Doc); CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; // Extra arguments are stored as the mode's dependencies, which is // used by (legacy) mechanisms like loadmode.js to automatically // load a mode. (Preferred mechanism is the require/define calls.) CodeMirror.defineMode = function(name/*, mode, …*/) { if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; } defineMode.apply(this, arguments); }; CodeMirror.defineMIME = defineMIME; // Minimal default mode. CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }); CodeMirror.defineMIME("text/plain", "null"); // EXTENSIONS CodeMirror.defineExtension = function (name, func) { CodeMirror.prototype[name] = func; }; CodeMirror.defineDocExtension = function (name, func) { Doc.prototype[name] = func; }; CodeMirror.fromTextArea = fromTextArea; addLegacyProps(CodeMirror); CodeMirror.version = "5.65.16"; return CodeMirror; }))); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/apl/apl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("apl", function() { var builtInOps = { ".": "innerProduct", "\\": "scan", "/": "reduce", "⌿": "reduce1Axis", "⍀": "scan1Axis", "¨": "each", "⍣": "power" }; var builtInFuncs = { "+": ["conjugate", "add"], "−": ["negate", "subtract"], "×": ["signOf", "multiply"], "÷": ["reciprocal", "divide"], "⌈": ["ceiling", "greaterOf"], "⌊": ["floor", "lesserOf"], "∣": ["absolute", "residue"], "⍳": ["indexGenerate", "indexOf"], "?": ["roll", "deal"], "⋆": ["exponentiate", "toThePowerOf"], "⍟": ["naturalLog", "logToTheBase"], "○": ["piTimes", "circularFuncs"], "!": ["factorial", "binomial"], "⌹": ["matrixInverse", "matrixDivide"], "<": [null, "lessThan"], "≤": [null, "lessThanOrEqual"], "=": [null, "equals"], ">": [null, "greaterThan"], "≥": [null, "greaterThanOrEqual"], "≠": [null, "notEqual"], "≡": ["depth", "match"], "≢": [null, "notMatch"], "∈": ["enlist", "membership"], "⍷": [null, "find"], "∪": ["unique", "union"], "∩": [null, "intersection"], "∼": ["not", "without"], "∨": [null, "or"], "∧": [null, "and"], "⍱": [null, "nor"], "⍲": [null, "nand"], "⍴": ["shapeOf", "reshape"], ",": ["ravel", "catenate"], "⍪": [null, "firstAxisCatenate"], "⌽": ["reverse", "rotate"], "⊖": ["axis1Reverse", "axis1Rotate"], "⍉": ["transpose", null], "↑": ["first", "take"], "↓": [null, "drop"], "⊂": ["enclose", "partitionWithAxis"], "⊃": ["diclose", "pick"], "⌷": [null, "index"], "⍋": ["gradeUp", null], "⍒": ["gradeDown", null], "⊤": ["encode", null], "⊥": ["decode", null], "⍕": ["format", "formatByExample"], "⍎": ["execute", null], "⊣": ["stop", "left"], "⊢": ["pass", "right"] }; var isOperator = /[\.\/⌿⍀¨⍣]/; var isNiladic = /⍬/; var isFunction = /[\+−×÷⌈⌊∣⍳\?⋆⍟○!⌹<≤=>≥≠≡≢∈⍷∪∩∼∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢]/; var isArrow = /←/; var isComment = /[⍝#].*$/; var stringEater = function(type) { var prev; prev = false; return function(c) { prev = c; if (c === type) { return prev === "\\"; } return true; }; }; return { startState: function() { return { prev: false, func: false, op: false, string: false, escape: false }; }, token: function(stream, state) { var ch, funcName; if (stream.eatSpace()) { return null; } ch = stream.next(); if (ch === '"' || ch === "'") { stream.eatWhile(stringEater(ch)); stream.next(); state.prev = true; return "string"; } if (/[\[{\(]/.test(ch)) { state.prev = false; return null; } if (/[\]}\)]/.test(ch)) { state.prev = true; return null; } if (isNiladic.test(ch)) { state.prev = false; return "niladic"; } if (/[¯\d]/.test(ch)) { if (state.func) { state.func = false; state.prev = false; } else { state.prev = true; } stream.eatWhile(/[\w\.]/); return "number"; } if (isOperator.test(ch)) { return "operator apl-" + builtInOps[ch]; } if (isArrow.test(ch)) { return "apl-arrow"; } if (isFunction.test(ch)) { funcName = "apl-"; if (builtInFuncs[ch] != null) { if (state.prev) { funcName += builtInFuncs[ch][1]; } else { funcName += builtInFuncs[ch][0]; } } state.func = true; state.prev = false; return "function " + funcName; } if (isComment.test(ch)) { stream.skipToEnd(); return "comment"; } if (ch === "∘" && stream.peek() === ".") { stream.next(); return "function jot-dot"; } stream.eatWhile(/[\w\$_]/); state.prev = true; return "keyword"; } }; }); CodeMirror.defineMIME("text/apl", "apl"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/apl/index.html ================================================ CodeMirror: APL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • APL

    APL mode

    Simple mode that tries to handle APL as well as it can.

    It attempts to label functions/operators based upon monadic/dyadic usage (but this is far from fully fleshed out). This means there are meaningful classnames so hover states can have popups etc.

    MIME types defined: text/apl (APL code)

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asciiarmor/asciiarmor.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function errorIfNotEmpty(stream) { var nonWS = stream.match(/^\s*\S/); stream.skipToEnd(); return nonWS ? "error" : null; } CodeMirror.defineMode("asciiarmor", function() { return { token: function(stream, state) { var m; if (state.state == "top") { if (stream.sol() && (m = stream.match(/^-----BEGIN (.*)?-----\s*$/))) { state.state = "headers"; state.type = m[1]; return "tag"; } return errorIfNotEmpty(stream); } else if (state.state == "headers") { if (stream.sol() && stream.match(/^\w+:/)) { state.state = "header"; return "atom"; } else { var result = errorIfNotEmpty(stream); if (result) state.state = "body"; return result; } } else if (state.state == "header") { stream.skipToEnd(); state.state = "headers"; return "string"; } else if (state.state == "body") { if (stream.sol() && (m = stream.match(/^-----END (.*)?-----\s*$/))) { if (m[1] != state.type) return "error"; state.state = "end"; return "tag"; } else { if (stream.eatWhile(/[A-Za-z0-9+\/=]/)) { return null; } else { stream.next(); return "error"; } } } else if (state.state == "end") { return errorIfNotEmpty(stream); } }, blankLine: function(state) { if (state.state == "headers") state.state = "body"; }, startState: function() { return {state: "top", type: null}; } }; }); CodeMirror.defineMIME("application/pgp", "asciiarmor"); CodeMirror.defineMIME("application/pgp-encrypted", "asciiarmor"); CodeMirror.defineMIME("application/pgp-keys", "asciiarmor"); CodeMirror.defineMIME("application/pgp-signature", "asciiarmor"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asciiarmor/index.html ================================================ CodeMirror: ASCII Armor (PGP) mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • ASCII Armor

    ASCII Armor (PGP) mode

    MIME types defined: application/pgp, application/pgp-encrypted, application/pgp-keys, application/pgp-signature

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asn.1/asn.1.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("asn.1", function(config, parserConfig) { var indentUnit = config.indentUnit, keywords = parserConfig.keywords || {}, cmipVerbs = parserConfig.cmipVerbs || {}, compareTypes = parserConfig.compareTypes || {}, status = parserConfig.status || {}, tags = parserConfig.tags || {}, storage = parserConfig.storage || {}, modifier = parserConfig.modifier || {}, accessTypes = parserConfig.accessTypes|| {}, multiLineStrings = parserConfig.multiLineStrings, indentStatements = parserConfig.indentStatements !== false; var isOperatorChar = /[\|\^]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\[\]\(\){}:=,;]/.test(ch)) { curPunc = ch; return "punctuation"; } if (ch == "-"){ if (stream.eat("-")) { stream.skipToEnd(); return "comment"; } } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\-]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) return "keyword"; if (cmipVerbs.propertyIsEnumerable(cur)) return "variable cmipVerbs"; if (compareTypes.propertyIsEnumerable(cur)) return "atom compareTypes"; if (status.propertyIsEnumerable(cur)) return "comment status"; if (tags.propertyIsEnumerable(cur)) return "variable-3 tags"; if (storage.propertyIsEnumerable(cur)) return "builtin storage"; if (modifier.propertyIsEnumerable(cur)) return "string-2 modifier"; if (accessTypes.propertyIsEnumerable(cur)) return "atom accessTypes"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped){ var afterNext = stream.peek(); //look if the character if the quote is like the B in '10100010'B if (afterNext){ afterNext = afterNext.toLowerCase(); if(afterNext == "b" || afterNext == "h" || afterNext == "o") stream.next(); } end = true; break; } escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { var indent = state.indented; if (state.context && state.context.type == "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } //Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement"){ popContext(state); } else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))) pushContext(state, stream.column(), "statement"); state.startOfLine = false; return style; }, electricChars: "{}", lineComment: "--", fold: "brace" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } CodeMirror.defineMIME("text/x-ttcn-asn", { name: "asn.1", keywords: words("DEFINITIONS OBJECTS IF DERIVED INFORMATION ACTION" + " REPLY ANY NAMED CHARACTERIZED BEHAVIOUR REGISTERED" + " WITH AS IDENTIFIED CONSTRAINED BY PRESENT BEGIN" + " IMPORTS FROM UNITS SYNTAX MIN-ACCESS MAX-ACCESS" + " MINACCESS MAXACCESS REVISION STATUS DESCRIPTION" + " SEQUENCE SET COMPONENTS OF CHOICE DistinguishedName" + " ENUMERATED SIZE MODULE END INDEX AUGMENTS EXTENSIBILITY" + " IMPLIED EXPORTS"), cmipVerbs: words("ACTIONS ADD GET NOTIFICATIONS REPLACE REMOVE"), compareTypes: words("OPTIONAL DEFAULT MANAGED MODULE-TYPE MODULE_IDENTITY" + " MODULE-COMPLIANCE OBJECT-TYPE OBJECT-IDENTITY" + " OBJECT-COMPLIANCE MODE CONFIRMED CONDITIONAL" + " SUBORDINATE SUPERIOR CLASS TRUE FALSE NULL" + " TEXTUAL-CONVENTION"), status: words("current deprecated mandatory obsolete"), tags: words("APPLICATION AUTOMATIC EXPLICIT IMPLICIT PRIVATE TAGS" + " UNIVERSAL"), storage: words("BOOLEAN INTEGER OBJECT IDENTIFIER BIT OCTET STRING" + " UTCTime InterfaceIndex IANAifType CMIP-Attribute" + " REAL PACKAGE PACKAGES IpAddress PhysAddress" + " NetworkAddress BITS BMPString TimeStamp TimeTicks" + " TruthValue RowStatus DisplayString GeneralString" + " GraphicString IA5String NumericString" + " PrintableString SnmpAdminString TeletexString" + " UTF8String VideotexString VisibleString StringStore" + " ISO646String T61String UniversalString Unsigned32" + " Integer32 Gauge Gauge32 Counter Counter32 Counter64"), modifier: words("ATTRIBUTE ATTRIBUTES MANDATORY-GROUP MANDATORY-GROUPS" + " GROUP GROUPS ELEMENTS EQUALITY ORDERING SUBSTRINGS" + " DEFINED"), accessTypes: words("not-accessible accessible-for-notify read-only" + " read-create read-write"), multiLineStrings: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asn.1/index.html ================================================  CodeMirror: ASN.1 mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • ASN.1

    ASN.1 example


    Language: Abstract Syntax Notation One (ASN.1)

    MIME types defined: text/x-ttcn-asn


    The development of this mode has been sponsored by Ericsson .

    Coded by Asmelash Tsegay Gebretsadkan

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asterisk/asterisk.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* * ===================================================================================== * * Filename: mode/asterisk/asterisk.js * * Description: CodeMirror mode for Asterisk dialplan * * Created: 05/17/2012 09:20:25 PM * Revision: 08/05/2019 AstLinux Project: Support block-comments * * Author: Stas Kobzar (stas@modulis.ca), * Company: Modulis.ca Inc. * * ===================================================================================== */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("asterisk", function() { var atoms = ["exten", "same", "include","ignorepat","switch"], dpcmd = ["#include","#exec"], apps = [ "addqueuemember","adsiprog","aelsub","agentlogin","agentmonitoroutgoing","agi", "alarmreceiver","amd","answer","authenticate","background","backgrounddetect", "bridge","busy","callcompletioncancel","callcompletionrequest","celgenuserevent", "changemonitor","chanisavail","channelredirect","chanspy","clearhash","confbridge", "congestion","continuewhile","controlplayback","dahdiacceptr2call","dahdibarge", "dahdiras","dahdiscan","dahdisendcallreroutingfacility","dahdisendkeypadfacility", "datetime","dbdel","dbdeltree","deadagi","dial","dictate","directory","disa", "dumpchan","eagi","echo","endwhile","exec","execif","execiftime","exitwhile","extenspy", "externalivr","festival","flash","followme","forkcdr","getcpeid","gosub","gosubif", "goto","gotoif","gotoiftime","hangup","iax2provision","ices","importvar","incomplete", "ivrdemo","jabberjoin","jabberleave","jabbersend","jabbersendgroup","jabberstatus", "jack","log","macro","macroexclusive","macroexit","macroif","mailboxexists","meetme", "meetmeadmin","meetmechanneladmin","meetmecount","milliwatt","minivmaccmess","minivmdelete", "minivmgreet","minivmmwi","minivmnotify","minivmrecord","mixmonitor","monitor","morsecode", "mp3player","mset","musiconhold","nbscat","nocdr","noop","odbc","odbc","odbcfinish", "originate","ospauth","ospfinish","osplookup","ospnext","page","park","parkandannounce", "parkedcall","pausemonitor","pausequeuemember","pickup","pickupchan","playback","playtones", "privacymanager","proceeding","progress","queue","queuelog","raiseexception","read","readexten", "readfile","receivefax","receivefax","receivefax","record","removequeuemember", "resetcdr","retrydial","return","ringing","sayalpha","saycountedadj","saycountednoun", "saycountpl","saydigits","saynumber","sayphonetic","sayunixtime","senddtmf","sendfax", "sendfax","sendfax","sendimage","sendtext","sendurl","set","setamaflags", "setcallerpres","setmusiconhold","sipaddheader","sipdtmfmode","sipremoveheader","skel", "slastation","slatrunk","sms","softhangup","speechactivategrammar","speechbackground", "speechcreate","speechdeactivategrammar","speechdestroy","speechloadgrammar","speechprocessingsound", "speechstart","speechunloadgrammar","stackpop","startmusiconhold","stopmixmonitor","stopmonitor", "stopmusiconhold","stopplaytones","system","testclient","testserver","transfer","tryexec", "trysystem","unpausemonitor","unpausequeuemember","userevent","verbose","vmauthenticate", "vmsayname","voicemail","voicemailmain","wait","waitexten","waitfornoise","waitforring", "waitforsilence","waitmusiconhold","waituntil","while","zapateller" ]; function basicToken(stream,state){ var cur = ''; var ch = stream.next(); // comment if (state.blockComment) { if (ch == "-" && stream.match("-;", true)) { state.blockComment = false; } else if (stream.skipTo("--;")) { stream.next(); stream.next(); stream.next(); state.blockComment = false; } else { stream.skipToEnd(); } return "comment"; } if(ch == ";") { if (stream.match("--", true)) { if (!stream.match("-", false)) { // Except ;--- is not a block comment state.blockComment = true; return "comment"; } } stream.skipToEnd(); return "comment"; } // context if(ch == '[') { stream.skipTo(']'); stream.eat(']'); return "header"; } // string if(ch == '"') { stream.skipTo('"'); return "string"; } if(ch == "'") { stream.skipTo("'"); return "string-2"; } // dialplan commands if(ch == '#') { stream.eatWhile(/\w/); cur = stream.current(); if(dpcmd.indexOf(cur) !== -1) { stream.skipToEnd(); return "strong"; } } // application args if(ch == '$'){ var ch1 = stream.peek(); if(ch1 == '{'){ stream.skipTo('}'); stream.eat('}'); return "variable-3"; } } // extension stream.eatWhile(/\w/); cur = stream.current(); if(atoms.indexOf(cur) !== -1) { state.extenStart = true; switch(cur) { case 'same': state.extenSame = true; break; case 'include': case 'switch': case 'ignorepat': state.extenInclude = true;break; default:break; } return "atom"; } } return { startState: function() { return { blockComment: false, extenStart: false, extenSame: false, extenInclude: false, extenExten: false, extenPriority: false, extenApplication: false }; }, token: function(stream, state) { var cur = ''; if(stream.eatSpace()) return null; // extension started if(state.extenStart){ stream.eatWhile(/[^\s]/); cur = stream.current(); if(/^=>?$/.test(cur)){ state.extenExten = true; state.extenStart = false; return "strong"; } else { state.extenStart = false; stream.skipToEnd(); return "error"; } } else if(state.extenExten) { // set exten and priority state.extenExten = false; state.extenPriority = true; stream.eatWhile(/[^,]/); if(state.extenInclude) { stream.skipToEnd(); state.extenPriority = false; state.extenInclude = false; } if(state.extenSame) { state.extenPriority = false; state.extenSame = false; state.extenApplication = true; } return "tag"; } else if(state.extenPriority) { state.extenPriority = false; state.extenApplication = true; stream.next(); // get comma if(state.extenSame) return null; stream.eatWhile(/[^,]/); return "number"; } else if(state.extenApplication) { stream.eatWhile(/,/); cur = stream.current(); if(cur === ',') return null; stream.eatWhile(/\w/); cur = stream.current().toLowerCase(); state.extenApplication = false; if(apps.indexOf(cur) !== -1){ return "def strong"; } } else{ return basicToken(stream,state); } return null; }, blockCommentStart: ";--", blockCommentEnd: "--;", lineComment: ";" }; }); CodeMirror.defineMIME("text/x-asterisk", "asterisk"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/asterisk/index.html ================================================ CodeMirror: Asterisk dialplan mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Asterisk dialplan

    Asterisk dialplan mode

    MIME types defined: text/x-asterisk.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/brainfuck/brainfuck.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Brainfuck mode created by Michael Kaminsky https://github.com/mkaminsky11 (function(mod) { if (typeof exports == "object" && typeof module == "object") mod(require("../../lib/codemirror")) else if (typeof define == "function" && define.amd) define(["../../lib/codemirror"], mod) else mod(CodeMirror) })(function(CodeMirror) { "use strict" var reserve = "><+-.,[]".split(""); /* comments can be either: placed behind lines +++ this is a comment where reserved characters cannot be used or in a loop [ this is ok to use [ ] and stuff ] or preceded by # */ CodeMirror.defineMode("brainfuck", function() { return { startState: function() { return { commentLine: false, left: 0, right: 0, commentLoop: false } }, token: function(stream, state) { if (stream.eatSpace()) return null if(stream.sol()){ state.commentLine = false; } var ch = stream.next().toString(); if(reserve.indexOf(ch) !== -1){ if(state.commentLine === true){ if(stream.eol()){ state.commentLine = false; } return "comment"; } if(ch === "]" || ch === "["){ if(ch === "["){ state.left++; } else{ state.right++; } return "bracket"; } else if(ch === "+" || ch === "-"){ return "keyword"; } else if(ch === "<" || ch === ">"){ return "atom"; } else if(ch === "." || ch === ","){ return "def"; } } else{ state.commentLine = true; if(stream.eol()){ state.commentLine = false; } return "comment"; } if(stream.eol()){ state.commentLine = false; } } }; }); CodeMirror.defineMIME("text/x-brainfuck","brainfuck") }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/brainfuck/index.html ================================================ CodeMirror: Brainfuck mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes

    Brainfuck mode

    A mode for Brainfuck

    MIME types defined: text/x-brainfuck

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clike/clike.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function Context(indented, column, type, info, align, prev) { this.indented = indented; this.column = column; this.type = type; this.info = info; this.align = align; this.prev = prev; } function pushContext(state, col, type, info) { var indent = state.indented; if (state.context && state.context.type == "statement" && type != "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, info, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } function typeBefore(stream, state, pos) { if (state.prevToken == "variable" || state.prevToken == "type") return true; if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true; if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true; } function isTopScope(context) { for (;;) { if (!context || context.type == "top") return true; if (context.type == "}" && context.prev.info != "namespace") return false; context = context.prev; } } CodeMirror.defineMode("clike", function(config, parserConfig) { var indentUnit = config.indentUnit, statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, dontAlignCalls = parserConfig.dontAlignCalls, keywords = parserConfig.keywords || {}, types = parserConfig.types || {}, builtin = parserConfig.builtin || {}, blockKeywords = parserConfig.blockKeywords || {}, defKeywords = parserConfig.defKeywords || {}, atoms = parserConfig.atoms || {}, hooks = parserConfig.hooks || {}, multiLineStrings = parserConfig.multiLineStrings, indentStatements = parserConfig.indentStatements !== false, indentSwitch = parserConfig.indentSwitch !== false, namespaceSeparator = parserConfig.namespaceSeparator, isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/, numberStart = parserConfig.numberStart || /[\d\.]/, number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/, isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/, // An optional function that takes a {string} token and returns true if it // should be treated as a builtin. isReservedIdentifier = parserConfig.isReservedIdentifier || false; var curPunc, isDefKeyword; function tokenBase(stream, state) { var ch = stream.next(); if (hooks[ch]) { var result = hooks[ch](stream, state); if (result !== false) return result; } if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (numberStart.test(ch)) { stream.backUp(1) if (stream.match(number)) return "number" stream.next() } if (isPunctuationChar.test(ch)) { curPunc = ch; return null; } if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {} return "operator"; } stream.eatWhile(isIdentifierChar); if (namespaceSeparator) while (stream.match(namespaceSeparator)) stream.eatWhile(isIdentifierChar); var cur = stream.current(); if (contains(keywords, cur)) { if (contains(blockKeywords, cur)) curPunc = "newstatement"; if (contains(defKeywords, cur)) isDefKeyword = true; return "keyword"; } if (contains(types, cur)) return "type"; if (contains(builtin, cur) || (isReservedIdentifier && isReservedIdentifier(cur))) { if (contains(blockKeywords, cur)) curPunc = "newstatement"; return "builtin"; } if (contains(atoms, cur)) return "atom"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } function maybeEOL(stream, state) { if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context)) state.typeAtEndOfLine = typeBefore(stream, state, stream.pos) } // Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false), indented: 0, startOfLine: true, prevToken: null }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) { maybeEOL(stream, state); return null; } curPunc = isDefKeyword = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; if (ctx.align == null) ctx.align = true; if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false))) while (state.context.type == "statement") popContext(state); else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || (ctx.type == "statement" && curPunc == "newstatement"))) { pushContext(state, stream.column(), "statement", stream.current()); } if (style == "variable" && ((state.prevToken == "def" || (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) && isTopScope(state.context) && stream.match(/^\s*\(/, false))))) style = "def"; if (hooks.token) { var result = hooks.token(stream, state, style); if (result !== undefined) style = result; } if (style == "def" && parserConfig.styleDefs === false) style = "variable"; state.startOfLine = false; state.prevToken = isDefKeyword ? "def" : style || curPunc; maybeEOL(stream, state); return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine && isTopScope(state.context)) return CodeMirror.Pass; var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); var closing = firstChar == ctx.type; if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; if (parserConfig.dontIndentStatements) while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info)) ctx = ctx.prev if (hooks.indent) { var hook = hooks.indent(state, ctx, textAfter, indentUnit); if (typeof hook == "number") return hook } var switchBlock = ctx.prev && ctx.prev.info == "switch"; if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev return ctx.indented } if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1); if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; return ctx.indented + (closing ? 0 : indentUnit) + (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0); }, electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/, blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", lineComment: "//", fold: "brace" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } function contains(words, word) { if (typeof words === "function") { return words(word); } else { return words.propertyIsEnumerable(word); } } var cKeywords = "auto if break case register continue return default do sizeof " + "static else struct switch extern typedef union for goto while enum const " + "volatile inline restrict asm fortran"; // Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20. var cppKeywords = "alignas alignof and and_eq audit axiom bitand bitor catch " + "class compl concept constexpr const_cast decltype delete dynamic_cast " + "explicit export final friend import module mutable namespace new noexcept " + "not not_eq operator or or_eq override private protected public " + "reinterpret_cast requires static_assert static_cast template this " + "thread_local throw try typeid typename using virtual xor xor_eq"; var objCKeywords = "bycopy byref in inout oneway out self super atomic nonatomic retain copy " + "readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " + "@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " + "@public @package @private @protected @required @optional @try @catch @finally @import " + "@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available"; var objCBuiltins = "FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION " + " NS_RETURNS_RETAINEDNS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER " + "NS_DESIGNATED_INITIALIZER NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION " + "NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT" // Do not use this. Use the cTypes function below. This is global just to avoid // excessive calls when cTypes is being called multiple times during a parse. var basicCTypes = words("int long char short double float unsigned signed " + "void bool"); // Do not use this. Use the objCTypes function below. This is global just to avoid // excessive calls when objCTypes is being called multiple times during a parse. var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL"); // Returns true if identifier is a "C" type. // C type is defined as those that are reserved by the compiler (basicTypes), // and those that end in _t (Reserved by POSIX for types) // http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html function cTypes(identifier) { return contains(basicCTypes, identifier) || /.+_t$/.test(identifier); } // Returns true if identifier is a "Objective C" type. function objCTypes(identifier) { return cTypes(identifier) || contains(basicObjCTypes, identifier); } var cBlockKeywords = "case do else for if switch while struct enum union"; var cDefKeywords = "struct enum union"; function cppHook(stream, state) { if (!state.startOfLine) return false for (var ch, next = null; ch = stream.peek();) { if (ch == "\\" && stream.match(/^.$/)) { next = cppHook break } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) { break } stream.next() } state.tokenize = next return "meta" } function pointerHook(_stream, state) { if (state.prevToken == "type") return "type"; return false; } // For C and C++ (and ObjC): identifiers starting with __ // or _ followed by a capital letter are reserved for the compiler. function cIsReservedIdentifier(token) { if (!token || token.length < 2) return false; if (token[0] != '_') return false; return (token[1] == '_') || (token[1] !== token[1].toLowerCase()); } function cpp14Literal(stream) { stream.eatWhile(/[\w\.']/); return "number"; } function cpp11StringHook(stream, state) { stream.backUp(1); // Raw strings. if (stream.match(/^(?:R|u8R|uR|UR|LR)/)) { var match = stream.match(/^"([^\s\\()]{0,16})\(/); if (!match) { return false; } state.cpp11RawStringDelim = match[1]; state.tokenize = tokenRawString; return tokenRawString(stream, state); } // Unicode strings/chars. if (stream.match(/^(?:u8|u|U|L)/)) { if (stream.match(/^["']/, /* eat */ false)) { return "string"; } return false; } // Ignore this hook. stream.next(); return false; } function cppLooksLikeConstructor(word) { var lastTwo = /(\w+)::~?(\w+)$/.exec(word); return lastTwo && lastTwo[1] == lastTwo[2]; } // C#-style strings where "" escapes a quote. function tokenAtString(stream, state) { var next; while ((next = stream.next()) != null) { if (next == '"' && !stream.eat('"')) { state.tokenize = null; break; } } return "string"; } // C++11 raw string literal is "( anything )", where // can be a string up to 16 characters long. function tokenRawString(stream, state) { // Escape characters that have special regex meanings. var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&'); var match = stream.match(new RegExp(".*?\\)" + delim + '"')); if (match) state.tokenize = null; else stream.skipToEnd(); return "string"; } function def(mimes, mode) { if (typeof mimes == "string") mimes = [mimes]; var words = []; function add(obj) { if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) words.push(prop); } add(mode.keywords); add(mode.types); add(mode.builtin); add(mode.atoms); if (words.length) { mode.helperType = mimes[0]; CodeMirror.registerHelper("hintWords", mimes[0], words); } for (var i = 0; i < mimes.length; ++i) CodeMirror.defineMIME(mimes[i], mode); } def(["text/x-csrc", "text/x-c", "text/x-chdr"], { name: "clike", keywords: words(cKeywords), types: cTypes, blockKeywords: words(cBlockKeywords), defKeywords: words(cDefKeywords), typeFirstDefinitions: true, atoms: words("NULL true false"), isReservedIdentifier: cIsReservedIdentifier, hooks: { "#": cppHook, "*": pointerHook, }, modeProps: {fold: ["brace", "include"]} }); def(["text/x-c++src", "text/x-c++hdr"], { name: "clike", keywords: words(cKeywords + " " + cppKeywords), types: cTypes, blockKeywords: words(cBlockKeywords + " class try catch"), defKeywords: words(cDefKeywords + " class namespace"), typeFirstDefinitions: true, atoms: words("true false NULL nullptr"), dontIndentStatements: /^template$/, isIdentifierChar: /[\w\$_~\xa1-\uffff]/, isReservedIdentifier: cIsReservedIdentifier, hooks: { "#": cppHook, "*": pointerHook, "u": cpp11StringHook, "U": cpp11StringHook, "L": cpp11StringHook, "R": cpp11StringHook, "0": cpp14Literal, "1": cpp14Literal, "2": cpp14Literal, "3": cpp14Literal, "4": cpp14Literal, "5": cpp14Literal, "6": cpp14Literal, "7": cpp14Literal, "8": cpp14Literal, "9": cpp14Literal, token: function(stream, state, style) { if (style == "variable" && stream.peek() == "(" && (state.prevToken == ";" || state.prevToken == null || state.prevToken == "}") && cppLooksLikeConstructor(stream.current())) return "def"; } }, namespaceSeparator: "::", modeProps: {fold: ["brace", "include"]} }); def("text/x-java", { name: "clike", keywords: words("abstract assert break case catch class const continue default " + "do else enum extends final finally for goto if implements import " + "instanceof interface native new package private protected public " + "return static strictfp super switch synchronized this throw throws transient " + "try volatile while @interface"), types: words("var byte short int long float double boolean char void Boolean Byte Character Double Float " + "Integer Long Number Object Short String StringBuffer StringBuilder Void"), blockKeywords: words("catch class do else finally for if switch try while"), defKeywords: words("class interface enum @interface"), typeFirstDefinitions: true, atoms: words("true false null"), number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, hooks: { "@": function(stream) { // Don't match the @interface keyword. if (stream.match('interface', false)) return false; stream.eatWhile(/[\w\$_]/); return "meta"; }, '"': function(stream, state) { if (!stream.match(/""$/)) return false; state.tokenize = tokenTripleString; return state.tokenize(stream, state); } }, modeProps: {fold: ["brace", "import"]} }); def("text/x-csharp", { name: "clike", keywords: words("abstract as async await base break case catch checked class const continue" + " default delegate do else enum event explicit extern finally fixed for" + " foreach goto if implicit in init interface internal is lock namespace new" + " operator out override params private protected public readonly record ref required return sealed" + " sizeof stackalloc static struct switch this throw try typeof unchecked" + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + " global group into join let orderby partial remove select set value var yield"), types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" + " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" + " UInt64 bool byte char decimal double short int long object" + " sbyte float string ushort uint ulong"), blockKeywords: words("catch class do else finally for foreach if struct switch try while"), defKeywords: words("class interface namespace record struct var"), typeFirstDefinitions: true, atoms: words("true false null"), hooks: { "@": function(stream, state) { if (stream.eat('"')) { state.tokenize = tokenAtString; return tokenAtString(stream, state); } stream.eatWhile(/[\w\$_]/); return "meta"; } } }); function tokenTripleString(stream, state) { var escaped = false; while (!stream.eol()) { if (!escaped && stream.match('"""')) { state.tokenize = null; break; } escaped = stream.next() == "\\" && !escaped; } return "string"; } function tokenNestedComment(depth) { return function (stream, state) { var ch while (ch = stream.next()) { if (ch == "*" && stream.eat("/")) { if (depth == 1) { state.tokenize = null break } else { state.tokenize = tokenNestedComment(depth - 1) return state.tokenize(stream, state) } } else if (ch == "/" && stream.eat("*")) { state.tokenize = tokenNestedComment(depth + 1) return state.tokenize(stream, state) } } return "comment" } } def("text/x-scala", { name: "clike", keywords: words( /* scala */ "abstract case catch class def do else extends final finally for forSome if " + "implicit import lazy match new null object override package private protected return " + "sealed super this throw trait try type val var while with yield _ " + /* package scala */ "assert assume require print println printf readLine readBoolean readByte readShort " + "readChar readInt readLong readFloat readDouble" ), types: words( "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " + /* package java.lang */ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" ), multiLineStrings: true, blockKeywords: words("catch class enum do else finally for forSome if match switch try while"), defKeywords: words("class enum def object package trait type val var"), atoms: words("true false null"), indentStatements: false, indentSwitch: false, isOperatorChar: /[+\-*&%=<>!?|\/#:@]/, hooks: { "@": function(stream) { stream.eatWhile(/[\w\$_]/); return "meta"; }, '"': function(stream, state) { if (!stream.match('""')) return false; state.tokenize = tokenTripleString; return state.tokenize(stream, state); }, "'": function(stream) { if (stream.match(/^(\\[^'\s]+|[^\\'])'/)) return "string-2" stream.eatWhile(/[\w\$_\xa1-\uffff]/); return "atom"; }, "=": function(stream, state) { var cx = state.context if (cx.type == "}" && cx.align && stream.eat(">")) { state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev) return "operator" } else { return false } }, "/": function(stream, state) { if (!stream.eat("*")) return false state.tokenize = tokenNestedComment(1) return state.tokenize(stream, state) } }, modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}} }); function tokenKotlinString(tripleString){ return function (stream, state) { var escaped = false, next, end = false; while (!stream.eol()) { if (!tripleString && !escaped && stream.match('"') ) {end = true; break;} if (tripleString && stream.match('"""')) {end = true; break;} next = stream.next(); if(!escaped && next == "$" && stream.match('{')) stream.skipTo("}"); escaped = !escaped && next == "\\" && !tripleString; } if (end || !tripleString) state.tokenize = null; return "string"; } } def("text/x-kotlin", { name: "clike", keywords: words( /*keywords*/ "package as typealias class interface this super val operator " + "var fun for is in This throw return annotation " + "break continue object if else while do try when !in !is as? " + /*soft keywords*/ "file import where by get set abstract enum open inner override private public internal " + "protected catch finally out final vararg reified dynamic companion constructor init " + "sealed field property receiver param sparam lateinit data inline noinline tailrec " + "external annotation crossinline const operator infix suspend actual expect setparam value" ), types: words( /* package java.lang */ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " + "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " + "LazyThreadSafetyMode LongArray Nothing ShortArray Unit" ), intendSwitch: false, indentStatements: false, multiLineStrings: true, number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, blockKeywords: words("catch class do else finally for if where try while enum"), defKeywords: words("class val var object interface fun"), atoms: words("true false null this"), hooks: { "@": function(stream) { stream.eatWhile(/[\w\$_]/); return "meta"; }, '*': function(_stream, state) { return state.prevToken == '.' ? 'variable' : 'operator'; }, '"': function(stream, state) { state.tokenize = tokenKotlinString(stream.match('""')); return state.tokenize(stream, state); }, "/": function(stream, state) { if (!stream.eat("*")) return false; state.tokenize = tokenNestedComment(1); return state.tokenize(stream, state) }, indent: function(state, ctx, textAfter, indentUnit) { var firstChar = textAfter && textAfter.charAt(0); if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "") return state.indented; if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") || state.prevToken == "variable" && firstChar == "." || (state.prevToken == "}" || state.prevToken == ")") && firstChar == ".") return indentUnit * 2 + ctx.indented; if (ctx.align && ctx.type == "}") return ctx.indented + (state.context.type == (textAfter || "").charAt(0) ? 0 : indentUnit); } }, modeProps: {closeBrackets: {triples: '"'}} }); def(["x-shader/x-vertex", "x-shader/x-fragment"], { name: "clike", keywords: words("sampler1D sampler2D sampler3D samplerCube " + "sampler1DShadow sampler2DShadow " + "const attribute uniform varying " + "break continue discard return " + "for while do if else struct " + "in out inout"), types: words("float int bool void " + "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + "mat2 mat3 mat4"), blockKeywords: words("for while do if else struct"), builtin: words("radians degrees sin cos tan asin acos atan " + "pow exp log exp2 sqrt inversesqrt " + "abs sign floor ceil fract mod min max clamp mix step smoothstep " + "length distance dot cross normalize ftransform faceforward " + "reflect refract matrixCompMult " + "lessThan lessThanEqual greaterThan greaterThanEqual " + "equal notEqual any all not " + "texture1D texture1DProj texture1DLod texture1DProjLod " + "texture2D texture2DProj texture2DLod texture2DProjLod " + "texture3D texture3DProj texture3DLod texture3DProjLod " + "textureCube textureCubeLod " + "shadow1D shadow2D shadow1DProj shadow2DProj " + "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " + "dFdx dFdy fwidth " + "noise1 noise2 noise3 noise4"), atoms: words("true false " + "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " + "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " + "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + "gl_FogCoord gl_PointCoord " + "gl_Position gl_PointSize gl_ClipVertex " + "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " + "gl_TexCoord gl_FogFragCoord " + "gl_FragCoord gl_FrontFacing " + "gl_FragData gl_FragDepth " + "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " + "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " + "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " + "gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose " + "gl_ProjectionMatrixInverseTranspose " + "gl_ModelViewProjectionMatrixInverseTranspose " + "gl_TextureMatrixInverseTranspose " + "gl_NormalScale gl_DepthRange gl_ClipPlane " + "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " + "gl_FrontLightModelProduct gl_BackLightModelProduct " + "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " + "gl_FogParameters " + "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " + "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " + "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + "gl_MaxDrawBuffers"), indentSwitch: false, hooks: {"#": cppHook}, modeProps: {fold: ["brace", "include"]} }); def("text/x-nesc", { name: "clike", keywords: words(cKeywords + " as atomic async call command component components configuration event generic " + "implementation includes interface module new norace nx_struct nx_union post provides " + "signal task uses abstract extends"), types: cTypes, blockKeywords: words(cBlockKeywords), atoms: words("null true false"), hooks: {"#": cppHook}, modeProps: {fold: ["brace", "include"]} }); def("text/x-objectivec", { name: "clike", keywords: words(cKeywords + " " + objCKeywords), types: objCTypes, builtin: words(objCBuiltins), blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"), defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"), dontIndentStatements: /^@.*$/, typeFirstDefinitions: true, atoms: words("YES NO NULL Nil nil true false nullptr"), isReservedIdentifier: cIsReservedIdentifier, hooks: { "#": cppHook, "*": pointerHook, }, modeProps: {fold: ["brace", "include"]} }); def("text/x-objectivec++", { name: "clike", keywords: words(cKeywords + " " + objCKeywords + " " + cppKeywords), types: objCTypes, builtin: words(objCBuiltins), blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"), defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class class namespace"), dontIndentStatements: /^@.*$|^template$/, typeFirstDefinitions: true, atoms: words("YES NO NULL Nil nil true false nullptr"), isReservedIdentifier: cIsReservedIdentifier, hooks: { "#": cppHook, "*": pointerHook, "u": cpp11StringHook, "U": cpp11StringHook, "L": cpp11StringHook, "R": cpp11StringHook, "0": cpp14Literal, "1": cpp14Literal, "2": cpp14Literal, "3": cpp14Literal, "4": cpp14Literal, "5": cpp14Literal, "6": cpp14Literal, "7": cpp14Literal, "8": cpp14Literal, "9": cpp14Literal, token: function(stream, state, style) { if (style == "variable" && stream.peek() == "(" && (state.prevToken == ";" || state.prevToken == null || state.prevToken == "}") && cppLooksLikeConstructor(stream.current())) return "def"; } }, namespaceSeparator: "::", modeProps: {fold: ["brace", "include"]} }); def("text/x-squirrel", { name: "clike", keywords: words("base break clone continue const default delete enum extends function in class" + " foreach local resume return this throw typeof yield constructor instanceof static"), types: cTypes, blockKeywords: words("case catch class else for foreach if switch try while"), defKeywords: words("function local class"), typeFirstDefinitions: true, atoms: words("true false null"), hooks: {"#": cppHook}, modeProps: {fold: ["brace", "include"]} }); // Ceylon Strings need to deal with interpolation var stringTokenizer = null; function tokenCeylonString(type) { return function(stream, state) { var escaped = false, next, end = false; while (!stream.eol()) { if (!escaped && stream.match('"') && (type == "single" || stream.match('""'))) { end = true; break; } if (!escaped && stream.match('``')) { stringTokenizer = tokenCeylonString(type); end = true; break; } next = stream.next(); escaped = type == "single" && !escaped && next == "\\"; } if (end) state.tokenize = null; return "string"; } } def("text/x-ceylon", { name: "clike", keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" + " exists extends finally for function given if import in interface is let module new" + " nonempty object of out outer package return satisfies super switch then this throw" + " try value void while"), types: function(word) { // In Ceylon all identifiers that start with an uppercase are types var first = word.charAt(0); return (first === first.toUpperCase() && first !== first.toLowerCase()); }, blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"), defKeywords: words("class dynamic function interface module object package value"), builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" + " native optional sealed see serializable shared suppressWarnings tagged throws variable"), isPunctuationChar: /[\[\]{}\(\),;\:\.`]/, isOperatorChar: /[+\-*&%=<>!?|^~:\/]/, numberStart: /[\d#$]/, number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i, multiLineStrings: true, typeFirstDefinitions: true, atoms: words("true false null larger smaller equal empty finished"), indentSwitch: false, styleDefs: false, hooks: { "@": function(stream) { stream.eatWhile(/[\w\$_]/); return "meta"; }, '"': function(stream, state) { state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single"); return state.tokenize(stream, state); }, '`': function(stream, state) { if (!stringTokenizer || !stream.match('`')) return false; state.tokenize = stringTokenizer; stringTokenizer = null; return state.tokenize(stream, state); }, "'": function(stream) { stream.eatWhile(/[\w\$_\xa1-\uffff]/); return "atom"; }, token: function(_stream, state, style) { if ((style == "variable" || style == "type") && state.prevToken == ".") { return "variable-2"; } } }, modeProps: { fold: ["brace", "import"], closeBrackets: {triples: '"'} } }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clike/index.html ================================================ CodeMirror: C-like mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • C-like

    C-like mode

    C++ example

    Objective-C example

    Java example

    Scala example

    Kotlin mode

    Ceylon mode

    Simple mode that tries to handle C-like languages as well as it can. Takes two configuration parameters: keywords, an object whose property names are the keywords in the language, and useCPP, which determines whether C preprocessor directives are recognized.

    MIME types defined: text/x-csrc (C), text/x-c++src (C++), text/x-java (Java), text/x-csharp (C#), text/x-objectivec (Objective-C), text/x-scala (Scala), text/x-vertex x-shader/x-fragment (shader programs), text/x-squirrel (Squirrel) and text/x-ceylon (Ceylon)

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clike/scala.html ================================================ CodeMirror: Scala mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Scala

    Scala mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clike/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-c"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("indent", "[type void] [def foo]([type void*] [variable a], [type int] [variable b]) {", " [type int] [variable c] [operator =] [variable b] [operator +]", " [number 1];", " [keyword return] [operator *][variable a];", "}"); MT("indent_switch", "[keyword switch] ([variable x]) {", " [keyword case] [number 10]:", " [keyword return] [number 20];", " [keyword default]:", " [variable printf]([string \"foo %c\"], [variable x]);", "}"); MT("def", "[type void] [def foo]() {}", "[keyword struct] [def bar]{}", "[keyword enum] [def zot]{}", "[keyword union] [def ugh]{}", "[type int] [type *][def baz]() {}"); MT("def_new_line", "::[variable std]::[variable SomeTerribleType][operator <][variable T][operator >]", "[def SomeLongMethodNameThatDoesntFitIntoOneLine]([keyword const] [variable MyType][operator &] [variable param]) {}") MT("double_block", "[keyword for] (;;)", " [keyword for] (;;)", " [variable x][operator ++];", "[keyword return];"); MT("preprocessor", "[meta #define FOO 3]", "[type int] [variable foo];", "[meta #define BAR\\]", "[meta 4]", "[type unsigned] [type int] [variable bar] [operator =] [number 8];", "[meta #include ][comment // comment]") MT("c_underscores", "[builtin __FOO];", "[builtin _Complex];", "[builtin __aName];", "[variable _aName];"); MT("c_types", "[type int];", "[type long];", "[type char];", "[type short];", "[type double];", "[type float];", "[type unsigned];", "[type signed];", "[type void];", "[type bool];", "[type foo_t];", "[variable foo_T];", "[variable _t];"); var mode_cpp = CodeMirror.getMode({indentUnit: 2}, "text/x-c++src"); function MTCPP(name) { test.mode(name, mode_cpp, Array.prototype.slice.call(arguments, 1)); } MTCPP("cpp14_literal", "[number 10'000];", "[number 0b10'000];", "[number 0x10'000];", "[string '100000'];"); MTCPP("ctor_dtor", "[def Foo::Foo]() {}", "[def Foo::~Foo]() {}"); MTCPP("cpp_underscores", "[builtin __FOO];", "[builtin _Complex];", "[builtin __aName];", "[variable _aName];"); var mode_objc = CodeMirror.getMode({indentUnit: 2}, "text/x-objectivec"); function MTOBJC(name) { test.mode(name, mode_objc, Array.prototype.slice.call(arguments, 1)); } MTOBJC("objc_underscores", "[builtin __FOO];", "[builtin _Complex];", "[builtin __aName];", "[variable _aName];"); MTOBJC("objc_interface", "[keyword @interface] [def foo] {", " [type int] [variable bar];", "}", "[keyword @property] ([keyword atomic], [keyword nullable]) [variable NSString][operator *] [variable a];", "[keyword @property] ([keyword nonatomic], [keyword assign]) [type int] [variable b];", "[operator -]([type instancetype])[variable initWithFoo]:([type int])[variable a] " + "[builtin NS_DESIGNATED_INITIALIZER];", "[keyword @end]"); MTOBJC("objc_implementation", "[keyword @implementation] [def foo] {", " [type int] [variable bar];", "}", "[keyword @property] ([keyword readwrite]) [type SEL] [variable a];", "[operator -]([type instancetype])[variable initWithFoo]:([type int])[variable a] {", " [keyword if](([keyword self] [operator =] [[[keyword super] [variable init] ]])) {}", " [keyword return] [keyword self];", "}", "[keyword @end]"); MTOBJC("objc_types", "[type int];", "[type foo_t];", "[variable foo_T];", "[type id];", "[type SEL];", "[type instancetype];", "[type Class];", "[type Protocol];", "[type BOOL];" ); var mode_scala = CodeMirror.getMode({indentUnit: 2}, "text/x-scala"); function MTSCALA(name) { test.mode("scala_" + name, mode_scala, Array.prototype.slice.call(arguments, 1)); } MTSCALA("nested_comments", "[comment /*]", "[comment But wait /* this is a nested comment */ for real]", "[comment /**** let * me * show * you ****/]", "[comment ///// let / me / show / you /////]", "[comment */]"); var mode_java = CodeMirror.getMode({indentUnit: 2}, "text/x-java"); function MTJAVA(name) { test.mode("java_" + name, mode_java, Array.prototype.slice.call(arguments, 1)); } MTJAVA("types", "[type byte];", "[type short];", "[type int];", "[type long];", "[type float];", "[type double];", "[type boolean];", "[type char];", "[type void];", "[type Boolean];", "[type Byte];", "[type Character];", "[type Double];", "[type Float];", "[type Integer];", "[type Long];", "[type Number];", "[type Object];", "[type Short];", "[type String];", "[type StringBuffer];", "[type StringBuilder];", "[type Void];"); MTJAVA("indent", "[keyword public] [keyword class] [def A] [keyword extends] [variable B]", "{", " [variable c]()") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clojure/clojure.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports === "object" && typeof module === "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define === "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("clojure", function (options) { var atoms = ["false", "nil", "true"]; var specialForms = [".", "catch", "def", "do", "if", "monitor-enter", "monitor-exit", "new", "quote", "recur", "set!", "throw", "try", "var"]; var coreSymbols = ["*", "*'", "*1", "*2", "*3", "*agent*", "*allow-unresolved-vars*", "*assert*", "*clojure-version*", "*command-line-args*", "*compile-files*", "*compile-path*", "*compiler-options*", "*data-readers*", "*default-data-reader-fn*", "*e", "*err*", "*file*", "*flush-on-newline*", "*fn-loader*", "*in*", "*math-context*", "*ns*", "*out*", "*print-dup*", "*print-length*", "*print-level*", "*print-meta*", "*print-namespace-maps*", "*print-readably*", "*read-eval*", "*reader-resolver*", "*source-path*", "*suppress-read*", "*unchecked-math*", "*use-context-classloader*", "*verbose-defrecords*", "*warn-on-reflection*", "+", "+'", "-", "-'", "->", "->>", "->ArrayChunk", "->Eduction", "->Vec", "->VecNode", "->VecSeq", "-cache-protocol-fn", "-reset-methods", "..", "/", "<", "<=", "=", "==", ">", ">=", "EMPTY-NODE", "Inst", "StackTraceElement->vec", "Throwable->map", "accessor", "aclone", "add-classpath", "add-watch", "agent", "agent-error", "agent-errors", "aget", "alength", "alias", "all-ns", "alter", "alter-meta!", "alter-var-root", "amap", "ancestors", "and", "any?", "apply", "areduce", "array-map", "as->", "aset", "aset-boolean", "aset-byte", "aset-char", "aset-double", "aset-float", "aset-int", "aset-long", "aset-short", "assert", "assoc", "assoc!", "assoc-in", "associative?", "atom", "await", "await-for", "await1", "bases", "bean", "bigdec", "bigint", "biginteger", "binding", "bit-and", "bit-and-not", "bit-clear", "bit-flip", "bit-not", "bit-or", "bit-set", "bit-shift-left", "bit-shift-right", "bit-test", "bit-xor", "boolean", "boolean-array", "boolean?", "booleans", "bound-fn", "bound-fn*", "bound?", "bounded-count", "butlast", "byte", "byte-array", "bytes", "bytes?", "case", "cast", "cat", "char", "char-array", "char-escape-string", "char-name-string", "char?", "chars", "chunk", "chunk-append", "chunk-buffer", "chunk-cons", "chunk-first", "chunk-next", "chunk-rest", "chunked-seq?", "class", "class?", "clear-agent-errors", "clojure-version", "coll?", "comment", "commute", "comp", "comparator", "compare", "compare-and-set!", "compile", "complement", "completing", "concat", "cond", "cond->", "cond->>", "condp", "conj", "conj!", "cons", "constantly", "construct-proxy", "contains?", "count", "counted?", "create-ns", "create-struct", "cycle", "dec", "dec'", "decimal?", "declare", "dedupe", "default-data-readers", "definline", "definterface", "defmacro", "defmethod", "defmulti", "defn", "defn-", "defonce", "defprotocol", "defrecord", "defstruct", "deftype", "delay", "delay?", "deliver", "denominator", "deref", "derive", "descendants", "destructure", "disj", "disj!", "dissoc", "dissoc!", "distinct", "distinct?", "doall", "dorun", "doseq", "dosync", "dotimes", "doto", "double", "double-array", "double?", "doubles", "drop", "drop-last", "drop-while", "eduction", "empty", "empty?", "ensure", "ensure-reduced", "enumeration-seq", "error-handler", "error-mode", "eval", "even?", "every-pred", "every?", "ex-data", "ex-info", "extend", "extend-protocol", "extend-type", "extenders", "extends?", "false?", "ffirst", "file-seq", "filter", "filterv", "find", "find-keyword", "find-ns", "find-protocol-impl", "find-protocol-method", "find-var", "first", "flatten", "float", "float-array", "float?", "floats", "flush", "fn", "fn?", "fnext", "fnil", "for", "force", "format", "frequencies", "future", "future-call", "future-cancel", "future-cancelled?", "future-done?", "future?", "gen-class", "gen-interface", "gensym", "get", "get-in", "get-method", "get-proxy-class", "get-thread-bindings", "get-validator", "group-by", "halt-when", "hash", "hash-combine", "hash-map", "hash-ordered-coll", "hash-set", "hash-unordered-coll", "ident?", "identical?", "identity", "if-let", "if-not", "if-some", "ifn?", "import", "in-ns", "inc", "inc'", "indexed?", "init-proxy", "inst-ms", "inst-ms*", "inst?", "instance?", "int", "int-array", "int?", "integer?", "interleave", "intern", "interpose", "into", "into-array", "ints", "io!", "isa?", "iterate", "iterator-seq", "juxt", "keep", "keep-indexed", "key", "keys", "keyword", "keyword?", "last", "lazy-cat", "lazy-seq", "let", "letfn", "line-seq", "list", "list*", "list?", "load", "load-file", "load-reader", "load-string", "loaded-libs", "locking", "long", "long-array", "longs", "loop", "macroexpand", "macroexpand-1", "make-array", "make-hierarchy", "map", "map-entry?", "map-indexed", "map?", "mapcat", "mapv", "max", "max-key", "memfn", "memoize", "merge", "merge-with", "meta", "method-sig", "methods", "min", "min-key", "mix-collection-hash", "mod", "munge", "name", "namespace", "namespace-munge", "nat-int?", "neg-int?", "neg?", "newline", "next", "nfirst", "nil?", "nnext", "not", "not-any?", "not-empty", "not-every?", "not=", "ns", "ns-aliases", "ns-imports", "ns-interns", "ns-map", "ns-name", "ns-publics", "ns-refers", "ns-resolve", "ns-unalias", "ns-unmap", "nth", "nthnext", "nthrest", "num", "number?", "numerator", "object-array", "odd?", "or", "parents", "partial", "partition", "partition-all", "partition-by", "pcalls", "peek", "persistent!", "pmap", "pop", "pop!", "pop-thread-bindings", "pos-int?", "pos?", "pr", "pr-str", "prefer-method", "prefers", "primitives-classnames", "print", "print-ctor", "print-dup", "print-method", "print-simple", "print-str", "printf", "println", "println-str", "prn", "prn-str", "promise", "proxy", "proxy-call-with-super", "proxy-mappings", "proxy-name", "proxy-super", "push-thread-bindings", "pvalues", "qualified-ident?", "qualified-keyword?", "qualified-symbol?", "quot", "rand", "rand-int", "rand-nth", "random-sample", "range", "ratio?", "rational?", "rationalize", "re-find", "re-groups", "re-matcher", "re-matches", "re-pattern", "re-seq", "read", "read-line", "read-string", "reader-conditional", "reader-conditional?", "realized?", "record?", "reduce", "reduce-kv", "reduced", "reduced?", "reductions", "ref", "ref-history-count", "ref-max-history", "ref-min-history", "ref-set", "refer", "refer-clojure", "reify", "release-pending-sends", "rem", "remove", "remove-all-methods", "remove-method", "remove-ns", "remove-watch", "repeat", "repeatedly", "replace", "replicate", "require", "reset!", "reset-meta!", "reset-vals!", "resolve", "rest", "restart-agent", "resultset-seq", "reverse", "reversible?", "rseq", "rsubseq", "run!", "satisfies?", "second", "select-keys", "send", "send-off", "send-via", "seq", "seq?", "seqable?", "seque", "sequence", "sequential?", "set", "set-agent-send-executor!", "set-agent-send-off-executor!", "set-error-handler!", "set-error-mode!", "set-validator!", "set?", "short", "short-array", "shorts", "shuffle", "shutdown-agents", "simple-ident?", "simple-keyword?", "simple-symbol?", "slurp", "some", "some->", "some->>", "some-fn", "some?", "sort", "sort-by", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "sorted?", "special-symbol?", "spit", "split-at", "split-with", "str", "string?", "struct", "struct-map", "subs", "subseq", "subvec", "supers", "swap!", "swap-vals!", "symbol", "symbol?", "sync", "tagged-literal", "tagged-literal?", "take", "take-last", "take-nth", "take-while", "test", "the-ns", "thread-bound?", "time", "to-array", "to-array-2d", "trampoline", "transduce", "transient", "tree-seq", "true?", "type", "unchecked-add", "unchecked-add-int", "unchecked-byte", "unchecked-char", "unchecked-dec", "unchecked-dec-int", "unchecked-divide-int", "unchecked-double", "unchecked-float", "unchecked-inc", "unchecked-inc-int", "unchecked-int", "unchecked-long", "unchecked-multiply", "unchecked-multiply-int", "unchecked-negate", "unchecked-negate-int", "unchecked-remainder-int", "unchecked-short", "unchecked-subtract", "unchecked-subtract-int", "underive", "unquote", "unquote-splicing", "unreduced", "unsigned-bit-shift-right", "update", "update-in", "update-proxy", "uri?", "use", "uuid?", "val", "vals", "var-get", "var-set", "var?", "vary-meta", "vec", "vector", "vector-of", "vector?", "volatile!", "volatile?", "vreset!", "vswap!", "when", "when-first", "when-let", "when-not", "when-some", "while", "with-bindings", "with-bindings*", "with-in-str", "with-loading-context", "with-local-vars", "with-meta", "with-open", "with-out-str", "with-precision", "with-redefs", "with-redefs-fn", "xml-seq", "zero?", "zipmap"]; var haveBodyParameter = [ "->", "->>", "as->", "binding", "bound-fn", "case", "catch", "comment", "cond", "cond->", "cond->>", "condp", "def", "definterface", "defmethod", "defn", "defmacro", "defprotocol", "defrecord", "defstruct", "deftype", "do", "doseq", "dotimes", "doto", "extend", "extend-protocol", "extend-type", "fn", "for", "future", "if", "if-let", "if-not", "if-some", "let", "letfn", "locking", "loop", "ns", "proxy", "reify", "struct-map", "some->", "some->>", "try", "when", "when-first", "when-let", "when-not", "when-some", "while", "with-bindings", "with-bindings*", "with-in-str", "with-loading-context", "with-local-vars", "with-meta", "with-open", "with-out-str", "with-precision", "with-redefs", "with-redefs-fn"]; CodeMirror.registerHelper("hintWords", "clojure", [].concat(atoms, specialForms, coreSymbols)); var atom = createLookupMap(atoms); var specialForm = createLookupMap(specialForms); var coreSymbol = createLookupMap(coreSymbols); var hasBodyParameter = createLookupMap(haveBodyParameter); var delimiter = /^(?:[\\\[\]\s"(),;@^`{}~]|$)/; var numberLiteral = /^(?:[+\-]?\d+(?:(?:N|(?:[eE][+\-]?\d+))|(?:\.?\d*(?:M|(?:[eE][+\-]?\d+))?)|\/\d+|[xX][0-9a-fA-F]+|r[0-9a-zA-Z]+)?(?=[\\\[\]\s"#'(),;@^`{}~]|$))/; var characterLiteral = /^(?:\\(?:backspace|formfeed|newline|return|space|tab|o[0-7]{3}|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{4}|.)?(?=[\\\[\]\s"(),;@^`{}~]|$))/; // simple-namespace := /^[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*/ // simple-symbol := /^(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)/ // qualified-symbol := ((<.>)*)? var qualifiedSymbol = /^(?:(?:[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*(?:\.[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*)*\/)?(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*(?=[\\\[\]\s"(),;@^`{}~]|$))/; function base(stream, state) { if (stream.eatSpace() || stream.eat(",")) return ["space", null]; if (stream.match(numberLiteral)) return [null, "number"]; if (stream.match(characterLiteral)) return [null, "string-2"]; if (stream.eat(/^"/)) return (state.tokenize = inString)(stream, state); if (stream.eat(/^[(\[{]/)) return ["open", "bracket"]; if (stream.eat(/^[)\]}]/)) return ["close", "bracket"]; if (stream.eat(/^;/)) {stream.skipToEnd(); return ["space", "comment"];} if (stream.eat(/^[#'@^`~]/)) return [null, "meta"]; var matches = stream.match(qualifiedSymbol); var symbol = matches && matches[0]; if (!symbol) { // advance stream by at least one character so we don't get stuck. stream.next(); stream.eatWhile(function (c) {return !is(c, delimiter);}); return [null, "error"]; } if (symbol === "comment" && state.lastToken === "(") return (state.tokenize = inComment)(stream, state); if (is(symbol, atom) || symbol.charAt(0) === ":") return ["symbol", "atom"]; if (is(symbol, specialForm) || is(symbol, coreSymbol)) return ["symbol", "keyword"]; if (state.lastToken === "(") return ["symbol", "builtin"]; // other operator return ["symbol", "variable"]; } function inString(stream, state) { var escaped = false, next; while (next = stream.next()) { if (next === "\"" && !escaped) {state.tokenize = base; break;} escaped = !escaped && next === "\\"; } return [null, "string"]; } function inComment(stream, state) { var parenthesisCount = 1; var next; while (next = stream.next()) { if (next === ")") parenthesisCount--; if (next === "(") parenthesisCount++; if (parenthesisCount === 0) { stream.backUp(1); state.tokenize = base; break; } } return ["space", "comment"]; } function createLookupMap(words) { var obj = {}; for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } function is(value, test) { if (test instanceof RegExp) return test.test(value); if (test instanceof Object) return test.propertyIsEnumerable(value); } return { startState: function () { return { ctx: {prev: null, start: 0, indentTo: 0}, lastToken: null, tokenize: base }; }, token: function (stream, state) { if (stream.sol() && (typeof state.ctx.indentTo !== "number")) state.ctx.indentTo = state.ctx.start + 1; var typeStylePair = state.tokenize(stream, state); var type = typeStylePair[0]; var style = typeStylePair[1]; var current = stream.current(); if (type !== "space") { if (state.lastToken === "(" && state.ctx.indentTo === null) { if (type === "symbol" && is(current, hasBodyParameter)) state.ctx.indentTo = state.ctx.start + options.indentUnit; else state.ctx.indentTo = "next"; } else if (state.ctx.indentTo === "next") { state.ctx.indentTo = stream.column(); } state.lastToken = current; } if (type === "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; else if (type === "close") state.ctx = state.ctx.prev || state.ctx; return style; }, indent: function (state) { var i = state.ctx.indentTo; return (typeof i === "number") ? i : state.ctx.start + 1; }, closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;" }; }); CodeMirror.defineMIME("text/x-clojure", "clojure"); CodeMirror.defineMIME("text/x-clojurescript", "clojure"); CodeMirror.defineMIME("application/edn", "clojure"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clojure/index.html ================================================ CodeMirror: Clojure mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Clojure

    Clojure mode

    MIME types defined: text/x-clojure.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/clojure/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function () { var mode = CodeMirror.getMode({indentUnit: 2}, "clojure"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("atoms", "[atom false]", "[atom nil]", "[atom true]" ); MT("keywords", "[atom :foo]", "[atom ::bar]", "[atom :foo/bar]", "[atom :foo.bar/baz]" ); MT("numbers", "[number 42] [number +42] [number -421]", "[number 42N] [number +42N] [number -42N]", "[number 0.42] [number +0.42] [number -0.42]", "[number 42M] [number +42M] [number -42M]", "[number 42.42M] [number +42.42M] [number -42.42M]", "[number 1/42] [number +1/42] [number -1/42]", "[number 0x42af] [number +0x42af] [number -0x42af]", "[number 0x42AF] [number +0x42AF] [number -0x42AF]", "[number 1e2] [number 1e+2] [number 1e-2]", "[number +1e2] [number +1e+2] [number +1e-2]", "[number -1e2] [number -1e+2] [number -1e-2]", "[number -1.0e2] [number -0.1e+2] [number -1.01e-2]", "[number 1E2] [number 1E+2] [number 1E-2]", "[number +1E2] [number +1E+2] [number +1E-2]", "[number -1E2] [number -1E+2] [number -1E-2]", "[number -1.0E2] [number -0.1E+2] [number -1.01E-2]", "[number 2r101010] [number +2r101010] [number -2r101010]", "[number 2r101010] [number +2r101010] [number -2r101010]", "[number 8r52] [number +8r52] [number -8r52]", "[number 36rhello] [number +36rhello] [number -36rhello]", "[number 36rz] [number +36rz] [number -36rz]", "[number 36rZ] [number +36rZ] [number -36rZ]", // invalid numbers "[error 42foo]", "[error 42Nfoo]", "[error 42Mfoo]", "[error 42.42Mfoo]", "[error 42.42M!]", "[error 42!]", "[error 0x42afm]" ); MT("characters", "[string-2 \\1]", "[string-2 \\a]", "[string-2 \\a\\b\\c]", "[string-2 \\#]", "[string-2 \\\\]", "[string-2 \\\"]", "[string-2 \\(]", "[string-2 \\A]", "[string-2 \\backspace]", "[string-2 \\formfeed]", "[string-2 \\newline]", "[string-2 \\space]", "[string-2 \\return]", "[string-2 \\tab]", "[string-2 \\u1000]", "[string-2 \\uAaAa]", "[string-2 \\u9F9F]", "[string-2 \\o123]", "[string-2 \\符]", "[string-2 \\シ]", "[string-2 \\ۇ]", // FIXME // "[string-2 \\🙂]", // invalid character literals "[error \\abc]", "[error \\a123]", "[error \\a!]", "[error \\newlines]", "[error \\NEWLINE]", "[error \\u9F9FF]", "[error \\o1234]" ); MT("strings", "[string \"I'm a teapot.\"]", "[string \"I'm a \\\"teapot\\\".\"]", "[string \"I'm]", // this is "[string a]", // a multi-line "[string teapot.\"]" // string // TODO unterminated (multi-line) strings? ); MT("comments", "[comment ; this is an in-line comment.]", "[comment ;; this is a line comment.]", "[keyword comment]", "[bracket (][comment comment (foo 1 2 3)][bracket )]" ); MT("reader macro characters", "[meta #][variable _]", "[meta #][variable -Inf]", "[meta ##][variable Inf]", "[meta ##][variable NaN]", "[meta @][variable x]", "[meta ^][bracket {][atom :tag] [variable String][bracket }]", "[meta `][bracket (][builtin f] [variable x][bracket )]", "[meta ~][variable foo#]", "[meta '][number 1]", "[meta '][atom :foo]", "[meta '][string \"foo\"]", "[meta '][variable x]", "[meta '][bracket (][builtin a] [variable b] [variable c][bracket )]", "[meta '][bracket [[][variable a] [variable b] [variable c][bracket ]]]", "[meta '][bracket {][variable a] [number 1] [atom :foo] [number 2] [variable c] [number 3][bracket }]", "[meta '#][bracket {][variable a] [number 1] [atom :foo][bracket }]" ); MT("symbols", "[variable foo!]", "[variable foo#]", "[variable foo$]", "[variable foo&]", "[variable foo']", "[variable foo*]", "[variable foo+]", "[variable foo-]", "[variable foo.]", "[variable foo/bar]", "[variable foo:bar]", "[variable foo<]", "[variable foo=]", "[variable foo>]", "[variable foo?]", "[variable foo_]", "[variable foo|]", "[variable foobarBaz]", "[variable foo¡]", "[variable 符号]", "[variable シンボル]", "[variable ئۇيغۇر]", "[variable 🙂❤🇺🇸]", // invalid symbols "[error 3foo]", "[error 3+]", "[error 3|]", "[error 3_]" ); MT("numbers and other forms", "[number 42][bracket (][builtin foo][bracket )]", "[number 42][bracket [[][variable foo][bracket ]]]", "[number 42][meta #][bracket {][variable foo][bracket }]", "[number 42][bracket {][atom :foo] [variable bar][bracket }]", "[number 42][meta `][variable foo]", "[number 42][meta ~][variable foo]", "[number 42][meta #][variable foo]" ); var specialForms = [".", "catch", "def", "do", "if", "monitor-enter", "monitor-exit", "new", "quote", "recur", "set!", "throw", "try", "var"]; MT("should highlight special forms as keywords", typeTokenPairs("keyword", specialForms) ); var coreSymbols1 = [ "*", "*'", "*1", "*2", "*3", "*agent*", "*allow-unresolved-vars*", "*assert*", "*clojure-version*", "*command-line-args*", "*compile-files*", "*compile-path*", "*compiler-options*", "*data-readers*", "*default-data-reader-fn*", "*e", "*err*", "*file*", "*flush-on-newline*", "*fn-loader*", "*in*", "*math-context*", "*ns*", "*out*", "*print-dup*", "*print-length*", "*print-level*", "*print-meta*", "*print-namespace-maps*", "*print-readably*", "*read-eval*", "*reader-resolver*", "*source-path*", "*suppress-read*", "*unchecked-math*", "*use-context-classloader*", "*verbose-defrecords*", "*warn-on-reflection*", "+", "+'", "-", "-'", "->", "->>", "->ArrayChunk", "->Eduction", "->Vec", "->VecNode", "->VecSeq", "-cache-protocol-fn", "-reset-methods", "..", "/", "<", "<=", "=", "==", ">", ">=", "EMPTY-NODE", "Inst", "StackTraceElement->vec", "Throwable->map", "accessor", "aclone", "add-classpath", "add-watch", "agent", "agent-error", "agent-errors", "aget", "alength", "alias", "all-ns", "alter", "alter-meta!", "alter-var-root", "amap", "ancestors", "and", "any?", "apply", "areduce", "array-map", "as->", "aset", "aset-boolean", "aset-byte", "aset-char", "aset-double", "aset-float", "aset-int", "aset-long", "aset-short", "assert", "assoc", "assoc!", "assoc-in", "associative?", "atom", "await", "await-for", "await1", "bases", "bean", "bigdec", "bigint", "biginteger", "binding", "bit-and", "bit-and-not", "bit-clear", "bit-flip", "bit-not", "bit-or", "bit-set", "bit-shift-left", "bit-shift-right", "bit-test", "bit-xor", "boolean", "boolean-array", "boolean?", "booleans", "bound-fn", "bound-fn*", "bound?", "bounded-count", "butlast", "byte", "byte-array", "bytes", "bytes?", "case", "cast", "cat", "char", "char-array", "char-escape-string", "char-name-string", "char?", "chars", "chunk", "chunk-append", "chunk-buffer", "chunk-cons", "chunk-first", "chunk-next", "chunk-rest", "chunked-seq?", "class", "class?", "clear-agent-errors", "clojure-version", "coll?", "comment", "commute", "comp", "comparator", "compare", "compare-and-set!", "compile", "complement", "completing", "concat", "cond", "cond->", "cond->>", "condp", "conj", "conj!", "cons", "constantly", "construct-proxy", "contains?", "count", "counted?", "create-ns", "create-struct", "cycle", "dec", "dec'", "decimal?", "declare", "dedupe", "default-data-readers", "definline", "definterface", "defmacro", "defmethod", "defmulti", "defn", "defn-", "defonce", "defprotocol", "defrecord", "defstruct", "deftype", "delay", "delay?", "deliver", "denominator", "deref", "derive", "descendants", "destructure", "disj", "disj!", "dissoc", "dissoc!", "distinct", "distinct?", "doall", "dorun", "doseq", "dosync", "dotimes", "doto", "double", "double-array", "double?", "doubles", "drop", "drop-last", "drop-while", "eduction", "empty", "empty?", "ensure", "ensure-reduced", "enumeration-seq", "error-handler", "error-mode", "eval", "even?", "every-pred", "every?", "ex-data", "ex-info", "extend", "extend-protocol", "extend-type", "extenders", "extends?", "false?", "ffirst", "file-seq", "filter", "filterv", "find", "find-keyword", "find-ns", "find-protocol-impl", "find-protocol-method", "find-var", "first", "flatten", "float", "float-array", "float?", "floats", "flush", "fn", "fn?", "fnext", "fnil", "for", "force", "format", "frequencies", "future", "future-call", "future-cancel", "future-cancelled?", "future-done?", "future?", "gen-class", "gen-interface", "gensym", "get", "get-in", "get-method", "get-proxy-class", "get-thread-bindings", "get-validator", "group-by", "halt-when", "hash", "hash-combine", "hash-map", "hash-ordered-coll", "hash-set", "hash-unordered-coll", "ident?", "identical?", "identity", "if-let", "if-not", "if-some", "ifn?", "import", "in-ns", "inc", "inc'", "indexed?", "init-proxy", "inst-ms", "inst-ms*", "inst?", "instance?", "int", "int-array", "int?", "integer?", "interleave", "intern", "interpose", "into", "into-array", "ints", "io!", "isa?", "iterate", "iterator-seq", "juxt", "keep", "keep-indexed", "key", "keys", "keyword", "keyword?", "last", "lazy-cat", "lazy-seq", "let", "letfn", "line-seq", "list", "list*", "list?", "load", "load-file", "load-reader", "load-string", "loaded-libs", "locking", "long", "long-array", "longs", "loop", "macroexpand", "macroexpand-1", "make-array", "make-hierarchy", "map", "map-entry?", "map-indexed", "map?", "mapcat", "mapv", "max", "max-key", "memfn", "memoize", "merge", "merge-with", "meta", "method-sig", "methods"]; var coreSymbols2 = [ "min", "min-key", "mix-collection-hash", "mod", "munge", "name", "namespace", "namespace-munge", "nat-int?", "neg-int?", "neg?", "newline", "next", "nfirst", "nil?", "nnext", "not", "not-any?", "not-empty", "not-every?", "not=", "ns", "ns-aliases", "ns-imports", "ns-interns", "ns-map", "ns-name", "ns-publics", "ns-refers", "ns-resolve", "ns-unalias", "ns-unmap", "nth", "nthnext", "nthrest", "num", "number?", "numerator", "object-array", "odd?", "or", "parents", "partial", "partition", "partition-all", "partition-by", "pcalls", "peek", "persistent!", "pmap", "pop", "pop!", "pop-thread-bindings", "pos-int?", "pos?", "pr", "pr-str", "prefer-method", "prefers", "primitives-classnames", "print", "print-ctor", "print-dup", "print-method", "print-simple", "print-str", "printf", "println", "println-str", "prn", "prn-str", "promise", "proxy", "proxy-call-with-super", "proxy-mappings", "proxy-name", "proxy-super", "push-thread-bindings", "pvalues", "qualified-ident?", "qualified-keyword?", "qualified-symbol?", "quot", "rand", "rand-int", "rand-nth", "random-sample", "range", "ratio?", "rational?", "rationalize", "re-find", "re-groups", "re-matcher", "re-matches", "re-pattern", "re-seq", "read", "read-line", "read-string", "reader-conditional", "reader-conditional?", "realized?", "record?", "reduce", "reduce-kv", "reduced", "reduced?", "reductions", "ref", "ref-history-count", "ref-max-history", "ref-min-history", "ref-set", "refer", "refer-clojure", "reify", "release-pending-sends", "rem", "remove", "remove-all-methods", "remove-method", "remove-ns", "remove-watch", "repeat", "repeatedly", "replace", "replicate", "require", "reset!", "reset-meta!", "reset-vals!", "resolve", "rest", "restart-agent", "resultset-seq", "reverse", "reversible?", "rseq", "rsubseq", "run!", "satisfies?", "second", "select-keys", "send", "send-off", "send-via", "seq", "seq?", "seqable?", "seque", "sequence", "sequential?", "set", "set-agent-send-executor!", "set-agent-send-off-executor!", "set-error-handler!", "set-error-mode!", "set-validator!", "set?", "short", "short-array", "shorts", "shuffle", "shutdown-agents", "simple-ident?", "simple-keyword?", "simple-symbol?", "slurp", "some", "some->", "some->>", "some-fn", "some?", "sort", "sort-by", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "sorted?", "special-symbol?", "spit", "split-at", "split-with", "str", "string?", "struct", "struct-map", "subs", "subseq", "subvec", "supers", "swap!", "swap-vals!", "symbol", "symbol?", "sync", "tagged-literal", "tagged-literal?", "take", "take-last", "take-nth", "take-while", "test", "the-ns", "thread-bound?", "time", "to-array", "to-array-2d", "trampoline", "transduce", "transient", "tree-seq", "true?", "type", "unchecked-add", "unchecked-add-int", "unchecked-byte", "unchecked-char", "unchecked-dec", "unchecked-dec-int", "unchecked-divide-int", "unchecked-double", "unchecked-float", "unchecked-inc", "unchecked-inc-int", "unchecked-int", "unchecked-long", "unchecked-multiply", "unchecked-multiply-int", "unchecked-negate", "unchecked-negate-int", "unchecked-remainder-int", "unchecked-short", "unchecked-subtract", "unchecked-subtract-int", "underive", "unquote", "unquote-splicing", "unreduced", "unsigned-bit-shift-right", "update", "update-in", "update-proxy", "uri?", "use", "uuid?", "val", "vals", "var-get", "var-set", "var?", "vary-meta", "vec", "vector", "vector-of", "vector?", "volatile!", "volatile?", "vreset!", "vswap!", "when", "when-first", "when-let", "when-not", "when-some", "while", "with-bindings", "with-bindings*", "with-in-str", "with-loading-context", "with-local-vars", "with-meta", "with-open", "with-out-str", "with-precision", "with-redefs", "with-redefs-fn", "xml-seq", "zero?", "zipmap" ]; MT("should highlight core symbols as keywords (part 1/2)", typeTokenPairs("keyword", coreSymbols1) ); MT("should highlight core symbols as keywords (part 2/2)", typeTokenPairs("keyword", coreSymbols2) ); MT("should properly indent forms in list literals", "[bracket (][builtin foo] [atom :a] [number 1] [atom true] [atom nil][bracket )]", "", "[bracket (][builtin foo] [atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket )]", "", "[bracket (][builtin foo] [atom :a] [number 1]", " [atom true]", " [atom nil][bracket )]", "", "[bracket (]", " [builtin foo]", " [atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket )]", "", "[bracket (][builtin foo] [bracket [[][atom :a][bracket ]]]", " [number 1]", " [atom true]", " [atom nil][bracket )]" ); MT("should properly indent forms in vector literals", "[bracket [[][atom :a] [number 1] [atom true] [atom nil][bracket ]]]", "", "[bracket [[][atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket ]]]", "", "[bracket [[][atom :a] [number 1]", " [atom true]", " [atom nil][bracket ]]]", "", "[bracket [[]", " [variable foo]", " [atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket ]]]" ); MT("should properly indent forms in map literals", "[bracket {][atom :a] [atom :a] [atom :b] [number 1] [atom :c] [atom true] [atom :d] [atom nil] [bracket }]", "", "[bracket {][atom :a] [atom :a]", " [atom :b] [number 1]", " [atom :c] [atom true]", " [atom :d] [atom nil][bracket }]", "", "[bracket {][atom :a]", " [atom :a]", " [atom :b]", " [number 1]", " [atom :c]", " [atom true]", " [atom :d]", " [atom nil][bracket }]", "", "[bracket {]", " [atom :a] [atom :a]", " [atom :b] [number 1]", " [atom :c] [atom true]", " [atom :d] [atom nil][bracket }]" ); MT("should properly indent forms in set literals", "[meta #][bracket {][atom :a] [number 1] [atom true] [atom nil] [bracket }]", "", "[meta #][bracket {][atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket }]", "", "[meta #][bracket {]", " [atom :a]", " [number 1]", " [atom true]", " [atom nil][bracket }]" ); var haveBodyParameter = [ "->", "->>", "as->", "binding", "bound-fn", "case", "catch", "cond", "cond->", "cond->>", "condp", "def", "definterface", "defmethod", "defn", "defmacro", "defprotocol", "defrecord", "defstruct", "deftype", "do", "doseq", "dotimes", "doto", "extend", "extend-protocol", "extend-type", "fn", "for", "future", "if", "if-let", "if-not", "if-some", "let", "letfn", "locking", "loop", "ns", "proxy", "reify", "some->", "some->>", "struct-map", "try", "when", "when-first", "when-let", "when-not", "when-some", "while", "with-bindings", "with-bindings*", "with-in-str", "with-loading-context", "with-local-vars", "with-meta", "with-open", "with-out-str", "with-precision", "with-redefs", "with-redefs-fn"]; function testFormsThatHaveBodyParameter(forms) { for (var i = 0; i < forms.length; i++) { MT("should indent body argument of `" + forms[i] + "` by `options.indentUnit` spaces", "[bracket (][keyword " + forms[i] + "] [variable foo] [variable bar]", " [variable baz]", " [variable qux][bracket )]" ); } } testFormsThatHaveBodyParameter(haveBodyParameter); MT("should indent body argument of `comment` by `options.indentUnit` spaces", "[bracket (][comment comment foo bar]", "[comment baz]", "[comment qux][bracket )]" ); function typeTokenPairs(type, tokens) { return "[" + type + " " + tokens.join("] [" + type + " ") + "]"; } })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cmake/cmake.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) define(["../../lib/codemirror"], mod); else mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("cmake", function () { var variable_regex = /({)?[a-zA-Z0-9_]+(})?/; function tokenString(stream, state) { var current, prev, found_var = false; while (!stream.eol() && (current = stream.next()) != state.pending) { if (current === '$' && prev != '\\' && state.pending == '"') { found_var = true; break; } prev = current; } if (found_var) { stream.backUp(1); } if (current == state.pending) { state.continueString = false; } else { state.continueString = true; } return "string"; } function tokenize(stream, state) { var ch = stream.next(); // Have we found a variable? if (ch === '$') { if (stream.match(variable_regex)) { return 'variable-2'; } return 'variable'; } // Should we still be looking for the end of a string? if (state.continueString) { // If so, go through the loop again stream.backUp(1); return tokenString(stream, state); } // Do we just have a function on our hands? // In 'cmake_minimum_required (VERSION 2.8.8)', 'cmake_minimum_required' is matched if (stream.match(/(\s+)?\w+\(/) || stream.match(/(\s+)?\w+\ \(/)) { stream.backUp(1); return 'def'; } if (ch == "#") { stream.skipToEnd(); return "comment"; } // Have we found a string? if (ch == "'" || ch == '"') { // Store the type (single or double) state.pending = ch; // Perform the looping function to find the end return tokenString(stream, state); } if (ch == '(' || ch == ')') { return 'bracket'; } if (ch.match(/[0-9]/)) { return 'number'; } stream.eatWhile(/[\w-]/); return null; } return { startState: function () { var state = {}; state.inDefinition = false; state.inInclude = false; state.continueString = false; state.pending = false; return state; }, token: function (stream, state) { if (stream.eatSpace()) return null; return tokenize(stream, state); } }; }); CodeMirror.defineMIME("text/x-cmake", "cmake"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cmake/index.html ================================================ CodeMirror: CMake mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • CMake

    CMake mode

    MIME types defined: text/x-cmake.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cobol/cobol.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Author: Gautam Mehta * Branched from CodeMirror's Scheme mode */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("cobol", function () { var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", ATOM = "atom", NUMBER = "number", KEYWORD = "keyword", MODTAG = "header", COBOLLINENUM = "def", PERIOD = "link"; function makeKeywords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var atoms = makeKeywords("TRUE FALSE ZEROES ZEROS ZERO SPACES SPACE LOW-VALUE LOW-VALUES "); var keywords = makeKeywords( "ACCEPT ACCESS ACQUIRE ADD ADDRESS " + "ADVANCING AFTER ALIAS ALL ALPHABET " + "ALPHABETIC ALPHABETIC-LOWER ALPHABETIC-UPPER ALPHANUMERIC ALPHANUMERIC-EDITED " + "ALSO ALTER ALTERNATE AND ANY " + "ARE AREA AREAS ARITHMETIC ASCENDING " + "ASSIGN AT ATTRIBUTE AUTHOR AUTO " + "AUTO-SKIP AUTOMATIC B-AND B-EXOR B-LESS " + "B-NOT B-OR BACKGROUND-COLOR BACKGROUND-COLOUR BEEP " + "BEFORE BELL BINARY BIT BITS " + "BLANK BLINK BLOCK BOOLEAN BOTTOM " + "BY CALL CANCEL CD CF " + "CH CHARACTER CHARACTERS CLASS CLOCK-UNITS " + "CLOSE COBOL CODE CODE-SET COL " + "COLLATING COLUMN COMMA COMMIT COMMITMENT " + "COMMON COMMUNICATION COMP COMP-0 COMP-1 " + "COMP-2 COMP-3 COMP-4 COMP-5 COMP-6 " + "COMP-7 COMP-8 COMP-9 COMPUTATIONAL COMPUTATIONAL-0 " + "COMPUTATIONAL-1 COMPUTATIONAL-2 COMPUTATIONAL-3 COMPUTATIONAL-4 COMPUTATIONAL-5 " + "COMPUTATIONAL-6 COMPUTATIONAL-7 COMPUTATIONAL-8 COMPUTATIONAL-9 COMPUTE " + "CONFIGURATION CONNECT CONSOLE CONTAINED CONTAINS " + "CONTENT CONTINUE CONTROL CONTROL-AREA CONTROLS " + "CONVERTING COPY CORR CORRESPONDING COUNT " + "CRT CRT-UNDER CURRENCY CURRENT CURSOR " + "DATA DATE DATE-COMPILED DATE-WRITTEN DAY " + "DAY-OF-WEEK DB DB-ACCESS-CONTROL-KEY DB-DATA-NAME DB-EXCEPTION " + "DB-FORMAT-NAME DB-RECORD-NAME DB-SET-NAME DB-STATUS DBCS " + "DBCS-EDITED DE DEBUG-CONTENTS DEBUG-ITEM DEBUG-LINE " + "DEBUG-NAME DEBUG-SUB-1 DEBUG-SUB-2 DEBUG-SUB-3 DEBUGGING " + "DECIMAL-POINT DECLARATIVES DEFAULT DELETE DELIMITED " + "DELIMITER DEPENDING DESCENDING DESCRIBED DESTINATION " + "DETAIL DISABLE DISCONNECT DISPLAY DISPLAY-1 " + "DISPLAY-2 DISPLAY-3 DISPLAY-4 DISPLAY-5 DISPLAY-6 " + "DISPLAY-7 DISPLAY-8 DISPLAY-9 DIVIDE DIVISION " + "DOWN DROP DUPLICATE DUPLICATES DYNAMIC " + "EBCDIC EGI EJECT ELSE EMI " + "EMPTY EMPTY-CHECK ENABLE END END. END-ACCEPT END-ACCEPT. " + "END-ADD END-CALL END-COMPUTE END-DELETE END-DISPLAY " + "END-DIVIDE END-EVALUATE END-IF END-INVOKE END-MULTIPLY " + "END-OF-PAGE END-PERFORM END-READ END-RECEIVE END-RETURN " + "END-REWRITE END-SEARCH END-START END-STRING END-SUBTRACT " + "END-UNSTRING END-WRITE END-XML ENTER ENTRY " + "ENVIRONMENT EOP EQUAL EQUALS ERASE " + "ERROR ESI EVALUATE EVERY EXCEEDS " + "EXCEPTION EXCLUSIVE EXIT EXTEND EXTERNAL " + "EXTERNALLY-DESCRIBED-KEY FD FETCH FILE FILE-CONTROL " + "FILE-STREAM FILES FILLER FINAL FIND " + "FINISH FIRST FOOTING FOR FOREGROUND-COLOR " + "FOREGROUND-COLOUR FORMAT FREE FROM FULL " + "FUNCTION GENERATE GET GIVING GLOBAL " + "GO GOBACK GREATER GROUP HEADING " + "HIGH-VALUE HIGH-VALUES HIGHLIGHT I-O I-O-CONTROL " + "ID IDENTIFICATION IF IN INDEX " + "INDEX-1 INDEX-2 INDEX-3 INDEX-4 INDEX-5 " + "INDEX-6 INDEX-7 INDEX-8 INDEX-9 INDEXED " + "INDIC INDICATE INDICATOR INDICATORS INITIAL " + "INITIALIZE INITIATE INPUT INPUT-OUTPUT INSPECT " + "INSTALLATION INTO INVALID INVOKE IS " + "JUST JUSTIFIED KANJI KEEP KEY " + "LABEL LAST LD LEADING LEFT " + "LEFT-JUSTIFY LENGTH LENGTH-CHECK LESS LIBRARY " + "LIKE LIMIT LIMITS LINAGE LINAGE-COUNTER " + "LINE LINE-COUNTER LINES LINKAGE LOCAL-STORAGE " + "LOCALE LOCALLY LOCK " + "MEMBER MEMORY MERGE MESSAGE METACLASS " + "MODE MODIFIED MODIFY MODULES MOVE " + "MULTIPLE MULTIPLY NATIONAL NATIVE NEGATIVE " + "NEXT NO NO-ECHO NONE NOT " + "NULL NULL-KEY-MAP NULL-MAP NULLS NUMBER " + "NUMERIC NUMERIC-EDITED OBJECT OBJECT-COMPUTER OCCURS " + "OF OFF OMITTED ON ONLY " + "OPEN OPTIONAL OR ORDER ORGANIZATION " + "OTHER OUTPUT OVERFLOW OWNER PACKED-DECIMAL " + "PADDING PAGE PAGE-COUNTER PARSE PERFORM " + "PF PH PIC PICTURE PLUS " + "POINTER POSITION POSITIVE PREFIX PRESENT " + "PRINTING PRIOR PROCEDURE PROCEDURE-POINTER PROCEDURES " + "PROCEED PROCESS PROCESSING PROGRAM PROGRAM-ID " + "PROMPT PROTECTED PURGE QUEUE QUOTE " + "QUOTES RANDOM RD READ READY " + "REALM RECEIVE RECONNECT RECORD RECORD-NAME " + "RECORDS RECURSIVE REDEFINES REEL REFERENCE " + "REFERENCE-MONITOR REFERENCES RELATION RELATIVE RELEASE " + "REMAINDER REMOVAL RENAMES REPEATED REPLACE " + "REPLACING REPORT REPORTING REPORTS REPOSITORY " + "REQUIRED RERUN RESERVE RESET RETAINING " + "RETRIEVAL RETURN RETURN-CODE RETURNING REVERSE-VIDEO " + "REVERSED REWIND REWRITE RF RH " + "RIGHT RIGHT-JUSTIFY ROLLBACK ROLLING ROUNDED " + "RUN SAME SCREEN SD SEARCH " + "SECTION SECURE SECURITY SEGMENT SEGMENT-LIMIT " + "SELECT SEND SENTENCE SEPARATE SEQUENCE " + "SEQUENTIAL SET SHARED SIGN SIZE " + "SKIP1 SKIP2 SKIP3 SORT SORT-MERGE " + "SORT-RETURN SOURCE SOURCE-COMPUTER SPACE-FILL " + "SPECIAL-NAMES STANDARD STANDARD-1 STANDARD-2 " + "START STARTING STATUS STOP STORE " + "STRING SUB-QUEUE-1 SUB-QUEUE-2 SUB-QUEUE-3 SUB-SCHEMA " + "SUBFILE SUBSTITUTE SUBTRACT SUM SUPPRESS " + "SYMBOLIC SYNC SYNCHRONIZED SYSIN SYSOUT " + "TABLE TALLYING TAPE TENANT TERMINAL " + "TERMINATE TEST TEXT THAN THEN " + "THROUGH THRU TIME TIMES TITLE " + "TO TOP TRAILING TRAILING-SIGN TRANSACTION " + "TYPE TYPEDEF UNDERLINE UNEQUAL UNIT " + "UNSTRING UNTIL UP UPDATE UPON " + "USAGE USAGE-MODE USE USING VALID " + "VALIDATE VALUE VALUES VARYING VLR " + "WAIT WHEN WHEN-COMPILED WITH WITHIN " + "WORDS WORKING-STORAGE WRITE XML XML-CODE " + "XML-EVENT XML-NTEXT XML-TEXT ZERO ZERO-FILL " ); var builtins = makeKeywords("- * ** / + < <= = > >= "); var tests = { digit: /\d/, digit_or_colon: /[\d:]/, hex: /[0-9a-f]/i, sign: /[+-]/, exponent: /e/i, keyword_char: /[^\s\(\[\;\)\]]/, symbol: /[\w*+\-]/ }; function isNumber(ch, stream){ // hex if ( ch === '0' && stream.eat(/x/i) ) { stream.eatWhile(tests.hex); return true; } // leading sign if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { stream.eat(tests.sign); ch = stream.next(); } if ( tests.digit.test(ch) ) { stream.eat(ch); stream.eatWhile(tests.digit); if ( '.' == stream.peek()) { stream.eat('.'); stream.eatWhile(tests.digit); } if ( stream.eat(tests.exponent) ) { stream.eat(tests.sign); stream.eatWhile(tests.digit); } return true; } return false; } return { startState: function () { return { indentStack: null, indentation: 0, mode: false }; }, token: function (stream, state) { if (state.indentStack == null && stream.sol()) { // update indentation, but only if indentStack is empty state.indentation = 6 ; //stream.indentation(); } // skip spaces if (stream.eatSpace()) { return null; } var returnType = null; switch(state.mode){ case "string": // multi-line string parsing mode var next = false; while ((next = stream.next()) != null) { if ((next == "\"" || next == "\'") && !stream.match(/['"]/, false)) { state.mode = false; break; } } returnType = STRING; // continue on in string mode break; default: // default parsing mode var ch = stream.next(); var col = stream.column(); if (col >= 0 && col <= 5) { returnType = COBOLLINENUM; } else if (col >= 72 && col <= 79) { stream.skipToEnd(); returnType = MODTAG; } else if (ch == "*" && col == 6) { // comment stream.skipToEnd(); // rest of the line is a comment returnType = COMMENT; } else if (ch == "\"" || ch == "\'") { state.mode = "string"; returnType = STRING; } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { returnType = ATOM; } else if (ch == ".") { returnType = PERIOD; } else if (isNumber(ch,stream)){ returnType = NUMBER; } else { if (stream.current().match(tests.symbol)) { while (col < 71) { if (stream.eat(tests.symbol) === undefined) { break; } else { col++; } } } if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) { returnType = KEYWORD; } else if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) { returnType = BUILTIN; } else if (atoms && atoms.propertyIsEnumerable(stream.current().toUpperCase())) { returnType = ATOM; } else returnType = null; } } return returnType; }, indent: function (state) { if (state.indentStack == null) return state.indentation; return state.indentStack.indent; } }; }); CodeMirror.defineMIME("text/x-cobol", "cobol"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cobol/index.html ================================================ CodeMirror: COBOL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • COBOL

    COBOL mode

    Select Theme Select Font Size

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/coffeescript/coffeescript.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Link to the project's GitHub page: * https://github.com/pickhardt/coffeescript-codemirror-mode */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("coffeescript", function(conf, parserConf) { var ERRORCLASS = "error"; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); } var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/; var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/; var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/; var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/; var wordOperators = wordRegexp(["and", "or", "not", "is", "isnt", "in", "instanceof", "typeof"]); var indentKeywords = ["for", "while", "loop", "if", "unless", "else", "switch", "try", "catch", "finally", "class"]; var commonKeywords = ["break", "by", "continue", "debugger", "delete", "do", "in", "of", "new", "return", "then", "this", "@", "throw", "when", "until", "extends"]; var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); indentKeywords = wordRegexp(indentKeywords); var stringPrefixes = /^('{3}|\"{3}|['\"])/; var regexPrefixes = /^(\/{3}|\/)/; var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"]; var constants = wordRegexp(commonConstants); // Tokenizers function tokenBase(stream, state) { // Handle scope changes if (stream.sol()) { if (state.scope.align === null) state.scope.align = false; var scopeOffset = state.scope.offset; if (stream.eatSpace()) { var lineOffset = stream.indentation(); if (lineOffset > scopeOffset && state.scope.type == "coffee") { return "indent"; } else if (lineOffset < scopeOffset) { return "dedent"; } return null; } else { if (scopeOffset > 0) { dedent(stream, state); } } } if (stream.eatSpace()) { return null; } var ch = stream.peek(); // Handle docco title comment (single line) if (stream.match("####")) { stream.skipToEnd(); return "comment"; } // Handle multi line comments if (stream.match("###")) { state.tokenize = longComment; return state.tokenize(stream, state); } // Single line comment if (ch === "#") { stream.skipToEnd(); return "comment"; } // Handle number literals if (stream.match(/^-?[0-9\.]/, false)) { var floatLiteral = false; // Floats if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } if (stream.match(/^-?\d+\.\d*/)) { floatLiteral = true; } if (stream.match(/^-?\.\d+/)) { floatLiteral = true; } if (floatLiteral) { // prevent from getting extra . on 1.. if (stream.peek() == "."){ stream.backUp(1); } return "number"; } // Integers var intLiteral = false; // Hex if (stream.match(/^-?0x[0-9a-f]+/i)) { intLiteral = true; } // Decimal if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { intLiteral = true; } // Zero by itself with no other piece of number. if (stream.match(/^-?0(?![\dx])/i)) { intLiteral = true; } if (intLiteral) { return "number"; } } // Handle strings if (stream.match(stringPrefixes)) { state.tokenize = tokenFactory(stream.current(), false, "string"); return state.tokenize(stream, state); } // Handle regex literals if (stream.match(regexPrefixes)) { if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division state.tokenize = tokenFactory(stream.current(), true, "string-2"); return state.tokenize(stream, state); } else { stream.backUp(1); } } // Handle operators and delimiters if (stream.match(operators) || stream.match(wordOperators)) { return "operator"; } if (stream.match(delimiters)) { return "punctuation"; } if (stream.match(constants)) { return "atom"; } if (stream.match(atProp) || state.prop && stream.match(identifiers)) { return "property"; } if (stream.match(keywords)) { return "keyword"; } if (stream.match(identifiers)) { return "variable"; } // Handle non-detected items stream.next(); return ERRORCLASS; } function tokenFactory(delimiter, singleline, outclass) { return function(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\/\\]/); if (stream.eat("\\")) { stream.next(); if (singleline && stream.eol()) { return outclass; } } else if (stream.match(delimiter)) { state.tokenize = tokenBase; return outclass; } else { stream.eat(/['"\/]/); } } if (singleline) { if (parserConf.singleLineStringErrors) { outclass = ERRORCLASS; } else { state.tokenize = tokenBase; } } return outclass; }; } function longComment(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^#]/); if (stream.match("###")) { state.tokenize = tokenBase; break; } stream.eatWhile("#"); } return "comment"; } function indent(stream, state, type) { type = type || "coffee"; var offset = 0, align = false, alignOffset = null; for (var scope = state.scope; scope; scope = scope.prev) { if (scope.type === "coffee" || scope.type == "}") { offset = scope.offset + conf.indentUnit; break; } } if (type !== "coffee") { align = null; alignOffset = stream.column() + stream.current().length; } else if (state.scope.align) { state.scope.align = false; } state.scope = { offset: offset, type: type, prev: state.scope, align: align, alignOffset: alignOffset }; } function dedent(stream, state) { if (!state.scope.prev) return; if (state.scope.type === "coffee") { var _indent = stream.indentation(); var matched = false; for (var scope = state.scope; scope; scope = scope.prev) { if (_indent === scope.offset) { matched = true; break; } } if (!matched) { return true; } while (state.scope.prev && state.scope.offset !== _indent) { state.scope = state.scope.prev; } return false; } else { state.scope = state.scope.prev; return false; } } function tokenLexer(stream, state) { var style = state.tokenize(stream, state); var current = stream.current(); // Handle scope changes. if (current === "return") { state.dedent = true; } if (((current === "->" || current === "=>") && stream.eol()) || style === "indent") { indent(stream, state); } var delimiter_index = "[({".indexOf(current); if (delimiter_index !== -1) { indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); } if (indentKeywords.exec(current)){ indent(stream, state); } if (current == "then"){ dedent(stream, state); } if (style === "dedent") { if (dedent(stream, state)) { return ERRORCLASS; } } delimiter_index = "])}".indexOf(current); if (delimiter_index !== -1) { while (state.scope.type == "coffee" && state.scope.prev) state.scope = state.scope.prev; if (state.scope.type == current) state.scope = state.scope.prev; } if (state.dedent && stream.eol()) { if (state.scope.type == "coffee" && state.scope.prev) state.scope = state.scope.prev; state.dedent = false; } return style; } var external = { startState: function(basecolumn) { return { tokenize: tokenBase, scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false}, prop: false, dedent: 0 }; }, token: function(stream, state) { var fillAlign = state.scope.align === null && state.scope; if (fillAlign && stream.sol()) fillAlign.align = false; var style = tokenLexer(stream, state); if (style && style != "comment") { if (fillAlign) fillAlign.align = true; state.prop = style == "punctuation" && stream.current() == "." } return style; }, indent: function(state, text) { if (state.tokenize != tokenBase) return 0; var scope = state.scope; var closer = text && "])}".indexOf(text.charAt(0)) > -1; if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev; var closes = closer && scope.type === text.charAt(0); if (scope.align) return scope.alignOffset - (closes ? 1 : 0); else return (closes ? scope.prev : scope).offset; }, lineComment: "#", fold: "indent" }; return external; }); // IANA registered media type // https://www.iana.org/assignments/media-types/ CodeMirror.defineMIME("application/vnd.coffeescript", "coffeescript"); CodeMirror.defineMIME("text/x-coffeescript", "coffeescript"); CodeMirror.defineMIME("text/coffeescript", "coffeescript"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/coffeescript/index.html ================================================ CodeMirror: CoffeeScript mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • CoffeeScript

    CoffeeScript mode

    MIME types defined: application/vnd.coffeescript, text/coffeescript, text/x-coffeescript.

    The CoffeeScript mode was written by Jeff Pickhardt.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/commonlisp/commonlisp.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("commonlisp", function (config) { var specialForm = /^(block|let*|return-from|catch|load-time-value|setq|eval-when|locally|symbol-macrolet|flet|macrolet|tagbody|function|multiple-value-call|the|go|multiple-value-prog1|throw|if|progn|unwind-protect|labels|progv|let|quote)$/; var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/; var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/; var symbol = /[^\s'`,@()\[\]";]/; var type; function readSym(stream) { var ch; while (ch = stream.next()) { if (ch == "\\") stream.next(); else if (!symbol.test(ch)) { stream.backUp(1); break; } } return stream.current(); } function base(stream, state) { if (stream.eatSpace()) {type = "ws"; return null;} if (stream.match(numLiteral)) return "number"; var ch = stream.next(); if (ch == "\\") ch = stream.next(); if (ch == '"') return (state.tokenize = inString)(stream, state); else if (ch == "(") { type = "open"; return "bracket"; } else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; } else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; } else if (/['`,@]/.test(ch)) return null; else if (ch == "|") { if (stream.skipTo("|")) { stream.next(); return "symbol"; } else { stream.skipToEnd(); return "error"; } } else if (ch == "#") { var ch = stream.next(); if (ch == "(") { type = "open"; return "bracket"; } else if (/[+\-=\.']/.test(ch)) return null; else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null; else if (ch == "|") return (state.tokenize = inComment)(stream, state); else if (ch == ":") { readSym(stream); return "meta"; } else if (ch == "\\") { stream.next(); readSym(stream); return "string-2" } else return "error"; } else { var name = readSym(stream); if (name == ".") return null; type = "symbol"; if (name == "nil" || name == "t" || name.charAt(0) == ":") return "atom"; if (state.lastType == "open" && (specialForm.test(name) || assumeBody.test(name))) return "keyword"; if (name.charAt(0) == "&") return "variable-2"; return "variable"; } } function inString(stream, state) { var escaped = false, next; while (next = stream.next()) { if (next == '"' && !escaped) { state.tokenize = base; break; } escaped = !escaped && next == "\\"; } return "string"; } function inComment(stream, state) { var next, last; while (next = stream.next()) { if (next == "#" && last == "|") { state.tokenize = base; break; } last = next; } type = "ws"; return "comment"; } return { startState: function () { return {ctx: {prev: null, start: 0, indentTo: 0}, lastType: null, tokenize: base}; }, token: function (stream, state) { if (stream.sol() && typeof state.ctx.indentTo != "number") state.ctx.indentTo = state.ctx.start + 1; type = null; var style = state.tokenize(stream, state); if (type != "ws") { if (state.ctx.indentTo == null) { if (type == "symbol" && assumeBody.test(stream.current())) state.ctx.indentTo = state.ctx.start + config.indentUnit; else state.ctx.indentTo = "next"; } else if (state.ctx.indentTo == "next") { state.ctx.indentTo = stream.column(); } state.lastType = type; } if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; else if (type == "close") state.ctx = state.ctx.prev || state.ctx; return style; }, indent: function (state, _textAfter) { var i = state.ctx.indentTo; return typeof i == "number" ? i : state.ctx.start + 1; }, closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;", fold: "brace-paren", blockCommentStart: "#|", blockCommentEnd: "|#" }; }); CodeMirror.defineMIME("text/x-common-lisp", "commonlisp"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/commonlisp/index.html ================================================ CodeMirror: Common Lisp mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Common Lisp

    Common Lisp mode

    MIME types defined: text/x-common-lisp.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/crystal/crystal.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("crystal", function(config) { function wordRegExp(words, end) { return new RegExp((end ? "" : "^") + "(?:" + words.join("|") + ")" + (end ? "$" : "\\b")); } function chain(tokenize, stream, state) { state.tokenize.push(tokenize); return tokenize(stream, state); } var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/; var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/; var indexingOperators = /^(?:\[\][?=]?)/; var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/; var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; var keywords = wordRegExp([ "abstract", "alias", "as", "asm", "begin", "break", "case", "class", "def", "do", "else", "elsif", "end", "ensure", "enum", "extend", "for", "fun", "if", "include", "instance_sizeof", "lib", "macro", "module", "next", "of", "out", "pointerof", "private", "protected", "rescue", "return", "require", "select", "sizeof", "struct", "super", "then", "type", "typeof", "uninitialized", "union", "unless", "until", "when", "while", "with", "yield", "__DIR__", "__END_LINE__", "__FILE__", "__LINE__" ]); var atomWords = wordRegExp(["true", "false", "nil", "self"]); var indentKeywordsArray = [ "def", "fun", "macro", "class", "module", "struct", "lib", "enum", "union", "do", "for" ]; var indentKeywords = wordRegExp(indentKeywordsArray); var indentExpressionKeywordsArray = ["if", "unless", "case", "while", "until", "begin", "then"]; var indentExpressionKeywords = wordRegExp(indentExpressionKeywordsArray); var dedentKeywordsArray = ["end", "else", "elsif", "rescue", "ensure"]; var dedentKeywords = wordRegExp(dedentKeywordsArray); var dedentPunctualsArray = ["\\)", "\\}", "\\]"]; var dedentPunctuals = new RegExp("^(?:" + dedentPunctualsArray.join("|") + ")$"); var nextTokenizer = { "def": tokenFollowIdent, "fun": tokenFollowIdent, "macro": tokenMacroDef, "class": tokenFollowType, "module": tokenFollowType, "struct": tokenFollowType, "lib": tokenFollowType, "enum": tokenFollowType, "union": tokenFollowType }; var matching = {"[": "]", "{": "}", "(": ")", "<": ">"}; function tokenBase(stream, state) { if (stream.eatSpace()) { return null; } // Macros if (state.lastToken != "\\" && stream.match("{%", false)) { return chain(tokenMacro("%", "%"), stream, state); } if (state.lastToken != "\\" && stream.match("{{", false)) { return chain(tokenMacro("{", "}"), stream, state); } // Comments if (stream.peek() == "#") { stream.skipToEnd(); return "comment"; } // Variables and keywords var matched; if (stream.match(idents)) { stream.eat(/[?!]/); matched = stream.current(); if (stream.eat(":")) { return "atom"; } else if (state.lastToken == ".") { return "property"; } else if (keywords.test(matched)) { if (indentKeywords.test(matched)) { if (!(matched == "fun" && state.blocks.indexOf("lib") >= 0) && !(matched == "def" && state.lastToken == "abstract")) { state.blocks.push(matched); state.currentIndent += 1; } } else if ((state.lastStyle == "operator" || !state.lastStyle) && indentExpressionKeywords.test(matched)) { state.blocks.push(matched); state.currentIndent += 1; } else if (matched == "end") { state.blocks.pop(); state.currentIndent -= 1; } if (nextTokenizer.hasOwnProperty(matched)) { state.tokenize.push(nextTokenizer[matched]); } return "keyword"; } else if (atomWords.test(matched)) { return "atom"; } return "variable"; } // Class variables and instance variables // or attributes if (stream.eat("@")) { if (stream.peek() == "[") { return chain(tokenNest("[", "]", "meta"), stream, state); } stream.eat("@"); stream.match(idents) || stream.match(types); return "variable-2"; } // Constants and types if (stream.match(types)) { return "tag"; } // Symbols or ':' operator if (stream.eat(":")) { if (stream.eat("\"")) { return chain(tokenQuote("\"", "atom", false), stream, state); } else if (stream.match(idents) || stream.match(types) || stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) { return "atom"; } stream.eat(":"); return "operator"; } // Strings if (stream.eat("\"")) { return chain(tokenQuote("\"", "string", true), stream, state); } // Strings or regexps or macro variables or '%' operator if (stream.peek() == "%") { var style = "string"; var embed = true; var delim; if (stream.match("%r")) { // Regexps style = "string-2"; delim = stream.next(); } else if (stream.match("%w")) { embed = false; delim = stream.next(); } else if (stream.match("%q")) { embed = false; delim = stream.next(); } else { if(delim = stream.match(/^%([^\w\s=])/)) { delim = delim[1]; } else if (stream.match(/^%[a-zA-Z_\u009F-\uFFFF][\w\u009F-\uFFFF]*/)) { // Macro variables return "meta"; } else if (stream.eat('%')) { // '%' operator return "operator"; } } if (matching.hasOwnProperty(delim)) { delim = matching[delim]; } return chain(tokenQuote(delim, style, embed), stream, state); } // Here Docs if (matched = stream.match(/^<<-('?)([A-Z]\w*)\1/)) { return chain(tokenHereDoc(matched[2], !matched[1]), stream, state) } // Characters if (stream.eat("'")) { stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/); stream.eat("'"); return "atom"; } // Numbers if (stream.eat("0")) { if (stream.eat("x")) { stream.match(/^[0-9a-fA-F_]+/); } else if (stream.eat("o")) { stream.match(/^[0-7_]+/); } else if (stream.eat("b")) { stream.match(/^[01_]+/); } return "number"; } if (stream.eat(/^\d/)) { stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?/); return "number"; } // Operators if (stream.match(operators)) { stream.eat("="); // Operators can follow assign symbol. return "operator"; } if (stream.match(conditionalOperators) || stream.match(anotherOperators)) { return "operator"; } // Parens and braces if (matched = stream.match(/[({[]/, false)) { matched = matched[0]; return chain(tokenNest(matched, matching[matched], null), stream, state); } // Escapes if (stream.eat("\\")) { stream.next(); return "meta"; } stream.next(); return null; } function tokenNest(begin, end, style, started) { return function (stream, state) { if (!started && stream.match(begin)) { state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true); state.currentIndent += 1; return style; } var nextStyle = tokenBase(stream, state); if (stream.current() === end) { state.tokenize.pop(); state.currentIndent -= 1; nextStyle = style; } return nextStyle; }; } function tokenMacro(begin, end, started) { return function (stream, state) { if (!started && stream.match("{" + begin)) { state.currentIndent += 1; state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true); return "meta"; } if (stream.match(end + "}")) { state.currentIndent -= 1; state.tokenize.pop(); return "meta"; } return tokenBase(stream, state); }; } function tokenMacroDef(stream, state) { if (stream.eatSpace()) { return null; } var matched; if (matched = stream.match(idents)) { if (matched == "def") { return "keyword"; } stream.eat(/[?!]/); } state.tokenize.pop(); return "def"; } function tokenFollowIdent(stream, state) { if (stream.eatSpace()) { return null; } if (stream.match(idents)) { stream.eat(/[!?]/); } else { stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators); } state.tokenize.pop(); return "def"; } function tokenFollowType(stream, state) { if (stream.eatSpace()) { return null; } stream.match(types); state.tokenize.pop(); return "def"; } function tokenQuote(end, style, embed) { return function (stream, state) { var escaped = false; while (stream.peek()) { if (!escaped) { if (stream.match("{%", false)) { state.tokenize.push(tokenMacro("%", "%")); return style; } if (stream.match("{{", false)) { state.tokenize.push(tokenMacro("{", "}")); return style; } if (embed && stream.match("#{", false)) { state.tokenize.push(tokenNest("#{", "}", "meta")); return style; } var ch = stream.next(); if (ch == end) { state.tokenize.pop(); return style; } escaped = embed && ch == "\\"; } else { stream.next(); escaped = false; } } return style; }; } function tokenHereDoc(phrase, embed) { return function (stream, state) { if (stream.sol()) { stream.eatSpace() if (stream.match(phrase)) { state.tokenize.pop(); return "string"; } } var escaped = false; while (stream.peek()) { if (!escaped) { if (stream.match("{%", false)) { state.tokenize.push(tokenMacro("%", "%")); return "string"; } if (stream.match("{{", false)) { state.tokenize.push(tokenMacro("{", "}")); return "string"; } if (embed && stream.match("#{", false)) { state.tokenize.push(tokenNest("#{", "}", "meta")); return "string"; } escaped = embed && stream.next() == "\\"; } else { stream.next(); escaped = false; } } return "string"; } } return { startState: function () { return { tokenize: [tokenBase], currentIndent: 0, lastToken: null, lastStyle: null, blocks: [] }; }, token: function (stream, state) { var style = state.tokenize[state.tokenize.length - 1](stream, state); var token = stream.current(); if (style && style != "comment") { state.lastToken = token; state.lastStyle = style; } return style; }, indent: function (state, textAfter) { textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, ""); if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) { return config.indentUnit * (state.currentIndent - 1); } return config.indentUnit * state.currentIndent; }, fold: "indent", electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true), lineComment: '#' }; }); CodeMirror.defineMIME("text/x-crystal", "crystal"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/crystal/index.html ================================================ CodeMirror: Crystal mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Crystal

    Crystal mode

    MIME types defined: text/x-crystal.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/css.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("css", function(config, parserConfig) { var inline = parserConfig.inline if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css"); var indentUnit = config.indentUnit, tokenHooks = parserConfig.tokenHooks, documentTypes = parserConfig.documentTypes || {}, mediaTypes = parserConfig.mediaTypes || {}, mediaFeatures = parserConfig.mediaFeatures || {}, mediaValueKeywords = parserConfig.mediaValueKeywords || {}, propertyKeywords = parserConfig.propertyKeywords || {}, nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {}, fontProperties = parserConfig.fontProperties || {}, counterDescriptors = parserConfig.counterDescriptors || {}, colorKeywords = parserConfig.colorKeywords || {}, valueKeywords = parserConfig.valueKeywords || {}, allowNested = parserConfig.allowNested, lineComment = parserConfig.lineComment, supportsAtComponent = parserConfig.supportsAtComponent === true, highlightNonStandardPropertyKeywords = config.highlightNonStandardPropertyKeywords !== false; var type, override; function ret(style, tp) { type = tp; return style; } // Tokenizers function tokenBase(stream, state) { var ch = stream.next(); if (tokenHooks[ch]) { var result = tokenHooks[ch](stream, state); if (result !== false) return result; } if (ch == "@") { stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current()); } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) { return ret(null, "compare"); } else if (ch == "\"" || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } else if (ch == "#") { stream.eatWhile(/[\w\\\-]/); return ret("atom", "hash"); } else if (ch == "!") { stream.match(/^\s*\w*/); return ret("keyword", "important"); } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } else if (ch === "-") { if (/[\d.]/.test(stream.peek())) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } else if (stream.match(/^-[\w\\\-]*/)) { stream.eatWhile(/[\w\\\-]/); if (stream.match(/^\s*:/, false)) return ret("variable-2", "variable-definition"); return ret("variable-2", "variable"); } else if (stream.match(/^\w+-/)) { return ret("meta", "meta"); } } else if (/[,+>*\/]/.test(ch)) { return ret(null, "select-op"); } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { return ret("qualifier", "qualifier"); } else if (/[:;{}\[\]\(\)]/.test(ch)) { return ret(null, ch); } else if (stream.match(/^[\w-.]+(?=\()/)) { if (/^(url(-prefix)?|domain|regexp)$/i.test(stream.current())) { state.tokenize = tokenParenthesized; } return ret("variable callee", "variable"); } else if (/[\w\\\-]/.test(ch)) { stream.eatWhile(/[\w\\\-]/); return ret("property", "word"); } else { return ret(null, null); } } function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { if (quote == ")") stream.backUp(1); break; } escaped = !escaped && ch == "\\"; } if (ch == quote || !escaped && quote != ")") state.tokenize = null; return ret("string", "string"); }; } function tokenParenthesized(stream, state) { stream.next(); // Must be '(' if (!stream.match(/^\s*[\"\')]/, false)) state.tokenize = tokenString(")"); else state.tokenize = null; return ret(null, "("); } // Context management function Context(type, indent, prev) { this.type = type; this.indent = indent; this.prev = prev; } function pushContext(state, stream, type, indent) { state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context); return type; } function popContext(state) { if (state.context.prev) state.context = state.context.prev; return state.context.type; } function pass(type, stream, state) { return states[state.context.type](type, stream, state); } function popAndPass(type, stream, state, n) { for (var i = n || 1; i > 0; i--) state.context = state.context.prev; return pass(type, stream, state); } // Parser function wordAsValue(stream) { var word = stream.current().toLowerCase(); if (valueKeywords.hasOwnProperty(word)) override = "atom"; else if (colorKeywords.hasOwnProperty(word)) override = "keyword"; else override = "variable"; } var states = {}; states.top = function(type, stream, state) { if (type == "{") { return pushContext(state, stream, "block"); } else if (type == "}" && state.context.prev) { return popContext(state); } else if (supportsAtComponent && /@component/i.test(type)) { return pushContext(state, stream, "atComponentBlock"); } else if (/^@(-moz-)?document$/i.test(type)) { return pushContext(state, stream, "documentTypes"); } else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) { return pushContext(state, stream, "atBlock"); } else if (/^@(font-face|counter-style)/i.test(type)) { state.stateArg = type; return "restricted_atBlock_before"; } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) { return "keyframes"; } else if (type && type.charAt(0) == "@") { return pushContext(state, stream, "at"); } else if (type == "hash") { override = "builtin"; } else if (type == "word") { override = "tag"; } else if (type == "variable-definition") { return "maybeprop"; } else if (type == "interpolation") { return pushContext(state, stream, "interpolation"); } else if (type == ":") { return "pseudo"; } else if (allowNested && type == "(") { return pushContext(state, stream, "parens"); } return state.context.type; }; states.block = function(type, stream, state) { if (type == "word") { var word = stream.current().toLowerCase(); if (propertyKeywords.hasOwnProperty(word)) { override = "property"; return "maybeprop"; } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) { override = highlightNonStandardPropertyKeywords ? "string-2" : "property"; return "maybeprop"; } else if (allowNested) { override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag"; return "block"; } else { override += " error"; return "maybeprop"; } } else if (type == "meta") { return "block"; } else if (!allowNested && (type == "hash" || type == "qualifier")) { override = "error"; return "block"; } else { return states.top(type, stream, state); } }; states.maybeprop = function(type, stream, state) { if (type == ":") return pushContext(state, stream, "prop"); return pass(type, stream, state); }; states.prop = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "{" && allowNested) return pushContext(state, stream, "propBlock"); if (type == "}" || type == "{") return popAndPass(type, stream, state); if (type == "(") return pushContext(state, stream, "parens"); if (type == "hash" && !/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(stream.current())) { override += " error"; } else if (type == "word") { wordAsValue(stream); } else if (type == "interpolation") { return pushContext(state, stream, "interpolation"); } return "prop"; }; states.propBlock = function(type, _stream, state) { if (type == "}") return popContext(state); if (type == "word") { override = "property"; return "maybeprop"; } return state.context.type; }; states.parens = function(type, stream, state) { if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == ")") return popContext(state); if (type == "(") return pushContext(state, stream, "parens"); if (type == "interpolation") return pushContext(state, stream, "interpolation"); if (type == "word") wordAsValue(stream); return "parens"; }; states.pseudo = function(type, stream, state) { if (type == "meta") return "pseudo"; if (type == "word") { override = "variable-3"; return state.context.type; } return pass(type, stream, state); }; states.documentTypes = function(type, stream, state) { if (type == "word" && documentTypes.hasOwnProperty(stream.current())) { override = "tag"; return state.context.type; } else { return states.atBlock(type, stream, state); } }; states.atBlock = function(type, stream, state) { if (type == "(") return pushContext(state, stream, "atBlock_parens"); if (type == "}" || type == ";") return popAndPass(type, stream, state); if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top"); if (type == "interpolation") return pushContext(state, stream, "interpolation"); if (type == "word") { var word = stream.current().toLowerCase(); if (word == "only" || word == "not" || word == "and" || word == "or") override = "keyword"; else if (mediaTypes.hasOwnProperty(word)) override = "attribute"; else if (mediaFeatures.hasOwnProperty(word)) override = "property"; else if (mediaValueKeywords.hasOwnProperty(word)) override = "keyword"; else if (propertyKeywords.hasOwnProperty(word)) override = "property"; else if (nonStandardPropertyKeywords.hasOwnProperty(word)) override = highlightNonStandardPropertyKeywords ? "string-2" : "property"; else if (valueKeywords.hasOwnProperty(word)) override = "atom"; else if (colorKeywords.hasOwnProperty(word)) override = "keyword"; else override = "error"; } return state.context.type; }; states.atComponentBlock = function(type, stream, state) { if (type == "}") return popAndPass(type, stream, state); if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false); if (type == "word") override = "error"; return state.context.type; }; states.atBlock_parens = function(type, stream, state) { if (type == ")") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state, 2); return states.atBlock(type, stream, state); }; states.restricted_atBlock_before = function(type, stream, state) { if (type == "{") return pushContext(state, stream, "restricted_atBlock"); if (type == "word" && state.stateArg == "@counter-style") { override = "variable"; return "restricted_atBlock_before"; } return pass(type, stream, state); }; states.restricted_atBlock = function(type, stream, state) { if (type == "}") { state.stateArg = null; return popContext(state); } if (type == "word") { if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) || (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase()))) override = "error"; else override = "property"; return "maybeprop"; } return "restricted_atBlock"; }; states.keyframes = function(type, stream, state) { if (type == "word") { override = "variable"; return "keyframes"; } if (type == "{") return pushContext(state, stream, "top"); return pass(type, stream, state); }; states.at = function(type, stream, state) { if (type == ";") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == "word") override = "tag"; else if (type == "hash") override = "builtin"; return "at"; }; states.interpolation = function(type, stream, state) { if (type == "}") return popContext(state); if (type == "{" || type == ";") return popAndPass(type, stream, state); if (type == "word") override = "variable"; else if (type != "variable" && type != "(" && type != ")") override = "error"; return "interpolation"; }; return { startState: function(base) { return {tokenize: null, state: inline ? "block" : "top", stateArg: null, context: new Context(inline ? "block" : "top", base || 0, null)}; }, token: function(stream, state) { if (!state.tokenize && stream.eatSpace()) return null; var style = (state.tokenize || tokenBase)(stream, state); if (style && typeof style == "object") { type = style[1]; style = style[0]; } override = style; if (type != "comment") state.state = states[state.state](type, stream, state); return override; }, indent: function(state, textAfter) { var cx = state.context, ch = textAfter && textAfter.charAt(0); var indent = cx.indent; if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev; if (cx.prev) { if (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "restricted_atBlock")) { // Resume indentation from parent context. cx = cx.prev; indent = cx.indent; } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") || ch == "{" && (cx.type == "at" || cx.type == "atBlock")) { // Dedent relative to current context. indent = Math.max(0, cx.indent - indentUnit); } } return indent; }, electricChars: "}", blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", lineComment: lineComment, fold: "brace" }; }); function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) { keys[array[i].toLowerCase()] = true; } return keys; } var documentTypes_ = [ "domain", "regexp", "url", "url-prefix" ], documentTypes = keySet(documentTypes_); var mediaTypes_ = [ "all", "aural", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "embossed" ], mediaTypes = keySet(mediaTypes_); var mediaFeatures_ = [ "width", "min-width", "max-width", "height", "min-height", "max-height", "device-width", "min-device-width", "max-device-width", "device-height", "min-device-height", "max-device-height", "aspect-ratio", "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", "max-color", "color-index", "min-color-index", "max-color-index", "monochrome", "min-monochrome", "max-monochrome", "resolution", "min-resolution", "max-resolution", "scan", "grid", "orientation", "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio", "pointer", "any-pointer", "hover", "any-hover", "prefers-color-scheme", "dynamic-range", "video-dynamic-range" ], mediaFeatures = keySet(mediaFeatures_); var mediaValueKeywords_ = [ "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover", "interlace", "progressive", "dark", "light", "standard", "high" ], mediaValueKeywords = keySet(mediaValueKeywords_); var propertyKeywords_ = [ "align-content", "align-items", "align-self", "alignment-adjust", "alignment-baseline", "all", "anchor-point", "animation", "animation-delay", "animation-direction", "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "appearance", "azimuth", "backdrop-filter", "backface-visibility", "background", "background-attachment", "background-blend-mode", "background-clip", "background-color", "background-image", "background-origin", "background-position", "background-position-x", "background-position-y", "background-repeat", "background-size", "baseline-shift", "binding", "bleed", "block-size", "bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target", "border", "border-bottom", "border-bottom-color", "border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-collapse", "border-color", "border-image", "border-image-outset", "border-image-repeat", "border-image-slice", "border-image-source", "border-image-width", "border-left", "border-left-color", "border-left-style", "border-left-width", "border-radius", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style", "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", "caption-side", "caret-color", "clear", "clip", "color", "color-profile", "column-count", "column-fill", "column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "contain", "content", "counter-increment", "counter-reset", "crop", "cue", "cue-after", "cue-before", "cursor", "direction", "display", "dominant-baseline", "drop-initial-after-adjust", "drop-initial-after-align", "drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size", "drop-initial-value", "elevation", "empty-cells", "fit", "fit-content", "fit-position", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into", "font", "font-family", "font-feature-settings", "font-kerning", "font-language-override", "font-optical-sizing", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-synthesis", "font-variant", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-variant-position", "font-variation-settings", "font-weight", "gap", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap", "grid-column-start", "grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns", "grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon", "image-orientation", "image-rendering", "image-resolution", "inline-box-align", "inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline", "inset-inline-end", "inset-inline-start", "isolation", "justify-content", "justify-items", "justify-self", "left", "letter-spacing", "line-break", "line-height", "line-height-step", "line-stacking", "line-stacking-ruby", "line-stacking-shift", "line-stacking-strategy", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "marks", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed", "marquee-style", "mask-clip", "mask-composite", "mask-image", "mask-mode", "mask-origin", "mask-position", "mask-repeat", "mask-size","mask-type", "max-block-size", "max-height", "max-inline-size", "max-width", "min-block-size", "min-height", "min-inline-size", "min-width", "mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right", "nav-up", "object-fit", "object-position", "offset", "offset-anchor", "offset-distance", "offset-path", "offset-position", "offset-rotate", "opacity", "order", "orphans", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page", "page-break-after", "page-break-before", "page-break-inside", "page-policy", "pause", "pause-after", "pause-before", "perspective", "perspective-origin", "pitch", "pitch-range", "place-content", "place-items", "place-self", "play-during", "position", "presentation-level", "punctuation-trim", "quotes", "region-break-after", "region-break-before", "region-break-inside", "region-fragment", "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", "right", "rotate", "rotation", "rotation-point", "row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span", "scale", "scroll-behavior", "scroll-margin", "scroll-margin-block", "scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom", "scroll-margin-inline", "scroll-margin-inline-end", "scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right", "scroll-margin-top", "scroll-padding", "scroll-padding-block", "scroll-padding-block-end", "scroll-padding-block-start", "scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end", "scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right", "scroll-padding-top", "scroll-snap-align", "scroll-snap-type", "shape-image-threshold", "shape-inside", "shape-margin", "shape-outside", "size", "speak", "speak-as", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", "tab-size", "table-layout", "target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-combine-upright", "text-decoration", "text-decoration-color", "text-decoration-line", "text-decoration-skip", "text-decoration-skip-ink", "text-decoration-style", "text-emphasis", "text-emphasis-color", "text-emphasis-position", "text-emphasis-style", "text-height", "text-indent", "text-justify", "text-orientation", "text-outline", "text-overflow", "text-rendering", "text-shadow", "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position", "text-wrap", "top", "touch-action", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "translate", "unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "widows", "width", "will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index", // SVG-specific "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color", "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events", "color-interpolation", "color-interpolation-filters", "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering", "marker", "marker-end", "marker-mid", "marker-start", "paint-order", "shape-rendering", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering", "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal", "glyph-orientation-vertical", "text-anchor", "writing-mode", ], propertyKeywords = keySet(propertyKeywords_); var nonStandardPropertyKeywords_ = [ "accent-color", "aspect-ratio", "border-block", "border-block-color", "border-block-end", "border-block-end-color", "border-block-end-style", "border-block-end-width", "border-block-start", "border-block-start-color", "border-block-start-style", "border-block-start-width", "border-block-style", "border-block-width", "border-inline", "border-inline-color", "border-inline-end", "border-inline-end-color", "border-inline-end-style", "border-inline-end-width", "border-inline-start", "border-inline-start-color", "border-inline-start-style", "border-inline-start-width", "border-inline-style", "border-inline-width", "content-visibility", "margin-block", "margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end", "margin-inline-start", "overflow-anchor", "overscroll-behavior", "padding-block", "padding-block-end", "padding-block-start", "padding-inline", "padding-inline-end", "padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color", "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color", "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color", "scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom" ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_); var fontProperties_ = [ "font-display", "font-family", "src", "unicode-range", "font-variant", "font-feature-settings", "font-stretch", "font-weight", "font-style" ], fontProperties = keySet(fontProperties_); var counterDescriptors_ = [ "additive-symbols", "fallback", "negative", "pad", "prefix", "range", "speak-as", "suffix", "symbols", "system" ], counterDescriptors = keySet(counterDescriptors_); var colorKeywords_ = [ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen" ], colorKeywords = keySet(colorKeywords_); var valueKeywords_ = [ "above", "absolute", "activeborder", "additive", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page", "avoid-region", "axis-pan", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink", "block", "block-axis", "blur", "bold", "bolder", "border", "border-box", "both", "bottom", "break", "break-all", "break-word", "brightness", "bullets", "button", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse", "compact", "condensed", "conic-gradient", "contain", "content", "contents", "content-box", "context-menu", "continuous", "contrast", "copy", "counter", "counters", "cover", "crop", "cross", "crosshair", "cubic-bezier", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal", "decimal-leading-zero", "default", "default-button", "dense", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "difference", "disc", "discard", "disclosure-closed", "disclosure-open", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "drop-shadow", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fill-box", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes", "forwards", "from", "geometricPrecision", "georgian", "grayscale", "graytext", "grid", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew", "help", "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "hue-rotate", "icon", "ignore", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "japanese-formal", "japanese-informal", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer", "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal", "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten", "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "manipulation", "match", "matrix", "matrix3d", "media-play-button", "media-slider", "media-sliderthumb", "media-volume-slider", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "myanmar", "n-resize", "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote", "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", "outside", "outside-shape", "overlay", "overline", "padding", "padding-box", "painted", "page", "paused", "persian", "perspective", "pinch-zoom", "plus-darker", "plus-lighter", "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", "radial-gradient", "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region", "relative", "repeat", "repeating-linear-gradient", "repeating-radial-gradient", "repeating-conic-gradient", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running", "s-resize", "sans-serif", "saturate", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end", "semi-condensed", "semi-expanded", "separate", "sepia", "serif", "show", "sidama", "simp-chinese-formal", "simp-chinese-informal", "single", "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "stroke-box", "sub", "subpixel-antialiased", "svg_masks", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", "tamil", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", "trad-chinese-formal", "trad-chinese-informal", "transform", "translate", "translate3d", "translateX", "translateY", "translateZ", "transparent", "ultra-condensed", "ultra-expanded", "underline", "unidirectional-pan", "unset", "up", "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "var", "vertical", "vertical-text", "view-box", "visible", "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "wider", "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor", "xx-large", "xx-small" ], valueKeywords = keySet(valueKeywords_); var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_) .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_) .concat(valueKeywords_); CodeMirror.registerHelper("hintWords", "css", allWords); function tokenCComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (maybeEnd && ch == "/") { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return ["comment", "comment"]; } CodeMirror.defineMIME("text/css", { documentTypes: documentTypes, mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, mediaValueKeywords: mediaValueKeywords, propertyKeywords: propertyKeywords, nonStandardPropertyKeywords: nonStandardPropertyKeywords, fontProperties: fontProperties, counterDescriptors: counterDescriptors, colorKeywords: colorKeywords, valueKeywords: valueKeywords, tokenHooks: { "/": function(stream, state) { if (!stream.eat("*")) return false; state.tokenize = tokenCComment; return tokenCComment(stream, state); } }, name: "css" }); CodeMirror.defineMIME("text/x-scss", { mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, mediaValueKeywords: mediaValueKeywords, propertyKeywords: propertyKeywords, nonStandardPropertyKeywords: nonStandardPropertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, fontProperties: fontProperties, allowNested: true, lineComment: "//", tokenHooks: { "/": function(stream, state) { if (stream.eat("/")) { stream.skipToEnd(); return ["comment", "comment"]; } else if (stream.eat("*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); } else { return ["operator", "operator"]; } }, ":": function(stream) { if (stream.match(/^\s*\{/, false)) return [null, null] return false; }, "$": function(stream) { stream.match(/^[\w-]+/); if (stream.match(/^\s*:/, false)) return ["variable-2", "variable-definition"]; return ["variable-2", "variable"]; }, "#": function(stream) { if (!stream.eat("{")) return false; return [null, "interpolation"]; } }, name: "css", helperType: "scss" }); CodeMirror.defineMIME("text/x-less", { mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, mediaValueKeywords: mediaValueKeywords, propertyKeywords: propertyKeywords, nonStandardPropertyKeywords: nonStandardPropertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, fontProperties: fontProperties, allowNested: true, lineComment: "//", tokenHooks: { "/": function(stream, state) { if (stream.eat("/")) { stream.skipToEnd(); return ["comment", "comment"]; } else if (stream.eat("*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); } else { return ["operator", "operator"]; } }, "@": function(stream) { if (stream.eat("{")) return [null, "interpolation"]; if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false; stream.eatWhile(/[\w\\\-]/); if (stream.match(/^\s*:/, false)) return ["variable-2", "variable-definition"]; return ["variable-2", "variable"]; }, "&": function() { return ["atom", "atom"]; } }, name: "css", helperType: "less" }); CodeMirror.defineMIME("text/x-gss", { documentTypes: documentTypes, mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, nonStandardPropertyKeywords: nonStandardPropertyKeywords, fontProperties: fontProperties, counterDescriptors: counterDescriptors, colorKeywords: colorKeywords, valueKeywords: valueKeywords, supportsAtComponent: true, tokenHooks: { "/": function(stream, state) { if (!stream.eat("*")) return false; state.tokenize = tokenCComment; return tokenCComment(stream, state); } }, name: "css", helperType: "gss" }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/gss.html ================================================ CodeMirror: Closure Stylesheets (GSS) mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Closure Stylesheets (GSS)

    Closure Stylesheets (GSS) mode

    A mode for Closure Stylesheets (GSS).

    MIME type defined: text/x-gss.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/gss_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { "use strict"; var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); } MT("atComponent", "[def @component] {", "[tag foo] {", " [property color]: [keyword black];", "}", "}"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/index.html ================================================ CodeMirror: CSS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • CSS

    CSS mode

    CSS mode supports this option:

    highlightNonStandardPropertyKeywords: boolean
    Whether to highlight non-standard CSS property keywords such as margin-inline or zoom (default: true).

    MIME types defined: text/css, text/x-scss (demo), text/x-less (demo).

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/less.html ================================================ CodeMirror: LESS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • LESS

    LESS mode

    The LESS mode is a sub-mode of the CSS mode (defined in css.js).

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/less_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { "use strict"; var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); } MT("variable", "[variable-2 @base]: [atom #f04615];", "[qualifier .class] {", " [property width]: [variable&callee percentage]([number 0.5]); [comment // returns `50%`]", " [property color]: [variable&callee saturate]([variable-2 @base], [number 5%]);", "}"); MT("amp", "[qualifier .child], [qualifier .sibling] {", " [qualifier .parent] [atom &] {", " [property color]: [keyword black];", " }", " [atom &] + [atom &] {", " [property color]: [keyword red];", " }", "}"); MT("mixin", "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {", " [property color]: [variable&callee darken]([variable-2 @color], [number 10%]);", "}", "[qualifier .mixin] ([variable light]; [variable-2 @color]) {", " [property color]: [variable&callee lighten]([variable-2 @color], [number 10%]);", "}", "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {", " [property display]: [atom block];", "}", "[variable-2 @switch]: [variable light];", "[qualifier .class] {", " [qualifier .mixin]([variable-2 @switch]; [atom #888]);", "}"); MT("nest", "[qualifier .one] {", " [def @media] ([property width]: [number 400px]) {", " [property font-size]: [number 1.2em];", " [def @media] [attribute print] [keyword and] [property color] {", " [property color]: [keyword blue];", " }", " }", "}"); MT("interpolation", ".@{[variable foo]} { [property font-weight]: [atom bold]; }"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/scss.html ================================================ CodeMirror: SCSS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • SCSS

    SCSS mode

    The SCSS mode is a sub-mode of the CSS mode (defined in css.js).

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/scss_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } MT('url_with_quotation', "[tag foo] { [property background]:[variable&callee url]([string test.jpg]) }"); MT('url_with_double_quotes', "[tag foo] { [property background]:[variable&callee url]([string \"test.jpg\"]) }"); MT('url_with_single_quotes', "[tag foo] { [property background]:[variable&callee url]([string \'test.jpg\']) }"); MT('string', "[def @import] [string \"compass/css3\"]"); MT('important_keyword', "[tag foo] { [property background]:[variable&callee url]([string \'test.jpg\']) [keyword !important] }"); MT('variable', "[variable-2 $blue]:[atom #333]"); MT('variable_as_attribute', "[tag foo] { [property color]:[variable-2 $blue] }"); MT('numbers', "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }"); MT('number_percentage', "[tag foo] { [property width]:[number 80%] }"); MT('selector', "[builtin #hello][qualifier .world]{}"); MT('singleline_comment', "[comment // this is a comment]"); MT('multiline_comment', "[comment /*foobar*/]"); MT('attribute_with_hyphen', "[tag foo] { [property font-size]:[number 10px] }"); MT('string_after_attribute', "[tag foo] { [property content]:[string \"::\"] }"); MT('directives', "[def @include] [qualifier .mixin]"); MT('basic_structure', "[tag p] { [property background]:[keyword red]; }"); MT('nested_structure', "[tag p] { [tag a] { [property color]:[keyword red]; } }"); MT('mixin', "[def @mixin] [tag table-base] {}"); MT('number_without_semicolon', "[tag p] {[property width]:[number 12]}", "[tag a] {[property color]:[keyword red];}"); MT('atom_in_nested_block', "[tag p] { [tag a] { [property color]:[atom #000]; } }"); MT('interpolation_in_property', "[tag foo] { #{[variable-2 $hello]}:[number 2]; }"); MT('interpolation_in_selector', "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }"); MT('interpolation_error', "[tag foo]#{[variable foo]} { [property color]:[atom #000]; }"); MT("divide_operator", "[tag foo] { [property width]:[number 4] [operator /] [number 2] }"); MT('nested_structure_with_id_selector', "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); MT('indent_mixin', "[def @mixin] [tag container] (", " [variable-2 $a]: [number 10],", " [variable-2 $b]: [number 10])", "{}"); MT('indent_nested', "[tag foo] {", " [tag bar] {", " }", "}"); MT('indent_parentheses', "[tag foo] {", " [property color]: [variable&callee darken]([variable-2 $blue],", " [number 9%]);", "}"); MT('indent_vardef', "[variable-2 $name]:", " [string 'val'];", "[tag tag] {", " [tag inner] {", " [property margin]: [number 3px];", " }", "}"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/css/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "css"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Error, because "foobarhello" is neither a known type or property, but // property was expected (after "and"), and it should be in parentheses. MT("atMediaUnknownType", "[def @media] [attribute screen] [keyword and] [error foobarhello] { }"); // Soft error, because "foobarhello" is not a known property or type. MT("atMediaUnknownProperty", "[def @media] [attribute screen] [keyword and] ([error foobarhello]) { }"); // Make sure nesting works with media queries MT("atMediaMaxWidthNested", "[def @media] [attribute screen] [keyword and] ([property max-width]: [number 25px]) { [tag foo] { } }"); MT("atMediaFeatureValueKeyword", "[def @media] ([property orientation]: [keyword landscape]) { }"); MT("atMediaUnknownFeatureValueKeyword", "[def @media] ([property orientation]: [error upsidedown]) { }"); MT("atMediaUppercase", "[def @MEDIA] ([property orienTAtion]: [keyword landScape]) { }"); MT("tagSelector", "[tag foo] { }"); MT("classSelector", "[qualifier .foo-bar_hello] { }"); MT("idSelector", "[builtin #foo] { [error #foo] }"); MT("tagSelectorUnclosed", "[tag foo] { [property margin]: [number 0] } [tag bar] { }"); MT("tagStringNoQuotes", "[tag foo] { [property font-family]: [variable hello] [variable world]; }"); MT("tagStringDouble", "[tag foo] { [property font-family]: [string \"hello world\"]; }"); MT("tagStringSingle", "[tag foo] { [property font-family]: [string 'hello world']; }"); MT("tagColorKeyword", "[tag foo] {", " [property color]: [keyword black];", " [property color]: [keyword navy];", " [property color]: [keyword yellow];", "}"); MT("tagColorHex3", "[tag foo] { [property background]: [atom #fff]; }"); MT("tagColorHex4", "[tag foo] { [property background]: [atom #ffff]; }"); MT("tagColorHex6", "[tag foo] { [property background]: [atom #ffffff]; }"); MT("tagColorHex8", "[tag foo] { [property background]: [atom #ffffffff]; }"); MT("tagColorHex5Invalid", "[tag foo] { [property background]: [atom&error #fffff]; }"); MT("tagColorHexInvalid", "[tag foo] { [property background]: [atom&error #ffg]; }"); MT("tagNegativeNumber", "[tag foo] { [property margin]: [number -5px]; }"); MT("tagPositiveNumber", "[tag foo] { [property padding]: [number 5px]; }"); MT("tagVendor", "[tag foo] { [meta -foo-][property box-sizing]: [meta -foo-][atom border-box]; }"); MT("tagBogusProperty", "[tag foo] { [property&error barhelloworld]: [number 0]; }"); MT("tagTwoProperties", "[tag foo] { [property margin]: [number 0]; [property padding]: [number 0]; }"); MT("tagTwoPropertiesURL", "[tag foo] { [property background]: [variable&callee url]([string //example.com/foo.png]); [property padding]: [number 0]; }"); MT("indent_tagSelector", "[tag strong], [tag em] {", " [property background]: [variable&callee rgba](", " [number 255], [number 255], [number 0], [number .2]", " );", "}"); MT("indent_atMedia", "[def @media] {", " [tag foo] {", " [property color]:", " [keyword yellow];", " }", "}"); MT("indent_comma", "[tag foo] {", " [property font-family]: [variable verdana],", " [atom sans-serif];", "}"); MT("indent_parentheses", "[tag foo]:[variable-3 before] {", " [property background]: [variable&callee url](", "[string blahblah]", "[string etc]", "[string ]) [keyword !important];", "}"); MT("font_face", "[def @font-face] {", " [property font-family]: [string 'myfont'];", " [error nonsense]: [string 'abc'];", " [property src]: [variable&callee url]([string http://blah]),", " [variable&callee url]([string http://foo]);", "}"); MT("empty_url", "[def @import] [variable&callee url]() [attribute screen];"); MT("parens", "[qualifier .foo] {", " [property background-image]: [variable&callee fade]([atom #000], [number 20%]);", " [property border-image]: [variable&callee linear-gradient](", " [atom to] [atom bottom],", " [variable&callee fade]([atom #000], [number 20%]) [number 0%],", " [variable&callee fade]([atom #000], [number 20%]) [number 100%]", " );", "}"); MT("css_variable", ":[variable-3 root] {", " [variable-2 --main-color]: [atom #06c];", "}", "[tag h1][builtin #foo] {", " [property color]: [variable&callee var]([variable-2 --main-color]);", "}"); MT("blank_css_variable", ":[variable-3 root] {", " [variable-2 --]: [atom #06c];", "}", "[tag h1][builtin #foo] {", " [property color]: [variable&callee var]([variable-2 --]);", "}"); MT("supports", "[def @supports] ([keyword not] (([property text-align-last]: [atom justify]) [keyword or] ([meta -moz-][property text-align-last]: [atom justify])) {", " [property text-align-last]: [atom justify];", "}"); MT("document", "[def @document] [variable&callee url]([string http://blah]),", " [variable&callee url-prefix]([string https://]),", " [variable&callee domain]([string blah.com]),", " [variable&callee regexp]([string \".*blah.+\"]) {", " [builtin #id] {", " [property background-color]: [keyword white];", " }", " [tag foo] {", " [property font-family]: [variable Verdana], [atom sans-serif];", " }", "}"); MT("document_url", "[def @document] [variable&callee url]([string http://blah]) { [qualifier .class] { } }"); MT("document_urlPrefix", "[def @document] [variable&callee url-prefix]([string https://]) { [builtin #id] { } }"); MT("document_domain", "[def @document] [variable&callee domain]([string blah.com]) { [tag foo] { } }"); MT("document_regexp", "[def @document] [variable&callee regexp]([string \".*blah.+\"]) { [builtin #id] { } }"); MT("counter-style", "[def @counter-style] [variable binary] {", " [property system]: [atom numeric];", " [property symbols]: [number 0] [number 1];", " [property suffix]: [string \".\"];", " [property range]: [atom infinite];", " [property speak-as]: [atom numeric];", "}"); MT("counter-style-additive-symbols", "[def @counter-style] [variable simple-roman] {", " [property system]: [atom additive];", " [property additive-symbols]: [number 10] [variable X], [number 5] [variable V], [number 1] [variable I];", " [property range]: [number 1] [number 49];", "}"); MT("counter-style-use", "[tag ol][qualifier .roman] { [property list-style]: [variable simple-roman]; }"); MT("counter-style-symbols", "[tag ol] { [property list-style]: [variable&callee symbols]([atom cyclic] [string \"*\"] [string \"\\2020\"] [string \"\\2021\"] [string \"\\A7\"]); }"); MT("comment-does-not-disrupt", "[def @font-face] [comment /* foo */] {", " [property src]: [variable&callee url]([string x]);", " [property font-family]: [variable One];", "}") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cypher/cypher.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // By the Neo4j Team and contributors. // https://github.com/neo4j-contrib/CodeMirror (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var wordRegexp = function(words) { return new RegExp("^(?:" + words.join("|") + ")$", "i"); }; CodeMirror.defineMode("cypher", function(config) { var tokenBase = function(stream/*, state*/) { curPunc = null var ch = stream.next(); if (ch ==='"') { stream.match(/^[^"]*"/); return "string"; } if (ch === "'") { stream.match(/^[^']*'/); return "string"; } if (/[{}\(\),\.;\[\]]/.test(ch)) { curPunc = ch; return "node"; } else if (ch === "/" && stream.eat("/")) { stream.skipToEnd(); return "comment"; } else if (operatorChars.test(ch)) { stream.eatWhile(operatorChars); return null; } else { stream.eatWhile(/[_\w\d]/); if (stream.eat(":")) { stream.eatWhile(/[\w\d_\-]/); return "atom"; } var word = stream.current(); if (funcs.test(word)) return "builtin"; if (preds.test(word)) return "def"; if (keywords.test(word) || systemKeywords.test(word)) return "keyword"; return "variable"; } }; var pushContext = function(state, type, col) { return state.context = { prev: state.context, indent: state.indent, col: col, type: type }; }; var popContext = function(state) { state.indent = state.context.indent; return state.context = state.context.prev; }; var indentUnit = config.indentUnit; var curPunc; var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]); var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]); var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with", "call", "yield"]); var systemKeywords = wordRegexp(["access", "active", "assign", "all", "alter", "as", "catalog", "change", "copy", "create", "constraint", "constraints", "current", "database", "databases", "dbms", "default", "deny", "drop", "element", "elements", "exists", "from", "grant", "graph", "graphs", "if", "index", "indexes", "label", "labels", "management", "match", "name", "names", "new", "node", "nodes", "not", "of", "on", "or", "password", "populated", "privileges", "property", "read", "relationship", "relationships", "remove", "replace", "required", "revoke", "role", "roles", "set", "show", "start", "status", "stop", "suspended", "to", "traverse", "type", "types", "user", "users", "with", "write"]); var operatorChars = /[*+\-<>=&|~%^]/; return { startState: function(/*base*/) { return { tokenize: tokenBase, context: null, indent: 0, col: 0 }; }, token: function(stream, state) { if (stream.sol()) { if (state.context && (state.context.align == null)) { state.context.align = false; } state.indent = stream.indentation(); } if (stream.eatSpace()) { return null; } var style = state.tokenize(stream, state); if (style !== "comment" && state.context && (state.context.align == null) && state.context.type !== "pattern") { state.context.align = true; } if (curPunc === "(") { pushContext(state, ")", stream.column()); } else if (curPunc === "[") { pushContext(state, "]", stream.column()); } else if (curPunc === "{") { pushContext(state, "}", stream.column()); } else if (/[\]\}\)]/.test(curPunc)) { while (state.context && state.context.type === "pattern") { popContext(state); } if (state.context && curPunc === state.context.type) { popContext(state); } } else if (curPunc === "." && state.context && state.context.type === "pattern") { popContext(state); } else if (/atom|string|variable/.test(style) && state.context) { if (/[\}\]]/.test(state.context.type)) { pushContext(state, "pattern", stream.column()); } else if (state.context.type === "pattern" && !state.context.align) { state.context.align = true; state.context.col = stream.column(); } } return style; }, indent: function(state, textAfter) { var firstChar = textAfter && textAfter.charAt(0); var context = state.context; if (/[\]\}]/.test(firstChar)) { while (context && context.type === "pattern") { context = context.prev; } } var closing = context && firstChar === context.type; if (!context) return 0; if (context.type === "keywords") return CodeMirror.commands.newlineAndIndent; if (context.align) return context.col + (closing ? 0 : 1); return context.indent + (closing ? 0 : indentUnit); } }; }); CodeMirror.modeExtensions["cypher"] = { autoFormatLineBreaks: function(text) { var i, lines, reProcessedPortion; var lines = text.split("\n"); var reProcessedPortion = /\s+\b(return|where|order by|match|with|skip|limit|create|delete|set)\b\s/g; for (var i = 0; i < lines.length; i++) lines[i] = lines[i].replace(reProcessedPortion, " \n$1 ").trim(); return lines.join("\n"); } }; CodeMirror.defineMIME("application/x-cypher-query", "cypher"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cypher/index.html ================================================ CodeMirror: Cypher Mode for CodeMirror

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Cypher Mode for CodeMirror

    Cypher Mode for CodeMirror

    MIME types defined: application/x-cypher-query

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/cypher/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "cypher"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("unbalancedDoubledQuotedString", "[string \"a'b\"][variable c]"); MT("unbalancedSingleQuotedString", "[string 'a\"b'][variable c]"); MT("doubleQuotedString", "[string \"a\"][variable b]"); MT("singleQuotedString", "[string 'a'][variable b]"); MT("single attribute (with content)", "[node {][atom a:][string 'a'][node }]"); MT("multiple attribute, singleQuotedString (with content)", "[node {][atom a:][string 'a'][node ,][atom b:][string 'b'][node }]"); MT("multiple attribute, doubleQuotedString (with content)", "[node {][atom a:][string \"a\"][node ,][atom b:][string \"b\"][node }]"); MT("single attribute (without content)", "[node {][atom a:][string 'a'][node }]"); MT("multiple attribute, singleQuotedString (without content)", "[node {][atom a:][string ''][node ,][atom b:][string ''][node }]"); MT("multiple attribute, doubleQuotedString (without content)", "[node {][atom a:][string \"\"][node ,][atom b:][string \"\"][node }]"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/d/d.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("d", function(config, parserConfig) { var indentUnit = config.indentUnit, statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, keywords = parserConfig.keywords || {}, builtin = parserConfig.builtin || {}, blockKeywords = parserConfig.blockKeywords || {}, atoms = parserConfig.atoms || {}, hooks = parserConfig.hooks || {}, multiLineStrings = parserConfig.multiLineStrings; var isOperatorChar = /[+\-*&%=<>!?|\/]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (hooks[ch]) { var result = hooks[ch](stream, state); if (result !== false) return result; } if (ch == '"' || ch == "'" || ch == "`") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\[\]{}\(\),;\:\.]/.test(ch)) { curPunc = ch; return null; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (ch == "/") { if (stream.eat("+")) { state.tokenize = tokenNestedComment; return tokenNestedComment(stream, state); } if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_\xa1-\uffff]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "keyword"; } if (builtin.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "builtin"; } if (atoms.propertyIsEnumerable(cur)) return "atom"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenNestedComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "+"); } return "comment"; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { var indent = state.indented; if (state.context && state.context.type == "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } // Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state); else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement")) pushContext(state, stream.column(), "statement"); state.startOfLine = false; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; var closing = firstChar == ctx.type; if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); else if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : indentUnit); }, electricChars: "{}", blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", lineComment: "//", fold: "brace" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var blockKeywords = "body catch class do else enum for foreach foreach_reverse if in interface mixin " + "out scope struct switch try union unittest version while with"; CodeMirror.defineMIME("text/x-d", { name: "d", keywords: words("abstract alias align asm assert auto break case cast cdouble cent cfloat const continue " + "debug default delegate delete deprecated export extern final finally function goto immutable " + "import inout invariant is lazy macro module new nothrow override package pragma private " + "protected public pure ref return shared short static super synchronized template this " + "throw typedef typeid typeof volatile __FILE__ __LINE__ __gshared __traits __vector __parameters " + blockKeywords), blockKeywords: words(blockKeywords), builtin: words("bool byte char creal dchar double float idouble ifloat int ireal long real short ubyte " + "ucent uint ulong ushort wchar wstring void size_t sizediff_t"), atoms: words("exit failure success true false null"), hooks: { "@": function(stream, _state) { stream.eatWhile(/[\w\$_]/); return "meta"; } } }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/d/index.html ================================================ CodeMirror: D mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • D

    D mode

    Simple mode that handle D-Syntax (DLang Homepage).

    MIME types defined: text/x-d .

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/d/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "d"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("nested_comments", "[comment /+]","[comment comment]","[comment +/]","[variable void] [variable main](){}"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dart/dart.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../clike/clike")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../clike/clike"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var keywords = ("this super static final const abstract class extends external factory " + "implements mixin get native set typedef with enum throw rethrow assert break case " + "continue default in return new deferred async await covariant try catch finally " + "do else for if switch while import library export part of show hide is as extension " + "on yield late required sealed base interface when").split(" "); var blockKeywords = "try catch finally do else for if switch while".split(" "); var atoms = "true false null".split(" "); var builtins = "void bool num int double dynamic var String Null Never".split(" "); function set(words) { var obj = {}; for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } function pushInterpolationStack(state) { (state.interpolationStack || (state.interpolationStack = [])).push(state.tokenize); } function popInterpolationStack(state) { return (state.interpolationStack || (state.interpolationStack = [])).pop(); } function sizeInterpolationStack(state) { return state.interpolationStack ? state.interpolationStack.length : 0; } CodeMirror.defineMIME("application/dart", { name: "clike", keywords: set(keywords), blockKeywords: set(blockKeywords), builtin: set(builtins), atoms: set(atoms), hooks: { "@": function(stream) { stream.eatWhile(/[\w\$_\.]/); return "meta"; }, // custom string handling to deal with triple-quoted strings and string interpolation "'": function(stream, state) { return tokenString("'", stream, state, false); }, "\"": function(stream, state) { return tokenString("\"", stream, state, false); }, "r": function(stream, state) { var peek = stream.peek(); if (peek == "'" || peek == "\"") { return tokenString(stream.next(), stream, state, true); } return false; }, "}": function(_stream, state) { // "}" is end of interpolation, if interpolation stack is non-empty if (sizeInterpolationStack(state) > 0) { state.tokenize = popInterpolationStack(state); return null; } return false; }, "/": function(stream, state) { if (!stream.eat("*")) return false state.tokenize = tokenNestedComment(1) return state.tokenize(stream, state) }, token: function(stream, _, style) { if (style == "variable") { // Assume uppercase symbols are classes using variable-2 var isUpper = RegExp('^[_$]*[A-Z][a-zA-Z0-9_$]*$','g'); if (isUpper.test(stream.current())) { return 'variable-2'; } } } } }); function tokenString(quote, stream, state, raw) { var tripleQuoted = false; if (stream.eat(quote)) { if (stream.eat(quote)) tripleQuoted = true; else return "string"; //empty string } function tokenStringHelper(stream, state) { var escaped = false; while (!stream.eol()) { if (!raw && !escaped && stream.peek() == "$") { pushInterpolationStack(state); state.tokenize = tokenInterpolation; return "string"; } var next = stream.next(); if (next == quote && !escaped && (!tripleQuoted || stream.match(quote + quote))) { state.tokenize = null; break; } escaped = !raw && !escaped && next == "\\"; } return "string"; } state.tokenize = tokenStringHelper; return tokenStringHelper(stream, state); } function tokenInterpolation(stream, state) { stream.eat("$"); if (stream.eat("{")) { // let clike handle the content of ${...}, // we take over again when "}" appears (see hooks). state.tokenize = null; } else { state.tokenize = tokenInterpolationIdentifier; } return null; } function tokenInterpolationIdentifier(stream, state) { stream.eatWhile(/[\w_]/); state.tokenize = popInterpolationStack(state); return "variable"; } function tokenNestedComment(depth) { return function (stream, state) { var ch while (ch = stream.next()) { if (ch == "*" && stream.eat("/")) { if (depth == 1) { state.tokenize = null break } else { state.tokenize = tokenNestedComment(depth - 1) return state.tokenize(stream, state) } } else if (ch == "/" && stream.eat("*")) { state.tokenize = tokenNestedComment(depth + 1) return state.tokenize(stream, state) } } return "comment" } } CodeMirror.registerHelper("hintWords", "application/dart", keywords.concat(atoms).concat(builtins)); // This is needed to make loading through meta.js work. CodeMirror.defineMode("dart", function(conf) { return CodeMirror.getMode(conf, "application/dart"); }, "clike"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dart/index.html ================================================ CodeMirror: Dart mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Dart

    Dart mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/diff/diff.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("diff", function() { var TOKEN_NAMES = { '+': 'positive', '-': 'negative', '@': 'meta' }; return { token: function(stream) { var tw_pos = stream.string.search(/[\t ]+?$/); if (!stream.sol() || tw_pos === 0) { stream.skipToEnd(); return ("error " + ( TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); } var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); if (tw_pos === -1) { stream.skipToEnd(); } else { stream.pos = tw_pos; } return token_name; } }; }); CodeMirror.defineMIME("text/x-diff", "diff"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/diff/index.html ================================================ CodeMirror: Diff mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Diff

    Diff mode

    MIME types defined: text/x-diff.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/django/django.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../../addon/mode/overlay")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../../addon/mode/overlay"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("django:inner", function() { var keywords = ["block", "endblock", "for", "endfor", "true", "false", "filter", "endfilter", "loop", "none", "self", "super", "if", "elif", "endif", "as", "else", "import", "with", "endwith", "without", "context", "ifequal", "endifequal", "ifnotequal", "endifnotequal", "extends", "include", "load", "comment", "endcomment", "empty", "url", "static", "trans", "blocktrans", "endblocktrans", "now", "regroup", "lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle", "csrf_token", "autoescape", "endautoescape", "spaceless", "endspaceless", "ssi", "templatetag", "verbatim", "endverbatim", "widthratio"], filters = ["add", "addslashes", "capfirst", "center", "cut", "date", "default", "default_if_none", "dictsort", "dictsortreversed", "divisibleby", "escape", "escapejs", "filesizeformat", "first", "floatformat", "force_escape", "get_digit", "iriencode", "join", "last", "length", "length_is", "linebreaks", "linebreaksbr", "linenumbers", "ljust", "lower", "make_list", "phone2numeric", "pluralize", "pprint", "random", "removetags", "rjust", "safe", "safeseq", "slice", "slugify", "stringformat", "striptags", "time", "timesince", "timeuntil", "title", "truncatechars", "truncatechars_html", "truncatewords", "truncatewords_html", "unordered_list", "upper", "urlencode", "urlize", "urlizetrunc", "wordcount", "wordwrap", "yesno"], operators = ["==", "!=", "<", ">", "<=", ">="], wordOperators = ["in", "not", "or", "and"]; keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b"); filters = new RegExp("^\\b(" + filters.join("|") + ")\\b"); operators = new RegExp("^\\b(" + operators.join("|") + ")\\b"); wordOperators = new RegExp("^\\b(" + wordOperators.join("|") + ")\\b"); // We have to return "null" instead of null, in order to avoid string // styling as the default, when using Django templates inside HTML // element attributes function tokenBase (stream, state) { // Attempt to identify a variable, template or comment tag respectively if (stream.match("{{")) { state.tokenize = inVariable; return "tag"; } else if (stream.match("{%")) { state.tokenize = inTag; return "tag"; } else if (stream.match("{#")) { state.tokenize = inComment; return "comment"; } // Ignore completely any stream series that do not match the // Django template opening tags. while (stream.next() != null && !stream.match(/\{[{%#]/, false)) {} return null; } // A string can be included in either single or double quotes (this is // the delimiter). Mark everything as a string until the start delimiter // occurs again. function inString (delimiter, previousTokenizer) { return function (stream, state) { if (!state.escapeNext && stream.eat(delimiter)) { state.tokenize = previousTokenizer; } else { if (state.escapeNext) { state.escapeNext = false; } var ch = stream.next(); // Take into account the backslash for escaping characters, such as // the string delimiter. if (ch == "\\") { state.escapeNext = true; } } return "string"; }; } // Apply Django template variable syntax highlighting function inVariable (stream, state) { // Attempt to match a dot that precedes a property if (state.waitDot) { state.waitDot = false; if (stream.peek() != ".") { return "null"; } // Dot followed by a non-word character should be considered an error. if (stream.match(/\.\W+/)) { return "error"; } else if (stream.eat(".")) { state.waitProperty = true; return "null"; } else { throw Error ("Unexpected error while waiting for property."); } } // Attempt to match a pipe that precedes a filter if (state.waitPipe) { state.waitPipe = false; if (stream.peek() != "|") { return "null"; } // Pipe followed by a non-word character should be considered an error. if (stream.match(/\.\W+/)) { return "error"; } else if (stream.eat("|")) { state.waitFilter = true; return "null"; } else { throw Error ("Unexpected error while waiting for filter."); } } // Highlight properties if (state.waitProperty) { state.waitProperty = false; if (stream.match(/\b(\w+)\b/)) { state.waitDot = true; // A property can be followed by another property state.waitPipe = true; // A property can be followed by a filter return "property"; } } // Highlight filters if (state.waitFilter) { state.waitFilter = false; if (stream.match(filters)) { return "variable-2"; } } // Ignore all white spaces if (stream.eatSpace()) { state.waitProperty = false; return "null"; } // Identify numbers if (stream.match(/\b\d+(\.\d+)?\b/)) { return "number"; } // Identify strings if (stream.match("'")) { state.tokenize = inString("'", state.tokenize); return "string"; } else if (stream.match('"')) { state.tokenize = inString('"', state.tokenize); return "string"; } // Attempt to find the variable if (stream.match(/\b(\w+)\b/) && !state.foundVariable) { state.waitDot = true; state.waitPipe = true; // A property can be followed by a filter return "variable"; } // If found closing tag reset if (stream.match("}}")) { state.waitProperty = null; state.waitFilter = null; state.waitDot = null; state.waitPipe = null; state.tokenize = tokenBase; return "tag"; } // If nothing was found, advance to the next character stream.next(); return "null"; } function inTag (stream, state) { // Attempt to match a dot that precedes a property if (state.waitDot) { state.waitDot = false; if (stream.peek() != ".") { return "null"; } // Dot followed by a non-word character should be considered an error. if (stream.match(/\.\W+/)) { return "error"; } else if (stream.eat(".")) { state.waitProperty = true; return "null"; } else { throw Error ("Unexpected error while waiting for property."); } } // Attempt to match a pipe that precedes a filter if (state.waitPipe) { state.waitPipe = false; if (stream.peek() != "|") { return "null"; } // Pipe followed by a non-word character should be considered an error. if (stream.match(/\.\W+/)) { return "error"; } else if (stream.eat("|")) { state.waitFilter = true; return "null"; } else { throw Error ("Unexpected error while waiting for filter."); } } // Highlight properties if (state.waitProperty) { state.waitProperty = false; if (stream.match(/\b(\w+)\b/)) { state.waitDot = true; // A property can be followed by another property state.waitPipe = true; // A property can be followed by a filter return "property"; } } // Highlight filters if (state.waitFilter) { state.waitFilter = false; if (stream.match(filters)) { return "variable-2"; } } // Ignore all white spaces if (stream.eatSpace()) { state.waitProperty = false; return "null"; } // Identify numbers if (stream.match(/\b\d+(\.\d+)?\b/)) { return "number"; } // Identify strings if (stream.match("'")) { state.tokenize = inString("'", state.tokenize); return "string"; } else if (stream.match('"')) { state.tokenize = inString('"', state.tokenize); return "string"; } // Attempt to match an operator if (stream.match(operators)) { return "operator"; } // Attempt to match a word operator if (stream.match(wordOperators)) { return "keyword"; } // Attempt to match a keyword var keywordMatch = stream.match(keywords); if (keywordMatch) { if (keywordMatch[0] == "comment") { state.blockCommentTag = true; } return "keyword"; } // Attempt to match a variable if (stream.match(/\b(\w+)\b/)) { state.waitDot = true; state.waitPipe = true; // A property can be followed by a filter return "variable"; } // If found closing tag reset if (stream.match("%}")) { state.waitProperty = null; state.waitFilter = null; state.waitDot = null; state.waitPipe = null; // If the tag that closes is a block comment tag, we want to mark the // following code as comment, until the tag closes. if (state.blockCommentTag) { state.blockCommentTag = false; // Release the "lock" state.tokenize = inBlockComment; } else { state.tokenize = tokenBase; } return "tag"; } // If nothing was found, advance to the next character stream.next(); return "null"; } // Mark everything as comment inside the tag and the tag itself. function inComment (stream, state) { if (stream.match(/^.*?#\}/)) state.tokenize = tokenBase else stream.skipToEnd() return "comment"; } // Mark everything as a comment until the `blockcomment` tag closes. function inBlockComment (stream, state) { if (stream.match(/\{%\s*endcomment\s*%\}/, false)) { state.tokenize = inTag; stream.match("{%"); return "tag"; } else { stream.next(); return "comment"; } } return { startState: function () { return {tokenize: tokenBase}; }, token: function (stream, state) { return state.tokenize(stream, state); }, blockCommentStart: "{% comment %}", blockCommentEnd: "{% endcomment %}" }; }); CodeMirror.defineMode("django", function(config) { var htmlBase = CodeMirror.getMode(config, "text/html"); var djangoInner = CodeMirror.getMode(config, "django:inner"); return CodeMirror.overlayMode(htmlBase, djangoInner); }); CodeMirror.defineMIME("text/x-django", "django"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/django/index.html ================================================ CodeMirror: Django template mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Django

    Django template mode

    Mode for HTML with embedded Django template markup.

    MIME types defined: text/x-django

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dockerfile/dockerfile.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var from = "from"; var fromRegex = new RegExp("^(\\s*)\\b(" + from + ")\\b", "i"); var shells = ["run", "cmd", "entrypoint", "shell"]; var shellsAsArrayRegex = new RegExp("^(\\s*)(" + shells.join('|') + ")(\\s+\\[)", "i"); var expose = "expose"; var exposeRegex = new RegExp("^(\\s*)(" + expose + ")(\\s+)", "i"); var others = [ "arg", "from", "maintainer", "label", "env", "add", "copy", "volume", "user", "workdir", "onbuild", "stopsignal", "healthcheck", "shell" ]; // Collect all Dockerfile directives var instructions = [from, expose].concat(shells).concat(others), instructionRegex = "(" + instructions.join('|') + ")", instructionOnlyLine = new RegExp("^(\\s*)" + instructionRegex + "(\\s*)(#.*)?$", "i"), instructionWithArguments = new RegExp("^(\\s*)" + instructionRegex + "(\\s+)", "i"); CodeMirror.defineSimpleMode("dockerfile", { start: [ // Block comment: This is a line starting with a comment { regex: /^\s*#.*$/, sol: true, token: "comment" }, { regex: fromRegex, token: [null, "keyword"], sol: true, next: "from" }, // Highlight an instruction without any arguments (for convenience) { regex: instructionOnlyLine, token: [null, "keyword", null, "error"], sol: true }, { regex: shellsAsArrayRegex, token: [null, "keyword", null], sol: true, next: "array" }, { regex: exposeRegex, token: [null, "keyword", null], sol: true, next: "expose" }, // Highlight an instruction followed by arguments { regex: instructionWithArguments, token: [null, "keyword", null], sol: true, next: "arguments" }, { regex: /./, token: null } ], from: [ { regex: /\s*$/, token: null, next: "start" }, { // Line comment without instruction arguments is an error regex: /(\s*)(#.*)$/, token: [null, "error"], next: "start" }, { regex: /(\s*\S+\s+)(as)/i, token: [null, "keyword"], next: "start" }, // Fail safe return to start { token: null, next: "start" } ], single: [ { regex: /(?:[^\\']|\\.)/, token: "string" }, { regex: /'/, token: "string", pop: true } ], double: [ { regex: /(?:[^\\"]|\\.)/, token: "string" }, { regex: /"/, token: "string", pop: true } ], array: [ { regex: /\]/, token: null, next: "start" }, { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" } ], expose: [ { regex: /\d+$/, token: "number", next: "start" }, { regex: /[^\d]+$/, token: null, next: "start" }, { regex: /\d+/, token: "number" }, { regex: /[^\d]+/, token: null }, // Fail safe return to start { token: null, next: "start" } ], arguments: [ { regex: /^\s*#.*$/, sol: true, token: "comment" }, { regex: /"(?:[^\\"]|\\.)*"?$/, token: "string", next: "start" }, { regex: /"/, token: "string", push: "double" }, { regex: /'(?:[^\\']|\\.)*'?$/, token: "string", next: "start" }, { regex: /'/, token: "string", push: "single" }, { regex: /[^#"']+[\\`]$/, token: null }, { regex: /[^#"']+$/, token: null, next: "start" }, { regex: /[^#"']+/, token: null }, // Fail safe return to start { token: null, next: "start" } ], meta: { lineComment: "#" } }); CodeMirror.defineMIME("text/x-dockerfile", "dockerfile"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dockerfile/index.html ================================================ CodeMirror: Dockerfile mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Dockerfile

    Dockerfile mode

    Dockerfile syntax highlighting for CodeMirror. Depends on the simplemode addon.

    MIME types defined: text/x-dockerfile

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dockerfile/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-dockerfile"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("simple_nodejs_dockerfile", "[keyword FROM] node:carbon", "[comment # Create app directory]", "[keyword WORKDIR] /usr/src/app", "[comment # Install app dependencies]", "[comment # A wildcard is used to ensure both package.json AND package-lock.json are copied]", "[comment # where available (npm@5+)]", "[keyword COPY] package*.json ./", "[keyword RUN] npm install", "[keyword COPY] . .", "[keyword EXPOSE] [number 8080] [number 3000]", "[keyword ENV] NODE_ENV development", "[keyword CMD] [[ [string \"npm\"], [string \"start\"] ]]"); // Ideally the last space should not be highlighted. MT("instruction_without_args_1", "[keyword CMD] "); MT("instruction_without_args_2", "[comment # An instruction without args...]", "[keyword ARG] [error #...is an error]"); MT("multiline", "[keyword RUN] apt-get update && apt-get install -y \\", " mercurial \\", " subversion \\", " && apt-get clean \\", " && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*"); MT("from_comment", " [keyword FROM] debian:stretch # I tend to use stable as that is more stable", " [keyword FROM] debian:stretch [keyword AS] stable # I am even more stable", " [keyword FROM] [error # this is an error]"); MT("from_as", "[keyword FROM] golang:1.9.2-alpine3.6 [keyword AS] build", "[keyword COPY] --from=build /bin/project /bin/project", "[keyword ENTRYPOINT] [[ [string \"/bin/project\"] ]]", "[keyword CMD] [[ [string \"--help\"] ]]"); MT("arg", "[keyword ARG] VERSION=latest", "[keyword FROM] busybox:$VERSION", "[keyword ARG] VERSION", "[keyword RUN] echo $VERSION > image_version"); MT("label", "[keyword LABEL] com.example.label-with-value=[string \"foo\"]"); MT("label_multiline", "[keyword LABEL] description=[string \"This text illustrates ]\\", "[string that label-values can span multiple lines.\"]"); MT("maintainer", "[keyword MAINTAINER] Foo Bar [string \"foo@bar.com\"] ", "[keyword MAINTAINER] Bar Baz "); MT("env", "[keyword ENV] BUNDLE_PATH=[string \"$GEM_HOME\"] \\", " BUNDLE_APP_CONFIG=[string \"$GEM_HOME\"]"); MT("verify_keyword", "[keyword RUN] add-apt-repository ppa:chris-lea/node.js"); MT("scripts", "[comment # Set an entrypoint, to automatically install node modules]", "[keyword ENTRYPOINT] [[ [string \"/bin/bash\"], [string \"-c\"], [string \"if [[ ! -d node_modules ]]; then npm install; fi; exec \\\"${@:0}\\\";\"] ]]", "[keyword CMD] npm start", "[keyword RUN] npm run build && \\", "[comment # a comment between the shell commands]", " npm run test"); MT("strings_single", "[keyword FROM] buildpack-deps:stretch", "[keyword RUN] { \\", " echo [string 'install: --no-document']; \\", " echo [string 'update: --no-document']; \\", " } >> /usr/local/etc/gemrc"); MT("strings_single_multiline", "[keyword RUN] set -ex \\", " \\", " && buildDeps=[string ' ]\\", "[string bison ]\\", "[string dpkg-dev ]\\", "[string libgdbm-dev ]\\", "[string ruby ]\\", "[string '] \\", " && apt-get update"); MT("strings_single_multiline_2", "[keyword RUN] echo [string 'say \\' ]\\", "[string it works'] "); MT("strings_double", "[keyword RUN] apt-get install -y --no-install-recommends $buildDeps \\", " \\", " && wget -O ruby.tar.xz [string \"https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR%-rc}/ruby-$RUBY_VERSION.tar.xz\"] \\", " && echo [string \"$RUBY_DOWNLOAD_SHA256 *ruby.tar.xz\"] | sha256sum -c - "); MT("strings_double_multiline", "[keyword RUN] echo [string \"say \\\" ]\\", "[string it works\"] "); MT("escape", "[comment # escape=`]", "[keyword FROM] microsoft/windowsservercore", "[keyword RUN] powershell.exe -Command `", " $ErrorActionPreference = [string 'Stop']; `", " wget https://www.python.org/ftp/python/3.5.1/python-3.5.1.exe -OutFile c:\python-3.5.1.exe ; `", " Start-Process c:\python-3.5.1.exe -ArgumentList [string '/quiet InstallAllUsers=1 PrependPath=1'] -Wait ; `", " Remove-Item c:\python-3.5.1.exe -Force)"); MT("escape_strings", "[comment # escape=`]", "[keyword FROM] python:3.6-windowsservercore [keyword AS] python", "[keyword RUN] $env:PATH = [string 'C:\\Python;C:\\Python\\Scripts;{0}'] -f $env:PATH ; `", // It should not consider \' as escaped. // " Set-ItemProperty -Path [string 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\'] -Name Path -Value $env:PATH ;"); " Set-ItemProperty -Path [string 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\\' -Name Path -Value $env:PATH ;]"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dtd/dtd.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* DTD mode Ported to CodeMirror by Peter Kroon Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues GitHub: @peterkroon */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("dtd", function(config) { var indentUnit = config.indentUnit, type; function ret(style, tp) {type = tp; return style;} function tokenBase(stream, state) { var ch = stream.next(); if (ch == "<" && stream.eat("!") ) { if (stream.eatWhile(/[\-]/)) { state.tokenize = tokenSGMLComment; return tokenSGMLComment(stream, state); } else if (stream.eatWhile(/[\w]/)) return ret("keyword", "doindent"); } else if (ch == "<" && stream.eat("?")) { //xml declaration state.tokenize = inBlock("meta", "?>"); return ret("meta", ch); } else if (ch == "#" && stream.eatWhile(/[\w]/)) return ret("atom", "tag"); else if (ch == "|") return ret("keyword", "separator"); else if (ch.match(/[\(\)\[\]\-\.,\+\?>]/)) return ret(null, ch);//if(ch === ">") return ret(null, "endtag"); else else if (ch.match(/[\[\]]/)) return ret("rule", ch); else if (ch == "\"" || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } else if (stream.eatWhile(/[a-zA-Z\?\+\d]/)) { var sc = stream.current(); if( sc.substr(sc.length-1,sc.length).match(/\?|\+/) !== null )stream.backUp(1); return ret("tag", "tag"); } else if (ch == "%" || ch == "*" ) return ret("number", "number"); else { stream.eatWhile(/[\w\\\-_%.{,]/); return ret(null, null); } } function tokenSGMLComment(stream, state) { var dashes = 0, ch; while ((ch = stream.next()) != null) { if (dashes >= 2 && ch == ">") { state.tokenize = tokenBase; break; } dashes = (ch == "-") ? dashes + 1 : 0; } return ret("comment", "comment"); } function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { state.tokenize = tokenBase; break; } escaped = !escaped && ch == "\\"; } return ret("string", "tag"); }; } function inBlock(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = tokenBase; break; } stream.next(); } return style; }; } return { startState: function(base) { return {tokenize: tokenBase, baseIndent: base || 0, stack: []}; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); var context = state.stack[state.stack.length-1]; if (stream.current() == "[" || type === "doindent" || type == "[") state.stack.push("rule"); else if (type === "endtag") state.stack[state.stack.length-1] = "endtag"; else if (stream.current() == "]" || type == "]" || (type == ">" && context == "rule")) state.stack.pop(); else if (type == "[") state.stack.push("["); return style; }, indent: function(state, textAfter) { var n = state.stack.length; if( textAfter.charAt(0) === ']' )n--; else if(textAfter.substr(textAfter.length-1, textAfter.length) === ">"){ if(textAfter.substr(0,1) === "<") {} else if( type == "doindent" && textAfter.length > 1 ) {} else if( type == "doindent")n--; else if( type == ">" && textAfter.length > 1) {} else if( type == "tag" && textAfter !== ">") {} else if( type == "tag" && state.stack[state.stack.length-1] == "rule")n--; else if( type == "tag")n++; else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule" && type === ">")n--; else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule") {} else if( textAfter.substr(0,1) !== "<" && textAfter.substr(0,1) === ">" )n=n-1; else if( textAfter === ">") {} else n=n-1; //over rule them all if(type == null || type == "]")n--; } return state.baseIndent + n * indentUnit; }, electricChars: "]>" }; }); CodeMirror.defineMIME("application/xml-dtd", "dtd"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dtd/index.html ================================================ CodeMirror: DTD mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • DTD

    DTD mode

    MIME types defined: application/xml-dtd.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dylan/dylan.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function forEach(arr, f) { for (var i = 0; i < arr.length; i++) f(arr[i], i) } function some(arr, f) { for (var i = 0; i < arr.length; i++) if (f(arr[i], i)) return true return false } CodeMirror.defineMode("dylan", function(_config) { // Words var words = { // Words that introduce unnamed definitions like "define interface" unnamedDefinition: ["interface"], // Words that introduce simple named definitions like "define library" namedDefinition: ["module", "library", "macro", "C-struct", "C-union", "C-function", "C-callable-wrapper" ], // Words that introduce type definitions like "define class". // These are also parameterized like "define method" and are // appended to otherParameterizedDefinitionWords typeParameterizedDefinition: ["class", "C-subtype", "C-mapped-subtype"], // Words that introduce trickier definitions like "define method". // These require special definitions to be added to startExpressions otherParameterizedDefinition: ["method", "function", "C-variable", "C-address" ], // Words that introduce module constant definitions. // These must also be simple definitions and are // appended to otherSimpleDefinitionWords constantSimpleDefinition: ["constant"], // Words that introduce module variable definitions. // These must also be simple definitions and are // appended to otherSimpleDefinitionWords variableSimpleDefinition: ["variable"], // Other words that introduce simple definitions // (without implicit bodies). otherSimpleDefinition: ["generic", "domain", "C-pointer-type", "table" ], // Words that begin statements with implicit bodies. statement: ["if", "block", "begin", "method", "case", "for", "select", "when", "unless", "until", "while", "iterate", "profiling", "dynamic-bind" ], // Patterns that act as separators in compound statements. // This may include any general pattern that must be indented // specially. separator: ["finally", "exception", "cleanup", "else", "elseif", "afterwards" ], // Keywords that do not require special indentation handling, // but which should be highlighted other: ["above", "below", "by", "from", "handler", "in", "instance", "let", "local", "otherwise", "slot", "subclass", "then", "to", "keyed-by", "virtual" ], // Condition signaling function calls signalingCalls: ["signal", "error", "cerror", "break", "check-type", "abort" ] }; words["otherDefinition"] = words["unnamedDefinition"] .concat(words["namedDefinition"]) .concat(words["otherParameterizedDefinition"]); words["definition"] = words["typeParameterizedDefinition"] .concat(words["otherDefinition"]); words["parameterizedDefinition"] = words["typeParameterizedDefinition"] .concat(words["otherParameterizedDefinition"]); words["simpleDefinition"] = words["constantSimpleDefinition"] .concat(words["variableSimpleDefinition"]) .concat(words["otherSimpleDefinition"]); words["keyword"] = words["statement"] .concat(words["separator"]) .concat(words["other"]); // Patterns var symbolPattern = "[-_a-zA-Z?!*@<>$%]+"; var symbol = new RegExp("^" + symbolPattern); var patterns = { // Symbols with special syntax symbolKeyword: symbolPattern + ":", symbolClass: "<" + symbolPattern + ">", symbolGlobal: "\\*" + symbolPattern + "\\*", symbolConstant: "\\$" + symbolPattern }; var patternStyles = { symbolKeyword: "atom", symbolClass: "tag", symbolGlobal: "variable-2", symbolConstant: "variable-3" }; // Compile all patterns to regular expressions for (var patternName in patterns) if (patterns.hasOwnProperty(patternName)) patterns[patternName] = new RegExp("^" + patterns[patternName]); // Names beginning "with-" and "without-" are commonly // used as statement macro patterns["keyword"] = [/^with(?:out)?-[-_a-zA-Z?!*@<>$%]+/]; var styles = {}; styles["keyword"] = "keyword"; styles["definition"] = "def"; styles["simpleDefinition"] = "def"; styles["signalingCalls"] = "builtin"; // protected words lookup table var wordLookup = {}; var styleLookup = {}; forEach([ "keyword", "definition", "simpleDefinition", "signalingCalls" ], function(type) { forEach(words[type], function(word) { wordLookup[word] = type; styleLookup[word] = styles[type]; }); }); function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenBase(stream, state) { // String var ch = stream.peek(); if (ch == "'" || ch == '"') { stream.next(); return chain(stream, state, tokenString(ch, "string")); } // Comment else if (ch == "/") { stream.next(); if (stream.eat("*")) { return chain(stream, state, tokenComment); } else if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } stream.backUp(1); } // Decimal else if (/[+\-\d\.]/.test(ch)) { if (stream.match(/^[+-]?[0-9]*\.[0-9]*([esdx][+-]?[0-9]+)?/i) || stream.match(/^[+-]?[0-9]+([esdx][+-]?[0-9]+)/i) || stream.match(/^[+-]?\d+/)) { return "number"; } } // Hash else if (ch == "#") { stream.next(); // Symbol with string syntax ch = stream.peek(); if (ch == '"') { stream.next(); return chain(stream, state, tokenString('"', "string")); } // Binary number else if (ch == "b") { stream.next(); stream.eatWhile(/[01]/); return "number"; } // Hex number else if (ch == "x") { stream.next(); stream.eatWhile(/[\da-f]/i); return "number"; } // Octal number else if (ch == "o") { stream.next(); stream.eatWhile(/[0-7]/); return "number"; } // Token concatenation in macros else if (ch == '#') { stream.next(); return "punctuation"; } // Sequence literals else if ((ch == '[') || (ch == '(')) { stream.next(); return "bracket"; // Hash symbol } else if (stream.match(/f|t|all-keys|include|key|next|rest/i)) { return "atom"; } else { stream.eatWhile(/[-a-zA-Z]/); return "error"; } } else if (ch == "~") { stream.next(); ch = stream.peek(); if (ch == "=") { stream.next(); ch = stream.peek(); if (ch == "=") { stream.next(); return "operator"; } return "operator"; } return "operator"; } else if (ch == ":") { stream.next(); ch = stream.peek(); if (ch == "=") { stream.next(); return "operator"; } else if (ch == ":") { stream.next(); return "punctuation"; } } else if ("[](){}".indexOf(ch) != -1) { stream.next(); return "bracket"; } else if (".,".indexOf(ch) != -1) { stream.next(); return "punctuation"; } else if (stream.match("end")) { return "keyword"; } for (var name in patterns) { if (patterns.hasOwnProperty(name)) { var pattern = patterns[name]; if ((pattern instanceof Array && some(pattern, function(p) { return stream.match(p); })) || stream.match(pattern)) return patternStyles[name]; } } if (/[+\-*\/^=<>&|]/.test(ch)) { stream.next(); return "operator"; } if (stream.match("define")) { return "def"; } else { stream.eatWhile(/[\w\-]/); // Keyword if (wordLookup.hasOwnProperty(stream.current())) { return styleLookup[stream.current()]; } else if (stream.current().match(symbol)) { return "variable"; } else { stream.next(); return "variable-2"; } } } function tokenComment(stream, state) { var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; while ((ch = stream.next())) { if (ch == "/" && maybeEnd) { if (nestedCount > 0) { nestedCount--; } else { state.tokenize = tokenBase; break; } } else if (ch == "*" && maybeNested) { nestedCount++; } maybeEnd = (ch == "*"); maybeNested = (ch == "/"); } return "comment"; } function tokenString(quote, style) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end || !escaped) { state.tokenize = tokenBase; } return style; }; } // Interface return { startState: function() { return { tokenize: tokenBase, currentIndent: 0 }; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); return style; }, blockCommentStart: "/*", blockCommentEnd: "*/" }; }); CodeMirror.defineMIME("text/x-dylan", "dylan"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dylan/index.html ================================================ CodeMirror: Dylan mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Dylan

    Dylan mode

    MIME types defined: text/x-dylan.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/dylan/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "dylan"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT('comments', '[comment // This is a line comment]', '[comment /* This is a block comment */]', '[comment /* This is a multi]', '[comment line comment]', '[comment */]', '[comment /* And this is a /*]', '[comment /* nested */ comment */]'); MT('unary_operators', '[operator -][variable a]', '[operator -] [variable a]', '[operator ~][variable a]', '[operator ~] [variable a]'); MT('binary_operators', '[variable a] [operator +] [variable b]', '[variable a] [operator -] [variable b]', '[variable a] [operator *] [variable b]', '[variable a] [operator /] [variable b]', '[variable a] [operator ^] [variable b]', '[variable a] [operator =] [variable b]', '[variable a] [operator ==] [variable b]', '[variable a] [operator ~=] [variable b]', '[variable a] [operator ~==] [variable b]', '[variable a] [operator <] [variable b]', '[variable a] [operator <=] [variable b]', '[variable a] [operator >] [variable b]', '[variable a] [operator >=] [variable b]', '[variable a] [operator &] [variable b]', '[variable a] [operator |] [variable b]', '[variable a] [operator :=] [variable b]'); MT('integers', '[number 1]', '[number 123]', '[number -123]', '[number +456]', '[number #b010]', '[number #o073]', '[number #xabcDEF123]'); MT('floats', '[number .3]', '[number -1.]', '[number -2.335]', '[number +3.78d1]', '[number 3.78s-1]', '[number -3.32e+5]'); MT('characters_and_strings', "[string 'a']", "[string '\\\\'']", '[string ""]', '[string "a"]', '[string "abc def"]', '[string "More escaped characters: \\\\\\\\ \\\\a \\\\b \\\\e \\\\f \\\\n \\\\r \\\\t \\\\0 ..."]'); MT('brackets', '[bracket #[[]]]', '[bracket #()]', '[bracket #(][number 1][bracket )]', '[bracket [[][number 1][punctuation ,] [number 3][bracket ]]]', '[bracket ()]', '[bracket {}]', '[keyword if] [bracket (][variable foo][bracket )]', '[bracket (][number 1][bracket )]', '[bracket [[][number 1][bracket ]]]'); MT('hash_words', '[punctuation ##]', '[atom #f]', '[atom #F]', '[atom #t]', '[atom #T]', '[atom #all-keys]', '[atom #include]', '[atom #key]', '[atom #next]', '[atom #rest]', '[string #"foo"]', '[error #invalid]'); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ebnf/ebnf.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ebnf", function (config) { var commentType = {slash: 0, parenthesis: 1}; var stateType = {comment: 0, _string: 1, characterClass: 2}; var bracesMode = null; if (config.bracesMode) bracesMode = CodeMirror.getMode(config, config.bracesMode); return { startState: function () { return { stringType: null, commentType: null, braced: 0, lhs: true, localState: null, stack: [], inDefinition: false }; }, token: function (stream, state) { if (!stream) return; //check for state changes if (state.stack.length === 0) { //strings if ((stream.peek() == '"') || (stream.peek() == "'")) { state.stringType = stream.peek(); stream.next(); // Skip quote state.stack.unshift(stateType._string); } else if (stream.match('/*')) { //comments starting with /* state.stack.unshift(stateType.comment); state.commentType = commentType.slash; } else if (stream.match('(*')) { //comments starting with (* state.stack.unshift(stateType.comment); state.commentType = commentType.parenthesis; } } //return state //stack has switch (state.stack[0]) { case stateType._string: while (state.stack[0] === stateType._string && !stream.eol()) { if (stream.peek() === state.stringType) { stream.next(); // Skip quote state.stack.shift(); // Clear flag } else if (stream.peek() === "\\") { stream.next(); stream.next(); } else { stream.match(/^.[^\\\"\']*/); } } return state.lhs ? "property string" : "string"; // Token style case stateType.comment: while (state.stack[0] === stateType.comment && !stream.eol()) { if (state.commentType === commentType.slash && stream.match('*/')) { state.stack.shift(); // Clear flag state.commentType = null; } else if (state.commentType === commentType.parenthesis && stream.match('*)')) { state.stack.shift(); // Clear flag state.commentType = null; } else { stream.match(/^.[^\*]*/); } } return "comment"; case stateType.characterClass: while (state.stack[0] === stateType.characterClass && !stream.eol()) { if (!(stream.match(/^[^\]\\]+/) || stream.match('.'))) { state.stack.shift(); } } return "operator"; } var peek = stream.peek(); if (bracesMode !== null && (state.braced || peek === "{")) { if (state.localState === null) state.localState = CodeMirror.startState(bracesMode); var token = bracesMode.token(stream, state.localState), text = stream.current(); if (!token) { for (var i = 0; i < text.length; i++) { if (text[i] === "{") { if (state.braced === 0) { token = "matchingbracket"; } state.braced++; } else if (text[i] === "}") { state.braced--; if (state.braced === 0) { token = "matchingbracket"; } } } } return token; } //no stack switch (peek) { case "[": stream.next(); state.stack.unshift(stateType.characterClass); return "bracket"; case ":": case "|": case ";": stream.next(); return "operator"; case "%": if (stream.match("%%")) { return "header"; } else if (stream.match(/[%][A-Za-z]+/)) { return "keyword"; } else if (stream.match(/[%][}]/)) { return "matchingbracket"; } break; case "/": if (stream.match(/[\/][A-Za-z]+/)) { return "keyword"; } case "\\": if (stream.match(/[\][a-z]+/)) { return "string-2"; } case ".": if (stream.match(".")) { return "atom"; } case "*": case "-": case "+": case "^": if (stream.match(peek)) { return "atom"; } case "$": if (stream.match("$$")) { return "builtin"; } else if (stream.match(/[$][0-9]+/)) { return "variable-3"; } case "<": if (stream.match(/<<[a-zA-Z_]+>>/)) { return "builtin"; } } if (stream.match('//')) { stream.skipToEnd(); return "comment"; } else if (stream.match('return')) { return "operator"; } else if (stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/)) { if (stream.match(/(?=[\(.])/)) { return "variable"; } else if (stream.match(/(?=[\s\n]*[:=])/)) { return "def"; } return "variable-2"; } else if (["[", "]", "(", ")"].indexOf(stream.peek()) != -1) { stream.next(); return "bracket"; } else if (!stream.eatSpace()) { stream.next(); } return null; } }; }); CodeMirror.defineMIME("text/x-ebnf", "ebnf"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ebnf/index.html ================================================ CodeMirror: EBNF Mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • EBNF Mode

    EBNF Mode (bracesMode setting = "javascript")

    The EBNF Mode

    Created by Robert Plummer

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ecl/ecl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ecl", function(config) { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } function metaHook(stream, state) { if (!state.startOfLine) return false; stream.skipToEnd(); return "meta"; } var indentUnit = config.indentUnit; var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode"); var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait"); var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath"); var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode"); var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when"); var blockKeywords = words("catch class do else finally for if switch try while"); var atoms = words("true false null"); var hooks = {"#": metaHook}; var isOperatorChar = /[+\-*&%=<>!?|\/]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (hooks[ch]) { var result = hooks[ch](stream, state); if (result !== false) return result; } if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\[\]{}\(\),;\:\.]/.test(ch)) { curPunc = ch; return null; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_]/); var cur = stream.current().toLowerCase(); if (keyword.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "keyword"; } else if (variable.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "variable"; } else if (variable_2.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "variable-2"; } else if (variable_3.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "variable-3"; } else if (builtin.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; return "builtin"; } else { //Data types are of from KEYWORD## var i = cur.length - 1; while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_')) --i; if (i > 0) { var cur2 = cur.substr(0, i + 1); if (variable_3.propertyIsEnumerable(cur2)) { if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement"; return "variable-3"; } } } if (atoms.propertyIsEnumerable(cur)) return "atom"; return null; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && next == "\\"; } if (end || !escaped) state.tokenize = tokenBase; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { return state.context = new Context(state.indented, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } // Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) pushContext(state, stream.column(), "statement"); state.startOfLine = false; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return 0; var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; var closing = firstChar == ctx.type; if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); else if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : indentUnit); }, electricChars: "{}" }; }); CodeMirror.defineMIME("text/x-ecl", "ecl"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ecl/index.html ================================================ CodeMirror: ECL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • ECL

    ECL mode

    Based on CodeMirror's clike mode. For more information see HPCC Systems web site.

    MIME types defined: text/x-ecl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/eiffel/eiffel.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("eiffel", function() { function wordObj(words) { var o = {}; for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; return o; } var keywords = wordObj([ 'note', 'across', 'when', 'variant', 'until', 'unique', 'undefine', 'then', 'strip', 'select', 'retry', 'rescue', 'require', 'rename', 'reference', 'redefine', 'prefix', 'once', 'old', 'obsolete', 'loop', 'local', 'like', 'is', 'inspect', 'infix', 'include', 'if', 'frozen', 'from', 'external', 'export', 'ensure', 'end', 'elseif', 'else', 'do', 'creation', 'create', 'check', 'alias', 'agent', 'separate', 'invariant', 'inherit', 'indexing', 'feature', 'expanded', 'deferred', 'class', 'Void', 'True', 'Result', 'Precursor', 'False', 'Current', 'create', 'attached', 'detachable', 'as', 'and', 'implies', 'not', 'or' ]); var operators = wordObj([":=", "and then","and", "or","<<",">>"]); function chain(newtok, stream, state) { state.tokenize.push(newtok); return newtok(stream, state); } function tokenBase(stream, state) { if (stream.eatSpace()) return null; var ch = stream.next(); if (ch == '"'||ch == "'") { return chain(readQuoted(ch, "string"), stream, state); } else if (ch == "-"&&stream.eat("-")) { stream.skipToEnd(); return "comment"; } else if (ch == ":"&&stream.eat("=")) { return "operator"; } else if (/[0-9]/.test(ch)) { stream.eatWhile(/[xXbBCc0-9\.]/); stream.eat(/[\?\!]/); return "ident"; } else if (/[a-zA-Z_0-9]/.test(ch)) { stream.eatWhile(/[a-zA-Z_0-9]/); stream.eat(/[\?\!]/); return "ident"; } else if (/[=+\-\/*^%<>~]/.test(ch)) { stream.eatWhile(/[=+\-\/*^%<>~]/); return "operator"; } else { return null; } } function readQuoted(quote, style, unescaped) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && (unescaped || !escaped)) { state.tokenize.pop(); break; } escaped = !escaped && ch == "%"; } return style; }; } return { startState: function() { return {tokenize: [tokenBase]}; }, token: function(stream, state) { var style = state.tokenize[state.tokenize.length-1](stream, state); if (style == "ident") { var word = stream.current(); style = keywords.propertyIsEnumerable(stream.current()) ? "keyword" : operators.propertyIsEnumerable(stream.current()) ? "operator" : /^[A-Z][A-Z_0-9]*$/g.test(word) ? "tag" : /^0[bB][0-1]+$/g.test(word) ? "number" : /^0[cC][0-7]+$/g.test(word) ? "number" : /^0[xX][a-fA-F0-9]+$/g.test(word) ? "number" : /^([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+)$/g.test(word) ? "number" : /^[0-9]+$/g.test(word) ? "number" : "variable"; } return style; }, lineComment: "--" }; }); CodeMirror.defineMIME("text/x-eiffel", "eiffel"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/eiffel/index.html ================================================ CodeMirror: Eiffel mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Eiffel

    Eiffel mode

    MIME types defined: text/x-eiffel.

    Created by YNH.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/elm/elm.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: http://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("elm", function() { function switchState(source, setState, f) { setState(f); return f(source, setState); } var lowerRE = /[a-z]/; var upperRE = /[A-Z]/; var innerRE = /[a-zA-Z0-9_]/; var digitRE = /[0-9]/; var hexRE = /[0-9A-Fa-f]/; var symbolRE = /[-&*+.\\/<>=?^|:]/; var specialRE = /[(),[\]{}]/; var spacesRE = /[ \v\f]/; // newlines are handled in tokenizer function normal() { return function(source, setState) { if (source.eatWhile(spacesRE)) { return null; } var char = source.next(); if (specialRE.test(char)) { return (char === '{' && source.eat('-')) ? switchState(source, setState, chompMultiComment(1)) : (char === '[' && source.match('glsl|')) ? switchState(source, setState, chompGlsl) : 'builtin'; } if (char === '\'') { return switchState(source, setState, chompChar); } if (char === '"') { return source.eat('"') ? source.eat('"') ? switchState(source, setState, chompMultiString) : 'string' : switchState(source, setState, chompSingleString); } if (upperRE.test(char)) { source.eatWhile(innerRE); return 'variable-2'; } if (lowerRE.test(char)) { var isDef = source.pos === 1; source.eatWhile(innerRE); return isDef ? "def" : "variable"; } if (digitRE.test(char)) { if (char === '0') { if (source.eat(/[xX]/)) { source.eatWhile(hexRE); // should require at least 1 return "number"; } } else { source.eatWhile(digitRE); } if (source.eat('.')) { source.eatWhile(digitRE); // should require at least 1 } if (source.eat(/[eE]/)) { source.eat(/[-+]/); source.eatWhile(digitRE); // should require at least 1 } return "number"; } if (symbolRE.test(char)) { if (char === '-' && source.eat('-')) { source.skipToEnd(); return "comment"; } source.eatWhile(symbolRE); return "keyword"; } if (char === '_') { return "keyword"; } return "error"; } } function chompMultiComment(nest) { if (nest == 0) { return normal(); } return function(source, setState) { while (!source.eol()) { var char = source.next(); if (char == '{' && source.eat('-')) { ++nest; } else if (char == '-' && source.eat('}')) { --nest; if (nest === 0) { setState(normal()); return 'comment'; } } } setState(chompMultiComment(nest)); return 'comment'; } } function chompMultiString(source, setState) { while (!source.eol()) { var char = source.next(); if (char === '"' && source.eat('"') && source.eat('"')) { setState(normal()); return 'string'; } } return 'string'; } function chompSingleString(source, setState) { while (source.skipTo('\\"')) { source.next(); source.next(); } if (source.skipTo('"')) { source.next(); setState(normal()); return 'string'; } source.skipToEnd(); setState(normal()); return 'error'; } function chompChar(source, setState) { while (source.skipTo("\\'")) { source.next(); source.next(); } if (source.skipTo("'")) { source.next(); setState(normal()); return 'string'; } source.skipToEnd(); setState(normal()); return 'error'; } function chompGlsl(source, setState) { while (!source.eol()) { var char = source.next(); if (char === '|' && source.eat(']')) { setState(normal()); return 'string'; } } return 'string'; } var wellKnownWords = { case: 1, of: 1, as: 1, if: 1, then: 1, else: 1, let: 1, in: 1, type: 1, alias: 1, module: 1, where: 1, import: 1, exposing: 1, port: 1 }; return { startState: function () { return { f: normal() }; }, copyState: function (s) { return { f: s.f }; }, lineComment: '--', token: function(stream, state) { var type = state.f(stream, function(s) { state.f = s; }); var word = stream.current(); return (wellKnownWords.hasOwnProperty(word)) ? 'keyword' : type; } }; }); CodeMirror.defineMIME("text/x-elm", "elm"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/elm/index.html ================================================ CodeMirror: Elm mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Elm

    Elm mode

    MIME types defined: text/x-elm.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/erlang/erlang.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /*jshint unused:true, eqnull:true, curly:true, bitwise:true */ /*jshint undef:true, latedef:true, trailing:true */ /*global CodeMirror:true */ // erlang mode. // tokenizer -> token types -> CodeMirror styles // tokenizer maintains a parse stack // indenter uses the parse stack // TODO indenter: // bit syntax // old guard/bif/conversion clashes (e.g. "float/1") // type/spec/opaque (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMIME("text/x-erlang", "erlang"); CodeMirror.defineMode("erlang", function(cmCfg) { "use strict"; ///////////////////////////////////////////////////////////////////////////// // constants var typeWords = [ "-type", "-spec", "-export_type", "-opaque"]; var keywordWords = [ "after","begin","catch","case","cond","end","fun","if", "let","of","query","receive","try","when"]; var separatorRE = /[\->,;]/; var separatorWords = [ "->",";",","]; var operatorAtomWords = [ "and","andalso","band","bnot","bor","bsl","bsr","bxor", "div","not","or","orelse","rem","xor"]; var operatorSymbolRE = /[\+\-\*\/<>=\|:!]/; var operatorSymbolWords = [ "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"]; var openParenRE = /[<\(\[\{]/; var openParenWords = [ "<<","(","[","{"]; var closeParenRE = /[>\)\]\}]/; var closeParenWords = [ "}","]",")",">>"]; var guardWords = [ "is_atom","is_binary","is_bitstring","is_boolean","is_float", "is_function","is_integer","is_list","is_number","is_pid", "is_port","is_record","is_reference","is_tuple", "atom","binary","bitstring","boolean","function","integer","list", "number","pid","port","record","reference","tuple"]; var bifWords = [ "abs","adler32","adler32_combine","alive","apply","atom_to_binary", "atom_to_list","binary_to_atom","binary_to_existing_atom", "binary_to_list","binary_to_term","bit_size","bitstring_to_list", "byte_size","check_process_code","contact_binary","crc32", "crc32_combine","date","decode_packet","delete_module", "disconnect_node","element","erase","exit","float","float_to_list", "garbage_collect","get","get_keys","group_leader","halt","hd", "integer_to_list","internal_bif","iolist_size","iolist_to_binary", "is_alive","is_atom","is_binary","is_bitstring","is_boolean", "is_float","is_function","is_integer","is_list","is_number","is_pid", "is_port","is_process_alive","is_record","is_reference","is_tuple", "length","link","list_to_atom","list_to_binary","list_to_bitstring", "list_to_existing_atom","list_to_float","list_to_integer", "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded", "monitor_node","node","node_link","node_unlink","nodes","notalive", "now","open_port","pid_to_list","port_close","port_command", "port_connect","port_control","pre_loaded","process_flag", "process_info","processes","purge_module","put","register", "registered","round","self","setelement","size","spawn","spawn_link", "spawn_monitor","spawn_opt","split_binary","statistics", "term_to_binary","time","throw","tl","trunc","tuple_size", "tuple_to_list","unlink","unregister","whereis"]; // upper case: [A-Z] [Ø-Þ] [À-Ö] // lower case: [a-z] [ß-ö] [ø-ÿ] var anumRE = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/; var escapesRE = /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/; ///////////////////////////////////////////////////////////////////////////// // tokenizer function tokenizer(stream,state) { // in multi-line string if (state.in_string) { state.in_string = (!doubleQuote(stream)); return rval(state,stream,"string"); } // in multi-line atom if (state.in_atom) { state.in_atom = (!singleQuote(stream)); return rval(state,stream,"atom"); } // whitespace if (stream.eatSpace()) { return rval(state,stream,"whitespace"); } // attributes and type specs if (!peekToken(state) && stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) { if (is_member(stream.current(),typeWords)) { return rval(state,stream,"type"); }else{ return rval(state,stream,"attribute"); } } var ch = stream.next(); // comment if (ch == '%') { stream.skipToEnd(); return rval(state,stream,"comment"); } // colon if (ch == ":") { return rval(state,stream,"colon"); } // macro if (ch == '?') { stream.eatSpace(); stream.eatWhile(anumRE); return rval(state,stream,"macro"); } // record if (ch == "#") { stream.eatSpace(); stream.eatWhile(anumRE); return rval(state,stream,"record"); } // dollar escape if (ch == "$") { if (stream.next() == "\\" && !stream.match(escapesRE)) { return rval(state,stream,"error"); } return rval(state,stream,"number"); } // dot if (ch == ".") { return rval(state,stream,"dot"); } // quoted atom if (ch == '\'') { if (!(state.in_atom = (!singleQuote(stream)))) { if (stream.match(/\s*\/\s*[0-9]/,false)) { stream.match(/\s*\/\s*[0-9]/,true); return rval(state,stream,"fun"); // 'f'/0 style fun } if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) { return rval(state,stream,"function"); } } return rval(state,stream,"atom"); } // string if (ch == '"') { state.in_string = (!doubleQuote(stream)); return rval(state,stream,"string"); } // variable if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) { stream.eatWhile(anumRE); return rval(state,stream,"variable"); } // atom/keyword/BIF/function if (/[a-z_ß-öø-ÿ]/.test(ch)) { stream.eatWhile(anumRE); if (stream.match(/\s*\/\s*[0-9]/,false)) { stream.match(/\s*\/\s*[0-9]/,true); return rval(state,stream,"fun"); // f/0 style fun } var w = stream.current(); if (is_member(w,keywordWords)) { return rval(state,stream,"keyword"); }else if (is_member(w,operatorAtomWords)) { return rval(state,stream,"operator"); }else if (stream.match(/\s*\(/,false)) { // 'put' and 'erlang:put' are bifs, 'foo:put' is not if (is_member(w,bifWords) && ((peekToken(state).token != ":") || (peekToken(state,2).token == "erlang"))) { return rval(state,stream,"builtin"); }else if (is_member(w,guardWords)) { return rval(state,stream,"guard"); }else{ return rval(state,stream,"function"); } }else if (lookahead(stream) == ":") { if (w == "erlang") { return rval(state,stream,"builtin"); } else { return rval(state,stream,"function"); } }else if (is_member(w,["true","false"])) { return rval(state,stream,"boolean"); }else{ return rval(state,stream,"atom"); } } // number var digitRE = /[0-9]/; var radixRE = /[0-9a-zA-Z]/; // 36#zZ style int if (digitRE.test(ch)) { stream.eatWhile(digitRE); if (stream.eat('#')) { // 36#aZ style integer if (!stream.eatWhile(radixRE)) { stream.backUp(1); //"36#" - syntax error } } else if (stream.eat('.')) { // float if (!stream.eatWhile(digitRE)) { stream.backUp(1); // "3." - probably end of function } else { if (stream.eat(/[eE]/)) { // float with exponent if (stream.eat(/[-+]/)) { if (!stream.eatWhile(digitRE)) { stream.backUp(2); // "2e-" - syntax error } } else { if (!stream.eatWhile(digitRE)) { stream.backUp(1); // "2e" - syntax error } } } } } return rval(state,stream,"number"); // normal integer } // open parens if (nongreedy(stream,openParenRE,openParenWords)) { return rval(state,stream,"open_paren"); } // close parens if (nongreedy(stream,closeParenRE,closeParenWords)) { return rval(state,stream,"close_paren"); } // separators if (greedy(stream,separatorRE,separatorWords)) { return rval(state,stream,"separator"); } // operators if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) { return rval(state,stream,"operator"); } return rval(state,stream,null); } ///////////////////////////////////////////////////////////////////////////// // utilities function nongreedy(stream,re,words) { if (stream.current().length == 1 && re.test(stream.current())) { stream.backUp(1); while (re.test(stream.peek())) { stream.next(); if (is_member(stream.current(),words)) { return true; } } stream.backUp(stream.current().length-1); } return false; } function greedy(stream,re,words) { if (stream.current().length == 1 && re.test(stream.current())) { while (re.test(stream.peek())) { stream.next(); } while (0 < stream.current().length) { if (is_member(stream.current(),words)) { return true; }else{ stream.backUp(1); } } stream.next(); } return false; } function doubleQuote(stream) { return quote(stream, '"', '\\'); } function singleQuote(stream) { return quote(stream,'\'','\\'); } function quote(stream,quoteChar,escapeChar) { while (!stream.eol()) { var ch = stream.next(); if (ch == quoteChar) { return true; }else if (ch == escapeChar) { stream.next(); } } return false; } function lookahead(stream) { var m = stream.match(/^\s*([^\s%])/, false) return m ? m[1] : ""; } function is_member(element,list) { return (-1 < list.indexOf(element)); } function rval(state,stream,type) { // parse stack pushToken(state,realToken(type,stream)); // map erlang token type to CodeMirror style class // erlang -> CodeMirror tag switch (type) { case "atom": return "atom"; case "attribute": return "attribute"; case "boolean": return "atom"; case "builtin": return "builtin"; case "close_paren": return null; case "colon": return null; case "comment": return "comment"; case "dot": return null; case "error": return "error"; case "fun": return "meta"; case "function": return "tag"; case "guard": return "property"; case "keyword": return "keyword"; case "macro": return "variable-2"; case "number": return "number"; case "open_paren": return null; case "operator": return "operator"; case "record": return "bracket"; case "separator": return null; case "string": return "string"; case "type": return "def"; case "variable": return "variable"; default: return null; } } function aToken(tok,col,ind,typ) { return {token: tok, column: col, indent: ind, type: typ}; } function realToken(type,stream) { return aToken(stream.current(), stream.column(), stream.indentation(), type); } function fakeToken(type) { return aToken(type,0,0,type); } function peekToken(state,depth) { var len = state.tokenStack.length; var dep = (depth ? depth : 1); if (len < dep) { return false; }else{ return state.tokenStack[len-dep]; } } function pushToken(state,token) { if (!(token.type == "comment" || token.type == "whitespace")) { state.tokenStack = maybe_drop_pre(state.tokenStack,token); state.tokenStack = maybe_drop_post(state.tokenStack); } } function maybe_drop_pre(s,token) { var last = s.length-1; if (0 < last && s[last].type === "record" && token.type === "dot") { s.pop(); }else if (0 < last && s[last].type === "group") { s.pop(); s.push(token); }else{ s.push(token); } return s; } function maybe_drop_post(s) { if (!s.length) return s var last = s.length-1; if (s[last].type === "dot") { return []; } if (last > 1 && s[last].type === "fun" && s[last-1].token === "fun") { return s.slice(0,last-1); } switch (s[last].token) { case "}": return d(s,{g:["{"]}); case "]": return d(s,{i:["["]}); case ")": return d(s,{i:["("]}); case ">>": return d(s,{i:["<<"]}); case "end": return d(s,{i:["begin","case","fun","if","receive","try"]}); case ",": return d(s,{e:["begin","try","when","->", ",","(","[","{","<<"]}); case "->": return d(s,{r:["when"], m:["try","if","case","receive"]}); case ";": return d(s,{E:["case","fun","if","receive","try","when"]}); case "catch":return d(s,{e:["try"]}); case "of": return d(s,{e:["case"]}); case "after":return d(s,{e:["receive","try"]}); default: return s; } } function d(stack,tt) { // stack is a stack of Token objects. // tt is an object; {type:tokens} // type is a char, tokens is a list of token strings. // The function returns (possibly truncated) stack. // It will descend the stack, looking for a Token such that Token.token // is a member of tokens. If it does not find that, it will normally (but // see "E" below) return stack. If it does find a match, it will remove // all the Tokens between the top and the matched Token. // If type is "m", that is all it does. // If type is "i", it will also remove the matched Token and the top Token. // If type is "g", like "i", but add a fake "group" token at the top. // If type is "r", it will remove the matched Token, but not the top Token. // If type is "e", it will keep the matched Token but not the top Token. // If type is "E", it behaves as for type "e", except if there is no match, // in which case it will return an empty stack. for (var type in tt) { var len = stack.length-1; var tokens = tt[type]; for (var i = len-1; -1 < i ; i--) { if (is_member(stack[i].token,tokens)) { var ss = stack.slice(0,i); switch (type) { case "m": return ss.concat(stack[i]).concat(stack[len]); case "r": return ss.concat(stack[len]); case "i": return ss; case "g": return ss.concat(fakeToken("group")); case "E": return ss.concat(stack[i]); case "e": return ss.concat(stack[i]); } } } } return (type == "E" ? [] : stack); } ///////////////////////////////////////////////////////////////////////////// // indenter function indenter(state,textAfter) { var t; var unit = cmCfg.indentUnit; var wordAfter = wordafter(textAfter); var currT = peekToken(state,1); var prevT = peekToken(state,2); if (state.in_string || state.in_atom) { return CodeMirror.Pass; }else if (!prevT) { return 0; }else if (currT.token == "when") { return currT.column+unit; }else if (wordAfter === "when" && prevT.type === "function") { return prevT.indent+unit; }else if (wordAfter === "(" && currT.token === "fun") { return currT.column+3; }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) { return t.column; }else if (is_member(wordAfter,["end","after","of"])) { t = getToken(state,["begin","case","fun","if","receive","try"]); return t ? t.column : CodeMirror.Pass; }else if (is_member(wordAfter,closeParenWords)) { t = getToken(state,openParenWords); return t ? t.column : CodeMirror.Pass; }else if (is_member(currT.token,[",","|","||"]) || is_member(wordAfter,[",","|","||"])) { t = postcommaToken(state); return t ? t.column+t.token.length : unit; }else if (currT.token == "->") { if (is_member(prevT.token, ["receive","case","if","try"])) { return prevT.column+unit+unit; }else{ return prevT.column+unit; } }else if (is_member(currT.token,openParenWords)) { return currT.column+currT.token.length; }else{ t = defaultToken(state); return truthy(t) ? t.column+unit : 0; } } function wordafter(str) { var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/); return truthy(m) && (m.index === 0) ? m[0] : ""; } function postcommaToken(state) { var objs = state.tokenStack.slice(0,-1); var i = getTokenIndex(objs,"type",["open_paren"]); return truthy(objs[i]) ? objs[i] : false; } function defaultToken(state) { var objs = state.tokenStack; var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]); var oper = getTokenIndex(objs,"type",["operator"]); if (truthy(stop) && truthy(oper) && stop < oper) { return objs[stop+1]; } else if (truthy(stop)) { return objs[stop]; } else { return false; } } function getToken(state,tokens) { var objs = state.tokenStack; var i = getTokenIndex(objs,"token",tokens); return truthy(objs[i]) ? objs[i] : false; } function getTokenIndex(objs,propname,propvals) { for (var i = objs.length-1; -1 < i ; i--) { if (is_member(objs[i][propname],propvals)) { return i; } } return false; } function truthy(x) { return (x !== false) && (x != null); } ///////////////////////////////////////////////////////////////////////////// // this object defines the mode return { startState: function() { return {tokenStack: [], in_string: false, in_atom: false}; }, token: function(stream, state) { return tokenizer(stream, state); }, indent: function(state, textAfter) { return indenter(state,textAfter); }, lineComment: "%" }; }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/erlang/index.html ================================================ CodeMirror: Erlang mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Erlang

    Erlang mode

    MIME types defined: text/x-erlang.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/factor/factor.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Factor syntax highlight - simple mode // // by Dimage Sapelkin (https://github.com/kerabromsmu) (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineSimpleMode("factor", { // The start state contains the rules that are initially used start: [ // comments {regex: /#?!.*/, token: "comment"}, // strings """, multiline --> state {regex: /"""/, token: "string", next: "string3"}, {regex: /(STRING:)(\s)/, token: ["keyword", null], next: "string2"}, {regex: /\S*?"/, token: "string", next: "string"}, // numbers: dec, hex, unicode, bin, fractional, complex {regex: /(?:0x[\d,a-f]+)|(?:0o[0-7]+)|(?:0b[0,1]+)|(?:\-?\d+.?\d*)(?=\s)/, token: "number"}, //{regex: /[+-]?/} //fractional // definition: defining word, defined word, etc {regex: /((?:GENERIC)|\:?\:)(\s+)(\S+)(\s+)(\()/, token: ["keyword", null, "def", null, "bracket"], next: "stack"}, // method definition: defining word, type, defined word, etc {regex: /(M\:)(\s+)(\S+)(\s+)(\S+)/, token: ["keyword", null, "def", null, "tag"]}, // vocabulary using --> state {regex: /USING\:/, token: "keyword", next: "vocabulary"}, // vocabulary definition/use {regex: /(USE\:|IN\:)(\s+)(\S+)(?=\s|$)/, token: ["keyword", null, "tag"]}, // definition: a defining word, defined word {regex: /(\S+\:)(\s+)(\S+)(?=\s|$)/, token: ["keyword", null, "def"]}, // "keywords", incl. ; t f . [ ] { } defining words {regex: /(?:;|\\|t|f|if|loop|while|until|do|PRIVATE>| and the like {regex: /\S+[\)>\.\*\?]+(?=\s|$)/, token: "builtin"}, {regex: /[\)><]+\S+(?=\s|$)/, token: "builtin"}, // operators {regex: /(?:[\+\-\=\/\*<>])(?=\s|$)/, token: "keyword"}, // any id (?) {regex: /\S+/, token: "variable"}, {regex: /\s+|./, token: null} ], vocabulary: [ {regex: /;/, token: "keyword", next: "start"}, {regex: /\S+/, token: "tag"}, {regex: /\s+|./, token: null} ], string: [ {regex: /(?:[^\\]|\\.)*?"/, token: "string", next: "start"}, {regex: /.*/, token: "string"} ], string2: [ {regex: /^;/, token: "keyword", next: "start"}, {regex: /.*/, token: "string"} ], string3: [ {regex: /(?:[^\\]|\\.)*?"""/, token: "string", next: "start"}, {regex: /.*/, token: "string"} ], stack: [ {regex: /\)/, token: "bracket", next: "start"}, {regex: /--/, token: "bracket"}, {regex: /\S+/, token: "meta"}, {regex: /\s+|./, token: null} ], // The meta property contains global information about the mode. It // can contain properties like lineComment, which are supported by // all modes, and also directives like dontIndentStates, which are // specific to simple modes. meta: { dontIndentStates: ["start", "vocabulary", "string", "string3", "stack"], lineComment: "!" } }); CodeMirror.defineMIME("text/x-factor", "factor"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/factor/index.html ================================================ CodeMirror: Factor mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Factor

    Factor mode

    Simple mode that handles Factor Syntax (Factor on Wikipedia).

    MIME types defined: text/x-factor.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/fcl/fcl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("fcl", function(config) { var indentUnit = config.indentUnit; var keywords = { "term": true, "method": true, "accu": true, "rule": true, "then": true, "is": true, "and": true, "or": true, "if": true, "default": true }; var start_blocks = { "var_input": true, "var_output": true, "fuzzify": true, "defuzzify": true, "function_block": true, "ruleblock": true }; var end_blocks = { "end_ruleblock": true, "end_defuzzify": true, "end_function_block": true, "end_fuzzify": true, "end_var": true }; var atoms = { "true": true, "false": true, "nan": true, "real": true, "min": true, "max": true, "cog": true, "cogs": true }; var isOperatorChar = /[+\-*&^%:=<>!|\/]/; function tokenBase(stream, state) { var ch = stream.next(); if (/[\d\.]/.test(ch)) { if (ch == ".") { stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); } else if (ch == "0") { stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); } else { stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); } return "number"; } if (ch == "/" || ch == "(") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_\xa1-\uffff]/); var cur = stream.current().toLowerCase(); if (keywords.propertyIsEnumerable(cur) || start_blocks.propertyIsEnumerable(cur) || end_blocks.propertyIsEnumerable(cur)) { return "keyword"; } if (atoms.propertyIsEnumerable(cur)) return "atom"; return "variable"; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if ((ch == "/" || ch == ")") && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { return state.context = new Context(state.indented, col, type, null, state.context); } function popContext(state) { if (!state.context.prev) return; var t = state.context.type; if (t == "end_block") state.indented = state.context.indented; return state.context = state.context.prev; } // Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; var cur = stream.current().toLowerCase(); if (start_blocks.propertyIsEnumerable(cur)) pushContext(state, stream.column(), "end_block"); else if (end_blocks.propertyIsEnumerable(cur)) popContext(state); state.startOfLine = false; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return 0; var ctx = state.context; var closing = end_blocks.propertyIsEnumerable(textAfter); if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : indentUnit); }, electricChars: "ryk", fold: "brace", blockCommentStart: "(*", blockCommentEnd: "*)", lineComment: "//" }; }); CodeMirror.defineMIME("text/x-fcl", "fcl"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/fcl/index.html ================================================ CodeMirror: FCL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • FCL

    FCL mode

    MIME type: text/x-fcl

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/forth/forth.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Author: Aliaksei Chapyzhenka (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function toWordList(words) { var ret = []; words.split(' ').forEach(function(e){ ret.push({name: e}); }); return ret; } var coreWordList = toWordList( 'INVERT AND OR XOR\ 2* 2/ LSHIFT RSHIFT\ 0= = 0< < > U< MIN MAX\ 2DROP 2DUP 2OVER 2SWAP ?DUP DEPTH DROP DUP OVER ROT SWAP\ >R R> R@\ + - 1+ 1- ABS NEGATE\ S>D * M* UM*\ FM/MOD SM/REM UM/MOD */ */MOD / /MOD MOD\ HERE , @ ! CELL+ CELLS C, C@ C! CHARS 2@ 2!\ ALIGN ALIGNED +! ALLOT\ CHAR [CHAR] [ ] BL\ FIND EXECUTE IMMEDIATE COUNT LITERAL STATE\ ; DOES> >BODY\ EVALUATE\ SOURCE >IN\ <# # #S #> HOLD SIGN BASE >NUMBER HEX DECIMAL\ FILL MOVE\ . CR EMIT SPACE SPACES TYPE U. .R U.R\ ACCEPT\ TRUE FALSE\ <> U> 0<> 0>\ NIP TUCK ROLL PICK\ 2>R 2R@ 2R>\ WITHIN UNUSED MARKER\ I J\ TO\ COMPILE, [COMPILE]\ SAVE-INPUT RESTORE-INPUT\ PAD ERASE\ 2LITERAL DNEGATE\ D- D+ D0< D0= D2* D2/ D< D= DMAX DMIN D>S DABS\ M+ M*/ D. D.R 2ROT DU<\ CATCH THROW\ FREE RESIZE ALLOCATE\ CS-PICK CS-ROLL\ GET-CURRENT SET-CURRENT FORTH-WORDLIST GET-ORDER SET-ORDER\ PREVIOUS SEARCH-WORDLIST WORDLIST FIND ALSO ONLY FORTH DEFINITIONS ORDER\ -TRAILING /STRING SEARCH COMPARE CMOVE CMOVE> BLANK SLITERAL'); var immediateWordList = toWordList('IF ELSE THEN BEGIN WHILE REPEAT UNTIL RECURSE [IF] [ELSE] [THEN] ?DO DO LOOP +LOOP UNLOOP LEAVE EXIT AGAIN CASE OF ENDOF ENDCASE'); CodeMirror.defineMode('forth', function() { function searchWordList (wordList, word) { var i; for (i = wordList.length - 1; i >= 0; i--) { if (wordList[i].name === word.toUpperCase()) { return wordList[i]; } } return undefined; } return { startState: function() { return { state: '', base: 10, coreWordList: coreWordList, immediateWordList: immediateWordList, wordList: [] }; }, token: function (stream, stt) { var mat; if (stream.eatSpace()) { return null; } if (stt.state === '') { // interpretation if (stream.match(/^(\]|:NONAME)(\s|$)/i)) { stt.state = ' compilation'; return 'builtin compilation'; } mat = stream.match(/^(\:)\s+(\S+)(\s|$)+/); if (mat) { stt.wordList.push({name: mat[2].toUpperCase()}); stt.state = ' compilation'; return 'def' + stt.state; } mat = stream.match(/^(VARIABLE|2VARIABLE|CONSTANT|2CONSTANT|CREATE|POSTPONE|VALUE|WORD)\s+(\S+)(\s|$)+/i); if (mat) { stt.wordList.push({name: mat[2].toUpperCase()}); return 'def' + stt.state; } mat = stream.match(/^(\'|\[\'\])\s+(\S+)(\s|$)+/); if (mat) { return 'builtin' + stt.state; } } else { // compilation // ; [ if (stream.match(/^(\;|\[)(\s)/)) { stt.state = ''; stream.backUp(1); return 'builtin compilation'; } if (stream.match(/^(\;|\[)($)/)) { stt.state = ''; return 'builtin compilation'; } if (stream.match(/^(POSTPONE)\s+\S+(\s|$)+/)) { return 'builtin'; } } // dynamic wordlist mat = stream.match(/^(\S+)(\s+|$)/); if (mat) { if (searchWordList(stt.wordList, mat[1]) !== undefined) { return 'variable' + stt.state; } // comments if (mat[1] === '\\') { stream.skipToEnd(); return 'comment' + stt.state; } // core words if (searchWordList(stt.coreWordList, mat[1]) !== undefined) { return 'builtin' + stt.state; } if (searchWordList(stt.immediateWordList, mat[1]) !== undefined) { return 'keyword' + stt.state; } if (mat[1] === '(') { stream.eatWhile(function (s) { return s !== ')'; }); stream.eat(')'); return 'comment' + stt.state; } // // strings if (mat[1] === '.(') { stream.eatWhile(function (s) { return s !== ')'; }); stream.eat(')'); return 'string' + stt.state; } if (mat[1] === 'S"' || mat[1] === '."' || mat[1] === 'C"') { stream.eatWhile(function (s) { return s !== '"'; }); stream.eat('"'); return 'string' + stt.state; } // numbers if (mat[1] - 0xfffffffff) { return 'number' + stt.state; } // if (mat[1].match(/^[-+]?[0-9]+\.[0-9]*/)) { // return 'number' + stt.state; // } return 'atom' + stt.state; } } }; }); CodeMirror.defineMIME("text/x-forth", "forth"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/forth/index.html ================================================ CodeMirror: Forth mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Forth

    Forth mode

    Simple mode that handle Forth-Syntax (Forth on Wikipedia).

    MIME types defined: text/x-forth.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/fortran/fortran.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("fortran", function() { function words(array) { var keys = {}; for (var i = 0; i < array.length; ++i) { keys[array[i]] = true; } return keys; } var keywords = words([ "abstract", "accept", "allocatable", "allocate", "array", "assign", "asynchronous", "backspace", "bind", "block", "byte", "call", "case", "class", "close", "common", "contains", "continue", "cycle", "data", "deallocate", "decode", "deferred", "dimension", "do", "elemental", "else", "encode", "end", "endif", "entry", "enumerator", "equivalence", "exit", "external", "extrinsic", "final", "forall", "format", "function", "generic", "go", "goto", "if", "implicit", "import", "include", "inquire", "intent", "interface", "intrinsic", "module", "namelist", "non_intrinsic", "non_overridable", "none", "nopass", "nullify", "open", "optional", "options", "parameter", "pass", "pause", "pointer", "print", "private", "program", "protected", "public", "pure", "read", "recursive", "result", "return", "rewind", "save", "select", "sequence", "stop", "subroutine", "target", "then", "to", "type", "use", "value", "volatile", "where", "while", "write"]); var builtins = words(["abort", "abs", "access", "achar", "acos", "adjustl", "adjustr", "aimag", "aint", "alarm", "all", "allocated", "alog", "amax", "amin", "amod", "and", "anint", "any", "asin", "associated", "atan", "besj", "besjn", "besy", "besyn", "bit_size", "btest", "cabs", "ccos", "ceiling", "cexp", "char", "chdir", "chmod", "clog", "cmplx", "command_argument_count", "complex", "conjg", "cos", "cosh", "count", "cpu_time", "cshift", "csin", "csqrt", "ctime", "c_funloc", "c_loc", "c_associated", "c_null_ptr", "c_null_funptr", "c_f_pointer", "c_null_char", "c_alert", "c_backspace", "c_form_feed", "c_new_line", "c_carriage_return", "c_horizontal_tab", "c_vertical_tab", "dabs", "dacos", "dasin", "datan", "date_and_time", "dbesj", "dbesj", "dbesjn", "dbesy", "dbesy", "dbesyn", "dble", "dcos", "dcosh", "ddim", "derf", "derfc", "dexp", "digits", "dim", "dint", "dlog", "dlog", "dmax", "dmin", "dmod", "dnint", "dot_product", "dprod", "dsign", "dsinh", "dsin", "dsqrt", "dtanh", "dtan", "dtime", "eoshift", "epsilon", "erf", "erfc", "etime", "exit", "exp", "exponent", "extends_type_of", "fdate", "fget", "fgetc", "float", "floor", "flush", "fnum", "fputc", "fput", "fraction", "fseek", "fstat", "ftell", "gerror", "getarg", "get_command", "get_command_argument", "get_environment_variable", "getcwd", "getenv", "getgid", "getlog", "getpid", "getuid", "gmtime", "hostnm", "huge", "iabs", "iachar", "iand", "iargc", "ibclr", "ibits", "ibset", "ichar", "idate", "idim", "idint", "idnint", "ieor", "ierrno", "ifix", "imag", "imagpart", "index", "int", "ior", "irand", "isatty", "ishft", "ishftc", "isign", "iso_c_binding", "is_iostat_end", "is_iostat_eor", "itime", "kill", "kind", "lbound", "len", "len_trim", "lge", "lgt", "link", "lle", "llt", "lnblnk", "loc", "log", "logical", "long", "lshift", "lstat", "ltime", "matmul", "max", "maxexponent", "maxloc", "maxval", "mclock", "merge", "move_alloc", "min", "minexponent", "minloc", "minval", "mod", "modulo", "mvbits", "nearest", "new_line", "nint", "not", "or", "pack", "perror", "precision", "present", "product", "radix", "rand", "random_number", "random_seed", "range", "real", "realpart", "rename", "repeat", "reshape", "rrspacing", "rshift", "same_type_as", "scale", "scan", "second", "selected_int_kind", "selected_real_kind", "set_exponent", "shape", "short", "sign", "signal", "sinh", "sin", "sleep", "sngl", "spacing", "spread", "sqrt", "srand", "stat", "sum", "symlnk", "system", "system_clock", "tan", "tanh", "time", "tiny", "transfer", "transpose", "trim", "ttynam", "ubound", "umask", "unlink", "unpack", "verify", "xor", "zabs", "zcos", "zexp", "zlog", "zsin", "zsqrt"]); var dataTypes = words(["c_bool", "c_char", "c_double", "c_double_complex", "c_float", "c_float_complex", "c_funptr", "c_int", "c_int16_t", "c_int32_t", "c_int64_t", "c_int8_t", "c_int_fast16_t", "c_int_fast32_t", "c_int_fast64_t", "c_int_fast8_t", "c_int_least16_t", "c_int_least32_t", "c_int_least64_t", "c_int_least8_t", "c_intmax_t", "c_intptr_t", "c_long", "c_long_double", "c_long_double_complex", "c_long_long", "c_ptr", "c_short", "c_signed_char", "c_size_t", "character", "complex", "double", "integer", "logical", "real"]); var isOperatorChar = /[+\-*&=<>\/\:]/; var litOperator = /^\.(and|or|eq|lt|le|gt|ge|ne|not|eqv|neqv)\./i; function tokenBase(stream, state) { if (stream.match(litOperator)){ return 'operator'; } var ch = stream.next(); if (ch == "!") { stream.skipToEnd(); return "comment"; } if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\[\]\(\),]/.test(ch)) { return null; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_]/); var word = stream.current().toLowerCase(); if (keywords.hasOwnProperty(word)){ return 'keyword'; } if (builtins.hasOwnProperty(word) || dataTypes.hasOwnProperty(word)) { return 'builtin'; } return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end || !escaped) state.tokenize = null; return "string"; }; } // Interface return { startState: function() { return {tokenize: null}; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; return style; } }; }); CodeMirror.defineMIME("text/x-fortran", "fortran"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/fortran/index.html ================================================ CodeMirror: Fortran mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Fortran

    Fortran mode

    MIME types defined: text/x-fortran.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gas/gas.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("gas", function(_config, parserConfig) { 'use strict'; // If an architecture is specified, its initialization function may // populate this array with custom parsing functions which will be // tried in the event that the standard functions do not find a match. var custom = []; // The symbol used to start a line comment changes based on the target // architecture. // If no architecture is pased in "parserConfig" then only multiline // comments will have syntax support. var lineCommentStartSymbol = ""; // These directives are architecture independent. // Machine specific directives should go in their respective // architecture initialization function. // Reference: // http://sourceware.org/binutils/docs/as/Pseudo-Ops.html#Pseudo-Ops var directives = { ".abort" : "builtin", ".align" : "builtin", ".altmacro" : "builtin", ".ascii" : "builtin", ".asciz" : "builtin", ".balign" : "builtin", ".balignw" : "builtin", ".balignl" : "builtin", ".bundle_align_mode" : "builtin", ".bundle_lock" : "builtin", ".bundle_unlock" : "builtin", ".byte" : "builtin", ".cfi_startproc" : "builtin", ".comm" : "builtin", ".data" : "builtin", ".def" : "builtin", ".desc" : "builtin", ".dim" : "builtin", ".double" : "builtin", ".eject" : "builtin", ".else" : "builtin", ".elseif" : "builtin", ".end" : "builtin", ".endef" : "builtin", ".endfunc" : "builtin", ".endif" : "builtin", ".equ" : "builtin", ".equiv" : "builtin", ".eqv" : "builtin", ".err" : "builtin", ".error" : "builtin", ".exitm" : "builtin", ".extern" : "builtin", ".fail" : "builtin", ".file" : "builtin", ".fill" : "builtin", ".float" : "builtin", ".func" : "builtin", ".global" : "builtin", ".gnu_attribute" : "builtin", ".hidden" : "builtin", ".hword" : "builtin", ".ident" : "builtin", ".if" : "builtin", ".incbin" : "builtin", ".include" : "builtin", ".int" : "builtin", ".internal" : "builtin", ".irp" : "builtin", ".irpc" : "builtin", ".lcomm" : "builtin", ".lflags" : "builtin", ".line" : "builtin", ".linkonce" : "builtin", ".list" : "builtin", ".ln" : "builtin", ".loc" : "builtin", ".loc_mark_labels" : "builtin", ".local" : "builtin", ".long" : "builtin", ".macro" : "builtin", ".mri" : "builtin", ".noaltmacro" : "builtin", ".nolist" : "builtin", ".octa" : "builtin", ".offset" : "builtin", ".org" : "builtin", ".p2align" : "builtin", ".popsection" : "builtin", ".previous" : "builtin", ".print" : "builtin", ".protected" : "builtin", ".psize" : "builtin", ".purgem" : "builtin", ".pushsection" : "builtin", ".quad" : "builtin", ".reloc" : "builtin", ".rept" : "builtin", ".sbttl" : "builtin", ".scl" : "builtin", ".section" : "builtin", ".set" : "builtin", ".short" : "builtin", ".single" : "builtin", ".size" : "builtin", ".skip" : "builtin", ".sleb128" : "builtin", ".space" : "builtin", ".stab" : "builtin", ".string" : "builtin", ".struct" : "builtin", ".subsection" : "builtin", ".symver" : "builtin", ".tag" : "builtin", ".text" : "builtin", ".title" : "builtin", ".type" : "builtin", ".uleb128" : "builtin", ".val" : "builtin", ".version" : "builtin", ".vtable_entry" : "builtin", ".vtable_inherit" : "builtin", ".warning" : "builtin", ".weak" : "builtin", ".weakref" : "builtin", ".word" : "builtin" }; var registers = {}; function x86(_parserConfig) { lineCommentStartSymbol = "#"; registers.al = "variable"; registers.ah = "variable"; registers.ax = "variable"; registers.eax = "variable-2"; registers.rax = "variable-3"; registers.bl = "variable"; registers.bh = "variable"; registers.bx = "variable"; registers.ebx = "variable-2"; registers.rbx = "variable-3"; registers.cl = "variable"; registers.ch = "variable"; registers.cx = "variable"; registers.ecx = "variable-2"; registers.rcx = "variable-3"; registers.dl = "variable"; registers.dh = "variable"; registers.dx = "variable"; registers.edx = "variable-2"; registers.rdx = "variable-3"; registers.si = "variable"; registers.esi = "variable-2"; registers.rsi = "variable-3"; registers.di = "variable"; registers.edi = "variable-2"; registers.rdi = "variable-3"; registers.sp = "variable"; registers.esp = "variable-2"; registers.rsp = "variable-3"; registers.bp = "variable"; registers.ebp = "variable-2"; registers.rbp = "variable-3"; registers.ip = "variable"; registers.eip = "variable-2"; registers.rip = "variable-3"; registers.cs = "keyword"; registers.ds = "keyword"; registers.ss = "keyword"; registers.es = "keyword"; registers.fs = "keyword"; registers.gs = "keyword"; } function armv6(_parserConfig) { // Reference: // http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0301h/DDI0301H_arm1176jzfs_r0p7_trm.pdf lineCommentStartSymbol = "@"; directives.syntax = "builtin"; registers.r0 = "variable"; registers.r1 = "variable"; registers.r2 = "variable"; registers.r3 = "variable"; registers.r4 = "variable"; registers.r5 = "variable"; registers.r6 = "variable"; registers.r7 = "variable"; registers.r8 = "variable"; registers.r9 = "variable"; registers.r10 = "variable"; registers.r11 = "variable"; registers.r12 = "variable"; registers.sp = "variable-2"; registers.lr = "variable-2"; registers.pc = "variable-2"; registers.r13 = registers.sp; registers.r14 = registers.lr; registers.r15 = registers.pc; custom.push(function(ch, stream) { if (ch === '#') { stream.eatWhile(/\w/); return "number"; } }); } var arch = (parserConfig.architecture || "x86").toLowerCase(); if (arch === "x86") { x86(parserConfig); } else if (arch === "arm" || arch === "armv6") { armv6(parserConfig); } function nextUntilUnescaped(stream, end) { var escaped = false, next; while ((next = stream.next()) != null) { if (next === end && !escaped) { return false; } escaped = !escaped && next === "\\"; } return escaped; } function clikeComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (ch === "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch === "*"); } return "comment"; } return { startState: function() { return { tokenize: null }; }, token: function(stream, state) { if (state.tokenize) { return state.tokenize(stream, state); } if (stream.eatSpace()) { return null; } var style, cur, ch = stream.next(); if (ch === "/") { if (stream.eat("*")) { state.tokenize = clikeComment; return clikeComment(stream, state); } } if (ch === lineCommentStartSymbol) { stream.skipToEnd(); return "comment"; } if (ch === '"') { nextUntilUnescaped(stream, '"'); return "string"; } if (ch === '.') { stream.eatWhile(/\w/); cur = stream.current().toLowerCase(); style = directives[cur]; return style || null; } if (ch === '=') { stream.eatWhile(/\w/); return "tag"; } if (ch === '{') { return "bracket"; } if (ch === '}') { return "bracket"; } if (/\d/.test(ch)) { if (ch === "0" && stream.eat("x")) { stream.eatWhile(/[0-9a-fA-F]/); return "number"; } stream.eatWhile(/\d/); return "number"; } if (/\w/.test(ch)) { stream.eatWhile(/\w/); if (stream.eat(":")) { return 'tag'; } cur = stream.current().toLowerCase(); style = registers[cur]; return style || null; } for (var i = 0; i < custom.length; i++) { style = custom[i](ch, stream, state); if (style) { return style; } } }, lineComment: lineCommentStartSymbol, blockCommentStart: "/*", blockCommentEnd: "*/" }; }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gas/index.html ================================================ CodeMirror: Gas mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Gas

    Gas mode

    Handles AT&T assembler syntax (more specifically this handles the GNU Assembler (gas) syntax.) It takes a single optional configuration parameter: architecture, which can be one of "ARM", "ARMv6" or "x86". Including the parameter adds syntax for the registers and special directives for the supplied architecture.

    MIME types defined: text/x-gas

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gfm/gfm.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../markdown/markdown"), require("../../addon/mode/overlay")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../markdown/markdown", "../../addon/mode/overlay"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var urlRE = /^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i CodeMirror.defineMode("gfm", function(config, modeConfig) { var codeDepth = 0; function blankLine(state) { state.code = false; return null; } var gfmOverlay = { startState: function() { return { code: false, codeBlock: false, ateSpace: false }; }, copyState: function(s) { return { code: s.code, codeBlock: s.codeBlock, ateSpace: s.ateSpace }; }, token: function(stream, state) { state.combineTokens = null; // Hack to prevent formatting override inside code blocks (block and inline) if (state.codeBlock) { if (stream.match(/^```+/)) { state.codeBlock = false; return null; } stream.skipToEnd(); return null; } if (stream.sol()) { state.code = false; } if (stream.sol() && stream.match(/^```+/)) { stream.skipToEnd(); state.codeBlock = true; return null; } // If this block is changed, it may need to be updated in Markdown mode if (stream.peek() === '`') { stream.next(); var before = stream.pos; stream.eatWhile('`'); var difference = 1 + stream.pos - before; if (!state.code) { codeDepth = difference; state.code = true; } else { if (difference === codeDepth) { // Must be exact state.code = false; } } return null; } else if (state.code) { stream.next(); return null; } // Check if space. If so, links can be formatted later on if (stream.eatSpace()) { state.ateSpace = true; return null; } if (stream.sol() || state.ateSpace) { state.ateSpace = false; if (modeConfig.gitHubSpice !== false) { if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/)) { // User/Project@SHA // User@SHA // SHA state.combineTokens = true; return "link"; } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) { // User/Project#Num // User#Num // #Num state.combineTokens = true; return "link"; } } } if (stream.match(urlRE) && stream.string.slice(stream.start - 2, stream.start) != "](" && (stream.start == 0 || /\W/.test(stream.string.charAt(stream.start - 1)))) { // URLs // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine // And then limited url schemes to the CommonMark list, so foo:bar isn't matched as a URL state.combineTokens = true; return "link"; } stream.next(); return null; }, blankLine: blankLine }; var markdownConfig = { taskLists: true, strikethrough: true, emoji: true }; for (var attr in modeConfig) { markdownConfig[attr] = modeConfig[attr]; } markdownConfig.name = "markdown"; return CodeMirror.overlayMode(CodeMirror.getMode(config, markdownConfig), gfmOverlay); }, "markdown"); CodeMirror.defineMIME("text/x-gfm", "gfm"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gfm/index.html ================================================ CodeMirror: GFM mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • GFM

    GFM mode

    Optionally depends on other modes for properly highlighted code blocks.

    Gfm mode supports these options (apart those from base Markdown mode):

    • gitHubSpice: boolean
      Hashes, issues... (default: true).
    • taskLists: boolean
      - [ ] syntax (default: true).
    • strikethrough: boolean
      ~~foo~~ syntax (default: true).
    • emoji: boolean
      :emoji: syntax (default: true).

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gfm/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var config = {tabSize: 4, indentUnit: 2} var mode = CodeMirror.getMode(config, "gfm"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } var modeHighlightFormatting = CodeMirror.getMode(config, {name: "gfm", highlightFormatting: true}); function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); } FT("codeBackticks", "[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]"); FT("doubleBackticks", "[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]"); FT("taskList", "[variable-2&formatting&formatting-list&formatting-list-ul - ][meta&formatting&formatting-task [ ]]][variable-2 foo]", "[variable-2&formatting&formatting-list&formatting-list-ul - ][property&formatting&formatting-task [x]]][variable-2 foo]"); FT("formatting_strikethrough", "[strikethrough&formatting&formatting-strikethrough ~~][strikethrough foo][strikethrough&formatting&formatting-strikethrough ~~]"); FT("formatting_strikethrough", "foo [strikethrough&formatting&formatting-strikethrough ~~][strikethrough bar][strikethrough&formatting&formatting-strikethrough ~~]"); FT("formatting_emoji", "foo [builtin&formatting&formatting-emoji :smile:] foo"); MT("emInWordAsterisk", "foo[em *bar*]hello"); MT("emInWordUnderscore", "foo_bar_hello"); MT("emStrongUnderscore", "[em&strong ___foo___] bar"); MT("taskListAsterisk", "[variable-2 * ][link&variable-2 [[]]][variable-2 foo]", // Invalid; must have space or x between [] "[variable-2 * ][link&variable-2 [[ ]]][variable-2 bar]", // Invalid; must have space after ] "[variable-2 * ][link&variable-2 [[x]]][variable-2 hello]", // Invalid; must have space after ] "[variable-2 * ][meta [ ]]][variable-2 ][link&variable-2 [[world]]]", // Valid; tests reference style links " [variable-3 * ][property [x]]][variable-3 foo]"); // Valid; can be nested MT("taskListPlus", "[variable-2 + ][link&variable-2 [[]]][variable-2 foo]", // Invalid; must have space or x between [] "[variable-2 + ][link&variable-2 [[x]]][variable-2 hello]", // Invalid; must have space after ] "[variable-2 + ][meta [ ]]][variable-2 ][link&variable-2 [[world]]]", // Valid; tests reference style links " [variable-3 + ][property [x]]][variable-3 foo]"); // Valid; can be nested MT("taskListDash", "[variable-2 - ][link&variable-2 [[]]][variable-2 foo]", // Invalid; must have space or x between [] "[variable-2 - ][link&variable-2 [[x]]][variable-2 hello]", // Invalid; must have space after ] "[variable-2 - ][meta [ ]]][variable-2 world]", // Valid; tests reference style links " [variable-3 - ][property [x]]][variable-3 foo]"); // Valid; can be nested MT("taskListNumber", "[variable-2 1. ][link&variable-2 [[]]][variable-2 foo]", // Invalid; must have space or x between [] "[variable-2 2. ][link&variable-2 [[ ]]][variable-2 bar]", // Invalid; must have space after ] "[variable-2 3. ][meta [ ]]][variable-2 world]", // Valid; tests reference style links " [variable-3 1. ][property [x]]][variable-3 foo]"); // Valid; can be nested MT("SHA", "foo [link be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] bar"); MT("SHAEmphasis", "[em *foo ][em&link be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2][em *]"); MT("shortSHA", "foo [link be6a8cc] bar"); MT("tooShortSHA", "foo be6a8c bar"); MT("longSHA", "foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar"); MT("badSHA", "foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar"); MT("userSHA", "foo [link bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] hello"); MT("userSHAEmphasis", "[em *foo ][em&link bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2][em *]"); MT("userProjectSHA", "foo [link bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] world"); MT("userProjectSHAEmphasis", "[em *foo ][em&link bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2][em *]"); MT("wordSHA", "ask for feedback") MT("num", "foo [link #1] bar"); MT("numEmphasis", "[em *foo ][em&link #1][em *]"); MT("badNum", "foo #1bar hello"); MT("userNum", "foo [link bar#1] hello"); MT("userNumEmphasis", "[em *foo ][em&link bar#1][em *]"); MT("userProjectNum", "foo [link bar/hello#1] world"); MT("userProjectNumEmphasis", "[em *foo ][em&link bar/hello#1][em *]"); MT("vanillaLink", "foo [link http://www.example.com/] bar"); MT("vanillaLinkNoScheme", "foo [link www.example.com] bar"); MT("vanillaLinkHttps", "foo [link https://www.example.com/] bar"); MT("vanillaLinkDataSchema", "foo [link data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==] bar"); MT("vanillaLinkPunctuation", "foo [link http://www.example.com/]. bar"); MT("vanillaLinkExtension", "foo [link http://www.example.com/index.html] bar"); MT("vanillaLinkEmphasis", "foo [em *][em&link http://www.example.com/index.html][em *] bar"); MT("notALink", "foo asfd:asdf bar"); MT("notALink", "[comment ``foo `bar` http://www.example.com/``] hello"); MT("notALink", "[comment `foo]", "[comment&link http://www.example.com/]", "[comment `] foo", "", "[link http://www.example.com/]"); MT("strikethrough", "[strikethrough ~~foo~~]"); MT("strikethroughWithStartingSpace", "~~ foo~~"); MT("strikethroughUnclosedStrayTildes", "[strikethrough ~~foo~~~]"); MT("strikethroughUnclosedStrayTildes", "[strikethrough ~~foo ~~]"); MT("strikethroughUnclosedStrayTildes", "[strikethrough ~~foo ~~ bar]"); MT("strikethroughUnclosedStrayTildes", "[strikethrough ~~foo ~~ bar~~]hello"); MT("strikethroughOneLetter", "[strikethrough ~~a~~]"); MT("strikethroughWrapped", "[strikethrough ~~foo]", "[strikethrough foo~~]"); MT("strikethroughParagraph", "[strikethrough ~~foo]", "", "foo[strikethrough ~~bar]"); MT("strikethroughEm", "[strikethrough ~~foo][em&strikethrough *bar*][strikethrough ~~]"); MT("strikethroughEm", "[em *][em&strikethrough ~~foo~~][em *]"); MT("strikethroughStrong", "[strikethrough ~~][strong&strikethrough **foo**][strikethrough ~~]"); MT("strikethroughStrong", "[strong **][strong&strikethrough ~~foo~~][strong **]"); MT("emoji", "text [builtin :blush:] text [builtin :v:] text [builtin :+1:] text", ":text text: [builtin :smiley_cat:]"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gherkin/gherkin.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* Gherkin mode - http://www.cukes.info/ Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues */ // Following Objs from Brackets implementation: https://github.com/tregusti/brackets-gherkin/blob/master/main.js //var Quotes = { // SINGLE: 1, // DOUBLE: 2 //}; //var regex = { // keywords: /(Feature| {2}(Scenario|In order to|As|I)| {4}(Given|When|Then|And))/ //}; (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("gherkin", function () { return { startState: function () { return { lineNumber: 0, tableHeaderLine: false, allowFeature: true, allowBackground: false, allowScenario: false, allowSteps: false, allowPlaceholders: false, allowMultilineArgument: false, inMultilineString: false, inMultilineTable: false, inKeywordLine: false }; }, token: function (stream, state) { if (stream.sol()) { state.lineNumber++; state.inKeywordLine = false; if (state.inMultilineTable) { state.tableHeaderLine = false; if (!stream.match(/\s*\|/, false)) { state.allowMultilineArgument = false; state.inMultilineTable = false; } } } stream.eatSpace(); if (state.allowMultilineArgument) { // STRING if (state.inMultilineString) { if (stream.match('"""')) { state.inMultilineString = false; state.allowMultilineArgument = false; } else { stream.match(/.*/); } return "string"; } // TABLE if (state.inMultilineTable) { if (stream.match(/\|\s*/)) { return "bracket"; } else { stream.match(/[^\|]*/); return state.tableHeaderLine ? "header" : "string"; } } // DETECT START if (stream.match('"""')) { // String state.inMultilineString = true; return "string"; } else if (stream.match("|")) { // Table state.inMultilineTable = true; state.tableHeaderLine = true; return "bracket"; } } // LINE COMMENT if (stream.match(/#.*/)) { return "comment"; // TAG } else if (!state.inKeywordLine && stream.match(/@\S+/)) { return "tag"; // FEATURE } else if (!state.inKeywordLine && state.allowFeature && stream.match(/(機能|功能|フィーチャ|기능|โครงหลัก|ความสามารถ|ความต้องการทางธุรกิจ|ಹೆಚ್ಚಳ|గుణము|ਮੁਹਾਂਦਰਾ|ਨਕਸ਼ ਨੁਹਾਰ|ਖਾਸੀਅਤ|रूप लेख|وِیژگی|خاصية|תכונה|Функціонал|Функция|Функционалност|Функционал|Үзенчәлеклелек|Свойство|Особина|Мөмкинлек|Могућност|Λειτουργία|Δυνατότητα|Właściwość|Vlastnosť|Trajto|Tính năng|Savybė|Pretty much|Požiadavka|Požadavek|Potrzeba biznesowa|Özellik|Osobina|Ominaisuus|Omadus|OH HAI|Mogućnost|Mogucnost|Jellemző|Hwæt|Hwaet|Funzionalità|Funktionalitéit|Funktionalität|Funkcja|Funkcionalnost|Funkcionalitāte|Funkcia|Fungsi|Functionaliteit|Funcționalitate|Funcţionalitate|Functionalitate|Funcionalitat|Funcionalidade|Fonctionnalité|Fitur|Fīča|Feature|Eiginleiki|Egenskap|Egenskab|Característica|Caracteristica|Business Need|Aspekt|Arwedd|Ahoy matey!|Ability):/)) { state.allowScenario = true; state.allowBackground = true; state.allowPlaceholders = false; state.allowSteps = false; state.allowMultilineArgument = false; state.inKeywordLine = true; return "keyword"; // BACKGROUND } else if (!state.inKeywordLine && state.allowBackground && stream.match(/(背景|배경|แนวคิด|ಹಿನ್ನೆಲೆ|నేపథ్యం|ਪਿਛੋਕੜ|पृष्ठभूमि|زمینه|الخلفية|רקע|Тарих|Предыстория|Предистория|Позадина|Передумова|Основа|Контекст|Кереш|Υπόβαθρο|Założenia|Yo\-ho\-ho|Tausta|Taust|Situācija|Rerefons|Pozadina|Pozadie|Pozadí|Osnova|Latar Belakang|Kontext|Konteksts|Kontekstas|Kontekst|Háttér|Hannergrond|Grundlage|Geçmiş|Fundo|Fono|First off|Dis is what went down|Dasar|Contexto|Contexte|Context|Contesto|Cenário de Fundo|Cenario de Fundo|Cefndir|Bối cảnh|Bakgrunnur|Bakgrunn|Bakgrund|Baggrund|Background|B4|Antecedents|Antecedentes|Ær|Aer|Achtergrond):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; state.allowMultilineArgument = false; state.inKeywordLine = true; return "keyword"; // SCENARIO OUTLINE } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景大綱|场景大纲|劇本大綱|剧本大纲|テンプレ|シナリオテンプレート|シナリオテンプレ|シナリオアウトライン|시나리오 개요|สรุปเหตุการณ์|โครงสร้างของเหตุการณ์|ವಿವರಣೆ|కథనం|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਟਕਥਾ ਢਾਂਚਾ|परिदृश्य रूपरेखा|سيناريو مخطط|الگوی سناریو|תבנית תרחיש|Сценарийның төзелеше|Сценарий структураси|Структура сценарію|Структура сценария|Структура сценарија|Скица|Рамка на сценарий|Концепт|Περιγραφή Σεναρίου|Wharrimean is|Template Situai|Template Senario|Template Keadaan|Tapausaihio|Szenariogrundriss|Szablon scenariusza|Swa hwær swa|Swa hwaer swa|Struktura scenarija|Structură scenariu|Structura scenariu|Skica|Skenario konsep|Shiver me timbers|Senaryo taslağı|Schema dello scenario|Scenariomall|Scenariomal|Scenario Template|Scenario Outline|Scenario Amlinellol|Scenārijs pēc parauga|Scenarijaus šablonas|Reckon it's like|Raamstsenaarium|Plang vum Szenario|Plan du Scénario|Plan du scénario|Osnova scénáře|Osnova Scenára|Náčrt Scenáru|Náčrt Scénáře|Náčrt Scenára|MISHUN SRSLY|Menggariskan Senario|Lýsing Dæma|Lýsing Atburðarásar|Konturo de la scenaro|Koncept|Khung tình huống|Khung kịch bản|Forgatókönyv vázlat|Esquema do Cenário|Esquema do Cenario|Esquema del escenario|Esquema de l'escenari|Esbozo do escenario|Delineação do Cenário|Delineacao do Cenario|All y'all|Abstrakt Scenario|Abstract Scenario):/)) { state.allowPlaceholders = true; state.allowSteps = true; state.allowMultilineArgument = false; state.inKeywordLine = true; return "keyword"; // EXAMPLES } else if (state.allowScenario && stream.match(/(例子|例|サンプル|예|ชุดของเหตุการณ์|ชุดของตัวอย่าง|ಉದಾಹರಣೆಗಳು|ఉదాహరణలు|ਉਦਾਹਰਨਾਂ|उदाहरण|نمونه ها|امثلة|דוגמאות|Үрнәкләр|Сценарији|Примеры|Примери|Приклади|Мисоллар|Мисаллар|Σενάρια|Παραδείγματα|You'll wanna|Voorbeelden|Variantai|Tapaukset|Se þe|Se the|Se ðe|Scenarios|Scenariji|Scenarijai|Przykłady|Primjeri|Primeri|Příklady|Príklady|Piemēri|Példák|Pavyzdžiai|Paraugs|Örnekler|Juhtumid|Exemplos|Exemples|Exemple|Exempel|EXAMPLZ|Examples|Esempi|Enghreifftiau|Ekzemploj|Eksempler|Ejemplos|Dữ liệu|Dead men tell no tales|Dæmi|Contoh|Cenários|Cenarios|Beispiller|Beispiele|Atburðarásir):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; state.allowMultilineArgument = true; return "keyword"; // SCENARIO } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景|场景|劇本|剧本|シナリオ|시나리오|เหตุการณ์|ಕಥಾಸಾರಾಂಶ|సన్నివేశం|ਪਟਕਥਾ|परिदृश्य|سيناريو|سناریو|תרחיש|Сценарій|Сценарио|Сценарий|Пример|Σενάριο|Tình huống|The thing of it is|Tapaus|Szenario|Swa|Stsenaarium|Skenario|Situai|Senaryo|Senario|Scenaro|Scenariusz|Scenariu|Scénario|Scenario|Scenarijus|Scenārijs|Scenarij|Scenarie|Scénář|Scenár|Primer|MISHUN|Kịch bản|Keadaan|Heave to|Forgatókönyv|Escenario|Escenari|Cenário|Cenario|Awww, look mate|Atburðarás):/)) { state.allowPlaceholders = false; state.allowSteps = true; state.allowBackground = false; state.allowMultilineArgument = false; state.inKeywordLine = true; return "keyword"; // STEPS } else if (!state.inKeywordLine && state.allowSteps && stream.match(/(那麼|那么|而且|當|当|并且|同時|同时|前提|假设|假設|假定|假如|但是|但し|並且|もし|ならば|ただし|しかし|かつ|하지만|조건|먼저|만일|만약|단|그리고|그러면|และ |เมื่อ |แต่ |ดังนั้น |กำหนดให้ |ಸ್ಥಿತಿಯನ್ನು |ಮತ್ತು |ನೀಡಿದ |ನಂತರ |ಆದರೆ |మరియు |చెప్పబడినది |కాని |ఈ పరిస్థితిలో |అప్పుడు |ਪਰ |ਤਦ |ਜੇਕਰ |ਜਿਵੇਂ ਕਿ |ਜਦੋਂ |ਅਤੇ |यदि |परन्तु |पर |तब |तदा |तथा |जब |चूंकि |किन्तु |कदा |और |अगर |و |هنگامی |متى |لكن |عندما |ثم |بفرض |با فرض |اما |اذاً |آنگاه |כאשר |וגם |בהינתן |אזי |אז |אבל |Якщо |Һәм |Унда |Тоді |Тогда |То |Также |Та |Пусть |Припустимо, що |Припустимо |Онда |Но |Нехай |Нәтиҗәдә |Лекин |Ләкин |Коли |Когда |Когато |Када |Кад |К тому же |І |И |Задато |Задати |Задате |Если |Допустим |Дано |Дадено |Вә |Ва |Бирок |Әмма |Әйтик |Әгәр |Аммо |Али |Але |Агар |А також |А |Τότε |Όταν |Και |Δεδομένου |Αλλά |Þurh |Þegar |Þa þe |Þá |Þa |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Za předpokladu |Za predpokladu |Youse know when youse got |Youse know like when |Yna |Yeah nah |Y'know |Y |Wun |Wtedy |When y'all |When |Wenn |WEN |wann |Ve |Và |Und |Un |ugeholl |Too right |Thurh |Thì |Then y'all |Then |Tha the |Tha |Tetapi |Tapi |Tak |Tada |Tad |Stel |Soit |Siis |Și |Şi |Si |Sed |Se |Så |Quando |Quand |Quan |Pryd |Potom |Pokud |Pokiaľ |Però |Pero |Pak |Oraz |Onda |Ond |Oletetaan |Og |Och |O zaman |Niin |Nhưng |När |Når |Mutta |Men |Mas |Maka |Majd |Mając |Mais |Maar |mä |Ma |Lorsque |Lorsqu'|Logo |Let go and haul |Kun |Kuid |Kui |Kiedy |Khi |Ketika |Kemudian |Keď |Když |Kaj |Kai |Kada |Kad |Jeżeli |Jeśli |Ja |It's just unbelievable |Ir |I CAN HAZ |I |Ha |Givun |Givet |Given y'all |Given |Gitt |Gegeven |Gegeben seien |Gegeben sei |Gdy |Gangway! |Fakat |Étant donnés |Etant donnés |Étant données |Etant données |Étant donnée |Etant donnée |Étant donné |Etant donné |Et |És |Entonces |Entón |Então |Entao |En |Eğer ki |Ef |Eeldades |E |Ðurh |Duota |Dun |Donitaĵo |Donat |Donada |Do |Diyelim ki |Diberi |Dengan |Den youse gotta |DEN |De |Dato |Dați fiind |Daţi fiind |Dati fiind |Dati |Date fiind |Date |Data |Dat fiind |Dar |Dann |dann |Dan |Dados |Dado |Dadas |Dada |Ða ðe |Ða |Cuando |Cho |Cando |Când |Cand |Cal |But y'all |But at the end of the day I reckon |BUT |But |Buh |Blimey! |Biết |Bet |Bagi |Aye |awer |Avast! |Atunci |Atesa |Atès |Apabila |Anrhegedig a |Angenommen |And y'all |And |AN |An |an |Amikor |Amennyiben |Ama |Als |Alors |Allora |Ali |Aleshores |Ale |Akkor |Ak |Adott |Ac |Aber |A zároveň |A tiež |A taktiež |A také |A |a |7 |\* )/)) { state.inStep = true; state.allowPlaceholders = true; state.allowMultilineArgument = true; state.inKeywordLine = true; return "keyword"; // INLINE STRING } else if (stream.match(/"[^"]*"?/)) { return "string"; // PLACEHOLDER } else if (state.allowPlaceholders && stream.match(/<[^>]*>?/)) { return "variable"; // Fall through } else { stream.next(); stream.eatWhile(/[^@"<#]/); return null; } } }; }); CodeMirror.defineMIME("text/x-feature", "gherkin"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/gherkin/index.html ================================================ CodeMirror: Gherkin mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Gherkin

    Gherkin mode

    MIME types defined: text/x-feature.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/go/go.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("go", function(config) { var indentUnit = config.indentUnit; var keywords = { "break":true, "case":true, "chan":true, "const":true, "continue":true, "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, "func":true, "go":true, "goto":true, "if":true, "import":true, "interface":true, "map":true, "package":true, "range":true, "return":true, "select":true, "struct":true, "switch":true, "type":true, "var":true, "bool":true, "byte":true, "complex64":true, "complex128":true, "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, "uint64":true, "int":true, "uint":true, "uintptr":true, "error": true, "rune":true, "any":true, "comparable":true }; var atoms = { "true":true, "false":true, "iota":true, "nil":true, "append":true, "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true, "len":true, "make":true, "new":true, "panic":true, "print":true, "println":true, "real":true, "recover":true }; var isOperatorChar = /[+\-*&^%:=<>!|\/]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'" || ch == "`") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\d\.]/.test(ch)) { if (ch == ".") { stream.match(/^[0-9_]+([eE][\-+]?[0-9_]+)?/); } else if (ch == "0") { stream.match(/^[xX][0-9a-fA-F_]+/) || stream.match(/^[0-7_]+/); } else { stream.match(/^[0-9_]*\.?[0-9_]*([eE][\-+]?[0-9_]+)?/); } return "number"; } if (/[\[\]{}\(\),;\:\.]/.test(ch)) { curPunc = ch; return null; } if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_\xa1-\uffff]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) { if (cur == "case" || cur == "default") curPunc = "case"; return "keyword"; } if (atoms.propertyIsEnumerable(cur)) return "atom"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && quote != "`" && next == "\\"; } if (end || !(escaped || quote == "`")) state.tokenize = tokenBase; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { return state.context = new Context(state.indented, col, type, null, state.context); } function popContext(state) { if (!state.context.prev) return; var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } // Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; if (ctx.type == "case") ctx.type = "}"; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "case") ctx.type = "case"; else if (curPunc == "}" && ctx.type == "}") popContext(state); else if (curPunc == ctx.type) popContext(state); state.startOfLine = false; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { state.context.type = "}"; return ctx.indented; } var closing = firstChar == ctx.type; if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : indentUnit); }, electricChars: "{}):", closeBrackets: "()[]{}''\"\"``", fold: "brace", blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//" }; }); CodeMirror.defineMIME("text/x-go", "go"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/go/index.html ================================================ CodeMirror: Go mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Go

    Go mode

    MIME type: text/x-go

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/groovy/groovy.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("groovy", function(config) { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = words( "abstract as assert boolean break byte case catch char class const continue def default " + "do double else enum extends final finally float for goto if implements import in " + "instanceof int interface long native new package private protected public return " + "short static strictfp super switch synchronized threadsafe throw throws trait transient " + "try void volatile while"); var blockKeywords = words("catch class def do else enum finally for if interface switch trait try while"); var standaloneKeywords = words("return break continue"); var atoms = words("null true false this"); var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { return startString(ch, stream, state); } if (/[\[\]{}\(\),;\:\.]/.test(ch)) { curPunc = ch; return null; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); } return "number"; } if (ch == "/") { if (stream.eat("*")) { state.tokenize.push(tokenComment); return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } if (expectExpression(state.lastToken, false)) { return startString(ch, stream, state); } } if (ch == "-" && stream.eat(">")) { curPunc = "->"; return null; } if (/[+\-*&%=<>!?|\/~]/.test(ch)) { stream.eatWhile(/[+\-*&%=<>|~]/); return "operator"; } stream.eatWhile(/[\w\$_]/); if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; } if (state.lastToken == ".") return "property"; if (stream.eat(":")) { curPunc = "proplabel"; return "property"; } var cur = stream.current(); if (atoms.propertyIsEnumerable(cur)) { return "atom"; } if (keywords.propertyIsEnumerable(cur)) { if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; else if (standaloneKeywords.propertyIsEnumerable(cur)) curPunc = "standalone"; return "keyword"; } return "variable"; } tokenBase.isBase = true; function startString(quote, stream, state) { var tripleQuoted = false; if (quote != "/" && stream.eat(quote)) { if (stream.eat(quote)) tripleQuoted = true; else return "string"; } function t(stream, state) { var escaped = false, next, end = !tripleQuoted; while ((next = stream.next()) != null) { if (next == quote && !escaped) { if (!tripleQuoted) { break; } if (stream.match(quote + quote)) { end = true; break; } } if (quote == '"' && next == "$" && !escaped) { if (stream.eat("{")) { state.tokenize.push(tokenBaseUntilBrace()); return "string"; } else if (stream.match(/^\w/, false)) { state.tokenize.push(tokenVariableDeref); return "string"; } } escaped = !escaped && next == "\\"; } if (end) state.tokenize.pop(); return "string"; } state.tokenize.push(t); return t(stream, state); } function tokenBaseUntilBrace() { var depth = 1; function t(stream, state) { if (stream.peek() == "}") { depth--; if (depth == 0) { state.tokenize.pop(); return state.tokenize[state.tokenize.length-1](stream, state); } } else if (stream.peek() == "{") { depth++; } return tokenBase(stream, state); } t.isBase = true; return t; } function tokenVariableDeref(stream, state) { var next = stream.match(/^(\.|[\w\$_]+)/) if (!next) { state.tokenize.pop() return state.tokenize[state.tokenize.length-1](stream, state) } return next[0] == "." ? null : "variable" } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize.pop(); break; } maybeEnd = (ch == "*"); } return "comment"; } function expectExpression(last, newline) { return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) || last == "newstatement" || last == "keyword" || last == "proplabel" || (last == "standalone" && !newline); } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { return state.context = new Context(state.indented, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } // Interface return { startState: function(basecolumn) { return { tokenize: [tokenBase], context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), indented: 0, startOfLine: true, lastToken: null }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; // Automatic semicolon insertion if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) { popContext(state); ctx = state.context; } } if (stream.eatSpace()) return null; curPunc = null; var style = state.tokenize[state.tokenize.length-1](stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); // Handle indentation for {x -> \n ... } else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") { popContext(state); state.context.align = false; } else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) pushContext(state, stream.column(), "statement"); state.startOfLine = false; state.lastToken = curPunc || style; return style; }, indent: function(state, textAfter) { if (!state.tokenize[state.tokenize.length-1].isBase) return CodeMirror.Pass; var firstChar = textAfter && textAfter.charAt(0), ctx = state.context; if (ctx.type == "statement" && !expectExpression(state.lastToken, true)) ctx = ctx.prev; var closing = firstChar == ctx.type; if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit); else if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : config.indentUnit); }, electricChars: "{}", closeBrackets: {triples: "'\""}, fold: "brace", blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//" }; }); CodeMirror.defineMIME("text/x-groovy", "groovy"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/groovy/index.html ================================================ CodeMirror: Groovy mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Groovy

    Groovy mode

    MIME types defined: text/x-groovy

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haml/haml.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; // full haml mode. This handled embedded ruby and html fragments too CodeMirror.defineMode("haml", function(config) { var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); var rubyMode = CodeMirror.getMode(config, "ruby"); function rubyInQuote(endQuote) { return function(stream, state) { var ch = stream.peek(); if (ch == endQuote && state.rubyState.tokenize.length == 1) { // step out of ruby context as it seems to complete processing all the braces stream.next(); state.tokenize = html; return "closeAttributeTag"; } else { return ruby(stream, state); } }; } function ruby(stream, state) { if (stream.match("-#")) { stream.skipToEnd(); return "comment"; } return rubyMode.token(stream, state.rubyState); } function html(stream, state) { var ch = stream.peek(); // handle haml declarations. All declarations that cant be handled here // will be passed to html mode if (state.previousToken.style == "comment" ) { if (state.indented > state.previousToken.indented) { stream.skipToEnd(); return "commentLine"; } } if (state.startOfLine) { if (ch == "!" && stream.match("!!")) { stream.skipToEnd(); return "tag"; } else if (stream.match(/^%[\w:#\.]+=/)) { state.tokenize = ruby; return "hamlTag"; } else if (stream.match(/^%[\w:]+/)) { return "hamlTag"; } else if (ch == "/" ) { stream.skipToEnd(); return "comment"; } } if (state.startOfLine || state.previousToken.style == "hamlTag") { if ( ch == "#" || ch == ".") { stream.match(/[\w-#\.]*/); return "hamlAttribute"; } } // do not handle --> as valid ruby, make it HTML close comment instead if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) { state.tokenize = ruby; return state.tokenize(stream, state); } if (state.previousToken.style == "hamlTag" || state.previousToken.style == "closeAttributeTag" || state.previousToken.style == "hamlAttribute") { if (ch == "(") { state.tokenize = rubyInQuote(")"); return state.tokenize(stream, state); } else if (ch == "{") { if (!stream.match(/^\{%.*/)) { state.tokenize = rubyInQuote("}"); return state.tokenize(stream, state); } } } return htmlMode.token(stream, state.htmlState); } return { // default to html mode startState: function() { var htmlState = CodeMirror.startState(htmlMode); var rubyState = CodeMirror.startState(rubyMode); return { htmlState: htmlState, rubyState: rubyState, indented: 0, previousToken: { style: null, indented: 0}, tokenize: html }; }, copyState: function(state) { return { htmlState : CodeMirror.copyState(htmlMode, state.htmlState), rubyState: CodeMirror.copyState(rubyMode, state.rubyState), indented: state.indented, previousToken: state.previousToken, tokenize: state.tokenize }; }, token: function(stream, state) { if (stream.sol()) { state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); state.startOfLine = false; // dont record comment line as we only want to measure comment line with // the opening comment block if (style && style != "commentLine") { state.previousToken = { style: style, indented: state.indented }; } // if current state is ruby and the previous token is not `,` reset the // tokenize to html if (stream.eol() && state.tokenize == ruby) { stream.backUp(1); var ch = stream.peek(); stream.next(); if (ch && ch != ",") { state.tokenize = html; } } // reprocess some of the specific style tag when finish setting previousToken if (style == "hamlTag") { style = "tag"; } else if (style == "commentLine") { style = "comment"; } else if (style == "hamlAttribute") { style = "attribute"; } else if (style == "closeAttributeTag") { style = null; } return style; } }; }, "htmlmixed", "ruby"); CodeMirror.defineMIME("text/x-haml", "haml"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haml/index.html ================================================ CodeMirror: HAML mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • HAML

    HAML mode

    MIME types defined: text/x-haml.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haml/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "haml"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Requires at least one media query MT("elementName", "[tag %h1] Hey There"); MT("oneElementPerLine", "[tag %h1] Hey There %h2"); MT("idSelector", "[tag %h1][attribute #test] Hey There"); MT("classSelector", "[tag %h1][attribute .hello] Hey There"); MT("docType", "[tag !!! XML]"); MT("comment", "[comment / Hello WORLD]"); MT("notComment", "[tag %h1] This is not a / comment "); MT("attributes", "[tag %a]([variable title][operator =][string \"test\"]){[atom :title] [operator =>] [string \"test\"]}"); MT("htmlCode", "[tag&bracket <][tag h1][tag&bracket >]Title[tag&bracket ]"); MT("rubyBlock", "[operator =][variable-2 @item]"); MT("selectorRubyBlock", "[tag %a.selector=] [variable-2 @item]"); MT("nestedRubyBlock", "[tag %a]", " [operator =][variable puts] [string \"test\"]"); MT("multilinePlaintext", "[tag %p]", " Hello,", " World"); MT("multilineRuby", "[tag %p]", " [comment -# this is a comment]", " [comment and this is a comment too]", " Date/Time", " [operator -] [variable now] [operator =] [tag DateTime][operator .][property now]", " [tag %strong=] [variable now]", " [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])", " [operator =][string \"Happy\"]", " [operator =][string \"Belated\"]", " [operator =][string \"Birthday\"]"); MT("multilineComment", "[comment /]", " [comment Multiline]", " [comment Comment]"); MT("hamlComment", "[comment -# this is a comment]"); MT("multilineHamlComment", "[comment -# this is a comment]", " [comment and this is a comment too]"); MT("multilineHTMLComment", "[comment ]"); MT("hamlAfterRubyTag", "[attribute .block]", " [tag %strong=] [variable now]", " [attribute .test]", " [operator =][variable now]", " [attribute .right]"); MT("stretchedRuby", "[operator =] [variable puts] [string \"Hello\"],", " [string \"World\"]"); MT("interpolationInHashAttribute", //"[tag %div]{[atom :id] [operator =>] [string \"#{][variable test][string }_#{][variable ting][string }\"]} test"); "[tag %div]{[atom :id] [operator =>] [string \"#{][variable test][string }_#{][variable ting][string }\"]} test"); MT("interpolationInHTMLAttribute", "[tag %div]([variable title][operator =][string \"#{][variable test][string }_#{][variable ting]()[string }\"]) Test"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/handlebars/handlebars.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple"), require("../../addon/mode/multiplex")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple", "../../addon/mode/multiplex"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineSimpleMode("handlebars-tags", { start: [ { regex: /\{\{\{/, push: "handlebars_raw", token: "tag" }, { regex: /\{\{!--/, push: "dash_comment", token: "comment" }, { regex: /\{\{!/, push: "comment", token: "comment" }, { regex: /\{\{/, push: "handlebars", token: "tag" } ], handlebars_raw: [ { regex: /\}\}\}/, pop: true, token: "tag" }, ], handlebars: [ { regex: /\}\}/, pop: true, token: "tag" }, // Double and single quotes { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" }, { regex: /'(?:[^\\']|\\.)*'?/, token: "string" }, // Handlebars keywords { regex: />|[#\/]([A-Za-z_]\w*)/, token: "keyword" }, { regex: /(?:else|this)\b/, token: "keyword" }, // Numeral { regex: /\d+/i, token: "number" }, // Atoms like = and . { regex: /=|~|@|true|false/, token: "atom" }, // Paths { regex: /(?:\.\.\/)*(?:[A-Za-z_][\w\.]*)+/, token: "variable-2" } ], dash_comment: [ { regex: /--\}\}/, pop: true, token: "comment" }, // Commented code { regex: /./, token: "comment"} ], comment: [ { regex: /\}\}/, pop: true, token: "comment" }, { regex: /./, token: "comment" } ], meta: { blockCommentStart: "{{--", blockCommentEnd: "--}}" } }); CodeMirror.defineMode("handlebars", function(config, parserConfig) { var handlebars = CodeMirror.getMode(config, "handlebars-tags"); if (!parserConfig || !parserConfig.base) return handlebars; return CodeMirror.multiplexingMode( CodeMirror.getMode(config, parserConfig.base), {open: "{{", close: /\}\}\}?/, mode: handlebars, parseDelimiters: true} ); }); CodeMirror.defineMIME("text/x-handlebars-template", "handlebars"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/handlebars/index.html ================================================ CodeMirror: Handlebars mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • HTML mixed

    Handlebars

    Handlebars syntax highlighting for CodeMirror.

    MIME types defined: text/x-handlebars-template

    Supported options: base to set the mode to wrap. For example, use

    mode: {name: "handlebars", base: "text/html"}

    to highlight an HTML template.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haskell/haskell.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("haskell", function(_config, modeConfig) { function switchState(source, setState, f) { setState(f); return f(source, setState); } // These should all be Unicode extended, as per the Haskell 2010 report var smallRE = /[a-z_]/; var largeRE = /[A-Z]/; var digitRE = /\d/; var hexitRE = /[0-9A-Fa-f]/; var octitRE = /[0-7]/; var idRE = /[a-z_A-Z0-9'\xa1-\uffff]/; var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/; var specialRE = /[(),;[\]`{}]/; var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer function normal(source, setState) { if (source.eatWhile(whiteCharRE)) { return null; } var ch = source.next(); if (specialRE.test(ch)) { if (ch == '{' && source.eat('-')) { var t = "comment"; if (source.eat('#')) { t = "meta"; } return switchState(source, setState, ncomment(t, 1)); } return null; } if (ch == '\'') { if (source.eat('\\')) { source.next(); // should handle other escapes here } else { source.next(); } if (source.eat('\'')) { return "string"; } return "string error"; } if (ch == '"') { return switchState(source, setState, stringLiteral); } if (largeRE.test(ch)) { source.eatWhile(idRE); if (source.eat('.')) { return "qualifier"; } return "variable-2"; } if (smallRE.test(ch)) { source.eatWhile(idRE); return "variable"; } if (digitRE.test(ch)) { if (ch == '0') { if (source.eat(/[xX]/)) { source.eatWhile(hexitRE); // should require at least 1 return "integer"; } if (source.eat(/[oO]/)) { source.eatWhile(octitRE); // should require at least 1 return "number"; } } source.eatWhile(digitRE); var t = "number"; if (source.match(/^\.\d+/)) { t = "number"; } if (source.eat(/[eE]/)) { t = "number"; source.eat(/[-+]/); source.eatWhile(digitRE); // should require at least 1 } return t; } if (ch == "." && source.eat(".")) return "keyword"; if (symbolRE.test(ch)) { if (ch == '-' && source.eat(/-/)) { source.eatWhile(/-/); if (!source.eat(symbolRE)) { source.skipToEnd(); return "comment"; } } var t = "variable"; if (ch == ':') { t = "variable-2"; } source.eatWhile(symbolRE); return t; } return "error"; } function ncomment(type, nest) { if (nest == 0) { return normal; } return function(source, setState) { var currNest = nest; while (!source.eol()) { var ch = source.next(); if (ch == '{' && source.eat('-')) { ++currNest; } else if (ch == '-' && source.eat('}')) { --currNest; if (currNest == 0) { setState(normal); return type; } } } setState(ncomment(type, currNest)); return type; }; } function stringLiteral(source, setState) { while (!source.eol()) { var ch = source.next(); if (ch == '"') { setState(normal); return "string"; } if (ch == '\\') { if (source.eol() || source.eat(whiteCharRE)) { setState(stringGap); return "string"; } if (source.eat('&')) { } else { source.next(); // should handle other escapes here } } } setState(normal); return "string error"; } function stringGap(source, setState) { if (source.eat('\\')) { return switchState(source, setState, stringLiteral); } source.next(); setState(normal); return "error"; } var wellKnownWords = (function() { var wkw = {}; function setType(t) { return function () { for (var i = 0; i < arguments.length; i++) wkw[arguments[i]] = t; }; } setType("keyword")( "case", "class", "data", "default", "deriving", "do", "else", "foreign", "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", "module", "newtype", "of", "then", "type", "where", "_"); setType("keyword")( "\.\.", ":", "::", "=", "\\", "<-", "->", "@", "~", "=>"); setType("builtin")( "!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<*", "<=", "<$>", "<*>", "=<<", "==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "*>", "**"); setType("builtin")( "Applicative", "Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq", "False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT", "IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left", "Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read", "ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS", "String", "True"); setType("builtin")( "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf", "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling", "compare", "concat", "concatMap", "const", "cos", "cosh", "curry", "cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either", "elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo", "enumFromTo", "error", "even", "exp", "exponent", "fail", "filter", "flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap", "foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger", "fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents", "getLine", "head", "id", "init", "interact", "ioError", "isDenormalized", "isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last", "lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map", "mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound", "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or", "otherwise", "pi", "pred", "print", "product", "properFraction", "pure", "putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile", "readIO", "readList", "readLn", "readParen", "reads", "readsPrec", "realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse", "round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq", "sequence", "sequence_", "show", "showChar", "showList", "showParen", "showString", "shows", "showsPrec", "significand", "signum", "sin", "sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum", "tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger", "toRational", "truncate", "uncurry", "undefined", "unlines", "until", "unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip", "zip3", "zipWith", "zipWith3"); var override = modeConfig.overrideKeywords; if (override) for (var word in override) if (override.hasOwnProperty(word)) wkw[word] = override[word]; return wkw; })(); return { startState: function () { return { f: normal }; }, copyState: function (s) { return { f: s.f }; }, token: function(stream, state) { var t = state.f(stream, function(s) { state.f = s; }); var w = stream.current(); return wellKnownWords.hasOwnProperty(w) ? wellKnownWords[w] : t; }, blockCommentStart: "{-", blockCommentEnd: "-}", lineComment: "--" }; }); CodeMirror.defineMIME("text/x-haskell", "haskell"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haskell/index.html ================================================ CodeMirror: Haskell mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Haskell

    Haskell mode

    MIME types defined: text/x-haskell.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haskell-literate/haskell-literate.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function (mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../haskell/haskell")) else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../haskell/haskell"], mod) else // Plain browser env mod(CodeMirror) })(function (CodeMirror) { "use strict" CodeMirror.defineMode("haskell-literate", function (config, parserConfig) { var baseMode = CodeMirror.getMode(config, (parserConfig && parserConfig.base) || "haskell") return { startState: function () { return { inCode: false, baseState: CodeMirror.startState(baseMode) } }, token: function (stream, state) { if (stream.sol()) { if (state.inCode = stream.eat(">")) return "meta" } if (state.inCode) { return baseMode.token(stream, state.baseState) } else { stream.skipToEnd() return "comment" } }, innerMode: function (state) { return state.inCode ? {state: state.baseState, mode: baseMode} : null } } }, "haskell") CodeMirror.defineMIME("text/x-literate-haskell", "haskell-literate") }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haskell-literate/index.html ================================================ CodeMirror: Haskell-literate mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Haskell-literate

    Haskell literate mode

    MIME types defined: text/x-literate-haskell.

    Parser configuration parameters recognized: base to set the base mode (defaults to "haskell").

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haxe/haxe.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("haxe", function(config, parserConfig) { var indentUnit = config.indentUnit; // Tokenizer function kw(type) {return {type: type, style: "keyword"};} var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"}; var type = kw("typedef"); var keywords = { "if": A, "while": A, "else": B, "do": B, "try": B, "return": C, "break": C, "continue": C, "new": C, "throw": C, "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"), "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"), "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"), "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), "in": operator, "never": kw("property_access"), "trace":kw("trace"), "class": type, "abstract":type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type, "true": atom, "false": atom, "null": atom }; var isOperatorChar = /[+\-*&%=<>!?|]/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function toUnescaped(stream, end) { var escaped = false, next; while ((next = stream.next()) != null) { if (next == end && !escaped) return true; escaped = !escaped && next == "\\"; } } // Used as scratch variables to communicate multiple values without // consing up tons of objects. var type, content; function ret(tp, style, cont) { type = tp; content = cont; return style; } function haxeTokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { return chain(stream, state, haxeTokenString(ch)); } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { return ret(ch); } else if (ch == "0" && stream.eat(/x/i)) { stream.eatWhile(/[\da-f]/i); return ret("number", "number"); } else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { stream.match(/^\d*(?:\.\d*(?!\.))?(?:[eE][+\-]?\d+)?/); return ret("number", "number"); } else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) { toUnescaped(stream, "/"); stream.eatWhile(/[gimsu]/); return ret("regexp", "string-2"); } else if (ch == "/") { if (stream.eat("*")) { return chain(stream, state, haxeTokenComment); } else if (stream.eat("/")) { stream.skipToEnd(); return ret("comment", "comment"); } else { stream.eatWhile(isOperatorChar); return ret("operator", null, stream.current()); } } else if (ch == "#") { stream.skipToEnd(); return ret("conditional", "meta"); } else if (ch == "@") { stream.eat(/:/); stream.eatWhile(/[\w_]/); return ret ("metadata", "meta"); } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return ret("operator", null, stream.current()); } else { var word; if(/[A-Z]/.test(ch)) { stream.eatWhile(/[\w_<>]/); word = stream.current(); return ret("type", "variable-3", word); } else { stream.eatWhile(/[\w_]/); var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; return (known && state.kwAllowed) ? ret(known.type, known.style, word) : ret("variable", "variable", word); } } } function haxeTokenString(quote) { return function(stream, state) { if (toUnescaped(stream, quote)) state.tokenize = haxeTokenBase; return ret("string", "string"); }; } function haxeTokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = haxeTokenBase; break; } maybeEnd = (ch == "*"); } return ret("comment", "comment"); } // Parser var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; function HaxeLexical(indented, column, type, align, prev, info) { this.indented = indented; this.column = column; this.type = type; this.prev = prev; this.info = info; if (align != null) this.align = align; } function inScope(state, varname) { for (var v = state.localVars; v; v = v.next) if (v.name == varname) return true; } function parseHaxe(state, style, type, content, stream) { var cc = state.cc; // Communicate our context to the combinators. // (Less wasteful than consing up a hundred closures on every call.) cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; if (!state.lexical.hasOwnProperty("align")) state.lexical.align = true; while(true) { var combinator = cc.length ? cc.pop() : statement; if (combinator(type, content)) { while(cc.length && cc[cc.length - 1].lex) cc.pop()(); if (cx.marked) return cx.marked; if (type == "variable" && inScope(state, content)) return "variable-2"; if (type == "variable" && imported(state, content)) return "variable-3"; return style; } } } function imported(state, typename) { if (/[a-z]/.test(typename.charAt(0))) return false; var len = state.importedtypes.length; for (var i = 0; i= 0; i--) cx.cc.push(arguments[i]); } function cont() { pass.apply(null, arguments); return true; } function inList(name, list) { for (var v = list; v; v = v.next) if (v.name == name) return true; return false; } function register(varname) { var state = cx.state; if (state.context) { cx.marked = "def"; if (inList(varname, state.localVars)) return; state.localVars = {name: varname, next: state.localVars}; } else if (state.globalVars) { if (inList(varname, state.globalVars)) return; state.globalVars = {name: varname, next: state.globalVars}; } } // Combinators var defaultVars = {name: "this", next: null}; function pushcontext() { if (!cx.state.context) cx.state.localVars = defaultVars; cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; } function popcontext() { cx.state.localVars = cx.state.context.vars; cx.state.context = cx.state.context.prev; } popcontext.lex = true; function pushlex(type, info) { var result = function() { var state = cx.state; state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); }; result.lex = true; return result; } function poplex() { var state = cx.state; if (state.lexical.prev) { if (state.lexical.type == ")") state.indented = state.lexical.indented; state.lexical = state.lexical.prev; } } poplex.lex = true; function expect(wanted) { function f(type) { if (type == wanted) return cont(); else if (wanted == ";") return pass(); else return cont(f); } return f; } function statement(type) { if (type == "@") return cont(metadef); if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext); if (type == ";") return cont(); if (type == "attribute") return cont(maybeattribute); if (type == "function") return cont(functiondef); if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); if (type == "case") return cont(expression, expect(":")); if (type == "default") return cont(expect(":")); if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); if (type == "import") return cont(importdef, expect(";")); if (type == "typedef") return cont(typedef); return pass(pushlex("stat"), expression, expect(";"), poplex); } function expression(type) { if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); if (type == "type" ) return cont(maybeoperator); if (type == "function") return cont(functiondef); if (type == "keyword c") return cont(maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); if (type == "operator") return cont(expression); if (type == "[") return cont(pushlex("]"), commasep(maybeexpression, "]"), poplex, maybeoperator); if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); return cont(); } function maybeexpression(type) { if (type.match(/[;\}\)\],]/)) return pass(); return pass(expression); } function maybeoperator(type, value) { if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); if (type == "operator" || type == ":") return cont(expression); if (type == ";") return; if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); if (type == ".") return cont(property, maybeoperator); if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); } function maybeattribute(type) { if (type == "attribute") return cont(maybeattribute); if (type == "function") return cont(functiondef); if (type == "var") return cont(vardef1); } function metadef(type) { if(type == ":") return cont(metadef); if(type == "variable") return cont(metadef); if(type == "(") return cont(pushlex(")"), commasep(metaargs, ")"), poplex, statement); } function metaargs(type) { if(type == "variable") return cont(); } function importdef (type, value) { if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef); } function typedef (type, value) { if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); } } function maybelabel(type) { if (type == ":") return cont(poplex, statement); return pass(maybeoperator, expect(";"), poplex); } function property(type) { if (type == "variable") {cx.marked = "property"; return cont();} } function objprop(type) { if (type == "variable") cx.marked = "property"; if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); } function commasep(what, end) { function proceed(type) { if (type == ",") return cont(what, proceed); if (type == end) return cont(); return cont(expect(end)); } return function(type) { if (type == end) return cont(); else return pass(what, proceed); }; } function block(type) { if (type == "}") return cont(); return pass(statement, block); } function vardef1(type, value) { if (type == "variable"){register(value); return cont(typeuse, vardef2);} return cont(); } function vardef2(type, value) { if (value == "=") return cont(expression, vardef2); if (type == ",") return cont(vardef1); } function forspec1(type, value) { if (type == "variable") { register(value); return cont(forin, expression) } else { return pass() } } function forin(_type, value) { if (value == "in") return cont(); } function functiondef(type, value) { //function names starting with upper-case letters are recognised as types, so cludging them together here. if (type == "variable" || type == "type") {register(value); return cont(functiondef);} if (value == "new") return cont(functiondef); if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext); } function typeuse(type) { if(type == ":") return cont(typestring); } function typestring(type) { if(type == "type") return cont(); if(type == "variable") return cont(); if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex); } function typeprop(type) { if(type == "variable") return cont(typeuse); } function funarg(type, value) { if (type == "variable") {register(value); return cont(typeuse);} } // Interface return { startState: function(basecolumn) { var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"]; var state = { tokenize: haxeTokenBase, reAllowed: true, kwAllowed: true, cc: [], lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, importedtypes: defaulttypes, context: parserConfig.localVars && {vars: parserConfig.localVars}, indented: 0 }; if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") state.globalVars = parserConfig.globalVars; return state; }, token: function(stream, state) { if (stream.sol()) { if (!state.lexical.hasOwnProperty("align")) state.lexical.align = false; state.indented = stream.indentation(); } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (type == "comment") return style; state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); state.kwAllowed = type != '.'; return parseHaxe(state, style, type, content, stream); }, indent: function(state, textAfter) { if (state.tokenize != haxeTokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; var type = lexical.type, closing = firstChar == type; if (type == "vardef") return lexical.indented + 4; else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "stat" || type == "form") return lexical.indented + indentUnit; else if (lexical.info == "switch" && !closing) return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); else if (lexical.align) return lexical.column + (closing ? 0 : 1); else return lexical.indented + (closing ? 0 : indentUnit); }, electricChars: "{}", blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//" }; }); CodeMirror.defineMIME("text/x-haxe", "haxe"); CodeMirror.defineMode("hxml", function () { return { startState: function () { return { define: false, inString: false }; }, token: function (stream, state) { var ch = stream.peek(); var sol = stream.sol(); ///* comments */ if (ch == "#") { stream.skipToEnd(); return "comment"; } if (sol && ch == "-") { var style = "variable-2"; stream.eat(/-/); if (stream.peek() == "-") { stream.eat(/-/); style = "keyword a"; } if (stream.peek() == "D") { stream.eat(/[D]/); style = "keyword c"; state.define = true; } stream.eatWhile(/[A-Z]/i); return style; } var ch = stream.peek(); if (state.inString == false && ch == "'") { state.inString = true; stream.next(); } if (state.inString == true) { if (stream.skipTo("'")) { } else { stream.skipToEnd(); } if (stream.peek() == "'") { stream.next(); state.inString = false; } return "string"; } stream.next(); return null; }, lineComment: "#" }; }); CodeMirror.defineMIME("text/x-hxml", "hxml"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/haxe/index.html ================================================ CodeMirror: Haxe mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Haxe

    Haxe mode

    Hxml mode:

    MIME types defined: text/x-haxe, text/x-hxml.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/htmlembedded/htmlembedded.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../../addon/mode/multiplex")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../../addon/mode/multiplex"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { var closeComment = parserConfig.closeComment || "--%>" return CodeMirror.multiplexingMode(CodeMirror.getMode(config, "htmlmixed"), { open: parserConfig.openComment || "<%--", close: closeComment, delimStyle: "comment", mode: {token: function(stream) { stream.skipTo(closeComment) || stream.skipToEnd() return "comment" }} }, { open: parserConfig.open || parserConfig.scriptStartRegex || "<%", close: parserConfig.close || parserConfig.scriptEndRegex || "%>", mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec) }); }, "htmlmixed"); CodeMirror.defineMIME("application/x-ejs", {name: "htmlembedded", scriptingModeSpec:"javascript"}); CodeMirror.defineMIME("application/x-aspx", {name: "htmlembedded", scriptingModeSpec:"text/x-csharp"}); CodeMirror.defineMIME("application/x-jsp", {name: "htmlembedded", scriptingModeSpec:"text/x-java"}); CodeMirror.defineMIME("application/x-erb", {name: "htmlembedded", scriptingModeSpec:"ruby"}); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/htmlembedded/index.html ================================================ CodeMirror: Html Embedded Scripts mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Html Embedded Scripts

    Html Embedded Scripts mode

    Mode for html embedded scripts like JSP and ASP.NET. Depends on multiplex and HtmlMixed which in turn depends on JavaScript, CSS and XML.
    Other dependencies include those of the scripting language chosen.

    MIME types defined: application/x-aspx (ASP.NET), application/x-ejs (Embedded JavaScript), application/x-jsp (JavaServer Pages) and application/x-erb

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/htmlmixed/htmlmixed.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var defaultTags = { script: [ ["lang", /(javascript|babel)/i, "javascript"], ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"], ["type", /./, "text/plain"], [null, null, "javascript"] ], style: [ ["lang", /^css$/i, "css"], ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"], ["type", /./, "text/plain"], [null, null, "css"] ] }; function maybeBackup(stream, pat, style) { var cur = stream.current(), close = cur.search(pat); if (close > -1) { stream.backUp(cur.length - close); } else if (cur.match(/<\/?$/)) { stream.backUp(cur.length); if (!stream.match(pat, false)) stream.match(cur); } return style; } var attrRegexpCache = {}; function getAttrRegexp(attr) { var regexp = attrRegexpCache[attr]; if (regexp) return regexp; return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"); } function getAttrValue(text, attr) { var match = text.match(getAttrRegexp(attr)) return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : "" } function getTagRegexp(tagName, anchored) { return new RegExp((anchored ? "^" : "") + "<\/\\s*" + tagName + "\\s*>", "i"); } function addTags(from, to) { for (var tag in from) { var dest = to[tag] || (to[tag] = []); var source = from[tag]; for (var i = source.length - 1; i >= 0; i--) dest.unshift(source[i]) } } function findMatchingMode(tagInfo, tagText) { for (var i = 0; i < tagInfo.length; i++) { var spec = tagInfo[i]; if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]; } } CodeMirror.defineMode("htmlmixed", function (config, parserConfig) { var htmlMode = CodeMirror.getMode(config, { name: "xml", htmlMode: true, multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag, allowMissingTagName: parserConfig.allowMissingTagName, }); var tags = {}; var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes; addTags(defaultTags, tags); if (configTags) addTags(configTags, tags); if (configScript) for (var i = configScript.length - 1; i >= 0; i--) tags.script.unshift(["type", configScript[i].matches, configScript[i].mode]) function html(stream, state) { var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName if (tag && !/[<>\s\/]/.test(stream.current()) && (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && tags.hasOwnProperty(tagName)) { state.inTag = tagName + " " } else if (state.inTag && tag && />$/.test(stream.current())) { var inTag = /^([\S]+) (.*)/.exec(state.inTag) state.inTag = null var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2]) var mode = CodeMirror.getMode(config, modeSpec) var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false); state.token = function (stream, state) { if (stream.match(endTagA, false)) { state.token = html; state.localState = state.localMode = null; return null; } return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState)); }; state.localMode = mode; state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", "")); } else if (state.inTag) { state.inTag += stream.current() if (stream.eol()) state.inTag += " " } return style; }; return { startState: function () { var state = CodeMirror.startState(htmlMode); return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; }, copyState: function (state) { var local; if (state.localState) { local = CodeMirror.copyState(state.localMode, state.localState); } return {token: state.token, inTag: state.inTag, localMode: state.localMode, localState: local, htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; }, token: function (stream, state) { return state.token(stream, state); }, indent: function (state, textAfter, line) { if (!state.localMode || /^\s*<\//.test(textAfter)) return htmlMode.indent(state.htmlState, textAfter, line); else if (state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line); else return CodeMirror.Pass; }, innerMode: function (state) { return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode}; } }; }, "xml", "javascript", "css"); CodeMirror.defineMIME("text/html", "htmlmixed"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/htmlmixed/index.html ================================================ CodeMirror: HTML mixed mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • HTML mixed

    HTML mixed mode

    The HTML mixed mode depends on the XML, JavaScript, and CSS modes.

    It takes an optional mode configuration option, tags, which can be used to add custom behavior for specific tags. When given, it should be an object mapping tag names (for example script) to arrays or three-element arrays. Those inner arrays indicate [attributeName, valueRegexp, modeSpec] specifications. For example, you could use ["type", /^foo$/, "foo"] to map the attribute type="foo" to the foo mode. When the first two fields are null ([null, null, "mode"]), the given mode is used for any such tag that doesn't match any of the previously given attributes. For example:

    var myModeSpec = {
      name: "htmlmixed",
      tags: {
        style: [["type", /^text\/(x-)?scss$/, "text/x-scss"],
                [null, null, "css"]],
        custom: [[null, null, "customMode"]]
      }
    }

    MIME types defined: text/html (redefined, only takes effect if you load this parser after the XML parser).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/http/http.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("http", function() { function failFirstLine(stream, state) { stream.skipToEnd(); state.cur = header; return "error"; } function start(stream, state) { if (stream.match(/^HTTP\/\d\.\d/)) { state.cur = responseStatusCode; return "keyword"; } else if (stream.match(/^[A-Z]+/) && /[ \t]/.test(stream.peek())) { state.cur = requestPath; return "keyword"; } else { return failFirstLine(stream, state); } } function responseStatusCode(stream, state) { var code = stream.match(/^\d+/); if (!code) return failFirstLine(stream, state); state.cur = responseStatusText; var status = Number(code[0]); if (status >= 100 && status < 200) { return "positive informational"; } else if (status >= 200 && status < 300) { return "positive success"; } else if (status >= 300 && status < 400) { return "positive redirect"; } else if (status >= 400 && status < 500) { return "negative client-error"; } else if (status >= 500 && status < 600) { return "negative server-error"; } else { return "error"; } } function responseStatusText(stream, state) { stream.skipToEnd(); state.cur = header; return null; } function requestPath(stream, state) { stream.eatWhile(/\S/); state.cur = requestProtocol; return "string-2"; } function requestProtocol(stream, state) { if (stream.match(/^HTTP\/\d\.\d$/)) { state.cur = header; return "keyword"; } else { return failFirstLine(stream, state); } } function header(stream) { if (stream.sol() && !stream.eat(/[ \t]/)) { if (stream.match(/^.*?:/)) { return "atom"; } else { stream.skipToEnd(); return "error"; } } else { stream.skipToEnd(); return "string"; } } function body(stream) { stream.skipToEnd(); return null; } return { token: function(stream, state) { var cur = state.cur; if (cur != header && cur != body && stream.eatSpace()) return null; return cur(stream, state); }, blankLine: function(state) { state.cur = body; }, startState: function() { return {cur: start}; } }; }); CodeMirror.defineMIME("message/http", "http"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/http/index.html ================================================ CodeMirror: HTTP mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • HTTP

    HTTP mode

    MIME types defined: message/http.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/idl/idl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function wordRegexp(words) { return new RegExp('^((' + words.join(')|(') + '))\\b', 'i'); }; var builtinArray = [ 'a_correlate', 'abs', 'acos', 'adapt_hist_equal', 'alog', 'alog2', 'alog10', 'amoeba', 'annotate', 'app_user_dir', 'app_user_dir_query', 'arg_present', 'array_equal', 'array_indices', 'arrow', 'ascii_template', 'asin', 'assoc', 'atan', 'axis', 'axis', 'bandpass_filter', 'bandreject_filter', 'barplot', 'bar_plot', 'beseli', 'beselj', 'beselk', 'besely', 'beta', 'biginteger', 'bilinear', 'bin_date', 'binary_template', 'bindgen', 'binomial', 'bit_ffs', 'bit_population', 'blas_axpy', 'blk_con', 'boolarr', 'boolean', 'boxplot', 'box_cursor', 'breakpoint', 'broyden', 'bubbleplot', 'butterworth', 'bytarr', 'byte', 'byteorder', 'bytscl', 'c_correlate', 'calendar', 'caldat', 'call_external', 'call_function', 'call_method', 'call_procedure', 'canny', 'catch', 'cd', 'cdf', 'ceil', 'chebyshev', 'check_math', 'chisqr_cvf', 'chisqr_pdf', 'choldc', 'cholsol', 'cindgen', 'cir_3pnt', 'clipboard', 'close', 'clust_wts', 'cluster', 'cluster_tree', 'cmyk_convert', 'code_coverage', 'color_convert', 'color_exchange', 'color_quan', 'color_range_map', 'colorbar', 'colorize_sample', 'colormap_applicable', 'colormap_gradient', 'colormap_rotation', 'colortable', 'comfit', 'command_line_args', 'common', 'compile_opt', 'complex', 'complexarr', 'complexround', 'compute_mesh_normals', 'cond', 'congrid', 'conj', 'constrained_min', 'contour', 'contour', 'convert_coord', 'convol', 'convol_fft', 'coord2to3', 'copy_lun', 'correlate', 'cos', 'cosh', 'cpu', 'cramer', 'createboxplotdata', 'create_cursor', 'create_struct', 'create_view', 'crossp', 'crvlength', 'ct_luminance', 'cti_test', 'cursor', 'curvefit', 'cv_coord', 'cvttobm', 'cw_animate', 'cw_animate_getp', 'cw_animate_load', 'cw_animate_run', 'cw_arcball', 'cw_bgroup', 'cw_clr_index', 'cw_colorsel', 'cw_defroi', 'cw_field', 'cw_filesel', 'cw_form', 'cw_fslider', 'cw_light_editor', 'cw_light_editor_get', 'cw_light_editor_set', 'cw_orient', 'cw_palette_editor', 'cw_palette_editor_get', 'cw_palette_editor_set', 'cw_pdmenu', 'cw_rgbslider', 'cw_tmpl', 'cw_zoom', 'db_exists', 'dblarr', 'dcindgen', 'dcomplex', 'dcomplexarr', 'define_key', 'define_msgblk', 'define_msgblk_from_file', 'defroi', 'defsysv', 'delvar', 'dendro_plot', 'dendrogram', 'deriv', 'derivsig', 'determ', 'device', 'dfpmin', 'diag_matrix', 'dialog_dbconnect', 'dialog_message', 'dialog_pickfile', 'dialog_printersetup', 'dialog_printjob', 'dialog_read_image', 'dialog_write_image', 'dictionary', 'digital_filter', 'dilate', 'dindgen', 'dissolve', 'dist', 'distance_measure', 'dlm_load', 'dlm_register', 'doc_library', 'double', 'draw_roi', 'edge_dog', 'efont', 'eigenql', 'eigenvec', 'ellipse', 'elmhes', 'emboss', 'empty', 'enable_sysrtn', 'eof', 'eos', 'erase', 'erf', 'erfc', 'erfcx', 'erode', 'errorplot', 'errplot', 'estimator_filter', 'execute', 'exit', 'exp', 'expand', 'expand_path', 'expint', 'extract', 'extract_slice', 'f_cvf', 'f_pdf', 'factorial', 'fft', 'file_basename', 'file_chmod', 'file_copy', 'file_delete', 'file_dirname', 'file_expand_path', 'file_gunzip', 'file_gzip', 'file_info', 'file_lines', 'file_link', 'file_mkdir', 'file_move', 'file_poll_input', 'file_readlink', 'file_same', 'file_search', 'file_tar', 'file_test', 'file_untar', 'file_unzip', 'file_which', 'file_zip', 'filepath', 'findgen', 'finite', 'fix', 'flick', 'float', 'floor', 'flow3', 'fltarr', 'flush', 'format_axis_values', 'forward_function', 'free_lun', 'fstat', 'fulstr', 'funct', 'function', 'fv_test', 'fx_root', 'fz_roots', 'gamma', 'gamma_ct', 'gauss_cvf', 'gauss_pdf', 'gauss_smooth', 'gauss2dfit', 'gaussfit', 'gaussian_function', 'gaussint', 'get_drive_list', 'get_dxf_objects', 'get_kbrd', 'get_login_info', 'get_lun', 'get_screen_size', 'getenv', 'getwindows', 'greg2jul', 'grib', 'grid_input', 'grid_tps', 'grid3', 'griddata', 'gs_iter', 'h_eq_ct', 'h_eq_int', 'hanning', 'hash', 'hdf', 'hdf5', 'heap_free', 'heap_gc', 'heap_nosave', 'heap_refcount', 'heap_save', 'help', 'hilbert', 'hist_2d', 'hist_equal', 'histogram', 'hls', 'hough', 'hqr', 'hsv', 'i18n_multibytetoutf8', 'i18n_multibytetowidechar', 'i18n_utf8tomultibyte', 'i18n_widechartomultibyte', 'ibeta', 'icontour', 'iconvertcoord', 'idelete', 'identity', 'idl_base64', 'idl_container', 'idl_validname', 'idlexbr_assistant', 'idlitsys_createtool', 'idlunit', 'iellipse', 'igamma', 'igetcurrent', 'igetdata', 'igetid', 'igetproperty', 'iimage', 'image', 'image_cont', 'image_statistics', 'image_threshold', 'imaginary', 'imap', 'indgen', 'int_2d', 'int_3d', 'int_tabulated', 'intarr', 'interpol', 'interpolate', 'interval_volume', 'invert', 'ioctl', 'iopen', 'ir_filter', 'iplot', 'ipolygon', 'ipolyline', 'iputdata', 'iregister', 'ireset', 'iresolve', 'irotate', 'isa', 'isave', 'iscale', 'isetcurrent', 'isetproperty', 'ishft', 'isocontour', 'isosurface', 'isurface', 'itext', 'itranslate', 'ivector', 'ivolume', 'izoom', 'journal', 'json_parse', 'json_serialize', 'jul2greg', 'julday', 'keyword_set', 'krig2d', 'kurtosis', 'kw_test', 'l64indgen', 'la_choldc', 'la_cholmprove', 'la_cholsol', 'la_determ', 'la_eigenproblem', 'la_eigenql', 'la_eigenvec', 'la_elmhes', 'la_gm_linear_model', 'la_hqr', 'la_invert', 'la_least_square_equality', 'la_least_squares', 'la_linear_equation', 'la_ludc', 'la_lumprove', 'la_lusol', 'la_svd', 'la_tridc', 'la_trimprove', 'la_triql', 'la_trired', 'la_trisol', 'label_date', 'label_region', 'ladfit', 'laguerre', 'lambda', 'lambdap', 'lambertw', 'laplacian', 'least_squares_filter', 'leefilt', 'legend', 'legendre', 'linbcg', 'lindgen', 'linfit', 'linkimage', 'list', 'll_arc_distance', 'lmfit', 'lmgr', 'lngamma', 'lnp_test', 'loadct', 'locale_get', 'logical_and', 'logical_or', 'logical_true', 'lon64arr', 'lonarr', 'long', 'long64', 'lsode', 'lu_complex', 'ludc', 'lumprove', 'lusol', 'm_correlate', 'machar', 'make_array', 'make_dll', 'make_rt', 'map', 'mapcontinents', 'mapgrid', 'map_2points', 'map_continents', 'map_grid', 'map_image', 'map_patch', 'map_proj_forward', 'map_proj_image', 'map_proj_info', 'map_proj_init', 'map_proj_inverse', 'map_set', 'matrix_multiply', 'matrix_power', 'max', 'md_test', 'mean', 'meanabsdev', 'mean_filter', 'median', 'memory', 'mesh_clip', 'mesh_decimate', 'mesh_issolid', 'mesh_merge', 'mesh_numtriangles', 'mesh_obj', 'mesh_smooth', 'mesh_surfacearea', 'mesh_validate', 'mesh_volume', 'message', 'min', 'min_curve_surf', 'mk_html_help', 'modifyct', 'moment', 'morph_close', 'morph_distance', 'morph_gradient', 'morph_hitormiss', 'morph_open', 'morph_thin', 'morph_tophat', 'multi', 'n_elements', 'n_params', 'n_tags', 'ncdf', 'newton', 'noise_hurl', 'noise_pick', 'noise_scatter', 'noise_slur', 'norm', 'obj_class', 'obj_destroy', 'obj_hasmethod', 'obj_isa', 'obj_new', 'obj_valid', 'objarr', 'on_error', 'on_ioerror', 'online_help', 'openr', 'openu', 'openw', 'oplot', 'oploterr', 'orderedhash', 'p_correlate', 'parse_url', 'particle_trace', 'path_cache', 'path_sep', 'pcomp', 'plot', 'plot3d', 'plot', 'plot_3dbox', 'plot_field', 'ploterr', 'plots', 'polar_contour', 'polar_surface', 'polyfill', 'polyshade', 'pnt_line', 'point_lun', 'polarplot', 'poly', 'poly_2d', 'poly_area', 'poly_fit', 'polyfillv', 'polygon', 'polyline', 'polywarp', 'popd', 'powell', 'pref_commit', 'pref_get', 'pref_set', 'prewitt', 'primes', 'print', 'printf', 'printd', 'pro', 'product', 'profile', 'profiler', 'profiles', 'project_vol', 'ps_show_fonts', 'psafm', 'pseudo', 'ptr_free', 'ptr_new', 'ptr_valid', 'ptrarr', 'pushd', 'qgrid3', 'qhull', 'qromb', 'qromo', 'qsimp', 'query_*', 'query_ascii', 'query_bmp', 'query_csv', 'query_dicom', 'query_gif', 'query_image', 'query_jpeg', 'query_jpeg2000', 'query_mrsid', 'query_pict', 'query_png', 'query_ppm', 'query_srf', 'query_tiff', 'query_video', 'query_wav', 'r_correlate', 'r_test', 'radon', 'randomn', 'randomu', 'ranks', 'rdpix', 'read', 'readf', 'read_ascii', 'read_binary', 'read_bmp', 'read_csv', 'read_dicom', 'read_gif', 'read_image', 'read_interfile', 'read_jpeg', 'read_jpeg2000', 'read_mrsid', 'read_pict', 'read_png', 'read_ppm', 'read_spr', 'read_srf', 'read_sylk', 'read_tiff', 'read_video', 'read_wav', 'read_wave', 'read_x11_bitmap', 'read_xwd', 'reads', 'readu', 'real_part', 'rebin', 'recall_commands', 'recon3', 'reduce_colors', 'reform', 'region_grow', 'register_cursor', 'regress', 'replicate', 'replicate_inplace', 'resolve_all', 'resolve_routine', 'restore', 'retall', 'return', 'reverse', 'rk4', 'roberts', 'rot', 'rotate', 'round', 'routine_filepath', 'routine_info', 'rs_test', 's_test', 'save', 'savgol', 'scale3', 'scale3d', 'scatterplot', 'scatterplot3d', 'scope_level', 'scope_traceback', 'scope_varfetch', 'scope_varname', 'search2d', 'search3d', 'sem_create', 'sem_delete', 'sem_lock', 'sem_release', 'set_plot', 'set_shading', 'setenv', 'sfit', 'shade_surf', 'shade_surf_irr', 'shade_volume', 'shift', 'shift_diff', 'shmdebug', 'shmmap', 'shmunmap', 'shmvar', 'show3', 'showfont', 'signum', 'simplex', 'sin', 'sindgen', 'sinh', 'size', 'skewness', 'skip_lun', 'slicer3', 'slide_image', 'smooth', 'sobel', 'socket', 'sort', 'spawn', 'sph_4pnt', 'sph_scat', 'spher_harm', 'spl_init', 'spl_interp', 'spline', 'spline_p', 'sprsab', 'sprsax', 'sprsin', 'sprstp', 'sqrt', 'standardize', 'stddev', 'stop', 'strarr', 'strcmp', 'strcompress', 'streamline', 'streamline', 'stregex', 'stretch', 'string', 'strjoin', 'strlen', 'strlowcase', 'strmatch', 'strmessage', 'strmid', 'strpos', 'strput', 'strsplit', 'strtrim', 'struct_assign', 'struct_hide', 'strupcase', 'surface', 'surface', 'surfr', 'svdc', 'svdfit', 'svsol', 'swap_endian', 'swap_endian_inplace', 'symbol', 'systime', 't_cvf', 't_pdf', 't3d', 'tag_names', 'tan', 'tanh', 'tek_color', 'temporary', 'terminal_size', 'tetra_clip', 'tetra_surface', 'tetra_volume', 'text', 'thin', 'thread', 'threed', 'tic', 'time_test2', 'timegen', 'timer', 'timestamp', 'timestamptovalues', 'tm_test', 'toc', 'total', 'trace', 'transpose', 'tri_surf', 'triangulate', 'trigrid', 'triql', 'trired', 'trisol', 'truncate_lun', 'ts_coef', 'ts_diff', 'ts_fcast', 'ts_smooth', 'tv', 'tvcrs', 'tvlct', 'tvrd', 'tvscl', 'typename', 'uindgen', 'uint', 'uintarr', 'ul64indgen', 'ulindgen', 'ulon64arr', 'ulonarr', 'ulong', 'ulong64', 'uniq', 'unsharp_mask', 'usersym', 'value_locate', 'variance', 'vector', 'vector_field', 'vel', 'velovect', 'vert_t3d', 'voigt', 'volume', 'voronoi', 'voxel_proj', 'wait', 'warp_tri', 'watershed', 'wdelete', 'wf_draw', 'where', 'widget_base', 'widget_button', 'widget_combobox', 'widget_control', 'widget_displaycontextmenu', 'widget_draw', 'widget_droplist', 'widget_event', 'widget_info', 'widget_label', 'widget_list', 'widget_propertysheet', 'widget_slider', 'widget_tab', 'widget_table', 'widget_text', 'widget_tree', 'widget_tree_move', 'widget_window', 'wiener_filter', 'window', 'window', 'write_bmp', 'write_csv', 'write_gif', 'write_image', 'write_jpeg', 'write_jpeg2000', 'write_nrif', 'write_pict', 'write_png', 'write_ppm', 'write_spr', 'write_srf', 'write_sylk', 'write_tiff', 'write_video', 'write_wav', 'write_wave', 'writeu', 'wset', 'wshow', 'wtn', 'wv_applet', 'wv_cwt', 'wv_cw_wavelet', 'wv_denoise', 'wv_dwt', 'wv_fn_coiflet', 'wv_fn_daubechies', 'wv_fn_gaussian', 'wv_fn_haar', 'wv_fn_morlet', 'wv_fn_paul', 'wv_fn_symlet', 'wv_import_data', 'wv_import_wavelet', 'wv_plot3d_wps', 'wv_plot_multires', 'wv_pwt', 'wv_tool_denoise', 'xbm_edit', 'xdisplayfile', 'xdxf', 'xfont', 'xinteranimate', 'xloadct', 'xmanager', 'xmng_tmpl', 'xmtool', 'xobjview', 'xobjview_rotate', 'xobjview_write_image', 'xpalette', 'xpcolor', 'xplot3d', 'xregistered', 'xroi', 'xsq_test', 'xsurface', 'xvaredit', 'xvolume', 'xvolume_rotate', 'xvolume_write_image', 'xyouts', 'zlib_compress', 'zlib_uncompress', 'zoom', 'zoom_24' ]; var builtins = wordRegexp(builtinArray); var keywordArray = [ 'begin', 'end', 'endcase', 'endfor', 'endwhile', 'endif', 'endrep', 'endforeach', 'break', 'case', 'continue', 'for', 'foreach', 'goto', 'if', 'then', 'else', 'repeat', 'until', 'switch', 'while', 'do', 'pro', 'function' ]; var keywords = wordRegexp(keywordArray); CodeMirror.registerHelper("hintWords", "idl", builtinArray.concat(keywordArray)); var identifiers = new RegExp('^[_a-z\xa1-\uffff][_a-z0-9\xa1-\uffff]*', 'i'); var singleOperators = /[+\-*&=<>\/@#~$]/; var boolOperators = new RegExp('(and|or|eq|lt|le|gt|ge|ne|not)', 'i'); function tokenBase(stream) { // whitespaces if (stream.eatSpace()) return null; // Handle one line Comments if (stream.match(';')) { stream.skipToEnd(); return 'comment'; } // Handle Number Literals if (stream.match(/^[0-9\.+-]/, false)) { if (stream.match(/^[+-]?0x[0-9a-fA-F]+/)) return 'number'; if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?/)) return 'number'; if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?/)) return 'number'; } // Handle Strings if (stream.match(/^"([^"]|(""))*"/)) { return 'string'; } if (stream.match(/^'([^']|(''))*'/)) { return 'string'; } // Handle words if (stream.match(keywords)) { return 'keyword'; } if (stream.match(builtins)) { return 'builtin'; } if (stream.match(identifiers)) { return 'variable'; } if (stream.match(singleOperators) || stream.match(boolOperators)) { return 'operator'; } // Handle non-detected items stream.next(); return null; }; CodeMirror.defineMode('idl', function() { return { token: function(stream) { return tokenBase(stream); } }; }); CodeMirror.defineMIME('text/x-idl', 'idl'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/idl/index.html ================================================ CodeMirror: IDL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • IDL

    IDL mode

    MIME types defined: text/x-idl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/index.html ================================================ CodeMirror: Language Modes

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes

    Language modes

    This is a list of every mode in the distribution. Each mode lives in a subdirectory of the mode/ directory, and typically defines a single JavaScript file that implements the mode. Loading such file will make the language available to CodeMirror, through the mode option.

    • APL
    • ASN.1
    • Asterisk dialplan
    • Brainfuck
    • C, C++, C#
    • Ceylon
    • Clojure
    • Closure Stylesheets (GSS)
    • CMake
    • COBOL
    • CoffeeScript
    • Common Lisp
    • Crystal
    • CSS
    • Cypher
    • Cython
    • D
    • Dart
    • Django (templating language)
    • Dockerfile
    • diff
    • DTD
    • Dylan
    • EBNF
    • ECL
    • Eiffel
    • Elixir
    • Elm
    • Erlang
    • Factor
    • FCL
    • Forth
    • Fortran
    • F#
    • Gas (AT&T-style assembly)
    • Gherkin
    • Go
    • Groovy
    • HAML
    • Handlebars
    • Haskell (Literate)
    • Haxe
    • HTML embedded (JSP, ASP.NET)
    • HTML mixed-mode
    • HTTP
    • IDL
    • Java
    • JavaScript (JSX)
    • Jinja2
    • Julia
    • Kotlin
    • LESS
    • LiveScript
    • Lua
    • Markdown (GitHub-flavour)
    • Mathematica
    • mbox
    • mIRC
    • Modelica
    • MscGen
    • MUMPS
    • Nginx
    • NSIS
    • N-Triples/N-Quads
    • Objective C
    • OCaml
    • Octave (MATLAB)
    • Oz
    • Pascal
    • PEG.js
    • Perl
    • PGP (ASCII armor)
    • PHP
    • Pig Latin
    • PowerShell
    • Properties files
    • ProtoBuf
    • Pug
    • Puppet
    • Python
    • Q
    • R
    • RPM
    • reStructuredText
    • Ruby
    • Rust
    • SAS
    • Sass
    • Spreadsheet
    • Scala
    • Scheme
    • SCSS
    • Shell
    • Sieve
    • Slim
    • Smalltalk
    • Smarty
    • Solr
    • Soy
    • Stylus
    • SQL (several dialects)
    • SPARQL
    • Squirrel
    • Swift
    • sTeX, LaTeX
    • Tcl
    • Textile
    • Tiddlywiki
    • Tiki wiki
    • TOML
    • Tornado (templating language)
    • troff (for manpages)
    • TTCN
    • TTCN Configuration
    • Turtle
    • Twig
    • VB.NET
    • VBScript
    • Velocity
    • Verilog/SystemVerilog
    • VHDL
    • Vue.js app
    • Web IDL
    • WebAssembly Text Format
    • XML/HTML
    • XQuery
    • Yacas
    • YAML
    • YAML frontmatter
    • Z80
    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/javascript/index.html ================================================ CodeMirror: JavaScript mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • JavaScript

    JavaScript mode

    JavaScript mode supports several configuration options:

    • json which will set the mode to expect JSON data rather than a JavaScript program.
    • jsonld which will set the mode to expect JSON-LD linked data rather than a JavaScript program (demo).
    • typescript which will activate additional syntax highlighting and some other things for TypeScript code (demo).
    • trackScope can be set to false to turn off tracking of local variables. This will prevent locals from getting the "variable-2" token type, and will break completion of locals with javascript-hint.
    • statementIndent which (given a number) will determine the amount of indentation to use for statements continued on a new line.
    • wordCharacters, a regexp that indicates which characters should be considered part of an identifier. Defaults to /[\w$]/, which does not handle non-ASCII identifiers. Can be set to something more elaborate to improve Unicode support.

    MIME types defined: text/javascript, application/javascript, application/x-javascript, text/ecmascript, application/ecmascript, application/json, application/x-json, application/manifest+json, application/ld+json, text/typescript, application/typescript.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/javascript/javascript.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("javascript", function(config, parserConfig) { var indentUnit = config.indentUnit; var statementIndent = parserConfig.statementIndent; var jsonldMode = parserConfig.jsonld; var jsonMode = parserConfig.json || jsonldMode; var trackScope = parserConfig.trackScope !== false var isTS = parserConfig.typescript; var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; // Tokenizer var keywords = function(){ function kw(type) {return {type: type, style: "keyword"};} var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d"); var operator = kw("operator"), atom = {type: "atom", style: "atom"}; return { "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C, "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"), "function": kw("function"), "catch": kw("catch"), "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), "in": operator, "typeof": operator, "instanceof": operator, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, "this": kw("this"), "class": kw("class"), "super": kw("atom"), "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, "await": C }; }(); var isOperatorChar = /[+\-*&%=<>!?|~^@]/; var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; function readRegexp(stream) { var escaped = false, next, inSet = false; while ((next = stream.next()) != null) { if (!escaped) { if (next == "/" && !inSet) return; if (next == "[") inSet = true; else if (inSet && next == "]") inSet = false; } escaped = !escaped && next == "\\"; } } // Used as scratch variables to communicate multiple values without // consing up tons of objects. var type, content; function ret(tp, style, cont) { type = tp; content = cont; return style; } function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) { return ret("number", "number"); } else if (ch == "." && stream.match("..")) { return ret("spread", "meta"); } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { return ret(ch); } else if (ch == "=" && stream.eat(">")) { return ret("=>", "operator"); } else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) { return ret("number", "number"); } else if (/\d/.test(ch)) { stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/); return ret("number", "number"); } else if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } else if (stream.eat("/")) { stream.skipToEnd(); return ret("comment", "comment"); } else if (expressionAllowed(stream, state, 1)) { readRegexp(stream); stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/); return ret("regexp", "string-2"); } else { stream.eat("="); return ret("operator", "operator", stream.current()); } } else if (ch == "`") { state.tokenize = tokenQuasi; return tokenQuasi(stream, state); } else if (ch == "#" && stream.peek() == "!") { stream.skipToEnd(); return ret("meta", "meta"); } else if (ch == "#" && stream.eatWhile(wordRE)) { return ret("variable", "property") } else if (ch == "<" && stream.match("!--") || (ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) { stream.skipToEnd() return ret("comment", "comment") } else if (isOperatorChar.test(ch)) { if (ch != ">" || !state.lexical || state.lexical.type != ">") { if (stream.eat("=")) { if (ch == "!" || ch == "=") stream.eat("=") } else if (/[<>*+\-|&?]/.test(ch)) { stream.eat(ch) if (ch == ">") stream.eat(ch) } } if (ch == "?" && stream.eat(".")) return ret(".") return ret("operator", "operator", stream.current()); } else if (wordRE.test(ch)) { stream.eatWhile(wordRE); var word = stream.current() if (state.lastType != ".") { if (keywords.propertyIsEnumerable(word)) { var kw = keywords[word] return ret(kw.type, kw.style, word) } if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false)) return ret("async", "keyword", word) } return ret("variable", "variable", word) } } function tokenString(quote) { return function(stream, state) { var escaped = false, next; if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ state.tokenize = tokenBase; return ret("jsonld-keyword", "meta"); } while ((next = stream.next()) != null) { if (next == quote && !escaped) break; escaped = !escaped && next == "\\"; } if (!escaped) state.tokenize = tokenBase; return ret("string", "string"); }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return ret("comment", "comment"); } function tokenQuasi(stream, state) { var escaped = false, next; while ((next = stream.next()) != null) { if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { state.tokenize = tokenBase; break; } escaped = !escaped && next == "\\"; } return ret("quasi", "string-2", stream.current()); } var brackets = "([{}])"; // This is a crude lookahead trick to try and notice that we're // parsing the argument patterns for a fat-arrow function before we // actually hit the arrow token. It only works if the arrow is on // the same line as the arguments and there's no strange noise // (comments) in between. Fallback is to only notice when we hit the // arrow, and not declare the arguments as locals for the arrow // body. function findFatArrow(stream, state) { if (state.fatArrowAt) state.fatArrowAt = null; var arrow = stream.string.indexOf("=>", stream.start); if (arrow < 0) return; if (isTS) { // Try to skip TypeScript return type declarations after the arguments var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) if (m) arrow = m.index } var depth = 0, sawSomething = false; for (var pos = arrow - 1; pos >= 0; --pos) { var ch = stream.string.charAt(pos); var bracket = brackets.indexOf(ch); if (bracket >= 0 && bracket < 3) { if (!depth) { ++pos; break; } if (--depth == 0) { if (ch == "(") sawSomething = true; break; } } else if (bracket >= 3 && bracket < 6) { ++depth; } else if (wordRE.test(ch)) { sawSomething = true; } else if (/["'\/`]/.test(ch)) { for (;; --pos) { if (pos == 0) return var next = stream.string.charAt(pos - 1) if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break } } } else if (sawSomething && !depth) { ++pos; break; } } if (sawSomething && !depth) state.fatArrowAt = pos; } // Parser var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "import": true, "jsonld-keyword": true}; function JSLexical(indented, column, type, align, prev, info) { this.indented = indented; this.column = column; this.type = type; this.prev = prev; this.info = info; if (align != null) this.align = align; } function inScope(state, varname) { if (!trackScope) return false for (var v = state.localVars; v; v = v.next) if (v.name == varname) return true; for (var cx = state.context; cx; cx = cx.prev) { for (var v = cx.vars; v; v = v.next) if (v.name == varname) return true; } } function parseJS(state, style, type, content, stream) { var cc = state.cc; // Communicate our context to the combinators. // (Less wasteful than consing up a hundred closures on every call.) cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; if (!state.lexical.hasOwnProperty("align")) state.lexical.align = true; while(true) { var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; if (combinator(type, content)) { while(cc.length && cc[cc.length - 1].lex) cc.pop()(); if (cx.marked) return cx.marked; if (type == "variable" && inScope(state, content)) return "variable-2"; return style; } } } // Combinator utils var cx = {state: null, column: null, marked: null, cc: null}; function pass() { for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); } function cont() { pass.apply(null, arguments); return true; } function inList(name, list) { for (var v = list; v; v = v.next) if (v.name == name) return true return false; } function register(varname) { var state = cx.state; cx.marked = "def"; if (!trackScope) return if (state.context) { if (state.lexical.info == "var" && state.context && state.context.block) { // FIXME function decls are also not block scoped var newContext = registerVarScoped(varname, state.context) if (newContext != null) { state.context = newContext return } } else if (!inList(varname, state.localVars)) { state.localVars = new Var(varname, state.localVars) return } } // Fall through means this is global if (parserConfig.globalVars && !inList(varname, state.globalVars)) state.globalVars = new Var(varname, state.globalVars) } function registerVarScoped(varname, context) { if (!context) { return null } else if (context.block) { var inner = registerVarScoped(varname, context.prev) if (!inner) return null if (inner == context.prev) return context return new Context(inner, context.vars, true) } else if (inList(varname, context.vars)) { return context } else { return new Context(context.prev, new Var(varname, context.vars), false) } } function isModifier(name) { return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly" } // Combinators function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block } function Var(name, next) { this.name = name; this.next = next } var defaultVars = new Var("this", new Var("arguments", null)) function pushcontext() { cx.state.context = new Context(cx.state.context, cx.state.localVars, false) cx.state.localVars = defaultVars } function pushblockcontext() { cx.state.context = new Context(cx.state.context, cx.state.localVars, true) cx.state.localVars = null } pushcontext.lex = pushblockcontext.lex = true function popcontext() { cx.state.localVars = cx.state.context.vars cx.state.context = cx.state.context.prev } popcontext.lex = true function pushlex(type, info) { var result = function() { var state = cx.state, indent = state.indented; if (state.lexical.type == "stat") indent = state.lexical.indented; else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) indent = outer.indented; state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); }; result.lex = true; return result; } function poplex() { var state = cx.state; if (state.lexical.prev) { if (state.lexical.type == ")") state.indented = state.lexical.indented; state.lexical = state.lexical.prev; } } poplex.lex = true; function expect(wanted) { function exp(type) { if (type == wanted) return cont(); else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass(); else return cont(exp); }; return exp; } function statement(type, value) { if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex); if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex); if (type == "debugger") return cont(expect(";")); if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext); if (type == ";") return cont(); if (type == "if") { if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) cx.state.cc.pop()(); return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); } if (type == "function") return cont(functiondef); if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex); if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword" return cont(pushlex("form", type == "class" ? type : value), className, poplex) } if (type == "variable") { if (isTS && value == "declare") { cx.marked = "keyword" return cont(statement) } else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) { cx.marked = "keyword" if (value == "enum") return cont(enumdef); else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";")); else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex) } else if (isTS && value == "namespace") { cx.marked = "keyword" return cont(pushlex("form"), expression, statement, poplex) } else if (isTS && value == "abstract") { cx.marked = "keyword" return cont(statement) } else { return cont(pushlex("stat"), maybelabel); } } if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext, block, poplex, poplex, popcontext); if (type == "case") return cont(expression, expect(":")); if (type == "default") return cont(expect(":")); if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext); if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex); if (type == "async") return cont(statement) if (value == "@") return cont(expression, statement) return pass(pushlex("stat"), expression, expect(";"), poplex); } function maybeCatchBinding(type) { if (type == "(") return cont(funarg, expect(")")) } function expression(type, value) { return expressionInner(type, value, false); } function expressionNoComma(type, value) { return expressionInner(type, value, true); } function parenExpr(type) { if (type != "(") return pass() return cont(pushlex(")"), maybeexpression, expect(")"), poplex) } function expressionInner(type, value, noComma) { if (cx.state.fatArrowAt == cx.stream.start) { var body = noComma ? arrowBodyNoComma : arrowBody; if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext); else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); } var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); if (type == "function") return cont(functiondef, maybeop); if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); } if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression); if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); if (type == "{") return contCommasep(objprop, "}", null, maybeop); if (type == "quasi") return pass(quasi, maybeop); if (type == "new") return cont(maybeTarget(noComma)); return cont(); } function maybeexpression(type) { if (type.match(/[;\}\)\],]/)) return pass(); return pass(expression); } function maybeoperatorComma(type, value) { if (type == ",") return cont(maybeexpression); return maybeoperatorNoComma(type, value, false); } function maybeoperatorNoComma(type, value, noComma) { var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; var expr = noComma == false ? expression : expressionNoComma; if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); if (type == "operator") { if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me); if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false)) return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me); if (value == "?") return cont(expression, expect(":"), expr); return cont(expr); } if (type == "quasi") { return pass(quasi, me); } if (type == ";") return; if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); if (type == ".") return cont(property, me); if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) } if (type == "regexp") { cx.state.lastType = cx.marked = "operator" cx.stream.backUp(cx.stream.pos - cx.stream.start - 1) return cont(expr) } } function quasi(type, value) { if (type != "quasi") return pass(); if (value.slice(value.length - 2) != "${") return cont(quasi); return cont(maybeexpression, continueQuasi); } function continueQuasi(type) { if (type == "}") { cx.marked = "string-2"; cx.state.tokenize = tokenQuasi; return cont(quasi); } } function arrowBody(type) { findFatArrow(cx.stream, cx.state); return pass(type == "{" ? statement : expression); } function arrowBodyNoComma(type) { findFatArrow(cx.stream, cx.state); return pass(type == "{" ? statement : expressionNoComma); } function maybeTarget(noComma) { return function(type) { if (type == ".") return cont(noComma ? targetNoComma : target); else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma) else return pass(noComma ? expressionNoComma : expression); }; } function target(_, value) { if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } } function targetNoComma(_, value) { if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } } function maybelabel(type) { if (type == ":") return cont(poplex, statement); return pass(maybeoperatorComma, expect(";"), poplex); } function property(type) { if (type == "variable") {cx.marked = "property"; return cont();} } function objprop(type, value) { if (type == "async") { cx.marked = "property"; return cont(objprop); } else if (type == "variable" || cx.style == "keyword") { cx.marked = "property"; if (value == "get" || value == "set") return cont(getterSetter); var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false))) cx.state.fatArrowAt = cx.stream.pos + m[0].length return cont(afterprop); } else if (type == "number" || type == "string") { cx.marked = jsonldMode ? "property" : (cx.style + " property"); return cont(afterprop); } else if (type == "jsonld-keyword") { return cont(afterprop); } else if (isTS && isModifier(value)) { cx.marked = "keyword" return cont(objprop) } else if (type == "[") { return cont(expression, maybetype, expect("]"), afterprop); } else if (type == "spread") { return cont(expressionNoComma, afterprop); } else if (value == "*") { cx.marked = "keyword"; return cont(objprop); } else if (type == ":") { return pass(afterprop) } } function getterSetter(type) { if (type != "variable") return pass(afterprop); cx.marked = "property"; return cont(functiondef); } function afterprop(type) { if (type == ":") return cont(expressionNoComma); if (type == "(") return pass(functiondef); } function commasep(what, end, sep) { function proceed(type, value) { if (sep ? sep.indexOf(type) > -1 : type == ",") { var lex = cx.state.lexical; if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; return cont(function(type, value) { if (type == end || value == end) return pass() return pass(what) }, proceed); } if (type == end || value == end) return cont(); if (sep && sep.indexOf(";") > -1) return pass(what) return cont(expect(end)); } return function(type, value) { if (type == end || value == end) return cont(); return pass(what, proceed); }; } function contCommasep(what, end, info) { for (var i = 3; i < arguments.length; i++) cx.cc.push(arguments[i]); return cont(pushlex(end, info), commasep(what, end), poplex); } function block(type) { if (type == "}") return cont(); return pass(statement, block); } function maybetype(type, value) { if (isTS) { if (type == ":") return cont(typeexpr); if (value == "?") return cont(maybetype); } } function maybetypeOrIn(type, value) { if (isTS && (type == ":" || value == "in")) return cont(typeexpr) } function mayberettype(type) { if (isTS && type == ":") { if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr) else return cont(typeexpr) } } function isKW(_, value) { if (value == "is") { cx.marked = "keyword" return cont() } } function typeexpr(type, value) { if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") { cx.marked = "keyword" return cont(value == "typeof" ? expressionNoComma : typeexpr) } if (type == "variable" || value == "void") { cx.marked = "type" return cont(afterType) } if (value == "|" || value == "&") return cont(typeexpr) if (type == "string" || type == "number" || type == "atom") return cont(afterType); if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType) if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType) if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType) if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr) if (type == "quasi") { return pass(quasiType, afterType); } } function maybeReturnType(type) { if (type == "=>") return cont(typeexpr) } function typeprops(type) { if (type.match(/[\}\)\]]/)) return cont() if (type == "," || type == ";") return cont(typeprops) return pass(typeprop, typeprops) } function typeprop(type, value) { if (type == "variable" || cx.style == "keyword") { cx.marked = "property" return cont(typeprop) } else if (value == "?" || type == "number" || type == "string") { return cont(typeprop) } else if (type == ":") { return cont(typeexpr) } else if (type == "[") { return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop) } else if (type == "(") { return pass(functiondecl, typeprop) } else if (!type.match(/[;\}\)\],]/)) { return cont() } } function quasiType(type, value) { if (type != "quasi") return pass(); if (value.slice(value.length - 2) != "${") return cont(quasiType); return cont(typeexpr, continueQuasiType); } function continueQuasiType(type) { if (type == "}") { cx.marked = "string-2"; cx.state.tokenize = tokenQuasi; return cont(quasiType); } } function typearg(type, value) { if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg) if (type == ":") return cont(typeexpr) if (type == "spread") return cont(typearg) return pass(typeexpr) } function afterType(type, value) { if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) if (value == "|" || type == "." || value == "&") return cont(typeexpr) if (type == "[") return cont(typeexpr, expect("]"), afterType) if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) } if (value == "?") return cont(typeexpr, expect(":"), typeexpr) } function maybeTypeArgs(_, value) { if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType) } function typeparam() { return pass(typeexpr, maybeTypeDefault) } function maybeTypeDefault(_, value) { if (value == "=") return cont(typeexpr) } function vardef(_, value) { if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)} return pass(pattern, maybetype, maybeAssign, vardefCont); } function pattern(type, value) { if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) } if (type == "variable") { register(value); return cont(); } if (type == "spread") return cont(pattern); if (type == "[") return contCommasep(eltpattern, "]"); if (type == "{") return contCommasep(proppattern, "}"); } function proppattern(type, value) { if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { register(value); return cont(maybeAssign); } if (type == "variable") cx.marked = "property"; if (type == "spread") return cont(pattern); if (type == "}") return pass(); if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern); return cont(expect(":"), pattern, maybeAssign); } function eltpattern() { return pass(pattern, maybeAssign) } function maybeAssign(_type, value) { if (value == "=") return cont(expressionNoComma); } function vardefCont(type) { if (type == ",") return cont(vardef); } function maybeelse(type, value) { if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); } function forspec(type, value) { if (value == "await") return cont(forspec); if (type == "(") return cont(pushlex(")"), forspec1, poplex); } function forspec1(type) { if (type == "var") return cont(vardef, forspec2); if (type == "variable") return cont(forspec2); return pass(forspec2) } function forspec2(type, value) { if (type == ")") return cont() if (type == ";") return cont(forspec2) if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) } return pass(expression, forspec2) } function functiondef(type, value) { if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} if (type == "variable") {register(value); return cont(functiondef);} if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext); if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef) } function functiondecl(type, value) { if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);} if (type == "variable") {register(value); return cont(functiondecl);} if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext); if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl) } function typename(type, value) { if (type == "keyword" || type == "variable") { cx.marked = "type" return cont(typename) } else if (value == "<") { return cont(pushlex(">"), commasep(typeparam, ">"), poplex) } } function funarg(type, value) { if (value == "@") cont(expression, funarg) if (type == "spread") return cont(funarg); if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); } if (isTS && type == "this") return cont(maybetype, maybeAssign) return pass(pattern, maybetype, maybeAssign); } function classExpression(type, value) { // Class expressions may have an optional name. if (type == "variable") return className(type, value); return classNameAfter(type, value); } function className(type, value) { if (type == "variable") {register(value); return cont(classNameAfter);} } function classNameAfter(type, value) { if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter) if (value == "extends" || value == "implements" || (isTS && type == ",")) { if (value == "implements") cx.marked = "keyword"; return cont(isTS ? typeexpr : expression, classNameAfter); } if (type == "{") return cont(pushlex("}"), classBody, poplex); } function classBody(type, value) { if (type == "async" || (type == "variable" && (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) && cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) { cx.marked = "keyword"; return cont(classBody); } if (type == "variable" || cx.style == "keyword") { cx.marked = "property"; return cont(classfield, classBody); } if (type == "number" || type == "string") return cont(classfield, classBody); if (type == "[") return cont(expression, maybetype, expect("]"), classfield, classBody) if (value == "*") { cx.marked = "keyword"; return cont(classBody); } if (isTS && type == "(") return pass(functiondecl, classBody) if (type == ";" || type == ",") return cont(classBody); if (type == "}") return cont(); if (value == "@") return cont(expression, classBody) } function classfield(type, value) { if (value == "!") return cont(classfield) if (value == "?") return cont(classfield) if (type == ":") return cont(typeexpr, maybeAssign) if (value == "=") return cont(expressionNoComma) var context = cx.state.lexical.prev, isInterface = context && context.info == "interface" return pass(isInterface ? functiondecl : functiondef) } function afterExport(type, value) { if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";")); return pass(statement); } function exportField(type, value) { if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); } if (type == "variable") return pass(expressionNoComma, exportField); } function afterImport(type) { if (type == "string") return cont(); if (type == "(") return pass(expression); if (type == ".") return pass(maybeoperatorComma); return pass(importSpec, maybeMoreImports, maybeFrom); } function importSpec(type, value) { if (type == "{") return contCommasep(importSpec, "}"); if (type == "variable") register(value); if (value == "*") cx.marked = "keyword"; return cont(maybeAs); } function maybeMoreImports(type) { if (type == ",") return cont(importSpec, maybeMoreImports) } function maybeAs(_type, value) { if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } } function maybeFrom(_type, value) { if (value == "from") { cx.marked = "keyword"; return cont(expression); } } function arrayLiteral(type) { if (type == "]") return cont(); return pass(commasep(expressionNoComma, "]")); } function enumdef() { return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex) } function enummember() { return pass(pattern, maybeAssign); } function isContinuedStatement(state, textAfter) { return state.lastType == "operator" || state.lastType == "," || isOperatorChar.test(textAfter.charAt(0)) || /[,.]/.test(textAfter.charAt(0)); } function expressionAllowed(stream, state, backUp) { return state.tokenize == tokenBase && /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) || (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) } // Interface return { startState: function(basecolumn) { var state = { tokenize: tokenBase, lastType: "sof", cc: [], lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, context: parserConfig.localVars && new Context(null, null, false), indented: basecolumn || 0 }; if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") state.globalVars = parserConfig.globalVars; return state; }, token: function(stream, state) { if (stream.sol()) { if (!state.lexical.hasOwnProperty("align")) state.lexical.align = false; state.indented = stream.indentation(); findFatArrow(stream, state); } if (state.tokenize != tokenComment && stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (type == "comment") return style; state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; return parseJS(state, style, type, content, stream); }, indent: function(state, textAfter) { if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass; if (state.tokenize != tokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top // Kludge to prevent 'maybelse' from blocking lexical scope pops if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { var c = state.cc[i]; if (c == poplex) lexical = lexical.prev; else if (c != maybeelse && c != popcontext) break; } while ((lexical.type == "stat" || lexical.type == "form") && (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && (top == maybeoperatorComma || top == maybeoperatorNoComma) && !/^[,\.=+\-*:?[\(]/.test(textAfter)))) lexical = lexical.prev; if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") lexical = lexical.prev; var type = lexical.type, closing = firstChar == type; if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0); else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "form") return lexical.indented + indentUnit; else if (type == "stat") return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); else if (lexical.align) return lexical.column + (closing ? 0 : 1); else return lexical.indented + (closing ? 0 : indentUnit); }, electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, blockCommentStart: jsonMode ? null : "/*", blockCommentEnd: jsonMode ? null : "*/", blockCommentContinue: jsonMode ? null : " * ", lineComment: jsonMode ? null : "//", fold: "brace", closeBrackets: "()[]{}''\"\"``", helperType: jsonMode ? "json" : "javascript", jsonldMode: jsonldMode, jsonMode: jsonMode, expressionAllowed: expressionAllowed, skipExpression: function(state) { parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null)) } }; }); CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); CodeMirror.defineMIME("text/javascript", "javascript"); CodeMirror.defineMIME("text/ecmascript", "javascript"); CodeMirror.defineMIME("application/javascript", "javascript"); CodeMirror.defineMIME("application/x-javascript", "javascript"); CodeMirror.defineMIME("application/ecmascript", "javascript"); CodeMirror.defineMIME("application/json", { name: "javascript", json: true }); CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true }); CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true }) CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true }); CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/javascript/json-ld.html ================================================ CodeMirror: JSON-LD mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • JSON-LD

    JSON-LD mode

    This is a specialization of the JavaScript mode.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/javascript/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "javascript"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("locals", "[keyword function] [def foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); MT("comma-and-binop", "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }"); MT("destructuring", "([keyword function]([def a], [[[def b], [def c] ]]) {", " [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);", " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];", "})();"); MT("destructure_trailing_comma", "[keyword let] {[def a], [def b],} [operator =] [variable foo];", "[keyword let] [def c];"); // Parser still in good state? MT("class_body", "[keyword class] [def Foo] {", " [property constructor]() {}", " [property sayName]() {", " [keyword return] [string-2 `foo${][variable foo][string-2 }oo`];", " }", "}"); MT("class", "[keyword class] [def Point] [keyword extends] [variable SuperThing] {", " [keyword get] [property prop]() { [keyword return] [number 24]; }", " [property constructor]([def x], [def y]) {", " [keyword super]([string 'something']);", " [keyword this].[property x] [operator =] [variable-2 x];", " }", "}"); MT("anonymous_class_expression", "[keyword const] [def Adder] [operator =] [keyword class] [keyword extends] [variable Arithmetic] {", " [property add]([def a], [def b]) {}", "};"); MT("named_class_expression", "[keyword const] [def Subber] [operator =] [keyword class] [def Subtract] {", " [property sub]([def a], [def b]) {}", "};"); MT("class_async_method", "[keyword class] [def Foo] {", " [property sayName1]() {}", " [keyword async] [property sayName2]() {}", "}"); MT("import", "[keyword function] [def foo]() {", " [keyword import] [def $] [keyword from] [string 'jquery'];", " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];", "}"); MT("import_trailing_comma", "[keyword import] {[def foo], [def bar],} [keyword from] [string 'baz']") MT("import_dynamic", "[keyword import]([string 'baz']).[property then]") MT("import_dynamic", "[keyword const] [def t] [operator =] [keyword import]([string 'baz']).[property then]") MT("const", "[keyword function] [def f]() {", " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];", "}"); MT("for/of", "[keyword for]([keyword let] [def of] [keyword of] [variable something]) {}"); MT("for await", "[keyword for] [keyword await]([keyword let] [def of] [keyword of] [variable something]) {}"); MT("generator", "[keyword function*] [def repeat]([def n]) {", " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])", " [keyword yield] [variable-2 i];", "}"); MT("let_scoping", "[keyword function] [def scoped]([def n]) {", " { [keyword var] [def i]; } [variable-2 i];", " { [keyword let] [def j]; [variable-2 j]; } [variable j];", " [keyword if] ([atom true]) { [keyword const] [def k]; [variable-2 k]; } [variable k];", "}"); MT("switch_scoping", "[keyword switch] ([variable x]) {", " [keyword default]:", " [keyword let] [def j];", " [keyword return] [variable-2 j]", "}", "[variable j];") MT("leaving_scope", "[keyword function] [def a]() {", " {", " [keyword const] [def x] [operator =] [number 1]", " [keyword if] ([atom true]) {", " [keyword let] [def y] [operator =] [number 2]", " [keyword var] [def z] [operator =] [number 3]", " [variable console].[property log]([variable-2 x], [variable-2 y], [variable-2 z])", " }", " [variable console].[property log]([variable-2 x], [variable y], [variable-2 z])", " }", " [variable console].[property log]([variable x], [variable y], [variable-2 z])", "}") MT("quotedStringAddition", "[keyword let] [def f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];"); MT("quotedFatArrow", "[keyword let] [def f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];"); MT("fatArrow", "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);", "[variable a];", // No longer in scope "[keyword let] [def f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", "[variable c];"); MT("fatArrow_stringDefault", "([def a], [def b] [operator =] [string 'x\\'y']) [operator =>] [variable-2 a] [operator +] [variable-2 b]") MT("spread", "[keyword function] [def f]([def a], [meta ...][def b]) {", " [variable something]([variable-2 a], [meta ...][variable-2 b]);", "}"); MT("quasi", "[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); MT("quasi_no_function", "[variable x] [operator =] [string-2 `fofdlakj${][variable x] [operator +] [string-2 `foo`] [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); MT("indent_statement", "[keyword var] [def x] [operator =] [number 10]", "[variable x] [operator +=] [variable y] [operator +]", " [atom Infinity]", "[keyword debugger];"); MT("indent_if", "[keyword if] ([number 1])", " [keyword break];", "[keyword else] [keyword if] ([number 2])", " [keyword continue];", "[keyword else]", " [number 10];", "[keyword if] ([number 1]) {", " [keyword break];", "} [keyword else] [keyword if] ([number 2]) {", " [keyword continue];", "} [keyword else] {", " [number 10];", "}"); MT("indent_for", "[keyword for] ([keyword var] [def i] [operator =] [number 0];", " [variable-2 i] [operator <] [number 100];", " [variable-2 i][operator ++])", " [variable doSomething]([variable-2 i]);", "[keyword debugger];"); MT("indent_c_style", "[keyword function] [def foo]()", "{", " [keyword debugger];", "}"); MT("indent_else", "[keyword for] (;;)", " [keyword if] ([variable foo])", " [keyword if] ([variable bar])", " [number 1];", " [keyword else]", " [number 2];", " [keyword else]", " [number 3];"); MT("indent_funarg", "[variable foo]([number 10000],", " [keyword function]([def a]) {", " [keyword debugger];", "};"); MT("indent_below_if", "[keyword for] (;;)", " [keyword if] ([variable foo])", " [number 1];", "[number 2];"); MT("indent_semicolonless_if", "[keyword function] [def foo]() {", " [keyword if] ([variable x])", " [variable foo]()", "}") MT("indent_semicolonless_if_with_statement", "[keyword function] [def foo]() {", " [keyword if] ([variable x])", " [variable foo]()", " [variable bar]()", "}") MT("multilinestring", "[keyword var] [def x] [operator =] [string 'foo\\]", "[string bar'];"); MT("scary_regexp", "[string-2 /foo[[/]]bar/];"); MT("indent_strange_array", "[keyword var] [def x] [operator =] [[", " [number 1],,", " [number 2],", "]];", "[number 10];"); MT("param_default", "[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {", " [keyword return] [variable-2 x];", "}"); MT( "param_destructuring", "[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {", " [keyword return] [variable-2 x];", "}"); MT("new_target", "[keyword function] [def F]([def target]) {", " [keyword if] ([variable-2 target] [operator &&] [keyword new].[keyword target].[property name]) {", " [keyword return] [keyword new]", " .[keyword target];", " }", "}"); MT("async", "[keyword async] [keyword function] [def foo]([def args]) { [keyword return] [atom true]; }"); MT("async_assignment", "[keyword const] [def foo] [operator =] [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; };"); MT("async_object", "[keyword let] [def obj] [operator =] { [property async]: [atom false] };"); // async be highlighted as keyword and foo as def, but it requires potentially expensive look-ahead. See #4173 MT("async_object_function", "[keyword let] [def obj] [operator =] { [property async] [property foo]([def args]) { [keyword return] [atom true]; } };"); MT("async_object_properties", "[keyword let] [def obj] [operator =] {", " [property prop1]: [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; },", " [property prop2]: [keyword async] [keyword function] ([def args]) { [keyword return] [atom true]; },", " [property prop3]: [keyword async] [keyword function] [def prop3]([def args]) { [keyword return] [atom true]; },", "};"); MT("async_arrow", "[keyword const] [def foo] [operator =] [keyword async] ([def args]) [operator =>] { [keyword return] [atom true]; };"); MT("async_jquery", "[variable $].[property ajax]({", " [property url]: [variable url],", " [property async]: [atom true],", " [property method]: [string 'GET']", "});"); MT("async_variable", "[keyword const] [def async] [operator =] {[property a]: [number 1]};", "[keyword const] [def foo] [operator =] [string-2 `bar ${][variable async].[property a][string-2 }`];") MT("bigint", "[number 1n] [operator +] [number 0x1afn] [operator +] [number 0o064n] [operator +] [number 0b100n];") MT("async_comment", "[keyword async] [comment /**/] [keyword function] [def foo]([def args]) { [keyword return] [atom true]; }"); MT("indent_switch", "[keyword switch] ([variable x]) {", " [keyword default]:", " [keyword return] [number 2]", "}") MT("regexp_corner_case", "[operator +]{} [operator /] [atom undefined];", "[[[meta ...][string-2 /\\//] ]];", "[keyword void] [string-2 /\\//];", "[keyword do] [string-2 /\\//]; [keyword while] ([number 0]);", "[keyword if] ([number 0]) {} [keyword else] [string-2 /\\//];", "[string-2 `${][variable async][operator ++][string-2 }//`];", "[string-2 `${]{} [operator /] [string-2 /\\//}`];") MT("return_eol", "[keyword return]", "{} [string-2 /5/]") MT("numeric separator", "[number 123_456];", "[number 0xdead_c0de];", "[number 0o123_456];", "[number 0b1101_1101];", "[number .123_456e0_1];", "[number 1E+123_456];", "[number 12_34_56n];") MT("underscore property", "[variable something].[property _property];", "[variable something].[property _123];", "[variable something].[property _for];", "[variable _for];", "[variable _123];") MT("private properties", "[keyword class] [def C] {", " [property #x] [operator =] [number 2];", " [property #read]() {", " [keyword return] [keyword this].[property #x]", " }", "}") var ts_mode = CodeMirror.getMode({indentUnit: 2}, "application/typescript") function TS(name) { test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1)) } TS("typescript_extend_type", "[keyword class] [def Foo] [keyword extends] [type Some][operator <][type Type][operator >] {}") TS("typescript_arrow_type", "[keyword let] [def x]: ([variable arg]: [type Type]) [operator =>] [type ReturnType]") TS("typescript_class", "[keyword class] [def Foo] {", " [keyword public] [keyword static] [property main]() {}", " [keyword private] [property _foo]: [type string];", "}") TS("typescript_literal_types", "[keyword import] [keyword *] [keyword as] [def Sequelize] [keyword from] [string 'sequelize'];", "[keyword interface] [def MyAttributes] {", " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];", " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];", "}", "[keyword interface] [def MyInstance] [keyword extends] [type Sequelize].[type Instance] [operator <] [type MyAttributes] [operator >] {", " [property rawAttributes]: [type MyAttributes];", " [property truthy]: [string 'true'] [operator |] [number 1] [operator |] [atom true];", " [property falsy]: [string 'false'] [operator |] [number 0] [operator |] [atom false];", "}") TS("typescript_extend_operators", "[keyword export] [keyword interface] [def UserModel] [keyword extends]", " [type Sequelize].[type Model] [operator <] [type UserInstance], [type UserAttributes] [operator >] {", " [property findById]: (", " [variable userId]: [type number]", " ) [operator =>] [type Promise] [operator <] [type Array] [operator <] { [property id], [property name] } [operator >>];", " [property updateById]: (", " [variable userId]: [type number],", " [variable isActive]: [type boolean]", " ) [operator =>] [type Promise] [operator <] [type AccountHolderNotificationPreferenceInstance] [operator >];", " }") TS("typescript_interface_with_const", "[keyword const] [def hello]: {", " [property prop1][operator ?]: [type string];", " [property prop2][operator ?]: [type string];", "} [operator =] {};") TS("typescript_double_extend", "[keyword export] [keyword interface] [def UserAttributes] {", " [property id][operator ?]: [type number];", " [property createdAt][operator ?]: [type Date];", "}", "[keyword export] [keyword interface] [def UserInstance] [keyword extends] [type Sequelize].[type Instance][operator <][type UserAttributes][operator >], [type UserAttributes] {", " [property id]: [type number];", " [property createdAt]: [type Date];", "}"); TS("typescript_index_signature", "[keyword interface] [def A] {", " [[ [variable prop]: [type string] ]]: [type any];", " [property prop1]: [type any];", "}"); TS("typescript_generic_class", "[keyword class] [def Foo][operator <][type T][operator >] {", " [property bar]() {}", " [property foo](): [type Foo] {}", "}") TS("typescript_type_when_keyword", "[keyword export] [keyword type] [type AB] [operator =] [type A] [operator |] [type B];", "[keyword type] [type Flags] [operator =] {", " [property p1]: [type string];", " [property p2]: [type boolean];", "};") TS("typescript_type_when_not_keyword", "[keyword class] [def HasType] {", " [property type]: [type string];", " [property constructor]([def type]: [type string]) {", " [keyword this].[property type] [operator =] [variable-2 type];", " }", " [property setType]({ [def type] }: { [property type]: [type string]; }) {", " [keyword this].[property type] [operator =] [variable-2 type];", " }", "}") TS("typescript_function_generics", "[keyword function] [def a]() {}", "[keyword function] [def b][operator <][type IA] [keyword extends] [type object], [type IB] [keyword extends] [type object][operator >]() {}", "[keyword function] [def c]() {}") TS("typescript_complex_return_type", "[keyword function] [def A]() {", " [keyword return] [keyword this].[property property];", "}", "[keyword function] [def B](): [type Promise][operator <]{ [[ [variable key]: [type string] ]]: [type any] } [operator |] [atom null][operator >] {", " [keyword return] [keyword this].[property property];", "}") TS("typescript_complex_type_casting", "[keyword const] [def giftpay] [operator =] [variable config].[property get]([string 'giftpay']) [keyword as] { [[ [variable platformUuid]: [type string] ]]: { [property version]: [type number]; [property apiCode]: [type string]; } };") TS("typescript_keyof", "[keyword function] [def x][operator <][type T] [keyword extends] [keyword keyof] [type X][operator >]([def a]: [type T]) {", " [keyword return]") TS("typescript_new_typeargs", "[keyword let] [def x] [operator =] [keyword new] [variable Map][operator <][type string], [type Date][operator >]([string-2 `foo${][variable bar][string-2 }`])") TS("modifiers", "[keyword class] [def Foo] {", " [keyword public] [keyword abstract] [property bar]() {}", " [property constructor]([keyword readonly] [keyword private] [def x]) {}", "}") TS("arrow prop", "({[property a]: [def p] [operator =>] [variable-2 p]})") TS("generic in function call", "[keyword this].[property a][operator <][type Type][operator >]([variable foo]);", "[keyword this].[property a][operator <][variable Type][operator >][variable foo];") TS("type guard", "[keyword class] [def Appler] {", " [keyword static] [property assertApple]([def fruit]: [type Fruit]): [variable-2 fruit] [keyword is] [type Apple] {", " [keyword if] ([operator !]([variable-2 fruit] [keyword instanceof] [variable Apple]))", " [keyword throw] [keyword new] [variable Error]();", " }", "}") TS("type as variable", "[variable type] [operator =] [variable x] [keyword as] [type Bar];"); TS("enum body", "[keyword export] [keyword const] [keyword enum] [def CodeInspectionResultType] {", " [def ERROR] [operator =] [string 'problem_type_error'],", " [def WARNING] [operator =] [string 'problem_type_warning'],", " [def META],", "}") TS("parenthesized type", "[keyword class] [def Foo] {", " [property x] [operator =] [keyword new] [variable A][operator <][type B], [type string][operator |](() [operator =>] [type void])[operator >]();", " [keyword private] [property bar]();", "}") TS("abstract class", "[keyword export] [keyword abstract] [keyword class] [def Foo] {}") TS("interface without semicolons", "[keyword interface] [def Foo] {", " [property greet]([def x]: [type int]): [type blah]", " [property bar]: [type void]", "}") var jsonld_mode = CodeMirror.getMode( {indentUnit: 2}, {name: "javascript", jsonld: true} ); function LD(name) { test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1)); } LD("json_ld_keywords", '{', ' [meta "@context"]: {', ' [meta "@base"]: [string "http://example.com"],', ' [meta "@vocab"]: [string "http://xmlns.com/foaf/0.1/"],', ' [property "likesFlavor"]: {', ' [meta "@container"]: [meta "@list"]', ' [meta "@reverse"]: [string "@beFavoriteOf"]', ' },', ' [property "nick"]: { [meta "@container"]: [meta "@set"] },', ' [property "nick"]: { [meta "@container"]: [meta "@index"] }', ' },', ' [meta "@graph"]: [[ {', ' [meta "@id"]: [string "http://dbpedia.org/resource/John_Lennon"],', ' [property "name"]: [string "John Lennon"],', ' [property "modified"]: {', ' [meta "@value"]: [string "2010-05-29T14:17:39+02:00"],', ' [meta "@type"]: [string "http://www.w3.org/2001/XMLSchema#dateTime"]', ' }', ' } ]]', '}'); LD("json_ld_fake", '{', ' [property "@fake"]: [string "@fake"],', ' [property "@contextual"]: [string "@identifier"],', ' [property "user@domain.com"]: [string "@graphical"],', ' [property "@ID"]: [string "@@ID"]', '}'); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/javascript/typescript.html ================================================ CodeMirror: TypeScript mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • TypeScript

    TypeScript mode

    This is a specialization of the JavaScript mode.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/jinja2/index.html ================================================ CodeMirror: Jinja2 mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Jinja2

    Jinja2 mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/jinja2/jinja2.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("jinja2", function() { var keywords = ["and", "as", "block", "endblock", "by", "cycle", "debug", "else", "elif", "extends", "filter", "endfilter", "firstof", "do", "for", "endfor", "if", "endif", "ifchanged", "endifchanged", "ifequal", "endifequal", "ifnotequal", "set", "raw", "endraw", "endifnotequal", "in", "include", "load", "not", "now", "or", "parsed", "regroup", "reversed", "spaceless", "call", "endcall", "macro", "endmacro", "endspaceless", "ssi", "templatetag", "openblock", "closeblock", "openvariable", "closevariable", "without", "context", "openbrace", "closebrace", "opencomment", "closecomment", "widthratio", "url", "with", "endwith", "get_current_language", "trans", "endtrans", "noop", "blocktrans", "endblocktrans", "get_available_languages", "get_current_language_bidi", "pluralize", "autoescape", "endautoescape"], operator = /^[+\-*&%=<>!?|~^]/, sign = /^[:\[\(\{]/, atom = ["true", "false"], number = /^(\d[+\-\*\/])?\d+(\.\d+)?/; keywords = new RegExp("((" + keywords.join(")|(") + "))\\b"); atom = new RegExp("((" + atom.join(")|(") + "))\\b"); function tokenBase (stream, state) { var ch = stream.peek(); //Comment if (state.incomment) { if(!stream.skipTo("#}")) { stream.skipToEnd(); } else { stream.eatWhile(/\#|}/); state.incomment = false; } return "comment"; //Tag } else if (state.intag) { //After operator if(state.operator) { state.operator = false; if(stream.match(atom)) { return "atom"; } if(stream.match(number)) { return "number"; } } //After sign if(state.sign) { state.sign = false; if(stream.match(atom)) { return "atom"; } if(stream.match(number)) { return "number"; } } if(state.instring) { if(ch == state.instring) { state.instring = false; } stream.next(); return "string"; } else if(ch == "'" || ch == '"') { state.instring = ch; stream.next(); return "string"; } else if (state.inbraces > 0 && ch ==")") { stream.next() state.inbraces--; } else if (ch == "(") { stream.next() state.inbraces++; } else if (state.inbrackets > 0 && ch =="]") { stream.next() state.inbrackets--; } else if (ch == "[") { stream.next() state.inbrackets++; } else if (!state.lineTag && (stream.match(state.intag + "}") || stream.eat("-") && stream.match(state.intag + "}"))) { state.intag = false; return "tag"; } else if(stream.match(operator)) { state.operator = true; return "operator"; } else if(stream.match(sign)) { state.sign = true; } else { if (stream.column() == 1 && state.lineTag && stream.match(keywords)) { //allow nospace after tag before the keyword return "keyword"; } if(stream.eat(" ") || stream.sol()) { if(stream.match(keywords)) { return "keyword"; } if(stream.match(atom)) { return "atom"; } if(stream.match(number)) { return "number"; } if(stream.sol()) { stream.next(); } } else { stream.next(); } } return "variable"; } else if (stream.eat("{")) { if (stream.eat("#")) { state.incomment = true; if(!stream.skipTo("#}")) { stream.skipToEnd(); } else { stream.eatWhile(/\#|}/); state.incomment = false; } return "comment"; //Open tag } else if (ch = stream.eat(/\{|%/)) { //Cache close tag state.intag = ch; state.inbraces = 0; state.inbrackets = 0; if(ch == "{") { state.intag = "}"; } stream.eat("-"); return "tag"; } //Line statements } else if (stream.eat('#')) { if (stream.peek() == '#') { stream.skipToEnd(); return "comment" } else if (!stream.eol()) { state.intag = true; state.lineTag = true; state.inbraces = 0; state.inbrackets = 0; return "tag"; } } stream.next(); }; return { startState: function () { return { tokenize: tokenBase, inbrackets:0, inbraces:0 }; }, token: function(stream, state) { var style = state.tokenize(stream, state); if (stream.eol() && state.lineTag && !state.instring && state.inbraces == 0 && state.inbrackets == 0) { //Close line statement at the EOL state.intag = false state.lineTag = false } return style; }, blockCommentStart: "{#", blockCommentEnd: "#}", lineComment: "##", }; }); CodeMirror.defineMIME("text/jinja2", "jinja2"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/jsx/index.html ================================================ CodeMirror: JSX mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • JSX

    JSX mode

    JSX Mode for React's JavaScript syntax extension.

    MIME types defined: text/jsx, text/typescript-jsx.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/jsx/jsx.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript")) else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript"], mod) else // Plain browser env mod(CodeMirror) })(function(CodeMirror) { "use strict" // Depth means the amount of open braces in JS context, in XML // context 0 means not in tag, 1 means in tag, and 2 means in tag // and js block comment. function Context(state, mode, depth, prev) { this.state = state; this.mode = mode; this.depth = depth; this.prev = prev } function copyContext(context) { return new Context(CodeMirror.copyState(context.mode, context.state), context.mode, context.depth, context.prev && copyContext(context.prev)) } CodeMirror.defineMode("jsx", function(config, modeConfig) { var xmlMode = CodeMirror.getMode(config, {name: "xml", allowMissing: true, multilineTagIndentPastTag: false, allowMissingTagName: true}) var jsMode = CodeMirror.getMode(config, modeConfig && modeConfig.base || "javascript") function flatXMLIndent(state) { var tagName = state.tagName state.tagName = null var result = xmlMode.indent(state, "", "") state.tagName = tagName return result } function token(stream, state) { if (state.context.mode == xmlMode) return xmlToken(stream, state, state.context) else return jsToken(stream, state, state.context) } function xmlToken(stream, state, cx) { if (cx.depth == 2) { // Inside a JS /* */ comment if (stream.match(/^.*?\*\//)) cx.depth = 1 else stream.skipToEnd() return "comment" } if (stream.peek() == "{") { xmlMode.skipAttribute(cx.state) var indent = flatXMLIndent(cx.state), xmlContext = cx.state.context // If JS starts on same line as tag if (xmlContext && stream.match(/^[^>]*>\s*$/, false)) { while (xmlContext.prev && !xmlContext.startOfLine) xmlContext = xmlContext.prev // If tag starts the line, use XML indentation level if (xmlContext.startOfLine) indent -= config.indentUnit // Else use JS indentation level else if (cx.prev.state.lexical) indent = cx.prev.state.lexical.indented // Else if inside of tag } else if (cx.depth == 1) { indent += config.indentUnit } state.context = new Context(CodeMirror.startState(jsMode, indent), jsMode, 0, state.context) return null } if (cx.depth == 1) { // Inside of tag if (stream.peek() == "<") { // Tag inside of tag xmlMode.skipAttribute(cx.state) state.context = new Context(CodeMirror.startState(xmlMode, flatXMLIndent(cx.state)), xmlMode, 0, state.context) return null } else if (stream.match("//")) { stream.skipToEnd() return "comment" } else if (stream.match("/*")) { cx.depth = 2 return token(stream, state) } } var style = xmlMode.token(stream, cx.state), cur = stream.current(), stop if (/\btag\b/.test(style)) { if (/>$/.test(cur)) { if (cx.state.context) cx.depth = 0 else state.context = state.context.prev } else if (/^ -1) { stream.backUp(cur.length - stop) } return style } function jsToken(stream, state, cx) { if (stream.peek() == "<" && !stream.match(/^<([^<>]|<[^>]*>)+,\s*>/, false) && jsMode.expressionAllowed(stream, cx.state)) { state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "", "")), xmlMode, 0, state.context) jsMode.skipExpression(cx.state) return null } var style = jsMode.token(stream, cx.state) if (!style && cx.depth != null) { var cur = stream.current() if (cur == "{") { cx.depth++ } else if (cur == "}") { if (--cx.depth == 0) state.context = state.context.prev } } return style } return { startState: function() { return {context: new Context(CodeMirror.startState(jsMode), jsMode)} }, copyState: function(state) { return {context: copyContext(state.context)} }, token: token, indent: function(state, textAfter, fullLine) { return state.context.mode.indent(state.context.state, textAfter, fullLine) }, innerMode: function(state) { return state.context } } }, "xml", "javascript") CodeMirror.defineMIME("text/jsx", "jsx") CodeMirror.defineMIME("text/typescript-jsx", {name: "jsx", base: {name: "javascript", typescript: true}}) }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/jsx/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "jsx") function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)) } MT("selfclose", "[keyword var] [def x] [operator =] [bracket&tag <] [tag foo] [bracket&tag />] [operator +] [number 1];") MT("openclose", "([bracket&tag <][tag foo][bracket&tag >]hello [atom &][bracket&tag ][operator ++])") MT("openclosefragment", "([bracket&tag <><][tag foo][bracket&tag >]hello [atom &][bracket&tag ][operator ++])") MT("attr", "([bracket&tag <][tag foo] [attribute abc]=[string 'value'][bracket&tag >]hello [atom &][bracket&tag ][operator ++])") MT("braced_attr", "([bracket&tag <][tag foo] [attribute abc]={[number 10]}[bracket&tag >]hello [atom &][bracket&tag ][operator ++])") MT("braced_text", "([bracket&tag <][tag foo][bracket&tag >]hello {[number 10]} [atom &][bracket&tag ][operator ++])") MT("nested_tag", "([bracket&tag <][tag foo][bracket&tag ><][tag bar][bracket&tag >][operator ++])") MT("nested_jsx", "[keyword return] (", " [bracket&tag <][tag foo][bracket&tag >]", " say {[number 1] [operator +] [bracket&tag <][tag bar] [attribute attr]={[number 10]}[bracket&tag />]}!", " [bracket&tag ][operator ++]", ")") MT("preserve_js_context", "[variable x] [operator =] [string-2 `quasi${][bracket&tag <][tag foo][bracket&tag />][string-2 }quoted`]") MT("string_interpolation", "[variable x] [operator =] [string-2 `quasi${] [number 10] [string-2 }`]") MT("line_comment", "([bracket&tag <][tag foo] [comment // hello]", " [bracket&tag >][operator ++])") MT("line_comment_not_in_tag", "([bracket&tag <][tag foo][bracket&tag >] // hello", " [bracket&tag ][operator ++])") MT("block_comment", "([bracket&tag <][tag foo] [comment /* hello]", "[comment line 2]", "[comment line 3 */] [bracket&tag >][operator ++])") MT("block_comment_not_in_tag", "([bracket&tag <][tag foo][bracket&tag >]/* hello", " line 2", " line 3 */ [bracket&tag ][operator ++])") MT("missing_attr", "([bracket&tag <][tag foo] [attribute selected][bracket&tag />][operator ++])") MT("indent_js", "([bracket&tag <][tag foo][bracket&tag >]", " [bracket&tag <][tag bar] [attribute baz]={[keyword function]() {", " [keyword return] [number 10]", " }}[bracket&tag />]", " [bracket&tag ])") MT("spread", "([bracket&tag <][tag foo] [attribute bar]={[meta ...][variable baz] [operator /][number 2]}[bracket&tag />])") MT("tag_attribute", "([bracket&tag <][tag foo] [attribute bar]=[bracket&tag <][tag foo][bracket&tag />/>][operator ++])") MT("in_array", "[[", " [bracket&tag <][tag Something][bracket&tag />],", " [string-2 `${][variable x][string-2 }`],", " [variable y]", "]]") var ts_mode = CodeMirror.getMode({indentUnit: 2}, "text/typescript-jsx") function TS(name) { test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1)) } TS("tsx_react_integration", "[keyword interface] [def Props] {", " [property foo]: [type string];", "}", "[keyword class] [def MyComponent] [keyword extends] [type React].[type Component] [operator <] [type Props], [type any] [operator >] {", " [property render]() {", " [keyword return] [bracket&tag <][tag span][bracket&tag >]{[keyword this].[property props].[property foo]}[bracket&tag ]", " }", "}", "[bracket&tag <][tag MyComponent] [attribute foo]=[string \"bar\"] [bracket&tag />]; [comment //ok]", "[bracket&tag <][tag MyComponent] [attribute foo]={[number 0]} [bracket&tag />]; [comment //error]") TS("tsx_react_generics", "[variable x] [operator =] [operator <] [variable T],[operator >] ([def v]: [type T]) [operator =>] [variable-2 v];") })() ================================================ FILE: public/assets/lib/vendor/codemirror/mode/julia/index.html ================================================ CodeMirror: Julia mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Julia

    Julia mode

    MIME types defined: text/x-julia.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/julia/julia.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("julia", function(config, parserConf) { function wordRegexp(words, end, pre) { if (typeof pre === "undefined") { pre = ""; } if (typeof end === "undefined") { end = "\\b"; } return new RegExp("^" + pre + "((" + words.join(")|(") + "))" + end); } var octChar = "\\\\[0-7]{1,3}"; var hexChar = "\\\\x[A-Fa-f0-9]{1,2}"; var sChar = "\\\\[abefnrtv0%?'\"\\\\]"; var uChar = "([^\\u0027\\u005C\\uD800-\\uDFFF]|[\\uD800-\\uDFFF][\\uDC00-\\uDFFF])"; var asciiOperatorsList = [ "[<>]:", "[<>=]=", "<<=?", ">>>?=?", "=>", "--?>", "<--[->]?", "\\/\\/", "\\.{2,3}", "[\\.\\\\%*+\\-<>!\\/^|&]=?", "\\?", "\\$", "~", ":" ]; var operators = parserConf.operators || wordRegexp([ "[<>]:", "[<>=]=", "[!=]==", "<<=?", ">>>?=?", "=>?", "--?>", "<--[->]?", "\\/\\/", "[\\\\%*+\\-<>!\\/^|&\\u00F7\\u22BB]=?", "\\?", "\\$", "~", ":", "\\u00D7", "\\u2208", "\\u2209", "\\u220B", "\\u220C", "\\u2218", "\\u221A", "\\u221B", "\\u2229", "\\u222A", "\\u2260", "\\u2264", "\\u2265", "\\u2286", "\\u2288", "\\u228A", "\\u22C5", "\\b(in|isa)\\b(?!\.?\\()" ], ""); var delimiters = parserConf.delimiters || /^[;,()[\]{}]/; var identifiers = parserConf.identifiers || /^[_A-Za-z\u00A1-\u2217\u2219-\uFFFF][\w\u00A1-\u2217\u2219-\uFFFF]*!*/; var chars = wordRegexp([octChar, hexChar, sChar, uChar], "'"); var openersList = ["begin", "function", "type", "struct", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch", "do"]; var closersList = ["end", "else", "elseif", "catch", "finally"]; var keywordsList = ["if", "else", "elseif", "while", "for", "begin", "let", "end", "do", "try", "catch", "finally", "return", "break", "continue", "global", "local", "const", "export", "import", "importall", "using", "function", "where", "macro", "module", "baremodule", "struct", "type", "mutable", "immutable", "quote", "typealias", "abstract", "primitive", "bitstype"]; var builtinsList = ["true", "false", "nothing", "NaN", "Inf"]; CodeMirror.registerHelper("hintWords", "julia", keywordsList.concat(builtinsList)); var openers = wordRegexp(openersList); var closers = wordRegexp(closersList); var keywords = wordRegexp(keywordsList); var builtins = wordRegexp(builtinsList); var macro = /^@[_A-Za-z\u00A1-\uFFFF][\w\u00A1-\uFFFF]*!*/; var symbol = /^:[_A-Za-z\u00A1-\uFFFF][\w\u00A1-\uFFFF]*!*/; var stringPrefixes = /^(`|([_A-Za-z\u00A1-\uFFFF]*"("")?))/; var macroOperators = wordRegexp(asciiOperatorsList, "", "@"); var symbolOperators = wordRegexp(asciiOperatorsList, "", ":"); function inArray(state) { return (state.nestedArrays > 0); } function inGenerator(state) { return (state.nestedGenerators > 0); } function currentScope(state, n) { if (typeof(n) === "undefined") { n = 0; } if (state.scopes.length <= n) { return null; } return state.scopes[state.scopes.length - (n + 1)]; } // tokenizers function tokenBase(stream, state) { // Handle multiline comments if (stream.match('#=', false)) { state.tokenize = tokenComment; return state.tokenize(stream, state); } // Handle scope changes var leavingExpr = state.leavingExpr; if (stream.sol()) { leavingExpr = false; } state.leavingExpr = false; if (leavingExpr) { if (stream.match(/^'+/)) { return "operator"; } } if (stream.match(/\.{4,}/)) { return "error"; } else if (stream.match(/\.{1,3}/)) { return "operator"; } if (stream.eatSpace()) { return null; } var ch = stream.peek(); // Handle single line comments if (ch === '#') { stream.skipToEnd(); return "comment"; } if (ch === '[') { state.scopes.push('['); state.nestedArrays++; } if (ch === '(') { state.scopes.push('('); state.nestedGenerators++; } if (inArray(state) && ch === ']') { while (state.scopes.length && currentScope(state) !== "[") { state.scopes.pop(); } state.scopes.pop(); state.nestedArrays--; state.leavingExpr = true; } if (inGenerator(state) && ch === ')') { while (state.scopes.length && currentScope(state) !== "(") { state.scopes.pop(); } state.scopes.pop(); state.nestedGenerators--; state.leavingExpr = true; } if (inArray(state)) { if (state.lastToken == "end" && stream.match(':')) { return "operator"; } if (stream.match('end')) { return "number"; } } var match; if (match = stream.match(openers, false)) { state.scopes.push(match[0]); } if (stream.match(closers, false)) { state.scopes.pop(); } // Handle type annotations if (stream.match(/^::(?![:\$])/)) { state.tokenize = tokenAnnotation; return state.tokenize(stream, state); } // Handle symbols if (!leavingExpr && (stream.match(symbol) || stream.match(symbolOperators))) { return "builtin"; } // Handle parametric types //if (stream.match(/^{[^}]*}(?=\()/)) { // return "builtin"; //} // Handle operators and Delimiters if (stream.match(operators)) { return "operator"; } // Handle Number Literals if (stream.match(/^\.?\d/, false)) { var imMatcher = RegExp(/^im\b/); var numberLiteral = false; if (stream.match(/^0x\.[0-9a-f_]+p[\+\-]?[_\d]+/i)) { numberLiteral = true; } // Integers if (stream.match(/^0x[0-9a-f_]+/i)) { numberLiteral = true; } // Hex if (stream.match(/^0b[01_]+/i)) { numberLiteral = true; } // Binary if (stream.match(/^0o[0-7_]+/i)) { numberLiteral = true; } // Octal // Floats if (stream.match(/^(?:(?:\d[_\d]*)?\.(?!\.)(?:\d[_\d]*)?|\d[_\d]*\.(?!\.)(?:\d[_\d]*))?([Eef][\+\-]?[_\d]+)?/i)) { numberLiteral = true; } if (stream.match(/^\d[_\d]*(e[\+\-]?\d+)?/i)) { numberLiteral = true; } // Decimal if (numberLiteral) { // Integer literals may be "long" stream.match(imMatcher); state.leavingExpr = true; return "number"; } } // Handle Chars if (stream.match('\'')) { state.tokenize = tokenChar; return state.tokenize(stream, state); } // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()); return state.tokenize(stream, state); } if (stream.match(macro) || stream.match(macroOperators)) { return "meta"; } if (stream.match(delimiters)) { return null; } if (stream.match(keywords)) { return "keyword"; } if (stream.match(builtins)) { return "builtin"; } var isDefinition = state.isDefinition || state.lastToken == "function" || state.lastToken == "macro" || state.lastToken == "type" || state.lastToken == "struct" || state.lastToken == "immutable"; if (stream.match(identifiers)) { if (isDefinition) { if (stream.peek() === '.') { state.isDefinition = true; return "variable"; } state.isDefinition = false; return "def"; } state.leavingExpr = true; return "variable"; } // Handle non-detected items stream.next(); return "error"; } function tokenAnnotation(stream, state) { stream.match(/.*?(?=[,;{}()=\s]|$)/); if (stream.match('{')) { state.nestedParameters++; } else if (stream.match('}') && state.nestedParameters > 0) { state.nestedParameters--; } if (state.nestedParameters > 0) { stream.match(/.*?(?={|})/) || stream.next(); } else if (state.nestedParameters == 0) { state.tokenize = tokenBase; } return "builtin"; } function tokenComment(stream, state) { if (stream.match('#=')) { state.nestedComments++; } if (!stream.match(/.*?(?=(#=|=#))/)) { stream.skipToEnd(); } if (stream.match('=#')) { state.nestedComments--; if (state.nestedComments == 0) state.tokenize = tokenBase; } return "comment"; } function tokenChar(stream, state) { var isChar = false, match; if (stream.match(chars)) { isChar = true; } else if (match = stream.match(/\\u([a-f0-9]{1,4})(?=')/i)) { var value = parseInt(match[1], 16); if (value <= 55295 || value >= 57344) { // (U+0,U+D7FF), (U+E000,U+FFFF) isChar = true; stream.next(); } } else if (match = stream.match(/\\U([A-Fa-f0-9]{5,8})(?=')/)) { var value = parseInt(match[1], 16); if (value <= 1114111) { // U+10FFFF isChar = true; stream.next(); } } if (isChar) { state.leavingExpr = true; state.tokenize = tokenBase; return "string"; } if (!stream.match(/^[^']+(?=')/)) { stream.skipToEnd(); } if (stream.match('\'')) { state.tokenize = tokenBase; } return "error"; } function tokenStringFactory(delimiter) { if (delimiter.substr(-3) === '"""') { delimiter = '"""'; } else if (delimiter.substr(-1) === '"') { delimiter = '"'; } function tokenString(stream, state) { if (stream.eat('\\')) { stream.next(); } else if (stream.match(delimiter)) { state.tokenize = tokenBase; state.leavingExpr = true; return "string"; } else { stream.eat(/[`"]/); } stream.eatWhile(/[^\\`"]/); return "string"; } return tokenString; } var external = { startState: function() { return { tokenize: tokenBase, scopes: [], lastToken: null, leavingExpr: false, isDefinition: false, nestedArrays: 0, nestedComments: 0, nestedGenerators: 0, nestedParameters: 0, firstParenPos: -1 }; }, token: function(stream, state) { var style = state.tokenize(stream, state); var current = stream.current(); if (current && style) { state.lastToken = current; } return style; }, indent: function(state, textAfter) { var delta = 0; if ( textAfter === ']' || textAfter === ')' || /^end\b/.test(textAfter) || /^else/.test(textAfter) || /^catch\b/.test(textAfter) || /^elseif\b/.test(textAfter) || /^finally/.test(textAfter) ) { delta = -1; } return (state.scopes.length + delta) * config.indentUnit; }, electricInput: /\b(end|else|catch|finally)\b/, blockCommentStart: "#=", blockCommentEnd: "=#", lineComment: "#", closeBrackets: "()[]{}\"\"", fold: "indent" }; return external; }); CodeMirror.defineMIME("text/x-julia", "julia"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/livescript/index.html ================================================ CodeMirror: LiveScript mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • LiveScript

    LiveScript mode

    MIME types defined: text/x-livescript.

    The LiveScript mode was written by Kenneth Bentley.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/livescript/livescript.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Link to the project's GitHub page: * https://github.com/duralog/CodeMirror */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('livescript', function(){ var tokenBase = function(stream, state) { var next_rule = state.next || "start"; if (next_rule) { state.next = state.next; var nr = Rules[next_rule]; if (nr.splice) { for (var i$ = 0; i$ < nr.length; ++i$) { var r = nr[i$]; if (r.regex && stream.match(r.regex)) { state.next = r.next || state.next; return r.token; } } stream.next(); return 'error'; } if (stream.match(r = Rules[next_rule])) { if (r.regex && stream.match(r.regex)) { state.next = r.next; return r.token; } else { stream.next(); return 'error'; } } } stream.next(); return 'error'; }; var external = { startState: function(){ return { next: 'start', lastToken: {style: null, indent: 0, content: ""} }; }, token: function(stream, state){ while (stream.pos == stream.start) var style = tokenBase(stream, state); state.lastToken = { style: style, indent: stream.indentation(), content: stream.current() }; return style.replace(/\./g, ' '); }, indent: function(state){ var indentation = state.lastToken.indent; if (state.lastToken.content.match(indenter)) { indentation += 2; } return indentation; } }; return external; }); var identifier = '(?![\\d\\s])[$\\w\\xAA-\\uFFDC](?:(?!\\s)[$\\w\\xAA-\\uFFDC]|-[A-Za-z])*'; var indenter = RegExp('(?:[({[=:]|[-~]>|\\b(?:e(?:lse|xport)|d(?:o|efault)|t(?:ry|hen)|finally|import(?:\\s*all)?|const|var|let|new|catch(?:\\s*' + identifier + ')?))\\s*$'); var keywordend = '(?![$\\w]|-[A-Za-z]|\\s*:(?![:=]))'; var stringfill = { token: 'string', regex: '.+' }; var Rules = { start: [ { token: 'comment.doc', regex: '/\\*', next: 'comment' }, { token: 'comment', regex: '#.*' }, { token: 'keyword', regex: '(?:t(?:h(?:is|row|en)|ry|ypeof!?)|c(?:on(?:tinue|st)|a(?:se|tch)|lass)|i(?:n(?:stanceof)?|mp(?:ort(?:\\s+all)?|lements)|[fs])|d(?:e(?:fault|lete|bugger)|o)|f(?:or(?:\\s+own)?|inally|unction)|s(?:uper|witch)|e(?:lse|x(?:tends|port)|val)|a(?:nd|rguments)|n(?:ew|ot)|un(?:less|til)|w(?:hile|ith)|o[fr]|return|break|let|var|loop)' + keywordend }, { token: 'constant.language', regex: '(?:true|false|yes|no|on|off|null|void|undefined)' + keywordend }, { token: 'invalid.illegal', regex: '(?:p(?:ackage|r(?:ivate|otected)|ublic)|i(?:mplements|nterface)|enum|static|yield)' + keywordend }, { token: 'language.support.class', regex: '(?:R(?:e(?:gExp|ferenceError)|angeError)|S(?:tring|yntaxError)|E(?:rror|valError)|Array|Boolean|Date|Function|Number|Object|TypeError|URIError)' + keywordend }, { token: 'language.support.function', regex: '(?:is(?:NaN|Finite)|parse(?:Int|Float)|Math|JSON|(?:en|de)codeURI(?:Component)?)' + keywordend }, { token: 'variable.language', regex: '(?:t(?:hat|il|o)|f(?:rom|allthrough)|it|by|e)' + keywordend }, { token: 'identifier', regex: identifier + '\\s*:(?![:=])' }, { token: 'variable', regex: identifier }, { token: 'keyword.operator', regex: '(?:\\.{3}|\\s+\\?)' }, { token: 'keyword.variable', regex: '(?:@+|::|\\.\\.)', next: 'key' }, { token: 'keyword.operator', regex: '\\.\\s*', next: 'key' }, { token: 'string', regex: '\\\\\\S[^\\s,;)}\\]]*' }, { token: 'string.doc', regex: '\'\'\'', next: 'qdoc' }, { token: 'string.doc', regex: '"""', next: 'qqdoc' }, { token: 'string', regex: '\'', next: 'qstring' }, { token: 'string', regex: '"', next: 'qqstring' }, { token: 'string', regex: '`', next: 'js' }, { token: 'string', regex: '<\\[', next: 'words' }, { token: 'string.regex', regex: '//', next: 'heregex' }, { token: 'string.regex', regex: '\\/(?:[^[\\/\\n\\\\]*(?:(?:\\\\.|\\[[^\\]\\n\\\\]*(?:\\\\.[^\\]\\n\\\\]*)*\\])[^[\\/\\n\\\\]*)*)\\/[gimy$]{0,4}', next: 'key' }, { token: 'constant.numeric', regex: '(?:0x[\\da-fA-F][\\da-fA-F_]*|(?:[2-9]|[12]\\d|3[0-6])r[\\da-zA-Z][\\da-zA-Z_]*|(?:\\d[\\d_]*(?:\\.\\d[\\d_]*)?|\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[\\w$]*)' }, { token: 'lparen', regex: '[({[]' }, { token: 'rparen', regex: '[)}\\]]', next: 'key' }, { token: 'keyword.operator', regex: '\\S+' }, { token: 'text', regex: '\\s+' } ], heregex: [ { token: 'string.regex', regex: '.*?//[gimy$?]{0,4}', next: 'start' }, { token: 'string.regex', regex: '\\s*#{' }, { token: 'comment.regex', regex: '\\s+(?:#.*)?' }, { token: 'string.regex', regex: '\\S+' } ], key: [ { token: 'keyword.operator', regex: '[.?@!]+' }, { token: 'identifier', regex: identifier, next: 'start' }, { token: 'text', regex: '', next: 'start' } ], comment: [ { token: 'comment.doc', regex: '.*?\\*/', next: 'start' }, { token: 'comment.doc', regex: '.+' } ], qdoc: [ { token: 'string', regex: ".*?'''", next: 'key' }, stringfill ], qqdoc: [ { token: 'string', regex: '.*?"""', next: 'key' }, stringfill ], qstring: [ { token: 'string', regex: '[^\\\\\']*(?:\\\\.[^\\\\\']*)*\'', next: 'key' }, stringfill ], qqstring: [ { token: 'string', regex: '[^\\\\"]*(?:\\\\.[^\\\\"]*)*"', next: 'key' }, stringfill ], js: [ { token: 'string', regex: '[^\\\\`]*(?:\\\\.[^\\\\`]*)*`', next: 'key' }, stringfill ], words: [ { token: 'string', regex: '.*?\\]>', next: 'key' }, stringfill ] }; for (var idx in Rules) { var r = Rules[idx]; if (r.splice) { for (var i = 0, len = r.length; i < len; ++i) { var rr = r[i]; if (typeof rr.regex === 'string') { Rules[idx][i].regex = new RegExp('^' + rr.regex); } } } else if (typeof rr.regex === 'string') { Rules[idx].regex = new RegExp('^' + r.regex); } } CodeMirror.defineMIME('text/x-livescript', 'livescript'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/lua/index.html ================================================ CodeMirror: Lua mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Lua

    Lua mode

    Loosely based on Franciszek Wawrzak's CodeMirror 1 mode. One configuration parameter is supported, specials, to which you can provide an array of strings to have those identifiers highlighted with the lua-special style.

    MIME types defined: text/x-lua.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/lua/lua.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's // CodeMirror 1 mode. // highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("lua", function(config, parserConfig) { var indentUnit = config.indentUnit; function prefixRE(words) { return new RegExp("^(?:" + words.join("|") + ")", "i"); } function wordRE(words) { return new RegExp("^(?:" + words.join("|") + ")$", "i"); } var specials = wordRE(parserConfig.specials || []); // long list of standard functions from lua manual var builtins = wordRE([ "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load", "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require", "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall", "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield", "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable", "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable", "debug.setupvalue","debug.traceback", "close","flush","lines","read","seek","setvbuf","write", "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin", "io.stdout","io.tmpfile","io.type","io.write", "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg", "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max", "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh", "math.sqrt","math.tan","math.tanh", "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale", "os.time","os.tmpname", "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload", "package.seeall", "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub", "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper", "table.concat","table.insert","table.maxn","table.remove","table.sort" ]); var keywords = wordRE(["and","break","elseif","false","nil","not","or","return", "true","function", "end", "if", "then", "else", "do", "while", "repeat", "until", "for", "in", "local" ]); var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]); var dedentTokens = wordRE(["end", "until", "\\)", "}"]); var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]); function readBracket(stream) { var level = 0; while (stream.eat("=")) ++level; stream.eat("["); return level; } function normal(stream, state) { var ch = stream.next(); if (ch == "-" && stream.eat("-")) { if (stream.eat("[") && stream.eat("[")) return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state); stream.skipToEnd(); return "comment"; } if (ch == "\"" || ch == "'") return (state.cur = string(ch))(stream, state); if (ch == "[" && /[\[=]/.test(stream.peek())) return (state.cur = bracketed(readBracket(stream), "string"))(stream, state); if (/\d/.test(ch)) { stream.eatWhile(/[\w.%]/); return "number"; } if (/[\w_]/.test(ch)) { stream.eatWhile(/[\w\\\-_.]/); return "variable"; } return null; } function bracketed(level, style) { return function(stream, state) { var curlev = null, ch; while ((ch = stream.next()) != null) { if (curlev == null) {if (ch == "]") curlev = 0;} else if (ch == "=") ++curlev; else if (ch == "]" && curlev == level) { state.cur = normal; break; } else curlev = null; } return style; }; } function string(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) break; escaped = !escaped && ch == "\\"; } if (!escaped) state.cur = normal; return "string"; }; } return { startState: function(basecol) { return {basecol: basecol || 0, indentDepth: 0, cur: normal}; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = state.cur(stream, state); var word = stream.current(); if (style == "variable") { if (keywords.test(word)) style = "keyword"; else if (builtins.test(word)) style = "builtin"; else if (specials.test(word)) style = "variable-2"; } if ((style != "comment") && (style != "string")){ if (indentTokens.test(word)) ++state.indentDepth; else if (dedentTokens.test(word)) --state.indentDepth; } return style; }, indent: function(state, textAfter) { var closing = dedentPartial.test(textAfter); return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0)); }, electricInput: /^\s*(?:end|until|else|\)|\})$/, lineComment: "--", blockCommentStart: "--[[", blockCommentEnd: "]]" }; }); CodeMirror.defineMIME("text/x-lua", "lua"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/markdown/index.html ================================================ CodeMirror: Markdown mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Markdown

    Markdown mode

    If you also want support strikethrough, emoji and few other goodies, check out GitHub-Flavored Markdown mode.

    Optionally depends on other modes for properly highlighted code blocks, and XML mode for properly highlighted inline XML blocks.

    Markdown mode supports these options:

    • highlightFormatting: boolean
      Whether to separately highlight markdown meta characters (*[]()etc.) (default: false).
    • maxBlockquoteDepth: boolean
      Maximum allowed blockquote nesting (default: 0 - infinite nesting).
    • xml: boolean
      Whether to highlight inline XML (default: true).
    • fencedCodeBlockHighlighting: boolean
      Whether to syntax-highlight fenced code blocks, if given mode is included, or fencedCodeBlockDefaultMode is set (default: true).
    • fencedCodeBlockDefaultMode: string
      Mode to use for fencedCodeBlockHighlighting, if given mode is not included.
    • tokenTypeOverrides: Object
      When you want to override default token type names (e.g. {code: "code"}).
    • allowAtxHeaderWithoutSpace: boolean
      Allow lazy headers without whitespace between hashtag and text (default: false).

    MIME types defined: text/x-markdown.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/markdown/markdown.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../xml/xml", "../meta"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var htmlMode = CodeMirror.getMode(cmCfg, "text/html"); var htmlModeMissing = htmlMode.name == "null" function getMode(name) { if (CodeMirror.findModeByName) { var found = CodeMirror.findModeByName(name); if (found) name = found.mime || found.mimes[0]; } var mode = CodeMirror.getMode(cmCfg, name); return mode.name == "null" ? null : mode; } // Should characters that affect highlighting be highlighted separate? // Does not include characters that will be output (such as `1.` and `-` for lists) if (modeCfg.highlightFormatting === undefined) modeCfg.highlightFormatting = false; // Maximum number of nested blockquotes. Set to 0 for infinite nesting. // Excess `>` will emit `error` token. if (modeCfg.maxBlockquoteDepth === undefined) modeCfg.maxBlockquoteDepth = 0; // Turn on task lists? ("- [ ] " and "- [x] ") if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; // Turn on strikethrough syntax if (modeCfg.strikethrough === undefined) modeCfg.strikethrough = false; if (modeCfg.emoji === undefined) modeCfg.emoji = false; if (modeCfg.fencedCodeBlockHighlighting === undefined) modeCfg.fencedCodeBlockHighlighting = true; if (modeCfg.fencedCodeBlockDefaultMode === undefined) modeCfg.fencedCodeBlockDefaultMode = 'text/plain'; if (modeCfg.xml === undefined) modeCfg.xml = true; // Allow token types to be overridden by user-provided token types. if (modeCfg.tokenTypeOverrides === undefined) modeCfg.tokenTypeOverrides = {}; var tokenTypes = { header: "header", code: "comment", quote: "quote", list1: "variable-2", list2: "variable-3", list3: "keyword", hr: "hr", image: "image", imageAltText: "image-alt-text", imageMarker: "image-marker", formatting: "formatting", linkInline: "link", linkEmail: "link", linkText: "link", linkHref: "string", em: "em", strong: "strong", strikethrough: "strikethrough", emoji: "builtin" }; for (var tokenType in tokenTypes) { if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) { tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType]; } } var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/ , listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/ , taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/ , setextHeaderRE = /^ {0,3}(?:\={1,}|-{2,})\s*$/ , textRE = /^[^#!\[\]*_\\<>` "'(~:]+/ , fencedCodeRE = /^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/ , linkDefRE = /^\s*\[[^\]]+?\]:.*$/ // naive link-definition , punctuation = /[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/ , expandedTab = " " // CommonMark specifies tab as 4 spaces function switchInline(stream, state, f) { state.f = state.inline = f; return f(stream, state); } function switchBlock(stream, state, f) { state.f = state.block = f; return f(stream, state); } function lineIsEmpty(line) { return !line || !/\S/.test(line.string) } // Blocks function blankLine(state) { // Reset linkTitle state state.linkTitle = false; state.linkHref = false; state.linkText = false; // Reset EM state state.em = false; // Reset STRONG state state.strong = false; // Reset strikethrough state state.strikethrough = false; // Reset state.quote state.quote = 0; // Reset state.indentedCode state.indentedCode = false; if (state.f == htmlBlock) { var exit = htmlModeMissing if (!exit) { var inner = CodeMirror.innerMode(htmlMode, state.htmlState) exit = inner.mode.name == "xml" && inner.state.tagStart === null && (!inner.state.context && inner.state.tokenize.isInText) } if (exit) { state.f = inlineNormal; state.block = blockNormal; state.htmlState = null; } } // Reset state.trailingSpace state.trailingSpace = 0; state.trailingSpaceNewLine = false; // Mark this line as blank state.prevLine = state.thisLine state.thisLine = {stream: null} return null; } function blockNormal(stream, state) { var firstTokenOnLine = stream.column() === state.indentation; var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream); var prevLineIsIndentedCode = state.indentedCode; var prevLineIsHr = state.prevLine.hr; var prevLineIsList = state.list !== false; var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3; state.indentedCode = false; var lineIndentation = state.indentation; // compute once per line (on first token) if (state.indentationDiff === null) { state.indentationDiff = state.indentation; if (prevLineIsList) { state.list = null; // While this list item's marker's indentation is less than the deepest // list item's content's indentation,pop the deepest list item // indentation off the stack, and update block indentation state while (lineIndentation < state.listStack[state.listStack.length - 1]) { state.listStack.pop(); if (state.listStack.length) { state.indentation = state.listStack[state.listStack.length - 1]; // less than the first list's indent -> the line is no longer a list } else { state.list = false; } } if (state.list !== false) { state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1] } } } // not comprehensive (currently only for setext detection purposes) var allowsInlineContinuation = ( !prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header && (!prevLineIsList || !prevLineIsIndentedCode) && !state.prevLine.fencedCodeEnd ); var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) && state.indentation <= maxNonCodeIndentation && stream.match(hrRE); var match = null; if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd || state.prevLine.header || prevLineLineIsEmpty)) { stream.skipToEnd(); state.indentedCode = true; return tokenTypes.code; } else if (stream.eatSpace()) { return null; } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) { state.quote = 0; state.header = match[1].length; state.thisLine.header = true; if (modeCfg.highlightFormatting) state.formatting = "header"; state.f = state.inline; return getType(state); } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) { state.quote = firstTokenOnLine ? 1 : state.quote + 1; if (modeCfg.highlightFormatting) state.formatting = "quote"; stream.eatSpace(); return getType(state); } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) { var listType = match[1] ? "ol" : "ul"; state.indentation = lineIndentation + stream.current().length; state.list = true; state.quote = 0; // Add this list item's content's indentation to the stack state.listStack.push(state.indentation); // Reset inline styles which shouldn't propagate across list items state.em = false; state.strong = false; state.code = false; state.strikethrough = false; if (modeCfg.taskLists && stream.match(taskListRE, false)) { state.taskList = true; } state.f = state.inline; if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; return getType(state); } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) { state.quote = 0; state.fencedEndRE = new RegExp(match[1] + "+ *$"); // try switching mode state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode ); if (state.localMode) state.localState = CodeMirror.startState(state.localMode); state.f = state.block = local; if (modeCfg.highlightFormatting) state.formatting = "code-block"; state.code = -1 return getType(state); // SETEXT has lowest block-scope precedence after HR, so check it after // the others (code, blockquote, list...) } else if ( // if setext set, indicates line after ---/=== state.setext || ( // line before ---/=== (!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false && !state.code && !isHr && !linkDefRE.test(stream.string) && (match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE)) ) ) { if ( !state.setext ) { state.header = match[0].charAt(0) == '=' ? 1 : 2; state.setext = state.header; } else { state.header = state.setext; // has no effect on type so we can reset it now state.setext = 0; stream.skipToEnd(); if (modeCfg.highlightFormatting) state.formatting = "header"; } state.thisLine.header = true; state.f = state.inline; return getType(state); } else if (isHr) { stream.skipToEnd(); state.hr = true; state.thisLine.hr = true; return tokenTypes.hr; } else if (stream.peek() === '[') { return switchInline(stream, state, footnoteLink); } return switchInline(stream, state, state.inline); } function htmlBlock(stream, state) { var style = htmlMode.token(stream, state.htmlState); if (!htmlModeMissing) { var inner = CodeMirror.innerMode(htmlMode, state.htmlState) if ((inner.mode.name == "xml" && inner.state.tagStart === null && (!inner.state.context && inner.state.tokenize.isInText)) || (state.md_inside && stream.current().indexOf(">") > -1)) { state.f = inlineNormal; state.block = blockNormal; state.htmlState = null; } } return style; } function local(stream, state) { var currListInd = state.listStack[state.listStack.length - 1] || 0; var hasExitedList = state.indentation < currListInd; var maxFencedEndInd = currListInd + 3; if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) { if (modeCfg.highlightFormatting) state.formatting = "code-block"; var returnType; if (!hasExitedList) returnType = getType(state) state.localMode = state.localState = null; state.block = blockNormal; state.f = inlineNormal; state.fencedEndRE = null; state.code = 0 state.thisLine.fencedCodeEnd = true; if (hasExitedList) return switchBlock(stream, state, state.block); return returnType; } else if (state.localMode) { return state.localMode.token(stream, state.localState); } else { stream.skipToEnd(); return tokenTypes.code; } } // Inline function getType(state) { var styles = []; if (state.formatting) { styles.push(tokenTypes.formatting); if (typeof state.formatting === "string") state.formatting = [state.formatting]; for (var i = 0; i < state.formatting.length; i++) { styles.push(tokenTypes.formatting + "-" + state.formatting[i]); if (state.formatting[i] === "header") { styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header); } // Add `formatting-quote` and `formatting-quote-#` for blockquotes // Add `error` instead if the maximum blockquote nesting depth is passed if (state.formatting[i] === "quote") { if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote); } else { styles.push("error"); } } } } if (state.taskOpen) { styles.push("meta"); return styles.length ? styles.join(' ') : null; } if (state.taskClosed) { styles.push("property"); return styles.length ? styles.join(' ') : null; } if (state.linkHref) { styles.push(tokenTypes.linkHref, "url"); } else { // Only apply inline styles to non-url text if (state.strong) { styles.push(tokenTypes.strong); } if (state.em) { styles.push(tokenTypes.em); } if (state.strikethrough) { styles.push(tokenTypes.strikethrough); } if (state.emoji) { styles.push(tokenTypes.emoji); } if (state.linkText) { styles.push(tokenTypes.linkText); } if (state.code) { styles.push(tokenTypes.code); } if (state.image) { styles.push(tokenTypes.image); } if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); } if (state.imageMarker) { styles.push(tokenTypes.imageMarker); } } if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); } if (state.quote) { styles.push(tokenTypes.quote); // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { styles.push(tokenTypes.quote + "-" + state.quote); } else { styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth); } } if (state.list !== false) { var listMod = (state.listStack.length - 1) % 3; if (!listMod) { styles.push(tokenTypes.list1); } else if (listMod === 1) { styles.push(tokenTypes.list2); } else { styles.push(tokenTypes.list3); } } if (state.trailingSpaceNewLine) { styles.push("trailing-space-new-line"); } else if (state.trailingSpace) { styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); } return styles.length ? styles.join(' ') : null; } function handleText(stream, state) { if (stream.match(textRE, true)) { return getType(state); } return undefined; } function inlineNormal(stream, state) { var style = state.text(stream, state); if (typeof style !== 'undefined') return style; if (state.list) { // List marker (*, +, -, 1., etc) state.list = null; return getType(state); } if (state.taskList) { var taskOpen = stream.match(taskListRE, true)[1] === " "; if (taskOpen) state.taskOpen = true; else state.taskClosed = true; if (modeCfg.highlightFormatting) state.formatting = "task"; state.taskList = false; return getType(state); } state.taskOpen = false; state.taskClosed = false; if (state.header && stream.match(/^#+$/, true)) { if (modeCfg.highlightFormatting) state.formatting = "header"; return getType(state); } var ch = stream.next(); // Matches link titles present on next line if (state.linkTitle) { state.linkTitle = false; var matchCh = ch; if (ch === '(') { matchCh = ')'; } matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1"); var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; if (stream.match(new RegExp(regex), true)) { return tokenTypes.linkHref; } } // If this block is changed, it may need to be updated in GFM mode if (ch === '`') { var previousFormatting = state.formatting; if (modeCfg.highlightFormatting) state.formatting = "code"; stream.eatWhile('`'); var count = stream.current().length if (state.code == 0 && (!state.quote || count == 1)) { state.code = count return getType(state) } else if (count == state.code) { // Must be exact var t = getType(state) state.code = 0 return t } else { state.formatting = previousFormatting return getType(state) } } else if (state.code) { return getType(state); } if (ch === '\\') { stream.next(); if (modeCfg.highlightFormatting) { var type = getType(state); var formattingEscape = tokenTypes.formatting + "-escape"; return type ? type + " " + formattingEscape : formattingEscape; } } if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { state.imageMarker = true; state.image = true; if (modeCfg.highlightFormatting) state.formatting = "image"; return getType(state); } if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) { state.imageMarker = false; state.imageAltText = true if (modeCfg.highlightFormatting) state.formatting = "image"; return getType(state); } if (ch === ']' && state.imageAltText) { if (modeCfg.highlightFormatting) state.formatting = "image"; var type = getType(state); state.imageAltText = false; state.image = false; state.inline = state.f = linkHref; return type; } if (ch === '[' && !state.image) { if (state.linkText && stream.match(/^.*?\]/)) return getType(state) state.linkText = true; if (modeCfg.highlightFormatting) state.formatting = "link"; return getType(state); } if (ch === ']' && state.linkText) { if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); state.linkText = false; state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal return type; } if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { state.f = state.inline = linkInline; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + tokenTypes.linkInline; } if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { state.f = state.inline = linkInline; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + tokenTypes.linkEmail; } if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i, false)) { var end = stream.string.indexOf(">", stream.pos); if (end != -1) { var atts = stream.string.substring(stream.start, end); if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true; } stream.backUp(1); state.htmlState = CodeMirror.startState(htmlMode); return switchBlock(stream, state, htmlBlock); } if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) { state.md_inside = false; return "tag"; } else if (ch === "*" || ch === "_") { var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2) while (len < 3 && stream.eat(ch)) len++ var after = stream.peek() || " " // See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before)) var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after)) var setEm = null, setStrong = null if (len % 2) { // Em if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) setEm = true else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) setEm = false } if (len > 1) { // Strong if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) setStrong = true else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) setStrong = false } if (setStrong != null || setEm != null) { if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em" if (setEm === true) state.em = ch if (setStrong === true) state.strong = ch var t = getType(state) if (setEm === false) state.em = false if (setStrong === false) state.strong = false return t } } else if (ch === ' ') { if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces if (stream.peek() === ' ') { // Surrounded by spaces, ignore return getType(state); } else { // Not surrounded by spaces, back up pointer stream.backUp(1); } } } if (modeCfg.strikethrough) { if (ch === '~' && stream.eatWhile(ch)) { if (state.strikethrough) {// Remove strikethrough if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; var t = getType(state); state.strikethrough = false; return t; } else if (stream.match(/^[^\s]/, false)) {// Add strikethrough state.strikethrough = true; if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; return getType(state); } } else if (ch === ' ') { if (stream.match('~~', true)) { // Probably surrounded by space if (stream.peek() === ' ') { // Surrounded by spaces, ignore return getType(state); } else { // Not surrounded by spaces, back up pointer stream.backUp(2); } } } } if (modeCfg.emoji && ch === ":" && stream.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)) { state.emoji = true; if (modeCfg.highlightFormatting) state.formatting = "emoji"; var retType = getType(state); state.emoji = false; return retType; } if (ch === ' ') { if (stream.match(/^ +$/, false)) { state.trailingSpace++; } else if (state.trailingSpace) { state.trailingSpaceNewLine = true; } } return getType(state); } function linkInline(stream, state) { var ch = stream.next(); if (ch === ">") { state.f = state.inline = inlineNormal; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + tokenTypes.linkInline; } stream.match(/^[^>]+/, true); return tokenTypes.linkInline; } function linkHref(stream, state) { // Check if space, and return NULL if so (to avoid marking the space) if(stream.eatSpace()){ return null; } var ch = stream.next(); if (ch === '(' || ch === '[') { state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]"); if (modeCfg.highlightFormatting) state.formatting = "link-string"; state.linkHref = true; return getType(state); } return 'error'; } var linkRE = { ")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/, "]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/ } function getLinkHrefInside(endChar) { return function(stream, state) { var ch = stream.next(); if (ch === endChar) { state.f = state.inline = inlineNormal; if (modeCfg.highlightFormatting) state.formatting = "link-string"; var returnState = getType(state); state.linkHref = false; return returnState; } stream.match(linkRE[endChar]) state.linkHref = true; return getType(state); }; } function footnoteLink(stream, state) { if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) { state.f = footnoteLinkInside; stream.next(); // Consume [ if (modeCfg.highlightFormatting) state.formatting = "link"; state.linkText = true; return getType(state); } return switchInline(stream, state, inlineNormal); } function footnoteLinkInside(stream, state) { if (stream.match(']:', true)) { state.f = state.inline = footnoteUrl; if (modeCfg.highlightFormatting) state.formatting = "link"; var returnType = getType(state); state.linkText = false; return returnType; } stream.match(/^([^\]\\]|\\.)+/, true); return tokenTypes.linkText; } function footnoteUrl(stream, state) { // Check if space, and return NULL if so (to avoid marking the space) if(stream.eatSpace()){ return null; } // Match URL stream.match(/^[^\s]+/, true); // Check for link title if (stream.peek() === undefined) { // End of line, set flag to check next line state.linkTitle = true; } else { // More content on line, check if link title stream.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/, true); } state.f = state.inline = inlineNormal; return tokenTypes.linkHref + " url"; } var mode = { startState: function() { return { f: blockNormal, prevLine: {stream: null}, thisLine: {stream: null}, block: blockNormal, htmlState: null, indentation: 0, inline: inlineNormal, text: handleText, formatting: false, linkText: false, linkHref: false, linkTitle: false, code: 0, em: false, strong: false, header: 0, setext: 0, hr: false, taskList: false, list: false, listStack: [], quote: 0, trailingSpace: 0, trailingSpaceNewLine: false, strikethrough: false, emoji: false, fencedEndRE: null }; }, copyState: function(s) { return { f: s.f, prevLine: s.prevLine, thisLine: s.thisLine, block: s.block, htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState), indentation: s.indentation, localMode: s.localMode, localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, inline: s.inline, text: s.text, formatting: false, linkText: s.linkText, linkTitle: s.linkTitle, linkHref: s.linkHref, code: s.code, em: s.em, strong: s.strong, strikethrough: s.strikethrough, emoji: s.emoji, header: s.header, setext: s.setext, hr: s.hr, taskList: s.taskList, list: s.list, listStack: s.listStack.slice(0), quote: s.quote, indentedCode: s.indentedCode, trailingSpace: s.trailingSpace, trailingSpaceNewLine: s.trailingSpaceNewLine, md_inside: s.md_inside, fencedEndRE: s.fencedEndRE }; }, token: function(stream, state) { // Reset state.formatting state.formatting = false; if (stream != state.thisLine.stream) { state.header = 0; state.hr = false; if (stream.match(/^\s*$/, true)) { blankLine(state); return null; } state.prevLine = state.thisLine state.thisLine = {stream: stream} // Reset state.taskList state.taskList = false; // Reset state.trailingSpace state.trailingSpace = 0; state.trailingSpaceNewLine = false; if (!state.localState) { state.f = state.block; if (state.f != htmlBlock) { var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length; state.indentation = indentation; state.indentationDiff = null; if (indentation > 0) return null; } } } return state.f(stream, state); }, innerMode: function(state) { if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; if (state.localState) return {state: state.localState, mode: state.localMode}; return {state: state, mode: mode}; }, indent: function(state, textAfter, line) { if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line) if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line) return CodeMirror.Pass }, blankLine: blankLine, getType: getType, blockCommentStart: "", closeBrackets: "()[]{}''\"\"``", fold: "markdown" }; return mode; }, "xml"); CodeMirror.defineMIME("text/markdown", "markdown"); CodeMirror.defineMIME("text/x-markdown", "markdown"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/markdown/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var config = {tabSize: 4, indentUnit: 2} var mode = CodeMirror.getMode(config, "markdown"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } var modeHighlightFormatting = CodeMirror.getMode(config, {name: "markdown", highlightFormatting: true}); function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); } var modeMT_noXml = CodeMirror.getMode(config, {name: "markdown", xml: false}); function MT_noXml(name) { test.mode(name, modeMT_noXml, Array.prototype.slice.call(arguments, 1)); } var modeMT_noFencedHighlight = CodeMirror.getMode(config, {name: "markdown", fencedCodeBlockHighlighting: false}); function MT_noFencedHighlight(name) { test.mode(name, modeMT_noFencedHighlight, Array.prototype.slice.call(arguments, 1)); } var modeAtxNoSpace = CodeMirror.getMode(config, {name: "markdown", allowAtxHeaderWithoutSpace: true}); function AtxNoSpaceTest(name) { test.mode(name, modeAtxNoSpace, Array.prototype.slice.call(arguments, 1)); } var modeOverrideClasses = CodeMirror.getMode(config, { name: "markdown", strikethrough: true, emoji: true, tokenTypeOverrides: { "header" : "override-header", "code" : "override-code", "quote" : "override-quote", "list1" : "override-list1", "list2" : "override-list2", "list3" : "override-list3", "hr" : "override-hr", "image" : "override-image", "imageAltText": "override-image-alt-text", "imageMarker": "override-image-marker", "linkInline" : "override-link-inline", "linkEmail" : "override-link-email", "linkText" : "override-link-text", "linkHref" : "override-link-href", "em" : "override-em", "strong" : "override-strong", "strikethrough" : "override-strikethrough", "emoji" : "override-emoji" }}); function TokenTypeOverrideTest(name) { test.mode(name, modeOverrideClasses, Array.prototype.slice.call(arguments, 1)); } var modeFormattingOverride = CodeMirror.getMode(config, { name: "markdown", highlightFormatting: true, tokenTypeOverrides: { "formatting" : "override-formatting" }}); function FormatTokenTypeOverrideTest(name) { test.mode(name, modeFormattingOverride, Array.prototype.slice.call(arguments, 1)); } var modeET = CodeMirror.getMode(config, {name: "markdown", emoji: true}); function ET(name) { test.mode(name, modeET, Array.prototype.slice.call(arguments, 1)); } FT("formatting_emAsterisk", "[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]"); FT("formatting_emUnderscore", "[em&formatting&formatting-em _][em foo][em&formatting&formatting-em _]"); FT("formatting_strongAsterisk", "[strong&formatting&formatting-strong **][strong foo][strong&formatting&formatting-strong **]"); FT("formatting_strongUnderscore", "[strong&formatting&formatting-strong __][strong foo][strong&formatting&formatting-strong __]"); FT("formatting_codeBackticks", "[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]"); FT("formatting_doubleBackticks", "[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]"); FT("formatting_atxHeader", "[header&header-1&formatting&formatting-header&formatting-header-1 # ][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]"); FT("formatting_setextHeader", "[header&header-1 foo]", "[header&header-1&formatting&formatting-header&formatting-header-1 =]"); FT("formatting_blockquote", "[quote"e-1&formatting&formatting-quote&formatting-quote-1 > ][quote"e-1 foo]"); FT("formatting_list", "[variable-2&formatting&formatting-list&formatting-list-ul - ][variable-2 foo]"); FT("formatting_list", "[variable-2&formatting&formatting-list&formatting-list-ol 1. ][variable-2 foo]"); FT("formatting_link", "[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url (][string&url http://example.com/][string&formatting&formatting-link-string&url )]"); FT("formatting_linkReference", "[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url [][string&url bar][string&formatting&formatting-link-string&url ]]]", "[link&formatting&formatting-link [][link bar][link&formatting&formatting-link ]]:] [string&url http://example.com/]"); FT("formatting_linkWeb", "[link&formatting&formatting-link <][link http://example.com/][link&formatting&formatting-link >]"); FT("formatting_linkEmail", "[link&formatting&formatting-link <][link user@example.com][link&formatting&formatting-link >]"); FT("formatting_escape", "[formatting-escape \\*]"); FT("formatting_image", "[formatting&formatting-image&image&image-marker !][formatting&formatting-image&image&image-alt-text&link [[][image&image-alt-text&link alt text][formatting&formatting-image&image&image-alt-text&link ]]][formatting&formatting-link-string&string&url (][url&string http://link.to/image.jpg][formatting&formatting-link-string&string&url )]"); FT("codeBlock", "[comment&formatting&formatting-code-block ```css]", "[tag foo]", "[comment&formatting&formatting-code-block ```]"); MT("plainText", "foo"); // Don't style single trailing space MT("trailingSpace1", "foo "); // Two or more trailing spaces should be styled with line break character MT("trailingSpace2", "foo[trailing-space-a ][trailing-space-new-line ]"); MT("trailingSpace3", "foo[trailing-space-a ][trailing-space-b ][trailing-space-new-line ]"); MT("trailingSpace4", "foo[trailing-space-a ][trailing-space-b ][trailing-space-a ][trailing-space-new-line ]"); // Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) MT("codeBlocksUsing4Spaces", " [comment foo]"); // Code blocks using 4 spaces with internal indentation MT("codeBlocksUsing4SpacesIndentation", " [comment bar]", " [comment hello]", " [comment world]", " [comment foo]", "bar"); // Code blocks should end even after extra indented lines MT("codeBlocksWithTrailingIndentedLine", " [comment foo]", " [comment bar]", " [comment baz]", " ", "hello"); // Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) MT("codeBlocksUsing1Tab", "\t[comment foo]"); // No code blocks directly after paragraph // http://spec.commonmark.org/0.19/#example-65 MT("noCodeBlocksAfterParagraph", "Foo", " Bar"); MT("codeBlocksAfterATX", "[header&header-1 # foo]", " [comment code]"); MT("codeBlocksAfterSetext", "[header&header-2 foo]", "[header&header-2 ---]", " [comment code]"); MT("codeBlocksAfterFencedCode", "[comment ```]", "[comment foo]", "[comment ```]", " [comment code]"); // Inline code using backticks MT("inlineCodeUsingBackticks", "foo [comment `bar`]"); // Block code using single backtick (shouldn't work) MT("blockCodeSingleBacktick", "[comment `]", "[comment foo]", "[comment `]"); // Unclosed backticks // Instead of simply marking as CODE, it would be nice to have an // incomplete flag for CODE, that is styled slightly different. MT("unclosedBackticks", "foo [comment `bar]"); // Per documentation: "To include a literal backtick character within a // code span, you can use multiple backticks as the opening and closing // delimiters" MT("doubleBackticks", "[comment ``foo ` bar``]"); // Tests based on Dingus // http://daringfireball.net/projects/markdown/dingus // // Multiple backticks within an inline code block MT("consecutiveBackticks", "[comment `foo```bar`]"); // Multiple backticks within an inline code block with a second code block MT("consecutiveBackticks", "[comment `foo```bar`] hello [comment `world`]"); // Unclosed with several different groups of backticks MT("unclosedBackticks", "[comment ``foo ``` bar` hello]"); // Closed with several different groups of backticks MT("closedBackticks", "[comment ``foo ``` bar` hello``] world"); // info string cannot contain backtick, thus should result in inline code MT("closingFencedMarksOnSameLine", "[comment ``` code ```] foo"); // atx headers // http://daringfireball.net/projects/markdown/syntax#header MT("atxH1", "[header&header-1 # foo]"); MT("atxH2", "[header&header-2 ## foo]"); MT("atxH3", "[header&header-3 ### foo]"); MT("atxH4", "[header&header-4 #### foo]"); MT("atxH5", "[header&header-5 ##### foo]"); MT("atxH6", "[header&header-6 ###### foo]"); // http://spec.commonmark.org/0.19/#example-24 MT("noAtxH7", "####### foo"); // http://spec.commonmark.org/0.19/#example-25 MT("noAtxH1WithoutSpace", "#5 bolt"); // CommonMark requires a space after # but most parsers don't AtxNoSpaceTest("atxNoSpaceAllowed_H1NoSpace", "[header&header-1 #foo]"); AtxNoSpaceTest("atxNoSpaceAllowed_H4NoSpace", "[header&header-4 ####foo]"); AtxNoSpaceTest("atxNoSpaceAllowed_H1Space", "[header&header-1 # foo]"); // Inline styles should be parsed inside headers MT("atxH1inline", "[header&header-1 # foo ][header&header-1&em *bar*]"); MT("atxIndentedTooMuch", "[header&header-1 # foo]", " [comment # bar]"); // disable atx inside blockquote until we implement proper blockquote inner mode // TODO: fix to be CommonMark-compliant MT("atxNestedInsideBlockquote", "[quote"e-1 > # foo]"); MT("atxAfterBlockquote", "[quote"e-1 > foo]", "[header&header-1 # bar]"); // Setext headers - H1, H2 // Per documentation, "Any number of underlining =’s or -’s will work." // http://daringfireball.net/projects/markdown/syntax#header // Ideally, the text would be marked as `header` as well, but this is // not really feasible at the moment. So, instead, we're testing against // what works today, to avoid any regressions. // // Check if single underlining = works MT("setextH1", "[header&header-1 foo]", "[header&header-1 =]"); // Check if 3+ ='s work MT("setextH1", "[header&header-1 foo]", "[header&header-1 ===]"); // Check if single underlining - should not be interpreted // as it might lead to an empty list: // https://spec.commonmark.org/0.28/#setext-heading-underline MT("setextH2Single", "foo", "-"); // Check if 3+ -'s work MT("setextH2", "[header&header-2 foo]", "[header&header-2 ---]"); // http://spec.commonmark.org/0.19/#example-45 MT("setextH2AllowSpaces", "[header&header-2 foo]", " [header&header-2 ---- ]"); // http://spec.commonmark.org/0.19/#example-44 MT("noSetextAfterIndentedCodeBlock", " [comment foo]", "[hr ---]"); MT("setextAfterFencedCode", "[comment ```]", "[comment foo]", "[comment ```]", "[header&header-2 bar]", "[header&header-2 ---]"); MT("setextAfterATX", "[header&header-1 # foo]", "[header&header-2 bar]", "[header&header-2 ---]"); // http://spec.commonmark.org/0.19/#example-51 MT("noSetextAfterQuote", "[quote"e-1 > foo]", "[hr ---]", "", "[quote"e-1 > foo]", "[quote"e-1 bar]", "[hr ---]"); MT("noSetextAfterList", "[variable-2 - foo]", "[hr ---]"); MT("noSetextAfterList_listContinuation", "[variable-2 - foo]", "bar", "[hr ---]"); MT("setextAfterList_afterIndentedCode", "[variable-2 - foo]", "", " [comment bar]", "[header&header-2 baz]", "[header&header-2 ---]"); MT("setextAfterList_afterFencedCodeBlocks", "[variable-2 - foo]", "", " [comment ```]", " [comment bar]", " [comment ```]", "[header&header-2 baz]", "[header&header-2 ---]"); MT("setextAfterList_afterHeader", "[variable-2 - foo]", " [variable-2&header&header-1 # bar]", "[header&header-2 baz]", "[header&header-2 ---]"); MT("setextAfterList_afterHr", "[variable-2 - foo]", "", " [hr ---]", "[header&header-2 bar]", "[header&header-2 ---]"); MT("setext_nestedInlineMarkup", "[header&header-1 foo ][em&header&header-1 *bar*]", "[header&header-1 =]"); MT("setext_linkDef", "[link [[aaa]]:] [string&url http://google.com 'title']", "[hr ---]"); // currently, looks max one line ahead, thus won't catch valid CommonMark // markup MT("setext_oneLineLookahead", "foo", "[header&header-1 bar]", "[header&header-1 =]"); // ensure we regard space after a single dash as a list MT("setext_emptyList", "foo", "[variable-2 - ]", "foo"); // Single-line blockquote with trailing space MT("blockquoteSpace", "[quote"e-1 > foo]"); // Single-line blockquote MT("blockquoteNoSpace", "[quote"e-1 >foo]"); // No blank line before blockquote MT("blockquoteNoBlankLine", "foo", "[quote"e-1 > bar]"); MT("blockquoteNested", "[quote"e-1 > foo]", "[quote"e-1 >][quote"e-2 > foo]", "[quote"e-1 >][quote"e-2 >][quote"e-3 > foo]"); // ensure quote-level is inferred correctly even if indented MT("blockquoteNestedIndented", " [quote"e-1 > foo]", " [quote"e-1 >][quote"e-2 > foo]", " [quote"e-1 >][quote"e-2 >][quote"e-3 > foo]"); // ensure quote-level is inferred correctly even if indented MT("blockquoteIndentedTooMuch", "foo", " > bar"); // Single-line blockquote followed by normal paragraph MT("blockquoteThenParagraph", "[quote"e-1 >foo]", "", "bar"); // Multi-line blockquote (lazy mode) MT("multiBlockquoteLazy", "[quote"e-1 >foo]", "[quote"e-1 bar]"); // Multi-line blockquote followed by normal paragraph (lazy mode) MT("multiBlockquoteLazyThenParagraph", "[quote"e-1 >foo]", "[quote"e-1 bar]", "", "hello"); // Multi-line blockquote (non-lazy mode) MT("multiBlockquote", "[quote"e-1 >foo]", "[quote"e-1 >bar]"); // Multi-line blockquote followed by normal paragraph (non-lazy mode) MT("multiBlockquoteThenParagraph", "[quote"e-1 >foo]", "[quote"e-1 >bar]", "", "hello"); // disallow lists inside blockquote for now because it causes problems outside blockquote // TODO: fix to be CommonMark-compliant MT("listNestedInBlockquote", "[quote"e-1 > - foo]"); // disallow fenced blocks inside blockquote because it causes problems outside blockquote // TODO: fix to be CommonMark-compliant MT("fencedBlockNestedInBlockquote", "[quote"e-1 > ```]", "[quote"e-1 > code]", "[quote"e-1 > ```]", // ensure we still allow inline code "[quote"e-1 > ][quote"e-1&comment `code`]"); // Header with leading space after continued blockquote (#3287, negative indentation) MT("headerAfterContinuedBlockquote", "[quote"e-1 > foo]", "[quote"e-1 bar]", "", " [header&header-1 # hello]"); // Check list types MT("listAsterisk", "foo", "bar", "", "[variable-2 * foo]", "[variable-2 * bar]"); MT("listPlus", "foo", "bar", "", "[variable-2 + foo]", "[variable-2 + bar]"); MT("listDash", "foo", "bar", "", "[variable-2 - foo]", "[variable-2 - bar]"); MT("listNumber", "foo", "bar", "", "[variable-2 1. foo]", "[variable-2 2. bar]"); MT("listFromParagraph", "foo", "[variable-2 1. bar]", "[variable-2 2. hello]"); // List after hr MT("listAfterHr", "[hr ---]", "[variable-2 - bar]"); // List after header MT("listAfterHeader", "[header&header-1 # foo]", "[variable-2 - bar]"); // hr after list MT("hrAfterList", "[variable-2 - foo]", "[hr -----]"); MT("hrAfterFencedCode", "[comment ```]", "[comment code]", "[comment ```]", "[hr ---]"); // allow hr inside lists // (require prev line to be empty or hr, TODO: non-CommonMark-compliant) MT("hrInsideList", "[variable-2 - foo]", "", " [hr ---]", " [hr ---]", "", " [comment ---]"); MT("consecutiveHr", "[hr ---]", "[hr ---]", "[hr ---]"); // Formatting in lists (*) MT("listAsteriskFormatting", "[variable-2 * ][variable-2&em *foo*][variable-2 bar]", "[variable-2 * ][variable-2&strong **foo**][variable-2 bar]", "[variable-2 * ][variable-2&em&strong ***foo***][variable-2 bar]", "[variable-2 * ][variable-2&comment `foo`][variable-2 bar]"); // Formatting in lists (+) MT("listPlusFormatting", "[variable-2 + ][variable-2&em *foo*][variable-2 bar]", "[variable-2 + ][variable-2&strong **foo**][variable-2 bar]", "[variable-2 + ][variable-2&em&strong ***foo***][variable-2 bar]", "[variable-2 + ][variable-2&comment `foo`][variable-2 bar]"); // Formatting in lists (-) MT("listDashFormatting", "[variable-2 - ][variable-2&em *foo*][variable-2 bar]", "[variable-2 - ][variable-2&strong **foo**][variable-2 bar]", "[variable-2 - ][variable-2&em&strong ***foo***][variable-2 bar]", "[variable-2 - ][variable-2&comment `foo`][variable-2 bar]"); // Formatting in lists (1.) MT("listNumberFormatting", "[variable-2 1. ][variable-2&em *foo*][variable-2 bar]", "[variable-2 2. ][variable-2&strong **foo**][variable-2 bar]", "[variable-2 3. ][variable-2&em&strong ***foo***][variable-2 bar]", "[variable-2 4. ][variable-2&comment `foo`][variable-2 bar]"); // Paragraph lists MT("listParagraph", "[variable-2 * foo]", "", "[variable-2 * bar]"); // Multi-paragraph lists // // 4 spaces MT("listMultiParagraph", "[variable-2 * foo]", "", "[variable-2 * bar]", "", " [variable-2 hello]"); // 4 spaces, extra blank lines (should still be list, per Dingus) MT("listMultiParagraphExtra", "[variable-2 * foo]", "", "[variable-2 * bar]", "", "", " [variable-2 hello]"); // 4 spaces, plus 1 space (should still be list, per Dingus) MT("listMultiParagraphExtraSpace", "[variable-2 * foo]", "", "[variable-2 * bar]", "", " [variable-2 hello]", "", " [variable-2 world]"); // 1 tab MT("listTab", "[variable-2 * foo]", "", "[variable-2 * bar]", "", "\t[variable-2 hello]"); // No indent MT("listNoIndent", "[variable-2 * foo]", "", "[variable-2 * bar]", "", "hello"); MT("listCommonMarkIndentationCode", "[variable-2 * Code blocks also affect]", " [variable-3 * The next level starts where the contents start.]", " [variable-3 * Anything less than that will keep the item on the same level.]", " [variable-3 * Each list item can indent the first level further and further.]", " [variable-3 * For the most part, this makes sense while writing a list.]", " [keyword * This means two items with same indentation can be different levels.]", " [keyword * Each level has an indent requirement that can change between items.]", " [keyword * A list item that meets this will be part of the next level.]", " [variable-3 * Otherwise, it will be part of the level where it does meet this.]", " [variable-2 * World]"); // should handle nested and un-nested lists MT("listCommonMark_MixedIndents", "[variable-2 * list1]", " [variable-2 list1]", " [variable-2&header&header-1 # heading still part of list1]", " [variable-2 text after heading still part of list1]", "", " [comment indented codeblock]", " [variable-2 list1 after code block]", " [variable-3 * list2]", // amount of spaces on empty lines between lists doesn't matter " ", // extra empty lines irrelevant "", "", " [variable-3 indented text part of list2]", " [keyword * list3]", "", " [variable-3 text at level of list2]", "", " [variable-2 de-indented text part of list1 again]", "", " [variable-2&comment ```]", " [comment code]", " [variable-2&comment ```]", "", " [variable-2 text after fenced code]"); // should correctly parse numbered list content indentation MT("listCommonMark_NumberedListIndent", "[variable-2 1000. list with base indent of 6]", "", " [variable-2 text must be indented 6 spaces at minimum]", "", " [variable-2 9-spaces indented text still part of list]", "", " [comment indented codeblock starts at 10 spaces]", "", " [comment text indented by 5 spaces no longer belong to list]"); // should consider tab as 4 spaces MT("listCommonMark_TabIndented", "[variable-2 * list]", "\t[variable-3 * list2]", "", "\t\t[variable-3 part of list2]"); MT("listAfterBlockquote", "[quote"e-1 > foo]", "[variable-2 - bar]"); // shouldn't create sublist if it's indented more than allowed MT("nestedListIndentedTooMuch", "[variable-2 - foo]", " [variable-2 - bar]"); MT("listIndentedTooMuchAfterParagraph", "foo", " - bar"); // Blockquote MT("blockquote", "[variable-2 * foo]", "", "[variable-2 * bar]", "", " [variable-2"e"e-1 > hello]"); // Code block MT("blockquoteCode", "[variable-2 * foo]", "", "[variable-2 * bar]", "", " [comment > hello]", "", " [variable-2 world]"); // Code block followed by text MT("blockquoteCodeText", "[variable-2 * foo]", "", " [variable-2 bar]", "", " [comment hello]", "", " [variable-2 world]"); // Nested list MT("listAsteriskNested", "[variable-2 * foo]", "", " [variable-3 * bar]"); MT("listPlusNested", "[variable-2 + foo]", "", " [variable-3 + bar]"); MT("listDashNested", "[variable-2 - foo]", "", " [variable-3 - bar]"); MT("listNumberNested", "[variable-2 1. foo]", "", " [variable-3 2. bar]"); MT("listMixed", "[variable-2 * foo]", "", " [variable-3 + bar]", "", " [keyword - hello]", "", " [variable-2 1. world]"); MT("listBlockquote", "[variable-2 * foo]", "", " [variable-3 + bar]", "", " [quote"e-1&variable-3 > hello]"); MT("listCode", "[variable-2 * foo]", "", " [variable-3 + bar]", "", " [comment hello]"); // Code with internal indentation MT("listCodeIndentation", "[variable-2 * foo]", "", " [comment bar]", " [comment hello]", " [comment world]", " [comment foo]", " [variable-2 bar]"); // List nesting edge cases MT("listNested", "[variable-2 * foo]", "", " [variable-3 * bar]", "", " [variable-3 hello]" ); MT("listNested", "[variable-2 * foo]", "", " [variable-3 * bar]", "", " [keyword * foo]" ); // Code followed by text MT("listCodeText", "[variable-2 * foo]", "", " [comment bar]", "", "hello"); // Following tests directly from official Markdown documentation // http://daringfireball.net/projects/markdown/syntax#hr MT("hrSpace", "[hr * * *]"); MT("hr", "[hr ***]"); MT("hrLong", "[hr *****]"); MT("hrSpaceDash", "[hr - - -]"); MT("hrDashLong", "[hr ---------------------------------------]"); //Images MT("Images", "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)]") //Images with highlight alt text MT("imageEm", "[image&image-marker !][image&image-alt-text&link [[][image-alt-text&em&image&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); MT("imageStrong", "[image&image-marker !][image&image-alt-text&link [[][image-alt-text&strong&image&link **alt text**][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); MT("imageEmStrong", "[image&image-marker !][image&image-alt-text&link [[][image&image-alt-text&em&strong&link ***alt text***][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); // Inline link with title MT("linkTitle", "[link [[foo]]][string&url (http://example.com/ \"bar\")] hello"); // Inline link without title MT("linkNoTitle", "[link [[foo]]][string&url (http://example.com/)] bar"); // Inline link with image MT("linkImage", "[link [[][link&image&image-marker !][link&image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)][link ]]][string&url (http://example.com/)] bar"); // Inline link with Em MT("linkEm", "[link [[][link&em *foo*][link ]]][string&url (http://example.com/)] bar"); // Inline link with Strong MT("linkStrong", "[link [[][link&strong **foo**][link ]]][string&url (http://example.com/)] bar"); // Inline link with EmStrong MT("linkEmStrong", "[link [[][link&em&strong ***foo***][link ]]][string&url (http://example.com/)] bar"); MT("multilineLink", "[link [[foo]", "[link bar]]][string&url (https://foo#_a)]", "should not be italics") // Image with title MT("imageTitle", "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/ \"bar\")] hello"); // Image without title MT("imageNoTitle", "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/)] bar"); // Image with asterisks MT("imageAsterisks", "[image&image-marker !][image&image-alt-text&link [[ ][image&image-alt-text&em&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)] bar"); // Not a link. Should be normal text due to square brackets being used // regularly in text, especially in quoted material, and no space is allowed // between square brackets and parentheses (per Dingus). MT("notALink", "[link [[foo]]] (bar)"); // Reference-style links MT("linkReference", "[link [[foo]]][string&url [[bar]]] hello"); // Reference-style links with Em MT("linkReferenceEm", "[link [[][link&em *foo*][link ]]][string&url [[bar]]] hello"); // Reference-style links with Strong MT("linkReferenceStrong", "[link [[][link&strong **foo**][link ]]][string&url [[bar]]] hello"); // Reference-style links with EmStrong MT("linkReferenceEmStrong", "[link [[][link&em&strong ***foo***][link ]]][string&url [[bar]]] hello"); // Reference-style links with optional space separator (per documentation) // "You can optionally use a space to separate the sets of brackets" MT("linkReferenceSpace", "[link [[foo]]] [string&url [[bar]]] hello"); // Should only allow a single space ("...use *a* space...") MT("linkReferenceDoubleSpace", "[link [[foo]]] [link [[bar]]] hello"); // Reference-style links with implicit link name MT("linkImplicit", "[link [[foo]]][string&url [[]]] hello"); // @todo It would be nice if, at some point, the document was actually // checked to see if the referenced link exists // Link label, for reference-style links (taken from documentation) MT("labelNoTitle", "[link [[foo]]:] [string&url http://example.com/]"); MT("labelIndented", " [link [[foo]]:] [string&url http://example.com/]"); MT("labelSpaceTitle", "[link [[foo bar]]:] [string&url http://example.com/ \"hello\"]"); MT("labelDoubleTitle", "[link [[foo bar]]:] [string&url http://example.com/ \"hello\"] \"world\""); MT("labelTitleDoubleQuotes", "[link [[foo]]:] [string&url http://example.com/ \"bar\"]"); MT("labelTitleSingleQuotes", "[link [[foo]]:] [string&url http://example.com/ 'bar']"); MT("labelTitleParentheses", "[link [[foo]]:] [string&url http://example.com/ (bar)]"); MT("labelTitleInvalid", "[link [[foo]]:] [string&url http://example.com/] bar"); MT("labelLinkAngleBrackets", "[link [[foo]]:] [string&url \"bar\"]"); MT("labelTitleNextDoubleQuotes", "[link [[foo]]:] [string&url http://example.com/]", "[string \"bar\"] hello"); MT("labelTitleNextSingleQuotes", "[link [[foo]]:] [string&url http://example.com/]", "[string 'bar'] hello"); MT("labelTitleNextParentheses", "[link [[foo]]:] [string&url http://example.com/]", "[string (bar)] hello"); MT("labelTitleNextMixed", "[link [[foo]]:] [string&url http://example.com/]", "(bar\" hello"); MT("labelEscape", "[link [[foo \\]] ]]:] [string&url http://example.com/]"); MT("labelEscapeColon", "[link [[foo \\]]: bar]]:] [string&url http://example.com/]"); MT("labelEscapeEnd", "\\[[foo\\]]: http://example.com/"); MT("linkWeb", "[link ] foo"); MT("linkWebDouble", "[link ] foo [link ]"); MT("linkEmail", "[link ] foo"); MT("linkEmailDouble", "[link ] foo [link ]"); MT("emAsterisk", "[em *foo*] bar"); MT("emUnderscore", "[em _foo_] bar"); MT("emInWordAsterisk", "foo[em *bar*]hello"); MT("emInWordUnderscore", "foo_bar_hello"); // Per documentation: "...surround an * or _ with spaces, it’ll be // treated as a literal asterisk or underscore." MT("emEscapedBySpaceIn", "foo [em _bar _ hello_] world"); MT("emEscapedBySpaceOut", "foo _ bar [em _hello_] world"); MT("emEscapedByNewline", "foo", "_ bar [em _hello_] world"); // Unclosed emphasis characters // Instead of simply marking as EM / STRONG, it would be nice to have an // incomplete flag for EM and STRONG, that is styled slightly different. MT("emIncompleteAsterisk", "foo [em *bar]"); MT("emIncompleteUnderscore", "foo [em _bar]"); MT("strongAsterisk", "[strong **foo**] bar"); MT("strongUnderscore", "[strong __foo__] bar"); MT("emStrongAsterisk", "[em *foo][em&strong **bar*][strong hello**] world"); MT("emStrongUnderscore", "[em _foo ][em&strong __bar_][strong hello__] world"); // "...same character must be used to open and close an emphasis span."" MT("emStrongMixed", "[em _foo][em&strong **bar*hello__ world]"); MT("emStrongMixed", "[em *foo ][em&strong __bar_hello** world]"); MT("linkWithNestedParens", "[link [[foo]]][string&url (bar(baz))]") // These characters should be escaped: // \ backslash // ` backtick // * asterisk // _ underscore // {} curly braces // [] square brackets // () parentheses // # hash mark // + plus sign // - minus sign (hyphen) // . dot // ! exclamation mark MT("escapeBacktick", "foo \\`bar\\`"); MT("doubleEscapeBacktick", "foo \\\\[comment `bar\\\\`]"); MT("escapeAsterisk", "foo \\*bar\\*"); MT("doubleEscapeAsterisk", "foo \\\\[em *bar\\\\*]"); MT("escapeUnderscore", "foo \\_bar\\_"); MT("doubleEscapeUnderscore", "foo \\\\[em _bar\\\\_]"); MT("escapeHash", "\\# foo"); MT("doubleEscapeHash", "\\\\# foo"); MT("escapeNewline", "\\", "[em *foo*]"); // Class override tests TokenTypeOverrideTest("overrideHeader1", "[override-header&override-header-1 # Foo]"); TokenTypeOverrideTest("overrideHeader2", "[override-header&override-header-2 ## Foo]"); TokenTypeOverrideTest("overrideHeader3", "[override-header&override-header-3 ### Foo]"); TokenTypeOverrideTest("overrideHeader4", "[override-header&override-header-4 #### Foo]"); TokenTypeOverrideTest("overrideHeader5", "[override-header&override-header-5 ##### Foo]"); TokenTypeOverrideTest("overrideHeader6", "[override-header&override-header-6 ###### Foo]"); TokenTypeOverrideTest("overrideCode", "[override-code `foo`]"); TokenTypeOverrideTest("overrideCodeBlock", "[override-code ```]", "[override-code foo]", "[override-code ```]"); TokenTypeOverrideTest("overrideQuote", "[override-quote&override-quote-1 > foo]", "[override-quote&override-quote-1 > bar]"); TokenTypeOverrideTest("overrideQuoteNested", "[override-quote&override-quote-1 > foo]", "[override-quote&override-quote-1 >][override-quote&override-quote-2 > bar]", "[override-quote&override-quote-1 >][override-quote&override-quote-2 >][override-quote&override-quote-3 > baz]"); TokenTypeOverrideTest("overrideLists", "[override-list1 - foo]", "", " [override-list2 + bar]", "", " [override-list3 * baz]", "", " [override-list1 1. qux]", "", " [override-list2 - quux]"); TokenTypeOverrideTest("overrideHr", "[override-hr * * *]"); TokenTypeOverrideTest("overrideImage", "[override-image&override-image-marker !][override-image&override-image-alt-text&link [[alt text]]][override-link-href&url (http://link.to/image.jpg)]"); TokenTypeOverrideTest("overrideLinkText", "[override-link-text [[foo]]][override-link-href&url (http://example.com)]"); TokenTypeOverrideTest("overrideLinkEmailAndInline", "[override-link-email <][override-link-inline foo@example.com>]"); TokenTypeOverrideTest("overrideEm", "[override-em *foo*]"); TokenTypeOverrideTest("overrideStrong", "[override-strong **foo**]"); TokenTypeOverrideTest("overrideStrikethrough", "[override-strikethrough ~~foo~~]"); TokenTypeOverrideTest("overrideEmoji", "[override-emoji :foo:]"); FormatTokenTypeOverrideTest("overrideFormatting", "[override-formatting-escape \\*]"); // Tests to make sure GFM-specific things aren't getting through MT("taskList", "[variable-2 * ][link&variable-2 [[ ]]][variable-2 bar]"); MT("fencedCodeBlocks", "[comment ```]", "[comment foo]", "", "[comment bar]", "[comment ```]", "baz"); MT("fencedCodeBlocks_invalidClosingFence_trailingText", "[comment ```]", "[comment foo]", "[comment ``` must not have trailing text]", "[comment baz]"); MT("fencedCodeBlocks_invalidClosingFence_trailingTabs", "[comment ```]", "[comment foo]", "[comment ```\t]", "[comment baz]"); MT("fencedCodeBlocks_validClosingFence", "[comment ```]", "[comment foo]", // may have trailing spaces "[comment ``` ]", "baz"); MT("fencedCodeBlocksInList_closingFenceIndented", "[variable-2 - list]", " [variable-2&comment ```]", " [comment foo]", " [variable-2&comment ```]", " [variable-2 baz]"); MT("fencedCodeBlocksInList_closingFenceIndentedTooMuch", "[variable-2 - list]", " [variable-2&comment ```]", " [comment foo]", " [comment ```]", " [comment baz]"); MT("fencedCodeBlockModeSwitching", "[comment ```javascript]", "[variable foo]", "", "[comment ```]", "bar"); MT_noFencedHighlight("fencedCodeBlock_noHighlight", "[comment ```javascript]", "[comment foo]", "[comment ```]"); MT("fencedCodeBlockModeSwitchingObjc", "[comment ```objective-c]", "[keyword @property] [variable NSString] [operator *] [variable foo];", "[comment ```]", "bar"); MT("fencedCodeBlocksMultipleChars", "[comment `````]", "[comment foo]", "[comment ```]", "[comment foo]", "[comment `````]", "bar"); MT("fencedCodeBlocksTildes", "[comment ~~~]", "[comment foo]", "[comment ~~~]", "bar"); MT("fencedCodeBlocksTildesMultipleChars", "[comment ~~~~~]", "[comment ~~~]", "[comment foo]", "[comment ~~~~~]", "bar"); MT("fencedCodeBlocksMultipleChars", "[comment `````]", "[comment foo]", "[comment ```]", "[comment foo]", "[comment `````]", "bar"); MT("fencedCodeBlocksMixed", "[comment ~~~]", "[comment ```]", "[comment foo]", "[comment ~~~]", "bar"); MT("fencedCodeBlocksAfterBlockquote", "[quote"e-1 > foo]", "[comment ```]", "[comment bar]", "[comment ```]"); // fencedCode indented too much should act as simple indentedCode // (hence has no highlight formatting) FT("tooMuchIndentedFencedCode", " [comment ```]", " [comment code]", " [comment ```]"); MT("autoTerminateFencedCodeWhenLeavingList", "[variable-2 - list1]", " [variable-3 - list2]", " [variable-3&comment ```]", " [comment code]", " [variable-3 - list2]", " [variable-2&comment ```]", " [comment code]", "[quote"e-1 > foo]"); // Tests that require XML mode MT("xmlMode", "[tag&bracket <][tag div][tag&bracket >]", " *foo*", " [tag&bracket <][tag http://github.com][tag&bracket />]", "[tag&bracket ]", "[link ]"); MT("xmlModeWithMarkdownInside", "[tag&bracket <][tag div] [attribute markdown]=[string 1][tag&bracket >]", "[em *foo*]", "[link ]", "[tag ]", "[link ]", "[tag&bracket <][tag div][tag&bracket >]", "[tag&bracket ]"); MT("xmlModeLineBreakInTags", "[tag&bracket <][tag div] [attribute id]=[string \"1\"]", " [attribute class]=[string \"sth\"][tag&bracket >]xxx", "[tag&bracket ]"); MT("xmlModeCommentWithBlankLine", "[comment ]"); MT("xmlModeCDATA", "[atom ]"); MT("xmlModePreprocessor", "[meta ]"); MT_noXml("xmlHighlightDisabled", "
    foo
    "); // Tests Emojis ET("emojiDefault", "[builtin :foobar:]"); ET("emojiTable", " :--:"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mathematica/index.html ================================================ CodeMirror: Mathematica mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Mathematica

    Mathematica mode

    MIME types defined: text/x-mathematica (Mathematica).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mathematica/mathematica.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Mathematica mode copyright (c) 2015 by Calin Barbat // Based on code by Patrick Scheibe (halirutan) // See: https://github.com/halirutan/Mathematica-Source-Highlighting/tree/master/src/lang-mma.js (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('mathematica', function(_config, _parserConfig) { // used pattern building blocks var Identifier = '[a-zA-Z\\$][a-zA-Z0-9\\$]*'; var pBase = "(?:\\d+)"; var pFloat = "(?:\\.\\d+|\\d+\\.\\d*|\\d+)"; var pFloatBase = "(?:\\.\\w+|\\w+\\.\\w*|\\w+)"; var pPrecision = "(?:`(?:`?"+pFloat+")?)"; // regular expressions var reBaseForm = new RegExp('(?:'+pBase+'(?:\\^\\^'+pFloatBase+pPrecision+'?(?:\\*\\^[+-]?\\d+)?))'); var reFloatForm = new RegExp('(?:' + pFloat + pPrecision + '?(?:\\*\\^[+-]?\\d+)?)'); var reIdInContext = new RegExp('(?:`?)(?:' + Identifier + ')(?:`(?:' + Identifier + '))*(?:`?)'); function tokenBase(stream, state) { var ch; // get next character ch = stream.next(); // string if (ch === '"') { state.tokenize = tokenString; return state.tokenize(stream, state); } // comment if (ch === '(') { if (stream.eat('*')) { state.commentLevel++; state.tokenize = tokenComment; return state.tokenize(stream, state); } } // go back one character stream.backUp(1); // look for numbers // Numbers in a baseform if (stream.match(reBaseForm, true, false)) { return 'number'; } // Mathematica numbers. Floats (1.2, .2, 1.) can have optionally a precision (`float) or an accuracy definition // (``float). Note: while 1.2` is possible 1.2`` is not. At the end an exponent (float*^+12) can follow. if (stream.match(reFloatForm, true, false)) { return 'number'; } /* In[23] and Out[34] */ if (stream.match(/(?:In|Out)\[[0-9]*\]/, true, false)) { return 'atom'; } // usage if (stream.match(/([a-zA-Z\$][a-zA-Z0-9\$]*(?:`[a-zA-Z0-9\$]+)*::usage)/, true, false)) { return 'meta'; } // message if (stream.match(/([a-zA-Z\$][a-zA-Z0-9\$]*(?:`[a-zA-Z0-9\$]+)*::[a-zA-Z\$][a-zA-Z0-9\$]*):?/, true, false)) { return 'string-2'; } // this makes a look-ahead match for something like variable:{_Integer} // the match is then forwarded to the mma-patterns tokenizer. if (stream.match(/([a-zA-Z\$][a-zA-Z0-9\$]*\s*:)(?:(?:[a-zA-Z\$][a-zA-Z0-9\$]*)|(?:[^:=>~@\^\&\*\)\[\]'\?,\|])).*/, true, false)) { return 'variable-2'; } // catch variables which are used together with Blank (_), BlankSequence (__) or BlankNullSequence (___) // Cannot start with a number, but can have numbers at any other position. Examples // blub__Integer, a1_, b34_Integer32 if (stream.match(/[a-zA-Z\$][a-zA-Z0-9\$]*_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false)) { return 'variable-2'; } if (stream.match(/[a-zA-Z\$][a-zA-Z0-9\$]*_+/, true, false)) { return 'variable-2'; } if (stream.match(/_+[a-zA-Z\$][a-zA-Z0-9\$]*/, true, false)) { return 'variable-2'; } // Named characters in Mathematica, like \[Gamma]. if (stream.match(/\\\[[a-zA-Z\$][a-zA-Z0-9\$]*\]/, true, false)) { return 'variable-3'; } // Match all braces separately if (stream.match(/(?:\[|\]|{|}|\(|\))/, true, false)) { return 'bracket'; } // Catch Slots (#, ##, #3, ##9 and the V10 named slots #name). I have never seen someone using more than one digit after #, so we match // only one. if (stream.match(/(?:#[a-zA-Z\$][a-zA-Z0-9\$]*|#+[0-9]?)/, true, false)) { return 'variable-2'; } // Literals like variables, keywords, functions if (stream.match(reIdInContext, true, false)) { return 'keyword'; } // operators. Note that operators like @@ or /; are matched separately for each symbol. if (stream.match(/(?:\\|\+|\-|\*|\/|,|;|\.|:|@|~|=|>|<|&|\||_|`|'|\^|\?|!|%)/, true, false)) { return 'operator'; } // everything else is an error stream.next(); // advance the stream. return 'error'; } function tokenString(stream, state) { var next, end = false, escaped = false; while ((next = stream.next()) != null) { if (next === '"' && !escaped) { end = true; break; } escaped = !escaped && next === '\\'; } if (end && !escaped) { state.tokenize = tokenBase; } return 'string'; }; function tokenComment(stream, state) { var prev, next; while(state.commentLevel > 0 && (next = stream.next()) != null) { if (prev === '(' && next === '*') state.commentLevel++; if (prev === '*' && next === ')') state.commentLevel--; prev = next; } if (state.commentLevel <= 0) { state.tokenize = tokenBase; } return 'comment'; } return { startState: function() {return {tokenize: tokenBase, commentLevel: 0};}, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, blockCommentStart: "(*", blockCommentEnd: "*)" }; }); CodeMirror.defineMIME('text/x-mathematica', { name: 'mathematica' }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mbox/index.html ================================================ CodeMirror: mbox mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • mbox

    mbox mode

    MIME types defined: application/mbox.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mbox/mbox.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var rfc2822 = [ "From", "Sender", "Reply-To", "To", "Cc", "Bcc", "Message-ID", "In-Reply-To", "References", "Resent-From", "Resent-Sender", "Resent-To", "Resent-Cc", "Resent-Bcc", "Resent-Message-ID", "Return-Path", "Received" ]; var rfc2822NoEmail = [ "Date", "Subject", "Comments", "Keywords", "Resent-Date" ]; CodeMirror.registerHelper("hintWords", "mbox", rfc2822.concat(rfc2822NoEmail)); var whitespace = /^[ \t]/; var separator = /^From /; // See RFC 4155 var rfc2822Header = new RegExp("^(" + rfc2822.join("|") + "): "); var rfc2822HeaderNoEmail = new RegExp("^(" + rfc2822NoEmail.join("|") + "): "); var header = /^[^:]+:/; // Optional fields defined in RFC 2822 var email = /^[^ ]+@[^ ]+/; var untilEmail = /^.*?(?=[^ ]+?@[^ ]+)/; var bracketedEmail = /^<.*?>/; var untilBracketedEmail = /^.*?(?=<.*>)/; function styleForHeader(header) { if (header === "Subject") return "header"; return "string"; } function readToken(stream, state) { if (stream.sol()) { // From last line state.inSeparator = false; if (state.inHeader && stream.match(whitespace)) { // Header folding return null; } else { state.inHeader = false; state.header = null; } if (stream.match(separator)) { state.inHeaders = true; state.inSeparator = true; return "atom"; } var match; var emailPermitted = false; if ((match = stream.match(rfc2822HeaderNoEmail)) || (emailPermitted = true) && (match = stream.match(rfc2822Header))) { state.inHeaders = true; state.inHeader = true; state.emailPermitted = emailPermitted; state.header = match[1]; return "atom"; } // Use vim's heuristics: recognize custom headers only if the line is in a // block of legitimate headers. if (state.inHeaders && (match = stream.match(header))) { state.inHeader = true; state.emailPermitted = true; state.header = match[1]; return "atom"; } state.inHeaders = false; stream.skipToEnd(); return null; } if (state.inSeparator) { if (stream.match(email)) return "link"; if (stream.match(untilEmail)) return "atom"; stream.skipToEnd(); return "atom"; } if (state.inHeader) { var style = styleForHeader(state.header); if (state.emailPermitted) { if (stream.match(bracketedEmail)) return style + " link"; if (stream.match(untilBracketedEmail)) return style; } stream.skipToEnd(); return style; } stream.skipToEnd(); return null; }; CodeMirror.defineMode("mbox", function() { return { startState: function() { return { // Is in a mbox separator inSeparator: false, // Is in a mail header inHeader: false, // If bracketed email is permitted. Only applicable when inHeader emailPermitted: false, // Name of current header header: null, // Is in a region of mail headers inHeaders: false }; }, token: readToken, blankLine: function(state) { state.inHeaders = state.inSeparator = state.inHeader = false; } }; }); CodeMirror.defineMIME("application/mbox", "mbox"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/meta.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.modeInfo = [ {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]}, {name: "PGP", mimes: ["application/pgp", "application/pgp-encrypted", "application/pgp-keys", "application/pgp-signature"], mode: "asciiarmor", ext: ["asc", "pgp", "sig"]}, {name: "ASN.1", mime: "text/x-ttcn-asn", mode: "asn.1", ext: ["asn", "asn1"]}, {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i}, {name: "Brainfuck", mime: "text/x-brainfuck", mode: "brainfuck", ext: ["b", "bf"]}, {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h", "ino"]}, {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]}, {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy", "cbl"]}, {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp", "cs"]}, {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj", "cljc", "cljx"]}, {name: "ClojureScript", mime: "text/x-clojurescript", mode: "clojure", ext: ["cljs"]}, {name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]}, {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists\.txt$/}, {name: "CoffeeScript", mimes: ["application/vnd.coffeescript", "text/coffeescript", "text/x-coffeescript"], mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]}, {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]}, {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]}, {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]}, {name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]}, {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]}, {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]}, {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]}, {name: "Dart", mimes: ["application/dart", "text/x-dart"], mode: "dart", ext: ["dart"]}, {name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]}, {name: "Django", mime: "text/x-django", mode: "django"}, {name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile", file: /^Dockerfile$/}, {name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]}, {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]}, {name: "EBNF", mime: "text/x-ebnf", mode: "ebnf"}, {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]}, {name: "edn", mime: "application/edn", mode: "clojure", ext: ["edn"]}, {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]}, {name: "Elm", mime: "text/x-elm", mode: "elm", ext: ["elm"]}, {name: "Embedded JavaScript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, {name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]}, {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, {name: "Esper", mime: "text/x-esper", mode: "sql"}, {name: "Factor", mime: "text/x-factor", mode: "factor", ext: ["factor"]}, {name: "FCL", mime: "text/x-fcl", mode: "fcl"}, {name: "Forth", mime: "text/x-forth", mode: "forth", ext: ["forth", "fth", "4th"]}, {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90", "f95"]}, {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]}, {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]}, {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history)\.md$/i}, {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"], file: /^Jenkinsfile$/}, {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]}, {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]}, {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]}, {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"], alias: ["asp", "aspx"]}, {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm", "handlebars", "hbs"], alias: ["xhtml"]}, {name: "HTTP", mime: "message/http", mode: "http"}, {name: "IDL", mime: "text/x-idl", mode: "idl", ext: ["pro"]}, {name: "Pug", mime: "text/x-pug", mode: "pug", ext: ["jade", "pug"], alias: ["jade"]}, {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]}, {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"], alias: ["jsp"]}, {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"], mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]}, {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]}, {name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]}, {name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]}, {name: "Jinja2", mime: "text/jinja2", mode: "jinja2", ext: ["j2", "jinja", "jinja2"]}, {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"], alias: ["jl"]}, {name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]}, {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]}, {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]}, {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]}, {name: "Markdown", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]}, {name: "mIRC", mime: "text/mirc", mode: "mirc"}, {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, {name: "Mathematica", mime: "text/x-mathematica", mode: "mathematica", ext: ["m", "nb", "wl", "wls"]}, {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, {name: "MUMPS", mime: "text/x-mumps", mode: "mumps", ext: ["mps"]}, {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, {name: "mbox", mime: "application/mbox", mode: "mbox", ext: ["mbox"]}, {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i}, {name: "NSIS", mime: "text/x-nsis", mode: "nsis", ext: ["nsh", "nsi"]}, {name: "NTriples", mimes: ["application/n-triples", "application/n-quads", "text/n-triples"], mode: "ntriples", ext: ["nt", "nq"]}, {name: "Objective-C", mime: "text/x-objectivec", mode: "clike", ext: ["m"], alias: ["objective-c", "objc"]}, {name: "Objective-C++", mime: "text/x-objectivec++", mode: "clike", ext: ["mm"], alias: ["objective-c++", "objc++"]}, {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]}, {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]}, {name: "Oz", mime: "text/x-oz", mode: "oz", ext: ["oz"]}, {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]}, {name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]}, {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]}, {name: "PHP", mimes: ["text/x-php", "application/x-httpd-php", "application/x-httpd-php-open"], mode: "php", ext: ["php", "php3", "php4", "php5", "php7", "phtml"]}, {name: "Pig", mime: "text/x-pig", mode: "pig", ext: ["pig"]}, {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]}, {name: "PLSQL", mime: "text/x-plsql", mode: "sql", ext: ["pls"]}, {name: "PostgreSQL", mime: "text/x-pgsql", mode: "sql"}, {name: "PowerShell", mime: "application/x-powershell", mode: "powershell", ext: ["ps1", "psd1", "psm1"]}, {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"], alias: ["ini", "properties"]}, {name: "ProtoBuf", mime: "text/x-protobuf", mode: "protobuf", ext: ["proto"]}, {name: "Python", mime: "text/x-python", mode: "python", ext: ["BUILD", "bzl", "py", "pyw"], file: /^(BUCK|BUILD)$/}, {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]}, {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]}, {name: "R", mime: "text/x-rsrc", mode: "r", ext: ["r", "R"], alias: ["rscript"]}, {name: "reStructuredText", mime: "text/x-rst", mode: "rst", ext: ["rst"], alias: ["rst"]}, {name: "RPM Changes", mime: "text/x-rpm-changes", mode: "rpm"}, {name: "RPM Spec", mime: "text/x-rpm-spec", mode: "rpm", ext: ["spec"]}, {name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: ["rb"], alias: ["jruby", "macruby", "rake", "rb", "rbx"]}, {name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: ["rs"]}, {name: "SAS", mime: "text/x-sas", mode: "sas", ext: ["sas"]}, {name: "Sass", mime: "text/x-sass", mode: "sass", ext: ["sass"]}, {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]}, {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]}, {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]}, {name: "Shell", mimes: ["text/x-sh", "application/x-sh"], mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"], file: /^PKGBUILD$/}, {name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]}, {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]}, {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]}, {name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]}, {name: "Solr", mime: "text/x-solr", mode: "solr"}, {name: "SML", mime: "text/x-sml", mode: "mllike", ext: ["sml", "sig", "fun", "smackspec"]}, {name: "Soy", mime: "text/x-soy", mode: "soy", ext: ["soy"], alias: ["closure template"]}, {name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]}, {name: "Spreadsheet", mime: "text/x-spreadsheet", mode: "spreadsheet", alias: ["excel", "formula"]}, {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]}, {name: "SQLite", mime: "text/x-sqlite", mode: "sql"}, {name: "Squirrel", mime: "text/x-squirrel", mode: "clike", ext: ["nut"]}, {name: "Stylus", mime: "text/x-styl", mode: "stylus", ext: ["styl"]}, {name: "Swift", mime: "text/x-swift", mode: "swift", ext: ["swift"]}, {name: "sTeX", mime: "text/x-stex", mode: "stex"}, {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx", "tex"], alias: ["tex"]}, {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v", "sv", "svh"]}, {name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]}, {name: "Textile", mime: "text/x-textile", mode: "textile", ext: ["textile"]}, {name: "TiddlyWiki", mime: "text/x-tiddlywiki", mode: "tiddlywiki"}, {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, {name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]}, {name: "Tornado", mime: "text/x-tornado", mode: "tornado"}, {name: "troff", mime: "text/troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]}, {name: "TTCN", mime: "text/x-ttcn", mode: "ttcn", ext: ["ttcn", "ttcn3", "ttcnpp"]}, {name: "TTCN_CFG", mime: "text/x-ttcn-cfg", mode: "ttcn-cfg", ext: ["cfg"]}, {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]}, {name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"], alias: ["ts"]}, {name: "TypeScript-JSX", mime: "text/typescript-jsx", mode: "jsx", ext: ["tsx"], alias: ["tsx"]}, {name: "Twig", mime: "text/x-twig", mode: "twig"}, {name: "Web IDL", mime: "text/x-webidl", mode: "webidl", ext: ["webidl"]}, {name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]}, {name: "VBScript", mime: "text/vbscript", mode: "vbscript", ext: ["vbs"]}, {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]}, {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]}, {name: "VHDL", mime: "text/x-vhdl", mode: "vhdl", ext: ["vhd", "vhdl"]}, {name: "Vue.js Component", mimes: ["script/x-vue", "text/x-vue"], mode: "vue", ext: ["vue"]}, {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd", "svg"], alias: ["rss", "wsdl", "xsd"]}, {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]}, {name: "Yacas", mime: "text/x-yacas", mode: "yacas", ext: ["ys"]}, {name: "YAML", mimes: ["text/x-yaml", "text/yaml"], mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]}, {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}, {name: "mscgen", mime: "text/x-mscgen", mode: "mscgen", ext: ["mscgen", "mscin", "msc"]}, {name: "xu", mime: "text/x-xu", mode: "mscgen", ext: ["xu"]}, {name: "msgenny", mime: "text/x-msgenny", mode: "mscgen", ext: ["msgenny"]}, {name: "WebAssembly", mime: "text/webassembly", mode: "wast", ext: ["wat", "wast"]}, ]; // Ensure all modes have a mime property for backwards compatibility for (var i = 0; i < CodeMirror.modeInfo.length; i++) { var info = CodeMirror.modeInfo[i]; if (info.mimes) info.mime = info.mimes[0]; } CodeMirror.findModeByMIME = function(mime) { mime = mime.toLowerCase(); for (var i = 0; i < CodeMirror.modeInfo.length; i++) { var info = CodeMirror.modeInfo[i]; if (info.mime == mime) return info; if (info.mimes) for (var j = 0; j < info.mimes.length; j++) if (info.mimes[j] == mime) return info; } if (/\+xml$/.test(mime)) return CodeMirror.findModeByMIME("application/xml") if (/\+json$/.test(mime)) return CodeMirror.findModeByMIME("application/json") }; CodeMirror.findModeByExtension = function(ext) { ext = ext.toLowerCase(); for (var i = 0; i < CodeMirror.modeInfo.length; i++) { var info = CodeMirror.modeInfo[i]; if (info.ext) for (var j = 0; j < info.ext.length; j++) if (info.ext[j] == ext) return info; } }; CodeMirror.findModeByFileName = function(filename) { for (var i = 0; i < CodeMirror.modeInfo.length; i++) { var info = CodeMirror.modeInfo[i]; if (info.file && info.file.test(filename)) return info; } var dot = filename.lastIndexOf("."); var ext = dot > -1 && filename.substring(dot + 1, filename.length); if (ext) return CodeMirror.findModeByExtension(ext); }; CodeMirror.findModeByName = function(name) { name = name.toLowerCase(); for (var i = 0; i < CodeMirror.modeInfo.length; i++) { var info = CodeMirror.modeInfo[i]; if (info.name.toLowerCase() == name) return info; if (info.alias) for (var j = 0; j < info.alias.length; j++) if (info.alias[j].toLowerCase() == name) return info; } }; }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mirc/index.html ================================================ CodeMirror: mIRC mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • mIRC

    mIRC mode

    MIME types defined: text/mirc.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mirc/mirc.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE //mIRC mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMIME("text/mirc", "mirc"); CodeMirror.defineMode("mirc", function() { function parseWords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var specials = parseWords("$! $$ $& $? $+ $abook $abs $active $activecid " + "$activewid $address $addtok $agent $agentname $agentstat $agentver " + "$alias $and $anick $ansi2mirc $aop $appactive $appstate $asc $asctime " + "$asin $atan $avoice $away $awaymsg $awaytime $banmask $base $bfind " + "$binoff $biton $bnick $bvar $bytes $calc $cb $cd $ceil $chan $chanmodes " + "$chantypes $chat $chr $cid $clevel $click $cmdbox $cmdline $cnick $color " + "$com $comcall $comchan $comerr $compact $compress $comval $cos $count " + "$cr $crc $creq $crlf $ctime $ctimer $ctrlenter $date $day $daylight " + "$dbuh $dbuw $dccignore $dccport $dde $ddename $debug $decode $decompress " + "$deltok $devent $dialog $did $didreg $didtok $didwm $disk $dlevel $dll " + "$dllcall $dname $dns $duration $ebeeps $editbox $emailaddr $encode $error " + "$eval $event $exist $feof $ferr $fgetc $file $filename $filtered $finddir " + "$finddirn $findfile $findfilen $findtok $fline $floor $fopen $fread $fserve " + "$fulladdress $fulldate $fullname $fullscreen $get $getdir $getdot $gettok $gmt " + "$group $halted $hash $height $hfind $hget $highlight $hnick $hotline " + "$hotlinepos $ial $ialchan $ibl $idle $iel $ifmatch $ignore $iif $iil " + "$inelipse $ini $inmidi $inpaste $inpoly $input $inrect $inroundrect " + "$insong $instok $int $inwave $ip $isalias $isbit $isdde $isdir $isfile " + "$isid $islower $istok $isupper $keychar $keyrpt $keyval $knick $lactive " + "$lactivecid $lactivewid $left $len $level $lf $line $lines $link $lock " + "$lock $locked $log $logstamp $logstampfmt $longfn $longip $lower $ltimer " + "$maddress $mask $matchkey $matchtok $md5 $me $menu $menubar $menucontext " + "$menutype $mid $middir $mircdir $mircexe $mircini $mklogfn $mnick $mode " + "$modefirst $modelast $modespl $mouse $msfile $network $newnick $nick $nofile " + "$nopath $noqt $not $notags $notify $null $numeric $numok $oline $onpoly " + "$opnick $or $ord $os $passivedcc $pic $play $pnick $port $portable $portfree " + "$pos $prefix $prop $protect $puttok $qt $query $rand $r $rawmsg $read $readomo " + "$readn $regex $regml $regsub $regsubex $remove $remtok $replace $replacex " + "$reptok $result $rgb $right $round $scid $scon $script $scriptdir $scriptline " + "$sdir $send $server $serverip $sfile $sha1 $shortfn $show $signal $sin " + "$site $sline $snick $snicks $snotify $sock $sockbr $sockerr $sockname " + "$sorttok $sound $sqrt $ssl $sreq $sslready $status $strip $str $stripped " + "$syle $submenu $switchbar $tan $target $ticks $time $timer $timestamp " + "$timestampfmt $timezone $tip $titlebar $toolbar $treebar $trust $ulevel " + "$ulist $upper $uptime $url $usermode $v1 $v2 $var $vcmd $vcmdstat $vcmdver " + "$version $vnick $vol $wid $width $wildsite $wildtok $window $wrap $xor"); var keywords = parseWords("abook ajinvite alias aline ame amsg anick aop auser autojoin avoice " + "away background ban bcopy beep bread break breplace bset btrunc bunset bwrite " + "channel clear clearall cline clipboard close cnick color comclose comopen " + "comreg continue copy creq ctcpreply ctcps dcc dccserver dde ddeserver " + "debug dec describe dialog did didtok disable disconnect dlevel dline dll " + "dns dqwindow drawcopy drawdot drawfill drawline drawpic drawrect drawreplace " + "drawrot drawsave drawscroll drawtext ebeeps echo editbox emailaddr enable " + "events exit fclose filter findtext finger firewall flash flist flood flush " + "flushini font fopen fseek fsend fserve fullname fwrite ghide gload gmove " + "gopts goto gplay gpoint gqreq groups gshow gsize gstop gtalk gunload hadd " + "halt haltdef hdec hdel help hfree hinc hload hmake hop hsave ial ialclear " + "ialmark identd if ignore iline inc invite iuser join kick linesep links list " + "load loadbuf localinfo log mdi me menubar mkdir mnick mode msg nick noop notice " + "notify omsg onotice part partall pdcc perform play playctrl pop protect pvoice " + "qme qmsg query queryn quit raw reload remini remote remove rename renwin " + "reseterror resetidle return rlevel rline rmdir run ruser save savebuf saveini " + "say scid scon server set showmirc signam sline sockaccept sockclose socklist " + "socklisten sockmark sockopen sockpause sockread sockrename sockudp sockwrite " + "sound speak splay sreq strip switchbar timer timestamp titlebar tnick tokenize " + "toolbar topic tray treebar ulist unload unset unsetall updatenl url uwho " + "var vcadd vcmd vcrem vol while whois window winhelp write writeint if isalnum " + "isalpha isaop isavoice isban ischan ishop isignore isin isincs isletter islower " + "isnotify isnum ison isop isprotect isreg isupper isvoice iswm iswmcs " + "elseif else goto menu nicklist status title icon size option text edit " + "button check radio box scroll list combo link tab item"); var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch"); var isOperatorChar = /[+\-*&%=<>!?^\/\|]/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenBase(stream, state) { var beforeParams = state.beforeParams; state.beforeParams = false; var ch = stream.next(); if (/[\[\]{}\(\),\.]/.test(ch)) { if (ch == "(" && beforeParams) state.inParams = true; else if (ch == ")") state.inParams = false; return null; } else if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } else if (ch == "\\") { stream.eat("\\"); stream.eat(/./); return "number"; } else if (ch == "/" && stream.eat("*")) { return chain(stream, state, tokenComment); } else if (ch == ";" && stream.match(/ *\( *\(/)) { return chain(stream, state, tokenUnparsed); } else if (ch == ";" && !state.inParams) { stream.skipToEnd(); return "comment"; } else if (ch == '"') { stream.eat(/"/); return "keyword"; } else if (ch == "$") { stream.eatWhile(/[$_a-z0-9A-Z\.:]/); if (specials && specials.propertyIsEnumerable(stream.current().toLowerCase())) { return "keyword"; } else { state.beforeParams = true; return "builtin"; } } else if (ch == "%") { stream.eatWhile(/[^,\s()]/); state.beforeParams = true; return "string"; } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } else { stream.eatWhile(/[\w\$_{}]/); var word = stream.current().toLowerCase(); if (keywords && keywords.propertyIsEnumerable(word)) return "keyword"; if (functions && functions.propertyIsEnumerable(word)) { state.beforeParams = true; return "keyword"; } return null; } } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenUnparsed(stream, state) { var maybeEnd = 0, ch; while (ch = stream.next()) { if (ch == ";" && maybeEnd == 2) { state.tokenize = tokenBase; break; } if (ch == ")") maybeEnd++; else if (ch != " ") maybeEnd = 0; } return "meta"; } return { startState: function() { return { tokenize: tokenBase, beforeParams: false, inParams: false }; }, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); } }; }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mllike/index.html ================================================ CodeMirror: ML-like mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • ML-like

    OCaml mode

    F# mode

    MIME types defined: text/x-ocaml (OCaml) and text/x-fsharp (F#).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mllike/mllike.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('mllike', function(_config, parserConfig) { var words = { 'as': 'keyword', 'do': 'keyword', 'else': 'keyword', 'end': 'keyword', 'exception': 'keyword', 'fun': 'keyword', 'functor': 'keyword', 'if': 'keyword', 'in': 'keyword', 'include': 'keyword', 'let': 'keyword', 'of': 'keyword', 'open': 'keyword', 'rec': 'keyword', 'struct': 'keyword', 'then': 'keyword', 'type': 'keyword', 'val': 'keyword', 'while': 'keyword', 'with': 'keyword' }; var extraWords = parserConfig.extraWords || {}; for (var prop in extraWords) { if (extraWords.hasOwnProperty(prop)) { words[prop] = parserConfig.extraWords[prop]; } } var hintWords = []; for (var k in words) { hintWords.push(k); } CodeMirror.registerHelper("hintWords", "mllike", hintWords); function tokenBase(stream, state) { var ch = stream.next(); if (ch === '"') { state.tokenize = tokenString; return state.tokenize(stream, state); } if (ch === '{') { if (stream.eat('|')) { state.longString = true; state.tokenize = tokenLongString; return state.tokenize(stream, state); } } if (ch === '(') { if (stream.match(/^\*(?!\))/)) { state.commentLevel++; state.tokenize = tokenComment; return state.tokenize(stream, state); } } if (ch === '~' || ch === '?') { stream.eatWhile(/\w/); return 'variable-2'; } if (ch === '`') { stream.eatWhile(/\w/); return 'quote'; } if (ch === '/' && parserConfig.slashComments && stream.eat('/')) { stream.skipToEnd(); return 'comment'; } if (/\d/.test(ch)) { if (ch === '0' && stream.eat(/[bB]/)) { stream.eatWhile(/[01]/); } if (ch === '0' && stream.eat(/[xX]/)) { stream.eatWhile(/[0-9a-fA-F]/) } if (ch === '0' && stream.eat(/[oO]/)) { stream.eatWhile(/[0-7]/); } else { stream.eatWhile(/[\d_]/); if (stream.eat('.')) { stream.eatWhile(/[\d]/); } if (stream.eat(/[eE]/)) { stream.eatWhile(/[\d\-+]/); } } return 'number'; } if ( /[+\-*&%=<>!?|@\.~:]/.test(ch)) { return 'operator'; } if (/[\w\xa1-\uffff]/.test(ch)) { stream.eatWhile(/[\w\xa1-\uffff]/); var cur = stream.current(); return words.hasOwnProperty(cur) ? words[cur] : 'variable'; } return null } function tokenString(stream, state) { var next, end = false, escaped = false; while ((next = stream.next()) != null) { if (next === '"' && !escaped) { end = true; break; } escaped = !escaped && next === '\\'; } if (end && !escaped) { state.tokenize = tokenBase; } return 'string'; }; function tokenComment(stream, state) { var prev, next; while(state.commentLevel > 0 && (next = stream.next()) != null) { if (prev === '(' && next === '*') state.commentLevel++; if (prev === '*' && next === ')') state.commentLevel--; prev = next; } if (state.commentLevel <= 0) { state.tokenize = tokenBase; } return 'comment'; } function tokenLongString(stream, state) { var prev, next; while (state.longString && (next = stream.next()) != null) { if (prev === '|' && next === '}') state.longString = false; prev = next; } if (!state.longString) { state.tokenize = tokenBase; } return 'string'; } return { startState: function() {return {tokenize: tokenBase, commentLevel: 0, longString: false};}, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, blockCommentStart: "(*", blockCommentEnd: "*)", lineComment: parserConfig.slashComments ? "//" : null }; }); CodeMirror.defineMIME('text/x-ocaml', { name: 'mllike', extraWords: { 'and': 'keyword', 'assert': 'keyword', 'begin': 'keyword', 'class': 'keyword', 'constraint': 'keyword', 'done': 'keyword', 'downto': 'keyword', 'external': 'keyword', 'function': 'keyword', 'initializer': 'keyword', 'lazy': 'keyword', 'match': 'keyword', 'method': 'keyword', 'module': 'keyword', 'mutable': 'keyword', 'new': 'keyword', 'nonrec': 'keyword', 'object': 'keyword', 'private': 'keyword', 'sig': 'keyword', 'to': 'keyword', 'try': 'keyword', 'value': 'keyword', 'virtual': 'keyword', 'when': 'keyword', // builtins 'raise': 'builtin', 'failwith': 'builtin', 'true': 'builtin', 'false': 'builtin', // Pervasives builtins 'asr': 'builtin', 'land': 'builtin', 'lor': 'builtin', 'lsl': 'builtin', 'lsr': 'builtin', 'lxor': 'builtin', 'mod': 'builtin', 'or': 'builtin', // More Pervasives 'raise_notrace': 'builtin', 'trace': 'builtin', 'exit': 'builtin', 'print_string': 'builtin', 'print_endline': 'builtin', 'int': 'type', 'float': 'type', 'bool': 'type', 'char': 'type', 'string': 'type', 'unit': 'type', // Modules 'List': 'builtin' } }); CodeMirror.defineMIME('text/x-fsharp', { name: 'mllike', extraWords: { 'abstract': 'keyword', 'assert': 'keyword', 'base': 'keyword', 'begin': 'keyword', 'class': 'keyword', 'default': 'keyword', 'delegate': 'keyword', 'do!': 'keyword', 'done': 'keyword', 'downcast': 'keyword', 'downto': 'keyword', 'elif': 'keyword', 'extern': 'keyword', 'finally': 'keyword', 'for': 'keyword', 'function': 'keyword', 'global': 'keyword', 'inherit': 'keyword', 'inline': 'keyword', 'interface': 'keyword', 'internal': 'keyword', 'lazy': 'keyword', 'let!': 'keyword', 'match': 'keyword', 'member': 'keyword', 'module': 'keyword', 'mutable': 'keyword', 'namespace': 'keyword', 'new': 'keyword', 'null': 'keyword', 'override': 'keyword', 'private': 'keyword', 'public': 'keyword', 'return!': 'keyword', 'return': 'keyword', 'select': 'keyword', 'static': 'keyword', 'to': 'keyword', 'try': 'keyword', 'upcast': 'keyword', 'use!': 'keyword', 'use': 'keyword', 'void': 'keyword', 'when': 'keyword', 'yield!': 'keyword', 'yield': 'keyword', // Reserved words 'atomic': 'keyword', 'break': 'keyword', 'checked': 'keyword', 'component': 'keyword', 'const': 'keyword', 'constraint': 'keyword', 'constructor': 'keyword', 'continue': 'keyword', 'eager': 'keyword', 'event': 'keyword', 'external': 'keyword', 'fixed': 'keyword', 'method': 'keyword', 'mixin': 'keyword', 'object': 'keyword', 'parallel': 'keyword', 'process': 'keyword', 'protected': 'keyword', 'pure': 'keyword', 'sealed': 'keyword', 'tailcall': 'keyword', 'trait': 'keyword', 'virtual': 'keyword', 'volatile': 'keyword', // builtins 'List': 'builtin', 'Seq': 'builtin', 'Map': 'builtin', 'Set': 'builtin', 'Option': 'builtin', 'int': 'builtin', 'string': 'builtin', 'not': 'builtin', 'true': 'builtin', 'false': 'builtin', 'raise': 'builtin', 'failwith': 'builtin' }, slashComments: true }); CodeMirror.defineMIME('text/x-sml', { name: 'mllike', extraWords: { 'abstype': 'keyword', 'and': 'keyword', 'andalso': 'keyword', 'case': 'keyword', 'datatype': 'keyword', 'fn': 'keyword', 'handle': 'keyword', 'infix': 'keyword', 'infixr': 'keyword', 'local': 'keyword', 'nonfix': 'keyword', 'op': 'keyword', 'orelse': 'keyword', 'raise': 'keyword', 'withtype': 'keyword', 'eqtype': 'keyword', 'sharing': 'keyword', 'sig': 'keyword', 'signature': 'keyword', 'structure': 'keyword', 'where': 'keyword', 'true': 'keyword', 'false': 'keyword', // types 'int': 'builtin', 'real': 'builtin', 'string': 'builtin', 'char': 'builtin', 'bool': 'builtin' }, slashComments: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/modelica/index.html ================================================ CodeMirror: Modelica mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Modelica

    Modelica mode

    Simple mode that tries to handle Modelica as well as it can.

    MIME types defined: text/x-modelica (Modlica code).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/modelica/modelica.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Modelica support for CodeMirror, copyright (c) by Lennart Ochel (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); }) (function(CodeMirror) { "use strict"; CodeMirror.defineMode("modelica", function(config, parserConfig) { var indentUnit = config.indentUnit; var keywords = parserConfig.keywords || {}; var builtin = parserConfig.builtin || {}; var atoms = parserConfig.atoms || {}; var isSingleOperatorChar = /[;=\(:\),{}.*<>+\-\/^\[\]]/; var isDoubleOperatorChar = /(:=|<=|>=|==|<>|\.\+|\.\-|\.\*|\.\/|\.\^)/; var isDigit = /[0-9]/; var isNonDigit = /[_a-zA-Z]/; function tokenLineComment(stream, state) { stream.skipToEnd(); state.tokenize = null; return "comment"; } function tokenBlockComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (maybeEnd && ch == "/") { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenString(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == '"' && !escaped) { state.tokenize = null; state.sol = false; break; } escaped = !escaped && ch == "\\"; } return "string"; } function tokenIdent(stream, state) { stream.eatWhile(isDigit); while (stream.eat(isDigit) || stream.eat(isNonDigit)) { } var cur = stream.current(); if(state.sol && (cur == "package" || cur == "model" || cur == "when" || cur == "connector")) state.level++; else if(state.sol && cur == "end" && state.level > 0) state.level--; state.tokenize = null; state.sol = false; if (keywords.propertyIsEnumerable(cur)) return "keyword"; else if (builtin.propertyIsEnumerable(cur)) return "builtin"; else if (atoms.propertyIsEnumerable(cur)) return "atom"; else return "variable"; } function tokenQIdent(stream, state) { while (stream.eat(/[^']/)) { } state.tokenize = null; state.sol = false; if(stream.eat("'")) return "variable"; else return "error"; } function tokenUnsignedNumber(stream, state) { stream.eatWhile(isDigit); if (stream.eat('.')) { stream.eatWhile(isDigit); } if (stream.eat('e') || stream.eat('E')) { if (!stream.eat('-')) stream.eat('+'); stream.eatWhile(isDigit); } state.tokenize = null; state.sol = false; return "number"; } // Interface return { startState: function() { return { tokenize: null, level: 0, sol: true }; }, token: function(stream, state) { if(state.tokenize != null) { return state.tokenize(stream, state); } if(stream.sol()) { state.sol = true; } // WHITESPACE if(stream.eatSpace()) { state.tokenize = null; return null; } var ch = stream.next(); // LINECOMMENT if(ch == '/' && stream.eat('/')) { state.tokenize = tokenLineComment; } // BLOCKCOMMENT else if(ch == '/' && stream.eat('*')) { state.tokenize = tokenBlockComment; } // TWO SYMBOL TOKENS else if(isDoubleOperatorChar.test(ch+stream.peek())) { stream.next(); state.tokenize = null; return "operator"; } // SINGLE SYMBOL TOKENS else if(isSingleOperatorChar.test(ch)) { state.tokenize = null; return "operator"; } // IDENT else if(isNonDigit.test(ch)) { state.tokenize = tokenIdent; } // Q-IDENT else if(ch == "'" && stream.peek() && stream.peek() != "'") { state.tokenize = tokenQIdent; } // STRING else if(ch == '"') { state.tokenize = tokenString; } // UNSIGNED_NUMBER else if(isDigit.test(ch)) { state.tokenize = tokenUnsignedNumber; } // ERROR else { state.tokenize = null; return "error"; } return state.tokenize(stream, state); }, indent: function(state, textAfter) { if (state.tokenize != null) return CodeMirror.Pass; var level = state.level; if(/(algorithm)/.test(textAfter)) level--; if(/(equation)/.test(textAfter)) level--; if(/(initial algorithm)/.test(textAfter)) level--; if(/(initial equation)/.test(textAfter)) level--; if(/(end)/.test(textAfter)) level--; if(level > 0) return indentUnit*level; else return 0; }, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i=0; i CodeMirror: MscGen mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • MscGen

    MscGen mode

    Xù mode

    MsGenny mode

    Simple mode for highlighting MscGen and two derived sequence chart languages.

    MIME types defined: text/x-mscgen text/x-xu text/x-msgenny

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mscgen/mscgen.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // mode(s) for the sequence chart dsl's mscgen, xù and msgenny // For more information on mscgen, see the site of the original author: // http://www.mcternan.me.uk/mscgen // // This mode for mscgen and the two derivative languages were // originally made for use in the mscgen_js interpreter // (https://sverweij.github.io/mscgen_js) (function(mod) { if ( typeof exports == "object" && typeof module == "object")// CommonJS mod(require("../../lib/codemirror")); else if ( typeof define == "function" && define.amd)// AMD define(["../../lib/codemirror"], mod); else// Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var languages = { mscgen: { "keywords" : ["msc"], "options" : ["hscale", "width", "arcgradient", "wordwraparcs"], "constants" : ["true", "false", "on", "off"], "attributes" : ["label", "idurl", "id", "url", "linecolor", "linecolour", "textcolor", "textcolour", "textbgcolor", "textbgcolour", "arclinecolor", "arclinecolour", "arctextcolor", "arctextcolour", "arctextbgcolor", "arctextbgcolour", "arcskip"], "brackets" : ["\\{", "\\}"], // [ and ] are brackets too, but these get handled in with lists "arcsWords" : ["note", "abox", "rbox", "box"], "arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"], "singlecomment" : ["//", "#"], "operators" : ["="] }, xu: { "keywords" : ["msc", "xu"], "options" : ["hscale", "width", "arcgradient", "wordwraparcs", "wordwrapentities", "watermark"], "constants" : ["true", "false", "on", "off", "auto"], "attributes" : ["label", "idurl", "id", "url", "linecolor", "linecolour", "textcolor", "textcolour", "textbgcolor", "textbgcolour", "arclinecolor", "arclinecolour", "arctextcolor", "arctextcolour", "arctextbgcolor", "arctextbgcolour", "arcskip", "title", "deactivate", "activate", "activation"], "brackets" : ["\\{", "\\}"], // [ and ] are brackets too, but these get handled in with lists "arcsWords" : ["note", "abox", "rbox", "box", "alt", "else", "opt", "break", "par", "seq", "strict", "neg", "critical", "ignore", "consider", "assert", "loop", "ref", "exc"], "arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"], "singlecomment" : ["//", "#"], "operators" : ["="] }, msgenny: { "keywords" : null, "options" : ["hscale", "width", "arcgradient", "wordwraparcs", "wordwrapentities", "watermark"], "constants" : ["true", "false", "on", "off", "auto"], "attributes" : null, "brackets" : ["\\{", "\\}"], "arcsWords" : ["note", "abox", "rbox", "box", "alt", "else", "opt", "break", "par", "seq", "strict", "neg", "critical", "ignore", "consider", "assert", "loop", "ref", "exc"], "arcsOthers" : ["\\|\\|\\|", "\\.\\.\\.", "---", "--", "<->", "==", "<<=>>", "<=>", "\\.\\.", "<<>>", "::", "<:>", "->", "=>>", "=>", ">>", ":>", "<-", "<<=", "<=", "<<", "<:", "x-", "-x"], "singlecomment" : ["//", "#"], "operators" : ["="] } } CodeMirror.defineMode("mscgen", function(_, modeConfig) { var language = languages[modeConfig && modeConfig.language || "mscgen"] return { startState: startStateFn, copyState: copyStateFn, token: produceTokenFunction(language), lineComment : "#", blockCommentStart : "/*", blockCommentEnd : "*/" }; }); CodeMirror.defineMIME("text/x-mscgen", "mscgen"); CodeMirror.defineMIME("text/x-xu", {name: "mscgen", language: "xu"}); CodeMirror.defineMIME("text/x-msgenny", {name: "mscgen", language: "msgenny"}); function wordRegexpBoundary(pWords) { return new RegExp("^\\b(?:" + pWords.join("|") + ")\\b", "i"); } function wordRegexp(pWords) { return new RegExp("^(?:" + pWords.join("|") + ")", "i"); } function startStateFn() { return { inComment : false, inString : false, inAttributeList : false, inScript : false }; } function copyStateFn(pState) { return { inComment : pState.inComment, inString : pState.inString, inAttributeList : pState.inAttributeList, inScript : pState.inScript }; } function produceTokenFunction(pConfig) { return function(pStream, pState) { if (pStream.match(wordRegexp(pConfig.brackets), true, true)) { return "bracket"; } /* comments */ if (!pState.inComment) { if (pStream.match(/\/\*[^\*\/]*/, true, true)) { pState.inComment = true; return "comment"; } if (pStream.match(wordRegexp(pConfig.singlecomment), true, true)) { pStream.skipToEnd(); return "comment"; } } if (pState.inComment) { if (pStream.match(/[^\*\/]*\*\//, true, true)) pState.inComment = false; else pStream.skipToEnd(); return "comment"; } /* strings */ if (!pState.inString && pStream.match(/\"(\\\"|[^\"])*/, true, true)) { pState.inString = true; return "string"; } if (pState.inString) { if (pStream.match(/[^\"]*\"/, true, true)) pState.inString = false; else pStream.skipToEnd(); return "string"; } /* keywords & operators */ if (!!pConfig.keywords && pStream.match(wordRegexpBoundary(pConfig.keywords), true, true)) return "keyword"; if (pStream.match(wordRegexpBoundary(pConfig.options), true, true)) return "keyword"; if (pStream.match(wordRegexpBoundary(pConfig.arcsWords), true, true)) return "keyword"; if (pStream.match(wordRegexp(pConfig.arcsOthers), true, true)) return "keyword"; if (!!pConfig.operators && pStream.match(wordRegexp(pConfig.operators), true, true)) return "operator"; if (!!pConfig.constants && pStream.match(wordRegexp(pConfig.constants), true, true)) return "variable"; /* attribute lists */ if (!pConfig.inAttributeList && !!pConfig.attributes && pStream.match('[', true, true)) { pConfig.inAttributeList = true; return "bracket"; } if (pConfig.inAttributeList) { if (pConfig.attributes !== null && pStream.match(wordRegexpBoundary(pConfig.attributes), true, true)) { return "attribute"; } if (pStream.match(']', true, true)) { pConfig.inAttributeList = false; return "bracket"; } } pStream.next(); return "base"; }; } }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mscgen/mscgen_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "mscgen"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("empty chart", "[keyword msc][bracket {]", "[base ]", "[bracket }]" ); MT("comments", "[comment // a single line comment]", "[comment # another single line comment /* and */ ignored here]", "[comment /* A multi-line comment even though it contains]", "[comment msc keywords and \"quoted text\"*/]"); MT("strings", "[string \"// a string\"]", "[string \"a string running over]", "[string two lines\"]", "[string \"with \\\"escaped quote\"]" ); MT("xù/ msgenny keywords classify as 'base'", "[base watermark]", "[base wordwrapentities]", "[base alt loop opt ref else break par seq assert]" ); MT("xù/ msgenny constants classify as 'base'", "[base auto]" ); MT("mscgen constants classify as 'variable'", "[variable true]", "[variable false]", "[variable on]", "[variable off]" ); MT("mscgen options classify as keyword", "[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]" ); MT("mscgen arcs classify as keyword", "[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]", "[keyword |||...---]", "[keyword ..--==::]", "[keyword ->]", "[keyword <-]", "[keyword <->]", "[keyword =>]", "[keyword <=]", "[keyword <=>]", "[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]", "[keyword >>]", "[keyword <<]", "[keyword <<>>]", "[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]", "[keyword :>]", "[keyword <:]", "[keyword <:>]" ); MT("within an attribute list, attributes classify as attribute", "[bracket [[][attribute label]", "[attribute id]","[attribute url]","[attribute idurl]", "[attribute linecolor]","[attribute linecolour]","[attribute textcolor]","[attribute textcolour]","[attribute textbgcolor]","[attribute textbgcolour]", "[attribute arclinecolor]","[attribute arclinecolour]","[attribute arctextcolor]","[attribute arctextcolour]","[attribute arctextbgcolor]","[attribute arctextbgcolour]", "[attribute arcskip][bracket ]]]" ); MT("outside an attribute list, attributes classify as base", "[base label]", "[base id]","[base url]","[base idurl]", "[base linecolor]","[base linecolour]","[base textcolor]","[base textcolour]","[base textbgcolor]","[base textbgcolour]", "[base arclinecolor]","[base arclinecolour]","[base arctextcolor]","[base arctextcolour]","[base arctextbgcolor]","[base arctextbgcolour]", "[base arcskip]" ); MT("a typical program", "[comment # typical mscgen program]", "[keyword msc][base ][bracket {]", "[keyword wordwraparcs][operator =][variable true][base , ][keyword hscale][operator =][string \"0.8\"][base , ][keyword arcgradient][operator =][base 30;]", "[base a][bracket [[][attribute label][operator =][string \"Entity A\"][bracket ]]][base ,]", "[base b][bracket [[][attribute label][operator =][string \"Entity B\"][bracket ]]][base ,]", "[base c][bracket [[][attribute label][operator =][string \"Entity C\"][bracket ]]][base ;]", "[base a ][keyword =>>][base b][bracket [[][attribute label][operator =][string \"Hello entity B\"][bracket ]]][base ;]", "[base a ][keyword <<][base b][bracket [[][attribute label][operator =][string \"Here's an answer dude!\"][bracket ]]][base ;]", "[base c ][keyword :>][base *][bracket [[][attribute label][operator =][string \"What about me?\"][base , ][attribute textcolor][operator =][base red][bracket ]]][base ;]", "[bracket }]" ); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mscgen/msgenny_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-msgenny"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "msgenny"); } MT("comments", "[comment // a single line comment]", "[comment # another single line comment /* and */ ignored here]", "[comment /* A multi-line comment even though it contains]", "[comment msc keywords and \"quoted text\"*/]"); MT("strings", "[string \"// a string\"]", "[string \"a string running over]", "[string two lines\"]", "[string \"with \\\"escaped quote\"]" ); MT("xù/ msgenny keywords classify as 'keyword'", "[keyword watermark]", "[keyword wordwrapentities]", "[keyword alt]","[keyword loop]","[keyword opt]","[keyword ref]","[keyword else]","[keyword break]","[keyword par]","[keyword seq]","[keyword assert]" ); MT("xù/ msgenny constants classify as 'variable'", "[variable auto]", "[variable true]", "[variable false]", "[variable on]", "[variable off]" ); MT("mscgen options classify as keyword", "[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]" ); MT("mscgen arcs classify as keyword", "[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]", "[keyword |||...---]", "[keyword ..--==::]", "[keyword ->]", "[keyword <-]", "[keyword <->]", "[keyword =>]", "[keyword <=]", "[keyword <=>]", "[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]", "[keyword >>]", "[keyword <<]", "[keyword <<>>]", "[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]", "[keyword :>]", "[keyword <:]", "[keyword <:>]" ); MT("within an attribute list, mscgen/ xù attributes classify as base", "[base [[label]", "[base idurl id url]", "[base linecolor linecolour textcolor textcolour textbgcolor textbgcolour]", "[base arclinecolor arclinecolour arctextcolor arctextcolour arctextbgcolor arctextbgcolour]", "[base arcskip]]]" ); MT("outside an attribute list, mscgen/ xù attributes classify as base", "[base label]", "[base idurl id url]", "[base linecolor linecolour textcolor textcolour textbgcolor textbgcolour]", "[base arclinecolor arclinecolour arctextcolor arctextcolour arctextbgcolor arctextbgcolour]", "[base arcskip]" ); MT("a typical program", "[comment # typical msgenny program]", "[keyword wordwraparcs][operator =][variable true][base , ][keyword hscale][operator =][string \"0.8\"][base , ][keyword arcgradient][operator =][base 30;]", "[base a : ][string \"Entity A\"][base ,]", "[base b : Entity B,]", "[base c : Entity C;]", "[base a ][keyword =>>][base b: ][string \"Hello entity B\"][base ;]", "[base a ][keyword alt][base c][bracket {]", "[base a ][keyword <<][base b: ][string \"Here's an answer dude!\"][base ;]", "[keyword ---][base : ][string \"sorry, won't march - comm glitch\"]", "[base a ][keyword x-][base b: ][string \"Here's an answer dude! (won't arrive...)\"][base ;]", "[bracket }]", "[base c ][keyword :>][base *: What about me?;]" ); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mscgen/xu_test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-xu"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "xu"); } MT("empty chart", "[keyword msc][bracket {]", "[base ]", "[bracket }]" ); MT("empty chart", "[keyword xu][bracket {]", "[base ]", "[bracket }]" ); MT("comments", "[comment // a single line comment]", "[comment # another single line comment /* and */ ignored here]", "[comment /* A multi-line comment even though it contains]", "[comment msc keywords and \"quoted text\"*/]"); MT("strings", "[string \"// a string\"]", "[string \"a string running over]", "[string two lines\"]", "[string \"with \\\"escaped quote\"]" ); MT("xù/ msgenny keywords classify as 'keyword'", "[keyword watermark]", "[keyword alt]","[keyword loop]","[keyword opt]","[keyword ref]","[keyword else]","[keyword break]","[keyword par]","[keyword seq]","[keyword assert]" ); MT("xù/ msgenny constants classify as 'variable'", "[variable auto]", "[variable true]", "[variable false]", "[variable on]", "[variable off]" ); MT("mscgen options classify as keyword", "[keyword hscale]", "[keyword width]", "[keyword arcgradient]", "[keyword wordwraparcs]" ); MT("mscgen arcs classify as keyword", "[keyword note]","[keyword abox]","[keyword rbox]","[keyword box]", "[keyword |||...---]", "[keyword ..--==::]", "[keyword ->]", "[keyword <-]", "[keyword <->]", "[keyword =>]", "[keyword <=]", "[keyword <=>]", "[keyword =>>]", "[keyword <<=]", "[keyword <<=>>]", "[keyword >>]", "[keyword <<]", "[keyword <<>>]", "[keyword -x]", "[keyword x-]", "[keyword -X]", "[keyword X-]", "[keyword :>]", "[keyword <:]", "[keyword <:>]" ); MT("within an attribute list, attributes classify as attribute", "[bracket [[][attribute label]", "[attribute id]","[attribute url]","[attribute idurl]", "[attribute linecolor]","[attribute linecolour]","[attribute textcolor]","[attribute textcolour]","[attribute textbgcolor]","[attribute textbgcolour]", "[attribute arclinecolor]","[attribute arclinecolour]","[attribute arctextcolor]","[attribute arctextcolour]","[attribute arctextbgcolor]","[attribute arctextbgcolour]", "[attribute arcskip]","[attribute title]", "[attribute activate]","[attribute deactivate]","[attribute activation][bracket ]]]" ); MT("outside an attribute list, attributes classify as base", "[base label]", "[base id]","[base url]","[base idurl]", "[base linecolor]","[base linecolour]","[base textcolor]","[base textcolour]","[base textbgcolor]","[base textbgcolour]", "[base arclinecolor]","[base arclinecolour]","[base arctextcolor]","[base arctextcolour]","[base arctextbgcolor]","[base arctextbgcolour]", "[base arcskip]", "[base title]" ); MT("a typical program", "[comment # typical xu program]", "[keyword xu][base ][bracket {]", "[keyword wordwraparcs][operator =][string \"true\"][base , ][keyword hscale][operator =][string \"0.8\"][base , ][keyword arcgradient][operator =][base 30, ][keyword width][operator =][variable auto][base ;]", "[base a][bracket [[][attribute label][operator =][string \"Entity A\"][bracket ]]][base ,]", "[base b][bracket [[][attribute label][operator =][string \"Entity B\"][bracket ]]][base ,]", "[base c][bracket [[][attribute label][operator =][string \"Entity C\"][bracket ]]][base ;]", "[base a ][keyword =>>][base b][bracket [[][attribute label][operator =][string \"Hello entity B\"][bracket ]]][base ;]", "[base a ][keyword <<][base b][bracket [[][attribute label][operator =][string \"Here's an answer dude!\"][base , ][attribute title][operator =][string \"This is a title for this message\"][bracket ]]][base ;]", "[base c ][keyword :>][base *][bracket [[][attribute label][operator =][string \"What about me?\"][base , ][attribute textcolor][operator =][base red][bracket ]]][base ;]", "[bracket }]" ); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mumps/index.html ================================================  CodeMirror: MUMPS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • MUMPS

    MUMPS mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/mumps/mumps.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* This MUMPS Language script was constructed using vbscript.js as a template. */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("mumps", function() { function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); } var singleOperators = new RegExp("^[\\+\\-\\*/&#!_?\\\\<>=\\'\\[\\]]"); var doubleOperators = new RegExp("^(('=)|(<=)|(>=)|('>)|('<)|([[)|(]])|(^$))"); var singleDelimiters = new RegExp("^[\\.,:]"); var brackets = new RegExp("[()]"); var identifiers = new RegExp("^[%A-Za-z][A-Za-z0-9]*"); var commandKeywords = ["break","close","do","else","for","goto", "halt", "hang", "if", "job","kill","lock","merge","new","open", "quit", "read", "set", "tcommit", "trollback", "tstart", "use", "view", "write", "xecute", "b","c","d","e","f","g", "h", "i", "j","k","l","m","n","o", "q", "r", "s", "tc", "tro", "ts", "u", "v", "w", "x"]; // The following list includes intrinsic functions _and_ special variables var intrinsicFuncsWords = ["\\$ascii", "\\$char", "\\$data", "\\$ecode", "\\$estack", "\\$etrap", "\\$extract", "\\$find", "\\$fnumber", "\\$get", "\\$horolog", "\\$io", "\\$increment", "\\$job", "\\$justify", "\\$length", "\\$name", "\\$next", "\\$order", "\\$piece", "\\$qlength", "\\$qsubscript", "\\$query", "\\$quit", "\\$random", "\\$reverse", "\\$select", "\\$stack", "\\$test", "\\$text", "\\$translate", "\\$view", "\\$x", "\\$y", "\\$a", "\\$c", "\\$d", "\\$e", "\\$ec", "\\$es", "\\$et", "\\$f", "\\$fn", "\\$g", "\\$h", "\\$i", "\\$j", "\\$l", "\\$n", "\\$na", "\\$o", "\\$p", "\\$q", "\\$ql", "\\$qs", "\\$r", "\\$re", "\\$s", "\\$st", "\\$t", "\\$tr", "\\$v", "\\$z"]; var intrinsicFuncs = wordRegexp(intrinsicFuncsWords); var command = wordRegexp(commandKeywords); function tokenBase(stream, state) { if (stream.sol()) { state.label = true; state.commandMode = 0; } // The character has meaning in MUMPS. Ignoring consecutive // spaces would interfere with interpreting whether the next non-space // character belongs to the command or argument context. // Examine each character and update a mode variable whose interpretation is: // >0 => command 0 => argument <0 => command post-conditional var ch = stream.peek(); if (ch == " " || ch == "\t") { // Pre-process state.label = false; if (state.commandMode == 0) state.commandMode = 1; else if ((state.commandMode < 0) || (state.commandMode == 2)) state.commandMode = 0; } else if ((ch != ".") && (state.commandMode > 0)) { if (ch == ":") state.commandMode = -1; // SIS - Command post-conditional else state.commandMode = 2; } // Do not color parameter list as line tag if ((ch === "(") || (ch === "\u0009")) state.label = false; // MUMPS comment starts with ";" if (ch === ";") { stream.skipToEnd(); return "comment"; } // Number Literals // SIS/RLM - MUMPS permits canonic number followed by concatenate operator if (stream.match(/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?/)) return "number"; // Handle Strings if (ch == '"') { if (stream.skipTo('"')) { stream.next(); return "string"; } else { stream.skipToEnd(); return "error"; } } // Handle operators and Delimiters if (stream.match(doubleOperators) || stream.match(singleOperators)) return "operator"; // Prevents leading "." in DO block from falling through to error if (stream.match(singleDelimiters)) return null; if (brackets.test(ch)) { stream.next(); return "bracket"; } if (state.commandMode > 0 && stream.match(command)) return "variable-2"; if (stream.match(intrinsicFuncs)) return "builtin"; if (stream.match(identifiers)) return "variable"; // Detect dollar-sign when not a documented intrinsic function // "^" may introduce a GVN or SSVN - Color same as function if (ch === "$" || ch === "^") { stream.next(); return "builtin"; } // MUMPS Indirection if (ch === "@") { stream.next(); return "string-2"; } if (/[\w%]/.test(ch)) { stream.eatWhile(/[\w%]/); return "variable"; } // Handle non-detected items stream.next(); return "error"; } return { startState: function() { return { label: false, commandMode: 0 }; }, token: function(stream, state) { var style = tokenBase(stream, state); if (state.label) return "tag"; return style; } }; }); CodeMirror.defineMIME("text/x-mumps", "mumps"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/nginx/index.html ================================================ CodeMirror: NGINX mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • NGINX

    NGINX mode

    MIME types defined: text/x-nginx-conf.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/nginx/nginx.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("nginx", function(config) { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = words( /* ngxDirectiveControl */ "break return rewrite set" + /* ngxDirective */ " accept_mutex accept_mutex_delay access_log add_after_body add_before_body add_header addition_types aio alias allow ancient_browser ancient_browser_value auth_basic auth_basic_user_file auth_http auth_http_header auth_http_timeout autoindex autoindex_exact_size autoindex_localtime charset charset_types client_body_buffer_size client_body_in_file_only client_body_in_single_buffer client_body_temp_path client_body_timeout client_header_buffer_size client_header_timeout client_max_body_size connection_pool_size create_full_put_path daemon dav_access dav_methods debug_connection debug_points default_type degradation degrade deny devpoll_changes devpoll_events directio directio_alignment empty_gif env epoll_events error_log eventport_events expires fastcgi_bind fastcgi_buffer_size fastcgi_buffers fastcgi_busy_buffers_size fastcgi_cache fastcgi_cache_key fastcgi_cache_methods fastcgi_cache_min_uses fastcgi_cache_path fastcgi_cache_use_stale fastcgi_cache_valid fastcgi_catch_stderr fastcgi_connect_timeout fastcgi_hide_header fastcgi_ignore_client_abort fastcgi_ignore_headers fastcgi_index fastcgi_intercept_errors fastcgi_max_temp_file_size fastcgi_next_upstream fastcgi_param fastcgi_pass_header fastcgi_pass_request_body fastcgi_pass_request_headers fastcgi_read_timeout fastcgi_send_lowat fastcgi_send_timeout fastcgi_split_path_info fastcgi_store fastcgi_store_access fastcgi_temp_file_write_size fastcgi_temp_path fastcgi_upstream_fail_timeout fastcgi_upstream_max_fails flv geoip_city geoip_country google_perftools_profiles gzip gzip_buffers gzip_comp_level gzip_disable gzip_hash gzip_http_version gzip_min_length gzip_no_buffer gzip_proxied gzip_static gzip_types gzip_vary gzip_window if_modified_since ignore_invalid_headers image_filter image_filter_buffer image_filter_jpeg_quality image_filter_transparency imap_auth imap_capabilities imap_client_buffer index ip_hash keepalive_requests keepalive_timeout kqueue_changes kqueue_events large_client_header_buffers limit_conn limit_conn_log_level limit_rate limit_rate_after limit_req limit_req_log_level limit_req_zone limit_zone lingering_time lingering_timeout lock_file log_format log_not_found log_subrequest map_hash_bucket_size map_hash_max_size master_process memcached_bind memcached_buffer_size memcached_connect_timeout memcached_next_upstream memcached_read_timeout memcached_send_timeout memcached_upstream_fail_timeout memcached_upstream_max_fails merge_slashes min_delete_depth modern_browser modern_browser_value msie_padding msie_refresh multi_accept open_file_cache open_file_cache_errors open_file_cache_events open_file_cache_min_uses open_file_cache_valid open_log_file_cache output_buffers override_charset perl perl_modules perl_require perl_set pid pop3_auth pop3_capabilities port_in_redirect postpone_gzipping postpone_output protocol proxy proxy_bind proxy_buffer proxy_buffer_size proxy_buffering proxy_buffers proxy_busy_buffers_size proxy_cache proxy_cache_key proxy_cache_methods proxy_cache_min_uses proxy_cache_path proxy_cache_use_stale proxy_cache_valid proxy_connect_timeout proxy_headers_hash_bucket_size proxy_headers_hash_max_size proxy_hide_header proxy_ignore_client_abort proxy_ignore_headers proxy_intercept_errors proxy_max_temp_file_size proxy_method proxy_next_upstream proxy_pass_error_message proxy_pass_header proxy_pass_request_body proxy_pass_request_headers proxy_read_timeout proxy_redirect proxy_send_lowat proxy_send_timeout proxy_set_body proxy_set_header proxy_ssl_session_reuse proxy_store proxy_store_access proxy_temp_file_write_size proxy_temp_path proxy_timeout proxy_upstream_fail_timeout proxy_upstream_max_fails random_index read_ahead real_ip_header recursive_error_pages request_pool_size reset_timedout_connection resolver resolver_timeout rewrite_log rtsig_overflow_events rtsig_overflow_test rtsig_overflow_threshold rtsig_signo satisfy secure_link_secret send_lowat send_timeout sendfile sendfile_max_chunk server_name_in_redirect server_names_hash_bucket_size server_names_hash_max_size server_tokens set_real_ip_from smtp_auth smtp_capabilities smtp_client_buffer smtp_greeting_delay so_keepalive source_charset ssi ssi_ignore_recycled_buffers ssi_min_file_chunk ssi_silent_errors ssi_types ssi_value_length ssl ssl_certificate ssl_certificate_key ssl_ciphers ssl_client_certificate ssl_crl ssl_dhparam ssl_engine ssl_prefer_server_ciphers ssl_protocols ssl_session_cache ssl_session_timeout ssl_verify_client ssl_verify_depth starttls stub_status sub_filter sub_filter_once sub_filter_types tcp_nodelay tcp_nopush thread_stack_size timeout timer_resolution types_hash_bucket_size types_hash_max_size underscores_in_headers uninitialized_variable_warn use user userid userid_domain userid_expires userid_mark userid_name userid_p3p userid_path userid_service valid_referers variables_hash_bucket_size variables_hash_max_size worker_connections worker_cpu_affinity worker_priority worker_processes worker_rlimit_core worker_rlimit_nofile worker_rlimit_sigpending worker_threads working_directory xclient xml_entities xslt_stylesheet xslt_typesdrew@li229-23" ); var keywords_block = words( /* ngxDirectiveBlock */ "http mail events server types location upstream charset_map limit_except if geo map" ); var keywords_important = words( /* ngxDirectiveImportant */ "include root server server_name listen internal proxy_pass memcached_pass fastcgi_pass try_files" ); var indentUnit = config.indentUnit, type; function ret(style, tp) {type = tp; return style;} function tokenBase(stream, state) { stream.eatWhile(/[\w\$_]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) { return "keyword"; } else if (keywords_block.propertyIsEnumerable(cur)) { return "variable-2"; } else if (keywords_important.propertyIsEnumerable(cur)) { return "string-2"; } /**/ var ch = stream.next(); if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());} else if (ch == "/" && stream.eat("*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); } else if (ch == "<" && stream.eat("!")) { state.tokenize = tokenSGMLComment; return tokenSGMLComment(stream, state); } else if (ch == "=") ret(null, "compare"); else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); else if (ch == "\"" || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } else if (ch == "#") { stream.skipToEnd(); return ret("comment", "comment"); } else if (ch == "!") { stream.match(/^\s*\w*/); return ret("keyword", "important"); } else if (/\d/.test(ch)) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } else if (/[,.+>*\/]/.test(ch)) { return ret(null, "select-op"); } else if (/[;{}:\[\]]/.test(ch)) { return ret(null, ch); } else { stream.eatWhile(/[\w\\\-]/); return ret("variable", "variable"); } } function tokenCComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (maybeEnd && ch == "/") { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return ret("comment", "comment"); } function tokenSGMLComment(stream, state) { var dashes = 0, ch; while ((ch = stream.next()) != null) { if (dashes >= 2 && ch == ">") { state.tokenize = tokenBase; break; } dashes = (ch == "-") ? dashes + 1 : 0; } return ret("comment", "comment"); } function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) break; escaped = !escaped && ch == "\\"; } if (!escaped) state.tokenize = tokenBase; return ret("string", "string"); }; } return { startState: function(base) { return {tokenize: tokenBase, baseIndent: base || 0, stack: []}; }, token: function(stream, state) { if (stream.eatSpace()) return null; type = null; var style = state.tokenize(stream, state); var context = state.stack[state.stack.length-1]; if (type == "hash" && context == "rule") style = "atom"; else if (style == "variable") { if (context == "rule") style = "number"; else if (!context || context == "@media{") style = "tag"; } if (context == "rule" && /^[\{\};]$/.test(type)) state.stack.pop(); if (type == "{") { if (context == "@media") state.stack[state.stack.length-1] = "@media{"; else state.stack.push("{"); } else if (type == "}") state.stack.pop(); else if (type == "@media") state.stack.push("@media"); else if (context == "{" && type != "comment") state.stack.push("rule"); return style; }, indent: function(state, textAfter) { var n = state.stack.length; if (/^\}/.test(textAfter)) n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; return state.baseIndent + n * indentUnit; }, electricChars: "}" }; }); CodeMirror.defineMIME("text/x-nginx-conf", "nginx"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/nsis/index.html ================================================ CodeMirror: NSIS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • NSIS

    NSIS mode

    MIME types defined: text/x-nsis.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/nsis/nsis.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Author: Jan T. Sott (http://github.com/idleberg) (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineSimpleMode("nsis",{ start:[ // Numbers {regex: /(?:[+-]?)(?:0x[\d,a-f]+)|(?:0o[0-7]+)|(?:0b[0,1]+)|(?:\d+.?\d*)/, token: "number"}, // Strings { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" }, { regex: /'(?:[^\\']|\\.)*'?/, token: "string" }, { regex: /`(?:[^\\`]|\\.)*`?/, token: "string" }, // Compile Time Commands {regex: /^\s*(?:\!(addincludedir|addplugindir|appendfile|assert|cd|define|delfile|echo|error|execute|finalize|getdllversion|gettlbversion|include|insertmacro|macro|macroend|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|uninstfinalize|verbose|warning))\b/i, token: "keyword"}, // Conditional Compilation {regex: /^\s*(?:\!(if(?:n?def)?|ifmacron?def|macro))\b/i, token: "keyword", indent: true}, {regex: /^\s*(?:\!(else|endif|macroend))\b/i, token: "keyword", dedent: true}, // Runtime Commands {regex: /^\s*(?:Abort|AddBrandingImage|AddSize|AllowRootDirInstall|AllowSkipFiles|AutoCloseWindow|BGFont|BGGradient|BrandingText|BringToFront|Call|CallInstDLL|Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CRCCheck|CreateDirectory|CreateFont|CreateShortCut|Delete|DeleteINISec|DeleteINIStr|DeleteRegKey|DeleteRegValue|DetailPrint|DetailsButtonText|DirText|DirVar|DirVerify|EnableWindow|EnumRegKey|EnumRegValue|Exch|Exec|ExecShell|ExecShellWait|ExecWait|ExpandEnvStrings|File|FileBufSize|FileClose|FileErrorText|FileOpen|FileRead|FileReadByte|FileReadUTF16LE|FileReadWord|FileWriteUTF16LE|FileSeek|FileWrite|FileWriteByte|FileWriteWord|FindClose|FindFirst|FindNext|FindWindow|FlushINI|GetCurInstType|GetCurrentAddress|GetDlgItem|GetDLLVersion|GetDLLVersionLocal|GetErrorLevel|GetFileTime|GetFileTimeLocal|GetFullPathName|GetFunctionAddress|GetInstDirError|GetKnownFolderPath|GetLabelAddress|GetTempFileName|GetWinVer|Goto|HideWindow|Icon|IfAbort|IfErrors|IfFileExists|IfRebootFlag|IfRtlLanguage|IfShellVarContextAll|IfSilent|InitPluginsDir|InstallButtonText|InstallColors|InstallDir|InstallDirRegKey|InstProgressFlags|InstType|InstTypeGetText|InstTypeSetText|Int64Cmp|Int64CmpU|Int64Fmt|IntCmp|IntCmpU|IntFmt|IntOp|IntPtrCmp|IntPtrCmpU|IntPtrOp|IsWindow|LangString|LicenseBkColor|LicenseData|LicenseForceSelection|LicenseLangString|LicenseText|LoadAndSetImage|LoadLanguageFile|LockWindow|LogSet|LogText|ManifestDPIAware|ManifestLongPathAware|ManifestMaxVersionTested|ManifestSupportedOS|MessageBox|MiscButtonText|Name|Nop|OutFile|Page|PageCallbacks|PEAddResource|PEDllCharacteristics|PERemoveResource|PESubsysVer|Pop|Push|Quit|ReadEnvStr|ReadINIStr|ReadRegDWORD|ReadRegStr|Reboot|RegDLL|Rename|RequestExecutionLevel|ReserveFile|Return|RMDir|SearchPath|SectionGetFlags|SectionGetInstTypes|SectionGetSize|SectionGetText|SectionIn|SectionSetFlags|SectionSetInstTypes|SectionSetSize|SectionSetText|SendMessage|SetAutoClose|SetBrandingImage|SetCompress|SetCompressor|SetCompressorDictSize|SetCtlColors|SetCurInstType|SetDatablockOptimize|SetDateSave|SetDetailsPrint|SetDetailsView|SetErrorLevel|SetErrors|SetFileAttributes|SetFont|SetOutPath|SetOverwrite|SetRebootFlag|SetRegView|SetShellVarContext|SetSilent|ShowInstDetails|ShowUninstDetails|ShowWindow|SilentInstall|SilentUnInstall|Sleep|SpaceTexts|StrCmp|StrCmpS|StrCpy|StrLen|SubCaption|Target|Unicode|UninstallButtonText|UninstallCaption|UninstallIcon|UninstallSubCaption|UninstallText|UninstPage|UnRegDLL|Var|VIAddVersionKey|VIFileVersion|VIProductVersion|WindowIcon|WriteINIStr|WriteRegBin|WriteRegDWORD|WriteRegExpandStr|WriteRegMultiStr|WriteRegNone|WriteRegStr|WriteUninstaller|XPStyle)\b/i, token: "keyword"}, {regex: /^\s*(?:Function|PageEx|Section(?:Group)?)\b/i, token: "keyword", indent: true}, {regex: /^\s*(?:(Function|PageEx|Section(?:Group)?)End)\b/i, token: "keyword", dedent: true}, // Command Options {regex: /\b(?:ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HIDDEN|HKCC|HKCR(32|64)?|HKCU(32|64)?|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM(32|64)?|HKPD|HKU|IDABORT|IDCANCEL|IDD_DIR|IDD_INST|IDD_INSTFILES|IDD_LICENSE|IDD_SELCOM|IDD_UNINST|IDD_VERIFY|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|MB_YESNOCANCEL|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SW_HIDE|SW_SHOWDEFAULT|SW_SHOWMAXIMIZED|SW_SHOWMINIMIZED|SW_SHOWNORMAL|SYSTEM|TEMPORARY)\b/i, token: "atom"}, {regex: /\b(?:admin|all|amd64-unicode|auto|both|bottom|bzip2|components|current|custom|directory|false|force|hide|highest|ifdiff|ifnewer|instfiles|lastused|leave|left|license|listonly|lzma|nevershow|none|normal|notset|off|on|right|show|silent|silentlog|textonly|top|true|try|un\.components|un\.custom|un\.directory|un\.instfiles|un\.license|uninstConfirm|user|Win10|Win7|Win8|WinVista|x-86-(ansi|unicode)|zlib)\b/i, token: "builtin"}, // LogicLib.nsh {regex: /\$\{(?:And(?:If(?:Not)?|Unless)|Break|Case(?:2|3|4|5|Else)?|Continue|Default|Do(?:Until|While)?|Else(?:If(?:Not)?|Unless)?|End(?:If|Select|Switch)|Exit(?:Do|For|While)|For(?:Each)?|If(?:Cmd|Not(?:Then)?|Then)?|Loop(?:Until|While)?|Or(?:If(?:Not)?|Unless)|Select|Switch|Unless|While)\}/i, token: "variable-2", indent: true}, // FileFunc.nsh {regex: /\$\{(?:BannerTrimPath|DirState|DriveSpace|Get(BaseName|Drives|ExeName|ExePath|FileAttributes|FileExt|FileName|FileVersion|Options|OptionsS|Parameters|Parent|Root|Size|Time)|Locate|RefreshShellIcons)\}/i, token: "variable-2", dedent: true}, // Memento.nsh {regex: /\$\{(?:Memento(?:Section(?:Done|End|Restore|Save)?|UnselectedSection))\}/i, token: "variable-2", dedent: true}, // TextFunc.nsh {regex: /\$\{(?:Config(?:Read|ReadS|Write|WriteS)|File(?:Join|ReadFromEnd|Recode)|Line(?:Find|Read|Sum)|Text(?:Compare|CompareS)|TrimNewLines)\}/i, token: "variable-2", dedent: true}, // WinVer.nsh {regex: /\$\{(?:(?:At(?:Least|Most)|Is)(?:ServicePack|Win(?:7|8|10|95|98|200(?:0|3|8(?:R2)?)|ME|NT4|Vista|XP))|Is(?:NT|Server))\}/i, token: "variable", dedent: true}, // WordFunc.nsh {regex: /\$\{(?:StrFilterS?|Version(?:Compare|Convert)|Word(?:AddS?|Find(?:(?:2|3)X)?S?|InsertS?|ReplaceS?))\}/i, token: "variable-2", dedent: true}, // x64.nsh {regex: /\$\{(?:RunningX64)\}/i, token: "variable", dedent: true}, {regex: /\$\{(?:Disable|Enable)X64FSRedirection\}/i, token: "variable-2", dedent: true}, // Line Comment {regex: /(#|;).*/, token: "comment"}, // Block Comment {regex: /\/\*/, token: "comment", next: "comment"}, // Operator {regex: /[-+\/*=<>!]+/, token: "operator"}, // Variable {regex: /\$\w[\w\.]*/, token: "variable"}, // Constant {regex: /\${[\!\w\.:-]+}/, token: "variable-2"}, // Language String {regex: /\$\([\!\w\.:-]+\)/, token: "variable-3"} ], comment: [ {regex: /.*?\*\//, token: "comment", next: "start"}, {regex: /.*/, token: "comment"} ], meta: { electricInput: /^\s*((Function|PageEx|Section|Section(Group)?)End|(\!(endif|macroend))|\$\{(End(If|Unless|While)|Loop(Until)|Next)\})$/i, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: ["#", ";"] } }); CodeMirror.defineMIME("text/x-nsis", "nsis"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ntriples/index.html ================================================ CodeMirror: N-Triples mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • N-Triples/N-Quads

    N-Triples mode

    The N-Triples mode also works well with on N-Quad documents.

    MIME types defined: application/n-triples.


    N-Quads add a fourth element to the statement to track which graph the statement is from. Otherwise, it's identical to N-Triples.

    MIME types defined: application/n-quads.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ntriples/ntriples.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /********************************************************** * This script provides syntax highlighting support for * the N-Triples format. * N-Triples format specification: * https://www.w3.org/TR/n-triples/ ***********************************************************/ /* The following expression defines the defined ASF grammar transitions. pre_subject -> { ( writing_subject_uri | writing_bnode_uri ) -> pre_predicate -> writing_predicate_uri -> pre_object -> writing_object_uri | writing_object_bnode | ( writing_object_literal -> writing_literal_lang | writing_literal_type ) -> post_object -> BEGIN } otherwise { -> ERROR } */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ntriples", function() { var Location = { PRE_SUBJECT : 0, WRITING_SUB_URI : 1, WRITING_BNODE_URI : 2, PRE_PRED : 3, WRITING_PRED_URI : 4, PRE_OBJ : 5, WRITING_OBJ_URI : 6, WRITING_OBJ_BNODE : 7, WRITING_OBJ_LITERAL : 8, WRITING_LIT_LANG : 9, WRITING_LIT_TYPE : 10, POST_OBJ : 11, ERROR : 12 }; function transitState(currState, c) { var currLocation = currState.location; var ret; // Opening. if (currLocation == Location.PRE_SUBJECT && c == '<') ret = Location.WRITING_SUB_URI; else if(currLocation == Location.PRE_SUBJECT && c == '_') ret = Location.WRITING_BNODE_URI; else if(currLocation == Location.PRE_PRED && c == '<') ret = Location.WRITING_PRED_URI; else if(currLocation == Location.PRE_OBJ && c == '<') ret = Location.WRITING_OBJ_URI; else if(currLocation == Location.PRE_OBJ && c == '_') ret = Location.WRITING_OBJ_BNODE; else if(currLocation == Location.PRE_OBJ && c == '"') ret = Location.WRITING_OBJ_LITERAL; // Closing. else if(currLocation == Location.WRITING_SUB_URI && c == '>') ret = Location.PRE_PRED; else if(currLocation == Location.WRITING_BNODE_URI && c == ' ') ret = Location.PRE_PRED; else if(currLocation == Location.WRITING_PRED_URI && c == '>') ret = Location.PRE_OBJ; else if(currLocation == Location.WRITING_OBJ_URI && c == '>') ret = Location.POST_OBJ; else if(currLocation == Location.WRITING_OBJ_BNODE && c == ' ') ret = Location.POST_OBJ; else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '"') ret = Location.POST_OBJ; else if(currLocation == Location.WRITING_LIT_LANG && c == ' ') ret = Location.POST_OBJ; else if(currLocation == Location.WRITING_LIT_TYPE && c == '>') ret = Location.POST_OBJ; // Closing typed and language literal. else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '@') ret = Location.WRITING_LIT_LANG; else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '^') ret = Location.WRITING_LIT_TYPE; // Spaces. else if( c == ' ' && ( currLocation == Location.PRE_SUBJECT || currLocation == Location.PRE_PRED || currLocation == Location.PRE_OBJ || currLocation == Location.POST_OBJ ) ) ret = currLocation; // Reset. else if(currLocation == Location.POST_OBJ && c == '.') ret = Location.PRE_SUBJECT; // Error else ret = Location.ERROR; currState.location=ret; } return { startState: function() { return { location : Location.PRE_SUBJECT, uris : [], anchors : [], bnodes : [], langs : [], types : [] }; }, token: function(stream, state) { var ch = stream.next(); if(ch == '<') { transitState(state, ch); var parsedURI = ''; stream.eatWhile( function(c) { if( c != '#' && c != '>' ) { parsedURI += c; return true; } return false;} ); state.uris.push(parsedURI); if( stream.match('#', false) ) return 'variable'; stream.next(); transitState(state, '>'); return 'variable'; } if(ch == '#') { var parsedAnchor = ''; stream.eatWhile(function(c) { if(c != '>' && c != ' ') { parsedAnchor+= c; return true; } return false;}); state.anchors.push(parsedAnchor); return 'variable-2'; } if(ch == '>') { transitState(state, '>'); return 'variable'; } if(ch == '_') { transitState(state, ch); var parsedBNode = ''; stream.eatWhile(function(c) { if( c != ' ' ) { parsedBNode += c; return true; } return false;}); state.bnodes.push(parsedBNode); stream.next(); transitState(state, ' '); return 'builtin'; } if(ch == '"') { transitState(state, ch); stream.eatWhile( function(c) { return c != '"'; } ); stream.next(); if( stream.peek() != '@' && stream.peek() != '^' ) { transitState(state, '"'); } return 'string'; } if( ch == '@' ) { transitState(state, '@'); var parsedLang = ''; stream.eatWhile(function(c) { if( c != ' ' ) { parsedLang += c; return true; } return false;}); state.langs.push(parsedLang); stream.next(); transitState(state, ' '); return 'string-2'; } if( ch == '^' ) { stream.next(); transitState(state, '^'); var parsedType = ''; stream.eatWhile(function(c) { if( c != '>' ) { parsedType += c; return true; } return false;} ); state.types.push(parsedType); stream.next(); transitState(state, '>'); return 'variable'; } if( ch == ' ' ) { transitState(state, ch); } if( ch == '.' ) { transitState(state, ch); } } }; }); // define the registered Media Type for n-triples: // https://www.w3.org/TR/n-triples/#n-triples-mediatype CodeMirror.defineMIME("application/n-triples", "ntriples"); // N-Quads is based on the N-Triples format (so same highlighting works) // https://www.w3.org/TR/n-quads/ CodeMirror.defineMIME("application/n-quads", "ntriples"); // previously used, though technically incorrect media type for n-triples CodeMirror.defineMIME("text/n-triples", "ntriples"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/octave/index.html ================================================ CodeMirror: Octave mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Octave

    Octave mode

    MIME types defined: text/x-octave.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/octave/octave.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("octave", function() { function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); } var singleOperators = new RegExp("^[\\+\\-\\*/&|\\^~<>!@'\\\\]"); var singleDelimiters = new RegExp('^[\\(\\[\\{\\},:=;\\.]'); var doubleOperators = new RegExp("^((==)|(~=)|(<=)|(>=)|(<<)|(>>)|(\\.[\\+\\-\\*/\\^\\\\]))"); var doubleDelimiters = new RegExp("^((!=)|(\\+=)|(\\-=)|(\\*=)|(/=)|(&=)|(\\|=)|(\\^=))"); var tripleDelimiters = new RegExp("^((>>=)|(<<=))"); var expressionEnd = new RegExp("^[\\]\\)]"); var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*"); var builtins = wordRegexp([ 'error', 'eval', 'function', 'abs', 'acos', 'atan', 'asin', 'cos', 'cosh', 'exp', 'log', 'prod', 'sum', 'log10', 'max', 'min', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'reshape', 'break', 'zeros', 'default', 'margin', 'round', 'ones', 'rand', 'syn', 'ceil', 'floor', 'size', 'clear', 'zeros', 'eye', 'mean', 'std', 'cov', 'det', 'eig', 'inv', 'norm', 'rank', 'trace', 'expm', 'logm', 'sqrtm', 'linspace', 'plot', 'title', 'xlabel', 'ylabel', 'legend', 'text', 'grid', 'meshgrid', 'mesh', 'num2str', 'fft', 'ifft', 'arrayfun', 'cellfun', 'input', 'fliplr', 'flipud', 'ismember' ]); var keywords = wordRegexp([ 'return', 'case', 'switch', 'else', 'elseif', 'end', 'endif', 'endfunction', 'if', 'otherwise', 'do', 'for', 'while', 'try', 'catch', 'classdef', 'properties', 'events', 'methods', 'global', 'persistent', 'endfor', 'endwhile', 'printf', 'sprintf', 'disp', 'until', 'continue', 'pkg' ]); // tokenizers function tokenTranspose(stream, state) { if (!stream.sol() && stream.peek() === '\'') { stream.next(); state.tokenize = tokenBase; return 'operator'; } state.tokenize = tokenBase; return tokenBase(stream, state); } function tokenComment(stream, state) { if (stream.match(/^.*%}/)) { state.tokenize = tokenBase; return 'comment'; }; stream.skipToEnd(); return 'comment'; } function tokenBase(stream, state) { // whitespaces if (stream.eatSpace()) return null; // Handle one line Comments if (stream.match('%{')){ state.tokenize = tokenComment; stream.skipToEnd(); return 'comment'; } if (stream.match(/^[%#]/)){ stream.skipToEnd(); return 'comment'; } // Handle Number Literals if (stream.match(/^[0-9\.+-]/, false)) { if (stream.match(/^[+-]?0x[0-9a-fA-F]+[ij]?/)) { stream.tokenize = tokenBase; return 'number'; }; if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; }; if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; }; } if (stream.match(wordRegexp(['nan','NaN','inf','Inf']))) { return 'number'; }; // Handle Strings var m = stream.match(/^"(?:[^"]|"")*("|$)/) || stream.match(/^'(?:[^']|'')*('|$)/) if (m) { return m[1] ? 'string' : "string error"; } // Handle words if (stream.match(keywords)) { return 'keyword'; } ; if (stream.match(builtins)) { return 'builtin'; } ; if (stream.match(identifiers)) { return 'variable'; } ; if (stream.match(singleOperators) || stream.match(doubleOperators)) { return 'operator'; }; if (stream.match(singleDelimiters) || stream.match(doubleDelimiters) || stream.match(tripleDelimiters)) { return null; }; if (stream.match(expressionEnd)) { state.tokenize = tokenTranspose; return null; }; // Handle non-detected items stream.next(); return 'error'; }; return { startState: function() { return { tokenize: tokenBase }; }, token: function(stream, state) { var style = state.tokenize(stream, state); if (style === 'number' || style === 'variable'){ state.tokenize = tokenTranspose; } return style; }, lineComment: '%', fold: 'indent' }; }); CodeMirror.defineMIME("text/x-octave", "octave"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/oz/index.html ================================================ CodeMirror: Oz mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Oz

    Oz mode

    MIME type defined: text/x-oz.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/oz/oz.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("oz", function (conf) { function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); } var singleOperators = /[\^@!\|<>#~\.\*\-\+\\/,=]/; var doubleOperators = /(<-)|(:=)|(=<)|(>=)|(<=)|(<:)|(>:)|(=:)|(\\=)|(\\=:)|(!!)|(==)|(::)/; var tripleOperators = /(:::)|(\.\.\.)|(=<:)|(>=:)/; var middle = ["in", "then", "else", "of", "elseof", "elsecase", "elseif", "catch", "finally", "with", "require", "prepare", "import", "export", "define", "do"]; var end = ["end"]; var atoms = wordRegexp(["true", "false", "nil", "unit"]); var commonKeywords = wordRegexp(["andthen", "at", "attr", "declare", "feat", "from", "lex", "mod", "div", "mode", "orelse", "parser", "prod", "prop", "scanner", "self", "syn", "token"]); var openingKeywords = wordRegexp(["local", "proc", "fun", "case", "class", "if", "cond", "or", "dis", "choice", "not", "thread", "try", "raise", "lock", "for", "suchthat", "meth", "functor"]); var middleKeywords = wordRegexp(middle); var endKeywords = wordRegexp(end); // Tokenizers function tokenBase(stream, state) { if (stream.eatSpace()) { return null; } // Brackets if(stream.match(/[{}]/)) { return "bracket"; } // Special [] keyword if (stream.match('[]')) { return "keyword" } // Operators if (stream.match(tripleOperators) || stream.match(doubleOperators)) { return "operator"; } // Atoms if(stream.match(atoms)) { return 'atom'; } // Opening keywords var matched = stream.match(openingKeywords); if (matched) { if (!state.doInCurrentLine) state.currentIndent++; else state.doInCurrentLine = false; // Special matching for signatures if(matched[0] == "proc" || matched[0] == "fun") state.tokenize = tokenFunProc; else if(matched[0] == "class") state.tokenize = tokenClass; else if(matched[0] == "meth") state.tokenize = tokenMeth; return 'keyword'; } // Middle and other keywords if (stream.match(middleKeywords) || stream.match(commonKeywords)) { return "keyword" } // End keywords if (stream.match(endKeywords)) { state.currentIndent--; return 'keyword'; } // Eat the next char for next comparisons var ch = stream.next(); // Strings if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } // Numbers if (/[~\d]/.test(ch)) { if (ch == "~") { if(! /^[0-9]/.test(stream.peek())) return null; else if (( stream.next() == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/)) return "number"; } if ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) || stream.match(/^[0-9]*(\.[0-9]+)?([eE][~+]?[0-9]+)?/)) return "number"; return null; } // Comments if (ch == "%") { stream.skipToEnd(); return 'comment'; } else if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } } // Single operators if(singleOperators.test(ch)) { return "operator"; } // If nothing match, we skip the entire alphanumeric block stream.eatWhile(/\w/); return "variable"; } function tokenClass(stream, state) { if (stream.eatSpace()) { return null; } stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)/); state.tokenize = tokenBase; return "variable-3" } function tokenMeth(stream, state) { if (stream.eatSpace()) { return null; } stream.match(/([a-zA-Z][A-Za-z0-9_]*)|(`.+`)/); state.tokenize = tokenBase; return "def" } function tokenFunProc(stream, state) { if (stream.eatSpace()) { return null; } if(!state.hasPassedFirstStage && stream.eat("{")) { state.hasPassedFirstStage = true; return "bracket"; } else if(state.hasPassedFirstStage) { stream.match(/([A-Z][A-Za-z0-9_]*)|(`.+`)|\$/); state.hasPassedFirstStage = false; state.tokenize = tokenBase; return "def" } else { state.tokenize = tokenBase; return null; } } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenString(quote) { return function (stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end || !escaped) state.tokenize = tokenBase; return "string"; }; } function buildElectricInputRegEx() { // Reindentation should occur on [] or on a match of any of // the block closing keywords, at the end of a line. var allClosings = middle.concat(end); return new RegExp("[\\[\\]]|(" + allClosings.join("|") + ")$"); } return { startState: function () { return { tokenize: tokenBase, currentIndent: 0, doInCurrentLine: false, hasPassedFirstStage: false }; }, token: function (stream, state) { if (stream.sol()) state.doInCurrentLine = 0; return state.tokenize(stream, state); }, indent: function (state, textAfter) { var trueText = textAfter.replace(/^\s+|\s+$/g, ''); if (trueText.match(endKeywords) || trueText.match(middleKeywords) || trueText.match(/(\[])/)) return conf.indentUnit * (state.currentIndent - 1); if (state.currentIndent < 0) return 0; return state.currentIndent * conf.indentUnit; }, fold: "indent", electricInput: buildElectricInputRegEx(), lineComment: "%", blockCommentStart: "/*", blockCommentEnd: "*/" }; }); CodeMirror.defineMIME("text/x-oz", "oz"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pascal/index.html ================================================ CodeMirror: Pascal mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Pascal

    Pascal mode

    MIME types defined: text/x-pascal.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pascal/pascal.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("pascal", function() { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = words( "absolute and array asm begin case const constructor destructor div do " + "downto else end file for function goto if implementation in inherited " + "inline interface label mod nil not object of operator or packed procedure " + "program record reintroduce repeat self set shl shr string then to type " + "unit until uses var while with xor as class dispinterface except exports " + "finalization finally initialization inline is library on out packed " + "property raise resourcestring threadvar try absolute abstract alias " + "assembler bitpacked break cdecl continue cppdecl cvar default deprecated " + "dynamic enumerator experimental export external far far16 forward generic " + "helper implements index interrupt iocheck local message name near " + "nodefault noreturn nostackframe oldfpccall otherwise overload override " + "pascal platform private protected public published read register " + "reintroduce result safecall saveregisters softfloat specialize static " + "stdcall stored strict unaligned unimplemented varargs virtual write"); var atoms = {"null": true}; var isOperatorChar = /[+\-*&%=<>!?|\/]/; function tokenBase(stream, state) { var ch = stream.next(); if (ch == "#" && state.startOfLine) { stream.skipToEnd(); return "meta"; } if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (ch == "(" && stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (ch == "{") { state.tokenize = tokenCommentBraces; return tokenCommentBraces(stream, state); } if (/[\[\]\(\),;\:\.]/.test(ch)) { return null; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (ch == "/") { if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) return "keyword"; if (atoms.propertyIsEnumerable(cur)) return "atom"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && next == "\\"; } if (end || !escaped) state.tokenize = null; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == ")" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenCommentBraces(stream, state) { var ch; while (ch = stream.next()) { if (ch == "}") { state.tokenize = null; break; } } return "comment"; } // Interface return { startState: function() { return {tokenize: null}; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; return style; }, electricChars: "{}" }; }); CodeMirror.defineMIME("text/x-pascal", "pascal"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pegjs/index.html ================================================ CodeMirror: PEG.js Mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • PEG.js Mode

    PEG.js Mode

    The PEG.js Mode

    Created by Forbes Lindesay.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pegjs/pegjs.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../javascript/javascript")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../javascript/javascript"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("pegjs", function (config) { var jsMode = CodeMirror.getMode(config, "javascript"); function identifier(stream) { return stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/); } return { startState: function () { return { inString: false, stringType: null, inComment: false, inCharacterClass: false, braced: 0, lhs: true, localState: null }; }, token: function (stream, state) { //check for state changes if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) { state.stringType = stream.peek(); stream.next(); // Skip quote state.inString = true; // Update state } if (!state.inString && !state.inComment && stream.match('/*')) { state.inComment = true; } if (state.inString) { while (state.inString && !stream.eol()) { if (stream.peek() === state.stringType) { stream.next(); // Skip quote state.inString = false; // Clear flag } else if (stream.peek() === '\\') { stream.next(); stream.next(); } else { stream.match(/^.[^\\\"\']*/); } } return state.lhs ? "property string" : "string"; // Token style } else if (state.inComment) { while (state.inComment && !stream.eol()) { if (stream.match('*/')) { state.inComment = false; // Clear flag } else { stream.match(/^.[^\*]*/); } } return "comment"; } else if (state.inCharacterClass) { while (state.inCharacterClass && !stream.eol()) { if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) { state.inCharacterClass = false; } } } else if (stream.peek() === '[') { stream.next(); state.inCharacterClass = true; return 'bracket'; } else if (stream.match('//')) { stream.skipToEnd(); return "comment"; } else if (state.braced || stream.peek() === '{') { if (state.localState === null) { state.localState = CodeMirror.startState(jsMode); } var token = jsMode.token(stream, state.localState); var text = stream.current(); if (!token) { for (var i = 0; i < text.length; i++) { if (text[i] === '{') { state.braced++; } else if (text[i] === '}') { state.braced--; } }; } return token; } else if (identifier(stream)) { if (stream.peek() === ':') { return 'variable'; } return 'variable-2'; } else if (['[', ']', '(', ')'].indexOf(stream.peek()) != -1) { stream.next(); return 'bracket'; } else if (!stream.eatSpace()) { stream.next(); } return null; } }; }, "javascript"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/perl/index.html ================================================ CodeMirror: Perl mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Perl

    Perl mode

    MIME types defined: text/x-perl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/perl/perl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // CodeMirror2 mode/perl/perl.js (text/x-perl) beta 0.10 (2011-11-08) // This is a part of CodeMirror from https://github.com/sabaca/CodeMirror_mode_perl (mail@sabaca.com) (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("perl",function(){ // http://perldoc.perl.org var PERL={ // null - magic touch // 1 - keyword // 2 - def // 3 - atom // 4 - operator // 5 - variable-2 (predefined) // [x,y] - x=1,2,3; y=must be defined if x{...} // PERL operators '->' : 4, '++' : 4, '--' : 4, '**' : 4, // ! ~ \ and unary + and - '=~' : 4, '!~' : 4, '*' : 4, '/' : 4, '%' : 4, 'x' : 4, '+' : 4, '-' : 4, '.' : 4, '<<' : 4, '>>' : 4, // named unary operators '<' : 4, '>' : 4, '<=' : 4, '>=' : 4, 'lt' : 4, 'gt' : 4, 'le' : 4, 'ge' : 4, '==' : 4, '!=' : 4, '<=>' : 4, 'eq' : 4, 'ne' : 4, 'cmp' : 4, '~~' : 4, '&' : 4, '|' : 4, '^' : 4, '&&' : 4, '||' : 4, '//' : 4, '..' : 4, '...' : 4, '?' : 4, ':' : 4, '=' : 4, '+=' : 4, '-=' : 4, '*=' : 4, // etc. ??? ',' : 4, '=>' : 4, '::' : 4, // list operators (rightward) 'not' : 4, 'and' : 4, 'or' : 4, 'xor' : 4, // PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;) 'BEGIN' : [5,1], 'END' : [5,1], 'PRINT' : [5,1], 'PRINTF' : [5,1], 'GETC' : [5,1], 'READ' : [5,1], 'READLINE' : [5,1], 'DESTROY' : [5,1], 'TIE' : [5,1], 'TIEHANDLE' : [5,1], 'UNTIE' : [5,1], 'STDIN' : 5, 'STDIN_TOP' : 5, 'STDOUT' : 5, 'STDOUT_TOP' : 5, 'STDERR' : 5, 'STDERR_TOP' : 5, '$ARG' : 5, '$_' : 5, '@ARG' : 5, '@_' : 5, '$LIST_SEPARATOR' : 5, '$"' : 5, '$PROCESS_ID' : 5, '$PID' : 5, '$$' : 5, '$REAL_GROUP_ID' : 5, '$GID' : 5, '$(' : 5, '$EFFECTIVE_GROUP_ID' : 5, '$EGID' : 5, '$)' : 5, '$PROGRAM_NAME' : 5, '$0' : 5, '$SUBSCRIPT_SEPARATOR' : 5, '$SUBSEP' : 5, '$;' : 5, '$REAL_USER_ID' : 5, '$UID' : 5, '$<' : 5, '$EFFECTIVE_USER_ID' : 5, '$EUID' : 5, '$>' : 5, '$a' : 5, '$b' : 5, '$COMPILING' : 5, '$^C' : 5, '$DEBUGGING' : 5, '$^D' : 5, '${^ENCODING}' : 5, '$ENV' : 5, '%ENV' : 5, '$SYSTEM_FD_MAX' : 5, '$^F' : 5, '@F' : 5, '${^GLOBAL_PHASE}' : 5, '$^H' : 5, '%^H' : 5, '@INC' : 5, '%INC' : 5, '$INPLACE_EDIT' : 5, '$^I' : 5, '$^M' : 5, '$OSNAME' : 5, '$^O' : 5, '${^OPEN}' : 5, '$PERLDB' : 5, '$^P' : 5, '$SIG' : 5, '%SIG' : 5, '$BASETIME' : 5, '$^T' : 5, '${^TAINT}' : 5, '${^UNICODE}' : 5, '${^UTF8CACHE}' : 5, '${^UTF8LOCALE}' : 5, '$PERL_VERSION' : 5, '$^V' : 5, '${^WIN32_SLOPPY_STAT}' : 5, '$EXECUTABLE_NAME' : 5, '$^X' : 5, '$1' : 5, // - regexp $1, $2... '$MATCH' : 5, '$&' : 5, '${^MATCH}' : 5, '$PREMATCH' : 5, '$`' : 5, '${^PREMATCH}' : 5, '$POSTMATCH' : 5, "$'" : 5, '${^POSTMATCH}' : 5, '$LAST_PAREN_MATCH' : 5, '$+' : 5, '$LAST_SUBMATCH_RESULT' : 5, '$^N' : 5, '@LAST_MATCH_END' : 5, '@+' : 5, '%LAST_PAREN_MATCH' : 5, '%+' : 5, '@LAST_MATCH_START' : 5, '@-' : 5, '%LAST_MATCH_START' : 5, '%-' : 5, '$LAST_REGEXP_CODE_RESULT' : 5, '$^R' : 5, '${^RE_DEBUG_FLAGS}' : 5, '${^RE_TRIE_MAXBUF}' : 5, '$ARGV' : 5, '@ARGV' : 5, 'ARGV' : 5, 'ARGVOUT' : 5, '$OUTPUT_FIELD_SEPARATOR' : 5, '$OFS' : 5, '$,' : 5, '$INPUT_LINE_NUMBER' : 5, '$NR' : 5, '$.' : 5, '$INPUT_RECORD_SEPARATOR' : 5, '$RS' : 5, '$/' : 5, '$OUTPUT_RECORD_SEPARATOR' : 5, '$ORS' : 5, '$\\' : 5, '$OUTPUT_AUTOFLUSH' : 5, '$|' : 5, '$ACCUMULATOR' : 5, '$^A' : 5, '$FORMAT_FORMFEED' : 5, '$^L' : 5, '$FORMAT_PAGE_NUMBER' : 5, '$%' : 5, '$FORMAT_LINES_LEFT' : 5, '$-' : 5, '$FORMAT_LINE_BREAK_CHARACTERS' : 5, '$:' : 5, '$FORMAT_LINES_PER_PAGE' : 5, '$=' : 5, '$FORMAT_TOP_NAME' : 5, '$^' : 5, '$FORMAT_NAME' : 5, '$~' : 5, '${^CHILD_ERROR_NATIVE}' : 5, '$EXTENDED_OS_ERROR' : 5, '$^E' : 5, '$EXCEPTIONS_BEING_CAUGHT' : 5, '$^S' : 5, '$WARNING' : 5, '$^W' : 5, '${^WARNING_BITS}' : 5, '$OS_ERROR' : 5, '$ERRNO' : 5, '$!' : 5, '%OS_ERROR' : 5, '%ERRNO' : 5, '%!' : 5, '$CHILD_ERROR' : 5, '$?' : 5, '$EVAL_ERROR' : 5, '$@' : 5, '$OFMT' : 5, '$#' : 5, '$*' : 5, '$ARRAY_BASE' : 5, '$[' : 5, '$OLD_PERL_VERSION' : 5, '$]' : 5, // PERL blocks 'if' :[1,1], elsif :[1,1], 'else' :[1,1], 'while' :[1,1], unless :[1,1], 'for' :[1,1], foreach :[1,1], // PERL functions 'abs' :1, // - absolute value function accept :1, // - accept an incoming socket connect alarm :1, // - schedule a SIGALRM 'atan2' :1, // - arctangent of Y/X in the range -PI to PI bind :1, // - binds an address to a socket binmode :1, // - prepare binary files for I/O bless :1, // - create an object bootstrap :1, // 'break' :1, // - break out of a "given" block caller :1, // - get context of the current subroutine call chdir :1, // - change your current working directory chmod :1, // - changes the permissions on a list of files chomp :1, // - remove a trailing record separator from a string chop :1, // - remove the last character from a string chown :1, // - change the ownership on a list of files chr :1, // - get character this number represents chroot :1, // - make directory new root for path lookups close :1, // - close file (or pipe or socket) handle closedir :1, // - close directory handle connect :1, // - connect to a remote socket 'continue' :[1,1], // - optional trailing block in a while or foreach 'cos' :1, // - cosine function crypt :1, // - one-way passwd-style encryption dbmclose :1, // - breaks binding on a tied dbm file dbmopen :1, // - create binding on a tied dbm file 'default' :1, // defined :1, // - test whether a value, variable, or function is defined 'delete' :1, // - deletes a value from a hash die :1, // - raise an exception or bail out 'do' :1, // - turn a BLOCK into a TERM dump :1, // - create an immediate core dump each :1, // - retrieve the next key/value pair from a hash endgrent :1, // - be done using group file endhostent :1, // - be done using hosts file endnetent :1, // - be done using networks file endprotoent :1, // - be done using protocols file endpwent :1, // - be done using passwd file endservent :1, // - be done using services file eof :1, // - test a filehandle for its end 'eval' :1, // - catch exceptions or compile and run code 'exec' :1, // - abandon this program to run another exists :1, // - test whether a hash key is present exit :1, // - terminate this program 'exp' :1, // - raise I to a power fcntl :1, // - file control system call fileno :1, // - return file descriptor from filehandle flock :1, // - lock an entire file with an advisory lock fork :1, // - create a new process just like this one format :1, // - declare a picture format with use by the write() function formline :1, // - internal function used for formats getc :1, // - get the next character from the filehandle getgrent :1, // - get next group record getgrgid :1, // - get group record given group user ID getgrnam :1, // - get group record given group name gethostbyaddr :1, // - get host record given its address gethostbyname :1, // - get host record given name gethostent :1, // - get next hosts record getlogin :1, // - return who logged in at this tty getnetbyaddr :1, // - get network record given its address getnetbyname :1, // - get networks record given name getnetent :1, // - get next networks record getpeername :1, // - find the other end of a socket connection getpgrp :1, // - get process group getppid :1, // - get parent process ID getpriority :1, // - get current nice value getprotobyname :1, // - get protocol record given name getprotobynumber :1, // - get protocol record numeric protocol getprotoent :1, // - get next protocols record getpwent :1, // - get next passwd record getpwnam :1, // - get passwd record given user login name getpwuid :1, // - get passwd record given user ID getservbyname :1, // - get services record given its name getservbyport :1, // - get services record given numeric port getservent :1, // - get next services record getsockname :1, // - retrieve the sockaddr for a given socket getsockopt :1, // - get socket options on a given socket given :1, // glob :1, // - expand filenames using wildcards gmtime :1, // - convert UNIX time into record or string using Greenwich time 'goto' :1, // - create spaghetti code grep :1, // - locate elements in a list test true against a given criterion hex :1, // - convert a string to a hexadecimal number 'import' :1, // - patch a module's namespace into your own index :1, // - find a substring within a string 'int' :1, // - get the integer portion of a number ioctl :1, // - system-dependent device control system call 'join' :1, // - join a list into a string using a separator keys :1, // - retrieve list of indices from a hash kill :1, // - send a signal to a process or process group last :1, // - exit a block prematurely lc :1, // - return lower-case version of a string lcfirst :1, // - return a string with just the next letter in lower case length :1, // - return the number of bytes in a string 'link' :1, // - create a hard link in the filesystem listen :1, // - register your socket as a server local : 2, // - create a temporary value for a global variable (dynamic scoping) localtime :1, // - convert UNIX time into record or string using local time lock :1, // - get a thread lock on a variable, subroutine, or method 'log' :1, // - retrieve the natural logarithm for a number lstat :1, // - stat a symbolic link m :null, // - match a string with a regular expression pattern map :1, // - apply a change to a list to get back a new list with the changes mkdir :1, // - create a directory msgctl :1, // - SysV IPC message control operations msgget :1, // - get SysV IPC message queue msgrcv :1, // - receive a SysV IPC message from a message queue msgsnd :1, // - send a SysV IPC message to a message queue my : 2, // - declare and assign a local variable (lexical scoping) 'new' :1, // next :1, // - iterate a block prematurely no :1, // - unimport some module symbols or semantics at compile time oct :1, // - convert a string to an octal number open :1, // - open a file, pipe, or descriptor opendir :1, // - open a directory ord :1, // - find a character's numeric representation our : 2, // - declare and assign a package variable (lexical scoping) pack :1, // - convert a list into a binary representation 'package' :1, // - declare a separate global namespace pipe :1, // - open a pair of connected filehandles pop :1, // - remove the last element from an array and return it pos :1, // - find or set the offset for the last/next m//g search print :1, // - output a list to a filehandle printf :1, // - output a formatted list to a filehandle prototype :1, // - get the prototype (if any) of a subroutine push :1, // - append one or more elements to an array q :null, // - singly quote a string qq :null, // - doubly quote a string qr :null, // - Compile pattern quotemeta :null, // - quote regular expression magic characters qw :null, // - quote a list of words qx :null, // - backquote quote a string rand :1, // - retrieve the next pseudorandom number read :1, // - fixed-length buffered input from a filehandle readdir :1, // - get a directory from a directory handle readline :1, // - fetch a record from a file readlink :1, // - determine where a symbolic link is pointing readpipe :1, // - execute a system command and collect standard output recv :1, // - receive a message over a Socket redo :1, // - start this loop iteration over again ref :1, // - find out the type of thing being referenced rename :1, // - change a filename require :1, // - load in external functions from a library at runtime reset :1, // - clear all variables of a given name 'return' :1, // - get out of a function early reverse :1, // - flip a string or a list rewinddir :1, // - reset directory handle rindex :1, // - right-to-left substring search rmdir :1, // - remove a directory s :null, // - replace a pattern with a string say :1, // - print with newline scalar :1, // - force a scalar context seek :1, // - reposition file pointer for random-access I/O seekdir :1, // - reposition directory pointer select :1, // - reset default output or do I/O multiplexing semctl :1, // - SysV semaphore control operations semget :1, // - get set of SysV semaphores semop :1, // - SysV semaphore operations send :1, // - send a message over a socket setgrent :1, // - prepare group file for use sethostent :1, // - prepare hosts file for use setnetent :1, // - prepare networks file for use setpgrp :1, // - set the process group of a process setpriority :1, // - set a process's nice value setprotoent :1, // - prepare protocols file for use setpwent :1, // - prepare passwd file for use setservent :1, // - prepare services file for use setsockopt :1, // - set some socket options shift :1, // - remove the first element of an array, and return it shmctl :1, // - SysV shared memory operations shmget :1, // - get SysV shared memory segment identifier shmread :1, // - read SysV shared memory shmwrite :1, // - write SysV shared memory shutdown :1, // - close down just half of a socket connection 'sin' :1, // - return the sine of a number sleep :1, // - block for some number of seconds socket :1, // - create a socket socketpair :1, // - create a pair of sockets 'sort' :1, // - sort a list of values splice :1, // - add or remove elements anywhere in an array 'split' :1, // - split up a string using a regexp delimiter sprintf :1, // - formatted print into a string 'sqrt' :1, // - square root function srand :1, // - seed the random number generator stat :1, // - get a file's status information state :1, // - declare and assign a state variable (persistent lexical scoping) study :1, // - optimize input data for repeated searches 'sub' :1, // - declare a subroutine, possibly anonymously 'substr' :1, // - get or alter a portion of a string symlink :1, // - create a symbolic link to a file syscall :1, // - execute an arbitrary system call sysopen :1, // - open a file, pipe, or descriptor sysread :1, // - fixed-length unbuffered input from a filehandle sysseek :1, // - position I/O pointer on handle used with sysread and syswrite system :1, // - run a separate program syswrite :1, // - fixed-length unbuffered output to a filehandle tell :1, // - get current seekpointer on a filehandle telldir :1, // - get current seekpointer on a directory handle tie :1, // - bind a variable to an object class tied :1, // - get a reference to the object underlying a tied variable time :1, // - return number of seconds since 1970 times :1, // - return elapsed time for self and child processes tr :null, // - transliterate a string truncate :1, // - shorten a file uc :1, // - return upper-case version of a string ucfirst :1, // - return a string with just the next letter in upper case umask :1, // - set file creation mode mask undef :1, // - remove a variable or function definition unlink :1, // - remove one link to a file unpack :1, // - convert binary structure into normal perl variables unshift :1, // - prepend more elements to the beginning of a list untie :1, // - break a tie binding to a variable use :1, // - load in a module at compile time utime :1, // - set a file's last access and modify times values :1, // - return a list of the values in a hash vec :1, // - test or set particular bits in a string wait :1, // - wait for any child process to die waitpid :1, // - wait for a particular child process to die wantarray :1, // - get void vs scalar vs list context of current subroutine call warn :1, // - print debugging info when :1, // write :1, // - print a picture record y :null}; // - transliterate a string var RXstyle="string-2"; var RXmodifiers=/[goseximacplud]/; // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type function tokenChain(stream,state,chain,style,tail){ // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;) state.chain=null; // 12 3tail state.style=null; state.tail=null; state.tokenize=function(stream,state){ var e=false,c,i=0; while(c=stream.next()){ if(c===chain[i]&&!e){ if(chain[++i]!==undefined){ state.chain=chain[i]; state.style=style; state.tail=tail;} else if(tail) stream.eatWhile(tail); state.tokenize=tokenPerl; return style;} e=!e&&c=="\\";} return style;}; return state.tokenize(stream,state);} function tokenSOMETHING(stream,state,string){ state.tokenize=function(stream,state){ if(stream.string==string) state.tokenize=tokenPerl; stream.skipToEnd(); return "string";}; return state.tokenize(stream,state);} function tokenPerl(stream,state){ if(stream.eatSpace()) return null; if(state.chain) return tokenChain(stream,state,state.chain,state.style,state.tail); if(stream.match(/^(\-?((\d[\d_]*)?\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F_]+|0b[01_]+|\d[\d_]*(e[+-]?\d+)?)/)) return 'number'; if(stream.match(/^<<(?=[_a-zA-Z])/)){ // NOTE: <"],RXstyle,RXmodifiers);} if(/[\^'"!~\/]/.test(c)){ eatSuffix(stream, 1); return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} else if(c=="q"){ c=look(stream, 1); if(c=="("){ eatSuffix(stream, 2); return tokenChain(stream,state,[")"],"string");} if(c=="["){ eatSuffix(stream, 2); return tokenChain(stream,state,["]"],"string");} if(c=="{"){ eatSuffix(stream, 2); return tokenChain(stream,state,["}"],"string");} if(c=="<"){ eatSuffix(stream, 2); return tokenChain(stream,state,[">"],"string");} if(/[\^'"!~\/]/.test(c)){ eatSuffix(stream, 1); return tokenChain(stream,state,[stream.eat(c)],"string");}} else if(c=="w"){ c=look(stream, 1); if(c=="("){ eatSuffix(stream, 2); return tokenChain(stream,state,[")"],"bracket");} if(c=="["){ eatSuffix(stream, 2); return tokenChain(stream,state,["]"],"bracket");} if(c=="{"){ eatSuffix(stream, 2); return tokenChain(stream,state,["}"],"bracket");} if(c=="<"){ eatSuffix(stream, 2); return tokenChain(stream,state,[">"],"bracket");} if(/[\^'"!~\/]/.test(c)){ eatSuffix(stream, 1); return tokenChain(stream,state,[stream.eat(c)],"bracket");}} else if(c=="r"){ c=look(stream, 1); if(c=="("){ eatSuffix(stream, 2); return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} if(c=="["){ eatSuffix(stream, 2); return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} if(c=="{"){ eatSuffix(stream, 2); return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} if(c=="<"){ eatSuffix(stream, 2); return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);} if(/[\^'"!~\/]/.test(c)){ eatSuffix(stream, 1); return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} else if(/[\^'"!~\/(\[{<]/.test(c)){ if(c=="("){ eatSuffix(stream, 1); return tokenChain(stream,state,[")"],"string");} if(c=="["){ eatSuffix(stream, 1); return tokenChain(stream,state,["]"],"string");} if(c=="{"){ eatSuffix(stream, 1); return tokenChain(stream,state,["}"],"string");} if(c=="<"){ eatSuffix(stream, 1); return tokenChain(stream,state,[">"],"string");} if(/[\^'"!~\/]/.test(c)){ return tokenChain(stream,state,[stream.eat(c)],"string");}}}} if(ch=="m"){ var c=look(stream, -2); if(!(c&&/\w/.test(c))){ c=stream.eat(/[(\[{<\^'"!~\/]/); if(c){ if(/[\^'"!~\/]/.test(c)){ return tokenChain(stream,state,[c],RXstyle,RXmodifiers);} if(c=="("){ return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} if(c=="["){ return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} if(c=="{"){ return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} if(c=="<"){ return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}}}} if(ch=="s"){ var c=/[\/>\]})\w]/.test(look(stream, -2)); if(!c){ c=stream.eat(/[(\[{<\^'"!~\/]/); if(c){ if(c=="[") return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); if(c=="{") return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); if(c=="<") return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); if(c=="(") return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} if(ch=="y"){ var c=/[\/>\]})\w]/.test(look(stream, -2)); if(!c){ c=stream.eat(/[(\[{<\^'"!~\/]/); if(c){ if(c=="[") return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); if(c=="{") return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); if(c=="<") return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); if(c=="(") return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} if(ch=="t"){ var c=/[\/>\]})\w]/.test(look(stream, -2)); if(!c){ c=stream.eat("r");if(c){ c=stream.eat(/[(\[{<\^'"!~\/]/); if(c){ if(c=="[") return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); if(c=="{") return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); if(c=="<") return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); if(c=="(") return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}} if(ch=="`"){ return tokenChain(stream,state,[ch],"variable-2");} if(ch=="/"){ if(!/~\s*$/.test(prefix(stream))) return "operator"; else return tokenChain(stream,state,[ch],RXstyle,RXmodifiers);} if(ch=="$"){ var p=stream.pos; if(stream.eatWhile(/\d/)||stream.eat("{")&&stream.eatWhile(/\d/)&&stream.eat("}")) return "variable-2"; else stream.pos=p;} if(/[$@%]/.test(ch)){ var p=stream.pos; if(stream.eat("^")&&stream.eat(/[A-Z]/)||!/[@$%&]/.test(look(stream, -2))&&stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){ var c=stream.current(); if(PERL[c]) return "variable-2";} stream.pos=p;} if(/[$@%&]/.test(ch)){ if(stream.eatWhile(/[\w$]/)||stream.eat("{")&&stream.eatWhile(/[\w$]/)&&stream.eat("}")){ var c=stream.current(); if(PERL[c]) return "variable-2"; else return "variable";}} if(ch=="#"){ if(look(stream, -2)!="$"){ stream.skipToEnd(); return "comment";}} if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)){ var p=stream.pos; stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/); if(PERL[stream.current()]) return "operator"; else stream.pos=p;} if(ch=="_"){ if(stream.pos==1){ if(suffix(stream, 6)=="_END__"){ return tokenChain(stream,state,['\0'],"comment");} else if(suffix(stream, 7)=="_DATA__"){ return tokenChain(stream,state,['\0'],"variable-2");} else if(suffix(stream, 7)=="_C__"){ return tokenChain(stream,state,['\0'],"string");}}} if(/\w/.test(ch)){ var p=stream.pos; if(look(stream, -2)=="{"&&(look(stream, 0)=="}"||stream.eatWhile(/\w/)&&look(stream, 0)=="}")) return "string"; else stream.pos=p;} if(/[A-Z]/.test(ch)){ var l=look(stream, -2); var p=stream.pos; stream.eatWhile(/[A-Z_]/); if(/[\da-z]/.test(look(stream, 0))){ stream.pos=p;} else{ var c=PERL[stream.current()]; if(!c) return "meta"; if(c[1]) c=c[0]; if(l!=":"){ if(c==1) return "keyword"; else if(c==2) return "def"; else if(c==3) return "atom"; else if(c==4) return "operator"; else if(c==5) return "variable-2"; else return "meta";} else return "meta";}} if(/[a-zA-Z_]/.test(ch)){ var l=look(stream, -2); stream.eatWhile(/\w/); var c=PERL[stream.current()]; if(!c) return "meta"; if(c[1]) c=c[0]; if(l!=":"){ if(c==1) return "keyword"; else if(c==2) return "def"; else if(c==3) return "atom"; else if(c==4) return "operator"; else if(c==5) return "variable-2"; else return "meta";} else return "meta";} return null;} return { startState: function() { return { tokenize: tokenPerl, chain: null, style: null, tail: null }; }, token: function(stream, state) { return (state.tokenize || tokenPerl)(stream, state); }, lineComment: '#' }; }); CodeMirror.registerHelper("wordChars", "perl", /[\w$]/); CodeMirror.defineMIME("text/x-perl", "perl"); // it's like "peek", but need for look-ahead or look-behind if index < 0 function look(stream, c){ return stream.string.charAt(stream.pos+(c||0)); } // return a part of prefix of current stream from current position function prefix(stream, c){ if(c){ var x=stream.pos-c; return stream.string.substr((x>=0?x:0),c);} else{ return stream.string.substr(0,stream.pos-1); } } // return a part of suffix of current stream from current position function suffix(stream, c){ var y=stream.string.length; var x=y-stream.pos+1; return stream.string.substr(stream.pos,(c&&c=(y=stream.string.length-1)) stream.pos=y; else stream.pos=x; } }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/php/index.html ================================================ CodeMirror: PHP mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • PHP

    PHP mode

    Simple HTML/PHP mode based on the C-like mode. Depends on XML, JavaScript, CSS, HTMLMixed, and C-like modes.

    MIME types defined: application/x-httpd-php (HTML with PHP code), text/x-php (plain, non-wrapped PHP code).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/php/php.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../clike/clike")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../clike/clike"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function keywords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } // Helper for phpString function matchSequence(list, end, escapes) { if (list.length == 0) return phpString(end); return function (stream, state) { var patterns = list[0]; for (var i = 0; i < patterns.length; i++) if (stream.match(patterns[i][0])) { state.tokenize = matchSequence(list.slice(1), end); return patterns[i][1]; } state.tokenize = phpString(end, escapes); return "string"; }; } function phpString(closing, escapes) { return function(stream, state) { return phpString_(stream, state, closing, escapes); }; } function phpString_(stream, state, closing, escapes) { // "Complex" syntax if (escapes !== false && stream.match("${", false) || stream.match("{$", false)) { state.tokenize = null; return "string"; } // Simple syntax if (escapes !== false && stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) { // After the variable name there may appear array or object operator. if (stream.match("[", false)) { // Match array operator state.tokenize = matchSequence([ [["[", null]], [[/\d[\w\.]*/, "number"], [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"], [/[\w\$]+/, "variable"]], [["]", null]] ], closing, escapes); } if (stream.match(/^->\w/, false)) { // Match object operator state.tokenize = matchSequence([ [["->", null]], [[/[\w]+/, "variable"]] ], closing, escapes); } return "variable-2"; } var escaped = false; // Normal string while (!stream.eol() && (escaped || escapes === false || (!stream.match("{$", false) && !stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false)))) { if (!escaped && stream.match(closing)) { state.tokenize = null; state.tokStack.pop(); state.tokStack.pop(); break; } escaped = stream.next() == "\\" && !escaped; } return "string"; } var phpKeywords = "abstract and array as break case catch class clone const continue declare default " + "do else elseif enddeclare endfor endforeach endif endswitch endwhile enum extends final " + "for foreach function global goto if implements interface instanceof namespace " + "new or private protected public static switch throw trait try use var while xor " + "die echo empty exit eval include include_once isset list require require_once return " + "print unset __halt_compiler self static parent yield insteadof finally readonly match"; var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__"; var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage memory_get_peak_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count"; CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" ")); CodeMirror.registerHelper("wordChars", "php", /[\w$]/); var phpConfig = { name: "clike", helperType: "php", keywords: keywords(phpKeywords), blockKeywords: keywords("catch do else elseif for foreach if switch try while finally"), defKeywords: keywords("class enum function interface namespace trait"), atoms: keywords(phpAtoms), builtin: keywords(phpBuiltin), multiLineStrings: true, hooks: { "$": function(stream) { stream.eatWhile(/[\w\$_]/); return "variable-2"; }, "<": function(stream, state) { var before; if (before = stream.match(/^<<\s*/)) { var quoted = stream.eat(/['"]/); stream.eatWhile(/[\w\.]/); var delim = stream.current().slice(before[0].length + (quoted ? 2 : 1)); if (quoted) stream.eat(quoted); if (delim) { (state.tokStack || (state.tokStack = [])).push(delim, 0); state.tokenize = phpString(delim, quoted != "'"); return "string"; } } return false; }, "#": function(stream) { while (!stream.eol() && !stream.match("?>", false)) stream.next(); return "comment"; }, "/": function(stream) { if (stream.eat("/")) { while (!stream.eol() && !stream.match("?>", false)) stream.next(); return "comment"; } return false; }, '"': function(_stream, state) { (state.tokStack || (state.tokStack = [])).push('"', 0); state.tokenize = phpString('"'); return "string"; }, "{": function(_stream, state) { if (state.tokStack && state.tokStack.length) state.tokStack[state.tokStack.length - 1]++; return false; }, "}": function(_stream, state) { if (state.tokStack && state.tokStack.length > 0 && !--state.tokStack[state.tokStack.length - 1]) { state.tokenize = phpString(state.tokStack[state.tokStack.length - 2]); } return false; } } }; CodeMirror.defineMode("php", function(config, parserConfig) { var htmlMode = CodeMirror.getMode(config, (parserConfig && parserConfig.htmlMode) || "text/html"); var phpMode = CodeMirror.getMode(config, phpConfig); function dispatch(stream, state) { var isPHP = state.curMode == phpMode; if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null; if (!isPHP) { if (stream.match(/^<\?\w*/)) { state.curMode = phpMode; if (!state.php) state.php = CodeMirror.startState(phpMode, htmlMode.indent(state.html, "", "")) state.curState = state.php; return "meta"; } if (state.pending == '"' || state.pending == "'") { while (!stream.eol() && stream.next() != state.pending) {} var style = "string"; } else if (state.pending && stream.pos < state.pending.end) { stream.pos = state.pending.end; var style = state.pending.style; } else { var style = htmlMode.token(stream, state.curState); } if (state.pending) state.pending = null; var cur = stream.current(), openPHP = cur.search(/<\?/), m; if (openPHP != -1) { if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0]; else state.pending = {end: stream.pos, style: style}; stream.backUp(cur.length - openPHP); } return style; } else if (isPHP && state.php.tokenize == null && stream.match("?>")) { state.curMode = htmlMode; state.curState = state.html; if (!state.php.context.prev) state.php = null; return "meta"; } else { return phpMode.token(stream, state.curState); } } return { startState: function() { var html = CodeMirror.startState(htmlMode) var php = parserConfig.startOpen ? CodeMirror.startState(phpMode) : null return {html: html, php: php, curMode: parserConfig.startOpen ? phpMode : htmlMode, curState: parserConfig.startOpen ? php : html, pending: null}; }, copyState: function(state) { var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), php = state.php, phpNew = php && CodeMirror.copyState(phpMode, php), cur; if (state.curMode == htmlMode) cur = htmlNew; else cur = phpNew; return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, pending: state.pending}; }, token: dispatch, indent: function(state, textAfter, line) { if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || (state.curMode == phpMode && /^\?>/.test(textAfter))) return htmlMode.indent(state.html, textAfter, line); return state.curMode.indent(state.curState, textAfter, line); }, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } }; }, "htmlmixed", "clike"); CodeMirror.defineMIME("application/x-httpd-php", "php"); CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); CodeMirror.defineMIME("text/x-php", phpConfig); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/php/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "php"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT('simple_test', '[meta ]'); MT('variable_interpolation_non_alphanumeric', '[meta $/$\\$}$\\\"$:$;$?$|$[[$]]$+$=aaa"]', '[meta ?>]'); MT('variable_interpolation_digits', '[meta ]'); MT('variable_interpolation_simple_syntax_1', '[meta ]'); MT('variable_interpolation_simple_syntax_2', '[meta ]'); MT('variable_interpolation_simple_syntax_3', '[meta [variable aaaaa][string .aaaaaa"];', '[keyword echo] [string "aaa][variable-2 $aaaa][string ->][variable-2 $aaaaa][string .aaaaaa"];', '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string [[2]].aaaaaa"];', '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string ->aaaa2.aaaaaa"];', '[meta ?>]'); MT('variable_interpolation_escaping', '[meta aaa.aaa"];', '[keyword echo] [string "aaa\\$aaaa[[2]]aaa.aaa"];', '[keyword echo] [string "aaa\\$aaaa[[asd]]aaa.aaa"];', '[keyword echo] [string "aaa{\\$aaaa->aaa.aaa"];', '[keyword echo] [string "aaa{\\$aaaa[[2]]aaa.aaa"];', '[keyword echo] [string "aaa{\\aaaaa[[asd]]aaa.aaa"];', '[keyword echo] [string "aaa\\${aaaa->aaa.aaa"];', '[keyword echo] [string "aaa\\${aaaa[[2]]aaa.aaa"];', '[keyword echo] [string "aaa\\${aaaa[[asd]]aaa.aaa"];', '[meta ?>]'); MT('variable_interpolation_complex_syntax_1', '[meta aaa.aaa"];', '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa]}[string ->aaa.aaa"];', '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa][[',' [number 42]',']]}[string ->aaa.aaa"];', '[keyword echo] [string "aaa][variable-2 $]{[variable aaaa][meta ?>]aaaaaa'); MT('variable_interpolation_complex_syntax_2', '[meta } $aaaaaa.aaa"];', '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>*/][[',' [string "aaa][variable-2 $aaa][string {}][variable-2 $]{[variable aaa]}[string "]',']]}[string ->aaa.aaa"];', '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*} } $aaa } */]}[string ->aaa.aaa"];'); function build_recursive_monsters(nt, t, n){ var monsters = [t]; for (var i = 1; i <= n; ++i) monsters[i] = nt.join(monsters[i - 1]); return monsters; } var m1 = build_recursive_monsters( ['[string "][variable-2 $]{[variable aaa] [operator +] ', '}[string "]'], '[comment /* }?>} */] [string "aaa][variable-2 $aaa][string .aaa"]', 10 ); MT('variable_interpolation_complex_syntax_3_1', '[meta ]'); var m2 = build_recursive_monsters( ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', '}[string .a"]'], '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', 5 ); MT('variable_interpolation_complex_syntax_3_2', '[meta ]'); function build_recursive_monsters_2(mf1, mf2, nt, t, n){ var monsters = [t]; for (var i = 1; i <= n; ++i) monsters[i] = nt[0] + mf1[i - 1] + nt[1] + mf2[i - 1] + nt[2] + monsters[i - 1] + nt[3]; return monsters; } var m3 = build_recursive_monsters_2( m1, m2, ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', ' [operator +] ', '}[string .a"]'], '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', 4 ); MT('variable_interpolation_complex_syntax_3_3', '[meta ]'); MT("variable_interpolation_heredoc", "[meta CodeMirror: Pig Latin mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Pig Latin

    Pig Latin mode

    Simple mode that handles Pig Latin language.

    MIME type defined: text/x-pig (PIG code)

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pig/pig.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* * Pig Latin Mode for CodeMirror 2 * @author Prasanth Jayachandran * @link https://github.com/prasanthj/pig-codemirror-2 * This implementation is adapted from PL/SQL mode in CodeMirror 2. */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("pig", function(_config, parserConfig) { var keywords = parserConfig.keywords, builtins = parserConfig.builtins, types = parserConfig.types, multiLineStrings = parserConfig.multiLineStrings; var isOperatorChar = /[*+\-%<>=&?:\/!|]/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenComment(stream, state) { var isEnd = false; var ch; while(ch = stream.next()) { if(ch == "/" && isEnd) { state.tokenize = tokenBase; break; } isEnd = (ch == "*"); } return "comment"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = tokenBase; return "error"; }; } function tokenBase(stream, state) { var ch = stream.next(); // is a start of string? if (ch == '"' || ch == "'") return chain(stream, state, tokenString(ch)); // is it one of the special chars else if(/[\[\]{}\(\),;\.]/.test(ch)) return null; // is it a number? else if(/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } // multi line comment or operator else if (ch == "/") { if (stream.eat("*")) { return chain(stream, state, tokenComment); } else { stream.eatWhile(isOperatorChar); return "operator"; } } // single line comment or operator else if (ch=="-") { if(stream.eat("-")){ stream.skipToEnd(); return "comment"; } else { stream.eatWhile(isOperatorChar); return "operator"; } } // is it an operator else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } else { // get the while word stream.eatWhile(/[\w\$_]/); // is it one of the listed keywords? if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) { //keywords can be used as variables like flatten(group), group.$0 etc.. if (!stream.eat(")") && !stream.eat(".")) return "keyword"; } // is it one of the builtin functions? if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) return "variable-2"; // is it one of the listed types? if (types && types.propertyIsEnumerable(stream.current().toUpperCase())) return "variable-3"; // default is a 'variable' return "variable"; } } // Interface return { startState: function() { return { tokenize: tokenBase, startOfLine: true }; }, token: function(stream, state) { if(stream.eatSpace()) return null; var style = state.tokenize(stream, state); return style; } }; }); (function() { function keywords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } // builtin funcs taken from trunk revision 1303237 var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER " + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS " + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER "; // taken from QueryLexer.g var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " + "NEQ MATCHES TRUE FALSE DUMP"; // data types var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP "; CodeMirror.defineMIME("text/x-pig", { name: "pig", builtins: keywords(pBuiltins), keywords: keywords(pKeywords), types: keywords(pTypes) }); CodeMirror.registerHelper("hintWords", "pig", (pBuiltins + pTypes + pKeywords).split(" ")); }()); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/powershell/index.html ================================================ CodeMirror: Powershell mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • JavaScript

    PowerShell mode

    MIME types defined: application/x-powershell.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/powershell/powershell.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { 'use strict'; if (typeof exports == 'object' && typeof module == 'object') // CommonJS mod(require('../../lib/codemirror')); else if (typeof define == 'function' && define.amd) // AMD define(['../../lib/codemirror'], mod); else // Plain browser env mod(window.CodeMirror); })(function(CodeMirror) { 'use strict'; CodeMirror.defineMode('powershell', function() { function buildRegexp(patterns, options) { options = options || {}; var prefix = options.prefix !== undefined ? options.prefix : '^'; var suffix = options.suffix !== undefined ? options.suffix : '\\b'; for (var i = 0; i < patterns.length; i++) { if (patterns[i] instanceof RegExp) { patterns[i] = patterns[i].source; } else { patterns[i] = patterns[i].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } } return new RegExp(prefix + '(' + patterns.join('|') + ')' + suffix, 'i'); } var notCharacterOrDash = '(?=[^A-Za-z\\d\\-_]|$)'; var varNames = /[\w\-:]/ var keywords = buildRegexp([ /begin|break|catch|continue|data|default|do|dynamicparam/, /else|elseif|end|exit|filter|finally|for|foreach|from|function|if|in/, /param|process|return|switch|throw|trap|try|until|where|while/ ], { suffix: notCharacterOrDash }); var punctuation = /[\[\]{},;`\\\.]|@[({]/; var wordOperators = buildRegexp([ 'f', /b?not/, /[ic]?split/, 'join', /is(not)?/, 'as', /[ic]?(eq|ne|[gl][te])/, /[ic]?(not)?(like|match|contains)/, /[ic]?replace/, /b?(and|or|xor)/ ], { prefix: '-' }); var symbolOperators = /[+\-*\/%]=|\+\+|--|\.\.|[+\-*&^%:=!|\/]|<(?!#)|(?!#)>/; var operators = buildRegexp([wordOperators, symbolOperators], { suffix: '' }); var numbers = /^((0x[\da-f]+)|((\d+\.\d+|\d\.|\.\d+|\d+)(e[\+\-]?\d+)?))[ld]?([kmgtp]b)?/i; var identifiers = /^[A-Za-z\_][A-Za-z\-\_\d]*\b/; var symbolBuiltins = /[A-Z]:|%|\?/i; var namedBuiltins = buildRegexp([ /Add-(Computer|Content|History|Member|PSSnapin|Type)/, /Checkpoint-Computer/, /Clear-(Content|EventLog|History|Host|Item(Property)?|Variable)/, /Compare-Object/, /Complete-Transaction/, /Connect-PSSession/, /ConvertFrom-(Csv|Json|SecureString|StringData)/, /Convert-Path/, /ConvertTo-(Csv|Html|Json|SecureString|Xml)/, /Copy-Item(Property)?/, /Debug-Process/, /Disable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)/, /Disconnect-PSSession/, /Enable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)/, /(Enter|Exit)-PSSession/, /Export-(Alias|Clixml|Console|Counter|Csv|FormatData|ModuleMember|PSSession)/, /ForEach-Object/, /Format-(Custom|List|Table|Wide)/, new RegExp('Get-(Acl|Alias|AuthenticodeSignature|ChildItem|Command|ComputerRestorePoint|Content|ControlPanelItem|Counter|Credential' + '|Culture|Date|Event|EventLog|EventSubscriber|ExecutionPolicy|FormatData|Help|History|Host|HotFix|Item|ItemProperty|Job' + '|Location|Member|Module|PfxCertificate|Process|PSBreakpoint|PSCallStack|PSDrive|PSProvider|PSSession|PSSessionConfiguration' + '|PSSnapin|Random|Service|TraceSource|Transaction|TypeData|UICulture|Unique|Variable|Verb|WinEvent|WmiObject)'), /Group-Object/, /Import-(Alias|Clixml|Counter|Csv|LocalizedData|Module|PSSession)/, /ImportSystemModules/, /Invoke-(Command|Expression|History|Item|RestMethod|WebRequest|WmiMethod)/, /Join-Path/, /Limit-EventLog/, /Measure-(Command|Object)/, /Move-Item(Property)?/, new RegExp('New-(Alias|Event|EventLog|Item(Property)?|Module|ModuleManifest|Object|PSDrive|PSSession|PSSessionConfigurationFile' + '|PSSessionOption|PSTransportOption|Service|TimeSpan|Variable|WebServiceProxy|WinEvent)'), /Out-(Default|File|GridView|Host|Null|Printer|String)/, /Pause/, /(Pop|Push)-Location/, /Read-Host/, /Receive-(Job|PSSession)/, /Register-(EngineEvent|ObjectEvent|PSSessionConfiguration|WmiEvent)/, /Remove-(Computer|Event|EventLog|Item(Property)?|Job|Module|PSBreakpoint|PSDrive|PSSession|PSSnapin|TypeData|Variable|WmiObject)/, /Rename-(Computer|Item(Property)?)/, /Reset-ComputerMachinePassword/, /Resolve-Path/, /Restart-(Computer|Service)/, /Restore-Computer/, /Resume-(Job|Service)/, /Save-Help/, /Select-(Object|String|Xml)/, /Send-MailMessage/, new RegExp('Set-(Acl|Alias|AuthenticodeSignature|Content|Date|ExecutionPolicy|Item(Property)?|Location|PSBreakpoint|PSDebug' + '|PSSessionConfiguration|Service|StrictMode|TraceSource|Variable|WmiInstance)'), /Show-(Command|ControlPanelItem|EventLog)/, /Sort-Object/, /Split-Path/, /Start-(Job|Process|Service|Sleep|Transaction|Transcript)/, /Stop-(Computer|Job|Process|Service|Transcript)/, /Suspend-(Job|Service)/, /TabExpansion2/, /Tee-Object/, /Test-(ComputerSecureChannel|Connection|ModuleManifest|Path|PSSessionConfigurationFile)/, /Trace-Command/, /Unblock-File/, /Undo-Transaction/, /Unregister-(Event|PSSessionConfiguration)/, /Update-(FormatData|Help|List|TypeData)/, /Use-Transaction/, /Wait-(Event|Job|Process)/, /Where-Object/, /Write-(Debug|Error|EventLog|Host|Output|Progress|Verbose|Warning)/, /cd|help|mkdir|more|oss|prompt/, /ac|asnp|cat|cd|chdir|clc|clear|clhy|cli|clp|cls|clv|cnsn|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|dnsn|ebp/, /echo|epal|epcsv|epsn|erase|etsn|exsn|fc|fl|foreach|ft|fw|gal|gbp|gc|gci|gcm|gcs|gdr|ghy|gi|gjb|gl|gm|gmo|gp|gps/, /group|gsn|gsnp|gsv|gu|gv|gwmi|h|history|icm|iex|ihy|ii|ipal|ipcsv|ipmo|ipsn|irm|ise|iwmi|iwr|kill|lp|ls|man|md/, /measure|mi|mount|move|mp|mv|nal|ndr|ni|nmo|npssc|nsn|nv|ogv|oh|popd|ps|pushd|pwd|r|rbp|rcjb|rcsn|rd|rdr|ren|ri/, /rjb|rm|rmdir|rmo|rni|rnp|rp|rsn|rsnp|rujb|rv|rvpa|rwmi|sajb|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls/, /sort|sp|spjb|spps|spsv|start|sujb|sv|swmi|tee|trcm|type|where|wjb|write/ ], { prefix: '', suffix: '' }); var variableBuiltins = buildRegexp([ /[$?^_]|Args|ConfirmPreference|ConsoleFileName|DebugPreference|Error|ErrorActionPreference|ErrorView|ExecutionContext/, /FormatEnumerationLimit|Home|Host|Input|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount/, /MaximumHistoryCount|MaximumVariableCount|MyInvocation|NestedPromptLevel|OutputEncoding|Pid|Profile|ProgressPreference/, /PSBoundParameters|PSCommandPath|PSCulture|PSDefaultParameterValues|PSEmailServer|PSHome|PSScriptRoot|PSSessionApplicationName/, /PSSessionConfigurationName|PSSessionOption|PSUICulture|PSVersionTable|Pwd|ShellId|StackTrace|VerbosePreference/, /WarningPreference|WhatIfPreference/, /Event|EventArgs|EventSubscriber|Sender/, /Matches|Ofs|ForEach|LastExitCode|PSCmdlet|PSItem|PSSenderInfo|This/, /true|false|null/ ], { prefix: '\\$', suffix: '' }); var builtins = buildRegexp([symbolBuiltins, namedBuiltins, variableBuiltins], { suffix: notCharacterOrDash }); var grammar = { keyword: keywords, number: numbers, operator: operators, builtin: builtins, punctuation: punctuation, identifier: identifiers }; // tokenizers function tokenBase(stream, state) { // Handle Comments //var ch = stream.peek(); var parent = state.returnStack[state.returnStack.length - 1]; if (parent && parent.shouldReturnFrom(state)) { state.tokenize = parent.tokenize; state.returnStack.pop(); return state.tokenize(stream, state); } if (stream.eatSpace()) { return null; } if (stream.eat('(')) { state.bracketNesting += 1; return 'punctuation'; } if (stream.eat(')')) { state.bracketNesting -= 1; return 'punctuation'; } for (var key in grammar) { if (stream.match(grammar[key])) { return key; } } var ch = stream.next(); // single-quote string if (ch === "'") { return tokenSingleQuoteString(stream, state); } if (ch === '$') { return tokenVariable(stream, state); } // double-quote string if (ch === '"') { return tokenDoubleQuoteString(stream, state); } if (ch === '<' && stream.eat('#')) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (ch === '#') { stream.skipToEnd(); return 'comment'; } if (ch === '@') { var quoteMatch = stream.eat(/["']/); if (quoteMatch && stream.eol()) { state.tokenize = tokenMultiString; state.startQuote = quoteMatch[0]; return tokenMultiString(stream, state); } else if (stream.eol()) { return 'error'; } else if (stream.peek().match(/[({]/)) { return 'punctuation'; } else if (stream.peek().match(varNames)) { // splatted variable return tokenVariable(stream, state); } } return 'error'; } function tokenSingleQuoteString(stream, state) { var ch; while ((ch = stream.peek()) != null) { stream.next(); if (ch === "'" && !stream.eat("'")) { state.tokenize = tokenBase; return 'string'; } } return 'error'; } function tokenDoubleQuoteString(stream, state) { var ch; while ((ch = stream.peek()) != null) { if (ch === '$') { state.tokenize = tokenStringInterpolation; return 'string'; } stream.next(); if (ch === '`') { stream.next(); continue; } if (ch === '"' && !stream.eat('"')) { state.tokenize = tokenBase; return 'string'; } } return 'error'; } function tokenStringInterpolation(stream, state) { return tokenInterpolation(stream, state, tokenDoubleQuoteString); } function tokenMultiStringReturn(stream, state) { state.tokenize = tokenMultiString; state.startQuote = '"' return tokenMultiString(stream, state); } function tokenHereStringInterpolation(stream, state) { return tokenInterpolation(stream, state, tokenMultiStringReturn); } function tokenInterpolation(stream, state, parentTokenize) { if (stream.match('$(')) { var savedBracketNesting = state.bracketNesting; state.returnStack.push({ /*jshint loopfunc:true */ shouldReturnFrom: function(state) { return state.bracketNesting === savedBracketNesting; }, tokenize: parentTokenize }); state.tokenize = tokenBase; state.bracketNesting += 1; return 'punctuation'; } else { stream.next(); state.returnStack.push({ shouldReturnFrom: function() { return true; }, tokenize: parentTokenize }); state.tokenize = tokenVariable; return state.tokenize(stream, state); } } function tokenComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (maybeEnd && ch == '>') { state.tokenize = tokenBase; break; } maybeEnd = (ch === '#'); } return 'comment'; } function tokenVariable(stream, state) { var ch = stream.peek(); if (stream.eat('{')) { state.tokenize = tokenVariableWithBraces; return tokenVariableWithBraces(stream, state); } else if (ch != undefined && ch.match(varNames)) { stream.eatWhile(varNames); state.tokenize = tokenBase; return 'variable-2'; } else { state.tokenize = tokenBase; return 'error'; } } function tokenVariableWithBraces(stream, state) { var ch; while ((ch = stream.next()) != null) { if (ch === '}') { state.tokenize = tokenBase; break; } } return 'variable-2'; } function tokenMultiString(stream, state) { var quote = state.startQuote; if (stream.sol() && stream.match(new RegExp(quote + '@'))) { state.tokenize = tokenBase; } else if (quote === '"') { while (!stream.eol()) { var ch = stream.peek(); if (ch === '$') { state.tokenize = tokenHereStringInterpolation; return 'string'; } stream.next(); if (ch === '`') { stream.next(); } } } else { stream.skipToEnd(); } return 'string'; } var external = { startState: function() { return { returnStack: [], bracketNesting: 0, tokenize: tokenBase }; }, token: function(stream, state) { return state.tokenize(stream, state); }, blockCommentStart: '<#', blockCommentEnd: '#>', lineComment: '#', fold: 'brace' }; return external; }); CodeMirror.defineMIME('application/x-powershell', 'powershell'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/powershell/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "powershell"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } function forEach(arr, f) { for (var i = 0; i < arr.length; i++) f(arr[i], i) } MT('comment', '[number 1][comment # A]'); MT('comment_multiline', '[number 1][comment <#]', '[comment ABC]', '[comment #>][number 2]'); forEach([ '0', '1234', '12kb', '12mb', '12Gb', '12Tb', '12PB', '12L', '12D', '12lkb', '12dtb', '1.234', '1.234e56', '1.', '1.e2', '.2', '.2e34', '1.2MB', '1.kb', '.1dTB', '1.e1gb', '.2', '.2e34', '0x1', '0xabcdef', '0x3tb', '0xelmb' ], function(number) { MT("number_" + number, "[number " + number + "]"); }); MT('string_literal_escaping', "[string 'a''']"); MT('string_literal_variable', "[string 'a $x']"); MT('string_escaping_1', '[string "a `""]'); MT('string_escaping_2', '[string "a """]'); MT('string_variable_escaping', '[string "a `$x"]'); MT('string_variable', '[string "a ][variable-2 $x][string b"]'); MT('string_variable_spaces', '[string "a ][variable-2 ${x y}][string b"]'); MT('string_expression', '[string "a ][punctuation $(][variable-2 $x][operator +][number 3][punctuation )][string b"]'); MT('string_expression_nested', '[string "A][punctuation $(][string "a][punctuation $(][string "w"][punctuation )][string b"][punctuation )][string B"]'); MT('string_heredoc', '[string @"]', '[string abc]', '[string "@]'); MT('string_heredoc_quotes', '[string @"]', '[string abc "\']', '[string "@]'); MT('string_heredoc_variable', '[string @"]', '[string a ][variable-2 $x][string b]', '[string "@]'); MT('string_heredoc_nested_string', '[string @"]', '[string a][punctuation $(][string "w"][punctuation )][string b]', '[string "@]'); MT('string_heredoc_literal_quotes', "[string @']", '[string abc "\']', "[string '@]"); MT('array', "[punctuation @(][string 'a'][punctuation ,][string 'b'][punctuation )]"); MT('hash', "[punctuation @{][string 'key'][operator :][string 'value'][punctuation }]"); MT('variable', "[variable-2 $test]"); MT('variable_global', "[variable-2 $global:test]"); MT('variable_spaces', "[variable-2 ${test test}]"); MT('operator_splat', "[variable-2 @x]"); MT('variable_builtin', "[builtin $ErrorActionPreference]"); MT('variable_builtin_symbols', "[builtin $$]"); MT('operator', "[operator +]"); MT('operator_unary', "[operator +][number 3]"); MT('operator_long', "[operator -match]"); forEach([ '(', ')', '[[', ']]', '{', '}', ',', '`', ';', '.', '\\' ], function(punctuation) { MT("punctuation_" + punctuation.replace(/^[\[\]]/,''), "[punctuation " + punctuation + "]"); }); MT('keyword', "[keyword if]"); MT('call_builtin', "[builtin Get-ChildItem]"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/properties/index.html ================================================ CodeMirror: Properties files mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Properties files

    Properties files mode

    MIME types defined: text/x-properties, text/x-ini.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/properties/properties.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("properties", function() { return { token: function(stream, state) { var sol = stream.sol() || state.afterSection; var eol = stream.eol(); state.afterSection = false; if (sol) { if (state.nextMultiline) { state.inMultiline = true; state.nextMultiline = false; } else { state.position = "def"; } } if (eol && ! state.nextMultiline) { state.inMultiline = false; state.position = "def"; } if (sol) { while(stream.eatSpace()) {} } var ch = stream.next(); if (sol && (ch === "#" || ch === "!" || ch === ";")) { state.position = "comment"; stream.skipToEnd(); return "comment"; } else if (sol && ch === "[") { state.afterSection = true; stream.skipTo("]"); stream.eat("]"); return "header"; } else if (ch === "=" || ch === ":") { state.position = "quote"; return null; } else if (ch === "\\" && state.position === "quote") { if (stream.eol()) { // end of line? // Multiline value state.nextMultiline = true; } } return state.position; }, startState: function() { return { position : "def", // Current position, "def", "quote" or "comment" nextMultiline : false, // Is the next line multiline value inMultiline : false, // Is the current line a multiline value afterSection : false // Did we just open a section }; } }; }); CodeMirror.defineMIME("text/x-properties", "properties"); CodeMirror.defineMIME("text/x-ini", "properties"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/protobuf/index.html ================================================ CodeMirror: ProtoBuf mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • ProtoBuf

    ProtoBuf mode

    MIME types defined: text/x-protobuf.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/protobuf/protobuf.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); }; var keywordArray = [ "package", "message", "import", "syntax", "required", "optional", "repeated", "reserved", "default", "extensions", "packed", "bool", "bytes", "double", "enum", "float", "string", "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", "option", "service", "rpc", "returns" ]; var keywords = wordRegexp(keywordArray); CodeMirror.registerHelper("hintWords", "protobuf", keywordArray); var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*"); function tokenBase(stream) { // whitespaces if (stream.eatSpace()) return null; // Handle one line Comments if (stream.match("//")) { stream.skipToEnd(); return "comment"; } // Handle Number Literals if (stream.match(/^[0-9\.+-]/, false)) { if (stream.match(/^[+-]?0x[0-9a-fA-F]+/)) return "number"; if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?/)) return "number"; if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?/)) return "number"; } // Handle Strings if (stream.match(/^"([^"]|(""))*"/)) { return "string"; } if (stream.match(/^'([^']|(''))*'/)) { return "string"; } // Handle words if (stream.match(keywords)) { return "keyword"; } if (stream.match(identifiers)) { return "variable"; } ; // Handle non-detected items stream.next(); return null; }; CodeMirror.defineMode("protobuf", function() { return { token: tokenBase, fold: "brace" }; }); CodeMirror.defineMIME("text/x-protobuf", "protobuf"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pug/index.html ================================================ CodeMirror: Pug Templating Mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Pug Templating Mode

    Pug Templating Mode

    The Pug Templating Mode

    Created by Forbes Lindesay. Managed as part of a Brackets extension at https://github.com/ForbesLindesay/jade-brackets.

    MIME type defined: text/x-pug, text/x-jade.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/pug/pug.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../javascript/javascript"), require("../css/css"), require("../htmlmixed/htmlmixed")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../javascript/javascript", "../css/css", "../htmlmixed/htmlmixed"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("pug", function (config) { // token types var KEYWORD = 'keyword'; var DOCTYPE = 'meta'; var ID = 'builtin'; var CLASS = 'qualifier'; var ATTRS_NEST = { '{': '}', '(': ')', '[': ']' }; var jsMode = CodeMirror.getMode(config, 'javascript'); function State() { this.javaScriptLine = false; this.javaScriptLineExcludesColon = false; this.javaScriptArguments = false; this.javaScriptArgumentsDepth = 0; this.isInterpolating = false; this.interpolationNesting = 0; this.jsState = CodeMirror.startState(jsMode); this.restOfLine = ''; this.isIncludeFiltered = false; this.isEach = false; this.lastTag = ''; this.scriptType = ''; // Attributes Mode this.isAttrs = false; this.attrsNest = []; this.inAttributeName = true; this.attributeIsType = false; this.attrValue = ''; // Indented Mode this.indentOf = Infinity; this.indentToken = ''; this.innerMode = null; this.innerState = null; this.innerModeForLine = false; } /** * Safely copy a state * * @return {State} */ State.prototype.copy = function () { var res = new State(); res.javaScriptLine = this.javaScriptLine; res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon; res.javaScriptArguments = this.javaScriptArguments; res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth; res.isInterpolating = this.isInterpolating; res.interpolationNesting = this.interpolationNesting; res.jsState = CodeMirror.copyState(jsMode, this.jsState); res.innerMode = this.innerMode; if (this.innerMode && this.innerState) { res.innerState = CodeMirror.copyState(this.innerMode, this.innerState); } res.restOfLine = this.restOfLine; res.isIncludeFiltered = this.isIncludeFiltered; res.isEach = this.isEach; res.lastTag = this.lastTag; res.scriptType = this.scriptType; res.isAttrs = this.isAttrs; res.attrsNest = this.attrsNest.slice(); res.inAttributeName = this.inAttributeName; res.attributeIsType = this.attributeIsType; res.attrValue = this.attrValue; res.indentOf = this.indentOf; res.indentToken = this.indentToken; res.innerModeForLine = this.innerModeForLine; return res; }; function javaScript(stream, state) { if (stream.sol()) { // if javaScriptLine was set at end of line, ignore it state.javaScriptLine = false; state.javaScriptLineExcludesColon = false; } if (state.javaScriptLine) { if (state.javaScriptLineExcludesColon && stream.peek() === ':') { state.javaScriptLine = false; state.javaScriptLineExcludesColon = false; return; } var tok = jsMode.token(stream, state.jsState); if (stream.eol()) state.javaScriptLine = false; return tok || true; } } function javaScriptArguments(stream, state) { if (state.javaScriptArguments) { if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') { state.javaScriptArguments = false; return; } if (stream.peek() === '(') { state.javaScriptArgumentsDepth++; } else if (stream.peek() === ')') { state.javaScriptArgumentsDepth--; } if (state.javaScriptArgumentsDepth === 0) { state.javaScriptArguments = false; return; } var tok = jsMode.token(stream, state.jsState); return tok || true; } } function yieldStatement(stream) { if (stream.match(/^yield\b/)) { return 'keyword'; } } function doctype(stream) { if (stream.match(/^(?:doctype) *([^\n]+)?/)) { return DOCTYPE; } } function interpolation(stream, state) { if (stream.match('#{')) { state.isInterpolating = true; state.interpolationNesting = 0; return 'punctuation'; } } function interpolationContinued(stream, state) { if (state.isInterpolating) { if (stream.peek() === '}') { state.interpolationNesting--; if (state.interpolationNesting < 0) { stream.next(); state.isInterpolating = false; return 'punctuation'; } } else if (stream.peek() === '{') { state.interpolationNesting++; } return jsMode.token(stream, state.jsState) || true; } } function caseStatement(stream, state) { if (stream.match(/^case\b/)) { state.javaScriptLine = true; return KEYWORD; } } function when(stream, state) { if (stream.match(/^when\b/)) { state.javaScriptLine = true; state.javaScriptLineExcludesColon = true; return KEYWORD; } } function defaultStatement(stream) { if (stream.match(/^default\b/)) { return KEYWORD; } } function extendsStatement(stream, state) { if (stream.match(/^extends?\b/)) { state.restOfLine = 'string'; return KEYWORD; } } function append(stream, state) { if (stream.match(/^append\b/)) { state.restOfLine = 'variable'; return KEYWORD; } } function prepend(stream, state) { if (stream.match(/^prepend\b/)) { state.restOfLine = 'variable'; return KEYWORD; } } function block(stream, state) { if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) { state.restOfLine = 'variable'; return KEYWORD; } } function include(stream, state) { if (stream.match(/^include\b/)) { state.restOfLine = 'string'; return KEYWORD; } } function includeFiltered(stream, state) { if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) { state.isIncludeFiltered = true; return KEYWORD; } } function includeFilteredContinued(stream, state) { if (state.isIncludeFiltered) { var tok = filter(stream, state); state.isIncludeFiltered = false; state.restOfLine = 'string'; return tok; } } function mixin(stream, state) { if (stream.match(/^mixin\b/)) { state.javaScriptLine = true; return KEYWORD; } } function call(stream, state) { if (stream.match(/^\+([-\w]+)/)) { if (!stream.match(/^\( *[-\w]+ *=/, false)) { state.javaScriptArguments = true; state.javaScriptArgumentsDepth = 0; } return 'variable'; } if (stream.match('+#{', false)) { stream.next(); state.mixinCallAfter = true; return interpolation(stream, state); } } function callArguments(stream, state) { if (state.mixinCallAfter) { state.mixinCallAfter = false; if (!stream.match(/^\( *[-\w]+ *=/, false)) { state.javaScriptArguments = true; state.javaScriptArgumentsDepth = 0; } return true; } } function conditional(stream, state) { if (stream.match(/^(if|unless|else if|else)\b/)) { state.javaScriptLine = true; return KEYWORD; } } function each(stream, state) { if (stream.match(/^(- *)?(each|for)\b/)) { state.isEach = true; return KEYWORD; } } function eachContinued(stream, state) { if (state.isEach) { if (stream.match(/^ in\b/)) { state.javaScriptLine = true; state.isEach = false; return KEYWORD; } else if (stream.sol() || stream.eol()) { state.isEach = false; } else if (stream.next()) { while (!stream.match(/^ in\b/, false) && stream.next()); return 'variable'; } } } function whileStatement(stream, state) { if (stream.match(/^while\b/)) { state.javaScriptLine = true; return KEYWORD; } } function tag(stream, state) { var captures; if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) { state.lastTag = captures[1].toLowerCase(); if (state.lastTag === 'script') { state.scriptType = 'application/javascript'; } return 'tag'; } } function filter(stream, state) { if (stream.match(/^:([\w\-]+)/)) { var innerMode; if (config && config.innerModes) { innerMode = config.innerModes(stream.current().substring(1)); } if (!innerMode) { innerMode = stream.current().substring(1); } if (typeof innerMode === 'string') { innerMode = CodeMirror.getMode(config, innerMode); } setInnerMode(stream, state, innerMode); return 'atom'; } } function code(stream, state) { if (stream.match(/^(!?=|-)/)) { state.javaScriptLine = true; return 'punctuation'; } } function id(stream) { if (stream.match(/^#([\w-]+)/)) { return ID; } } function className(stream) { if (stream.match(/^\.([\w-]+)/)) { return CLASS; } } function attrs(stream, state) { if (stream.peek() == '(') { stream.next(); state.isAttrs = true; state.attrsNest = []; state.inAttributeName = true; state.attrValue = ''; state.attributeIsType = false; return 'punctuation'; } } function attrsContinued(stream, state) { if (state.isAttrs) { if (ATTRS_NEST[stream.peek()]) { state.attrsNest.push(ATTRS_NEST[stream.peek()]); } if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) { state.attrsNest.pop(); } else if (stream.eat(')')) { state.isAttrs = false; return 'punctuation'; } if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) { if (stream.peek() === '=' || stream.peek() === '!') { state.inAttributeName = false; state.jsState = CodeMirror.startState(jsMode); if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') { state.attributeIsType = true; } else { state.attributeIsType = false; } } return 'attribute'; } var tok = jsMode.token(stream, state.jsState); if (state.attributeIsType && tok === 'string') { state.scriptType = stream.current().toString(); } if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) { try { Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, '')); state.inAttributeName = true; state.attrValue = ''; stream.backUp(stream.current().length); return attrsContinued(stream, state); } catch (ex) { //not the end of an attribute } } state.attrValue += stream.current(); return tok || true; } } function attributesBlock(stream, state) { if (stream.match(/^&attributes\b/)) { state.javaScriptArguments = true; state.javaScriptArgumentsDepth = 0; return 'keyword'; } } function indent(stream) { if (stream.sol() && stream.eatSpace()) { return 'indent'; } } function comment(stream, state) { if (stream.match(/^ *\/\/(-)?([^\n]*)/)) { state.indentOf = stream.indentation(); state.indentToken = 'comment'; return 'comment'; } } function colon(stream) { if (stream.match(/^: */)) { return 'colon'; } } function text(stream, state) { if (stream.match(/^(?:\| ?| )([^\n]+)/)) { return 'string'; } if (stream.match(/^(<[^\n]*)/, false)) { // html string setInnerMode(stream, state, 'htmlmixed'); state.innerModeForLine = true; return innerMode(stream, state, true); } } function dot(stream, state) { if (stream.eat('.')) { var innerMode = null; if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) { innerMode = state.scriptType.toLowerCase().replace(/"|'/g, ''); } else if (state.lastTag === 'style') { innerMode = 'css'; } setInnerMode(stream, state, innerMode); return 'dot'; } } function fail(stream) { stream.next(); return null; } function setInnerMode(stream, state, mode) { mode = CodeMirror.mimeModes[mode] || mode; mode = config.innerModes ? config.innerModes(mode) || mode : mode; mode = CodeMirror.mimeModes[mode] || mode; mode = CodeMirror.getMode(config, mode); state.indentOf = stream.indentation(); if (mode && mode.name !== 'null') { state.innerMode = mode; } else { state.indentToken = 'string'; } } function innerMode(stream, state, force) { if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) { if (state.innerMode) { if (!state.innerState) { state.innerState = state.innerMode.startState ? CodeMirror.startState(state.innerMode, stream.indentation()) : {}; } return stream.hideFirstChars(state.indentOf + 2, function () { return state.innerMode.token(stream, state.innerState) || true; }); } else { stream.skipToEnd(); return state.indentToken; } } else if (stream.sol()) { state.indentOf = Infinity; state.indentToken = null; state.innerMode = null; state.innerState = null; } } function restOfLine(stream, state) { if (stream.sol()) { // if restOfLine was set at end of line, ignore it state.restOfLine = ''; } if (state.restOfLine) { stream.skipToEnd(); var tok = state.restOfLine; state.restOfLine = ''; return tok; } } function startState() { return new State(); } function copyState(state) { return state.copy(); } /** * Get the next token in the stream * * @param {Stream} stream * @param {State} state */ function nextToken(stream, state) { var tok = innerMode(stream, state) || restOfLine(stream, state) || interpolationContinued(stream, state) || includeFilteredContinued(stream, state) || eachContinued(stream, state) || attrsContinued(stream, state) || javaScript(stream, state) || javaScriptArguments(stream, state) || callArguments(stream, state) || yieldStatement(stream) || doctype(stream) || interpolation(stream, state) || caseStatement(stream, state) || when(stream, state) || defaultStatement(stream) || extendsStatement(stream, state) || append(stream, state) || prepend(stream, state) || block(stream, state) || include(stream, state) || includeFiltered(stream, state) || mixin(stream, state) || call(stream, state) || conditional(stream, state) || each(stream, state) || whileStatement(stream, state) || tag(stream, state) || filter(stream, state) || code(stream, state) || id(stream) || className(stream) || attrs(stream, state) || attributesBlock(stream, state) || indent(stream) || text(stream, state) || comment(stream, state) || colon(stream) || dot(stream, state) || fail(stream); return tok === true ? null : tok; } return { startState: startState, copyState: copyState, token: nextToken }; }, 'javascript', 'css', 'htmlmixed'); CodeMirror.defineMIME('text/x-pug', 'pug'); CodeMirror.defineMIME('text/x-jade', 'pug'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/puppet/index.html ================================================ CodeMirror: Puppet mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Puppet

    Puppet mode

    MIME types defined: text/x-puppet.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/puppet/puppet.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("puppet", function () { // Stores the words from the define method var words = {}; // Taken, mostly, from the Puppet official variable standards regex var variable_regex = /({)?([a-z][a-z0-9_]*)?((::[a-z][a-z0-9_]*)*::)?[a-zA-Z0-9_]+(})?/; // Takes a string of words separated by spaces and adds them as // keys with the value of the first argument 'style' function define(style, string) { var split = string.split(' '); for (var i = 0; i < split.length; i++) { words[split[i]] = style; } } // Takes commonly known puppet types/words and classifies them to a style define('keyword', 'class define site node include import inherits'); define('keyword', 'case if else in and elsif default or'); define('atom', 'false true running present absent file directory undef'); define('builtin', 'action augeas burst chain computer cron destination dport exec ' + 'file filebucket group host icmp iniface interface jump k5login limit log_level ' + 'log_prefix macauthorization mailalias maillist mcx mount nagios_command ' + 'nagios_contact nagios_contactgroup nagios_host nagios_hostdependency ' + 'nagios_hostescalation nagios_hostextinfo nagios_hostgroup nagios_service ' + 'nagios_servicedependency nagios_serviceescalation nagios_serviceextinfo ' + 'nagios_servicegroup nagios_timeperiod name notify outiface package proto reject ' + 'resources router schedule scheduled_task selboolean selmodule service source ' + 'sport ssh_authorized_key sshkey stage state table tidy todest toports tosource ' + 'user vlan yumrepo zfs zone zpool'); // After finding a start of a string ('|") this function attempts to find the end; // If a variable is encountered along the way, we display it differently when it // is encapsulated in a double-quoted string. function tokenString(stream, state) { var current, prev, found_var = false; while (!stream.eol() && (current = stream.next()) != state.pending) { if (current === '$' && prev != '\\' && state.pending == '"') { found_var = true; break; } prev = current; } if (found_var) { stream.backUp(1); } if (current == state.pending) { state.continueString = false; } else { state.continueString = true; } return "string"; } // Main function function tokenize(stream, state) { // Matches one whole word var word = stream.match(/[\w]+/, false); // Matches attributes (i.e. ensure => present ; 'ensure' would be matched) var attribute = stream.match(/(\s+)?\w+\s+=>.*/, false); // Matches non-builtin resource declarations // (i.e. "apache::vhost {" or "mycustomclasss {" would be matched) var resource = stream.match(/(\s+)?[\w:_]+(\s+)?{/, false); // Matches virtual and exported resources (i.e. @@user { ; and the like) var special_resource = stream.match(/(\s+)?[@]{1,2}[\w:_]+(\s+)?{/, false); // Finally advance the stream var ch = stream.next(); // Have we found a variable? if (ch === '$') { if (stream.match(variable_regex)) { // If so, and its in a string, assign it a different color return state.continueString ? 'variable-2' : 'variable'; } // Otherwise return an invalid variable return "error"; } // Should we still be looking for the end of a string? if (state.continueString) { // If so, go through the loop again stream.backUp(1); return tokenString(stream, state); } // Are we in a definition (class, node, define)? if (state.inDefinition) { // If so, return def (i.e. for 'class myclass {' ; 'myclass' would be matched) if (stream.match(/(\s+)?[\w:_]+(\s+)?/)) { return 'def'; } // Match the rest it the next time around stream.match(/\s+{/); state.inDefinition = false; } // Are we in an 'include' statement? if (state.inInclude) { // Match and return the included class stream.match(/(\s+)?\S+(\s+)?/); state.inInclude = false; return 'def'; } // Do we just have a function on our hands? // In 'ensure_resource("myclass")', 'ensure_resource' is matched if (stream.match(/(\s+)?\w+\(/)) { stream.backUp(1); return 'def'; } // Have we matched the prior attribute regex? if (attribute) { stream.match(/(\s+)?\w+/); return 'tag'; } // Do we have Puppet specific words? if (word && words.hasOwnProperty(word)) { // Negates the initial next() stream.backUp(1); // rs move the stream stream.match(/[\w]+/); // We want to process these words differently // do to the importance they have in Puppet if (stream.match(/\s+\S+\s+{/, false)) { state.inDefinition = true; } if (word == 'include') { state.inInclude = true; } // Returns their value as state in the prior define methods return words[word]; } // Is there a match on a reference? if (/(^|\s+)[A-Z][\w:_]+/.test(word)) { // Negate the next() stream.backUp(1); // Match the full reference stream.match(/(^|\s+)[A-Z][\w:_]+/); return 'def'; } // Have we matched the prior resource regex? if (resource) { stream.match(/(\s+)?[\w:_]+/); return 'def'; } // Have we matched the prior special_resource regex? if (special_resource) { stream.match(/(\s+)?[@]{1,2}/); return 'special'; } // Match all the comments. All of them. if (ch == "#") { stream.skipToEnd(); return "comment"; } // Have we found a string? if (ch == "'" || ch == '"') { // Store the type (single or double) state.pending = ch; // Perform the looping function to find the end return tokenString(stream, state); } // Match all the brackets if (ch == '{' || ch == '}') { return 'bracket'; } // Match characters that we are going to assume // are trying to be regex if (ch == '/') { stream.match(/^[^\/]*\//); return 'variable-3'; } // Match all the numbers if (ch.match(/[0-9]/)) { stream.eatWhile(/[0-9]+/); return 'number'; } // Match the '=' and '=>' operators if (ch == '=') { if (stream.peek() == '>') { stream.next(); } return "operator"; } // Keep advancing through all the rest stream.eatWhile(/[\w-]/); // Return a blank line for everything else return null; } // Start it all return { startState: function () { var state = {}; state.inDefinition = false; state.inInclude = false; state.continueString = false; state.pending = false; return state; }, token: function (stream, state) { // Strip the spaces, but regex will account for them eitherway if (stream.eatSpace()) return null; // Go through the main process return tokenize(stream, state); } }; }); CodeMirror.defineMIME("text/x-puppet", "puppet"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/python/index.html ================================================ CodeMirror: Python mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Python

    Python mode

    Cython mode

    Configuration Options for Python mode:

    • version - 2/3 - The version of Python to recognize. Default is 3.
    • singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
    • hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.

    Advanced Configuration Options:

    Useful for superset of python syntax like Enthought enaml, IPython magics and questionmark help

    • singleOperators - RegEx - Regular Expression for single operator matching, default :
      ^[\\+\\-\\*/%&|\\^~<>!]
      including
      @
      on Python 3
    • singleDelimiters - RegEx - Regular Expression for single delimiter matching, default :
      ^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]
    • doubleOperators - RegEx - Regular Expression for double operators matching, default :
      ^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))
    • doubleDelimiters - RegEx - Regular Expression for double delimiters matching, default :
      ^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))
    • tripleDelimiters - RegEx - Regular Expression for triple delimiters matching, default :
      ^((//=)|(>>=)|(<<=)|(\\*\\*=))
    • identifiers - RegEx - Regular Expression for identifier, default :
      ^[_A-Za-z][_A-Za-z0-9]*
      on Python 2 and
      ^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*
      on Python 3.
    • extra_keywords - list of string - List of extra words ton consider as keywords
    • extra_builtins - list of string - List of extra words ton consider as builtins

    MIME types defined: text/x-python and text/x-cython.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/python/python.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); } var wordOperators = wordRegexp(["and", "or", "not", "is"]); var commonKeywords = ["as", "assert", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "lambda", "pass", "raise", "return", "try", "while", "with", "yield", "in", "False", "True"]; var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", "enumerate", "eval", "filter", "float", "format", "frozenset", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", "input", "int", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", "next", "object", "oct", "open", "ord", "pow", "property", "range", "repr", "reversed", "round", "set", "setattr", "slice", "sorted", "staticmethod", "str", "sum", "super", "tuple", "type", "vars", "zip", "__import__", "NotImplemented", "Ellipsis", "__debug__"]; CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins).concat(["exec", "print"])); function top(state) { return state.scopes[state.scopes.length - 1]; } CodeMirror.defineMode("python", function(conf, parserConf) { var ERRORCLASS = "error"; var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.\\]/; // (Backwards-compatibility with old, cumbersome config system) var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters, parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/] for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1) var hangingIndent = parserConf.hangingIndent || conf.indentUnit; var myKeywords = commonKeywords, myBuiltins = commonBuiltins; if (parserConf.extra_keywords != undefined) myKeywords = myKeywords.concat(parserConf.extra_keywords); if (parserConf.extra_builtins != undefined) myBuiltins = myBuiltins.concat(parserConf.extra_builtins); var py3 = !(parserConf.version && Number(parserConf.version) < 3) if (py3) { // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; myKeywords = myKeywords.concat(["nonlocal", "None", "aiter", "anext", "async", "await", "breakpoint", "match", "case"]); myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]); var stringPrefixes = new RegExp("^(([rbuf]|(br)|(rb)|(fr)|(rf))?('{3}|\"{3}|['\"]))", "i"); } else { var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; myKeywords = myKeywords.concat(["exec", "print"]); myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile", "file", "intern", "long", "raw_input", "reduce", "reload", "unichr", "unicode", "xrange", "None"]); var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); } var keywords = wordRegexp(myKeywords); var builtins = wordRegexp(myBuiltins); // tokenizers function tokenBase(stream, state) { var sol = stream.sol() && state.lastToken != "\\" if (sol) state.indent = stream.indentation() // Handle scope changes if (sol && top(state).type == "py") { var scopeOffset = top(state).offset; if (stream.eatSpace()) { var lineOffset = stream.indentation(); if (lineOffset > scopeOffset) pushPyScope(state); else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != "#") state.errorToken = true; return null; } else { var style = tokenBaseInner(stream, state); if (scopeOffset > 0 && dedent(stream, state)) style += " " + ERRORCLASS; return style; } } return tokenBaseInner(stream, state); } function tokenBaseInner(stream, state, inFormat) { if (stream.eatSpace()) return null; // Handle Comments if (!inFormat && stream.match(/^#.*/)) return "comment"; // Handle Number Literals if (stream.match(/^[0-9\.]/, false)) { var floatLiteral = false; // Floats if (stream.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } if (stream.match(/^[\d_]+\.\d*/)) { floatLiteral = true; } if (stream.match(/^\.\d+/)) { floatLiteral = true; } if (floatLiteral) { // Float literals may be "imaginary" stream.eat(/J/i); return "number"; } // Integers var intLiteral = false; // Hex if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true; // Binary if (stream.match(/^0b[01_]+/i)) intLiteral = true; // Octal if (stream.match(/^0o[0-7_]+/i)) intLiteral = true; // Decimal if (stream.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)) { // Decimal literals may be "imaginary" stream.eat(/J/i); // TODO - Can you have imaginary longs? intLiteral = true; } // Zero by itself with no other piece of number. if (stream.match(/^0(?![\dx])/i)) intLiteral = true; if (intLiteral) { // Integer literals may be "long" stream.eat(/L/i); return "number"; } } // Handle Strings if (stream.match(stringPrefixes)) { var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1; if (!isFmtString) { state.tokenize = tokenStringFactory(stream.current(), state.tokenize); return state.tokenize(stream, state); } else { state.tokenize = formatStringFactory(stream.current(), state.tokenize); return state.tokenize(stream, state); } } for (var i = 0; i < operators.length; i++) if (stream.match(operators[i])) return "operator" if (stream.match(delimiters)) return "punctuation"; if (state.lastToken == "." && stream.match(identifiers)) return "property"; if (stream.match(keywords) || stream.match(wordOperators)) return "keyword"; if (stream.match(builtins)) return "builtin"; if (stream.match(/^(self|cls)\b/)) return "variable-2"; if (stream.match(identifiers)) { if (state.lastToken == "def" || state.lastToken == "class") return "def"; return "variable"; } // Handle non-detected items stream.next(); return inFormat ? null :ERRORCLASS; } function formatStringFactory(delimiter, tokenOuter) { while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) delimiter = delimiter.substr(1); var singleline = delimiter.length == 1; var OUTCLASS = "string"; function tokenNestedExpr(depth) { return function(stream, state) { var inner = tokenBaseInner(stream, state, true) if (inner == "punctuation") { if (stream.current() == "{") { state.tokenize = tokenNestedExpr(depth + 1) } else if (stream.current() == "}") { if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1) else state.tokenize = tokenString } } return inner } } function tokenString(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\{\}\\]/); if (stream.eat("\\")) { stream.next(); if (singleline && stream.eol()) return OUTCLASS; } else if (stream.match(delimiter)) { state.tokenize = tokenOuter; return OUTCLASS; } else if (stream.match('{{')) { // ignore {{ in f-str return OUTCLASS; } else if (stream.match('{', false)) { // switch to nested mode state.tokenize = tokenNestedExpr(0) if (stream.current()) return OUTCLASS; else return state.tokenize(stream, state) } else if (stream.match('}}')) { return OUTCLASS; } else if (stream.match('}')) { // single } in f-string is an error return ERRORCLASS; } else { stream.eat(/['"]/); } } if (singleline) { if (parserConf.singleLineStringErrors) return ERRORCLASS; else state.tokenize = tokenOuter; } return OUTCLASS; } tokenString.isString = true; return tokenString; } function tokenStringFactory(delimiter, tokenOuter) { while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) delimiter = delimiter.substr(1); var singleline = delimiter.length == 1; var OUTCLASS = "string"; function tokenString(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\\]/); if (stream.eat("\\")) { stream.next(); if (singleline && stream.eol()) return OUTCLASS; } else if (stream.match(delimiter)) { state.tokenize = tokenOuter; return OUTCLASS; } else { stream.eat(/['"]/); } } if (singleline) { if (parserConf.singleLineStringErrors) return ERRORCLASS; else state.tokenize = tokenOuter; } return OUTCLASS; } tokenString.isString = true; return tokenString; } function pushPyScope(state) { while (top(state).type != "py") state.scopes.pop() state.scopes.push({offset: top(state).offset + conf.indentUnit, type: "py", align: null}) } function pushBracketScope(stream, state, type) { var align = stream.match(/^[\s\[\{\(]*(?:#|$)/, false) ? null : stream.column() + 1 state.scopes.push({offset: state.indent + hangingIndent, type: type, align: align}) } function dedent(stream, state) { var indented = stream.indentation(); while (state.scopes.length > 1 && top(state).offset > indented) { if (top(state).type != "py") return true; state.scopes.pop(); } return top(state).offset != indented; } function tokenLexer(stream, state) { if (stream.sol()) { state.beginningOfLine = true; state.dedent = false; } var style = state.tokenize(stream, state); var current = stream.current(); // Handle decorators if (state.beginningOfLine && current == "@") return stream.match(identifiers, false) ? "meta" : py3 ? "operator" : ERRORCLASS; if (/\S/.test(current)) state.beginningOfLine = false; if ((style == "variable" || style == "builtin") && state.lastToken == "meta") style = "meta"; // Handle scope changes. if (current == "pass" || current == "return") state.dedent = true; if (current == "lambda") state.lambda = true; if (current == ":" && !state.lambda && top(state).type == "py" && stream.match(/^\s*(?:#|$)/, false)) pushPyScope(state); if (current.length == 1 && !/string|comment/.test(style)) { var delimiter_index = "[({".indexOf(current); if (delimiter_index != -1) pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); delimiter_index = "])}".indexOf(current); if (delimiter_index != -1) { if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent else return ERRORCLASS; } } if (state.dedent && stream.eol() && top(state).type == "py" && state.scopes.length > 1) state.scopes.pop(); return style; } var external = { startState: function(basecolumn) { return { tokenize: tokenBase, scopes: [{offset: basecolumn || 0, type: "py", align: null}], indent: basecolumn || 0, lastToken: null, lambda: false, dedent: 0 }; }, token: function(stream, state) { var addErr = state.errorToken; if (addErr) state.errorToken = false; var style = tokenLexer(stream, state); if (style && style != "comment") state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style; if (style == "punctuation") style = null; if (stream.eol() && state.lambda) state.lambda = false; return addErr ? style + " " + ERRORCLASS : style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase) return state.tokenize.isString ? CodeMirror.Pass : 0; var scope = top(state) var closing = scope.type == textAfter.charAt(0) || scope.type == "py" && !state.dedent && /^(else:|elif |except |finally:)/.test(textAfter) if (scope.align != null) return scope.align - (closing ? 1 : 0) else return scope.offset - (closing ? hangingIndent : 0) }, electricInput: /^\s*([\}\]\)]|else:|elif |except |finally:)$/, closeBrackets: {triples: "'\""}, lineComment: "#", fold: "indent" }; return external; }); CodeMirror.defineMIME("text/x-python", "python"); var words = function(str) { return str.split(" "); }; CodeMirror.defineMIME("text/x-cython", { name: "python", extra_keywords: words("by cdef cimport cpdef ctypedef enum except "+ "extern gil include nogil property public "+ "readonly struct union DEF IF ELIF ELSE") }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/python/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 4}, {name: "python", version: 3, singleLineStringErrors: false}); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Error, because "foobarhello" is neither a known type or property, but // property was expected (after "and"), and it should be in parentheses. MT("decoratorStartOfLine", "[meta @dec]", "[keyword def] [def function]():", " [keyword pass]"); MT("decoratorIndented", "[keyword class] [def Foo]:", " [meta @dec]", " [keyword def] [def function]():", " [keyword pass]"); MT("matmulWithSpace:", "[variable a] [operator @] [variable b]"); MT("matmulWithoutSpace:", "[variable a][operator @][variable b]"); MT("matmulSpaceBefore:", "[variable a] [operator @][variable b]"); var before_equal_sign = ["+", "-", "*", "/", "=", "!", ">", "<"]; for (var i = 0; i < before_equal_sign.length; ++i) { var c = before_equal_sign[i] MT("before_equal_sign_" + c, "[variable a] [operator " + c + "=] [variable b]"); } MT("fValidStringPrefix", "[string f'this is a]{[variable formatted]}[string string']"); MT("fValidExpressionInFString", "[string f'expression ]{[number 100][operator *][number 5]}[string string']"); MT("fInvalidFString", "[error f'this is wrong}]"); MT("fNestedFString", "[string f'expression ]{[number 100] [operator +] [string f'inner]{[number 5]}[string ']}[string string']"); MT("uValidStringPrefix", "[string u'this is an unicode string']"); MT("nestedString", "[string f']{[variable b][[ [string \"c\"] ]]}[string f'] [comment # oops]") MT("bracesInFString", "[string f']{[variable x] [operator +] {}}[string !']") MT("nestedFString", "[string f']{[variable b][[ [string f\"c\"] ]]}[string f'] [comment # oops]") MT("dontIndentTypeDecl", "[variable i]: [builtin int] [operator =] [number 32]", "[builtin print]([variable i])") MT("dedentElse", "[keyword if] [variable x]:", " [variable foo]()", "[keyword elif] [variable y]:", " [variable bar]()", "[keyword else]:", " [variable baz]()") MT("dedentElsePass", "[keyword if] [variable x]:", " [keyword pass]", "[keyword elif] [variable y]:", " [keyword pass]", "[keyword else]:", " [keyword pass]") MT("dedentElseInFunction", "[keyword def] [def foo]():", " [keyword if] [variable x]:", " [variable foo]()", " [keyword elif] [variable y]:", " [variable bar]()", " [keyword pass]", " [keyword else]:", " [variable baz]()") MT("dedentCase", "[keyword match] [variable x]:", " [keyword case] [variable y]:", " [variable foo]()") MT("dedentCasePass", "[keyword match] [variable x]:", " [keyword case] [variable y]:", " [keyword pass]") MT("dedentCaseInFunction", "[keyword def] [def foo]():", " [keyword match] [variable x]:", " [keyword case] [variable y]:", " [variable foo]()") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/q/index.html ================================================ CodeMirror: Q mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Q

    Q mode

    MIME type defined: text/x-q.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/q/q.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("q",function(config){ var indentUnit=config.indentUnit, curPunc, keywords=buildRE(["abs","acos","aj","aj0","all","and","any","asc","asin","asof","atan","attr","avg","avgs","bin","by","ceiling","cols","cor","cos","count","cov","cross","csv","cut","delete","deltas","desc","dev","differ","distinct","div","do","each","ej","enlist","eval","except","exec","exit","exp","fby","fills","first","fkeys","flip","floor","from","get","getenv","group","gtime","hclose","hcount","hdel","hopen","hsym","iasc","idesc","if","ij","in","insert","inter","inv","key","keys","last","like","list","lj","load","log","lower","lsq","ltime","ltrim","mavg","max","maxs","mcount","md5","mdev","med","meta","min","mins","mmax","mmin","mmu","mod","msum","neg","next","not","null","or","over","parse","peach","pj","plist","prd","prds","prev","prior","rand","rank","ratios","raze","read0","read1","reciprocal","reverse","rload","rotate","rsave","rtrim","save","scan","select","set","setenv","show","signum","sin","sqrt","ss","ssr","string","sublist","sum","sums","sv","system","tables","tan","til","trim","txf","type","uj","ungroup","union","update","upper","upsert","value","var","view","views","vs","wavg","where","where","while","within","wj","wj1","wsum","xasc","xbar","xcol","xcols","xdesc","xexp","xgroup","xkey","xlog","xprev","xrank"]), E=/[|/&^!+:\\\-*%$=~#;@><,?_\'\"\[\(\]\)\s{}]/; function buildRE(w){return new RegExp("^("+w.join("|")+")$");} function tokenBase(stream,state){ var sol=stream.sol(),c=stream.next(); curPunc=null; if(sol) if(c=="/") return(state.tokenize=tokenLineComment)(stream,state); else if(c=="\\"){ if(stream.eol()||/\s/.test(stream.peek())) return stream.skipToEnd(),/^\\\s*$/.test(stream.current())?(state.tokenize=tokenCommentToEOF)(stream):state.tokenize=tokenBase,"comment"; else return state.tokenize=tokenBase,"builtin"; } if(/\s/.test(c)) return stream.peek()=="/"?(stream.skipToEnd(),"comment"):"whitespace"; if(c=='"') return(state.tokenize=tokenString)(stream,state); if(c=='`') return stream.eatWhile(/[A-Za-z\d_:\/.]/),"symbol"; if(("."==c&&/\d/.test(stream.peek()))||/\d/.test(c)){ var t=null; stream.backUp(1); if(stream.match(/^\d{4}\.\d{2}(m|\.\d{2}([DT](\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)?)?)/) || stream.match(/^\d+D(\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)/) || stream.match(/^\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?/) || stream.match(/^\d+[ptuv]{1}/)) t="temporal"; else if(stream.match(/^0[NwW]{1}/) || stream.match(/^0x[\da-fA-F]*/) || stream.match(/^[01]+[b]{1}/) || stream.match(/^\d+[chijn]{1}/) || stream.match(/-?\d*(\.\d*)?(e[+\-]?\d+)?(e|f)?/)) t="number"; return(t&&(!(c=stream.peek())||E.test(c)))?t:(stream.next(),"error"); } if(/[A-Za-z]|\./.test(c)) return stream.eatWhile(/[A-Za-z._\d]/),keywords.test(stream.current())?"keyword":"variable"; if(/[|/&^!+:\\\-*%$=~#;@><\.,?_\']/.test(c)) return null; if(/[{}\(\[\]\)]/.test(c)) return null; return"error"; } function tokenLineComment(stream,state){ return stream.skipToEnd(),/\/\s*$/.test(stream.current())?(state.tokenize=tokenBlockComment)(stream,state):(state.tokenize=tokenBase),"comment"; } function tokenBlockComment(stream,state){ var f=stream.sol()&&stream.peek()=="\\"; stream.skipToEnd(); if(f&&/^\\\s*$/.test(stream.current())) state.tokenize=tokenBase; return"comment"; } function tokenCommentToEOF(stream){return stream.skipToEnd(),"comment";} function tokenString(stream,state){ var escaped=false,next,end=false; while((next=stream.next())){ if(next=="\""&&!escaped){end=true;break;} escaped=!escaped&&next=="\\"; } if(end)state.tokenize=tokenBase; return"string"; } function pushContext(state,type,col){state.context={prev:state.context,indent:state.indent,col:col,type:type};} function popContext(state){state.indent=state.context.indent;state.context=state.context.prev;} return{ startState:function(){ return{tokenize:tokenBase, context:null, indent:0, col:0}; }, token:function(stream,state){ if(stream.sol()){ if(state.context&&state.context.align==null) state.context.align=false; state.indent=stream.indentation(); } //if (stream.eatSpace()) return null; var style=state.tokenize(stream,state); if(style!="comment"&&state.context&&state.context.align==null&&state.context.type!="pattern"){ state.context.align=true; } if(curPunc=="(")pushContext(state,")",stream.column()); else if(curPunc=="[")pushContext(state,"]",stream.column()); else if(curPunc=="{")pushContext(state,"}",stream.column()); else if(/[\]\}\)]/.test(curPunc)){ while(state.context&&state.context.type=="pattern")popContext(state); if(state.context&&curPunc==state.context.type)popContext(state); } else if(curPunc=="."&&state.context&&state.context.type=="pattern")popContext(state); else if(/atom|string|variable/.test(style)&&state.context){ if(/[\}\]]/.test(state.context.type)) pushContext(state,"pattern",stream.column()); else if(state.context.type=="pattern"&&!state.context.align){ state.context.align=true; state.context.col=stream.column(); } } return style; }, indent:function(state,textAfter){ var firstChar=textAfter&&textAfter.charAt(0); var context=state.context; if(/[\]\}]/.test(firstChar)) while (context&&context.type=="pattern")context=context.prev; var closing=context&&firstChar==context.type; if(!context) return 0; else if(context.type=="pattern") return context.col; else if(context.align) return context.col+(closing?0:1); else return context.indent+(closing?0:indentUnit); } }; }); CodeMirror.defineMIME("text/x-q","q"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/r/index.html ================================================ CodeMirror: R mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • R

    R mode

    MIME types defined: text/x-rsrc.

    Development of the CodeMirror R mode was kindly sponsored by Ubalo.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/r/r.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.registerHelper("wordChars", "r", /[\w.]/); CodeMirror.defineMode("r", function(config) { function wordObj(words) { var res = {}; for (var i = 0; i < words.length; ++i) res[words[i]] = true; return res; } var commonAtoms = ["NULL", "NA", "Inf", "NaN", "NA_integer_", "NA_real_", "NA_complex_", "NA_character_", "TRUE", "FALSE"]; var commonBuiltins = ["list", "quote", "bquote", "eval", "return", "call", "parse", "deparse"]; var commonKeywords = ["if", "else", "repeat", "while", "function", "for", "in", "next", "break"]; var commonBlockKeywords = ["if", "else", "repeat", "while", "function", "for"]; CodeMirror.registerHelper("hintWords", "r", commonAtoms.concat(commonBuiltins, commonKeywords)); var atoms = wordObj(commonAtoms); var builtins = wordObj(commonBuiltins); var keywords = wordObj(commonKeywords); var blockkeywords = wordObj(commonBlockKeywords); var opChars = /[+\-*\/^<>=!&|~$:]/; var curPunc; function tokenBase(stream, state) { curPunc = null; var ch = stream.next(); if (ch == "#") { stream.skipToEnd(); return "comment"; } else if (ch == "0" && stream.eat("x")) { stream.eatWhile(/[\da-f]/i); return "number"; } else if (ch == "." && stream.eat(/\d/)) { stream.match(/\d*(?:e[+\-]?\d+)?/); return "number"; } else if (/\d/.test(ch)) { stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); return "number"; } else if (ch == "'" || ch == '"') { state.tokenize = tokenString(ch); return "string"; } else if (ch == "`") { stream.match(/[^`]+`/); return "variable-3"; } else if (ch == "." && stream.match(/.(?:[.]|\d+)/)) { return "keyword"; } else if (/[a-zA-Z\.]/.test(ch)) { stream.eatWhile(/[\w\.]/); var word = stream.current(); if (atoms.propertyIsEnumerable(word)) return "atom"; if (keywords.propertyIsEnumerable(word)) { // Block keywords start new blocks, except 'else if', which only starts // one new block for the 'if', no block for the 'else'. if (blockkeywords.propertyIsEnumerable(word) && !stream.match(/\s*if(\s+|$)/, false)) curPunc = "block"; return "keyword"; } if (builtins.propertyIsEnumerable(word)) return "builtin"; return "variable"; } else if (ch == "%") { if (stream.skipTo("%")) stream.next(); return "operator variable-2"; } else if ( (ch == "<" && stream.eat("-")) || (ch == "<" && stream.match("<-")) || (ch == "-" && stream.match(/>>?/)) ) { return "operator arrow"; } else if (ch == "=" && state.ctx.argList) { return "arg-is"; } else if (opChars.test(ch)) { if (ch == "$") return "operator dollar"; stream.eatWhile(opChars); return "operator"; } else if (/[\(\){}\[\];]/.test(ch)) { curPunc = ch; if (ch == ";") return "semi"; return null; } else { return null; } } function tokenString(quote) { return function(stream, state) { if (stream.eat("\\")) { var ch = stream.next(); if (ch == "x") stream.match(/^[a-f0-9]{2}/i); else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); return "string-2"; } else { var next; while ((next = stream.next()) != null) { if (next == quote) { state.tokenize = tokenBase; break; } if (next == "\\") { stream.backUp(1); break; } } return "string"; } }; } var ALIGN_YES = 1, ALIGN_NO = 2, BRACELESS = 4 function push(state, type, stream) { state.ctx = {type: type, indent: state.indent, flags: 0, column: stream.column(), prev: state.ctx}; } function setFlag(state, flag) { var ctx = state.ctx state.ctx = {type: ctx.type, indent: ctx.indent, flags: ctx.flags | flag, column: ctx.column, prev: ctx.prev} } function pop(state) { state.indent = state.ctx.indent; state.ctx = state.ctx.prev; } return { startState: function() { return {tokenize: tokenBase, ctx: {type: "top", indent: -config.indentUnit, flags: ALIGN_NO}, indent: 0, afterIdent: false}; }, token: function(stream, state) { if (stream.sol()) { if ((state.ctx.flags & 3) == 0) state.ctx.flags |= ALIGN_NO if (state.ctx.flags & BRACELESS) pop(state) state.indent = stream.indentation(); } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (style != "comment" && (state.ctx.flags & ALIGN_NO) == 0) setFlag(state, ALIGN_YES) if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && state.ctx.type == "block") pop(state); if (curPunc == "{") push(state, "}", stream); else if (curPunc == "(") { push(state, ")", stream); if (state.afterIdent) state.ctx.argList = true; } else if (curPunc == "[") push(state, "]", stream); else if (curPunc == "block") push(state, "block", stream); else if (curPunc == state.ctx.type) pop(state); else if (state.ctx.type == "block" && style != "comment") setFlag(state, BRACELESS) state.afterIdent = style == "variable" || style == "keyword"; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, closing = firstChar == ctx.type; if (ctx.flags & BRACELESS) ctx = ctx.prev if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); else if (ctx.flags & ALIGN_YES) return ctx.column + (closing ? 0 : 1); else return ctx.indent + (closing ? 0 : config.indentUnit); }, lineComment: "#" }; }); CodeMirror.defineMIME("text/x-rsrc", "r"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rpm/changes/index.html ================================================ CodeMirror: RPM changes mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • RPM changes

    RPM changes mode

    MIME types defined: text/x-rpm-changes.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rpm/index.html ================================================ CodeMirror: RPM changes mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • RPM

    RPM changes mode

    RPM spec mode

    MIME types defined: text/x-rpm-spec, text/x-rpm-changes.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rpm/rpm.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("rpm-changes", function() { var headerSeparator = /^-+$/; var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; var simpleEmail = /^[\w+.-]+@[\w.-]+/; return { token: function(stream) { if (stream.sol()) { if (stream.match(headerSeparator)) { return 'tag'; } if (stream.match(headerLine)) { return 'tag'; } } if (stream.match(simpleEmail)) { return 'string'; } stream.next(); return null; } }; }); CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes"); // Quick and dirty spec file highlighting CodeMirror.defineMode("rpm-spec", function() { var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; var preamble = /^[a-zA-Z0-9()]+:/; var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/; var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros var control_flow_simple = /^%(else|endif)/; // rpm control flow macros var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros return { startState: function () { return { controlFlow: false, macroParameters: false, section: false }; }, token: function (stream, state) { var ch = stream.peek(); if (ch == "#") { stream.skipToEnd(); return "comment"; } if (stream.sol()) { if (stream.match(preamble)) { return "header"; } if (stream.match(section)) { return "atom"; } } if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' if (stream.match(control_flow_simple)) { return "keyword"; } if (stream.match(control_flow_complex)) { state.controlFlow = true; return "keyword"; } if (state.controlFlow) { if (stream.match(operators)) { return "operator"; } if (stream.match(/^(\d+)/)) { return "number"; } if (stream.eol()) { state.controlFlow = false; } } if (stream.match(arch)) { if (stream.eol()) { state.controlFlow = false; } return "number"; } // Macros like '%make_install' or '%attr(0775,root,root)' if (stream.match(/^%[\w]+/)) { if (stream.match('(')) { state.macroParameters = true; } return "keyword"; } if (state.macroParameters) { if (stream.match(/^\d+/)) { return "number";} if (stream.match(')')) { state.macroParameters = false; return "keyword"; } } // Macros like '%{defined fedora}' if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) { if (stream.eol()) { state.controlFlow = false; } return "def"; } //TODO: Include bash script sub-parser (CodeMirror supports that) stream.next(); return null; } }; }); CodeMirror.defineMIME("text/x-rpm-spec", "rpm-spec"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rst/index.html ================================================ CodeMirror: reStructuredText mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • reStructuredText

    reStructuredText mode

    The python mode will be used for highlighting blocks containing Python/IPython terminal sessions: blocks starting with >>> (for Python) or In [num]: (for IPython). Further, the stex mode will be used for highlighting blocks containing LaTex code.

    MIME types defined: text/x-rst.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rst/rst.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../python/python"), require("../stex/stex"), require("../../addon/mode/overlay")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../python/python", "../stex/stex", "../../addon/mode/overlay"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('rst', function (config, options) { var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/; var rx_emphasis = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/; var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``/; var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/; var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; var rx_uri_protocol = "[Hh][Tt][Tt][Pp][Ss]?://"; var rx_uri_domain = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})"; var rx_uri_path = "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*"; var rx_uri = new RegExp("^" + rx_uri_protocol + rx_uri_domain + rx_uri_path); var overlay = { token: function (stream) { if (stream.match(rx_strong) && stream.match (/\W+|$/, false)) return 'strong'; if (stream.match(rx_emphasis) && stream.match (/\W+|$/, false)) return 'em'; if (stream.match(rx_literal) && stream.match (/\W+|$/, false)) return 'string-2'; if (stream.match(rx_number)) return 'number'; if (stream.match(rx_positive)) return 'positive'; if (stream.match(rx_negative)) return 'negative'; if (stream.match(rx_uri)) return 'link'; while (stream.next() != null) { if (stream.match(rx_strong, false)) break; if (stream.match(rx_emphasis, false)) break; if (stream.match(rx_literal, false)) break; if (stream.match(rx_number, false)) break; if (stream.match(rx_positive, false)) break; if (stream.match(rx_negative, false)) break; if (stream.match(rx_uri, false)) break; } return null; } }; var mode = CodeMirror.getMode( config, options.backdrop || 'rst-base' ); return CodeMirror.overlayMode(mode, overlay, true); // combine }, 'python', 'stex'); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// CodeMirror.defineMode('rst-base', function (config) { /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function format(string) { var args = Array.prototype.slice.call(arguments, 1); return string.replace(/{(\d+)}/g, function (match, n) { return typeof args[n] != 'undefined' ? args[n] : match; }); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// var mode_python = CodeMirror.getMode(config, 'python'); var mode_stex = CodeMirror.getMode(config, 'stex'); /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// var SEPA = "\\s+"; var TAIL = "(?:\\s*|\\W|$)", rx_TAIL = new RegExp(format('^{0}', TAIL)); var NAME = "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)", rx_NAME = new RegExp(format('^{0}', NAME)); var NAME_WWS = "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; var REF_NAME = format('(?:{0}|`{1}`)', NAME, NAME_WWS); var TEXT1 = "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)"; var TEXT2 = "(?:[^\\`]+)", rx_TEXT2 = new RegExp(format('^{0}', TEXT2)); var rx_section = new RegExp( "^([!'#$%&\"()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~])\\1{3,}\\s*$"); var rx_explicit = new RegExp( format('^\\.\\.{0}', SEPA)); var rx_link = new RegExp( format('^_{0}:{1}|^__:{1}', REF_NAME, TAIL)); var rx_directive = new RegExp( format('^{0}::{1}', REF_NAME, TAIL)); var rx_substitution = new RegExp( format('^\\|{0}\\|{1}{2}::{3}', TEXT1, SEPA, REF_NAME, TAIL)); var rx_footnote = new RegExp( format('^\\[(?:\\d+|#{0}?|\\*)]{1}', REF_NAME, TAIL)); var rx_citation = new RegExp( format('^\\[{0}\\]{1}', REF_NAME, TAIL)); var rx_substitution_ref = new RegExp( format('^\\|{0}\\|', TEXT1)); var rx_footnote_ref = new RegExp( format('^\\[(?:\\d+|#{0}?|\\*)]_', REF_NAME)); var rx_citation_ref = new RegExp( format('^\\[{0}\\]_', REF_NAME)); var rx_link_ref1 = new RegExp( format('^{0}__?', REF_NAME)); var rx_link_ref2 = new RegExp( format('^`{0}`_', TEXT2)); var rx_role_pre = new RegExp( format('^:{0}:`{1}`{2}', NAME, TEXT2, TAIL)); var rx_role_suf = new RegExp( format('^`{1}`:{0}:{2}', NAME, TEXT2, TAIL)); var rx_role = new RegExp( format('^:{0}:{1}', NAME, TAIL)); var rx_directive_name = new RegExp(format('^{0}', REF_NAME)); var rx_directive_tail = new RegExp(format('^::{0}', TAIL)); var rx_substitution_text = new RegExp(format('^\\|{0}\\|', TEXT1)); var rx_substitution_sepa = new RegExp(format('^{0}', SEPA)); var rx_substitution_name = new RegExp(format('^{0}', REF_NAME)); var rx_substitution_tail = new RegExp(format('^::{0}', TAIL)); var rx_link_head = new RegExp("^_"); var rx_link_name = new RegExp(format('^{0}|_', REF_NAME)); var rx_link_tail = new RegExp(format('^:{0}', TAIL)); var rx_verbatim = new RegExp('^::\\s*$'); var rx_examples = new RegExp('^\\s+(?:>>>|In \\[\\d+\\]:)\\s'); /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function to_normal(stream, state) { var token = null; if (stream.sol() && stream.match(rx_examples, false)) { change(state, to_mode, { mode: mode_python, local: CodeMirror.startState(mode_python) }); } else if (stream.sol() && stream.match(rx_explicit)) { change(state, to_explicit); token = 'meta'; } else if (stream.sol() && stream.match(rx_section)) { change(state, to_normal); token = 'header'; } else if (phase(state) == rx_role_pre || stream.match(rx_role_pre, false)) { switch (stage(state)) { case 0: change(state, to_normal, context(rx_role_pre, 1)); stream.match(/^:/); token = 'meta'; break; case 1: change(state, to_normal, context(rx_role_pre, 2)); stream.match(rx_NAME); token = 'keyword'; if (stream.current().match(/^(?:math|latex)/)) { state.tmp_stex = true; } break; case 2: change(state, to_normal, context(rx_role_pre, 3)); stream.match(/^:`/); token = 'meta'; break; case 3: if (state.tmp_stex) { state.tmp_stex = undefined; state.tmp = { mode: mode_stex, local: CodeMirror.startState(mode_stex) }; } if (state.tmp) { if (stream.peek() == '`') { change(state, to_normal, context(rx_role_pre, 4)); state.tmp = undefined; break; } token = state.tmp.mode.token(stream, state.tmp.local); break; } change(state, to_normal, context(rx_role_pre, 4)); stream.match(rx_TEXT2); token = 'string'; break; case 4: change(state, to_normal, context(rx_role_pre, 5)); stream.match(/^`/); token = 'meta'; break; case 5: change(state, to_normal, context(rx_role_pre, 6)); stream.match(rx_TAIL); break; default: change(state, to_normal); } } else if (phase(state) == rx_role_suf || stream.match(rx_role_suf, false)) { switch (stage(state)) { case 0: change(state, to_normal, context(rx_role_suf, 1)); stream.match(/^`/); token = 'meta'; break; case 1: change(state, to_normal, context(rx_role_suf, 2)); stream.match(rx_TEXT2); token = 'string'; break; case 2: change(state, to_normal, context(rx_role_suf, 3)); stream.match(/^`:/); token = 'meta'; break; case 3: change(state, to_normal, context(rx_role_suf, 4)); stream.match(rx_NAME); token = 'keyword'; break; case 4: change(state, to_normal, context(rx_role_suf, 5)); stream.match(/^:/); token = 'meta'; break; case 5: change(state, to_normal, context(rx_role_suf, 6)); stream.match(rx_TAIL); break; default: change(state, to_normal); } } else if (phase(state) == rx_role || stream.match(rx_role, false)) { switch (stage(state)) { case 0: change(state, to_normal, context(rx_role, 1)); stream.match(/^:/); token = 'meta'; break; case 1: change(state, to_normal, context(rx_role, 2)); stream.match(rx_NAME); token = 'keyword'; break; case 2: change(state, to_normal, context(rx_role, 3)); stream.match(/^:/); token = 'meta'; break; case 3: change(state, to_normal, context(rx_role, 4)); stream.match(rx_TAIL); break; default: change(state, to_normal); } } else if (phase(state) == rx_substitution_ref || stream.match(rx_substitution_ref, false)) { switch (stage(state)) { case 0: change(state, to_normal, context(rx_substitution_ref, 1)); stream.match(rx_substitution_text); token = 'variable-2'; break; case 1: change(state, to_normal, context(rx_substitution_ref, 2)); if (stream.match(/^_?_?/)) token = 'link'; break; default: change(state, to_normal); } } else if (stream.match(rx_footnote_ref)) { change(state, to_normal); token = 'quote'; } else if (stream.match(rx_citation_ref)) { change(state, to_normal); token = 'quote'; } else if (stream.match(rx_link_ref1)) { change(state, to_normal); if (!stream.peek() || stream.peek().match(/^\W$/)) { token = 'link'; } } else if (phase(state) == rx_link_ref2 || stream.match(rx_link_ref2, false)) { switch (stage(state)) { case 0: if (!stream.peek() || stream.peek().match(/^\W$/)) { change(state, to_normal, context(rx_link_ref2, 1)); } else { stream.match(rx_link_ref2); } break; case 1: change(state, to_normal, context(rx_link_ref2, 2)); stream.match(/^`/); token = 'link'; break; case 2: change(state, to_normal, context(rx_link_ref2, 3)); stream.match(rx_TEXT2); break; case 3: change(state, to_normal, context(rx_link_ref2, 4)); stream.match(/^`_/); token = 'link'; break; default: change(state, to_normal); } } else if (stream.match(rx_verbatim)) { change(state, to_verbatim); } else { if (stream.next()) change(state, to_normal); } return token; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function to_explicit(stream, state) { var token = null; if (phase(state) == rx_substitution || stream.match(rx_substitution, false)) { switch (stage(state)) { case 0: change(state, to_explicit, context(rx_substitution, 1)); stream.match(rx_substitution_text); token = 'variable-2'; break; case 1: change(state, to_explicit, context(rx_substitution, 2)); stream.match(rx_substitution_sepa); break; case 2: change(state, to_explicit, context(rx_substitution, 3)); stream.match(rx_substitution_name); token = 'keyword'; break; case 3: change(state, to_explicit, context(rx_substitution, 4)); stream.match(rx_substitution_tail); token = 'meta'; break; default: change(state, to_normal); } } else if (phase(state) == rx_directive || stream.match(rx_directive, false)) { switch (stage(state)) { case 0: change(state, to_explicit, context(rx_directive, 1)); stream.match(rx_directive_name); token = 'keyword'; if (stream.current().match(/^(?:math|latex)/)) state.tmp_stex = true; else if (stream.current().match(/^python/)) state.tmp_py = true; break; case 1: change(state, to_explicit, context(rx_directive, 2)); stream.match(rx_directive_tail); token = 'meta'; if (stream.match(/^latex\s*$/) || state.tmp_stex) { state.tmp_stex = undefined; change(state, to_mode, { mode: mode_stex, local: CodeMirror.startState(mode_stex) }); } break; case 2: change(state, to_explicit, context(rx_directive, 3)); if (stream.match(/^python\s*$/) || state.tmp_py) { state.tmp_py = undefined; change(state, to_mode, { mode: mode_python, local: CodeMirror.startState(mode_python) }); } break; default: change(state, to_normal); } } else if (phase(state) == rx_link || stream.match(rx_link, false)) { switch (stage(state)) { case 0: change(state, to_explicit, context(rx_link, 1)); stream.match(rx_link_head); stream.match(rx_link_name); token = 'link'; break; case 1: change(state, to_explicit, context(rx_link, 2)); stream.match(rx_link_tail); token = 'meta'; break; default: change(state, to_normal); } } else if (stream.match(rx_footnote)) { change(state, to_normal); token = 'quote'; } else if (stream.match(rx_citation)) { change(state, to_normal); token = 'quote'; } else { stream.eatSpace(); if (stream.eol()) { change(state, to_normal); } else { stream.skipToEnd(); change(state, to_comment); token = 'comment'; } } return token; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function to_comment(stream, state) { return as_block(stream, state, 'comment'); } function to_verbatim(stream, state) { return as_block(stream, state, 'meta'); } function as_block(stream, state, token) { if (stream.eol() || stream.eatSpace()) { stream.skipToEnd(); return token; } else { change(state, to_normal); return null; } } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function to_mode(stream, state) { if (state.ctx.mode && state.ctx.local) { if (stream.sol()) { if (!stream.eatSpace()) change(state, to_normal); return null; } return state.ctx.mode.token(stream, state.ctx.local); } change(state, to_normal); return null; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// function context(phase, stage, mode, local) { return {phase: phase, stage: stage, mode: mode, local: local}; } function change(state, tok, ctx) { state.tok = tok; state.ctx = ctx || {}; } function stage(state) { return state.ctx.stage || 0; } function phase(state) { return state.ctx.phase; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// return { startState: function () { return {tok: to_normal, ctx: context(undefined, 0)}; }, copyState: function (state) { var ctx = state.ctx, tmp = state.tmp; if (ctx.local) ctx = {mode: ctx.mode, local: CodeMirror.copyState(ctx.mode, ctx.local)}; if (tmp) tmp = {mode: tmp.mode, local: CodeMirror.copyState(tmp.mode, tmp.local)}; return {tok: state.tok, ctx: ctx, tmp: tmp}; }, innerMode: function (state) { return state.tmp ? {state: state.tmp.local, mode: state.tmp.mode} : state.ctx.mode ? {state: state.ctx.local, mode: state.ctx.mode} : null; }, token: function (stream, state) { return state.tok(stream, state); } }; }, 'python', 'stex'); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// CodeMirror.defineMIME('text/x-rst', 'rst'); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ruby/index.html ================================================ CodeMirror: Ruby mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Ruby

    Ruby mode

    MIME types defined: text/x-ruby.

    Development of the CodeMirror Ruby mode was kindly sponsored by Ubalo.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ruby/ruby.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function wordObj(words) { var o = {}; for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; return o; } var keywordList = [ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", "caller", "lambda", "proc", "public", "protected", "private", "require", "load", "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__" ], keywords = wordObj(keywordList); var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "catch", "loop", "proc", "begin"]); var dedentWords = wordObj(["end", "until"]); var opening = {"[": "]", "{": "}", "(": ")"}; var closing = {"]": "[", "}": "{", ")": "("}; CodeMirror.defineMode("ruby", function(config) { var curPunc; function chain(newtok, stream, state) { state.tokenize.push(newtok); return newtok(stream, state); } function tokenBase(stream, state) { if (stream.sol() && stream.match("=begin") && stream.eol()) { state.tokenize.push(readBlockComment); return "comment"; } if (stream.eatSpace()) return null; var ch = stream.next(), m; if (ch == "`" || ch == "'" || ch == '"') { return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); } else if (ch == "/") { if (regexpAhead(stream)) return chain(readQuoted(ch, "string-2", true), stream, state); else return "operator"; } else if (ch == "%") { var style = "string", embed = true; if (stream.eat("s")) style = "atom"; else if (stream.eat(/[WQ]/)) style = "string"; else if (stream.eat(/[r]/)) style = "string-2"; else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } var delim = stream.eat(/[^\w\s=]/); if (!delim) return "operator"; if (opening.propertyIsEnumerable(delim)) delim = opening[delim]; return chain(readQuoted(delim, style, embed, true), stream, state); } else if (ch == "#") { stream.skipToEnd(); return "comment"; } else if (ch == "<" && (m = stream.match(/^<([-~])[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { return chain(readHereDoc(m[2], m[1]), stream, state); } else if (ch == "0") { if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); else if (stream.eat("b")) stream.eatWhile(/[01]/); else stream.eatWhile(/[0-7]/); return "number"; } else if (/\d/.test(ch)) { stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); return "number"; } else if (ch == "?") { while (stream.match(/^\\[CM]-/)) {} if (stream.eat("\\")) stream.eatWhile(/\w/); else stream.next(); return "string"; } else if (ch == ":") { if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); // :> :>> :< :<< are valid symbols if (stream.eat(/[\<\>]/)) { stream.eat(/[\<\>]/); return "atom"; } // :+ :- :/ :* :| :& :! are valid symbols if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) { return "atom"; } // Symbols can't start by a digit if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) { stream.eatWhile(/[\w$\xa1-\uffff]/); // Only one ? ! = is allowed and only as the last character stream.eat(/[\?\!\=]/); return "atom"; } return "operator"; } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) { stream.eat("@"); stream.eatWhile(/[\w\xa1-\uffff]/); return "variable-2"; } else if (ch == "$") { if (stream.eat(/[a-zA-Z_]/)) { stream.eatWhile(/[\w]/); } else if (stream.eat(/\d/)) { stream.eat(/\d/); } else { stream.next(); // Must be a special global like $: or $! } return "variable-3"; } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) { stream.eatWhile(/[\w\xa1-\uffff]/); stream.eat(/[\?\!]/); if (stream.eat(":")) return "atom"; return "ident"; } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { curPunc = "|"; return null; } else if (/[\(\)\[\]{}\\;]/.test(ch)) { curPunc = ch; return null; } else if (ch == "-" && stream.eat(">")) { return "arrow"; } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); if (ch == "." && !more) curPunc = "."; return "operator"; } else { return null; } } function regexpAhead(stream) { var start = stream.pos, depth = 0, next, found = false, escaped = false while ((next = stream.next()) != null) { if (!escaped) { if ("[{(".indexOf(next) > -1) { depth++ } else if ("]})".indexOf(next) > -1) { depth-- if (depth < 0) break } else if (next == "/" && depth == 0) { found = true break } escaped = next == "\\" } else { escaped = false } } stream.backUp(stream.pos - start) return found } function tokenBaseUntilBrace(depth) { if (!depth) depth = 1; return function(stream, state) { if (stream.peek() == "}") { if (depth == 1) { state.tokenize.pop(); return state.tokenize[state.tokenize.length-1](stream, state); } else { state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1); } } else if (stream.peek() == "{") { state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1); } return tokenBase(stream, state); }; } function tokenBaseOnce() { var alreadyCalled = false; return function(stream, state) { if (alreadyCalled) { state.tokenize.pop(); return state.tokenize[state.tokenize.length-1](stream, state); } alreadyCalled = true; return tokenBase(stream, state); }; } function readQuoted(quote, style, embed, unescaped) { return function(stream, state) { var escaped = false, ch; if (state.context.type === 'read-quoted-paused') { state.context = state.context.prev; stream.eat("}"); } while ((ch = stream.next()) != null) { if (ch == quote && (unescaped || !escaped)) { state.tokenize.pop(); break; } if (embed && ch == "#" && !escaped) { if (stream.eat("{")) { if (quote == "}") { state.context = {prev: state.context, type: 'read-quoted-paused'}; } state.tokenize.push(tokenBaseUntilBrace()); break; } else if (/[@\$]/.test(stream.peek())) { state.tokenize.push(tokenBaseOnce()); break; } } escaped = !escaped && ch == "\\"; } return style; }; } function readHereDoc(phrase, mayIndent) { return function(stream, state) { if (mayIndent) stream.eatSpace() if (stream.match(phrase)) state.tokenize.pop(); else stream.skipToEnd(); return "string"; }; } function readBlockComment(stream, state) { if (stream.sol() && stream.match("=end") && stream.eol()) state.tokenize.pop(); stream.skipToEnd(); return "comment"; } return { startState: function() { return {tokenize: [tokenBase], indented: 0, context: {type: "top", indented: -config.indentUnit}, continuedLine: false, lastTok: null, varList: false}; }, token: function(stream, state) { curPunc = null; if (stream.sol()) state.indented = stream.indentation(); var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; var thisTok = curPunc; if (style == "ident") { var word = stream.current(); style = state.lastTok == "." ? "property" : keywords.propertyIsEnumerable(stream.current()) ? "keyword" : /^[A-Z]/.test(word) ? "tag" : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" : "variable"; if (style == "keyword") { thisTok = word; if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) kwtype = "indent"; else if (word == "do" && state.context.indented < state.indented) kwtype = "indent"; } } if (curPunc || (style && style != "comment")) state.lastTok = thisTok; if (curPunc == "|") state.varList = !state.varList; if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) state.context = state.context.prev; if (stream.eol()) state.continuedLine = (curPunc == "\\" || style == "operator"); return style; }, indent: function(state, textAfter) { if (state.tokenize[state.tokenize.length-1] != tokenBase) return CodeMirror.Pass; var firstChar = textAfter && textAfter.charAt(0); var ct = state.context; var closed = ct.type == closing[firstChar] || ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); return ct.indented + (closed ? 0 : config.indentUnit) + (state.continuedLine ? config.indentUnit : 0); }, electricInput: /^\s*(?:end|rescue|elsif|else|\})$/, lineComment: "#", fold: "indent" }; }); CodeMirror.defineMIME("text/x-ruby", "ruby"); CodeMirror.registerHelper("hintWords", "ruby", keywordList); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ruby/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "ruby"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("divide_equal_operator", "[variable bar] [operator /=] [variable foo]"); MT("divide_equal_operator_no_spacing", "[variable foo][operator /=][number 42]"); MT("complex_regexp", "[keyword if] [variable cr] [operator =~] [string-2 /(?: \\( #{][tag RE_NOT][string-2 }\\( | #{][tag RE_NOT_PAR_OR][string-2 }* #{][tag RE_OPA_OR][string-2 } )/][variable x]") MT("indented_heredoc", "[keyword def] [def x]", " [variable y] [operator =] [string <<-FOO]", "[string bar]", "[string FOO]", "[keyword end]") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rust/index.html ================================================ CodeMirror: Rust mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Rust

    Rust mode

    MIME types defined: text/x-rustsrc.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rust/rust.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineSimpleMode("rust",{ start: [ // string and byte string {regex: /b?"/, token: "string", next: "string"}, // raw string and raw byte string {regex: /b?r"/, token: "string", next: "string_raw"}, {regex: /b?r#+"/, token: "string", next: "string_raw_hash"}, // character {regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"}, // byte {regex: /b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/, token: "string-2"}, {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/, token: "number"}, {regex: /(let(?:\s+mut)?|fn|enum|mod|struct|type|union)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null, "def"]}, {regex: /(?:abstract|alignof|as|async|await|box|break|continue|const|crate|do|dyn|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/, token: "keyword"}, {regex: /\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/, token: "atom"}, {regex: /\b(?:true|false|Some|None|Ok|Err)\b/, token: "builtin"}, {regex: /\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null ,"def"]}, {regex: /#!?\[.*\]/, token: "meta"}, {regex: /\/\/.*/, token: "comment"}, {regex: /\/\*/, token: "comment", next: "comment"}, {regex: /[-+\/*=<>!]+/, token: "operator"}, {regex: /[a-zA-Z_]\w*!/,token: "variable-3"}, {regex: /[a-zA-Z_]\w*/, token: "variable"}, {regex: /[\{\[\(]/, indent: true}, {regex: /[\}\]\)]/, dedent: true} ], string: [ {regex: /"/, token: "string", next: "start"}, {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"} ], string_raw: [ {regex: /"/, token: "string", next: "start"}, {regex: /[^"]*/, token: "string"} ], string_raw_hash: [ {regex: /"#+/, token: "string", next: "start"}, {regex: /(?:[^"]|"(?!#))*/, token: "string"} ], comment: [ {regex: /.*?\*\//, token: "comment", next: "start"}, {regex: /.*/, token: "comment"} ], meta: { dontIndentStates: ["comment"], electricInput: /^\s*\}$/, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", fold: "brace" } }); CodeMirror.defineMIME("text/x-rustsrc", "rust"); CodeMirror.defineMIME("text/rust", "rust"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/rust/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 4}, "rust"); function MT(name) {test.mode(name, mode, Array.prototype.slice.call(arguments, 1));} MT('integer_test', '[number 123i32]', '[number 123u32]', '[number 123_u32]', '[number 0xff_u8]', '[number 0o70_i16]', '[number 0b1111_1111_1001_0000_i32]', '[number 0usize]'); MT('float_test', '[number 123.0f64]', '[number 0.1f64]', '[number 0.1f32]', '[number 12E+99_f64]'); MT('string-literals-test', '[string "foo"]', '[string r"foo"]', '[string "\\"foo\\""]', '[string r#""foo""#]', '[string "foo #\\"# bar"]', '[string b"foo"]', '[string br"foo"]', '[string b"\\"foo\\""]', '[string br#""foo""#]', '[string br##"foo #" bar"##]', "[string-2 'h']", "[string-2 b'h']"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sas/index.html ================================================ CodeMirror: SAS mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • SAS

    SAS mode

    MIME types defined: text/x-sas.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sas/sas.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // SAS mode copyright (c) 2016 Jared Dean, SAS Institute // Created by Jared Dean // TODO // indent and de-indent // identify macro variables //Definitions // comment -- text within * ; or /* */ // keyword -- SAS language variable // variable -- macro variables starts with '&' or variable formats // variable-2 -- DATA Step, proc, or macro names // string -- text within ' ' or " " // operator -- numeric operator + / - * ** le eq ge ... and so on // builtin -- proc %macro data run mend // atom // def (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("sas", function () { var words = {}; var isDoubleOperatorSym = { eq: 'operator', lt: 'operator', le: 'operator', gt: 'operator', ge: 'operator', "in": 'operator', ne: 'operator', or: 'operator' }; var isDoubleOperatorChar = /(<=|>=|!=|<>)/; var isSingleOperatorChar = /[=\(:\),{}.*<>+\-\/^\[\]]/; // Takes a string of words separated by spaces and adds them as // keys with the value of the first argument 'style' function define(style, string, context) { if (context) { var split = string.split(' '); for (var i = 0; i < split.length; i++) { words[split[i]] = {style: style, state: context}; } } } //datastep define('def', 'stack pgm view source debug nesting nolist', ['inDataStep']); define('def', 'if while until for do do; end end; then else cancel', ['inDataStep']); define('def', 'label format _n_ _error_', ['inDataStep']); define('def', 'ALTER BUFNO BUFSIZE CNTLLEV COMPRESS DLDMGACTION ENCRYPT ENCRYPTKEY EXTENDOBSCOUNTER GENMAX GENNUM INDEX LABEL OBSBUF OUTREP PW PWREQ READ REPEMPTY REPLACE REUSE ROLE SORTEDBY SPILL TOBSNO TYPE WRITE FILECLOSE FIRSTOBS IN OBS POINTOBS WHERE WHEREUP IDXNAME IDXWHERE DROP KEEP RENAME', ['inDataStep']); define('def', 'filevar finfo finv fipname fipnamel fipstate first firstobs floor', ['inDataStep']); define('def', 'varfmt varinfmt varlabel varlen varname varnum varray varrayx vartype verify vformat vformatd vformatdx vformatn vformatnx vformatw vformatwx vformatx vinarray vinarrayx vinformat vinformatd vinformatdx vinformatn vinformatnx vinformatw vinformatwx vinformatx vlabel vlabelx vlength vlengthx vname vnamex vnferr vtype vtypex weekday', ['inDataStep']); define('def', 'zipfips zipname zipnamel zipstate', ['inDataStep']); define('def', 'put putc putn', ['inDataStep']); define('builtin', 'data run', ['inDataStep']); //proc define('def', 'data', ['inProc']); // flow control for macros define('def', '%if %end %end; %else %else; %do %do; %then', ['inMacro']); //everywhere define('builtin', 'proc run; quit; libname filename %macro %mend option options', ['ALL']); define('def', 'footnote title libname ods', ['ALL']); define('def', '%let %put %global %sysfunc %eval ', ['ALL']); // automatic macro variables http://support.sas.com/documentation/cdl/en/mcrolref/61885/HTML/default/viewer.htm#a003167023.htm define('variable', '&sysbuffr &syscc &syscharwidth &syscmd &sysdate &sysdate9 &sysday &sysdevic &sysdmg &sysdsn &sysencoding &sysenv &syserr &syserrortext &sysfilrc &syshostname &sysindex &sysinfo &sysjobid &syslast &syslckrc &syslibrc &syslogapplname &sysmacroname &sysmenv &sysmsg &sysncpu &sysodspath &sysparm &syspbuff &sysprocessid &sysprocessname &sysprocname &sysrc &sysscp &sysscpl &sysscpl &syssite &sysstartid &sysstartname &systcpiphostname &systime &sysuserid &sysver &sysvlong &sysvlong4 &syswarningtext', ['ALL']); //footnote[1-9]? title[1-9]? //options statement define('def', 'source2 nosource2 page pageno pagesize', ['ALL']); //proc and datastep define('def', '_all_ _character_ _cmd_ _freq_ _i_ _infile_ _last_ _msg_ _null_ _numeric_ _temporary_ _type_ abort abs addr adjrsq airy alpha alter altlog altprint and arcos array arsin as atan attrc attrib attrn authserver autoexec awscontrol awsdef awsmenu awsmenumerge awstitle backward band base betainv between blocksize blshift bnot bor brshift bufno bufsize bxor by byerr byline byte calculated call cards cards4 catcache cbufno cdf ceil center cexist change chisq cinv class cleanup close cnonct cntllev coalesce codegen col collate collin column comamid comaux1 comaux2 comdef compbl compound compress config continue convert cos cosh cpuid create cross crosstab css curobs cv daccdb daccdbsl daccsl daccsyd dacctab dairy datalines datalines4 datejul datepart datetime day dbcslang dbcstype dclose ddfm ddm delete delimiter depdb depdbsl depsl depsyd deptab dequote descending descript design= device dflang dhms dif digamma dim dinfo display distinct dkricond dkrocond dlm dnum do dopen doptname doptnum dread drop dropnote dsname dsnferr echo else emaildlg emailid emailpw emailserver emailsys encrypt end endsas engine eof eov erf erfc error errorcheck errors exist exp fappend fclose fcol fdelete feedback fetch fetchobs fexist fget file fileclose fileexist filefmt filename fileref fmterr fmtsearch fnonct fnote font fontalias fopen foptname foptnum force formatted formchar formdelim formdlim forward fpoint fpos fput fread frewind frlen from fsep fuzz fwrite gaminv gamma getoption getvarc getvarn go goto group gwindow hbar hbound helpenv helploc hms honorappearance hosthelp hostprint hour hpct html hvar ibessel ibr id if index indexc indexw initcmd initstmt inner input inputc inputn inr insert int intck intnx into intrr invaliddata irr is jbessel join juldate keep kentb kurtosis label lag last lbound leave left length levels lgamma lib library libref line linesize link list log log10 log2 logpdf logpmf logsdf lostcard lowcase lrecl ls macro macrogen maps mautosource max maxdec maxr mdy mean measures median memtype merge merror min minute missing missover mlogic mod mode model modify month mopen mort mprint mrecall msglevel msymtabmax mvarsize myy n nest netpv new news nmiss no nobatch nobs nocaps nocardimage nocenter nocharcode nocmdmac nocol nocum nodate nodbcs nodetails nodmr nodms nodmsbatch nodup nodupkey noduplicates noechoauto noequals noerrorabend noexitwindows nofullstimer noicon noimplmac noint nolist noloadlist nomiss nomlogic nomprint nomrecall nomsgcase nomstored nomultenvappl nonotes nonumber noobs noovp nopad nopercent noprint noprintinit normal norow norsasuser nosetinit nosplash nosymbolgen note notes notitle notitles notsorted noverbose noxsync noxwait npv null number numkeys nummousekeys nway obs on open order ordinal otherwise out outer outp= output over ovp p(1 5 10 25 50 75 90 95 99) pad pad2 paired parm parmcards path pathdll pathname pdf peek peekc pfkey pmf point poisson poke position printer probbeta probbnml probchi probf probgam probhypr probit probnegb probnorm probsig probt procleave prt ps pw pwreq qtr quote r ranbin rancau random ranexp rangam range ranks rannor ranpoi rantbl rantri ranuni rcorr read recfm register regr remote remove rename repeat repeated replace resolve retain return reuse reverse rewind right round rsquare rtf rtrace rtraceloc s s2 samploc sasautos sascontrol sasfrscr sasmsg sasmstore sasscript sasuser saving scan sdf second select selection separated seq serror set setcomm setot sign simple sin sinh siteinfo skewness skip sle sls sortedby sortpgm sortseq sortsize soundex spedis splashlocation split spool sqrt start std stderr stdin stfips stimer stname stnamel stop stopover sub subgroup subpopn substr sum sumwgt symbol symbolgen symget symput sysget sysin sysleave sysmsg sysparm sysprint sysprintfont sysprod sysrc system t table tables tan tanh tapeclose tbufsize terminal test then timepart tinv tnonct to today tol tooldef totper transformout translate trantab tranwrd trigamma trim trimn trunc truncover type unformatted uniform union until upcase update user usericon uss validate value var weight when where while wincharset window work workinit workterm write wsum xsync xwait yearcutoff yes yyq min max', ['inDataStep', 'inProc']); define('operator', 'and not ', ['inDataStep', 'inProc']); // Main function function tokenize(stream, state) { // Finally advance the stream var ch = stream.next(); // BLOCKCOMMENT if (ch === '/' && stream.eat('*')) { state.continueComment = true; return "comment"; } else if (state.continueComment === true) { // in comment block //comment ends at the beginning of the line if (ch === '*' && stream.peek() === '/') { stream.next(); state.continueComment = false; } else if (stream.skipTo('*')) { //comment is potentially later in line stream.skipTo('*'); stream.next(); if (stream.eat('/')) state.continueComment = false; } else { stream.skipToEnd(); } return "comment"; } if (ch == "*" && stream.column() == stream.indentation()) { stream.skipToEnd() return "comment" } // DoubleOperator match var doubleOperator = ch + stream.peek(); if ((ch === '"' || ch === "'") && !state.continueString) { state.continueString = ch return "string" } else if (state.continueString) { if (state.continueString == ch) { state.continueString = null; } else if (stream.skipTo(state.continueString)) { // quote found on this line stream.next(); state.continueString = null; } else { stream.skipToEnd(); } return "string"; } else if (state.continueString !== null && stream.eol()) { stream.skipTo(state.continueString) || stream.skipToEnd(); return "string"; } else if (/[\d\.]/.test(ch)) { //find numbers if (ch === ".") stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); else if (ch === "0") stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); else stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); return "number"; } else if (isDoubleOperatorChar.test(ch + stream.peek())) { // TWO SYMBOL TOKENS stream.next(); return "operator"; } else if (isDoubleOperatorSym.hasOwnProperty(doubleOperator)) { stream.next(); if (stream.peek() === ' ') return isDoubleOperatorSym[doubleOperator.toLowerCase()]; } else if (isSingleOperatorChar.test(ch)) { // SINGLE SYMBOL TOKENS return "operator"; } // Matches one whole word -- even if the word is a character var word; if (stream.match(/[%&;\w]+/, false) != null) { word = ch + stream.match(/[%&;\w]+/, true); if (/&/.test(word)) return 'variable' } else { word = ch; } // the word after DATA PROC or MACRO if (state.nextword) { stream.match(/[\w]+/); // match memname.libname if (stream.peek() === '.') stream.skipTo(' '); state.nextword = false; return 'variable-2'; } word = word.toLowerCase() // Are we in a DATA Step? if (state.inDataStep) { if (word === 'run;' || stream.match(/run\s;/)) { state.inDataStep = false; return 'builtin'; } // variable formats if ((word) && stream.next() === '.') { //either a format or libname.memname if (/\w/.test(stream.peek())) return 'variable-2'; else return 'variable'; } // do we have a DATA Step keyword if (word && words.hasOwnProperty(word) && (words[word].state.indexOf("inDataStep") !== -1 || words[word].state.indexOf("ALL") !== -1)) { //backup to the start of the word if (stream.start < stream.pos) stream.backUp(stream.pos - stream.start); //advance the length of the word and return for (var i = 0; i < word.length; ++i) stream.next(); return words[word].style; } } // Are we in an Proc statement? if (state.inProc) { if (word === 'run;' || word === 'quit;') { state.inProc = false; return 'builtin'; } // do we have a proc keyword if (word && words.hasOwnProperty(word) && (words[word].state.indexOf("inProc") !== -1 || words[word].state.indexOf("ALL") !== -1)) { stream.match(/[\w]+/); return words[word].style; } } // Are we in a Macro statement? if (state.inMacro) { if (word === '%mend') { if (stream.peek() === ';') stream.next(); state.inMacro = false; return 'builtin'; } if (word && words.hasOwnProperty(word) && (words[word].state.indexOf("inMacro") !== -1 || words[word].state.indexOf("ALL") !== -1)) { stream.match(/[\w]+/); return words[word].style; } return 'atom'; } // Do we have Keywords specific words? if (word && words.hasOwnProperty(word)) { // Negates the initial next() stream.backUp(1); // Actually move the stream stream.match(/[\w]+/); if (word === 'data' && /=/.test(stream.peek()) === false) { state.inDataStep = true; state.nextword = true; return 'builtin'; } if (word === 'proc') { state.inProc = true; state.nextword = true; return 'builtin'; } if (word === '%macro') { state.inMacro = true; state.nextword = true; return 'builtin'; } if (/title[1-9]/.test(word)) return 'def'; if (word === 'footnote') { stream.eat(/[1-9]/); return 'def'; } // Returns their value as state in the prior define methods if (state.inDataStep === true && words[word].state.indexOf("inDataStep") !== -1) return words[word].style; if (state.inProc === true && words[word].state.indexOf("inProc") !== -1) return words[word].style; if (state.inMacro === true && words[word].state.indexOf("inMacro") !== -1) return words[word].style; if (words[word].state.indexOf("ALL") !== -1) return words[word].style; return null; } // Unrecognized syntax return null; } return { startState: function () { return { inDataStep: false, inProc: false, inMacro: false, nextword: false, continueString: null, continueComment: false }; }, token: function (stream, state) { // Strip the spaces, but regex will account for them either way if (stream.eatSpace()) return null; // Go through the main process return tokenize(stream, state); }, blockCommentStart: "/*", blockCommentEnd: "*/" }; }); CodeMirror.defineMIME("text/x-sas", "sas"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sass/index.html ================================================ CodeMirror: Sass mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Sass

    Sass mode

    MIME types defined: text/x-sass.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sass/sass.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../css/css")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../css/css"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("sass", function(config) { var cssMode = CodeMirror.mimeModes["text/css"]; var propertyKeywords = cssMode.propertyKeywords || {}, colorKeywords = cssMode.colorKeywords || {}, valueKeywords = cssMode.valueKeywords || {}, fontProperties = cssMode.fontProperties || {}; function tokenRegexp(words) { return new RegExp("^" + words.join("|")); } var keywords = ["true", "false", "null", "auto"]; var keywordsRegexp = new RegExp("^" + keywords.join("|")); var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", "\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"]; var opRegexp = tokenRegexp(operators); var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/; var word; function isEndLine(stream) { return !stream.peek() || stream.match(/\s+$/, false); } function urlTokens(stream, state) { var ch = stream.peek(); if (ch === ")") { stream.next(); state.tokenizer = tokenBase; return "operator"; } else if (ch === "(") { stream.next(); stream.eatSpace(); return "operator"; } else if (ch === "'" || ch === '"') { state.tokenizer = buildStringTokenizer(stream.next()); return "string"; } else { state.tokenizer = buildStringTokenizer(")", false); return "string"; } } function comment(indentation, multiLine) { return function(stream, state) { if (stream.sol() && stream.indentation() <= indentation) { state.tokenizer = tokenBase; return tokenBase(stream, state); } if (multiLine && stream.skipTo("*/")) { stream.next(); stream.next(); state.tokenizer = tokenBase; } else { stream.skipToEnd(); } return "comment"; }; } function buildStringTokenizer(quote, greedy) { if (greedy == null) { greedy = true; } function stringTokenizer(stream, state) { var nextChar = stream.next(); var peekChar = stream.peek(); var previousChar = stream.string.charAt(stream.pos-2); var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); if (endingString) { if (nextChar !== quote && greedy) { stream.next(); } if (isEndLine(stream)) { state.cursorHalf = 0; } state.tokenizer = tokenBase; return "string"; } else if (nextChar === "#" && peekChar === "{") { state.tokenizer = buildInterpolationTokenizer(stringTokenizer); stream.next(); return "operator"; } else { return "string"; } } return stringTokenizer; } function buildInterpolationTokenizer(currentTokenizer) { return function(stream, state) { if (stream.peek() === "}") { stream.next(); state.tokenizer = currentTokenizer; return "operator"; } else { return tokenBase(stream, state); } }; } function indent(state) { if (state.indentCount == 0) { state.indentCount++; var lastScopeOffset = state.scopes[0].offset; var currentOffset = lastScopeOffset + config.indentUnit; state.scopes.unshift({ offset:currentOffset }); } } function dedent(state) { if (state.scopes.length == 1) return; state.scopes.shift(); } function tokenBase(stream, state) { var ch = stream.peek(); // Comment if (stream.match("/*")) { state.tokenizer = comment(stream.indentation(), true); return state.tokenizer(stream, state); } if (stream.match("//")) { state.tokenizer = comment(stream.indentation(), false); return state.tokenizer(stream, state); } // Interpolation if (stream.match("#{")) { state.tokenizer = buildInterpolationTokenizer(tokenBase); return "operator"; } // Strings if (ch === '"' || ch === "'") { stream.next(); state.tokenizer = buildStringTokenizer(ch); return "string"; } if(!state.cursorHalf){// state.cursorHalf === 0 // first half i.e. before : for key-value pairs // including selectors if (ch === "-") { if (stream.match(/^-\w+-/)) { return "meta"; } } if (ch === ".") { stream.next(); if (stream.match(/^[\w-]+/)) { indent(state); return "qualifier"; } else if (stream.peek() === "#") { indent(state); return "tag"; } } if (ch === "#") { stream.next(); // ID selectors if (stream.match(/^[\w-]+/)) { indent(state); return "builtin"; } if (stream.peek() === "#") { indent(state); return "tag"; } } // Variables if (ch === "$") { stream.next(); stream.eatWhile(/[\w-]/); return "variable-2"; } // Numbers if (stream.match(/^-?[0-9\.]+/)) return "number"; // Units if (stream.match(/^(px|em|in)\b/)) return "unit"; if (stream.match(keywordsRegexp)) return "keyword"; if (stream.match(/^url/) && stream.peek() === "(") { state.tokenizer = urlTokens; return "atom"; } if (ch === "=") { // Match shortcut mixin definition if (stream.match(/^=[\w-]+/)) { indent(state); return "meta"; } } if (ch === "+") { // Match shortcut mixin definition if (stream.match(/^\+[\w-]+/)){ return "variable-3"; } } if(ch === "@"){ if(stream.match('@extend')){ if(!stream.match(/\s*[\w]/)) dedent(state); } } // Indent Directives if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { indent(state); return "def"; } // Other Directives if (ch === "@") { stream.next(); stream.eatWhile(/[\w-]/); return "def"; } if (stream.eatWhile(/[\w-]/)){ if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ word = stream.current().toLowerCase(); var prop = state.prevProp + "-" + word; if (propertyKeywords.hasOwnProperty(prop)) { return "property"; } else if (propertyKeywords.hasOwnProperty(word)) { state.prevProp = word; return "property"; } else if (fontProperties.hasOwnProperty(word)) { return "property"; } return "tag"; } else if(stream.match(/ *:/,false)){ indent(state); state.cursorHalf = 1; state.prevProp = stream.current().toLowerCase(); return "property"; } else if(stream.match(/ *,/,false)){ return "tag"; } else{ indent(state); return "tag"; } } if(ch === ":"){ if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element return "variable-3"; } stream.next(); state.cursorHalf=1; return "operator"; } } // cursorHalf===0 ends here else{ if (ch === "#") { stream.next(); // Hex numbers if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "number"; } } // Numbers if (stream.match(/^-?[0-9\.]+/)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "number"; } // Units if (stream.match(/^(px|em|in)\b/)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "unit"; } if (stream.match(keywordsRegexp)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "keyword"; } if (stream.match(/^url/) && stream.peek() === "(") { state.tokenizer = urlTokens; if (isEndLine(stream)) { state.cursorHalf = 0; } return "atom"; } // Variables if (ch === "$") { stream.next(); stream.eatWhile(/[\w-]/); if (isEndLine(stream)) { state.cursorHalf = 0; } return "variable-2"; } // bang character for !important, !default, etc. if (ch === "!") { stream.next(); state.cursorHalf = 0; return stream.match(/^[\w]+/) ? "keyword": "operator"; } if (stream.match(opRegexp)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "operator"; } // attributes if (stream.eatWhile(/[\w-]/)) { if (isEndLine(stream)) { state.cursorHalf = 0; } word = stream.current().toLowerCase(); if (valueKeywords.hasOwnProperty(word)) { return "atom"; } else if (colorKeywords.hasOwnProperty(word)) { return "keyword"; } else if (propertyKeywords.hasOwnProperty(word)) { state.prevProp = stream.current().toLowerCase(); return "property"; } else { return "tag"; } } //stream.eatSpace(); if (isEndLine(stream)) { state.cursorHalf = 0; return null; } } // else ends here if (stream.match(opRegexp)) return "operator"; // If we haven't returned by now, we move 1 character // and return an error stream.next(); return null; } function tokenLexer(stream, state) { if (stream.sol()) state.indentCount = 0; var style = state.tokenizer(stream, state); var current = stream.current(); if (current === "@return" || current === "}"){ dedent(state); } if (style !== null) { var startOfToken = stream.pos - current.length; var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); var newScopes = []; for (var i = 0; i < state.scopes.length; i++) { var scope = state.scopes[i]; if (scope.offset <= withCurrentIndent) newScopes.push(scope); } state.scopes = newScopes; } return style; } return { startState: function() { return { tokenizer: tokenBase, scopes: [{offset: 0, type: "sass"}], indentCount: 0, cursorHalf: 0, // cursor half tells us if cursor lies after (1) // or before (0) colon (well... more or less) definedVars: [], definedMixins: [] }; }, token: function(stream, state) { var style = tokenLexer(stream, state); state.lastToken = { style: style, content: stream.current() }; return style; }, indent: function(state) { return state.scopes[0].offset; }, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", fold: "indent" }; }, "css"); CodeMirror.defineMIME("text/x-sass", "sass"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sass/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "sass"); // Since Sass has an indent-based syntax, is almost impossible to test correctly the indentation in all cases. // So disable it for tests. mode.indent = undefined; function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("comment", "[comment // this is a comment]", "[comment also this is a comment]") MT("comment_multiline", "[comment /* this is a comment]", "[comment also this is a comment]") MT("variable", "[variable-2 $page-width][operator :] [number 800][unit px]") MT("global_attributes", "[tag body]", " [property font][operator :]", " [property family][operator :] [atom sans-serif]", " [property size][operator :] [number 30][unit em]", " [property weight][operator :] [atom bold]") MT("scoped_styles", "[builtin #contents]", " [property width][operator :] [variable-2 $page-width]", " [builtin #sidebar]", " [property float][operator :] [atom right]", " [property width][operator :] [variable-2 $sidebar-width]", " [builtin #main]", " [property width][operator :] [variable-2 $page-width] [operator -] [variable-2 $sidebar-width]", " [property background][operator :] [variable-2 $primary-color]", " [tag h2]", " [property color][operator :] [keyword blue]") // Sass allows to write the colon as first char instead of a "separator". // :color red // Not supported // MT("property_syntax", // "[qualifier .foo]", // " [operator :][property color] [keyword red]") MT("import", "[def @import] [string \"sass/variables\"]", // Probably it should parsed as above: as a string even without the " or ' // "[def @import] [string sass/baz]" "[def @import] [tag sass][operator /][tag baz]") MT("def", "[def @if] [variable-2 $foo] [def @else]") MT("tag_on_more_lines", "[tag td],", "[tag th]", " [property font-family][operator :] [string \"Arial\"], [atom serif]") MT("important", "[qualifier .foo]", " [property text-decoration][operator :] [atom none] [keyword !important]", "[tag h1]", " [property font-size][operator :] [number 2.5][unit em]") MT("selector", // SCSS doesn't highlight the : // "[tag h1]:[variable-3 before],", // "[tag h2]:[variable-3 before]", "[tag h1][variable-3 :before],", "[tag h2][variable-3 :before]", " [property content][operator :] [string \"::\"]") MT("definition_mixin_equal", "[variable-2 $defined-bs-type][operator :] [atom border-box] [keyword !default]", "[meta =bs][operator (][variable-2 $bs-type][operator :] [variable-2 $defined-bs-type][operator )]", " [meta -webkit-][property box-sizing][operator :] [variable-2 $bs-type]", " [property box-sizing][operator :] [variable-2 $bs-type]") MT("definition_mixin_with_space", "[variable-2 $defined-bs-type][operator :] [atom border-box] [keyword !default]", "[def @mixin] [tag bs][operator (][variable-2 $bs-type][operator :] [variable-2 $defined-bs-type][operator )] ", " [meta -moz-][property box-sizing][operator :] [variable-2 $bs-type]", " [property box-sizing][operator :] [variable-2 $bs-type]") MT("numbers_start_dot_include_plus", // The % is not highlighted correctly // "[meta =button-links][operator (][variable-2 $button-base][operator :] [atom darken][operator (][variable-2 $color11], [number 10][unit %][operator )][operator )]", "[meta =button-links][operator (][variable-2 $button-base][operator :] [atom darken][operator (][variable-2 $color11], [number 10][operator %))]", " [property padding][operator :] [number .3][unit em] [number .6][unit em]", " [variable-3 +border-radius][operator (][number 8][unit px][operator )]", " [property background-color][operator :] [variable-2 $button-base]") MT("include", "[qualifier .bar]", " [def @include] [tag border-radius][operator (][number 8][unit px][operator )]") MT("reference_parent", "[qualifier .col]", " [property clear][operator :] [atom both]", // SCSS doesn't highlight the : // " &:[variable-3 after]", " &[variable-3 :after]", " [property content][operator :] [string '']", " [property clear][operator :] [atom both]") MT("reference_parent_with_spaces", "[tag section]", " [property border-left][operator :] [number 20][unit px] [atom transparent] [atom solid] ", " &[qualifier .section3]", " [qualifier .title]", " [property color][operator :] [keyword white] ", " [qualifier .vermas]", " [property display][operator :] [atom none]") MT("font_face", "[def @font-face]", " [property font-family][operator :] [string 'icomoon']", " [property src][operator :] [atom url][operator (][string fonts/icomoon.ttf][operator )]") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/scheme/index.html ================================================ CodeMirror: Scheme mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Scheme

    Scheme mode

    MIME types defined: text/x-scheme.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/scheme/scheme.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Author: Koh Zi Han, based on implementation by Koh Zi Chun * Improved by: Jakub T. Jankiewicz */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("scheme", function () { var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", SYMBOL = "symbol", ATOM = "atom", NUMBER = "number", BRACKET = "bracket"; var INDENT_WORD_SKIP = 2; function makeKeywords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = makeKeywords("λ case-lambda call/cc class cond-expand define-class define-values exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax define-macro defmacro delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"); var indentKeys = makeKeywords("define let letrec let* lambda define-macro defmacro let-syntax letrec-syntax let-values let*-values define-syntax syntax-rules define-values when unless"); function stateStack(indent, type, prev) { // represents a state stack object this.indent = indent; this.type = type; this.prev = prev; } function pushStack(state, indent, type) { state.indentStack = new stateStack(indent, type, state.indentStack); } function popStack(state) { state.indentStack = state.indentStack.prev; } var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i); var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i); var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i); var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i); function isBinaryNumber (stream) { return stream.match(binaryMatcher); } function isOctalNumber (stream) { return stream.match(octalMatcher); } function isDecimalNumber (stream, backup) { if (backup === true) { stream.backUp(1); } return stream.match(decimalMatcher); } function isHexNumber (stream) { return stream.match(hexMatcher); } function processEscapedSequence(stream, options) { var next, escaped = false; while ((next = stream.next()) != null) { if (next == options.token && !escaped) { options.state.mode = false; break; } escaped = !escaped && next == "\\"; } } return { startState: function () { return { indentStack: null, indentation: 0, mode: false, sExprComment: false, sExprQuote: false }; }, token: function (stream, state) { if (state.indentStack == null && stream.sol()) { // update indentation, but only if indentStack is empty state.indentation = stream.indentation(); } // skip spaces if (stream.eatSpace()) { return null; } var returnType = null; switch(state.mode){ case "string": // multi-line string parsing mode processEscapedSequence(stream, { token: "\"", state: state }); returnType = STRING; // continue on in scheme-string mode break; case "symbol": // escape symbol processEscapedSequence(stream, { token: "|", state: state }); returnType = SYMBOL; // continue on in scheme-symbol mode break; case "comment": // comment parsing mode var next, maybeEnd = false; while ((next = stream.next()) != null) { if (next == "#" && maybeEnd) { state.mode = false; break; } maybeEnd = (next == "|"); } returnType = COMMENT; break; case "s-expr-comment": // s-expr commenting mode state.mode = false; if(stream.peek() == "(" || stream.peek() == "["){ // actually start scheme s-expr commenting mode state.sExprComment = 0; }else{ // if not we just comment the entire of the next token stream.eatWhile(/[^\s\(\)\[\]]/); // eat symbol atom returnType = COMMENT; break; } default: // default parsing mode var ch = stream.next(); if (ch == "\"") { state.mode = "string"; returnType = STRING; } else if (ch == "'") { if (stream.peek() == "(" || stream.peek() == "["){ if (typeof state.sExprQuote != "number") { state.sExprQuote = 0; } // else already in a quoted expression returnType = ATOM; } else { stream.eatWhile(/[\w_\-!$%&*+\.\/:<=>?@\^~]/); returnType = ATOM; } } else if (ch == '|') { state.mode = "symbol"; returnType = SYMBOL; } else if (ch == '#') { if (stream.eat("|")) { // Multi-line comment state.mode = "comment"; // toggle to comment mode returnType = COMMENT; } else if (stream.eat(/[tf]/i)) { // #t/#f (atom) returnType = ATOM; } else if (stream.eat(';')) { // S-Expr comment state.mode = "s-expr-comment"; returnType = COMMENT; } else { var numTest = null, hasExactness = false, hasRadix = true; if (stream.eat(/[ei]/i)) { hasExactness = true; } else { stream.backUp(1); // must be radix specifier } if (stream.match(/^#b/i)) { numTest = isBinaryNumber; } else if (stream.match(/^#o/i)) { numTest = isOctalNumber; } else if (stream.match(/^#x/i)) { numTest = isHexNumber; } else if (stream.match(/^#d/i)) { numTest = isDecimalNumber; } else if (stream.match(/^[-+0-9.]/, false)) { hasRadix = false; numTest = isDecimalNumber; // re-consume the initial # if all matches failed } else if (!hasExactness) { stream.eat('#'); } if (numTest != null) { if (hasRadix && !hasExactness) { // consume optional exactness after radix stream.match(/^#[ei]/i); } if (numTest(stream)) returnType = NUMBER; } } } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal returnType = NUMBER; } else if (ch == ";") { // comment stream.skipToEnd(); // rest of the line is a comment returnType = COMMENT; } else if (ch == "(" || ch == "[") { var keyWord = ''; var indentTemp = stream.column(), letter; /** Either (indent-word .. (non-indent-word .. (;something else, bracket, etc. */ while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) { keyWord += letter; } if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); } else { // non-indent word // we continue eating the spaces stream.eatSpace(); if (stream.eol() || stream.peek() == ";") { // nothing significant after // we restart indentation 1 space after pushStack(state, indentTemp + 1, ch); } else { pushStack(state, indentTemp + stream.current().length, ch); // else we match } } stream.backUp(stream.current().length - 1); // undo all the eating if(typeof state.sExprComment == "number") state.sExprComment++; if(typeof state.sExprQuote == "number") state.sExprQuote++; returnType = BRACKET; } else if (ch == ")" || ch == "]") { returnType = BRACKET; if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { popStack(state); if(typeof state.sExprComment == "number"){ if(--state.sExprComment == 0){ returnType = COMMENT; // final closing bracket state.sExprComment = false; // turn off s-expr commenting mode } } if(typeof state.sExprQuote == "number"){ if(--state.sExprQuote == 0){ returnType = ATOM; // final closing bracket state.sExprQuote = false; // turn off s-expr quote mode } } } } else { stream.eatWhile(/[\w_\-!$%&*+\.\/:<=>?@\^~]/); if (keywords && keywords.propertyIsEnumerable(stream.current())) { returnType = BUILTIN; } else returnType = "variable"; } } return (typeof state.sExprComment == "number") ? COMMENT : ((typeof state.sExprQuote == "number") ? ATOM : returnType); }, indent: function (state) { if (state.indentStack == null) return state.indentation; return state.indentStack.indent; }, fold: "brace-paren", closeBrackets: {pairs: "()[]{}\"\""}, lineComment: ";;" }; }); CodeMirror.defineMIME("text/x-scheme", "scheme"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/shell/index.html ================================================ CodeMirror: Shell mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Shell

    Shell mode

    MIME types defined: text/x-sh, application/x-sh.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/shell/shell.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('shell', function() { var words = {}; function define(style, dict) { for(var i = 0; i < dict.length; i++) { words[dict[i]] = style; } }; var commonAtoms = ["true", "false"]; var commonKeywords = ["if", "then", "do", "else", "elif", "while", "until", "for", "in", "esac", "fi", "fin", "fil", "done", "exit", "set", "unset", "export", "function"]; var commonCommands = ["ab", "awk", "bash", "beep", "cat", "cc", "cd", "chown", "chmod", "chroot", "clear", "cp", "curl", "cut", "diff", "echo", "find", "gawk", "gcc", "get", "git", "grep", "hg", "kill", "killall", "ln", "ls", "make", "mkdir", "openssl", "mv", "nc", "nl", "node", "npm", "ping", "ps", "restart", "rm", "rmdir", "sed", "service", "sh", "shopt", "shred", "source", "sort", "sleep", "ssh", "start", "stop", "su", "sudo", "svn", "tee", "telnet", "top", "touch", "vi", "vim", "wall", "wc", "wget", "who", "write", "yes", "zsh"]; CodeMirror.registerHelper("hintWords", "shell", commonAtoms.concat(commonKeywords, commonCommands)); define('atom', commonAtoms); define('keyword', commonKeywords); define('builtin', commonCommands); function tokenBase(stream, state) { if (stream.eatSpace()) return null; var sol = stream.sol(); var ch = stream.next(); if (ch === '\\') { stream.next(); return null; } if (ch === '\'' || ch === '"' || ch === '`') { state.tokens.unshift(tokenString(ch, ch === "`" ? "quote" : "string")); return tokenize(stream, state); } if (ch === '#') { if (sol && stream.eat('!')) { stream.skipToEnd(); return 'meta'; // 'comment'? } stream.skipToEnd(); return 'comment'; } if (ch === '$') { state.tokens.unshift(tokenDollar); return tokenize(stream, state); } if (ch === '+' || ch === '=') { return 'operator'; } if (ch === '-') { stream.eat('-'); stream.eatWhile(/\w/); return 'attribute'; } if (ch == "<") { if (stream.match("<<")) return "operator" var heredoc = stream.match(/^<-?\s*['"]?([^'"]*)['"]?/) if (heredoc) { state.tokens.unshift(tokenHeredoc(heredoc[1])) return 'string-2' } } if (/\d/.test(ch)) { stream.eatWhile(/\d/); if(stream.eol() || !/\w/.test(stream.peek())) { return 'number'; } } stream.eatWhile(/[\w-]/); var cur = stream.current(); if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; return words.hasOwnProperty(cur) ? words[cur] : null; } function tokenString(quote, style) { var close = quote == "(" ? ")" : quote == "{" ? "}" : quote return function(stream, state) { var next, escaped = false; while ((next = stream.next()) != null) { if (next === close && !escaped) { state.tokens.shift(); break; } else if (next === '$' && !escaped && quote !== "'" && stream.peek() != close) { escaped = true; stream.backUp(1); state.tokens.unshift(tokenDollar); break; } else if (!escaped && quote !== close && next === quote) { state.tokens.unshift(tokenString(quote, style)) return tokenize(stream, state) } else if (!escaped && /['"]/.test(next) && !/['"]/.test(quote)) { state.tokens.unshift(tokenStringStart(next, "string")); stream.backUp(1); break; } escaped = !escaped && next === '\\'; } return style; }; }; function tokenStringStart(quote, style) { return function(stream, state) { state.tokens[0] = tokenString(quote, style) stream.next() return tokenize(stream, state) } } var tokenDollar = function(stream, state) { if (state.tokens.length > 1) stream.eat('$'); var ch = stream.next() if (/['"({]/.test(ch)) { state.tokens[0] = tokenString(ch, ch == "(" ? "quote" : ch == "{" ? "def" : "string"); return tokenize(stream, state); } if (!/\d/.test(ch)) stream.eatWhile(/\w/); state.tokens.shift(); return 'def'; }; function tokenHeredoc(delim) { return function(stream, state) { if (stream.sol() && stream.string == delim) state.tokens.shift() stream.skipToEnd() return "string-2" } } function tokenize(stream, state) { return (state.tokens[0] || tokenBase) (stream, state); }; return { startState: function() {return {tokens:[]};}, token: function(stream, state) { return tokenize(stream, state); }, closeBrackets: "()[]{}''\"\"``", lineComment: '#', fold: "brace" }; }); CodeMirror.defineMIME('text/x-sh', 'shell'); // Apache uses a slightly different Media Type for Shell scripts // http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types CodeMirror.defineMIME('application/x-sh', 'shell'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/shell/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({}, "shell"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("var", "text [def $var] text"); MT("varBraces", "text[def ${var}]text"); MT("varVar", "text [def $a$b] text"); MT("varBracesVarBraces", "text[def ${a}${b}]text"); MT("singleQuotedVar", "[string 'text $var text']"); MT("singleQuotedVarBraces", "[string 'text ${var} text']"); MT("doubleQuotedVar", '[string "text ][def $var][string text"]'); MT("doubleQuotedVarBraces", '[string "text][def ${var}][string text"]'); MT("doubleQuotedVarPunct", '[string "text ][def $@][string text"]'); MT("doubleQuotedVarVar", '[string "][def $a$b][string "]'); MT("doubleQuotedVarBracesVarBraces", '[string "][def ${a}${b}][string "]'); MT("notAString", "text\\'text"); MT("escapes", "outside\\'\\\"\\`\\\\[string \"inside\\`\\'\\\"\\\\`\\$notAVar\"]outside\\$\\(notASubShell\\)"); MT("subshell", "[builtin echo] [quote $(whoami)] s log, stardate [quote `date`]."); MT("doubleQuotedSubshell", "[builtin echo] [string \"][quote $(whoami)][string 's log, stardate `date`.\"]"); MT("hashbang", "[meta #!/bin/bash]"); MT("comment", "text [comment # Blurb]"); MT("numbers", "[number 0] [number 1] [number 2]"); MT("keywords", "[keyword while] [atom true]; [keyword do]", " [builtin sleep] [number 3]", "[keyword done]"); MT("options", "[builtin ls] [attribute -l] [attribute --human-readable]"); MT("operator", "[def var][operator =]value"); MT("doubleParens", "foo [quote $((bar))]") MT("nested braces", "[builtin echo] [def ${A[${B}]]}]") MT("strings in parens", "[def FOO][operator =]([quote $(<][string \"][def $MYDIR][string \"][quote /myfile grep ][string 'hello$'][quote )])") MT("string ending in dollar", '[def a][operator =][string "xyz$"]; [def b][operator =][string "y"]') MT("quote ending in dollar", "[quote $(echo a$)]") MT("heredoc", "[builtin cat] [string-2 <<- end]", "[string-2 content one]", "[string-2 content two end]", "[string-2 end]", "[builtin echo]") })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sieve/index.html ================================================ CodeMirror: Sieve (RFC5228) mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Sieve (RFC5228)

    Sieve (RFC5228) mode

    MIME types defined: application/sieve.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sieve/sieve.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("sieve", function(config) { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = words("if elsif else stop require"); var atoms = words("true false not"); var indentUnit = config.indentUnit; function tokenBase(stream, state) { var ch = stream.next(); if (ch == "/" && stream.eat("*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); } if (ch === '#') { stream.skipToEnd(); return "comment"; } if (ch == "\"") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (ch == "(") { state._indent.push("("); // add virtual angel wings so that editor behaves... // ...more sane in case of broken brackets state._indent.push("{"); return null; } if (ch === "{") { state._indent.push("{"); return null; } if (ch == ")") { state._indent.pop(); state._indent.pop(); } if (ch === "}") { state._indent.pop(); return null; } if (ch == ",") return null; if (ch == ";") return null; if (/[{}\(\),;]/.test(ch)) return null; // 1*DIGIT "K" / "M" / "G" if (/\d/.test(ch)) { stream.eatWhile(/[\d]/); stream.eat(/[KkMmGg]/); return "number"; } // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_") if (ch == ":") { stream.eatWhile(/[a-zA-Z_]/); stream.eatWhile(/[a-zA-Z0-9_]/); return "operator"; } stream.eatWhile(/\w/); var cur = stream.current(); // "text:" *(SP / HTAB) (hash-comment / CRLF) // *(multiline-literal / multiline-dotstart) // "." CRLF if ((cur == "text") && stream.eat(":")) { state.tokenize = tokenMultiLineString; return "string"; } if (keywords.propertyIsEnumerable(cur)) return "keyword"; if (atoms.propertyIsEnumerable(cur)) return "atom"; return null; } function tokenMultiLineString(stream, state) { state._multiLineString = true; // the first line is special it may contain a comment if (!stream.sol()) { stream.eatSpace(); if (stream.peek() == "#") { stream.skipToEnd(); return "comment"; } stream.skipToEnd(); return "string"; } if ((stream.next() == ".") && (stream.eol())) { state._multiLineString = false; state.tokenize = tokenBase; } return "string"; } function tokenCComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (maybeEnd && ch == "/") { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) break; escaped = !escaped && ch == "\\"; } if (!escaped) state.tokenize = tokenBase; return "string"; }; } return { startState: function(base) { return {tokenize: tokenBase, baseIndent: base || 0, _indent: []}; }, token: function(stream, state) { if (stream.eatSpace()) return null; return (state.tokenize || tokenBase)(stream, state); }, indent: function(state, _textAfter) { var length = state._indent.length; if (_textAfter && (_textAfter[0] == "}")) length--; if (length <0) length = 0; return length * indentUnit; }, electricChars: "}" }; }); CodeMirror.defineMIME("application/sieve", "sieve"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/slim/index.html ================================================ CodeMirror: SLIM mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • SLIM

    SLIM mode

    MIME types defined: application/x-slim.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/slim/slim.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("slim", function(config) { var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); var rubyMode = CodeMirror.getMode(config, "ruby"); var modes = { html: htmlMode, ruby: rubyMode }; var embedded = { ruby: "ruby", javascript: "javascript", css: "text/css", sass: "text/x-sass", scss: "text/x-scss", less: "text/x-less", styl: "text/x-styl", // no highlighting so far coffee: "coffeescript", asciidoc: "text/x-asciidoc", markdown: "text/x-markdown", textile: "text/x-textile", // no highlighting so far creole: "text/x-creole", // no highlighting so far wiki: "text/x-wiki", // no highlighting so far mediawiki: "text/x-mediawiki", // no highlighting so far rdoc: "text/x-rdoc", // no highlighting so far builder: "text/x-builder", // no highlighting so far nokogiri: "text/x-nokogiri", // no highlighting so far erb: "application/x-erb" }; var embeddedRegexp = function(map){ var arr = []; for(var key in map) arr.push(key); return new RegExp("^("+arr.join('|')+"):"); }(embedded); var styleMap = { "commentLine": "comment", "slimSwitch": "operator special", "slimTag": "tag", "slimId": "attribute def", "slimClass": "attribute qualifier", "slimAttribute": "attribute", "slimSubmode": "keyword special", "closeAttributeTag": null, "slimDoctype": null, "lineContinuation": null }; var closing = { "{": "}", "[": "]", "(": ")" }; var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD"; var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040"; var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)"); var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)"); var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*"); var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/; var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/; function backup(pos, tokenize, style) { var restore = function(stream, state) { state.tokenize = tokenize; if (stream.pos < pos) { stream.pos = pos; return style; } return state.tokenize(stream, state); }; return function(stream, state) { state.tokenize = restore; return tokenize(stream, state); }; } function maybeBackup(stream, state, pat, offset, style) { var cur = stream.current(); var idx = cur.search(pat); if (idx > -1) { state.tokenize = backup(stream.pos, state.tokenize, style); stream.backUp(cur.length - idx - offset); } return style; } function continueLine(state, column) { state.stack = { parent: state.stack, style: "continuation", indented: column, tokenize: state.line }; state.line = state.tokenize; } function finishContinue(state) { if (state.line == state.tokenize) { state.line = state.stack.tokenize; state.stack = state.stack.parent; } } function lineContinuable(column, tokenize) { return function(stream, state) { finishContinue(state); if (stream.match(/^\\$/)) { continueLine(state, column); return "lineContinuation"; } var style = tokenize(stream, state); if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) { stream.backUp(1); } return style; }; } function commaContinuable(column, tokenize) { return function(stream, state) { finishContinue(state); var style = tokenize(stream, state); if (stream.eol() && stream.current().match(/,$/)) { continueLine(state, column); } return style; }; } function rubyInQuote(endQuote, tokenize) { // TODO: add multi line support return function(stream, state) { var ch = stream.peek(); if (ch == endQuote && state.rubyState.tokenize.length == 1) { // step out of ruby context as it seems to complete processing all the braces stream.next(); state.tokenize = tokenize; return "closeAttributeTag"; } else { return ruby(stream, state); } }; } function startRubySplat(tokenize) { var rubyState; var runSplat = function(stream, state) { if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) { stream.backUp(1); if (stream.eatSpace()) { state.rubyState = rubyState; state.tokenize = tokenize; return tokenize(stream, state); } stream.next(); } return ruby(stream, state); }; return function(stream, state) { rubyState = state.rubyState; state.rubyState = CodeMirror.startState(rubyMode); state.tokenize = runSplat; return ruby(stream, state); }; } function ruby(stream, state) { return rubyMode.token(stream, state.rubyState); } function htmlLine(stream, state) { if (stream.match(/^\\$/)) { return "lineContinuation"; } return html(stream, state); } function html(stream, state) { if (stream.match(/^#\{/)) { state.tokenize = rubyInQuote("}", state.tokenize); return null; } return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState)); } function startHtmlLine(lastTokenize) { return function(stream, state) { var style = htmlLine(stream, state); if (stream.eol()) state.tokenize = lastTokenize; return style; }; } function startHtmlMode(stream, state, offset) { state.stack = { parent: state.stack, style: "html", indented: stream.column() + offset, // pipe + space tokenize: state.line }; state.line = state.tokenize = html; return null; } function comment(stream, state) { stream.skipToEnd(); return state.stack.style; } function commentMode(stream, state) { state.stack = { parent: state.stack, style: "comment", indented: state.indented + 1, tokenize: state.line }; state.line = comment; return comment(stream, state); } function attributeWrapper(stream, state) { if (stream.eat(state.stack.endQuote)) { state.line = state.stack.line; state.tokenize = state.stack.tokenize; state.stack = state.stack.parent; return null; } if (stream.match(wrappedAttributeNameRegexp)) { state.tokenize = attributeWrapperAssign; return "slimAttribute"; } stream.next(); return null; } function attributeWrapperAssign(stream, state) { if (stream.match(/^==?/)) { state.tokenize = attributeWrapperValue; return null; } return attributeWrapper(stream, state); } function attributeWrapperValue(stream, state) { var ch = stream.peek(); if (ch == '"' || ch == "\'") { state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper); stream.next(); return state.tokenize(stream, state); } if (ch == '[') { return startRubySplat(attributeWrapper)(stream, state); } if (stream.match(/^(true|false|nil)\b/)) { state.tokenize = attributeWrapper; return "keyword"; } return startRubySplat(attributeWrapper)(stream, state); } function startAttributeWrapperMode(state, endQuote, tokenize) { state.stack = { parent: state.stack, style: "wrapper", indented: state.indented + 1, tokenize: tokenize, line: state.line, endQuote: endQuote }; state.line = state.tokenize = attributeWrapper; return null; } function sub(stream, state) { if (stream.match(/^#\{/)) { state.tokenize = rubyInQuote("}", state.tokenize); return null; } var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize); subStream.pos = stream.pos - state.stack.indented; subStream.start = stream.start - state.stack.indented; subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented; subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented; var style = state.subMode.token(subStream, state.subState); stream.pos = subStream.pos + state.stack.indented; return style; } function firstSub(stream, state) { state.stack.indented = stream.column(); state.line = state.tokenize = sub; return state.tokenize(stream, state); } function createMode(mode) { var query = embedded[mode]; var spec = CodeMirror.mimeModes[query]; if (spec) { return CodeMirror.getMode(config, spec); } var factory = CodeMirror.modes[query]; if (factory) { return factory(config, {name: query}); } return CodeMirror.getMode(config, "null"); } function getMode(mode) { if (!modes.hasOwnProperty(mode)) { return modes[mode] = createMode(mode); } return modes[mode]; } function startSubMode(mode, state) { var subMode = getMode(mode); var subState = CodeMirror.startState(subMode); state.subMode = subMode; state.subState = subState; state.stack = { parent: state.stack, style: "sub", indented: state.indented + 1, tokenize: state.line }; state.line = state.tokenize = firstSub; return "slimSubmode"; } function doctypeLine(stream, _state) { stream.skipToEnd(); return "slimDoctype"; } function startLine(stream, state) { var ch = stream.peek(); if (ch == '<') { return (state.tokenize = startHtmlLine(state.tokenize))(stream, state); } if (stream.match(/^[|']/)) { return startHtmlMode(stream, state, 1); } if (stream.match(/^\/(!|\[\w+])?/)) { return commentMode(stream, state); } if (stream.match(/^(-|==?[<>]?)/)) { state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby)); return "slimSwitch"; } if (stream.match(/^doctype\b/)) { state.tokenize = doctypeLine; return "keyword"; } var m = stream.match(embeddedRegexp); if (m) { return startSubMode(m[1], state); } return slimTag(stream, state); } function slim(stream, state) { if (state.startOfLine) { return startLine(stream, state); } return slimTag(stream, state); } function slimTag(stream, state) { if (stream.eat('*')) { state.tokenize = startRubySplat(slimTagExtras); return null; } if (stream.match(nameRegexp)) { state.tokenize = slimTagExtras; return "slimTag"; } return slimClass(stream, state); } function slimTagExtras(stream, state) { if (stream.match(/^(<>?|> state.indented && state.last != "slimSubmode") { state.line = state.tokenize = state.stack.tokenize; state.stack = state.stack.parent; state.subMode = null; state.subState = null; } } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); state.startOfLine = false; if (style) state.last = style; return styleMap.hasOwnProperty(style) ? styleMap[style] : style; }, blankLine: function(state) { if (state.subMode && state.subMode.blankLine) { return state.subMode.blankLine(state.subState); } }, innerMode: function(state) { if (state.subMode) return {state: state.subState, mode: state.subMode}; return {state: state, mode: mode}; } //indent: function(state) { // return state.indented; //} }; return mode; }, "htmlmixed", "ruby"); CodeMirror.defineMIME("text/x-slim", "slim"); CodeMirror.defineMIME("application/x-slim", "slim"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/slim/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh (function() { var mode = CodeMirror.getMode({tabSize: 4, indentUnit: 2}, "slim"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Requires at least one media query MT("elementName", "[tag h1] Hey There"); MT("oneElementPerLine", "[tag h1] Hey There .h2"); MT("idShortcut", "[attribute&def #test] Hey There"); MT("tagWithIdShortcuts", "[tag h1][attribute&def #test] Hey There"); MT("classShortcut", "[attribute&qualifier .hello] Hey There"); MT("tagWithIdAndClassShortcuts", "[tag h1][attribute&def #test][attribute&qualifier .hello] Hey There"); MT("docType", "[keyword doctype] xml"); MT("comment", "[comment / Hello WORLD]"); MT("notComment", "[tag h1] This is not a / comment "); MT("attributes", "[tag a]([attribute title]=[string \"test\"]) [attribute href]=[string \"link\"]}"); MT("multiLineAttributes", "[tag a]([attribute title]=[string \"test\"]", " ) [attribute href]=[string \"link\"]}"); MT("htmlCode", "[tag&bracket <][tag h1][tag&bracket >]Title[tag&bracket ]"); MT("rubyBlock", "[operator&special =][variable-2 @item]"); MT("selectorRubyBlock", "[tag a][attribute&qualifier .test][operator&special =] [variable-2 @item]"); MT("nestedRubyBlock", "[tag a]", " [operator&special =][variable puts] [string \"test\"]"); MT("multilinePlaintext", "[tag p]", " | Hello,", " World"); MT("multilineRuby", "[tag p]", " [comment /# this is a comment]", " [comment and this is a comment too]", " | Date/Time", " [operator&special -] [variable now] [operator =] [tag DateTime][operator .][property now]", " [tag strong][operator&special =] [variable now]", " [operator&special -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][property parse]([string \"December 31, 2006\"])", " [operator&special =][string \"Happy\"]", " [operator&special =][string \"Belated\"]", " [operator&special =][string \"Birthday\"]"); MT("multilineComment", "[comment /]", " [comment Multiline]", " [comment Comment]"); MT("hamlAfterRubyTag", "[attribute&qualifier .block]", " [tag strong][operator&special =] [variable now]", " [attribute&qualifier .test]", " [operator&special =][variable now]", " [attribute&qualifier .right]"); MT("stretchedRuby", "[operator&special =] [variable puts] [string \"Hello\"],", " [string \"World\"]"); MT("interpolationInHashAttribute", "[tag div]{[attribute id] = [string \"]#{[variable test]}[string _]#{[variable ting]}[string \"]} test"); MT("interpolationInHTMLAttribute", "[tag div]([attribute title]=[string \"]#{[variable test]}[string _]#{[variable ting]()}[string \"]) Test"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/smalltalk/index.html ================================================ CodeMirror: Smalltalk mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Smalltalk

    Smalltalk mode

    Simple Smalltalk mode.

    MIME types defined: text/x-stsrc.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/smalltalk/smalltalk.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('smalltalk', function(config) { var specialChars = /[+\-\/\\*~<>=@%|&?!.,:;^]/; var keywords = /true|false|nil|self|super|thisContext/; var Context = function(tokenizer, parent) { this.next = tokenizer; this.parent = parent; }; var Token = function(name, context, eos) { this.name = name; this.context = context; this.eos = eos; }; var State = function() { this.context = new Context(next, null); this.expectVariable = true; this.indentation = 0; this.userIndentationDelta = 0; }; State.prototype.userIndent = function(indentation) { this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; }; var next = function(stream, context, state) { var token = new Token(null, context, false); var aChar = stream.next(); if (aChar === '"') { token = nextComment(stream, new Context(nextComment, context)); } else if (aChar === '\'') { token = nextString(stream, new Context(nextString, context)); } else if (aChar === '#') { if (stream.peek() === '\'') { stream.next(); token = nextSymbol(stream, new Context(nextSymbol, context)); } else { if (stream.eatWhile(/[^\s.{}\[\]()]/)) token.name = 'string-2'; else token.name = 'meta'; } } else if (aChar === '$') { if (stream.next() === '<') { stream.eatWhile(/[^\s>]/); stream.next(); } token.name = 'string-2'; } else if (aChar === '|' && state.expectVariable) { token.context = new Context(nextTemporaries, context); } else if (/[\[\]{}()]/.test(aChar)) { token.name = 'bracket'; token.eos = /[\[{(]/.test(aChar); if (aChar === '[') { state.indentation++; } else if (aChar === ']') { state.indentation = Math.max(0, state.indentation - 1); } } else if (specialChars.test(aChar)) { stream.eatWhile(specialChars); token.name = 'operator'; token.eos = aChar !== ';'; // ; cascaded message expression } else if (/\d/.test(aChar)) { stream.eatWhile(/[\w\d]/); token.name = 'number'; } else if (/[\w_]/.test(aChar)) { stream.eatWhile(/[\w\d_]/); token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; } else { token.eos = state.expectVariable; } return token; }; var nextComment = function(stream, context) { stream.eatWhile(/[^"]/); return new Token('comment', stream.eat('"') ? context.parent : context, true); }; var nextString = function(stream, context) { stream.eatWhile(/[^']/); return new Token('string', stream.eat('\'') ? context.parent : context, false); }; var nextSymbol = function(stream, context) { stream.eatWhile(/[^']/); return new Token('string-2', stream.eat('\'') ? context.parent : context, false); }; var nextTemporaries = function(stream, context) { var token = new Token(null, context, false); var aChar = stream.next(); if (aChar === '|') { token.context = context.parent; token.eos = true; } else { stream.eatWhile(/[^|]/); token.name = 'variable'; } return token; }; return { startState: function() { return new State; }, token: function(stream, state) { state.userIndent(stream.indentation()); if (stream.eatSpace()) { return null; } var token = state.context.next(stream, state.context, state); state.context = token.context; state.expectVariable = token.eos; return token.name; }, blankLine: function(state) { state.userIndent(0); }, indent: function(state, textAfter) { var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; return (state.indentation + i) * config.indentUnit; }, electricChars: ']' }; }); CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/smarty/index.html ================================================ CodeMirror: Smarty mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Smarty

    Smarty mode

    Mode for Smarty version 2 or 3, which allows for custom delimiter tags.

    Several configuration parameters are supported:

    • leftDelimiter and rightDelimiter, which should be strings that determine where the Smarty syntax starts and ends.
    • version, which should be 2 or 3.
    • baseMode, which can be a mode spec like "text/html" to set a different background mode.

    MIME types defined: text/x-smarty

    Smarty 2, custom delimiters

    Smarty 3

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/smarty/smarty.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /** * Smarty 2 and 3 mode. */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("smarty", function(config, parserConf) { var rightDelimiter = parserConf.rightDelimiter || "}"; var leftDelimiter = parserConf.leftDelimiter || "{"; var version = parserConf.version || 2; var baseMode = CodeMirror.getMode(config, parserConf.baseMode || "null"); var keyFunctions = ["debug", "extends", "function", "include", "literal"]; var regs = { operatorChars: /[+\-*&%=<>!?]/, validIdentifier: /[a-zA-Z0-9_]/, stringChar: /['"]/ }; var last; function cont(style, lastType) { last = lastType; return style; } function chain(stream, state, parser) { state.tokenize = parser; return parser(stream, state); } // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode function doesNotCount(stream, pos) { if (pos == null) pos = stream.pos; return version === 3 && leftDelimiter == "{" && (pos == stream.string.length || /\s/.test(stream.string.charAt(pos))); } function tokenTop(stream, state) { var string = stream.string; for (var scan = stream.pos;;) { var nextMatch = string.indexOf(leftDelimiter, scan); scan = nextMatch + leftDelimiter.length; if (nextMatch == -1 || !doesNotCount(stream, nextMatch + leftDelimiter.length)) break; } if (nextMatch == stream.pos) { stream.match(leftDelimiter); if (stream.eat("*")) { return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter)); } else { state.depth++; state.tokenize = tokenSmarty; last = "startTag"; return "tag"; } } if (nextMatch > -1) stream.string = string.slice(0, nextMatch); var token = baseMode.token(stream, state.base); if (nextMatch > -1) stream.string = string; return token; } // parsing Smarty content function tokenSmarty(stream, state) { if (stream.match(rightDelimiter, true)) { if (version === 3) { state.depth--; if (state.depth <= 0) { state.tokenize = tokenTop; } } else { state.tokenize = tokenTop; } return cont("tag", null); } if (stream.match(leftDelimiter, true)) { state.depth++; return cont("tag", "startTag"); } var ch = stream.next(); if (ch == "$") { stream.eatWhile(regs.validIdentifier); return cont("variable-2", "variable"); } else if (ch == "|") { return cont("operator", "pipe"); } else if (ch == ".") { return cont("operator", "property"); } else if (regs.stringChar.test(ch)) { state.tokenize = tokenAttribute(ch); return cont("string", "string"); } else if (regs.operatorChars.test(ch)) { stream.eatWhile(regs.operatorChars); return cont("operator", "operator"); } else if (ch == "[" || ch == "]") { return cont("bracket", "bracket"); } else if (ch == "(" || ch == ")") { return cont("bracket", "operator"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); return cont("number", "number"); } else { if (state.last == "variable") { if (ch == "@") { stream.eatWhile(regs.validIdentifier); return cont("property", "property"); } else if (ch == "|") { stream.eatWhile(regs.validIdentifier); return cont("qualifier", "modifier"); } } else if (state.last == "pipe") { stream.eatWhile(regs.validIdentifier); return cont("qualifier", "modifier"); } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); return cont("attribute", "modifier"); } if (state.last == "property") { stream.eatWhile(regs.validIdentifier); return cont("property", null); } else if (/\s/.test(ch)) { last = "whitespace"; return null; } var str = ""; if (ch != "/") { str += ch; } var c = null; while (c = stream.eat(regs.validIdentifier)) { str += c; } for (var i=0, j=keyFunctions.length; i CodeMirror: Solr mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Solr

    Solr mode

    MIME types defined: text/x-solr.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/solr/solr.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("solr", function() { "use strict"; var isStringChar = /[^\s\|\!\+\-\*\?\~\^\&\:\(\)\[\]\{\}\"\\]/; var isOperatorChar = /[\|\!\+\-\*\?\~\^\&]/; var isOperatorString = /^(OR|AND|NOT|TO)$/i; function isNumber(word) { return parseFloat(word).toString() === word; } function tokenString(quote) { return function(stream, state) { var escaped = false, next; while ((next = stream.next()) != null) { if (next == quote && !escaped) break; escaped = !escaped && next == "\\"; } if (!escaped) state.tokenize = tokenBase; return "string"; }; } function tokenOperator(operator) { return function(stream, state) { var style = "operator"; if (operator == "+") style += " positive"; else if (operator == "-") style += " negative"; else if (operator == "|") stream.eat(/\|/); else if (operator == "&") stream.eat(/\&/); else if (operator == "^") style += " boost"; state.tokenize = tokenBase; return style; }; } function tokenWord(ch) { return function(stream, state) { var word = ch; while ((ch = stream.peek()) && ch.match(isStringChar) != null) { word += stream.next(); } state.tokenize = tokenBase; if (isOperatorString.test(word)) return "operator"; else if (isNumber(word)) return "number"; else if (stream.peek() == ":") return "field"; else return "string"; }; } function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"') state.tokenize = tokenString(ch); else if (isOperatorChar.test(ch)) state.tokenize = tokenOperator(ch); else if (isStringChar.test(ch)) state.tokenize = tokenWord(ch); return (state.tokenize != tokenBase) ? state.tokenize(stream, state) : null; } return { startState: function() { return { tokenize: tokenBase }; }, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); } }; }); CodeMirror.defineMIME("text/x-solr", "solr"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/soy/index.html ================================================ CodeMirror: Soy (Closure Template) mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Soy (Closure Template)

    Soy (Closure Template) mode

    A mode for Closure Templates (Soy).

    MIME type defined: text/x-soy.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/soy/soy.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var paramData = { noEndTag: true, soyState: "param-def" }; var tags = { "alias": { noEndTag: true }, "delpackage": { noEndTag: true }, "namespace": { noEndTag: true, soyState: "namespace-def" }, "@attribute": paramData, "@attribute?": paramData, "@param": paramData, "@param?": paramData, "@inject": paramData, "@inject?": paramData, "@state": paramData, "template": { soyState: "templ-def", variableScope: true}, "extern": {soyState: "param-def"}, "export": {soyState: "export"}, "literal": { }, "msg": {}, "fallbackmsg": { noEndTag: true, reduceIndent: true}, "select": {}, "plural": {}, "let": { soyState: "var-def" }, "if": {}, "javaimpl": {}, "jsimpl": {}, "elseif": { noEndTag: true, reduceIndent: true}, "else": { noEndTag: true, reduceIndent: true}, "switch": {}, "case": { noEndTag: true, reduceIndent: true}, "default": { noEndTag: true, reduceIndent: true}, "foreach": { variableScope: true, soyState: "for-loop" }, "ifempty": { noEndTag: true, reduceIndent: true}, "for": { variableScope: true, soyState: "for-loop" }, "call": { soyState: "templ-ref" }, "param": { soyState: "param-ref"}, "print": { noEndTag: true }, "deltemplate": { soyState: "templ-def", variableScope: true}, "delcall": { soyState: "templ-ref" }, "log": {}, "element": { variableScope: true }, "velog": {}, "const": { soyState: "const-def"}, }; var indentingTags = Object.keys(tags).filter(function(tag) { return !tags[tag].noEndTag || tags[tag].reduceIndent; }); CodeMirror.defineMode("soy", function(config) { var textMode = CodeMirror.getMode(config, "text/plain"); var modes = { html: CodeMirror.getMode(config, {name: "text/html", multilineTagIndentFactor: 2, multilineTagIndentPastTag: false, allowMissingTagName: true}), attributes: textMode, text: textMode, uri: textMode, trusted_resource_uri: textMode, css: CodeMirror.getMode(config, "text/css"), js: CodeMirror.getMode(config, {name: "text/javascript", statementIndent: 2 * config.indentUnit}) }; function last(array) { return array[array.length - 1]; } function tokenUntil(stream, state, untilRegExp) { if (stream.sol()) { for (var indent = 0; indent < state.indent; indent++) { if (!stream.eat(/\s/)) break; } if (indent) return null; } var oldString = stream.string; var match = untilRegExp.exec(oldString.substr(stream.pos)); if (match) { // We don't use backUp because it backs up just the position, not the state. // This uses an undocumented API. stream.string = oldString.substr(0, stream.pos + match.index); } var result = stream.hideFirstChars(state.indent, function() { var localState = last(state.localStates); return localState.mode.token(stream, localState.state); }); stream.string = oldString; return result; } function contains(list, element) { while (list) { if (list.element === element) return true; list = list.next; } return false; } function prepend(list, element) { return { element: element, next: list }; } function popcontext(state) { if (!state.context) return; if (state.context.scope) { state.variables = state.context.scope; } state.context = state.context.previousContext; } // Reference a variable `name` in `list`. // Let `loose` be truthy to ignore missing identifiers. function ref(list, name, loose) { return contains(list, name) ? "variable-2" : (loose ? "variable" : "variable-2 error"); } // Data for an open soy tag. function Context(previousContext, tag, scope) { this.previousContext = previousContext; this.tag = tag; this.kind = null; this.scope = scope; } function expression(stream, state) { var match; if (stream.match(/[[]/)) { state.soyState.push("list-literal"); state.context = new Context(state.context, "list-literal", state.variables); state.lookupVariables = false; return null; } else if (stream.match(/\bmap(?=\()/)) { state.soyState.push("map-literal"); return "keyword"; } else if (stream.match(/\brecord(?=\()/)) { state.soyState.push("record-literal"); return "keyword"; } else if (stream.match(/([\w]+)(?=\()/)) { return "variable callee"; } else if (match = stream.match(/^["']/)) { state.soyState.push("string"); state.quoteKind = match[0]; return "string"; } else if (stream.match(/^[(]/)) { state.soyState.push("open-parentheses"); return null; } else if (stream.match(/(null|true|false)(?!\w)/) || stream.match(/0x([0-9a-fA-F]{2,})/) || stream.match(/-?([0-9]*[.])?[0-9]+(e[0-9]*)?/)) { return "atom"; } else if (stream.match(/(\||[+\-*\/%]|[=!]=|\?:|[<>]=?)/)) { // Tokenize filter, binary, null propagator, and equality operators. return "operator"; } else if (match = stream.match(/^\$([\w]+)/)) { return ref(state.variables, match[1], !state.lookupVariables); } else if (match = stream.match(/^\w+/)) { return /^(?:as|and|or|not|in|if)$/.test(match[0]) ? "keyword" : null; } stream.next(); return null; } return { startState: function() { return { soyState: [], variables: prepend(null, 'ij'), scopes: null, indent: 0, quoteKind: null, context: null, lookupVariables: true, // Is unknown variables considered an error localStates: [{ mode: modes.html, state: CodeMirror.startState(modes.html) }] }; }, copyState: function(state) { return { tag: state.tag, // Last seen Soy tag. soyState: state.soyState.concat([]), variables: state.variables, context: state.context, indent: state.indent, // Indentation of the following line. quoteKind: state.quoteKind, lookupVariables: state.lookupVariables, localStates: state.localStates.map(function(localState) { return { mode: localState.mode, state: CodeMirror.copyState(localState.mode, localState.state) }; }) }; }, token: function(stream, state) { var match; switch (last(state.soyState)) { case "comment": if (stream.match(/^.*?\*\//)) { state.soyState.pop(); } else { stream.skipToEnd(); } if (!state.context || !state.context.scope) { var paramRe = /@param\??\s+(\S+)/g; var current = stream.current(); for (var match; (match = paramRe.exec(current)); ) { state.variables = prepend(state.variables, match[1]); } } return "comment"; case "string": var match = stream.match(/^.*?(["']|\\[\s\S])/); if (!match) { stream.skipToEnd(); } else if (match[1] == state.quoteKind) { state.quoteKind = null; state.soyState.pop(); } return "string"; } if (!state.soyState.length || last(state.soyState) != "literal") { if (stream.match(/^\/\*/)) { state.soyState.push("comment"); return "comment"; } else if (stream.match(stream.sol() ? /^\s*\/\/.*/ : /^\s+\/\/.*/)) { return "comment"; } } switch (last(state.soyState)) { case "templ-def": if (match = stream.match(/^\.?([\w]+(?!\.[\w]+)*)/)) { state.soyState.pop(); return "def"; } stream.next(); return null; case "templ-ref": if (match = stream.match(/(\.?[a-zA-Z_][a-zA-Z_0-9]+)+/)) { state.soyState.pop(); // If the first character is '.', it can only be a local template. if (match[0][0] == '.') { return "variable-2" } // Otherwise return "variable"; } if (match = stream.match(/^\$([\w]+)/)) { state.soyState.pop(); return ref(state.variables, match[1], !state.lookupVariables); } stream.next(); return null; case "namespace-def": if (match = stream.match(/^\.?([\w\.]+)/)) { state.soyState.pop(); return "variable"; } stream.next(); return null; case "param-def": if (match = stream.match(/^\*/)) { state.soyState.pop(); state.soyState.push("param-type"); return "type"; } if (match = stream.match(/^\w+/)) { state.variables = prepend(state.variables, match[0]); state.soyState.pop(); state.soyState.push("param-type"); return "def"; } stream.next(); return null; case "param-ref": if (match = stream.match(/^\w+/)) { state.soyState.pop(); return "property"; } stream.next(); return null; case "open-parentheses": if (stream.match(/[)]/)) { state.soyState.pop(); return null; } return expression(stream, state); case "param-type": var peekChar = stream.peek(); if ("}]=>,".indexOf(peekChar) != -1) { state.soyState.pop(); return null; } else if (peekChar == "[") { state.soyState.push('param-type-record'); return null; } else if (peekChar == "(") { state.soyState.push('param-type-template'); return null; } else if (peekChar == "<") { state.soyState.push('param-type-parameter'); return null; } else if (match = stream.match(/^([\w]+|[?])/)) { return "type"; } stream.next(); return null; case "param-type-record": var peekChar = stream.peek(); if (peekChar == "]") { state.soyState.pop(); return null; } if (stream.match(/^\w+/)) { state.soyState.push('param-type'); return "property"; } stream.next(); return null; case "param-type-parameter": if (stream.match(/^[>]/)) { state.soyState.pop(); return null; } if (stream.match(/^[<,]/)) { state.soyState.push('param-type'); return null; } stream.next(); return null; case "param-type-template": if (stream.match(/[>]/)) { state.soyState.pop(); state.soyState.push('param-type'); return null; } if (stream.match(/^\w+/)) { state.soyState.push('param-type'); return "def"; } stream.next(); return null; case "var-def": if (match = stream.match(/^\$([\w]+)/)) { state.variables = prepend(state.variables, match[1]); state.soyState.pop(); return "def"; } stream.next(); return null; case "for-loop": if (stream.match(/\bin\b/)) { state.soyState.pop(); return "keyword"; } if (stream.peek() == "$") { state.soyState.push('var-def'); return null; } stream.next(); return null; case "record-literal": if (stream.match(/^[)]/)) { state.soyState.pop(); return null; } if (stream.match(/[(,]/)) { state.soyState.push("map-value") state.soyState.push("record-key") return null; } stream.next() return null; case "map-literal": if (stream.match(/^[)]/)) { state.soyState.pop(); return null; } if (stream.match(/[(,]/)) { state.soyState.push("map-value") state.soyState.push("map-value") return null; } stream.next() return null; case "list-literal": if (stream.match(']')) { state.soyState.pop(); state.lookupVariables = true; popcontext(state); return null; } if (stream.match(/\bfor\b/)) { state.lookupVariables = true; state.soyState.push('for-loop'); return "keyword"; } return expression(stream, state); case "record-key": if (stream.match(/[\w]+/)) { return "property"; } if (stream.match(/^[:]/)) { state.soyState.pop(); return null; } stream.next(); return null; case "map-value": if (stream.peek() == ")" || stream.peek() == "," || stream.match(/^[:)]/)) { state.soyState.pop(); return null; } return expression(stream, state); case "import": if (stream.eat(";")) { state.soyState.pop(); state.indent -= 2 * config.indentUnit; return null; } if (stream.match(/\w+(?=\s+as\b)/)) { return "variable"; } if (match = stream.match(/\w+/)) { return /\b(from|as)\b/.test(match[0]) ? "keyword" : "def"; } if (match = stream.match(/^["']/)) { state.soyState.push("string"); state.quoteKind = match[0]; return "string"; } stream.next(); return null; case "tag": var endTag; var tagName; if (state.tag === undefined) { endTag = true; tagName = ''; } else { endTag = state.tag[0] == "/"; tagName = endTag ? state.tag.substring(1) : state.tag; } var tag = tags[tagName]; if (stream.match(/^\/?}/)) { var selfClosed = stream.current() == "/}"; if (selfClosed && !endTag) { popcontext(state); } if (state.tag == "/template" || state.tag == "/deltemplate") { state.variables = prepend(null, 'ij'); state.indent = 0; } else { state.indent -= config.indentUnit * (selfClosed || indentingTags.indexOf(state.tag) == -1 ? 2 : 1); } state.soyState.pop(); return "keyword"; } else if (stream.match(/^([\w?]+)(?==)/)) { if (state.context && state.context.tag == tagName && stream.current() == "kind" && (match = stream.match(/^="([^"]+)/, false))) { var kind = match[1]; state.context.kind = kind; var mode = modes[kind] || modes.html; var localState = last(state.localStates); if (localState.mode.indent) { state.indent += localState.mode.indent(localState.state, "", ""); } state.localStates.push({ mode: mode, state: CodeMirror.startState(mode) }); } return "attribute"; } return expression(stream, state); case "template-call-expression": if (stream.match(/^([\w-?]+)(?==)/)) { return "attribute"; } else if (stream.eat('>')) { state.soyState.pop(); return "keyword"; } else if (stream.eat('/>')) { state.soyState.pop(); return "keyword"; } return expression(stream, state); case "literal": if (stream.match('{/literal}', false)) { state.soyState.pop(); return this.token(stream, state); } return tokenUntil(stream, state, /\{\/literal}/); case "export": if (match = stream.match(/\w+/)) { state.soyState.pop(); if (match == "const") { state.soyState.push("const-def") return "keyword"; } else if (match == "extern") { state.soyState.push("param-def") return "keyword"; } } else { stream.next(); } return null; case "const-def": if (stream.match(/^\w+/)) { state.soyState.pop(); return "def"; } stream.next(); return null; } if (stream.match('{literal}')) { state.indent += config.indentUnit; state.soyState.push("literal"); state.context = new Context(state.context, "literal", state.variables); return "keyword"; // A tag-keyword must be followed by whitespace, comment or a closing tag. } else if (match = stream.match(/^\{([/@\\]?\w+\??)(?=$|[\s}]|\/[/*])/)) { var prevTag = state.tag; state.tag = match[1]; var endTag = state.tag[0] == "/"; var indentingTag = !!tags[state.tag]; var tagName = endTag ? state.tag.substring(1) : state.tag; var tag = tags[tagName]; if (state.tag != "/switch") state.indent += ((endTag || tag && tag.reduceIndent) && prevTag != "switch" ? 1 : 2) * config.indentUnit; state.soyState.push("tag"); var tagError = false; if (tag) { if (!endTag) { if (tag.soyState) state.soyState.push(tag.soyState); } // If a new tag, open a new context. if (!tag.noEndTag && (indentingTag || !endTag)) { state.context = new Context(state.context, state.tag, tag.variableScope ? state.variables : null); // Otherwise close the current context. } else if (endTag) { var isBalancedForExtern = tagName == 'extern' && (state.context && state.context.tag == 'export'); if (!state.context || ((state.context.tag != tagName) && !isBalancedForExtern)) { tagError = true; } else if (state.context) { if (state.context.kind) { state.localStates.pop(); var localState = last(state.localStates); if (localState.mode.indent) { state.indent -= localState.mode.indent(localState.state, "", ""); } } popcontext(state); } } } else if (endTag) { // Assume all tags with a closing tag are defined in the config. tagError = true; } return (tagError ? "error " : "") + "keyword"; // Not a tag-keyword; it's an implicit print tag. } else if (stream.eat('{')) { state.tag = "print"; state.indent += 2 * config.indentUnit; state.soyState.push("tag"); return "keyword"; } else if (!state.context && stream.sol() && stream.match(/import\b/)) { state.soyState.push("import"); state.indent += 2 * config.indentUnit; return "keyword"; } else if (match = stream.match('<{')) { state.soyState.push("template-call-expression"); state.indent += 2 * config.indentUnit; state.soyState.push("tag"); return "keyword"; } else if (match = stream.match('')) { state.indent -= 1 * config.indentUnit; return "keyword"; } return tokenUntil(stream, state, /\{|\s+\/\/|\/\*/); }, indent: function(state, textAfter, line) { var indent = state.indent, top = last(state.soyState); if (top == "comment") return CodeMirror.Pass; if (top == "literal") { if (/^\{\/literal}/.test(textAfter)) indent -= config.indentUnit; } else { if (/^\s*\{\/(template|deltemplate)\b/.test(textAfter)) return 0; if (/^\{(\/|(fallbackmsg|elseif|else|ifempty)\b)/.test(textAfter)) indent -= config.indentUnit; if (state.tag != "switch" && /^\{(case|default)\b/.test(textAfter)) indent -= config.indentUnit; if (/^\{\/switch\b/.test(textAfter)) indent -= config.indentUnit; } var localState = last(state.localStates); if (indent && localState.mode.indent) { indent += localState.mode.indent(localState.state, textAfter, line); } return indent; }, innerMode: function(state) { if (state.soyState.length && last(state.soyState) != "literal") return null; else return last(state.localStates); }, electricInput: /^\s*\{(\/|\/template|\/deltemplate|\/switch|fallbackmsg|elseif|else|case|default|ifempty|\/literal\})$/, lineComment: "//", blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", useInnerComments: false, fold: "indent" }; }, "htmlmixed"); CodeMirror.registerHelper("wordChars", "soy", /[\w$]/); CodeMirror.registerHelper("hintWords", "soy", Object.keys(tags).concat( ["css", "debugger"])); CodeMirror.defineMIME("text/x-soy", "soy"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/soy/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "soy"); function MT(name) {test.mode(name, mode, Array.prototype.slice.call(arguments, 1));} // Test of small keywords and words containing them. MT('keywords-test', '[keyword {] [keyword as] worrying [keyword and] notorious [keyword as]', ' the Fandor[operator -]alias assassin, [keyword or]', ' Corcand cannot fit [keyword in] [keyword }]'); MT('let-test', '[keyword {template] [def .name][keyword }]', ' [keyword {let] [def $name]: [string "world"][keyword /}]', ' [tag&bracket <][tag h1][tag&bracket >]', ' Hello, [keyword {][variable-2 $name][keyword }]', ' [tag&bracket ]', '[keyword {/template}]', ''); MT('function-test', '[keyword {] [callee&variable css]([string "MyClass"])[keyword }]', '[tag&bracket <][tag input] [attribute value]=[string "]' + '[keyword {] [callee&variable index]([variable-2&error $list])[keyword }]' + '[string "][tag&bracket />]'); MT('soy-element-composition-test', '[keyword <{][callee&variable foo]()[keyword }]', '[keyword >]'); MT('soy-element-composition-attribute-test', '[keyword <{][callee&variable foo]()[keyword }]', '[attribute class]=[string "Foo"]', '[keyword >]'); MT('namespace-test', '[keyword {namespace] [variable namespace][keyword }]') MT('namespace-with-attribute-test', '[keyword {namespace] [variable my.namespace.templates] ' + '[attribute requirecss]=[string "my.namespace"][keyword }]'); MT('operators-test', '[keyword {] [atom 1] [operator ==] [atom 1] [keyword }]', '[keyword {] [atom 1] [operator !=] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator +] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator -] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator *] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator /] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator %] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator <=] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator >=] [atom 2] [keyword }]', '[keyword {] [atom 3] [operator >] [atom 2] [keyword }]', '[keyword {] [atom 2] [operator >] [atom 3] [keyword }]', '[keyword {] [atom null] [operator ?:] [string ""] [keyword }]', '[keyword {] [variable-2&error $variable] [operator |] safeHtml [keyword }]') MT('primitive-test', '[keyword {] [atom true] [keyword }]', '[keyword {] [atom false] [keyword }]', '[keyword {] truethy [keyword }]', '[keyword {] falsey [keyword }]', '[keyword {] [atom 42] [keyword }]', '[keyword {] [atom .42] [keyword }]', '[keyword {] [atom 0.42] [keyword }]', '[keyword {] [atom -0.42] [keyword }]', '[keyword {] [atom -.2] [keyword }]', '[keyword {] [atom 6.03e23] [keyword }]', '[keyword {] [atom -0.03e0] [keyword }]', '[keyword {] [atom 0x1F] [keyword }]', '[keyword {] [atom 0x1F00BBEA] [keyword }]'); MT('param-type-record', '[keyword {@param] [def record]: [[[property foo]: [type bool], [property bar]: [type int] ]][keyword }]' ); MT('param-type-map', '[keyword {@param] [def unknown]: [type map]<[type string], [type bool]>[keyword }]' ); MT('param-type-list', '[keyword {@param] [def list]: [type list]<[type ?]>[keyword }]' ); MT('param-type-any', '[keyword {@param] [def unknown]: [type ?][keyword }]' ); MT('param-type-nested', '[keyword {@param] [def a]: ' + '[type list]<[[[property a]: [type int], ' + '[property b]: [type map]<[type string], ' + '[type bool]>]]>][keyword }]'); MT('undefined-var', '[keyword {][variable-2&error $var]'); MT('param-scope-test', '[keyword {template] [def .a][keyword }]', ' [keyword {@param] [def x]: [type string][keyword }]', ' [keyword {][variable-2 $x][keyword }]', '[keyword {/template}]', '', '[keyword {template] [def .b][keyword }]', ' [keyword {][variable-2&error $x][keyword }]', '[keyword {/template}]', ''); MT('if-variable-test', '[keyword {if] [variable-2&error $showThing][keyword }]', ' Yo!', '[keyword {/if}]', ''); MT('defined-if-variable-test', '[keyword {template] [def .foo][keyword }]', ' [keyword {@param?] [def showThing]: [type bool][keyword }]', ' [keyword {if] [variable-2 $showThing][keyword }]', ' Yo!', ' [keyword {/if}]', '[keyword {/template}]', ''); MT('template-calls-test', '[keyword {call] [variable-2 .foo][keyword /}]', '[keyword {call] [variable foo][keyword /}]', '[keyword {call] [variable foo][keyword }] [keyword {/call}]', '[keyword {call] [variable first1.second.third_3][keyword /}]', '[keyword {call] [variable first1.second.third_3] [keyword }] [keyword {/call}]', ''); MT('foreach-scope-test', '[keyword {@param] [def bar]: [type string][keyword }]', '[keyword {foreach] [def $foo] [keyword in] [variable-2&error $foos][keyword }]', ' [keyword {][variable-2 $foo][keyword }]', '[keyword {/foreach}]', '[keyword {][variable-2&error $foo][keyword }]', '[keyword {][variable-2 $bar][keyword }]'); MT('foreach-ifempty-indent-test', '[keyword {foreach] [def $foo] [keyword in] [variable-2&error $foos][keyword }]', ' something', '[keyword {ifempty}]', ' nothing', '[keyword {/foreach}]', ''); MT('foreach-index', '[keyword {foreach] [def $foo],[def $index] [keyword in] [[]] [keyword }]', ' [keyword {][variable-2 $foo][keyword }] [keyword {][variable-2 $index][keyword }]', '[keyword {/foreach}]'); MT('nested-kind-test', '[keyword {template] [def .foo] [attribute kind]=[string "html"][keyword }]', ' [tag&bracket <][tag div][tag&bracket >]', ' [keyword {call] [variable-2 .bar][keyword }]', ' [keyword {param] [property propertyName] [attribute kind]=[string "js"][keyword }]', ' [keyword var] [def bar] [operator =] [number 5];', ' [keyword {/param}]', ' [keyword {/call}]', ' [tag&bracket ]', '[keyword {/template}]', ''); MT('tag-starting-with-function-call-is-not-a-keyword', '[keyword {][callee&variable index]([variable-2&error $foo])[keyword }]', '[keyword {css] [string "some-class"][keyword }]', '[keyword {][callee&variable css]([string "some-class"])[keyword }]', ''); MT('allow-missing-colon-in-@param', '[keyword {template] [def .foo][keyword }]', ' [keyword {@param] [def showThing] [type bool][keyword }]', ' [keyword {if] [variable-2 $showThing][keyword }]', ' Yo!', ' [keyword {/if}]', '[keyword {/template}]', ''); MT('param-type-and-default-value', '[keyword {template] [def .foo][keyword }]', ' [keyword {@param] [def bar]: [type bool] = [atom true][keyword }]', '[keyword {/template}]', ''); MT('attribute-type', '[keyword {template] [def .foo][keyword }]', ' [keyword {@attribute] [def bar]: [type string][keyword }]', '[keyword {/template}]', ''); MT('attribute-type-optional', '[keyword {template] [def .foo][keyword }]', ' [keyword {@attribute] [def bar]: [type string][keyword }]', '[keyword {/template}]', ''); MT('attribute-type-all', '[keyword {template] [def .foo][keyword }]', ' [keyword {@attribute] [type *][keyword }]', '[keyword {/template}]', ''); MT('state-variable-reference', '[keyword {template] [def .foo][keyword }]', ' [keyword {@param] [def bar]:= [atom true][keyword }]', ' [keyword {@state] [def foobar]:= [variable-2 $bar][keyword }]', '[keyword {/template}]', ''); MT('param-type-template', '[keyword {template] [def .foo][keyword }]', ' [keyword {@param] [def renderer]: ([def s]:[type string])=>[type html][keyword }]', ' [keyword {call] [variable-2 $renderer] [keyword /}]', '[keyword {/template}]', ''); MT('single-quote-strings', '[keyword {][string "foo"] [string \'bar\'][keyword }]', ''); MT('literal-comments', '[keyword {literal}]/* comment */ // comment[keyword {/literal}]'); MT('highlight-command-at-eol', '[keyword {msg]', ' [keyword }]'); MT('switch-indent-test', '[keyword {let] [def $marbles]: [atom 5] [keyword /}]', '[keyword {switch] [variable-2 $marbles][keyword }]', ' [keyword {case] [atom 0][keyword }]', ' No marbles', ' [keyword {default}]', ' At least 1 marble', '[keyword {/switch}]', ''); MT('if-elseif-else-indent', '[keyword {if] [atom true][keyword }]', ' [keyword {let] [def $a]: [atom 5] [keyword /}]', '[keyword {elseif] [atom false][keyword }]', ' [keyword {let] [def $bar]: [atom 5] [keyword /}]', '[keyword {else}]', ' [keyword {let] [def $bar]: [atom 5] [keyword /}]', '[keyword {/if}]'); MT('msg-fallbackmsg-indent', '[keyword {msg] [attribute desc]=[string "A message"][keyword }]', ' A message', '[keyword {fallbackmsg] [attribute desc]=[string "A message"][keyword }]', ' Old message', '[keyword {/msg}]'); MT('literal-indent', '[keyword {template] [def .name][keyword }]', ' [keyword {literal}]', ' Lerum', ' [keyword {/literal}]', ' Ipsum', '[keyword {/template}]'); MT('special-chars', '[keyword {sp}]', '[keyword {nil}]', '[keyword {\\r}]', '[keyword {\\n}]', '[keyword {\\t}]', '[keyword {lb}]', '[keyword {rb}]'); MT('let-list-literal', '[keyword {let] [def $test]: [[[[[string \'a\'] ], [[[string \'b\'] ] ] [keyword /}]'); MT('let-record-literal', '[keyword {let] [def $test]: [keyword record]([property test]: [callee&variable bidiGlobalDir](), ' + '[property foo]: [atom 5]) [keyword /}]'); MT('let-map-literal', '[keyword {let] [def $test]: [keyword map]([string \'outer\']: [keyword map]([atom 5]: [atom false]), ' + '[string \'foo\']: [string \'bar\']) [keyword /}]'); MT('wrong-closing-tag', '[keyword {if] [atom true][keyword }]', ' Optional', '[keyword&error {/badend][keyword }]'); MT('list-comprehension', '[keyword {let] [def $myList]: [[[[[string \'a\'] ] ] [keyword /}] ' + '[keyword {let] [def $test]: [[[variable $a] [operator +] [atom 1] [keyword for] ' + '[def $a] [keyword in] [variable-2 $myList] [keyword if] [variable-2 $a] [operator >=] [atom 3] ] [keyword /}]'); MT('list-comprehension-index', '[keyword {let] [def $test]: [[[variable $a] [operator +] [variable $index] [keyword for] ' + '[def $a],[def $index] [keyword in] [[]] [keyword if] [variable-2 $a] [operator >=] [variable-2 $index] ] [keyword /}]'); MT('list-comprehension-variable-scope', '[keyword {let] [def $name]: [string "world"][keyword /}]', '[keyword {let] [def $test]: [[[variable $a] [operator +] [variable $index] [keyword for] ' + '[def $a],[def $index] [keyword in] [[]] [keyword if] [variable-2 $a] [operator >=] [variable-2 $index] ] [keyword /}]', '[keyword {][variable-2&error $a][keyword }]', '[keyword {][variable-2&error $index][keyword }]', '[keyword {][variable-2 $test][keyword }]', '[keyword {][variable-2 $name][keyword }]'); MT('import', '[keyword import] {[def Name], [variable Person] [keyword as] [def P]} [keyword from] [string \'examples/proto/example.proto\'];'); MT('velog', '[keyword {velog] [variable-2&error $data][keyword }] Logged [keyword {/velog}]'); MT('extern', '[keyword {extern] [def renderer]: ([def s]:[type string])=>[type string][keyword }] [keyword {/extern}]'); MT('export extern', '[keyword {export] [keyword extern] [def renderer]: ([def s]:[type string])=>[type string][keyword }] [keyword {/extern}]'); MT('const', '[keyword {const] [def FOO] = [atom 5] [keyword /}]', '[keyword {export] [keyword const] [def FOO] = [atom 5] [keyword /}]'); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sparql/index.html ================================================ CodeMirror: SPARQL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • SPARQL

    SPARQL mode

    MIME types defined: application/sparql-query.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sparql/sparql.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("sparql", function(config) { var indentUnit = config.indentUnit; var curPunc; function wordRegexp(words) { return new RegExp("^(?:" + words.join("|") + ")$", "i"); } var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", "iri", "uri", "bnode", "count", "sum", "min", "max", "avg", "sample", "group_concat", "rand", "abs", "ceil", "floor", "round", "concat", "substr", "strlen", "replace", "ucase", "lcase", "encode_for_uri", "contains", "strstarts", "strends", "strbefore", "strafter", "year", "month", "day", "hours", "minutes", "seconds", "timezone", "tz", "now", "uuid", "struuid", "md5", "sha1", "sha256", "sha384", "sha512", "coalesce", "if", "strlang", "strdt", "isnumeric", "regex", "exists", "isblank", "isliteral", "a", "bind"]); var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union", "true", "false", "with", "data", "copy", "to", "move", "add", "create", "drop", "clear", "load", "into"]); var operatorChars = /[*+\-<>=&|\^\/!\?]/; var PN_CHARS = "[A-Za-z_\\-0-9]"; var PREFIX_START = new RegExp("[A-Za-z]"); var PREFIX_REMAINDER = new RegExp("((" + PN_CHARS + "|\\.)*(" + PN_CHARS + "))?:"); function tokenBase(stream, state) { var ch = stream.next(); curPunc = null; if (ch == "$" || ch == "?") { if(ch == "?" && stream.match(/\s/, false)){ return "operator"; } stream.match(/^[A-Za-z0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][A-Za-z0-9_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]*/); return "variable-2"; } else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { stream.match(/^[^\s\u00a0>]*>?/); return "atom"; } else if (ch == "\"" || ch == "'") { state.tokenize = tokenLiteral(ch); return state.tokenize(stream, state); } else if (/[{}\(\),\.;\[\]]/.test(ch)) { curPunc = ch; return "bracket"; } else if (ch == "#") { stream.skipToEnd(); return "comment"; } else if (operatorChars.test(ch)) { return "operator"; } else if (ch == ":") { eatPnLocal(stream); return "atom"; } else if (ch == "@") { stream.eatWhile(/[a-z\d\-]/i); return "meta"; } else if (PREFIX_START.test(ch) && stream.match(PREFIX_REMAINDER)) { eatPnLocal(stream); return "atom"; } stream.eatWhile(/[_\w\d]/); var word = stream.current(); if (ops.test(word)) return "builtin"; else if (keywords.test(word)) return "keyword"; else return "variable"; } function eatPnLocal(stream) { stream.match(/(\.(?=[\w_\-\\%])|[:\w_-]|\\[-\\_~.!$&'()*+,;=/?#@%]|%[a-f\d][a-f\d])+/i); } function tokenLiteral(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { state.tokenize = tokenBase; break; } escaped = !escaped && ch == "\\"; } return "string"; }; } function pushContext(state, type, col) { state.context = {prev: state.context, indent: state.indent, col: col, type: type}; } function popContext(state) { state.indent = state.context.indent; state.context = state.context.prev; } return { startState: function() { return {tokenize: tokenBase, context: null, indent: 0, col: 0}; }, token: function(stream, state) { if (stream.sol()) { if (state.context && state.context.align == null) state.context.align = false; state.indent = stream.indentation(); } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { state.context.align = true; } if (curPunc == "(") pushContext(state, ")", stream.column()); else if (curPunc == "[") pushContext(state, "]", stream.column()); else if (curPunc == "{") pushContext(state, "}", stream.column()); else if (/[\]\}\)]/.test(curPunc)) { while (state.context && state.context.type == "pattern") popContext(state); if (state.context && curPunc == state.context.type) { popContext(state); if (curPunc == "}" && state.context && state.context.type == "pattern") popContext(state); } } else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); else if (/atom|string|variable/.test(style) && state.context) { if (/[\}\]]/.test(state.context.type)) pushContext(state, "pattern", stream.column()); else if (state.context.type == "pattern" && !state.context.align) { state.context.align = true; state.context.col = stream.column(); } } return style; }, indent: function(state, textAfter) { var firstChar = textAfter && textAfter.charAt(0); var context = state.context; if (/[\]\}]/.test(firstChar)) while (context && context.type == "pattern") context = context.prev; var closing = context && firstChar == context.type; if (!context) return 0; else if (context.type == "pattern") return context.col; else if (context.align) return context.col + (closing ? 0 : 1); else return context.indent + (closing ? 0 : indentUnit); }, lineComment: "#" }; }); CodeMirror.defineMIME("application/sparql-query", "sparql"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/spreadsheet/index.html ================================================ CodeMirror: Spreadsheet mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Spreadsheet

    Spreadsheet mode

    MIME types defined: text/x-spreadsheet.

    The Spreadsheet Mode

    Created by Robert Plummer

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/spreadsheet/spreadsheet.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("spreadsheet", function () { return { startState: function () { return { stringType: null, stack: [] }; }, token: function (stream, state) { if (!stream) return; //check for state changes if (state.stack.length === 0) { //strings if ((stream.peek() == '"') || (stream.peek() == "'")) { state.stringType = stream.peek(); stream.next(); // Skip quote state.stack.unshift("string"); } } //return state //stack has switch (state.stack[0]) { case "string": while (state.stack[0] === "string" && !stream.eol()) { if (stream.peek() === state.stringType) { stream.next(); // Skip quote state.stack.shift(); // Clear flag } else if (stream.peek() === "\\") { stream.next(); stream.next(); } else { stream.match(/^.[^\\\"\']*/); } } return "string"; case "characterClass": while (state.stack[0] === "characterClass" && !stream.eol()) { if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) state.stack.shift(); } return "operator"; } var peek = stream.peek(); //no stack switch (peek) { case "[": stream.next(); state.stack.unshift("characterClass"); return "bracket"; case ":": stream.next(); return "operator"; case "\\": if (stream.match(/\\[a-z]+/)) return "string-2"; else { stream.next(); return "atom"; } case ".": case ",": case ";": case "*": case "-": case "+": case "^": case "<": case "/": case "=": stream.next(); return "atom"; case "$": stream.next(); return "builtin"; } if (stream.match(/\d+/)) { if (stream.match(/^\w+/)) return "error"; return "number"; } else if (stream.match(/^[a-zA-Z_]\w*/)) { if (stream.match(/(?=[\(.])/, false)) return "keyword"; return "variable-2"; } else if (["[", "]", "(", ")", "{", "}"].indexOf(peek) != -1) { stream.next(); return "bracket"; } else if (!stream.eatSpace()) { stream.next(); } return null; } }; }); CodeMirror.defineMIME("text/x-spreadsheet", "spreadsheet"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sql/index.html ================================================ CodeMirror: SQL Mode for CodeMirror

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • SQL Mode for CodeMirror

    SQL Mode for CodeMirror

    MIME types defined: text/x-sql, text/x-mysql, text/x-mariadb, text/x-cassandra, text/x-plsql, text/x-mssql, text/x-hive, text/x-pgsql, text/x-gql, text/x-gpsql. text/x-esper. text/x-sqlite. text/x-sparksql. text/x-trino.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/sql/sql.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("sql", function(config, parserConfig) { var client = parserConfig.client || {}, atoms = parserConfig.atoms || {"false": true, "true": true, "null": true}, builtin = parserConfig.builtin || set(defaultBuiltin), keywords = parserConfig.keywords || set(sqlKeywords), operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^\/]/, support = parserConfig.support || {}, hooks = parserConfig.hooks || {}, dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true}, backslashStringEscapes = parserConfig.backslashStringEscapes !== false, brackets = parserConfig.brackets || /^[\{}\(\)\[\]]/, punctuation = parserConfig.punctuation || /^[;.,:]/ function tokenBase(stream, state) { var ch = stream.next(); // call hooks from the mime type if (hooks[ch]) { var result = hooks[ch](stream, state); if (result !== false) return result; } if (support.hexNumber && ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]*'/))) { // hex // ref: https://dev.mysql.com/doc/refman/8.0/en/hexadecimal-literals.html return "number"; } else if (support.binaryNumber && (((ch == "b" || ch == "B") && stream.match(/^'[01]*'/)) || (ch == "0" && stream.match(/^b[01]+/)))) { // bitstring // ref: https://dev.mysql.com/doc/refman/8.0/en/bit-value-literals.html return "number"; } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { // numbers // ref: https://dev.mysql.com/doc/refman/8.0/en/number-literals.html stream.match(/^[0-9]*(\.[0-9]+)?([eE][-+]?[0-9]+)?/); support.decimallessFloat && stream.match(/^\.(?!\.)/); return "number"; } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { // placeholders return "variable-3"; } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { // strings // ref: https://dev.mysql.com/doc/refman/8.0/en/string-literals.html state.tokenize = tokenLiteral(ch); return state.tokenize(stream, state); } else if ((((support.nCharCast && (ch == "n" || ch == "N")) || (support.charsetCast && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) && (stream.peek() == "'" || stream.peek() == '"'))) { // charset casting: _utf8'str', N'str', n'str' // ref: https://dev.mysql.com/doc/refman/8.0/en/string-literals.html return "keyword"; } else if (support.escapeConstant && (ch == "e" || ch == "E") && (stream.peek() == "'" || (stream.peek() == '"' && support.doubleQuote))) { // escape constant: E'str', e'str' // ref: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE state.tokenize = function(stream, state) { return (state.tokenize = tokenLiteral(stream.next(), true))(stream, state); } return "keyword"; } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) { // 1-line comment stream.skipToEnd(); return "comment"; } else if ((support.commentHash && ch == "#") || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { // 1-line comments // ref: https://kb.askmonty.org/en/comment-syntax/ stream.skipToEnd(); return "comment"; } else if (ch == "/" && stream.eat("*")) { // multi-line comments // ref: https://kb.askmonty.org/en/comment-syntax/ state.tokenize = tokenComment(1); return state.tokenize(stream, state); } else if (ch == ".") { // .1 for 0.1 if (support.zerolessFloat && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) return "number"; if (stream.match(/^\.+/)) return null if (stream.match(/^[\w\d_$#]+/)) return "variable-2"; } else if (operatorChars.test(ch)) { // operators stream.eatWhile(operatorChars); return "operator"; } else if (brackets.test(ch)) { // brackets return "bracket"; } else if (punctuation.test(ch)) { // punctuation stream.eatWhile(punctuation); return "punctuation"; } else if (ch == '{' && (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) { // dates (weird ODBC syntax) // ref: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-literals.html return "number"; } else { stream.eatWhile(/^[_\w\d]/); var word = stream.current().toLowerCase(); // dates (standard SQL syntax) // ref: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-literals.html if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) return "number"; if (atoms.hasOwnProperty(word)) return "atom"; if (builtin.hasOwnProperty(word)) return "type"; if (keywords.hasOwnProperty(word)) return "keyword"; if (client.hasOwnProperty(word)) return "builtin"; return null; } } // 'string', with char specified in quote escaped by '\' function tokenLiteral(quote, backslashEscapes) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { state.tokenize = tokenBase; break; } escaped = (backslashStringEscapes || backslashEscapes) && !escaped && ch == "\\"; } return "string"; }; } function tokenComment(depth) { return function(stream, state) { var m = stream.match(/^.*?(\/\*|\*\/)/) if (!m) stream.skipToEnd() else if (m[1] == "/*") state.tokenize = tokenComment(depth + 1) else if (depth > 1) state.tokenize = tokenComment(depth - 1) else state.tokenize = tokenBase return "comment" } } function pushContext(stream, state, type) { state.context = { prev: state.context, indent: stream.indentation(), col: stream.column(), type: type }; } function popContext(state) { state.indent = state.context.indent; state.context = state.context.prev; } return { startState: function() { return {tokenize: tokenBase, context: null}; }, token: function(stream, state) { if (stream.sol()) { if (state.context && state.context.align == null) state.context.align = false; } if (state.tokenize == tokenBase && stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (style == "comment") return style; if (state.context && state.context.align == null) state.context.align = true; var tok = stream.current(); if (tok == "(") pushContext(stream, state, ")"); else if (tok == "[") pushContext(stream, state, "]"); else if (state.context && state.context.type == tok) popContext(state); return style; }, indent: function(state, textAfter) { var cx = state.context; if (!cx) return CodeMirror.Pass; var closing = textAfter.charAt(0) == cx.type; if (cx.align) return cx.col + (closing ? 0 : 1); else return cx.indent + (closing ? 0 : config.indentUnit); }, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: support.commentSlashSlash ? "//" : support.commentHash ? "#" : "--", closeBrackets: "()[]{}''\"\"``", config: parserConfig }; }); // `identifier` function hookIdentifier(stream) { // MySQL/MariaDB identifiers // ref: https://dev.mysql.com/doc/refman/8.0/en/identifier-qualifiers.html var ch; while ((ch = stream.next()) != null) { if (ch == "`" && !stream.eat("`")) return "variable-2"; } stream.backUp(stream.current().length - 1); return stream.eatWhile(/\w/) ? "variable-2" : null; } // "identifier" function hookIdentifierDoublequote(stream) { // Standard SQL /SQLite identifiers // ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier // ref: http://sqlite.org/lang_keywords.html var ch; while ((ch = stream.next()) != null) { if (ch == "\"" && !stream.eat("\"")) return "variable-2"; } stream.backUp(stream.current().length - 1); return stream.eatWhile(/\w/) ? "variable-2" : null; } // variable token function hookVar(stream) { // variables // @@prefix.varName @varName // varName can be quoted with ` or ' or " // ref: https://dev.mysql.com/doc/refman/8.0/en/user-variables.html if (stream.eat("@")) { stream.match('session.'); stream.match('local.'); stream.match('global.'); } if (stream.eat("'")) { stream.match(/^.*'/); return "variable-2"; } else if (stream.eat('"')) { stream.match(/^.*"/); return "variable-2"; } else if (stream.eat("`")) { stream.match(/^.*`/); return "variable-2"; } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { return "variable-2"; } return null; }; // short client keyword token function hookClient(stream) { // \N means NULL // ref: https://dev.mysql.com/doc/refman/8.0/en/null-values.html if (stream.eat("N")) { return "atom"; } // \g, etc // ref: https://dev.mysql.com/doc/refman/8.0/en/mysql-commands.html return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; } // these keywords are used by all SQL dialects (however, a mode can still overwrite it) var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit "; // turn a space-separated list into an array function set(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var defaultBuiltin = "bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric" // A generic SQL Mode. It's not a standard, it just tries to support what is generally supported CodeMirror.defineMIME("text/x-sql", { name: "sql", keywords: set(sqlKeywords + "begin"), builtin: set(defaultBuiltin), atoms: set("false true null unknown"), dateSQL: set("date time timestamp"), support: set("doubleQuote binaryNumber hexNumber") }); CodeMirror.defineMIME("text/x-mssql", { name: "sql", client: set("$partition binary_checksum checksum connectionproperty context_info current_request_id error_line error_message error_number error_procedure error_severity error_state formatmessage get_filestream_transaction_context getansinull host_id host_name isnull isnumeric min_active_rowversion newid newsequentialid rowcount_big xact_state object_id"), keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec go if use index holdlock nolock nowait paglock readcommitted readcommittedlock readpast readuncommitted repeatableread rowlock serializable snapshot tablock tablockx updlock with"), builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "), atoms: set("is not null like and or in left right between inner outer join all any some cross unpivot pivot exists"), operatorChars: /^[*+\-%<>!=^\&|\/]/, brackets: /^[\{}\(\)]/, punctuation: /^[;.,:/]/, backslashStringEscapes: false, dateSQL: set("date datetimeoffset datetime2 smalldatetime datetime time"), hooks: { "@": hookVar } }); CodeMirror.defineMIME("text/x-mysql", { name: "sql", client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), atoms: set("false true null unknown"), operatorChars: /^[*+\-%<>!=&|^]/, dateSQL: set("date time timestamp"), support: set("decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), hooks: { "@": hookVar, "`": hookIdentifier, "\\": hookClient } }); CodeMirror.defineMIME("text/x-mariadb", { name: "sql", client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group group_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), atoms: set("false true null unknown"), operatorChars: /^[*+\-%<>!=&|^]/, dateSQL: set("date time timestamp"), support: set("decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), hooks: { "@": hookVar, "`": hookIdentifier, "\\": hookClient } }); // provided by the phpLiteAdmin project - phpliteadmin.org CodeMirror.defineMIME("text/x-sqlite", { name: "sql", // commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd client: set("auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width"), // ref: http://sqlite.org/lang_keywords.html keywords: set(sqlKeywords + "abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without"), // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real"), // ref: http://sqlite.org/syntax/literal-value.html atoms: set("null current_date current_time current_timestamp"), // ref: http://sqlite.org/lang_expr.html#binaryops operatorChars: /^[*+\-%<>!=&|/~]/, // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types. dateSQL: set("date time timestamp datetime"), support: set("decimallessFloat zerolessFloat"), identifierQuote: "\"", //ref: http://sqlite.org/lang_keywords.html hooks: { // bind-parameters ref:http://sqlite.org/lang_expr.html#varparam "@": hookVar, ":": hookVar, "?": hookVar, "$": hookVar, // The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html "\"": hookIdentifierDoublequote, // there is also support for backticks, ref: http://sqlite.org/lang_keywords.html "`": hookIdentifier } }); // the query language used by Apache Cassandra is called CQL, but this mime type // is called Cassandra to avoid confusion with Contextual Query Language CodeMirror.defineMIME("text/x-cassandra", { name: "sql", client: { }, keywords: set("add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime"), builtin: set("ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint"), atoms: set("false true infinity NaN"), operatorChars: /^[<>=]/, dateSQL: { }, support: set("commentSlashSlash decimallessFloat"), hooks: { } }); // this is based on Peter Raganitsch's 'plsql' mode CodeMirror.defineMIME("text/x-plsql", { name: "sql", client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), builtin: set("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"), operatorChars: /^[*\/+\-%<>!=~]/, dateSQL: set("date time timestamp"), support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") }); // Created to support specific hive keywords CodeMirror.defineMIME("text/x-hive", { name: "sql", keywords: set("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with admin authorization char compact compactions conf cube current current_date current_timestamp day decimal defined dependency directories elem_type exchange file following for grouping hour ignore inner interval jar less logical macro minute month more none noscan over owner partialscan preceding pretty principals protection reload rewrite role roles rollup rows second server sets skewed transactions truncate unbounded unset uri user values window year"), builtin: set("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype key_type utctimestamp value_type varchar"), atoms: set("false true null unknown"), operatorChars: /^[*+\-%<>!=]/, dateSQL: set("date timestamp"), support: set("doubleQuote binaryNumber hexNumber") }); CodeMirror.defineMIME("text/x-pgsql", { name: "sql", client: set("source"), // For PostgreSQL - https://www.postgresql.org/docs/11/sql-keywords-appendix.html // For pl/pgsql lang - https://github.com/postgres/postgres/blob/REL_11_2/src/pl/plpgsql/src/pl_scanner.c keywords: set(sqlKeywords + "a abort abs absent absolute access according action ada add admin after aggregate alias all allocate also alter always analyse analyze and any are array array_agg array_max_cardinality as asc asensitive assert assertion assignment asymmetric at atomic attach attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli between bigint binary bit bit_length blob blocked bom boolean both breadth by c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain char char_length character character_length character_set_catalog character_set_name character_set_schema characteristics characters check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column column_name columns command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constant constraint constraint_catalog constraint_name constraint_schema constraints constructor contains content continue control conversion convert copy corr corresponding cost count covar_pop covar_samp create cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datatype date datetime_interval_code datetime_interval_precision day db deallocate debug dec decimal declare default defaults deferrable deferred defined definer degree delete delimiter delimiters dense_rank depends depth deref derived desc describe descriptor detach detail deterministic diagnostics dictionary disable discard disconnect dispatch distinct dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain double drop dump dynamic dynamic_function dynamic_function_code each element else elseif elsif empty enable encoding encrypted end end_frame end_partition endexec enforced enum equals errcode error escape event every except exception exclude excluding exclusive exec execute exists exit exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreach foreign fortran forward found frame_row free freeze from fs full function functions fusion g general generated get global go goto grant granted greatest group grouping groups handler having header hex hierarchy hint hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import in include including increment indent index indexes indicator info inherit inherits initially inline inner inout input insensitive insert instance instantiable instead int integer integrity intersect intersection interval into invoker is isnull isolation join k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like like_regex limit link listen ln load local localtime localtimestamp location locator lock locked log logged loop lower m map mapping match matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized not nothing notice notify notnull nowait nth_value ntile null nullable nullif nulls number numeric object occurrences_regex octet_length octets of off offset oids old on only open operator option options or order ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parallel parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password path percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding precision prepare prepared preserve primary print_strict_params prior privileges procedural procedure procedures program public publication query quote raise range rank read reads real reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result result_oid return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns reverse revoke right role rollback rollup routine routine_catalog routine_name routine_schema routines row row_count row_number rows rowtype rule savepoint scale schema schema_name schemas scope scope_catalog scope_name scope_schema scroll search second section security select selective self sensitive sequence sequences serializable server server_name session session_user set setof sets share show similar simple size skip slice smallint snapshot some source space specific specific_name specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable stacked standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time system_user t table table_name tables tablesample tablespace temp template temporary text then ties time timestamp timezone_hour timezone_minute to token top_level_count trailing transaction transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted union unique unknown unlink unlisten unlogged unnamed unnest until untyped update upper uri usage use_column use_variable user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of values var_pop var_samp varbinary varchar variable_conflict variadic varying verbose version versioning view views volatile warning when whenever where while whitespace width_bucket window with within without work wrapper write xml xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes zone"), // https://www.postgresql.org/docs/11/datatype.html builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time zone timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), atoms: set("false true null unknown"), operatorChars: /^[*\/+\-%<>!=&|^\/#@?~]/, backslashStringEscapes: false, dateSQL: set("date time timestamp"), support: set("decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast escapeConstant") }); // Google's SQL-like query language, GQL CodeMirror.defineMIME("text/x-gql", { name: "sql", keywords: set("ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where"), atoms: set("false true"), builtin: set("blob datetime first key __key__ string integer double boolean null"), operatorChars: /^[*+\-%<>!=]/ }); // Greenplum CodeMirror.defineMIME("text/x-gpsql", { name: "sql", client: set("source"), //https://github.com/greenplum-db/gpdb/blob/master/src/include/parser/kwlist.h keywords: set("abort absolute access action active add admin after aggregate all also alter always analyse analyze and any array as asc assertion assignment asymmetric at authorization backward before begin between bigint binary bit boolean both by cache called cascade cascaded case cast chain char character characteristics check checkpoint class close cluster coalesce codegen collate column comment commit committed concurrency concurrently configuration connection constraint constraints contains content continue conversion copy cost cpu_rate_limit create createdb createexttable createrole createuser cross csv cube current current_catalog current_date current_role current_schema current_time current_timestamp current_user cursor cycle data database day deallocate dec decimal declare decode default defaults deferrable deferred definer delete delimiter delimiters deny desc dictionary disable discard distinct distributed do document domain double drop dxl each else enable encoding encrypted end enum errors escape every except exchange exclude excluding exclusive execute exists explain extension external extract false family fetch fields filespace fill filter first float following for force foreign format forward freeze from full function global grant granted greatest group group_id grouping handler hash having header hold host hour identity if ignore ilike immediate immutable implicit in including inclusive increment index indexes inherit inherits initially inline inner inout input insensitive insert instead int integer intersect interval into invoker is isnull isolation join key language large last leading least left level like limit list listen load local localtime localtimestamp location lock log login mapping master match maxvalue median merge minute minvalue missing mode modifies modify month move name names national natural nchar new newline next no nocreatedb nocreateexttable nocreaterole nocreateuser noinherit nologin none noovercommit nosuperuser not nothing notify notnull nowait null nullif nulls numeric object of off offset oids old on only operator option options or order ordered others out outer over overcommit overlaps overlay owned owner parser partial partition partitions passing password percent percentile_cont percentile_disc placing plans position preceding precision prepare prepared preserve primary prior privileges procedural procedure protocol queue quote randomly range read readable reads real reassign recheck recursive ref references reindex reject relative release rename repeatable replace replica reset resource restart restrict returning returns revoke right role rollback rollup rootpartition row rows rule savepoint scatter schema scroll search second security segment select sequence serializable session session_user set setof sets share show similar simple smallint some split sql stable standalone start statement statistics stdin stdout storage strict strip subpartition subpartitions substring superuser symmetric sysid system table tablespace temp template temporary text then threshold ties time timestamp to trailing transaction treat trigger trim true truncate trusted type unbounded uncommitted unencrypted union unique unknown unlisten until update user using vacuum valid validation validator value values varchar variadic varying verbose version view volatile web when where whitespace window with within without work writable write xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot xmlserialize year yes zone"), builtin: set("bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml"), atoms: set("false true null unknown"), operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, dateSQL: set("date time timestamp"), support: set("decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast") }); // Spark SQL CodeMirror.defineMIME("text/x-sparksql", { name: "sql", keywords: set("add after all alter analyze and anti archive array as asc at between bucket buckets by cache cascade case cast change clear cluster clustered codegen collection column columns comment commit compact compactions compute concatenate cost create cross cube current current_date current_timestamp database databases data dbproperties defined delete delimited deny desc describe dfs directories distinct distribute drop else end escaped except exchange exists explain export extended external false fields fileformat first following for format formatted from full function functions global grant group grouping having if ignore import in index indexes inner inpath inputformat insert intersect interval into is items join keys last lateral lazy left like limit lines list load local location lock locks logical macro map minus msck natural no not null nulls of on optimize option options or order out outer outputformat over overwrite partition partitioned partitions percent preceding principals purge range recordreader recordwriter recover reduce refresh regexp rename repair replace reset restrict revoke right rlike role roles rollback rollup row rows schema schemas select semi separated serde serdeproperties set sets show skewed sort sorted start statistics stored stratify struct table tables tablesample tblproperties temp temporary terminated then to touch transaction transactions transform true truncate unarchive unbounded uncache union unlock unset use using values view when where window with"), builtin: set("abs acos acosh add_months aggregate and any approx_count_distinct approx_percentile array array_contains array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_repeat array_sort array_union arrays_overlap arrays_zip ascii asin asinh assert_true atan atan2 atanh avg base64 between bigint bin binary bit_and bit_count bit_get bit_length bit_or bit_xor bool_and bool_or boolean bround btrim cardinality case cast cbrt ceil ceiling char char_length character_length chr coalesce collect_list collect_set concat concat_ws conv corr cos cosh cot count count_if count_min_sketch covar_pop covar_samp crc32 cume_dist current_catalog current_database current_date current_timestamp current_timezone current_user date date_add date_format date_from_unix_date date_part date_sub date_trunc datediff day dayofmonth dayofweek dayofyear decimal decode degrees delimited dense_rank div double e element_at elt encode every exists exp explode explode_outer expm1 extract factorial filter find_in_set first first_value flatten float floor forall format_number format_string from_csv from_json from_unixtime from_utc_timestamp get_json_object getbit greatest grouping grouping_id hash hex hour hypot if ifnull in initcap inline inline_outer input_file_block_length input_file_block_start input_file_name inputformat instr int isnan isnotnull isnull java_method json_array_length json_object_keys json_tuple kurtosis lag last last_day last_value lcase lead least left length levenshtein like ln locate log log10 log1p log2 lower lpad ltrim make_date make_dt_interval make_interval make_timestamp make_ym_interval map map_concat map_entries map_filter map_from_arrays map_from_entries map_keys map_values map_zip_with max max_by md5 mean min min_by minute mod monotonically_increasing_id month months_between named_struct nanvl negative next_day not now nth_value ntile nullif nvl nvl2 octet_length or outputformat overlay parse_url percent_rank percentile percentile_approx pi pmod posexplode posexplode_outer position positive pow power printf quarter radians raise_error rand randn random rank rcfile reflect regexp regexp_extract regexp_extract_all regexp_like regexp_replace repeat replace reverse right rint rlike round row_number rpad rtrim schema_of_csv schema_of_json second sentences sequence sequencefile serde session_window sha sha1 sha2 shiftleft shiftright shiftrightunsigned shuffle sign signum sin sinh size skewness slice smallint some sort_array soundex space spark_partition_id split sqrt stack std stddev stddev_pop stddev_samp str_to_map string struct substr substring substring_index sum tan tanh textfile timestamp timestamp_micros timestamp_millis timestamp_seconds tinyint to_csv to_date to_json to_timestamp to_unix_timestamp to_utc_timestamp transform transform_keys transform_values translate trim trunc try_add try_divide typeof ucase unbase64 unhex uniontype unix_date unix_micros unix_millis unix_seconds unix_timestamp upper uuid var_pop var_samp variance version weekday weekofyear when width_bucket window xpath xpath_boolean xpath_double xpath_float xpath_int xpath_long xpath_number xpath_short xpath_string xxhash64 year zip_with"), atoms: set("false true null"), operatorChars: /^[*\/+\-%<>!=~&|^]/, dateSQL: set("date time timestamp"), support: set("doubleQuote zerolessFloat") }); // Esper CodeMirror.defineMIME("text/x-esper", { name: "sql", client: set("source"), // http://www.espertech.com/esper/release-5.5.0/esper-reference/html/appendix_keywords.html keywords: set("alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window"), builtin: {}, atoms: set("false true null"), operatorChars: /^[*+\-%<>!=&|^\/#@?~]/, dateSQL: set("time"), support: set("decimallessFloat zerolessFloat binaryNumber hexNumber") }); // Trino (formerly known as Presto) CodeMirror.defineMIME("text/x-trino", { name: "sql", // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L859-L1129 // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/docs/src/main/sphinx/functions/list.rst keywords: set("abs absent acos add admin after all all_match alter analyze and any any_match approx_distinct approx_most_frequent approx_percentile approx_set arbitrary array_agg array_distinct array_except array_intersect array_join array_max array_min array_position array_remove array_sort array_union arrays_overlap as asc asin at at_timezone atan atan2 authorization avg bar bernoulli beta_cdf between bing_tile bing_tile_at bing_tile_coordinates bing_tile_polygon bing_tile_quadkey bing_tile_zoom_level bing_tiles_around bit_count bitwise_and bitwise_and_agg bitwise_left_shift bitwise_not bitwise_or bitwise_or_agg bitwise_right_shift bitwise_right_shift_arithmetic bitwise_xor bool_and bool_or both by call cardinality cascade case cast catalogs cbrt ceil ceiling char2hexint checksum chr classify coalesce codepoint column columns combinations comment commit committed concat concat_ws conditional constraint contains contains_sequence convex_hull_agg copartition corr cos cosh cosine_similarity count count_if covar_pop covar_samp crc32 create cross cube cume_dist current current_catalog current_date current_groups current_path current_role current_schema current_time current_timestamp current_timezone current_user data date_add date_diff date_format date_parse date_trunc day day_of_month day_of_week day_of_year deallocate default define definer degrees delete dense_rank deny desc describe descriptor distinct distributed dow doy drop e element_at else empty empty_approx_set encoding end error escape evaluate_classifier_predictions every except excluding execute exists exp explain extract false features fetch filter final first first_value flatten floor following for format format_datetime format_number from from_base from_base32 from_base64 from_base64url from_big_endian_32 from_big_endian_64 from_encoded_polyline from_geojson_geometry from_hex from_ieee754_32 from_ieee754_64 from_iso8601_date from_iso8601_timestamp from_iso8601_timestamp_nanos from_unixtime from_unixtime_nanos from_utf8 full functions geometric_mean geometry_from_hadoop_shape geometry_invalid_reason geometry_nearest_points geometry_to_bing_tiles geometry_union geometry_union_agg grant granted grants graphviz great_circle_distance greatest group grouping groups hamming_distance hash_counts having histogram hmac_md5 hmac_sha1 hmac_sha256 hmac_sha512 hour human_readable_seconds if ignore in including index infinity initial inner input insert intersect intersection_cardinality into inverse_beta_cdf inverse_normal_cdf invoker io is is_finite is_infinite is_json_scalar is_nan isolation jaccard_index join json_array json_array_contains json_array_get json_array_length json_exists json_extract json_extract_scalar json_format json_object json_parse json_query json_size json_value keep key keys kurtosis lag last last_day_of_month last_value lateral lead leading learn_classifier learn_libsvm_classifier learn_libsvm_regressor learn_regressor least left length level levenshtein_distance like limit line_interpolate_point line_interpolate_points line_locate_point listagg ln local localtime localtimestamp log log10 log2 logical lower lpad ltrim luhn_check make_set_digest map_agg map_concat map_entries map_filter map_from_entries map_keys map_union map_values map_zip_with match match_recognize matched matches materialized max max_by md5 measures merge merge_set_digest millisecond min min_by minute mod month multimap_agg multimap_from_entries murmur3 nan natural next nfc nfd nfkc nfkd ngrams no none none_match normal_cdf normalize not now nth_value ntile null nullif nulls numeric_histogram object objectid_timestamp of offset omit on one only option or order ordinality outer output over overflow parse_data_size parse_datetime parse_duration partition partitions passing past path pattern per percent_rank permute pi position pow power preceding prepare privileges properties prune qdigest_agg quarter quotes radians rand random range rank read recursive reduce reduce_agg refresh regexp_count regexp_extract regexp_extract_all regexp_like regexp_position regexp_replace regexp_split regr_intercept regr_slope regress rename render repeat repeatable replace reset respect restrict returning reverse revoke rgb right role roles rollback rollup round row_number rows rpad rtrim running scalar schema schemas second security seek select sequence serializable session set sets sha1 sha256 sha512 show shuffle sign simplify_geometry sin skewness skip slice some soundex spatial_partitioning spatial_partitions split split_part split_to_map split_to_multimap spooky_hash_v2_32 spooky_hash_v2_64 sqrt st_area st_asbinary st_astext st_boundary st_buffer st_centroid st_contains st_convexhull st_coorddim st_crosses st_difference st_dimension st_disjoint st_distance st_endpoint st_envelope st_envelopeaspts st_equals st_exteriorring st_geometries st_geometryfromtext st_geometryn st_geometrytype st_geomfrombinary st_interiorringn st_interiorrings st_intersection st_intersects st_isclosed st_isempty st_isring st_issimple st_isvalid st_length st_linefromtext st_linestring st_multipoint st_numgeometries st_numinteriorring st_numpoints st_overlaps st_point st_pointn st_points st_polygon st_relate st_startpoint st_symdifference st_touches st_union st_within st_x st_xmax st_xmin st_y st_ymax st_ymin start starts_with stats stddev stddev_pop stddev_samp string strpos subset substr substring sum system table tables tablesample tan tanh tdigest_agg text then ties timestamp_objectid timezone_hour timezone_minute to to_base to_base32 to_base64 to_base64url to_big_endian_32 to_big_endian_64 to_char to_date to_encoded_polyline to_geojson_geometry to_geometry to_hex to_ieee754_32 to_ieee754_64 to_iso8601 to_milliseconds to_spherical_geography to_timestamp to_unixtime to_utf8 trailing transaction transform transform_keys transform_values translate trim trim_array true truncate try try_cast type typeof uescape unbounded uncommitted unconditional union unique unknown unmatched unnest update upper url_decode url_encode url_extract_fragment url_extract_host url_extract_parameter url_extract_path url_extract_port url_extract_protocol url_extract_query use user using utf16 utf32 utf8 validate value value_at_quantile values values_at_quantiles var_pop var_samp variance verbose version view week week_of_year when where width_bucket wilson_interval_lower wilson_interval_upper window with with_timezone within without word_stem work wrapper write xxhash64 year year_of_week yow zip zip_with"), // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/core/trino-main/src/main/java/io/trino/metadata/TypeRegistry.java#L131-L168 // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-ml/src/main/java/io/trino/plugin/ml/MLPlugin.java#L35 // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoPlugin.java#L32 // https://github.com/trinodb/trino/blob/bc7a4eeedde28684c7ae6f74cefcaf7c6e782174/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoPlugin.java#L37 builtin: set("array bigint bingtile boolean char codepoints color date decimal double function geometry hyperloglog int integer interval ipaddress joniregexp json json2016 jsonpath kdbtree likepattern map model objectid p4hyperloglog precision qdigest re2jregexp real regressor row setdigest smallint sphericalgeography tdigest time timestamp tinyint uuid varbinary varchar zone"), atoms: set("false true null unknown"), // https://trino.io/docs/current/functions/list.html#id1 operatorChars: /^[[\]|<>=!\-+*/%]/, dateSQL: set("date time timestamp zone"), // hexNumber is necessary for VARBINARY literals, e.g. X'65683F' // but it also enables 0xFF hex numbers, which Trino doesn't support. support: set("decimallessFloat zerolessFloat hexNumber") }); }); /* How Properties of Mime Types are used by SQL Mode ================================================= keywords: A list of keywords you want to be highlighted. builtin: A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). operatorChars: All characters that must be handled as operators. client: Commands parsed and executed by the client (not the server). support: A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. * zerolessFloat: .1 * decimallessFloat: 1. * hexNumber: X'01AF' X'01af' x'01AF' x'01af' 0x01AF 0x01af * binaryNumber: b'01' B'01' 0b01 * doubleQuote: "string" * escapeConstant: E'' * nCharCast: N'string' * charsetCast: _utf8'string' * commentHash: use # char for comments * commentSlashSlash: use // for comments * commentSpaceRequired: require a space after -- for comments atoms: Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: UNKNOWN, INFINITY, UNDERFLOW, NaN... dateSQL: Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. */ ================================================ FILE: public/assets/lib/vendor/codemirror/mode/stex/index.html ================================================ CodeMirror: sTeX mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • sTeX

    sTeX mode

    sTeX mode supports this option:

    inMathMode: boolean
    Whether to start parsing in math mode (default: false).

    MIME types defined: text/x-stex.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/stex/stex.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) * Licence: MIT */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("stex", function(_config, parserConfig) { "use strict"; function pushCommand(state, command) { state.cmdState.push(command); } function peekCommand(state) { if (state.cmdState.length > 0) { return state.cmdState[state.cmdState.length - 1]; } else { return null; } } function popCommand(state) { var plug = state.cmdState.pop(); if (plug) { plug.closeBracket(); } } // returns the non-default plugin closest to the end of the list function getMostPowerful(state) { var context = state.cmdState; for (var i = context.length - 1; i >= 0; i--) { var plug = context[i]; if (plug.name == "DEFAULT") { continue; } return plug; } return { styleIdentifier: function() { return null; } }; } function addPluginPattern(pluginName, cmdStyle, styles) { return function () { this.name = pluginName; this.bracketNo = 0; this.style = cmdStyle; this.styles = styles; this.argument = null; // \begin and \end have arguments that follow. These are stored in the plugin this.styleIdentifier = function() { return this.styles[this.bracketNo - 1] || null; }; this.openBracket = function() { this.bracketNo++; return "bracket"; }; this.closeBracket = function() {}; }; } var plugins = {}; plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]); plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]); plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]); plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]); plugins["end"] = addPluginPattern("end", "tag", ["atom"]); plugins["label" ] = addPluginPattern("label" , "tag", ["atom"]); plugins["ref" ] = addPluginPattern("ref" , "tag", ["atom"]); plugins["eqref" ] = addPluginPattern("eqref" , "tag", ["atom"]); plugins["cite" ] = addPluginPattern("cite" , "tag", ["atom"]); plugins["bibitem" ] = addPluginPattern("bibitem" , "tag", ["atom"]); plugins["Bibitem" ] = addPluginPattern("Bibitem" , "tag", ["atom"]); plugins["RBibitem" ] = addPluginPattern("RBibitem" , "tag", ["atom"]); plugins["DEFAULT"] = function () { this.name = "DEFAULT"; this.style = "tag"; this.styleIdentifier = this.openBracket = this.closeBracket = function() {}; }; function setState(state, f) { state.f = f; } // called when in a normal (no environment) context function normal(source, state) { var plug; // Do we look like '\command' ? If so, attempt to apply the plugin 'command' if (source.match(/^\\[a-zA-Z@]+/)) { var cmdName = source.current().slice(1); plug = plugins.hasOwnProperty(cmdName) ? plugins[cmdName] : plugins["DEFAULT"]; plug = new plug(); pushCommand(state, plug); setState(state, beginParams); return plug.style; } // escape characters if (source.match(/^\\[$&%#{}_]/)) { return "tag"; } // white space control characters if (source.match(/^\\[,;!\/\\]/)) { return "tag"; } // find if we're starting various math modes if (source.match("\\[")) { setState(state, function(source, state){ return inMathMode(source, state, "\\]"); }); return "keyword"; } if (source.match("\\(")) { setState(state, function(source, state){ return inMathMode(source, state, "\\)"); }); return "keyword"; } if (source.match("$$")) { setState(state, function(source, state){ return inMathMode(source, state, "$$"); }); return "keyword"; } if (source.match("$")) { setState(state, function(source, state){ return inMathMode(source, state, "$"); }); return "keyword"; } var ch = source.next(); if (ch == "%") { source.skipToEnd(); return "comment"; } else if (ch == '}' || ch == ']') { plug = peekCommand(state); if (plug) { plug.closeBracket(ch); setState(state, beginParams); } else { return "error"; } return "bracket"; } else if (ch == '{' || ch == '[') { plug = plugins["DEFAULT"]; plug = new plug(); pushCommand(state, plug); return "bracket"; } else if (/\d/.test(ch)) { source.eatWhile(/[\w.%]/); return "atom"; } else { source.eatWhile(/[\w\-_]/); plug = getMostPowerful(state); if (plug.name == 'begin') { plug.argument = source.current(); } return plug.styleIdentifier(); } } function inMathMode(source, state, endModeSeq) { if (source.eatSpace()) { return null; } if (endModeSeq && source.match(endModeSeq)) { setState(state, normal); return "keyword"; } if (source.match(/^\\[a-zA-Z@]+/)) { return "tag"; } if (source.match(/^[a-zA-Z]+/)) { return "variable-2"; } // escape characters if (source.match(/^\\[$&%#{}_]/)) { return "tag"; } // white space control characters if (source.match(/^\\[,;!\/]/)) { return "tag"; } // special math-mode characters if (source.match(/^[\^_&]/)) { return "tag"; } // non-special characters if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) { return null; } if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) { return "number"; } var ch = source.next(); if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") { return "bracket"; } if (ch == "%") { source.skipToEnd(); return "comment"; } return "error"; } function beginParams(source, state) { var ch = source.peek(), lastPlug; if (ch == '{' || ch == '[') { lastPlug = peekCommand(state); lastPlug.openBracket(ch); source.eat(ch); setState(state, normal); return "bracket"; } if (/[ \t\r]/.test(ch)) { source.eat(ch); return null; } setState(state, normal); popCommand(state); return normal(source, state); } return { startState: function() { var f = parserConfig.inMathMode ? function(source, state){ return inMathMode(source, state); } : normal; return { cmdState: [], f: f }; }, copyState: function(s) { return { cmdState: s.cmdState.slice(), f: s.f }; }, token: function(stream, state) { return state.f(stream, state); }, blankLine: function(state) { state.f = normal; state.cmdState.length = 0; }, lineComment: "%" }; }); CodeMirror.defineMIME("text/x-stex", "stex"); CodeMirror.defineMIME("text/x-latex", "stex"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/stex/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({tabSize: 4}, "stex"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("word", "foo"); MT("twoWords", "foo bar"); MT("beginEndDocument", "[tag \\begin][bracket {][atom document][bracket }]", "[tag \\end][bracket {][atom document][bracket }]"); MT("beginEndEquation", "[tag \\begin][bracket {][atom equation][bracket }]", " E=mc^2", "[tag \\end][bracket {][atom equation][bracket }]"); MT("beginModule", "[tag \\begin][bracket {][atom module][bracket }[[]]]"); MT("beginModuleId", "[tag \\begin][bracket {][atom module][bracket }[[]id=bbt-size[bracket ]]]"); MT("importModule", "[tag \\importmodule][bracket [[][string b-b-t][bracket ]]{][builtin b-b-t][bracket }]"); MT("importModulePath", "[tag \\importmodule][bracket [[][tag \\KWARCslides][bracket {][string dmath/en/cardinality][bracket }]]{][builtin card][bracket }]"); MT("psForPDF", "[tag \\PSforPDF][bracket [[][atom 1][bracket ]]{]#1[bracket }]"); MT("comment", "[comment % foo]"); MT("tagComment", "[tag \\item][comment % bar]"); MT("commentTag", " [comment % \\item]"); MT("commentLineBreak", "[comment %]", "foo"); MT("tagErrorCurly", "[tag \\begin][error }][bracket {]"); MT("tagErrorSquare", "[tag \\item][error ]]][bracket {]"); MT("commentCurly", "[comment % }]"); MT("tagHash", "the [tag \\#] key"); MT("tagNumber", "a [tag \\$][atom 5] stetson"); MT("tagPercent", "[atom 100][tag \\%] beef"); MT("tagAmpersand", "L [tag \\&] N"); MT("tagUnderscore", "foo[tag \\_]bar"); MT("tagBracketOpen", "[tag \\emph][bracket {][tag \\{][bracket }]"); MT("tagBracketClose", "[tag \\emph][bracket {][tag \\}][bracket }]"); MT("tagLetterNumber", "section [tag \\S][atom 1]"); MT("textTagNumber", "para [tag \\P][atom 2]"); MT("thinspace", "x[tag \\,]y"); MT("thickspace", "x[tag \\;]y"); MT("negativeThinspace", "x[tag \\!]y"); MT("periodNotSentence", "J.\\ L.\\ is"); MT("periodSentence", "X[tag \\@]. The"); MT("italicCorrection", "[bracket {][tag \\em] If[tag \\/][bracket }] I"); MT("tagBracket", "[tag \\newcommand][bracket {][tag \\pop][bracket }]"); MT("inlineMathTagFollowedByNumber", "[keyword $][tag \\pi][number 2][keyword $]"); MT("inlineMath", "[keyword $][number 3][variable-2 x][tag ^][number 2.45]-[tag \\sqrt][bracket {][tag \\$\\alpha][bracket }] = [number 2][keyword $] other text"); MT("inlineMathLatexStyle", "[keyword \\(][number 3][variable-2 x][tag ^][number 2.45]-[tag \\sqrt][bracket {][tag \\$\\alpha][bracket }] = [number 2][keyword \\)] other text"); MT("displayMath", "More [keyword $$]\t[variable-2 S][tag ^][variable-2 n][tag \\sum] [variable-2 i][keyword $$] other text"); MT("displayMath environment", "[tag \\begin][bracket {][atom equation][bracket }] x [tag \\end][bracket {][atom equation][bracket }] other text"); MT("displayMath environment with label", "[tag \\begin][bracket {][atom equation][bracket }][tag \\label][bracket {][atom eq1][bracket }] x [tag \\end][bracket {][atom equation][bracket }] other text~[tag \\ref][bracket {][atom eq1][bracket }]"); MT("mathWithComment", "[keyword $][variable-2 x] [comment % $]", "[variable-2 y][keyword $] other text"); MT("lineBreakArgument", "[tag \\\\][bracket [[][atom 1cm][bracket ]]]"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/stylus/index.html ================================================ CodeMirror: Stylus mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Stylus

    Stylus mode

    MIME types defined: text/x-styl.

    Created by Dmitry Kiselyov

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/stylus/stylus.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Stylus mode created by Dmitry Kiselyov http://git.io/AaRB (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("stylus", function(config) { var indentUnit = config.indentUnit, indentUnitString = '', tagKeywords = keySet(tagKeywords_), tagVariablesRegexp = /^(a|b|i|s|col|em)$/i, propertyKeywords = keySet(propertyKeywords_), nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_), valueKeywords = keySet(valueKeywords_), colorKeywords = keySet(colorKeywords_), documentTypes = keySet(documentTypes_), documentTypesRegexp = wordRegexp(documentTypes_), mediaFeatures = keySet(mediaFeatures_), mediaTypes = keySet(mediaTypes_), fontProperties = keySet(fontProperties_), operatorsRegexp = /^\s*([.]{2,3}|&&|\|\||\*\*|[?!=:]?=|[-+*\/%<>]=?|\?:|\~)/, wordOperatorKeywordsRegexp = wordRegexp(wordOperatorKeywords_), blockKeywords = keySet(blockKeywords_), vendorPrefixesRegexp = new RegExp(/^\-(moz|ms|o|webkit)-/i), commonAtoms = keySet(commonAtoms_), firstWordMatch = "", states = {}, ch, style, type, override; while (indentUnitString.length < indentUnit) indentUnitString += ' '; /** * Tokenizers */ function tokenBase(stream, state) { firstWordMatch = stream.string.match(/(^[\w-]+\s*=\s*$)|(^\s*[\w-]+\s*=\s*[\w-])|(^\s*(\.|#|@|\$|\&|\[|\d|\+|::?|\{|\>|~|\/)?\s*[\w-]*([a-z0-9-]|\*|\/\*)(\(|,)?)/); state.context.line.firstWord = firstWordMatch ? firstWordMatch[0].replace(/^\s*/, "") : ""; state.context.line.indent = stream.indentation(); ch = stream.peek(); // Line comment if (stream.match("//")) { stream.skipToEnd(); return ["comment", "comment"]; } // Block comment if (stream.match("/*")) { state.tokenize = tokenCComment; return tokenCComment(stream, state); } // String if (ch == "\"" || ch == "'") { stream.next(); state.tokenize = tokenString(ch); return state.tokenize(stream, state); } // Def if (ch == "@") { stream.next(); stream.eatWhile(/[\w\\-]/); return ["def", stream.current()]; } // ID selector or Hex color if (ch == "#") { stream.next(); // Hex color if (stream.match(/^[0-9a-f]{3}([0-9a-f]([0-9a-f]{2}){0,2})?\b(?!-)/i)) { return ["atom", "atom"]; } // ID selector if (stream.match(/^[a-z][\w-]*/i)) { return ["builtin", "hash"]; } } // Vendor prefixes if (stream.match(vendorPrefixesRegexp)) { return ["meta", "vendor-prefixes"]; } // Numbers if (stream.match(/^-?[0-9]?\.?[0-9]/)) { stream.eatWhile(/[a-z%]/i); return ["number", "unit"]; } // !important|optional if (ch == "!") { stream.next(); return [stream.match(/^(important|optional)/i) ? "keyword": "operator", "important"]; } // Class if (ch == "." && stream.match(/^\.[a-z][\w-]*/i)) { return ["qualifier", "qualifier"]; } // url url-prefix domain regexp if (stream.match(documentTypesRegexp)) { if (stream.peek() == "(") state.tokenize = tokenParenthesized; return ["property", "word"]; } // Mixins / Functions if (stream.match(/^[a-z][\w-]*\(/i)) { stream.backUp(1); return ["keyword", "mixin"]; } // Block mixins if (stream.match(/^(\+|-)[a-z][\w-]*\(/i)) { stream.backUp(1); return ["keyword", "block-mixin"]; } // Parent Reference BEM naming if (stream.string.match(/^\s*&/) && stream.match(/^[-_]+[a-z][\w-]*/)) { return ["qualifier", "qualifier"]; } // / Root Reference & Parent Reference if (stream.match(/^(\/|&)(-|_|:|\.|#|[a-z])/)) { stream.backUp(1); return ["variable-3", "reference"]; } if (stream.match(/^&{1}\s*$/)) { return ["variable-3", "reference"]; } // Word operator if (stream.match(wordOperatorKeywordsRegexp)) { return ["operator", "operator"]; } // Word if (stream.match(/^\$?[-_]*[a-z0-9]+[\w-]*/i)) { // Variable if (stream.match(/^(\.|\[)[\w-\'\"\]]+/i, false)) { if (!wordIsTag(stream.current())) { stream.match('.'); return ["variable-2", "variable-name"]; } } return ["variable-2", "word"]; } // Operators if (stream.match(operatorsRegexp)) { return ["operator", stream.current()]; } // Delimiters if (/[:;,{}\[\]\(\)]/.test(ch)) { stream.next(); return [null, ch]; } // Non-detected items stream.next(); return [null, null]; } /** * Token comment */ function tokenCComment(stream, state) { var maybeEnd = false, ch; while ((ch = stream.next()) != null) { if (maybeEnd && ch == "/") { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return ["comment", "comment"]; } /** * Token string */ function tokenString(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { if (quote == ")") stream.backUp(1); break; } escaped = !escaped && ch == "\\"; } if (ch == quote || !escaped && quote != ")") state.tokenize = null; return ["string", "string"]; }; } /** * Token parenthesized */ function tokenParenthesized(stream, state) { stream.next(); // Must be "(" if (!stream.match(/\s*[\"\')]/, false)) state.tokenize = tokenString(")"); else state.tokenize = null; return [null, "("]; } /** * Context management */ function Context(type, indent, prev, line) { this.type = type; this.indent = indent; this.prev = prev; this.line = line || {firstWord: "", indent: 0}; } function pushContext(state, stream, type, indent) { indent = indent >= 0 ? indent : indentUnit; state.context = new Context(type, stream.indentation() + indent, state.context); return type; } function popContext(state, currentIndent) { var contextIndent = state.context.indent - indentUnit; currentIndent = currentIndent || false; state.context = state.context.prev; if (currentIndent) state.context.indent = contextIndent; return state.context.type; } function pass(type, stream, state) { return states[state.context.type](type, stream, state); } function popAndPass(type, stream, state, n) { for (var i = n || 1; i > 0; i--) state.context = state.context.prev; return pass(type, stream, state); } /** * Parser */ function wordIsTag(word) { return word.toLowerCase() in tagKeywords; } function wordIsProperty(word) { word = word.toLowerCase(); return word in propertyKeywords || word in fontProperties; } function wordIsBlock(word) { return word.toLowerCase() in blockKeywords; } function wordIsVendorPrefix(word) { return word.toLowerCase().match(vendorPrefixesRegexp); } function wordAsValue(word) { var wordLC = word.toLowerCase(); var override = "variable-2"; if (wordIsTag(word)) override = "tag"; else if (wordIsBlock(word)) override = "block-keyword"; else if (wordIsProperty(word)) override = "property"; else if (wordLC in valueKeywords || wordLC in commonAtoms) override = "atom"; else if (wordLC == "return" || wordLC in colorKeywords) override = "keyword"; // Font family else if (word.match(/^[A-Z]/)) override = "string"; return override; } function typeIsBlock(type, stream) { return ((endOfLine(stream) && (type == "{" || type == "]" || type == "hash" || type == "qualifier")) || type == "block-mixin"); } function typeIsInterpolation(type, stream) { return type == "{" && stream.match(/^\s*\$?[\w-]+/i, false); } function typeIsPseudo(type, stream) { return type == ":" && stream.match(/^[a-z-]+/, false); } function startOfLine(stream) { return stream.sol() || stream.string.match(new RegExp("^\\s*" + escapeRegExp(stream.current()))); } function endOfLine(stream) { return stream.eol() || stream.match(/^\s*$/, false); } function firstWordOfLine(line) { var re = /^\s*[-_]*[a-z0-9]+[\w-]*/i; var result = typeof line == "string" ? line.match(re) : line.string.match(re); return result ? result[0].replace(/^\s*/, "") : ""; } /** * Block */ states.block = function(type, stream, state) { if ((type == "comment" && startOfLine(stream)) || (type == "," && endOfLine(stream)) || type == "mixin") { return pushContext(state, stream, "block", 0); } if (typeIsInterpolation(type, stream)) { return pushContext(state, stream, "interpolation"); } if (endOfLine(stream) && type == "]") { if (!/^\s*(\.|#|:|\[|\*|&)/.test(stream.string) && !wordIsTag(firstWordOfLine(stream))) { return pushContext(state, stream, "block", 0); } } if (typeIsBlock(type, stream)) { return pushContext(state, stream, "block"); } if (type == "}" && endOfLine(stream)) { return pushContext(state, stream, "block", 0); } if (type == "variable-name") { if (stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/) || wordIsBlock(firstWordOfLine(stream))) { return pushContext(state, stream, "variableName"); } else { return pushContext(state, stream, "variableName", 0); } } if (type == "=") { if (!endOfLine(stream) && !wordIsBlock(firstWordOfLine(stream))) { return pushContext(state, stream, "block", 0); } return pushContext(state, stream, "block"); } if (type == "*") { if (endOfLine(stream) || stream.match(/\s*(,|\.|#|\[|:|{)/,false)) { override = "tag"; return pushContext(state, stream, "block"); } } if (typeIsPseudo(type, stream)) { return pushContext(state, stream, "pseudo"); } if (/@(font-face|media|supports|(-moz-)?document)/.test(type)) { return pushContext(state, stream, endOfLine(stream) ? "block" : "atBlock"); } if (/@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) { return pushContext(state, stream, "keyframes"); } if (/@extends?/.test(type)) { return pushContext(state, stream, "extend", 0); } if (type && type.charAt(0) == "@") { // Property Lookup if (stream.indentation() > 0 && wordIsProperty(stream.current().slice(1))) { override = "variable-2"; return "block"; } if (/(@import|@require|@charset)/.test(type)) { return pushContext(state, stream, "block", 0); } return pushContext(state, stream, "block"); } if (type == "reference" && endOfLine(stream)) { return pushContext(state, stream, "block"); } if (type == "(") { return pushContext(state, stream, "parens"); } if (type == "vendor-prefixes") { return pushContext(state, stream, "vendorPrefixes"); } if (type == "word") { var word = stream.current(); override = wordAsValue(word); if (override == "property") { if (startOfLine(stream)) { return pushContext(state, stream, "block", 0); } else { override = "atom"; return "block"; } } if (override == "tag") { // tag is a css value if (/embed|menu|pre|progress|sub|table/.test(word)) { if (wordIsProperty(firstWordOfLine(stream))) { override = "atom"; return "block"; } } // tag is an attribute if (stream.string.match(new RegExp("\\[\\s*" + word + "|" + word +"\\s*\\]"))) { override = "atom"; return "block"; } // tag is a variable if (tagVariablesRegexp.test(word)) { if ((startOfLine(stream) && stream.string.match(/=/)) || (!startOfLine(stream) && !stream.string.match(/^(\s*\.|#|\&|\[|\/|>|\*)/) && !wordIsTag(firstWordOfLine(stream)))) { override = "variable-2"; if (wordIsBlock(firstWordOfLine(stream))) return "block"; return pushContext(state, stream, "block", 0); } } if (endOfLine(stream)) return pushContext(state, stream, "block"); } if (override == "block-keyword") { override = "keyword"; // Postfix conditionals if (stream.current(/(if|unless)/) && !startOfLine(stream)) { return "block"; } return pushContext(state, stream, "block"); } if (word == "return") return pushContext(state, stream, "block", 0); // Placeholder selector if (override == "variable-2" && stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/)) { return pushContext(state, stream, "block"); } } return state.context.type; }; /** * Parens */ states.parens = function(type, stream, state) { if (type == "(") return pushContext(state, stream, "parens"); if (type == ")") { if (state.context.prev.type == "parens") { return popContext(state); } if ((stream.string.match(/^[a-z][\w-]*\(/i) && endOfLine(stream)) || wordIsBlock(firstWordOfLine(stream)) || /(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(firstWordOfLine(stream)) || (!stream.string.match(/^-?[a-z][\w-\.\[\]\'\"]*\s*=/) && wordIsTag(firstWordOfLine(stream)))) { return pushContext(state, stream, "block"); } if (stream.string.match(/^[\$-]?[a-z][\w-\.\[\]\'\"]*\s*=/) || stream.string.match(/^\s*(\(|\)|[0-9])/) || stream.string.match(/^\s+[a-z][\w-]*\(/i) || stream.string.match(/^\s+[\$-]?[a-z]/i)) { return pushContext(state, stream, "block", 0); } if (endOfLine(stream)) return pushContext(state, stream, "block"); else return pushContext(state, stream, "block", 0); } if (type && type.charAt(0) == "@" && wordIsProperty(stream.current().slice(1))) { override = "variable-2"; } if (type == "word") { var word = stream.current(); override = wordAsValue(word); if (override == "tag" && tagVariablesRegexp.test(word)) { override = "variable-2"; } if (override == "property" || word == "to") override = "atom"; } if (type == "variable-name") { return pushContext(state, stream, "variableName"); } if (typeIsPseudo(type, stream)) { return pushContext(state, stream, "pseudo"); } return state.context.type; }; /** * Vendor prefixes */ states.vendorPrefixes = function(type, stream, state) { if (type == "word") { override = "property"; return pushContext(state, stream, "block", 0); } return popContext(state); }; /** * Pseudo */ states.pseudo = function(type, stream, state) { if (!wordIsProperty(firstWordOfLine(stream.string))) { stream.match(/^[a-z-]+/); override = "variable-3"; if (endOfLine(stream)) return pushContext(state, stream, "block"); return popContext(state); } return popAndPass(type, stream, state); }; /** * atBlock */ states.atBlock = function(type, stream, state) { if (type == "(") return pushContext(state, stream, "atBlock_parens"); if (typeIsBlock(type, stream)) { return pushContext(state, stream, "block"); } if (typeIsInterpolation(type, stream)) { return pushContext(state, stream, "interpolation"); } if (type == "word") { var word = stream.current().toLowerCase(); if (/^(only|not|and|or)$/.test(word)) override = "keyword"; else if (documentTypes.hasOwnProperty(word)) override = "tag"; else if (mediaTypes.hasOwnProperty(word)) override = "attribute"; else if (mediaFeatures.hasOwnProperty(word)) override = "property"; else if (nonStandardPropertyKeywords.hasOwnProperty(word)) override = "string-2"; else override = wordAsValue(stream.current()); if (override == "tag" && endOfLine(stream)) { return pushContext(state, stream, "block"); } } if (type == "operator" && /^(not|and|or)$/.test(stream.current())) { override = "keyword"; } return state.context.type; }; states.atBlock_parens = function(type, stream, state) { if (type == "{" || type == "}") return state.context.type; if (type == ")") { if (endOfLine(stream)) return pushContext(state, stream, "block"); else return pushContext(state, stream, "atBlock"); } if (type == "word") { var word = stream.current().toLowerCase(); override = wordAsValue(word); if (/^(max|min)/.test(word)) override = "property"; if (override == "tag") { tagVariablesRegexp.test(word) ? override = "variable-2" : override = "atom"; } return state.context.type; } return states.atBlock(type, stream, state); }; /** * Keyframes */ states.keyframes = function(type, stream, state) { if (stream.indentation() == "0" && ((type == "}" && startOfLine(stream)) || type == "]" || type == "hash" || type == "qualifier" || wordIsTag(stream.current()))) { return popAndPass(type, stream, state); } if (type == "{") return pushContext(state, stream, "keyframes"); if (type == "}") { if (startOfLine(stream)) return popContext(state, true); else return pushContext(state, stream, "keyframes"); } if (type == "unit" && /^[0-9]+\%$/.test(stream.current())) { return pushContext(state, stream, "keyframes"); } if (type == "word") { override = wordAsValue(stream.current()); if (override == "block-keyword") { override = "keyword"; return pushContext(state, stream, "keyframes"); } } if (/@(font-face|media|supports|(-moz-)?document)/.test(type)) { return pushContext(state, stream, endOfLine(stream) ? "block" : "atBlock"); } if (type == "mixin") { return pushContext(state, stream, "block", 0); } return state.context.type; }; /** * Interpolation */ states.interpolation = function(type, stream, state) { if (type == "{") popContext(state) && pushContext(state, stream, "block"); if (type == "}") { if (stream.string.match(/^\s*(\.|#|:|\[|\*|&|>|~|\+|\/)/i) || (stream.string.match(/^\s*[a-z]/i) && wordIsTag(firstWordOfLine(stream)))) { return pushContext(state, stream, "block"); } if (!stream.string.match(/^(\{|\s*\&)/) || stream.match(/\s*[\w-]/,false)) { return pushContext(state, stream, "block", 0); } return pushContext(state, stream, "block"); } if (type == "variable-name") { return pushContext(state, stream, "variableName", 0); } if (type == "word") { override = wordAsValue(stream.current()); if (override == "tag") override = "atom"; } return state.context.type; }; /** * Extend/s */ states.extend = function(type, stream, state) { if (type == "[" || type == "=") return "extend"; if (type == "]") return popContext(state); if (type == "word") { override = wordAsValue(stream.current()); return "extend"; } return popContext(state); }; /** * Variable name */ states.variableName = function(type, stream, state) { if (type == "string" || type == "[" || type == "]" || stream.current().match(/^(\.|\$)/)) { if (stream.current().match(/^\.[\w-]+/i)) override = "variable-2"; return "variableName"; } return popAndPass(type, stream, state); }; return { startState: function(base) { return { tokenize: null, state: "block", context: new Context("block", base || 0, null) }; }, token: function(stream, state) { if (!state.tokenize && stream.eatSpace()) return null; style = (state.tokenize || tokenBase)(stream, state); if (style && typeof style == "object") { type = style[1]; style = style[0]; } override = style; state.state = states[state.state](type, stream, state); return override; }, indent: function(state, textAfter, line) { var cx = state.context, ch = textAfter && textAfter.charAt(0), indent = cx.indent, lineFirstWord = firstWordOfLine(textAfter), lineIndent = line.match(/^\s*/)[0].replace(/\t/g, indentUnitString).length, prevLineFirstWord = state.context.prev ? state.context.prev.line.firstWord : "", prevLineIndent = state.context.prev ? state.context.prev.line.indent : lineIndent; if (cx.prev && (ch == "}" && (cx.type == "block" || cx.type == "atBlock" || cx.type == "keyframes") || ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") || ch == "{" && (cx.type == "at"))) { indent = cx.indent - indentUnit; } else if (!(/(\})/.test(ch))) { if (/@|\$|\d/.test(ch) || /^\{/.test(textAfter) || /^\s*\/(\/|\*)/.test(textAfter) || /^\s*\/\*/.test(prevLineFirstWord) || /^\s*[\w-\.\[\]\'\"]+\s*(\?|:|\+)?=/i.test(textAfter) || /^(\+|-)?[a-z][\w-]*\(/i.test(textAfter) || /^return/.test(textAfter) || wordIsBlock(lineFirstWord)) { indent = lineIndent; } else if (/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(ch) || wordIsTag(lineFirstWord)) { if (/\,\s*$/.test(prevLineFirstWord)) { indent = prevLineIndent; } else if (/^\s+/.test(line) && (/(\.|#|:|\[|\*|&|>|~|\+|\/)/.test(prevLineFirstWord) || wordIsTag(prevLineFirstWord))) { indent = lineIndent <= prevLineIndent ? prevLineIndent : prevLineIndent + indentUnit; } else { indent = lineIndent; } } else if (!/,\s*$/.test(line) && (wordIsVendorPrefix(lineFirstWord) || wordIsProperty(lineFirstWord))) { if (wordIsBlock(prevLineFirstWord)) { indent = lineIndent <= prevLineIndent ? prevLineIndent : prevLineIndent + indentUnit; } else if (/^\{/.test(prevLineFirstWord)) { indent = lineIndent <= prevLineIndent ? lineIndent : prevLineIndent + indentUnit; } else if (wordIsVendorPrefix(prevLineFirstWord) || wordIsProperty(prevLineFirstWord)) { indent = lineIndent >= prevLineIndent ? prevLineIndent : lineIndent; } else if (/^(\.|#|:|\[|\*|&|@|\+|\-|>|~|\/)/.test(prevLineFirstWord) || /=\s*$/.test(prevLineFirstWord) || wordIsTag(prevLineFirstWord) || /^\$[\w-\.\[\]\'\"]/.test(prevLineFirstWord)) { indent = prevLineIndent + indentUnit; } else { indent = lineIndent; } } } return indent; }, electricChars: "}", blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", lineComment: "//", fold: "indent" }; }); // developer.mozilla.org/en-US/docs/Web/HTML/Element var tagKeywords_ = ["a","abbr","address","area","article","aside","audio", "b", "base","bdi", "bdo","bgsound","blockquote","body","br","button","canvas","caption","cite", "code","col","colgroup","data","datalist","dd","del","details","dfn","div", "dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1", "h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe", "img","input","ins","kbd","keygen","label","legend","li","link","main","map", "mark","marquee","menu","menuitem","meta","meter","nav","nobr","noframes", "noscript","object","ol","optgroup","option","output","p","param","pre", "progress","q","rp","rt","ruby","s","samp","script","section","select", "small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","track", "u","ul","var","video"]; // github.com/codemirror/CodeMirror/blob/master/mode/css/css.js // Note, "url-prefix" should precede "url" in order to match correctly in documentTypesRegexp var documentTypes_ = ["domain", "regexp", "url-prefix", "url"]; var mediaTypes_ = ["all","aural","braille","handheld","print","projection","screen","tty","tv","embossed"]; var mediaFeatures_ = ["width","min-width","max-width","height","min-height","max-height","device-width","min-device-width","max-device-width","device-height","min-device-height","max-device-height","aspect-ratio","min-aspect-ratio","max-aspect-ratio","device-aspect-ratio","min-device-aspect-ratio","max-device-aspect-ratio","color","min-color","max-color","color-index","min-color-index","max-color-index","monochrome","min-monochrome","max-monochrome","resolution","min-resolution","max-resolution","scan","grid","dynamic-range","video-dynamic-range"]; var propertyKeywords_ = ["align-content","align-items","align-self","alignment-adjust","alignment-baseline","anchor-point","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","appearance","azimuth","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","baseline-shift","binding","bleed","bookmark-label","bookmark-level","bookmark-state","bookmark-target","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","color","color-profile","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","crop","cue","cue-after","cue-before","cursor","direction","display","dominant-baseline","drop-initial-after-adjust","drop-initial-after-align","drop-initial-before-adjust","drop-initial-before-align","drop-initial-size","drop-initial-value","elevation","empty-cells","fit","fit-position","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","float-offset","flow-from","flow-into","font","font-feature-settings","font-family","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-position","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","inline-box-align","justify-content","left","letter-spacing","line-break","line-height","line-stacking","line-stacking-ruby","line-stacking-shift","line-stacking-strategy","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marker-offset","marks","marquee-direction","marquee-loop","marquee-play-count","marquee-speed","marquee-style","max-height","max-width","min-height","min-width","move-to","nav-down","nav-index","nav-left","nav-right","nav-up","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-style","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","page-policy","pause","pause-after","pause-before","perspective","perspective-origin","pitch","pitch-range","play-during","position","presentation-level","punctuation-trim","quotes","region-break-after","region-break-before","region-break-inside","region-fragment","rendering-intent","resize","rest","rest-after","rest-before","richness","right","rotation","rotation-point","ruby-align","ruby-overhang","ruby-position","ruby-span","shape-image-threshold","shape-inside","shape-margin","shape-outside","size","speak","speak-as","speak-header","speak-numeral","speak-punctuation","speech-rate","stress","string-set","tab-size","table-layout","target","target-name","target-new","target-position","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-height","text-indent","text-justify","text-outline","text-overflow","text-shadow","text-size-adjust","text-space-collapse","text-transform","text-underline-position","text-wrap","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","z-index","clip-path","clip-rule","mask","enable-background","filter","flood-color","flood-opacity","lighting-color","stop-color","stop-opacity","pointer-events","color-interpolation","color-interpolation-filters","color-rendering","fill","fill-opacity","fill-rule","image-rendering","marker","marker-end","marker-mid","marker-start","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-rendering","baseline-shift","dominant-baseline","glyph-orientation-horizontal","glyph-orientation-vertical","text-anchor","writing-mode","font-smoothing","osx-font-smoothing"]; var nonStandardPropertyKeywords_ = ["scrollbar-arrow-color","scrollbar-base-color","scrollbar-dark-shadow-color","scrollbar-face-color","scrollbar-highlight-color","scrollbar-shadow-color","scrollbar-3d-light-color","scrollbar-track-color","shape-inside","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","zoom"]; var fontProperties_ = ["font-family","src","unicode-range","font-variant","font-feature-settings","font-stretch","font-weight","font-style"]; var colorKeywords_ = ["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"]; var valueKeywords_ = ["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","conic-gradient","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","high","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","matrix","matrix3d","media-play-button","media-slider","media-sliderthumb","media-volume-slider","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeating-conic-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scale","scale3d","scaleX","scaleY","scaleZ","scroll","scrollbar","scroll-position","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","spell-out","square","square-button","standard","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale","row","row-reverse","wrap","wrap-reverse","column-reverse","flex-start","flex-end","space-between","space-around", "unset"]; var wordOperatorKeywords_ = ["in","and","or","not","is not","is a","is","isnt","defined","if unless"], blockKeywords_ = ["for","if","else","unless", "from", "to"], commonAtoms_ = ["null","true","false","href","title","type","not-allowed","readonly","disabled"], commonDef_ = ["@font-face", "@keyframes", "@media", "@viewport", "@page", "@host", "@supports", "@block", "@css"]; var hintWords = tagKeywords_.concat(documentTypes_,mediaTypes_,mediaFeatures_, propertyKeywords_,nonStandardPropertyKeywords_, colorKeywords_,valueKeywords_,fontProperties_, wordOperatorKeywords_,blockKeywords_, commonAtoms_,commonDef_); function wordRegexp(words) { words = words.sort(function(a,b){return b > a;}); return new RegExp("^((" + words.join(")|(") + "))\\b"); } function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } function escapeRegExp(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } CodeMirror.registerHelper("hintWords", "stylus", hintWords); CodeMirror.defineMIME("text/x-styl", "stylus"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/swift/index.html ================================================ CodeMirror: Swift mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Swift

    Swift mode

    A simple mode for Swift

    MIME types defined: text/x-swift (Swift code)

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/swift/swift.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Swift mode created by Michael Kaminsky https://github.com/mkaminsky11 (function(mod) { if (typeof exports == "object" && typeof module == "object") mod(require("../../lib/codemirror")) else if (typeof define == "function" && define.amd) define(["../../lib/codemirror"], mod) else mod(CodeMirror) })(function(CodeMirror) { "use strict" function wordSet(words) { var set = {} for (var i = 0; i < words.length; i++) set[words[i]] = true return set } var keywords = wordSet(["_","var","let","actor","class","enum","extension","import","protocol","struct","func","typealias","associatedtype", "open","public","internal","fileprivate","private","deinit","init","new","override","self","subscript","super", "convenience","dynamic","final","indirect","lazy","required","static","unowned","unowned(safe)","unowned(unsafe)","weak","as","is", "break","case","continue","default","else","fallthrough","for","guard","if","in","repeat","switch","where","while", "defer","return","inout","mutating","nonmutating","isolated","nonisolated","catch","do","rethrows","throw","throws","async","await","try","didSet","get","set","willSet", "assignment","associativity","infix","left","none","operator","postfix","precedence","precedencegroup","prefix","right", "Any","AnyObject","Type","dynamicType","Self","Protocol","__COLUMN__","__FILE__","__FUNCTION__","__LINE__"]) var definingKeywords = wordSet(["var","let","actor","class","enum","extension","import","protocol","struct","func","typealias","associatedtype","for"]) var atoms = wordSet(["true","false","nil","self","super","_"]) var types = wordSet(["Array","Bool","Character","Dictionary","Double","Float","Int","Int8","Int16","Int32","Int64","Never","Optional","Set","String", "UInt8","UInt16","UInt32","UInt64","Void"]) var operators = "+-/*%=|&<>~^?!" var punc = ":;,.(){}[]" var binary = /^\-?0b[01][01_]*/ var octal = /^\-?0o[0-7][0-7_]*/ var hexadecimal = /^\-?0x[\dA-Fa-f][\dA-Fa-f_]*(?:(?:\.[\dA-Fa-f][\dA-Fa-f_]*)?[Pp]\-?\d[\d_]*)?/ var decimal = /^\-?\d[\d_]*(?:\.\d[\d_]*)?(?:[Ee]\-?\d[\d_]*)?/ var identifier = /^\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1/ var property = /^\.(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/ var instruction = /^\#[A-Za-z]+/ var attribute = /^@(?:\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1)/ //var regexp = /^\/(?!\s)(?:\/\/)?(?:\\.|[^\/])+\// function tokenBase(stream, state, prev) { if (stream.sol()) state.indented = stream.indentation() if (stream.eatSpace()) return null var ch = stream.peek() if (ch == "/") { if (stream.match("//")) { stream.skipToEnd() return "comment" } if (stream.match("/*")) { state.tokenize.push(tokenComment) return tokenComment(stream, state) } } if (stream.match(instruction)) return "builtin" if (stream.match(attribute)) return "attribute" if (stream.match(binary)) return "number" if (stream.match(octal)) return "number" if (stream.match(hexadecimal)) return "number" if (stream.match(decimal)) return "number" if (stream.match(property)) return "property" if (operators.indexOf(ch) > -1) { stream.next() return "operator" } if (punc.indexOf(ch) > -1) { stream.next() stream.match("..") return "punctuation" } var stringMatch if (stringMatch = stream.match(/("""|"|')/)) { var tokenize = tokenString.bind(null, stringMatch[0]) state.tokenize.push(tokenize) return tokenize(stream, state) } if (stream.match(identifier)) { var ident = stream.current() if (types.hasOwnProperty(ident)) return "variable-2" if (atoms.hasOwnProperty(ident)) return "atom" if (keywords.hasOwnProperty(ident)) { if (definingKeywords.hasOwnProperty(ident)) state.prev = "define" return "keyword" } if (prev == "define") return "def" return "variable" } stream.next() return null } function tokenUntilClosingParen() { var depth = 0 return function(stream, state, prev) { var inner = tokenBase(stream, state, prev) if (inner == "punctuation") { if (stream.current() == "(") ++depth else if (stream.current() == ")") { if (depth == 0) { stream.backUp(1) state.tokenize.pop() return state.tokenize[state.tokenize.length - 1](stream, state) } else --depth } } return inner } } function tokenString(openQuote, stream, state) { var singleLine = openQuote.length == 1 var ch, escaped = false while (ch = stream.peek()) { if (escaped) { stream.next() if (ch == "(") { state.tokenize.push(tokenUntilClosingParen()) return "string" } escaped = false } else if (stream.match(openQuote)) { state.tokenize.pop() return "string" } else { stream.next() escaped = ch == "\\" } } if (singleLine) { state.tokenize.pop() } return "string" } function tokenComment(stream, state) { var ch while (ch = stream.next()) { if (ch === "/" && stream.eat("*")) { state.tokenize.push(tokenComment) } else if (ch === "*" && stream.eat("/")) { state.tokenize.pop() break } } return "comment" } function Context(prev, align, indented) { this.prev = prev this.align = align this.indented = indented } function pushContext(state, stream) { var align = stream.match(/^\s*($|\/[\/\*])/, false) ? null : stream.column() + 1 state.context = new Context(state.context, align, state.indented) } function popContext(state) { if (state.context) { state.indented = state.context.indented state.context = state.context.prev } } CodeMirror.defineMode("swift", function(config) { return { startState: function() { return { prev: null, context: null, indented: 0, tokenize: [] } }, token: function(stream, state) { var prev = state.prev state.prev = null var tokenize = state.tokenize[state.tokenize.length - 1] || tokenBase var style = tokenize(stream, state, prev) if (!style || style == "comment") state.prev = prev else if (!state.prev) state.prev = style if (style == "punctuation") { var bracket = /[\(\[\{]|([\]\)\}])/.exec(stream.current()) if (bracket) (bracket[1] ? popContext : pushContext)(state, stream) } return style }, indent: function(state, textAfter) { var cx = state.context if (!cx) return 0 var closing = /^[\]\}\)]/.test(textAfter) if (cx.align != null) return cx.align - (closing ? 1 : 0) return cx.indented + (closing ? 0 : config.indentUnit) }, electricInput: /^\s*[\)\}\]]$/, lineComment: "//", blockCommentStart: "/*", blockCommentEnd: "*/", fold: "brace", closeBrackets: "()[]{}''\"\"``" } }) CodeMirror.defineMIME("text/x-swift","swift") }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/swift/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "swift"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } // Ensure all number types are properly represented. MT("numbers", "[keyword var] [def a] [operator =] [number 17]", "[keyword var] [def b] [operator =] [number -0.5]", "[keyword var] [def c] [operator =] [number 0.3456e-4]", "[keyword var] [def d] [operator =] [number 345e2]", "[keyword var] [def e] [operator =] [number 0o7324]", "[keyword var] [def f] [operator =] [number 0b10010]", "[keyword var] [def g] [operator =] [number -0x35ade]", "[keyword var] [def h] [operator =] [number 0xaea.ep-13]", "[keyword var] [def i] [operator =] [number 0x13ep6]"); // Variable/class/etc definition. MT("definition", "[keyword var] [def a] [operator =] [number 5]", "[keyword let] [def b][punctuation :] [variable-2 Int] [operator =] [number 10]", "[keyword class] [def C] [punctuation {] [punctuation }]", "[keyword struct] [def D] [punctuation {] [punctuation }]", "[keyword enum] [def E] [punctuation {] [punctuation }]", "[keyword extension] [def F] [punctuation {] [punctuation }]", "[keyword protocol] [def G] [punctuation {] [punctuation }]", "[keyword func] [def h][punctuation ()] [punctuation {] [punctuation }]", "[keyword import] [def Foundation]", "[keyword typealias] [def NewString] [operator =] [variable-2 String]", "[keyword associatedtype] [def I]", "[keyword for] [def j] [keyword in] [number 0][punctuation ..][operator <][number 3] [punctuation {] [punctuation }]"); // Strings and string interpolation. MT("strings", "[keyword var] [def a][punctuation :] [variable-2 String] [operator =] [string \"test\"]", "[keyword var] [def b][punctuation :] [variable-2 String] [operator =] [string \"\\(][variable a][string )\"]", "[keyword var] [def c] [operator =] [string \"\"\"]", "[string multi]", "[string line]", "[string \"test\"]", "[string \"\"\"]", "[variable print][punctuation (][string \"\"][punctuation )]"); // Comments. MT("comments", "[comment // This is a comment]", "[comment /* This is another comment */]", "[keyword var] [def a] [operator =] [number 5] [comment // Third comment]"); // Atoms. MT("atoms", "[keyword class] [def FooClass] [punctuation {]", " [keyword let] [def fooBool][punctuation :] [variable-2 Bool][operator ?]", " [keyword let] [def fooInt][punctuation :] [variable-2 Int][operator ?]", " [keyword func] [keyword init][punctuation (][variable fooBool][punctuation :] [variable-2 Bool][punctuation ,] [variable barBool][punctuation :] [variable-2 Bool][punctuation )] [punctuation {]", " [atom super][property .init][punctuation ()]", " [atom self][property .fooBool] [operator =] [variable fooBool]", " [variable fooInt] [operator =] [atom nil]", " [keyword if] [variable barBool] [operator ==] [atom true] [punctuation {]", " [variable print][punctuation (][string \"True!\"][punctuation )]", " [punctuation }] [keyword else] [keyword if] [variable barBool] [operator ==] [atom false] [punctuation {]", " [keyword for] [atom _] [keyword in] [number 0][punctuation ...][number 5] [punctuation {]", " [variable print][punctuation (][string \"False!\"][punctuation )]", " [punctuation }]", " [punctuation }]", " [punctuation }]", "[punctuation }]"); // Types. MT("types", "[keyword var] [def a] [operator =] [variable-2 Array][operator <][variable-2 Int][operator >]", "[keyword var] [def b] [operator =] [variable-2 Set][operator <][variable-2 Bool][operator >]", "[keyword var] [def c] [operator =] [variable-2 Dictionary][operator <][variable-2 String][punctuation ,][variable-2 Character][operator >]", "[keyword var] [def d][punctuation :] [variable-2 Int64][operator ?] [operator =] [variable-2 Optional][punctuation (][number 8][punctuation )]", "[keyword func] [def e][punctuation ()] [operator ->] [variable-2 Void] [punctuation {]", " [keyword var] [def e1][punctuation :] [variable-2 Float] [operator =] [number 1.2]", "[punctuation }]", "[keyword func] [def f][punctuation ()] [operator ->] [variable-2 Never] [punctuation {]", " [keyword var] [def f1][punctuation :] [variable-2 Double] [operator =] [number 2.4]", "[punctuation }]"); // Operators. MT("operators", "[keyword var] [def a] [operator =] [number 1] [operator +] [number 2]", "[keyword var] [def b] [operator =] [number 1] [operator -] [number 2]", "[keyword var] [def c] [operator =] [number 1] [operator *] [number 2]", "[keyword var] [def d] [operator =] [number 1] [operator /] [number 2]", "[keyword var] [def e] [operator =] [number 1] [operator %] [number 2]", "[keyword var] [def f] [operator =] [number 1] [operator |] [number 2]", "[keyword var] [def g] [operator =] [number 1] [operator &] [number 2]", "[keyword var] [def h] [operator =] [number 1] [operator <<] [number 2]", "[keyword var] [def i] [operator =] [number 1] [operator >>] [number 2]", "[keyword var] [def j] [operator =] [number 1] [operator ^] [number 2]", "[keyword var] [def k] [operator =] [operator ~][number 1]", "[keyword var] [def l] [operator =] [variable foo] [operator ?] [number 1] [punctuation :] [number 2]", "[keyword var] [def m][punctuation :] [variable-2 Int] [operator =] [variable-2 Optional][punctuation (][number 8][punctuation )][operator !]"); // Punctuation. MT("punctuation", "[keyword let] [def a] [operator =] [number 1][punctuation ;] [keyword let] [def b] [operator =] [number 2]", "[keyword let] [def testArr][punctuation :] [punctuation [[][variable-2 Int][punctuation ]]] [operator =] [punctuation [[][variable a][punctuation ,] [variable b][punctuation ]]]", "[keyword for] [def i] [keyword in] [number 0][punctuation ..][operator <][variable testArr][property .count] [punctuation {]", " [variable print][punctuation (][variable testArr][punctuation [[][variable i][punctuation ]])]", "[punctuation }]"); // Identifiers. MT("identifiers", "[keyword let] [def abc] [operator =] [number 1]", "[keyword let] [def ABC] [operator =] [number 2]", "[keyword let] [def _123] [operator =] [number 3]", "[keyword let] [def _$1$2$3] [operator =] [number 4]", "[keyword let] [def A1$_c32_$_] [operator =] [number 5]", "[keyword let] [def `var`] [operator =] [punctuation [[][number 1][punctuation ,] [number 2][punctuation ,] [number 3][punctuation ]]]", "[keyword let] [def square$] [operator =] [variable `var`][property .map] [punctuation {][variable $0] [operator *] [variable $0][punctuation }]", "$$ [number 1][variable a] $[atom _] [variable _$] [variable __] `[variable a] [variable b]`"); // Properties. MT("properties", "[variable print][punctuation (][variable foo][property .abc][punctuation )]", "[variable print][punctuation (][variable foo][property .ABC][punctuation )]", "[variable print][punctuation (][variable foo][property ._123][punctuation )]", "[variable print][punctuation (][variable foo][property ._$1$2$3][punctuation )]", "[variable print][punctuation (][variable foo][property .A1$_c32_$_][punctuation )]", "[variable print][punctuation (][variable foo][property .`var`][punctuation )]", "[variable print][punctuation (][variable foo][property .__][punctuation )]"); // Instructions or other things that start with #. MT("instructions", "[keyword if] [builtin #available][punctuation (][variable iOS] [number 9][punctuation ,] [operator *][punctuation )] [punctuation {}]", "[variable print][punctuation (][builtin #file][punctuation ,] [builtin #function][punctuation )]", "[variable print][punctuation (][builtin #line][punctuation ,] [builtin #column][punctuation )]", "[builtin #if] [atom true]", "[keyword import] [def A]", "[builtin #elseif] [atom false]", "[keyword import] [def B]", "[builtin #endif]", "[builtin #sourceLocation][punctuation (][variable file][punctuation :] [string \"file.swift\"][punctuation ,] [variable line][punctuation :] [number 2][punctuation )]"); // Attributes; things that start with @. MT("attributes", "[attribute @objc][punctuation (][variable objcFoo][punctuation :)]", "[attribute @available][punctuation (][variable iOS][punctuation )]"); // Property/number edge case. MT("property_number", "[variable print][punctuation (][variable foo][property ._123][punctuation )]", "[variable print][punctuation (]") MT("nested_comments", "[comment /*]", "[comment But wait /* this is a nested comment */ for real]", "[comment /**** let * me * show * you ****/]", "[comment ///// let / me / show / you /////]", "[comment */]"); // TODO: correctly identify when multiple variables are being declared // by use of a comma-separated list. // TODO: correctly identify when variables are being declared in a tuple. // TODO: identify protocols as types when used before an extension? })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tcl/index.html ================================================ CodeMirror: Tcl mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Tcl

    Tcl mode

    MIME types defined: text/x-tcl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tcl/tcl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE //tcl mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("tcl", function() { function parseWords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = parseWords("Tcl safe after append array auto_execok auto_import auto_load " + "auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror " + "binary break catch cd close concat continue dde eof encoding error " + "eval exec exit expr fblocked fconfigure fcopy file fileevent filename " + "filename flush for foreach format gets glob global history http if " + "incr info interp join lappend lindex linsert list llength load lrange " + "lreplace lsearch lset lsort memory msgcat namespace open package parray " + "pid pkg::create pkg_mkIndex proc puts pwd re_syntax read regex regexp " + "registry regsub rename resource return scan seek set socket source split " + "string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord " + "tcl_wordBreakAfter tcl_startOfPreviousWord tcl_wordBreakBefore tcltest " + "tclvars tell time trace unknown unset update uplevel upvar variable " + "vwait"); var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch"); var isOperatorChar = /[+\-*&%=<>!?^\/\|]/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenBase(stream, state) { var beforeParams = state.beforeParams; state.beforeParams = false; var ch = stream.next(); if ((ch == '"' || ch == "'") && state.inParams) { return chain(stream, state, tokenString(ch)); } else if (/[\[\]{}\(\),;\.]/.test(ch)) { if (ch == "(" && beforeParams) state.inParams = true; else if (ch == ")") state.inParams = false; return null; } else if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } else if (ch == "#") { if (stream.eat("*")) return chain(stream, state, tokenComment); if (ch == "#" && stream.match(/ *\[ *\[/)) return chain(stream, state, tokenUnparsed); stream.skipToEnd(); return "comment"; } else if (ch == '"') { stream.skipTo(/"/); return "comment"; } else if (ch == "$") { stream.eatWhile(/[$_a-z0-9A-Z\.{:]/); stream.eatWhile(/}/); state.beforeParams = true; return "builtin"; } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "comment"; } else { stream.eatWhile(/[\w\$_{}\xa1-\uffff]/); var word = stream.current().toLowerCase(); if (keywords && keywords.propertyIsEnumerable(word)) return "keyword"; if (functions && functions.propertyIsEnumerable(word)) { state.beforeParams = true; return "keyword"; } return null; } } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end) state.tokenize = tokenBase; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "#" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenUnparsed(stream, state) { var maybeEnd = 0, ch; while (ch = stream.next()) { if (ch == "#" && maybeEnd == 2) { state.tokenize = tokenBase; break; } if (ch == "]") maybeEnd++; else if (ch != " ") maybeEnd = 0; } return "meta"; } return { startState: function() { return { tokenize: tokenBase, beforeParams: false, inParams: false }; }, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, lineComment: "#" }; }); CodeMirror.defineMIME("text/x-tcl", "tcl"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/textile/index.html ================================================ CodeMirror: Textile mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Textile

    Textile mode

    MIME types defined: text/x-textile.

    Parsing/Highlighting Tests: normal, verbose.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/textile/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({tabSize: 4}, 'textile'); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT('simpleParagraphs', 'Some text.', '', 'Some more text.'); /* * Phrase Modifiers */ MT('em', 'foo [em _bar_]'); MT('emBoogus', 'code_mirror'); MT('strong', 'foo [strong *bar*]'); MT('strongBogus', '3 * 3 = 9'); MT('italic', 'foo [em __bar__]'); MT('italicBogus', 'code__mirror'); MT('bold', 'foo [strong **bar**]'); MT('boldBogus', '3 ** 3 = 27'); MT('simpleLink', '[link "CodeMirror":https://codemirror.net]'); MT('referenceLink', '[link "CodeMirror":code_mirror]', 'Normal Text.', '[link [[code_mirror]]https://codemirror.net]'); MT('footCite', 'foo bar[qualifier [[1]]]'); MT('footCiteBogus', 'foo bar[[1a2]]'); MT('special-characters', 'Registered [tag (r)], ' + 'Trademark [tag (tm)], and ' + 'Copyright [tag (c)] 2008'); MT('cite', "A book is [keyword ??The Count of Monte Cristo??] by Dumas."); MT('additionAndDeletion', 'The news networks declared [negative -Al Gore-] ' + '[positive +George W. Bush+] the winner in Florida.'); MT('subAndSup', 'f(x, n) = log [builtin ~4~] x [builtin ^n^]'); MT('spanAndCode', 'A [quote %span element%] and [atom @code element@]'); MT('spanBogus', 'Percentage 25% is not a span.'); MT('citeBogus', 'Question? is not a citation.'); MT('codeBogus', 'user@example.com'); MT('subBogus', '~username'); MT('supBogus', 'foo ^ bar'); MT('deletionBogus', '3 - 3 = 0'); MT('additionBogus', '3 + 3 = 6'); MT('image', 'An image: [string !http://www.example.com/image.png!]'); MT('imageWithAltText', 'An image: [string !http://www.example.com/image.png (Alt Text)!]'); MT('imageWithUrl', 'An image: [string !http://www.example.com/image.png!:http://www.example.com/]'); /* * Headers */ MT('h1', '[header&header-1 h1. foo]'); MT('h2', '[header&header-2 h2. foo]'); MT('h3', '[header&header-3 h3. foo]'); MT('h4', '[header&header-4 h4. foo]'); MT('h5', '[header&header-5 h5. foo]'); MT('h6', '[header&header-6 h6. foo]'); MT('h7Bogus', 'h7. foo'); MT('multipleHeaders', '[header&header-1 h1. Heading 1]', '', 'Some text.', '', '[header&header-2 h2. Heading 2]', '', 'More text.'); MT('h1inline', '[header&header-1 h1. foo ][header&header-1&em _bar_][header&header-1 baz]'); /* * Lists */ MT('ul', 'foo', 'bar', '', '[variable-2 * foo]', '[variable-2 * bar]'); MT('ulNoBlank', 'foo', 'bar', '[variable-2 * foo]', '[variable-2 * bar]'); MT('ol', 'foo', 'bar', '', '[variable-2 # foo]', '[variable-2 # bar]'); MT('olNoBlank', 'foo', 'bar', '[variable-2 # foo]', '[variable-2 # bar]'); MT('ulFormatting', '[variable-2 * ][variable-2&em _foo_][variable-2 bar]', '[variable-2 * ][variable-2&strong *][variable-2&em&strong _foo_]' + '[variable-2&strong *][variable-2 bar]', '[variable-2 * ][variable-2&strong *foo*][variable-2 bar]'); MT('olFormatting', '[variable-2 # ][variable-2&em _foo_][variable-2 bar]', '[variable-2 # ][variable-2&strong *][variable-2&em&strong _foo_]' + '[variable-2&strong *][variable-2 bar]', '[variable-2 # ][variable-2&strong *foo*][variable-2 bar]'); MT('ulNested', '[variable-2 * foo]', '[variable-3 ** bar]', '[keyword *** bar]', '[variable-2 **** bar]', '[variable-3 ** bar]'); MT('olNested', '[variable-2 # foo]', '[variable-3 ## bar]', '[keyword ### bar]', '[variable-2 #### bar]', '[variable-3 ## bar]'); MT('ulNestedWithOl', '[variable-2 * foo]', '[variable-3 ## bar]', '[keyword *** bar]', '[variable-2 #### bar]', '[variable-3 ** bar]'); MT('olNestedWithUl', '[variable-2 # foo]', '[variable-3 ** bar]', '[keyword ### bar]', '[variable-2 **** bar]', '[variable-3 ## bar]'); MT('definitionList', '[number - coffee := Hot ][number&em _and_][number black]', '', 'Normal text.'); MT('definitionListSpan', '[number - coffee :=]', '', '[number Hot ][number&em _and_][number black =:]', '', 'Normal text.'); MT('boo', '[number - dog := woof woof]', '[number - cat := meow meow]', '[number - whale :=]', '[number Whale noises.]', '', '[number Also, ][number&em _splashing_][number . =:]'); /* * Attributes */ MT('divWithAttribute', '[punctuation div][punctuation&attribute (#my-id)][punctuation . foo bar]'); MT('divWithAttributeAnd2emRightPadding', '[punctuation div][punctuation&attribute (#my-id)((][punctuation . foo bar]'); MT('divWithClassAndId', '[punctuation div][punctuation&attribute (my-class#my-id)][punctuation . foo bar]'); MT('paragraphWithCss', 'p[attribute {color:red;}]. foo bar'); MT('paragraphNestedStyles', 'p. [strong *foo ][strong&em _bar_][strong *]'); MT('paragraphWithLanguage', 'p[attribute [[fr]]]. Parlez-vous français?'); MT('paragraphLeftAlign', 'p[attribute <]. Left'); MT('paragraphRightAlign', 'p[attribute >]. Right'); MT('paragraphRightAlign', 'p[attribute =]. Center'); MT('paragraphJustified', 'p[attribute <>]. Justified'); MT('paragraphWithLeftIndent1em', 'p[attribute (]. Left'); MT('paragraphWithRightIndent1em', 'p[attribute )]. Right'); MT('paragraphWithLeftIndent2em', 'p[attribute ((]. Left'); MT('paragraphWithRightIndent2em', 'p[attribute ))]. Right'); MT('paragraphWithLeftIndent3emRightIndent2em', 'p[attribute ((())]. Right'); MT('divFormatting', '[punctuation div. ][punctuation&strong *foo ]' + '[punctuation&strong&em _bar_][punctuation&strong *]'); MT('phraseModifierAttributes', 'p[attribute (my-class)]. This is a paragraph that has a class and' + ' this [em _][em&attribute (#special-phrase)][em emphasized phrase_]' + ' has an id.'); MT('linkWithClass', '[link "(my-class). This is a link with class":http://redcloth.org]'); /* * Layouts */ MT('paragraphLayouts', 'p. This is one paragraph.', '', 'p. This is another.'); MT('div', '[punctuation div. foo bar]'); MT('pre', '[operator pre. Text]'); MT('bq.', '[bracket bq. foo bar]', '', 'Normal text.'); MT('footnote', '[variable fn123. foo ][variable&strong *bar*]'); /* * Spanning Layouts */ MT('bq..ThenParagraph', '[bracket bq.. foo bar]', '', '[bracket More quote.]', 'p. Normal Text'); MT('bq..ThenH1', '[bracket bq.. foo bar]', '', '[bracket More quote.]', '[header&header-1 h1. Header Text]'); MT('bc..ThenParagraph', '[atom bc.. # Some ruby code]', '[atom obj = {foo: :bar}]', '[atom puts obj]', '', '[atom obj[[:love]] = "*love*"]', '[atom puts obj.love.upcase]', '', 'p. Normal text.'); MT('fn1..ThenParagraph', '[variable fn1.. foo bar]', '', '[variable More.]', 'p. Normal Text'); MT('pre..ThenParagraph', '[operator pre.. foo bar]', '', '[operator More.]', 'p. Normal Text'); /* * Tables */ MT('table', '[variable-3&operator |_. name |_. age|]', '[variable-3 |][variable-3&strong *Walter*][variable-3 | 5 |]', '[variable-3 |Florence| 6 |]', '', 'p. Normal text.'); MT('tableWithAttributes', '[variable-3&operator |_. name |_. age|]', '[variable-3 |][variable-3&attribute /2.][variable-3 Jim |]', '[variable-3 |][variable-3&attribute \\2{color: red}.][variable-3 Sam |]'); /* * HTML */ MT('html', '[comment
    ]', '[comment
    ]', '', '[header&header-1 h1. Welcome]', '', '[variable-2 * Item one]', '[variable-2 * Item two]', '', '[comment Example]', '', '[comment
    ]', '[comment
    ]'); MT('inlineHtml', 'I can use HTML directly in my [comment Textile].'); /* * No-Textile */ MT('notextile', '[string-2 notextile. *No* formatting]'); MT('notextileInline', 'Use [string-2 ==*asterisks*==] for [strong *strong*] text.'); MT('notextileWithPre', '[operator pre. *No* formatting]'); MT('notextileWithSpanningPre', '[operator pre.. *No* formatting]', '', '[operator *No* formatting]'); /* Only toggling phrases between non-word chars. */ MT('phrase-in-word', 'foo_bar_baz'); MT('phrase-non-word', '[negative -x-] aaa-bbb ccc-ddd [negative -eee-] fff [negative -ggg-]'); MT('phrase-lone-dash', 'foo - bar - baz'); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/textile/textile.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") { // CommonJS mod(require("../../lib/codemirror")); } else if (typeof define == "function" && define.amd) { // AMD define(["../../lib/codemirror"], mod); } else { // Plain browser env mod(CodeMirror); } })(function(CodeMirror) { "use strict"; var TOKEN_STYLES = { addition: "positive", attributes: "attribute", bold: "strong", cite: "keyword", code: "atom", definitionList: "number", deletion: "negative", div: "punctuation", em: "em", footnote: "variable", footCite: "qualifier", header: "header", html: "comment", image: "string", italic: "em", link: "link", linkDefinition: "link", list1: "variable-2", list2: "variable-3", list3: "keyword", notextile: "string-2", pre: "operator", p: "property", quote: "bracket", span: "quote", specialChar: "tag", strong: "strong", sub: "builtin", sup: "builtin", table: "variable-3", tableHeading: "operator" }; function startNewLine(stream, state) { state.mode = Modes.newLayout; state.tableHeading = false; if (state.layoutType === "definitionList" && state.spanningLayout && stream.match(RE("definitionListEnd"), false)) state.spanningLayout = false; } function handlePhraseModifier(stream, state, ch) { if (ch === "_") { if (stream.eat("_")) return togglePhraseModifier(stream, state, "italic", /__/, 2); else return togglePhraseModifier(stream, state, "em", /_/, 1); } if (ch === "*") { if (stream.eat("*")) { return togglePhraseModifier(stream, state, "bold", /\*\*/, 2); } return togglePhraseModifier(stream, state, "strong", /\*/, 1); } if (ch === "[") { if (stream.match(/\d+\]/)) state.footCite = true; return tokenStyles(state); } if (ch === "(") { var spec = stream.match(/^(r|tm|c)\)/); if (spec) return tokenStylesWith(state, TOKEN_STYLES.specialChar); } if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) return tokenStylesWith(state, TOKEN_STYLES.html); if (ch === "?" && stream.eat("?")) return togglePhraseModifier(stream, state, "cite", /\?\?/, 2); if (ch === "=" && stream.eat("=")) return togglePhraseModifier(stream, state, "notextile", /==/, 2); if (ch === "-" && !stream.eat("-")) return togglePhraseModifier(stream, state, "deletion", /-/, 1); if (ch === "+") return togglePhraseModifier(stream, state, "addition", /\+/, 1); if (ch === "~") return togglePhraseModifier(stream, state, "sub", /~/, 1); if (ch === "^") return togglePhraseModifier(stream, state, "sup", /\^/, 1); if (ch === "%") return togglePhraseModifier(stream, state, "span", /%/, 1); if (ch === "@") return togglePhraseModifier(stream, state, "code", /@/, 1); if (ch === "!") { var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1); stream.match(/^:\S+/); // optional Url portion return type; } return tokenStyles(state); } function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) { var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null; var charAfter = stream.peek(); if (state[phraseModifier]) { if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) { var type = tokenStyles(state); state[phraseModifier] = false; return type; } } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) && stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) { state[phraseModifier] = true; state.mode = Modes.attributes; } return tokenStyles(state); }; function tokenStyles(state) { var disabled = textileDisabled(state); if (disabled) return disabled; var styles = []; if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]); styles = styles.concat(activeStyles( state, "addition", "bold", "cite", "code", "deletion", "em", "footCite", "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading")); if (state.layoutType === "header") styles.push(TOKEN_STYLES.header + "-" + state.header); return styles.length ? styles.join(" ") : null; } function textileDisabled(state) { var type = state.layoutType; switch(type) { case "notextile": case "code": case "pre": return TOKEN_STYLES[type]; default: if (state.notextile) return TOKEN_STYLES.notextile + (type ? (" " + TOKEN_STYLES[type]) : ""); return null; } } function tokenStylesWith(state, extraStyles) { var disabled = textileDisabled(state); if (disabled) return disabled; var type = tokenStyles(state); if (extraStyles) return type ? (type + " " + extraStyles) : extraStyles; else return type; } function activeStyles(state) { var styles = []; for (var i = 1; i < arguments.length; ++i) { if (state[arguments[i]]) styles.push(TOKEN_STYLES[arguments[i]]); } return styles; } function blankLine(state) { var spanningLayout = state.spanningLayout, type = state.layoutType; for (var key in state) if (state.hasOwnProperty(key)) delete state[key]; state.mode = Modes.newLayout; if (spanningLayout) { state.layoutType = type; state.spanningLayout = true; } } var REs = { cache: {}, single: { bc: "bc", bq: "bq", definitionList: /- .*?:=+/, definitionListEnd: /.*=:\s*$/, div: "div", drawTable: /\|.*\|/, foot: /fn\d+/, header: /h[1-6]/, html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/, link: /[^"]+":\S/, linkDefinition: /\[[^\s\]]+\]\S+/, list: /(?:#+|\*+)/, notextile: "notextile", para: "p", pre: "pre", table: "table", tableCellAttributes: /[\/\\]\d+/, tableHeading: /\|_\./, tableText: /[^"_\*\[\(\?\+~\^%@|-]+/, text: /[^!"_=\*\[\(<\?\+~\^%@-]+/ }, attributes: { align: /(?:<>|<|>|=)/, selector: /\([^\(][^\)]+\)/, lang: /\[[^\[\]]+\]/, pad: /(?:\(+|\)+){1,2}/, css: /\{[^\}]+\}/ }, createRe: function(name) { switch (name) { case "drawTable": return REs.makeRe("^", REs.single.drawTable, "$"); case "html": return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$"); case "linkDefinition": return REs.makeRe("^", REs.single.linkDefinition, "$"); case "listLayout": return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+"); case "tableCellAttributes": return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes, RE("allAttributes")), "+\\."); case "type": return REs.makeRe("^", RE("allTypes")); case "typeLayout": return REs.makeRe("^", RE("allTypes"), RE("allAttributes"), "*\\.\\.?", "(\\s+|$)"); case "attributes": return REs.makeRe("^", RE("allAttributes"), "+"); case "allTypes": return REs.choiceRe(REs.single.div, REs.single.foot, REs.single.header, REs.single.bc, REs.single.bq, REs.single.notextile, REs.single.pre, REs.single.table, REs.single.para); case "allAttributes": return REs.choiceRe(REs.attributes.selector, REs.attributes.css, REs.attributes.lang, REs.attributes.align, REs.attributes.pad); default: return REs.makeRe("^", REs.single[name]); } }, makeRe: function() { var pattern = ""; for (var i = 0; i < arguments.length; ++i) { var arg = arguments[i]; pattern += (typeof arg === "string") ? arg : arg.source; } return new RegExp(pattern); }, choiceRe: function() { var parts = [arguments[0]]; for (var i = 1; i < arguments.length; ++i) { parts[i * 2 - 1] = "|"; parts[i * 2] = arguments[i]; } parts.unshift("(?:"); parts.push(")"); return REs.makeRe.apply(null, parts); } }; function RE(name) { return (REs.cache[name] || (REs.cache[name] = REs.createRe(name))); } var Modes = { newLayout: function(stream, state) { if (stream.match(RE("typeLayout"), false)) { state.spanningLayout = false; return (state.mode = Modes.blockType)(stream, state); } var newMode; if (!textileDisabled(state)) { if (stream.match(RE("listLayout"), false)) newMode = Modes.list; else if (stream.match(RE("drawTable"), false)) newMode = Modes.table; else if (stream.match(RE("linkDefinition"), false)) newMode = Modes.linkDefinition; else if (stream.match(RE("definitionList"))) newMode = Modes.definitionList; else if (stream.match(RE("html"), false)) newMode = Modes.html; } return (state.mode = (newMode || Modes.text))(stream, state); }, blockType: function(stream, state) { var match, type; state.layoutType = null; if (match = stream.match(RE("type"))) type = match[0]; else return (state.mode = Modes.text)(stream, state); if (match = type.match(RE("header"))) { state.layoutType = "header"; state.header = parseInt(match[0][1]); } else if (type.match(RE("bq"))) { state.layoutType = "quote"; } else if (type.match(RE("bc"))) { state.layoutType = "code"; } else if (type.match(RE("foot"))) { state.layoutType = "footnote"; } else if (type.match(RE("notextile"))) { state.layoutType = "notextile"; } else if (type.match(RE("pre"))) { state.layoutType = "pre"; } else if (type.match(RE("div"))) { state.layoutType = "div"; } else if (type.match(RE("table"))) { state.layoutType = "table"; } state.mode = Modes.attributes; return tokenStyles(state); }, text: function(stream, state) { if (stream.match(RE("text"))) return tokenStyles(state); var ch = stream.next(); if (ch === '"') return (state.mode = Modes.link)(stream, state); return handlePhraseModifier(stream, state, ch); }, attributes: function(stream, state) { state.mode = Modes.layoutLength; if (stream.match(RE("attributes"))) return tokenStylesWith(state, TOKEN_STYLES.attributes); else return tokenStyles(state); }, layoutLength: function(stream, state) { if (stream.eat(".") && stream.eat(".")) state.spanningLayout = true; state.mode = Modes.text; return tokenStyles(state); }, list: function(stream, state) { var match = stream.match(RE("list")); state.listDepth = match[0].length; var listMod = (state.listDepth - 1) % 3; if (!listMod) state.layoutType = "list1"; else if (listMod === 1) state.layoutType = "list2"; else state.layoutType = "list3"; state.mode = Modes.attributes; return tokenStyles(state); }, link: function(stream, state) { state.mode = Modes.text; if (stream.match(RE("link"))) { stream.match(/\S+/); return tokenStylesWith(state, TOKEN_STYLES.link); } return tokenStyles(state); }, linkDefinition: function(stream, state) { stream.skipToEnd(); return tokenStylesWith(state, TOKEN_STYLES.linkDefinition); }, definitionList: function(stream, state) { stream.match(RE("definitionList")); state.layoutType = "definitionList"; if (stream.match(/\s*$/)) state.spanningLayout = true; else state.mode = Modes.attributes; return tokenStyles(state); }, html: function(stream, state) { stream.skipToEnd(); return tokenStylesWith(state, TOKEN_STYLES.html); }, table: function(stream, state) { state.layoutType = "table"; return (state.mode = Modes.tableCell)(stream, state); }, tableCell: function(stream, state) { if (stream.match(RE("tableHeading"))) state.tableHeading = true; else stream.eat("|"); state.mode = Modes.tableCellAttributes; return tokenStyles(state); }, tableCellAttributes: function(stream, state) { state.mode = Modes.tableText; if (stream.match(RE("tableCellAttributes"))) return tokenStylesWith(state, TOKEN_STYLES.attributes); else return tokenStyles(state); }, tableText: function(stream, state) { if (stream.match(RE("tableText"))) return tokenStyles(state); if (stream.peek() === "|") { // end of cell state.mode = Modes.tableCell; return tokenStyles(state); } return handlePhraseModifier(stream, state, stream.next()); } }; CodeMirror.defineMode("textile", function() { return { startState: function() { return { mode: Modes.newLayout }; }, token: function(stream, state) { if (stream.sol()) startNewLine(stream, state); return state.mode(stream, state); }, blankLine: blankLine }; }); CodeMirror.defineMIME("text/x-textile", "textile"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiddlywiki/index.html ================================================ CodeMirror: TiddlyWiki mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • TiddlyWiki

    TiddlyWiki mode

    TiddlyWiki mode supports a single configuration.

    MIME types defined: text/x-tiddlywiki.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiddlywiki/tiddlywiki.css ================================================ span.cm-underlined { text-decoration: underline; } span.cm-strikethrough { text-decoration: line-through; } span.cm-brace { color: #170; font-weight: bold; } span.cm-table { color: blue; font-weight: bold; } ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiddlywiki/tiddlywiki.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /*** |''Name''|tiddlywiki.js| |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror| |''Author''|PMario| |''Version''|0.1.7| |''Status''|''stable''| |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]| |''Documentation''|https://codemirror.tiddlyspace.com/| |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]| |''CoreVersion''|2.5.0| |''Requires''|codemirror.js| |''Keywords''|syntax highlighting color code mirror codemirror| ! Info CoreVersion parameter is needed for TiddlyWiki only! ***/ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("tiddlywiki", function () { // Tokenizer var textwords = {}; var keywords = { "allTags": true, "closeAll": true, "list": true, "newJournal": true, "newTiddler": true, "permaview": true, "saveChanges": true, "search": true, "slider": true, "tabs": true, "tag": true, "tagging": true, "tags": true, "tiddler": true, "timeline": true, "today": true, "version": true, "option": true, "with": true, "filter": true }; var isSpaceName = /[\w_\-]/i, reHR = /^\-\-\-\-+$/, //
    reWikiCommentStart = /^\/\*\*\*$/, // /*** reWikiCommentStop = /^\*\*\*\/$/, // ***/ reBlockQuote = /^<<<$/, reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop reXmlCodeStart = /^$/, // xml block start reXmlCodeStop = /^$/, // xml stop reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop reUntilCodeStop = /.*?\}\}\}/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenBase(stream, state) { var sol = stream.sol(), ch = stream.peek(); state.block = false; // indicates the start of a code block. // check start of blocks if (sol && /[<\/\*{}\-]/.test(ch)) { if (stream.match(reCodeBlockStart)) { state.block = true; return chain(stream, state, twTokenCode); } if (stream.match(reBlockQuote)) return 'quote'; if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) return 'comment'; if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) return 'comment'; if (stream.match(reHR)) return 'hr'; } stream.next(); if (sol && /[\/\*!#;:>|]/.test(ch)) { if (ch == "!") { // tw header stream.skipToEnd(); return "header"; } if (ch == "*") { // tw list stream.eatWhile('*'); return "comment"; } if (ch == "#") { // tw numbered list stream.eatWhile('#'); return "comment"; } if (ch == ";") { // definition list, term stream.eatWhile(';'); return "comment"; } if (ch == ":") { // definition list, description stream.eatWhile(':'); return "comment"; } if (ch == ">") { // single line quote stream.eatWhile(">"); return "quote"; } if (ch == '|') return 'header'; } if (ch == '{' && stream.match('{{')) return chain(stream, state, twTokenCode); // rudimentary html:// file:// link matching. TW knows much more ... if (/[hf]/i.test(ch) && /[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) return "link"; // just a little string indicator, don't want to have the whole string covered if (ch == '"') return 'string'; if (ch == '~') // _no_ CamelCase indicator should be bold return 'brace'; if (/[\[\]]/.test(ch) && stream.match(ch)) // check for [[..]] return 'brace'; if (ch == "@") { // check for space link. TODO fix @@...@@ highlighting stream.eatWhile(isSpaceName); return "link"; } if (/\d/.test(ch)) { // numbers stream.eatWhile(/\d/); return "number"; } if (ch == "/") { // tw invisible comment if (stream.eat("%")) { return chain(stream, state, twTokenComment); } else if (stream.eat("/")) { // return chain(stream, state, twTokenEm); } } if (ch == "_" && stream.eat("_")) // tw underline return chain(stream, state, twTokenUnderline); // strikethrough and mdash handling if (ch == "-" && stream.eat("-")) { // if strikethrough looks ugly, change CSS. if (stream.peek() != ' ') return chain(stream, state, twTokenStrike); // mdash if (stream.peek() == ' ') return 'brace'; } if (ch == "'" && stream.eat("'")) // tw bold return chain(stream, state, twTokenStrong); if (ch == "<" && stream.eat("<")) // tw macro return chain(stream, state, twTokenMacro); // core macro handling stream.eatWhile(/[\w\$_]/); return textwords.propertyIsEnumerable(stream.current()) ? "keyword" : null } // tw invisible comment function twTokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "%"); } return "comment"; } // tw strong / bold function twTokenStrong(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "'" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "'"); } return "strong"; } // tw code function twTokenCode(stream, state) { var sb = state.block; if (sb && stream.current()) { return "comment"; } if (!sb && stream.match(reUntilCodeStop)) { state.tokenize = tokenBase; return "comment"; } if (sb && stream.sol() && stream.match(reCodeBlockStop)) { state.tokenize = tokenBase; return "comment"; } stream.next(); return "comment"; } // tw em / italic function twTokenEm(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "/"); } return "em"; } // tw underlined text function twTokenUnderline(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "_" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "_"); } return "underlined"; } // tw strike through text looks ugly // change CSS if needed function twTokenStrike(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "-" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "-"); } return "strikethrough"; } // macro function twTokenMacro(stream, state) { if (stream.current() == '<<') { return 'macro'; } var ch = stream.next(); if (!ch) { state.tokenize = tokenBase; return null; } if (ch == ">") { if (stream.peek() == '>') { stream.next(); state.tokenize = tokenBase; return "macro"; } } stream.eatWhile(/[\w\$_]/); return keywords.propertyIsEnumerable(stream.current()) ? "keyword" : null } // Interface return { startState: function () { return {tokenize: tokenBase}; }, token: function (stream, state) { if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); return style; } }; }); CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiki/index.html ================================================ CodeMirror: Tiki wiki mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Tiki wiki

    Tiki wiki mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiki/tiki.css ================================================ .cm-tw-syntaxerror { color: #FFF; background-color: #900; } .cm-tw-deleted { text-decoration: line-through; } .cm-tw-header5 { font-weight: bold; } .cm-tw-listitem:first-child { /*Added first child to fix duplicate padding when highlighting*/ padding-left: 10px; } .cm-tw-box { border-top-width: 0px !important; border-style: solid; border-width: 1px; border-color: inherit; } .cm-tw-underline { text-decoration: underline; } ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tiki/tiki.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('tiki', function(config) { function inBlock(style, terminator, returnTokenizer) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = inText; break; } stream.next(); } if (returnTokenizer) state.tokenize = returnTokenizer; return style; }; } function inLine(style) { return function(stream, state) { while(!stream.eol()) { stream.next(); } state.tokenize = inText; return style; }; } function inText(stream, state) { function chain(parser) { state.tokenize = parser; return parser(stream, state); } var sol = stream.sol(); var ch = stream.next(); //non start of line switch (ch) { //switch is generally much faster than if, so it is used here case "{": //plugin stream.eat("/"); stream.eatSpace(); stream.eatWhile(/[^\s\u00a0=\"\'\/?(}]/); state.tokenize = inPlugin; return "tag"; case "_": //bold if (stream.eat("_")) return chain(inBlock("strong", "__", inText)); break; case "'": //italics if (stream.eat("'")) return chain(inBlock("em", "''", inText)); break; case "(":// Wiki Link if (stream.eat("(")) return chain(inBlock("variable-2", "))", inText)); break; case "[":// Weblink return chain(inBlock("variable-3", "]", inText)); break; case "|": //table if (stream.eat("|")) return chain(inBlock("comment", "||")); break; case "-": if (stream.eat("=")) {//titleBar return chain(inBlock("header string", "=-", inText)); } else if (stream.eat("-")) {//deleted return chain(inBlock("error tw-deleted", "--", inText)); } break; case "=": //underline if (stream.match("==")) return chain(inBlock("tw-underline", "===", inText)); break; case ":": if (stream.eat(":")) return chain(inBlock("comment", "::")); break; case "^": //box return chain(inBlock("tw-box", "^")); break; case "~": //np if (stream.match("np~")) return chain(inBlock("meta", "~/np~")); break; } //start of line types if (sol) { switch (ch) { case "!": //header at start of line if (stream.match('!!!!!')) { return chain(inLine("header string")); } else if (stream.match('!!!!')) { return chain(inLine("header string")); } else if (stream.match('!!!')) { return chain(inLine("header string")); } else if (stream.match('!!')) { return chain(inLine("header string")); } else { return chain(inLine("header string")); } break; case "*": //unordered list line item, or
  • at start of line case "#": //ordered list line item, or
  • at start of line case "+": //ordered list line item, or
  • at start of line return chain(inLine("tw-listitem bracket")); break; } } //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki return null; } var indentUnit = config.indentUnit; // Return variables for tokenizers var pluginName, type; function inPlugin(stream, state) { var ch = stream.next(); var peek = stream.peek(); if (ch == "}") { state.tokenize = inText; //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin return "tag"; } else if (ch == "(" || ch == ")") { return "bracket"; } else if (ch == "=") { type = "equals"; if (peek == ">") { stream.next(); peek = stream.peek(); } //here we detect values directly after equal character with no quotes if (!/[\'\"]/.test(peek)) { state.tokenize = inAttributeNoQuote(); } //end detect values return "operator"; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); return state.tokenize(stream, state); } else { stream.eatWhile(/[^\s\u00a0=\"\'\/?]/); return "keyword"; } } function inAttribute(quote) { return function(stream, state) { while (!stream.eol()) { if (stream.next() == quote) { state.tokenize = inPlugin; break; } } return "string"; }; } function inAttributeNoQuote() { return function(stream, state) { while (!stream.eol()) { var ch = stream.next(); var peek = stream.peek(); if (ch == " " || ch == "," || /[ )}]/.test(peek)) { state.tokenize = inPlugin; break; } } return "string"; }; } var curState, setStyle; function pass() { for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); } function cont() { pass.apply(null, arguments); return true; } function pushContext(pluginName, startOfLine) { var noIndent = curState.context && curState.context.noIndent; curState.context = { prev: curState.context, pluginName: pluginName, indent: curState.indented, startOfLine: startOfLine, noIndent: noIndent }; } function popContext() { if (curState.context) curState.context = curState.context.prev; } function element(type) { if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));} else if (type == "closePlugin") { var err = false; if (curState.context) { err = curState.context.pluginName != pluginName; popContext(); } else { err = true; } if (err) setStyle = "error"; return cont(endcloseplugin(err)); } else if (type == "string") { if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata"); if (curState.tokenize == inText) popContext(); return cont(); } else return cont(); } function endplugin(startOfLine) { return function(type) { if ( type == "selfclosePlugin" || type == "endPlugin" ) return cont(); if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();} return cont(); }; } function endcloseplugin(err) { return function(type) { if (err) setStyle = "error"; if (type == "endPlugin") return cont(); return pass(); }; } function attributes(type) { if (type == "keyword") {setStyle = "attribute"; return cont(attributes);} if (type == "equals") return cont(attvalue, attributes); return pass(); } function attvalue(type) { if (type == "keyword") {setStyle = "string"; return cont();} if (type == "string") return cont(attvaluemaybe); return pass(); } function attvaluemaybe(type) { if (type == "string") return cont(attvaluemaybe); else return pass(); } return { startState: function() { return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null}; }, token: function(stream, state) { if (stream.sol()) { state.startOfLine = true; state.indented = stream.indentation(); } if (stream.eatSpace()) return null; setStyle = type = pluginName = null; var style = state.tokenize(stream, state); if ((style || type) && style != "comment") { curState = state; while (true) { var comb = state.cc.pop() || element; if (comb(type || style)) break; } } state.startOfLine = false; return setStyle || style; }, indent: function(state, textAfter) { var context = state.context; if (context && context.noIndent) return 0; if (context && /^{\//.test(textAfter)) context = context.prev; while (context && !context.startOfLine) context = context.prev; if (context) return context.indent + indentUnit; else return 0; }, electricChars: "/" }; }); CodeMirror.defineMIME("text/tiki", "tiki"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/toml/index.html ================================================ CodeMirror: TOML Mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • TOML Mode

    TOML Mode

    The TOML Mode

    Created by Forbes Lindesay.

    MIME type defined: text/x-toml.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/toml/toml.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("toml", function () { return { startState: function () { return { inString: false, stringType: "", lhs: true, inArray: 0 }; }, token: function (stream, state) { //check for state changes if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) { state.stringType = stream.peek(); stream.next(); // Skip quote state.inString = true; // Update state } if (stream.sol() && state.inArray === 0) { state.lhs = true; } //return state if (state.inString) { while (state.inString && !stream.eol()) { if (stream.peek() === state.stringType) { stream.next(); // Skip quote state.inString = false; // Clear flag } else if (stream.peek() === '\\') { stream.next(); stream.next(); } else { stream.match(/^.[^\\\"\']*/); } } return state.lhs ? "property string" : "string"; // Token style } else if (state.inArray && stream.peek() === ']') { stream.next(); state.inArray--; return 'bracket'; } else if (state.lhs && stream.peek() === '[' && stream.skipTo(']')) { stream.next();//skip closing ] // array of objects has an extra open & close [] if (stream.peek() === ']') stream.next(); return "atom"; } else if (stream.peek() === "#") { stream.skipToEnd(); return "comment"; } else if (stream.eatSpace()) { return null; } else if (state.lhs && stream.eatWhile(function (c) { return c != '=' && c != ' '; })) { return "property"; } else if (state.lhs && stream.peek() === "=") { stream.next(); state.lhs = false; return null; } else if (!state.lhs && stream.match(/^\d\d\d\d[\d\-\:\.T]*Z/)) { return 'atom'; //date } else if (!state.lhs && (stream.match('true') || stream.match('false'))) { return 'atom'; } else if (!state.lhs && stream.peek() === '[') { state.inArray++; stream.next(); return 'bracket'; } else if (!state.lhs && stream.match(/^\-?\d+(?:\.\d+)?/)) { return 'number'; } else if (!stream.eatSpace()) { stream.next(); } return null; } }; }); CodeMirror.defineMIME('text/x-toml', 'toml'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tornado/index.html ================================================ CodeMirror: Tornado template mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Tornado

    Tornado template mode

    Mode for HTML with embedded Tornado template markup.

    MIME types defined: text/x-tornado

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/tornado/tornado.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../../addon/mode/overlay")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../../addon/mode/overlay"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("tornado:inner", function() { var keywords = ["and","as","assert","autoescape","block","break","class","comment","context", "continue","datetime","def","del","elif","else","end","escape","except", "exec","extends","false","finally","for","from","global","if","import","in", "include","is","json_encode","lambda","length","linkify","load","module", "none","not","or","pass","print","put","raise","raw","return","self","set", "squeeze","super","true","try","url_escape","while","with","without","xhtml_escape","yield"]; keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); function tokenBase (stream, state) { stream.eatWhile(/[^\{]/); var ch = stream.next(); if (ch == "{") { if (ch = stream.eat(/\{|%|#/)) { state.tokenize = inTag(ch); return "tag"; } } } function inTag (close) { if (close == "{") { close = "}"; } return function (stream, state) { var ch = stream.next(); if ((ch == close) && stream.eat("}")) { state.tokenize = tokenBase; return "tag"; } if (stream.match(keywords)) { return "keyword"; } return close == "#" ? "comment" : "string"; }; } return { startState: function () { return {tokenize: tokenBase}; }, token: function (stream, state) { return state.tokenize(stream, state); } }; }); CodeMirror.defineMode("tornado", function(config) { var htmlBase = CodeMirror.getMode(config, "text/html"); var tornadoInner = CodeMirror.getMode(config, "tornado:inner"); return CodeMirror.overlayMode(htmlBase, tornadoInner); }); CodeMirror.defineMIME("text/x-tornado", "tornado"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/troff/index.html ================================================ CodeMirror: troff mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • troff

    troff

    MIME types defined: troff.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/troff/troff.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) define(["../../lib/codemirror"], mod); else mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('troff', function() { var words = {}; function tokenBase(stream) { if (stream.eatSpace()) return null; var sol = stream.sol(); var ch = stream.next(); if (ch === '\\') { if (stream.match('fB') || stream.match('fR') || stream.match('fI') || stream.match('u') || stream.match('d') || stream.match('%') || stream.match('&')) { return 'string'; } if (stream.match('m[')) { stream.skipTo(']'); stream.next(); return 'string'; } if (stream.match('s+') || stream.match('s-')) { stream.eatWhile(/[\d-]/); return 'string'; } if (stream.match('\(') || stream.match('*\(')) { stream.eatWhile(/[\w-]/); return 'string'; } return 'string'; } if (sol && (ch === '.' || ch === '\'')) { if (stream.eat('\\') && stream.eat('\"')) { stream.skipToEnd(); return 'comment'; } } if (sol && ch === '.') { if (stream.match('B ') || stream.match('I ') || stream.match('R ')) { return 'attribute'; } if (stream.match('TH ') || stream.match('SH ') || stream.match('SS ') || stream.match('HP ')) { stream.skipToEnd(); return 'quote'; } if ((stream.match(/[A-Z]/) && stream.match(/[A-Z]/)) || (stream.match(/[a-z]/) && stream.match(/[a-z]/))) { return 'attribute'; } } stream.eatWhile(/[\w-]/); var cur = stream.current(); return words.hasOwnProperty(cur) ? words[cur] : null; } function tokenize(stream, state) { return (state.tokens[0] || tokenBase) (stream, state); }; return { startState: function() {return {tokens:[]};}, token: function(stream, state) { return tokenize(stream, state); } }; }); CodeMirror.defineMIME('text/troff', 'troff'); CodeMirror.defineMIME('text/x-troff', 'troff'); CodeMirror.defineMIME('application/x-troff', 'troff'); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ttcn/index.html ================================================ CodeMirror: TTCN mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • TTCN

    TTCN example


    Language: Testing and Test Control Notation (TTCN)

    MIME types defined: text/x-ttcn, text/x-ttcn3, text/x-ttcnpp.


    The development of this mode has been sponsored by Ericsson .

    Coded by Asmelash Tsegay Gebretsadkan

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ttcn/ttcn.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ttcn", function(config, parserConfig) { var indentUnit = config.indentUnit, keywords = parserConfig.keywords || {}, builtin = parserConfig.builtin || {}, timerOps = parserConfig.timerOps || {}, portOps = parserConfig.portOps || {}, configOps = parserConfig.configOps || {}, verdictOps = parserConfig.verdictOps || {}, sutOps = parserConfig.sutOps || {}, functionOps = parserConfig.functionOps || {}, verdictConsts = parserConfig.verdictConsts || {}, booleanConsts = parserConfig.booleanConsts || {}, otherConsts = parserConfig.otherConsts || {}, types = parserConfig.types || {}, visibilityModifiers = parserConfig.visibilityModifiers || {}, templateMatch = parserConfig.templateMatch || {}, multiLineStrings = parserConfig.multiLineStrings, indentStatements = parserConfig.indentStatements !== false; var isOperatorChar = /[+\-*&@=<>!\/]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[\[\]{}\(\),;\\:\?\.]/.test(ch)) { curPunc = ch; return "punctuation"; } if (ch == "#"){ stream.skipToEnd(); return "atom preprocessor"; } if (ch == "%"){ stream.eatWhile(/\b/); return "atom ttcn3Macros"; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { if(ch == "@"){ if(stream.match("try") || stream.match("catch") || stream.match("lazy")){ return "keyword"; } } stream.eatWhile(isOperatorChar); return "operator"; } stream.eatWhile(/[\w\$_\xa1-\uffff]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) return "keyword"; if (builtin.propertyIsEnumerable(cur)) return "builtin"; if (timerOps.propertyIsEnumerable(cur)) return "def timerOps"; if (configOps.propertyIsEnumerable(cur)) return "def configOps"; if (verdictOps.propertyIsEnumerable(cur)) return "def verdictOps"; if (portOps.propertyIsEnumerable(cur)) return "def portOps"; if (sutOps.propertyIsEnumerable(cur)) return "def sutOps"; if (functionOps.propertyIsEnumerable(cur)) return "def functionOps"; if (verdictConsts.propertyIsEnumerable(cur)) return "string verdictConsts"; if (booleanConsts.propertyIsEnumerable(cur)) return "string booleanConsts"; if (otherConsts.propertyIsEnumerable(cur)) return "string otherConsts"; if (types.propertyIsEnumerable(cur)) return "builtin types"; if (visibilityModifiers.propertyIsEnumerable(cur)) return "builtin visibilityModifiers"; if (templateMatch.propertyIsEnumerable(cur)) return "atom templateMatch"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped){ var afterQuote = stream.peek(); //look if the character after the quote is like the B in '10100010'B if (afterQuote){ afterQuote = afterQuote.toLowerCase(); if(afterQuote == "b" || afterQuote == "h" || afterQuote == "o") stream.next(); } end = true; break; } escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { var indent = state.indented; if (state.context && state.context.type == "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } //Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement"){ popContext(state); } else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))) pushContext(state, stream.column(), "statement"); state.startOfLine = false; return style; }, electricChars: "{}", blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", fold: "brace" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } function def(mimes, mode) { if (typeof mimes == "string") mimes = [mimes]; var words = []; function add(obj) { if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) words.push(prop); } add(mode.keywords); add(mode.builtin); add(mode.timerOps); add(mode.portOps); if (words.length) { mode.helperType = mimes[0]; CodeMirror.registerHelper("hintWords", mimes[0], words); } for (var i = 0; i < mimes.length; ++i) CodeMirror.defineMIME(mimes[i], mode); } def(["text/x-ttcn", "text/x-ttcn3", "text/x-ttcnpp"], { name: "ttcn", keywords: words("activate address alive all alt altstep and and4b any" + " break case component const continue control deactivate" + " display do else encode enumerated except exception" + " execute extends extension external for from function" + " goto group if import in infinity inout interleave" + " label language length log match message mixed mod" + " modifies module modulepar mtc noblock not not4b nowait" + " of on optional or or4b out override param pattern port" + " procedure record recursive rem repeat return runs select" + " self sender set signature system template testcase to" + " type union value valueof var variant while with xor xor4b"), builtin: words("bit2hex bit2int bit2oct bit2str char2int char2oct encvalue" + " decomp decvalue float2int float2str hex2bit hex2int" + " hex2oct hex2str int2bit int2char int2float int2hex" + " int2oct int2str int2unichar isbound ischosen ispresent" + " isvalue lengthof log2str oct2bit oct2char oct2hex oct2int" + " oct2str regexp replace rnd sizeof str2bit str2float" + " str2hex str2int str2oct substr unichar2int unichar2char" + " enum2int"), types: words("anytype bitstring boolean char charstring default float" + " hexstring integer objid octetstring universal verdicttype timer"), timerOps: words("read running start stop timeout"), portOps: words("call catch check clear getcall getreply halt raise receive" + " reply send trigger"), configOps: words("create connect disconnect done kill killed map unmap"), verdictOps: words("getverdict setverdict"), sutOps: words("action"), functionOps: words("apply derefers refers"), verdictConsts: words("error fail inconc none pass"), booleanConsts: words("true false"), otherConsts: words("null NULL omit"), visibilityModifiers: words("private public friend"), templateMatch: words("complement ifpresent subset superset permutation"), multiLineStrings: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ttcn-cfg/index.html ================================================ CodeMirror: TTCN-CFG mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • TTCN-CFG

    TTCN-CFG example


    Language: Testing and Test Control Notation - Configuration files (TTCN-CFG)

    MIME types defined: text/x-ttcn-cfg.


    The development of this mode has been sponsored by Ericsson .

    Coded by Asmelash Tsegay Gebretsadkan

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/ttcn-cfg/ttcn-cfg.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ttcn-cfg", function(config, parserConfig) { var indentUnit = config.indentUnit, keywords = parserConfig.keywords || {}, fileNCtrlMaskOptions = parserConfig.fileNCtrlMaskOptions || {}, externalCommands = parserConfig.externalCommands || {}, multiLineStrings = parserConfig.multiLineStrings, indentStatements = parserConfig.indentStatements !== false; var isOperatorChar = /[\|]/; var curPunc; function tokenBase(stream, state) { var ch = stream.next(); if (ch == '"' || ch == "'") { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (/[:=]/.test(ch)) { curPunc = ch; return "punctuation"; } if (ch == "#"){ stream.skipToEnd(); return "comment"; } if (/\d/.test(ch)) { stream.eatWhile(/[\w\.]/); return "number"; } if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return "operator"; } if (ch == "["){ stream.eatWhile(/[\w_\]]/); return "number sectionTitle"; } stream.eatWhile(/[\w\$_]/); var cur = stream.current(); if (keywords.propertyIsEnumerable(cur)) return "keyword"; if (fileNCtrlMaskOptions.propertyIsEnumerable(cur)) return "negative fileNCtrlMaskOptions"; if (externalCommands.propertyIsEnumerable(cur)) return "negative externalCommands"; return "variable"; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped){ var afterNext = stream.peek(); //look if the character if the quote is like the B in '10100010'B if (afterNext){ afterNext = afterNext.toLowerCase(); if(afterNext == "b" || afterNext == "h" || afterNext == "o") stream.next(); } end = true; break; } escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function Context(indented, column, type, align, prev) { this.indented = indented; this.column = column; this.type = type; this.align = align; this.prev = prev; } function pushContext(state, col, type) { var indent = state.indented; if (state.context && state.context.type == "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, null, state.context); } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } //Interface return { startState: function(basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), indented: 0, startOfLine: true }; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) return null; curPunc = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment") return style; if (ctx.align == null) ctx.align = true; if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement"){ popContext(state); } else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))) pushContext(state, stream.column(), "statement"); state.startOfLine = false; return style; }, electricChars: "{}", lineComment: "#", fold: "brace" }; }); function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } CodeMirror.defineMIME("text/x-ttcn-cfg", { name: "ttcn-cfg", keywords: words("Yes No LogFile FileMask ConsoleMask AppendFile" + " TimeStampFormat LogEventTypes SourceInfoFormat" + " LogEntityName LogSourceInfo DiskFullAction" + " LogFileNumber LogFileSize MatchingHints Detailed" + " Compact SubCategories Stack Single None Seconds" + " DateTime Time Stop Error Retry Delete TCPPort KillTimer" + " NumHCs UnixSocketsEnabled LocalAddress"), fileNCtrlMaskOptions: words("TTCN_EXECUTOR TTCN_ERROR TTCN_WARNING" + " TTCN_PORTEVENT TTCN_TIMEROP TTCN_VERDICTOP" + " TTCN_DEFAULTOP TTCN_TESTCASE TTCN_ACTION" + " TTCN_USER TTCN_FUNCTION TTCN_STATISTICS" + " TTCN_PARALLEL TTCN_MATCHING TTCN_DEBUG" + " EXECUTOR ERROR WARNING PORTEVENT TIMEROP" + " VERDICTOP DEFAULTOP TESTCASE ACTION USER" + " FUNCTION STATISTICS PARALLEL MATCHING DEBUG" + " LOG_ALL LOG_NOTHING ACTION_UNQUALIFIED" + " DEBUG_ENCDEC DEBUG_TESTPORT" + " DEBUG_UNQUALIFIED DEFAULTOP_ACTIVATE" + " DEFAULTOP_DEACTIVATE DEFAULTOP_EXIT" + " DEFAULTOP_UNQUALIFIED ERROR_UNQUALIFIED" + " EXECUTOR_COMPONENT EXECUTOR_CONFIGDATA" + " EXECUTOR_EXTCOMMAND EXECUTOR_LOGOPTIONS" + " EXECUTOR_RUNTIME EXECUTOR_UNQUALIFIED" + " FUNCTION_RND FUNCTION_UNQUALIFIED" + " MATCHING_DONE MATCHING_MCSUCCESS" + " MATCHING_MCUNSUCC MATCHING_MMSUCCESS" + " MATCHING_MMUNSUCC MATCHING_PCSUCCESS" + " MATCHING_PCUNSUCC MATCHING_PMSUCCESS" + " MATCHING_PMUNSUCC MATCHING_PROBLEM" + " MATCHING_TIMEOUT MATCHING_UNQUALIFIED" + " PARALLEL_PORTCONN PARALLEL_PORTMAP" + " PARALLEL_PTC PARALLEL_UNQUALIFIED" + " PORTEVENT_DUALRECV PORTEVENT_DUALSEND" + " PORTEVENT_MCRECV PORTEVENT_MCSEND" + " PORTEVENT_MMRECV PORTEVENT_MMSEND" + " PORTEVENT_MQUEUE PORTEVENT_PCIN" + " PORTEVENT_PCOUT PORTEVENT_PMIN" + " PORTEVENT_PMOUT PORTEVENT_PQUEUE" + " PORTEVENT_STATE PORTEVENT_UNQUALIFIED" + " STATISTICS_UNQUALIFIED STATISTICS_VERDICT" + " TESTCASE_FINISH TESTCASE_START" + " TESTCASE_UNQUALIFIED TIMEROP_GUARD" + " TIMEROP_READ TIMEROP_START TIMEROP_STOP" + " TIMEROP_TIMEOUT TIMEROP_UNQUALIFIED" + " USER_UNQUALIFIED VERDICTOP_FINAL" + " VERDICTOP_GETVERDICT VERDICTOP_SETVERDICT" + " VERDICTOP_UNQUALIFIED WARNING_UNQUALIFIED"), externalCommands: words("BeginControlPart EndControlPart BeginTestCase" + " EndTestCase"), multiLineStrings: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/turtle/index.html ================================================ CodeMirror: Turtle mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Turtle

    Turtle mode

    MIME types defined: text/turtle.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/turtle/turtle.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("turtle", function(config) { var indentUnit = config.indentUnit; var curPunc; function wordRegexp(words) { return new RegExp("^(?:" + words.join("|") + ")$", "i"); } var ops = wordRegexp([]); var keywords = wordRegexp(["@prefix", "@base", "a"]); var operatorChars = /[*+\-<>=&|]/; function tokenBase(stream, state) { var ch = stream.next(); curPunc = null; if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { stream.match(/^[^\s\u00a0>]*>?/); return "atom"; } else if (ch == "\"" || ch == "'") { state.tokenize = tokenLiteral(ch); return state.tokenize(stream, state); } else if (/[{}\(\),\.;\[\]]/.test(ch)) { curPunc = ch; return null; } else if (ch == "#") { stream.skipToEnd(); return "comment"; } else if (operatorChars.test(ch)) { stream.eatWhile(operatorChars); return null; } else if (ch == ":") { return "operator"; } else { stream.eatWhile(/[_\w\d]/); if(stream.peek() == ":") { return "variable-3"; } else { var word = stream.current(); if(keywords.test(word)) { return "meta"; } if(ch >= "A" && ch <= "Z") { return "comment"; } else { return "keyword"; } } var word = stream.current(); if (ops.test(word)) return null; else if (keywords.test(word)) return "meta"; else return "variable"; } } function tokenLiteral(quote) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { if (ch == quote && !escaped) { state.tokenize = tokenBase; break; } escaped = !escaped && ch == "\\"; } return "string"; }; } function pushContext(state, type, col) { state.context = {prev: state.context, indent: state.indent, col: col, type: type}; } function popContext(state) { state.indent = state.context.indent; state.context = state.context.prev; } return { startState: function() { return {tokenize: tokenBase, context: null, indent: 0, col: 0}; }, token: function(stream, state) { if (stream.sol()) { if (state.context && state.context.align == null) state.context.align = false; state.indent = stream.indentation(); } if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { state.context.align = true; } if (curPunc == "(") pushContext(state, ")", stream.column()); else if (curPunc == "[") pushContext(state, "]", stream.column()); else if (curPunc == "{") pushContext(state, "}", stream.column()); else if (/[\]\}\)]/.test(curPunc)) { while (state.context && state.context.type == "pattern") popContext(state); if (state.context && curPunc == state.context.type) popContext(state); } else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); else if (/atom|string|variable/.test(style) && state.context) { if (/[\}\]]/.test(state.context.type)) pushContext(state, "pattern", stream.column()); else if (state.context.type == "pattern" && !state.context.align) { state.context.align = true; state.context.col = stream.column(); } } return style; }, indent: function(state, textAfter) { var firstChar = textAfter && textAfter.charAt(0); var context = state.context; if (/[\]\}]/.test(firstChar)) while (context && context.type == "pattern") context = context.prev; var closing = context && firstChar == context.type; if (!context) return 0; else if (context.type == "pattern") return context.col; else if (context.align) return context.col + (closing ? 0 : 1); else return context.indent + (closing ? 0 : indentUnit); }, lineComment: "#" }; }); CodeMirror.defineMIME("text/turtle", "turtle"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/twig/index.html ================================================ CodeMirror: Twig mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Twig

    Twig mode

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/twig/twig.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/multiplex")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/multiplex"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("twig:inner", function() { var keywords = ["and", "as", "autoescape", "endautoescape", "block", "do", "endblock", "else", "elseif", "extends", "for", "endfor", "embed", "endembed", "filter", "endfilter", "flush", "from", "if", "endif", "in", "is", "include", "import", "not", "or", "set", "spaceless", "endspaceless", "with", "endwith", "trans", "endtrans", "blocktrans", "endblocktrans", "macro", "endmacro", "use", "verbatim", "endverbatim"], operator = /^[+\-*&%=<>!?|~^]/, sign = /^[:\[\(\{]/, atom = ["true", "false", "null", "empty", "defined", "divisibleby", "divisible by", "even", "odd", "iterable", "sameas", "same as"], number = /^(\d[+\-\*\/])?\d+(\.\d+)?/; keywords = new RegExp("((" + keywords.join(")|(") + "))\\b"); atom = new RegExp("((" + atom.join(")|(") + "))\\b"); function tokenBase (stream, state) { var ch = stream.peek(); //Comment if (state.incomment) { if (!stream.skipTo("#}")) { stream.skipToEnd(); } else { stream.eatWhile(/\#|}/); state.incomment = false; } return "comment"; //Tag } else if (state.intag) { //After operator if (state.operator) { state.operator = false; if (stream.match(atom)) { return "atom"; } if (stream.match(number)) { return "number"; } } //After sign if (state.sign) { state.sign = false; if (stream.match(atom)) { return "atom"; } if (stream.match(number)) { return "number"; } } if (state.instring) { if (ch == state.instring) { state.instring = false; } stream.next(); return "string"; } else if (ch == "'" || ch == '"') { state.instring = ch; stream.next(); return "string"; } else if (stream.match(state.intag + "}") || stream.eat("-") && stream.match(state.intag + "}")) { state.intag = false; return "tag"; } else if (stream.match(operator)) { state.operator = true; return "operator"; } else if (stream.match(sign)) { state.sign = true; } else { if (stream.eat(" ") || stream.sol()) { if (stream.match(keywords)) { return "keyword"; } if (stream.match(atom)) { return "atom"; } if (stream.match(number)) { return "number"; } if (stream.sol()) { stream.next(); } } else { stream.next(); } } return "variable"; } else if (stream.eat("{")) { if (stream.eat("#")) { state.incomment = true; if (!stream.skipTo("#}")) { stream.skipToEnd(); } else { stream.eatWhile(/\#|}/); state.incomment = false; } return "comment"; //Open tag } else if (ch = stream.eat(/\{|%/)) { //Cache close tag state.intag = ch; if (ch == "{") { state.intag = "}"; } stream.eat("-"); return "tag"; } } stream.next(); }; return { startState: function () { return {}; }, token: function (stream, state) { return tokenBase(stream, state); } }; }); CodeMirror.defineMode("twig", function(config, parserConfig) { var twigInner = CodeMirror.getMode(config, "twig:inner"); if (!parserConfig || !parserConfig.base) return twigInner; return CodeMirror.multiplexingMode( CodeMirror.getMode(config, parserConfig.base), { open: /\{[{#%]/, close: /[}#%]\}/, mode: twigInner, parseDelimiters: true } ); }); CodeMirror.defineMIME("text/x-twig", "twig"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vb/index.html ================================================ CodeMirror: VB.NET mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • VB.NET

    VB.NET mode

    MIME type defined: text/x-vb.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vb/vb.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("vb", function(conf, parserConf) { var ERRORCLASS = 'error'; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); } var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]"); var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try', 'structure', 'synclock', 'using', 'with']; var middleKeywords = ['else','elseif','case', 'catch', 'finally']; var endKeywords = ['next','loop']; var operatorKeywords = ['and', "andalso", 'or', 'orelse', 'xor', 'in', 'not', 'is', 'isnot', 'like']; var wordOperators = wordRegexp(operatorKeywords); var commonKeywords = ["#const", "#else", "#elseif", "#end", "#if", "#region", "addhandler", "addressof", "alias", "as", "byref", "byval", "cbool", "cbyte", "cchar", "cdate", "cdbl", "cdec", "cint", "clng", "cobj", "compare", "const", "continue", "csbyte", "cshort", "csng", "cstr", "cuint", "culng", "cushort", "declare", "default", "delegate", "dim", "directcast", "each", "erase", "error", "event", "exit", "explicit", "false", "for", "friend", "gettype", "goto", "handles", "implements", "imports", "infer", "inherits", "interface", "isfalse", "istrue", "lib", "me", "mod", "mustinherit", "mustoverride", "my", "mybase", "myclass", "namespace", "narrowing", "new", "nothing", "notinheritable", "notoverridable", "of", "off", "on", "operator", "option", "optional", "out", "overloads", "overridable", "overrides", "paramarray", "partial", "private", "protected", "public", "raiseevent", "readonly", "redim", "removehandler", "resume", "return", "shadows", "shared", "static", "step", "stop", "strict", "then", "throw", "to", "true", "trycast", "typeof", "until", "until", "when", "widening", "withevents", "writeonly"]; var commontypes = ['object', 'boolean', 'char', 'string', 'byte', 'sbyte', 'short', 'ushort', 'int16', 'uint16', 'integer', 'uinteger', 'int32', 'uint32', 'long', 'ulong', 'int64', 'uint64', 'decimal', 'single', 'double', 'float', 'date', 'datetime', 'intptr', 'uintptr']; var keywords = wordRegexp(commonKeywords); var types = wordRegexp(commontypes); var stringPrefixes = '"'; var opening = wordRegexp(openingKeywords); var middle = wordRegexp(middleKeywords); var closing = wordRegexp(endKeywords); var doubleClosing = wordRegexp(['end']); var doOpening = wordRegexp(['do']); var indentInfo = null; CodeMirror.registerHelper("hintWords", "vb", openingKeywords.concat(middleKeywords).concat(endKeywords) .concat(operatorKeywords).concat(commonKeywords).concat(commontypes)); function indent(_stream, state) { state.currentIndent++; } function dedent(_stream, state) { state.currentIndent--; } // tokenizers function tokenBase(stream, state) { if (stream.eatSpace()) { return null; } var ch = stream.peek(); // Handle Comments if (ch === "'") { stream.skipToEnd(); return 'comment'; } // Handle Number Literals if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) { var floatLiteral = false; // Floats if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; } else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; } else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; } if (floatLiteral) { // Float literals may be "imaginary" stream.eat(/J/i); return 'number'; } // Integers var intLiteral = false; // Hex if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } // Octal else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } // Decimal else if (stream.match(/^[1-9]\d*F?/)) { // Decimal literals may be "imaginary" stream.eat(/J/i); // TODO - Can you have imaginary longs? intLiteral = true; } // Zero by itself with no other piece of number. else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } if (intLiteral) { // Integer literals may be "long" stream.eat(/L/i); return 'number'; } } // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()); return state.tokenize(stream, state); } // Handle operators and Delimiters if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { return null; } if (stream.match(doubleOperators) || stream.match(singleOperators) || stream.match(wordOperators)) { return 'operator'; } if (stream.match(singleDelimiters)) { return null; } if (stream.match(doOpening)) { indent(stream,state); state.doInCurrentLine = true; return 'keyword'; } if (stream.match(opening)) { if (! state.doInCurrentLine) indent(stream,state); else state.doInCurrentLine = false; return 'keyword'; } if (stream.match(middle)) { return 'keyword'; } if (stream.match(doubleClosing)) { dedent(stream,state); dedent(stream,state); return 'keyword'; } if (stream.match(closing)) { dedent(stream,state); return 'keyword'; } if (stream.match(types)) { return 'keyword'; } if (stream.match(keywords)) { return 'keyword'; } if (stream.match(identifiers)) { return 'variable'; } // Handle non-detected items stream.next(); return ERRORCLASS; } function tokenStringFactory(delimiter) { var singleline = delimiter.length == 1; var OUTCLASS = 'string'; return function(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"]/); if (stream.match(delimiter)) { state.tokenize = tokenBase; return OUTCLASS; } else { stream.eat(/['"]/); } } if (singleline) { if (parserConf.singleLineStringErrors) { return ERRORCLASS; } else { state.tokenize = tokenBase; } } return OUTCLASS; }; } function tokenLexer(stream, state) { var style = state.tokenize(stream, state); var current = stream.current(); // Handle '.' connected identifiers if (current === '.') { style = state.tokenize(stream, state); if (style === 'variable') { return 'variable'; } else { return ERRORCLASS; } } var delimiter_index = '[({'.indexOf(current); if (delimiter_index !== -1) { indent(stream, state ); } if (indentInfo === 'dedent') { if (dedent(stream, state)) { return ERRORCLASS; } } delimiter_index = '])}'.indexOf(current); if (delimiter_index !== -1) { if (dedent(stream, state)) { return ERRORCLASS; } } return style; } var external = { electricChars:"dDpPtTfFeE ", startState: function() { return { tokenize: tokenBase, lastToken: null, currentIndent: 0, nextLineIndent: 0, doInCurrentLine: false }; }, token: function(stream, state) { if (stream.sol()) { state.currentIndent += state.nextLineIndent; state.nextLineIndent = 0; state.doInCurrentLine = 0; } var style = tokenLexer(stream, state); state.lastToken = {style:style, content: stream.current()}; return style; }, indent: function(state, textAfter) { var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); if(state.currentIndent < 0) return 0; return state.currentIndent * conf.indentUnit; }, lineComment: "'" }; return external; }); CodeMirror.defineMIME("text/x-vb", "vb"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vbscript/index.html ================================================ CodeMirror: VBScript mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • VBScript

    VBScript mode

    MIME types defined: text/vbscript.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vbscript/vbscript.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE /* For extra ASP classic objects, initialize CodeMirror instance with this option: isASP: true E.G.: var editor = CodeMirror.fromTextArea(document.getElementById("code"), { lineNumbers: true, isASP: true }); */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("vbscript", function(conf, parserConf) { var ERRORCLASS = 'error'; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); } var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]"); var doubleOperators = new RegExp("^((<>)|(<=)|(>=))"); var singleDelimiters = new RegExp('^[\\.,]'); var brackets = new RegExp('^[\\(\\)]'); var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*"); var openingKeywords = ['class','sub','select','while','if','function', 'property', 'with', 'for']; var middleKeywords = ['else','elseif','case']; var endKeywords = ['next','loop','wend']; var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']); var commonkeywords = ['dim', 'redim', 'then', 'until', 'randomize', 'byval','byref','new','property', 'exit', 'in', 'const','private', 'public', 'get','set','let', 'stop', 'on error resume next', 'on error goto 0', 'option explicit', 'call', 'me']; //This list was from: http://msdn.microsoft.com/en-us/library/f8tbc79x(v=vs.84).aspx var atomWords = ['true', 'false', 'nothing', 'empty', 'null']; //This list was from: http://msdn.microsoft.com/en-us/library/3ca8tfek(v=vs.84).aspx var builtinFuncsWords = ['abs', 'array', 'asc', 'atn', 'cbool', 'cbyte', 'ccur', 'cdate', 'cdbl', 'chr', 'cint', 'clng', 'cos', 'csng', 'cstr', 'date', 'dateadd', 'datediff', 'datepart', 'dateserial', 'datevalue', 'day', 'escape', 'eval', 'execute', 'exp', 'filter', 'formatcurrency', 'formatdatetime', 'formatnumber', 'formatpercent', 'getlocale', 'getobject', 'getref', 'hex', 'hour', 'inputbox', 'instr', 'instrrev', 'int', 'fix', 'isarray', 'isdate', 'isempty', 'isnull', 'isnumeric', 'isobject', 'join', 'lbound', 'lcase', 'left', 'len', 'loadpicture', 'log', 'ltrim', 'rtrim', 'trim', 'maths', 'mid', 'minute', 'month', 'monthname', 'msgbox', 'now', 'oct', 'replace', 'rgb', 'right', 'rnd', 'round', 'scriptengine', 'scriptenginebuildversion', 'scriptenginemajorversion', 'scriptengineminorversion', 'second', 'setlocale', 'sgn', 'sin', 'space', 'split', 'sqr', 'strcomp', 'string', 'strreverse', 'tan', 'time', 'timer', 'timeserial', 'timevalue', 'typename', 'ubound', 'ucase', 'unescape', 'vartype', 'weekday', 'weekdayname', 'year']; //This list was from: http://msdn.microsoft.com/en-us/library/ydz4cfk3(v=vs.84).aspx var builtinConsts = ['vbBlack', 'vbRed', 'vbGreen', 'vbYellow', 'vbBlue', 'vbMagenta', 'vbCyan', 'vbWhite', 'vbBinaryCompare', 'vbTextCompare', 'vbSunday', 'vbMonday', 'vbTuesday', 'vbWednesday', 'vbThursday', 'vbFriday', 'vbSaturday', 'vbUseSystemDayOfWeek', 'vbFirstJan1', 'vbFirstFourDays', 'vbFirstFullWeek', 'vbGeneralDate', 'vbLongDate', 'vbShortDate', 'vbLongTime', 'vbShortTime', 'vbObjectError', 'vbOKOnly', 'vbOKCancel', 'vbAbortRetryIgnore', 'vbYesNoCancel', 'vbYesNo', 'vbRetryCancel', 'vbCritical', 'vbQuestion', 'vbExclamation', 'vbInformation', 'vbDefaultButton1', 'vbDefaultButton2', 'vbDefaultButton3', 'vbDefaultButton4', 'vbApplicationModal', 'vbSystemModal', 'vbOK', 'vbCancel', 'vbAbort', 'vbRetry', 'vbIgnore', 'vbYes', 'vbNo', 'vbCr', 'VbCrLf', 'vbFormFeed', 'vbLf', 'vbNewLine', 'vbNullChar', 'vbNullString', 'vbTab', 'vbVerticalTab', 'vbUseDefault', 'vbTrue', 'vbFalse', 'vbEmpty', 'vbNull', 'vbInteger', 'vbLong', 'vbSingle', 'vbDouble', 'vbCurrency', 'vbDate', 'vbString', 'vbObject', 'vbError', 'vbBoolean', 'vbVariant', 'vbDataObject', 'vbDecimal', 'vbByte', 'vbArray']; //This list was from: http://msdn.microsoft.com/en-us/library/hkc375ea(v=vs.84).aspx var builtinObjsWords = ['WScript', 'err', 'debug', 'RegExp']; var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count']; var knownMethods = ['clear', 'execute', 'raise', 'replace', 'test', 'write', 'writeline', 'close', 'open', 'state', 'eof', 'update', 'addnew', 'end', 'createobject', 'quit']; var aspBuiltinObjsWords = ['server', 'response', 'request', 'session', 'application']; var aspKnownProperties = ['buffer', 'cachecontrol', 'charset', 'contenttype', 'expires', 'expiresabsolute', 'isclientconnected', 'pics', 'status', //response 'clientcertificate', 'cookies', 'form', 'querystring', 'servervariables', 'totalbytes', //request 'contents', 'staticobjects', //application 'codepage', 'lcid', 'sessionid', 'timeout', //session 'scripttimeout']; //server var aspKnownMethods = ['addheader', 'appendtolog', 'binarywrite', 'end', 'flush', 'redirect', //response 'binaryread', //request 'remove', 'removeall', 'lock', 'unlock', //application 'abandon', //session 'getlasterror', 'htmlencode', 'mappath', 'transfer', 'urlencode']; //server var knownWords = knownMethods.concat(knownProperties); builtinObjsWords = builtinObjsWords.concat(builtinConsts); if (conf.isASP){ builtinObjsWords = builtinObjsWords.concat(aspBuiltinObjsWords); knownWords = knownWords.concat(aspKnownMethods, aspKnownProperties); }; var keywords = wordRegexp(commonkeywords); var atoms = wordRegexp(atomWords); var builtinFuncs = wordRegexp(builtinFuncsWords); var builtinObjs = wordRegexp(builtinObjsWords); var known = wordRegexp(knownWords); var stringPrefixes = '"'; var opening = wordRegexp(openingKeywords); var middle = wordRegexp(middleKeywords); var closing = wordRegexp(endKeywords); var doubleClosing = wordRegexp(['end']); var doOpening = wordRegexp(['do']); var noIndentWords = wordRegexp(['on error resume next', 'exit']); var comment = wordRegexp(['rem']); function indent(_stream, state) { state.currentIndent++; } function dedent(_stream, state) { state.currentIndent--; } // tokenizers function tokenBase(stream, state) { if (stream.eatSpace()) { return 'space'; //return null; } var ch = stream.peek(); // Handle Comments if (ch === "'") { stream.skipToEnd(); return 'comment'; } if (stream.match(comment)){ stream.skipToEnd(); return 'comment'; } // Handle Number Literals if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) { var floatLiteral = false; // Floats if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; } else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } else if (stream.match(/^\.\d+/)) { floatLiteral = true; } if (floatLiteral) { // Float literals may be "imaginary" stream.eat(/J/i); return 'number'; } // Integers var intLiteral = false; // Hex if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } // Octal else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } // Decimal else if (stream.match(/^[1-9]\d*F?/)) { // Decimal literals may be "imaginary" stream.eat(/J/i); // TODO - Can you have imaginary longs? intLiteral = true; } // Zero by itself with no other piece of number. else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } if (intLiteral) { // Integer literals may be "long" stream.eat(/L/i); return 'number'; } } // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()); return state.tokenize(stream, state); } // Handle operators and Delimiters if (stream.match(doubleOperators) || stream.match(singleOperators) || stream.match(wordOperators)) { return 'operator'; } if (stream.match(singleDelimiters)) { return null; } if (stream.match(brackets)) { return "bracket"; } if (stream.match(noIndentWords)) { state.doInCurrentLine = true; return 'keyword'; } if (stream.match(doOpening)) { indent(stream,state); state.doInCurrentLine = true; return 'keyword'; } if (stream.match(opening)) { if (! state.doInCurrentLine) indent(stream,state); else state.doInCurrentLine = false; return 'keyword'; } if (stream.match(middle)) { return 'keyword'; } if (stream.match(doubleClosing)) { dedent(stream,state); dedent(stream,state); return 'keyword'; } if (stream.match(closing)) { if (! state.doInCurrentLine) dedent(stream,state); else state.doInCurrentLine = false; return 'keyword'; } if (stream.match(keywords)) { return 'keyword'; } if (stream.match(atoms)) { return 'atom'; } if (stream.match(known)) { return 'variable-2'; } if (stream.match(builtinFuncs)) { return 'builtin'; } if (stream.match(builtinObjs)){ return 'variable-2'; } if (stream.match(identifiers)) { return 'variable'; } // Handle non-detected items stream.next(); return ERRORCLASS; } function tokenStringFactory(delimiter) { var singleline = delimiter.length == 1; var OUTCLASS = 'string'; return function(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"]/); if (stream.match(delimiter)) { state.tokenize = tokenBase; return OUTCLASS; } else { stream.eat(/['"]/); } } if (singleline) { if (parserConf.singleLineStringErrors) { return ERRORCLASS; } else { state.tokenize = tokenBase; } } return OUTCLASS; }; } function tokenLexer(stream, state) { var style = state.tokenize(stream, state); var current = stream.current(); // Handle '.' connected identifiers if (current === '.') { style = state.tokenize(stream, state); current = stream.current(); if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) { if (style === 'builtin' || style === 'keyword') style='variable'; if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2'; return style; } else { return ERRORCLASS; } } return style; } var external = { electricChars:"dDpPtTfFeE ", startState: function() { return { tokenize: tokenBase, lastToken: null, currentIndent: 0, nextLineIndent: 0, doInCurrentLine: false, ignoreKeyword: false }; }, token: function(stream, state) { if (stream.sol()) { state.currentIndent += state.nextLineIndent; state.nextLineIndent = 0; state.doInCurrentLine = 0; } var style = tokenLexer(stream, state); state.lastToken = {style:style, content: stream.current()}; if (style==='space') style=null; return style; }, indent: function(state, textAfter) { var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); if(state.currentIndent < 0) return 0; return state.currentIndent * conf.indentUnit; } }; return external; }); CodeMirror.defineMIME("text/vbscript", "vbscript"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/velocity/index.html ================================================ CodeMirror: Velocity mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Velocity

    Velocity mode

    MIME types defined: text/velocity.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/velocity/velocity.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("velocity", function() { function parseWords(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var keywords = parseWords("#end #else #break #stop #[[ #]] " + "#{end} #{else} #{break} #{stop}"); var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " + "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}"); var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent.count $foreach.parent.hasNext $foreach.parent.first $foreach.parent.last $foreach.parent $velocityCount $!bodyContent $bodyContent"); var isOperatorChar = /[+\-*&%=<>!?:\/|]/; function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } function tokenBase(stream, state) { var beforeParams = state.beforeParams; state.beforeParams = false; var ch = stream.next(); // start of unparsed string? if ((ch == "'") && !state.inString && state.inParams) { state.lastTokenWasBuiltin = false; return chain(stream, state, tokenString(ch)); } // start of parsed string? else if ((ch == '"')) { state.lastTokenWasBuiltin = false; if (state.inString) { state.inString = false; return "string"; } else if (state.inParams) return chain(stream, state, tokenString(ch)); } // is it one of the special signs []{}().,;? Separator? else if (/[\[\]{}\(\),;\.]/.test(ch)) { if (ch == "(" && beforeParams) state.inParams = true; else if (ch == ")") { state.inParams = false; state.lastTokenWasBuiltin = true; } return null; } // start of a number value? else if (/\d/.test(ch)) { state.lastTokenWasBuiltin = false; stream.eatWhile(/[\w\.]/); return "number"; } // multi line comment? else if (ch == "#" && stream.eat("*")) { state.lastTokenWasBuiltin = false; return chain(stream, state, tokenComment); } // unparsed content? else if (ch == "#" && stream.match(/ *\[ *\[/)) { state.lastTokenWasBuiltin = false; return chain(stream, state, tokenUnparsed); } // single line comment? else if (ch == "#" && stream.eat("#")) { state.lastTokenWasBuiltin = false; stream.skipToEnd(); return "comment"; } // variable? else if (ch == "$") { stream.eat("!"); stream.eatWhile(/[\w\d\$_\.{}-]/); // is it one of the specials? if (specials && specials.propertyIsEnumerable(stream.current())) { return "keyword"; } else { state.lastTokenWasBuiltin = true; state.beforeParams = true; return "builtin"; } } // is it a operator? else if (isOperatorChar.test(ch)) { state.lastTokenWasBuiltin = false; stream.eatWhile(isOperatorChar); return "operator"; } else { // get the whole word stream.eatWhile(/[\w\$_{}@]/); var word = stream.current(); // is it one of the listed keywords? if (keywords && keywords.propertyIsEnumerable(word)) return "keyword"; // is it one of the listed functions? if (functions && functions.propertyIsEnumerable(word) || (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") && !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) { state.beforeParams = true; state.lastTokenWasBuiltin = false; return "keyword"; } if (state.inString) { state.lastTokenWasBuiltin = false; return "string"; } if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin) return "builtin"; // default: just a "word" state.lastTokenWasBuiltin = false; return null; } } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if ((next == quote) && !escaped) { end = true; break; } if (quote=='"' && stream.peek() == '$' && !escaped) { state.inString = true; end = true; break; } escaped = !escaped && next == "\\"; } if (end) state.tokenize = tokenBase; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "#" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function tokenUnparsed(stream, state) { var maybeEnd = 0, ch; while (ch = stream.next()) { if (ch == "#" && maybeEnd == 2) { state.tokenize = tokenBase; break; } if (ch == "]") maybeEnd++; else if (ch != " ") maybeEnd = 0; } return "meta"; } // Interface return { startState: function() { return { tokenize: tokenBase, beforeParams: false, inParams: false, inString: false, lastTokenWasBuiltin: false }; }, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, blockCommentStart: "#*", blockCommentEnd: "*#", lineComment: "##", fold: "velocity" }; }); CodeMirror.defineMIME("text/velocity", "velocity"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/verilog/index.html ================================================ CodeMirror: Verilog/SystemVerilog mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Verilog/SystemVerilog

    SystemVerilog mode

    Syntax highlighting and indentation for the Verilog and SystemVerilog languages (IEEE 1800).

    Configuration options:

    • noIndentKeywords - List of keywords which should not cause indentation to increase. E.g. ["package", "module"]. Default: None

    MIME types defined: text/x-verilog and text/x-systemverilog.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/verilog/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 4}, "verilog"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("binary_literals", "[number 1'b0]", "[number 1'b1]", "[number 1'bx]", "[number 1'bz]", "[number 1'bX]", "[number 1'bZ]", "[number 1'B0]", "[number 1'B1]", "[number 1'Bx]", "[number 1'Bz]", "[number 1'BX]", "[number 1'BZ]", "[number 1'b0]", "[number 1'b1]", "[number 2'b01]", "[number 2'bxz]", "[number 2'b11]", "[number 2'b10]", "[number 2'b1Z]", "[number 12'b0101_0101_0101]", "[number 1'b 0]", "[number 'b0101]" ); MT("octal_literals", "[number 3'o7]", "[number 3'O7]", "[number 3'so7]", "[number 3'SO7]" ); MT("decimal_literals", "[number 0]", "[number 1]", "[number 7]", "[number 123_456]", "[number 'd33]", "[number 8'd255]", "[number 8'D255]", "[number 8'sd255]", "[number 8'SD255]", "[number 32'd123]", "[number 32 'd123]", "[number 32 'd 123]" ); MT("hex_literals", "[number 4'h0]", "[number 4'ha]", "[number 4'hF]", "[number 4'hx]", "[number 4'hz]", "[number 4'hX]", "[number 4'hZ]", "[number 32'hdc78]", "[number 32'hDC78]", "[number 32 'hDC78]", "[number 32'h DC78]", "[number 32 'h DC78]", "[number 32'h44x7]", "[number 32'hFFF?]" ); MT("real_number_literals", "[number 1.2]", "[number 0.1]", "[number 2394.26331]", "[number 1.2E12]", "[number 1.2e12]", "[number 1.30e-2]", "[number 0.1e-0]", "[number 23E10]", "[number 29E-2]", "[number 236.123_763_e-12]" ); MT("operators", "[meta ^]" ); MT("keywords", "[keyword logic]", "[keyword logic] [variable foo]", "[keyword reg] [variable abc]" ); MT("variables", "[variable _leading_underscore]", "[variable _if]", "[number 12] [variable foo]", "[variable foo] [number 14]" ); MT("tick_defines", "[def `FOO]", "[def `foo]", "[def `FOO_bar]" ); MT("system_calls", "[meta $display]", "[meta $vpi_printf]" ); MT("line_comment", "[comment // Hello world]"); // Alignment tests MT("align_port_map_style1", /** * mod mod(.a(a), * .b(b) * ); */ "[variable mod] [variable mod][bracket (].[variable a][bracket (][variable a][bracket )],", " .[variable b][bracket (][variable b][bracket )]", " [bracket )];", "" ); MT("align_port_map_style2", /** * mod mod( * .a(a), * .b(b) * ); */ "[variable mod] [variable mod][bracket (]", " .[variable a][bracket (][variable a][bracket )],", " .[variable b][bracket (][variable b][bracket )]", "[bracket )];", "" ); MT("align_assignments", /** * always @(posedge clk) begin * if (rst) * data_out <= 8'b0 + * 8'b1; * else * data_out = 8'b0 + * 8'b1; * data_out = * 8'b0 + 8'b1; * end */ "[keyword always] [def @][bracket (][keyword posedge] [variable clk][bracket )] [keyword begin]", " [keyword if] [bracket (][variable rst][bracket )]", " [variable data_out] [meta <=] [number 8'b0] [meta +]", " [number 8'b1];", " [keyword else]", " [variable data_out] [meta =] [number 8'b0] [meta +]", " [number 8'b1];", " [variable data_out] [meta =] [number 8'b0] [meta +]", " [number 8'b1];", "[keyword end]", "" ); // Indentation tests MT("indent_single_statement_if", "[keyword if] [bracket (][variable foo][bracket )]", " [keyword break];", "" ); MT("no_indent_after_single_line_if", "[keyword if] [bracket (][variable foo][bracket )] [keyword break];", "" ); MT("indent_after_if_begin_same_line", "[keyword if] [bracket (][variable foo][bracket )] [keyword begin]", " [keyword break];", " [keyword break];", "[keyword end]", "" ); MT("indent_after_if_begin_next_line", "[keyword if] [bracket (][variable foo][bracket )]", " [keyword begin]", " [keyword break];", " [keyword break];", " [keyword end]", "" ); MT("indent_single_statement_if_else", "[keyword if] [bracket (][variable foo][bracket )]", " [keyword break];", "[keyword else]", " [keyword break];", "" ); MT("indent_if_else_begin_same_line", "[keyword if] [bracket (][variable foo][bracket )] [keyword begin]", " [keyword break];", " [keyword break];", "[keyword end] [keyword else] [keyword begin]", " [keyword break];", " [keyword break];", "[keyword end]", "" ); MT("indent_if_else_begin_next_line", "[keyword if] [bracket (][variable foo][bracket )]", " [keyword begin]", " [keyword break];", " [keyword break];", " [keyword end]", "[keyword else]", " [keyword begin]", " [keyword break];", " [keyword break];", " [keyword end]", "" ); MT("indent_if_nested_without_begin", "[keyword if] [bracket (][variable foo][bracket )]", " [keyword if] [bracket (][variable foo][bracket )]", " [keyword if] [bracket (][variable foo][bracket )]", " [keyword break];", "" ); MT("indent_case", "[keyword case] [bracket (][variable state][bracket )]", " [variable FOO]:", " [keyword break];", " [variable BAR]:", " [keyword break];", "[keyword endcase]", "" ); MT("unindent_after_end_with_preceding_text", "[keyword begin]", " [keyword break]; [keyword end]", "" ); MT("export_function_one_line_does_not_indent", "[keyword export] [string \"DPI-C\"] [keyword function] [variable helloFromSV];", "" ); MT("export_task_one_line_does_not_indent", "[keyword export] [string \"DPI-C\"] [keyword task] [variable helloFromSV];", "" ); MT("export_function_two_lines_indents_properly", "[keyword export]", " [string \"DPI-C\"] [keyword function] [variable helloFromSV];", "" ); MT("export_task_two_lines_indents_properly", "[keyword export]", " [string \"DPI-C\"] [keyword task] [variable helloFromSV];", "" ); MT("import_function_one_line_does_not_indent", "[keyword import] [string \"DPI-C\"] [keyword function] [variable helloFromC];", "" ); MT("import_task_one_line_does_not_indent", "[keyword import] [string \"DPI-C\"] [keyword task] [variable helloFromC];", "" ); MT("import_package_single_line_does_not_indent", "[keyword import] [variable p]::[variable x];", "[keyword import] [variable p]::[variable y];", "" ); MT("covergroup_with_function_indents_properly", "[keyword covergroup] [variable cg] [keyword with] [keyword function] [variable sample][bracket (][keyword bit] [variable b][bracket )];", " [variable c] : [keyword coverpoint] [variable c];", "[keyword endgroup]: [variable cg]", "" ); MT("indent_uvm_macros", /** * `uvm_object_utils_begin(foo) * `uvm_field_event(foo, UVM_ALL_ON) * `uvm_object_utils_end */ "[def `uvm_object_utils_begin][bracket (][variable foo][bracket )]", " [def `uvm_field_event][bracket (][variable foo], [variable UVM_ALL_ON][bracket )]", "[def `uvm_object_utils_end]", "" ); MT("indent_uvm_macros2", /** * `uvm_do_with(mem_read,{ * bar_nb == 0; * }) */ "[def `uvm_do_with][bracket (][variable mem_read],[bracket {]", " [variable bar_nb] [meta ==] [number 0];", "[bracket })]", "" ); MT("indent_wait_disable_fork", /** * virtual task body(); * repeat (20) begin * fork * `uvm_create_on(t,p_seq) * join_none * end * wait fork; * disable fork; * endtask : body */ "[keyword virtual] [keyword task] [variable body][bracket ()];", " [keyword repeat] [bracket (][number 20][bracket )] [keyword begin]", " [keyword fork]", " [def `uvm_create_on][bracket (][variable t],[variable p_seq][bracket )]", " [keyword join_none]", " [keyword end]", " [keyword wait] [keyword fork];", " [keyword disable] [keyword fork];", "[keyword endtask] : [variable body]", "" ); MT("indent_typedef_class", /** * typedef class asdf; * typedef p p_t[]; * typedef enum { * ASDF * } t; */ "[keyword typedef] [keyword class] [variable asdf];", "[keyword typedef] [variable p] [variable p_t][bracket [[]]];", "[keyword typedef] [keyword enum] [bracket {]", " [variable ASDF]", "[bracket }] [variable t];", "" ); MT("indent_case_with_macro", /** * // It should be assumed that Macros can have ';' inside, or 'begin'/'end' blocks. * // As such, 'case' statement should indent correctly with macros inside. * case(foo) * ASDF : this.foo = seqNum; * ABCD : `update(f) * EFGH : `update(g) * endcase */ "[keyword case][bracket (][variable foo][bracket )]", " [variable ASDF] : [keyword this].[variable foo] [meta =] [variable seqNum];", " [variable ABCD] : [def `update][bracket (][variable f][bracket )]", " [variable EFGH] : [def `update][bracket (][variable g][bracket )]", "[keyword endcase]", "" ); MT("indent_extern_function", /** * extern virtual function void do(ref packet trans); * extern virtual function void do2(ref packet trans); */ "[keyword extern] [keyword virtual] [keyword function] [keyword void] [variable do1][bracket (][keyword ref] [variable packet] [variable trans][bracket )];", "[keyword extern] [keyword virtual] [keyword function] [keyword void] [variable do2][bracket (][keyword ref] [variable packet] [variable trans][bracket )];", "" ); MT("indent_assignment", /** * for (int i=1;i < fun;i++) begin * foo = 2 << asdf || 11'h35 >> abcd * && 8'h6 | 1'b1; * end */ "[keyword for] [bracket (][keyword int] [variable i][meta =][number 1];[variable i] [meta <] [variable fun];[variable i][meta ++][bracket )] [keyword begin]", " [variable foo] [meta =] [number 2] [meta <<] [variable asdf] [meta ||] [number 11'h35] [meta >>] [variable abcd]", " [meta &&] [number 8'h6] [meta |] [number 1'b1];", "[keyword end]", "" ); MT("indent_foreach_constraint", /** * `uvm_rand_send_with(wrTlp, { * length ==1; * foreach (Data[i]) { * payload[i] == Data[i]; * } * }) */ "[def `uvm_rand_send_with][bracket (][variable wrTlp], [bracket {]", " [variable length] [meta ==][number 1];", " [keyword foreach] [bracket (][variable Data][bracket [[][variable i][bracket ]])] [bracket {]", " [variable payload][bracket [[][variable i][bracket ]]] [meta ==] [variable Data][bracket [[][variable i][bracket ]]];", " [bracket }]", "[bracket })]", "" ); MT("indent_compiler_directives", /** * `ifdef DUT * `else * `ifndef FOO * `define FOO * `endif * `endif * `timescale 1ns/1ns */ "[def `ifdef] [variable DUT]", "[def `else]", " [def `ifndef] [variable FOO]", " [def `define] [variable FOO]", " [def `endif]", "[def `endif]", "[def `timescale] [number 1][variable ns][meta /][number 1][variable ns]", "" ); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/verilog/verilog.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("verilog", function(config, parserConfig) { var indentUnit = config.indentUnit, statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, dontAlignCalls = parserConfig.dontAlignCalls, // compilerDirectivesUseRegularIndentation - If set, Compiler directive // indentation follows the same rules as everything else. Otherwise if // false, compiler directives will track their own indentation. // For example, `ifdef nested inside another `ifndef will be indented, // but a `ifdef inside a function block may not be indented. compilerDirectivesUseRegularIndentation = parserConfig.compilerDirectivesUseRegularIndentation, noIndentKeywords = parserConfig.noIndentKeywords || [], multiLineStrings = parserConfig.multiLineStrings, hooks = parserConfig.hooks || {}; function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } /** * Keywords from IEEE 1800-2012 */ var keywords = words( "accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind " + "bins binsof bit break buf bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config " + "const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable " + "dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup " + "endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask " + "enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin " + "function generate genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import " + "incdir include initial inout input inside instance int integer interconnect interface intersect join join_any " + "join_none large let liblist library local localparam logic longint macromodule matches medium modport module " + "nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 null or output package packed " + "parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup " + "pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg " + "reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime " + "s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify " + "specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on " + "table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior " + "trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void " + "wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor"); /** Operators from IEEE 1800-2012 unary_operator ::= + | - | ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ binary_operator ::= + | - | * | / | % | == | != | === | !== | ==? | !=? | && | || | ** | < | <= | > | >= | & | | | ^ | ^~ | ~^ | >> | << | >>> | <<< | -> | <-> inc_or_dec_operator ::= ++ | -- unary_module_path_operator ::= ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ binary_module_path_operator ::= == | != | && | || | & | | | ^ | ^~ | ~^ */ var isOperatorChar = /[\+\-\*\/!~&|^%=?:<>]/; var isBracketChar = /[\[\]{}()]/; var unsignedNumber = /\d[0-9_]*/; var decimalLiteral = /\d*\s*'s?d\s*\d[0-9_]*/i; var binaryLiteral = /\d*\s*'s?b\s*[xz01][xz01_]*/i; var octLiteral = /\d*\s*'s?o\s*[xz0-7][xz0-7_]*/i; var hexLiteral = /\d*\s*'s?h\s*[0-9a-fxz?][0-9a-fxz?_]*/i; var realLiteral = /(\d[\d_]*(\.\d[\d_]*)?E-?[\d_]+)|(\d[\d_]*\.\d[\d_]*)/i; var closingBracketOrWord = /^((`?\w+)|[)}\]])/; var closingBracket = /[)}\]]/; var compilerDirectiveRegex = new RegExp( "^(`(?:ifdef|ifndef|elsif|else|endif|undef|undefineall|define|include|begin_keywords|celldefine|default|" + "nettype|end_keywords|endcelldefine|line|nounconnected_drive|pragma|resetall|timescale|unconnected_drive))\\b"); var compilerDirectiveBeginRegex = /^(`(?:ifdef|ifndef|elsif|else))\b/; var compilerDirectiveEndRegex = /^(`(?:elsif|else|endif))\b/; var curPunc; var curKeyword; // Block openings which are closed by a matching keyword in the form of ("end" + keyword) // E.g. "task" => "endtask" var blockKeywords = words( "case checker class clocking config function generate interface module package " + "primitive program property specify sequence table task" ); // Opening/closing pairs var openClose = {}; for (var keyword in blockKeywords) { openClose[keyword] = "end" + keyword; } openClose["begin"] = "end"; openClose["casex"] = "endcase"; openClose["casez"] = "endcase"; openClose["do" ] = "while"; openClose["fork" ] = "join;join_any;join_none"; openClose["covergroup"] = "endgroup"; openClose["macro_begin"] = "macro_end"; for (var i in noIndentKeywords) { var keyword = noIndentKeywords[i]; if (openClose[keyword]) { openClose[keyword] = undefined; } } // Keywords which open statements that are ended with a semi-colon var statementKeywords = words("always always_comb always_ff always_latch assert assign assume else export for foreach forever if import initial repeat while extern typedef"); function tokenBase(stream, state) { var ch = stream.peek(), style; if (hooks[ch] && (style = hooks[ch](stream, state)) != false) return style; if (hooks.tokenBase && (style = hooks.tokenBase(stream, state)) != false) return style; if (/[,;:\.]/.test(ch)) { curPunc = stream.next(); return null; } if (isBracketChar.test(ch)) { curPunc = stream.next(); return "bracket"; } // Macros (tick-defines) if (ch == '`') { stream.next(); if (stream.eatWhile(/[\w\$_]/)) { var cur = stream.current(); curKeyword = cur; // Macros that end in _begin, are start of block and end with _end if (cur.startsWith("`uvm_") && cur.endsWith("_begin")) { var keywordClose = curKeyword.substr(0,curKeyword.length - 5) + "end"; openClose[cur] = keywordClose; curPunc = "newblock"; } else { stream.eatSpace(); if (stream.peek() == '(') { // Check if this is a block curPunc = "newmacro"; } var withSpace = stream.current(); // Move the stream back before the spaces stream.backUp(withSpace.length - cur.length); } return "def"; } else { return null; } } // System calls if (ch == '$') { stream.next(); if (stream.eatWhile(/[\w\$_]/)) { return "meta"; } else { return null; } } // Time literals if (ch == '#') { stream.next(); stream.eatWhile(/[\d_.]/); return "def"; } // Event if (ch == '@') { stream.next(); stream.eatWhile(/[@]/); return "def"; } // Strings if (ch == '"') { stream.next(); state.tokenize = tokenString(ch); return state.tokenize(stream, state); } // Comments if (ch == "/") { stream.next(); if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } stream.backUp(1); } // Numeric literals if (stream.match(realLiteral) || stream.match(decimalLiteral) || stream.match(binaryLiteral) || stream.match(octLiteral) || stream.match(hexLiteral) || stream.match(unsignedNumber) || stream.match(realLiteral)) { return "number"; } // Operators if (stream.eatWhile(isOperatorChar)) { curPunc = stream.current(); return "meta"; } // Keywords / plain variables if (stream.eatWhile(/[\w\$_]/)) { var cur = stream.current(); if (keywords[cur]) { if (openClose[cur]) { curPunc = "newblock"; if (cur === "fork") { // Fork can be a statement instead of block in cases of: // "disable fork;" and "wait fork;" (trailing semicolon) stream.eatSpace() if (stream.peek() == ';') { curPunc = "newstatement"; } stream.backUp(stream.current().length - cur.length); } } if (statementKeywords[cur]) { curPunc = "newstatement"; } curKeyword = cur; return "keyword"; } return "variable"; } stream.next(); return null; } function tokenString(quote) { return function(stream, state) { var escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) {end = true; break;} escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = tokenBase; return "string"; }; } function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); } return "comment"; } function Context(indented, column, type, scopekind, align, prev) { this.indented = indented; this.column = column; this.type = type; this.scopekind = scopekind; this.align = align; this.prev = prev; } function pushContext(state, col, type, scopekind) { var indent = state.indented; var c = new Context(indent, col, type, scopekind ? scopekind : "", null, state.context); return state.context = c; } function popContext(state) { var t = state.context.type; if (t == ")" || t == "]" || t == "}") { state.indented = state.context.indented; } return state.context = state.context.prev; } function isClosing(text, contextClosing) { if (text == contextClosing) { return true; } else { // contextClosing may be multiple keywords separated by ; var closingKeywords = contextClosing.split(";"); for (var i in closingKeywords) { if (text == closingKeywords[i]) { return true; } } return false; } } function isInsideScopeKind(ctx, scopekind) { if (ctx == null) { return false; } if (ctx.scopekind === scopekind) { return true; } return isInsideScopeKind(ctx.prev, scopekind); } function buildElectricInputRegEx() { // Reindentation should occur on any bracket char: {}()[] // or on a match of any of the block closing keywords, at // the end of a line var allClosings = []; for (var i in openClose) { if (openClose[i]) { var closings = openClose[i].split(";"); for (var j in closings) { allClosings.push(closings[j]); } } } var re = new RegExp("[{}()\\[\\]]|(" + allClosings.join("|") + ")$"); return re; } // Interface return { // Regex to force current line to reindent electricInput: buildElectricInputRegEx(), startState: function(basecolumn) { var state = { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", "top", false), indented: 0, compilerDirectiveIndented: 0, startOfLine: true }; if (hooks.startState) hooks.startState(state); return state; }, token: function(stream, state) { var ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (hooks.token) { // Call hook, with an optional return value of a style to override verilog styling. var style = hooks.token(stream, state); if (style !== undefined) { return style; } } if (stream.eatSpace()) return null; curPunc = null; curKeyword = null; var style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta" || style == "variable") { if (((curPunc === "=") || (curPunc === "<=")) && !isInsideScopeKind(ctx, "assignment")) { // '<=' could be nonblocking assignment or lessthan-equals (which shouldn't cause indent) // Search through the context to see if we are already in an assignment. // '=' could be inside port declaration with comma or ')' afterward, or inside for(;;) block. pushContext(state, stream.column() + curPunc.length, "assignment", "assignment"); if (ctx.align == null) ctx.align = true; } return style; } if (ctx.align == null) ctx.align = true; var isClosingAssignment = ctx.type == "assignment" && closingBracket.test(curPunc) && ctx.prev && ctx.prev.type === curPunc; if (curPunc == ctx.type || isClosingAssignment) { if (isClosingAssignment) { ctx = popContext(state); } ctx = popContext(state); if (curPunc == ")") { // Handle closing macros, assuming they could have a semicolon or begin/end block inside. if (ctx && (ctx.type === "macro")) { ctx = popContext(state); while (ctx && (ctx.type == "statement" || ctx.type == "assignment")) ctx = popContext(state); } } else if (curPunc == "}") { // Handle closing statements like constraint block: "foreach () {}" which // do not have semicolon at end. if (ctx && (ctx.type === "statement")) { while (ctx && (ctx.type == "statement")) ctx = popContext(state); } } } else if (((curPunc == ";" || curPunc == ",") && (ctx.type == "statement" || ctx.type == "assignment")) || (ctx.type && isClosing(curKeyword, ctx.type))) { ctx = popContext(state); while (ctx && (ctx.type == "statement" || ctx.type == "assignment")) ctx = popContext(state); } else if (curPunc == "{") { pushContext(state, stream.column(), "}"); } else if (curPunc == "[") { pushContext(state, stream.column(), "]"); } else if (curPunc == "(") { pushContext(state, stream.column(), ")"); } else if (ctx && ctx.type == "endcase" && curPunc == ":") { pushContext(state, stream.column(), "statement", "case"); } else if (curPunc == "newstatement") { pushContext(state, stream.column(), "statement", curKeyword); } else if (curPunc == "newblock") { if (curKeyword == "function" && ctx && (ctx.type == "statement" || ctx.type == "endgroup")) { // The 'function' keyword can appear in some other contexts where it actually does not // indicate a function (import/export DPI and covergroup definitions). // Do nothing in this case } else if (curKeyword == "task" && ctx && ctx.type == "statement") { // Same thing for task } else if (curKeyword == "class" && ctx && ctx.type == "statement") { // Same thing for class (e.g. typedef) } else { var close = openClose[curKeyword]; pushContext(state, stream.column(), close, curKeyword); } } else if (curPunc == "newmacro" || (curKeyword && curKeyword.match(compilerDirectiveRegex))) { if (curPunc == "newmacro") { // Macros (especially if they have parenthesis) potentially have a semicolon // or complete statement/block inside, and should be treated as such. pushContext(state, stream.column(), "macro", "macro"); } if (curKeyword.match(compilerDirectiveEndRegex)) { state.compilerDirectiveIndented -= statementIndentUnit; } if (curKeyword.match(compilerDirectiveBeginRegex)) { state.compilerDirectiveIndented += statementIndentUnit; } } state.startOfLine = false; return style; }, indent: function(state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; if (hooks.indent) { var fromHook = hooks.indent(state); if (fromHook >= 0) return fromHook; } var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; var closing = false; var possibleClosing = textAfter.match(closingBracketOrWord); if (possibleClosing) closing = isClosing(possibleClosing[0], ctx.type); if (!compilerDirectivesUseRegularIndentation && textAfter.match(compilerDirectiveRegex)) { if (textAfter.match(compilerDirectiveEndRegex)) { return state.compilerDirectiveIndented - statementIndentUnit; } return state.compilerDirectiveIndented; } if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); else if ((closingBracket.test(ctx.type) || ctx.type == "assignment") && ctx.align && !dontAlignCalls) return ctx.column + (closing ? 0 : 1); else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; else return ctx.indented + (closing ? 0 : indentUnit); }, blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", fold: "indent" }; }); CodeMirror.defineMIME("text/x-verilog", { name: "verilog" }); CodeMirror.defineMIME("text/x-systemverilog", { name: "verilog" }); // TL-Verilog mode. // See tl-x.org for language spec. // See the mode in action at makerchip.com. // Contact: steve.hoover@redwoodeda.com // TLV Identifier prefixes. // Note that sign is not treated separately, so "+/-" versions of numeric identifiers // are included. var tlvIdentifierStyle = { "|": "link", ">": "property", // Should condition this off for > TLV 1c. "$": "variable", "$$": "variable", "?$": "qualifier", "?*": "qualifier", "-": "hr", "/": "property", "/-": "property", "@": "variable-3", "@-": "variable-3", "@++": "variable-3", "@+=": "variable-3", "@+=-": "variable-3", "@--": "variable-3", "@-=": "variable-3", "%+": "tag", "%-": "tag", "%": "tag", ">>": "tag", "<<": "tag", "<>": "tag", "#": "tag", // Need to choose a style for this. "^": "attribute", "^^": "attribute", "^!": "attribute", "*": "variable-2", "**": "variable-2", "\\": "keyword", "\"": "comment" }; // Lines starting with these characters define scope (result in indentation). var tlvScopePrefixChars = { "/": "beh-hier", ">": "beh-hier", "-": "phys-hier", "|": "pipe", "?": "when", "@": "stage", "\\": "keyword" }; var tlvIndentUnit = 3; var tlvTrackStatements = false; var tlvIdentMatch = /^([~!@#\$%\^&\*-\+=\?\/\\\|'"<>]+)([\d\w_]*)/; // Matches an identifier. // Note that ':' is excluded, because of it's use in [:]. var tlvFirstLevelIndentMatch = /^[! ] /; var tlvLineIndentationMatch = /^[! ] */; var tlvCommentMatch = /^\/[\/\*]/; // Returns a style specific to the scope at the given indentation column. // Type is one of: "indent", "scope-ident", "before-scope-ident". function tlvScopeStyle(state, indentation, type) { // Begin scope. var depth = indentation / tlvIndentUnit; // TODO: Pass this in instead. return "tlv-" + state.tlvIndentationStyle[depth] + "-" + type; } // Return true if the next thing in the stream is an identifier with a mnemonic. function tlvIdentNext(stream) { var match; return (match = stream.match(tlvIdentMatch, false)) && match[2].length > 0; } CodeMirror.defineMIME("text/x-tlv", { name: "verilog", hooks: { electricInput: false, // Return undefined for verilog tokenizing, or style for TLV token (null not used). // Standard CM styles are used for most formatting, but some TL-Verilog-specific highlighting // can be enabled with the definition of cm-tlv-* styles, including highlighting for: // - M4 tokens // - TLV scope indentation // - Statement delimitation (enabled by tlvTrackStatements) token: function(stream, state) { var style = undefined; var match; // Return value of pattern matches. // Set highlighting mode based on code region (TLV or SV). if (stream.sol() && ! state.tlvInBlockComment) { // Process region. if (stream.peek() == '\\') { style = "def"; stream.skipToEnd(); if (stream.string.match(/\\SV/)) { state.tlvCodeActive = false; } else if (stream.string.match(/\\TLV/)){ state.tlvCodeActive = true; } } // Correct indentation in the face of a line prefix char. if (state.tlvCodeActive && stream.pos == 0 && (state.indented == 0) && (match = stream.match(tlvLineIndentationMatch, false))) { state.indented = match[0].length; } // Compute indentation state: // o Auto indentation on next line // o Indentation scope styles var indented = state.indented; var depth = indented / tlvIndentUnit; if (depth <= state.tlvIndentationStyle.length) { // not deeper than current scope var blankline = stream.string.length == indented; var chPos = depth * tlvIndentUnit; if (chPos < stream.string.length) { var bodyString = stream.string.slice(chPos); var ch = bodyString[0]; if (tlvScopePrefixChars[ch] && ((match = bodyString.match(tlvIdentMatch)) && tlvIdentifierStyle[match[1]])) { // This line begins scope. // Next line gets indented one level. indented += tlvIndentUnit; // Style the next level of indentation (except non-region keyword identifiers, // which are statements themselves) if (!(ch == "\\" && chPos > 0)) { state.tlvIndentationStyle[depth] = tlvScopePrefixChars[ch]; if (tlvTrackStatements) {state.statementComment = false;} depth++; } } } // Clear out deeper indentation levels unless line is blank. if (!blankline) { while (state.tlvIndentationStyle.length > depth) { state.tlvIndentationStyle.pop(); } } } // Set next level of indentation. state.tlvNextIndent = indented; } if (state.tlvCodeActive) { // Highlight as TLV. var beginStatement = false; if (tlvTrackStatements) { // This starts a statement if the position is at the scope level // and we're not within a statement leading comment. beginStatement = (stream.peek() != " ") && // not a space (style === undefined) && // not a region identifier !state.tlvInBlockComment && // not in block comment //!stream.match(tlvCommentMatch, false) && // not comment start (stream.column() == state.tlvIndentationStyle.length * tlvIndentUnit); // at scope level if (beginStatement) { if (state.statementComment) { // statement already started by comment beginStatement = false; } state.statementComment = stream.match(tlvCommentMatch, false); // comment start } } var match; if (style !== undefined) { // Region line. style += " " + tlvScopeStyle(state, 0, "scope-ident") } else if (((stream.pos / tlvIndentUnit) < state.tlvIndentationStyle.length) && (match = stream.match(stream.sol() ? tlvFirstLevelIndentMatch : /^ /))) { // Indentation style = // make this style distinct from the previous one to prevent // codemirror from combining spans "tlv-indent-" + (((stream.pos % 2) == 0) ? "even" : "odd") + // and style it " " + tlvScopeStyle(state, stream.pos - tlvIndentUnit, "indent"); // Style the line prefix character. if (match[0].charAt(0) == "!") { style += " tlv-alert-line-prefix"; } // Place a class before a scope identifier. if (tlvIdentNext(stream)) { style += " " + tlvScopeStyle(state, stream.pos, "before-scope-ident"); } } else if (state.tlvInBlockComment) { // In a block comment. if (stream.match(/^.*?\*\//)) { // Exit block comment. state.tlvInBlockComment = false; if (tlvTrackStatements && !stream.eol()) { // Anything after comment is assumed to be real statement content. state.statementComment = false; } } else { stream.skipToEnd(); } style = "comment"; } else if ((match = stream.match(tlvCommentMatch)) && !state.tlvInBlockComment) { // Start comment. if (match[0] == "//") { // Line comment. stream.skipToEnd(); } else { // Block comment. state.tlvInBlockComment = true; } style = "comment"; } else if (match = stream.match(tlvIdentMatch)) { // looks like an identifier (or identifier prefix) var prefix = match[1]; var mnemonic = match[2]; if (// is identifier prefix tlvIdentifierStyle.hasOwnProperty(prefix) && // has mnemonic or we're at the end of the line (maybe it hasn't been typed yet) (mnemonic.length > 0 || stream.eol())) { style = tlvIdentifierStyle[prefix]; if (stream.column() == state.indented) { // Begin scope. style += " " + tlvScopeStyle(state, stream.column(), "scope-ident") } } else { // Just swallow one character and try again. // This enables subsequent identifier match with preceding symbol character, which // is legal within a statement. (E.g., !$reset). It also enables detection of // comment start with preceding symbols. stream.backUp(stream.current().length - 1); style = "tlv-default"; } } else if (stream.match(/^\t+/)) { // Highlight tabs, which are illegal. style = "tlv-tab"; } else if (stream.match(/^[\[\]{}\(\);\:]+/)) { // [:], (), {}, ;. style = "meta"; } else if (match = stream.match(/^[mM]4([\+_])?[\w\d_]*/)) { // m4 pre proc style = (match[1] == "+") ? "tlv-m4-plus" : "tlv-m4"; } else if (stream.match(/^ +/)){ // Skip over spaces. if (stream.eol()) { // Trailing spaces. style = "error"; } else { // Non-trailing spaces. style = "tlv-default"; } } else if (stream.match(/^[\w\d_]+/)) { // alpha-numeric token. style = "number"; } else { // Eat the next char w/ no formatting. stream.next(); style = "tlv-default"; } if (beginStatement) { style += " tlv-statement"; } } else { if (stream.match(/^[mM]4([\w\d_]*)/)) { // m4 pre proc style = "tlv-m4"; } } return style; }, indent: function(state) { return (state.tlvCodeActive == true) ? state.tlvNextIndent : -1; }, startState: function(state) { state.tlvIndentationStyle = []; // Styles to use for each level of indentation. state.tlvCodeActive = true; // True when we're in a TLV region (and at beginning of file). state.tlvNextIndent = -1; // The number of spaces to autoindent the next line if tlvCodeActive. state.tlvInBlockComment = false; // True inside /**/ comment. if (tlvTrackStatements) { state.statementComment = false; // True inside a statement's header comment. } } } }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vhdl/index.html ================================================ CodeMirror: VHDL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • VHDL

    VHDL mode

    Syntax highlighting and indentation for the VHDL language.

    Configuration options:

    • atoms - List of atom words. Default: "null"
    • hooks - List of meta hooks. Default: ["`", "$"]
    • multiLineStrings - Whether multi-line strings are accepted. Default: false

    MIME types defined: text/x-vhdl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vhdl/vhdl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Originally written by Alf Nielsen, re-written by Michael Zhou (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function words(str) { var obj = {}, words = str.split(","); for (var i = 0; i < words.length; ++i) { var allCaps = words[i].toUpperCase(); var firstCap = words[i].charAt(0).toUpperCase() + words[i].slice(1); obj[words[i]] = true; obj[allCaps] = true; obj[firstCap] = true; } return obj; } function metaHook(stream) { stream.eatWhile(/[\w\$_]/); return "meta"; } CodeMirror.defineMode("vhdl", function(config, parserConfig) { var indentUnit = config.indentUnit, atoms = parserConfig.atoms || words("null"), hooks = parserConfig.hooks || {"`": metaHook, "$": metaHook}, multiLineStrings = parserConfig.multiLineStrings; var keywords = words("abs,access,after,alias,all,and,architecture,array,assert,attribute,begin,block," + "body,buffer,bus,case,component,configuration,constant,disconnect,downto,else,elsif,end,end block,end case," + "end component,end for,end generate,end if,end loop,end process,end record,end units,entity,exit,file,for," + "function,generate,generic,generic map,group,guarded,if,impure,in,inertial,inout,is,label,library,linkage," + "literal,loop,map,mod,nand,new,next,nor,null,of,on,open,or,others,out,package,package body,port,port map," + "postponed,procedure,process,pure,range,record,register,reject,rem,report,return,rol,ror,select,severity,signal," + "sla,sll,sra,srl,subtype,then,to,transport,type,unaffected,units,until,use,variable,wait,when,while,with,xnor,xor"); var blockKeywords = words("architecture,entity,begin,case,port,else,elsif,end,for,function,if"); var isOperatorChar = /[&|~> CodeMirror: Vue.js mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Vue.js mode

    Vue.js mode

    MIME types defined: text/x-vue

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/vue/vue.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function (mod) { "use strict"; if (typeof exports === "object" && typeof module === "object") {// CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/overlay"), require("../xml/xml"), require("../javascript/javascript"), require("../coffeescript/coffeescript"), require("../css/css"), require("../sass/sass"), require("../stylus/stylus"), require("../pug/pug"), require("../handlebars/handlebars")); } else if (typeof define === "function" && define.amd) { // AMD define(["../../lib/codemirror", "../../addon/mode/overlay", "../xml/xml", "../javascript/javascript", "../coffeescript/coffeescript", "../css/css", "../sass/sass", "../stylus/stylus", "../pug/pug", "../handlebars/handlebars"], mod); } else { // Plain browser env mod(CodeMirror); } })(function (CodeMirror) { var tagLanguages = { script: [ ["lang", /coffee(script)?/, "coffeescript"], ["type", /^(?:text|application)\/(?:x-)?coffee(?:script)?$/, "coffeescript"], ["lang", /^babel$/, "javascript"], ["type", /^text\/babel$/, "javascript"], ["type", /^text\/ecmascript-\d+$/, "javascript"] ], style: [ ["lang", /^stylus$/i, "stylus"], ["lang", /^sass$/i, "sass"], ["lang", /^less$/i, "text/x-less"], ["lang", /^scss$/i, "text/x-scss"], ["type", /^(text\/)?(x-)?styl(us)?$/i, "stylus"], ["type", /^text\/sass/i, "sass"], ["type", /^(text\/)?(x-)?scss$/i, "text/x-scss"], ["type", /^(text\/)?(x-)?less$/i, "text/x-less"] ], template: [ ["lang", /^vue-template$/i, "vue"], ["lang", /^pug$/i, "pug"], ["lang", /^handlebars$/i, "handlebars"], ["type", /^(text\/)?(x-)?pug$/i, "pug"], ["type", /^text\/x-handlebars-template$/i, "handlebars"], [null, null, "vue-template"] ] }; CodeMirror.defineMode("vue-template", function (config, parserConfig) { var mustacheOverlay = { token: function (stream) { if (stream.match(/^\{\{.*?\}\}/)) return "meta mustache"; while (stream.next() && !stream.match("{{", false)) {} return null; } }; return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), mustacheOverlay); }); CodeMirror.defineMode("vue", function (config) { return CodeMirror.getMode(config, {name: "htmlmixed", tags: tagLanguages}); }, "htmlmixed", "xml", "javascript", "coffeescript", "css", "sass", "stylus", "pug", "handlebars"); CodeMirror.defineMIME("script/x-vue", "vue"); CodeMirror.defineMIME("text/x-vue", "vue"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/wast/index.html ================================================ CodeMirror: WebAssembly mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • WebAssembly

    WebAssembly mode

    MIME types defined: text/webassembly.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/wast/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 4}, "wast"); function MT(name) {test.mode(name, mode, Array.prototype.slice.call(arguments, 1));} MT('number-test', '[number 0]', '[number 123]', '[number nan]', '[number inf]', '[number infinity]', '[number 0.1]', '[number 123.0]', '[number 12E+99]'); MT('string-literals-test', '[string "foo"]', '[string "\\"foo\\""]', '[string "foo #\\"# bar"]'); MT('atom-test', '[atom funcref]', '[atom externref]', '[atom i32]', '[atom i64]', '[atom f32]', '[atom f64]'); MT('keyword-test', '[keyword br]', '[keyword if]', '[keyword loop]', '[keyword i32.add]', '[keyword local.get]'); MT('control-instructions', '[keyword unreachable]', '[keyword nop]', '[keyword br] [variable-2 $label0]', '[keyword br_if] [variable-2 $label0]', '[keyword br_table] [variable-2 $label0] [variable-2 $label1] [variable-2 $label3]', '[keyword return]', '[keyword call] [variable-2 $func0]', '[keyword call_indirect] [variable-2 $table] ([keyword param] [atom f32] [atom f64]) ([keyword result] [atom i32] [atom i64])', '[keyword return_call] [variable-2 $func0]', '[keyword return_call_indirect] ([keyword param] [atom f32] [atom f64]) ([keyword result] [atom i32] [atom i64])', '[keyword select] ([keyword local.get] [number 1]) ([keyword local.get] [number 2]) ([keyword local.get] [number 3])', '[keyword try] ([keyword result] [atom i32])', '[keyword throw] [number 0]', '[keyword rethrow] [number 0]', '[keyword catch] [number 0]', '[keyword catch_all]', '[keyword delegate] [number 0]', '[keyword unwind]'); MT('memory-instructions', '[keyword i32.load] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.load8_s] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.load8_u] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.load16_s] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.load16_u] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.store] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.store8] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i32.store16] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.store] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load8_s] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load8_u] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load16_s] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load16_u] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load32_s] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.load32_u] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.store8] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.store16] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword i64.store32] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword f32.load] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword f32.store] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword f64.load] [keyword offset]=[number 4] [keyword align]=[number 4]', '[keyword f64.store] [keyword offset]=[number 4] [keyword align]=[number 4]'); MT('atomic-memory-instructions', '[keyword memory.atomic.notify] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword memory.atomic.wait32] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword memory.atomic.wait64] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.load] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.load8_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.load16_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.store] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.store8] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.store16] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.load] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.load8_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.load16_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.load32_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.store] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.store8] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.store16] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.store32] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.add] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.add_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.add_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.add] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.add_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.add_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.add_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.sub] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.sub_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.sub_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.sub] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.sub_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.sub_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.sub_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.and] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.and_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.and_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.and] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.and_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.and_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.and_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.or] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.or_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.or_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.or] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.or_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.or_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.or_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.xor] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.xor_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.xor_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.xor] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.xor_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.xor_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.xor_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.xchg] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.xchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.xchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.xchg] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.xchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.xchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.xchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw.cmpxchg] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw8.cmpxchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i32.atomic.rmw16.cmpxchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw.cmpxchg] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw8.cmpxchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw16.cmpxchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword i64.atomic.rmw32.cmpxchg_u] [keyword offset]=[number 32] [keyword align]=[number 4]'); MT('simd-instructions', '[keyword v128.load] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword v128.load8x8_s] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load8x8_u] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load16x4_s] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load16x4_u] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load32x2_s] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load32x2_u] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load8_splat] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load16_splat] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load32_splat] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load64_splat] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.store] [keyword offset]=[number 32] [keyword align]=[number 4]', '[keyword v128.const] [number 0] [number 1] [number 2] [number 3] [number 4] [number 5] [number 6] [number 7] [number 8] [number 9] [number 10] [number 11] [number 12] [number 13] [number 14] [number 15]', '[keyword i8x16.shuffle] [number 0] [number 1] [number 2] [number 3] [number 4] [number 5] [number 6] [number 7] [number 8] [number 9] [number 10] [number 11] [number 12] [number 13] [number 14] [number 15]', '[keyword i8x16.swizzle]', '[keyword i8x16.splat]', '[keyword i16x8.splat]', '[keyword i32x4.splat]', '[keyword i64x2.splat]', '[keyword f32x4.splat]', '[keyword f64x2.splat]', '[keyword i8x16.extract_lane_s] [number 1]', '[keyword i8x16.extract_lane_u] [number 1]', '[keyword i8x16.replace_lane] [number 1]', '[keyword i16x8.extract_lane_s] [number 1]', '[keyword i16x8.extract_lane_u] [number 1]', '[keyword i16x8.replace_lane] [number 1]', '[keyword i32x4.extract_lane] [number 1]', '[keyword i32x4.replace_lane] [number 1]', '[keyword i64x2.extract_lane] [number 1]', '[keyword i64x2.replace_lane] [number 1]', '[keyword f32x4.extract_lane] [number 1]', '[keyword f32x4.replace_lane] [number 1]', '[keyword f64x2.extract_lane] [number 1]', '[keyword f64x2.replace_lane] [number 1]', '[keyword i8x16.eq]', '[keyword i8x16.ne]', '[keyword i8x16.lt_s]', '[keyword i8x16.lt_u]', '[keyword i8x16.gt_s]', '[keyword i8x16.gt_u]', '[keyword i8x16.le_s]', '[keyword i8x16.le_u]', '[keyword i8x16.ge_s]', '[keyword i8x16.ge_u]', '[keyword i16x8.eq]', '[keyword i16x8.ne]', '[keyword i16x8.lt_s]', '[keyword i16x8.lt_u]', '[keyword i16x8.gt_s]', '[keyword i16x8.gt_u]', '[keyword i16x8.le_s]', '[keyword i16x8.le_u]', '[keyword i16x8.ge_s]', '[keyword i16x8.ge_u]', '[keyword i32x4.eq]', '[keyword i32x4.ne]', '[keyword i32x4.lt_s]', '[keyword i32x4.lt_u]', '[keyword i32x4.gt_s]', '[keyword i32x4.gt_u]', '[keyword i32x4.le_s]', '[keyword i32x4.le_u]', '[keyword i32x4.ge_s]', '[keyword i32x4.ge_u]', '[keyword f32x4.eq]', '[keyword f32x4.ne]', '[keyword f32x4.lt]', '[keyword f32x4.gt]', '[keyword f32x4.le]', '[keyword f32x4.ge]', '[keyword f64x2.eq]', '[keyword f64x2.ne]', '[keyword f64x2.lt]', '[keyword f64x2.gt]', '[keyword f64x2.le]', '[keyword f64x2.ge]', '[keyword v128.not]', '[keyword v128.and]', '[keyword v128.andnot]', '[keyword v128.or]', '[keyword v128.xor]', '[keyword v128.bitselect]', '[keyword v128.any_true]', '[keyword v128.load8_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.load16_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.load32_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.load64_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.store8_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.store16_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.store32_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.store64_lane] [keyword offset]=[number 64] [keyword align]=[number 0] [number 1]', '[keyword v128.load32_zero] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword v128.load64_zero] [keyword offset]=[number 64] [keyword align]=[number 0]', '[keyword f32x4.demote_f64x2_zero]', '[keyword f64x2.promote_low_f32x4]', '[keyword i8x16.abs]', '[keyword i8x16.neg]', '[keyword i8x16.popcnt]', '[keyword i8x16.all_true]', '[keyword i8x16.bitmask]', '[keyword i8x16.narrow_i16x8_s]', '[keyword i8x16.narrow_i16x8_u]', '[keyword f32x4.ceil]', '[keyword f32x4.floor]', '[keyword f32x4.trunc]', '[keyword f32x4.nearest]', '[keyword i8x16.shl]', '[keyword i8x16.shr_s]', '[keyword i8x16.shr_u]', '[keyword i8x16.add]', '[keyword i8x16.add_sat_s]', '[keyword i8x16.add_sat_u]', '[keyword i8x16.sub]', '[keyword i8x16.sub_sat_s]', '[keyword i8x16.sub_sat_u]', '[keyword f64x2.ceil]', '[keyword f64x2.floor]', '[keyword i8x16.min_s]', '[keyword i8x16.min_u]', '[keyword i8x16.max_s]', '[keyword i8x16.max_u]', '[keyword f64x2.trunc]', '[keyword i8x16.avgr_u]', '[keyword i16x8.extadd_pairwise_i8x16_s]', '[keyword i16x8.extadd_pairwise_i8x16_u]', '[keyword i32x4.extadd_pairwise_i16x8_s]', '[keyword i32x4.extadd_pairwise_i16x8_u]', '[keyword i16x8.abs]', '[keyword i16x8.neg]', '[keyword i16x8.q15mulr_sat_s]', '[keyword i16x8.all_true]', '[keyword i16x8.bitmask]', '[keyword i16x8.narrow_i32x4_s]', '[keyword i16x8.narrow_i32x4_u]', '[keyword i16x8.extend_low_i8x16_s]', '[keyword i16x8.extend_high_i8x16_s]', '[keyword i16x8.extend_low_i8x16_u]', '[keyword i16x8.extend_high_i8x16_u]', '[keyword i16x8.shl]', '[keyword i16x8.shr_s]', '[keyword i16x8.shr_u]', '[keyword i16x8.add]', '[keyword i16x8.add_sat_s]', '[keyword i16x8.add_sat_u]', '[keyword i16x8.sub]', '[keyword i16x8.sub_sat_s]', '[keyword i16x8.sub_sat_u]', '[keyword f64x2.nearest]', '[keyword i16x8.mul]', '[keyword i16x8.min_s]', '[keyword i16x8.min_u]', '[keyword i16x8.max_s]', '[keyword i16x8.max_u]', '[keyword i16x8.avgr_u]', '[keyword i16x8.extmul_low_i8x16_s]', '[keyword i16x8.extmul_high_i8x16_s]', '[keyword i16x8.extmul_low_i8x16_u]', '[keyword i16x8.extmul_high_i8x16_u]', '[keyword i32x4.abs]', '[keyword i32x4.neg]', '[keyword i32x4.all_true]', '[keyword i32x4.bitmask]', '[keyword i32x4.extend_low_i16x8_s]', '[keyword i32x4.extend_high_i16x8_s]', '[keyword i32x4.extend_low_i16x8_u]', '[keyword i32x4.extend_high_i16x8_u]', '[keyword i32x4.shl]', '[keyword i32x4.shr_s]', '[keyword i32x4.shr_u]', '[keyword i32x4.add]', '[keyword i32x4.sub]', '[keyword i32x4.mul]', '[keyword i32x4.min_s]', '[keyword i32x4.min_u]', '[keyword i32x4.max_s]', '[keyword i32x4.max_u]', '[keyword i32x4.dot_i16x8_s]', '[keyword i32x4.extmul_low_i16x8_s]', '[keyword i32x4.extmul_high_i16x8_s]', '[keyword i32x4.extmul_low_i16x8_u]', '[keyword i32x4.extmul_high_i16x8_u]', '[keyword i64x2.abs]', '[keyword i64x2.neg]', '[keyword i64x2.all_true]', '[keyword i64x2.bitmask]', '[keyword i64x2.extend_low_i32x4_s]', '[keyword i64x2.extend_high_i32x4_s]', '[keyword i64x2.extend_low_i32x4_u]', '[keyword i64x2.extend_high_i32x4_u]', '[keyword i64x2.shl]', '[keyword i64x2.shr_s]', '[keyword i64x2.shr_u]', '[keyword i64x2.add]', '[keyword i64x2.sub]', '[keyword i64x2.mul]', '[keyword i64x2.eq]', '[keyword i64x2.ne]', '[keyword i64x2.lt_s]', '[keyword i64x2.gt_s]', '[keyword i64x2.le_s]', '[keyword i64x2.ge_s]', '[keyword i64x2.extmul_low_i32x4_s]', '[keyword i64x2.extmul_high_i32x4_s]', '[keyword i64x2.extmul_low_i32x4_u]', '[keyword i64x2.extmul_high_i32x4_u]', '[keyword f32x4.abs]', '[keyword f32x4.neg]', '[keyword f32x4.sqrt]', '[keyword f32x4.add]', '[keyword f32x4.sub]', '[keyword f32x4.mul]', '[keyword f32x4.div]', '[keyword f32x4.min]', '[keyword f32x4.max]', '[keyword f64x2.abs]', '[keyword f64x2.neg]', '[keyword f64x2.sqrt]', '[keyword f64x2.add]', '[keyword f64x2.sub]', '[keyword f64x2.mul]', '[keyword f64x2.div]', '[keyword f64x2.min]', '[keyword f64x2.max]', '[keyword i32x4.trunc_sat_f32x4_s]', '[keyword i32x4.trunc_sat_f32x4_u]', '[keyword f32x4.convert_i32x4_s]', '[keyword f32x4.convert_i32x4_u]', '[keyword i32x4.trunc_sat_f64x2_s_zero]', '[keyword i32x4.trunc_sat_f64x2_u_zero]', '[keyword f64x2.convert_low_i32x4_s]', '[keyword f64x2.convert_low_i32x4_u]'); MT('reference-type-instructions', '[keyword ref.null] [keyword extern]', '[keyword ref.null] [keyword func]', '[keyword ref.is_null] ([keyword ref.func] [variable-2 $f])', '[keyword ref.func] [variable-2 $f]'); MT('table-instructions', '[keyword table.get] [variable-2 $t] ([keyword i32.const] [number 5])', '[keyword table.set] [variable-2 $t] ([keyword i32.const] [number 5]) ([keyword ref.func] [variable-2 $f])', '[keyword table.size] [variable-2 $t]', '[keyword table.grow] [variable-2 $t] ([keyword ref.null] [keyword extern]) ([keyword i32.const] [number 5])', '[keyword table.fill] [variable-2 $t] ([keyword i32.const] [number 5]) ([keyword param] [variable-2 $r] [atom externref]) ([keyword i32.const] [number 5])', '[keyword table.init] [variable-2 $t] [number 1] ([keyword i32.const] [number 5]) ([keyword i32.const] [number 10]) ([keyword i32.const] [number 15])', '[keyword table.copy] [variable-2 $t] [variable-2 $t2] ([keyword i32.const] [number 5]) ([keyword i32.const] [number 10]) ([keyword i32.const] [number 15])' ); MT('gc-proposal', '[keyword call_ref] [keyword return_call_ref]', '[keyword ref.as_non_null] [keyword br_on_null] [keyword ref.eq]'); MT('gc-proposal-structs', '[keyword struct.new_with_rtt] [keyword struct.new_default_with_rtt]', '[keyword struct.get] [keyword struct.get_s] [keyword struct.get_u]', '[keyword struct.set]'); MT('gc-proposal-arrays', '[keyword array.new_with_rtt] [keyword array.new_default_with_rtt]', '[keyword array.get] [keyword array.get_s] [keyword array.get_u]', '[keyword array.len] [keyword array.set]'); MT('gc-proposal-i31', '[keyword i31.new] [keyword i31.get_s] [keyword i31.get_u]'); MT('gc-proposal-rtt', '[keyword rtt.canon] [keyword rtt.sub]'); MT('gc-proposal-typechecks', '[keyword ref.test] [keyword ref.cast] [keyword br_on_cast]', '[keyword ref.is_func] [keyword ref.is_data] [keyword ref.is_i31]', '[keyword ref.as_func] [keyword ref.as_data] [keyword ref.as_i31]', '[keyword br_on_func] [keyword br_on_data] [keyword br_on_i31]'); MT('gc-proposal-types', '[atom i8] [atom i16]', '[atom anyref] [atom dataref] [atom eqref] [atom i31ref]'); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/wast/wast.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../../addon/mode/simple")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../../addon/mode/simple"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var kKeywords = [ "align", "block", "br(_if|_table|_on_(cast|data|func|i31|null))?", "call(_indirect|_ref)?", "current_memory", "\\bdata\\b", "catch(_all)?", "delegate", "drop", "elem", "else", "end", "export", "\\bextern\\b", "\\bfunc\\b", "global(\\.(get|set))?", "if", "import", "local(\\.(get|set|tee))?", "loop", "module", "mut", "nop", "offset", "param", "result", "rethrow", "return(_call(_indirect|_ref)?)?", "select", "start", "table(\\.(size|get|set|size|grow|fill|init|copy))?", "then", "throw", "try", "type", "unreachable", "unwind", // Numeric opcodes. "i(32|64)\\.(store(8|16)|(load(8|16)_[su]))", "i64\\.(load32_[su]|store32)", "[fi](32|64)\\.(const|load|store)", "f(32|64)\\.(abs|add|ceil|copysign|div|eq|floor|[gl][et]|max|min|mul|nearest|neg?|sqrt|sub|trunc)", "i(32|64)\\.(a[dn]d|c[lt]z|(div|rem)_[su]|eqz?|[gl][te]_[su]|mul|ne|popcnt|rot[lr]|sh(l|r_[su])|sub|x?or)", "i64\\.extend_[su]_i32", "i32\\.wrap_i64", "i(32|64)\\.trunc_f(32|64)_[su]", "f(32|64)\\.convert_i(32|64)_[su]", "f64\\.promote_f32", "f32\\.demote_f64", "f32\\.reinterpret_i32", "i32\\.reinterpret_f32", "f64\\.reinterpret_i64", "i64\\.reinterpret_f64", // Atomics. "memory(\\.((atomic\\.(notify|wait(32|64)))|grow|size))?", "i64\.atomic\\.(load32_u|store32|rmw32\\.(a[dn]d|sub|x?or|(cmp)?xchg)_u)", "i(32|64)\\.atomic\\.(load((8|16)_u)?|store(8|16)?|rmw(\\.(a[dn]d|sub|x?or|(cmp)?xchg)|(8|16)\\.(a[dn]d|sub|x?or|(cmp)?xchg)_u))", // SIMD. "v128\\.load(8x8|16x4|32x2)_[su]", "v128\\.load(8|16|32|64)_splat", "v128\\.(load|store)(8|16|32|64)_lane", "v128\\.load(32|64)_zero", "v128\.(load|store|const|not|andnot|and|or|xor|bitselect|any_true)", "i(8x16|16x8)\\.(extract_lane_[su]|(add|sub)_sat_[su]|avgr_u)", "i(8x16|16x8|32x4|64x2)\\.(neg|add|sub|abs|shl|shr_[su]|all_true|bitmask|eq|ne|[lg][te]_s)", "(i(8x16|16x8|32x4|64x2)|f(32x4|64x2))\.(splat|replace_lane)", "i(8x16|16x8|32x4)\\.(([lg][te]_u)|((min|max)_[su]))", "f(32x4|64x2)\\.(neg|add|sub|abs|nearest|eq|ne|[lg][te]|sqrt|mul|div|min|max|ceil|floor|trunc)", "[fi](32x4|64x2)\\.extract_lane", "i8x16\\.(shuffle|swizzle|popcnt|narrow_i16x8_[su])", "i16x8\\.(narrow_i32x4_[su]|mul|extadd_pairwise_i8x16_[su]|q15mulr_sat_s)", "i16x8\\.(extend|extmul)_(low|high)_i8x16_[su]", "i32x4\\.(mul|dot_i16x8_s|trunc_sat_f64x2_[su]_zero)", "i32x4\\.((extend|extmul)_(low|high)_i16x8_|trunc_sat_f32x4_|extadd_pairwise_i16x8_)[su]", "i64x2\\.(mul|(extend|extmul)_(low|high)_i32x4_[su])", "f32x4\\.(convert_i32x4_[su]|demote_f64x2_zero)", "f64x2\\.(promote_low_f32x4|convert_low_i32x4_[su])", // Reference types, function references, and GC. "\\bany\\b", "array\\.len", "(array|struct)(\\.(new_(default_)?with_rtt|get(_[su])?|set))?", "\\beq\\b", "field", "i31\\.(new|get_[su])", "\\bnull\\b", "ref(\\.(([ai]s_(data|func|i31))|cast|eq|func|(is_|as_non_)?null|test))?", "rtt(\\.(canon|sub))?", ]; CodeMirror.defineSimpleMode('wast', { start: [ {regex: /[+\-]?(?:nan(?::0x[0-9a-fA-F]+)?|infinity|inf|0x[0-9a-fA-F]+\.?[0-9a-fA-F]*p[+\/-]?\d+|\d+(?:\.\d*)?[eE][+\-]?\d*|\d+\.\d*|0x[0-9a-fA-F]+|\d+)/, token: "number"}, {regex: new RegExp(kKeywords.join('|')), token: "keyword"}, {regex: /\b((any|data|eq|extern|i31|func)ref|[fi](32|64)|i(8|16))\b/, token: "atom"}, {regex: /\$([a-zA-Z0-9_`\+\-\*\/\\\^~=<>!\?@#$%&|:\.]+)/, token: "variable-2"}, {regex: /"(?:[^"\\\x00-\x1f\x7f]|\\[nt\\'"]|\\[0-9a-fA-F][0-9a-fA-F])*"/, token: "string"}, {regex: /\(;.*?/, token: "comment", next: "comment"}, {regex: /;;.*$/, token: "comment"}, {regex: /\(/, indent: true}, {regex: /\)/, dedent: true}, ], comment: [ {regex: /.*?;\)/, token: "comment", next: "start"}, {regex: /.*/, token: "comment"}, ], meta: { dontIndentStates: ['comment'], }, }); // https://github.com/WebAssembly/design/issues/981 mentions text/webassembly, // which seems like a reasonable choice, although it's not standard right now. CodeMirror.defineMIME("text/webassembly", "wast"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/webidl/index.html ================================================ CodeMirror: Web IDL mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Web IDL

    Web IDL mode

    MIME type defined: text/x-webidl.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/webidl/webidl.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); }; var builtinArray = [ "Clamp", "Constructor", "EnforceRange", "Exposed", "ImplicitThis", "Global", "PrimaryGlobal", "LegacyArrayClass", "LegacyUnenumerableNamedProperties", "LenientThis", "NamedConstructor", "NewObject", "NoInterfaceObject", "OverrideBuiltins", "PutForwards", "Replaceable", "SameObject", "TreatNonObjectAsNull", "TreatNullAs", "EmptyString", "Unforgeable", "Unscopeable" ]; var builtins = wordRegexp(builtinArray); var typeArray = [ "unsigned", "short", "long", // UnsignedIntegerType "unrestricted", "float", "double", // UnrestrictedFloatType "boolean", "byte", "octet", // Rest of PrimitiveType "Promise", // PromiseType "ArrayBuffer", "DataView", "Int8Array", "Int16Array", "Int32Array", "Uint8Array", "Uint16Array", "Uint32Array", "Uint8ClampedArray", "Float32Array", "Float64Array", // BufferRelatedType "ByteString", "DOMString", "USVString", "sequence", "object", "RegExp", "Error", "DOMException", "FrozenArray", // Rest of NonAnyType "any", // Rest of SingleType "void" // Rest of ReturnType ]; var types = wordRegexp(typeArray); var keywordArray = [ "attribute", "callback", "const", "deleter", "dictionary", "enum", "getter", "implements", "inherit", "interface", "iterable", "legacycaller", "maplike", "partial", "required", "serializer", "setlike", "setter", "static", "stringifier", "typedef", // ArgumentNameKeyword except // "unrestricted" "optional", "readonly", "or" ]; var keywords = wordRegexp(keywordArray); var atomArray = [ "true", "false", // BooleanLiteral "Infinity", "NaN", // FloatLiteral "null" // Rest of ConstValue ]; var atoms = wordRegexp(atomArray); CodeMirror.registerHelper("hintWords", "webidl", builtinArray.concat(typeArray).concat(keywordArray).concat(atomArray)); var startDefArray = ["callback", "dictionary", "enum", "interface"]; var startDefs = wordRegexp(startDefArray); var endDefArray = ["typedef"]; var endDefs = wordRegexp(endDefArray); var singleOperators = /^[:<=>?]/; var integers = /^-?([1-9][0-9]*|0[Xx][0-9A-Fa-f]+|0[0-7]*)/; var floats = /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)/; var identifiers = /^_?[A-Za-z][0-9A-Z_a-z-]*/; var identifiersEnd = /^_?[A-Za-z][0-9A-Z_a-z-]*(?=\s*;)/; var strings = /^"[^"]*"/; var multilineComments = /^\/\*.*?\*\//; var multilineCommentsStart = /^\/\*.*/; var multilineCommentsEnd = /^.*?\*\//; function readToken(stream, state) { // whitespace if (stream.eatSpace()) return null; // comment if (state.inComment) { if (stream.match(multilineCommentsEnd)) { state.inComment = false; return "comment"; } stream.skipToEnd(); return "comment"; } if (stream.match("//")) { stream.skipToEnd(); return "comment"; } if (stream.match(multilineComments)) return "comment"; if (stream.match(multilineCommentsStart)) { state.inComment = true; return "comment"; } // integer and float if (stream.match(/^-?[0-9\.]/, false)) { if (stream.match(integers) || stream.match(floats)) return "number"; } // string if (stream.match(strings)) return "string"; // identifier if (state.startDef && stream.match(identifiers)) return "def"; if (state.endDef && stream.match(identifiersEnd)) { state.endDef = false; return "def"; } if (stream.match(keywords)) return "keyword"; if (stream.match(types)) { var lastToken = state.lastToken; var nextToken = (stream.match(/^\s*(.+?)\b/, false) || [])[1]; if (lastToken === ":" || lastToken === "implements" || nextToken === "implements" || nextToken === "=") { // Used as identifier return "builtin"; } else { // Used as type return "variable-3"; } } if (stream.match(builtins)) return "builtin"; if (stream.match(atoms)) return "atom"; if (stream.match(identifiers)) return "variable"; // other if (stream.match(singleOperators)) return "operator"; // unrecognized stream.next(); return null; }; CodeMirror.defineMode("webidl", function() { return { startState: function() { return { // Is in multiline comment inComment: false, // Last non-whitespace, matched token lastToken: "", // Next token is a definition startDef: false, // Last token of the statement is a definition endDef: false }; }, token: function(stream, state) { var style = readToken(stream, state); if (style) { var cur = stream.current(); state.lastToken = cur; if (style === "keyword") { state.startDef = startDefs.test(cur); state.endDef = state.endDef || endDefs.test(cur); } else { state.startDef = false; } } return style; } }; }); CodeMirror.defineMIME("text/x-webidl", "webidl"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xml/index.html ================================================ CodeMirror: XML mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • XML

    XML mode

    The XML mode supports these configuration parameters:

    htmlMode (boolean)
    This switches the mode to parse HTML instead of XML. This means attributes do not have to be quoted, and some elements (such as br) do not require a closing tag.
    matchClosing (boolean)
    Controls whether the mode checks that close tags match the corresponding opening tag, and highlights mismatches as errors. Defaults to true.
    alignCDATA (boolean)
    Setting this to true will force the opening tag of CDATA blocks to not be indented.

    MIME types defined: application/xml, text/html.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xml/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "xml"), mname = "xml"; function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), mname); } MT("matching", "[tag&bracket <][tag top][tag&bracket >]", " text", " [tag&bracket <][tag inner][tag&bracket />]", "[tag&bracket ]"); MT("nonmatching", "[tag&bracket <][tag top][tag&bracket >]", " [tag&bracket <][tag inner][tag&bracket />]", " [tag&bracket ]"); MT("doctype", "[meta ]", "[tag&bracket <][tag top][tag&bracket />]"); MT("cdata", "[tag&bracket <][tag top][tag&bracket >]", " [atom ]", "[tag&bracket ]"); // HTML tests mode = CodeMirror.getMode({indentUnit: 2}, "text/html"); MT("selfclose", "[tag&bracket <][tag html][tag&bracket >]", " [tag&bracket <][tag link] [attribute rel]=[string stylesheet] [attribute href]=[string \"/foobar\"][tag&bracket >]", "[tag&bracket ]"); MT("list", "[tag&bracket <][tag ol][tag&bracket >]", " [tag&bracket <][tag li][tag&bracket >]one", " [tag&bracket <][tag li][tag&bracket >]two", "[tag&bracket ]"); MT("valueless", "[tag&bracket <][tag input] [attribute type]=[string checkbox] [attribute checked][tag&bracket />]"); MT("pThenArticle", "[tag&bracket <][tag p][tag&bracket >]", " foo", "[tag&bracket <][tag article][tag&bracket >]bar"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xml/xml.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; var htmlConfig = { autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 'track': true, 'wbr': true, 'menuitem': true}, implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 'th': true, 'tr': true}, contextGrabbers: { 'dd': {'dd': true, 'dt': true}, 'dt': {'dd': true, 'dt': true}, 'li': {'li': true}, 'option': {'option': true, 'optgroup': true}, 'optgroup': {'optgroup': true}, 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 'rp': {'rp': true, 'rt': true}, 'rt': {'rp': true, 'rt': true}, 'tbody': {'tbody': true, 'tfoot': true}, 'td': {'td': true, 'th': true}, 'tfoot': {'tbody': true}, 'th': {'td': true, 'th': true}, 'thead': {'tbody': true, 'tfoot': true}, 'tr': {'tr': true} }, doNotIndent: {"pre": true}, allowUnquoted: true, allowMissing: true, caseFold: true } var xmlConfig = { autoSelfClosers: {}, implicitlyClosed: {}, contextGrabbers: {}, doNotIndent: {}, allowUnquoted: false, allowMissing: false, allowMissingTagName: false, caseFold: false } CodeMirror.defineMode("xml", function(editorConf, config_) { var indentUnit = editorConf.indentUnit var config = {} var defaults = config_.htmlMode ? htmlConfig : xmlConfig for (var prop in defaults) config[prop] = defaults[prop] for (var prop in config_) config[prop] = config_[prop] // Return variables for tokenizers var type, setStyle; function inText(stream, state) { function chain(parser) { state.tokenize = parser; return parser(stream, state); } var ch = stream.next(); if (ch == "<") { if (stream.eat("!")) { if (stream.eat("[")) { if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); else return null; } else if (stream.match("--")) { return chain(inBlock("comment", "-->")); } else if (stream.match("DOCTYPE", true, true)) { stream.eatWhile(/[\w\._\-]/); return chain(doctype(1)); } else { return null; } } else if (stream.eat("?")) { stream.eatWhile(/[\w\._\-]/); state.tokenize = inBlock("meta", "?>"); return "meta"; } else { type = stream.eat("/") ? "closeTag" : "openTag"; state.tokenize = inTag; return "tag bracket"; } } else if (ch == "&") { var ok; if (stream.eat("#")) { if (stream.eat("x")) { ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); } else { ok = stream.eatWhile(/[\d]/) && stream.eat(";"); } } else { ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); } return ok ? "atom" : "error"; } else { stream.eatWhile(/[^&<]/); return null; } } inText.isInText = true; function inTag(stream, state) { var ch = stream.next(); if (ch == ">" || (ch == "/" && stream.eat(">"))) { state.tokenize = inText; type = ch == ">" ? "endTag" : "selfcloseTag"; return "tag bracket"; } else if (ch == "=") { type = "equals"; return null; } else if (ch == "<") { state.tokenize = inText; state.state = baseState; state.tagName = state.tagStart = null; var next = state.tokenize(stream, state); return next ? next + " tag error" : "tag error"; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); state.stringStartCol = stream.column(); return state.tokenize(stream, state); } else { stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); return "word"; } } function inAttribute(quote) { var closure = function(stream, state) { while (!stream.eol()) { if (stream.next() == quote) { state.tokenize = inTag; break; } } return "string"; }; closure.isInAttribute = true; return closure; } function inBlock(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = inText; break; } stream.next(); } return style; } } function doctype(depth) { return function(stream, state) { var ch; while ((ch = stream.next()) != null) { if (ch == "<") { state.tokenize = doctype(depth + 1); return state.tokenize(stream, state); } else if (ch == ">") { if (depth == 1) { state.tokenize = inText; break; } else { state.tokenize = doctype(depth - 1); return state.tokenize(stream, state); } } } return "meta"; }; } function lower(tagName) { return tagName && tagName.toLowerCase(); } function Context(state, tagName, startOfLine) { this.prev = state.context; this.tagName = tagName || ""; this.indent = state.indented; this.startOfLine = startOfLine; if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) this.noIndent = true; } function popContext(state) { if (state.context) state.context = state.context.prev; } function maybePopContext(state, nextTagName) { var parentTagName; while (true) { if (!state.context) { return; } parentTagName = state.context.tagName; if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) || !config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) { return; } popContext(state); } } function baseState(type, stream, state) { if (type == "openTag") { state.tagStart = stream.column(); return tagNameState; } else if (type == "closeTag") { return closeTagNameState; } else { return baseState; } } function tagNameState(type, stream, state) { if (type == "word") { state.tagName = stream.current(); setStyle = "tag"; return attrState; } else if (config.allowMissingTagName && type == "endTag") { setStyle = "tag bracket"; return attrState(type, stream, state); } else { setStyle = "error"; return tagNameState; } } function closeTagNameState(type, stream, state) { if (type == "word") { var tagName = stream.current(); if (state.context && state.context.tagName != tagName && config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName))) popContext(state); if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { setStyle = "tag"; return closeState; } else { setStyle = "tag error"; return closeStateErr; } } else if (config.allowMissingTagName && type == "endTag") { setStyle = "tag bracket"; return closeState(type, stream, state); } else { setStyle = "error"; return closeStateErr; } } function closeState(type, _stream, state) { if (type != "endTag") { setStyle = "error"; return closeState; } popContext(state); return baseState; } function closeStateErr(type, stream, state) { setStyle = "error"; return closeState(type, stream, state); } function attrState(type, _stream, state) { if (type == "word") { setStyle = "attribute"; return attrEqState; } else if (type == "endTag" || type == "selfcloseTag") { var tagName = state.tagName, tagStart = state.tagStart; state.tagName = state.tagStart = null; if (type == "selfcloseTag" || config.autoSelfClosers.hasOwnProperty(lower(tagName))) { maybePopContext(state, tagName); } else { maybePopContext(state, tagName); state.context = new Context(state, tagName, tagStart == state.indented); } return baseState; } setStyle = "error"; return attrState; } function attrEqState(type, stream, state) { if (type == "equals") return attrValueState; if (!config.allowMissing) setStyle = "error"; return attrState(type, stream, state); } function attrValueState(type, stream, state) { if (type == "string") return attrContinuedState; if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} setStyle = "error"; return attrState(type, stream, state); } function attrContinuedState(type, stream, state) { if (type == "string") return attrContinuedState; return attrState(type, stream, state); } return { startState: function(baseIndent) { var state = {tokenize: inText, state: baseState, indented: baseIndent || 0, tagName: null, tagStart: null, context: null} if (baseIndent != null) state.baseIndent = baseIndent return state }, token: function(stream, state) { if (!state.tagName && stream.sol()) state.indented = stream.indentation(); if (stream.eatSpace()) return null; type = null; var style = state.tokenize(stream, state); if ((style || type) && style != "comment") { setStyle = null; state.state = state.state(type || style, stream, state); if (setStyle) style = setStyle == "error" ? style + " error" : setStyle; } return style; }, indent: function(state, textAfter, fullLine) { var context = state.context; // Indent multi-line strings (e.g. css). if (state.tokenize.isInAttribute) { if (state.tagStart == state.indented) return state.stringStartCol + 1; else return state.indented + indentUnit; } if (context && context.noIndent) return CodeMirror.Pass; if (state.tokenize != inTag && state.tokenize != inText) return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; // Indent the starts of attribute names. if (state.tagName) { if (config.multilineTagIndentPastTag !== false) return state.tagStart + state.tagName.length + 2; else return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); } if (config.alignCDATA && /$/, blockCommentStart: "", configuration: config.htmlMode ? "html" : "xml", helperType: config.htmlMode ? "html" : "xml", skipAttribute: function(state) { if (state.state == attrValueState) state.state = attrState }, xmlCurrentTag: function(state) { return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null }, xmlCurrentContext: function(state) { var context = [] for (var cx = state.context; cx; cx = cx.prev) context.push(cx.tagName) return context.reverse() } }; }); CodeMirror.defineMIME("text/xml", "xml"); CodeMirror.defineMIME("application/xml", "xml"); if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xquery/index.html ================================================ CodeMirror: XQuery mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • XQuery

    XQuery mode

    MIME types defined: application/xquery.

    Development of the CodeMirror XQuery mode was sponsored by MarkLogic and developed by Mike Brevoort.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xquery/test.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Don't take these too seriously -- the expected results appear to be // based on the results of actual runs without any serious manual // verification. If a change you made causes them to fail, the test is // as likely to wrong as the code. (function() { var mode = CodeMirror.getMode({tabSize: 4}, "xquery"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } MT("eviltest", "[keyword xquery] [keyword version] [variable "1][keyword .][atom 0][keyword -][variable ml"][def&variable ;] [comment (: this is : a \"comment\" :)]", " [keyword let] [variable $let] [keyword :=] [variable <x] [variable attr][keyword =][variable "value">"test"<func>][def&variable ;function]() [variable $var] {[keyword function]()} {[variable $var]}[variable <][keyword /][variable func><][keyword /][variable x>]", " [keyword let] [variable $joe][keyword :=][atom 1]", " [keyword return] [keyword element] [variable element] {", " [keyword attribute] [variable attribute] { [atom 1] },", " [keyword element] [variable test] { [variable 'a'] }, [keyword attribute] [variable foo] { [variable "bar"] },", " [def&variable fn:doc]()[[ [variable foo][keyword /][variable @bar] [keyword eq] [variable $let] ]],", " [keyword //][variable x] } [comment (: a more 'evil' test :)]", " [comment (: Modified Blakeley example (: with nested comment :) ... :)]", " [keyword declare] [variable private] [keyword function] [def&variable local:declare]() {()}[variable ;]", " [keyword declare] [variable private] [keyword function] [def&variable local:private]() {()}[variable ;]", " [keyword declare] [variable private] [keyword function] [def&variable local:function]() {()}[variable ;]", " [keyword declare] [variable private] [keyword function] [def&variable local:local]() {()}[variable ;]", " [keyword let] [variable $let] [keyword :=] [variable <let>let] [variable $let] [keyword :=] [variable "let"<][keyword /let][variable >]", " [keyword return] [keyword element] [variable element] {", " [keyword attribute] [variable attribute] { [keyword try] { [def&variable xdmp:version]() } [keyword catch]([variable $e]) { [def&variable xdmp:log]([variable $e]) } },", " [keyword attribute] [variable fn:doc] { [variable "bar"] [keyword castable] [keyword as] [atom xs:string] },", " [keyword element] [variable text] { [keyword text] { [variable "text"] } },", " [def&variable fn:doc]()[[ [qualifier child::][variable eq][keyword /]([variable @bar] [keyword |] [qualifier attribute::][variable attribute]) [keyword eq] [variable $let] ]],", " [keyword //][variable fn:doc]", " }"); MT("testEmptySequenceKeyword", "[string \"foo\"] [keyword instance] [keyword of] [keyword empty-sequence]()"); MT("testMultiAttr", "[tag

    ][variable hello] [variable world][tag

    ]"); MT("test namespaced variable", "[keyword declare] [keyword namespace] [variable e] [keyword =] [string \"http://example.com/ANamespace\"][variable ;declare] [keyword variable] [variable $e:exampleComThisVarIsNotRecognized] [keyword as] [keyword element]([keyword *]) [variable external;]"); MT("test EQName variable", "[keyword declare] [keyword variable] [variable $\"http://www.example.com/ns/my\":var] [keyword :=] [atom 12][variable ;]", "[tag ]{[variable $\"http://www.example.com/ns/my\":var]}[tag ]"); MT("test EQName function", "[keyword declare] [keyword function] [def&variable \"http://www.example.com/ns/my\":fn] ([variable $a] [keyword as] [atom xs:integer]) [keyword as] [atom xs:integer] {", " [variable $a] [keyword +] [atom 2]", "}[variable ;]", "[tag ]{[def&variable \"http://www.example.com/ns/my\":fn]([atom 12])}[tag ]"); MT("test EQName function with single quotes", "[keyword declare] [keyword function] [def&variable 'http://www.example.com/ns/my':fn] ([variable $a] [keyword as] [atom xs:integer]) [keyword as] [atom xs:integer] {", " [variable $a] [keyword +] [atom 2]", "}[variable ;]", "[tag ]{[def&variable 'http://www.example.com/ns/my':fn]([atom 12])}[tag ]"); MT("testProcessingInstructions", "[def&variable data]([comment&meta ]) [keyword instance] [keyword of] [atom xs:string]"); MT("testQuoteEscapeDouble", "[keyword let] [variable $rootfolder] [keyword :=] [string \"c:\\builds\\winnt\\HEAD\\qa\\scripts\\\"]", "[keyword let] [variable $keysfolder] [keyword :=] [def&variable concat]([variable $rootfolder], [string \"keys\\\"])"); })(); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/xquery/xquery.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("xquery", function() { // The keywords object is set to the result of this self executing // function. Each keyword is a property of the keywords object whose // value is {type: atype, style: astyle} var keywords = function(){ // convenience functions used to build keywords object function kw(type) {return {type: type, style: "keyword"};} var operator = kw("operator") , atom = {type: "atom", style: "atom"} , punctuation = {type: "punctuation", style: null} , qualifier = {type: "axis_specifier", style: "qualifier"}; // kwObj is what is return from this function at the end var kwObj = { ',': punctuation }; // a list of 'basic' keywords. For each add a property to kwObj with the value of // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} var basic = ['after', 'all', 'allowing', 'ancestor', 'ancestor-or-self', 'any', 'array', 'as', 'ascending', 'at', 'attribute', 'base-uri', 'before', 'boundary-space', 'by', 'case', 'cast', 'castable', 'catch', 'child', 'collation', 'comment', 'construction', 'contains', 'content', 'context', 'copy', 'copy-namespaces', 'count', 'decimal-format', 'declare', 'default', 'delete', 'descendant', 'descendant-or-self', 'descending', 'diacritics', 'different', 'distance', 'document', 'document-node', 'element', 'else', 'empty', 'empty-sequence', 'encoding', 'end', 'entire', 'every', 'exactly', 'except', 'external', 'first', 'following', 'following-sibling', 'for', 'from', 'ftand', 'ftnot', 'ft-option', 'ftor', 'function', 'fuzzy', 'greatest', 'group', 'if', 'import', 'in', 'inherit', 'insensitive', 'insert', 'instance', 'intersect', 'into', 'invoke', 'is', 'item', 'language', 'last', 'lax', 'least', 'let', 'levels', 'lowercase', 'map', 'modify', 'module', 'most', 'namespace', 'next', 'no', 'node', 'nodes', 'no-inherit', 'no-preserve', 'not', 'occurs', 'of', 'only', 'option', 'order', 'ordered', 'ordering', 'paragraph', 'paragraphs', 'parent', 'phrase', 'preceding', 'preceding-sibling', 'preserve', 'previous', 'processing-instruction', 'relationship', 'rename', 'replace', 'return', 'revalidation', 'same', 'satisfies', 'schema', 'schema-attribute', 'schema-element', 'score', 'self', 'sensitive', 'sentence', 'sentences', 'sequence', 'skip', 'sliding', 'some', 'stable', 'start', 'stemming', 'stop', 'strict', 'strip', 'switch', 'text', 'then', 'thesaurus', 'times', 'to', 'transform', 'treat', 'try', 'tumbling', 'type', 'typeswitch', 'union', 'unordered', 'update', 'updating', 'uppercase', 'using', 'validate', 'value', 'variable', 'version', 'weight', 'when', 'where', 'wildcards', 'window', 'with', 'without', 'word', 'words', 'xquery']; for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; // a list of types. For each add a property to kwObj with the value of // {type: "atom", style: "atom"} var types = ['xs:anyAtomicType', 'xs:anySimpleType', 'xs:anyType', 'xs:anyURI', 'xs:base64Binary', 'xs:boolean', 'xs:byte', 'xs:date', 'xs:dateTime', 'xs:dateTimeStamp', 'xs:dayTimeDuration', 'xs:decimal', 'xs:double', 'xs:duration', 'xs:ENTITIES', 'xs:ENTITY', 'xs:float', 'xs:gDay', 'xs:gMonth', 'xs:gMonthDay', 'xs:gYear', 'xs:gYearMonth', 'xs:hexBinary', 'xs:ID', 'xs:IDREF', 'xs:IDREFS', 'xs:int', 'xs:integer', 'xs:item', 'xs:java', 'xs:language', 'xs:long', 'xs:Name', 'xs:NCName', 'xs:negativeInteger', 'xs:NMTOKEN', 'xs:NMTOKENS', 'xs:nonNegativeInteger', 'xs:nonPositiveInteger', 'xs:normalizedString', 'xs:NOTATION', 'xs:numeric', 'xs:positiveInteger', 'xs:precisionDecimal', 'xs:QName', 'xs:short', 'xs:string', 'xs:time', 'xs:token', 'xs:unsignedByte', 'xs:unsignedInt', 'xs:unsignedLong', 'xs:unsignedShort', 'xs:untyped', 'xs:untypedAtomic', 'xs:yearMonthDuration']; for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; return kwObj; }(); function chain(stream, state, f) { state.tokenize = f; return f(stream, state); } // the primary mode tokenizer function tokenBase(stream, state) { var ch = stream.next(), mightBeFunction = false, isEQName = isEQNameAhead(stream); // an XML tag (if not in some sub, chained tokenizer) if (ch == "<") { if(stream.match("!--", true)) return chain(stream, state, tokenXMLComment); if(stream.match("![CDATA", false)) { state.tokenize = tokenCDATA; return "tag"; } if(stream.match("?", false)) { return chain(stream, state, tokenPreProcessing); } var isclose = stream.eat("/"); stream.eatSpace(); var tagName = "", c; while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; return chain(stream, state, tokenTag(tagName, isclose)); } // start code block else if(ch == "{") { pushStateStack(state, { type: "codeblock"}); return null; } // end code block else if(ch == "}") { popStateStack(state); return null; } // if we're in an XML block else if(isInXmlBlock(state)) { if(ch == ">") return "tag"; else if(ch == "/" && stream.eat(">")) { popStateStack(state); return "tag"; } else return "variable"; } // if a number else if (/\d/.test(ch)) { stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); return "atom"; } // comment start else if (ch === "(" && stream.eat(":")) { pushStateStack(state, { type: "comment"}); return chain(stream, state, tokenComment); } // quoted string else if (!isEQName && (ch === '"' || ch === "'")) return chain(stream, state, tokenString(ch)); // variable else if(ch === "$") { return chain(stream, state, tokenVariable); } // assignment else if(ch ===":" && stream.eat("=")) { return "keyword"; } // open paren else if(ch === "(") { pushStateStack(state, { type: "paren"}); return null; } // close paren else if(ch === ")") { popStateStack(state); return null; } // open paren else if(ch === "[") { pushStateStack(state, { type: "bracket"}); return null; } // close paren else if(ch === "]") { popStateStack(state); return null; } else { var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; // if there's a EQName ahead, consume the rest of the string portion, it's likely a function if(isEQName && ch === '\"') while(stream.next() !== '"'){} if(isEQName && ch === '\'') while(stream.next() !== '\''){} // gobble up a word if the character is not known if(!known) stream.eatWhile(/[\w\$_-]/); // gobble a colon in the case that is a lib func type call fn:doc var foundColon = stream.eat(":"); // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier // which should get matched as a keyword if(!stream.eat(":") && foundColon) { stream.eatWhile(/[\w\$_-]/); } // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) if(stream.match(/^[ \t]*\(/, false)) { mightBeFunction = true; } // is the word a keyword? var word = stream.current(); known = keywords.propertyIsEnumerable(word) && keywords[word]; // if we think it's a function call but not yet known, // set style to variable for now for lack of something better if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; // if the previous word was element, attribute, axis specifier, this word should be the name of that if(isInXmlConstructor(state)) { popStateStack(state); return "variable"; } // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and // push the stack so we know to look for it on the next word if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); // if the word is known, return the details of that else just call this a generic 'word' return known ? known.style : "variable"; } } // handle comments, including nested function tokenComment(stream, state) { var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; while (ch = stream.next()) { if (ch == ")" && maybeEnd) { if(nestedCount > 0) nestedCount--; else { popStateStack(state); break; } } else if(ch == ":" && maybeNested) { nestedCount++; } maybeEnd = (ch == ":"); maybeNested = (ch == "("); } return "comment"; } // tokenizer for string literals // optionally pass a tokenizer function to set state.tokenize back to when finished function tokenString(quote, f) { return function(stream, state) { var ch; if(isInString(state) && stream.current() == quote) { popStateStack(state); if(f) state.tokenize = f; return "string"; } pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); // if we're in a string and in an XML block, allow an embedded code block if(stream.match("{", false) && isInXmlAttributeBlock(state)) { state.tokenize = tokenBase; return "string"; } while (ch = stream.next()) { if (ch == quote) { popStateStack(state); if(f) state.tokenize = f; break; } else { // if we're in a string and in an XML block, allow an embedded code block in an attribute if(stream.match("{", false) && isInXmlAttributeBlock(state)) { state.tokenize = tokenBase; return "string"; } } } return "string"; }; } // tokenizer for variables function tokenVariable(stream, state) { var isVariableChar = /[\w\$_-]/; // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote if(stream.eat("\"")) { while(stream.next() !== '\"'){}; stream.eat(":"); } else { stream.eatWhile(isVariableChar); if(!stream.match(":=", false)) stream.eat(":"); } stream.eatWhile(isVariableChar); state.tokenize = tokenBase; return "variable"; } // tokenizer for XML tags function tokenTag(name, isclose) { return function(stream, state) { stream.eatSpace(); if(isclose && stream.eat(">")) { popStateStack(state); state.tokenize = tokenBase; return "tag"; } // self closing tag without attributes? if(!stream.eat("/")) pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); if(!stream.eat(">")) { state.tokenize = tokenAttribute; return "tag"; } else { state.tokenize = tokenBase; } return "tag"; }; } // tokenizer for XML attributes function tokenAttribute(stream, state) { var ch = stream.next(); if(ch == "/" && stream.eat(">")) { if(isInXmlAttributeBlock(state)) popStateStack(state); if(isInXmlBlock(state)) popStateStack(state); return "tag"; } if(ch == ">") { if(isInXmlAttributeBlock(state)) popStateStack(state); return "tag"; } if(ch == "=") return null; // quoted string if (ch == '"' || ch == "'") return chain(stream, state, tokenString(ch, tokenAttribute)); if(!isInXmlAttributeBlock(state)) pushStateStack(state, { type: "attribute", tokenize: tokenAttribute}); stream.eat(/[a-zA-Z_:]/); stream.eatWhile(/[-a-zA-Z0-9_:.]/); stream.eatSpace(); // the case where the attribute has not value and the tag was closed if(stream.match(">", false) || stream.match("/", false)) { popStateStack(state); state.tokenize = tokenBase; } return "attribute"; } // handle comments, including nested function tokenXMLComment(stream, state) { var ch; while (ch = stream.next()) { if (ch == "-" && stream.match("->", true)) { state.tokenize = tokenBase; return "comment"; } } } // handle CDATA function tokenCDATA(stream, state) { var ch; while (ch = stream.next()) { if (ch == "]" && stream.match("]", true)) { state.tokenize = tokenBase; return "comment"; } } } // handle preprocessing instructions function tokenPreProcessing(stream, state) { var ch; while (ch = stream.next()) { if (ch == "?" && stream.match(">", true)) { state.tokenize = tokenBase; return "comment meta"; } } } // functions to test the current context of the state function isInXmlBlock(state) { return isIn(state, "tag"); } function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } function isInString(state) { return isIn(state, "string"); } function isEQNameAhead(stream) { // assume we've already eaten a quote (") if(stream.current() === '"') return stream.match(/^[^\"]+\"\:/, false); else if(stream.current() === '\'') return stream.match(/^[^\"]+\'\:/, false); else return false; } function isIn(state, type) { return (state.stack.length && state.stack[state.stack.length - 1].type == type); } function pushStateStack(state, newState) { state.stack.push(newState); } function popStateStack(state) { state.stack.pop(); var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; state.tokenize = reinstateTokenize || tokenBase; } // the interface for the mode API return { startState: function() { return { tokenize: tokenBase, cc: [], stack: [] }; }, token: function(stream, state) { if (stream.eatSpace()) return null; var style = state.tokenize(stream, state); return style; }, blockCommentStart: "(:", blockCommentEnd: ":)" }; }); CodeMirror.defineMIME("application/xquery", "xquery"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yacas/index.html ================================================ CodeMirror: yacas mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • yacas

    yacas mode

    MIME types defined: text/x-yacas (yacas).

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yacas/yacas.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // Yacas mode copyright (c) 2015 by Grzegorz Mazur // Loosely based on mathematica mode by Calin Barbat (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('yacas', function(_config, _parserConfig) { function words(str) { var obj = {}, words = str.split(" "); for (var i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } var bodiedOps = words("Assert BackQuote D Defun Deriv For ForEach FromFile " + "FromString Function Integrate InverseTaylor Limit " + "LocalSymbols Macro MacroRule MacroRulePattern " + "NIntegrate Rule RulePattern Subst TD TExplicitSum " + "TSum Taylor Taylor1 Taylor2 Taylor3 ToFile " + "ToStdout ToString TraceRule Until While"); // patterns var pFloatForm = "(?:(?:\\.\\d+|\\d+\\.\\d*|\\d+)(?:[eE][+-]?\\d+)?)"; var pIdentifier = "(?:[a-zA-Z\\$'][a-zA-Z0-9\\$']*)"; // regular expressions var reFloatForm = new RegExp(pFloatForm); var reIdentifier = new RegExp(pIdentifier); var rePattern = new RegExp(pIdentifier + "?_" + pIdentifier); var reFunctionLike = new RegExp(pIdentifier + "\\s*\\("); function tokenBase(stream, state) { var ch; // get next character ch = stream.next(); // string if (ch === '"') { state.tokenize = tokenString; return state.tokenize(stream, state); } // comment if (ch === '/') { if (stream.eat('*')) { state.tokenize = tokenComment; return state.tokenize(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } // go back one character stream.backUp(1); // update scope info var m = stream.match(/^(\w+)\s*\(/, false); if (m !== null && bodiedOps.hasOwnProperty(m[1])) state.scopes.push('bodied'); var scope = currentScope(state); if (scope === 'bodied' && ch === '[') state.scopes.pop(); if (ch === '[' || ch === '{' || ch === '(') state.scopes.push(ch); scope = currentScope(state); if (scope === '[' && ch === ']' || scope === '{' && ch === '}' || scope === '(' && ch === ')') state.scopes.pop(); if (ch === ';') { while (scope === 'bodied') { state.scopes.pop(); scope = currentScope(state); } } // look for ordered rules if (stream.match(/\d+ *#/, true, false)) { return 'qualifier'; } // look for numbers if (stream.match(reFloatForm, true, false)) { return 'number'; } // look for placeholders if (stream.match(rePattern, true, false)) { return 'variable-3'; } // match all braces separately if (stream.match(/(?:\[|\]|{|}|\(|\))/, true, false)) { return 'bracket'; } // literals looking like function calls if (stream.match(reFunctionLike, true, false)) { stream.backUp(1); return 'variable'; } // all other identifiers if (stream.match(reIdentifier, true, false)) { return 'variable-2'; } // operators; note that operators like @@ or /; are matched separately for each symbol. if (stream.match(/(?:\\|\+|\-|\*|\/|,|;|\.|:|@|~|=|>|<|&|\||_|`|'|\^|\?|!|%|#)/, true, false)) { return 'operator'; } // everything else is an error return 'error'; } function tokenString(stream, state) { var next, end = false, escaped = false; while ((next = stream.next()) != null) { if (next === '"' && !escaped) { end = true; break; } escaped = !escaped && next === '\\'; } if (end && !escaped) { state.tokenize = tokenBase; } return 'string'; }; function tokenComment(stream, state) { var prev, next; while((next = stream.next()) != null) { if (prev === '*' && next === '/') { state.tokenize = tokenBase; break; } prev = next; } return 'comment'; } function currentScope(state) { var scope = null; if (state.scopes.length > 0) scope = state.scopes[state.scopes.length - 1]; return scope; } return { startState: function() { return { tokenize: tokenBase, scopes: [] }; }, token: function(stream, state) { if (stream.eatSpace()) return null; return state.tokenize(stream, state); }, indent: function(state, textAfter) { if (state.tokenize !== tokenBase && state.tokenize !== null) return CodeMirror.Pass; var delta = 0; if (textAfter === ']' || textAfter === '];' || textAfter === '}' || textAfter === '};' || textAfter === ');') delta = -1; return (state.scopes.length + delta) * _config.indentUnit; }, electricChars: "{}[]();", blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//" }; }); CodeMirror.defineMIME('text/x-yacas', { name: 'yacas' }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yaml/index.html ================================================ CodeMirror: YAML mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • YAML

    YAML mode

    MIME types defined: text/x-yaml.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yaml/yaml.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("yaml", function() { var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); return { token: function(stream, state) { var ch = stream.peek(); var esc = state.escaped; state.escaped = false; /* comments */ if (ch == "#" && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) { stream.skipToEnd(); return "comment"; } if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/)) return "string"; if (state.literal && stream.indentation() > state.keyCol) { stream.skipToEnd(); return "string"; } else if (state.literal) { state.literal = false; } if (stream.sol()) { state.keyCol = 0; state.pair = false; state.pairStart = false; /* document start */ if(stream.match('---')) { return "def"; } /* document end */ if (stream.match('...')) { return "def"; } /* array list item */ if (stream.match(/\s*-\s+/)) { return 'meta'; } } /* inline pairs/lists */ if (stream.match(/^(\{|\}|\[|\])/)) { if (ch == '{') state.inlinePairs++; else if (ch == '}') state.inlinePairs--; else if (ch == '[') state.inlineList++; else state.inlineList--; return 'meta'; } /* list separator */ if (state.inlineList > 0 && !esc && ch == ',') { stream.next(); return 'meta'; } /* pairs separator */ if (state.inlinePairs > 0 && !esc && ch == ',') { state.keyCol = 0; state.pair = false; state.pairStart = false; stream.next(); return 'meta'; } /* start of value of a pair */ if (state.pairStart) { /* block literals */ if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; /* references */ if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } /* numbers */ if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } /* keywords */ if (stream.match(keywordRegex)) { return 'keyword'; } } /* pairs (associative arrays) -> key */ if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/)) { state.pair = true; state.keyCol = stream.indentation(); return "atom"; } if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } /* nothing found, continue */ state.pairStart = false; state.escaped = (ch == '\\'); stream.next(); return null; }, startState: function() { return { pair: false, pairStart: false, keyCol: 0, inlinePairs: 0, inlineList: 0, literal: false, escaped: false }; }, lineComment: "#", fold: "indent" }; }); CodeMirror.defineMIME("text/x-yaml", "yaml"); CodeMirror.defineMIME("text/yaml", "yaml"); }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yaml-frontmatter/index.html ================================================ CodeMirror: YAML front matter mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • YAML-Frontmatter

    YAML front matter mode

    Defines a mode that parses a YAML frontmatter at the start of a file, switching to a base mode at the end of that. Takes a mode configuration option base to configure the base mode, which defaults to "gfm".

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function (mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../yaml/yaml")) else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror", "../yaml/yaml"], mod) else // Plain browser env mod(CodeMirror) })(function (CodeMirror) { var START = 0, FRONTMATTER = 1, BODY = 2 // a mixed mode for Markdown text with an optional YAML front matter CodeMirror.defineMode("yaml-frontmatter", function (config, parserConfig) { var yamlMode = CodeMirror.getMode(config, "yaml") var innerMode = CodeMirror.getMode(config, parserConfig && parserConfig.base || "gfm") function localMode(state) { return state.state == FRONTMATTER ? {mode: yamlMode, state: state.yaml} : {mode: innerMode, state: state.inner} } return { startState: function () { return { state: START, yaml: null, inner: CodeMirror.startState(innerMode) } }, copyState: function (state) { return { state: state.state, yaml: state.yaml && CodeMirror.copyState(yamlMode, state.yaml), inner: CodeMirror.copyState(innerMode, state.inner) } }, token: function (stream, state) { if (state.state == START) { if (stream.match('---', false)) { state.state = FRONTMATTER state.yaml = CodeMirror.startState(yamlMode) return yamlMode.token(stream, state.yaml) } else { state.state = BODY return innerMode.token(stream, state.inner) } } else if (state.state == FRONTMATTER) { var end = stream.sol() && stream.match(/(---|\.\.\.)/, false) var style = yamlMode.token(stream, state.yaml) if (end) { state.state = BODY state.yaml = null } return style } else { return innerMode.token(stream, state.inner) } }, innerMode: localMode, indent: function(state, a, b) { var m = localMode(state) return m.mode.indent ? m.mode.indent(m.state, a, b) : CodeMirror.Pass }, blankLine: function (state) { var m = localMode(state) if (m.mode.blankLine) return m.mode.blankLine(m.state) } } }) }); ================================================ FILE: public/assets/lib/vendor/codemirror/mode/z80/index.html ================================================ CodeMirror: Z80 assembly mode

    CodeMirror

    • Home
    • Manual
    • Code
    • Language modes
    • Z80 assembly

    Z80 assembly mode

    MIME types defined: text/x-z80, text/x-ez80.

    ================================================ FILE: public/assets/lib/vendor/codemirror/mode/z80/z80.js ================================================ // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode('z80', function(_config, parserConfig) { var ez80 = parserConfig.ez80; var keywords1, keywords2; if (ez80) { keywords1 = /^(exx?|(ld|cp)([di]r?)?|[lp]ea|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|[de]i|halt|im|in([di]mr?|ir?|irx|2r?)|ot(dmr?|[id]rx|imr?)|out(0?|[di]r?|[di]2r?)|tst(io)?|slp)(\.([sl]?i)?[sl])?\b/i; keywords2 = /^(((call|j[pr]|rst|ret[in]?)(\.([sl]?i)?[sl])?)|(rs|st)mix)\b/i; } else { keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i; keywords2 = /^(call|j[pr]|ret[in]?|b_?(call|jump))\b/i; } var variables1 = /^(af?|bc?|c|de?|e|hl?|l|i[xy]?|r|sp)\b/i; var variables2 = /^(n?[zc]|p[oe]?|m)\b/i; var errors = /^([hl][xy]|i[xy][hl]|slia|sll)\b/i; var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+d?)\b/i; return { startState: function() { return { context: 0 }; }, token: function(stream, state) { if (!stream.column()) state.context = 0; if (stream.eatSpace()) return null; var w; if (stream.eatWhile(/\w/)) { if (ez80 && stream.eat('.')) { stream.eatWhile(/\w/); } w = stream.current(); if (stream.indentation()) { if ((state.context == 1 || state.context == 4) && variables1.test(w)) { state.context = 4; return 'var2'; } if (state.context == 2 && variables2.test(w)) { state.context = 4; return 'var3'; } if (keywords1.test(w)) { state.context = 1; return 'keyword'; } else if (keywords2.test(w)) { state.context = 2; return 'keyword'; } else if (state.context == 4 && numbers.test(w)) { return 'number'; } if (errors.test(w)) return 'error'; } else if (stream.match(numbers)) { return 'number'; } else { return null; } } else if (stream.eat(';')) { stream.skipToEnd(); return 'comment'; } else if (stream.eat('"')) { while (w = stream.next()) { if (w == '"') break; if (w == '\\') stream.next(); } return 'string'; } else if (stream.eat('\'')) { if (stream.match(/\\?.'/)) return 'number'; } else if (stream.eat('.') || stream.sol() && stream.eat('#')) { state.context = 5; if (stream.eatWhile(/\w/)) return 'def'; } else if (stream.eat('$')) { if (stream.eatWhile(/[\da-f]/i)) return 'number'; } else if (stream.eat('%')) { if (stream.eatWhile(/[01]/)) return 'number'; } else { stream.next(); } return null; } }; }); CodeMirror.defineMIME("text/x-z80", "z80"); CodeMirror.defineMIME("text/x-ez80", { name: "z80", ez80: true }); }); ================================================ FILE: public/assets/lib/vendor/codemirror/package.json ================================================ { "name": "codemirror", "version": "5.65.16", "main": "lib/codemirror.js", "style": "lib/codemirror.css", "author": { "name": "Marijn Haverbeke", "email": "marijn@haverbeke.berlin", "url": "http://marijnhaverbeke.nl" }, "description": "Full-featured in-browser code editor", "license": "MIT", "directories": { "lib": "./lib" }, "scripts": { "build": "rollup -c", "watch": "rollup -w -c", "prepare": "npm run-script build", "test": "node ./test/run.js", "lint": "bin/lint" }, "devDependencies": { "@rollup/plugin-buble": "^0.21.3", "blint": "^1.1.2", "cm5-vim": "^0.0.5", "node-static": "0.7.11", "puppeteer": "^1.20.0", "rollup": "^1.26.3", "rollup-plugin-copy": "^3.4.0" }, "bugs": "http://github.com/codemirror/CodeMirror/issues", "keywords": [ "JavaScript", "CodeMirror", "Editor" ], "homepage": "https://codemirror.net/5/", "repository": { "type": "git", "url": "https://github.com/codemirror/CodeMirror.git" }, "jspm": { "directories": {}, "dependencies": {}, "devDependencies": {} }, "dependencies": {}, "publishConfig": { "tag": "version5" } } ================================================ FILE: public/assets/lib/vendor/codemirror/rollup.config.js ================================================ import buble from '@rollup/plugin-buble'; import copy from 'rollup-plugin-copy' let copyVim = copy({ targets: [ { src: require.resolve("cm5-vim/vim.js").replace(/\\/g, "/"), dest: "./keymap" } ] }); export default [ { input: "src/codemirror.js", output: { banner: `// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/5/LICENSE // This is CodeMirror (https://codemirror.net/5), a code editor // implemented in JavaScript on top of the browser's DOM. // // You can find some technical background for some of the code below // at http://marijnhaverbeke.nl/blog/#cm-internals . `, format: "umd", file: "lib/codemirror.js", name: "CodeMirror" }, plugins: [ buble({namedFunctionExpressions: false}), copyVim ] }, { input: ["src/addon/runmode/runmode-standalone.js"], output: { format: "iife", file: "addon/runmode/runmode-standalone.js", name: "CodeMirror", freeze: false, // IE8 doesn't support Object.freeze. }, plugins: [ buble({namedFunctionExpressions: false}) ] }, { input: ["src/addon/runmode/runmode.node.js"], output: { format: "cjs", file: "addon/runmode/runmode.node.js", name: "CodeMirror", freeze: false, // IE8 doesn't support Object.freeze. }, plugins: [ buble({namedFunctionExpressions: false}) ] }, ]; ================================================ FILE: public/assets/lib/vendor/codemirror/src/addon/runmode/codemirror-standalone.js ================================================ import StringStream from "../../util/StringStream.js" import { countColumn } from "../../util/misc.js" import * as modeMethods from "../../modes.js" // declare global: globalThis, CodeMirror // Create a minimal CodeMirror needed to use runMode, and assign to root. var root = typeof globalThis !== 'undefined' ? globalThis : window root.CodeMirror = {} // Copy StringStream and mode methods into CodeMirror object. CodeMirror.StringStream = StringStream for (var exported in modeMethods) CodeMirror[exported] = modeMethods[exported] // Minimal default mode. CodeMirror.defineMode("null", () => ({token: stream => stream.skipToEnd()})) CodeMirror.defineMIME("text/plain", "null") CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min CodeMirror.splitLines = function(string) { return string.split(/\r?\n|\r/) } CodeMirror.countColumn = countColumn CodeMirror.defaults = { indentUnit: 2 } export default CodeMirror ================================================ FILE: public/assets/lib/vendor/codemirror/src/addon/runmode/codemirror.node.js ================================================ import StringStream from "../../util/StringStream.js" import * as modeMethods from "../../modes.js" import {countColumn} from "../../util/misc.js" // Copy StringStream and mode methods into exports (CodeMirror) object. exports.StringStream = StringStream exports.countColumn = countColumn for (var exported in modeMethods) exports[exported] = modeMethods[exported] // Shim library CodeMirror with the minimal CodeMirror defined above. require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")] require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")] // Minimal default mode. exports.defineMode("null", () => ({token: stream => stream.skipToEnd()})) exports.defineMIME("text/plain", "null") exports.registerHelper = exports.registerGlobalHelper = Math.min exports.splitLines = function(string) { return string.split(/\r?\n|\r/) } exports.defaults = { indentUnit: 2 } ================================================ FILE: public/assets/lib/vendor/codemirror/src/addon/runmode/runmode-standalone.js ================================================ import "./codemirror-standalone.js" import "../../../addon/runmode/runmode.js" ================================================ FILE: public/assets/lib/vendor/codemirror/src/addon/runmode/runmode.node.js ================================================ import "./codemirror.node.js" import "../../../addon/runmode/runmode.js" ================================================ FILE: public/assets/lib/vendor/codemirror/src/codemirror.js ================================================ import { CodeMirror } from "./edit/main.js" export default CodeMirror ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/Display.js ================================================ import { gecko, ie, ie_version, mobile, webkit, chrome, chrome_version } from "../util/browser.js" import { elt, eltP } from "../util/dom.js" import { scrollerGap } from "../util/misc.js" import { getGutters, renderGutters } from "./gutters.js" // The display handles the DOM integration, both for input reading // and content drawing. It holds references to DOM nodes and // display-related state. export function Display(place, doc, input, options) { let d = this this.input = input // Covers bottom-right square when both scrollbars are present. d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler") d.scrollbarFiller.setAttribute("cm-not-content", "true") // Covers bottom of gutter when coverGutterNextToScrollbar is on // and h scrollbar is present. d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler") d.gutterFiller.setAttribute("cm-not-content", "true") // Will contain the actual code, positioned to cover the viewport. d.lineDiv = eltP("div", null, "CodeMirror-code") // Elements are added to these to represent selection and cursors. d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1") d.cursorDiv = elt("div", null, "CodeMirror-cursors") // A visibility: hidden element used to find the size of things. d.measure = elt("div", null, "CodeMirror-measure") // When lines outside of the viewport are measured, they are drawn in this. d.lineMeasure = elt("div", null, "CodeMirror-measure") // Wraps everything that needs to exist inside the vertically-padded coordinate system d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], null, "position: relative; outline: none") let lines = eltP("div", [d.lineSpace], "CodeMirror-lines") // Moved around its parent to cover visible view. d.mover = elt("div", [lines], null, "position: relative") // Set to the height of the document, allowing scrolling. d.sizer = elt("div", [d.mover], "CodeMirror-sizer") d.sizerWidth = null // Behavior of elts with overflow: auto and padding is // inconsistent across browsers. This is used to ensure the // scrollable area is big enough. d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;") // Will contain the gutters, if any. d.gutters = elt("div", null, "CodeMirror-gutters") d.lineGutter = null // Actual scrollable element. d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll") d.scroller.setAttribute("tabIndex", "-1") // The element in which the editor lives. d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror") // See #6982. FIXME remove when this has been fixed for a while in Chrome if (chrome && chrome_version >= 105) d.wrapper.style.clipPath = "inset(0px)" // This attribute is respected by automatic translation systems such as Google Translate, // and may also be respected by tools used by human translators. d.wrapper.setAttribute('translate', 'no') // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0 } if (!webkit && !(gecko && mobile)) d.scroller.draggable = true if (place) { if (place.appendChild) place.appendChild(d.wrapper) else place(d.wrapper) } // Current rendered range (may be bigger than the view window). d.viewFrom = d.viewTo = doc.first d.reportedViewFrom = d.reportedViewTo = doc.first // Information about the rendered lines. d.view = [] d.renderedView = null // Holds info about a single rendered line when it was rendered // for measurement, while not in view. d.externalMeasured = null // Empty space (in pixels) above the view d.viewOffset = 0 d.lastWrapHeight = d.lastWrapWidth = 0 d.updateLineNumbers = null d.nativeBarWidth = d.barHeight = d.barWidth = 0 d.scrollbarsClipped = false // Used to only resize the line number gutter when necessary (when // the amount of lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null // Set to true when a non-horizontal-scrolling line widget is // added. As an optimization, line widget aligning is skipped when // this is false. d.alignWidgets = false d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. d.maxLine = null d.maxLineLength = 0 d.maxLineChanged = false // Used for measuring wheel scrolling granularity d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null // True when shift is held down. d.shift = false // Used to track whether anything happened since the context menu // was opened. d.selForContextMenu = null d.activeTouch = null d.gutterSpecs = getGutters(options.gutters, options.lineNumbers) renderGutters(d) input.init(d) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/focus.js ================================================ import { restartBlink } from "./selection.js" import { webkit } from "../util/browser.js" import { addClass, rmClass } from "../util/dom.js" import { signal } from "../util/event.js" export function ensureFocus(cm) { if (!cm.hasFocus()) { cm.display.input.focus() if (!cm.state.focused) onFocus(cm) } } export function delayBlurEvent(cm) { cm.state.delayingBlurEvent = true setTimeout(() => { if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false if (cm.state.focused) onBlur(cm) } }, 100) } export function onFocus(cm, e) { if (cm.state.delayingBlurEvent && !cm.state.draggingText) cm.state.delayingBlurEvent = false if (cm.options.readOnly == "nocursor") return if (!cm.state.focused) { signal(cm, "focus", cm, e) cm.state.focused = true addClass(cm.display.wrapper, "CodeMirror-focused") // This test prevents this from firing when a context // menu is closed (since the input reset would kill the // select-all detection hack) if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { cm.display.input.reset() if (webkit) setTimeout(() => cm.display.input.reset(true), 20) // Issue #1730 } cm.display.input.receivedFocus() } restartBlink(cm) } export function onBlur(cm, e) { if (cm.state.delayingBlurEvent) return if (cm.state.focused) { signal(cm, "blur", cm, e) cm.state.focused = false rmClass(cm.display.wrapper, "CodeMirror-focused") } clearInterval(cm.display.blinker) setTimeout(() => { if (!cm.state.focused) cm.display.shift = false }, 150) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/gutters.js ================================================ import { elt, removeChildren } from "../util/dom.js" import { regChange } from "./view_tracking.js" import { alignHorizontally } from "./line_numbers.js" import { updateGutterSpace } from "./update_display.js" export function getGutters(gutters, lineNumbers) { let result = [], sawLineNumbers = false for (let i = 0; i < gutters.length; i++) { let name = gutters[i], style = null if (typeof name != "string") { style = name.style; name = name.className } if (name == "CodeMirror-linenumbers") { if (!lineNumbers) continue else sawLineNumbers = true } result.push({className: name, style}) } if (lineNumbers && !sawLineNumbers) result.push({className: "CodeMirror-linenumbers", style: null}) return result } // Rebuild the gutter elements, ensure the margin to the left of the // code matches their width. export function renderGutters(display) { let gutters = display.gutters, specs = display.gutterSpecs removeChildren(gutters) display.lineGutter = null for (let i = 0; i < specs.length; ++i) { let {className, style} = specs[i] let gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)) if (style) gElt.style.cssText = style if (className == "CodeMirror-linenumbers") { display.lineGutter = gElt gElt.style.width = (display.lineNumWidth || 1) + "px" } } gutters.style.display = specs.length ? "" : "none" updateGutterSpace(display) } export function updateGutters(cm) { renderGutters(cm.display) regChange(cm) alignHorizontally(cm) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/highlight_worker.js ================================================ import { getContextBefore, highlightLine, processLine } from "../line/highlight.js" import { copyState } from "../modes.js" import { bind } from "../util/misc.js" import { runInOp } from "./operations.js" import { regLineChange } from "./view_tracking.js" // HIGHLIGHT WORKER export function startWorker(cm, time) { if (cm.doc.highlightFrontier < cm.display.viewTo) cm.state.highlight.set(time, bind(highlightWorker, cm)) } function highlightWorker(cm) { let doc = cm.doc if (doc.highlightFrontier >= cm.display.viewTo) return let end = +new Date + cm.options.workTime let context = getContextBefore(cm, doc.highlightFrontier) let changedLines = [] doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => { if (context.line >= cm.display.viewFrom) { // Visible let oldStyles = line.styles let resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null let highlighted = highlightLine(cm, line, context, true) if (resetState) context.state = resetState line.styles = highlighted.styles let oldCls = line.styleClasses, newCls = highlighted.classes if (newCls) line.styleClasses = newCls else if (oldCls) line.styleClasses = null let ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) for (let i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i] if (ischange) changedLines.push(context.line) line.stateAfter = context.save() context.nextLine() } else { if (line.text.length <= cm.options.maxHighlightLength) processLine(cm, line.text, context) line.stateAfter = context.line % 5 == 0 ? context.save() : null context.nextLine() } if (+new Date > end) { startWorker(cm, cm.options.workDelay) return true } }) doc.highlightFrontier = context.line doc.modeFrontier = Math.max(doc.modeFrontier, context.line) if (changedLines.length) runInOp(cm, () => { for (let i = 0; i < changedLines.length; i++) regLineChange(cm, changedLines[i], "text") }) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/line_numbers.js ================================================ import { lineNumberFor } from "../line/utils_line.js" import { compensateForHScroll } from "../measurement/position_measurement.js" import { elt } from "../util/dom.js" import { updateGutterSpace } from "./update_display.js" // Re-align line numbers and gutter marks to compensate for // horizontal scrolling. export function alignHorizontally(cm) { let display = cm.display, view = display.view if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return let comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft let gutterW = display.gutters.offsetWidth, left = comp + "px" for (let i = 0; i < view.length; i++) if (!view[i].hidden) { if (cm.options.fixedGutter) { if (view[i].gutter) view[i].gutter.style.left = left if (view[i].gutterBackground) view[i].gutterBackground.style.left = left } let align = view[i].alignable if (align) for (let j = 0; j < align.length; j++) align[j].style.left = left } if (cm.options.fixedGutter) display.gutters.style.left = (comp + gutterW) + "px" } // Used to ensure that the line number gutter is still the right // size for the current document size. Returns true when an update // is needed. export function maybeUpdateLineNumberWidth(cm) { if (!cm.options.lineNumbers) return false let doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display if (last.length != display.lineNumChars) { let test = display.measure.appendChild(elt("div", [elt("div", last)], "CodeMirror-linenumber CodeMirror-gutter-elt")) let innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW display.lineGutter.style.width = "" display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1 display.lineNumWidth = display.lineNumInnerWidth + padding display.lineNumChars = display.lineNumInnerWidth ? last.length : -1 display.lineGutter.style.width = display.lineNumWidth + "px" updateGutterSpace(cm.display) return true } return false } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/mode_state.js ================================================ import { getMode } from "../modes.js" import { startWorker } from "./highlight_worker.js" import { regChange } from "./view_tracking.js" // Used to get the editor into a consistent state again when options change. export function loadMode(cm) { cm.doc.mode = getMode(cm.options, cm.doc.modeOption) resetModeState(cm) } export function resetModeState(cm) { cm.doc.iter(line => { if (line.stateAfter) line.stateAfter = null if (line.styles) line.styles = null }) cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first startWorker(cm, 100) cm.state.modeGen++ if (cm.curOp) regChange(cm) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/operations.js ================================================ import { clipPos } from "../line/pos.js" import { findMaxLine } from "../line/spans.js" import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js" import { signal } from "../util/event.js" import { activeElt, root } from "../util/dom.js" import { finishOperation, pushOperation } from "../util/operation_group.js" import { ensureFocus } from "./focus.js" import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" import { restartBlink } from "./selection.js" import { maybeScrollWindow, scrollPosIntoView, setScrollLeft, setScrollTop } from "./scrolling.js" import { DisplayUpdate, maybeClipScrollbars, postUpdateDisplay, setDocumentHeight, updateDisplayIfNeeded } from "./update_display.js" import { updateHeightsInViewport } from "./update_lines.js" // Operations are used to wrap a series of changes to the editor // state in such a way that each change won't have to update the // cursor and display (which would be awkward, slow, and // error-prone). Instead, display updates are batched and then all // combined and executed at once. let nextOpId = 0 // Start a new operation. export function startOperation(cm) { cm.curOp = { cm: cm, viewChanged: false, // Flag that indicates that lines might need to be redrawn startHeight: cm.doc.height, // Used to detect need to update scrollbar forceUpdate: false, // Used to force a redraw updateInput: 0, // Whether to reset the input textarea typing: false, // Whether this reset should be careful to leave existing text (for compositing) changeObjs: null, // Accumulated changes, for firing change events cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already selectionChanged: false, // Whether the selection needs to be redrawn updateMaxLine: false, // Set when the widest line needs to be determined anew scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet scrollToPos: null, // Used to scroll to a specific position focus: false, id: ++nextOpId, // Unique ID markArrays: null // Used by addMarkedSpan } pushOperation(cm.curOp) } // Finish an operation, updating the display and signalling delayed events export function endOperation(cm) { let op = cm.curOp if (op) finishOperation(op, group => { for (let i = 0; i < group.ops.length; i++) group.ops[i].cm.curOp = null endOperations(group) }) } // The DOM updates done when an operation finishes are batched so // that the minimum number of relayouts are required. function endOperations(group) { let ops = group.ops for (let i = 0; i < ops.length; i++) // Read DOM endOperation_R1(ops[i]) for (let i = 0; i < ops.length; i++) // Write DOM (maybe) endOperation_W1(ops[i]) for (let i = 0; i < ops.length; i++) // Read DOM endOperation_R2(ops[i]) for (let i = 0; i < ops.length; i++) // Write DOM (maybe) endOperation_W2(ops[i]) for (let i = 0; i < ops.length; i++) // Read DOM endOperation_finish(ops[i]) } function endOperation_R1(op) { let cm = op.cm, display = cm.display maybeClipScrollbars(cm) if (op.updateMaxLine) findMaxLine(cm) op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || op.scrollToPos.to.line >= display.viewTo) || display.maxLineChanged && cm.options.lineWrapping op.update = op.mustUpdate && new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate) } function endOperation_W1(op) { op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update) } function endOperation_R2(op) { let cm = op.cm, display = cm.display if (op.updatedDisplay) updateHeightsInViewport(cm) op.barMeasure = measureForScrollbars(cm) // If the max line changed since it was last measured, measure it, // and ensure the document's width matches it. // updateDisplay_W2 will use these properties to do the actual resizing if (display.maxLineChanged && !cm.options.lineWrapping) { op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3 cm.display.sizerWidth = op.adjustWidthTo op.barMeasure.scrollWidth = Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth) op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)) } if (op.updatedDisplay || op.selectionChanged) op.preparedSelection = display.input.prepareSelection() } function endOperation_W2(op) { let cm = op.cm if (op.adjustWidthTo != null) { cm.display.sizer.style.minWidth = op.adjustWidthTo + "px" if (op.maxScrollLeft < cm.doc.scrollLeft) setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true) cm.display.maxLineChanged = false } let takeFocus = op.focus && op.focus == activeElt(root(cm)) if (op.preparedSelection) cm.display.input.showSelection(op.preparedSelection, takeFocus) if (op.updatedDisplay || op.startHeight != cm.doc.height) updateScrollbars(cm, op.barMeasure) if (op.updatedDisplay) setDocumentHeight(cm, op.barMeasure) if (op.selectionChanged) restartBlink(cm) if (cm.state.focused && op.updateInput) cm.display.input.reset(op.typing) if (takeFocus) ensureFocus(op.cm) } function endOperation_finish(op) { let cm = op.cm, display = cm.display, doc = cm.doc if (op.updatedDisplay) postUpdateDisplay(cm, op.update) // Abort mouse wheel delta measurement, when scrolling explicitly if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) display.wheelStartX = display.wheelStartY = null // Propagate the scroll position to the actual DOM scroller if (op.scrollTop != null) setScrollTop(cm, op.scrollTop, op.forceScroll) if (op.scrollLeft != null) setScrollLeft(cm, op.scrollLeft, true, true) // If we need to scroll a specific position into view, do so. if (op.scrollToPos) { let rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin) maybeScrollWindow(cm, rect) } // Fire events for markers that are hidden/unidden by editing or // undoing let hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers if (hidden) for (let i = 0; i < hidden.length; ++i) if (!hidden[i].lines.length) signal(hidden[i], "hide") if (unhidden) for (let i = 0; i < unhidden.length; ++i) if (unhidden[i].lines.length) signal(unhidden[i], "unhide") if (display.wrapper.offsetHeight) doc.scrollTop = cm.display.scroller.scrollTop // Fire change events, and delayed event handlers if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs) if (op.update) op.update.finish() } // Run the given function in an operation export function runInOp(cm, f) { if (cm.curOp) return f() startOperation(cm) try { return f() } finally { endOperation(cm) } } // Wraps a function in an operation. Returns the wrapped function. export function operation(cm, f) { return function() { if (cm.curOp) return f.apply(cm, arguments) startOperation(cm) try { return f.apply(cm, arguments) } finally { endOperation(cm) } } } // Used to add methods to editor and doc instances, wrapping them in // operations. export function methodOp(f) { return function() { if (this.curOp) return f.apply(this, arguments) startOperation(this) try { return f.apply(this, arguments) } finally { endOperation(this) } } } export function docMethodOp(f) { return function() { let cm = this.cm if (!cm || cm.curOp) return f.apply(this, arguments) startOperation(cm) try { return f.apply(this, arguments) } finally { endOperation(cm) } } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/scroll_events.js ================================================ import { chrome, chrome_version, gecko, ie, mac, presto, safari, webkit } from "../util/browser.js" import { e_preventDefault } from "../util/event.js" import { updateDisplaySimple } from "./update_display.js" import { setScrollLeft, updateScrollTop } from "./scrolling.js" // Since the delta values reported on mouse wheel events are // unstandardized between browsers and even browser versions, and // generally horribly unpredictable, this code starts by measuring // the scroll effect that the first few mouse wheel events have, // and, from that, detects the way it can convert deltas to pixel // offsets afterwards. // // The reason we want to know the amount a wheel event will scroll // is that it gives us a chance to update the display before the // actual scrolling happens, reducing flickering. let wheelSamples = 0, wheelPixelsPerUnit = null // Fill in a browser-detected starting value on browsers where we // know one. These don't have to be accurate -- the result of them // being wrong would just be a slight flicker on the first wheel // scroll (if it is large enough). if (ie) wheelPixelsPerUnit = -.53 else if (gecko) wheelPixelsPerUnit = 15 else if (chrome) wheelPixelsPerUnit = -.7 else if (safari) wheelPixelsPerUnit = -1/3 function wheelEventDelta(e) { let dx = e.wheelDeltaX, dy = e.wheelDeltaY if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail else if (dy == null) dy = e.wheelDelta return {x: dx, y: dy} } export function wheelEventPixels(e) { let delta = wheelEventDelta(e) delta.x *= wheelPixelsPerUnit delta.y *= wheelPixelsPerUnit return delta } export function onScrollWheel(cm, e) { // On Chrome 102, viewport updates somehow stop wheel-based // scrolling. Turning off pointer events during the scroll seems // to avoid the issue. if (chrome && chrome_version == 102) { if (cm.display.chromeScrollHack == null) cm.display.sizer.style.pointerEvents = "none" else clearTimeout(cm.display.chromeScrollHack) cm.display.chromeScrollHack = setTimeout(() => { cm.display.chromeScrollHack = null cm.display.sizer.style.pointerEvents = "" }, 100) } let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y let pixelsPerUnit = wheelPixelsPerUnit if (e.deltaMode === 0) { dx = e.deltaX dy = e.deltaY pixelsPerUnit = 1 } let display = cm.display, scroll = display.scroller // Quit if there's nothing to scroll here let canScrollX = scroll.scrollWidth > scroll.clientWidth let canScrollY = scroll.scrollHeight > scroll.clientHeight if (!(dx && canScrollX || dy && canScrollY)) return // Webkit browsers on OS X abort momentum scrolls when the target // of the scroll event is removed from the scrollable element. // This hack (see related code in patchDisplay) makes sure the // element is kept around. if (dy && mac && webkit) { outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { for (let i = 0; i < view.length; i++) { if (view[i].node == cur) { cm.display.currentWheelTarget = cur break outer } } } } // On some browsers, horizontal scrolling will cause redraws to // happen before the gutter has been realigned, causing it to // wriggle around in a most unseemly way. When we have an // estimated pixels/delta value, we just handle horizontal // scrolling entirely here. It'll be slightly off from native, but // better than glitching out. if (dx && !gecko && !presto && pixelsPerUnit != null) { if (dy && canScrollY) updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)) setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit)) // Only prevent default scrolling if vertical scrolling is // actually possible. Otherwise, it causes vertical scroll // jitter on OSX trackpads when deltaX is small and deltaY // is large (issue #3579) if (!dy || (dy && canScrollY)) e_preventDefault(e) display.wheelStartX = null // Abort measurement, if in progress return } // 'Project' the visible viewport to cover the area that is being // scrolled into view (if we know enough to estimate it). if (dy && pixelsPerUnit != null) { let pixels = dy * pixelsPerUnit let top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight if (pixels < 0) top = Math.max(0, top + pixels - 50) else bot = Math.min(cm.doc.height, bot + pixels + 50) updateDisplaySimple(cm, {top: top, bottom: bot}) } if (wheelSamples < 20 && e.deltaMode !== 0) { if (display.wheelStartX == null) { display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop display.wheelDX = dx; display.wheelDY = dy setTimeout(() => { if (display.wheelStartX == null) return let movedX = scroll.scrollLeft - display.wheelStartX let movedY = scroll.scrollTop - display.wheelStartY let sample = (movedY && display.wheelDY && movedY / display.wheelDY) || (movedX && display.wheelDX && movedX / display.wheelDX) display.wheelStartX = display.wheelStartY = null if (!sample) return wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1) ++wheelSamples }, 200) } else { display.wheelDX += dx; display.wheelDY += dy } } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/scrollbars.js ================================================ import { addClass, elt, rmClass } from "../util/dom.js" import { on } from "../util/event.js" import { scrollGap, paddingVert } from "../measurement/position_measurement.js" import { ie, ie_version, mac, mac_geMountainLion } from "../util/browser.js" import { updateHeightsInViewport } from "./update_lines.js" import { Delayed } from "../util/misc.js" import { setScrollLeft, updateScrollTop } from "./scrolling.js" // SCROLLBARS // Prepare DOM reads needed to update the scrollbars. Done in one // shot to minimize update/measure roundtrips. export function measureForScrollbars(cm) { let d = cm.display, gutterW = d.gutters.offsetWidth let docH = Math.round(cm.doc.height + paddingVert(cm.display)) return { clientHeight: d.scroller.clientHeight, viewHeight: d.wrapper.clientHeight, scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, viewWidth: d.wrapper.clientWidth, barLeft: cm.options.fixedGutter ? gutterW : 0, docHeight: docH, scrollHeight: docH + scrollGap(cm) + d.barHeight, nativeBarWidth: d.nativeBarWidth, gutterWidth: gutterW } } class NativeScrollbars { constructor(place, scroll, cm) { this.cm = cm let vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") let horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") vert.tabIndex = horiz.tabIndex = -1 place(vert); place(horiz) on(vert, "scroll", () => { if (vert.clientHeight) scroll(vert.scrollTop, "vertical") }) on(horiz, "scroll", () => { if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal") }) this.checkedZeroWidth = false // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px" } update(measure) { let needsH = measure.scrollWidth > measure.clientWidth + 1 let needsV = measure.scrollHeight > measure.clientHeight + 1 let sWidth = measure.nativeBarWidth if (needsV) { this.vert.style.display = "block" this.vert.style.bottom = needsH ? sWidth + "px" : "0" let totalHeight = measure.viewHeight - (needsH ? sWidth : 0) // A bug in IE8 can cause this value to be negative, so guard it. this.vert.firstChild.style.height = Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" } else { this.vert.scrollTop = 0 this.vert.style.display = "" this.vert.firstChild.style.height = "0" } if (needsH) { this.horiz.style.display = "block" this.horiz.style.right = needsV ? sWidth + "px" : "0" this.horiz.style.left = measure.barLeft + "px" let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) this.horiz.firstChild.style.width = Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" } else { this.horiz.style.display = "" this.horiz.firstChild.style.width = "0" } if (!this.checkedZeroWidth && measure.clientHeight > 0) { if (sWidth == 0) this.zeroWidthHack() this.checkedZeroWidth = true } return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} } setScrollLeft(pos) { if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") } setScrollTop(pos) { if (this.vert.scrollTop != pos) this.vert.scrollTop = pos if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert, "vert") } zeroWidthHack() { let w = mac && !mac_geMountainLion ? "12px" : "18px" this.horiz.style.height = this.vert.style.width = w this.horiz.style.visibility = this.vert.style.visibility = "hidden" this.disableHoriz = new Delayed this.disableVert = new Delayed } enableZeroWidthBar(bar, delay, type) { bar.style.visibility = "" function maybeDisable() { // To find out whether the scrollbar is still visible, we // check whether the element under the pixel in the bottom // right corner of the scrollbar box is the scrollbar box // itself (when the bar is still visible) or its filler child // (when the bar is hidden). If it is still visible, we keep // it enabled, if it's hidden, we disable pointer events. let box = bar.getBoundingClientRect() let elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) if (elt != bar) bar.style.visibility = "hidden" else delay.set(1000, maybeDisable) } delay.set(1000, maybeDisable) } clear() { let parent = this.horiz.parentNode parent.removeChild(this.horiz) parent.removeChild(this.vert) } } class NullScrollbars { update() { return {bottom: 0, right: 0} } setScrollLeft() {} setScrollTop() {} clear() {} } export function updateScrollbars(cm, measure) { if (!measure) measure = measureForScrollbars(cm) let startWidth = cm.display.barWidth, startHeight = cm.display.barHeight updateScrollbarsInner(cm, measure) for (let i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { if (startWidth != cm.display.barWidth && cm.options.lineWrapping) updateHeightsInViewport(cm) updateScrollbarsInner(cm, measureForScrollbars(cm)) startWidth = cm.display.barWidth; startHeight = cm.display.barHeight } } // Re-synchronize the fake scrollbars with the actual size of the // content. function updateScrollbarsInner(cm, measure) { let d = cm.display let sizes = d.scrollbars.update(measure) d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" if (sizes.right && sizes.bottom) { d.scrollbarFiller.style.display = "block" d.scrollbarFiller.style.height = sizes.bottom + "px" d.scrollbarFiller.style.width = sizes.right + "px" } else d.scrollbarFiller.style.display = "" if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { d.gutterFiller.style.display = "block" d.gutterFiller.style.height = sizes.bottom + "px" d.gutterFiller.style.width = measure.gutterWidth + "px" } else d.gutterFiller.style.display = "" } export let scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} export function initScrollbars(cm) { if (cm.display.scrollbars) { cm.display.scrollbars.clear() if (cm.display.scrollbars.addClass) rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) } cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](node => { cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) // Prevent clicks in the scrollbars from killing focus on(node, "mousedown", () => { if (cm.state.focused) setTimeout(() => cm.display.input.focus(), 0) }) node.setAttribute("cm-not-content", "true") }, (pos, axis) => { if (axis == "horizontal") setScrollLeft(cm, pos) else updateScrollTop(cm, pos) }, cm) if (cm.display.scrollbars.addClass) addClass(cm.display.wrapper, cm.display.scrollbars.addClass) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/scrolling.js ================================================ import { Pos } from "../line/pos.js" import { cursorCoords, displayHeight, displayWidth, estimateCoords, paddingTop, paddingVert, scrollGap, textHeight } from "../measurement/position_measurement.js" import { gecko, phantom } from "../util/browser.js" import { elt } from "../util/dom.js" import { signalDOMEvent } from "../util/event.js" import { startWorker } from "./highlight_worker.js" import { alignHorizontally } from "./line_numbers.js" import { updateDisplaySimple } from "./update_display.js" // SCROLLING THINGS INTO VIEW // If an editor sits on the top or bottom of the window, partially // scrolled out of view, this ensures that the cursor is visible. export function maybeScrollWindow(cm, rect) { if (signalDOMEvent(cm, "scrollCursorIntoView")) return let display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null let doc = display.wrapper.ownerDocument if (rect.top + box.top < 0) doScroll = true else if (rect.bottom + box.top > (doc.defaultView.innerHeight || doc.documentElement.clientHeight)) doScroll = false if (doScroll != null && !phantom) { let scrollNode = elt("div", "\u200b", null, `position: absolute; top: ${rect.top - display.viewOffset - paddingTop(cm.display)}px; height: ${rect.bottom - rect.top + scrollGap(cm) + display.barHeight}px; left: ${rect.left}px; width: ${Math.max(2, rect.right - rect.left)}px;`) cm.display.lineSpace.appendChild(scrollNode) scrollNode.scrollIntoView(doScroll) cm.display.lineSpace.removeChild(scrollNode) } } // Scroll a given position into view (immediately), verifying that // it actually became visible (as line heights are accurately // measured, the position of something may 'drift' during drawing). export function scrollPosIntoView(cm, pos, end, margin) { if (margin == null) margin = 0 let rect if (!cm.options.lineWrapping && pos == end) { // Set pos and end to the cursor positions around the character pos sticks to // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch // If pos == Pos(_, 0, "before"), pos and end are unchanged end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos } for (let limit = 0; limit < 5; limit++) { let changed = false let coords = cursorCoords(cm, pos) let endCoords = !end || end == pos ? coords : cursorCoords(cm, end) rect = {left: Math.min(coords.left, endCoords.left), top: Math.min(coords.top, endCoords.top) - margin, right: Math.max(coords.left, endCoords.left), bottom: Math.max(coords.bottom, endCoords.bottom) + margin} let scrollPos = calculateScrollPos(cm, rect) let startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop) if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true } if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft) if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true } if (!changed) break } return rect } // Scroll a given set of coordinates into view (immediately). export function scrollIntoView(cm, rect) { let scrollPos = calculateScrollPos(cm, rect) if (scrollPos.scrollTop != null) updateScrollTop(cm, scrollPos.scrollTop) if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft) } // Calculate a new scroll position needed to scroll the given // rectangle into view. Returns an object with scrollTop and // scrollLeft properties. When these are undefined, the // vertical/horizontal position does not need to be adjusted. function calculateScrollPos(cm, rect) { let display = cm.display, snapMargin = textHeight(cm.display) if (rect.top < 0) rect.top = 0 let screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop let screen = displayHeight(cm), result = {} if (rect.bottom - rect.top > screen) rect.bottom = rect.top + screen let docBottom = cm.doc.height + paddingVert(display) let atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin if (rect.top < screentop) { result.scrollTop = atTop ? 0 : rect.top } else if (rect.bottom > screentop + screen) { let newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen) if (newTop != screentop) result.scrollTop = newTop } let gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth let screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace let screenw = displayWidth(cm) - display.gutters.offsetWidth let tooWide = rect.right - rect.left > screenw if (tooWide) rect.right = rect.left + screenw if (rect.left < 10) result.scrollLeft = 0 else if (rect.left < screenleft) result.scrollLeft = Math.max(0, rect.left + gutterSpace - (tooWide ? 0 : 10)) else if (rect.right > screenw + screenleft - 3) result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw return result } // Store a relative adjustment to the scroll position in the current // operation (to be applied when the operation finishes). export function addToScrollTop(cm, top) { if (top == null) return resolveScrollToPos(cm) cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top } // Make sure that at the end of the operation the current cursor is // shown. export function ensureCursorVisible(cm) { resolveScrollToPos(cm) let cur = cm.getCursor() cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin} } export function scrollToCoords(cm, x, y) { if (x != null || y != null) resolveScrollToPos(cm) if (x != null) cm.curOp.scrollLeft = x if (y != null) cm.curOp.scrollTop = y } export function scrollToRange(cm, range) { resolveScrollToPos(cm) cm.curOp.scrollToPos = range } // When an operation has its scrollToPos property set, and another // scroll action is applied before the end of the operation, this // 'simulates' scrolling that position into view in a cheap way, so // that the effect of intermediate scroll commands is not ignored. function resolveScrollToPos(cm) { let range = cm.curOp.scrollToPos if (range) { cm.curOp.scrollToPos = null let from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to) scrollToCoordsRange(cm, from, to, range.margin) } } export function scrollToCoordsRange(cm, from, to, margin) { let sPos = calculateScrollPos(cm, { left: Math.min(from.left, to.left), top: Math.min(from.top, to.top) - margin, right: Math.max(from.right, to.right), bottom: Math.max(from.bottom, to.bottom) + margin }) scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop) } // Sync the scrollable area and scrollbars, ensure the viewport // covers the visible area. export function updateScrollTop(cm, val) { if (Math.abs(cm.doc.scrollTop - val) < 2) return if (!gecko) updateDisplaySimple(cm, {top: val}) setScrollTop(cm, val, true) if (gecko) updateDisplaySimple(cm) startWorker(cm, 100) } export function setScrollTop(cm, val, forceScroll) { val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val)) if (cm.display.scroller.scrollTop == val && !forceScroll) return cm.doc.scrollTop = val cm.display.scrollbars.setScrollTop(val) if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val } // Sync scroller and scrollbar, ensure the gutter elements are // aligned. export function setScrollLeft(cm, val, isScroller, forceScroll) { val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth)) if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) return cm.doc.scrollLeft = val alignHorizontally(cm) if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val cm.display.scrollbars.setScrollLeft(val) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/selection.js ================================================ import { Pos } from "../line/pos.js" import { visualLine } from "../line/spans.js" import { getLine } from "../line/utils_line.js" import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement.js" import { getOrder, iterateBidiSections } from "../util/bidi.js" import { elt } from "../util/dom.js" import { onBlur } from "./focus.js" export function updateSelection(cm) { cm.display.input.showSelection(cm.display.input.prepareSelection()) } export function prepareSelection(cm, primary = true) { let doc = cm.doc, result = {} let curFragment = result.cursors = document.createDocumentFragment() let selFragment = result.selection = document.createDocumentFragment() let customCursor = cm.options.$customCursor if (customCursor) primary = true for (let i = 0; i < doc.sel.ranges.length; i++) { if (!primary && i == doc.sel.primIndex) continue let range = doc.sel.ranges[i] if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue let collapsed = range.empty() if (customCursor) { let head = customCursor(cm, range) if (head) drawSelectionCursor(cm, head, curFragment) } else if (collapsed || cm.options.showCursorWhenSelecting) { drawSelectionCursor(cm, range.head, curFragment) } if (!collapsed) drawSelectionRange(cm, range, selFragment) } return result } // Draws a cursor for the given range export function drawSelectionCursor(cm, head, output) { let pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) let cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) cursor.style.left = pos.left + "px" cursor.style.top = pos.top + "px" cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) { let charPos = charCoords(cm, head, "div", null, null) let width = charPos.right - charPos.left cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px" } if (pos.other) { // Secondary cursor, shown when on a 'jump' in bi-directional text let otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) otherCursor.style.display = "" otherCursor.style.left = pos.other.left + "px" otherCursor.style.top = pos.other.top + "px" otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" } } function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } // Draws the given range as a highlighted selection function drawSelectionRange(cm, range, output) { let display = cm.display, doc = cm.doc let fragment = document.createDocumentFragment() let padding = paddingH(cm.display), leftSide = padding.left let rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right let docLTR = doc.direction == "ltr" function add(left, top, width, bottom) { if (top < 0) top = 0 top = Math.round(top) bottom = Math.round(bottom) fragment.appendChild(elt("div", null, "CodeMirror-selected", `position: absolute; left: ${left}px; top: ${top}px; width: ${width == null ? rightSide - left : width}px; height: ${bottom - top}px`)) } function drawForLine(line, fromArg, toArg) { let lineObj = getLine(doc, line) let lineLen = lineObj.text.length let start, end function coords(ch, bias) { return charCoords(cm, Pos(line, ch), "div", lineObj, bias) } function wrapX(pos, dir, side) { let extent = wrappedLineExtentChar(cm, lineObj, null, pos) let prop = (dir == "ltr") == (side == "after") ? "left" : "right" let ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) return coords(ch, prop)[prop] } let order = getOrder(lineObj, doc.direction) iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => { let ltr = dir == "ltr" let fromPos = coords(from, ltr ? "left" : "right") let toPos = coords(to - 1, ltr ? "right" : "left") let openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen let first = i == 0, last = !order || i == order.length - 1 if (toPos.top - fromPos.top <= 3) { // Single line let openLeft = (docLTR ? openStart : openEnd) && first let openRight = (docLTR ? openEnd : openStart) && last let left = openLeft ? leftSide : (ltr ? fromPos : toPos).left let right = openRight ? rightSide : (ltr ? toPos : fromPos).right add(left, fromPos.top, right - left, fromPos.bottom) } else { // Multiple lines let topLeft, topRight, botLeft, botRight if (ltr) { topLeft = docLTR && openStart && first ? leftSide : fromPos.left topRight = docLTR ? rightSide : wrapX(from, dir, "before") botLeft = docLTR ? leftSide : wrapX(to, dir, "after") botRight = docLTR && openEnd && last ? rightSide : toPos.right } else { topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") topRight = !docLTR && openStart && first ? rightSide : fromPos.right botLeft = !docLTR && openEnd && last ? leftSide : toPos.left botRight = !docLTR ? rightSide : wrapX(to, dir, "after") } add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) add(botLeft, toPos.top, botRight - botLeft, toPos.bottom) } if (!start || cmpCoords(fromPos, start) < 0) start = fromPos if (cmpCoords(toPos, start) < 0) start = toPos if (!end || cmpCoords(fromPos, end) < 0) end = fromPos if (cmpCoords(toPos, end) < 0) end = toPos }) return {start: start, end: end} } let sFrom = range.from(), sTo = range.to() if (sFrom.line == sTo.line) { drawForLine(sFrom.line, sFrom.ch, sTo.ch) } else { let fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) let singleVLine = visualLine(fromLine) == visualLine(toLine) let leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end let rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start if (singleVLine) { if (leftEnd.top < rightStart.top - 2) { add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) } else { add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) } } if (leftEnd.bottom < rightStart.top) add(leftSide, leftEnd.bottom, null, rightStart.top) } output.appendChild(fragment) } // Cursor-blinking export function restartBlink(cm) { if (!cm.state.focused) return let display = cm.display clearInterval(display.blinker) let on = true display.cursorDiv.style.visibility = "" if (cm.options.cursorBlinkRate > 0) display.blinker = setInterval(() => { if (!cm.hasFocus()) onBlur(cm) display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden" }, cm.options.cursorBlinkRate) else if (cm.options.cursorBlinkRate < 0) display.cursorDiv.style.visibility = "hidden" } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/update_display.js ================================================ import { sawCollapsedSpans } from "../line/saw_special_spans.js" import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js" import { getLine, lineNumberFor } from "../line/utils_line.js" import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js" import { mac, webkit } from "../util/browser.js" import { activeElt, removeChildren, contains, win, root, rootNode } from "../util/dom.js" import { hasHandler, signal } from "../util/event.js" import { signalLater } from "../util/operation_group.js" import { indexOf } from "../util/misc.js" import { buildLineElement, updateLineForChanges } from "./update_line.js" import { startWorker } from "./highlight_worker.js" import { maybeUpdateLineNumberWidth } from "./line_numbers.js" import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" import { updateSelection } from "./selection.js" import { updateHeightsInViewport, visibleLines } from "./update_lines.js" import { adjustView, countDirtyView, resetView } from "./view_tracking.js" // DISPLAY DRAWING export class DisplayUpdate { constructor(cm, viewport, force) { let display = cm.display this.viewport = viewport // Store some values that we'll need later (but don't want to force a relayout for) this.visible = visibleLines(display, cm.doc, viewport) this.editorIsHidden = !display.wrapper.offsetWidth this.wrapperHeight = display.wrapper.clientHeight this.wrapperWidth = display.wrapper.clientWidth this.oldDisplayWidth = displayWidth(cm) this.force = force this.dims = getDimensions(cm) this.events = [] } signal(emitter, type) { if (hasHandler(emitter, type)) this.events.push(arguments) } finish() { for (let i = 0; i < this.events.length; i++) signal.apply(null, this.events[i]) } } export function maybeClipScrollbars(cm) { let display = cm.display if (!display.scrollbarsClipped && display.scroller.offsetWidth) { display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth display.heightForcer.style.height = scrollGap(cm) + "px" display.sizer.style.marginBottom = -display.nativeBarWidth + "px" display.sizer.style.borderRightWidth = scrollGap(cm) + "px" display.scrollbarsClipped = true } } function selectionSnapshot(cm) { if (cm.hasFocus()) return null let active = activeElt(root(cm)) if (!active || !contains(cm.display.lineDiv, active)) return null let result = {activeElt: active} if (window.getSelection) { let sel = win(cm).getSelection() if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { result.anchorNode = sel.anchorNode result.anchorOffset = sel.anchorOffset result.focusNode = sel.focusNode result.focusOffset = sel.focusOffset } } return result } function restoreSelection(snapshot) { if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) return snapshot.activeElt.focus() if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) && snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { let doc = snapshot.activeElt.ownerDocument let sel = doc.defaultView.getSelection(), range = doc.createRange() range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) range.collapse(false) sel.removeAllRanges() sel.addRange(range) sel.extend(snapshot.focusNode, snapshot.focusOffset) } } // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. export function updateDisplayIfNeeded(cm, update) { let display = cm.display, doc = cm.doc if (update.editorIsHidden) { resetView(cm) return false } // Bail out if the visible area is already rendered and nothing changed. if (!update.force && update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && display.renderedView == display.view && countDirtyView(cm) == 0) return false if (maybeUpdateLineNumberWidth(cm)) { resetView(cm) update.dims = getDimensions(cm) } // Compute a suitable new viewport (from & to) let end = doc.first + doc.size let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) let to = Math.min(end, update.visible.to + cm.options.viewportMargin) if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom) if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo) if (sawCollapsedSpans) { from = visualLineNo(cm.doc, from) to = visualLineEndNo(cm.doc, to) } let different = from != display.viewFrom || to != display.viewTo || display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth adjustView(cm, from, to) display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) // Position the mover div to align with the current scroll position cm.display.mover.style.top = display.viewOffset + "px" let toUpdate = countDirtyView(cm) if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) return false // For big changes, we hide the enclosing element during the // update, since that speeds up the operations on most browsers. let selSnapshot = selectionSnapshot(cm) if (toUpdate > 4) display.lineDiv.style.display = "none" patchDisplay(cm, display.updateLineNumbers, update.dims) if (toUpdate > 4) display.lineDiv.style.display = "" display.renderedView = display.view // There might have been a widget with a focused element that got // hidden or updated, if so re-focus it. restoreSelection(selSnapshot) // Prevent selection and cursors from interfering with the scroll // width and height. removeChildren(display.cursorDiv) removeChildren(display.selectionDiv) display.gutters.style.height = display.sizer.style.minHeight = 0 if (different) { display.lastWrapHeight = update.wrapperHeight display.lastWrapWidth = update.wrapperWidth startWorker(cm, 400) } display.updateLineNumbers = null return true } export function postUpdateDisplay(cm, update) { let viewport = update.viewport for (let first = true;; first = false) { if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { // Clip forced viewport to actual scrollable area. if (viewport && viewport.top != null) viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} // Updated line heights might result in the drawn area not // actually covering the viewport. Keep looping until it does. update.visible = visibleLines(cm.display, cm.doc, viewport) if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) break } else if (first) { update.visible = visibleLines(cm.display, cm.doc, viewport) } if (!updateDisplayIfNeeded(cm, update)) break updateHeightsInViewport(cm) let barMeasure = measureForScrollbars(cm) updateSelection(cm) updateScrollbars(cm, barMeasure) setDocumentHeight(cm, barMeasure) update.force = false } update.signal(cm, "update", cm) if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo } } export function updateDisplaySimple(cm, viewport) { let update = new DisplayUpdate(cm, viewport) if (updateDisplayIfNeeded(cm, update)) { updateHeightsInViewport(cm) postUpdateDisplay(cm, update) let barMeasure = measureForScrollbars(cm) updateSelection(cm) updateScrollbars(cm, barMeasure) setDocumentHeight(cm, barMeasure) update.finish() } } // Sync the actual display DOM structure with display.view, removing // nodes for lines that are no longer in view, and creating the ones // that are not there yet, and updating the ones that are out of // date. function patchDisplay(cm, updateNumbersFrom, dims) { let display = cm.display, lineNumbers = cm.options.lineNumbers let container = display.lineDiv, cur = container.firstChild function rm(node) { let next = node.nextSibling // Works around a throw-scroll bug in OS X Webkit if (webkit && mac && cm.display.currentWheelTarget == node) node.style.display = "none" else node.parentNode.removeChild(node) return next } let view = display.view, lineN = display.viewFrom // Loop over the elements in the view, syncing cur (the DOM nodes // in display.lineDiv) with the view as we go. for (let i = 0; i < view.length; i++) { let lineView = view[i] if (lineView.hidden) { } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet let node = buildLineElement(cm, lineView, lineN, dims) container.insertBefore(node, cur) } else { // Already drawn while (cur != lineView.node) cur = rm(cur) let updateNumber = lineNumbers && updateNumbersFrom != null && updateNumbersFrom <= lineN && lineView.lineNumber if (lineView.changes) { if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false updateLineForChanges(cm, lineView, lineN, dims) } if (updateNumber) { removeChildren(lineView.lineNumber) lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) } cur = lineView.node.nextSibling } lineN += lineView.size } while (cur) cur = rm(cur) } export function updateGutterSpace(display) { let width = display.gutters.offsetWidth display.sizer.style.marginLeft = width + "px" // Send an event to consumers responding to changes in gutter width. signalLater(display, "gutterChanged", display) } export function setDocumentHeight(cm, measure) { cm.display.sizer.style.minHeight = measure.docHeight + "px" cm.display.heightForcer.style.top = measure.docHeight + "px" cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/update_line.js ================================================ import { buildLineContent } from "../line/line_data.js" import { lineNumberFor } from "../line/utils_line.js" import { ie, ie_version } from "../util/browser.js" import { elt, classTest } from "../util/dom.js" import { signalLater } from "../util/operation_group.js" // When an aspect of a line changes, a string is added to // lineView.changes. This updates the relevant part of the line's // DOM structure. export function updateLineForChanges(cm, lineView, lineN, dims) { for (let j = 0; j < lineView.changes.length; j++) { let type = lineView.changes[j] if (type == "text") updateLineText(cm, lineView) else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims) else if (type == "class") updateLineClasses(cm, lineView) else if (type == "widget") updateLineWidgets(cm, lineView, dims) } lineView.changes = null } // Lines with gutter elements, widgets or a background class need to // be wrapped, and have the extra elements added to the wrapper div function ensureLineWrapped(lineView) { if (lineView.node == lineView.text) { lineView.node = elt("div", null, null, "position: relative") if (lineView.text.parentNode) lineView.text.parentNode.replaceChild(lineView.node, lineView.text) lineView.node.appendChild(lineView.text) if (ie && ie_version < 8) lineView.node.style.zIndex = 2 } return lineView.node } function updateLineBackground(cm, lineView) { let cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass if (cls) cls += " CodeMirror-linebackground" if (lineView.background) { if (cls) lineView.background.className = cls else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null } } else if (cls) { let wrap = ensureLineWrapped(lineView) lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild) cm.display.input.setUneditable(lineView.background) } } // Wrapper around buildLineContent which will reuse the structure // in display.externalMeasured when possible. function getLineContent(cm, lineView) { let ext = cm.display.externalMeasured if (ext && ext.line == lineView.line) { cm.display.externalMeasured = null lineView.measure = ext.measure return ext.built } return buildLineContent(cm, lineView) } // Redraw the line's text. Interacts with the background and text // classes because the mode may output tokens that influence these // classes. function updateLineText(cm, lineView) { let cls = lineView.text.className let built = getLineContent(cm, lineView) if (lineView.text == lineView.node) lineView.node = built.pre lineView.text.parentNode.replaceChild(built.pre, lineView.text) lineView.text = built.pre if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { lineView.bgClass = built.bgClass lineView.textClass = built.textClass updateLineClasses(cm, lineView) } else if (cls) { lineView.text.className = cls } } function updateLineClasses(cm, lineView) { updateLineBackground(cm, lineView) if (lineView.line.wrapClass) ensureLineWrapped(lineView).className = lineView.line.wrapClass else if (lineView.node != lineView.text) lineView.node.className = "" let textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass lineView.text.className = textClass || "" } function updateLineGutter(cm, lineView, lineN, dims) { if (lineView.gutter) { lineView.node.removeChild(lineView.gutter) lineView.gutter = null } if (lineView.gutterBackground) { lineView.node.removeChild(lineView.gutterBackground) lineView.gutterBackground = null } if (lineView.line.gutterClass) { let wrap = ensureLineWrapped(lineView) lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px; width: ${dims.gutterTotalWidth}px`) cm.display.input.setUneditable(lineView.gutterBackground) wrap.insertBefore(lineView.gutterBackground, lineView.text) } let markers = lineView.line.gutterMarkers if (cm.options.lineNumbers || markers) { let wrap = ensureLineWrapped(lineView) let gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", `left: ${cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth}px`) gutterWrap.setAttribute("aria-hidden", "true") cm.display.input.setUneditable(gutterWrap) wrap.insertBefore(gutterWrap, lineView.text) if (lineView.line.gutterClass) gutterWrap.className += " " + lineView.line.gutterClass if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) lineView.lineNumber = gutterWrap.appendChild( elt("div", lineNumberFor(cm.options, lineN), "CodeMirror-linenumber CodeMirror-gutter-elt", `left: ${dims.gutterLeft["CodeMirror-linenumbers"]}px; width: ${cm.display.lineNumInnerWidth}px`)) if (markers) for (let k = 0; k < cm.display.gutterSpecs.length; ++k) { let id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id] if (found) gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", `left: ${dims.gutterLeft[id]}px; width: ${dims.gutterWidth[id]}px`)) } } } function updateLineWidgets(cm, lineView, dims) { if (lineView.alignable) lineView.alignable = null let isWidget = classTest("CodeMirror-linewidget") for (let node = lineView.node.firstChild, next; node; node = next) { next = node.nextSibling if (isWidget.test(node.className)) lineView.node.removeChild(node) } insertLineWidgets(cm, lineView, dims) } // Build a line's DOM representation from scratch export function buildLineElement(cm, lineView, lineN, dims) { let built = getLineContent(cm, lineView) lineView.text = lineView.node = built.pre if (built.bgClass) lineView.bgClass = built.bgClass if (built.textClass) lineView.textClass = built.textClass updateLineClasses(cm, lineView) updateLineGutter(cm, lineView, lineN, dims) insertLineWidgets(cm, lineView, dims) return lineView.node } // A lineView may contain multiple logical lines (when merged by // collapsed spans). The widgets for all of them need to be drawn. function insertLineWidgets(cm, lineView, dims) { insertLineWidgetsFor(cm, lineView.line, lineView, dims, true) if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++) insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false) } function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { if (!line.widgets) return let wrap = ensureLineWrapped(lineView) for (let i = 0, ws = line.widgets; i < ws.length; ++i) { let widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget" + (widget.className ? " " + widget.className : "")) if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true") positionLineWidget(widget, node, lineView, dims) cm.display.input.setUneditable(node) if (allowAbove && widget.above) wrap.insertBefore(node, lineView.gutter || lineView.text) else wrap.appendChild(node) signalLater(widget, "redraw") } } function positionLineWidget(widget, node, lineView, dims) { if (widget.noHScroll) { ;(lineView.alignable || (lineView.alignable = [])).push(node) let width = dims.wrapperWidth node.style.left = dims.fixedPos + "px" if (!widget.coverGutter) { width -= dims.gutterTotalWidth node.style.paddingLeft = dims.gutterTotalWidth + "px" } node.style.width = width + "px" } if (widget.coverGutter) { node.style.zIndex = 5 node.style.position = "relative" if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px" } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/update_lines.js ================================================ import { heightAtLine } from "../line/spans.js" import { getLine, lineAtHeight, updateLineHeight } from "../line/utils_line.js" import { paddingTop, charWidth } from "../measurement/position_measurement.js" import { ie, ie_version } from "../util/browser.js" // Read the actual heights of the rendered lines, and update their // stored heights to match. export function updateHeightsInViewport(cm) { let display = cm.display let prevBottom = display.lineDiv.offsetTop let viewTop = Math.max(0, display.scroller.getBoundingClientRect().top) let oldHeight = display.lineDiv.getBoundingClientRect().top let mustScroll = 0 for (let i = 0; i < display.view.length; i++) { let cur = display.view[i], wrapping = cm.options.lineWrapping let height, width = 0 if (cur.hidden) continue oldHeight += cur.line.height if (ie && ie_version < 8) { let bot = cur.node.offsetTop + cur.node.offsetHeight height = bot - prevBottom prevBottom = bot } else { let box = cur.node.getBoundingClientRect() height = box.bottom - box.top // Check that lines don't extend past the right of the current // editor width if (!wrapping && cur.text.firstChild) width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1 } let diff = cur.line.height - height if (diff > .005 || diff < -.005) { if (oldHeight < viewTop) mustScroll -= diff updateLineHeight(cur.line, height) updateWidgetHeight(cur.line) if (cur.rest) for (let j = 0; j < cur.rest.length; j++) updateWidgetHeight(cur.rest[j]) } if (width > cm.display.sizerWidth) { let chWidth = Math.ceil(width / charWidth(cm.display)) if (chWidth > cm.display.maxLineLength) { cm.display.maxLineLength = chWidth cm.display.maxLine = cur.line cm.display.maxLineChanged = true } } } if (Math.abs(mustScroll) > 2) display.scroller.scrollTop += mustScroll } // Read and store the height of line widgets associated with the // given line. function updateWidgetHeight(line) { if (line.widgets) for (let i = 0; i < line.widgets.length; ++i) { let w = line.widgets[i], parent = w.node.parentNode if (parent) w.height = parent.offsetHeight } } // Compute the lines that are visible in a given viewport (defaults // the the current scroll position). viewport may contain top, // height, and ensure (see op.scrollToPos) properties. export function visibleLines(display, doc, viewport) { let top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop top = Math.floor(top - paddingTop(display)) let bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight let from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom) // Ensure is a {from: {line, ch}, to: {line, ch}} object, and // forces those lines into the viewport (if possible). if (viewport && viewport.ensure) { let ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line if (ensureFrom < from) { from = ensureFrom to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight) } else if (Math.min(ensureTo, doc.lastLine()) >= to) { from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight) to = ensureTo } } return {from: from, to: Math.max(to, from + 1)} } ================================================ FILE: public/assets/lib/vendor/codemirror/src/display/view_tracking.js ================================================ import { buildViewArray } from "../line/line_data.js" import { sawCollapsedSpans } from "../line/saw_special_spans.js" import { visualLineEndNo, visualLineNo } from "../line/spans.js" import { findViewIndex } from "../measurement/position_measurement.js" import { indexOf } from "../util/misc.js" // Updates the display.view data structure for a given change to the // document. From and to are in pre-change coordinates. Lendiff is // the amount of lines added or subtracted by the change. This is // used for changes that span multiple lines, or change the way // lines are divided into visual lines. regLineChange (below) // registers single-line changes. export function regChange(cm, from, to, lendiff) { if (from == null) from = cm.doc.first if (to == null) to = cm.doc.first + cm.doc.size if (!lendiff) lendiff = 0 let display = cm.display if (lendiff && to < display.viewTo && (display.updateLineNumbers == null || display.updateLineNumbers > from)) display.updateLineNumbers = from cm.curOp.viewChanged = true if (from >= display.viewTo) { // Change after if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) resetView(cm) } else if (to <= display.viewFrom) { // Change before if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { resetView(cm) } else { display.viewFrom += lendiff display.viewTo += lendiff } } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap resetView(cm) } else if (from <= display.viewFrom) { // Top overlap let cut = viewCuttingPoint(cm, to, to + lendiff, 1) if (cut) { display.view = display.view.slice(cut.index) display.viewFrom = cut.lineN display.viewTo += lendiff } else { resetView(cm) } } else if (to >= display.viewTo) { // Bottom overlap let cut = viewCuttingPoint(cm, from, from, -1) if (cut) { display.view = display.view.slice(0, cut.index) display.viewTo = cut.lineN } else { resetView(cm) } } else { // Gap in the middle let cutTop = viewCuttingPoint(cm, from, from, -1) let cutBot = viewCuttingPoint(cm, to, to + lendiff, 1) if (cutTop && cutBot) { display.view = display.view.slice(0, cutTop.index) .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) .concat(display.view.slice(cutBot.index)) display.viewTo += lendiff } else { resetView(cm) } } let ext = display.externalMeasured if (ext) { if (to < ext.lineN) ext.lineN += lendiff else if (from < ext.lineN + ext.size) display.externalMeasured = null } } // Register a change to a single line. Type must be one of "text", // "gutter", "class", "widget" export function regLineChange(cm, line, type) { cm.curOp.viewChanged = true let display = cm.display, ext = cm.display.externalMeasured if (ext && line >= ext.lineN && line < ext.lineN + ext.size) display.externalMeasured = null if (line < display.viewFrom || line >= display.viewTo) return let lineView = display.view[findViewIndex(cm, line)] if (lineView.node == null) return let arr = lineView.changes || (lineView.changes = []) if (indexOf(arr, type) == -1) arr.push(type) } // Clear the view. export function resetView(cm) { cm.display.viewFrom = cm.display.viewTo = cm.doc.first cm.display.view = [] cm.display.viewOffset = 0 } function viewCuttingPoint(cm, oldN, newN, dir) { let index = findViewIndex(cm, oldN), diff, view = cm.display.view if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) return {index: index, lineN: newN} let n = cm.display.viewFrom for (let i = 0; i < index; i++) n += view[i].size if (n != oldN) { if (dir > 0) { if (index == view.length - 1) return null diff = (n + view[index].size) - oldN index++ } else { diff = n - oldN } oldN += diff; newN += diff } while (visualLineNo(cm.doc, newN) != newN) { if (index == (dir < 0 ? 0 : view.length - 1)) return null newN += dir * view[index - (dir < 0 ? 1 : 0)].size index += dir } return {index: index, lineN: newN} } // Force the view to cover a given range, adding empty view element // or clipping off existing ones as needed. export function adjustView(cm, from, to) { let display = cm.display, view = display.view if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { display.view = buildViewArray(cm, from, to) display.viewFrom = from } else { if (display.viewFrom > from) display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view) else if (display.viewFrom < from) display.view = display.view.slice(findViewIndex(cm, from)) display.viewFrom = from if (display.viewTo < to) display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)) else if (display.viewTo > to) display.view = display.view.slice(0, findViewIndex(cm, to)) } display.viewTo = to } // Count the number of lines in the view whose DOM representation is // out of date (or nonexistent). export function countDirtyView(cm) { let view = cm.display.view, dirty = 0 for (let i = 0; i < view.length; i++) { let lineView = view[i] if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty } return dirty } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/CodeMirror.js ================================================ import { Display } from "../display/Display.js" import { onFocus, onBlur } from "../display/focus.js" import { maybeUpdateLineNumberWidth } from "../display/line_numbers.js" import { endOperation, operation, startOperation } from "../display/operations.js" import { initScrollbars } from "../display/scrollbars.js" import { onScrollWheel } from "../display/scroll_events.js" import { setScrollLeft, updateScrollTop } from "../display/scrolling.js" import { clipPos, Pos } from "../line/pos.js" import { posFromMouse } from "../measurement/position_measurement.js" import { eventInWidget } from "../measurement/widgets.js" import Doc from "../model/Doc.js" import { attachDoc } from "../model/document_data.js" import { Range } from "../model/selection.js" import { extendSelection } from "../model/selection_updates.js" import { ie, ie_version, mobile, webkit } from "../util/browser.js" import { e_preventDefault, e_stop, on, signal, signalDOMEvent } from "../util/event.js" import { copyObj, Delayed } from "../util/misc.js" import { clearDragCursor, onDragOver, onDragStart, onDrop } from "./drop_events.js" import { ensureGlobalHandlers } from "./global_events.js" import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" import { clickInGutter, onContextMenu, onMouseDown } from "./mouse_events.js" import { themeChanged } from "./utils.js" import { defaults, optionHandlers, Init } from "./options.js" // A CodeMirror instance represents an editor. This is the object // that user code is usually dealing with. export function CodeMirror(place, options) { if (!(this instanceof CodeMirror)) return new CodeMirror(place, options) this.options = options = options ? copyObj(options) : {} // Determine effective options based on given values and defaults. copyObj(defaults, options, false) let doc = options.value if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction) else if (options.mode) doc.modeOption = options.mode this.doc = doc let input = new CodeMirror.inputStyles[options.inputStyle](this) let display = this.display = new Display(place, doc, input, options) display.wrapper.CodeMirror = this themeChanged(this) if (options.lineWrapping) this.display.wrapper.className += " CodeMirror-wrap" initScrollbars(this) this.state = { keyMaps: [], // stores maps added by addKeyMap overlays: [], // highlighting overlays, as added by addOverlay modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info overwrite: false, delayingBlurEvent: false, focused: false, suppressEdits: false, // used to disable editing during key handlers when in readOnly mode pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll selectingText: false, draggingText: false, highlight: new Delayed(), // stores highlight worker timeout keySeq: null, // Unfinished key sequence specialChars: null } if (options.autofocus && !mobile) display.input.focus() // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload if (ie && ie_version < 11) setTimeout(() => this.display.input.reset(true), 20) registerEventHandlers(this) ensureGlobalHandlers() startOperation(this) this.curOp.forceUpdate = true attachDoc(this, doc) if ((options.autofocus && !mobile) || this.hasFocus()) setTimeout(() => { if (this.hasFocus() && !this.state.focused) onFocus(this) }, 20) else onBlur(this) for (let opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) optionHandlers[opt](this, options[opt], Init) maybeUpdateLineNumberWidth(this) if (options.finishInit) options.finishInit(this) for (let i = 0; i < initHooks.length; ++i) initHooks[i](this) endOperation(this) // Suppress optimizelegibility in Webkit, since it breaks text // measuring on line wrapping boundaries. if (webkit && options.lineWrapping && getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") display.lineDiv.style.textRendering = "auto" } // The default configuration options. CodeMirror.defaults = defaults // Functions to run when options are changed. CodeMirror.optionHandlers = optionHandlers export default CodeMirror // Attach the necessary event handlers when initializing the editor function registerEventHandlers(cm) { let d = cm.display on(d.scroller, "mousedown", operation(cm, onMouseDown)) // Older IE's will not fire a second mousedown for a double click if (ie && ie_version < 11) on(d.scroller, "dblclick", operation(cm, e => { if (signalDOMEvent(cm, e)) return let pos = posFromMouse(cm, e) if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return e_preventDefault(e) let word = cm.findWordAt(pos) extendSelection(cm.doc, word.anchor, word.head) })) else on(d.scroller, "dblclick", e => signalDOMEvent(cm, e) || e_preventDefault(e)) // Some browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is // handled in onMouseDown for these browsers. on(d.scroller, "contextmenu", e => onContextMenu(cm, e)) on(d.input.getField(), "contextmenu", e => { if (!d.scroller.contains(e.target)) onContextMenu(cm, e) }) // Used to suppress mouse event handling when a touch happens let touchFinished, prevTouch = {end: 0} function finishTouch() { if (d.activeTouch) { touchFinished = setTimeout(() => d.activeTouch = null, 1000) prevTouch = d.activeTouch prevTouch.end = +new Date } } function isMouseLikeTouchEvent(e) { if (e.touches.length != 1) return false let touch = e.touches[0] return touch.radiusX <= 1 && touch.radiusY <= 1 } function farAway(touch, other) { if (other.left == null) return true let dx = other.left - touch.left, dy = other.top - touch.top return dx * dx + dy * dy > 20 * 20 } on(d.scroller, "touchstart", e => { if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { d.input.ensurePolled() clearTimeout(touchFinished) let now = +new Date d.activeTouch = {start: now, moved: false, prev: now - prevTouch.end <= 300 ? prevTouch : null} if (e.touches.length == 1) { d.activeTouch.left = e.touches[0].pageX d.activeTouch.top = e.touches[0].pageY } } }) on(d.scroller, "touchmove", () => { if (d.activeTouch) d.activeTouch.moved = true }) on(d.scroller, "touchend", e => { let touch = d.activeTouch if (touch && !eventInWidget(d, e) && touch.left != null && !touch.moved && new Date - touch.start < 300) { let pos = cm.coordsChar(d.activeTouch, "page"), range if (!touch.prev || farAway(touch, touch.prev)) // Single tap range = new Range(pos, pos) else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap range = cm.findWordAt(pos) else // Triple tap range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) cm.setSelection(range.anchor, range.head) cm.focus() e_preventDefault(e) } finishTouch() }) on(d.scroller, "touchcancel", finishTouch) // Sync scrolling between fake scrollbars and real scrollable // area, ensure viewport is updated when scrolling. on(d.scroller, "scroll", () => { if (d.scroller.clientHeight) { updateScrollTop(cm, d.scroller.scrollTop) setScrollLeft(cm, d.scroller.scrollLeft, true) signal(cm, "scroll", cm) } }) // Listen to wheel events in order to try and update the viewport on time. on(d.scroller, "mousewheel", e => onScrollWheel(cm, e)) on(d.scroller, "DOMMouseScroll", e => onScrollWheel(cm, e)) // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", () => d.wrapper.scrollTop = d.wrapper.scrollLeft = 0) d.dragFunctions = { enter: e => {if (!signalDOMEvent(cm, e)) e_stop(e)}, over: e => {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e) }}, start: e => onDragStart(cm, e), drop: operation(cm, onDrop), leave: e => {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm) }} } let inp = d.input.getField() on(inp, "keyup", e => onKeyUp.call(cm, e)) on(inp, "keydown", operation(cm, onKeyDown)) on(inp, "keypress", operation(cm, onKeyPress)) on(inp, "focus", e => onFocus(cm, e)) on(inp, "blur", e => onBlur(cm, e)) } let initHooks = [] CodeMirror.defineInitHook = f => initHooks.push(f) ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/commands.js ================================================ import { deleteNearSelection } from "./deleteNearSelection.js" import { runInOp } from "../display/operations.js" import { ensureCursorVisible } from "../display/scrolling.js" import { endOfLine } from "../input/movement.js" import { clipPos, Pos } from "../line/pos.js" import { visualLine, visualLineEnd } from "../line/spans.js" import { getLine, lineNo } from "../line/utils_line.js" import { Range } from "../model/selection.js" import { selectAll } from "../model/selection_updates.js" import { countColumn, sel_dontScroll, sel_move, spaceStr } from "../util/misc.js" import { getOrder } from "../util/bidi.js" // Commands are parameter-less actions that can be performed on an // editor, mostly used for keybindings. export let commands = { selectAll: selectAll, singleSelection: cm => cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll), killLine: cm => deleteNearSelection(cm, range => { if (range.empty()) { let len = getLine(cm.doc, range.head.line).text.length if (range.head.ch == len && range.head.line < cm.lastLine()) return {from: range.head, to: Pos(range.head.line + 1, 0)} else return {from: range.head, to: Pos(range.head.line, len)} } else { return {from: range.from(), to: range.to()} } }), deleteLine: cm => deleteNearSelection(cm, range => ({ from: Pos(range.from().line, 0), to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) })), delLineLeft: cm => deleteNearSelection(cm, range => ({ from: Pos(range.from().line, 0), to: range.from() })), delWrappedLineLeft: cm => deleteNearSelection(cm, range => { let top = cm.charCoords(range.head, "div").top + 5 let leftPos = cm.coordsChar({left: 0, top: top}, "div") return {from: leftPos, to: range.from()} }), delWrappedLineRight: cm => deleteNearSelection(cm, range => { let top = cm.charCoords(range.head, "div").top + 5 let rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") return {from: range.from(), to: rightPos } }), undo: cm => cm.undo(), redo: cm => cm.redo(), undoSelection: cm => cm.undoSelection(), redoSelection: cm => cm.redoSelection(), goDocStart: cm => cm.extendSelection(Pos(cm.firstLine(), 0)), goDocEnd: cm => cm.extendSelection(Pos(cm.lastLine())), goLineStart: cm => cm.extendSelectionsBy(range => lineStart(cm, range.head.line), {origin: "+move", bias: 1} ), goLineStartSmart: cm => cm.extendSelectionsBy(range => lineStartSmart(cm, range.head), {origin: "+move", bias: 1} ), goLineEnd: cm => cm.extendSelectionsBy(range => lineEnd(cm, range.head.line), {origin: "+move", bias: -1} ), goLineRight: cm => cm.extendSelectionsBy(range => { let top = cm.cursorCoords(range.head, "div").top + 5 return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") }, sel_move), goLineLeft: cm => cm.extendSelectionsBy(range => { let top = cm.cursorCoords(range.head, "div").top + 5 return cm.coordsChar({left: 0, top: top}, "div") }, sel_move), goLineLeftSmart: cm => cm.extendSelectionsBy(range => { let top = cm.cursorCoords(range.head, "div").top + 5 let pos = cm.coordsChar({left: 0, top: top}, "div") if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head) return pos }, sel_move), goLineUp: cm => cm.moveV(-1, "line"), goLineDown: cm => cm.moveV(1, "line"), goPageUp: cm => cm.moveV(-1, "page"), goPageDown: cm => cm.moveV(1, "page"), goCharLeft: cm => cm.moveH(-1, "char"), goCharRight: cm => cm.moveH(1, "char"), goColumnLeft: cm => cm.moveH(-1, "column"), goColumnRight: cm => cm.moveH(1, "column"), goWordLeft: cm => cm.moveH(-1, "word"), goGroupRight: cm => cm.moveH(1, "group"), goGroupLeft: cm => cm.moveH(-1, "group"), goWordRight: cm => cm.moveH(1, "word"), delCharBefore: cm => cm.deleteH(-1, "codepoint"), delCharAfter: cm => cm.deleteH(1, "char"), delWordBefore: cm => cm.deleteH(-1, "word"), delWordAfter: cm => cm.deleteH(1, "word"), delGroupBefore: cm => cm.deleteH(-1, "group"), delGroupAfter: cm => cm.deleteH(1, "group"), indentAuto: cm => cm.indentSelection("smart"), indentMore: cm => cm.indentSelection("add"), indentLess: cm => cm.indentSelection("subtract"), insertTab: cm => cm.replaceSelection("\t"), insertSoftTab: cm => { let spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize for (let i = 0; i < ranges.length; i++) { let pos = ranges[i].from() let col = countColumn(cm.getLine(pos.line), pos.ch, tabSize) spaces.push(spaceStr(tabSize - col % tabSize)) } cm.replaceSelections(spaces) }, defaultTab: cm => { if (cm.somethingSelected()) cm.indentSelection("add") else cm.execCommand("insertTab") }, // Swap the two chars left and right of each selection's head. // Move cursor behind the two swapped characters afterwards. // // Doesn't consider line feeds a character. // Doesn't scan more than one line above to find a character. // Doesn't do anything on an empty line. // Doesn't do anything with non-empty selections. transposeChars: cm => runInOp(cm, () => { let ranges = cm.listSelections(), newSel = [] for (let i = 0; i < ranges.length; i++) { if (!ranges[i].empty()) continue let cur = ranges[i].head, line = getLine(cm.doc, cur.line).text if (line) { if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1) if (cur.ch > 0) { cur = new Pos(cur.line, cur.ch + 1) cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), Pos(cur.line, cur.ch - 2), cur, "+transpose") } else if (cur.line > cm.doc.first) { let prev = getLine(cm.doc, cur.line - 1).text if (prev) { cur = new Pos(cur.line, 1) cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + prev.charAt(prev.length - 1), Pos(cur.line - 1, prev.length - 1), cur, "+transpose") } } } newSel.push(new Range(cur, cur)) } cm.setSelections(newSel) }), newlineAndIndent: cm => runInOp(cm, () => { let sels = cm.listSelections() for (let i = sels.length - 1; i >= 0; i--) cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input") sels = cm.listSelections() for (let i = 0; i < sels.length; i++) cm.indentLine(sels[i].from().line, null, true) ensureCursorVisible(cm) }), openLine: cm => cm.replaceSelection("\n", "start"), toggleOverwrite: cm => cm.toggleOverwrite() } function lineStart(cm, lineN) { let line = getLine(cm.doc, lineN) let visual = visualLine(line) if (visual != line) lineN = lineNo(visual) return endOfLine(true, cm, visual, lineN, 1) } function lineEnd(cm, lineN) { let line = getLine(cm.doc, lineN) let visual = visualLineEnd(line) if (visual != line) lineN = lineNo(visual) return endOfLine(true, cm, line, lineN, -1) } function lineStartSmart(cm, pos) { let start = lineStart(cm, pos.line) let line = getLine(cm.doc, start.line) let order = getOrder(line, cm.doc.direction) if (!order || order[0].level == 0) { let firstNonWS = Math.max(start.ch, line.text.search(/\S/)) let inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) } return start } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/deleteNearSelection.js ================================================ import { runInOp } from "../display/operations.js" import { ensureCursorVisible } from "../display/scrolling.js" import { cmp } from "../line/pos.js" import { replaceRange } from "../model/changes.js" import { lst } from "../util/misc.js" // Helper for deleting text near the selection(s), used to implement // backspace, delete, and similar functionality. export function deleteNearSelection(cm, compute) { let ranges = cm.doc.sel.ranges, kill = [] // Build up a set of ranges to kill first, merging overlapping // ranges. for (let i = 0; i < ranges.length; i++) { let toKill = compute(ranges[i]) while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { let replaced = kill.pop() if (cmp(replaced.from, toKill.from) < 0) { toKill.from = replaced.from break } } kill.push(toKill) } // Next, remove those actual ranges. runInOp(cm, () => { for (let i = kill.length - 1; i >= 0; i--) replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete") ensureCursorVisible(cm) }) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/drop_events.js ================================================ import { drawSelectionCursor } from "../display/selection.js" import { operation } from "../display/operations.js" import { clipPos } from "../line/pos.js" import { posFromMouse } from "../measurement/position_measurement.js" import { eventInWidget } from "../measurement/widgets.js" import { makeChange, replaceRange } from "../model/changes.js" import { changeEnd } from "../model/change_measurement.js" import { simpleSelection } from "../model/selection.js" import { setSelectionNoUndo, setSelectionReplaceHistory } from "../model/selection_updates.js" import { ie, presto, safari } from "../util/browser.js" import { elt, removeChildrenAndAdd } from "../util/dom.js" import { e_preventDefault, e_stop, signalDOMEvent } from "../util/event.js" import { indexOf } from "../util/misc.js" // Kludge to work around strange IE behavior where it'll sometimes // re-fire a series of drag-related events right after the drop (#1551) let lastDrop = 0 export function onDrop(e) { let cm = this clearDragCursor(cm) if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return e_preventDefault(e) if (ie) lastDrop = +new Date let pos = posFromMouse(cm, e, true), files = e.dataTransfer.files if (!pos || cm.isReadOnly()) return // Might be a file drop, in which case we simply extract the text // and insert it. if (files && files.length && window.FileReader && window.File) { let n = files.length, text = Array(n), read = 0 const markAsReadAndPasteIfAllFilesAreRead = () => { if (++read == n) { operation(cm, () => { pos = clipPos(cm.doc, pos) let change = {from: pos, to: pos, text: cm.doc.splitLines( text.filter(t => t != null).join(cm.doc.lineSeparator())), origin: "paste"} makeChange(cm.doc, change) setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change)))) })() } } const readTextFromFile = (file, i) => { if (cm.options.allowDropFileTypes && indexOf(cm.options.allowDropFileTypes, file.type) == -1) { markAsReadAndPasteIfAllFilesAreRead() return } let reader = new FileReader reader.onerror = () => markAsReadAndPasteIfAllFilesAreRead() reader.onload = () => { let content = reader.result if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { markAsReadAndPasteIfAllFilesAreRead() return } text[i] = content markAsReadAndPasteIfAllFilesAreRead() } reader.readAsText(file) } for (let i = 0; i < files.length; i++) readTextFromFile(files[i], i) } else { // Normal drop // Don't do a replace if the drop happened inside of the selected text. if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { cm.state.draggingText(e) // Ensure the editor is re-focused setTimeout(() => cm.display.input.focus(), 20) return } try { let text = e.dataTransfer.getData("Text") if (text) { let selected if (cm.state.draggingText && !cm.state.draggingText.copy) selected = cm.listSelections() setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) if (selected) for (let i = 0; i < selected.length; ++i) replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag") cm.replaceSelection(text, "around", "paste") cm.display.input.focus() } } catch(e){} } } export function onDragStart(cm, e) { if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return e.dataTransfer.setData("Text", cm.getSelection()) e.dataTransfer.effectAllowed = "copyMove" // Use dummy image instead of default browsers image. // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. if (e.dataTransfer.setDragImage && !safari) { let img = elt("img", null, null, "position: fixed; left: 0; top: 0;") img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" if (presto) { img.width = img.height = 1 cm.display.wrapper.appendChild(img) // Force a relayout, or Opera won't use our image for some obscure reason img._top = img.offsetTop } e.dataTransfer.setDragImage(img, 0, 0) if (presto) img.parentNode.removeChild(img) } } export function onDragOver(cm, e) { let pos = posFromMouse(cm, e) if (!pos) return let frag = document.createDocumentFragment() drawSelectionCursor(cm, pos, frag) if (!cm.display.dragCursor) { cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) } removeChildrenAndAdd(cm.display.dragCursor, frag) } export function clearDragCursor(cm) { if (cm.display.dragCursor) { cm.display.lineSpace.removeChild(cm.display.dragCursor) cm.display.dragCursor = null } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/fromTextArea.js ================================================ import { CodeMirror } from "./CodeMirror.js" import { activeElt, rootNode } from "../util/dom.js" import { off, on } from "../util/event.js" import { copyObj } from "../util/misc.js" export function fromTextArea(textarea, options) { options = options ? copyObj(options) : {} options.value = textarea.value if (!options.tabindex && textarea.tabIndex) options.tabindex = textarea.tabIndex if (!options.placeholder && textarea.placeholder) options.placeholder = textarea.placeholder // Set autofocus to true if this textarea is focused, or if it has // autofocus and no other element is focused. if (options.autofocus == null) { let hasFocus = activeElt(rootNode(textarea)) options.autofocus = hasFocus == textarea || textarea.getAttribute("autofocus") != null && hasFocus == document.body } function save() {textarea.value = cm.getValue()} let realSubmit if (textarea.form) { on(textarea.form, "submit", save) // Deplorable hack to make the submit method do the right thing. if (!options.leaveSubmitMethodAlone) { let form = textarea.form realSubmit = form.submit try { let wrappedSubmit = form.submit = () => { save() form.submit = realSubmit form.submit() form.submit = wrappedSubmit } } catch(e) {} } } options.finishInit = cm => { cm.save = save cm.getTextArea = () => textarea cm.toTextArea = () => { cm.toTextArea = isNaN // Prevent this from being ran twice save() textarea.parentNode.removeChild(cm.getWrapperElement()) textarea.style.display = "" if (textarea.form) { off(textarea.form, "submit", save) if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function") textarea.form.submit = realSubmit } } } textarea.style.display = "none" let cm = CodeMirror(node => textarea.parentNode.insertBefore(node, textarea.nextSibling), options) return cm } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/global_events.js ================================================ import { onBlur } from "../display/focus.js" import { on } from "../util/event.js" // These must be handled carefully, because naively registering a // handler for each editor will cause the editors to never be // garbage collected. function forEachCodeMirror(f) { if (!document.getElementsByClassName) return let byClass = document.getElementsByClassName("CodeMirror"), editors = [] for (let i = 0; i < byClass.length; i++) { let cm = byClass[i].CodeMirror if (cm) editors.push(cm) } if (editors.length) editors[0].operation(() => { for (let i = 0; i < editors.length; i++) f(editors[i]) }) } let globalsRegistered = false export function ensureGlobalHandlers() { if (globalsRegistered) return registerGlobalHandlers() globalsRegistered = true } function registerGlobalHandlers() { // When the window resizes, we need to refresh active editors. let resizeTimer on(window, "resize", () => { if (resizeTimer == null) resizeTimer = setTimeout(() => { resizeTimer = null forEachCodeMirror(onResize) }, 100) }) // When the window loses focus, we want to show the editor as blurred on(window, "blur", () => forEachCodeMirror(onBlur)) } // Called when the window resizes function onResize(cm) { let d = cm.display // Might be a text scaling operation, clear size caches. d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null d.scrollbarsClipped = false cm.setSize() } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/key_events.js ================================================ import { signalLater } from "../util/operation_group.js" import { restartBlink } from "../display/selection.js" import { isModifierKey, keyName, lookupKey } from "../input/keymap.js" import { eventInWidget } from "../measurement/widgets.js" import { ie, ie_version, mac, presto, gecko } from "../util/browser.js" import { activeElt, addClass, rmClass, root } from "../util/dom.js" import { e_preventDefault, off, on, signalDOMEvent } from "../util/event.js" import { hasCopyEvent } from "../util/feature_detection.js" import { Delayed, Pass } from "../util/misc.js" import { commands } from "./commands.js" // Run a handler that was bound to a key. function doHandleBinding(cm, bound, dropShift) { if (typeof bound == "string") { bound = commands[bound] if (!bound) return false } // Ensure previous input has been read, so that the handler sees a // consistent view of the document cm.display.input.ensurePolled() let prevShift = cm.display.shift, done = false try { if (cm.isReadOnly()) cm.state.suppressEdits = true if (dropShift) cm.display.shift = false done = bound(cm) != Pass } finally { cm.display.shift = prevShift cm.state.suppressEdits = false } return done } function lookupKeyForEditor(cm, name, handle) { for (let i = 0; i < cm.state.keyMaps.length; i++) { let result = lookupKey(name, cm.state.keyMaps[i], handle, cm) if (result) return result } return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) || lookupKey(name, cm.options.keyMap, handle, cm) } // Note that, despite the name, this function is also used to check // for bound mouse clicks. let stopSeq = new Delayed export function dispatchKey(cm, name, e, handle) { let seq = cm.state.keySeq if (seq) { if (isModifierKey(name)) return "handled" if (/\'$/.test(name)) cm.state.keySeq = null else stopSeq.set(50, () => { if (cm.state.keySeq == seq) { cm.state.keySeq = null cm.display.input.reset() } }) if (dispatchKeyInner(cm, seq + " " + name, e, handle)) return true } return dispatchKeyInner(cm, name, e, handle) } function dispatchKeyInner(cm, name, e, handle) { let result = lookupKeyForEditor(cm, name, handle) if (result == "multi") cm.state.keySeq = name if (result == "handled") signalLater(cm, "keyHandled", cm, name, e) if (result == "handled" || result == "multi") { e_preventDefault(e) restartBlink(cm) } return !!result } // Handle a key from the keydown event. function handleKeyBinding(cm, e) { let name = keyName(e, true) if (!name) return false if (e.shiftKey && !cm.state.keySeq) { // First try to resolve full name (including 'Shift-'). Failing // that, see if there is a cursor-motion command (starting with // 'go') bound to the keyname without 'Shift-'. return dispatchKey(cm, "Shift-" + name, e, b => doHandleBinding(cm, b, true)) || dispatchKey(cm, name, e, b => { if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) return doHandleBinding(cm, b) }) } else { return dispatchKey(cm, name, e, b => doHandleBinding(cm, b)) } } // Handle a key from the keypress event function handleCharBinding(cm, e, ch) { return dispatchKey(cm, "'" + ch + "'", e, b => doHandleBinding(cm, b, true)) } let lastStoppedKey = null export function onKeyDown(e) { let cm = this if (e.target && e.target != cm.display.input.getField()) return cm.curOp.focus = activeElt(root(cm)) if (signalDOMEvent(cm, e)) return // IE does strange things with escape. if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false let code = e.keyCode cm.display.shift = code == 16 || e.shiftKey let handled = handleKeyBinding(cm, e) if (presto) { lastStoppedKey = handled ? code : null // Opera has no cut event... we try to at least catch the key combo if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) cm.replaceSelection("", null, "cut") } if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand) document.execCommand("cut") // Turn mouse into crosshair when Alt is held on Mac. if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) showCrossHair(cm) } function showCrossHair(cm) { let lineDiv = cm.display.lineDiv addClass(lineDiv, "CodeMirror-crosshair") function up(e) { if (e.keyCode == 18 || !e.altKey) { rmClass(lineDiv, "CodeMirror-crosshair") off(document, "keyup", up) off(document, "mouseover", up) } } on(document, "keyup", up) on(document, "mouseover", up) } export function onKeyUp(e) { if (e.keyCode == 16) this.doc.sel.shift = false signalDOMEvent(this, e) } export function onKeyPress(e) { let cm = this if (e.target && e.target != cm.display.input.getField()) return if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return let keyCode = e.keyCode, charCode = e.charCode if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return let ch = String.fromCharCode(charCode == null ? keyCode : charCode) // Some browsers fire keypress events for backspace if (ch == "\x08") return if (handleCharBinding(cm, e, ch)) return cm.display.input.onKeyPress(e) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/legacy.js ================================================ import { scrollbarModel } from "../display/scrollbars.js" import { wheelEventPixels } from "../display/scroll_events.js" import { keyMap, keyName, isModifierKey, lookupKey, normalizeKeyMap } from "../input/keymap.js" import { keyNames } from "../input/keynames.js" import { Line } from "../line/line_data.js" import { cmp, Pos } from "../line/pos.js" import { changeEnd } from "../model/change_measurement.js" import Doc from "../model/Doc.js" import { LineWidget } from "../model/line_widget.js" import { SharedTextMarker, TextMarker } from "../model/mark_text.js" import { copyState, extendMode, getMode, innerMode, mimeModes, modeExtensions, modes, resolveMode, startState } from "../modes.js" import { addClass, contains, rmClass } from "../util/dom.js" import { e_preventDefault, e_stop, e_stopPropagation, off, on, signal } from "../util/event.js" import { splitLinesAuto } from "../util/feature_detection.js" import { countColumn, findColumn, isWordCharBasic, Pass } from "../util/misc.js" import StringStream from "../util/StringStream.js" import { commands } from "./commands.js" export function addLegacyProps(CodeMirror) { CodeMirror.off = off CodeMirror.on = on CodeMirror.wheelEventPixels = wheelEventPixels CodeMirror.Doc = Doc CodeMirror.splitLines = splitLinesAuto CodeMirror.countColumn = countColumn CodeMirror.findColumn = findColumn CodeMirror.isWordChar = isWordCharBasic CodeMirror.Pass = Pass CodeMirror.signal = signal CodeMirror.Line = Line CodeMirror.changeEnd = changeEnd CodeMirror.scrollbarModel = scrollbarModel CodeMirror.Pos = Pos CodeMirror.cmpPos = cmp CodeMirror.modes = modes CodeMirror.mimeModes = mimeModes CodeMirror.resolveMode = resolveMode CodeMirror.getMode = getMode CodeMirror.modeExtensions = modeExtensions CodeMirror.extendMode = extendMode CodeMirror.copyState = copyState CodeMirror.startState = startState CodeMirror.innerMode = innerMode CodeMirror.commands = commands CodeMirror.keyMap = keyMap CodeMirror.keyName = keyName CodeMirror.isModifierKey = isModifierKey CodeMirror.lookupKey = lookupKey CodeMirror.normalizeKeyMap = normalizeKeyMap CodeMirror.StringStream = StringStream CodeMirror.SharedTextMarker = SharedTextMarker CodeMirror.TextMarker = TextMarker CodeMirror.LineWidget = LineWidget CodeMirror.e_preventDefault = e_preventDefault CodeMirror.e_stopPropagation = e_stopPropagation CodeMirror.e_stop = e_stop CodeMirror.addClass = addClass CodeMirror.contains = contains CodeMirror.rmClass = rmClass CodeMirror.keyNames = keyNames } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/main.js ================================================ // EDITOR CONSTRUCTOR import { CodeMirror } from "./CodeMirror.js" export { CodeMirror } from "./CodeMirror.js" import { eventMixin } from "../util/event.js" import { indexOf } from "../util/misc.js" import { defineOptions } from "./options.js" defineOptions(CodeMirror) import addEditorMethods from "./methods.js" addEditorMethods(CodeMirror) import Doc from "../model/Doc.js" // Set up methods on CodeMirror's prototype to redirect to the editor's document. let dontDelegate = "iter insert remove copy getEditor constructor".split(" ") for (let prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) CodeMirror.prototype[prop] = (function(method) { return function() {return method.apply(this.doc, arguments)} })(Doc.prototype[prop]) eventMixin(Doc) // INPUT HANDLING import ContentEditableInput from "../input/ContentEditableInput.js" import TextareaInput from "../input/TextareaInput.js" CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput} // MODE DEFINITION AND QUERYING import { defineMIME, defineMode } from "../modes.js" // Extra arguments are stored as the mode's dependencies, which is // used by (legacy) mechanisms like loadmode.js to automatically // load a mode. (Preferred mechanism is the require/define calls.) CodeMirror.defineMode = function(name/*, mode, …*/) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name defineMode.apply(this, arguments) } CodeMirror.defineMIME = defineMIME // Minimal default mode. CodeMirror.defineMode("null", () => ({token: stream => stream.skipToEnd()})) CodeMirror.defineMIME("text/plain", "null") // EXTENSIONS CodeMirror.defineExtension = (name, func) => { CodeMirror.prototype[name] = func } CodeMirror.defineDocExtension = (name, func) => { Doc.prototype[name] = func } import { fromTextArea } from "./fromTextArea.js" CodeMirror.fromTextArea = fromTextArea import { addLegacyProps } from "./legacy.js" addLegacyProps(CodeMirror) CodeMirror.version = "5.65.16" ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/methods.js ================================================ import { deleteNearSelection } from "./deleteNearSelection.js" import { commands } from "./commands.js" import { attachDoc } from "../model/document_data.js" import { activeElt, addClass, rmClass, root, win } from "../util/dom.js" import { eventMixin, signal } from "../util/event.js" import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js" import { indentLine } from "../input/indent.js" import { triggerElectric } from "../input/input.js" import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" import { onMouseDown } from "./mouse_events.js" import { getKeyMap } from "../input/keymap.js" import { endOfLine, moveLogically, moveVisually } from "../input/movement.js" import { endOperation, methodOp, operation, runInOp, startOperation } from "../display/operations.js" import { clipLine, clipPos, equalCursorPos, Pos } from "../line/pos.js" import { charCoords, charWidth, clearCaches, clearLineMeasurementCache, coordsChar, cursorCoords, displayHeight, displayWidth, estimateLineHeights, fromCoordSystem, intoCoordSystem, scrollGap, textHeight } from "../measurement/position_measurement.js" import { Range } from "../model/selection.js" import { replaceOneSelection, skipAtomic } from "../model/selection_updates.js" import { addToScrollTop, ensureCursorVisible, scrollIntoView, scrollToCoords, scrollToCoordsRange, scrollToRange } from "../display/scrolling.js" import { heightAtLine } from "../line/spans.js" import { updateGutterSpace } from "../display/update_display.js" import { indexOf, insertSorted, isWordChar, sel_dontScroll, sel_move } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" import { getLine, isLine, lineAtHeight } from "../line/utils_line.js" import { regChange, regLineChange } from "../display/view_tracking.js" // The publicly visible API. Note that methodOp(f) means // 'wrap f in an operation, performed on its `this` parameter'. // This is not the complete set of editor methods. Most of the // methods defined on the Doc type are also injected into // CodeMirror.prototype, for backwards compatibility and // convenience. export default function(CodeMirror) { let optionHandlers = CodeMirror.optionHandlers let helpers = CodeMirror.helpers = {} CodeMirror.prototype = { constructor: CodeMirror, focus: function(){win(this).focus(); this.display.input.focus()}, setOption: function(option, value) { let options = this.options, old = options[option] if (options[option] == value && option != "mode") return options[option] = value if (optionHandlers.hasOwnProperty(option)) operation(this, optionHandlers[option])(this, value, old) signal(this, "optionChange", this, option) }, getOption: function(option) {return this.options[option]}, getDoc: function() {return this.doc}, addKeyMap: function(map, bottom) { this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) }, removeKeyMap: function(map) { let maps = this.state.keyMaps for (let i = 0; i < maps.length; ++i) if (maps[i] == map || maps[i].name == map) { maps.splice(i, 1) return true } }, addOverlay: methodOp(function(spec, options) { let mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) if (mode.startState) throw new Error("Overlays may not be stateful.") insertSorted(this.state.overlays, {mode: mode, modeSpec: spec, opaque: options && options.opaque, priority: (options && options.priority) || 0}, overlay => overlay.priority) this.state.modeGen++ regChange(this) }), removeOverlay: methodOp(function(spec) { let overlays = this.state.overlays for (let i = 0; i < overlays.length; ++i) { let cur = overlays[i].modeSpec if (cur == spec || typeof spec == "string" && cur.name == spec) { overlays.splice(i, 1) this.state.modeGen++ regChange(this) return } } }), indentLine: methodOp(function(n, dir, aggressive) { if (typeof dir != "string" && typeof dir != "number") { if (dir == null) dir = this.options.smartIndent ? "smart" : "prev" else dir = dir ? "add" : "subtract" } if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive) }), indentSelection: methodOp(function(how) { let ranges = this.doc.sel.ranges, end = -1 for (let i = 0; i < ranges.length; i++) { let range = ranges[i] if (!range.empty()) { let from = range.from(), to = range.to() let start = Math.max(end, from.line) end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 for (let j = start; j < end; ++j) indentLine(this, j, how) let newRanges = this.doc.sel.ranges if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) } else if (range.head.line > end) { indentLine(this, range.head.line, how, true) end = range.head.line if (i == this.doc.sel.primIndex) ensureCursorVisible(this) } } }), // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). getTokenAt: function(pos, precise) { return takeToken(this, pos, precise) }, getLineTokens: function(line, precise) { return takeToken(this, Pos(line), precise, true) }, getTokenTypeAt: function(pos) { pos = clipPos(this.doc, pos) let styles = getLineStyles(this, getLine(this.doc, pos.line)) let before = 0, after = (styles.length - 1) / 2, ch = pos.ch let type if (ch == 0) type = styles[2] else for (;;) { let mid = (before + after) >> 1 if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid else if (styles[mid * 2 + 1] < ch) before = mid + 1 else { type = styles[mid * 2 + 2]; break } } let cut = type ? type.indexOf("overlay ") : -1 return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) }, getModeAt: function(pos) { let mode = this.doc.mode if (!mode.innerMode) return mode return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode }, getHelper: function(pos, type) { return this.getHelpers(pos, type)[0] }, getHelpers: function(pos, type) { let found = [] if (!helpers.hasOwnProperty(type)) return found let help = helpers[type], mode = this.getModeAt(pos) if (typeof mode[type] == "string") { if (help[mode[type]]) found.push(help[mode[type]]) } else if (mode[type]) { for (let i = 0; i < mode[type].length; i++) { let val = help[mode[type][i]] if (val) found.push(val) } } else if (mode.helperType && help[mode.helperType]) { found.push(help[mode.helperType]) } else if (help[mode.name]) { found.push(help[mode.name]) } for (let i = 0; i < help._global.length; i++) { let cur = help._global[i] if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) found.push(cur.val) } return found }, getStateAfter: function(line, precise) { let doc = this.doc line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) return getContextBefore(this, line + 1, precise).state }, cursorCoords: function(start, mode) { let pos, range = this.doc.sel.primary() if (start == null) pos = range.head else if (typeof start == "object") pos = clipPos(this.doc, start) else pos = start ? range.from() : range.to() return cursorCoords(this, pos, mode || "page") }, charCoords: function(pos, mode) { return charCoords(this, clipPos(this.doc, pos), mode || "page") }, coordsChar: function(coords, mode) { coords = fromCoordSystem(this, coords, mode || "page") return coordsChar(this, coords.left, coords.top) }, lineAtHeight: function(height, mode) { height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top return lineAtHeight(this.doc, height + this.display.viewOffset) }, heightAtLine: function(line, mode, includeWidgets) { let end = false, lineObj if (typeof line == "number") { let last = this.doc.first + this.doc.size - 1 if (line < this.doc.first) line = this.doc.first else if (line > last) { line = last; end = true } lineObj = getLine(this.doc, line) } else { lineObj = line } return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + (end ? this.doc.height - heightAtLine(lineObj) : 0) }, defaultTextHeight: function() { return textHeight(this.display) }, defaultCharWidth: function() { return charWidth(this.display) }, getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, addWidget: function(pos, node, scroll, vert, horiz) { let display = this.display pos = cursorCoords(this, clipPos(this.doc, pos)) let top = pos.bottom, left = pos.left node.style.position = "absolute" node.setAttribute("cm-ignore-events", "true") this.display.input.setUneditable(node) display.sizer.appendChild(node) if (vert == "over") { top = pos.top } else if (vert == "above" || vert == "near") { let vspace = Math.max(display.wrapper.clientHeight, this.doc.height), hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth) // Default to positioning above (if specified and possible); otherwise default to positioning below if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) top = pos.top - node.offsetHeight else if (pos.bottom + node.offsetHeight <= vspace) top = pos.bottom if (left + node.offsetWidth > hspace) left = hspace - node.offsetWidth } node.style.top = top + "px" node.style.left = node.style.right = "" if (horiz == "right") { left = display.sizer.clientWidth - node.offsetWidth node.style.right = "0px" } else { if (horiz == "left") left = 0 else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2 node.style.left = left + "px" } if (scroll) scrollIntoView(this, {left, top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) }, triggerOnKeyDown: methodOp(onKeyDown), triggerOnKeyPress: methodOp(onKeyPress), triggerOnKeyUp: onKeyUp, triggerOnMouseDown: methodOp(onMouseDown), execCommand: function(cmd) { if (commands.hasOwnProperty(cmd)) return commands[cmd].call(null, this) }, triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), findPosH: function(from, amount, unit, visually) { let dir = 1 if (amount < 0) { dir = -1; amount = -amount } let cur = clipPos(this.doc, from) for (let i = 0; i < amount; ++i) { cur = findPosH(this.doc, cur, dir, unit, visually) if (cur.hitSide) break } return cur }, moveH: methodOp(function(dir, unit) { this.extendSelectionsBy(range => { if (this.display.shift || this.doc.extend || range.empty()) return findPosH(this.doc, range.head, dir, unit, this.options.rtlMoveVisually) else return dir < 0 ? range.from() : range.to() }, sel_move) }), deleteH: methodOp(function(dir, unit) { let sel = this.doc.sel, doc = this.doc if (sel.somethingSelected()) doc.replaceSelection("", null, "+delete") else deleteNearSelection(this, range => { let other = findPosH(doc, range.head, dir, unit, false) return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} }) }), findPosV: function(from, amount, unit, goalColumn) { let dir = 1, x = goalColumn if (amount < 0) { dir = -1; amount = -amount } let cur = clipPos(this.doc, from) for (let i = 0; i < amount; ++i) { let coords = cursorCoords(this, cur, "div") if (x == null) x = coords.left else coords.left = x cur = findPosV(this, coords, dir, unit) if (cur.hitSide) break } return cur }, moveV: methodOp(function(dir, unit) { let doc = this.doc, goals = [] let collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() doc.extendSelectionsBy(range => { if (collapse) return dir < 0 ? range.from() : range.to() let headPos = cursorCoords(this, range.head, "div") if (range.goalColumn != null) headPos.left = range.goalColumn goals.push(headPos.left) let pos = findPosV(this, headPos, dir, unit) if (unit == "page" && range == doc.sel.primary()) addToScrollTop(this, charCoords(this, pos, "div").top - headPos.top) return pos }, sel_move) if (goals.length) for (let i = 0; i < doc.sel.ranges.length; i++) doc.sel.ranges[i].goalColumn = goals[i] }), // Find the word at the given position (as returned by coordsChar). findWordAt: function(pos) { let doc = this.doc, line = getLine(doc, pos.line).text let start = pos.ch, end = pos.ch if (line) { let helper = this.getHelper(pos, "wordChars") if ((pos.sticky == "before" || end == line.length) && start) --start; else ++end let startChar = line.charAt(start) let check = isWordChar(startChar, helper) ? ch => isWordChar(ch, helper) : /\s/.test(startChar) ? ch => /\s/.test(ch) : ch => (!/\s/.test(ch) && !isWordChar(ch)) while (start > 0 && check(line.charAt(start - 1))) --start while (end < line.length && check(line.charAt(end))) ++end } return new Range(Pos(pos.line, start), Pos(pos.line, end)) }, toggleOverwrite: function(value) { if (value != null && value == this.state.overwrite) return if (this.state.overwrite = !this.state.overwrite) addClass(this.display.cursorDiv, "CodeMirror-overwrite") else rmClass(this.display.cursorDiv, "CodeMirror-overwrite") signal(this, "overwriteToggle", this, this.state.overwrite) }, hasFocus: function() { return this.display.input.getField() == activeElt(root(this)) }, isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), getScrollInfo: function() { let scroller = this.display.scroller return {left: scroller.scrollLeft, top: scroller.scrollTop, height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, clientHeight: displayHeight(this), clientWidth: displayWidth(this)} }, scrollIntoView: methodOp(function(range, margin) { if (range == null) { range = {from: this.doc.sel.primary().head, to: null} if (margin == null) margin = this.options.cursorScrollMargin } else if (typeof range == "number") { range = {from: Pos(range, 0), to: null} } else if (range.from == null) { range = {from: range, to: null} } if (!range.to) range.to = range.from range.margin = margin || 0 if (range.from.line != null) { scrollToRange(this, range) } else { scrollToCoordsRange(this, range.from, range.to, range.margin) } }), setSize: methodOp(function(width, height) { let interpret = val => typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val if (width != null) this.display.wrapper.style.width = interpret(width) if (height != null) this.display.wrapper.style.height = interpret(height) if (this.options.lineWrapping) clearLineMeasurementCache(this) let lineNo = this.display.viewFrom this.doc.iter(lineNo, this.display.viewTo, line => { if (line.widgets) for (let i = 0; i < line.widgets.length; i++) if (line.widgets[i].noHScroll) { regLineChange(this, lineNo, "widget"); break } ++lineNo }) this.curOp.forceUpdate = true signal(this, "refresh", this) }), operation: function(f){return runInOp(this, f)}, startOperation: function(){return startOperation(this)}, endOperation: function(){return endOperation(this)}, refresh: methodOp(function() { let oldHeight = this.display.cachedTextHeight regChange(this) this.curOp.forceUpdate = true clearCaches(this) scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) updateGutterSpace(this.display) if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping) estimateLineHeights(this) signal(this, "refresh", this) }), swapDoc: methodOp(function(doc) { let old = this.doc old.cm = null // Cancel the current text selection if any (#5821) if (this.state.selectingText) this.state.selectingText() attachDoc(this, doc) clearCaches(this) this.display.input.reset() scrollToCoords(this, doc.scrollLeft, doc.scrollTop) this.curOp.forceScroll = true signalLater(this, "swapDoc", this, old) return old }), phrase: function(phraseText) { let phrases = this.options.phrases return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText }, getInputField: function(){return this.display.input.getField()}, getWrapperElement: function(){return this.display.wrapper}, getScrollerElement: function(){return this.display.scroller}, getGutterElement: function(){return this.display.gutters} } eventMixin(CodeMirror) CodeMirror.registerHelper = function(type, name, value) { if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []} helpers[type][name] = value } CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { CodeMirror.registerHelper(type, name, value) helpers[type]._global.push({pred: predicate, val: value}) } } // Used for horizontal relative motion. Dir is -1 or 1 (left or // right), unit can be "codepoint", "char", "column" (like char, but // doesn't cross line boundaries), "word" (across next word), or // "group" (to the start of next group of word or // non-word-non-whitespace chars). The visually param controls // whether, in right-to-left text, direction 1 means to move towards // the next index in the string, or towards the character to the right // of the current position. The resulting position will have a // hitSide=true property if it reached the end of the document. function findPosH(doc, pos, dir, unit, visually) { let oldPos = pos let origDir = dir let lineObj = getLine(doc, pos.line) let lineDir = visually && doc.direction == "rtl" ? -dir : dir function findNextLine() { let l = pos.line + lineDir if (l < doc.first || l >= doc.first + doc.size) return false pos = new Pos(l, pos.ch, pos.sticky) return lineObj = getLine(doc, l) } function moveOnce(boundToLine) { let next if (unit == "codepoint") { let ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1)) if (isNaN(ch)) { next = null } else { let astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir) } } else if (visually) { next = moveVisually(doc.cm, lineObj, pos, dir) } else { next = moveLogically(lineObj, pos, dir) } if (next == null) { if (!boundToLine && findNextLine()) pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir) else return false } else { pos = next } return true } if (unit == "char" || unit == "codepoint") { moveOnce() } else if (unit == "column") { moveOnce(true) } else if (unit == "word" || unit == "group") { let sawType = null, group = unit == "group" let helper = doc.cm && doc.cm.getHelper(pos, "wordChars") for (let first = true;; first = false) { if (dir < 0 && !moveOnce(!first)) break let cur = lineObj.text.charAt(pos.ch) || "\n" let type = isWordChar(cur, helper) ? "w" : group && cur == "\n" ? "n" : !group || /\s/.test(cur) ? null : "p" if (group && !first && !type) type = "s" if (sawType && sawType != type) { if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} break } if (type) sawType = type if (dir > 0 && !moveOnce(!first)) break } } let result = skipAtomic(doc, pos, oldPos, origDir, true) if (equalCursorPos(oldPos, result)) result.hitSide = true return result } // For relative vertical movement. Dir may be -1 or 1. Unit can be // "page" or "line". The resulting position will have a hitSide=true // property if it reached the end of the document. function findPosV(cm, pos, dir, unit) { let doc = cm.doc, x = pos.left, y if (unit == "page") { let pageSize = Math.min(cm.display.wrapper.clientHeight, win(cm).innerHeight || doc(cm).documentElement.clientHeight) let moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount } else if (unit == "line") { y = dir > 0 ? pos.bottom + 3 : pos.top - 3 } let target for (;;) { target = coordsChar(cm, x, y) if (!target.outside) break if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } y += dir * 5 } return target } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/mouse_events.js ================================================ import { delayBlurEvent, ensureFocus } from "../display/focus.js" import { operation } from "../display/operations.js" import { visibleLines } from "../display/update_lines.js" import { clipPos, cmp, maxPos, minPos, Pos } from "../line/pos.js" import { getLine, lineAtHeight } from "../line/utils_line.js" import { posFromMouse } from "../measurement/position_measurement.js" import { eventInWidget } from "../measurement/widgets.js" import { normalizeSelection, Range, Selection } from "../model/selection.js" import { extendRange, extendSelection, replaceOneSelection, setSelection } from "../model/selection_updates.js" import { captureRightClick, chromeOS, ie, ie_version, mac, webkit, safari } from "../util/browser.js" import { getOrder, getBidiPartAt } from "../util/bidi.js" import { activeElt, root, win } from "../util/dom.js" import { e_button, e_defaultPrevented, e_preventDefault, e_target, hasHandler, off, on, signal, signalDOMEvent } from "../util/event.js" import { dragAndDrop } from "../util/feature_detection.js" import { bind, countColumn, findColumn, sel_mouse } from "../util/misc.js" import { addModifierNames } from "../input/keymap.js" import { Pass } from "../util/misc.js" import { dispatchKey } from "./key_events.js" import { commands } from "./commands.js" const DOUBLECLICK_DELAY = 400 class PastClick { constructor(time, pos, button) { this.time = time this.pos = pos this.button = button } compare(time, pos, button) { return this.time + DOUBLECLICK_DELAY > time && cmp(pos, this.pos) == 0 && button == this.button } } let lastClick, lastDoubleClick function clickRepeat(pos, button) { let now = +new Date if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { lastClick = lastDoubleClick = null return "triple" } else if (lastClick && lastClick.compare(now, pos, button)) { lastDoubleClick = new PastClick(now, pos, button) lastClick = null return "double" } else { lastClick = new PastClick(now, pos, button) lastDoubleClick = null return "single" } } // A mouse down can be a single click, double click, triple click, // start of selection drag, start of text drag, new cursor // (ctrl-click), rectangle drag (alt-drag), or xwin // middle-click-paste. Or it might be a click on something we should // not interfere with, such as a scrollbar or widget. export function onMouseDown(e) { let cm = this, display = cm.display if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return display.input.ensurePolled() display.shift = e.shiftKey if (eventInWidget(display, e)) { if (!webkit) { // Briefly turn off draggability, to allow widgets to do // normal dragging things. display.scroller.draggable = false setTimeout(() => display.scroller.draggable = true, 100) } return } if (clickInGutter(cm, e)) return let pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single" win(cm).focus() // #3261: make sure, that we're not starting a second selection if (button == 1 && cm.state.selectingText) cm.state.selectingText(e) if (pos && handleMappedButton(cm, button, pos, repeat, e)) return if (button == 1) { if (pos) leftButtonDown(cm, pos, repeat, e) else if (e_target(e) == display.scroller) e_preventDefault(e) } else if (button == 2) { if (pos) extendSelection(cm.doc, pos) setTimeout(() => display.input.focus(), 20) } else if (button == 3) { if (captureRightClick) cm.display.input.onContextMenu(e) else delayBlurEvent(cm) } } function handleMappedButton(cm, button, pos, repeat, event) { let name = "Click" if (repeat == "double") name = "Double" + name else if (repeat == "triple") name = "Triple" + name name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name return dispatchKey(cm, addModifierNames(name, event), event, bound => { if (typeof bound == "string") bound = commands[bound] if (!bound) return false let done = false try { if (cm.isReadOnly()) cm.state.suppressEdits = true done = bound(cm, pos) != Pass } finally { cm.state.suppressEdits = false } return done }) } function configureMouse(cm, repeat, event) { let option = cm.getOption("configureMouse") let value = option ? option(cm, repeat, event) : {} if (value.unit == null) { let rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line" } if (value.extend == null || cm.doc.extend) value.extend = cm.doc.extend || event.shiftKey if (value.addNew == null) value.addNew = mac ? event.metaKey : event.ctrlKey if (value.moveOnDrag == null) value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey) return value } function leftButtonDown(cm, pos, repeat, event) { if (ie) setTimeout(bind(ensureFocus, cm), 0) else cm.curOp.focus = activeElt(root(cm)) let behavior = configureMouse(cm, repeat, event) let sel = cm.doc.sel, contained if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && repeat == "single" && (contained = sel.contains(pos)) > -1 && (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) leftButtonStartDrag(cm, event, pos, behavior) else leftButtonSelect(cm, event, pos, behavior) } // Start a text drag. When it ends, see if any dragging actually // happen, and treat as a click if it didn't. function leftButtonStartDrag(cm, event, pos, behavior) { let display = cm.display, moved = false let dragEnd = operation(cm, e => { if (webkit) display.scroller.draggable = false cm.state.draggingText = false if (cm.state.delayingBlurEvent) { if (cm.hasFocus()) cm.state.delayingBlurEvent = false else delayBlurEvent(cm) } off(display.wrapper.ownerDocument, "mouseup", dragEnd) off(display.wrapper.ownerDocument, "mousemove", mouseMove) off(display.scroller, "dragstart", dragStart) off(display.scroller, "drop", dragEnd) if (!moved) { e_preventDefault(e) if (!behavior.addNew) extendSelection(cm.doc, pos, null, null, behavior.extend) // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) if ((webkit && !safari) || ie && ie_version == 9) setTimeout(() => {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus()}, 20) else display.input.focus() } }) let mouseMove = function(e2) { moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10 } let dragStart = () => moved = true // Let the drag handler handle this. if (webkit) display.scroller.draggable = true cm.state.draggingText = dragEnd dragEnd.copy = !behavior.moveOnDrag on(display.wrapper.ownerDocument, "mouseup", dragEnd) on(display.wrapper.ownerDocument, "mousemove", mouseMove) on(display.scroller, "dragstart", dragStart) on(display.scroller, "drop", dragEnd) cm.state.delayingBlurEvent = true setTimeout(() => display.input.focus(), 20) // IE's approach to draggable if (display.scroller.dragDrop) display.scroller.dragDrop() } function rangeForUnit(cm, pos, unit) { if (unit == "char") return new Range(pos, pos) if (unit == "word") return cm.findWordAt(pos) if (unit == "line") return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) let result = unit(cm, pos) return new Range(result.from, result.to) } // Normal selection, as opposed to text dragging. function leftButtonSelect(cm, event, start, behavior) { if (ie) delayBlurEvent(cm) let display = cm.display, doc = cm.doc e_preventDefault(event) let ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges if (behavior.addNew && !behavior.extend) { ourIndex = doc.sel.contains(start) if (ourIndex > -1) ourRange = ranges[ourIndex] else ourRange = new Range(start, start) } else { ourRange = doc.sel.primary() ourIndex = doc.sel.primIndex } if (behavior.unit == "rectangle") { if (!behavior.addNew) ourRange = new Range(start, start) start = posFromMouse(cm, event, true, true) ourIndex = -1 } else { let range = rangeForUnit(cm, start, behavior.unit) if (behavior.extend) ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend) else ourRange = range } if (!behavior.addNew) { ourIndex = 0 setSelection(doc, new Selection([ourRange], 0), sel_mouse) startSel = doc.sel } else if (ourIndex == -1) { ourIndex = ranges.length setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), {scroll: false, origin: "*mouse"}) } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), {scroll: false, origin: "*mouse"}) startSel = doc.sel } else { replaceOneSelection(doc, ourIndex, ourRange, sel_mouse) } let lastPos = start function extendTo(pos) { if (cmp(lastPos, pos) == 0) return lastPos = pos if (behavior.unit == "rectangle") { let ranges = [], tabSize = cm.options.tabSize let startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize) let posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize) let left = Math.min(startCol, posCol), right = Math.max(startCol, posCol) for (let line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); line <= end; line++) { let text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize) if (left == right) ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))) else if (text.length > leftPos) ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))) } if (!ranges.length) ranges.push(new Range(start, start)) setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), {origin: "*mouse", scroll: false}) cm.scrollIntoView(pos) } else { let oldRange = ourRange let range = rangeForUnit(cm, pos, behavior.unit) let anchor = oldRange.anchor, head if (cmp(range.anchor, anchor) > 0) { head = range.head anchor = minPos(oldRange.from(), range.anchor) } else { head = range.anchor anchor = maxPos(oldRange.to(), range.head) } let ranges = startSel.ranges.slice(0) ranges[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head)) setSelection(doc, normalizeSelection(cm, ranges, ourIndex), sel_mouse) } } let editorSize = display.wrapper.getBoundingClientRect() // Used to ensure timeout re-tries don't fire when another extend // happened in the meantime (clearTimeout isn't reliable -- at // least on Chrome, the timeouts still happen even when cleared, // if the clear happens after their scheduled firing time). let counter = 0 function extend(e) { let curCount = ++counter let cur = posFromMouse(cm, e, true, behavior.unit == "rectangle") if (!cur) return if (cmp(cur, lastPos) != 0) { cm.curOp.focus = activeElt(root(cm)) extendTo(cur) let visible = visibleLines(display, doc) if (cur.line >= visible.to || cur.line < visible.from) setTimeout(operation(cm, () => {if (counter == curCount) extend(e)}), 150) } else { let outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0 if (outside) setTimeout(operation(cm, () => { if (counter != curCount) return display.scroller.scrollTop += outside extend(e) }), 50) } } function done(e) { cm.state.selectingText = false counter = Infinity // If e is null or undefined we interpret this as someone trying // to explicitly cancel the selection rather than the user // letting go of the mouse button. if (e) { e_preventDefault(e) display.input.focus() } off(display.wrapper.ownerDocument, "mousemove", move) off(display.wrapper.ownerDocument, "mouseup", up) doc.history.lastSelOrigin = null } let move = operation(cm, e => { if (e.buttons === 0 || !e_button(e)) done(e) else extend(e) }) let up = operation(cm, done) cm.state.selectingText = up on(display.wrapper.ownerDocument, "mousemove", move) on(display.wrapper.ownerDocument, "mouseup", up) } // Used when mouse-selecting to adjust the anchor to the proper side // of a bidi jump depending on the visual position of the head. function bidiSimplify(cm, range) { let {anchor, head} = range, anchorLine = getLine(cm.doc, anchor.line) if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) return range let order = getOrder(anchorLine) if (!order) return range let index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index] if (part.from != anchor.ch && part.to != anchor.ch) return range let boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1) if (boundary == 0 || boundary == order.length) return range // Compute the relative visual position of the head compared to the // anchor (<0 is to the left, >0 to the right) let leftSide if (head.line != anchor.line) { leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0 } else { let headIndex = getBidiPartAt(order, head.ch, head.sticky) let dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1) if (headIndex == boundary - 1 || headIndex == boundary) leftSide = dir < 0 else leftSide = dir > 0 } let usePart = order[boundary + (leftSide ? -1 : 0)] let from = leftSide == (usePart.level == 1) let ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before" return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) } // Determines whether an event happened in the gutter, and fires the // handlers for the corresponding event. function gutterEvent(cm, e, type, prevent) { let mX, mY if (e.touches) { mX = e.touches[0].clientX mY = e.touches[0].clientY } else { try { mX = e.clientX; mY = e.clientY } catch(e) { return false } } if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false if (prevent) e_preventDefault(e) let display = cm.display let lineBox = display.lineDiv.getBoundingClientRect() if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e) mY -= lineBox.top - display.viewOffset for (let i = 0; i < cm.display.gutterSpecs.length; ++i) { let g = display.gutters.childNodes[i] if (g && g.getBoundingClientRect().right >= mX) { let line = lineAtHeight(cm.doc, mY) let gutter = cm.display.gutterSpecs[i] signal(cm, type, cm, line, gutter.className, e) return e_defaultPrevented(e) } } } export function clickInGutter(cm, e) { return gutterEvent(cm, e, "gutterClick", true) } // CONTEXT MENU HANDLING // To make the context menu work, we need to briefly unhide the // textarea (making it as unobtrusive as possible) to let the // right-click take effect on it. export function onContextMenu(cm, e) { if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return if (signalDOMEvent(cm, e, "contextmenu")) return if (!captureRightClick) cm.display.input.onContextMenu(e) } function contextMenuInGutter(cm, e) { if (!hasHandler(cm, "gutterContextMenu")) return false return gutterEvent(cm, e, "gutterContextMenu", false) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/options.js ================================================ import { onBlur } from "../display/focus.js" import { getGutters, updateGutters } from "../display/gutters.js" import { loadMode, resetModeState } from "../display/mode_state.js" import { initScrollbars, updateScrollbars } from "../display/scrollbars.js" import { updateSelection } from "../display/selection.js" import { regChange } from "../display/view_tracking.js" import { getKeyMap } from "../input/keymap.js" import { defaultSpecialCharPlaceholder } from "../line/line_data.js" import { Pos } from "../line/pos.js" import { findMaxLine } from "../line/spans.js" import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement.js" import { replaceRange } from "../model/changes.js" import { mobile, windows } from "../util/browser.js" import { addClass, rmClass } from "../util/dom.js" import { off, on } from "../util/event.js" import { themeChanged } from "./utils.js" export let Init = {toString: function(){return "CodeMirror.Init"}} export let defaults = {} export let optionHandlers = {} export function defineOptions(CodeMirror) { let optionHandlers = CodeMirror.optionHandlers function option(name, deflt, handle, notOnInit) { CodeMirror.defaults[name] = deflt if (handle) optionHandlers[name] = notOnInit ? (cm, val, old) => {if (old != Init) handle(cm, val, old)} : handle } CodeMirror.defineOption = option // Passed to option handlers when there is no old value. CodeMirror.Init = Init // These two are, on init, called from the constructor because they // have to be initialized before the editor can start at all. option("value", "", (cm, val) => cm.setValue(val), true) option("mode", null, (cm, val) => { cm.doc.modeOption = val loadMode(cm) }, true) option("indentUnit", 2, loadMode, true) option("indentWithTabs", false) option("smartIndent", true) option("tabSize", 4, cm => { resetModeState(cm) clearCaches(cm) regChange(cm) }, true) option("lineSeparator", null, (cm, val) => { cm.doc.lineSep = val if (!val) return let newBreaks = [], lineNo = cm.doc.first cm.doc.iter(line => { for (let pos = 0;;) { let found = line.text.indexOf(val, pos) if (found == -1) break pos = found + val.length newBreaks.push(Pos(lineNo, found)) } lineNo++ }) for (let i = newBreaks.length - 1; i >= 0; i--) replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) }) option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g, (cm, val, old) => { cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") if (old != Init) cm.refresh() }) option("specialCharPlaceholder", defaultSpecialCharPlaceholder, cm => cm.refresh(), true) option("electricChars", true) option("inputStyle", mobile ? "contenteditable" : "textarea", () => { throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME }, true) option("spellcheck", false, (cm, val) => cm.getInputField().spellcheck = val, true) option("autocorrect", false, (cm, val) => cm.getInputField().autocorrect = val, true) option("autocapitalize", false, (cm, val) => cm.getInputField().autocapitalize = val, true) option("rtlMoveVisually", !windows) option("wholeLineUpdateBefore", true) option("theme", "default", cm => { themeChanged(cm) updateGutters(cm) }, true) option("keyMap", "default", (cm, val, old) => { let next = getKeyMap(val) let prev = old != Init && getKeyMap(old) if (prev && prev.detach) prev.detach(cm, next) if (next.attach) next.attach(cm, prev || null) }) option("extraKeys", null) option("configureMouse", null) option("lineWrapping", false, wrappingChanged, true) option("gutters", [], (cm, val) => { cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers) updateGutters(cm) }, true) option("fixedGutter", true, (cm, val) => { cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" cm.refresh() }, true) option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true) option("scrollbarStyle", "native", cm => { initScrollbars(cm) updateScrollbars(cm) cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) }, true) option("lineNumbers", false, (cm, val) => { cm.display.gutterSpecs = getGutters(cm.options.gutters, val) updateGutters(cm) }, true) option("firstLineNumber", 1, updateGutters, true) option("lineNumberFormatter", integer => integer, updateGutters, true) option("showCursorWhenSelecting", false, updateSelection, true) option("resetSelectionOnContextMenu", true) option("lineWiseCopyCut", true) option("pasteLinesPerSelection", true) option("selectionsMayTouch", false) option("readOnly", false, (cm, val) => { if (val == "nocursor") { onBlur(cm) cm.display.input.blur() } cm.display.input.readOnlyChanged(val) }) option("screenReaderLabel", null, (cm, val) => { val = (val === '') ? null : val cm.display.input.screenReaderLabelChanged(val) }) option("disableInput", false, (cm, val) => {if (!val) cm.display.input.reset()}, true) option("dragDrop", true, dragDropChanged) option("allowDropFileTypes", null) option("cursorBlinkRate", 530) option("cursorScrollMargin", 0) option("cursorHeight", 1, updateSelection, true) option("singleCursorHeightPerLine", true, updateSelection, true) option("workTime", 100) option("workDelay", 100) option("flattenSpans", true, resetModeState, true) option("addModeClass", false, resetModeState, true) option("pollInterval", 100) option("undoDepth", 200, (cm, val) => cm.doc.history.undoDepth = val) option("historyEventDelay", 1250) option("viewportMargin", 10, cm => cm.refresh(), true) option("maxHighlightLength", 10000, resetModeState, true) option("moveInputWithCursor", true, (cm, val) => { if (!val) cm.display.input.resetPosition() }) option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "") option("autofocus", null) option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true) option("phrases", null) } function dragDropChanged(cm, value, old) { let wasOn = old && old != Init if (!value != !wasOn) { let funcs = cm.display.dragFunctions let toggle = value ? on : off toggle(cm.display.scroller, "dragstart", funcs.start) toggle(cm.display.scroller, "dragenter", funcs.enter) toggle(cm.display.scroller, "dragover", funcs.over) toggle(cm.display.scroller, "dragleave", funcs.leave) toggle(cm.display.scroller, "drop", funcs.drop) } } function wrappingChanged(cm) { if (cm.options.lineWrapping) { addClass(cm.display.wrapper, "CodeMirror-wrap") cm.display.sizer.style.minWidth = "" cm.display.sizerWidth = null } else { rmClass(cm.display.wrapper, "CodeMirror-wrap") findMaxLine(cm) } estimateLineHeights(cm) regChange(cm) clearCaches(cm) setTimeout(() => updateScrollbars(cm), 100) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/edit/utils.js ================================================ import { clearCaches } from "../measurement/position_measurement.js" export function themeChanged(cm) { cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-") clearCaches(cm) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/ContentEditableInput.js ================================================ import { operation, runInOp } from "../display/operations.js" import { prepareSelection } from "../display/selection.js" import { regChange } from "../display/view_tracking.js" import { applyTextInput, copyableRanges, disableBrowserMagic, handlePaste, hiddenTextarea, lastCopied, setLastCopied } from "./input.js" import { cmp, maxPos, minPos, Pos } from "../line/pos.js" import { getBetween, getLine, lineNo } from "../line/utils_line.js" import { findViewForLine, findViewIndex, mapFromLineView, nodeAndOffsetInLineMap } from "../measurement/position_measurement.js" import { replaceRange } from "../model/changes.js" import { simpleSelection } from "../model/selection.js" import { setSelection } from "../model/selection_updates.js" import { getBidiPartAt, getOrder } from "../util/bidi.js" import { android, chrome, gecko, ie_version } from "../util/browser.js" import { activeElt, contains, range, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js" import { on, signalDOMEvent } from "../util/event.js" import { Delayed, lst, sel_dontScroll } from "../util/misc.js" // CONTENTEDITABLE INPUT STYLE export default class ContentEditableInput { constructor(cm) { this.cm = cm this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null this.polling = new Delayed() this.composing = null this.gracePeriod = false this.readDOMTimeout = null } init(display) { let input = this, cm = input.cm let div = input.div = display.lineDiv div.contentEditable = true disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize) function belongsToInput(e) { for (let t = e.target; t; t = t.parentNode) { if (t == div) return true if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) break } return false } on(div, "paste", e => { if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) return // IE doesn't fire input events, so we schedule a read for the pasted content in this way if (ie_version <= 11) setTimeout(operation(cm, () => this.updateFromDOM()), 20) }) on(div, "compositionstart", e => { this.composing = {data: e.data, done: false} }) on(div, "compositionupdate", e => { if (!this.composing) this.composing = {data: e.data, done: false} }) on(div, "compositionend", e => { if (this.composing) { if (e.data != this.composing.data) this.readFromDOMSoon() this.composing.done = true } }) on(div, "touchstart", () => input.forceCompositionEnd()) on(div, "input", () => { if (!this.composing) this.readFromDOMSoon() }) function onCopyCut(e) { if (!belongsToInput(e) || signalDOMEvent(cm, e)) return if (cm.somethingSelected()) { setLastCopied({lineWise: false, text: cm.getSelections()}) if (e.type == "cut") cm.replaceSelection("", null, "cut") } else if (!cm.options.lineWiseCopyCut) { return } else { let ranges = copyableRanges(cm) setLastCopied({lineWise: true, text: ranges.text}) if (e.type == "cut") { cm.operation(() => { cm.setSelections(ranges.ranges, 0, sel_dontScroll) cm.replaceSelection("", null, "cut") }) } } if (e.clipboardData) { e.clipboardData.clearData() let content = lastCopied.text.join("\n") // iOS exposes the clipboard API, but seems to discard content inserted into it e.clipboardData.setData("Text", content) if (e.clipboardData.getData("Text") == content) { e.preventDefault() return } } // Old-fashioned briefly-focus-a-textarea hack let kludge = hiddenTextarea(), te = kludge.firstChild disableBrowserMagic(te) cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) te.value = lastCopied.text.join("\n") let hadFocus = activeElt(rootNode(div)) selectInput(te) setTimeout(() => { cm.display.lineSpace.removeChild(kludge) hadFocus.focus() if (hadFocus == div) input.showPrimarySelection() }, 50) } on(div, "copy", onCopyCut) on(div, "cut", onCopyCut) } screenReaderLabelChanged(label) { // Label for screenreaders, accessibility if(label) { this.div.setAttribute('aria-label', label) } else { this.div.removeAttribute('aria-label') } } prepareSelection() { let result = prepareSelection(this.cm, false) result.focus = activeElt(rootNode(this.div)) == this.div return result } showSelection(info, takeFocus) { if (!info || !this.cm.display.view.length) return if (info.focus || takeFocus) this.showPrimarySelection() this.showMultipleSelections(info) } getSelection() { return this.cm.display.wrapper.ownerDocument.getSelection() } showPrimarySelection() { let sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() let from = prim.from(), to = prim.to() if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { sel.removeAllRanges() return } let curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) let curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && cmp(minPos(curAnchor, curFocus), from) == 0 && cmp(maxPos(curAnchor, curFocus), to) == 0) return let view = cm.display.view let start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || {node: view[0].measure.map[2], offset: 0} let end = to.line < cm.display.viewTo && posToDOM(cm, to) if (!end) { let measure = view[view.length - 1].measure let map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} } if (!start || !end) { sel.removeAllRanges() return } let old = sel.rangeCount && sel.getRangeAt(0), rng try { rng = range(start.node, start.offset, end.offset, end.node) } catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible if (rng) { if (!gecko && cm.state.focused) { sel.collapse(start.node, start.offset) if (!rng.collapsed) { sel.removeAllRanges() sel.addRange(rng) } } else { sel.removeAllRanges() sel.addRange(rng) } if (old && sel.anchorNode == null) sel.addRange(old) else if (gecko) this.startGracePeriod() } this.rememberSelection() } startGracePeriod() { clearTimeout(this.gracePeriod) this.gracePeriod = setTimeout(() => { this.gracePeriod = false if (this.selectionChanged()) this.cm.operation(() => this.cm.curOp.selectionChanged = true) }, 20) } showMultipleSelections(info) { removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) } rememberSelection() { let sel = this.getSelection() this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset } selectionInEditor() { let sel = this.getSelection() if (!sel.rangeCount) return false let node = sel.getRangeAt(0).commonAncestorContainer return contains(this.div, node) } focus() { if (this.cm.options.readOnly != "nocursor") { if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div) this.showSelection(this.prepareSelection(), true) this.div.focus() } } blur() { this.div.blur() } getField() { return this.div } supportsTouch() { return true } receivedFocus() { let input = this if (this.selectionInEditor()) setTimeout(() => this.pollSelection(), 20) else runInOp(this.cm, () => input.cm.curOp.selectionChanged = true) function poll() { if (input.cm.state.focused) { input.pollSelection() input.polling.set(input.cm.options.pollInterval, poll) } } this.polling.set(this.cm.options.pollInterval, poll) } selectionChanged() { let sel = this.getSelection() return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset } pollSelection() { if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) return let sel = this.getSelection(), cm = this.cm // On Android Chrome (version 56, at least), backspacing into an // uneditable block element will put the cursor in that element, // and then, because it's not editable, hide the virtual keyboard. // Because Android doesn't allow us to actually detect backspace // presses in a sane way, this code checks for when that happens // and simulates a backspace press in this case. if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) { this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) this.blur() this.focus() return } if (this.composing) return this.rememberSelection() let anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) let head = domToPos(cm, sel.focusNode, sel.focusOffset) if (anchor && head) runInOp(cm, () => { setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) if (anchor.bad || head.bad) cm.curOp.selectionChanged = true }) } pollContent() { if (this.readDOMTimeout != null) { clearTimeout(this.readDOMTimeout) this.readDOMTimeout = null } let cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() let from = sel.from(), to = sel.to() if (from.ch == 0 && from.line > cm.firstLine()) from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) to = Pos(to.line + 1, 0) if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false let fromIndex, fromLine, fromNode if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { fromLine = lineNo(display.view[0].line) fromNode = display.view[0].node } else { fromLine = lineNo(display.view[fromIndex].line) fromNode = display.view[fromIndex - 1].node.nextSibling } let toIndex = findViewIndex(cm, to.line) let toLine, toNode if (toIndex == display.view.length - 1) { toLine = display.viewTo - 1 toNode = display.lineDiv.lastChild } else { toLine = lineNo(display.view[toIndex + 1].line) - 1 toNode = display.view[toIndex + 1].node.previousSibling } if (!fromNode) return false let newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) let oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) while (newText.length > 1 && oldText.length > 1) { if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } else break } let cutFront = 0, cutEnd = 0 let newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) ++cutFront let newBot = lst(newText), oldBot = lst(oldText) let maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), oldBot.length - (oldText.length == 1 ? cutFront : 0)) while (cutEnd < maxCutEnd && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) ++cutEnd // Try to move start of change to start of selection if ambiguous if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { while (cutFront && cutFront > from.ch && newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { cutFront-- cutEnd++ } } newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") let chFrom = Pos(fromLine, cutFront) let chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { replaceRange(cm.doc, newText, chFrom, chTo, "+input") return true } } ensurePolled() { this.forceCompositionEnd() } reset() { this.forceCompositionEnd() } forceCompositionEnd() { if (!this.composing) return clearTimeout(this.readDOMTimeout) this.composing = null this.updateFromDOM() this.div.blur() this.div.focus() } readFromDOMSoon() { if (this.readDOMTimeout != null) return this.readDOMTimeout = setTimeout(() => { this.readDOMTimeout = null if (this.composing) { if (this.composing.done) this.composing = null else return } this.updateFromDOM() }, 80) } updateFromDOM() { if (this.cm.isReadOnly() || !this.pollContent()) runInOp(this.cm, () => regChange(this.cm)) } setUneditable(node) { node.contentEditable = "false" } onKeyPress(e) { if (e.charCode == 0 || this.composing) return e.preventDefault() if (!this.cm.isReadOnly()) operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) } readOnlyChanged(val) { this.div.contentEditable = String(val != "nocursor") } onContextMenu() {} resetPosition() {} } ContentEditableInput.prototype.needsContentAttribute = true function posToDOM(cm, pos) { let view = findViewForLine(cm, pos.line) if (!view || view.hidden) return null let line = getLine(cm.doc, pos.line) let info = mapFromLineView(view, line, pos.line) let order = getOrder(line, cm.doc.direction), side = "left" if (order) { let partPos = getBidiPartAt(order, pos.ch) side = partPos % 2 ? "right" : "left" } let result = nodeAndOffsetInLineMap(info.map, pos.ch, side) result.offset = result.collapse == "right" ? result.end : result.start return result } function isInGutter(node) { for (let scan = node; scan; scan = scan.parentNode) if (/CodeMirror-gutter-wrapper/.test(scan.className)) return true return false } function badPos(pos, bad) { if (bad) pos.bad = true; return pos } function domTextBetween(cm, from, to, fromLine, toLine) { let text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false function recognizeMarker(id) { return marker => marker.id == id } function close() { if (closing) { text += lineSep if (extraLinebreak) text += lineSep closing = extraLinebreak = false } } function addText(str) { if (str) { close() text += str } } function walk(node) { if (node.nodeType == 1) { let cmText = node.getAttribute("cm-text") if (cmText) { addText(cmText) return } let markerID = node.getAttribute("cm-marker"), range if (markerID) { let found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) if (found.length && (range = found[0].find(0))) addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) return } if (node.getAttribute("contenteditable") == "false") return let isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName) if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) return if (isBlock) close() for (let i = 0; i < node.childNodes.length; i++) walk(node.childNodes[i]) if (/^(pre|p)$/i.test(node.nodeName)) extraLinebreak = true if (isBlock) closing = true } else if (node.nodeType == 3) { addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")) } } for (;;) { walk(from) if (from == to) break from = from.nextSibling extraLinebreak = false } return text } function domToPos(cm, node, offset) { let lineNode if (node == cm.display.lineDiv) { lineNode = cm.display.lineDiv.childNodes[offset] if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) node = null; offset = 0 } else { for (lineNode = node;; lineNode = lineNode.parentNode) { if (!lineNode || lineNode == cm.display.lineDiv) return null if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break } } for (let i = 0; i < cm.display.view.length; i++) { let lineView = cm.display.view[i] if (lineView.node == lineNode) return locateNodeInLineView(lineView, node, offset) } } function locateNodeInLineView(lineView, node, offset) { let wrapper = lineView.text.firstChild, bad = false if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true) if (node == wrapper) { bad = true node = wrapper.childNodes[offset] offset = 0 if (!node) { let line = lineView.rest ? lst(lineView.rest) : lineView.line return badPos(Pos(lineNo(line), line.text.length), bad) } } let textNode = node.nodeType == 3 ? node : null, topNode = node if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { textNode = node.firstChild if (offset) offset = textNode.nodeValue.length } while (topNode.parentNode != wrapper) topNode = topNode.parentNode let measure = lineView.measure, maps = measure.maps function find(textNode, topNode, offset) { for (let i = -1; i < (maps ? maps.length : 0); i++) { let map = i < 0 ? measure.map : maps[i] for (let j = 0; j < map.length; j += 3) { let curNode = map[j + 2] if (curNode == textNode || curNode == topNode) { let line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) let ch = map[j] + offset if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)] return Pos(line, ch) } } } } let found = find(textNode, topNode, offset) if (found) return badPos(found, bad) // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems for (let after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { found = find(after, after.firstChild, 0) if (found) return badPos(Pos(found.line, found.ch - dist), bad) else dist += after.textContent.length } for (let before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { found = find(before, before.firstChild, -1) if (found) return badPos(Pos(found.line, found.ch + dist), bad) else dist += before.textContent.length } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/TextareaInput.js ================================================ import { operation, runInOp } from "../display/operations.js" import { prepareSelection } from "../display/selection.js" import { applyTextInput, copyableRanges, handlePaste, hiddenTextarea, disableBrowserMagic, setLastCopied } from "./input.js" import { cursorCoords, posFromMouse } from "../measurement/position_measurement.js" import { eventInWidget } from "../measurement/widgets.js" import { simpleSelection } from "../model/selection.js" import { selectAll, setSelection } from "../model/selection_updates.js" import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js" import { activeElt, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js" import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js" import { hasSelection } from "../util/feature_detection.js" import { Delayed, sel_dontScroll } from "../util/misc.js" // TEXTAREA INPUT STYLE export default class TextareaInput { constructor(cm) { this.cm = cm // See input.poll and input.reset this.prevInput = "" // Flag that indicates whether we expect input to appear real soon // now (after some event like 'keypress' or 'input') and are // polling intensively. this.pollingFast = false // Self-resetting timeout for the poller this.polling = new Delayed() // Used to work around IE issue with selection being forgotten when focus moves away from textarea this.hasSelection = false this.composing = null this.resetting = false } init(display) { let input = this, cm = this.cm this.createField(display) const te = this.textarea display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild) // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) if (ios) te.style.width = "0px" on(te, "input", () => { if (ie && ie_version >= 9 && this.hasSelection) this.hasSelection = null input.poll() }) on(te, "paste", e => { if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return cm.state.pasteIncoming = +new Date input.fastPoll() }) function prepareCopyCut(e) { if (signalDOMEvent(cm, e)) return if (cm.somethingSelected()) { setLastCopied({lineWise: false, text: cm.getSelections()}) } else if (!cm.options.lineWiseCopyCut) { return } else { let ranges = copyableRanges(cm) setLastCopied({lineWise: true, text: ranges.text}) if (e.type == "cut") { cm.setSelections(ranges.ranges, null, sel_dontScroll) } else { input.prevInput = "" te.value = ranges.text.join("\n") selectInput(te) } } if (e.type == "cut") cm.state.cutIncoming = +new Date } on(te, "cut", prepareCopyCut) on(te, "copy", prepareCopyCut) on(display.scroller, "paste", e => { if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return if (!te.dispatchEvent) { cm.state.pasteIncoming = +new Date input.focus() return } // Pass the `paste` event to the textarea so it's handled by its event listener. const event = new Event("paste") event.clipboardData = e.clipboardData te.dispatchEvent(event) }) // Prevent normal selection in the editor (we handle our own) on(display.lineSpace, "selectstart", e => { if (!eventInWidget(display, e)) e_preventDefault(e) }) on(te, "compositionstart", () => { let start = cm.getCursor("from") if (input.composing) input.composing.range.clear() input.composing = { start: start, range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) } }) on(te, "compositionend", () => { if (input.composing) { input.poll() input.composing.range.clear() input.composing = null } }) } createField(_display) { // Wraps and hides input textarea this.wrapper = hiddenTextarea() // The semihidden textarea that is focused when the editor is // focused, and receives input. this.textarea = this.wrapper.firstChild let opts = this.cm.options disableBrowserMagic(this.textarea, opts.spellcheck, opts.autocorrect, opts.autocapitalize) } screenReaderLabelChanged(label) { // Label for screenreaders, accessibility if(label) { this.textarea.setAttribute('aria-label', label) } else { this.textarea.removeAttribute('aria-label') } } prepareSelection() { // Redraw the selection and/or cursor let cm = this.cm, display = cm.display, doc = cm.doc let result = prepareSelection(cm) // Move the hidden textarea near the cursor to prevent scrolling artifacts if (cm.options.moveInputWithCursor) { let headPos = cursorCoords(cm, doc.sel.primary().head, "div") let wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, headPos.top + lineOff.top - wrapOff.top)) result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, headPos.left + lineOff.left - wrapOff.left)) } return result } showSelection(drawn) { let cm = this.cm, display = cm.display removeChildrenAndAdd(display.cursorDiv, drawn.cursors) removeChildrenAndAdd(display.selectionDiv, drawn.selection) if (drawn.teTop != null) { this.wrapper.style.top = drawn.teTop + "px" this.wrapper.style.left = drawn.teLeft + "px" } } // Reset the input to correspond to the selection (or to be empty, // when not typing and nothing is selected) reset(typing) { if (this.contextMenuPending || this.composing && typing) return let cm = this.cm this.resetting = true if (cm.somethingSelected()) { this.prevInput = "" let content = cm.getSelection() this.textarea.value = content if (cm.state.focused) selectInput(this.textarea) if (ie && ie_version >= 9) this.hasSelection = content } else if (!typing) { this.prevInput = this.textarea.value = "" if (ie && ie_version >= 9) this.hasSelection = null } this.resetting = false } getField() { return this.textarea } supportsTouch() { return false } focus() { if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(rootNode(this.textarea)) != this.textarea)) { try { this.textarea.focus() } catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM } } blur() { this.textarea.blur() } resetPosition() { this.wrapper.style.top = this.wrapper.style.left = 0 } receivedFocus() { this.slowPoll() } // Poll for input changes, using the normal rate of polling. This // runs as long as the editor is focused. slowPoll() { if (this.pollingFast) return this.polling.set(this.cm.options.pollInterval, () => { this.poll() if (this.cm.state.focused) this.slowPoll() }) } // When an event has just come in that is likely to add or change // something in the input textarea, we poll faster, to ensure that // the change appears on the screen quickly. fastPoll() { let missed = false, input = this input.pollingFast = true function p() { let changed = input.poll() if (!changed && !missed) {missed = true; input.polling.set(60, p)} else {input.pollingFast = false; input.slowPoll()} } input.polling.set(20, p) } // Read input from the textarea, and update the document to match. // When something is selected, it is present in the textarea, and // selected (unless it is huge, in which case a placeholder is // used). When nothing is selected, the cursor sits after previously // seen text (can be empty), which is stored in prevInput (we must // not reset the textarea when typing, because that breaks IME). poll() { let cm = this.cm, input = this.textarea, prevInput = this.prevInput // Since this is called a *lot*, try to bail out as cheaply as // possible when it is clear that nothing happened. hasSelection // will be the case when there is a lot of text in the textarea, // in which case reading its value would be expensive. if (this.contextMenuPending || this.resetting || !cm.state.focused || (hasSelection(input) && !prevInput && !this.composing) || cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) return false let text = input.value // If nothing changed, bail. if (text == prevInput && !cm.somethingSelected()) return false // Work around nonsensical selection resetting in IE9/10, and // inexplicable appearance of private area unicode characters on // some key combos in Mac (#2689). if (ie && ie_version >= 9 && this.hasSelection === text || mac && /[\uf700-\uf7ff]/.test(text)) { cm.display.input.reset() return false } if (cm.doc.sel == cm.display.selForContextMenu) { let first = text.charCodeAt(0) if (first == 0x200b && !prevInput) prevInput = "\u200b" if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } } // Find the part of the input that is actually new let same = 0, l = Math.min(prevInput.length, text.length) while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same runInOp(cm, () => { applyTextInput(cm, text.slice(same), prevInput.length - same, null, this.composing ? "*compose" : null) // Don't leave long text in the textarea, since it makes further polling slow if (text.length > 1000 || text.indexOf("\n") > -1) input.value = this.prevInput = "" else this.prevInput = text if (this.composing) { this.composing.range.clear() this.composing.range = cm.markText(this.composing.start, cm.getCursor("to"), {className: "CodeMirror-composing"}) } }) return true } ensurePolled() { if (this.pollingFast && this.poll()) this.pollingFast = false } onKeyPress() { if (ie && ie_version >= 9) this.hasSelection = null this.fastPoll() } onContextMenu(e) { let input = this, cm = input.cm, display = cm.display, te = input.textarea if (input.contextMenuPending) input.contextMenuPending() let pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop if (!pos || presto) return // Opera is difficult. // Reset the current text selection only if the click is done outside of the selection // and 'resetSelectionOnContextMenu' option is true. let reset = cm.options.resetSelectionOnContextMenu if (reset && cm.doc.sel.contains(pos) == -1) operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) let oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText let wrapperBox = input.wrapper.offsetParent.getBoundingClientRect() input.wrapper.style.cssText = "position: static" te.style.cssText = `position: absolute; width: 30px; height: 30px; top: ${e.clientY - wrapperBox.top - 5}px; left: ${e.clientX - wrapperBox.left - 5}px; z-index: 1000; background: ${ie ? "rgba(255, 255, 255, .05)" : "transparent"}; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);` let oldScrollY if (webkit) oldScrollY = te.ownerDocument.defaultView.scrollY // Work around Chrome issue (#2712) display.input.focus() if (webkit) te.ownerDocument.defaultView.scrollTo(null, oldScrollY) display.input.reset() // Adds "Select all" to context menu in FF if (!cm.somethingSelected()) te.value = input.prevInput = " " input.contextMenuPending = rehide display.selForContextMenu = cm.doc.sel clearTimeout(display.detectingSelectAll) // Select-all will be greyed out if there's nothing to select, so // this adds a zero-width space so that we can later check whether // it got selected. function prepareSelectAllHack() { if (te.selectionStart != null) { let selected = cm.somethingSelected() let extval = "\u200b" + (selected ? te.value : "") te.value = "\u21da" // Used to catch context-menu undo te.value = extval input.prevInput = selected ? "" : "\u200b" te.selectionStart = 1; te.selectionEnd = extval.length // Re-set this, in case some other handler touched the // selection in the meantime. display.selForContextMenu = cm.doc.sel } } function rehide() { if (input.contextMenuPending != rehide) return input.contextMenuPending = false input.wrapper.style.cssText = oldWrapperCSS te.style.cssText = oldCSS if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) // Try to detect the user choosing select-all if (te.selectionStart != null) { if (!ie || (ie && ie_version < 9)) prepareSelectAllHack() let i = 0, poll = () => { if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && te.selectionEnd > 0 && input.prevInput == "\u200b") { operation(cm, selectAll)(cm) } else if (i++ < 10) { display.detectingSelectAll = setTimeout(poll, 500) } else { display.selForContextMenu = null display.input.reset() } } display.detectingSelectAll = setTimeout(poll, 200) } } if (ie && ie_version >= 9) prepareSelectAllHack() if (captureRightClick) { e_stop(e) let mouseup = () => { off(window, "mouseup", mouseup) setTimeout(rehide, 20) } on(window, "mouseup", mouseup) } else { setTimeout(rehide, 50) } } readOnlyChanged(val) { if (!val) this.reset() this.textarea.disabled = val == "nocursor" this.textarea.readOnly = !!val } setUneditable() {} } TextareaInput.prototype.needsContentAttribute = false ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/indent.js ================================================ import { getContextBefore } from "../line/highlight.js" import { Pos } from "../line/pos.js" import { getLine } from "../line/utils_line.js" import { replaceRange } from "../model/changes.js" import { Range } from "../model/selection.js" import { replaceOneSelection } from "../model/selection_updates.js" import { countColumn, Pass, spaceStr } from "../util/misc.js" // Indent the given line. The how parameter can be "smart", // "add"/null, "subtract", or "prev". When aggressive is false // (typically set to true for forced single-line indents), empty // lines are not indented, and places where the mode returns Pass // are left alone. export function indentLine(cm, n, how, aggressive) { let doc = cm.doc, state if (how == null) how = "add" if (how == "smart") { // Fall back to "prev" when the mode doesn't have an indentation // method. if (!doc.mode.indent) how = "prev" else state = getContextBefore(cm, n).state } let tabSize = cm.options.tabSize let line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize) if (line.stateAfter) line.stateAfter = null let curSpaceString = line.text.match(/^\s*/)[0], indentation if (!aggressive && !/\S/.test(line.text)) { indentation = 0 how = "not" } else if (how == "smart") { indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text) if (indentation == Pass || indentation > 150) { if (!aggressive) return how = "prev" } } if (how == "prev") { if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize) else indentation = 0 } else if (how == "add") { indentation = curSpace + cm.options.indentUnit } else if (how == "subtract") { indentation = curSpace - cm.options.indentUnit } else if (typeof how == "number") { indentation = curSpace + how } indentation = Math.max(0, indentation) let indentString = "", pos = 0 if (cm.options.indentWithTabs) for (let i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t"} if (pos < indentation) indentString += spaceStr(indentation - pos) if (indentString != curSpaceString) { replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input") line.stateAfter = null return true } else { // Ensure that, if the cursor was in the whitespace at the start // of the line, it is moved to the end of that space. for (let i = 0; i < doc.sel.ranges.length; i++) { let range = doc.sel.ranges[i] if (range.head.line == n && range.head.ch < curSpaceString.length) { let pos = Pos(n, curSpaceString.length) replaceOneSelection(doc, i, new Range(pos, pos)) break } } } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/input.js ================================================ import { runInOp } from "../display/operations.js" import { ensureCursorVisible } from "../display/scrolling.js" import { Pos } from "../line/pos.js" import { getLine } from "../line/utils_line.js" import { makeChange } from "../model/changes.js" import { ios, webkit } from "../util/browser.js" import { elt } from "../util/dom.js" import { lst, map } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" import { splitLinesAuto } from "../util/feature_detection.js" import { indentLine } from "./indent.js" // This will be set to a {lineWise: bool, text: [string]} object, so // that, when pasting, we know what kind of selections the copied // text was made out of. export let lastCopied = null export function setLastCopied(newLastCopied) { lastCopied = newLastCopied } export function applyTextInput(cm, inserted, deleted, sel, origin) { let doc = cm.doc cm.display.shift = false if (!sel) sel = doc.sel let recent = +new Date - 200 let paste = origin == "paste" || cm.state.pasteIncoming > recent let textLines = splitLinesAuto(inserted), multiPaste = null // When pasting N lines into N selections, insert one line per selection if (paste && sel.ranges.length > 1) { if (lastCopied && lastCopied.text.join("\n") == inserted) { if (sel.ranges.length % lastCopied.text.length == 0) { multiPaste = [] for (let i = 0; i < lastCopied.text.length; i++) multiPaste.push(doc.splitLines(lastCopied.text[i])) } } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { multiPaste = map(textLines, l => [l]) } } let updateInput = cm.curOp.updateInput // Normal behavior is to insert the new text into every selection for (let i = sel.ranges.length - 1; i >= 0; i--) { let range = sel.ranges[i] let from = range.from(), to = range.to() if (range.empty()) { if (deleted && deleted > 0) // Handle deletion from = Pos(from.line, from.ch - deleted) else if (cm.state.overwrite && !paste) // Handle overwrite to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)) else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == textLines.join("\n")) from = to = Pos(from.line, 0) } let changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")} makeChange(cm.doc, changeEvent) signalLater(cm, "inputRead", cm, changeEvent) } if (inserted && !paste) triggerElectric(cm, inserted) ensureCursorVisible(cm) if (cm.curOp.updateInput < 2) cm.curOp.updateInput = updateInput cm.curOp.typing = true cm.state.pasteIncoming = cm.state.cutIncoming = -1 } export function handlePaste(e, cm) { let pasted = e.clipboardData && e.clipboardData.getData("Text") if (pasted) { e.preventDefault() if (!cm.isReadOnly() && !cm.options.disableInput && cm.hasFocus()) runInOp(cm, () => applyTextInput(cm, pasted, 0, null, "paste")) return true } } export function triggerElectric(cm, inserted) { // When an 'electric' character is inserted, immediately trigger a reindent if (!cm.options.electricChars || !cm.options.smartIndent) return let sel = cm.doc.sel for (let i = sel.ranges.length - 1; i >= 0; i--) { let range = sel.ranges[i] if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue let mode = cm.getModeAt(range.head) let indented = false if (mode.electricChars) { for (let j = 0; j < mode.electricChars.length; j++) if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { indented = indentLine(cm, range.head.line, "smart") break } } else if (mode.electricInput) { if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) indented = indentLine(cm, range.head.line, "smart") } if (indented) signalLater(cm, "electricInput", cm, range.head.line) } } export function copyableRanges(cm) { let text = [], ranges = [] for (let i = 0; i < cm.doc.sel.ranges.length; i++) { let line = cm.doc.sel.ranges[i].head.line let lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)} ranges.push(lineRange) text.push(cm.getRange(lineRange.anchor, lineRange.head)) } return {text: text, ranges: ranges} } export function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { field.setAttribute("autocorrect", autocorrect ? "on" : "off") field.setAttribute("autocapitalize", autocapitalize ? "on" : "off") field.setAttribute("spellcheck", !!spellcheck) } export function hiddenTextarea() { let te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-height: 1em; outline: none") let div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;") // The textarea is kept positioned near the cursor to prevent the // fact that it'll be scrolled into view on input from scrolling // our fake cursor out of view. On webkit, when wrap=off, paste is // very slow. So make the area wide instead. if (webkit) te.style.width = "1000px" else te.setAttribute("wrap", "off") // If border: 0; -- iOS fails to open keyboard (issue #1287) if (ios) te.style.border = "1px solid black" return div } ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/keymap.js ================================================ import { flipCtrlCmd, mac, presto } from "../util/browser.js" import { map } from "../util/misc.js" import { keyNames } from "./keynames.js" export let keyMap = {} keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", "Esc": "singleSelection" } // Note that the save and find-related commands aren't defined by // default. User code or addons can define them. Unknown commands // are simply ignored. keyMap.pcDefault = { "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", "fallthrough": "basic" } // Very basic readline/emacs-style bindings, which are standard on Mac. keyMap.emacsy = { "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars", "Ctrl-O": "openLine" } keyMap.macDefault = { "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", "fallthrough": ["basic", "emacsy"] } keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault // KEYMAP DISPATCH function normalizeKeyName(name) { let parts = name.split(/-(?!$)/) name = parts[parts.length - 1] let alt, ctrl, shift, cmd for (let i = 0; i < parts.length - 1; i++) { let mod = parts[i] if (/^(cmd|meta|m)$/i.test(mod)) cmd = true else if (/^a(lt)?$/i.test(mod)) alt = true else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true else if (/^s(hift)?$/i.test(mod)) shift = true else throw new Error("Unrecognized modifier name: " + mod) } if (alt) name = "Alt-" + name if (ctrl) name = "Ctrl-" + name if (cmd) name = "Cmd-" + name if (shift) name = "Shift-" + name return name } // This is a kludge to keep keymaps mostly working as raw objects // (backwards compatibility) while at the same time support features // like normalization and multi-stroke key bindings. It compiles a // new normalized keymap, and then updates the old object to reflect // this. export function normalizeKeyMap(keymap) { let copy = {} for (let keyname in keymap) if (keymap.hasOwnProperty(keyname)) { let value = keymap[keyname] if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue if (value == "...") { delete keymap[keyname]; continue } let keys = map(keyname.split(" "), normalizeKeyName) for (let i = 0; i < keys.length; i++) { let val, name if (i == keys.length - 1) { name = keys.join(" ") val = value } else { name = keys.slice(0, i + 1).join(" ") val = "..." } let prev = copy[name] if (!prev) copy[name] = val else if (prev != val) throw new Error("Inconsistent bindings for " + name) } delete keymap[keyname] } for (let prop in copy) keymap[prop] = copy[prop] return keymap } export function lookupKey(key, map, handle, context) { map = getKeyMap(map) let found = map.call ? map.call(key, context) : map[key] if (found === false) return "nothing" if (found === "...") return "multi" if (found != null && handle(found)) return "handled" if (map.fallthrough) { if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") return lookupKey(key, map.fallthrough, handle, context) for (let i = 0; i < map.fallthrough.length; i++) { let result = lookupKey(key, map.fallthrough[i], handle, context) if (result) return result } } } // Modifier key presses don't count as 'real' key presses for the // purpose of keymap fallthrough. export function isModifierKey(value) { let name = typeof value == "string" ? value : keyNames[value.keyCode] return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" } export function addModifierNames(name, event, noShift) { let base = name if (event.altKey && base != "Alt") name = "Alt-" + name if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Mod") name = "Cmd-" + name if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name return name } // Look up the name of a key as indicated by an event object. export function keyName(event, noShift) { if (presto && event.keyCode == 34 && event["char"]) return false let name = keyNames[event.keyCode] if (name == null || event.altGraphKey) return false // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) if (event.keyCode == 3 && event.code) name = event.code return addModifierNames(name, event, noShift) } export function getKeyMap(val) { return typeof val == "string" ? keyMap[val] : val } ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/keynames.js ================================================ export let keyNames = { 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock", 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 224: "Mod", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" } // Number keys for (let i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i) // Alphabetic keys for (let i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i) // Function keys for (let i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i ================================================ FILE: public/assets/lib/vendor/codemirror/src/input/movement.js ================================================ import { Pos } from "../line/pos.js" import { prepareMeasureForLine, measureCharPrepared, wrappedLineExtentChar } from "../measurement/position_measurement.js" import { getBidiPartAt, getOrder } from "../util/bidi.js" import { findFirst, lst, skipExtendingChars } from "../util/misc.js" function moveCharLogically(line, ch, dir) { let target = skipExtendingChars(line.text, ch + dir, dir) return target < 0 || target > line.text.length ? null : target } export function moveLogically(line, start, dir) { let ch = moveCharLogically(line, start.ch, dir) return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") } export function endOfLine(visually, cm, lineObj, lineNo, dir) { if (visually) { if (cm.doc.direction == "rtl") dir = -dir let order = getOrder(lineObj, cm.doc.direction) if (order) { let part = dir < 0 ? lst(order) : order[0] let moveInStorageOrder = (dir < 0) == (part.level == 1) let sticky = moveInStorageOrder ? "after" : "before" let ch // With a wrapped rtl chunk (possibly spanning multiple bidi parts), // it could be that the last bidi part is not on the last visual line, // since visual lines contain content order-consecutive chunks. // Thus, in rtl, we are looking for the first (content-order) character // in the rtl chunk that is on the last line (that is, the same line // as the last (content-order) character). if (part.level > 0 || cm.doc.direction == "rtl") { let prep = prepareMeasureForLine(cm, lineObj) ch = dir < 0 ? lineObj.text.length - 1 : 0 let targetTop = measureCharPrepared(cm, prep, ch).top ch = findFirst(ch => measureCharPrepared(cm, prep, ch).top == targetTop, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch) if (sticky == "before") ch = moveCharLogically(lineObj, ch, 1) } else ch = dir < 0 ? part.to : part.from return new Pos(lineNo, ch, sticky) } } return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") } export function moveVisually(cm, line, start, dir) { let bidi = getOrder(line, cm.doc.direction) if (!bidi) return moveLogically(line, start, dir) if (start.ch >= line.text.length) { start.ch = line.text.length start.sticky = "before" } else if (start.ch <= 0) { start.ch = 0 start.sticky = "after" } let partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos] if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, // nothing interesting happens. return moveLogically(line, start, dir) } let mv = (pos, dir) => moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir) let prep let getWrappedLineExtent = ch => { if (!cm.options.lineWrapping) return {begin: 0, end: line.text.length} prep = prep || prepareMeasureForLine(cm, line) return wrappedLineExtentChar(cm, line, prep, ch) } let wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch) if (cm.doc.direction == "rtl" || part.level == 1) { let moveInStorageOrder = (part.level == 1) == (dir < 0) let ch = mv(start, moveInStorageOrder ? 1 : -1) if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { // Case 2: We move within an rtl part or in an rtl editor on the same visual line let sticky = moveInStorageOrder ? "before" : "after" return new Pos(start.line, ch, sticky) } } // Case 3: Could not move within this bidi part in this visual line, so leave // the current bidi part let searchInVisualLine = (partPos, dir, wrappedLineExtent) => { let getRes = (ch, moveInStorageOrder) => moveInStorageOrder ? new Pos(start.line, mv(ch, 1), "before") : new Pos(start.line, ch, "after") for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { let part = bidi[partPos] let moveInStorageOrder = (dir > 0) == (part.level != 1) let ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1) if (part.from <= ch && ch < part.to) return getRes(ch, moveInStorageOrder) ch = moveInStorageOrder ? part.from : mv(part.to, -1) if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) return getRes(ch, moveInStorageOrder) } } // Case 3a: Look for other bidi parts on the same visual line let res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent) if (res) return res // Case 3b: Look for other bidi parts on the next visual line let nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1) if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)) if (res) return res } // Case 4: Nowhere to move return null } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/highlight.js ================================================ import { countColumn } from "../util/misc.js" import { copyState, innerMode, startState } from "../modes.js" import StringStream from "../util/StringStream.js" import { getLine, lineNo } from "./utils_line.js" import { clipPos } from "./pos.js" class SavedContext { constructor(state, lookAhead) { this.state = state this.lookAhead = lookAhead } } class Context { constructor(doc, state, line, lookAhead) { this.state = state this.doc = doc this.line = line this.maxLookAhead = lookAhead || 0 this.baseTokens = null this.baseTokenPos = 1 } lookAhead(n) { let line = this.doc.getLine(this.line + n) if (line != null && n > this.maxLookAhead) this.maxLookAhead = n return line } baseToken(n) { if (!this.baseTokens) return null while (this.baseTokens[this.baseTokenPos] <= n) this.baseTokenPos += 2 let type = this.baseTokens[this.baseTokenPos + 1] return {type: type && type.replace(/( |^)overlay .*/, ""), size: this.baseTokens[this.baseTokenPos] - n} } nextLine() { this.line++ if (this.maxLookAhead > 0) this.maxLookAhead-- } static fromSaved(doc, saved, line) { if (saved instanceof SavedContext) return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) else return new Context(doc, copyState(doc.mode, saved), line) } save(copy) { let state = copy !== false ? copyState(this.doc.mode, this.state) : this.state return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state } } // Compute a style array (an array starting with a mode generation // -- for invalidation -- followed by pairs of end positions and // style strings), which is used to highlight the tokens on the // line. export function highlightLine(cm, line, context, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). let st = [cm.state.modeGen], lineClasses = {} // Compute the base array of styles runMode(cm, line.text, cm.doc.mode, context, (end, style) => st.push(end, style), lineClasses, forceToEnd) let state = context.state // Run overlays, adjust style array. for (let o = 0; o < cm.state.overlays.length; ++o) { context.baseTokens = st let overlay = cm.state.overlays[o], i = 1, at = 0 context.state = true runMode(cm, line.text, overlay.mode, context, (end, style) => { let start = i // Ensure there's a token end at the current position, and that i points at it while (at < end) { let i_end = st[i] if (i_end > end) st.splice(i, 1, end, st[i+1], i_end) i += 2 at = Math.min(end, i_end) } if (!style) return if (overlay.opaque) { st.splice(start, i - start, end, "overlay " + style) i = start + 2 } else { for (; start < i; start += 2) { let cur = st[start+1] st[start+1] = (cur ? cur + " " : "") + "overlay " + style } } }, lineClasses) context.state = state context.baseTokens = null context.baseTokenPos = 1 } return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} } export function getLineStyles(cm, line, updateFrontier) { if (!line.styles || line.styles[0] != cm.state.modeGen) { let context = getContextBefore(cm, lineNo(line)) let resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) let result = highlightLine(cm, line, context) if (resetState) context.state = resetState line.stateAfter = context.save(!resetState) line.styles = result.styles if (result.classes) line.styleClasses = result.classes else if (line.styleClasses) line.styleClasses = null if (updateFrontier === cm.doc.highlightFrontier) cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } return line.styles } export function getContextBefore(cm, n, precise) { let doc = cm.doc, display = cm.display if (!doc.mode.startState) return new Context(doc, true, n) let start = findStartLine(cm, n, precise) let saved = start > doc.first && getLine(doc, start - 1).stateAfter let context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) doc.iter(start, n, line => { processLine(cm, line.text, context) let pos = context.line line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null context.nextLine() }) if (precise) doc.modeFrontier = context.line return context } // Lightweight form of highlight -- proceed over this line and // update state, but don't save a style array. Used for lines that // aren't currently visible. export function processLine(cm, text, context, startAt) { let mode = cm.doc.mode let stream = new StringStream(text, cm.options.tabSize, context) stream.start = stream.pos = startAt || 0 if (text == "") callBlankLine(mode, context.state) while (!stream.eol()) { readToken(mode, stream, context.state) stream.start = stream.pos } } function callBlankLine(mode, state) { if (mode.blankLine) return mode.blankLine(state) if (!mode.innerMode) return let inner = innerMode(mode, state) if (inner.mode.blankLine) return inner.mode.blankLine(inner.state) } function readToken(mode, stream, state, inner) { for (let i = 0; i < 10; i++) { if (inner) inner[0] = innerMode(mode, state).mode let style = mode.token(stream, state) if (stream.pos > stream.start) return style } throw new Error("Mode " + mode.name + " failed to advance stream.") } class Token { constructor(stream, type, state) { this.start = stream.start; this.end = stream.pos this.string = stream.current() this.type = type || null this.state = state } } // Utility for getTokenAt and getLineTokens export function takeToken(cm, pos, precise, asArray) { let doc = cm.doc, mode = doc.mode, style pos = clipPos(doc, pos) let line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) let stream = new StringStream(line.text, cm.options.tabSize, context), tokens if (asArray) tokens = [] while ((asArray || stream.pos < pos.ch) && !stream.eol()) { stream.start = stream.pos style = readToken(mode, stream, context.state) if (asArray) tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) } return asArray ? tokens : new Token(stream, style, context.state) } function extractLineClasses(type, output) { if (type) for (;;) { let lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) if (!lineClass) break type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) let prop = lineClass[1] ? "bgClass" : "textClass" if (output[prop] == null) output[prop] = lineClass[2] else if (!(new RegExp("(?:^|\\s)" + lineClass[2] + "(?:$|\\s)")).test(output[prop])) output[prop] += " " + lineClass[2] } return type } // Run the given mode's parser over a line, calling f for each token. function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { let flattenSpans = mode.flattenSpans if (flattenSpans == null) flattenSpans = cm.options.flattenSpans let curStart = 0, curStyle = null let stream = new StringStream(text, cm.options.tabSize, context), style let inner = cm.options.addModeClass && [null] if (text == "") extractLineClasses(callBlankLine(mode, context.state), lineClasses) while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false if (forceToEnd) processLine(cm, text, context, stream.pos) stream.pos = text.length style = null } else { style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) } if (inner) { let mName = inner[0].name if (mName) style = "m-" + (style ? mName + " " + style : mName) } if (!flattenSpans || curStyle != style) { while (curStart < stream.start) { curStart = Math.min(stream.start, curStart + 5000) f(curStart, curStyle) } curStyle = style } stream.start = stream.pos } while (curStart < stream.pos) { // Webkit seems to refuse to render text nodes longer than 57444 // characters, and returns inaccurate measurements in nodes // starting around 5000 chars. let pos = Math.min(stream.pos, curStart + 5000) f(pos, curStyle) curStart = pos } } // Finds the line to start with when starting a parse. Tries to // find a line with a stateAfter, so that it can start with a // valid state. If that fails, it returns the line with the // smallest indentation, which tends to need the least context to // parse correctly. function findStartLine(cm, n, precise) { let minindent, minline, doc = cm.doc let lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) for (let search = n; search > lim; --search) { if (search <= doc.first) return doc.first let line = getLine(doc, search - 1), after = line.stateAfter if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) return search let indented = countColumn(line.text, null, cm.options.tabSize) if (minline == null || minindent > indented) { minline = search - 1 minindent = indented } } return minline } export function retreatFrontier(doc, n) { doc.modeFrontier = Math.min(doc.modeFrontier, n) if (doc.highlightFrontier < n - 10) return let start = doc.first for (let line = n - 1; line > start; line--) { let saved = getLine(doc, line).stateAfter // change is on 3 // state on line 1 looked ahead 2 -- so saw 3 // test 1 + 2 < 3 should cover this if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { start = line + 1 break } } doc.highlightFrontier = Math.min(doc.highlightFrontier, start) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/line_data.js ================================================ import { getOrder } from "../util/bidi.js" import { ie, ie_version, webkit } from "../util/browser.js" import { elt, eltP, joinClasses } from "../util/dom.js" import { eventMixin, signal } from "../util/event.js" import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js" import { lst, spaceStr } from "../util/misc.js" import { getLineStyles } from "./highlight.js" import { attachMarkedSpans, compareCollapsedMarkers, detachMarkedSpans, lineIsHidden, visualLineContinued } from "./spans.js" import { getLine, lineNo, updateLineHeight } from "./utils_line.js" // LINE DATA STRUCTURE // Line objects. These hold state related to a line, including // highlighting info (the styles array). export class Line { constructor(text, markedSpans, estimateHeight) { this.text = text attachMarkedSpans(this, markedSpans) this.height = estimateHeight ? estimateHeight(this) : 1 } lineNo() { return lineNo(this) } } eventMixin(Line) // Change the content (text, markers) of a line. Automatically // invalidates cached information and tries to re-estimate the // line's height. export function updateLine(line, text, markedSpans, estimateHeight) { line.text = text if (line.stateAfter) line.stateAfter = null if (line.styles) line.styles = null if (line.order != null) line.order = null detachMarkedSpans(line) attachMarkedSpans(line, markedSpans) let estHeight = estimateHeight ? estimateHeight(line) : 1 if (estHeight != line.height) updateLineHeight(line, estHeight) } // Detach a line from the document tree and its markers. export function cleanUpLine(line) { line.parent = null detachMarkedSpans(line) } // Convert a style as returned by a mode (either null, or a string // containing one or more styles) to a CSS style. This is cached, // and also looks for line-wide styles. let styleToClassCache = {}, styleToClassCacheWithMode = {} function interpretTokenStyle(style, options) { if (!style || /^\s*$/.test(style)) return null let cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache return cache[style] || (cache[style] = style.replace(/\S+/g, "cm-$&")) } // Render the DOM representation of the text of a line. Also builds // up a 'line map', which points at the DOM nodes that represent // specific stretches of text, and is used by the measuring code. // The returned object contains the DOM node, this map, and // information about line-wide styles that were set by the mode. export function buildLineContent(cm, lineView) { // The padding-right forces the element to have a 'border', which // is needed on Webkit to be able to get line-level bounding // rectangles for it (in measureChar). let content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) let builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, col: 0, pos: 0, cm: cm, trailingSpace: false, splitSpaces: cm.getOption("lineWrapping")} lineView.measure = {} // Iterate over the logical lines that make up this visual line. for (let i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { let line = i ? lineView.rest[i - 1] : lineView.line, order builder.pos = 0 builder.addToken = buildToken // Optionally wire in some hacks into the token-rendering // algorithm, to deal with browser quirks. if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) builder.addToken = buildTokenBadBidi(builder.addToken, order) builder.map = [] let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) if (line.styleClasses) { if (line.styleClasses.bgClass) builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") if (line.styleClasses.textClass) builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") } // Ensure at least a single node is present, for measuring. if (builder.map.length == 0) builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) // Store the map and a cache object for the current logical line if (i == 0) { lineView.measure.map = builder.map lineView.measure.cache = {} } else { ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) } } // See issue #2901 if (webkit) { let last = builder.content.lastChild if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) builder.content.className = "cm-tab-wrap-hack" } signal(cm, "renderLine", cm, lineView.line, builder.pre) if (builder.pre.className) builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") return builder } export function defaultSpecialCharPlaceholder(ch) { let token = elt("span", "\u2022", "cm-invalidchar") token.title = "\\u" + ch.charCodeAt(0).toString(16) token.setAttribute("aria-label", token.title) return token } // Build up the DOM representation for a single token, and add it to // the line map. Takes care to render special characters separately. function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { if (!text) return let displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text let special = builder.cm.state.specialChars, mustWrap = false let content if (!special.test(text)) { builder.col += text.length content = document.createTextNode(displayText) builder.map.push(builder.pos, builder.pos + text.length, content) if (ie && ie_version < 9) mustWrap = true builder.pos += text.length } else { content = document.createDocumentFragment() let pos = 0 while (true) { special.lastIndex = pos let m = special.exec(text) let skipped = m ? m.index - pos : text.length - pos if (skipped) { let txt = document.createTextNode(displayText.slice(pos, pos + skipped)) if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) else content.appendChild(txt) builder.map.push(builder.pos, builder.pos + skipped, txt) builder.col += skipped builder.pos += skipped } if (!m) break pos += skipped + 1 let txt if (m[0] == "\t") { let tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) txt.setAttribute("role", "presentation") txt.setAttribute("cm-text", "\t") builder.col += tabWidth } else if (m[0] == "\r" || m[0] == "\n") { txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) txt.setAttribute("cm-text", m[0]) builder.col += 1 } else { txt = builder.cm.options.specialCharPlaceholder(m[0]) txt.setAttribute("cm-text", m[0]) if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) else content.appendChild(txt) builder.col += 1 } builder.map.push(builder.pos, builder.pos + 1, txt) builder.pos++ } } builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 if (style || startStyle || endStyle || mustWrap || css || attributes) { let fullStyle = style || "" if (startStyle) fullStyle += startStyle if (endStyle) fullStyle += endStyle let token = elt("span", [content], fullStyle, css) if (attributes) { for (let attr in attributes) if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") token.setAttribute(attr, attributes[attr]) } return builder.content.appendChild(token) } builder.content.appendChild(content) } // Change some spaces to NBSP to prevent the browser from collapsing // trailing spaces at the end of a line when rendering text (issue #1362). function splitSpaces(text, trailingBefore) { if (text.length > 1 && !/ /.test(text)) return text let spaceBefore = trailingBefore, result = "" for (let i = 0; i < text.length; i++) { let ch = text.charAt(i) if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) ch = "\u00a0" result += ch spaceBefore = ch == " " } return result } // Work around nonsense dimensions being reported for stretches of // right-to-left text. function buildTokenBadBidi(inner, order) { return (builder, text, style, startStyle, endStyle, css, attributes) => { style = style ? style + " cm-force-border" : "cm-force-border" let start = builder.pos, end = start + text.length for (;;) { // Find the part that overlaps with the start of this text let part for (let i = 0; i < order.length; i++) { part = order[i] if (part.to > start && part.from <= start) break } if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, css, attributes) inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes) startStyle = null text = text.slice(part.to - start) start = part.to } } } function buildCollapsedSpan(builder, size, marker, ignoreWidget) { let widget = !ignoreWidget && marker.widgetNode if (widget) builder.map.push(builder.pos, builder.pos + size, widget) if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { if (!widget) widget = builder.content.appendChild(document.createElement("span")) widget.setAttribute("cm-marker", marker.id) } if (widget) { builder.cm.display.input.setUneditable(widget) builder.content.appendChild(widget) } builder.pos += size builder.trailingSpace = false } // Outputs a number of spans to make up a line, taking highlighting // and marked text into account. function insertLineContent(line, builder, styles) { let spans = line.markedSpans, allText = line.text, at = 0 if (!spans) { for (let i = 1; i < styles.length; i+=2) builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)) return } let len = allText.length, pos = 0, i = 1, text = "", style, css let nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes for (;;) { if (nextChange == pos) { // Update current marker set spanStyle = spanEndStyle = spanStartStyle = css = "" attributes = null collapsed = null; nextChange = Infinity let foundBookmarks = [], endStyles for (let j = 0; j < spans.length; ++j) { let sp = spans[j], m = sp.marker if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { foundBookmarks.push(m) } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { if (sp.to != null && sp.to != pos && nextChange > sp.to) { nextChange = sp.to spanEndStyle = "" } if (m.className) spanStyle += " " + m.className if (m.css) css = (css ? css + ";" : "") + m.css if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) // support for the old title property // https://github.com/codemirror/CodeMirror/pull/5673 if (m.title) (attributes || (attributes = {})).title = m.title if (m.attributes) { for (let attr in m.attributes) (attributes || (attributes = {}))[attr] = m.attributes[attr] } if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) collapsed = sp } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from } } if (endStyles) for (let j = 0; j < endStyles.length; j += 2) if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] if (!collapsed || collapsed.from == pos) for (let j = 0; j < foundBookmarks.length; ++j) buildCollapsedSpan(builder, 0, foundBookmarks[j]) if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, collapsed.marker, collapsed.from == null) if (collapsed.to == null) return if (collapsed.to == pos) collapsed = false } } if (pos >= len) break let upto = Math.min(len, nextChange) while (true) { if (text) { let end = pos + text.length if (!collapsed) { let tokenText = end > upto ? text.slice(0, upto - pos) : text builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes) } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} pos = end spanStartStyle = "" } text = allText.slice(at, at = styles[i++]) style = interpretTokenStyle(styles[i++], builder.cm.options) } } } // These objects are used to represent the visible (currently drawn) // part of the document. A LineView may correspond to multiple // logical lines, if those are connected by collapsed ranges. export function LineView(doc, line, lineN) { // The starting line this.line = line // Continuing lines, if any this.rest = visualLineContinued(line) // Number of logical lines in this visual line this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 this.node = this.text = null this.hidden = lineIsHidden(doc, line) } // Create a range of LineView objects for the given lines. export function buildViewArray(cm, from, to) { let array = [], nextPos for (let pos = from; pos < to; pos = nextPos) { let view = new LineView(cm.doc, getLine(cm.doc, pos), pos) nextPos = pos + view.size array.push(view) } return array } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/pos.js ================================================ import { getLine } from "./utils_line.js" // A Pos instance represents a position within the text. export function Pos(line, ch, sticky = null) { if (!(this instanceof Pos)) return new Pos(line, ch, sticky) this.line = line this.ch = ch this.sticky = sticky } // Compare two positions, return 0 if they are the same, a negative // number when a is less, and a positive number otherwise. export function cmp(a, b) { return a.line - b.line || a.ch - b.ch } export function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } export function copyPos(x) {return Pos(x.line, x.ch)} export function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } export function minPos(a, b) { return cmp(a, b) < 0 ? a : b } // Most of the external API clips given positions to make sure they // actually exist within the document. export function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} export function clipPos(doc, pos) { if (pos.line < doc.first) return Pos(doc.first, 0) let last = doc.first + doc.size - 1 if (pos.line > last) return Pos(last, getLine(doc, last).text.length) return clipToLen(pos, getLine(doc, pos.line).text.length) } function clipToLen(pos, linelen) { let ch = pos.ch if (ch == null || ch > linelen) return Pos(pos.line, linelen) else if (ch < 0) return Pos(pos.line, 0) else return pos } export function clipPosArray(doc, array) { let out = [] for (let i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]) return out } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/saw_special_spans.js ================================================ // Optimize some code when these features are not used. export let sawReadOnlySpans = false, sawCollapsedSpans = false export function seeReadOnlySpans() { sawReadOnlySpans = true } export function seeCollapsedSpans() { sawCollapsedSpans = true } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/spans.js ================================================ import { indexOf, lst } from "../util/misc.js" import { cmp } from "./pos.js" import { sawCollapsedSpans } from "./saw_special_spans.js" import { getLine, isLine, lineNo } from "./utils_line.js" // TEXTMARKER SPANS export function MarkedSpan(marker, from, to) { this.marker = marker this.from = from; this.to = to } // Search an array of spans for a span matching the given marker. export function getMarkedSpanFor(spans, marker) { if (spans) for (let i = 0; i < spans.length; ++i) { let span = spans[i] if (span.marker == marker) return span } } // Remove a span from an array, returning undefined if no spans are // left (we don't store arrays for lines without spans). export function removeMarkedSpan(spans, span) { let r for (let i = 0; i < spans.length; ++i) if (spans[i] != span) (r || (r = [])).push(spans[i]) return r } // Add a span to a line. export function addMarkedSpan(line, span, op) { let inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet)) if (inThisOp && line.markedSpans && inThisOp.has(line.markedSpans)) { line.markedSpans.push(span) } else { line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] if (inThisOp) inThisOp.add(line.markedSpans) } span.marker.attachLine(line) } // Used for the algorithm that adjusts markers for a change in the // document. These functions cut an array of spans at a given // character position, returning an array of remaining chunks (or // undefined if nothing remains). function markedSpansBefore(old, startCh, isInsert) { let nw if (old) for (let i = 0; i < old.length; ++i) { let span = old[i], marker = span.marker let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) } } return nw } function markedSpansAfter(old, endCh, isInsert) { let nw if (old) for (let i = 0; i < old.length; ++i) { let span = old[i], marker = span.marker let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, span.to == null ? null : span.to - endCh)) } } return nw } // Given a change object, compute the new set of marker spans that // cover the line in which the change took place. Removes spans // entirely within the change, reconnects spans belonging to the // same marker that appear on both sides of the change, and cuts off // spans partially within the change. Returns an array of span // arrays with one element for each line in (after) the change. export function stretchSpansOverChange(doc, change) { if (change.full) return null let oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans let oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans if (!oldFirst && !oldLast) return null let startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 // Get the spans that 'stick out' on both sides let first = markedSpansBefore(oldFirst, startCh, isInsert) let last = markedSpansAfter(oldLast, endCh, isInsert) // Next, merge those two ends let sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) if (first) { // Fix up .to properties of first for (let i = 0; i < first.length; ++i) { let span = first[i] if (span.to == null) { let found = getMarkedSpanFor(last, span.marker) if (!found) span.to = startCh else if (sameLine) span.to = found.to == null ? null : found.to + offset } } } if (last) { // Fix up .from in last (or move them into first in case of sameLine) for (let i = 0; i < last.length; ++i) { let span = last[i] if (span.to != null) span.to += offset if (span.from == null) { let found = getMarkedSpanFor(first, span.marker) if (!found) { span.from = offset if (sameLine) (first || (first = [])).push(span) } } else { span.from += offset if (sameLine) (first || (first = [])).push(span) } } } // Make sure we didn't create any zero-length spans if (first) first = clearEmptySpans(first) if (last && last != first) last = clearEmptySpans(last) let newMarkers = [first] if (!sameLine) { // Fill gap with whole-line-spans let gap = change.text.length - 2, gapMarkers if (gap > 0 && first) for (let i = 0; i < first.length; ++i) if (first[i].to == null) (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)) for (let i = 0; i < gap; ++i) newMarkers.push(gapMarkers) newMarkers.push(last) } return newMarkers } // Remove spans that are empty and don't have a clearWhenEmpty // option of false. function clearEmptySpans(spans) { for (let i = 0; i < spans.length; ++i) { let span = spans[i] if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) spans.splice(i--, 1) } if (!spans.length) return null return spans } // Used to 'clip' out readOnly ranges when making a change. export function removeReadOnlyRanges(doc, from, to) { let markers = null doc.iter(from.line, to.line + 1, line => { if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { let mark = line.markedSpans[i].marker if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) (markers || (markers = [])).push(mark) } }) if (!markers) return null let parts = [{from: from, to: to}] for (let i = 0; i < markers.length; ++i) { let mk = markers[i], m = mk.find(0) for (let j = 0; j < parts.length; ++j) { let p = parts[j] if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue let newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) newParts.push({from: p.from, to: m.from}) if (dto > 0 || !mk.inclusiveRight && !dto) newParts.push({from: m.to, to: p.to}) parts.splice.apply(parts, newParts) j += newParts.length - 3 } } return parts } // Connect or disconnect spans from a line. export function detachMarkedSpans(line) { let spans = line.markedSpans if (!spans) return for (let i = 0; i < spans.length; ++i) spans[i].marker.detachLine(line) line.markedSpans = null } export function attachMarkedSpans(line, spans) { if (!spans) return for (let i = 0; i < spans.length; ++i) spans[i].marker.attachLine(line) line.markedSpans = spans } // Helpers used when computing which overlapping collapsed span // counts as the larger one. function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } // Returns a number indicating which of two overlapping collapsed // spans is larger (and thus includes the other). Falls back to // comparing ids when the spans cover exactly the same range. export function compareCollapsedMarkers(a, b) { let lenDiff = a.lines.length - b.lines.length if (lenDiff != 0) return lenDiff let aPos = a.find(), bPos = b.find() let fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) if (fromCmp) return -fromCmp let toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) if (toCmp) return toCmp return b.id - a.id } // Find out whether a line ends or starts in a collapsed span. If // so, return the marker for that span. function collapsedSpanAtSide(line, start) { let sps = sawCollapsedSpans && line.markedSpans, found if (sps) for (let sp, i = 0; i < sps.length; ++i) { sp = sps[i] if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && (!found || compareCollapsedMarkers(found, sp.marker) < 0)) found = sp.marker } return found } export function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } export function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } export function collapsedSpanAround(line, ch) { let sps = sawCollapsedSpans && line.markedSpans, found if (sps) for (let i = 0; i < sps.length; ++i) { let sp = sps[i] if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && (!found || compareCollapsedMarkers(found, sp.marker) < 0)) found = sp.marker } return found } // Test whether there exists a collapsed span that partially // overlaps (covers the start or end, but not both) of a new span. // Such overlap is not allowed. export function conflictingCollapsedRange(doc, lineNo, from, to, marker) { let line = getLine(doc, lineNo) let sps = sawCollapsedSpans && line.markedSpans if (sps) for (let i = 0; i < sps.length; ++i) { let sp = sps[i] if (!sp.marker.collapsed) continue let found = sp.marker.find(0) let fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) let toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) return true } } // A visual line is a line as drawn on the screen. Folding, for // example, can cause multiple logical lines to appear on the same // visual line. This finds the start of the visual line that the // given line is part of (usually that is the line itself). export function visualLine(line) { let merged while (merged = collapsedSpanAtStart(line)) line = merged.find(-1, true).line return line } export function visualLineEnd(line) { let merged while (merged = collapsedSpanAtEnd(line)) line = merged.find(1, true).line return line } // Returns an array of logical lines that continue the visual line // started by the argument, or undefined if there are no such lines. export function visualLineContinued(line) { let merged, lines while (merged = collapsedSpanAtEnd(line)) { line = merged.find(1, true).line ;(lines || (lines = [])).push(line) } return lines } // Get the line number of the start of the visual line that the // given line number is part of. export function visualLineNo(doc, lineN) { let line = getLine(doc, lineN), vis = visualLine(line) if (line == vis) return lineN return lineNo(vis) } // Get the line number of the start of the next visual line after // the given line. export function visualLineEndNo(doc, lineN) { if (lineN > doc.lastLine()) return lineN let line = getLine(doc, lineN), merged if (!lineIsHidden(doc, line)) return lineN while (merged = collapsedSpanAtEnd(line)) line = merged.find(1, true).line return lineNo(line) + 1 } // Compute whether a line is hidden. Lines count as hidden when they // are part of a visual line that starts with another line, or when // they are entirely covered by collapsed, non-widget span. export function lineIsHidden(doc, line) { let sps = sawCollapsedSpans && line.markedSpans if (sps) for (let sp, i = 0; i < sps.length; ++i) { sp = sps[i] if (!sp.marker.collapsed) continue if (sp.from == null) return true if (sp.marker.widgetNode) continue if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) return true } } function lineIsHiddenInner(doc, line, span) { if (span.to == null) { let end = span.marker.find(1, true) return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) } if (span.marker.inclusiveRight && span.to == line.text.length) return true for (let sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i] if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && (sp.to == null || sp.to != span.from) && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) return true } } // Find the height above the given line. export function heightAtLine(lineObj) { lineObj = visualLine(lineObj) let h = 0, chunk = lineObj.parent for (let i = 0; i < chunk.lines.length; ++i) { let line = chunk.lines[i] if (line == lineObj) break else h += line.height } for (let p = chunk.parent; p; chunk = p, p = chunk.parent) { for (let i = 0; i < p.children.length; ++i) { let cur = p.children[i] if (cur == chunk) break else h += cur.height } } return h } // Compute the character length of a line, taking into account // collapsed ranges (see markText) that might hide parts, and join // other lines onto it. export function lineLength(line) { if (line.height == 0) return 0 let len = line.text.length, merged, cur = line while (merged = collapsedSpanAtStart(cur)) { let found = merged.find(0, true) cur = found.from.line len += found.from.ch - found.to.ch } cur = line while (merged = collapsedSpanAtEnd(cur)) { let found = merged.find(0, true) len -= cur.text.length - found.from.ch cur = found.to.line len += cur.text.length - found.to.ch } return len } // Find the longest line in the document. export function findMaxLine(cm) { let d = cm.display, doc = cm.doc d.maxLine = getLine(doc, doc.first) d.maxLineLength = lineLength(d.maxLine) d.maxLineChanged = true doc.iter(line => { let len = lineLength(line) if (len > d.maxLineLength) { d.maxLineLength = len d.maxLine = line } }) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/line/utils_line.js ================================================ import { indexOf } from "../util/misc.js" // Find the line object corresponding to the given line number. export function getLine(doc, n) { n -= doc.first if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.") let chunk = doc while (!chunk.lines) { for (let i = 0;; ++i) { let child = chunk.children[i], sz = child.chunkSize() if (n < sz) { chunk = child; break } n -= sz } } return chunk.lines[n] } // Get the part of a document between two positions, as an array of // strings. export function getBetween(doc, start, end) { let out = [], n = start.line doc.iter(start.line, end.line + 1, line => { let text = line.text if (n == end.line) text = text.slice(0, end.ch) if (n == start.line) text = text.slice(start.ch) out.push(text) ++n }) return out } // Get the lines between from and to, as array of strings. export function getLines(doc, from, to) { let out = [] doc.iter(from, to, line => { out.push(line.text) }) // iter aborts when callback returns truthy value return out } // Update the height of a line, propagating the height change // upwards to parent nodes. export function updateLineHeight(line, height) { let diff = height - line.height if (diff) for (let n = line; n; n = n.parent) n.height += diff } // Given a line object, find its line number by walking up through // its parent links. export function lineNo(line) { if (line.parent == null) return null let cur = line.parent, no = indexOf(cur.lines, line) for (let chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { for (let i = 0;; ++i) { if (chunk.children[i] == cur) break no += chunk.children[i].chunkSize() } } return no + cur.first } // Find the line at the given vertical position, using the height // information in the document tree. export function lineAtHeight(chunk, h) { let n = chunk.first outer: do { for (let i = 0; i < chunk.children.length; ++i) { let child = chunk.children[i], ch = child.height if (h < ch) { chunk = child; continue outer } h -= ch n += child.chunkSize() } return n } while (!chunk.lines) let i = 0 for (; i < chunk.lines.length; ++i) { let line = chunk.lines[i], lh = line.height if (h < lh) break h -= lh } return n + i } export function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} export function lineNumberFor(options, i) { return String(options.lineNumberFormatter(i + options.firstLineNumber)) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/measurement/position_measurement.js ================================================ import { buildLineContent, LineView } from "../line/line_data.js" import { clipPos, Pos } from "../line/pos.js" import { collapsedSpanAround, heightAtLine, lineIsHidden, visualLine } from "../line/spans.js" import { getLine, lineAtHeight, lineNo, updateLineHeight } from "../line/utils_line.js" import { bidiOther, getBidiPartAt, getOrder } from "../util/bidi.js" import { chrome, android, ie, ie_version } from "../util/browser.js" import { elt, removeChildren, range, removeChildrenAndAdd, doc } from "../util/dom.js" import { e_target } from "../util/event.js" import { hasBadZoomedRects } from "../util/feature_detection.js" import { countColumn, findFirst, isExtendingChar, scrollerGap, skipExtendingChars } from "../util/misc.js" import { updateLineForChanges } from "../display/update_line.js" import { widgetHeight } from "./widgets.js" // POSITION MEASUREMENT export function paddingTop(display) {return display.lineSpace.offsetTop} export function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} export function paddingH(display) { if (display.cachedPaddingH) return display.cachedPaddingH let e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like")) let style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle let data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)} if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data return data } export function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } export function displayWidth(cm) { return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth } export function displayHeight(cm) { return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight } // Ensure the lineView.wrapping.heights array is populated. This is // an array of bottom offsets for the lines that make up a drawn // line. When lineWrapping is on, there might be more than one // height. function ensureLineHeights(cm, lineView, rect) { let wrapping = cm.options.lineWrapping let curWidth = wrapping && displayWidth(cm) if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { let heights = lineView.measure.heights = [] if (wrapping) { lineView.measure.width = curWidth let rects = lineView.text.firstChild.getClientRects() for (let i = 0; i < rects.length - 1; i++) { let cur = rects[i], next = rects[i + 1] if (Math.abs(cur.bottom - next.bottom) > 2) heights.push((cur.bottom + next.top) / 2 - rect.top) } } heights.push(rect.bottom - rect.top) } } // Find a line map (mapping character offsets to text nodes) and a // measurement cache for the given line number. (A line view might // contain multiple lines when collapsed ranges are present.) export function mapFromLineView(lineView, line, lineN) { if (lineView.line == line) return {map: lineView.measure.map, cache: lineView.measure.cache} if (lineView.rest) { for (let i = 0; i < lineView.rest.length; i++) if (lineView.rest[i] == line) return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} for (let i = 0; i < lineView.rest.length; i++) if (lineNo(lineView.rest[i]) > lineN) return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true} } } // Render a line into the hidden node display.externalMeasured. Used // when measurement is needed for a line that's not in the viewport. function updateExternalMeasurement(cm, line) { line = visualLine(line) let lineN = lineNo(line) let view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN) view.lineN = lineN let built = view.built = buildLineContent(cm, view) view.text = built.pre removeChildrenAndAdd(cm.display.lineMeasure, built.pre) return view } // Get a {top, bottom, left, right} box (in line-local coordinates) // for a given character. export function measureChar(cm, line, ch, bias) { return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) } // Find a line view that corresponds to the given line number. export function findViewForLine(cm, lineN) { if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) return cm.display.view[findViewIndex(cm, lineN)] let ext = cm.display.externalMeasured if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) return ext } // Measurement can be split in two steps, the set-up work that // applies to the whole line, and the measurement of the actual // character. Functions like coordsChar, that need to do a lot of // measurements in a row, can thus ensure that the set-up work is // only done once. export function prepareMeasureForLine(cm, line) { let lineN = lineNo(line) let view = findViewForLine(cm, lineN) if (view && !view.text) { view = null } else if (view && view.changes) { updateLineForChanges(cm, view, lineN, getDimensions(cm)) cm.curOp.forceUpdate = true } if (!view) view = updateExternalMeasurement(cm, line) let info = mapFromLineView(view, line, lineN) return { line: line, view: view, rect: null, map: info.map, cache: info.cache, before: info.before, hasHeights: false } } // Given a prepared measurement object, measures the position of an // actual character (or fetches it from the cache). export function measureCharPrepared(cm, prepared, ch, bias, varHeight) { if (prepared.before) ch = -1 let key = ch + (bias || ""), found if (prepared.cache.hasOwnProperty(key)) { found = prepared.cache[key] } else { if (!prepared.rect) prepared.rect = prepared.view.text.getBoundingClientRect() if (!prepared.hasHeights) { ensureLineHeights(cm, prepared.view, prepared.rect) prepared.hasHeights = true } found = measureCharInner(cm, prepared, ch, bias) if (!found.bogus) prepared.cache[key] = found } return {left: found.left, right: found.right, top: varHeight ? found.rtop : found.top, bottom: varHeight ? found.rbottom : found.bottom} } let nullRect = {left: 0, right: 0, top: 0, bottom: 0} export function nodeAndOffsetInLineMap(map, ch, bias) { let node, start, end, collapse, mStart, mEnd // First, search the line map for the text node corresponding to, // or closest to, the target character. for (let i = 0; i < map.length; i += 3) { mStart = map[i] mEnd = map[i + 1] if (ch < mStart) { start = 0; end = 1 collapse = "left" } else if (ch < mEnd) { start = ch - mStart end = start + 1 } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { end = mEnd - mStart start = end - 1 if (ch >= mEnd) collapse = "right" } if (start != null) { node = map[i + 2] if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) collapse = bias if (bias == "left" && start == 0) while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { node = map[(i -= 3) + 2] collapse = "left" } if (bias == "right" && start == mEnd - mStart) while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { node = map[(i += 3) + 2] collapse = "right" } break } } return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} } function getUsefulRect(rects, bias) { let rect = nullRect if (bias == "left") for (let i = 0; i < rects.length; i++) { if ((rect = rects[i]).left != rect.right) break } else for (let i = rects.length - 1; i >= 0; i--) { if ((rect = rects[i]).left != rect.right) break } return rect } function measureCharInner(cm, prepared, ch, bias) { let place = nodeAndOffsetInLineMap(prepared.map, ch, bias) let node = place.node, start = place.start, end = place.end, collapse = place.collapse let rect if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. for (let i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) rect = node.parentNode.getBoundingClientRect() else rect = getUsefulRect(range(node, start, end).getClientRects(), bias) if (rect.left || rect.right || start == 0) break end = start start = start - 1 collapse = "right" } if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect) } else { // If it is a widget, simply get the box for the whole widget. if (start > 0) collapse = bias = "right" let rects if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) rect = rects[bias == "right" ? rects.length - 1 : 0] else rect = node.getBoundingClientRect() } if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { let rSpan = node.parentNode.getClientRects()[0] if (rSpan) rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom} else rect = nullRect } let rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top let mid = (rtop + rbot) / 2 let heights = prepared.view.measure.heights let i = 0 for (; i < heights.length - 1; i++) if (mid < heights[i]) break let top = i ? heights[i - 1] : 0, bot = heights[i] let result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, top: top, bottom: bot} if (!rect.left && !rect.right) result.bogus = true if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot } return result } // Work around problem with bounding client rects on ranges being // returned incorrectly when zoomed on IE10 and below. function maybeUpdateRectForZooming(measure, rect) { if (!window.screen || screen.logicalXDPI == null || screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) return rect let scaleX = screen.logicalXDPI / screen.deviceXDPI let scaleY = screen.logicalYDPI / screen.deviceYDPI return {left: rect.left * scaleX, right: rect.right * scaleX, top: rect.top * scaleY, bottom: rect.bottom * scaleY} } export function clearLineMeasurementCacheFor(lineView) { if (lineView.measure) { lineView.measure.cache = {} lineView.measure.heights = null if (lineView.rest) for (let i = 0; i < lineView.rest.length; i++) lineView.measure.caches[i] = {} } } export function clearLineMeasurementCache(cm) { cm.display.externalMeasure = null removeChildren(cm.display.lineMeasure) for (let i = 0; i < cm.display.view.length; i++) clearLineMeasurementCacheFor(cm.display.view[i]) } export function clearCaches(cm) { clearLineMeasurementCache(cm) cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null if (!cm.options.lineWrapping) cm.display.maxLineChanged = true cm.display.lineNumChars = null } function pageScrollX(doc) { // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 // which causes page_Offset and bounding client rects to use // different reference viewports and invalidate our calculations. if (chrome && android) return -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft)) return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft } function pageScrollY(doc) { if (chrome && android) return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop)) return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop } function widgetTopHeight(lineObj) { let {widgets} = visualLine(lineObj), height = 0 if (widgets) for (let i = 0; i < widgets.length; ++i) if (widgets[i].above) height += widgetHeight(widgets[i]) return height } // Converts a {top, bottom, left, right} box from line-local // coordinates into another coordinate system. Context may be one of // "line", "div" (display.lineDiv), "local"./null (editor), "window", // or "page". export function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { if (!includeWidgets) { let height = widgetTopHeight(lineObj) rect.top += height; rect.bottom += height } if (context == "line") return rect if (!context) context = "local" let yOff = heightAtLine(lineObj) if (context == "local") yOff += paddingTop(cm.display) else yOff -= cm.display.viewOffset if (context == "page" || context == "window") { let lOff = cm.display.lineSpace.getBoundingClientRect() yOff += lOff.top + (context == "window" ? 0 : pageScrollY(doc(cm))) let xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm))) rect.left += xOff; rect.right += xOff } rect.top += yOff; rect.bottom += yOff return rect } // Coverts a box from "div" coords to another coordinate system. // Context may be "window", "page", "div", or "local"./null. export function fromCoordSystem(cm, coords, context) { if (context == "div") return coords let left = coords.left, top = coords.top // First move into "page" coordinate system if (context == "page") { left -= pageScrollX(doc(cm)) top -= pageScrollY(doc(cm)) } else if (context == "local" || !context) { let localBox = cm.display.sizer.getBoundingClientRect() left += localBox.left top += localBox.top } let lineSpaceBox = cm.display.lineSpace.getBoundingClientRect() return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} } export function charCoords(cm, pos, context, lineObj, bias) { if (!lineObj) lineObj = getLine(cm.doc, pos.line) return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) } // Returns a box for a given cursor position, which may have an // 'other' property containing the position of the secondary cursor // on a bidi boundary. // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` // and after `char - 1` in writing order of `char - 1` // A cursor Pos(line, char, "after") is on the same visual line as `char` // and before `char` in writing order of `char` // Examples (upper-case letters are RTL, lower-case are LTR): // Pos(0, 1, ...) // before after // ab a|b a|b // aB a|B aB| // Ab |Ab A|b // AB B|A B|A // Every position after the last character on a line is considered to stick // to the last character on the line. export function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { lineObj = lineObj || getLine(cm.doc, pos.line) if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) function get(ch, right) { let m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight) if (right) m.left = m.right; else m.right = m.left return intoCoordSystem(cm, lineObj, m, context) } let order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky if (ch >= lineObj.text.length) { ch = lineObj.text.length sticky = "before" } else if (ch <= 0) { ch = 0 sticky = "after" } if (!order) return get(sticky == "before" ? ch - 1 : ch, sticky == "before") function getBidi(ch, partPos, invert) { let part = order[partPos], right = part.level == 1 return get(invert ? ch - 1 : ch, right != invert) } let partPos = getBidiPartAt(order, ch, sticky) let other = bidiOther let val = getBidi(ch, partPos, sticky == "before") if (other != null) val.other = getBidi(ch, other, sticky != "before") return val } // Used to cheaply estimate the coordinates for a position. Used for // intermediate scroll updates. export function estimateCoords(cm, pos) { let left = 0 pos = clipPos(cm.doc, pos) if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch let lineObj = getLine(cm.doc, pos.line) let top = heightAtLine(lineObj) + paddingTop(cm.display) return {left: left, right: left, top: top, bottom: top + lineObj.height} } // Positions returned by coordsChar contain some extra information. // xRel is the relative x position of the input coordinates compared // to the found position (so xRel > 0 means the coordinates are to // the right of the character position, for example). When outside // is true, that means the coordinates lie outside the line's // vertical range. function PosWithInfo(line, ch, sticky, outside, xRel) { let pos = Pos(line, ch, sticky) pos.xRel = xRel if (outside) pos.outside = outside return pos } // Compute the character position closest to the given coordinates. // Input must be lineSpace-local ("div" coordinate system). export function coordsChar(cm, x, y) { let doc = cm.doc y += cm.display.viewOffset if (y < 0) return PosWithInfo(doc.first, 0, null, -1, -1) let lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1 if (lineN > last) return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) if (x < 0) x = 0 let lineObj = getLine(doc, lineN) for (;;) { let found = coordsCharInner(cm, lineObj, lineN, x, y) let collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0)) if (!collapsed) return found let rangeEnd = collapsed.find(1) if (rangeEnd.line == lineN) return rangeEnd lineObj = getLine(doc, lineN = rangeEnd.line) } } function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { y -= widgetTopHeight(lineObj) let end = lineObj.text.length let begin = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y, end, 0) end = findFirst(ch => measureCharPrepared(cm, preparedMeasure, ch).top > y, begin, end) return {begin, end} } export function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj) let targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) } // Returns true if the given side of a box is after the given // coordinates, in top-to-bottom, left-to-right order. function boxIsAfter(box, x, y, left) { return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x } function coordsCharInner(cm, lineObj, lineNo, x, y) { // Move y into line-local coordinate space y -= heightAtLine(lineObj) let preparedMeasure = prepareMeasureForLine(cm, lineObj) // When directly calling `measureCharPrepared`, we have to adjust // for the widgets at this line. let widgetHeight = widgetTopHeight(lineObj) let begin = 0, end = lineObj.text.length, ltr = true let order = getOrder(lineObj, cm.doc.direction) // If the line isn't plain left-to-right text, first figure out // which bidi section the coordinates fall into. if (order) { let part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) (cm, lineObj, lineNo, preparedMeasure, order, x, y) ltr = part.level != 1 // The awkward -1 offsets are needed because findFirst (called // on these below) will treat its first bound as inclusive, // second as exclusive, but we want to actually address the // characters in the part's range begin = ltr ? part.from : part.to - 1 end = ltr ? part.to : part.from - 1 } // A binary search to find the first character whose bounding box // starts after the coordinates. If we run across any whose box wrap // the coordinates, store that. let chAround = null, boxAround = null let ch = findFirst(ch => { let box = measureCharPrepared(cm, preparedMeasure, ch) box.top += widgetHeight; box.bottom += widgetHeight if (!boxIsAfter(box, x, y, false)) return false if (box.top <= y && box.left <= x) { chAround = ch boxAround = box } return true }, begin, end) let baseX, sticky, outside = false // If a box around the coordinates was found, use that if (boxAround) { // Distinguish coordinates nearer to the left or right side of the box let atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr ch = chAround + (atStart ? 0 : 1) sticky = atStart ? "after" : "before" baseX = atLeft ? boxAround.left : boxAround.right } else { // (Adjust for extended bound, if necessary.) if (!ltr && (ch == end || ch == begin)) ch++ // To determine which side to associate with, get the box to the // left of the character and compare it's vertical position to the // coordinates sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? "after" : "before" // Now get accurate coordinates for this place, in order to get a // base X position let coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure) baseX = coords.left outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0 } ch = skipExtendingChars(lineObj.text, ch, 1) return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) } function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { // Bidi parts are sorted left-to-right, and in a non-line-wrapping // situation, we can take this ordering to correspond to the visual // ordering. This finds the first part whose end is after the given // coordinates. let index = findFirst(i => { let part = order[i], ltr = part.level != 1 return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), "line", lineObj, preparedMeasure), x, y, true) }, 0, order.length - 1) let part = order[index] // If this isn't the first part, the part's start is also after // the coordinates, and the coordinates aren't on the same line as // that start, move one part back. if (index > 0) { let ltr = part.level != 1 let start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), "line", lineObj, preparedMeasure) if (boxIsAfter(start, x, y, true) && start.top > y) part = order[index - 1] } return part } function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { // In a wrapped line, rtl text on wrapping boundaries can do things // that don't correspond to the ordering in our `order` array at // all, so a binary search doesn't work, and we want to return a // part that only spans one line so that the binary search in // coordsCharInner is safe. As such, we first find the extent of the // wrapped line, and then do a flat search in which we discard any // spans that aren't on the line. let {begin, end} = wrappedLineExtent(cm, lineObj, preparedMeasure, y) if (/\s/.test(lineObj.text.charAt(end - 1))) end-- let part = null, closestDist = null for (let i = 0; i < order.length; i++) { let p = order[i] if (p.from >= end || p.to <= begin) continue let ltr = p.level != 1 let endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right // Weigh against spans ending before this, so that they are only // picked if nothing ends after let dist = endX < x ? x - endX + 1e9 : endX - x if (!part || closestDist > dist) { part = p closestDist = dist } } if (!part) part = order[order.length - 1] // Clip the part to the wrapped line. if (part.from < begin) part = {from: begin, to: part.to, level: part.level} if (part.to > end) part = {from: part.from, to: end, level: part.level} return part } let measureText // Compute the default text height. export function textHeight(display) { if (display.cachedTextHeight != null) return display.cachedTextHeight if (measureText == null) { measureText = elt("pre", null, "CodeMirror-line-like") // Measure a bunch of lines, for browsers that compute // fractional heights. for (let i = 0; i < 49; ++i) { measureText.appendChild(document.createTextNode("x")) measureText.appendChild(elt("br")) } measureText.appendChild(document.createTextNode("x")) } removeChildrenAndAdd(display.measure, measureText) let height = measureText.offsetHeight / 50 if (height > 3) display.cachedTextHeight = height removeChildren(display.measure) return height || 1 } // Compute the default character width. export function charWidth(display) { if (display.cachedCharWidth != null) return display.cachedCharWidth let anchor = elt("span", "xxxxxxxxxx") let pre = elt("pre", [anchor], "CodeMirror-line-like") removeChildrenAndAdd(display.measure, pre) let rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10 if (width > 2) display.cachedCharWidth = width return width || 10 } // Do a bulk-read of the DOM positions and sizes needed to draw the // view, so that we don't interleave reading and writing to the DOM. export function getDimensions(cm) { let d = cm.display, left = {}, width = {} let gutterLeft = d.gutters.clientLeft for (let n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { let id = cm.display.gutterSpecs[i].className left[id] = n.offsetLeft + n.clientLeft + gutterLeft width[id] = n.clientWidth } return {fixedPos: compensateForHScroll(d), gutterTotalWidth: d.gutters.offsetWidth, gutterLeft: left, gutterWidth: width, wrapperWidth: d.wrapper.clientWidth} } // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, // but using getBoundingClientRect to get a sub-pixel-accurate // result. export function compensateForHScroll(display) { return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left } // Returns a function that estimates the height of a line, to use as // first approximation until the line becomes visible (and is thus // properly measurable). export function estimateHeight(cm) { let th = textHeight(cm.display), wrapping = cm.options.lineWrapping let perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3) return line => { if (lineIsHidden(cm.doc, line)) return 0 let widgetsHeight = 0 if (line.widgets) for (let i = 0; i < line.widgets.length; i++) { if (line.widgets[i].height) widgetsHeight += line.widgets[i].height } if (wrapping) return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th else return widgetsHeight + th } } export function estimateLineHeights(cm) { let doc = cm.doc, est = estimateHeight(cm) doc.iter(line => { let estHeight = est(line) if (estHeight != line.height) updateLineHeight(line, estHeight) }) } // Given a mouse event, find the corresponding position. If liberal // is false, it checks whether a gutter or scrollbar was clicked, // and returns null if it was. forRect is used by rectangular // selections, and tries to estimate a character position even for // coordinates beyond the right of the text. export function posFromMouse(cm, e, liberal, forRect) { let display = cm.display if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null let x, y, space = display.lineSpace.getBoundingClientRect() // Fails unpredictably on IE[67] when mouse is dragged around quickly. try { x = e.clientX - space.left; y = e.clientY - space.top } catch (e) { return null } let coords = coordsChar(cm, x, y), line if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { let colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)) } return coords } // Find the view element corresponding to a given line. Return null // when the line isn't visible. export function findViewIndex(cm, n) { if (n >= cm.display.viewTo) return null n -= cm.display.viewFrom if (n < 0) return null let view = cm.display.view for (let i = 0; i < view.length; i++) { n -= view[i].size if (n < 0) return i } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/measurement/widgets.js ================================================ import { contains, elt, removeChildrenAndAdd } from "../util/dom.js" import { e_target } from "../util/event.js" export function widgetHeight(widget) { if (widget.height != null) return widget.height let cm = widget.doc.cm if (!cm) return 0 if (!contains(document.body, widget.node)) { let parentStyle = "position: relative;" if (widget.coverGutter) parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;" if (widget.noHScroll) parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;" removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)) } return widget.height = widget.node.parentNode.offsetHeight } // Return true when the given mouse event happened in a widget export function eventInWidget(display, e) { for (let n = e_target(e); n != display.wrapper; n = n.parentNode) { if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || (n.parentNode == display.sizer && n != display.mover)) return true } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/Doc.js ================================================ import CodeMirror from "../edit/CodeMirror.js" import { docMethodOp } from "../display/operations.js" import { Line } from "../line/line_data.js" import { clipPos, clipPosArray, Pos } from "../line/pos.js" import { visualLine } from "../line/spans.js" import { getBetween, getLine, getLines, isLine, lineNo } from "../line/utils_line.js" import { classTest } from "../util/dom.js" import { splitLinesAuto } from "../util/feature_detection.js" import { createObj, map, isEmpty, sel_dontScroll } from "../util/misc.js" import { ensureCursorVisible, scrollToCoords } from "../display/scrolling.js" import { changeLine, makeChange, makeChangeFromHistory, replaceRange } from "./changes.js" import { computeReplacedSel } from "./change_measurement.js" import { BranchChunk, LeafChunk } from "./chunk.js" import { directionChanged, linkedDocs, updateDoc } from "./document_data.js" import { copyHistoryArray, History } from "./history.js" import { addLineWidget } from "./line_widget.js" import { copySharedMarkers, detachSharedMarkers, findSharedMarkers, markText } from "./mark_text.js" import { normalizeSelection, Range, simpleSelection } from "./selection.js" import { extendSelection, extendSelections, setSelection, setSelectionReplaceHistory, setSimpleSelection } from "./selection_updates.js" let nextDocId = 0 let Doc = function(text, mode, firstLine, lineSep, direction) { if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep, direction) if (firstLine == null) firstLine = 0 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]) this.first = firstLine this.scrollTop = this.scrollLeft = 0 this.cantEdit = false this.cleanGeneration = 1 this.modeFrontier = this.highlightFrontier = firstLine let start = Pos(firstLine, 0) this.sel = simpleSelection(start) this.history = new History(null) this.id = ++nextDocId this.modeOption = mode this.lineSep = lineSep this.direction = (direction == "rtl") ? "rtl" : "ltr" this.extend = false if (typeof text == "string") text = this.splitLines(text) updateDoc(this, {from: start, to: start, text: text}) setSelection(this, simpleSelection(start), sel_dontScroll) } Doc.prototype = createObj(BranchChunk.prototype, { constructor: Doc, // Iterate over the document. Supports two forms -- with only one // argument, it calls that for each line in the document. With // three, it iterates over the range given by the first two (with // the second being non-inclusive). iter: function(from, to, op) { if (op) this.iterN(from - this.first, to - from, op) else this.iterN(this.first, this.first + this.size, from) }, // Non-public interface for adding and removing lines. insert: function(at, lines) { let height = 0 for (let i = 0; i < lines.length; ++i) height += lines[i].height this.insertInner(at - this.first, lines, height) }, remove: function(at, n) { this.removeInner(at - this.first, n) }, // From here, the methods are part of the public interface. Most // are also available from CodeMirror (editor) instances. getValue: function(lineSep) { let lines = getLines(this, this.first, this.first + this.size) if (lineSep === false) return lines return lines.join(lineSep || this.lineSeparator()) }, setValue: docMethodOp(function(code) { let top = Pos(this.first, 0), last = this.first + this.size - 1 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), text: this.splitLines(code), origin: "setValue", full: true}, true) if (this.cm) scrollToCoords(this.cm, 0, 0) setSelection(this, simpleSelection(top), sel_dontScroll) }), replaceRange: function(code, from, to, origin) { from = clipPos(this, from) to = to ? clipPos(this, to) : from replaceRange(this, code, from, to, origin) }, getRange: function(from, to, lineSep) { let lines = getBetween(this, clipPos(this, from), clipPos(this, to)) if (lineSep === false) return lines if (lineSep === '') return lines.join('') return lines.join(lineSep || this.lineSeparator()) }, getLine: function(line) {let l = this.getLineHandle(line); return l && l.text}, getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line)}, getLineNumber: function(line) {return lineNo(line)}, getLineHandleVisualStart: function(line) { if (typeof line == "number") line = getLine(this, line) return visualLine(line) }, lineCount: function() {return this.size}, firstLine: function() {return this.first}, lastLine: function() {return this.first + this.size - 1}, clipPos: function(pos) {return clipPos(this, pos)}, getCursor: function(start) { let range = this.sel.primary(), pos if (start == null || start == "head") pos = range.head else if (start == "anchor") pos = range.anchor else if (start == "end" || start == "to" || start === false) pos = range.to() else pos = range.from() return pos }, listSelections: function() { return this.sel.ranges }, somethingSelected: function() {return this.sel.somethingSelected()}, setCursor: docMethodOp(function(line, ch, options) { setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) }), setSelection: docMethodOp(function(anchor, head, options) { setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) }), extendSelection: docMethodOp(function(head, other, options) { extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) }), extendSelections: docMethodOp(function(heads, options) { extendSelections(this, clipPosArray(this, heads), options) }), extendSelectionsBy: docMethodOp(function(f, options) { let heads = map(this.sel.ranges, f) extendSelections(this, clipPosArray(this, heads), options) }), setSelections: docMethodOp(function(ranges, primary, options) { if (!ranges.length) return let out = [] for (let i = 0; i < ranges.length; i++) out[i] = new Range(clipPos(this, ranges[i].anchor), clipPos(this, ranges[i].head || ranges[i].anchor)) if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex) setSelection(this, normalizeSelection(this.cm, out, primary), options) }), addSelection: docMethodOp(function(anchor, head, options) { let ranges = this.sel.ranges.slice(0) ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options) }), getSelection: function(lineSep) { let ranges = this.sel.ranges, lines for (let i = 0; i < ranges.length; i++) { let sel = getBetween(this, ranges[i].from(), ranges[i].to()) lines = lines ? lines.concat(sel) : sel } if (lineSep === false) return lines else return lines.join(lineSep || this.lineSeparator()) }, getSelections: function(lineSep) { let parts = [], ranges = this.sel.ranges for (let i = 0; i < ranges.length; i++) { let sel = getBetween(this, ranges[i].from(), ranges[i].to()) if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()) parts[i] = sel } return parts }, replaceSelection: function(code, collapse, origin) { let dup = [] for (let i = 0; i < this.sel.ranges.length; i++) dup[i] = code this.replaceSelections(dup, collapse, origin || "+input") }, replaceSelections: docMethodOp(function(code, collapse, origin) { let changes = [], sel = this.sel for (let i = 0; i < sel.ranges.length; i++) { let range = sel.ranges[i] changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin} } let newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) for (let i = changes.length - 1; i >= 0; i--) makeChange(this, changes[i]) if (newSel) setSelectionReplaceHistory(this, newSel) else if (this.cm) ensureCursorVisible(this.cm) }), undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), setExtending: function(val) {this.extend = val}, getExtending: function() {return this.extend}, historySize: function() { let hist = this.history, done = 0, undone = 0 for (let i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done for (let i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone return {undo: done, redo: undone} }, clearHistory: function() { this.history = new History(this.history) linkedDocs(this, doc => doc.history = this.history, true) }, markClean: function() { this.cleanGeneration = this.changeGeneration(true) }, changeGeneration: function(forceSplit) { if (forceSplit) this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null return this.history.generation }, isClean: function (gen) { return this.history.generation == (gen || this.cleanGeneration) }, getHistory: function() { return {done: copyHistoryArray(this.history.done), undone: copyHistoryArray(this.history.undone)} }, setHistory: function(histData) { let hist = this.history = new History(this.history) hist.done = copyHistoryArray(histData.done.slice(0), null, true) hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) }, setGutterMarker: docMethodOp(function(line, gutterID, value) { return changeLine(this, line, "gutter", line => { let markers = line.gutterMarkers || (line.gutterMarkers = {}) markers[gutterID] = value if (!value && isEmpty(markers)) line.gutterMarkers = null return true }) }), clearGutter: docMethodOp(function(gutterID) { this.iter(line => { if (line.gutterMarkers && line.gutterMarkers[gutterID]) { changeLine(this, line, "gutter", () => { line.gutterMarkers[gutterID] = null if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null return true }) } }) }), lineInfo: function(line) { let n if (typeof line == "number") { if (!isLine(this, line)) return null n = line line = getLine(this, line) if (!line) return null } else { n = lineNo(line) if (n == null) return null } return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, widgets: line.widgets} }, addLineClass: docMethodOp(function(handle, where, cls) { return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { let prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass" if (!line[prop]) line[prop] = cls else if (classTest(cls).test(line[prop])) return false else line[prop] += " " + cls return true }) }), removeLineClass: docMethodOp(function(handle, where, cls) { return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { let prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : where == "gutter" ? "gutterClass" : "wrapClass" let cur = line[prop] if (!cur) return false else if (cls == null) line[prop] = null else { let found = cur.match(classTest(cls)) if (!found) return false let end = found.index + found[0].length line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null } return true }) }), addLineWidget: docMethodOp(function(handle, node, options) { return addLineWidget(this, handle, node, options) }), removeLineWidget: function(widget) { widget.clear() }, markText: function(from, to, options) { return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") }, setBookmark: function(pos, options) { let realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), insertLeft: options && options.insertLeft, clearWhenEmpty: false, shared: options && options.shared, handleMouseEvents: options && options.handleMouseEvents} pos = clipPos(this, pos) return markText(this, pos, pos, realOpts, "bookmark") }, findMarksAt: function(pos) { pos = clipPos(this, pos) let markers = [], spans = getLine(this, pos.line).markedSpans if (spans) for (let i = 0; i < spans.length; ++i) { let span = spans[i] if ((span.from == null || span.from <= pos.ch) && (span.to == null || span.to >= pos.ch)) markers.push(span.marker.parent || span.marker) } return markers }, findMarks: function(from, to, filter) { from = clipPos(this, from); to = clipPos(this, to) let found = [], lineNo = from.line this.iter(from.line, to.line + 1, line => { let spans = line.markedSpans if (spans) for (let i = 0; i < spans.length; i++) { let span = spans[i] if (!(span.to != null && lineNo == from.line && from.ch >= span.to || span.from == null && lineNo != from.line || span.from != null && lineNo == to.line && span.from >= to.ch) && (!filter || filter(span.marker))) found.push(span.marker.parent || span.marker) } ++lineNo }) return found }, getAllMarks: function() { let markers = [] this.iter(line => { let sps = line.markedSpans if (sps) for (let i = 0; i < sps.length; ++i) if (sps[i].from != null) markers.push(sps[i].marker) }) return markers }, posFromIndex: function(off) { let ch, lineNo = this.first, sepSize = this.lineSeparator().length this.iter(line => { let sz = line.text.length + sepSize if (sz > off) { ch = off; return true } off -= sz ++lineNo }) return clipPos(this, Pos(lineNo, ch)) }, indexFromPos: function (coords) { coords = clipPos(this, coords) let index = coords.ch if (coords.line < this.first || coords.ch < 0) return 0 let sepSize = this.lineSeparator().length this.iter(this.first, coords.line, line => { // iter aborts when callback returns a truthy value index += line.text.length + sepSize }) return index }, copy: function(copyHistory) { let doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first, this.lineSep, this.direction) doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft doc.sel = this.sel doc.extend = false if (copyHistory) { doc.history.undoDepth = this.history.undoDepth doc.setHistory(this.getHistory()) } return doc }, linkedDoc: function(options) { if (!options) options = {} let from = this.first, to = this.first + this.size if (options.from != null && options.from > from) from = options.from if (options.to != null && options.to < to) to = options.to let copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) if (options.sharedHist) copy.history = this.history ;(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] copySharedMarkers(copy, findSharedMarkers(this)) return copy }, unlinkDoc: function(other) { if (other instanceof CodeMirror) other = other.doc if (this.linked) for (let i = 0; i < this.linked.length; ++i) { let link = this.linked[i] if (link.doc != other) continue this.linked.splice(i, 1) other.unlinkDoc(this) detachSharedMarkers(findSharedMarkers(this)) break } // If the histories were shared, split them again if (other.history == this.history) { let splitIds = [other.id] linkedDocs(other, doc => splitIds.push(doc.id), true) other.history = new History(null) other.history.done = copyHistoryArray(this.history.done, splitIds) other.history.undone = copyHistoryArray(this.history.undone, splitIds) } }, iterLinkedDocs: function(f) {linkedDocs(this, f)}, getMode: function() {return this.mode}, getEditor: function() {return this.cm}, splitLines: function(str) { if (this.lineSep) return str.split(this.lineSep) return splitLinesAuto(str) }, lineSeparator: function() { return this.lineSep || "\n" }, setDirection: docMethodOp(function (dir) { if (dir != "rtl") dir = "ltr" if (dir == this.direction) return this.direction = dir this.iter(line => line.order = null) if (this.cm) directionChanged(this.cm) }) }) // Public alias. Doc.prototype.eachLine = Doc.prototype.iter export default Doc ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/change_measurement.js ================================================ import { cmp, Pos } from "../line/pos.js" import { lst } from "../util/misc.js" import { normalizeSelection, Range, Selection } from "./selection.js" // Compute the position of the end of a change (its 'to' property // refers to the pre-change end). export function changeEnd(change) { if (!change.text) return change.to return Pos(change.from.line + change.text.length - 1, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) } // Adjust a position to refer to the post-change position of the // same text, or the end of the change if the change covers it. function adjustForChange(pos, change) { if (cmp(pos, change.from) < 0) return pos if (cmp(pos, change.to) <= 0) return changeEnd(change) let line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch return Pos(line, ch) } export function computeSelAfterChange(doc, change) { let out = [] for (let i = 0; i < doc.sel.ranges.length; i++) { let range = doc.sel.ranges[i] out.push(new Range(adjustForChange(range.anchor, change), adjustForChange(range.head, change))) } return normalizeSelection(doc.cm, out, doc.sel.primIndex) } function offsetPos(pos, old, nw) { if (pos.line == old.line) return Pos(nw.line, pos.ch - old.ch + nw.ch) else return Pos(nw.line + (pos.line - old.line), pos.ch) } // Used by replaceSelections to allow moving the selection to the // start or around the replaced test. Hint may be "start" or "around". export function computeReplacedSel(doc, changes, hint) { let out = [] let oldPrev = Pos(doc.first, 0), newPrev = oldPrev for (let i = 0; i < changes.length; i++) { let change = changes[i] let from = offsetPos(change.from, oldPrev, newPrev) let to = offsetPos(changeEnd(change), oldPrev, newPrev) oldPrev = change.to newPrev = to if (hint == "around") { let range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0 out[i] = new Range(inv ? to : from, inv ? from : to) } else { out[i] = new Range(from, from) } } return new Selection(out, doc.sel.primIndex) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/changes.js ================================================ import { retreatFrontier } from "../line/highlight.js" import { startWorker } from "../display/highlight_worker.js" import { operation } from "../display/operations.js" import { regChange, regLineChange } from "../display/view_tracking.js" import { clipLine, clipPos, cmp, Pos } from "../line/pos.js" import { sawReadOnlySpans } from "../line/saw_special_spans.js" import { lineLength, removeReadOnlyRanges, stretchSpansOverChange, visualLine } from "../line/spans.js" import { getBetween, getLine, lineNo } from "../line/utils_line.js" import { estimateHeight } from "../measurement/position_measurement.js" import { hasHandler, signal, signalCursorActivity } from "../util/event.js" import { indexOf, lst, map, sel_dontScroll } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" import { changeEnd, computeSelAfterChange } from "./change_measurement.js" import { isWholeLineUpdate, linkedDocs, updateDoc } from "./document_data.js" import { addChangeToHistory, historyChangeFromChange, mergeOldSpans, pushSelectionToHistory } from "./history.js" import { Range, Selection } from "./selection.js" import { setSelection, setSelectionNoUndo, skipAtomic } from "./selection_updates.js" // UPDATING // Allow "beforeChange" event handlers to influence a change function filterChange(doc, change, update) { let obj = { canceled: false, from: change.from, to: change.to, text: change.text, origin: change.origin, cancel: () => obj.canceled = true } if (update) obj.update = (from, to, text, origin) => { if (from) obj.from = clipPos(doc, from) if (to) obj.to = clipPos(doc, to) if (text) obj.text = text if (origin !== undefined) obj.origin = origin } signal(doc, "beforeChange", doc, obj) if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj) if (obj.canceled) { if (doc.cm) doc.cm.curOp.updateInput = 2 return null } return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} } // Apply a change to a document, and add it to the document's // history, and propagating it to all linked documents. export function makeChange(doc, change, ignoreReadOnly) { if (doc.cm) { if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) if (doc.cm.state.suppressEdits) return } if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { change = filterChange(doc, change, true) if (!change) return } // Possibly split or suppress the update based on the presence // of read-only spans in its range. let split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to) if (split) { for (let i = split.length - 1; i >= 0; --i) makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}) } else { makeChangeInner(doc, change) } } function makeChangeInner(doc, change) { if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return let selAfter = computeSelAfterChange(doc, change) addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN) makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)) let rebased = [] linkedDocs(doc, (doc, sharedHist) => { if (!sharedHist && indexOf(rebased, doc.history) == -1) { rebaseHist(doc.history, change) rebased.push(doc.history) } makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)) }) } // Revert a change stored in a document's history. export function makeChangeFromHistory(doc, type, allowSelectionOnly) { let suppress = doc.cm && doc.cm.state.suppressEdits if (suppress && !allowSelectionOnly) return let hist = doc.history, event, selAfter = doc.sel let source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done // Verify that there is a useable event (so that ctrl-z won't // needlessly clear selection events) let i = 0 for (; i < source.length; i++) { event = source[i] if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) break } if (i == source.length) return hist.lastOrigin = hist.lastSelOrigin = null for (;;) { event = source.pop() if (event.ranges) { pushSelectionToHistory(event, dest) if (allowSelectionOnly && !event.equals(doc.sel)) { setSelection(doc, event, {clearRedo: false}) return } selAfter = event } else if (suppress) { source.push(event) return } else break } // Build up a reverse change object to add to the opposite history // stack (redo when undoing, and vice versa). let antiChanges = [] pushSelectionToHistory(selAfter, dest) dest.push({changes: antiChanges, generation: hist.generation}) hist.generation = event.generation || ++hist.maxGeneration let filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange") for (let i = event.changes.length - 1; i >= 0; --i) { let change = event.changes[i] change.origin = type if (filter && !filterChange(doc, change, false)) { source.length = 0 return } antiChanges.push(historyChangeFromChange(doc, change)) let after = i ? computeSelAfterChange(doc, change) : lst(source) makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)) if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}) let rebased = [] // Propagate to the linked documents linkedDocs(doc, (doc, sharedHist) => { if (!sharedHist && indexOf(rebased, doc.history) == -1) { rebaseHist(doc.history, change) rebased.push(doc.history) } makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)) }) } } // Sub-views need their line numbers shifted when text is added // above or below them in the parent document. function shiftDoc(doc, distance) { if (distance == 0) return doc.first += distance doc.sel = new Selection(map(doc.sel.ranges, range => new Range( Pos(range.anchor.line + distance, range.anchor.ch), Pos(range.head.line + distance, range.head.ch) )), doc.sel.primIndex) if (doc.cm) { regChange(doc.cm, doc.first, doc.first - distance, distance) for (let d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) regLineChange(doc.cm, l, "gutter") } } // More lower-level change function, handling only a single document // (not linked ones). function makeChangeSingleDoc(doc, change, selAfter, spans) { if (doc.cm && !doc.cm.curOp) return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) if (change.to.line < doc.first) { shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)) return } if (change.from.line > doc.lastLine()) return // Clip the change to the size of this doc if (change.from.line < doc.first) { let shift = change.text.length - 1 - (doc.first - change.from.line) shiftDoc(doc, shift) change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), text: [lst(change.text)], origin: change.origin} } let last = doc.lastLine() if (change.to.line > last) { change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), text: [change.text[0]], origin: change.origin} } change.removed = getBetween(doc, change.from, change.to) if (!selAfter) selAfter = computeSelAfterChange(doc, change) if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans) else updateDoc(doc, change, spans) setSelectionNoUndo(doc, selAfter, sel_dontScroll) if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0))) doc.cantEdit = false } // Handle the interaction of a change to a document with the editor // that this document is part of. function makeChangeSingleDocInEditor(cm, change, spans) { let doc = cm.doc, display = cm.display, from = change.from, to = change.to let recomputeMaxLength = false, checkWidthStart = from.line if (!cm.options.lineWrapping) { checkWidthStart = lineNo(visualLine(getLine(doc, from.line))) doc.iter(checkWidthStart, to.line + 1, line => { if (line == display.maxLine) { recomputeMaxLength = true return true } }) } if (doc.sel.contains(change.from, change.to) > -1) signalCursorActivity(cm) updateDoc(doc, change, spans, estimateHeight(cm)) if (!cm.options.lineWrapping) { doc.iter(checkWidthStart, from.line + change.text.length, line => { let len = lineLength(line) if (len > display.maxLineLength) { display.maxLine = line display.maxLineLength = len display.maxLineChanged = true recomputeMaxLength = false } }) if (recomputeMaxLength) cm.curOp.updateMaxLine = true } retreatFrontier(doc, from.line) startWorker(cm, 400) let lendiff = change.text.length - (to.line - from.line) - 1 // Remember that these lines changed, for updating the display if (change.full) regChange(cm) else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) regLineChange(cm, from.line, "text") else regChange(cm, from.line, to.line + 1, lendiff) let changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change") if (changeHandler || changesHandler) { let obj = { from: from, to: to, text: change.text, removed: change.removed, origin: change.origin } if (changeHandler) signalLater(cm, "change", cm, obj) if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) } cm.display.selForContextMenu = null } export function replaceRange(doc, code, from, to, origin) { if (!to) to = from if (cmp(to, from) < 0) [from, to] = [to, from] if (typeof code == "string") code = doc.splitLines(code) makeChange(doc, {from, to, text: code, origin}) } // Rebasing/resetting history to deal with externally-sourced changes function rebaseHistSelSingle(pos, from, to, diff) { if (to < pos.line) { pos.line += diff } else if (from < pos.line) { pos.line = from pos.ch = 0 } } // Tries to rebase an array of history events given a change in the // document. If the change touches the same lines as the event, the // event, and everything 'behind' it, is discarded. If the change is // before the event, the event's positions are updated. Uses a // copy-on-write scheme for the positions, to avoid having to // reallocate them all on every rebase, but also avoid problems with // shared position objects being unsafely updated. function rebaseHistArray(array, from, to, diff) { for (let i = 0; i < array.length; ++i) { let sub = array[i], ok = true if (sub.ranges) { if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true } for (let j = 0; j < sub.ranges.length; j++) { rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff) rebaseHistSelSingle(sub.ranges[j].head, from, to, diff) } continue } for (let j = 0; j < sub.changes.length; ++j) { let cur = sub.changes[j] if (to < cur.from.line) { cur.from = Pos(cur.from.line + diff, cur.from.ch) cur.to = Pos(cur.to.line + diff, cur.to.ch) } else if (from <= cur.to.line) { ok = false break } } if (!ok) { array.splice(0, i + 1) i = 0 } } } function rebaseHist(hist, change) { let from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1 rebaseHistArray(hist.done, from, to, diff) rebaseHistArray(hist.undone, from, to, diff) } // Utility for applying a change to a line by handle or number, // returning the number and optionally registering the line as // changed. export function changeLine(doc, handle, changeType, op) { let no = handle, line = handle if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)) else no = lineNo(handle) if (no == null) return null if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType) return line } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/chunk.js ================================================ import { cleanUpLine } from "../line/line_data.js" import { indexOf } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" // The document is represented as a BTree consisting of leaves, with // chunk of lines in them, and branches, with up to ten leaves or // other branch nodes below them. The top node is always a branch // node, and is the document object itself (meaning it has // additional methods and properties). // // All nodes have parent links. The tree is used both to go from // line numbers to line objects, and to go from objects to numbers. // It also indexes by height, and is used to convert between height // and line object, and to find the total height of the document. // // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html export function LeafChunk(lines) { this.lines = lines this.parent = null let height = 0 for (let i = 0; i < lines.length; ++i) { lines[i].parent = this height += lines[i].height } this.height = height } LeafChunk.prototype = { chunkSize() { return this.lines.length }, // Remove the n lines at offset 'at'. removeInner(at, n) { for (let i = at, e = at + n; i < e; ++i) { let line = this.lines[i] this.height -= line.height cleanUpLine(line) signalLater(line, "delete") } this.lines.splice(at, n) }, // Helper used to collapse a small branch into a single leaf. collapse(lines) { lines.push.apply(lines, this.lines) }, // Insert the given array of lines at offset 'at', count them as // having the given height. insertInner(at, lines, height) { this.height += height this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) for (let i = 0; i < lines.length; ++i) lines[i].parent = this }, // Used to iterate over a part of the tree. iterN(at, n, op) { for (let e = at + n; at < e; ++at) if (op(this.lines[at])) return true } } export function BranchChunk(children) { this.children = children let size = 0, height = 0 for (let i = 0; i < children.length; ++i) { let ch = children[i] size += ch.chunkSize(); height += ch.height ch.parent = this } this.size = size this.height = height this.parent = null } BranchChunk.prototype = { chunkSize() { return this.size }, removeInner(at, n) { this.size -= n for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at < sz) { let rm = Math.min(n, sz - at), oldHeight = child.height child.removeInner(at, rm) this.height -= oldHeight - child.height if (sz == rm) { this.children.splice(i--, 1); child.parent = null } if ((n -= rm) == 0) break at = 0 } else at -= sz } // If the result is smaller than 25 lines, ensure that it is a // single leaf node. if (this.size - n < 25 && (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { let lines = [] this.collapse(lines) this.children = [new LeafChunk(lines)] this.children[0].parent = this } }, collapse(lines) { for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines) }, insertInner(at, lines, height) { this.size += lines.length this.height += height for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at <= sz) { child.insertInner(at, lines, height) if (child.lines && child.lines.length > 50) { // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. let remaining = child.lines.length % 25 + 25 for (let pos = remaining; pos < child.lines.length;) { let leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) child.height -= leaf.height this.children.splice(++i, 0, leaf) leaf.parent = this } child.lines = child.lines.slice(0, remaining) this.maybeSpill() } break } at -= sz } }, // When a node has grown, check whether it should be split. maybeSpill() { if (this.children.length <= 10) return let me = this do { let spilled = me.children.splice(me.children.length - 5, 5) let sibling = new BranchChunk(spilled) if (!me.parent) { // Become the parent node let copy = new BranchChunk(me.children) copy.parent = me me.children = [copy, sibling] me = copy } else { me.size -= sibling.size me.height -= sibling.height let myIndex = indexOf(me.parent.children, me) me.parent.children.splice(myIndex + 1, 0, sibling) } sibling.parent = me.parent } while (me.children.length > 10) me.parent.maybeSpill() }, iterN(at, n, op) { for (let i = 0; i < this.children.length; ++i) { let child = this.children[i], sz = child.chunkSize() if (at < sz) { let used = Math.min(n, sz - at) if (child.iterN(at, used, op)) return true if ((n -= used) == 0) break at = 0 } else at -= sz } } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/document_data.js ================================================ import { loadMode } from "../display/mode_state.js" import { runInOp } from "../display/operations.js" import { regChange } from "../display/view_tracking.js" import { Line, updateLine } from "../line/line_data.js" import { findMaxLine } from "../line/spans.js" import { getLine } from "../line/utils_line.js" import { estimateLineHeights } from "../measurement/position_measurement.js" import { addClass, rmClass } from "../util/dom.js" import { lst } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" // DOCUMENT DATA STRUCTURE // By default, updates that start and end at the beginning of a line // are treated specially, in order to make the association of line // widgets and marker elements with the text behave more intuitive. export function isWholeLineUpdate(doc, change) { return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && (!doc.cm || doc.cm.options.wholeLineUpdateBefore) } // Perform a change on the document data structure. export function updateDoc(doc, change, markedSpans, estimateHeight) { function spansFor(n) {return markedSpans ? markedSpans[n] : null} function update(line, text, spans) { updateLine(line, text, spans, estimateHeight) signalLater(line, "change", line, change) } function linesFor(start, end) { let result = [] for (let i = start; i < end; ++i) result.push(new Line(text[i], spansFor(i), estimateHeight)) return result } let from = change.from, to = change.to, text = change.text let firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) let lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line // Adjust the line structure if (change.full) { doc.insert(0, linesFor(0, text.length)) doc.remove(text.length, doc.size - text.length) } else if (isWholeLineUpdate(doc, change)) { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. let added = linesFor(0, text.length - 1) update(lastLine, lastLine.text, lastSpans) if (nlines) doc.remove(from.line, nlines) if (added.length) doc.insert(from.line, added) } else if (firstLine == lastLine) { if (text.length == 1) { update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) } else { let added = linesFor(1, text.length - 1) added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) doc.insert(from.line + 1, added) } } else if (text.length == 1) { update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)) doc.remove(from.line + 1, nlines) } else { update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans) let added = linesFor(1, text.length - 1) if (nlines > 1) doc.remove(from.line + 1, nlines - 1) doc.insert(from.line + 1, added) } signalLater(doc, "change", doc, change) } // Call f for all linked documents. export function linkedDocs(doc, f, sharedHistOnly) { function propagate(doc, skip, sharedHist) { if (doc.linked) for (let i = 0; i < doc.linked.length; ++i) { let rel = doc.linked[i] if (rel.doc == skip) continue let shared = sharedHist && rel.sharedHist if (sharedHistOnly && !shared) continue f(rel.doc, shared) propagate(rel.doc, doc, shared) } } propagate(doc, null, true) } // Attach a document to an editor. export function attachDoc(cm, doc) { if (doc.cm) throw new Error("This document is already in use.") cm.doc = doc doc.cm = cm estimateLineHeights(cm) loadMode(cm) setDirectionClass(cm) cm.options.direction = doc.direction if (!cm.options.lineWrapping) findMaxLine(cm) cm.options.mode = doc.modeOption regChange(cm) } function setDirectionClass(cm) { ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") } export function directionChanged(cm) { runInOp(cm, () => { setDirectionClass(cm) regChange(cm) }) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/history.js ================================================ import { cmp, copyPos } from "../line/pos.js" import { stretchSpansOverChange } from "../line/spans.js" import { getBetween } from "../line/utils_line.js" import { signal } from "../util/event.js" import { indexOf, lst } from "../util/misc.js" import { changeEnd } from "./change_measurement.js" import { linkedDocs } from "./document_data.js" import { Selection } from "./selection.js" export function History(prev) { // Arrays of change events and selections. Doing something adds an // event to done and clears undo. Undoing moves events from done // to undone, redoing moves them in the other direction. this.done = []; this.undone = [] this.undoDepth = prev ? prev.undoDepth : Infinity // Used to track when changes can be merged into a single undo // event this.lastModTime = this.lastSelTime = 0 this.lastOp = this.lastSelOp = null this.lastOrigin = this.lastSelOrigin = null // Used by the isClean() method this.generation = this.maxGeneration = prev ? prev.maxGeneration : 1 } // Create a history change event from an updateDoc-style change // object. export function historyChangeFromChange(doc, change) { let histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) linkedDocs(doc, doc => attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1), true) return histChange } // Pop all selection events off the end of a history array. Stop at // a change event. function clearSelectionEvents(array) { while (array.length) { let last = lst(array) if (last.ranges) array.pop() else break } } // Find the top change event in the history. Pop off selection // events that are in the way. function lastChangeEvent(hist, force) { if (force) { clearSelectionEvents(hist.done) return lst(hist.done) } else if (hist.done.length && !lst(hist.done).ranges) { return lst(hist.done) } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { hist.done.pop() return lst(hist.done) } } // Register a change in the history. Merges changes that are within // a single operation, or are close together with an origin that // allows merging (starting with "+") into a single event. export function addChangeToHistory(doc, change, selAfter, opId) { let hist = doc.history hist.undone.length = 0 let time = +new Date, cur let last if ((hist.lastOp == opId || hist.lastOrigin == change.origin && change.origin && ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || change.origin.charAt(0) == "*")) && (cur = lastChangeEvent(hist, hist.lastOp == opId))) { // Merge this change into the last event last = lst(cur.changes) if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { // Optimized case for simple insertion -- don't want to add // new changesets for every character typed last.to = changeEnd(change) } else { // Add new sub-event cur.changes.push(historyChangeFromChange(doc, change)) } } else { // Can not be merged, start a new event. let before = lst(hist.done) if (!before || !before.ranges) pushSelectionToHistory(doc.sel, hist.done) cur = {changes: [historyChangeFromChange(doc, change)], generation: hist.generation} hist.done.push(cur) while (hist.done.length > hist.undoDepth) { hist.done.shift() if (!hist.done[0].ranges) hist.done.shift() } } hist.done.push(selAfter) hist.generation = ++hist.maxGeneration hist.lastModTime = hist.lastSelTime = time hist.lastOp = hist.lastSelOp = opId hist.lastOrigin = hist.lastSelOrigin = change.origin if (!last) signal(doc, "historyAdded") } function selectionEventCanBeMerged(doc, origin, prev, sel) { let ch = origin.charAt(0) return ch == "*" || ch == "+" && prev.ranges.length == sel.ranges.length && prev.somethingSelected() == sel.somethingSelected() && new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) } // Called whenever the selection changes, sets the new selection as // the pending selection in the history, and pushes the old pending // selection into the 'done' array when it was significantly // different (in number of selected ranges, emptiness, or time). export function addSelectionToHistory(doc, sel, opId, options) { let hist = doc.history, origin = options && options.origin // A new event is started when the previous origin does not match // the current, or the origins don't allow matching. Origins // starting with * are always merged, those starting with + are // merged when similar and close together in time. if (opId == hist.lastSelOp || (origin && hist.lastSelOrigin == origin && (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) hist.done[hist.done.length - 1] = sel else pushSelectionToHistory(sel, hist.done) hist.lastSelTime = +new Date hist.lastSelOrigin = origin hist.lastSelOp = opId if (options && options.clearRedo !== false) clearSelectionEvents(hist.undone) } export function pushSelectionToHistory(sel, dest) { let top = lst(dest) if (!(top && top.ranges && top.equals(sel))) dest.push(sel) } // Used to store marked span information in the history. function attachLocalSpans(doc, change, from, to) { let existing = change["spans_" + doc.id], n = 0 doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), line => { if (line.markedSpans) (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans ++n }) } // When un/re-doing restores text containing marked spans, those // that have been explicitly cleared should not be restored. function removeClearedSpans(spans) { if (!spans) return null let out for (let i = 0; i < spans.length; ++i) { if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i) } else if (out) out.push(spans[i]) } return !out ? spans : out.length ? out : null } // Retrieve and filter the old marked spans stored in a change event. function getOldSpans(doc, change) { let found = change["spans_" + doc.id] if (!found) return null let nw = [] for (let i = 0; i < change.text.length; ++i) nw.push(removeClearedSpans(found[i])) return nw } // Used for un/re-doing changes from the history. Combines the // result of computing the existing spans with the set of spans that // existed in the history (so that deleting around a span and then // undoing brings back the span). export function mergeOldSpans(doc, change) { let old = getOldSpans(doc, change) let stretched = stretchSpansOverChange(doc, change) if (!old) return stretched if (!stretched) return old for (let i = 0; i < old.length; ++i) { let oldCur = old[i], stretchCur = stretched[i] if (oldCur && stretchCur) { spans: for (let j = 0; j < stretchCur.length; ++j) { let span = stretchCur[j] for (let k = 0; k < oldCur.length; ++k) if (oldCur[k].marker == span.marker) continue spans oldCur.push(span) } } else if (stretchCur) { old[i] = stretchCur } } return old } // Used both to provide a JSON-safe object in .getHistory, and, when // detaching a document, to split the history in two export function copyHistoryArray(events, newGroup, instantiateSel) { let copy = [] for (let i = 0; i < events.length; ++i) { let event = events[i] if (event.ranges) { copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) continue } let changes = event.changes, newChanges = [] copy.push({changes: newChanges}) for (let j = 0; j < changes.length; ++j) { let change = changes[j], m newChanges.push({from: change.from, to: change.to, text: change.text}) if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { if (indexOf(newGroup, Number(m[1])) > -1) { lst(newChanges)[prop] = change[prop] delete change[prop] } } } } return copy } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/line_widget.js ================================================ import { runInOp } from "../display/operations.js" import { addToScrollTop } from "../display/scrolling.js" import { regLineChange } from "../display/view_tracking.js" import { heightAtLine, lineIsHidden } from "../line/spans.js" import { lineNo, updateLineHeight } from "../line/utils_line.js" import { widgetHeight } from "../measurement/widgets.js" import { changeLine } from "./changes.js" import { eventMixin } from "../util/event.js" import { signalLater } from "../util/operation_group.js" // Line widgets are block elements displayed above or below a line. export class LineWidget { constructor(doc, node, options) { if (options) for (let opt in options) if (options.hasOwnProperty(opt)) this[opt] = options[opt] this.doc = doc this.node = node } clear() { let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) if (no == null || !ws) return for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1) if (!ws.length) line.widgets = null let height = widgetHeight(this) updateLineHeight(line, Math.max(0, line.height - height)) if (cm) { runInOp(cm, () => { adjustScrollWhenAboveVisible(cm, line, -height) regLineChange(cm, no, "widget") }) signalLater(cm, "lineWidgetCleared", cm, this, no) } } changed() { let oldH = this.height, cm = this.doc.cm, line = this.line this.height = null let diff = widgetHeight(this) - oldH if (!diff) return if (!lineIsHidden(this.doc, line)) updateLineHeight(line, line.height + diff) if (cm) { runInOp(cm, () => { cm.curOp.forceUpdate = true adjustScrollWhenAboveVisible(cm, line, diff) signalLater(cm, "lineWidgetChanged", cm, this, lineNo(line)) }) } } } eventMixin(LineWidget) function adjustScrollWhenAboveVisible(cm, line, diff) { if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) addToScrollTop(cm, diff) } export function addLineWidget(doc, handle, node, options) { let widget = new LineWidget(doc, node, options) let cm = doc.cm if (cm && widget.noHScroll) cm.display.alignWidgets = true changeLine(doc, handle, "widget", line => { let widgets = line.widgets || (line.widgets = []) if (widget.insertAt == null) widgets.push(widget) else widgets.splice(Math.min(widgets.length, Math.max(0, widget.insertAt)), 0, widget) widget.line = line if (cm && !lineIsHidden(doc, line)) { let aboveVisible = heightAtLine(line) < doc.scrollTop updateLineHeight(line, line.height + widgetHeight(widget)) if (aboveVisible) addToScrollTop(cm, widget.height) cm.curOp.forceUpdate = true } return true }) if (cm) signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)) return widget } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/mark_text.js ================================================ import { eltP } from "../util/dom.js" import { eventMixin, hasHandler, on } from "../util/event.js" import { endOperation, operation, runInOp, startOperation } from "../display/operations.js" import { clipPos, cmp, Pos } from "../line/pos.js" import { lineNo, updateLineHeight } from "../line/utils_line.js" import { clearLineMeasurementCacheFor, findViewForLine, textHeight } from "../measurement/position_measurement.js" import { seeReadOnlySpans, seeCollapsedSpans } from "../line/saw_special_spans.js" import { addMarkedSpan, conflictingCollapsedRange, getMarkedSpanFor, lineIsHidden, lineLength, MarkedSpan, removeMarkedSpan, visualLine } from "../line/spans.js" import { copyObj, indexOf, lst } from "../util/misc.js" import { signalLater } from "../util/operation_group.js" import { widgetHeight } from "../measurement/widgets.js" import { regChange, regLineChange } from "../display/view_tracking.js" import { linkedDocs } from "./document_data.js" import { addChangeToHistory } from "./history.js" import { reCheckSelection } from "./selection_updates.js" // TEXTMARKERS // Created with markText and setBookmark methods. A TextMarker is a // handle that can be used to clear or find a marked position in the // document. Line objects hold arrays (markedSpans) containing // {from, to, marker} object pointing to such marker objects, and // indicating that such a marker is present on that line. Multiple // lines may point to the same marker when it spans across lines. // The spans will have null for their from/to properties when the // marker continues beyond the start/end of the line. Markers have // links back to the lines they currently touch. // Collapsed markers have unique ids, in order to be able to order // them, which is needed for uniquely determining an outer marker // when they overlap (they may nest, but not partially overlap). let nextMarkerId = 0 export class TextMarker { constructor(doc, type) { this.lines = [] this.type = type this.doc = doc this.id = ++nextMarkerId } // Clear the marker. clear() { if (this.explicitlyCleared) return let cm = this.doc.cm, withOp = cm && !cm.curOp if (withOp) startOperation(cm) if (hasHandler(this, "clear")) { let found = this.find() if (found) signalLater(this, "clear", found.from, found.to) } let min = null, max = null for (let i = 0; i < this.lines.length; ++i) { let line = this.lines[i] let span = getMarkedSpanFor(line.markedSpans, this) if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text") else if (cm) { if (span.to != null) max = lineNo(line) if (span.from != null) min = lineNo(line) } line.markedSpans = removeMarkedSpan(line.markedSpans, span) if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) updateLineHeight(line, textHeight(cm.display)) } if (cm && this.collapsed && !cm.options.lineWrapping) for (let i = 0; i < this.lines.length; ++i) { let visual = visualLine(this.lines[i]), len = lineLength(visual) if (len > cm.display.maxLineLength) { cm.display.maxLine = visual cm.display.maxLineLength = len cm.display.maxLineChanged = true } } if (min != null && cm && this.collapsed) regChange(cm, min, max + 1) this.lines.length = 0 this.explicitlyCleared = true if (this.atomic && this.doc.cantEdit) { this.doc.cantEdit = false if (cm) reCheckSelection(cm.doc) } if (cm) signalLater(cm, "markerCleared", cm, this, min, max) if (withOp) endOperation(cm) if (this.parent) this.parent.clear() } // Find the position of the marker in the document. Returns a {from, // to} object by default. Side can be passed to get a specific side // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the // Pos objects returned contain a line object, rather than a line // number (used to prevent looking up the same line twice). find(side, lineObj) { if (side == null && this.type == "bookmark") side = 1 let from, to for (let i = 0; i < this.lines.length; ++i) { let line = this.lines[i] let span = getMarkedSpanFor(line.markedSpans, this) if (span.from != null) { from = Pos(lineObj ? line : lineNo(line), span.from) if (side == -1) return from } if (span.to != null) { to = Pos(lineObj ? line : lineNo(line), span.to) if (side == 1) return to } } return from && {from: from, to: to} } // Signals that the marker's widget changed, and surrounding layout // should be recomputed. changed() { let pos = this.find(-1, true), widget = this, cm = this.doc.cm if (!pos || !cm) return runInOp(cm, () => { let line = pos.line, lineN = lineNo(pos.line) let view = findViewForLine(cm, lineN) if (view) { clearLineMeasurementCacheFor(view) cm.curOp.selectionChanged = cm.curOp.forceUpdate = true } cm.curOp.updateMaxLine = true if (!lineIsHidden(widget.doc, line) && widget.height != null) { let oldHeight = widget.height widget.height = null let dHeight = widgetHeight(widget) - oldHeight if (dHeight) updateLineHeight(line, line.height + dHeight) } signalLater(cm, "markerChanged", cm, this) }) } attachLine(line) { if (!this.lines.length && this.doc.cm) { let op = this.doc.cm.curOp if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } this.lines.push(line) } detachLine(line) { this.lines.splice(indexOf(this.lines, line), 1) if (!this.lines.length && this.doc.cm) { let op = this.doc.cm.curOp ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) } } } eventMixin(TextMarker) // Create a marker, wire it up to the right lines, and export function markText(doc, from, to, options, type) { // Shared markers (across linked documents) are handled separately // (markTextShared will call out to this again, once per // document). if (options && options.shared) return markTextShared(doc, from, to, options, type) // Ensure we are in an operation. if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type) let marker = new TextMarker(doc, type), diff = cmp(from, to) if (options) copyObj(options, marker, false) // Don't connect empty markers unless clearWhenEmpty is false if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) return marker if (marker.replacedWith) { // Showing up as a widget implies collapsed (widget replaces text) marker.collapsed = true marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget") if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true") if (options.insertLeft) marker.widgetNode.insertLeft = true } if (marker.collapsed) { if (conflictingCollapsedRange(doc, from.line, from, to, marker) || from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) throw new Error("Inserting collapsed marker partially overlapping an existing one") seeCollapsedSpans() } if (marker.addToHistory) addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN) let curLine = from.line, cm = doc.cm, updateMaxLine doc.iter(curLine, to.line + 1, line => { if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) updateMaxLine = true if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0) addMarkedSpan(line, new MarkedSpan(marker, curLine == from.line ? from.ch : null, curLine == to.line ? to.ch : null), doc.cm && doc.cm.curOp) ++curLine }) // lineIsHidden depends on the presence of the spans, so needs a second pass if (marker.collapsed) doc.iter(from.line, to.line + 1, line => { if (lineIsHidden(doc, line)) updateLineHeight(line, 0) }) if (marker.clearOnEnter) on(marker, "beforeCursorEnter", () => marker.clear()) if (marker.readOnly) { seeReadOnlySpans() if (doc.history.done.length || doc.history.undone.length) doc.clearHistory() } if (marker.collapsed) { marker.id = ++nextMarkerId marker.atomic = true } if (cm) { // Sync editor state if (updateMaxLine) cm.curOp.updateMaxLine = true if (marker.collapsed) regChange(cm, from.line, to.line + 1) else if (marker.className || marker.startStyle || marker.endStyle || marker.css || marker.attributes || marker.title) for (let i = from.line; i <= to.line; i++) regLineChange(cm, i, "text") if (marker.atomic) reCheckSelection(cm.doc) signalLater(cm, "markerAdded", cm, marker) } return marker } // SHARED TEXTMARKERS // A shared marker spans multiple linked documents. It is // implemented as a meta-marker-object controlling multiple normal // markers. export class SharedTextMarker { constructor(markers, primary) { this.markers = markers this.primary = primary for (let i = 0; i < markers.length; ++i) markers[i].parent = this } clear() { if (this.explicitlyCleared) return this.explicitlyCleared = true for (let i = 0; i < this.markers.length; ++i) this.markers[i].clear() signalLater(this, "clear") } find(side, lineObj) { return this.primary.find(side, lineObj) } } eventMixin(SharedTextMarker) function markTextShared(doc, from, to, options, type) { options = copyObj(options) options.shared = false let markers = [markText(doc, from, to, options, type)], primary = markers[0] let widget = options.widgetNode linkedDocs(doc, doc => { if (widget) options.widgetNode = widget.cloneNode(true) markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)) for (let i = 0; i < doc.linked.length; ++i) if (doc.linked[i].isParent) return primary = lst(markers) }) return new SharedTextMarker(markers, primary) } export function findSharedMarkers(doc) { return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), m => m.parent) } export function copySharedMarkers(doc, markers) { for (let i = 0; i < markers.length; i++) { let marker = markers[i], pos = marker.find() let mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to) if (cmp(mFrom, mTo)) { let subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type) marker.markers.push(subMark) subMark.parent = marker } } } export function detachSharedMarkers(markers) { for (let i = 0; i < markers.length; i++) { let marker = markers[i], linked = [marker.primary.doc] linkedDocs(marker.primary.doc, d => linked.push(d)) for (let j = 0; j < marker.markers.length; j++) { let subMarker = marker.markers[j] if (indexOf(linked, subMarker.doc) == -1) { subMarker.parent = null marker.markers.splice(j--, 1) } } } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/selection.js ================================================ import { cmp, copyPos, equalCursorPos, maxPos, minPos } from "../line/pos.js" import { indexOf } from "../util/misc.js" // Selection objects are immutable. A new one is created every time // the selection changes. A selection is one or more non-overlapping // (and non-touching) ranges, sorted, and an integer that indicates // which one is the primary selection (the one that's scrolled into // view, that getCursor returns, etc). export class Selection { constructor(ranges, primIndex) { this.ranges = ranges this.primIndex = primIndex } primary() { return this.ranges[this.primIndex] } equals(other) { if (other == this) return true if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false for (let i = 0; i < this.ranges.length; i++) { let here = this.ranges[i], there = other.ranges[i] if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) return false } return true } deepCopy() { let out = [] for (let i = 0; i < this.ranges.length; i++) out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)) return new Selection(out, this.primIndex) } somethingSelected() { for (let i = 0; i < this.ranges.length; i++) if (!this.ranges[i].empty()) return true return false } contains(pos, end) { if (!end) end = pos for (let i = 0; i < this.ranges.length; i++) { let range = this.ranges[i] if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) return i } return -1 } } export class Range { constructor(anchor, head) { this.anchor = anchor; this.head = head } from() { return minPos(this.anchor, this.head) } to() { return maxPos(this.anchor, this.head) } empty() { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch } } // Take an unsorted, potentially overlapping set of ranges, and // build a selection out of it. 'Consumes' ranges array (modifying // it). export function normalizeSelection(cm, ranges, primIndex) { let mayTouch = cm && cm.options.selectionsMayTouch let prim = ranges[primIndex] ranges.sort((a, b) => cmp(a.from(), b.from())) primIndex = indexOf(ranges, prim) for (let i = 1; i < ranges.length; i++) { let cur = ranges[i], prev = ranges[i - 1] let diff = cmp(prev.to(), cur.from()) if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { let from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()) let inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head if (i <= primIndex) --primIndex ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)) } } return new Selection(ranges, primIndex) } export function simpleSelection(anchor, head) { return new Selection([new Range(anchor, head || anchor)], 0) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/model/selection_updates.js ================================================ import { signalLater } from "../util/operation_group.js" import { ensureCursorVisible } from "../display/scrolling.js" import { clipPos, cmp, Pos } from "../line/pos.js" import { getLine } from "../line/utils_line.js" import { hasHandler, signal, signalCursorActivity } from "../util/event.js" import { lst, sel_dontScroll } from "../util/misc.js" import { addSelectionToHistory } from "./history.js" import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js" // The 'scroll' parameter given to many of these indicated whether // the new cursor position should be scrolled into view after // modifying the selection. // If shift is held or the extend flag is set, extends a range to // include a given position (and optionally a second position). // Otherwise, simply returns the range between the given positions. // Used for cursor motion and such. export function extendRange(range, head, other, extend) { if (extend) { let anchor = range.anchor if (other) { let posBefore = cmp(head, anchor) < 0 if (posBefore != (cmp(other, anchor) < 0)) { anchor = head head = other } else if (posBefore != (cmp(head, other) < 0)) { head = other } } return new Range(anchor, head) } else { return new Range(other || head, head) } } // Extend the primary selection range, discard the rest. export function extendSelection(doc, head, other, options, extend) { if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend) setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) } // Extend all selections (pos is an array of selections with length // equal the number of selections) export function extendSelections(doc, heads, options) { let out = [] let extend = doc.cm && (doc.cm.display.shift || doc.extend) for (let i = 0; i < doc.sel.ranges.length; i++) out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) let newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex) setSelection(doc, newSel, options) } // Updates a single range in the selection. export function replaceOneSelection(doc, i, range, options) { let ranges = doc.sel.ranges.slice(0) ranges[i] = range setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options) } // Reset the selection to a single range. export function setSimpleSelection(doc, anchor, head, options) { setSelection(doc, simpleSelection(anchor, head), options) } // Give beforeSelectionChange handlers a change to influence a // selection update. function filterSelectionChange(doc, sel, options) { let obj = { ranges: sel.ranges, update: function(ranges) { this.ranges = [] for (let i = 0; i < ranges.length; i++) this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), clipPos(doc, ranges[i].head)) }, origin: options && options.origin } signal(doc, "beforeSelectionChange", doc, obj) if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj) if (obj.ranges != sel.ranges) return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) else return sel } export function setSelectionReplaceHistory(doc, sel, options) { let done = doc.history.done, last = lst(done) if (last && last.ranges) { done[done.length - 1] = sel setSelectionNoUndo(doc, sel, options) } else { setSelection(doc, sel, options) } } // Set a new selection. export function setSelection(doc, sel, options) { setSelectionNoUndo(doc, sel, options) addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) } export function setSelectionNoUndo(doc, sel, options) { if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) sel = filterSelectionChange(doc, sel, options) let bias = options && options.bias || (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor") ensureCursorVisible(doc.cm) } function setSelectionInner(doc, sel) { if (sel.equals(doc.sel)) return doc.sel = sel if (doc.cm) { doc.cm.curOp.updateInput = 1 doc.cm.curOp.selectionChanged = true signalCursorActivity(doc.cm) } signalLater(doc, "cursorActivity", doc) } // Verify that the selection does not partially select any atomic // marked ranges. export function reCheckSelection(doc) { setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) } // Return a selection that does not partially select any atomic // ranges. function skipAtomicInSelection(doc, sel, bias, mayClear) { let out for (let i = 0; i < sel.ranges.length; i++) { let range = sel.ranges[i] let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) let newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear) if (out || newAnchor != range.anchor || newHead != range.head) { if (!out) out = sel.ranges.slice(0, i) out[i] = new Range(newAnchor, newHead) } } return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel } function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { let line = getLine(doc, pos.line) if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { let sp = line.markedSpans[i], m = sp.marker // Determine if we should prevent the cursor being placed to the left/right of an atomic marker // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it // is with selectLeft/Right let preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft let preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) { if (mayClear) { signal(m, "beforeCursorEnter") if (m.explicitlyCleared) { if (!line.markedSpans) break else {--i; continue} } } if (!m.atomic) continue if (oldPos) { let near = m.find(dir < 0 ? 1 : -1), diff if (dir < 0 ? preventCursorRight : preventCursorLeft) near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) return skipAtomicInner(doc, near, pos, dir, mayClear) } let far = m.find(dir < 0 ? -1 : 1) if (dir < 0 ? preventCursorLeft : preventCursorRight) far = movePos(doc, far, dir, far.line == pos.line ? line : null) return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null } } return pos } // Ensure a given position is not inside an atomic range. export function skipAtomic(doc, pos, oldPos, bias, mayClear) { let dir = bias || 1 let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) if (!found) { doc.cantEdit = true return Pos(doc.first, 0) } return found } function movePos(doc, pos, dir, line) { if (dir < 0 && pos.ch == 0) { if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)) else return null } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0) else return null } else { return new Pos(pos.line, pos.ch + dir) } } export function selectAll(cm) { cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) } ================================================ FILE: public/assets/lib/vendor/codemirror/src/modes.js ================================================ import { copyObj, createObj } from "./util/misc.js" // Known modes, by name and by MIME export let modes = {}, mimeModes = {} // Extra arguments are stored as the mode's dependencies, which is // used by (legacy) mechanisms like loadmode.js to automatically // load a mode. (Preferred mechanism is the require/define calls.) export function defineMode(name, mode) { if (arguments.length > 2) mode.dependencies = Array.prototype.slice.call(arguments, 2) modes[name] = mode } export function defineMIME(mime, spec) { mimeModes[mime] = spec } // Given a MIME type, a {name, ...options} config object, or a name // string, return a mode config object. export function resolveMode(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec] } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { let found = mimeModes[spec.name] if (typeof found == "string") found = {name: found} spec = createObj(found, spec) spec.name = found.name } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { return resolveMode("application/xml") } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { return resolveMode("application/json") } if (typeof spec == "string") return {name: spec} else return spec || {name: "null"} } // Given a mode spec (anything that resolveMode accepts), find and // initialize an actual mode object. export function getMode(options, spec) { spec = resolveMode(spec) let mfactory = modes[spec.name] if (!mfactory) return getMode(options, "text/plain") let modeObj = mfactory(options, spec) if (modeExtensions.hasOwnProperty(spec.name)) { let exts = modeExtensions[spec.name] for (let prop in exts) { if (!exts.hasOwnProperty(prop)) continue if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop] modeObj[prop] = exts[prop] } } modeObj.name = spec.name if (spec.helperType) modeObj.helperType = spec.helperType if (spec.modeProps) for (let prop in spec.modeProps) modeObj[prop] = spec.modeProps[prop] return modeObj } // This can be used to attach properties to mode objects from // outside the actual mode definition. export let modeExtensions = {} export function extendMode(mode, properties) { let exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) copyObj(properties, exts) } export function copyState(mode, state) { if (state === true) return state if (mode.copyState) return mode.copyState(state) let nstate = {} for (let n in state) { let val = state[n] if (val instanceof Array) val = val.concat([]) nstate[n] = val } return nstate } // Given a mode and a state (for that mode), find the inner mode and // state at the position that the state refers to. export function innerMode(mode, state) { let info while (mode.innerMode) { info = mode.innerMode(state) if (!info || info.mode == mode) break state = info.state mode = info.mode } return info || {mode: mode, state: state} } export function startState(mode, a1, a2) { return mode.startState ? mode.startState(a1, a2) : true } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/StringStream.js ================================================ import { countColumn } from "./misc.js" // STRING STREAM // Fed to the mode parsers, provides helper functions to make // parsers more succinct. class StringStream { constructor(string, tabSize, lineOracle) { this.pos = this.start = 0 this.string = string this.tabSize = tabSize || 8 this.lastColumnPos = this.lastColumnValue = 0 this.lineStart = 0 this.lineOracle = lineOracle } eol() {return this.pos >= this.string.length} sol() {return this.pos == this.lineStart} peek() {return this.string.charAt(this.pos) || undefined} next() { if (this.pos < this.string.length) return this.string.charAt(this.pos++) } eat(match) { let ch = this.string.charAt(this.pos) let ok if (typeof match == "string") ok = ch == match else ok = ch && (match.test ? match.test(ch) : match(ch)) if (ok) {++this.pos; return ch} } eatWhile(match) { let start = this.pos while (this.eat(match)){} return this.pos > start } eatSpace() { let start = this.pos while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos return this.pos > start } skipToEnd() {this.pos = this.string.length} skipTo(ch) { let found = this.string.indexOf(ch, this.pos) if (found > -1) {this.pos = found; return true} } backUp(n) {this.pos -= n} column() { if (this.lastColumnPos < this.start) { this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue) this.lastColumnPos = this.start } return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) } indentation() { return countColumn(this.string, null, this.tabSize) - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) } match(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { let cased = str => caseInsensitive ? str.toLowerCase() : str let substr = this.string.substr(this.pos, pattern.length) if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length return true } } else { let match = this.string.slice(this.pos).match(pattern) if (match && match.index > 0) return null if (match && consume !== false) this.pos += match[0].length return match } } current(){return this.string.slice(this.start, this.pos)} hideFirstChars(n, inner) { this.lineStart += n try { return inner() } finally { this.lineStart -= n } } lookAhead(n) { let oracle = this.lineOracle return oracle && oracle.lookAhead(n) } baseToken() { let oracle = this.lineOracle return oracle && oracle.baseToken(this.pos) } } export default StringStream ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/bidi.js ================================================ import { lst } from "./misc.js" // BIDI HELPERS export function iterateBidiSections(order, from, to, f) { if (!order) return f(from, to, "ltr", 0) let found = false for (let i = 0; i < order.length; ++i) { let part = order[i] if (part.from < to && part.to > from || from == to && part.to == from) { f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i) found = true } } if (!found) f(from, to, "ltr") } export let bidiOther = null export function getBidiPartAt(order, ch, sticky) { let found bidiOther = null for (let i = 0; i < order.length; ++i) { let cur = order[i] if (cur.from < ch && cur.to > ch) return i if (cur.to == ch) { if (cur.from != cur.to && sticky == "before") found = i else bidiOther = i } if (cur.from == ch) { if (cur.from != cur.to && sticky != "before") found = i else bidiOther = i } } return found != null ? found : bidiOther } // Bidirectional ordering algorithm // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm // that this (partially) implements. // One-char codes used for character types: // L (L): Left-to-Right // R (R): Right-to-Left // r (AL): Right-to-Left Arabic // 1 (EN): European Number // + (ES): European Number Separator // % (ET): European Number Terminator // n (AN): Arabic Number // , (CS): Common Number Separator // m (NSM): Non-Spacing Mark // b (BN): Boundary Neutral // s (B): Paragraph Separator // t (S): Segment Separator // w (WS): Whitespace // N (ON): Other Neutrals // Returns null if characters are ordered as they appear // (left-to-right), or an array of sections ({from, to, level} // objects) in the order in which they occur visually. let bidiOrdering = (function() { // Character types for codepoints 0 to 0xff let lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" // Character types for codepoints 0x600 to 0x6f9 let arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" function charType(code) { if (code <= 0xf7) return lowTypes.charAt(code) else if (0x590 <= code && code <= 0x5f4) return "R" else if (0x600 <= code && code <= 0x6f9) return arabicTypes.charAt(code - 0x600) else if (0x6ee <= code && code <= 0x8ac) return "r" else if (0x2000 <= code && code <= 0x200b) return "w" else if (code == 0x200c) return "b" else return "L" } let bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ let isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ function BidiSpan(level, from, to) { this.level = level this.from = from; this.to = to } return function(str, direction) { let outerType = direction == "ltr" ? "L" : "R" if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) return false let len = str.length, types = [] for (let i = 0; i < len; ++i) types.push(charType(str.charCodeAt(i))) // W1. Examine each non-spacing mark (NSM) in the level run, and // change the type of the NSM to the type of the previous // character. If the NSM is at the start of the level run, it will // get the type of sor. for (let i = 0, prev = outerType; i < len; ++i) { let type = types[i] if (type == "m") types[i] = prev else prev = type } // W2. Search backwards from each instance of a European number // until the first strong type (R, L, AL, or sor) is found. If an // AL is found, change the type of the European number to Arabic // number. // W3. Change all ALs to R. for (let i = 0, cur = outerType; i < len; ++i) { let type = types[i] if (type == "1" && cur == "r") types[i] = "n" else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R" } } // W4. A single European separator between two European numbers // changes to a European number. A single common separator between // two numbers of the same type changes to that type. for (let i = 1, prev = types[0]; i < len - 1; ++i) { let type = types[i] if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1" else if (type == "," && prev == types[i+1] && (prev == "1" || prev == "n")) types[i] = prev prev = type } // W5. A sequence of European terminators adjacent to European // numbers changes to all European numbers. // W6. Otherwise, separators and terminators change to Other // Neutral. for (let i = 0; i < len; ++i) { let type = types[i] if (type == ",") types[i] = "N" else if (type == "%") { let end for (end = i + 1; end < len && types[end] == "%"; ++end) {} let replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" for (let j = i; j < end; ++j) types[j] = replace i = end - 1 } } // W7. Search backwards from each instance of a European number // until the first strong type (R, L, or sor) is found. If an L is // found, then change the type of the European number to L. for (let i = 0, cur = outerType; i < len; ++i) { let type = types[i] if (cur == "L" && type == "1") types[i] = "L" else if (isStrong.test(type)) cur = type } // N1. A sequence of neutrals takes the direction of the // surrounding strong text if the text on both sides has the same // direction. European and Arabic numbers act as if they were R in // terms of their influence on neutrals. Start-of-level-run (sor) // and end-of-level-run (eor) are used at level run boundaries. // N2. Any remaining neutrals take the embedding direction. for (let i = 0; i < len; ++i) { if (isNeutral.test(types[i])) { let end for (end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} let before = (i ? types[i-1] : outerType) == "L" let after = (end < len ? types[end] : outerType) == "L" let replace = before == after ? (before ? "L" : "R") : outerType for (let j = i; j < end; ++j) types[j] = replace i = end - 1 } } // Here we depart from the documented algorithm, in order to avoid // building up an actual levels array. Since there are only three // levels (0, 1, 2) in an implementation that doesn't take // explicit embedding into account, we can build up the order on // the fly, without following the level-based algorithm. let order = [], m for (let i = 0; i < len;) { if (countsAsLeft.test(types[i])) { let start = i for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} order.push(new BidiSpan(0, start, i)) } else { let pos = i, at = order.length, isRTL = direction == "rtl" ? 1 : 0 for (++i; i < len && types[i] != "L"; ++i) {} for (let j = pos; j < i;) { if (countsAsNum.test(types[j])) { if (pos < j) { order.splice(at, 0, new BidiSpan(1, pos, j)); at += isRTL } let nstart = j for (++j; j < i && countsAsNum.test(types[j]); ++j) {} order.splice(at, 0, new BidiSpan(2, nstart, j)) at += isRTL pos = j } else ++j } if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)) } } if (direction == "ltr") { if (order[0].level == 1 && (m = str.match(/^\s+/))) { order[0].from = m[0].length order.unshift(new BidiSpan(0, 0, m[0].length)) } if (lst(order).level == 1 && (m = str.match(/\s+$/))) { lst(order).to -= m[0].length order.push(new BidiSpan(0, len - m[0].length, len)) } } return direction == "rtl" ? order.reverse() : order } })() // Get the bidi ordering for the given line (and cache it). Returns // false for lines that are fully left-to-right, and an array of // BidiSpan objects otherwise. export function getOrder(line, direction) { let order = line.order if (order == null) order = line.order = bidiOrdering(line.text, direction) return order } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/browser.js ================================================ // Kludges for bugs and behavior differences that can't be feature // detected are enabled based on userAgent etc sniffing. let userAgent = navigator.userAgent let platform = navigator.platform export let gecko = /gecko\/\d/i.test(userAgent) let ie_upto10 = /MSIE \d/.test(userAgent) let ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) let edge = /Edge\/(\d+)/.exec(userAgent) export let ie = ie_upto10 || ie_11up || edge export let ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) export let webkit = !edge && /WebKit\//.test(userAgent) let qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) export let chrome = !edge && /Chrome\/(\d+)/.exec(userAgent) export let chrome_version = chrome && +chrome[1] export let presto = /Opera\//.test(userAgent) export let safari = /Apple Computer/.test(navigator.vendor) export let mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) export let phantom = /PhantomJS/.test(userAgent) export let ios = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2) export let android = /Android/.test(userAgent) // This is woefully incomplete. Suggestions for alternative methods welcome. export let mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) export let mac = ios || /Mac/.test(platform) export let chromeOS = /\bCrOS\b/.test(userAgent) export let windows = /win/i.test(platform) let presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) if (presto_version) presto_version = Number(presto_version[1]) if (presto_version && presto_version >= 15) { presto = false; webkit = true } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X export let flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) export let captureRightClick = gecko || (ie && ie_version >= 9) ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/dom.js ================================================ import { ie, ios } from "./browser.js" export function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } export let rmClass = function(node, cls) { let current = node.className let match = classTest(cls).exec(current) if (match) { let after = current.slice(match.index + match[0].length) node.className = current.slice(0, match.index) + (after ? match[1] + after : "") } } export function removeChildren(e) { for (let count = e.childNodes.length; count > 0; --count) e.removeChild(e.firstChild) return e } export function removeChildrenAndAdd(parent, e) { return removeChildren(parent).appendChild(e) } export function elt(tag, content, className, style) { let e = document.createElement(tag) if (className) e.className = className if (style) e.style.cssText = style if (typeof content == "string") e.appendChild(document.createTextNode(content)) else if (content) for (let i = 0; i < content.length; ++i) e.appendChild(content[i]) return e } // wrapper for elt, which removes the elt from the accessibility tree export function eltP(tag, content, className, style) { let e = elt(tag, content, className, style) e.setAttribute("role", "presentation") return e } export let range if (document.createRange) range = function(node, start, end, endNode) { let r = document.createRange() r.setEnd(endNode || node, end) r.setStart(node, start) return r } else range = function(node, start, end) { let r = document.body.createTextRange() try { r.moveToElementText(node.parentNode) } catch(e) { return r } r.collapse(true) r.moveEnd("character", end) r.moveStart("character", start) return r } export function contains(parent, child) { if (child.nodeType == 3) // Android browser always returns false when child is a textnode child = child.parentNode if (parent.contains) return parent.contains(child) do { if (child.nodeType == 11) child = child.host if (child == parent) return true } while (child = child.parentNode) } export function activeElt(rootNode) { // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. // IE < 10 will throw when accessed while the page is loading or in an iframe. // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. let doc = rootNode.ownerDocument || rootNode let activeElement try { activeElement = rootNode.activeElement } catch(e) { activeElement = doc.body || null } while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) activeElement = activeElement.shadowRoot.activeElement return activeElement } export function addClass(node, cls) { let current = node.className if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls } export function joinClasses(a, b) { let as = a.split(" ") for (let i = 0; i < as.length; i++) if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i] return b } export let selectInput = function(node) { node.select() } if (ios) // Mobile Safari apparently has a bug where select() is broken. selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length } else if (ie) // Suppress mysterious IE10 errors selectInput = function(node) { try { node.select() } catch(_e) {} } export function doc(cm) { return cm.display.wrapper.ownerDocument } export function root(cm) { return rootNode(cm.display.wrapper) } export function rootNode(element) { // Detect modern browsers (2017+). return element.getRootNode ? element.getRootNode() : element.ownerDocument } export function win(cm) { return doc(cm).defaultView } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/event.js ================================================ import { mac } from "./browser.js" import { indexOf } from "./misc.js" // EVENT HANDLING // Lightweight event framework. on/off also work on DOM nodes, // registering native DOM handlers. const noHandlers = [] export let on = function(emitter, type, f) { if (emitter.addEventListener) { emitter.addEventListener(type, f, false) } else if (emitter.attachEvent) { emitter.attachEvent("on" + type, f) } else { let map = emitter._handlers || (emitter._handlers = {}) map[type] = (map[type] || noHandlers).concat(f) } } export function getHandlers(emitter, type) { return emitter._handlers && emitter._handlers[type] || noHandlers } export function off(emitter, type, f) { if (emitter.removeEventListener) { emitter.removeEventListener(type, f, false) } else if (emitter.detachEvent) { emitter.detachEvent("on" + type, f) } else { let map = emitter._handlers, arr = map && map[type] if (arr) { let index = indexOf(arr, f) if (index > -1) map[type] = arr.slice(0, index).concat(arr.slice(index + 1)) } } } export function signal(emitter, type /*, values...*/) { let handlers = getHandlers(emitter, type) if (!handlers.length) return let args = Array.prototype.slice.call(arguments, 2) for (let i = 0; i < handlers.length; ++i) handlers[i].apply(null, args) } // The DOM events that CodeMirror handles can be overridden by // registering a (non-DOM) handler on the editor for the event name, // and preventDefault-ing the event in that handler. export function signalDOMEvent(cm, e, override) { if (typeof e == "string") e = {type: e, preventDefault: function() { this.defaultPrevented = true }} signal(cm, override || e.type, cm, e) return e_defaultPrevented(e) || e.codemirrorIgnore } export function signalCursorActivity(cm) { let arr = cm._handlers && cm._handlers.cursorActivity if (!arr) return let set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []) for (let i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) set.push(arr[i]) } export function hasHandler(emitter, type) { return getHandlers(emitter, type).length > 0 } // Add on and off methods to a constructor's prototype, to make // registering events on such objects more convenient. export function eventMixin(ctor) { ctor.prototype.on = function(type, f) {on(this, type, f)} ctor.prototype.off = function(type, f) {off(this, type, f)} } // Due to the fact that we still support jurassic IE versions, some // compatibility wrappers are needed. export function e_preventDefault(e) { if (e.preventDefault) e.preventDefault() else e.returnValue = false } export function e_stopPropagation(e) { if (e.stopPropagation) e.stopPropagation() else e.cancelBubble = true } export function e_defaultPrevented(e) { return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false } export function e_stop(e) {e_preventDefault(e); e_stopPropagation(e)} export function e_target(e) {return e.target || e.srcElement} export function e_button(e) { let b = e.which if (b == null) { if (e.button & 1) b = 1 else if (e.button & 2) b = 3 else if (e.button & 4) b = 2 } if (mac && e.ctrlKey && b == 1) b = 3 return b } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/feature_detection.js ================================================ import { elt, range, removeChildren, removeChildrenAndAdd } from "./dom.js" import { ie, ie_version } from "./browser.js" // Detect drag-and-drop export let dragAndDrop = function() { // There is *some* kind of drag-and-drop support in IE6-8, but I // couldn't get it to work yet. if (ie && ie_version < 9) return false let div = elt('div') return "draggable" in div || "dragDrop" in div }() let zwspSupported export function zeroWidthElement(measure) { if (zwspSupported == null) { let test = elt("span", "\u200b") removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])) if (measure.firstChild.offsetHeight != 0) zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8) } let node = zwspSupported ? elt("span", "\u200b") : elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") node.setAttribute("cm-text", "") return node } // Feature-detect IE's crummy client rect reporting for bidi text let badBidiRects export function hasBadBidiRects(measure) { if (badBidiRects != null) return badBidiRects let txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) let r0 = range(txt, 0, 1).getBoundingClientRect() let r1 = range(txt, 1, 2).getBoundingClientRect() removeChildren(measure) if (!r0 || r0.left == r0.right) return false // Safari returns null in some cases (#2780) return badBidiRects = (r1.right - r0.right < 3) } // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. export let splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? string => { let pos = 0, result = [], l = string.length while (pos <= l) { let nl = string.indexOf("\n", pos) if (nl == -1) nl = string.length let line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) let rt = line.indexOf("\r") if (rt != -1) { result.push(line.slice(0, rt)) pos += rt + 1 } else { result.push(line) pos = nl + 1 } } return result } : string => string.split(/\r\n?|\n/) export let hasSelection = window.getSelection ? te => { try { return te.selectionStart != te.selectionEnd } catch(e) { return false } } : te => { let range try {range = te.ownerDocument.selection.createRange()} catch(e) {} if (!range || range.parentElement() != te) return false return range.compareEndPoints("StartToEnd", range) != 0 } export let hasCopyEvent = (() => { let e = elt("div") if ("oncopy" in e) return true e.setAttribute("oncopy", "return;") return typeof e.oncopy == "function" })() let badZoomedRects = null export function hasBadZoomedRects(measure) { if (badZoomedRects != null) return badZoomedRects let node = removeChildrenAndAdd(measure, elt("span", "x")) let normal = node.getBoundingClientRect() let fromRange = range(node, 0, 1).getBoundingClientRect() return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/misc.js ================================================ export function bind(f) { let args = Array.prototype.slice.call(arguments, 1) return function(){return f.apply(null, args)} } export function copyObj(obj, target, overwrite) { if (!target) target = {} for (let prop in obj) if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) target[prop] = obj[prop] return target } // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. export function countColumn(string, end, tabSize, startIndex, startValue) { if (end == null) { end = string.search(/[^\s\u00a0]/) if (end == -1) end = string.length } for (let i = startIndex || 0, n = startValue || 0;;) { let nextTab = string.indexOf("\t", i) if (nextTab < 0 || nextTab >= end) return n + (end - i) n += nextTab - i n += tabSize - (n % tabSize) i = nextTab + 1 } } export class Delayed { constructor() { this.id = null this.f = null this.time = 0 this.handler = bind(this.onTimeout, this) } onTimeout(self) { self.id = 0 if (self.time <= +new Date) { self.f() } else { setTimeout(self.handler, self.time - +new Date) } } set(ms, f) { this.f = f const time = +new Date + ms if (!this.id || time < this.time) { clearTimeout(this.id) this.id = setTimeout(this.handler, ms) this.time = time } } } export function indexOf(array, elt) { for (let i = 0; i < array.length; ++i) if (array[i] == elt) return i return -1 } // Number of pixels added to scroller and sizer to hide scrollbar export let scrollerGap = 50 // Returned or thrown by various protocols to signal 'I'm not // handling this'. export let Pass = {toString: function(){return "CodeMirror.Pass"}} // Reused option objects for setSelection & friends export let sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"} // The inverse of countColumn -- find the offset that corresponds to // a particular column. export function findColumn(string, goal, tabSize) { for (let pos = 0, col = 0;;) { let nextTab = string.indexOf("\t", pos) if (nextTab == -1) nextTab = string.length let skipped = nextTab - pos if (nextTab == string.length || col + skipped >= goal) return pos + Math.min(skipped, goal - col) col += nextTab - pos col += tabSize - (col % tabSize) pos = nextTab + 1 if (col >= goal) return pos } } let spaceStrs = [""] export function spaceStr(n) { while (spaceStrs.length <= n) spaceStrs.push(lst(spaceStrs) + " ") return spaceStrs[n] } export function lst(arr) { return arr[arr.length-1] } export function map(array, f) { let out = [] for (let i = 0; i < array.length; i++) out[i] = f(array[i], i) return out } export function insertSorted(array, value, score) { let pos = 0, priority = score(value) while (pos < array.length && score(array[pos]) <= priority) pos++ array.splice(pos, 0, value) } function nothing() {} export function createObj(base, props) { let inst if (Object.create) { inst = Object.create(base) } else { nothing.prototype = base inst = new nothing() } if (props) copyObj(props, inst) return inst } let nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/ export function isWordCharBasic(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) } export function isWordChar(ch, helper) { if (!helper) return isWordCharBasic(ch) if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true return helper.test(ch) } export function isEmpty(obj) { for (let n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false return true } // Extending unicode characters. A series of a non-extending char + // any number of extending chars is treated as a single unit as far // as editing and measuring is concerned. This is not fully correct, // since some scripts/fonts/browsers also treat other configurations // of code points as a group. let extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/ export function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. export function skipExtendingChars(str, pos, dir) { while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) pos += dir return pos } // Returns the value from the range [`from`; `to`] that satisfies // `pred` and is closest to `from`. Assumes that at least `to` // satisfies `pred`. Supports `from` being greater than `to`. export function findFirst(pred, from, to) { // At any point we are certain `to` satisfies `pred`, don't know // whether `from` does. let dir = from > to ? -1 : 1 for (;;) { if (from == to) return from let midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF) if (mid == from) return pred(mid) ? from : to if (pred(mid)) to = mid else from = mid + dir } } ================================================ FILE: public/assets/lib/vendor/codemirror/src/util/operation_group.js ================================================ import { getHandlers } from "./event.js" let operationGroup = null export function pushOperation(op) { if (operationGroup) { operationGroup.ops.push(op) } else { op.ownsGroup = operationGroup = { ops: [op], delayedCallbacks: [] } } } function fireCallbacksForOps(group) { // Calls delayed callbacks and cursorActivity handlers until no // new ones appear let callbacks = group.delayedCallbacks, i = 0 do { for (; i < callbacks.length; i++) callbacks[i].call(null) for (let j = 0; j < group.ops.length; j++) { let op = group.ops[j] if (op.cursorActivityHandlers) while (op.cursorActivityCalled < op.cursorActivityHandlers.length) op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm) } } while (i < callbacks.length) } export function finishOperation(op, endCb) { let group = op.ownsGroup if (!group) return try { fireCallbacksForOps(group) } finally { operationGroup = null endCb(group) } } let orphanDelayedCallbacks = null // Often, we want to signal events at a point where we are in the // middle of some work, but don't want the handler to start calling // other methods on the editor, which might be in an inconsistent // state or simply not expect any other events to happen. // signalLater looks whether there are any handlers, and schedules // them to be executed when the last operation ends, or, if no // operation is active, when a timeout fires. export function signalLater(emitter, type /*, values...*/) { let arr = getHandlers(emitter, type) if (!arr.length) return let args = Array.prototype.slice.call(arguments, 2), list if (operationGroup) { list = operationGroup.delayedCallbacks } else if (orphanDelayedCallbacks) { list = orphanDelayedCallbacks } else { list = orphanDelayedCallbacks = [] setTimeout(fireOrphanDelayed, 0) } for (let i = 0; i < arr.length; ++i) list.push(() => arr[i].apply(null, args)) } function fireOrphanDelayed() { let delayed = orphanDelayedCallbacks orphanDelayedCallbacks = null for (let i = 0; i < delayed.length; ++i) delayed[i]() } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/3024-day.css ================================================ /* Name: 3024 day Author: Jan T. Sott (http://github.com/idleberg) CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-3024-day.CodeMirror { background: #f7f7f7; color: #3a3432; } .cm-s-3024-day div.CodeMirror-selected { background: #d6d5d4; } .cm-s-3024-day .CodeMirror-line::selection, .cm-s-3024-day .CodeMirror-line > span::selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d6d5d4; } .cm-s-3024-day .CodeMirror-line::-moz-selection, .cm-s-3024-day .CodeMirror-line > span::-moz-selection, .cm-s-3024-day .CodeMirror-line > span > span::selection { background: #d9d9d9; } .cm-s-3024-day .CodeMirror-gutters { background: #f7f7f7; border-right: 0px; } .cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; } .cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; } .cm-s-3024-day .CodeMirror-linenumber { color: #807d7c; } .cm-s-3024-day .CodeMirror-cursor { border-left: 1px solid #5c5855; } .cm-s-3024-day span.cm-comment { color: #cdab53; } .cm-s-3024-day span.cm-atom { color: #a16a94; } .cm-s-3024-day span.cm-number { color: #a16a94; } .cm-s-3024-day span.cm-property, .cm-s-3024-day span.cm-attribute { color: #01a252; } .cm-s-3024-day span.cm-keyword { color: #db2d20; } .cm-s-3024-day span.cm-string { color: #fded02; } .cm-s-3024-day span.cm-variable { color: #01a252; } .cm-s-3024-day span.cm-variable-2 { color: #01a0e4; } .cm-s-3024-day span.cm-def { color: #e8bbd0; } .cm-s-3024-day span.cm-bracket { color: #3a3432; } .cm-s-3024-day span.cm-tag { color: #db2d20; } .cm-s-3024-day span.cm-link { color: #a16a94; } .cm-s-3024-day span.cm-error { background: #db2d20; color: #5c5855; } .cm-s-3024-day .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/3024-night.css ================================================ /* Name: 3024 night Author: Jan T. Sott (http://github.com/idleberg) CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-3024-night.CodeMirror { background: #090300; color: #d6d5d4; } .cm-s-3024-night div.CodeMirror-selected { background: #3a3432; } .cm-s-3024-night .CodeMirror-line::selection, .cm-s-3024-night .CodeMirror-line > span::selection, .cm-s-3024-night .CodeMirror-line > span > span::selection { background: rgba(58, 52, 50, .99); } .cm-s-3024-night .CodeMirror-line::-moz-selection, .cm-s-3024-night .CodeMirror-line > span::-moz-selection, .cm-s-3024-night .CodeMirror-line > span > span::-moz-selection { background: rgba(58, 52, 50, .99); } .cm-s-3024-night .CodeMirror-gutters { background: #090300; border-right: 0px; } .cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; } .cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; } .cm-s-3024-night .CodeMirror-linenumber { color: #5c5855; } .cm-s-3024-night .CodeMirror-cursor { border-left: 1px solid #807d7c; } .cm-s-3024-night span.cm-comment { color: #cdab53; } .cm-s-3024-night span.cm-atom { color: #a16a94; } .cm-s-3024-night span.cm-number { color: #a16a94; } .cm-s-3024-night span.cm-property, .cm-s-3024-night span.cm-attribute { color: #01a252; } .cm-s-3024-night span.cm-keyword { color: #db2d20; } .cm-s-3024-night span.cm-string { color: #fded02; } .cm-s-3024-night span.cm-variable { color: #01a252; } .cm-s-3024-night span.cm-variable-2 { color: #01a0e4; } .cm-s-3024-night span.cm-def { color: #e8bbd0; } .cm-s-3024-night span.cm-bracket { color: #d6d5d4; } .cm-s-3024-night span.cm-tag { color: #db2d20; } .cm-s-3024-night span.cm-link { color: #a16a94; } .cm-s-3024-night span.cm-error { background: #db2d20; color: #807d7c; } .cm-s-3024-night .CodeMirror-activeline-background { background: #2F2F2F; } .cm-s-3024-night .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/abbott.css ================================================ /* * abbott.css * A warm, dark theme for prose and code, with pastels and pretty greens. * * Ported from abbott.vim (https://github.com/bcat/abbott.vim) version 2.1. * Original design and CodeMirror port by Jonathan Rascher. * * This theme shares the following color palette with the Vim color scheme. * * Brown shades: * bistre: #231c14 * chocolate: #3c3022 * cocoa: #745d42 * vanilla_cream: #fef3b4 * * Red shades: * crimson: #d80450 * cinnabar: #f63f05 * * Green shades: * dark_olive: #273900 * forest_green: #24a507 * chartreuse: #a0ea00 * pastel_chartreuse: #d8ff84 * * Yellow shades: * marigold: #fbb32f * lemon_meringue: #fbec5d * * Blue shades: * cornflower_blue: #3f91f1 * periwinkle_blue: #8ccdf0 * * Magenta shades: * french_pink: #ec6c99 * lavender: #e6a2f3 * * Cyan shades: * zomp: #39a78d * seafoam_green: #00ff7f */ /* Style the UI: */ /* Equivalent to Vim's Normal group. */ .cm-s-abbott.CodeMirror { background: #231c14 /* bistre */; color: #d8ff84 /* pastel_chartreuse */; } /* Roughly equivalent to Vim's LineNr group. */ .cm-s-abbott .CodeMirror-gutters { background: #231c14 /* bistre */; border: none; } .cm-s-abbott .CodeMirror-linenumber { color: #fbec5d /* lemon_meringue */; } .cm-s-abbott .CodeMirror-guttermarker { color: #f63f05 /* cinnabar */; } /* Roughly equivalent to Vim's FoldColumn group. */ .cm-s-abbott .CodeMirror-guttermarker-subtle { color: #fbb32f /* marigold */; } /* * Roughly equivalent to Vim's CursorColumn group. (We use a brighter color * since Vim's cursorcolumn option highlights a whole column, whereas * CodeMirror's rule just highlights a thin line.) */ .cm-s-abbott .CodeMirror-ruler { border-color: #745d42 /* cocoa */; } /* Equivalent to Vim's Cursor group in insert mode. */ .cm-s-abbott .CodeMirror-cursor { border-color: #a0ea00 /* chartreuse */; } /* Equivalent to Vim's Cursor group in normal mode. */ .cm-s-abbott.cm-fat-cursor .CodeMirror-cursor, .cm-s-abbott .cm-animate-fat-cursor { /* * CodeMirror doesn't allow changing the foreground color of the character * under the cursor, so we can't use a reverse video effect for the cursor. * Instead, make it semitransparent. */ background: rgba(160, 234, 0, 0.5) /* chartreuse */; } .cm-s-abbott.cm-fat-cursor .CodeMirror-cursors { /* * Boost the z-index so the fat cursor shows up on top of text and * matchingbracket/matchingtag highlights. */ z-index: 3; } /* Equivalent to Vim's Cursor group in replace mode. */ .cm-s-abbott .CodeMirror-overwrite .CodeMirror-cursor { border-bottom: 1px solid #a0ea00 /* chartreuse */; border-left: none; width: auto; } /* Roughly equivalent to Vim's CursorIM group. */ .cm-s-abbott .CodeMirror-secondarycursor { border-color: #00ff7f /* seafoam_green */; } /* Roughly equivalent to Vim's Visual group. */ .cm-s-abbott .CodeMirror-selected, .cm-s-abbott.CodeMirror-focused .CodeMirror-selected { background: #273900 /* dark_olive */; } .cm-s-abbott .CodeMirror-line::selection, .cm-s-abbott .CodeMirror-line > span::selection, .cm-s-abbott .CodeMirror-line > span > span::selection { background: #273900 /* dark_olive */; } .cm-s-abbott .CodeMirror-line::-moz-selection, .cm-s-abbott .CodeMirror-line > span::-moz-selection, .cm-s-abbott .CodeMirror-line > span > span::-moz-selection { background: #273900 /* dark_olive */; } /* Roughly equivalent to Vim's SpecialKey group. */ .cm-s-abbott .cm-tab { color: #00ff7f /* seafoam_green */; } /* Equivalent to Vim's Search group. */ .cm-s-abbott .cm-searching { background: #fef3b4 /* vanilla_cream */ !important; color: #231c14 /* bistre */ !important; } /* Style syntax highlighting modes: */ /* Equivalent to Vim's Comment group. */ .cm-s-abbott span.cm-comment { color: #fbb32f /* marigold */; font-style: italic; } /* Equivalent to Vim's String group. */ .cm-s-abbott span.cm-string, .cm-s-abbott span.cm-string-2 { color: #e6a2f3 /* lavender */; } /* Equivalent to Vim's Constant group. */ .cm-s-abbott span.cm-number, .cm-s-abbott span.cm-string.cm-url { color: #f63f05 /* cinnabar */; } /* Roughly equivalent to Vim's SpecialKey group. */ .cm-s-abbott span.cm-invalidchar { color: #00ff7f /* seafoam_green */; } /* Equivalent to Vim's Special group. */ .cm-s-abbott span.cm-atom { color: #fef3b4 /* vanilla_cream */; } /* Equivalent to Vim's Delimiter group. */ .cm-s-abbott span.cm-bracket, .cm-s-abbott span.cm-punctuation { color: #fef3b4 /* vanilla_cream */; } /* Equivalent Vim's Operator group. */ .cm-s-abbott span.cm-operator { font-weight: bold; } /* Roughly equivalent to Vim's Identifier group. */ .cm-s-abbott span.cm-def, .cm-s-abbott span.cm-variable, .cm-s-abbott span.cm-variable-2, .cm-s-abbott span.cm-variable-3 { color: #8ccdf0 /* periwinkle_blue */; } /* Roughly equivalent to Vim's Function group. */ .cm-s-abbott span.cm-builtin, .cm-s-abbott span.cm-property, .cm-s-abbott span.cm-qualifier { color: #3f91f1 /* cornflower_blue */; } /* Equivalent to Vim's Type group. */ .cm-s-abbott span.cm-type { color: #24a507 /* forest_green */; } /* Equivalent to Vim's Keyword group. */ .cm-s-abbott span.cm-keyword { color: #d80450 /* crimson */; font-weight: bold; } /* Equivalent to Vim's PreProc group. */ .cm-s-abbott span.cm-meta { color: #ec6c99 /* french_pink */; } /* Equivalent to Vim's htmlTagName group (linked to Statement). */ .cm-s-abbott span.cm-tag { color: #d80450 /* crimson */; font-weight: bold; } /* Equivalent to Vim's htmlArg group (linked to Type). */ .cm-s-abbott span.cm-attribute { color: #24a507 /* forest_green */; } /* Equivalent to Vim's htmlH1, markdownH1, etc. groups (linked to Title). */ .cm-s-abbott span.cm-header { color: #d80450 /* crimson */; font-weight: bold; } /* Equivalent to Vim's markdownRule group (linked to PreProc). */ .cm-s-abbott span.cm-hr { color: #ec6c99 /* french_pink */; } /* Roughly equivalent to Vim's Underlined group. */ .cm-s-abbott span.cm-link { color: #e6a2f3 /* lavender */; } /* Equivalent to Vim's diffRemoved group. */ .cm-s-abbott span.cm-negative { background: #d80450 /* crimson */; color: #231c14 /* bistre */; } /* Equivalent to Vim's diffAdded group. */ .cm-s-abbott span.cm-positive { background: #a0ea00 /* chartreuse */; color: #231c14 /* bistre */; font-weight: bold; } /* Equivalent to Vim's Error group. */ .cm-s-abbott span.cm-error { background: #d80450 /* crimson */; color: #231c14 /* bistre */; } /* Style addons: */ /* Equivalent to Vim's MatchParen group. */ .cm-s-abbott span.CodeMirror-matchingbracket { background: #745d42 /* cocoa */ !important; color: #231c14 /* bistre */ !important; font-weight: bold; } /* * Roughly equivalent to Vim's Error group. (Vim doesn't seem to have a direct * equivalent in its own matchparen plugin, but many syntax highlighting plugins * mark mismatched brackets as Error.) */ .cm-s-abbott span.CodeMirror-nonmatchingbracket { background: #d80450 /* crimson */ !important; color: #231c14 /* bistre */ !important; } .cm-s-abbott .CodeMirror-matchingtag, .cm-s-abbott .cm-matchhighlight { outline: 1px solid #39a78d /* zomp */; } /* Equivalent to Vim's CursorLine group. */ .cm-s-abbott .CodeMirror-activeline-background, .cm-s-abbott .CodeMirror-activeline-gutter { background: #3c3022 /* chocolate */; } /* Equivalent to Vim's CursorLineNr group. */ .cm-s-abbott .CodeMirror-activeline-gutter .CodeMirror-linenumber { color: #d8ff84 /* pastel_chartreuse */; font-weight: bold; } /* Roughly equivalent to Vim's Folded group. */ .cm-s-abbott .CodeMirror-foldmarker { color: #f63f05 /* cinnabar */; text-shadow: none; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/abcdef.css ================================================ .cm-s-abcdef.CodeMirror { background: #0f0f0f; color: #defdef; } .cm-s-abcdef div.CodeMirror-selected { background: #515151; } .cm-s-abcdef .CodeMirror-line::selection, .cm-s-abcdef .CodeMirror-line > span::selection, .cm-s-abcdef .CodeMirror-line > span > span::selection { background: rgba(56, 56, 56, 0.99); } .cm-s-abcdef .CodeMirror-line::-moz-selection, .cm-s-abcdef .CodeMirror-line > span::-moz-selection, .cm-s-abcdef .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 56, 56, 0.99); } .cm-s-abcdef .CodeMirror-gutters { background: #555; border-right: 2px solid #314151; } .cm-s-abcdef .CodeMirror-guttermarker { color: #222; } .cm-s-abcdef .CodeMirror-guttermarker-subtle { color: azure; } .cm-s-abcdef .CodeMirror-linenumber { color: #FFFFFF; } .cm-s-abcdef .CodeMirror-cursor { border-left: 1px solid #00FF00; } .cm-s-abcdef span.cm-keyword { color: darkgoldenrod; font-weight: bold; } .cm-s-abcdef span.cm-atom { color: #77F; } .cm-s-abcdef span.cm-number { color: violet; } .cm-s-abcdef span.cm-def { color: #fffabc; } .cm-s-abcdef span.cm-variable { color: #abcdef; } .cm-s-abcdef span.cm-variable-2 { color: #cacbcc; } .cm-s-abcdef span.cm-variable-3, .cm-s-abcdef span.cm-type { color: #def; } .cm-s-abcdef span.cm-property { color: #fedcba; } .cm-s-abcdef span.cm-operator { color: #ff0; } .cm-s-abcdef span.cm-comment { color: #7a7b7c; font-style: italic;} .cm-s-abcdef span.cm-string { color: #2b4; } .cm-s-abcdef span.cm-meta { color: #C9F; } .cm-s-abcdef span.cm-qualifier { color: #FFF700; } .cm-s-abcdef span.cm-builtin { color: #30aabc; } .cm-s-abcdef span.cm-bracket { color: #8a8a8a; } .cm-s-abcdef span.cm-tag { color: #FFDD44; } .cm-s-abcdef span.cm-attribute { color: #DDFF00; } .cm-s-abcdef span.cm-error { color: #FF0000; } .cm-s-abcdef span.cm-header { color: aquamarine; font-weight: bold; } .cm-s-abcdef span.cm-link { color: blueviolet; } .cm-s-abcdef .CodeMirror-activeline-background { background: #314151; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ambiance-mobile.css ================================================ .cm-s-ambiance.CodeMirror { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ambiance.css ================================================ /* ambiance theme for codemirror */ /* Color scheme */ .cm-s-ambiance .cm-header { color: blue; } .cm-s-ambiance .cm-quote { color: #24C2C7; } .cm-s-ambiance .cm-keyword { color: #cda869; } .cm-s-ambiance .cm-atom { color: #CF7EA9; } .cm-s-ambiance .cm-number { color: #78CF8A; } .cm-s-ambiance .cm-def { color: #aac6e3; } .cm-s-ambiance .cm-variable { color: #ffb795; } .cm-s-ambiance .cm-variable-2 { color: #eed1b3; } .cm-s-ambiance .cm-variable-3, .cm-s-ambiance .cm-type { color: #faded3; } .cm-s-ambiance .cm-property { color: #eed1b3; } .cm-s-ambiance .cm-operator { color: #fa8d6a; } .cm-s-ambiance .cm-comment { color: #555; font-style:italic; } .cm-s-ambiance .cm-string { color: #8f9d6a; } .cm-s-ambiance .cm-string-2 { color: #9d937c; } .cm-s-ambiance .cm-meta { color: #D2A8A1; } .cm-s-ambiance .cm-qualifier { color: yellow; } .cm-s-ambiance .cm-builtin { color: #9999cc; } .cm-s-ambiance .cm-bracket { color: #24C2C7; } .cm-s-ambiance .cm-tag { color: #fee4ff; } .cm-s-ambiance .cm-attribute { color: #9B859D; } .cm-s-ambiance .cm-hr { color: pink; } .cm-s-ambiance .cm-link { color: #F4C20B; } .cm-s-ambiance .cm-special { color: #FF9D00; } .cm-s-ambiance .cm-error { color: #AF2018; } .cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } .cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } .cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } .cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } .cm-s-ambiance .CodeMirror-line::selection, .cm-s-ambiance .CodeMirror-line > span::selection, .cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } .cm-s-ambiance .CodeMirror-line::-moz-selection, .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } /* Editor styling */ .cm-s-ambiance.CodeMirror { line-height: 1.40em; color: #E6E1DC; background-color: #202020; -webkit-box-shadow: inset 0 0 10px black; -moz-box-shadow: inset 0 0 10px black; box-shadow: inset 0 0 10px black; } .cm-s-ambiance .CodeMirror-gutters { background: #3D3D3D; border-right: 1px solid #4D4D4D; box-shadow: 0 10px 20px black; } .cm-s-ambiance .CodeMirror-linenumber { text-shadow: 0px 1px 1px #4d4d4d; color: #111; padding: 0 5px; } .cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } .cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } .cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8; } .cm-s-ambiance .CodeMirror-activeline-background { background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); } .cm-s-ambiance.CodeMirror, .cm-s-ambiance .CodeMirror-gutters { background-image: url("data:image/png;base64,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"); } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ayu-dark.css ================================================ /* Based on https://github.com/dempfi/ayu */ .cm-s-ayu-dark.CodeMirror { background: #0a0e14; color: #b3b1ad; } .cm-s-ayu-dark div.CodeMirror-selected { background: #273747; } .cm-s-ayu-dark .CodeMirror-line::selection, .cm-s-ayu-dark .CodeMirror-line > span::selection, .cm-s-ayu-dark .CodeMirror-line > span > span::selection { background: rgba(39, 55, 71, 99); } .cm-s-ayu-dark .CodeMirror-line::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 55, 71, 99); } .cm-s-ayu-dark .CodeMirror-gutters { background: #0a0e14; border-right: 0px; } .cm-s-ayu-dark .CodeMirror-guttermarker { color: white; } .cm-s-ayu-dark .CodeMirror-guttermarker-subtle { color: #3d424d; } .cm-s-ayu-dark .CodeMirror-linenumber { color: #3d424d; } .cm-s-ayu-dark .CodeMirror-cursor { border-left: 1px solid #e6b450; } .cm-s-ayu-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #a2a8a175 !important; } .cm-s-ayu-dark .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } .cm-s-ayu-dark span.cm-comment { color: #626a73; } .cm-s-ayu-dark span.cm-atom { color: #ae81ff; } .cm-s-ayu-dark span.cm-number { color: #e6b450; } .cm-s-ayu-dark span.cm-comment.cm-attribute { color: #ffb454; } .cm-s-ayu-dark span.cm-comment.cm-def { color: rgba(57, 186, 230, 80); } .cm-s-ayu-dark span.cm-comment.cm-tag { color: #39bae6; } .cm-s-ayu-dark span.cm-comment.cm-type { color: #5998a6; } .cm-s-ayu-dark span.cm-property, .cm-s-ayu-dark span.cm-attribute { color: #ffb454; } .cm-s-ayu-dark span.cm-keyword { color: #ff8f40; } .cm-s-ayu-dark span.cm-builtin { color: #e6b450; } .cm-s-ayu-dark span.cm-string { color: #c2d94c; } .cm-s-ayu-dark span.cm-variable { color: #b3b1ad; } .cm-s-ayu-dark span.cm-variable-2 { color: #f07178; } .cm-s-ayu-dark span.cm-variable-3 { color: #39bae6; } .cm-s-ayu-dark span.cm-type { color: #ff8f40; } .cm-s-ayu-dark span.cm-def { color: #ffee99; } .cm-s-ayu-dark span.cm-bracket { color: #f8f8f2; } .cm-s-ayu-dark span.cm-tag { color: rgba(57, 186, 230, 80); } .cm-s-ayu-dark span.cm-header { color: #c2d94c; } .cm-s-ayu-dark span.cm-link { color: #39bae6; } .cm-s-ayu-dark span.cm-error { color: #ff3333; } .cm-s-ayu-dark .CodeMirror-activeline-background { background: #01060e; } .cm-s-ayu-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ayu-mirage.css ================================================ /* Based on https://github.com/dempfi/ayu */ .cm-s-ayu-mirage.CodeMirror { background: #1f2430; color: #cbccc6; } .cm-s-ayu-mirage div.CodeMirror-selected { background: #34455a; } .cm-s-ayu-mirage .CodeMirror-line::selection, .cm-s-ayu-mirage .CodeMirror-line > span::selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::selection { background: #34455a; } .cm-s-ayu-mirage .CodeMirror-line::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::-moz-selection { background: rgba(25, 30, 42, 99); } .cm-s-ayu-mirage .CodeMirror-gutters { background: #1f2430; border-right: 0px; } .cm-s-ayu-mirage .CodeMirror-guttermarker { color: white; } .cm-s-ayu-mirage .CodeMirror-guttermarker-subtle { color: rgba(112, 122, 140, 66); } .cm-s-ayu-mirage .CodeMirror-linenumber { color: rgba(61, 66, 77, 99); } .cm-s-ayu-mirage .CodeMirror-cursor { border-left: 1px solid #ffcc66; } .cm-s-ayu-mirage.cm-fat-cursor .CodeMirror-cursor {background-color: #a2a8a175 !important;} .cm-s-ayu-mirage .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } .cm-s-ayu-mirage span.cm-comment { color: #5c6773; font-style:italic; } .cm-s-ayu-mirage span.cm-atom { color: #ae81ff; } .cm-s-ayu-mirage span.cm-number { color: #ffcc66; } .cm-s-ayu-mirage span.cm-comment.cm-attribute { color: #ffd580; } .cm-s-ayu-mirage span.cm-comment.cm-def { color: #d4bfff; } .cm-s-ayu-mirage span.cm-comment.cm-tag { color: #5ccfe6; } .cm-s-ayu-mirage span.cm-comment.cm-type { color: #5998a6; } .cm-s-ayu-mirage span.cm-property { color: #f29e74; } .cm-s-ayu-mirage span.cm-attribute { color: #ffd580; } .cm-s-ayu-mirage span.cm-keyword { color: #ffa759; } .cm-s-ayu-mirage span.cm-builtin { color: #ffcc66; } .cm-s-ayu-mirage span.cm-string { color: #bae67e; } .cm-s-ayu-mirage span.cm-variable { color: #cbccc6; } .cm-s-ayu-mirage span.cm-variable-2 { color: #f28779; } .cm-s-ayu-mirage span.cm-variable-3 { color: #5ccfe6; } .cm-s-ayu-mirage span.cm-type { color: #ffa759; } .cm-s-ayu-mirage span.cm-def { color: #ffd580; } .cm-s-ayu-mirage span.cm-bracket { color: rgba(92, 207, 230, 80); } .cm-s-ayu-mirage span.cm-tag { color: #5ccfe6; } .cm-s-ayu-mirage span.cm-header { color: #bae67e; } .cm-s-ayu-mirage span.cm-link { color: #5ccfe6; } .cm-s-ayu-mirage span.cm-error { color: #ff3333; } .cm-s-ayu-mirage .CodeMirror-activeline-background { background: #191e2a; } .cm-s-ayu-mirage .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/base16-dark.css ================================================ /* Name: Base16 Default Dark Author: Chris Kempson (http://chriskempson.com) CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-base16-dark.CodeMirror { background: #151515; color: #e0e0e0; } .cm-s-base16-dark div.CodeMirror-selected { background: #303030; } .cm-s-base16-dark .CodeMirror-line::selection, .cm-s-base16-dark .CodeMirror-line > span::selection, .cm-s-base16-dark .CodeMirror-line > span > span::selection { background: rgba(48, 48, 48, .99); } .cm-s-base16-dark .CodeMirror-line::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span::-moz-selection, .cm-s-base16-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(48, 48, 48, .99); } .cm-s-base16-dark .CodeMirror-gutters { background: #151515; border-right: 0px; } .cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; } .cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; } .cm-s-base16-dark .CodeMirror-linenumber { color: #505050; } .cm-s-base16-dark .CodeMirror-cursor { border-left: 1px solid #b0b0b0; } .cm-s-base16-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #8e8d8875 !important; } .cm-s-base16-dark .cm-animate-fat-cursor { background-color: #8e8d8875 !important; } .cm-s-base16-dark span.cm-comment { color: #8f5536; } .cm-s-base16-dark span.cm-atom { color: #aa759f; } .cm-s-base16-dark span.cm-number { color: #aa759f; } .cm-s-base16-dark span.cm-property, .cm-s-base16-dark span.cm-attribute { color: #90a959; } .cm-s-base16-dark span.cm-keyword { color: #ac4142; } .cm-s-base16-dark span.cm-string { color: #f4bf75; } .cm-s-base16-dark span.cm-variable { color: #90a959; } .cm-s-base16-dark span.cm-variable-2 { color: #6a9fb5; } .cm-s-base16-dark span.cm-def { color: #d28445; } .cm-s-base16-dark span.cm-bracket { color: #e0e0e0; } .cm-s-base16-dark span.cm-tag { color: #ac4142; } .cm-s-base16-dark span.cm-link { color: #aa759f; } .cm-s-base16-dark span.cm-error { background: #ac4142; color: #b0b0b0; } .cm-s-base16-dark .CodeMirror-activeline-background { background: #202020; } .cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/base16-light.css ================================================ /* Name: Base16 Default Light Author: Chris Kempson (http://chriskempson.com) CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; } .cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; } .cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; } .cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; } .cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; } .cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; } .cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; } .cm-s-base16-light .CodeMirror-linenumber { color: #b0b0b0; } .cm-s-base16-light .CodeMirror-cursor { border-left: 1px solid #505050; } .cm-s-base16-light span.cm-comment { color: #8f5536; } .cm-s-base16-light span.cm-atom { color: #aa759f; } .cm-s-base16-light span.cm-number { color: #aa759f; } .cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { color: #90a959; } .cm-s-base16-light span.cm-keyword { color: #ac4142; } .cm-s-base16-light span.cm-string { color: #f4bf75; } .cm-s-base16-light span.cm-variable { color: #90a959; } .cm-s-base16-light span.cm-variable-2 { color: #6a9fb5; } .cm-s-base16-light span.cm-def { color: #d28445; } .cm-s-base16-light span.cm-bracket { color: #202020; } .cm-s-base16-light span.cm-tag { color: #ac4142; } .cm-s-base16-light span.cm-link { color: #aa759f; } .cm-s-base16-light span.cm-error { background: #ac4142; color: #505050; } .cm-s-base16-light .CodeMirror-activeline-background { background: #DDDCDC; } .cm-s-base16-light .CodeMirror-matchingbracket { color: #f5f5f5 !important; background-color: #6A9FB5 !important} ================================================ FILE: public/assets/lib/vendor/codemirror/theme/bespin.css ================================================ /* Name: Bespin Author: Mozilla / Jan T. Sott CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-bespin.CodeMirror {background: #28211c; color: #9d9b97;} .cm-s-bespin div.CodeMirror-selected {background: #59554f !important;} .cm-s-bespin .CodeMirror-gutters {background: #28211c; border-right: 0px;} .cm-s-bespin .CodeMirror-linenumber {color: #666666;} .cm-s-bespin .CodeMirror-cursor {border-left: 1px solid #797977 !important;} .cm-s-bespin span.cm-comment {color: #937121;} .cm-s-bespin span.cm-atom {color: #9b859d;} .cm-s-bespin span.cm-number {color: #9b859d;} .cm-s-bespin span.cm-property, .cm-s-bespin span.cm-attribute {color: #54be0d;} .cm-s-bespin span.cm-keyword {color: #cf6a4c;} .cm-s-bespin span.cm-string {color: #f9ee98;} .cm-s-bespin span.cm-variable {color: #54be0d;} .cm-s-bespin span.cm-variable-2 {color: #5ea6ea;} .cm-s-bespin span.cm-def {color: #cf7d34;} .cm-s-bespin span.cm-error {background: #cf6a4c; color: #797977;} .cm-s-bespin span.cm-bracket {color: #9d9b97;} .cm-s-bespin span.cm-tag {color: #cf6a4c;} .cm-s-bespin span.cm-link {color: #9b859d;} .cm-s-bespin .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} .cm-s-bespin .CodeMirror-activeline-background { background: #404040; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/blackboard.css ================================================ /* Port of TextMate's Blackboard theme */ .cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } .cm-s-blackboard div.CodeMirror-selected { background: #253B76; } .cm-s-blackboard .CodeMirror-line::selection, .cm-s-blackboard .CodeMirror-line > span::selection, .cm-s-blackboard .CodeMirror-line > span > span::selection { background: rgba(37, 59, 118, .99); } .cm-s-blackboard .CodeMirror-line::-moz-selection, .cm-s-blackboard .CodeMirror-line > span::-moz-selection, .cm-s-blackboard .CodeMirror-line > span > span::-moz-selection { background: rgba(37, 59, 118, .99); } .cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } .cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; } .cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; } .cm-s-blackboard .CodeMirror-linenumber { color: #888; } .cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } .cm-s-blackboard .cm-keyword { color: #FBDE2D; } .cm-s-blackboard .cm-atom { color: #D8FA3C; } .cm-s-blackboard .cm-number { color: #D8FA3C; } .cm-s-blackboard .cm-def { color: #8DA6CE; } .cm-s-blackboard .cm-variable { color: #FF6400; } .cm-s-blackboard .cm-operator { color: #FBDE2D; } .cm-s-blackboard .cm-comment { color: #AEAEAE; } .cm-s-blackboard .cm-string { color: #61CE3C; } .cm-s-blackboard .cm-string-2 { color: #61CE3C; } .cm-s-blackboard .cm-meta { color: #D8FA3C; } .cm-s-blackboard .cm-builtin { color: #8DA6CE; } .cm-s-blackboard .cm-tag { color: #8DA6CE; } .cm-s-blackboard .cm-attribute { color: #8DA6CE; } .cm-s-blackboard .cm-header { color: #FF6400; } .cm-s-blackboard .cm-hr { color: #AEAEAE; } .cm-s-blackboard .cm-link { color: #8DA6CE; } .cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; } .cm-s-blackboard .CodeMirror-activeline-background { background: #3C3636; } .cm-s-blackboard .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/cobalt.css ================================================ .cm-s-cobalt.CodeMirror { background: #002240; color: white; } .cm-s-cobalt div.CodeMirror-selected { background: #b36539; } .cm-s-cobalt .CodeMirror-line::selection, .cm-s-cobalt .CodeMirror-line > span::selection, .cm-s-cobalt .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } .cm-s-cobalt .CodeMirror-line::-moz-selection, .cm-s-cobalt .CodeMirror-line > span::-moz-selection, .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } .cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } .cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; } .cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-cobalt span.cm-comment { color: #08f; } .cm-s-cobalt span.cm-atom { color: #845dc4; } .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; } .cm-s-cobalt span.cm-keyword { color: #ffee80; } .cm-s-cobalt span.cm-string { color: #3ad900; } .cm-s-cobalt span.cm-meta { color: #ff9d00; } .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; } .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def, .cm-s-cobalt .cm-type { color: white; } .cm-s-cobalt span.cm-bracket { color: #d8d8d8; } .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; } .cm-s-cobalt span.cm-link { color: #845dc4; } .cm-s-cobalt span.cm-error { color: #9d1e15; } .cm-s-cobalt .CodeMirror-activeline-background { background: #002D57; } .cm-s-cobalt .CodeMirror-matchingbracket { outline:1px solid grey;color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/colorforth.css ================================================ .cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; } .cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } .cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; } .cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; } .cm-s-colorforth .CodeMirror-linenumber { color: #bababa; } .cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-colorforth span.cm-comment { color: #ededed; } .cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; } .cm-s-colorforth span.cm-keyword { color: #ffd900; } .cm-s-colorforth span.cm-builtin { color: #00d95a; } .cm-s-colorforth span.cm-variable { color: #73ff00; } .cm-s-colorforth span.cm-string { color: #007bff; } .cm-s-colorforth span.cm-number { color: #00c4ff; } .cm-s-colorforth span.cm-atom { color: #606060; } .cm-s-colorforth span.cm-variable-2 { color: #EEE; } .cm-s-colorforth span.cm-variable-3, .cm-s-colorforth span.cm-type { color: #DDD; } .cm-s-colorforth span.cm-property {} .cm-s-colorforth span.cm-operator {} .cm-s-colorforth span.cm-meta { color: yellow; } .cm-s-colorforth span.cm-qualifier { color: #FFF700; } .cm-s-colorforth span.cm-bracket { color: #cc7; } .cm-s-colorforth span.cm-tag { color: #FFBD40; } .cm-s-colorforth span.cm-attribute { color: #FFF700; } .cm-s-colorforth span.cm-error { color: #f00; } .cm-s-colorforth div.CodeMirror-selected { background: #333d53; } .cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); } .cm-s-colorforth .CodeMirror-activeline-background { background: #253540; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/darcula.css ================================================ /** Name: IntelliJ IDEA darcula theme From IntelliJ IDEA by JetBrains */ .cm-s-darcula { font-family: Consolas, Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', monospace, serif;} .cm-s-darcula.CodeMirror { background: #2B2B2B; color: #A9B7C6; } .cm-s-darcula span.cm-meta { color: #BBB529; } .cm-s-darcula span.cm-number { color: #6897BB; } .cm-s-darcula span.cm-keyword { color: #CC7832; line-height: 1em; font-weight: bold; } .cm-s-darcula span.cm-def { color: #A9B7C6; font-style: italic; } .cm-s-darcula span.cm-variable { color: #A9B7C6; } .cm-s-darcula span.cm-variable-2 { color: #A9B7C6; } .cm-s-darcula span.cm-variable-3 { color: #9876AA; } .cm-s-darcula span.cm-type { color: #AABBCC; font-weight: bold; } .cm-s-darcula span.cm-property { color: #FFC66D; } .cm-s-darcula span.cm-operator { color: #A9B7C6; } .cm-s-darcula span.cm-string { color: #6A8759; } .cm-s-darcula span.cm-string-2 { color: #6A8759; } .cm-s-darcula span.cm-comment { color: #61A151; font-style: italic; } .cm-s-darcula span.cm-link { color: #CC7832; } .cm-s-darcula span.cm-atom { color: #CC7832; } .cm-s-darcula span.cm-error { color: #BC3F3C; } .cm-s-darcula span.cm-tag { color: #629755; font-weight: bold; font-style: italic; text-decoration: underline; } .cm-s-darcula span.cm-attribute { color: #6897bb; } .cm-s-darcula span.cm-qualifier { color: #6A8759; } .cm-s-darcula span.cm-bracket { color: #A9B7C6; } .cm-s-darcula span.cm-builtin { color: #FF9E59; } .cm-s-darcula span.cm-special { color: #FF9E59; } .cm-s-darcula span.cm-matchhighlight { color: #FFFFFF; background-color: rgba(50, 89, 48, .7); font-weight: normal;} .cm-s-darcula span.cm-searching { color: #FFFFFF; background-color: rgba(61, 115, 59, .7); font-weight: normal;} .cm-s-darcula .CodeMirror-cursor { border-left: 1px solid #A9B7C6; } .cm-s-darcula .CodeMirror-activeline-background { background: #323232; } .cm-s-darcula .CodeMirror-gutters { background: #313335; border-right: 1px solid #313335; } .cm-s-darcula .CodeMirror-guttermarker { color: #FFEE80; } .cm-s-darcula .CodeMirror-guttermarker-subtle { color: #D0D0D0; } .cm-s-darcula .CodeMirrir-linenumber { color: #606366; } .cm-s-darcula .CodeMirror-matchingbracket { background-color: #3B514D; color: #FFEF28 !important; font-weight: bold; } .cm-s-darcula div.CodeMirror-selected { background: #214283; } .CodeMirror-hints.darcula { font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; color: #9C9E9E; background-color: #3B3E3F !important; } .CodeMirror-hints.darcula .CodeMirror-hint-active { background-color: #494D4E !important; color: #9C9E9E !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/dracula.css ================================================ /* Name: dracula Author: Michael Kaminsky (http://github.com/mkaminsky11) Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) */ .cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { background-color: #282a36 !important; color: #f8f8f2 !important; border: none; } .cm-s-dracula .CodeMirror-gutters { color: #282a36; } .cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } .cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } .cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } .cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } .cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } .cm-s-dracula span.cm-comment { color: #6272a4; } .cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } .cm-s-dracula span.cm-number { color: #bd93f9; } .cm-s-dracula span.cm-variable { color: #50fa7b; } .cm-s-dracula span.cm-variable-2 { color: white; } .cm-s-dracula span.cm-def { color: #50fa7b; } .cm-s-dracula span.cm-operator { color: #ff79c6; } .cm-s-dracula span.cm-keyword { color: #ff79c6; } .cm-s-dracula span.cm-atom { color: #bd93f9; } .cm-s-dracula span.cm-meta { color: #f8f8f2; } .cm-s-dracula span.cm-tag { color: #ff79c6; } .cm-s-dracula span.cm-attribute { color: #50fa7b; } .cm-s-dracula span.cm-qualifier { color: #50fa7b; } .cm-s-dracula span.cm-property { color: #66d9ef; } .cm-s-dracula span.cm-builtin { color: #50fa7b; } .cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; } .cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } .cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/duotone-dark.css ================================================ /* Name: DuoTone-Dark Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) */ .cm-s-duotone-dark.CodeMirror { background: #2a2734; color: #6c6783; } .cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; } .cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734; border-right: 0px; } .cm-s-duotone-dark .CodeMirror-linenumber { color: #545167; } /* begin cursor */ .cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c; /* border-right: .5em solid #ffad5c80; */ opacity: .5; } .cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342; /* background: #36334280; */ opacity: .5;} .cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c; /* background: #ffad5c80; */ opacity: .5;} /* end cursor */ .cm-s-duotone-dark span.cm-atom, .cm-s-duotone-dark span.cm-number, .cm-s-duotone-dark span.cm-keyword, .cm-s-duotone-dark span.cm-variable, .cm-s-duotone-dark span.cm-attribute, .cm-s-duotone-dark span.cm-quote, .cm-s-duotone-dark span.cm-hr, .cm-s-duotone-dark span.cm-link { color: #ffcc99; } .cm-s-duotone-dark span.cm-property { color: #9a86fd; } .cm-s-duotone-dark span.cm-punctuation, .cm-s-duotone-dark span.cm-unit, .cm-s-duotone-dark span.cm-negative { color: #e09142; } .cm-s-duotone-dark span.cm-string { color: #ffb870; } .cm-s-duotone-dark span.cm-operator { color: #ffad5c; } .cm-s-duotone-dark span.cm-positive { color: #6a51e6; } .cm-s-duotone-dark span.cm-variable-2, .cm-s-duotone-dark span.cm-variable-3, .cm-s-duotone-dark span.cm-type, .cm-s-duotone-dark span.cm-string-2, .cm-s-duotone-dark span.cm-url { color: #7a63ee; } .cm-s-duotone-dark span.cm-def, .cm-s-duotone-dark span.cm-tag, .cm-s-duotone-dark span.cm-builtin, .cm-s-duotone-dark span.cm-qualifier, .cm-s-duotone-dark span.cm-header, .cm-s-duotone-dark span.cm-em { color: #eeebff; } .cm-s-duotone-dark span.cm-bracket, .cm-s-duotone-dark span.cm-comment { color: #6c6783; } /* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ .cm-s-duotone-dark span.cm-error, .cm-s-duotone-dark span.cm-invalidchar { color: #f00; } .cm-s-duotone-dark span.cm-header { font-weight: normal; } .cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline; color: #eeebff !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/duotone-light.css ================================================ /* Name: DuoTone-Light Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) */ .cm-s-duotone-light.CodeMirror { background: #faf8f5; color: #b29762; } .cm-s-duotone-light div.CodeMirror-selected { background: #e3dcce !important; } .cm-s-duotone-light .CodeMirror-gutters { background: #faf8f5; border-right: 0px; } .cm-s-duotone-light .CodeMirror-linenumber { color: #cdc4b1; } /* begin cursor */ .cm-s-duotone-light .CodeMirror-cursor { border-left: 1px solid #93abdc; /* border-left: 1px solid #93abdc80; */ border-right: .5em solid #93abdc; /* border-right: .5em solid #93abdc80; */ opacity: .5; } .cm-s-duotone-light .CodeMirror-activeline-background { background: #e3dcce; /* background: #e3dcce80; */ opacity: .5; } .cm-s-duotone-light .cm-fat-cursor .CodeMirror-cursor { background: #93abdc; /* #93abdc80; */ opacity: .5; } /* end cursor */ .cm-s-duotone-light span.cm-atom, .cm-s-duotone-light span.cm-number, .cm-s-duotone-light span.cm-keyword, .cm-s-duotone-light span.cm-variable, .cm-s-duotone-light span.cm-attribute, .cm-s-duotone-light span.cm-quote, .cm-s-duotone-light-light span.cm-hr, .cm-s-duotone-light-light span.cm-link { color: #063289; } .cm-s-duotone-light span.cm-property { color: #b29762; } .cm-s-duotone-light span.cm-punctuation, .cm-s-duotone-light span.cm-unit, .cm-s-duotone-light span.cm-negative { color: #063289; } .cm-s-duotone-light span.cm-string, .cm-s-duotone-light span.cm-operator { color: #1659df; } .cm-s-duotone-light span.cm-positive { color: #896724; } .cm-s-duotone-light span.cm-variable-2, .cm-s-duotone-light span.cm-variable-3, .cm-s-duotone-light span.cm-type, .cm-s-duotone-light span.cm-string-2, .cm-s-duotone-light span.cm-url { color: #896724; } .cm-s-duotone-light span.cm-def, .cm-s-duotone-light span.cm-tag, .cm-s-duotone-light span.cm-builtin, .cm-s-duotone-light span.cm-qualifier, .cm-s-duotone-light span.cm-header, .cm-s-duotone-light span.cm-em { color: #2d2006; } .cm-s-duotone-light span.cm-bracket, .cm-s-duotone-light span.cm-comment { color: #b6ad9a; } /* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ /* .cm-s-duotone-light span.cm-error { background: #896724; color: #728fcb; } */ .cm-s-duotone-light span.cm-error, .cm-s-duotone-light span.cm-invalidchar { color: #f00; } .cm-s-duotone-light span.cm-header { font-weight: normal; } .cm-s-duotone-light .CodeMirror-matchingbracket { text-decoration: underline; color: #faf8f5 !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/eclipse.css ================================================ .cm-s-eclipse span.cm-meta { color: #FF1717; } .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } .cm-s-eclipse span.cm-atom { color: #219; } .cm-s-eclipse span.cm-number { color: #164; } .cm-s-eclipse span.cm-def { color: #00f; } .cm-s-eclipse span.cm-variable { color: black; } .cm-s-eclipse span.cm-variable-2 { color: #0000C0; } .cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; } .cm-s-eclipse span.cm-property { color: black; } .cm-s-eclipse span.cm-operator { color: black; } .cm-s-eclipse span.cm-comment { color: #3F7F5F; } .cm-s-eclipse span.cm-string { color: #2A00FF; } .cm-s-eclipse span.cm-string-2 { color: #f50; } .cm-s-eclipse span.cm-qualifier { color: #555; } .cm-s-eclipse span.cm-builtin { color: #30a; } .cm-s-eclipse span.cm-bracket { color: #cc7; } .cm-s-eclipse span.cm-tag { color: #170; } .cm-s-eclipse span.cm-attribute { color: #00c; } .cm-s-eclipse span.cm-link { color: #219; } .cm-s-eclipse span.cm-error { color: #f00; } .cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/elegant.css ================================================ .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom { color: #762; } .cm-s-elegant span.cm-comment { color: #262; font-style: italic; line-height: 1em; } .cm-s-elegant span.cm-meta { color: #555; font-style: italic; line-height: 1em; } .cm-s-elegant span.cm-variable { color: black; } .cm-s-elegant span.cm-variable-2 { color: #b11; } .cm-s-elegant span.cm-qualifier { color: #555; } .cm-s-elegant span.cm-keyword { color: #730; } .cm-s-elegant span.cm-builtin { color: #30a; } .cm-s-elegant span.cm-link { color: #762; } .cm-s-elegant span.cm-error { background-color: #fdd; } .cm-s-elegant .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-elegant .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/erlang-dark.css ================================================ .cm-s-erlang-dark.CodeMirror { background: #002240; color: white; } .cm-s-erlang-dark div.CodeMirror-selected { background: #b36539; } .cm-s-erlang-dark .CodeMirror-line::selection, .cm-s-erlang-dark .CodeMirror-line > span::selection, .cm-s-erlang-dark .CodeMirror-line > span > span::selection { background: rgba(179, 101, 57, .99); } .cm-s-erlang-dark .CodeMirror-line::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span::-moz-selection, .cm-s-erlang-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(179, 101, 57, .99); } .cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } .cm-s-erlang-dark .CodeMirror-guttermarker { color: white; } .cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-erlang-dark span.cm-quote { color: #ccc; } .cm-s-erlang-dark span.cm-atom { color: #f133f1; } .cm-s-erlang-dark span.cm-attribute { color: #ff80e1; } .cm-s-erlang-dark span.cm-bracket { color: #ff9d00; } .cm-s-erlang-dark span.cm-builtin { color: #eaa; } .cm-s-erlang-dark span.cm-comment { color: #77f; } .cm-s-erlang-dark span.cm-def { color: #e7a; } .cm-s-erlang-dark span.cm-keyword { color: #ffee80; } .cm-s-erlang-dark span.cm-meta { color: #50fefe; } .cm-s-erlang-dark span.cm-number { color: #ffd0d0; } .cm-s-erlang-dark span.cm-operator { color: #d55; } .cm-s-erlang-dark span.cm-property { color: #ccc; } .cm-s-erlang-dark span.cm-qualifier { color: #ccc; } .cm-s-erlang-dark span.cm-special { color: #ffbbbb; } .cm-s-erlang-dark span.cm-string { color: #3ad900; } .cm-s-erlang-dark span.cm-string-2 { color: #ccc; } .cm-s-erlang-dark span.cm-tag { color: #9effff; } .cm-s-erlang-dark span.cm-variable { color: #50fe50; } .cm-s-erlang-dark span.cm-variable-2 { color: #e0e; } .cm-s-erlang-dark span.cm-variable-3, .cm-s-erlang-dark span.cm-type { color: #ccc; } .cm-s-erlang-dark span.cm-error { color: #9d1e15; } .cm-s-erlang-dark .CodeMirror-activeline-background { background: #013461; } .cm-s-erlang-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/gruvbox-dark.css ================================================ /* Name: gruvbox-dark Author: kRkk (https://github.com/krkk) Original gruvbox color scheme by Pavel Pertsev (https://github.com/morhetz/gruvbox) */ .cm-s-gruvbox-dark.CodeMirror, .cm-s-gruvbox-dark .CodeMirror-gutters { background-color: #282828; color: #bdae93; } .cm-s-gruvbox-dark .CodeMirror-gutters {background: #282828; border-right: 0px;} .cm-s-gruvbox-dark .CodeMirror-linenumber {color: #7c6f64;} .cm-s-gruvbox-dark .CodeMirror-cursor { border-left: 1px solid #ebdbb2; } .cm-s-gruvbox-dark.cm-fat-cursor .CodeMirror-cursor { background-color: #8e8d8875 !important; } .cm-s-gruvbox-dark .cm-animate-fat-cursor { background-color: #8e8d8875 !important; } .cm-s-gruvbox-dark div.CodeMirror-selected { background: #928374; } .cm-s-gruvbox-dark span.cm-meta { color: #83a598; } .cm-s-gruvbox-dark span.cm-comment { color: #928374; } .cm-s-gruvbox-dark span.cm-number, span.cm-atom { color: #d3869b; } .cm-s-gruvbox-dark span.cm-keyword { color: #f84934; } .cm-s-gruvbox-dark span.cm-variable { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-variable-2 { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-variable-3, .cm-s-gruvbox-dark span.cm-type { color: #fabd2f; } .cm-s-gruvbox-dark span.cm-operator { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-callee { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-def { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-property { color: #ebdbb2; } .cm-s-gruvbox-dark span.cm-string { color: #b8bb26; } .cm-s-gruvbox-dark span.cm-string-2 { color: #8ec07c; } .cm-s-gruvbox-dark span.cm-qualifier { color: #8ec07c; } .cm-s-gruvbox-dark span.cm-attribute { color: #8ec07c; } .cm-s-gruvbox-dark .CodeMirror-activeline-background { background: #3c3836; } .cm-s-gruvbox-dark .CodeMirror-matchingbracket { background: #928374; color:#282828 !important; } .cm-s-gruvbox-dark span.cm-builtin { color: #fe8019; } .cm-s-gruvbox-dark span.cm-tag { color: #fe8019; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/hopscotch.css ================================================ /* Name: Hopscotch Author: Jan T. Sott CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-hopscotch.CodeMirror {background: #322931; color: #d5d3d5;} .cm-s-hopscotch div.CodeMirror-selected {background: #433b42 !important;} .cm-s-hopscotch .CodeMirror-gutters {background: #322931; border-right: 0px;} .cm-s-hopscotch .CodeMirror-linenumber {color: #797379;} .cm-s-hopscotch .CodeMirror-cursor {border-left: 1px solid #989498 !important;} .cm-s-hopscotch span.cm-comment {color: #b33508;} .cm-s-hopscotch span.cm-atom {color: #c85e7c;} .cm-s-hopscotch span.cm-number {color: #c85e7c;} .cm-s-hopscotch span.cm-property, .cm-s-hopscotch span.cm-attribute {color: #8fc13e;} .cm-s-hopscotch span.cm-keyword {color: #dd464c;} .cm-s-hopscotch span.cm-string {color: #fdcc59;} .cm-s-hopscotch span.cm-variable {color: #8fc13e;} .cm-s-hopscotch span.cm-variable-2 {color: #1290bf;} .cm-s-hopscotch span.cm-def {color: #fd8b19;} .cm-s-hopscotch span.cm-error {background: #dd464c; color: #989498;} .cm-s-hopscotch span.cm-bracket {color: #d5d3d5;} .cm-s-hopscotch span.cm-tag {color: #dd464c;} .cm-s-hopscotch span.cm-link {color: #c85e7c;} .cm-s-hopscotch .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} .cm-s-hopscotch .CodeMirror-activeline-background { background: #302020; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/icecoder.css ================================================ /* ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net */ .cm-s-icecoder { color: #666; background: #1d1d1b; } .cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; } /* off-white 1 */ .cm-s-icecoder span.cm-atom { color: #e1c76e; } /* yellow */ .cm-s-icecoder span.cm-number { color: #6cb5d9; } /* blue */ .cm-s-icecoder span.cm-def { color: #b9ca4a; } /* green */ .cm-s-icecoder span.cm-variable { color: #6cb5d9; } /* blue */ .cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; } /* pink */ .cm-s-icecoder span.cm-variable-3, .cm-s-icecoder span.cm-type { color: #f9602c; } /* orange */ .cm-s-icecoder span.cm-property { color: #eee; } /* off-white 1 */ .cm-s-icecoder span.cm-operator { color: #9179bb; } /* purple */ .cm-s-icecoder span.cm-comment { color: #97a3aa; } /* grey-blue */ .cm-s-icecoder span.cm-string { color: #b9ca4a; } /* green */ .cm-s-icecoder span.cm-string-2 { color: #6cb5d9; } /* blue */ .cm-s-icecoder span.cm-meta { color: #555; } /* grey */ .cm-s-icecoder span.cm-qualifier { color: #555; } /* grey */ .cm-s-icecoder span.cm-builtin { color: #214e7b; } /* bright blue */ .cm-s-icecoder span.cm-bracket { color: #cc7; } /* grey-yellow */ .cm-s-icecoder span.cm-tag { color: #e8e8e8; } /* off-white 2 */ .cm-s-icecoder span.cm-attribute { color: #099; } /* teal */ .cm-s-icecoder span.cm-header { color: #6a0d6a; } /* purple-pink */ .cm-s-icecoder span.cm-quote { color: #186718; } /* dark green */ .cm-s-icecoder span.cm-hr { color: #888; } /* mid-grey */ .cm-s-icecoder span.cm-link { color: #e1c76e; } /* yellow */ .cm-s-icecoder span.cm-error { color: #d00; } /* red */ .cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; } .cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b; min-width: 41px; border-right: 0; } .cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; } .cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; } .cm-s-icecoder .CodeMirror-activeline-background { background: #000; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/idea.css ================================================ /** Name: IDEA default theme From IntelliJ IDEA by JetBrains */ .cm-s-idea span.cm-meta { color: #808000; } .cm-s-idea span.cm-number { color: #0000FF; } .cm-s-idea span.cm-keyword { line-height: 1em; font-weight: bold; color: #000080; } .cm-s-idea span.cm-atom { font-weight: bold; color: #000080; } .cm-s-idea span.cm-def { color: #000000; } .cm-s-idea span.cm-variable { color: black; } .cm-s-idea span.cm-variable-2 { color: black; } .cm-s-idea span.cm-variable-3, .cm-s-idea span.cm-type { color: black; } .cm-s-idea span.cm-property { color: black; } .cm-s-idea span.cm-operator { color: black; } .cm-s-idea span.cm-comment { color: #808080; } .cm-s-idea span.cm-string { color: #008000; } .cm-s-idea span.cm-string-2 { color: #008000; } .cm-s-idea span.cm-qualifier { color: #555; } .cm-s-idea span.cm-error { color: #FF0000; } .cm-s-idea span.cm-attribute { color: #0000FF; } .cm-s-idea span.cm-tag { color: #000080; } .cm-s-idea span.cm-link { color: #0000FF; } .cm-s-idea .CodeMirror-activeline-background { background: #FFFAE3; } .cm-s-idea span.cm-builtin { color: #30a; } .cm-s-idea span.cm-bracket { color: #cc7; } .cm-s-idea { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;} .cm-s-idea .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } .CodeMirror-hints.idea { font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; color: #616569; background-color: #ebf3fd !important; } .CodeMirror-hints.idea .CodeMirror-hint-active { background-color: #a2b8c9 !important; color: #5c6065 !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/isotope.css ================================================ /* Name: Isotope Author: David Desandro / Jan T. Sott CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-isotope.CodeMirror {background: #000000; color: #e0e0e0;} .cm-s-isotope div.CodeMirror-selected {background: #404040 !important;} .cm-s-isotope .CodeMirror-gutters {background: #000000; border-right: 0px;} .cm-s-isotope .CodeMirror-linenumber {color: #808080;} .cm-s-isotope .CodeMirror-cursor {border-left: 1px solid #c0c0c0 !important;} .cm-s-isotope span.cm-comment {color: #3300ff;} .cm-s-isotope span.cm-atom {color: #cc00ff;} .cm-s-isotope span.cm-number {color: #cc00ff;} .cm-s-isotope span.cm-property, .cm-s-isotope span.cm-attribute {color: #33ff00;} .cm-s-isotope span.cm-keyword {color: #ff0000;} .cm-s-isotope span.cm-string {color: #ff0099;} .cm-s-isotope span.cm-variable {color: #33ff00;} .cm-s-isotope span.cm-variable-2 {color: #0066ff;} .cm-s-isotope span.cm-def {color: #ff9900;} .cm-s-isotope span.cm-error {background: #ff0000; color: #c0c0c0;} .cm-s-isotope span.cm-bracket {color: #e0e0e0;} .cm-s-isotope span.cm-tag {color: #ff0000;} .cm-s-isotope span.cm-link {color: #cc00ff;} .cm-s-isotope .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} .cm-s-isotope .CodeMirror-activeline-background { background: #202020; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/juejin.css ================================================ .cm-s-juejin.CodeMirror { background: #f8f9fa; } .cm-s-juejin .cm-header, .cm-s-juejin .cm-def { color: #1ba2f0; } .cm-s-juejin .cm-comment { color: #009e9d; } .cm-s-juejin .cm-quote, .cm-s-juejin .cm-link, .cm-s-juejin .cm-strong, .cm-s-juejin .cm-attribute { color: #fd7741; } .cm-s-juejin .cm-url, .cm-s-juejin .cm-keyword, .cm-s-juejin .cm-builtin { color: #bb51b8; } .cm-s-juejin .cm-hr { color: #909090; } .cm-s-juejin .cm-tag { color: #107000; } .cm-s-juejin .cm-variable-2 { color: #0050a0; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/lesser-dark.css ================================================ /* http://lesscss.org/ dark theme Ported to CodeMirror by Peter Kroon */ .cm-s-lesser-dark { line-height: 1.3em; } .cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } .cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/ .cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(69, 68, 59, .99); } .cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); } .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ .cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ .cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } .cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; } .cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; } .cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } .cm-s-lesser-dark span.cm-header { color: #a0a; } .cm-s-lesser-dark span.cm-quote { color: #090; } .cm-s-lesser-dark span.cm-keyword { color: #599eff; } .cm-s-lesser-dark span.cm-atom { color: #C2B470; } .cm-s-lesser-dark span.cm-number { color: #B35E4D; } .cm-s-lesser-dark span.cm-def { color: white; } .cm-s-lesser-dark span.cm-variable { color:#D9BF8C; } .cm-s-lesser-dark span.cm-variable-2 { color: #669199; } .cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; } .cm-s-lesser-dark span.cm-property { color: #92A75C; } .cm-s-lesser-dark span.cm-operator { color: #92A75C; } .cm-s-lesser-dark span.cm-comment { color: #666; } .cm-s-lesser-dark span.cm-string { color: #BCD279; } .cm-s-lesser-dark span.cm-string-2 { color: #f50; } .cm-s-lesser-dark span.cm-meta { color: #738C73; } .cm-s-lesser-dark span.cm-qualifier { color: #555; } .cm-s-lesser-dark span.cm-builtin { color: #ff9e59; } .cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; } .cm-s-lesser-dark span.cm-tag { color: #669199; } .cm-s-lesser-dark span.cm-attribute { color: #81a4d5; } .cm-s-lesser-dark span.cm-hr { color: #999; } .cm-s-lesser-dark span.cm-link { color: #7070E6; } .cm-s-lesser-dark span.cm-error { color: #9d1e15; } .cm-s-lesser-dark .CodeMirror-activeline-background { background: #3C3A3A; } .cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/liquibyte.css ================================================ .cm-s-liquibyte.CodeMirror { background-color: #000; color: #fff; line-height: 1.2em; font-size: 1em; } .cm-s-liquibyte .CodeMirror-focused .cm-matchhighlight { text-decoration: underline; text-decoration-color: #0f0; text-decoration-style: wavy; } .cm-s-liquibyte .cm-trailingspace { text-decoration: line-through; text-decoration-color: #f00; text-decoration-style: dotted; } .cm-s-liquibyte .cm-tab { text-decoration: line-through; text-decoration-color: #404040; text-decoration-style: dotted; } .cm-s-liquibyte .CodeMirror-gutters { background-color: #262626; border-right: 1px solid #505050; padding-right: 0.8em; } .cm-s-liquibyte .CodeMirror-gutter-elt div { font-size: 1.2em; } .cm-s-liquibyte .CodeMirror-guttermarker { } .cm-s-liquibyte .CodeMirror-guttermarker-subtle { } .cm-s-liquibyte .CodeMirror-linenumber { color: #606060; padding-left: 0; } .cm-s-liquibyte .CodeMirror-cursor { border-left: 1px solid #eee; } .cm-s-liquibyte span.cm-comment { color: #008000; } .cm-s-liquibyte span.cm-def { color: #ffaf40; font-weight: bold; } .cm-s-liquibyte span.cm-keyword { color: #c080ff; font-weight: bold; } .cm-s-liquibyte span.cm-builtin { color: #ffaf40; font-weight: bold; } .cm-s-liquibyte span.cm-variable { color: #5967ff; font-weight: bold; } .cm-s-liquibyte span.cm-string { color: #ff8000; } .cm-s-liquibyte span.cm-number { color: #0f0; font-weight: bold; } .cm-s-liquibyte span.cm-atom { color: #bf3030; font-weight: bold; } .cm-s-liquibyte span.cm-variable-2 { color: #007f7f; font-weight: bold; } .cm-s-liquibyte span.cm-variable-3, .cm-s-liquibyte span.cm-type { color: #c080ff; font-weight: bold; } .cm-s-liquibyte span.cm-property { color: #999; font-weight: bold; } .cm-s-liquibyte span.cm-operator { color: #fff; } .cm-s-liquibyte span.cm-meta { color: #0f0; } .cm-s-liquibyte span.cm-qualifier { color: #fff700; font-weight: bold; } .cm-s-liquibyte span.cm-bracket { color: #cc7; } .cm-s-liquibyte span.cm-tag { color: #ff0; font-weight: bold; } .cm-s-liquibyte span.cm-attribute { color: #c080ff; font-weight: bold; } .cm-s-liquibyte span.cm-error { color: #f00; } .cm-s-liquibyte div.CodeMirror-selected { background-color: rgba(255, 0, 0, 0.25); } .cm-s-liquibyte span.cm-compilation { background-color: rgba(255, 255, 255, 0.12); } .cm-s-liquibyte .CodeMirror-activeline-background { background-color: rgba(0, 255, 0, 0.15); } /* Default styles for common addons */ .cm-s-liquibyte .CodeMirror span.CodeMirror-matchingbracket { color: #0f0; font-weight: bold; } .cm-s-liquibyte .CodeMirror span.CodeMirror-nonmatchingbracket { color: #f00; font-weight: bold; } .CodeMirror-matchingtag { background-color: rgba(150, 255, 0, .3); } /* Scrollbars */ /* Simple */ .cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div:hover, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div:hover { background-color: rgba(80, 80, 80, .7); } .cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div, .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { background-color: rgba(80, 80, 80, .3); border: 1px solid #404040; border-radius: 5px; } .cm-s-liquibyte div.CodeMirror-simplescroll-vertical div { border-top: 1px solid #404040; border-bottom: 1px solid #404040; } .cm-s-liquibyte div.CodeMirror-simplescroll-horizontal div { border-left: 1px solid #404040; border-right: 1px solid #404040; } .cm-s-liquibyte div.CodeMirror-simplescroll-vertical { background-color: #262626; } .cm-s-liquibyte div.CodeMirror-simplescroll-horizontal { background-color: #262626; border-top: 1px solid #404040; } /* Overlay */ .cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div, div.CodeMirror-overlayscroll-vertical div { background-color: #404040; border-radius: 5px; } .cm-s-liquibyte div.CodeMirror-overlayscroll-vertical div { border: 1px solid #404040; } .cm-s-liquibyte div.CodeMirror-overlayscroll-horizontal div { border: 1px solid #404040; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/lucario.css ================================================ /* Name: lucario Author: Raphael Amorim Original Lucario color scheme (https://github.com/raphamorim/lucario) */ .cm-s-lucario.CodeMirror, .cm-s-lucario .CodeMirror-gutters { background-color: #2b3e50 !important; color: #f8f8f2 !important; border: none; } .cm-s-lucario .CodeMirror-gutters { color: #2b3e50; } .cm-s-lucario .CodeMirror-cursor { border-left: solid thin #E6C845; } .cm-s-lucario .CodeMirror-linenumber { color: #f8f8f2; } .cm-s-lucario .CodeMirror-selected { background: #243443; } .cm-s-lucario .CodeMirror-line::selection, .cm-s-lucario .CodeMirror-line > span::selection, .cm-s-lucario .CodeMirror-line > span > span::selection { background: #243443; } .cm-s-lucario .CodeMirror-line::-moz-selection, .cm-s-lucario .CodeMirror-line > span::-moz-selection, .cm-s-lucario .CodeMirror-line > span > span::-moz-selection { background: #243443; } .cm-s-lucario span.cm-comment { color: #5c98cd; } .cm-s-lucario span.cm-string, .cm-s-lucario span.cm-string-2 { color: #E6DB74; } .cm-s-lucario span.cm-number { color: #ca94ff; } .cm-s-lucario span.cm-variable { color: #f8f8f2; } .cm-s-lucario span.cm-variable-2 { color: #f8f8f2; } .cm-s-lucario span.cm-def { color: #72C05D; } .cm-s-lucario span.cm-operator { color: #66D9EF; } .cm-s-lucario span.cm-keyword { color: #ff6541; } .cm-s-lucario span.cm-atom { color: #bd93f9; } .cm-s-lucario span.cm-meta { color: #f8f8f2; } .cm-s-lucario span.cm-tag { color: #ff6541; } .cm-s-lucario span.cm-attribute { color: #66D9EF; } .cm-s-lucario span.cm-qualifier { color: #72C05D; } .cm-s-lucario span.cm-property { color: #f8f8f2; } .cm-s-lucario span.cm-builtin { color: #72C05D; } .cm-s-lucario span.cm-variable-3, .cm-s-lucario span.cm-type { color: #ffb86c; } .cm-s-lucario .CodeMirror-activeline-background { background: #243443; } .cm-s-lucario .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/material-darker.css ================================================ /* Name: material Author: Mattia Astorino (http://github.com/equinusocio) Website: https://material-theme.site/ */ .cm-s-material-darker.CodeMirror { background-color: #212121; color: #EEFFFF; } .cm-s-material-darker .CodeMirror-gutters { background: #212121; color: #545454; border: none; } .cm-s-material-darker .CodeMirror-guttermarker, .cm-s-material-darker .CodeMirror-guttermarker-subtle, .cm-s-material-darker .CodeMirror-linenumber { color: #545454; } .cm-s-material-darker .CodeMirror-cursor { border-left: 1px solid #FFCC00; } .cm-s-material-darker div.CodeMirror-selected { background: rgba(97, 97, 97, 0.2); } .cm-s-material-darker.CodeMirror-focused div.CodeMirror-selected { background: rgba(97, 97, 97, 0.2); } .cm-s-material-darker .CodeMirror-line::selection, .cm-s-material-darker .CodeMirror-line>span::selection, .cm-s-material-darker .CodeMirror-line>span>span::selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-darker .CodeMirror-line::-moz-selection, .cm-s-material-darker .CodeMirror-line>span::-moz-selection, .cm-s-material-darker .CodeMirror-line>span>span::-moz-selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-darker .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.5); } .cm-s-material-darker .cm-keyword { color: #C792EA; } .cm-s-material-darker .cm-operator { color: #89DDFF; } .cm-s-material-darker .cm-variable-2 { color: #EEFFFF; } .cm-s-material-darker .cm-variable-3, .cm-s-material-darker .cm-type { color: #f07178; } .cm-s-material-darker .cm-builtin { color: #FFCB6B; } .cm-s-material-darker .cm-atom { color: #F78C6C; } .cm-s-material-darker .cm-number { color: #FF5370; } .cm-s-material-darker .cm-def { color: #82AAFF; } .cm-s-material-darker .cm-string { color: #C3E88D; } .cm-s-material-darker .cm-string-2 { color: #f07178; } .cm-s-material-darker .cm-comment { color: #545454; } .cm-s-material-darker .cm-variable { color: #f07178; } .cm-s-material-darker .cm-tag { color: #FF5370; } .cm-s-material-darker .cm-meta { color: #FFCB6B; } .cm-s-material-darker .cm-attribute { color: #C792EA; } .cm-s-material-darker .cm-property { color: #C792EA; } .cm-s-material-darker .cm-qualifier { color: #DECB6B; } .cm-s-material-darker .cm-variable-3, .cm-s-material-darker .cm-type { color: #DECB6B; } .cm-s-material-darker .cm-error { color: rgba(255, 255, 255, 1.0); background-color: #FF5370; } .cm-s-material-darker .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/material-ocean.css ================================================ /* Name: material Author: Mattia Astorino (http://github.com/equinusocio) Website: https://material-theme.site/ */ .cm-s-material-ocean.CodeMirror { background-color: #0F111A; color: #8F93A2; } .cm-s-material-ocean .CodeMirror-gutters { background: #0F111A; color: #464B5D; border: none; } .cm-s-material-ocean .CodeMirror-guttermarker, .cm-s-material-ocean .CodeMirror-guttermarker-subtle, .cm-s-material-ocean .CodeMirror-linenumber { color: #464B5D; } .cm-s-material-ocean .CodeMirror-cursor { border-left: 1px solid #FFCC00; } .cm-s-material-ocean.cm-fat-cursor .CodeMirror-cursor { background-color: #a2a8a175 !important; } .cm-s-material-ocean .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } .cm-s-material-ocean div.CodeMirror-selected { background: rgba(113, 124, 180, 0.2); } .cm-s-material-ocean.CodeMirror-focused div.CodeMirror-selected { background: rgba(113, 124, 180, 0.2); } .cm-s-material-ocean .CodeMirror-line::selection, .cm-s-material-ocean .CodeMirror-line>span::selection, .cm-s-material-ocean .CodeMirror-line>span>span::selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-ocean .CodeMirror-line::-moz-selection, .cm-s-material-ocean .CodeMirror-line>span::-moz-selection, .cm-s-material-ocean .CodeMirror-line>span>span::-moz-selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-ocean .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.5); } .cm-s-material-ocean .cm-keyword { color: #C792EA; } .cm-s-material-ocean .cm-operator { color: #89DDFF; } .cm-s-material-ocean .cm-variable-2 { color: #EEFFFF; } .cm-s-material-ocean .cm-variable-3, .cm-s-material-ocean .cm-type { color: #f07178; } .cm-s-material-ocean .cm-builtin { color: #FFCB6B; } .cm-s-material-ocean .cm-atom { color: #F78C6C; } .cm-s-material-ocean .cm-number { color: #FF5370; } .cm-s-material-ocean .cm-def { color: #82AAFF; } .cm-s-material-ocean .cm-string { color: #C3E88D; } .cm-s-material-ocean .cm-string-2 { color: #f07178; } .cm-s-material-ocean .cm-comment { color: #464B5D; } .cm-s-material-ocean .cm-variable { color: #f07178; } .cm-s-material-ocean .cm-tag { color: #FF5370; } .cm-s-material-ocean .cm-meta { color: #FFCB6B; } .cm-s-material-ocean .cm-attribute { color: #C792EA; } .cm-s-material-ocean .cm-property { color: #C792EA; } .cm-s-material-ocean .cm-qualifier { color: #DECB6B; } .cm-s-material-ocean .cm-variable-3, .cm-s-material-ocean .cm-type { color: #DECB6B; } .cm-s-material-ocean .cm-error { color: rgba(255, 255, 255, 1.0); background-color: #FF5370; } .cm-s-material-ocean .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/material-palenight.css ================================================ /* Name: material Author: Mattia Astorino (http://github.com/equinusocio) Website: https://material-theme.site/ */ .cm-s-material-palenight.CodeMirror { background-color: #292D3E; color: #A6ACCD; } .cm-s-material-palenight .CodeMirror-gutters { background: #292D3E; color: #676E95; border: none; } .cm-s-material-palenight .CodeMirror-guttermarker, .cm-s-material-palenight .CodeMirror-guttermarker-subtle, .cm-s-material-palenight .CodeMirror-linenumber { color: #676E95; } .cm-s-material-palenight .CodeMirror-cursor { border-left: 1px solid #FFCC00; } .cm-s-material-palenight.cm-fat-cursor .CodeMirror-cursor { background-color: #607c8b80 !important; } .cm-s-material-palenight .cm-animate-fat-cursor { background-color: #607c8b80 !important; } .cm-s-material-palenight div.CodeMirror-selected { background: rgba(113, 124, 180, 0.2); } .cm-s-material-palenight.CodeMirror-focused div.CodeMirror-selected { background: rgba(113, 124, 180, 0.2); } .cm-s-material-palenight .CodeMirror-line::selection, .cm-s-material-palenight .CodeMirror-line>span::selection, .cm-s-material-palenight .CodeMirror-line>span>span::selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-palenight .CodeMirror-line::-moz-selection, .cm-s-material-palenight .CodeMirror-line>span::-moz-selection, .cm-s-material-palenight .CodeMirror-line>span>span::-moz-selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material-palenight .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.5); } .cm-s-material-palenight .cm-keyword { color: #C792EA; } .cm-s-material-palenight .cm-operator { color: #89DDFF; } .cm-s-material-palenight .cm-variable-2 { color: #EEFFFF; } .cm-s-material-palenight .cm-variable-3, .cm-s-material-palenight .cm-type { color: #f07178; } .cm-s-material-palenight .cm-builtin { color: #FFCB6B; } .cm-s-material-palenight .cm-atom { color: #F78C6C; } .cm-s-material-palenight .cm-number { color: #FF5370; } .cm-s-material-palenight .cm-def { color: #82AAFF; } .cm-s-material-palenight .cm-string { color: #C3E88D; } .cm-s-material-palenight .cm-string-2 { color: #f07178; } .cm-s-material-palenight .cm-comment { color: #676E95; } .cm-s-material-palenight .cm-variable { color: #f07178; } .cm-s-material-palenight .cm-tag { color: #FF5370; } .cm-s-material-palenight .cm-meta { color: #FFCB6B; } .cm-s-material-palenight .cm-attribute { color: #C792EA; } .cm-s-material-palenight .cm-property { color: #C792EA; } .cm-s-material-palenight .cm-qualifier { color: #DECB6B; } .cm-s-material-palenight .cm-variable-3, .cm-s-material-palenight .cm-type { color: #DECB6B; } .cm-s-material-palenight .cm-error { color: rgba(255, 255, 255, 1.0); background-color: #FF5370; } .cm-s-material-palenight .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/material.css ================================================ /* Name: material Author: Mattia Astorino (http://github.com/equinusocio) Website: https://material-theme.site/ */ .cm-s-material.CodeMirror { background-color: #263238; color: #EEFFFF; } .cm-s-material .CodeMirror-gutters { background: #263238; color: #546E7A; border: none; } .cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: #546E7A; } .cm-s-material .CodeMirror-cursor { border-left: 1px solid #FFCC00; } .cm-s-material.cm-fat-cursor .CodeMirror-cursor { background-color: #5d6d5c80 !important; } .cm-s-material .cm-animate-fat-cursor { background-color: #5d6d5c80 !important; } .cm-s-material div.CodeMirror-selected { background: rgba(128, 203, 196, 0.2); } .cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(128, 203, 196, 0.2); } .cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line>span::selection, .cm-s-material .CodeMirror-line>span>span::selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line>span::-moz-selection, .cm-s-material .CodeMirror-line>span>span::-moz-selection { background: rgba(128, 203, 196, 0.2); } .cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.5); } .cm-s-material .cm-keyword { color: #C792EA; } .cm-s-material .cm-operator { color: #89DDFF; } .cm-s-material .cm-variable-2 { color: #EEFFFF; } .cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #f07178; } .cm-s-material .cm-builtin { color: #FFCB6B; } .cm-s-material .cm-atom { color: #F78C6C; } .cm-s-material .cm-number { color: #FF5370; } .cm-s-material .cm-def { color: #82AAFF; } .cm-s-material .cm-string { color: #C3E88D; } .cm-s-material .cm-string-2 { color: #f07178; } .cm-s-material .cm-comment { color: #546E7A; } .cm-s-material .cm-variable { color: #f07178; } .cm-s-material .cm-tag { color: #FF5370; } .cm-s-material .cm-meta { color: #FFCB6B; } .cm-s-material .cm-attribute { color: #C792EA; } .cm-s-material .cm-property { color: #C792EA; } .cm-s-material .cm-qualifier { color: #DECB6B; } .cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #DECB6B; } .cm-s-material .cm-error { color: rgba(255, 255, 255, 1.0); background-color: #FF5370; } .cm-s-material .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/mbo.css ================================================ /****************************************************************/ /* Based on mbonaci's Brackets mbo theme */ /* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ /* Create your own: http://tmtheme-editor.herokuapp.com */ /****************************************************************/ .cm-s-mbo.CodeMirror { background: #2c2c2c; color: #ffffec; } .cm-s-mbo div.CodeMirror-selected { background: #716C62; } .cm-s-mbo .CodeMirror-line::selection, .cm-s-mbo .CodeMirror-line > span::selection, .cm-s-mbo .CodeMirror-line > span > span::selection { background: rgba(113, 108, 98, .99); } .cm-s-mbo .CodeMirror-line::-moz-selection, .cm-s-mbo .CodeMirror-line > span::-moz-selection, .cm-s-mbo .CodeMirror-line > span > span::-moz-selection { background: rgba(113, 108, 98, .99); } .cm-s-mbo .CodeMirror-gutters { background: #4e4e4e; border-right: 0px; } .cm-s-mbo .CodeMirror-guttermarker { color: white; } .cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; } .cm-s-mbo .CodeMirror-linenumber { color: #dadada; } .cm-s-mbo .CodeMirror-cursor { border-left: 1px solid #ffffec; } .cm-s-mbo span.cm-comment { color: #95958a; } .cm-s-mbo span.cm-atom { color: #00a8c6; } .cm-s-mbo span.cm-number { color: #00a8c6; } .cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute { color: #9ddfe9; } .cm-s-mbo span.cm-keyword { color: #ffb928; } .cm-s-mbo span.cm-string { color: #ffcf6c; } .cm-s-mbo span.cm-string.cm-property { color: #ffffec; } .cm-s-mbo span.cm-variable { color: #ffffec; } .cm-s-mbo span.cm-variable-2 { color: #00a8c6; } .cm-s-mbo span.cm-def { color: #ffffec; } .cm-s-mbo span.cm-bracket { color: #fffffc; font-weight: bold; } .cm-s-mbo span.cm-tag { color: #9ddfe9; } .cm-s-mbo span.cm-link { color: #f54b07; } .cm-s-mbo span.cm-error { border-bottom: #636363; color: #ffffec; } .cm-s-mbo span.cm-qualifier { color: #ffffec; } .cm-s-mbo .CodeMirror-activeline-background { background: #494b41; } .cm-s-mbo .CodeMirror-matchingbracket { color: #ffb928 !important; } .cm-s-mbo .CodeMirror-matchingtag { background: rgba(255, 255, 255, .37); } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/mdn-like.css ================================================ /* MDN-LIKE Theme - Mozilla Ported to CodeMirror by Peter Kroon Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues GitHub: @peterkroon The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation */ .cm-s-mdn-like.CodeMirror { color: #999; background-color: #fff; } .cm-s-mdn-like div.CodeMirror-selected { background: #cfc; } .cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #cfc; } .cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #cfc; } .cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; } .cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; } .cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; } .cm-s-mdn-like .cm-keyword { color: #6262FF; } .cm-s-mdn-like .cm-atom { color: #F90; } .cm-s-mdn-like .cm-number { color: #ca7841; } .cm-s-mdn-like .cm-def { color: #8DA6CE; } .cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; } .cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def, .cm-s-mdn-like span.cm-type { color: #07a; } .cm-s-mdn-like .cm-variable { color: #07a; } .cm-s-mdn-like .cm-property { color: #905; } .cm-s-mdn-like .cm-qualifier { color: #690; } .cm-s-mdn-like .cm-operator { color: #cda869; } .cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; } .cm-s-mdn-like .cm-string { color:#07a; font-style:italic; } .cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/ .cm-s-mdn-like .cm-meta { color: #000; } /*?*/ .cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/ .cm-s-mdn-like .cm-tag { color: #997643; } .cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/ .cm-s-mdn-like .cm-header { color: #FF6400; } .cm-s-mdn-like .cm-hr { color: #AEAEAE; } .cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } .cm-s-mdn-like .cm-error { border-bottom: 1px solid red; } div.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; } div.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; } .cm-s-mdn-like.CodeMirror { background-image: url(data:image/png;base64,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); } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/midnight.css ================================================ /* Based on the theme at http://bonsaiden.github.com/JavaScript-Garden */ /**/ .cm-s-midnight .CodeMirror-activeline-background { background: #253540; } .cm-s-midnight.CodeMirror { background: #0F192A; color: #D1EDFF; } .cm-s-midnight div.CodeMirror-selected { background: #314D67; } .cm-s-midnight .CodeMirror-line::selection, .cm-s-midnight .CodeMirror-line > span::selection, .cm-s-midnight .CodeMirror-line > span > span::selection { background: rgba(49, 77, 103, .99); } .cm-s-midnight .CodeMirror-line::-moz-selection, .cm-s-midnight .CodeMirror-line > span::-moz-selection, .cm-s-midnight .CodeMirror-line > span > span::-moz-selection { background: rgba(49, 77, 103, .99); } .cm-s-midnight .CodeMirror-gutters { background: #0F192A; border-right: 1px solid; } .cm-s-midnight .CodeMirror-guttermarker { color: white; } .cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-midnight .CodeMirror-linenumber { color: #D0D0D0; } .cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0; } .cm-s-midnight span.cm-comment { color: #428BDD; } .cm-s-midnight span.cm-atom { color: #AE81FF; } .cm-s-midnight span.cm-number { color: #D1EDFF; } .cm-s-midnight span.cm-property, .cm-s-midnight span.cm-attribute { color: #A6E22E; } .cm-s-midnight span.cm-keyword { color: #E83737; } .cm-s-midnight span.cm-string { color: #1DC116; } .cm-s-midnight span.cm-variable { color: #FFAA3E; } .cm-s-midnight span.cm-variable-2 { color: #FFAA3E; } .cm-s-midnight span.cm-def { color: #4DD; } .cm-s-midnight span.cm-bracket { color: #D1EDFF; } .cm-s-midnight span.cm-tag { color: #449; } .cm-s-midnight span.cm-link { color: #AE81FF; } .cm-s-midnight span.cm-error { background: #F92672; color: #F8F8F0; } .cm-s-midnight .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/monokai.css ================================================ /* Based on Sublime Text's Monokai theme */ .cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; } .cm-s-monokai div.CodeMirror-selected { background: #49483E; } .cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); } .cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); } .cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; } .cm-s-monokai .CodeMirror-guttermarker { color: white; } .cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } .cm-s-monokai span.cm-comment { color: #75715e; } .cm-s-monokai span.cm-atom { color: #ae81ff; } .cm-s-monokai span.cm-number { color: #ae81ff; } .cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; } .cm-s-monokai span.cm-comment.cm-def { color: #bc9262; } .cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; } .cm-s-monokai span.cm-comment.cm-type { color: #5998a6; } .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; } .cm-s-monokai span.cm-keyword { color: #f92672; } .cm-s-monokai span.cm-builtin { color: #66d9ef; } .cm-s-monokai span.cm-string { color: #e6db74; } .cm-s-monokai span.cm-variable { color: #f8f8f2; } .cm-s-monokai span.cm-variable-2 { color: #9effff; } .cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; } .cm-s-monokai span.cm-def { color: #fd971f; } .cm-s-monokai span.cm-bracket { color: #f8f8f2; } .cm-s-monokai span.cm-tag { color: #f92672; } .cm-s-monokai span.cm-header { color: #ae81ff; } .cm-s-monokai span.cm-link { color: #ae81ff; } .cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; } .cm-s-monokai .CodeMirror-activeline-background { background: #373831; } .cm-s-monokai .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/moxer.css ================================================ /* Name: Moxer Theme Author: Mattia Astorino (http://github.com/equinusocio) Website: https://github.com/moxer-theme/moxer-code */ .cm-s-moxer.CodeMirror { background-color: #090A0F; color: #8E95B4; line-height: 1.8; } .cm-s-moxer .CodeMirror-gutters { background: #090A0F; color: #35394B; border: none; } .cm-s-moxer .CodeMirror-guttermarker, .cm-s-moxer .CodeMirror-guttermarker-subtle, .cm-s-moxer .CodeMirror-linenumber { color: #35394B; } .cm-s-moxer .CodeMirror-cursor { border-left: 1px solid #FFCC00; } .cm-s-moxer div.CodeMirror-selected { background: rgba(128, 203, 196, 0.2); } .cm-s-moxer.CodeMirror-focused div.CodeMirror-selected { background: #212431; } .cm-s-moxer .CodeMirror-line::selection, .cm-s-moxer .CodeMirror-line>span::selection, .cm-s-moxer .CodeMirror-line>span>span::selection { background: #212431; } .cm-s-moxer .CodeMirror-line::-moz-selection, .cm-s-moxer .CodeMirror-line>span::-moz-selection, .cm-s-moxer .CodeMirror-line>span>span::-moz-selection { background: #212431; } .cm-s-moxer .CodeMirror-activeline-background, .cm-s-moxer .CodeMirror-activeline-gutter .CodeMirror-linenumber { background: rgba(33, 36, 49, 0.5); } .cm-s-moxer .cm-keyword { color: #D46C6C; } .cm-s-moxer .cm-operator { color: #D46C6C; } .cm-s-moxer .cm-variable-2 { color: #81C5DA; } .cm-s-moxer .cm-variable-3, .cm-s-moxer .cm-type { color: #f07178; } .cm-s-moxer .cm-builtin { color: #FFCB6B; } .cm-s-moxer .cm-atom { color: #A99BE2; } .cm-s-moxer .cm-number { color: #7CA4C0; } .cm-s-moxer .cm-def { color: #F5DFA5; } .cm-s-moxer .CodeMirror-line .cm-def ~ .cm-def { color: #81C5DA; } .cm-s-moxer .cm-string { color: #B2E4AE; } .cm-s-moxer .cm-string-2 { color: #f07178; } .cm-s-moxer .cm-comment { color: #3F445A; } .cm-s-moxer .cm-variable { color: #8E95B4; } .cm-s-moxer .cm-tag { color: #FF5370; } .cm-s-moxer .cm-meta { color: #FFCB6B; } .cm-s-moxer .cm-attribute { color: #C792EA; } .cm-s-moxer .cm-property { color: #81C5DA; } .cm-s-moxer .cm-qualifier { color: #DECB6B; } .cm-s-moxer .cm-variable-3, .cm-s-moxer .cm-type { color: #DECB6B; } .cm-s-moxer .cm-error { color: rgba(255, 255, 255, 1.0); background-color: #FF5370; } .cm-s-moxer .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/neat.css ================================================ .cm-s-neat span.cm-comment { color: #a86; } .cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; } .cm-s-neat span.cm-string { color: #a22; } .cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } .cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } .cm-s-neat span.cm-variable { color: black; } .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; } .cm-s-neat span.cm-meta { color: #555; } .cm-s-neat span.cm-link { color: #3a3; } .cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/neo.css ================================================ /* neo theme for codemirror */ /* Color scheme */ .cm-s-neo.CodeMirror { background-color:#ffffff; color:#2e383c; line-height:1.4375; } .cm-s-neo .cm-comment { color:#75787b; } .cm-s-neo .cm-keyword, .cm-s-neo .cm-property { color:#1d75b3; } .cm-s-neo .cm-atom,.cm-s-neo .cm-number { color:#75438a; } .cm-s-neo .cm-node,.cm-s-neo .cm-tag { color:#9c3328; } .cm-s-neo .cm-string { color:#b35e14; } .cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier { color:#047d65; } /* Editor styling */ .cm-s-neo pre { padding:0; } .cm-s-neo .CodeMirror-gutters { border:none; border-right:10px solid transparent; background-color:transparent; } .cm-s-neo .CodeMirror-linenumber { padding:0; color:#e0e2e5; } .cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } .cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } .cm-s-neo .CodeMirror-cursor { width: auto; border: 0; background: rgba(155,157,162,0.37); z-index: 1; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/night.css ================================================ /* Loosely based on the Midnight Textmate theme */ .cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-night div.CodeMirror-selected { background: #447; } .cm-s-night .CodeMirror-line::selection, .cm-s-night .CodeMirror-line > span::selection, .cm-s-night .CodeMirror-line > span > span::selection { background: rgba(68, 68, 119, .99); } .cm-s-night .CodeMirror-line::-moz-selection, .cm-s-night .CodeMirror-line > span::-moz-selection, .cm-s-night .CodeMirror-line > span > span::-moz-selection { background: rgba(68, 68, 119, .99); } .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } .cm-s-night .CodeMirror-guttermarker { color: white; } .cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } .cm-s-night .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-night span.cm-comment { color: #8900d1; } .cm-s-night span.cm-atom { color: #845dc4; } .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; } .cm-s-night span.cm-keyword { color: #599eff; } .cm-s-night span.cm-string { color: #37f14a; } .cm-s-night span.cm-meta { color: #7678e2; } .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; } .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def, .cm-s-night span.cm-type { color: white; } .cm-s-night span.cm-bracket { color: #8da6ce; } .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; } .cm-s-night span.cm-link { color: #845dc4; } .cm-s-night span.cm-error { color: #9d1e15; } .cm-s-night .CodeMirror-activeline-background { background: #1C005A; } .cm-s-night .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/nord.css ================================================ /* Based on arcticicestudio's Nord theme */ /* https://github.com/arcticicestudio/nord */ .cm-s-nord.CodeMirror { background: #2e3440; color: #d8dee9; } .cm-s-nord div.CodeMirror-selected { background: #434c5e; } .cm-s-nord .CodeMirror-line::selection, .cm-s-nord .CodeMirror-line > span::selection, .cm-s-nord .CodeMirror-line > span > span::selection { background: #3b4252; } .cm-s-nord .CodeMirror-line::-moz-selection, .cm-s-nord .CodeMirror-line > span::-moz-selection, .cm-s-nord .CodeMirror-line > span > span::-moz-selection { background: #3b4252; } .cm-s-nord .CodeMirror-gutters { background: #2e3440; border-right: 0px; } .cm-s-nord .CodeMirror-guttermarker { color: #4c566a; } .cm-s-nord .CodeMirror-guttermarker-subtle { color: #4c566a; } .cm-s-nord .CodeMirror-linenumber { color: #4c566a; } .cm-s-nord .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } .cm-s-nord span.cm-comment { color: #4c566a; } .cm-s-nord span.cm-atom { color: #b48ead; } .cm-s-nord span.cm-number { color: #b48ead; } .cm-s-nord span.cm-comment.cm-attribute { color: #97b757; } .cm-s-nord span.cm-comment.cm-def { color: #bc9262; } .cm-s-nord span.cm-comment.cm-tag { color: #bc6283; } .cm-s-nord span.cm-comment.cm-type { color: #5998a6; } .cm-s-nord span.cm-property, .cm-s-nord span.cm-attribute { color: #8FBCBB; } .cm-s-nord span.cm-keyword { color: #81A1C1; } .cm-s-nord span.cm-builtin { color: #81A1C1; } .cm-s-nord span.cm-string { color: #A3BE8C; } .cm-s-nord span.cm-variable { color: #d8dee9; } .cm-s-nord span.cm-variable-2 { color: #d8dee9; } .cm-s-nord span.cm-variable-3, .cm-s-nord span.cm-type { color: #d8dee9; } .cm-s-nord span.cm-def { color: #8FBCBB; } .cm-s-nord span.cm-bracket { color: #81A1C1; } .cm-s-nord span.cm-tag { color: #bf616a; } .cm-s-nord span.cm-header { color: #b48ead; } .cm-s-nord span.cm-link { color: #b48ead; } .cm-s-nord span.cm-error { background: #bf616a; color: #f8f8f0; } .cm-s-nord .CodeMirror-activeline-background { background: #3b4252; } .cm-s-nord .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/oceanic-next.css ================================================ /* Name: oceanic-next Author: Filype Pereira (https://github.com/fpereira1) Original oceanic-next color scheme by Dmitri Voronianski (https://github.com/voronianski/oceanic-next-color-scheme) */ .cm-s-oceanic-next.CodeMirror { background: #304148; color: #f8f8f2; } .cm-s-oceanic-next div.CodeMirror-selected { background: rgba(101, 115, 126, 0.33); } .cm-s-oceanic-next .CodeMirror-line::selection, .cm-s-oceanic-next .CodeMirror-line > span::selection, .cm-s-oceanic-next .CodeMirror-line > span > span::selection { background: rgba(101, 115, 126, 0.33); } .cm-s-oceanic-next .CodeMirror-line::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span::-moz-selection, .cm-s-oceanic-next .CodeMirror-line > span > span::-moz-selection { background: rgba(101, 115, 126, 0.33); } .cm-s-oceanic-next .CodeMirror-gutters { background: #304148; border-right: 10px; } .cm-s-oceanic-next .CodeMirror-guttermarker { color: white; } .cm-s-oceanic-next .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-oceanic-next .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-oceanic-next .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } .cm-s-oceanic-next.cm-fat-cursor .CodeMirror-cursor { background-color: #a2a8a175 !important; } .cm-s-oceanic-next .cm-animate-fat-cursor { background-color: #a2a8a175 !important; } .cm-s-oceanic-next span.cm-comment { color: #65737E; } .cm-s-oceanic-next span.cm-atom { color: #C594C5; } .cm-s-oceanic-next span.cm-number { color: #F99157; } .cm-s-oceanic-next span.cm-property { color: #99C794; } .cm-s-oceanic-next span.cm-attribute, .cm-s-oceanic-next span.cm-keyword { color: #C594C5; } .cm-s-oceanic-next span.cm-builtin { color: #66d9ef; } .cm-s-oceanic-next span.cm-string { color: #99C794; } .cm-s-oceanic-next span.cm-variable, .cm-s-oceanic-next span.cm-variable-2, .cm-s-oceanic-next span.cm-variable-3 { color: #f8f8f2; } .cm-s-oceanic-next span.cm-def { color: #6699CC; } .cm-s-oceanic-next span.cm-bracket { color: #5FB3B3; } .cm-s-oceanic-next span.cm-tag { color: #C594C5; } .cm-s-oceanic-next span.cm-header { color: #C594C5; } .cm-s-oceanic-next span.cm-link { color: #C594C5; } .cm-s-oceanic-next span.cm-error { background: #C594C5; color: #f8f8f0; } .cm-s-oceanic-next .CodeMirror-activeline-background { background: rgba(101, 115, 126, 0.33); } .cm-s-oceanic-next .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/panda-syntax.css ================================================ /* Name: Panda Syntax Author: Siamak Mokhtari (http://github.com/siamak/) CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax) */ .cm-s-panda-syntax { background: #292A2B; color: #E6E6E6; line-height: 1.5; font-family: 'Operator Mono', 'Source Code Pro', Menlo, Monaco, Consolas, Courier New, monospace; } .cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; } .cm-s-panda-syntax .CodeMirror-activeline-background { background: rgba(99, 123, 156, 0.1); } .cm-s-panda-syntax .CodeMirror-selected { background: #FFF; } .cm-s-panda-syntax .cm-comment { font-style: italic; color: #676B79; } .cm-s-panda-syntax .cm-operator { color: #f3f3f3; } .cm-s-panda-syntax .cm-string { color: #19F9D8; } .cm-s-panda-syntax .cm-string-2 { color: #FFB86C; } .cm-s-panda-syntax .cm-tag { color: #ff2c6d; } .cm-s-panda-syntax .cm-meta { color: #b084eb; } .cm-s-panda-syntax .cm-number { color: #FFB86C; } .cm-s-panda-syntax .cm-atom { color: #ff2c6d; } .cm-s-panda-syntax .cm-keyword { color: #FF75B5; } .cm-s-panda-syntax .cm-variable { color: #ffb86c; } .cm-s-panda-syntax .cm-variable-2 { color: #ff9ac1; } .cm-s-panda-syntax .cm-variable-3, .cm-s-panda-syntax .cm-type { color: #ff9ac1; } .cm-s-panda-syntax .cm-def { color: #e6e6e6; } .cm-s-panda-syntax .cm-property { color: #f3f3f3; } .cm-s-panda-syntax .cm-unit { color: #ffb86c; } .cm-s-panda-syntax .cm-attribute { color: #ffb86c; } .cm-s-panda-syntax .CodeMirror-matchingbracket { border-bottom: 1px dotted #19F9D8; padding-bottom: 2px; color: #e6e6e6; } .cm-s-panda-syntax .CodeMirror-gutters { background: #292a2b; border-right-color: rgba(255, 255, 255, 0.1); } .cm-s-panda-syntax .CodeMirror-linenumber { color: #e6e6e6; opacity: 0.6; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/paraiso-dark.css ================================================ /* Name: Paraíso (Dark) Author: Jan T. Sott Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) Inspired by the art of Rubens LP (http://www.rubenslp.com.br) */ .cm-s-paraiso-dark.CodeMirror { background: #2f1e2e; color: #b9b6b0; } .cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f; } .cm-s-paraiso-dark .CodeMirror-line::selection, .cm-s-paraiso-dark .CodeMirror-line > span::selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99); } .cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99); } .cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e; border-right: 0px; } .cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; } .cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; } .cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71; } .cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687; } .cm-s-paraiso-dark span.cm-comment { color: #e96ba8; } .cm-s-paraiso-dark span.cm-atom { color: #815ba4; } .cm-s-paraiso-dark span.cm-number { color: #815ba4; } .cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute { color: #48b685; } .cm-s-paraiso-dark span.cm-keyword { color: #ef6155; } .cm-s-paraiso-dark span.cm-string { color: #fec418; } .cm-s-paraiso-dark span.cm-variable { color: #48b685; } .cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef; } .cm-s-paraiso-dark span.cm-def { color: #f99b15; } .cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0; } .cm-s-paraiso-dark span.cm-tag { color: #ef6155; } .cm-s-paraiso-dark span.cm-link { color: #815ba4; } .cm-s-paraiso-dark span.cm-error { background: #ef6155; color: #8d8687; } .cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A; } .cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/paraiso-light.css ================================================ /* Name: Paraíso (Light) Author: Jan T. Sott Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror) Inspired by the art of Rubens LP (http://www.rubenslp.com.br) */ .cm-s-paraiso-light.CodeMirror { background: #e7e9db; color: #41323f; } .cm-s-paraiso-light div.CodeMirror-selected { background: #b9b6b0; } .cm-s-paraiso-light .CodeMirror-line::selection, .cm-s-paraiso-light .CodeMirror-line > span::selection, .cm-s-paraiso-light .CodeMirror-line > span > span::selection { background: #b9b6b0; } .cm-s-paraiso-light .CodeMirror-line::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span > span::-moz-selection { background: #b9b6b0; } .cm-s-paraiso-light .CodeMirror-gutters { background: #e7e9db; border-right: 0px; } .cm-s-paraiso-light .CodeMirror-guttermarker { color: black; } .cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; } .cm-s-paraiso-light .CodeMirror-linenumber { color: #8d8687; } .cm-s-paraiso-light .CodeMirror-cursor { border-left: 1px solid #776e71; } .cm-s-paraiso-light span.cm-comment { color: #e96ba8; } .cm-s-paraiso-light span.cm-atom { color: #815ba4; } .cm-s-paraiso-light span.cm-number { color: #815ba4; } .cm-s-paraiso-light span.cm-property, .cm-s-paraiso-light span.cm-attribute { color: #48b685; } .cm-s-paraiso-light span.cm-keyword { color: #ef6155; } .cm-s-paraiso-light span.cm-string { color: #fec418; } .cm-s-paraiso-light span.cm-variable { color: #48b685; } .cm-s-paraiso-light span.cm-variable-2 { color: #06b6ef; } .cm-s-paraiso-light span.cm-def { color: #f99b15; } .cm-s-paraiso-light span.cm-bracket { color: #41323f; } .cm-s-paraiso-light span.cm-tag { color: #ef6155; } .cm-s-paraiso-light span.cm-link { color: #815ba4; } .cm-s-paraiso-light span.cm-error { background: #ef6155; color: #776e71; } .cm-s-paraiso-light .CodeMirror-activeline-background { background: #CFD1C4; } .cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/pastel-on-dark.css ================================================ /** * Pastel On Dark theme ported from ACE editor * @license MIT * @copyright AtomicPages LLC 2014 * @author Dennis Thompson, AtomicPages LLC * @version 1.1 * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme */ .cm-s-pastel-on-dark.CodeMirror { background: #2c2827; color: #8F938F; line-height: 1.5; } .cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); } .cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); } .cm-s-pastel-on-dark .CodeMirror-line::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(221,240,255,0.2); } .cm-s-pastel-on-dark .CodeMirror-gutters { background: #34302f; border-right: 0px; padding: 0 3px; } .cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } .cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } .cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } .cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } .cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } .cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } .cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } .cm-s-pastel-on-dark span.cm-property { color: #8F938F; } .cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } .cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } .cm-s-pastel-on-dark span.cm-string { color: #66A968; } .cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } .cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } .cm-s-pastel-on-dark span.cm-variable-3, .cm-s-pastel-on-dark span.cm-type { color: #DE8E30; } .cm-s-pastel-on-dark span.cm-def { color: #757aD8; } .cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } .cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } .cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } .cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } .cm-s-pastel-on-dark span.cm-error { background: #757aD8; color: #f8f8f0; } .cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031); } .cm-s-pastel-on-dark .CodeMirror-matchingbracket { border: 1px solid rgba(255,255,255,0.25); color: #8F938F !important; margin: -1px -1px 0 -1px; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/railscasts.css ================================================ /* Name: Railscasts Author: Ryan Bates (http://railscasts.com) CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-railscasts.CodeMirror {background: #2b2b2b; color: #f4f1ed;} .cm-s-railscasts div.CodeMirror-selected {background: #272935 !important;} .cm-s-railscasts .CodeMirror-gutters {background: #2b2b2b; border-right: 0px;} .cm-s-railscasts .CodeMirror-linenumber {color: #5a647e;} .cm-s-railscasts .CodeMirror-cursor {border-left: 1px solid #d4cfc9 !important;} .cm-s-railscasts span.cm-comment {color: #bc9458;} .cm-s-railscasts span.cm-atom {color: #b6b3eb;} .cm-s-railscasts span.cm-number {color: #b6b3eb;} .cm-s-railscasts span.cm-property, .cm-s-railscasts span.cm-attribute {color: #a5c261;} .cm-s-railscasts span.cm-keyword {color: #da4939;} .cm-s-railscasts span.cm-string {color: #ffc66d;} .cm-s-railscasts span.cm-variable {color: #a5c261;} .cm-s-railscasts span.cm-variable-2 {color: #6d9cbe;} .cm-s-railscasts span.cm-def {color: #cc7833;} .cm-s-railscasts span.cm-error {background: #da4939; color: #d4cfc9;} .cm-s-railscasts span.cm-bracket {color: #f4f1ed;} .cm-s-railscasts span.cm-tag {color: #da4939;} .cm-s-railscasts span.cm-link {color: #b6b3eb;} .cm-s-railscasts .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} .cm-s-railscasts .CodeMirror-activeline-background { background: #303040; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/rubyblue.css ================================================ .cm-s-rubyblue.CodeMirror { background: #112435; color: white; } .cm-s-rubyblue div.CodeMirror-selected { background: #38566F; } .cm-s-rubyblue .CodeMirror-line::selection, .cm-s-rubyblue .CodeMirror-line > span::selection, .cm-s-rubyblue .CodeMirror-line > span > span::selection { background: rgba(56, 86, 111, 0.99); } .cm-s-rubyblue .CodeMirror-line::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 86, 111, 0.99); } .cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } .cm-s-rubyblue .CodeMirror-guttermarker { color: white; } .cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087; } .cm-s-rubyblue .CodeMirror-linenumber { color: white; } .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } .cm-s-rubyblue span.cm-atom { color: #F4C20B; } .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } .cm-s-rubyblue span.cm-keyword { color: #F0F; } .cm-s-rubyblue span.cm-string { color: #F08047; } .cm-s-rubyblue span.cm-meta { color: #F0F; } .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def, .cm-s-rubyblue span.cm-type { color: white; } .cm-s-rubyblue span.cm-bracket { color: #F0F; } .cm-s-rubyblue span.cm-link { color: #F4C20B; } .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } .cm-s-rubyblue span.cm-error { color: #AF2018; } .cm-s-rubyblue .CodeMirror-activeline-background { background: #173047; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/seti.css ================================================ /* Name: seti Author: Michael Kaminsky (http://github.com/mkaminsky11) Original seti color scheme by Jesse Weed (https://github.com/jesseweed/seti-syntax) */ .cm-s-seti.CodeMirror { background-color: #151718 !important; color: #CFD2D1 !important; border: none; } .cm-s-seti .CodeMirror-gutters { color: #404b53; background-color: #0E1112; border: none; } .cm-s-seti .CodeMirror-cursor { border-left: solid thin #f8f8f0; } .cm-s-seti .CodeMirror-linenumber { color: #6D8A88; } .cm-s-seti.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } .cm-s-seti .CodeMirror-line::selection, .cm-s-seti .CodeMirror-line > span::selection, .cm-s-seti .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } .cm-s-seti .CodeMirror-line::-moz-selection, .cm-s-seti .CodeMirror-line > span::-moz-selection, .cm-s-seti .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } .cm-s-seti span.cm-comment { color: #41535b; } .cm-s-seti span.cm-string, .cm-s-seti span.cm-string-2 { color: #55b5db; } .cm-s-seti span.cm-number { color: #cd3f45; } .cm-s-seti span.cm-variable { color: #55b5db; } .cm-s-seti span.cm-variable-2 { color: #a074c4; } .cm-s-seti span.cm-def { color: #55b5db; } .cm-s-seti span.cm-keyword { color: #ff79c6; } .cm-s-seti span.cm-operator { color: #9fca56; } .cm-s-seti span.cm-keyword { color: #e6cd69; } .cm-s-seti span.cm-atom { color: #cd3f45; } .cm-s-seti span.cm-meta { color: #55b5db; } .cm-s-seti span.cm-tag { color: #55b5db; } .cm-s-seti span.cm-attribute { color: #9fca56; } .cm-s-seti span.cm-qualifier { color: #9fca56; } .cm-s-seti span.cm-property { color: #a074c4; } .cm-s-seti span.cm-variable-3, .cm-s-seti span.cm-type { color: #9fca56; } .cm-s-seti span.cm-builtin { color: #9fca56; } .cm-s-seti .CodeMirror-activeline-background { background: #101213; } .cm-s-seti .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/shadowfox.css ================================================ /* Name: shadowfox Author: overdodactyl (http://github.com/overdodactyl) Original shadowfox color scheme by Firefox */ .cm-s-shadowfox.CodeMirror { background: #2a2a2e; color: #b1b1b3; } .cm-s-shadowfox div.CodeMirror-selected { background: #353B48; } .cm-s-shadowfox .CodeMirror-line::selection, .cm-s-shadowfox .CodeMirror-line > span::selection, .cm-s-shadowfox .CodeMirror-line > span > span::selection { background: #353B48; } .cm-s-shadowfox .CodeMirror-line::-moz-selection, .cm-s-shadowfox .CodeMirror-line > span::-moz-selection, .cm-s-shadowfox .CodeMirror-line > span > span::-moz-selection { background: #353B48; } .cm-s-shadowfox .CodeMirror-gutters { background: #0c0c0d ; border-right: 1px solid #0c0c0d; } .cm-s-shadowfox .CodeMirror-guttermarker { color: #555; } .cm-s-shadowfox .CodeMirror-linenumber { color: #939393; } .cm-s-shadowfox .CodeMirror-cursor { border-left: 1px solid #fff; } .cm-s-shadowfox span.cm-comment { color: #939393; } .cm-s-shadowfox span.cm-atom { color: #FF7DE9; } .cm-s-shadowfox span.cm-quote { color: #FF7DE9; } .cm-s-shadowfox span.cm-builtin { color: #FF7DE9; } .cm-s-shadowfox span.cm-attribute { color: #FF7DE9; } .cm-s-shadowfox span.cm-keyword { color: #FF7DE9; } .cm-s-shadowfox span.cm-error { color: #FF7DE9; } .cm-s-shadowfox span.cm-number { color: #6B89FF; } .cm-s-shadowfox span.cm-string { color: #6B89FF; } .cm-s-shadowfox span.cm-string-2 { color: #6B89FF; } .cm-s-shadowfox span.cm-meta { color: #939393; } .cm-s-shadowfox span.cm-hr { color: #939393; } .cm-s-shadowfox span.cm-header { color: #75BFFF; } .cm-s-shadowfox span.cm-qualifier { color: #75BFFF; } .cm-s-shadowfox span.cm-variable-2 { color: #75BFFF; } .cm-s-shadowfox span.cm-property { color: #86DE74; } .cm-s-shadowfox span.cm-def { color: #75BFFF; } .cm-s-shadowfox span.cm-bracket { color: #75BFFF; } .cm-s-shadowfox span.cm-tag { color: #75BFFF; } .cm-s-shadowfox span.cm-link:visited { color: #75BFFF; } .cm-s-shadowfox span.cm-variable { color: #B98EFF; } .cm-s-shadowfox span.cm-variable-3 { color: #d7d7db; } .cm-s-shadowfox span.cm-link { color: #737373; } .cm-s-shadowfox span.cm-operator { color: #b1b1b3; } .cm-s-shadowfox span.cm-special { color: #d7d7db; } .cm-s-shadowfox .CodeMirror-activeline-background { background: rgba(185, 215, 253, .15) } .cm-s-shadowfox .CodeMirror-matchingbracket { outline: solid 1px rgba(255, 255, 255, .25); color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/solarized.css ================================================ /* Solarized theme for code-mirror http://ethanschoonover.com/solarized */ /* Solarized color palette http://ethanschoonover.com/solarized/img/solarized-palette.png */ .solarized.base03 { color: #002b36; } .solarized.base02 { color: #073642; } .solarized.base01 { color: #586e75; } .solarized.base00 { color: #657b83; } .solarized.base0 { color: #839496; } .solarized.base1 { color: #93a1a1; } .solarized.base2 { color: #eee8d5; } .solarized.base3 { color: #fdf6e3; } .solarized.solar-yellow { color: #b58900; } .solarized.solar-orange { color: #cb4b16; } .solarized.solar-red { color: #dc322f; } .solarized.solar-magenta { color: #d33682; } .solarized.solar-violet { color: #6c71c4; } .solarized.solar-blue { color: #268bd2; } .solarized.solar-cyan { color: #2aa198; } .solarized.solar-green { color: #859900; } /* Color scheme for code-mirror */ .cm-s-solarized { line-height: 1.45em; color-profile: sRGB; rendering-intent: auto; } .cm-s-solarized.cm-s-dark { color: #839496; background-color: #002b36; } .cm-s-solarized.cm-s-light { background-color: #fdf6e3; color: #657b83; } .cm-s-solarized .CodeMirror-widget { text-shadow: none; } .cm-s-solarized .cm-header { color: #586e75; } .cm-s-solarized .cm-quote { color: #93a1a1; } .cm-s-solarized .cm-keyword { color: #cb4b16; } .cm-s-solarized .cm-atom { color: #d33682; } .cm-s-solarized .cm-number { color: #d33682; } .cm-s-solarized .cm-def { color: #2aa198; } .cm-s-solarized .cm-variable { color: #839496; } .cm-s-solarized .cm-variable-2 { color: #b58900; } .cm-s-solarized .cm-variable-3, .cm-s-solarized .cm-type { color: #6c71c4; } .cm-s-solarized .cm-property { color: #2aa198; } .cm-s-solarized .cm-operator { color: #6c71c4; } .cm-s-solarized .cm-comment { color: #586e75; font-style:italic; } .cm-s-solarized .cm-string { color: #859900; } .cm-s-solarized .cm-string-2 { color: #b58900; } .cm-s-solarized .cm-meta { color: #859900; } .cm-s-solarized .cm-qualifier { color: #b58900; } .cm-s-solarized .cm-builtin { color: #d33682; } .cm-s-solarized .cm-bracket { color: #cb4b16; } .cm-s-solarized .CodeMirror-matchingbracket { color: #859900; } .cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; } .cm-s-solarized .cm-tag { color: #93a1a1; } .cm-s-solarized .cm-attribute { color: #2aa198; } .cm-s-solarized .cm-hr { color: transparent; border-top: 1px solid #586e75; display: block; } .cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; } .cm-s-solarized .cm-special { color: #6c71c4; } .cm-s-solarized .cm-em { color: #999; text-decoration: underline; text-decoration-style: dotted; } .cm-s-solarized .cm-error, .cm-s-solarized .cm-invalidchar { color: #586e75; border-bottom: 1px dotted #dc322f; } .cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642; } .cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99); } .cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99); } .cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5; } .cm-s-solarized.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5; } .cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-light .CodeMirror-line > span::-moz-selection, .cm-s-light .CodeMirror-line > span > span::-moz-selection { background: #eee8d5; } /* Editor styling */ /* Little shadow on the view-port of the buffer view */ .cm-s-solarized.CodeMirror { -moz-box-shadow: inset 7px 0 12px -6px #000; -webkit-box-shadow: inset 7px 0 12px -6px #000; box-shadow: inset 7px 0 12px -6px #000; } /* Remove gutter border */ .cm-s-solarized .CodeMirror-gutters { border-right: 0; } /* Gutter colors and line number styling based of color scheme (dark / light) */ /* Dark */ .cm-s-solarized.cm-s-dark .CodeMirror-gutters { background-color: #073642; } .cm-s-solarized.cm-s-dark .CodeMirror-linenumber { color: #586e75; } /* Light */ .cm-s-solarized.cm-s-light .CodeMirror-gutters { background-color: #eee8d5; } .cm-s-solarized.cm-s-light .CodeMirror-linenumber { color: #839496; } /* Common */ .cm-s-solarized .CodeMirror-linenumber { padding: 0 5px; } .cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; } .cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; } .cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; } .cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { color: #586e75; } /* Cursor */ .cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; } /* Fat cursor */ .cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77; } .cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77; } .cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75; } .cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75; } /* Active line */ .cm-s-solarized.cm-s-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.06); } .cm-s-solarized.cm-s-light .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.06); } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ssms.css ================================================ .cm-s-ssms span.cm-keyword { color: blue; } .cm-s-ssms span.cm-comment { color: darkgreen; } .cm-s-ssms span.cm-string { color: red; } .cm-s-ssms span.cm-def { color: black; } .cm-s-ssms span.cm-variable { color: black; } .cm-s-ssms span.cm-variable-2 { color: black; } .cm-s-ssms span.cm-atom { color: darkgray; } .cm-s-ssms .CodeMirror-linenumber { color: teal; } .cm-s-ssms .CodeMirror-activeline-background { background: #ffffff; } .cm-s-ssms span.cm-string-2 { color: #FF00FF; } .cm-s-ssms span.cm-operator, .cm-s-ssms span.cm-bracket, .cm-s-ssms span.cm-punctuation { color: darkgray; } .cm-s-ssms .CodeMirror-gutters { border-right: 3px solid #ffee62; background-color: #ffffff; } .cm-s-ssms div.CodeMirror-selected { background: #ADD6FF; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/the-matrix.css ================================================ .cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } .cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D; } .cm-s-the-matrix .CodeMirror-line::selection, .cm-s-the-matrix .CodeMirror-line > span::selection, .cm-s-the-matrix .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } .cm-s-the-matrix .CodeMirror-line::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } .cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } .cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0; } .cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white; } .cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } .cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00; } .cm-s-the-matrix span.cm-keyword { color: #008803; font-weight: bold; } .cm-s-the-matrix span.cm-atom { color: #3FF; } .cm-s-the-matrix span.cm-number { color: #FFB94F; } .cm-s-the-matrix span.cm-def { color: #99C; } .cm-s-the-matrix span.cm-variable { color: #F6C; } .cm-s-the-matrix span.cm-variable-2 { color: #C6F; } .cm-s-the-matrix span.cm-variable-3, .cm-s-the-matrix span.cm-type { color: #96F; } .cm-s-the-matrix span.cm-property { color: #62FFA0; } .cm-s-the-matrix span.cm-operator { color: #999; } .cm-s-the-matrix span.cm-comment { color: #CCCCCC; } .cm-s-the-matrix span.cm-string { color: #39C; } .cm-s-the-matrix span.cm-meta { color: #C9F; } .cm-s-the-matrix span.cm-qualifier { color: #FFF700; } .cm-s-the-matrix span.cm-builtin { color: #30a; } .cm-s-the-matrix span.cm-bracket { color: #cc7; } .cm-s-the-matrix span.cm-tag { color: #FFBD40; } .cm-s-the-matrix span.cm-attribute { color: #FFF700; } .cm-s-the-matrix span.cm-error { color: #FF0000; } .cm-s-the-matrix .CodeMirror-activeline-background { background: #040; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/tomorrow-night-bright.css ================================================ /* Name: Tomorrow Night - Bright Author: Chris Kempson Port done by Gerard Braad */ .cm-s-tomorrow-night-bright.CodeMirror { background: #000000; color: #eaeaea; } .cm-s-tomorrow-night-bright div.CodeMirror-selected { background: #424242; } .cm-s-tomorrow-night-bright .CodeMirror-gutters { background: #000000; border-right: 0px; } .cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; } .cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; } .cm-s-tomorrow-night-bright .CodeMirror-linenumber { color: #424242; } .cm-s-tomorrow-night-bright .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } .cm-s-tomorrow-night-bright span.cm-comment { color: #d27b53; } .cm-s-tomorrow-night-bright span.cm-atom { color: #a16a94; } .cm-s-tomorrow-night-bright span.cm-number { color: #a16a94; } .cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute { color: #99cc99; } .cm-s-tomorrow-night-bright span.cm-keyword { color: #d54e53; } .cm-s-tomorrow-night-bright span.cm-string { color: #e7c547; } .cm-s-tomorrow-night-bright span.cm-variable { color: #b9ca4a; } .cm-s-tomorrow-night-bright span.cm-variable-2 { color: #7aa6da; } .cm-s-tomorrow-night-bright span.cm-def { color: #e78c45; } .cm-s-tomorrow-night-bright span.cm-bracket { color: #eaeaea; } .cm-s-tomorrow-night-bright span.cm-tag { color: #d54e53; } .cm-s-tomorrow-night-bright span.cm-link { color: #a16a94; } .cm-s-tomorrow-night-bright span.cm-error { background: #d54e53; color: #6A6A6A; } .cm-s-tomorrow-night-bright .CodeMirror-activeline-background { background: #2a2a2a; } .cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/tomorrow-night-eighties.css ================================================ /* Name: Tomorrow Night - Eighties Author: Chris Kempson CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ .cm-s-tomorrow-night-eighties.CodeMirror { background: #000000; color: #CCCCCC; } .cm-s-tomorrow-night-eighties div.CodeMirror-selected { background: #2D2D2D; } .cm-s-tomorrow-night-eighties .CodeMirror-line::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } .cm-s-tomorrow-night-eighties .CodeMirror-line::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } .cm-s-tomorrow-night-eighties .CodeMirror-gutters { background: #000000; border-right: 0px; } .cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a; } .cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777; } .cm-s-tomorrow-night-eighties .CodeMirror-linenumber { color: #515151; } .cm-s-tomorrow-night-eighties .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } .cm-s-tomorrow-night-eighties span.cm-comment { color: #d27b53; } .cm-s-tomorrow-night-eighties span.cm-atom { color: #a16a94; } .cm-s-tomorrow-night-eighties span.cm-number { color: #a16a94; } .cm-s-tomorrow-night-eighties span.cm-property, .cm-s-tomorrow-night-eighties span.cm-attribute { color: #99cc99; } .cm-s-tomorrow-night-eighties span.cm-keyword { color: #f2777a; } .cm-s-tomorrow-night-eighties span.cm-string { color: #ffcc66; } .cm-s-tomorrow-night-eighties span.cm-variable { color: #99cc99; } .cm-s-tomorrow-night-eighties span.cm-variable-2 { color: #6699cc; } .cm-s-tomorrow-night-eighties span.cm-def { color: #f99157; } .cm-s-tomorrow-night-eighties span.cm-bracket { color: #CCCCCC; } .cm-s-tomorrow-night-eighties span.cm-tag { color: #f2777a; } .cm-s-tomorrow-night-eighties span.cm-link { color: #a16a94; } .cm-s-tomorrow-night-eighties span.cm-error { background: #f2777a; color: #6A6A6A; } .cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { background: #343600; } .cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/ttcn.css ================================================ .cm-s-ttcn .cm-quote { color: #090; } .cm-s-ttcn .cm-negative { color: #d44; } .cm-s-ttcn .cm-positive { color: #292; } .cm-s-ttcn .cm-header, .cm-strong { font-weight: bold; } .cm-s-ttcn .cm-em { font-style: italic; } .cm-s-ttcn .cm-link { text-decoration: underline; } .cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } .cm-s-ttcn .cm-header { color: #00f; font-weight: bold; } .cm-s-ttcn .cm-atom { color: #219; } .cm-s-ttcn .cm-attribute { color: #00c; } .cm-s-ttcn .cm-bracket { color: #997; } .cm-s-ttcn .cm-comment { color: #333333; } .cm-s-ttcn .cm-def { color: #00f; } .cm-s-ttcn .cm-em { font-style: italic; } .cm-s-ttcn .cm-error { color: #f00; } .cm-s-ttcn .cm-hr { color: #999; } .cm-s-ttcn .cm-invalidchar { color: #f00; } .cm-s-ttcn .cm-keyword { font-weight:bold; } .cm-s-ttcn .cm-link { color: #00c; text-decoration: underline; } .cm-s-ttcn .cm-meta { color: #555; } .cm-s-ttcn .cm-negative { color: #d44; } .cm-s-ttcn .cm-positive { color: #292; } .cm-s-ttcn .cm-qualifier { color: #555; } .cm-s-ttcn .cm-strikethrough { text-decoration: line-through; } .cm-s-ttcn .cm-string { color: #006400; } .cm-s-ttcn .cm-string-2 { color: #f50; } .cm-s-ttcn .cm-strong { font-weight: bold; } .cm-s-ttcn .cm-tag { color: #170; } .cm-s-ttcn .cm-variable { color: #8B2252; } .cm-s-ttcn .cm-variable-2 { color: #05a; } .cm-s-ttcn .cm-variable-3, .cm-s-ttcn .cm-type { color: #085; } .cm-s-ttcn .cm-invalidchar { color: #f00; } /* ASN */ .cm-s-ttcn .cm-accessTypes, .cm-s-ttcn .cm-compareTypes { color: #27408B; } .cm-s-ttcn .cm-cmipVerbs { color: #8B2252; } .cm-s-ttcn .cm-modifier { color:#D2691E; } .cm-s-ttcn .cm-status { color:#8B4545; } .cm-s-ttcn .cm-storage { color:#A020F0; } .cm-s-ttcn .cm-tags { color:#006400; } /* CFG */ .cm-s-ttcn .cm-externalCommands { color: #8B4545; font-weight:bold; } .cm-s-ttcn .cm-fileNCtrlMaskOptions, .cm-s-ttcn .cm-sectionTitle { color: #2E8B57; font-weight:bold; } /* TTCN */ .cm-s-ttcn .cm-booleanConsts, .cm-s-ttcn .cm-otherConsts, .cm-s-ttcn .cm-verdictConsts { color: #006400; } .cm-s-ttcn .cm-configOps, .cm-s-ttcn .cm-functionOps, .cm-s-ttcn .cm-portOps, .cm-s-ttcn .cm-sutOps, .cm-s-ttcn .cm-timerOps, .cm-s-ttcn .cm-verdictOps { color: #0000FF; } .cm-s-ttcn .cm-preprocessor, .cm-s-ttcn .cm-templateMatch, .cm-s-ttcn .cm-ttcn3Macros { color: #27408B; } .cm-s-ttcn .cm-types { color: #A52A2A; font-weight:bold; } .cm-s-ttcn .cm-visibilityModifiers { font-weight:bold; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/twilight.css ================================================ .cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ .cm-s-twilight div.CodeMirror-selected { background: #323232; } /**/ .cm-s-twilight .CodeMirror-line::selection, .cm-s-twilight .CodeMirror-line > span::selection, .cm-s-twilight .CodeMirror-line > span > span::selection { background: rgba(50, 50, 50, 0.99); } .cm-s-twilight .CodeMirror-line::-moz-selection, .cm-s-twilight .CodeMirror-line > span::-moz-selection, .cm-s-twilight .CodeMirror-line > span > span::-moz-selection { background: rgba(50, 50, 50, 0.99); } .cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } .cm-s-twilight .CodeMirror-guttermarker { color: white; } .cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } .cm-s-twilight .CodeMirror-linenumber { color: #aaa; } .cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-twilight .cm-keyword { color: #f9ee98; } /**/ .cm-s-twilight .cm-atom { color: #FC0; } .cm-s-twilight .cm-number { color: #ca7841; } /**/ .cm-s-twilight .cm-def { color: #8DA6CE; } .cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/ .cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def, .cm-s-twilight span.cm-type { color: #607392; } /**/ .cm-s-twilight .cm-operator { color: #cda869; } /**/ .cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/ .cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/ .cm-s-twilight .cm-string-2 { color:#bd6b18; } /*?*/ .cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/ .cm-s-twilight .cm-builtin { color: #cda869; } /*?*/ .cm-s-twilight .cm-tag { color: #997643; } /**/ .cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/ .cm-s-twilight .cm-header { color: #FF6400; } .cm-s-twilight .cm-hr { color: #AEAEAE; } .cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/ .cm-s-twilight .cm-error { border-bottom: 1px solid red; } .cm-s-twilight .CodeMirror-activeline-background { background: #27282E; } .cm-s-twilight .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/vibrant-ink.css ================================================ /* Taken from the popular Visual Studio Vibrant Ink Schema */ .cm-s-vibrant-ink.CodeMirror { background: black; color: white; } .cm-s-vibrant-ink div.CodeMirror-selected { background: #35493c; } .cm-s-vibrant-ink .CodeMirror-line::selection, .cm-s-vibrant-ink .CodeMirror-line > span::selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::selection { background: rgba(53, 73, 60, 0.99); } .cm-s-vibrant-ink .CodeMirror-line::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::-moz-selection { background: rgba(53, 73, 60, 0.99); } .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } .cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } .cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-vibrant-ink .cm-keyword { color: #CC7832; } .cm-s-vibrant-ink .cm-atom { color: #FC0; } .cm-s-vibrant-ink .cm-number { color: #FFEE98; } .cm-s-vibrant-ink .cm-def { color: #8DA6CE; } .cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D; } .cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def, .cm-s-vibrant span.cm-type { color: #FFC66D; } .cm-s-vibrant-ink .cm-operator { color: #888; } .cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } .cm-s-vibrant-ink .cm-string { color: #A5C25C; } .cm-s-vibrant-ink .cm-string-2 { color: red; } .cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } .cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } .cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } .cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } .cm-s-vibrant-ink .cm-header { color: #FF6400; } .cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } .cm-s-vibrant-ink .cm-link { color: #5656F3; } .cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } .cm-s-vibrant-ink .CodeMirror-activeline-background { background: #27282E; } .cm-s-vibrant-ink .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/xq-dark.css ================================================ /* Copyright (C) 2011 by MarkLogic Corporation Author: Mike Brevoort Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-xq-dark div.CodeMirror-selected { background: #27007A; } .cm-s-xq-dark .CodeMirror-line::selection, .cm-s-xq-dark .CodeMirror-line > span::selection, .cm-s-xq-dark .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); } .cm-s-xq-dark .CodeMirror-line::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span::-moz-selection, .cm-s-xq-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); } .cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } .cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40; } .cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8; } .cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } .cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-xq-dark span.cm-keyword { color: #FFBD40; } .cm-s-xq-dark span.cm-atom { color: #6C8CD5; } .cm-s-xq-dark span.cm-number { color: #164; } .cm-s-xq-dark span.cm-def { color: #FFF; text-decoration:underline; } .cm-s-xq-dark span.cm-variable { color: #FFF; } .cm-s-xq-dark span.cm-variable-2 { color: #EEE; } .cm-s-xq-dark span.cm-variable-3, .cm-s-xq-dark span.cm-type { color: #DDD; } .cm-s-xq-dark span.cm-property {} .cm-s-xq-dark span.cm-operator {} .cm-s-xq-dark span.cm-comment { color: gray; } .cm-s-xq-dark span.cm-string { color: #9FEE00; } .cm-s-xq-dark span.cm-meta { color: yellow; } .cm-s-xq-dark span.cm-qualifier { color: #FFF700; } .cm-s-xq-dark span.cm-builtin { color: #30a; } .cm-s-xq-dark span.cm-bracket { color: #cc7; } .cm-s-xq-dark span.cm-tag { color: #FFBD40; } .cm-s-xq-dark span.cm-attribute { color: #FFF700; } .cm-s-xq-dark span.cm-error { color: #f00; } .cm-s-xq-dark .CodeMirror-activeline-background { background: #27282E; } .cm-s-xq-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/xq-light.css ================================================ /* Copyright (C) 2011 by MarkLogic Corporation Author: Mike Brevoort Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ .cm-s-xq-light span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; } .cm-s-xq-light span.cm-atom { color: #6C8CD5; } .cm-s-xq-light span.cm-number { color: #164; } .cm-s-xq-light span.cm-def { text-decoration:underline; } .cm-s-xq-light span.cm-variable { color: black; } .cm-s-xq-light span.cm-variable-2 { color:black; } .cm-s-xq-light span.cm-variable-3, .cm-s-xq-light span.cm-type { color: black; } .cm-s-xq-light span.cm-property {} .cm-s-xq-light span.cm-operator {} .cm-s-xq-light span.cm-comment { color: #0080FF; font-style: italic; } .cm-s-xq-light span.cm-string { color: red; } .cm-s-xq-light span.cm-meta { color: yellow; } .cm-s-xq-light span.cm-qualifier { color: grey; } .cm-s-xq-light span.cm-builtin { color: #7EA656; } .cm-s-xq-light span.cm-bracket { color: #cc7; } .cm-s-xq-light span.cm-tag { color: #3F7F7F; } .cm-s-xq-light span.cm-attribute { color: #7F007F; } .cm-s-xq-light span.cm-error { color: #f00; } .cm-s-xq-light .CodeMirror-activeline-background { background: #e8f2ff; } .cm-s-xq-light .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/yeti.css ================================================ /* Name: yeti Author: Michael Kaminsky (http://github.com/mkaminsky11) Original yeti color scheme by Jesse Weed (https://github.com/jesseweed/yeti-syntax) */ .cm-s-yeti.CodeMirror { background-color: #ECEAE8 !important; color: #d1c9c0 !important; border: none; } .cm-s-yeti .CodeMirror-gutters { color: #adaba6; background-color: #E5E1DB; border: none; } .cm-s-yeti .CodeMirror-cursor { border-left: solid thin #d1c9c0; } .cm-s-yeti .CodeMirror-linenumber { color: #adaba6; } .cm-s-yeti.CodeMirror-focused div.CodeMirror-selected { background: #DCD8D2; } .cm-s-yeti .CodeMirror-line::selection, .cm-s-yeti .CodeMirror-line > span::selection, .cm-s-yeti .CodeMirror-line > span > span::selection { background: #DCD8D2; } .cm-s-yeti .CodeMirror-line::-moz-selection, .cm-s-yeti .CodeMirror-line > span::-moz-selection, .cm-s-yeti .CodeMirror-line > span > span::-moz-selection { background: #DCD8D2; } .cm-s-yeti span.cm-comment { color: #d4c8be; } .cm-s-yeti span.cm-string, .cm-s-yeti span.cm-string-2 { color: #96c0d8; } .cm-s-yeti span.cm-number { color: #a074c4; } .cm-s-yeti span.cm-variable { color: #55b5db; } .cm-s-yeti span.cm-variable-2 { color: #a074c4; } .cm-s-yeti span.cm-def { color: #55b5db; } .cm-s-yeti span.cm-operator { color: #9fb96e; } .cm-s-yeti span.cm-keyword { color: #9fb96e; } .cm-s-yeti span.cm-atom { color: #a074c4; } .cm-s-yeti span.cm-meta { color: #96c0d8; } .cm-s-yeti span.cm-tag { color: #96c0d8; } .cm-s-yeti span.cm-attribute { color: #9fb96e; } .cm-s-yeti span.cm-qualifier { color: #96c0d8; } .cm-s-yeti span.cm-property { color: #a074c4; } .cm-s-yeti span.cm-builtin { color: #a074c4; } .cm-s-yeti span.cm-variable-3, .cm-s-yeti span.cm-type { color: #96c0d8; } .cm-s-yeti .CodeMirror-activeline-background { background: #E7E4E0; } .cm-s-yeti .CodeMirror-matchingbracket { text-decoration: underline; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/yonce.css ================================================ /* Name: yoncé Author: Thomas MacLean (http://github.com/thomasmaclean) Original yoncé color scheme by Mina Markham (https://github.com/minamarkham) */ .cm-s-yonce.CodeMirror { background: #1C1C1C; color: #d4d4d4; } /**/ .cm-s-yonce div.CodeMirror-selected { background: rgba(252, 69, 133, 0.478); } /**/ .cm-s-yonce .CodeMirror-selectedtext, .cm-s-yonce .CodeMirror-selected, .cm-s-yonce .CodeMirror-line::selection, .cm-s-yonce .CodeMirror-line > span::selection, .cm-s-yonce .CodeMirror-line > span > span::selection, .cm-s-yonce .CodeMirror-line::-moz-selection, .cm-s-yonce .CodeMirror-line > span::-moz-selection, .cm-s-yonce .CodeMirror-line > span > span::-moz-selection { background: rgba(252, 67, 132, 0.47); } .cm-s-yonce.CodeMirror pre { padding-left: 0px; } .cm-s-yonce .CodeMirror-gutters {background: #1C1C1C; border-right: 0px;} .cm-s-yonce .CodeMirror-linenumber {color: #777777; padding-right: 10px; } .cm-s-yonce .CodeMirror-activeline .CodeMirror-linenumber.CodeMirror-gutter-elt { background: #1C1C1C; color: #fc4384; } .cm-s-yonce .CodeMirror-linenumber { color: #777; } .cm-s-yonce .CodeMirror-cursor { border-left: 2px solid #FC4384; } .cm-s-yonce .cm-searching { background: rgba(243, 155, 53, .3) !important; outline: 1px solid #F39B35; } .cm-s-yonce .cm-searching.CodeMirror-selectedtext { background: rgba(243, 155, 53, .7) !important; color: white; } .cm-s-yonce .cm-keyword { color: #00A7AA; } /**/ .cm-s-yonce .cm-atom { color: #F39B35; } .cm-s-yonce .cm-number, .cm-s-yonce span.cm-type { color: #A06FCA; } /**/ .cm-s-yonce .cm-def { color: #98E342; } .cm-s-yonce .cm-property, .cm-s-yonce span.cm-variable { color: #D4D4D4; font-style: italic; } .cm-s-yonce span.cm-variable-2 { color: #da7dae; font-style: italic; } .cm-s-yonce span.cm-variable-3 { color: #A06FCA; } .cm-s-yonce .cm-type.cm-def { color: #FC4384; font-style: normal; text-decoration: underline; } .cm-s-yonce .cm-property.cm-def { color: #FC4384; font-style: normal; } .cm-s-yonce .cm-callee { color: #FC4384; font-style: normal; } .cm-s-yonce .cm-operator { color: #FC4384; } /**/ .cm-s-yonce .cm-qualifier, .cm-s-yonce .cm-tag { color: #FC4384; } .cm-s-yonce .cm-tag.cm-bracket { color: #D4D4D4; } .cm-s-yonce .cm-attribute { color: #A06FCA; } .cm-s-yonce .cm-comment { color:#696d70; font-style:italic; font-weight:normal; } /**/ .cm-s-yonce .cm-comment.cm-tag { color: #FC4384 } .cm-s-yonce .cm-comment.cm-attribute { color: #D4D4D4; } .cm-s-yonce .cm-string { color:#E6DB74; } /**/ .cm-s-yonce .cm-string-2 { color:#F39B35; } /*?*/ .cm-s-yonce .cm-meta { color: #D4D4D4; background: inherit; } .cm-s-yonce .cm-builtin { color: #FC4384; } /*?*/ .cm-s-yonce .cm-header { color: #da7dae; } .cm-s-yonce .cm-hr { color: #98E342; } .cm-s-yonce .cm-link { color:#696d70; font-style:italic; text-decoration:none; } /**/ .cm-s-yonce .cm-error { border-bottom: 1px solid #C42412; } .cm-s-yonce .CodeMirror-activeline-background { background: #272727; } .cm-s-yonce .CodeMirror-matchingbracket { outline:1px solid grey; color:#D4D4D4 !important; } ================================================ FILE: public/assets/lib/vendor/codemirror/theme/zenburn.css ================================================ /** * " * Using Zenburn color palette from the Emacs Zenburn Theme * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el * * Also using parts of https://github.com/xavi/coderay-lighttable-theme * " * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css */ .cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } .cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } .cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white; } .cm-s-zenburn.CodeMirror { background-color: #3f3f3f; color: #dcdccc; } .cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } .cm-s-zenburn span.cm-comment { color: #7f9f7f; } .cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } .cm-s-zenburn span.cm-atom { color: #bfebbf; } .cm-s-zenburn span.cm-def { color: #dcdccc; } .cm-s-zenburn span.cm-variable { color: #dfaf8f; } .cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } .cm-s-zenburn span.cm-string { color: #cc9393; } .cm-s-zenburn span.cm-string-2 { color: #cc9393; } .cm-s-zenburn span.cm-number { color: #dcdccc; } .cm-s-zenburn span.cm-tag { color: #93e0e3; } .cm-s-zenburn span.cm-property { color: #dfaf8f; } .cm-s-zenburn span.cm-attribute { color: #dfaf8f; } .cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } .cm-s-zenburn span.cm-meta { color: #f0dfaf; } .cm-s-zenburn span.cm-header { color: #f0efd0; } .cm-s-zenburn span.cm-operator { color: #f0efd0; } .cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } .cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } .cm-s-zenburn .CodeMirror-activeline { background: #000000; } .cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } .cm-s-zenburn div.CodeMirror-selected { background: #545454; } .cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f; } ================================================ FILE: public/assets/lib/vendor/exif-js.js ================================================ /** * Minified by jsDelivr using UglifyJS v3.3.25. * Original file: /npm/exif-js@2.3.0/exif.js * * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files */ (function(){var d=!1,l=function(e){return e instanceof l?e:this instanceof l?void(this.EXIFwrapped=e):new l(e)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=l),exports.EXIF=l):this.EXIF=l;var u=l.Tags={36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubsecTime",37521:"SubsecTimeOriginal",37522:"SubsecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"ISOSpeedRatings",34856:"OECF",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRation",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",40965:"InteroperabilityIFDPointer",42016:"ImageUniqueID"},c=l.TiffTags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright"},f=l.GPSTags={0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential"},g=l.IFD1Tags={256:"ImageWidth",257:"ImageHeight",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",273:"StripOffsets",274:"Orientation",277:"SamplesPerPixel",278:"RowsPerStrip",279:"StripByteCounts",282:"XResolution",283:"YResolution",284:"PlanarConfiguration",296:"ResolutionUnit",513:"JpegIFOffset",514:"JpegIFByteCount",529:"YCbCrCoefficients",530:"YCbCrSubSampling",531:"YCbCrPositioning",532:"ReferenceBlackWhite"},m=l.StringValues={ExposureProgram:{0:"Not defined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Not defined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},Components:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"}};function i(e){return!!e.exifdata}function r(i,o){function t(e){var t=p(e);i.exifdata=t||{};var n=function(e){var t=new DataView(e);d&&console.log("Got file of length "+e.byteLength);if(255!=t.getUint8(0)||216!=t.getUint8(1))return d&&console.log("Not a valid JPEG"),!1;var n=2,r=e.byteLength;for(;n")+8,u=(s=s.substring(s.indexOf("e.byteLength)return{};var u=P(e,t,t+l,g,r);if(u.Compression)switch(u.Compression){case 6:if(u.JpegIFOffset&&u.JpegIFByteCount){var c=t+u.JpegIFOffset,d=u.JpegIFByteCount;u.blob=new Blob([new Uint8Array(e.buffer,c,d)],{type:"image/jpeg"})}break;case 1:console.log("Thumbnail image format is TIFF, which is not implemented.");break;default:console.log("Unknown thumbnail image format '%s'",u.Compression)}else 2==u.PhotometricInterpretation&&console.log("Thumbnail image format is RGB, which is not implemented.");return u}(e,s,l,n),r}function b(e){var t={};if(1==e.nodeType){if(0 http://a.com/e/f/../g // With opts.alwaysNormalize = true (not spec compliant) // http://a.com/b/cd + /e/f/../g => http://a.com/e/g buildAbsoluteURL: function (baseURL, relativeURL, opts) { opts = opts || {}; // remove any remaining space and CRLF baseURL = baseURL.trim(); relativeURL = relativeURL.trim(); if (!relativeURL) { // 2a) If the embedded URL is entirely empty, it inherits the // entire base URL (i.e., is set equal to the base URL) // and we are done. if (!opts.alwaysNormalize) { return baseURL; } var basePartsForNormalise = URLToolkit.parseURL(baseURL); if (!basePartsForNormalise) { throw new Error('Error trying to parse base URL.'); } basePartsForNormalise.path = URLToolkit.normalizePath( basePartsForNormalise.path ); return URLToolkit.buildURLFromParts(basePartsForNormalise); } var relativeParts = URLToolkit.parseURL(relativeURL); if (!relativeParts) { throw new Error('Error trying to parse relative URL.'); } if (relativeParts.scheme) { // 2b) If the embedded URL starts with a scheme name, it is // interpreted as an absolute URL and we are done. if (!opts.alwaysNormalize) { return relativeURL; } relativeParts.path = URLToolkit.normalizePath(relativeParts.path); return URLToolkit.buildURLFromParts(relativeParts); } var baseParts = URLToolkit.parseURL(baseURL); if (!baseParts) { throw new Error('Error trying to parse base URL.'); } if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); baseParts.netLoc = pathParts[1]; baseParts.path = pathParts[2]; } if (baseParts.netLoc && !baseParts.path) { baseParts.path = '/'; } var builtParts = { // 2c) Otherwise, the embedded URL inherits the scheme of // the base URL. scheme: baseParts.scheme, netLoc: relativeParts.netLoc, path: null, params: relativeParts.params, query: relativeParts.query, fragment: relativeParts.fragment, }; if (!relativeParts.netLoc) { // 3) If the embedded URL's is non-empty, we skip to // Step 7. Otherwise, the embedded URL inherits the // (if any) of the base URL. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the // path is not relative and we skip to Step 7. if (relativeParts.path[0] !== '/') { if (!relativeParts.path) { // 5) If the embedded URL path is empty (and not preceded by a // slash), then the embedded URL inherits the base URL path builtParts.path = baseParts.path; // 5a) if the embedded URL's is non-empty, we skip to // step 7; otherwise, it inherits the of the base // URL (if any) and if (!relativeParts.params) { builtParts.params = baseParts.params; // 5b) if the embedded URL's is non-empty, we skip to // step 7; otherwise, it inherits the of the base // URL (if any) and we skip to step 7. if (!relativeParts.query) { builtParts.query = baseParts.query; } } } else { // 6) The last segment of the base URL's path (anything // following the rightmost slash "/", or the entire path if no // slash is present) is removed and the embedded URL's path is // appended in its place. var baseURLPath = baseParts.path; var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; builtParts.path = URLToolkit.normalizePath(newPath); } } } if (builtParts.path === null) { builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; } return URLToolkit.buildURLFromParts(builtParts); }, parseURL: function (url) { var parts = URL_REGEX.exec(url); if (!parts) { return null; } return { scheme: parts[1] || '', netLoc: parts[2] || '', path: parts[3] || '', params: parts[4] || '', query: parts[5] || '', fragment: parts[6] || '', }; }, normalizePath: function (path) { // The following operations are // then applied, in order, to the new path: // 6a) All occurrences of "./", where "." is a complete path // segment, are removed. // 6b) If the path ends with "." as a complete path segment, // that "." is removed. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "/../", where is a // complete path segment not equal to "..", are removed. // Removal of these path segments is performed iteratively, // removing the leftmost matching pattern on each iteration, // until no matching pattern remains. // 6d) If the path ends with "/..", where is a // complete path segment not equal to "..", that // "/.." is removed. while ( path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length ) {} return path.split('').reverse().join(''); }, buildURLFromParts: function (parts) { return ( parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment ); }, }; module.exports = URLToolkit; })(); } (urlToolkit)); var urlToolkitExports = urlToolkit.exports; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } // https://caniuse.com/mdn-javascript_builtins_number_isfinite const isFiniteNumber = Number.isFinite || function (value) { return typeof value === 'number' && isFinite(value); }; // https://caniuse.com/mdn-javascript_builtins_number_issafeinteger const isSafeInteger = Number.isSafeInteger || function (value) { return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER; }; const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; let Events = /*#__PURE__*/function (Events) { Events["MEDIA_ATTACHING"] = "hlsMediaAttaching"; Events["MEDIA_ATTACHED"] = "hlsMediaAttached"; Events["MEDIA_DETACHING"] = "hlsMediaDetaching"; Events["MEDIA_DETACHED"] = "hlsMediaDetached"; Events["BUFFER_RESET"] = "hlsBufferReset"; Events["BUFFER_CODECS"] = "hlsBufferCodecs"; Events["BUFFER_CREATED"] = "hlsBufferCreated"; Events["BUFFER_APPENDING"] = "hlsBufferAppending"; Events["BUFFER_APPENDED"] = "hlsBufferAppended"; Events["BUFFER_EOS"] = "hlsBufferEos"; Events["BUFFER_FLUSHING"] = "hlsBufferFlushing"; Events["BUFFER_FLUSHED"] = "hlsBufferFlushed"; Events["MANIFEST_LOADING"] = "hlsManifestLoading"; Events["MANIFEST_LOADED"] = "hlsManifestLoaded"; Events["MANIFEST_PARSED"] = "hlsManifestParsed"; Events["LEVEL_SWITCHING"] = "hlsLevelSwitching"; Events["LEVEL_SWITCHED"] = "hlsLevelSwitched"; Events["LEVEL_LOADING"] = "hlsLevelLoading"; Events["LEVEL_LOADED"] = "hlsLevelLoaded"; Events["LEVEL_UPDATED"] = "hlsLevelUpdated"; Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated"; Events["LEVELS_UPDATED"] = "hlsLevelsUpdated"; Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated"; Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching"; Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched"; Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading"; Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded"; Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated"; Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared"; Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch"; Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading"; Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded"; Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed"; Events["CUES_PARSED"] = "hlsCuesParsed"; Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound"; Events["INIT_PTS_FOUND"] = "hlsInitPtsFound"; Events["FRAG_LOADING"] = "hlsFragLoading"; Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted"; Events["FRAG_LOADED"] = "hlsFragLoaded"; Events["FRAG_DECRYPTED"] = "hlsFragDecrypted"; Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment"; Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata"; Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata"; Events["FRAG_PARSED"] = "hlsFragParsed"; Events["FRAG_BUFFERED"] = "hlsFragBuffered"; Events["FRAG_CHANGED"] = "hlsFragChanged"; Events["FPS_DROP"] = "hlsFpsDrop"; Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping"; Events["ERROR"] = "hlsError"; Events["DESTROYING"] = "hlsDestroying"; Events["KEY_LOADING"] = "hlsKeyLoading"; Events["KEY_LOADED"] = "hlsKeyLoaded"; Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached"; Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached"; return Events; }({}); /** * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API. */ let ErrorTypes = /*#__PURE__*/function (ErrorTypes) { ErrorTypes["NETWORK_ERROR"] = "networkError"; ErrorTypes["MEDIA_ERROR"] = "mediaError"; ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError"; ErrorTypes["MUX_ERROR"] = "muxError"; ErrorTypes["OTHER_ERROR"] = "otherError"; return ErrorTypes; }({}); let ErrorDetails = /*#__PURE__*/function (ErrorDetails) { ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys"; ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess"; ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession"; ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense"; ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed"; ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed"; ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted"; ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError"; ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError"; ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut"; ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError"; ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError"; ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError"; ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError"; ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut"; ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError"; ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError"; ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError"; ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut"; ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError"; ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut"; ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError"; ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut"; ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError"; ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError"; ErrorDetails["FRAG_GAP"] = "fragGap"; ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError"; ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError"; ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut"; ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError"; ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError"; ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError"; ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError"; ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError"; ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError"; ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole"; ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall"; ErrorDetails["INTERNAL_EXCEPTION"] = "internalException"; ErrorDetails["INTERNAL_ABORTED"] = "aborted"; ErrorDetails["UNKNOWN"] = "unknown"; return ErrorDetails; }({}); const noop = function noop() {}; const fakeLogger = { trace: noop, debug: noop, log: noop, warn: noop, info: noop, error: noop }; let exportedLogger = fakeLogger; // let lastCallTime; // function formatMsgWithTimeInfo(type, msg) { // const now = Date.now(); // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0'; // lastCallTime = now; // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )'; // return msg; // } function consolePrintFn(type) { const func = self.console[type]; if (func) { return func.bind(self.console, `[${type}] >`); } return noop; } function exportLoggerFunctions(debugConfig, ...functions) { functions.forEach(function (type) { exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type); }); } function enableLogs(debugConfig, id) { // check that console is available if (self.console && debugConfig === true || typeof debugConfig === 'object') { exportLoggerFunctions(debugConfig, // Remove out from list here to hard-disable a log-level // 'trace', 'debug', 'log', 'info', 'warn', 'error'); // Some browsers don't allow to use bind on console object anyway // fallback to default if needed try { exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.4.13"}`); } catch (e) { exportedLogger = fakeLogger; } } else { exportedLogger = fakeLogger; } } const logger = exportedLogger; const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g; // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js class AttrList { constructor(attrs) { if (typeof attrs === 'string') { attrs = AttrList.parseAttrList(attrs); } for (const attr in attrs) { if (attrs.hasOwnProperty(attr)) { if (attr.substring(0, 2) === 'X-') { this.clientAttrs = this.clientAttrs || []; this.clientAttrs.push(attr); } this[attr] = attrs[attr]; } } } decimalInteger(attrName) { const intValue = parseInt(this[attrName], 10); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } hexadecimalInteger(attrName) { if (this[attrName]) { let stringValue = (this[attrName] || '0x').slice(2); stringValue = (stringValue.length & 1 ? '0' : '') + stringValue; const value = new Uint8Array(stringValue.length / 2); for (let i = 0; i < stringValue.length / 2; i++) { value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16); } return value; } else { return null; } } hexadecimalIntegerAsNumber(attrName) { const intValue = parseInt(this[attrName], 16); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } decimalFloatingPoint(attrName) { return parseFloat(this[attrName]); } optionalFloat(attrName, defaultValue) { const value = this[attrName]; return value ? parseFloat(value) : defaultValue; } enumeratedString(attrName) { return this[attrName]; } bool(attrName) { return this[attrName] === 'YES'; } decimalResolution(attrName) { const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]); if (res === null) { return undefined; } return { width: parseInt(res[1], 10), height: parseInt(res[2], 10) }; } static parseAttrList(input) { let match; const attrs = {}; const quote = '"'; ATTR_LIST_REGEX.lastIndex = 0; while ((match = ATTR_LIST_REGEX.exec(input)) !== null) { let value = match[2]; if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) { value = value.slice(1, -1); } const name = match[1].trim(); attrs[name] = value; } return attrs; } } // Avoid exporting const enum so that these values can be inlined function isDateRangeCueAttribute(attrName) { return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT"; } function isSCTE35Attribute(attrName) { return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN"; } class DateRange { constructor(dateRangeAttr, dateRangeWithSameId) { this.attr = void 0; this._startDate = void 0; this._endDate = void 0; this._badValueForSameId = void 0; if (dateRangeWithSameId) { const previousAttr = dateRangeWithSameId.attr; for (const key in previousAttr) { if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) { logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`); this._badValueForSameId = key; break; } } // Merge DateRange tags with the same ID dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr); } this.attr = dateRangeAttr; this._startDate = new Date(dateRangeAttr["START-DATE"]); if ("END-DATE" in this.attr) { const endDate = new Date(this.attr["END-DATE"]); if (isFiniteNumber(endDate.getTime())) { this._endDate = endDate; } } } get id() { return this.attr.ID; } get class() { return this.attr.CLASS; } get startDate() { return this._startDate; } get endDate() { if (this._endDate) { return this._endDate; } const duration = this.duration; if (duration !== null) { return new Date(this._startDate.getTime() + duration * 1000); } return null; } get duration() { if ("DURATION" in this.attr) { const duration = this.attr.decimalFloatingPoint("DURATION"); if (isFiniteNumber(duration)) { return duration; } } else if (this._endDate) { return (this._endDate.getTime() - this._startDate.getTime()) / 1000; } return null; } get plannedDuration() { if ("PLANNED-DURATION" in this.attr) { return this.attr.decimalFloatingPoint("PLANNED-DURATION"); } return null; } get endOnNext() { return this.attr.bool("END-ON-NEXT"); } get isValid() { return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class); } } class LoadStats { constructor() { this.aborted = false; this.loaded = 0; this.retry = 0; this.total = 0; this.chunkCount = 0; this.bwEstimate = 0; this.loading = { start: 0, first: 0, end: 0 }; this.parsing = { start: 0, end: 0 }; this.buffering = { start: 0, first: 0, end: 0 }; } } var ElementaryStreamTypes = { AUDIO: "audio", VIDEO: "video", AUDIOVIDEO: "audiovideo" }; class BaseSegment { // baseurl is the URL to the playlist // relurl is the portion of the URL that comes from inside the playlist. // Holds the types of data this fragment supports constructor(baseurl) { this._byteRange = null; this._url = null; this.baseurl = void 0; this.relurl = void 0; this.elementaryStreams = { [ElementaryStreamTypes.AUDIO]: null, [ElementaryStreamTypes.VIDEO]: null, [ElementaryStreamTypes.AUDIOVIDEO]: null }; this.baseurl = baseurl; } // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array setByteRange(value, previous) { const params = value.split('@', 2); const byteRange = []; if (params.length === 1) { byteRange[0] = previous ? previous.byteRangeEndOffset : 0; } else { byteRange[0] = parseInt(params[1]); } byteRange[1] = parseInt(params[0]) + byteRange[0]; this._byteRange = byteRange; } get byteRange() { if (!this._byteRange) { return []; } return this._byteRange; } get byteRangeStartOffset() { return this.byteRange[0]; } get byteRangeEndOffset() { return this.byteRange[1]; } get url() { if (!this._url && this.baseurl && this.relurl) { this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true }); } return this._url || ''; } set url(value) { this._url = value; } } /** * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}. */ class Fragment extends BaseSegment { // EXTINF has to be present for a m3u8 to be considered valid // sn notates the sequence number for a segment, and if set to a string can be 'initSegment' // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption // core difference from the private field _decryptdata is the lack of the initialized IV // _decryptdata will set the IV for this segment based on the segment number in the fragment // A string representing the fragment type // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading // The level/track index to which the fragment belongs // The continuity counter of the fragment // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete. // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete. // The start time of the fragment, as listed in the manifest. Updated after transmux complete. // Set by `updateFragPTSDTS` in level-helper // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. // Load/parse timing information // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered // #EXTINF segment title // The Media Initialization Section for this segment // Fragment is the last fragment in the media playlist // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded constructor(type, baseurl) { super(baseurl); this._decryptdata = null; this.rawProgramDateTime = null; this.programDateTime = null; this.tagList = []; this.duration = 0; this.sn = 0; this.levelkeys = void 0; this.type = void 0; this.loader = null; this.keyLoader = null; this.level = -1; this.cc = 0; this.startPTS = void 0; this.endPTS = void 0; this.startDTS = void 0; this.endDTS = void 0; this.start = 0; this.deltaPTS = void 0; this.maxStartPTS = void 0; this.minEndPTS = void 0; this.stats = new LoadStats(); this.urlId = 0; this.data = void 0; this.bitrateTest = false; this.title = null; this.initSegment = null; this.endList = void 0; this.gap = void 0; this.type = type; } get decryptdata() { const { levelkeys } = this; if (!levelkeys && !this._decryptdata) { return null; } if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) { const key = this.levelkeys.identity; if (key) { this._decryptdata = key.getDecryptData(this.sn); } else { const keyFormats = Object.keys(this.levelkeys); if (keyFormats.length === 1) { return this._decryptdata = this.levelkeys[keyFormats[0]].getDecryptData(this.sn); } } } return this._decryptdata; } get end() { return this.start + this.duration; } get endProgramDateTime() { if (this.programDateTime === null) { return null; } if (!isFiniteNumber(this.programDateTime)) { return null; } const duration = !isFiniteNumber(this.duration) ? 0 : this.duration; return this.programDateTime + duration * 1000; } get encrypted() { var _this$_decryptdata; // At the m3u8-parser level we need to add support for manifest signalled keyformats // when we want the fragment to start reporting that it is encrypted. // Currently, keyFormat will only be set for identity keys if ((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.encrypted) { return true; } else if (this.levelkeys) { const keyFormats = Object.keys(this.levelkeys); const len = keyFormats.length; if (len > 1 || len === 1 && this.levelkeys[keyFormats[0]].encrypted) { return true; } } return false; } setKeyFormat(keyFormat) { if (this.levelkeys) { const key = this.levelkeys[keyFormat]; if (key && !this._decryptdata) { this._decryptdata = key.getDecryptData(this.sn); } } } abortRequests() { var _this$loader, _this$keyLoader; (_this$loader = this.loader) == null ? void 0 : _this$loader.abort(); (_this$keyLoader = this.keyLoader) == null ? void 0 : _this$keyLoader.abort(); } setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) { const { elementaryStreams } = this; const info = elementaryStreams[type]; if (!info) { elementaryStreams[type] = { startPTS, endPTS, startDTS, endDTS, partial }; return; } info.startPTS = Math.min(info.startPTS, startPTS); info.endPTS = Math.max(info.endPTS, endPTS); info.startDTS = Math.min(info.startDTS, startDTS); info.endDTS = Math.max(info.endDTS, endDTS); } clearElementaryStreamInfo() { const { elementaryStreams } = this; elementaryStreams[ElementaryStreamTypes.AUDIO] = null; elementaryStreams[ElementaryStreamTypes.VIDEO] = null; elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null; } } /** * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}. */ class Part extends BaseSegment { constructor(partAttrs, frag, baseurl, index, previous) { super(baseurl); this.fragOffset = 0; this.duration = 0; this.gap = false; this.independent = false; this.relurl = void 0; this.fragment = void 0; this.index = void 0; this.stats = new LoadStats(); this.duration = partAttrs.decimalFloatingPoint('DURATION'); this.gap = partAttrs.bool('GAP'); this.independent = partAttrs.bool('INDEPENDENT'); this.relurl = partAttrs.enumeratedString('URI'); this.fragment = frag; this.index = index; const byteRange = partAttrs.enumeratedString('BYTERANGE'); if (byteRange) { this.setByteRange(byteRange, previous); } if (previous) { this.fragOffset = previous.fragOffset + previous.duration; } } get start() { return this.fragment.start + this.fragOffset; } get end() { return this.start + this.duration; } get loaded() { const { elementaryStreams } = this; return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo); } } const DEFAULT_TARGET_DURATION = 10; /** * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}. */ class LevelDetails { // Manifest reload synchronization constructor(baseUrl) { this.PTSKnown = false; this.alignedSliding = false; this.averagetargetduration = void 0; this.endCC = 0; this.endSN = 0; this.fragments = void 0; this.fragmentHint = void 0; this.partList = null; this.dateRanges = void 0; this.live = true; this.ageHeader = 0; this.advancedDateTime = void 0; this.updated = true; this.advanced = true; this.availabilityDelay = void 0; this.misses = 0; this.startCC = 0; this.startSN = 0; this.startTimeOffset = null; this.targetduration = 0; this.totalduration = 0; this.type = null; this.url = void 0; this.m3u8 = ''; this.version = null; this.canBlockReload = false; this.canSkipUntil = 0; this.canSkipDateRanges = false; this.skippedSegments = 0; this.recentlyRemovedDateranges = void 0; this.partHoldBack = 0; this.holdBack = 0; this.partTarget = 0; this.preloadHint = void 0; this.renditionReports = void 0; this.tuneInGoal = 0; this.deltaUpdateFailed = void 0; this.driftStartTime = 0; this.driftEndTime = 0; this.driftStart = 0; this.driftEnd = 0; this.encryptedFragments = void 0; this.playlistParsingError = null; this.variableList = null; this.hasVariableRefs = false; this.fragments = []; this.encryptedFragments = []; this.dateRanges = {}; this.url = baseUrl; } reloaded(previous) { if (!previous) { this.advanced = true; this.updated = true; return; } const partSnDiff = this.lastPartSn - previous.lastPartSn; const partIndexDiff = this.lastPartIndex - previous.lastPartIndex; this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live; this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0; if (this.updated || this.advanced) { this.misses = Math.floor(previous.misses * 0.6); } else { this.misses = previous.misses + 1; } this.availabilityDelay = previous.availabilityDelay; } get hasProgramDateTime() { if (this.fragments.length) { return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime); } return false; } get levelTargetDuration() { return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION; } get drift() { const runTime = this.driftEndTime - this.driftStartTime; if (runTime > 0) { const runDuration = this.driftEnd - this.driftStart; return runDuration * 1000 / runTime; } return 1; } get edge() { return this.partEnd || this.fragmentEnd; } get partEnd() { var _this$partList; if ((_this$partList = this.partList) != null && _this$partList.length) { return this.partList[this.partList.length - 1].end; } return this.fragmentEnd; } get fragmentEnd() { var _this$fragments; if ((_this$fragments = this.fragments) != null && _this$fragments.length) { return this.fragments[this.fragments.length - 1].end; } return 0; } get age() { if (this.advancedDateTime) { return Math.max(Date.now() - this.advancedDateTime, 0) / 1000; } return 0; } get lastPartIndex() { var _this$partList2; if ((_this$partList2 = this.partList) != null && _this$partList2.length) { return this.partList[this.partList.length - 1].index; } return -1; } get lastPartSn() { var _this$partList3; if ((_this$partList3 = this.partList) != null && _this$partList3.length) { return this.partList[this.partList.length - 1].fragment.sn; } return this.endSN; } } function base64Decode(base64encodedStr) { return Uint8Array.from(atob(base64encodedStr), c => c.charCodeAt(0)); } function getKeyIdBytes(str) { const keyIdbytes = strToUtf8array(str).subarray(0, 16); const paddedkeyIdbytes = new Uint8Array(16); paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length); return paddedkeyIdbytes; } function changeEndianness(keyId) { const swap = function swap(array, from, to) { const cur = array[from]; array[from] = array[to]; array[to] = cur; }; swap(keyId, 0, 3); swap(keyId, 1, 2); swap(keyId, 4, 5); swap(keyId, 6, 7); } function convertDataUriToArrayBytes(uri) { // data:[ const colonsplit = uri.split(':'); let keydata = null; if (colonsplit[0] === 'data' && colonsplit.length === 2) { const semicolonsplit = colonsplit[1].split(';'); const commasplit = semicolonsplit[semicolonsplit.length - 1].split(','); if (commasplit.length === 2) { const isbase64 = commasplit[0] === 'base64'; const data = commasplit[1]; if (isbase64) { semicolonsplit.splice(-1, 1); // remove from processing keydata = base64Decode(data); } else { keydata = getKeyIdBytes(data); } } } return keydata; } function strToUtf8array(str) { return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0)); } /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess */ var KeySystems = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.fps", PLAYREADY: "com.microsoft.playready", WIDEVINE: "com.widevine.alpha" }; // Playlist #EXT-X-KEY KEYFORMAT values var KeySystemFormats = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.streamingkeydelivery", PLAYREADY: "com.microsoft.playready", WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" }; function keySystemFormatToKeySystemDomain(format) { switch (format) { case KeySystemFormats.FAIRPLAY: return KeySystems.FAIRPLAY; case KeySystemFormats.PLAYREADY: return KeySystems.PLAYREADY; case KeySystemFormats.WIDEVINE: return KeySystems.WIDEVINE; case KeySystemFormats.CLEARKEY: return KeySystems.CLEARKEY; } } // System IDs for which we can extract a key ID from "encrypted" event PSSH var KeySystemIds = { WIDEVINE: "edef8ba979d64acea3c827dcd51d21ed" }; function keySystemIdToKeySystemDomain(systemId) { if (systemId === KeySystemIds.WIDEVINE) { return KeySystems.WIDEVINE; // } else if (systemId === KeySystemIds.PLAYREADY) { // return KeySystems.PLAYREADY; // } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) { // return KeySystems.CLEARKEY; } } function keySystemDomainToKeySystemFormat(keySystem) { switch (keySystem) { case KeySystems.FAIRPLAY: return KeySystemFormats.FAIRPLAY; case KeySystems.PLAYREADY: return KeySystemFormats.PLAYREADY; case KeySystems.WIDEVINE: return KeySystemFormats.WIDEVINE; case KeySystems.CLEARKEY: return KeySystemFormats.CLEARKEY; } } function getKeySystemsForConfig(config) { const { drmSystems, widevineLicenseUrl } = config; const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem => !!drmSystems[keySystem]) : []; if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) { keySystemsToAttempt.push(KeySystems.WIDEVINE); } return keySystemsToAttempt; } const requestMediaKeySystemAccess = function () { if (typeof self !== 'undefined' && self.navigator && self.navigator.requestMediaKeySystemAccess) { return self.navigator.requestMediaKeySystemAccess.bind(self.navigator); } else { return null; } }(); /** * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration */ function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) { let initDataTypes; switch (keySystem) { case KeySystems.FAIRPLAY: initDataTypes = ['cenc', 'sinf']; break; case KeySystems.WIDEVINE: case KeySystems.PLAYREADY: initDataTypes = ['cenc']; break; case KeySystems.CLEARKEY: initDataTypes = ['cenc', 'keyids']; break; default: throw new Error(`Unknown key-system: ${keySystem}`); } return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions); } function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) { const baseConfig = { initDataTypes: initDataTypes, persistentState: drmSystemOptions.persistentState || 'not-allowed', distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'not-allowed', sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'], audioCapabilities: audioCodecs.map(codec => ({ contentType: `audio/mp4; codecs="${codec}"`, robustness: drmSystemOptions.audioRobustness || '', encryptionScheme: drmSystemOptions.audioEncryptionScheme || null })), videoCapabilities: videoCodecs.map(codec => ({ contentType: `video/mp4; codecs="${codec}"`, robustness: drmSystemOptions.videoRobustness || '', encryptionScheme: drmSystemOptions.videoEncryptionScheme || null })) }; return [baseConfig]; } function sliceUint8(array, start, end) { // @ts-expect-error This polyfills IE11 usage of Uint8Array slice. // It always exists in the TypeScript definition so fails, but it fails at runtime on IE11. return Uint8Array.prototype.slice ? array.slice(start, end) : new Uint8Array(Array.prototype.slice.call(array, start, end)); } // breaking up those two types in order to clarify what is happening in the decoding path. /** * Returns true if an ID3 header can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isHeader$2 = (data, offset) => { /* * http://id3.org/id3v2.3.0 * [0] = 'I' * [1] = 'D' * [2] = '3' * [3,4] = {Version} * [5] = {Flags} * [6-9] = {ID3 Size} * * An ID3v2 tag can be detected with the following pattern: * $49 44 33 yy yy xx zz zz zz zz * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80 */ if (offset + 10 <= data.length) { // look for 'ID3' identifier if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns true if an ID3 footer can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isFooter = (data, offset) => { /* * The footer is a copy of the header, but with a different identifier */ if (offset + 10 <= data.length) { // look for '3DI' identifier if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns any adjacent ID3 tags found in data starting at offset, as one block of data * @param data - The data to search in * @param offset - The offset at which to start searching * @returns the block of data containing any ID3 tags found * or *undefined* if no header is found at the starting offset */ const getID3Data = (data, offset) => { const front = offset; let length = 0; while (isHeader$2(data, offset)) { // ID3 header is 10 bytes length += 10; const size = readSize(data, offset + 6); length += size; if (isFooter(data, offset + 10)) { // ID3 footer is 10 bytes length += 10; } offset += length; } if (length > 0) { return data.subarray(front, front + length); } return undefined; }; const readSize = (data, offset) => { let size = 0; size = (data[offset] & 0x7f) << 21; size |= (data[offset + 1] & 0x7f) << 14; size |= (data[offset + 2] & 0x7f) << 7; size |= data[offset + 3] & 0x7f; return size; }; const canParse$2 = (data, offset) => { return isHeader$2(data, offset) && readSize(data, offset + 6) + 10 <= data.length - offset; }; /** * Searches for the Elementary Stream timestamp found in the ID3 data chunk * @param data - Block of data containing one or more ID3 tags */ const getTimeStamp = data => { const frames = getID3Frames(data); for (let i = 0; i < frames.length; i++) { const frame = frames[i]; if (isTimeStampFrame(frame)) { return readTimeStamp(frame); } } return undefined; }; /** * Returns true if the ID3 frame is an Elementary Stream timestamp frame */ const isTimeStampFrame = frame => { return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp'; }; const getFrameData = data => { /* Frame ID $xx xx xx xx (four characters) Size $xx xx xx xx Flags $xx xx */ const type = String.fromCharCode(data[0], data[1], data[2], data[3]); const size = readSize(data, 4); // skip frame id, size, and flags const offset = 10; return { type, size, data: data.subarray(offset, offset + size) }; }; /** * Returns an array of ID3 frames found in all the ID3 tags in the id3Data * @param id3Data - The ID3 data containing one or more ID3 tags */ const getID3Frames = id3Data => { let offset = 0; const frames = []; while (isHeader$2(id3Data, offset)) { const size = readSize(id3Data, offset + 6); // skip past ID3 header offset += 10; const end = offset + size; // loop through frames in the ID3 tag while (offset + 8 < end) { const frameData = getFrameData(id3Data.subarray(offset)); const frame = decodeFrame(frameData); if (frame) { frames.push(frame); } // skip frame header and frame data offset += frameData.size + 10; } if (isFooter(id3Data, offset)) { offset += 10; } } return frames; }; const decodeFrame = frame => { if (frame.type === 'PRIV') { return decodePrivFrame(frame); } else if (frame.type[0] === 'W') { return decodeURLFrame(frame); } return decodeTextFrame(frame); }; const decodePrivFrame = frame => { /* Format: \0 */ if (frame.size < 2) { return undefined; } const owner = utf8ArrayToStr(frame.data, true); const privateData = new Uint8Array(frame.data.subarray(owner.length + 1)); return { key: frame.type, info: owner, data: privateData.buffer }; }; const decodeTextFrame = frame => { if (frame.size < 2) { return undefined; } if (frame.type === 'TXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{Value} */ let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0] = {Text Encoding} [1-?] = {Value} */ const text = utf8ArrayToStr(frame.data.subarray(1)); return { key: frame.type, data: text }; }; const decodeURLFrame = frame => { if (frame.type === 'WXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{URL} */ if (frame.size < 2) { return undefined; } let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0-?] = {URL} */ const url = utf8ArrayToStr(frame.data); return { key: frame.type, data: url }; }; const readTimeStamp = timeStampFrame => { if (timeStampFrame.data.byteLength === 8) { const data = new Uint8Array(timeStampFrame.data); // timestamp is 33 bit expressed as a big-endian eight-octet number, // with the upper 31 bits set to zero. const pts33Bit = data[3] & 0x1; let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7]; timestamp /= 45; if (pts33Bit) { timestamp += 47721858.84; } // 2^32 / 90 return Math.round(timestamp); } return undefined; }; // http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197 // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt /* utf.js - UTF-8 <=> UTF-16 convertion * * Copyright (C) 1999 Masanao Izumo * Version: 1.0 * LastModified: Dec 25 1999 * This library is free. You can redistribute it and/or modify it. */ const utf8ArrayToStr = (array, exitOnNull = false) => { const decoder = getTextDecoder(); if (decoder) { const decoded = decoder.decode(array); if (exitOnNull) { // grab up to the first null const idx = decoded.indexOf('\0'); return idx !== -1 ? decoded.substring(0, idx) : decoded; } // remove any null characters return decoded.replace(/\0/g, ''); } const len = array.length; let c; let char2; let char3; let out = ''; let i = 0; while (i < len) { c = array[i++]; if (c === 0x00 && exitOnNull) { return out; } else if (c === 0x00 || c === 0x03) { // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it continue; } switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx out += String.fromCharCode(c); break; case 12: case 13: // 110x xxxx 10xx xxxx char2 = array[i++]; out += String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f); break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx char2 = array[i++]; char3 = array[i++]; out += String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0); break; } } return out; }; let decoder; function getTextDecoder() { if (!decoder && typeof self.TextDecoder !== 'undefined') { decoder = new self.TextDecoder('utf-8'); } return decoder; } /** * hex dump helper class */ const Hex = { hexDump: function (array) { let str = ''; for (let i = 0; i < array.length; i++) { let h = array[i].toString(16); if (h.length < 2) { h = '0' + h; } str += h; } return str; } }; const UINT32_MAX$1 = Math.pow(2, 32) - 1; const push = [].push; // We are using fixed track IDs for driving the MP4 remuxer // instead of following the TS PIDs. // There is no reason not to do this and some browsers/SourceBuffer-demuxers // may not like if there are TrackID "switches" // See https://github.com/video-dev/hls.js/issues/1331 // Here we are mapping our internal track types to constant MP4 track IDs // With MSE currently one can only have one track of each, and we are muxing // whatever video/audio rendition in them. const RemuxerTrackIdConfig = { video: 1, audio: 2, id3: 3, text: 4 }; function bin2str(data) { return String.fromCharCode.apply(null, data); } function readUint16(buffer, offset) { const val = buffer[offset] << 8 | buffer[offset + 1]; return val < 0 ? 65536 + val : val; } function readUint32(buffer, offset) { const val = readSint32(buffer, offset); return val < 0 ? 4294967296 + val : val; } function readSint32(buffer, offset) { return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3]; } function writeUint32(buffer, offset, value) { buffer[offset] = value >> 24; buffer[offset + 1] = value >> 16 & 0xff; buffer[offset + 2] = value >> 8 & 0xff; buffer[offset + 3] = value & 0xff; } // Find the data for a box specified by its path function findBox(data, path) { const results = []; if (!path.length) { // short-circuit the search for empty paths return results; } const end = data.byteLength; for (let i = 0; i < end;) { const size = readUint32(data, i); const type = bin2str(data.subarray(i + 4, i + 8)); const endbox = size > 1 ? i + size : end; if (type === path[0]) { if (path.length === 1) { // this is the end of the path and we've found the box we were // looking for results.push(data.subarray(i + 8, endbox)); } else { // recursively search for the next box along the path const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1)); if (subresults.length) { push.apply(results, subresults); } } } i = endbox; } // we've finished searching all of data return results; } function parseSegmentIndex(sidx) { const references = []; const version = sidx[0]; // set initial offset, we skip the reference ID (not needed) let index = 8; const timescale = readUint32(sidx, index); index += 4; // TODO: parse earliestPresentationTime and firstOffset // usually zero in our case const earliestPresentationTime = 0; const firstOffset = 0; if (version === 0) { index += 8; } else { index += 16; } // skip reserved index += 2; let startByte = sidx.length + firstOffset; const referencesCount = readUint16(sidx, index); index += 2; for (let i = 0; i < referencesCount; i++) { let referenceIndex = index; const referenceInfo = readUint32(sidx, referenceIndex); referenceIndex += 4; const referenceSize = referenceInfo & 0x7fffffff; const referenceType = (referenceInfo & 0x80000000) >>> 31; if (referenceType === 1) { logger.warn('SIDX has hierarchical references (not supported)'); return null; } const subsegmentDuration = readUint32(sidx, referenceIndex); referenceIndex += 4; references.push({ referenceSize, subsegmentDuration, // unscaled info: { duration: subsegmentDuration / timescale, start: startByte, end: startByte + referenceSize - 1 } }); startByte += referenceSize; // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits // for |sapDelta|. referenceIndex += 4; // skip to next ref index = referenceIndex; } return { earliestPresentationTime, timescale, version, referencesCount, references }; } /** * Parses an MP4 initialization segment and extracts stream type and * timescale values for any declared tracks. Timescale values indicate the * number of clock ticks per second to assume for time-based values * elsewhere in the MP4. * * To determine the start time of an MP4, you need two pieces of * information: the timescale unit and the earliest base media decode * time. Multiple timescales can be specified within an MP4 but the * base media decode time is always expressed in the timescale from * the media header box for the track: * ``` * moov > trak > mdia > mdhd.timescale * moov > trak > mdia > hdlr * ``` * @param initSegment the bytes of the init segment * @returns a hash of track type to timescale values or null if * the init segment is malformed. */ function parseInitSegment(initSegment) { const result = []; const traks = findBox(initSegment, ['moov', 'trak']); for (let i = 0; i < traks.length; i++) { const trak = traks[i]; const tkhd = findBox(trak, ['tkhd'])[0]; if (tkhd) { let version = tkhd[0]; let index = version === 0 ? 12 : 20; const trackId = readUint32(tkhd, index); const mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; if (mdhd) { version = mdhd[0]; index = version === 0 ? 12 : 20; const timescale = readUint32(mdhd, index); const hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; if (hdlr) { const hdlrType = bin2str(hdlr.subarray(8, 12)); const type = { soun: ElementaryStreamTypes.AUDIO, vide: ElementaryStreamTypes.VIDEO }[hdlrType]; if (type) { // Parse codec details const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; let codec; if (stsd) { codec = bin2str(stsd.subarray(12, 16)); // TODO: Parse codec details to be able to build MIME type. // stsd.start += 8; // const codecBox = findBox(stsd, [codec])[0]; // if (codecBox) { // TODO: Codec parsing support for avc1, mp4a, hevc, av01... // } } result[trackId] = { timescale, type }; result[type] = { timescale, id: trackId, codec }; } } } } } const trex = findBox(initSegment, ['moov', 'mvex', 'trex']); trex.forEach(trex => { const trackId = readUint32(trex, 4); const track = result[trackId]; if (track) { track.default = { duration: readUint32(trex, 12), flags: readUint32(trex, 20) }; } }); return result; } function patchEncyptionData(initSegment, decryptdata) { if (!initSegment || !decryptdata) { return initSegment; } const keyId = decryptdata.keyId; if (keyId && decryptdata.isCommonEncryption) { const traks = findBox(initSegment, ['moov', 'trak']); traks.forEach(trak => { const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; // skip the sample entry count const sampleEntries = stsd.subarray(8); let encBoxes = findBox(sampleEntries, ['enca']); const isAudio = encBoxes.length > 0; if (!isAudio) { encBoxes = findBox(sampleEntries, ['encv']); } encBoxes.forEach(enc => { const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78); const sinfBoxes = findBox(encBoxChildren, ['sinf']); sinfBoxes.forEach(sinf => { const tenc = parseSinf(sinf); if (tenc) { // Look for default key id (keyID offset is always 8 within the tenc box): const tencKeyId = tenc.subarray(8, 24); if (!tencKeyId.some(b => b !== 0)) { logger.log(`[eme] Patching keyId in 'enc${isAudio ? 'a' : 'v'}>sinf>>tenc' box: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump(keyId)}`); tenc.set(keyId, 8); } } }); }); }); } return initSegment; } function parseSinf(sinf) { const schm = findBox(sinf, ['schm'])[0]; if (schm) { const scheme = bin2str(schm.subarray(4, 8)); if (scheme === 'cbcs' || scheme === 'cenc') { return findBox(sinf, ['schi', 'tenc'])[0]; } } logger.error(`[eme] missing 'schm' box`); return null; } /** * Determine the base media decode start time, in seconds, for an MP4 * fragment. If multiple fragments are specified, the earliest time is * returned. * * The base media decode time can be parsed from track fragment * metadata: * ``` * moof > traf > tfdt.baseMediaDecodeTime * ``` * It requires the timescale value from the mdhd to interpret. * * @param initData - a hash of track type to timescale values * @param fmp4 - the bytes of the mp4 fragment * @returns the earliest base media decode start time for the * fragment, in seconds */ function getStartDTS(initData, fmp4) { // we need info from two children of each track fragment box return findBox(fmp4, ['moof', 'traf']).reduce((result, traf) => { const tfdt = findBox(traf, ['tfdt'])[0]; const version = tfdt[0]; const start = findBox(traf, ['tfhd']).reduce((result, tfhd) => { // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (track) { let baseTime = readUint32(tfdt, 4); if (version === 1) { // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild. // This prevents large values from being used for initPTS, which can cause playlist sync issues. // https://github.com/video-dev/hls.js/issues/5303 if (baseTime === UINT32_MAX$1) { logger.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`); return result; } baseTime *= UINT32_MAX$1 + 1; baseTime += readUint32(tfdt, 8); } // assume a 90kHz clock if no timescale was specified const scale = track.timescale || 90e3; // convert base time to seconds const startTime = baseTime / scale; if (isFiniteNumber(startTime) && (result === null || startTime < result)) { return startTime; } } return result; }, null); if (start !== null && isFiniteNumber(start) && (result === null || start < result)) { return start; } return result; }, null); } /* For Reference: aligned(8) class TrackFragmentHeaderBox extends FullBox(‘tfhd’, 0, tf_flags){ unsigned int(32) track_ID; // all the following are optional fields unsigned int(64) base_data_offset; unsigned int(32) sample_description_index; unsigned int(32) default_sample_duration; unsigned int(32) default_sample_size; unsigned int(32) default_sample_flags } */ function getDuration(data, initData) { let rawDuration = 0; let videoDuration = 0; let audioDuration = 0; const trafs = findBox(data, ['moof', 'traf']); for (let i = 0; i < trafs.length; i++) { const traf = trafs[i]; // There is only one tfhd & trun per traf // This is true for CMAF style content, and we should perhaps check the ftyp // and only look for a single trun then, but for ISOBMFF we should check // for multiple track runs. const tfhd = findBox(traf, ['tfhd'])[0]; // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (!track) { continue; } const trackDefault = track.default; const tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags); let sampleDuration = trackDefault == null ? void 0 : trackDefault.duration; if (tfhdFlags & 0x000008) { // 0x000008 indicates the presence of the default_sample_duration field if (tfhdFlags & 0x000002) { // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration // If present, the default_sample_duration exists at byte offset 12 sampleDuration = readUint32(tfhd, 12); } else { // Otherwise, the duration is at byte offset 8 sampleDuration = readUint32(tfhd, 8); } } // assume a 90kHz clock if no timescale was specified const timescale = track.timescale || 90e3; const truns = findBox(traf, ['trun']); for (let j = 0; j < truns.length; j++) { rawDuration = computeRawDurationFromSamples(truns[j]); if (!rawDuration && sampleDuration) { const sampleCount = readUint32(truns[j], 4); rawDuration = sampleDuration * sampleCount; } if (track.type === ElementaryStreamTypes.VIDEO) { videoDuration += rawDuration / timescale; } else if (track.type === ElementaryStreamTypes.AUDIO) { audioDuration += rawDuration / timescale; } } } if (videoDuration === 0 && audioDuration === 0) { // If duration samples are not available in the traf use sidx subsegment_duration let sidxDuration = 0; const sidxs = findBox(data, ['sidx']); for (let i = 0; i < sidxs.length; i++) { const sidx = parseSegmentIndex(sidxs[i]); if (sidx != null && sidx.references) { sidxDuration += sidx.references.reduce((dur, ref) => dur + ref.info.duration || 0, 0); } } return sidxDuration; } if (videoDuration) { return videoDuration; } return audioDuration; } /* For Reference: aligned(8) class TrackRunBox extends FullBox(‘trun’, version, tr_flags) { unsigned int(32) sample_count; // the following are optional fields signed int(32) data_offset; unsigned int(32) first_sample_flags; // all fields in the following array are optional { unsigned int(32) sample_duration; unsigned int(32) sample_size; unsigned int(32) sample_flags if (version == 0) { unsigned int(32) else { signed int(32) }[ sample_count ] } */ function computeRawDurationFromSamples(trun) { const flags = readUint32(trun, 0); // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in. // Each field is an int32, which is 4 bytes let offset = 8; // data-offset-present flag if (flags & 0x000001) { offset += 4; } // first-sample-flags-present flag if (flags & 0x000004) { offset += 4; } let duration = 0; const sampleCount = readUint32(trun, 4); for (let i = 0; i < sampleCount; i++) { // sample-duration-present flag if (flags & 0x000100) { const sampleDuration = readUint32(trun, offset); duration += sampleDuration; offset += 4; } // sample-size-present flag if (flags & 0x000200) { offset += 4; } // sample-flags-present flag if (flags & 0x000400) { offset += 4; } // sample-composition-time-offsets-present flag if (flags & 0x000800) { offset += 4; } } return duration; } function offsetStartDTS(initData, fmp4, timeOffset) { findBox(fmp4, ['moof', 'traf']).forEach(traf => { findBox(traf, ['tfhd']).forEach(tfhd => { // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (!track) { return; } // assume a 90kHz clock if no timescale was specified const timescale = track.timescale || 90e3; // get the base media decode time from the tfdt findBox(traf, ['tfdt']).forEach(tfdt => { const version = tfdt[0]; let baseMediaDecodeTime = readUint32(tfdt, 4); if (version === 0) { baseMediaDecodeTime -= timeOffset * timescale; baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); writeUint32(tfdt, 4, baseMediaDecodeTime); } else { baseMediaDecodeTime *= Math.pow(2, 32); baseMediaDecodeTime += readUint32(tfdt, 8); baseMediaDecodeTime -= timeOffset * timescale; baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); const upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX$1 + 1)); const lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX$1 + 1)); writeUint32(tfdt, 4, upper); writeUint32(tfdt, 8, lower); } }); }); }); } // TODO: Check if the last moof+mdat pair is part of the valid range function segmentValidRange(data) { const segmentedRange = { valid: null, remainder: null }; const moofs = findBox(data, ['moof']); if (!moofs) { return segmentedRange; } else if (moofs.length < 2) { segmentedRange.remainder = data; return segmentedRange; } const last = moofs[moofs.length - 1]; // Offset by 8 bytes; findBox offsets the start by as much segmentedRange.valid = sliceUint8(data, 0, last.byteOffset - 8); segmentedRange.remainder = sliceUint8(data, last.byteOffset - 8); return segmentedRange; } function appendUint8Array(data1, data2) { const temp = new Uint8Array(data1.length + data2.length); temp.set(data1); temp.set(data2, data1.length); return temp; } function parseSamples(timeOffset, track) { const seiSamples = []; const videoData = track.samples; const timescale = track.timescale; const trackId = track.id; let isHEVCFlavor = false; const moofs = findBox(videoData, ['moof']); moofs.map(moof => { const moofOffset = moof.byteOffset - 8; const trafs = findBox(moof, ['traf']); trafs.map(traf => { // get the base media decode time from the tfdt const baseTime = findBox(traf, ['tfdt']).map(tfdt => { const version = tfdt[0]; let result = readUint32(tfdt, 4); if (version === 1) { result *= Math.pow(2, 32); result += readUint32(tfdt, 8); } return result / timescale; })[0]; if (baseTime !== undefined) { timeOffset = baseTime; } return findBox(traf, ['tfhd']).map(tfhd => { const id = readUint32(tfhd, 4); const tfhdFlags = readUint32(tfhd, 0) & 0xffffff; const baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0; const sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0; const defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0; let defaultSampleDuration = 0; const defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0; let defaultSampleSize = 0; const defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0; let tfhdOffset = 8; if (id === trackId) { if (baseDataOffsetPresent) { tfhdOffset += 8; } if (sampleDescriptionIndexPresent) { tfhdOffset += 4; } if (defaultSampleDurationPresent) { defaultSampleDuration = readUint32(tfhd, tfhdOffset); tfhdOffset += 4; } if (defaultSampleSizePresent) { defaultSampleSize = readUint32(tfhd, tfhdOffset); tfhdOffset += 4; } if (defaultSampleFlagsPresent) { tfhdOffset += 4; } if (track.type === 'video') { isHEVCFlavor = isHEVC(track.codec); } findBox(traf, ['trun']).map(trun => { const version = trun[0]; const flags = readUint32(trun, 0) & 0xffffff; const dataOffsetPresent = (flags & 0x000001) !== 0; let dataOffset = 0; const firstSampleFlagsPresent = (flags & 0x000004) !== 0; const sampleDurationPresent = (flags & 0x000100) !== 0; let sampleDuration = 0; const sampleSizePresent = (flags & 0x000200) !== 0; let sampleSize = 0; const sampleFlagsPresent = (flags & 0x000400) !== 0; const sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0; let compositionOffset = 0; const sampleCount = readUint32(trun, 4); let trunOffset = 8; // past version, flags, and sample count if (dataOffsetPresent) { dataOffset = readUint32(trun, trunOffset); trunOffset += 4; } if (firstSampleFlagsPresent) { trunOffset += 4; } let sampleOffset = dataOffset + moofOffset; for (let ix = 0; ix < sampleCount; ix++) { if (sampleDurationPresent) { sampleDuration = readUint32(trun, trunOffset); trunOffset += 4; } else { sampleDuration = defaultSampleDuration; } if (sampleSizePresent) { sampleSize = readUint32(trun, trunOffset); trunOffset += 4; } else { sampleSize = defaultSampleSize; } if (sampleFlagsPresent) { trunOffset += 4; } if (sampleCompositionOffsetsPresent) { if (version === 0) { compositionOffset = readUint32(trun, trunOffset); } else { compositionOffset = readSint32(trun, trunOffset); } trunOffset += 4; } if (track.type === ElementaryStreamTypes.VIDEO) { let naluTotalSize = 0; while (naluTotalSize < sampleSize) { const naluSize = readUint32(videoData, sampleOffset); sampleOffset += 4; if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) { const data = videoData.subarray(sampleOffset, sampleOffset + naluSize); parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples); } sampleOffset += naluSize; naluTotalSize += naluSize + 4; } } timeOffset += sampleDuration / timescale; } }); } }); }); }); return seiSamples; } function isHEVC(codec) { if (!codec) { return false; } const delimit = codec.indexOf('.'); const baseCodec = delimit < 0 ? codec : codec.substring(0, delimit); return baseCodec === 'hvc1' || baseCodec === 'hev1' || // Dolby Vision baseCodec === 'dvh1' || baseCodec === 'dvhe'; } function isSEIMessage(isHEVCFlavor, naluHeader) { if (isHEVCFlavor) { const naluType = naluHeader >> 1 & 0x3f; return naluType === 39 || naluType === 40; } else { const naluType = naluHeader & 0x1f; return naluType === 6; } } function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) { const data = discardEPB(unescapedData); let seiPtr = 0; // skip nal header seiPtr += headerSize; let payloadType = 0; let payloadSize = 0; let endOfCaptions = false; let b = 0; while (seiPtr < data.length) { payloadType = 0; do { if (seiPtr >= data.length) { break; } b = data[seiPtr++]; payloadType += b; } while (b === 0xff); // Parse payload size. payloadSize = 0; do { if (seiPtr >= data.length) { break; } b = data[seiPtr++]; payloadSize += b; } while (b === 0xff); const leftOver = data.length - seiPtr; if (!endOfCaptions && payloadType === 4 && seiPtr < data.length) { endOfCaptions = true; const countryCode = data[seiPtr++]; if (countryCode === 181) { const providerCode = readUint16(data, seiPtr); seiPtr += 2; if (providerCode === 49) { const userStructure = readUint32(data, seiPtr); seiPtr += 4; if (userStructure === 0x47413934) { const userDataType = data[seiPtr++]; // Raw CEA-608 bytes wrapped in CEA-708 packet if (userDataType === 3) { const firstByte = data[seiPtr++]; const totalCCs = 0x1f & firstByte; const enabled = 0x40 & firstByte; const totalBytes = enabled ? 2 + totalCCs * 3 : 0; const byteArray = new Uint8Array(totalBytes); if (enabled) { byteArray[0] = firstByte; for (let i = 1; i < totalBytes; i++) { byteArray[i] = data[seiPtr++]; } } samples.push({ type: userDataType, payloadType, pts, bytes: byteArray }); } } } } } else if (payloadType === 5 && payloadSize < leftOver) { endOfCaptions = true; if (payloadSize > 16) { const uuidStrArray = []; for (let i = 0; i < 16; i++) { const _b = data[seiPtr++].toString(16); uuidStrArray.push(_b.length == 1 ? '0' + _b : _b); if (i === 3 || i === 5 || i === 7 || i === 9) { uuidStrArray.push('-'); } } const length = payloadSize - 16; const userDataBytes = new Uint8Array(length); for (let i = 0; i < length; i++) { userDataBytes[i] = data[seiPtr++]; } samples.push({ payloadType, pts, uuid: uuidStrArray.join(''), userData: utf8ArrayToStr(userDataBytes), userDataBytes }); } } else if (payloadSize < leftOver) { seiPtr += payloadSize; } else if (payloadSize > leftOver) { break; } } } /** * remove Emulation Prevention bytes from a RBSP */ function discardEPB(data) { const length = data.byteLength; const EPBPositions = []; let i = 1; // Find all `Emulation Prevention Bytes` while (i < length - 2) { if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { EPBPositions.push(i + 2); i += 2; } else { i++; } } // If no Emulation Prevention Bytes were found just return the original // array if (EPBPositions.length === 0) { return data; } // Create a new array to hold the NAL unit data const newLength = length - EPBPositions.length; const newData = new Uint8Array(newLength); let sourceIndex = 0; for (i = 0; i < newLength; sourceIndex++, i++) { if (sourceIndex === EPBPositions[0]) { // Skip this byte sourceIndex++; // Remove this position index EPBPositions.shift(); } newData[i] = data[sourceIndex]; } return newData; } function parseEmsg(data) { const version = data[0]; let schemeIdUri = ''; let value = ''; let timeScale = 0; let presentationTimeDelta = 0; let presentationTime = 0; let eventDuration = 0; let id = 0; let offset = 0; if (version === 0) { while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; } schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } value += bin2str(data.subarray(offset, offset + 1)); offset += 1; timeScale = readUint32(data, 12); presentationTimeDelta = readUint32(data, 16); eventDuration = readUint32(data, 20); id = readUint32(data, 24); offset = 28; } else if (version === 1) { offset += 4; timeScale = readUint32(data, offset); offset += 4; const leftPresentationTime = readUint32(data, offset); offset += 4; const rightPresentationTime = readUint32(data, offset); offset += 4; presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime; if (!isSafeInteger(presentationTime)) { presentationTime = Number.MAX_SAFE_INTEGER; logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box'); } eventDuration = readUint32(data, offset); offset += 4; id = readUint32(data, offset); offset += 4; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; } schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } const payload = data.subarray(offset, data.byteLength); return { schemeIdUri, value, timeScale, presentationTime, presentationTimeDelta, eventDuration, id, payload }; } function mp4Box(type, ...payload) { const len = payload.length; let size = 8; let i = len; while (i--) { size += payload[i].byteLength; } const result = new Uint8Array(size); result[0] = size >> 24 & 0xff; result[1] = size >> 16 & 0xff; result[2] = size >> 8 & 0xff; result[3] = size & 0xff; result.set(type, 4); for (i = 0, size = 8; i < len; i++) { result.set(payload[i], size); size += payload[i].byteLength; } return result; } function mp4pssh(systemId, keyids, data) { if (systemId.byteLength !== 16) { throw new RangeError('Invalid system id'); } let version; let kids; if (keyids) { version = 1; kids = new Uint8Array(keyids.length * 16); for (let ix = 0; ix < keyids.length; ix++) { const k = keyids[ix]; // uint8array if (k.byteLength !== 16) { throw new RangeError('Invalid key'); } kids.set(k, ix * 16); } } else { version = 0; kids = new Uint8Array(); } let kidCount; if (version > 0) { kidCount = new Uint8Array(4); if (keyids.length > 0) { new DataView(kidCount.buffer).setUint32(0, keyids.length, false); } } else { kidCount = new Uint8Array(); } const dataSize = new Uint8Array(4); if (data && data.byteLength > 0) { new DataView(dataSize.buffer).setUint32(0, data.byteLength, false); } return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags ]), systemId, // 16 bytes kidCount, kids, dataSize, data || new Uint8Array()); } function parsePssh(initData) { if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) { return null; } const result = { version: 0, systemId: '', kids: null, data: null }; const view = new DataView(initData); const boxSize = view.getUint32(0); if (initData.byteLength !== boxSize && boxSize > 44) { return null; } const type = view.getUint32(4); if (type !== 0x70737368) { return null; } result.version = view.getUint32(8) >>> 24; if (result.version > 1) { return null; } result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16)); const dataSizeOrKidCount = view.getUint32(28); if (result.version === 0) { if (boxSize - 32 < dataSizeOrKidCount) { return null; } result.data = new Uint8Array(initData, 32, dataSizeOrKidCount); } else if (result.version === 1) { result.kids = []; for (let i = 0; i < dataSizeOrKidCount; i++) { result.kids.push(new Uint8Array(initData, 32 + i * 16, 16)); } } return result; } let keyUriToKeyIdMap = {}; class LevelKey { static clearKeyUriToKeyIdMap() { keyUriToKeyIdMap = {}; } constructor(method, uri, format, formatversions = [1], iv = null) { this.uri = void 0; this.method = void 0; this.keyFormat = void 0; this.keyFormatVersions = void 0; this.encrypted = void 0; this.isCommonEncryption = void 0; this.iv = null; this.key = null; this.keyId = null; this.pssh = null; this.method = method; this.uri = uri; this.keyFormat = format; this.keyFormatVersions = formatversions; this.iv = iv; this.encrypted = method ? method !== 'NONE' : false; this.isCommonEncryption = this.encrypted && method !== 'AES-128'; } isSupported() { // If it's Segment encryption or No encryption, just select that key system if (this.method) { if (this.method === 'AES-128' || this.method === 'NONE') { return true; } if (this.keyFormat === 'identity') { // Maintain support for clear SAMPLE-AES with MPEG-3 TS return this.method === 'SAMPLE-AES'; } else { switch (this.keyFormat) { case KeySystemFormats.FAIRPLAY: case KeySystemFormats.WIDEVINE: case KeySystemFormats.PLAYREADY: case KeySystemFormats.CLEARKEY: return ['ISO-23001-7', 'SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) !== -1; } } } return false; } getDecryptData(sn) { if (!this.encrypted || !this.uri) { return null; } if (this.method === 'AES-128' && this.uri && !this.iv) { if (typeof sn !== 'number') { // We are fetching decryption data for a initialization segment // If the segment was encrypted with AES-128 // It must have an IV defined. We cannot substitute the Segment Number in. if (this.method === 'AES-128' && !this.iv) { logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`); } // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. sn = 0; } const iv = createInitializationVector(sn); const decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv); return decryptdata; } // Initialize keyId if possible const keyBytes = convertDataUriToArrayBytes(this.uri); if (keyBytes) { switch (this.keyFormat) { case KeySystemFormats.WIDEVINE: this.pssh = keyBytes; // In case of widevine keyID is embedded in PSSH box. Read Key ID. if (keyBytes.length >= 22) { this.keyId = keyBytes.subarray(keyBytes.length - 22, keyBytes.length - 6); } break; case KeySystemFormats.PLAYREADY: { const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]); this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes); const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2); const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16)); // Parse Playready WRMHeader XML const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml'); const keyData = xmlDoc.getElementsByTagName('KID')[0]; if (keyData) { const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE'); if (keyId) { const keyIdArray = base64Decode(keyId).subarray(0, 16); // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID changeEndianness(keyIdArray); this.keyId = keyIdArray; } } break; } default: { let keydata = keyBytes.subarray(0, 16); if (keydata.length !== 16) { const padded = new Uint8Array(16); padded.set(keydata, 16 - keydata.length); keydata = padded; } this.keyId = keydata; break; } } } // Default behavior: assign a new keyId for each uri if (!this.keyId || this.keyId.byteLength !== 16) { let keyId = keyUriToKeyIdMap[this.uri]; if (!keyId) { const val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; keyId = new Uint8Array(16); const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes dv.setUint32(0, val); keyUriToKeyIdMap[this.uri] = keyId; } this.keyId = keyId; } return this; } } function createInitializationVector(segmentNumber) { const uint8View = new Uint8Array(16); for (let i = 12; i < 16; i++) { uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff; } return uint8View; } const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g; function hasVariableReferences(str) { return VARIABLE_REPLACEMENT_REGEX.test(str); } function substituteVariablesInAttributes(parsed, attr, attributeNames) { if (parsed.variableList !== null || parsed.hasVariableRefs) { for (let i = attributeNames.length; i--;) { const name = attributeNames[i]; const value = attr[name]; if (value) { attr[name] = substituteVariables(parsed, value); } } } } function substituteVariables(parsed, value) { if (parsed.variableList !== null || parsed.hasVariableRefs) { const variableList = parsed.variableList; return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => { const variableName = variableReference.substring(2, variableReference.length - 1); const variableValue = variableList == null ? void 0 : variableList[variableName]; if (variableValue === undefined) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`)); return variableReference; } return variableValue; }); } return value; } function addVariableDefinition(parsed, attr, parentUrl) { let variableList = parsed.variableList; if (!variableList) { parsed.variableList = variableList = {}; } let NAME; let VALUE; if ('QUERYPARAM' in attr) { NAME = attr.QUERYPARAM; try { const searchParams = new self.URL(parentUrl).searchParams; if (searchParams.has(NAME)) { VALUE = searchParams.get(NAME); } else { throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`); } } catch (error) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`)); } } else { NAME = attr.NAME; VALUE = attr.VALUE; } if (NAME in variableList) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`)); } else { variableList[NAME] = VALUE || ''; } } function importVariableDefinition(parsed, attr, sourceVariableList) { const IMPORT = attr.IMPORT; if (sourceVariableList && IMPORT in sourceVariableList) { let variableList = parsed.variableList; if (!variableList) { parsed.variableList = variableList = {}; } variableList[IMPORT] = sourceVariableList[IMPORT]; } else { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`)); } } /** * MediaSource helper */ function getMediaSource() { if (typeof self === 'undefined') return undefined; return self.MediaSource || self.WebKitMediaSource; } // from http://mp4ra.org/codecs.html const sampleEntryCodesISO = { audio: { a3ds: true, 'ac-3': true, 'ac-4': true, alac: true, alaw: true, dra1: true, 'dts+': true, 'dts-': true, dtsc: true, dtse: true, dtsh: true, 'ec-3': true, enca: true, g719: true, g726: true, m4ae: true, mha1: true, mha2: true, mhm1: true, mhm2: true, mlpa: true, mp4a: true, 'raw ': true, Opus: true, opus: true, // browsers expect this to be lowercase despite MP4RA says 'Opus' samr: true, sawb: true, sawp: true, sevc: true, sqcp: true, ssmv: true, twos: true, ulaw: true }, video: { avc1: true, avc2: true, avc3: true, avc4: true, avcp: true, av01: true, drac: true, dva1: true, dvav: true, dvh1: true, dvhe: true, encv: true, hev1: true, hvc1: true, mjp2: true, mp4v: true, mvc1: true, mvc2: true, mvc3: true, mvc4: true, resv: true, rv60: true, s263: true, svc1: true, svc2: true, 'vc-1': true, vp08: true, vp09: true }, text: { stpp: true, wvtt: true } }; const MediaSource$2 = getMediaSource(); function isCodecType(codec, type) { const typeCodes = sampleEntryCodesISO[type]; return !!typeCodes && typeCodes[codec.slice(0, 4)] === true; } function isCodecSupportedInMp4(codec, type) { var _MediaSource$isTypeSu; return (_MediaSource$isTypeSu = MediaSource$2 == null ? void 0 : MediaSource$2.isTypeSupported(`${type || 'video'}/mp4;codecs="${codec}"`)) != null ? _MediaSource$isTypeSu : false; } const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g; const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g; const IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present) const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source, // duration (#EXTINF:,), group 1 => duration, group 2 => title /(?!#) *(\S[\S ]*)/.source, // segment URI, group 3 => the URI (note newline is not eaten) /#EXT-X-BYTERANGE:*(.+)/.source, // next segment's byterange, group 4 => range spec (x@y) /#EXT-X-PROGRAM-DATE-TIME:(.+)/.source, // next segment's program date/time group 5 => the datetime spec /#.*/.source // All other non-segment oriented tags will match with all groups empty ].join('|'), 'g'); const LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#(EXTM3U)/.source, /#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\r?\n?/.source].join('|')); class M3U8Parser { static findGroup(groups, mediaGroupId) { for (let i = 0; i < groups.length; i++) { const group = groups[i]; if (group.id === mediaGroupId) { return group; } } } static convertAVC1ToAVCOTI(codec) { // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported const avcdata = codec.split('.'); if (avcdata.length > 2) { let result = avcdata.shift() + '.'; result += parseInt(avcdata.shift()).toString(16); result += ('000' + parseInt(avcdata.shift()).toString(16)).slice(-4); return result; } return codec; } static resolve(url, baseUrl) { return urlToolkitExports.buildAbsoluteURL(baseUrl, url, { alwaysNormalize: true }); } static isMediaPlaylist(str) { return IS_MEDIA_PLAYLIST.test(str); } static parseMasterPlaylist(string, baseurl) { const hasVariableRefs = hasVariableReferences(string) ; const parsed = { contentSteering: null, levels: [], playlistParsingError: null, sessionData: null, sessionKeys: null, startTimeOffset: null, variableList: null, hasVariableRefs }; const levelsWithKnownCodecs = []; MASTER_PLAYLIST_REGEX.lastIndex = 0; let result; while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { if (result[1]) { var _level$unknownCodecs; // '#EXT-X-STREAM-INF' is found, parse level tag in group 1 const attrs = new AttrList(result[1]); { substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']); } const uri = substituteVariables(parsed, result[2]) ; const level = { attrs, bitrate: attrs.decimalInteger('AVERAGE-BANDWIDTH') || attrs.decimalInteger('BANDWIDTH'), name: attrs.NAME, url: M3U8Parser.resolve(uri, baseurl) }; const resolution = attrs.decimalResolution('RESOLUTION'); if (resolution) { level.width = resolution.width; level.height = resolution.height; } setCodecs((attrs.CODECS || '').split(/[ ,]+/).filter(c => c), level); if (level.videoCodec && level.videoCodec.indexOf('avc1') !== -1) { level.videoCodec = M3U8Parser.convertAVC1ToAVCOTI(level.videoCodec); } if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) { levelsWithKnownCodecs.push(level); } parsed.levels.push(level); } else if (result[3]) { const tag = result[3]; const attributes = result[4]; switch (tag) { case 'SESSION-DATA': { // #EXT-X-SESSION-DATA const sessionAttrs = new AttrList(attributes); { substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']); } const dataId = sessionAttrs['DATA-ID']; if (dataId) { if (parsed.sessionData === null) { parsed.sessionData = {}; } parsed.sessionData[dataId] = sessionAttrs; } break; } case 'SESSION-KEY': { // #EXT-X-SESSION-KEY const sessionKey = parseKey(attributes, baseurl, parsed); if (sessionKey.encrypted && sessionKey.isSupported()) { if (parsed.sessionKeys === null) { parsed.sessionKeys = []; } parsed.sessionKeys.push(sessionKey); } else { logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${attributes}"`); } break; } case 'DEFINE': { // #EXT-X-DEFINE { const variableAttributes = new AttrList(attributes); substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']); addVariableDefinition(parsed, variableAttributes, baseurl); } break; } case 'CONTENT-STEERING': { // #EXT-X-CONTENT-STEERING const contentSteeringAttributes = new AttrList(attributes); { substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']); } parsed.contentSteering = { uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl), pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.' }; break; } case 'START': { // #EXT-X-START parsed.startTimeOffset = parseStartTimeOffset(attributes); break; } } } } // Filter out levels with unknown codecs if it does not remove all levels const stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length; parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels; if (parsed.levels.length === 0) { parsed.playlistParsingError = new Error('no levels found in manifest'); } return parsed; } static parseMasterPlaylistMedia(string, baseurl, parsed) { let result; const results = {}; const levels = parsed.levels; const groupsByType = { AUDIO: levels.map(level => ({ id: level.attrs.AUDIO, audioCodec: level.audioCodec })), SUBTITLES: levels.map(level => ({ id: level.attrs.SUBTITLES, textCodec: level.textCodec })), 'CLOSED-CAPTIONS': [] }; let id = 0; MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0; while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) { const attrs = new AttrList(result[1]); const type = attrs.TYPE; if (type) { const groups = groupsByType[type]; const medias = results[type] || []; results[type] = medias; { substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']); } const media = { attrs, bitrate: 0, id: id++, groupId: attrs['GROUP-ID'] || '', instreamId: attrs['INSTREAM-ID'], name: attrs.NAME || attrs.LANGUAGE || '', type, default: attrs.bool('DEFAULT'), autoselect: attrs.bool('AUTOSELECT'), forced: attrs.bool('FORCED'), lang: attrs.LANGUAGE, url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : '' }; if (groups != null && groups.length) { // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track // If we don't find the track signalled, lets use the first audio groups codec we have // Acting as a best guess const groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0]; assignCodec(media, groupCodec, 'audioCodec'); assignCodec(media, groupCodec, 'textCodec'); } medias.push(media); } } return results; } static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) { const level = new LevelDetails(baseurl); const fragments = level.fragments; // The most recent init segment seen (applies to all subsequent segments) let currentInitSegment = null; let currentSN = 0; let currentPart = 0; let totalduration = 0; let discontinuityCounter = 0; let prevFrag = null; let frag = new Fragment(type, baseurl); let result; let i; let levelkeys; let firstPdtIndex = -1; let createNextFrag = false; LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0; level.m3u8 = string; level.hasVariableRefs = hasVariableReferences(string) ; while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) { if (createNextFrag) { createNextFrag = false; frag = new Fragment(type, baseurl); // setup the next fragment for part loading frag.start = totalduration; frag.sn = currentSN; frag.cc = discontinuityCounter; frag.level = id; if (currentInitSegment) { frag.initSegment = currentInitSegment; frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; currentInitSegment.rawProgramDateTime = null; } } const duration = result[1]; if (duration) { // INF frag.duration = parseFloat(duration); // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const title = (' ' + result[2]).slice(1); frag.title = title || null; frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]); } else if (result[3]) { // url if (isFiniteNumber(frag.duration)) { frag.start = totalduration; if (levelkeys) { setFragLevelKeys(frag, levelkeys, level); } frag.sn = currentSN; frag.level = id; frag.cc = discontinuityCounter; frag.urlId = levelUrlId; fragments.push(frag); // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const uri = (' ' + result[3]).slice(1); frag.relurl = substituteVariables(level, uri) ; assignProgramDateTime(frag, prevFrag); prevFrag = frag; totalduration += frag.duration; currentSN++; currentPart = 0; createNextFrag = true; } } else if (result[4]) { // X-BYTERANGE const data = (' ' + result[4]).slice(1); if (prevFrag) { frag.setByteRange(data, prevFrag); } else { frag.setByteRange(data); } } else if (result[5]) { // PROGRAM-DATE-TIME // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 frag.rawProgramDateTime = (' ' + result[5]).slice(1); frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]); if (firstPdtIndex === -1) { firstPdtIndex = fragments.length; } } else { result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW); if (!result) { logger.warn('No matches on slow regex match for level playlist!'); continue; } for (i = 1; i < result.length; i++) { if (typeof result[i] !== 'undefined') { break; } } // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const tag = (' ' + result[i]).slice(1); const value1 = (' ' + result[i + 1]).slice(1); const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : ''; switch (tag) { case 'PLAYLIST-TYPE': level.type = value1.toUpperCase(); break; case 'MEDIA-SEQUENCE': currentSN = level.startSN = parseInt(value1); break; case 'SKIP': { const skipAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']); } const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS'); if (isFiniteNumber(skippedSegments)) { level.skippedSegments = skippedSegments; // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails` for (let _i = skippedSegments; _i--;) { fragments.unshift(null); } currentSN += skippedSegments; } const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES'); if (recentlyRemovedDateranges) { level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t'); } break; } case 'TARGETDURATION': level.targetduration = Math.max(parseInt(value1), 1); break; case 'VERSION': level.version = parseInt(value1); break; case 'EXTM3U': break; case 'ENDLIST': level.live = false; break; case '#': if (value1 || value2) { frag.tagList.push(value2 ? [value1, value2] : [value1]); } break; case 'DISCONTINUITY': discontinuityCounter++; frag.tagList.push(['DIS']); break; case 'GAP': frag.gap = true; frag.tagList.push([tag]); break; case 'BITRATE': frag.tagList.push([tag, value1]); break; case 'DATERANGE': { const dateRangeAttr = new AttrList(value1); { substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']); substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs); } const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]); if (dateRange.isValid || level.skippedSegments) { level.dateRanges[dateRange.id] = dateRange; } else { logger.warn(`Ignoring invalid DATERANGE tag: "${value1}"`); } // Add to fragment tag list for backwards compatibility (< v1.2.0) frag.tagList.push(['EXT-X-DATERANGE', value1]); break; } case 'DEFINE': { { const variableAttributes = new AttrList(value1); substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']); if ('IMPORT' in variableAttributes) { importVariableDefinition(level, variableAttributes, multivariantVariableList); } else { addVariableDefinition(level, variableAttributes, baseurl); } } break; } case 'DISCONTINUITY-SEQUENCE': discontinuityCounter = parseInt(value1); break; case 'KEY': { const levelKey = parseKey(value1, baseurl, level); if (levelKey.isSupported()) { if (levelKey.method === 'NONE') { levelkeys = undefined; break; } if (!levelkeys) { levelkeys = {}; } if (levelkeys[levelKey.keyFormat]) { levelkeys = _extends({}, levelkeys); } levelkeys[levelKey.keyFormat] = levelKey; } else { logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: "${value1}"`); } break; } case 'START': level.startTimeOffset = parseStartTimeOffset(value1); break; case 'MAP': { const mapAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']); } if (frag.duration) { // Initial segment tag is after segment duration tag. // #EXTINF: 6.0 // #EXT-X-MAP:URI="init.mp4 const init = new Fragment(type, baseurl); setInitSegment(init, mapAttrs, id, levelkeys); currentInitSegment = init; frag.initSegment = currentInitSegment; if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) { frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; } } else { // Initial segment tag is before segment duration tag setInitSegment(frag, mapAttrs, id, levelkeys); currentInitSegment = frag; createNextFrag = true; } break; } case 'SERVER-CONTROL': { const serverControlAttrs = new AttrList(value1); level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD'); level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0); level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES'); level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0); level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0); break; } case 'PART-INF': { const partInfAttrs = new AttrList(value1); level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET'); break; } case 'PART': { let partList = level.partList; if (!partList) { partList = level.partList = []; } const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined; const index = currentPart++; const partAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']); } const part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart); partList.push(part); frag.duration += part.duration; break; } case 'PRELOAD-HINT': { const preloadHintAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']); } level.preloadHint = preloadHintAttrs; break; } case 'RENDITION-REPORT': { const renditionReportAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']); } level.renditionReports = level.renditionReports || []; level.renditionReports.push(renditionReportAttrs); break; } default: logger.warn(`line parsed but not handled: ${result}`); break; } } } if (prevFrag && !prevFrag.relurl) { fragments.pop(); totalduration -= prevFrag.duration; if (level.partList) { level.fragmentHint = prevFrag; } } else if (level.partList) { assignProgramDateTime(frag, prevFrag); frag.cc = discontinuityCounter; level.fragmentHint = frag; if (levelkeys) { setFragLevelKeys(frag, levelkeys, level); } } const fragmentLength = fragments.length; const firstFragment = fragments[0]; const lastFragment = fragments[fragmentLength - 1]; totalduration += level.skippedSegments * level.targetduration; if (totalduration > 0 && fragmentLength && lastFragment) { level.averagetargetduration = totalduration / fragmentLength; const lastSn = lastFragment.sn; level.endSN = lastSn !== 'initSegment' ? lastSn : 0; if (!level.live) { lastFragment.endList = true; } if (firstFragment) { level.startCC = firstFragment.cc; } } else { level.endSN = 0; level.startCC = 0; } if (level.fragmentHint) { totalduration += level.fragmentHint.duration; } level.totalduration = totalduration; level.endCC = discontinuityCounter; /** * Backfill any missing PDT values * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after * one or more Media Segment URIs, the client SHOULD extrapolate * backward from that tag (using EXTINF durations and/or media * timestamps) to associate dates with those segments." * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs * computed. */ if (firstPdtIndex > 0) { backfillProgramDateTimes(fragments, firstPdtIndex); } return level; } } function parseKey(keyTagAttributes, baseurl, parsed) { var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT; // https://tools.ietf.org/html/rfc8216#section-4.3.2.4 const keyAttrs = new AttrList(keyTagAttributes); { substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']); } const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : ''; const decrypturi = keyAttrs.URI; const decryptiv = keyAttrs.hexadecimalInteger('IV'); const decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS; // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity". const decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity'; if (decrypturi && keyAttrs.IV && !decryptiv) { logger.error(`Invalid IV: ${keyAttrs.IV}`); } // If decrypturi is a URI with a scheme, then baseurl will be ignored // No uri is allowed when METHOD is NONE const resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : ''; const keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite); return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv); } function parseStartTimeOffset(startAttributes) { const startAttrs = new AttrList(startAttributes); const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET'); if (isFiniteNumber(startTimeOffset)) { return startTimeOffset; } return null; } function setCodecs(codecs, level) { ['video', 'audio', 'text'].forEach(type => { const filtered = codecs.filter(codec => isCodecType(codec, type)); if (filtered.length) { const preferred = filtered.filter(codec => { return codec.lastIndexOf('avc1', 0) === 0 || codec.lastIndexOf('mp4a', 0) === 0; }); level[`${type}Codec`] = preferred.length > 0 ? preferred[0] : filtered[0]; // remove from list codecs = codecs.filter(codec => filtered.indexOf(codec) === -1); } }); level.unknownCodecs = codecs; } function assignCodec(media, groupItem, codecProperty) { const codecValue = groupItem[codecProperty]; if (codecValue) { media[codecProperty] = codecValue; } } function backfillProgramDateTimes(fragments, firstPdtIndex) { let fragPrev = fragments[firstPdtIndex]; for (let i = firstPdtIndex; i--;) { const frag = fragments[i]; // Exit on delta-playlist skipped segments if (!frag) { return; } frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000; fragPrev = frag; } } function assignProgramDateTime(frag, prevFrag) { if (frag.rawProgramDateTime) { frag.programDateTime = Date.parse(frag.rawProgramDateTime); } else if (prevFrag != null && prevFrag.programDateTime) { frag.programDateTime = prevFrag.endProgramDateTime; } if (!isFiniteNumber(frag.programDateTime)) { frag.programDateTime = null; frag.rawProgramDateTime = null; } } function setInitSegment(frag, mapAttrs, id, levelkeys) { frag.relurl = mapAttrs.URI; if (mapAttrs.BYTERANGE) { frag.setByteRange(mapAttrs.BYTERANGE); } frag.level = id; frag.sn = 'initSegment'; if (levelkeys) { frag.levelkeys = levelkeys; } frag.initSegment = null; } function setFragLevelKeys(frag, levelkeys, level) { frag.levelkeys = levelkeys; const { encryptedFragments } = level; if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(format => levelkeys[format].isCommonEncryption)) { encryptedFragments.push(frag); } } var PlaylistContextType = { MANIFEST: "manifest", LEVEL: "level", AUDIO_TRACK: "audioTrack", SUBTITLE_TRACK: "subtitleTrack" }; var PlaylistLevelType = { MAIN: "main", AUDIO: "audio", SUBTITLE: "subtitle" }; function mapContextToLevelType(context) { const { type } = context; switch (type) { case PlaylistContextType.AUDIO_TRACK: return PlaylistLevelType.AUDIO; case PlaylistContextType.SUBTITLE_TRACK: return PlaylistLevelType.SUBTITLE; default: return PlaylistLevelType.MAIN; } } function getResponseUrl(response, context) { let url = response.url; // responseURL not supported on some browsers (it is used to detect URL redirection) // data-uri mode also not supported (but no need to detect redirection) if (url === undefined || url.indexOf('data:') === 0) { // fallback to initial URL url = context.url; } return url; } class PlaylistLoader { constructor(hls) { this.hls = void 0; this.loaders = Object.create(null); this.variableList = null; this.hls = hls; this.registerListeners(); } startLoad(startPosition) {} stopLoad() { this.destroyInternalLoaders(); } registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); } /** * Returns defaults or configured loader-type overloads (pLoader and loader config params) */ createInternalLoader(context) { const config = this.hls.config; const PLoader = config.pLoader; const Loader = config.loader; const InternalLoader = PLoader || Loader; const loader = new InternalLoader(config); this.loaders[context.type] = loader; return loader; } getInternalLoader(context) { return this.loaders[context.type]; } resetInternalLoader(contextType) { if (this.loaders[contextType]) { delete this.loaders[contextType]; } } /** * Call `destroy` on all internal loader instances mapped (one per context type) */ destroyInternalLoaders() { for (const contextType in this.loaders) { const loader = this.loaders[contextType]; if (loader) { loader.destroy(); } this.resetInternalLoader(contextType); } } destroy() { this.variableList = null; this.unregisterListeners(); this.destroyInternalLoaders(); } onManifestLoading(event, data) { const { url } = data; this.variableList = null; this.load({ id: null, level: 0, responseType: 'text', type: PlaylistContextType.MANIFEST, url, deliveryDirectives: null }); } onLevelLoading(event, data) { const { id, level, url, deliveryDirectives } = data; this.load({ id, level, responseType: 'text', type: PlaylistContextType.LEVEL, url, deliveryDirectives }); } onAudioTrackLoading(event, data) { const { id, groupId, url, deliveryDirectives } = data; this.load({ id, groupId, level: null, responseType: 'text', type: PlaylistContextType.AUDIO_TRACK, url, deliveryDirectives }); } onSubtitleTrackLoading(event, data) { const { id, groupId, url, deliveryDirectives } = data; this.load({ id, groupId, level: null, responseType: 'text', type: PlaylistContextType.SUBTITLE_TRACK, url, deliveryDirectives }); } load(context) { var _context$deliveryDire; const config = this.hls.config; // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`); // Check if a loader for this context already exists let loader = this.getInternalLoader(context); if (loader) { const loaderContext = loader.context; if (loaderContext && loaderContext.url === context.url) { // same URL can't overlap logger.trace('[playlist-loader]: playlist request ongoing'); return; } logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`); loader.abort(); } // apply different configs for retries depending on // context (manifest, level, audio/subs playlist) let loadPolicy; if (context.type === PlaylistContextType.MANIFEST) { loadPolicy = config.manifestLoadPolicy.default; } else { loadPolicy = _extends({}, config.playlistLoadPolicy.default, { timeoutRetry: null, errorRetry: null }); } loader = this.createInternalLoader(context); // Override level/track timeout for LL-HLS requests // (the default of 10000ms is counter productive to blocking playlist reload requests) if ((_context$deliveryDire = context.deliveryDirectives) != null && _context$deliveryDire.part) { let levelDetails; if (context.type === PlaylistContextType.LEVEL && context.level !== null) { levelDetails = this.hls.levels[context.level].details; } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) { levelDetails = this.hls.audioTracks[context.id].details; } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) { levelDetails = this.hls.subtitleTracks[context.id].details; } if (levelDetails) { const partTarget = levelDetails.partTarget; const targetDuration = levelDetails.targetduration; if (partTarget && targetDuration) { const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000; loadPolicy = _extends({}, loadPolicy, { maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs), maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs) }); } } } const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: legacyRetryCompatibility.maxNumRetry || 0, retryDelay: legacyRetryCompatibility.retryDelayMs || 0, maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { const loader = this.getInternalLoader(context); this.resetInternalLoader(context.type); const string = response.data; // Validate if it is an M3U8 at all if (string.indexOf('#EXTM3U') !== 0) { this.handleManifestParsingError(response, context, new Error('no EXTM3U delimiter'), networkDetails || null, stats); return; } stats.parsing.start = performance.now(); if (M3U8Parser.isMediaPlaylist(string)) { this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader); } else { this.handleMasterPlaylist(response, stats, context, networkDetails); } }, onError: (response, context, networkDetails, stats) => { this.handleNetworkError(context, networkDetails, false, response, stats); }, onTimeout: (stats, context, networkDetails) => { this.handleNetworkError(context, networkDetails, true, undefined, stats); } }; // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`); loader.load(context, loaderConfig, loaderCallbacks); } handleMasterPlaylist(response, stats, context, networkDetails) { const hls = this.hls; const string = response.data; const url = getResponseUrl(response, context); const parsedResult = M3U8Parser.parseMasterPlaylist(string, url); if (parsedResult.playlistParsingError) { this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats); return; } const { contentSteering, levels, sessionData, sessionKeys, startTimeOffset, variableList } = parsedResult; this.variableList = variableList; const { AUDIO: audioTracks = [], SUBTITLES: subtitles, 'CLOSED-CAPTIONS': captions } = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult); if (audioTracks.length) { // check if we have found an audio track embedded in main playlist (audio track without URI attribute) const embeddedAudioFound = audioTracks.some(audioTrack => !audioTrack.url); // if no embedded audio track defined, but audio codec signaled in quality level, // we need to signal this main audio track this could happen with playlists with // alt audio rendition in which quality levels (main) // contains both audio+video. but with mixed audio track not signaled if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) { logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one'); audioTracks.unshift({ type: 'main', name: 'main', groupId: 'main', default: false, autoselect: false, forced: false, id: -1, attrs: new AttrList({}), bitrate: 0, url: '' }); } } hls.trigger(Events.MANIFEST_LOADED, { levels, audioTracks, subtitles, captions, contentSteering, url, stats, networkDetails, sessionData, sessionKeys, startTimeOffset, variableList }); } handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) { const hls = this.hls; const { id, level, type } = context; const url = getResponseUrl(response, context); const levelUrlId = isFiniteNumber(id) ? id : 0; const levelId = isFiniteNumber(level) ? level : levelUrlId; const levelType = mapContextToLevelType(context); const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList); // We have done our first request (Manifest-type) and receive // not a master playlist but a chunk-list (track/level) // We fire the manifest-loaded event anyway with the parsed level-details // by creating a single-level structure for it. if (type === PlaylistContextType.MANIFEST) { const singleLevel = { attrs: new AttrList({}), bitrate: 0, details: levelDetails, name: '', url }; hls.trigger(Events.MANIFEST_LOADED, { levels: [singleLevel], audioTracks: [], url, stats, networkDetails, sessionData: null, sessionKeys: null, contentSteering: null, startTimeOffset: null, variableList: null }); } // save parsing time stats.parsing.end = performance.now(); // extend the context with the new levelDetails property context.levelDetails = levelDetails; this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader); } handleManifestParsingError(response, context, error, networkDetails, stats) { this.hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.MANIFEST_PARSING_ERROR, fatal: context.type === PlaylistContextType.MANIFEST, url: response.url, err: error, error, reason: error.message, response, context, networkDetails, stats }); } handleNetworkError(context, networkDetails, timeout = false, response, stats) { let message = `A network ${timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')} occurred while loading ${context.type}`; if (context.type === PlaylistContextType.LEVEL) { message += `: ${context.level} id: ${context.id}`; } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) { message += ` id: ${context.id} group-id: "${context.groupId}"`; } const error = new Error(message); logger.warn(`[playlist-loader]: ${message}`); let details = ErrorDetails.UNKNOWN; let fatal = false; const loader = this.getInternalLoader(context); switch (context.type) { case PlaylistContextType.MANIFEST: details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR; fatal = true; break; case PlaylistContextType.LEVEL: details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR; fatal = false; break; case PlaylistContextType.AUDIO_TRACK: details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR; fatal = false; break; case PlaylistContextType.SUBTITLE_TRACK: details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR; fatal = false; break; } if (loader) { this.resetInternalLoader(context.type); } const errorData = { type: ErrorTypes.NETWORK_ERROR, details, fatal, url: context.url, loader, context, error, networkDetails, stats }; if (response) { const url = (networkDetails == null ? void 0 : networkDetails.url) || context.url; errorData.response = _objectSpread2({ url, data: undefined }, response); } this.hls.trigger(Events.ERROR, errorData); } handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) { const hls = this.hls; const { type, level, id, groupId, deliveryDirectives } = context; const url = getResponseUrl(response, context); const parent = mapContextToLevelType(context); const levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined; if (!levelDetails.fragments.length) { const _error = new Error('No Segments found in Playlist'); hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_EMPTY_ERROR, fatal: false, url, error: _error, reason: _error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } if (!levelDetails.targetduration) { levelDetails.playlistParsingError = new Error('Missing Target Duration'); } const error = levelDetails.playlistParsingError; if (error) { hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_PARSING_ERROR, fatal: false, url, error, reason: error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } if (levelDetails.live && loader) { if (loader.getCacheAge) { levelDetails.ageHeader = loader.getCacheAge() || 0; } if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) { levelDetails.ageHeader = 0; } } switch (type) { case PlaylistContextType.MANIFEST: case PlaylistContextType.LEVEL: hls.trigger(Events.LEVEL_LOADED, { details: levelDetails, level: levelIndex || 0, id: id || 0, stats, networkDetails, deliveryDirectives }); break; case PlaylistContextType.AUDIO_TRACK: hls.trigger(Events.AUDIO_TRACK_LOADED, { details: levelDetails, id: id || 0, groupId: groupId || '', stats, networkDetails, deliveryDirectives }); break; case PlaylistContextType.SUBTITLE_TRACK: hls.trigger(Events.SUBTITLE_TRACK_LOADED, { details: levelDetails, id: id || 0, groupId: groupId || '', stats, networkDetails, deliveryDirectives }); break; } } } function sendAddTrackEvent(track, videoEl) { let event; try { event = new Event('addtrack'); } catch (err) { // for IE11 event = document.createEvent('Event'); event.initEvent('addtrack', false, false); } event.track = track; videoEl.dispatchEvent(event); } function addCueToTrack(track, cue) { // Sometimes there are cue overlaps on segmented vtts so the same // cue can appear more than once in different vtt files. // This avoid showing duplicated cues with same timecode and text. const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues && !track.cues.getCueById(cue.id)) { try { track.addCue(cue); if (!track.cues.getCueById(cue.id)) { throw new Error(`addCue is failed for: ${cue}`); } } catch (err) { logger.debug(`[texttrack-utils]: ${err}`); try { const textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text); textTrackCue.id = cue.id; track.addCue(textTrackCue); } catch (err2) { logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`); } } } if (mode === 'disabled') { track.mode = mode; } } function clearCurrentCues(track) { // When track.mode is disabled, track.cues will be null. // To guarantee the removal of cues, we need to temporarily // change the mode to hidden const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues) { for (let i = track.cues.length; i--;) { track.removeCue(track.cues[i]); } } if (mode === 'disabled') { track.mode = mode; } } function removeCuesInRange(track, start, end, predicate) { const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues && track.cues.length > 0) { const cues = getCuesInRange(track.cues, start, end); for (let i = 0; i < cues.length; i++) { if (!predicate || predicate(cues[i])) { track.removeCue(cues[i]); } } } if (mode === 'disabled') { track.mode = mode; } } // Find first cue starting after given time. // Modified version of binary search O(log(n)). function getFirstCueIndexAfterTime(cues, time) { // If first cue starts after time, start there if (time < cues[0].startTime) { return 0; } // If the last cue ends before time there is no overlap const len = cues.length - 1; if (time > cues[len].endTime) { return -1; } let left = 0; let right = len; while (left <= right) { const mid = Math.floor((right + left) / 2); if (time < cues[mid].startTime) { right = mid - 1; } else if (time > cues[mid].startTime && left < len) { left = mid + 1; } else { // If it's not lower or higher, it must be equal. return mid; } } // At this point, left and right have swapped. // No direct match was found, left or right element must be the closest. Check which one has the smallest diff. return cues[left].startTime - time < time - cues[right].startTime ? left : right; } function getCuesInRange(cues, start, end) { const cuesFound = []; const firstCueInRange = getFirstCueIndexAfterTime(cues, start); if (firstCueInRange > -1) { for (let i = firstCueInRange, len = cues.length; i < len; i++) { const cue = cues[i]; if (cue.startTime >= start && cue.endTime <= end) { cuesFound.push(cue); } else if (cue.startTime > end) { return cuesFound; } } } return cuesFound; } var MetadataSchema = { audioId3: "org.id3", dateRange: "com.apple.quicktime.HLS", emsg: "https://aomedia.org/emsg/ID3" }; const MIN_CUE_DURATION = 0.25; function getCueClass() { if (typeof self === 'undefined') return undefined; return self.VTTCue || self.TextTrackCue; } function createCueWithDataFields(Cue, startTime, endTime, data, type) { let cue = new Cue(startTime, endTime, ''); try { cue.value = data; if (type) { cue.type = type; } } catch (e) { cue = new Cue(startTime, endTime, JSON.stringify(type ? _objectSpread2({ type }, data) : data)); } return cue; } // VTTCue latest draft allows an infinite duration, fallback // to MAX_VALUE if necessary const MAX_CUE_ENDTIME = (() => { const Cue = getCueClass(); try { Cue && new Cue(0, Number.POSITIVE_INFINITY, ''); } catch (e) { return Number.MAX_VALUE; } return Number.POSITIVE_INFINITY; })(); function dateRangeDateToTimelineSeconds(date, offset) { return date.getTime() / 1000 - offset; } function hexToArrayBuffer(str) { return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer; } class ID3TrackController { constructor(hls) { this.hls = void 0; this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; this.hls = hls; this._registerListeners(); } destroy() { this._unregisterListeners(); this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; // @ts-ignore this.hls = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } // Add ID3 metatadata text track. onMediaAttached(event, data) { this.media = data.media; } onMediaDetaching() { if (!this.id3Track) { return; } clearCurrentCues(this.id3Track); this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; } onManifestLoading() { this.dateRangeCuesAppended = {}; } createTrack(media) { const track = this.getID3Track(media.textTracks); track.mode = 'hidden'; return track; } getID3Track(textTracks) { if (!this.media) { return; } for (let i = 0; i < textTracks.length; i++) { const textTrack = textTracks[i]; if (textTrack.kind === 'metadata' && textTrack.label === 'id3') { // send 'addtrack' when reusing the textTrack for metadata, // same as what we do for captions sendAddTrackEvent(textTrack, this.media); return textTrack; } } return this.media.addTextTrack('metadata', 'id3'); } onFragParsingMetadata(event, data) { if (!this.media) { return; } const { hls: { config: { enableEmsgMetadataCues, enableID3MetadataCues } } } = this; if (!enableEmsgMetadataCues && !enableID3MetadataCues) { return; } const { samples } = data; // create track dynamically if (!this.id3Track) { this.id3Track = this.createTrack(this.media); } const Cue = getCueClass(); if (!Cue) { return; } for (let i = 0; i < samples.length; i++) { const type = samples[i].type; if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) { continue; } const frames = getID3Frames(samples[i].data); if (frames) { const startTime = samples[i].pts; let endTime = startTime + samples[i].duration; if (endTime > MAX_CUE_ENDTIME) { endTime = MAX_CUE_ENDTIME; } const timeDiff = endTime - startTime; if (timeDiff <= 0) { endTime = startTime + MIN_CUE_DURATION; } for (let j = 0; j < frames.length; j++) { const frame = frames[j]; // Safari doesn't put the timestamp frame in the TextTrack if (!isTimeStampFrame(frame)) { // add a bounds to any unbounded cues this.updateId3CueEnds(startTime, type); const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type); if (cue) { this.id3Track.addCue(cue); } } } } } } updateId3CueEnds(startTime, type) { var _this$id3Track; const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues; if (cues) { for (let i = cues.length; i--;) { const cue = cues[i]; if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) { cue.endTime = startTime; } } } } onBufferFlushing(event, { startOffset, endOffset, type }) { const { id3Track, hls } = this; if (!hls) { return; } const { config: { enableEmsgMetadataCues, enableID3MetadataCues } } = hls; if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) { let predicate; if (type === 'audio') { predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues; } else if (type === 'video') { predicate = cue => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } else { predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } removeCuesInRange(id3Track, startOffset, endOffset, predicate); } } onLevelUpdated(event, { details }) { if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) { return; } const { dateRangeCuesAppended, id3Track } = this; const { dateRanges } = details; const ids = Object.keys(dateRanges); // Remove cues from track not found in details.dateRanges if (id3Track) { const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id)); for (let i = idsToRemove.length; i--;) { const id = idsToRemove[i]; Object.keys(dateRangeCuesAppended[id].cues).forEach(key => { id3Track.removeCue(dateRangeCuesAppended[id].cues[key]); }); delete dateRangeCuesAppended[id]; } } // Exit if the playlist does not have Date Ranges or does not have Program Date Time const lastFragment = details.fragments[details.fragments.length - 1]; if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) { return; } if (!this.id3Track) { this.id3Track = this.createTrack(this.media); } const dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start; const Cue = getCueClass(); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const dateRange = dateRanges[id]; const appendedDateRangeCues = dateRangeCuesAppended[id]; const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {}; let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false; const startTime = dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset); let endTime = MAX_CUE_ENDTIME; const endDate = dateRange.endDate; if (endDate) { endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset); durationKnown = true; } else if (dateRange.endOnNext && !durationKnown) { const nextDateRangeWithSameClass = ids.reduce((filterMapArray, id) => { const candidate = dateRanges[id]; if (candidate.class === dateRange.class && candidate.id !== id && candidate.startDate > dateRange.startDate) { filterMapArray.push(candidate); } return filterMapArray; }, []).sort((a, b) => a.startDate.getTime() - b.startDate.getTime())[0]; if (nextDateRangeWithSameClass) { endTime = dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset); durationKnown = true; } } const attributes = Object.keys(dateRange.attr); for (let j = 0; j < attributes.length; j++) { const key = attributes[j]; if (!isDateRangeCueAttribute(key)) { continue; } const cue = cues[key]; if (cue) { if (durationKnown && !appendedDateRangeCues.durationKnown) { cue.endTime = endTime; } } else if (Cue) { let data = dateRange.attr[key]; if (isSCTE35Attribute(key)) { data = hexToArrayBuffer(data); } const _cue = createCueWithDataFields(Cue, startTime, endTime, { key, data }, MetadataSchema.dateRange); if (_cue) { _cue.id = id; this.id3Track.addCue(_cue); cues[key] = _cue; } } } dateRangeCuesAppended[id] = { cues, dateRange, durationKnown }; } } } class LatencyController { constructor(hls) { this.hls = void 0; this.config = void 0; this.media = null; this.levelDetails = null; this.currentTime = 0; this.stallCount = 0; this._latency = null; this.timeupdateHandler = () => this.timeupdate(); this.hls = hls; this.config = hls.config; this.registerListeners(); } get latency() { return this._latency || 0; } get maxLatency() { const { config, levelDetails } = this; if (config.liveMaxLatencyDuration !== undefined) { return config.liveMaxLatencyDuration; } return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0; } get targetLatency() { const { levelDetails } = this; if (levelDetails === null) { return null; } const { holdBack, partHoldBack, targetduration } = levelDetails; const { liveSyncDuration, liveSyncDurationCount, lowLatencyMode } = this.config; const userConfig = this.hls.userConfig; let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack; if (userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) { targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration; } const maxLiveSyncOnStallIncrease = targetduration; const liveSyncOnStallIncrease = 1.0; return targetLatency + Math.min(this.stallCount * liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease); } get liveSyncPosition() { const liveEdge = this.estimateLiveEdge(); const targetLatency = this.targetLatency; const levelDetails = this.levelDetails; if (liveEdge === null || targetLatency === null || levelDetails === null) { return null; } const edge = levelDetails.edge; const syncPosition = liveEdge - targetLatency - this.edgeStalled; const min = edge - levelDetails.totalduration; const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration); return Math.min(Math.max(min, syncPosition), max); } get drift() { const { levelDetails } = this; if (levelDetails === null) { return 1; } return levelDetails.drift; } get edgeStalled() { const { levelDetails } = this; if (levelDetails === null) { return 0; } const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3; return Math.max(levelDetails.age - maxLevelUpdateAge, 0); } get forwardBufferLength() { const { media, levelDetails } = this; if (!media || !levelDetails) { return 0; } const bufferedRanges = media.buffered.length; return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime; } destroy() { this.unregisterListeners(); this.onMediaDetaching(); this.levelDetails = null; // @ts-ignore this.hls = this.timeupdateHandler = null; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); this.hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); this.hls.off(Events.ERROR, this.onError, this); } onMediaAttached(event, data) { this.media = data.media; this.media.addEventListener('timeupdate', this.timeupdateHandler); } onMediaDetaching() { if (this.media) { this.media.removeEventListener('timeupdate', this.timeupdateHandler); this.media = null; } } onManifestLoading() { this.levelDetails = null; this._latency = null; this.stallCount = 0; } onLevelUpdated(event, { details }) { this.levelDetails = details; if (details.advanced) { this.timeupdate(); } if (!details.live && this.media) { this.media.removeEventListener('timeupdate', this.timeupdateHandler); } } onError(event, data) { var _this$levelDetails; if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) { return; } this.stallCount++; if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) { logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency'); } } timeupdate() { const { media, levelDetails } = this; if (!media || !levelDetails) { return; } this.currentTime = media.currentTime; const latency = this.computeLatency(); if (latency === null) { return; } this._latency = latency; // Adapt playbackRate to meet target latency in low-latency mode const { lowLatencyMode, maxLiveSyncPlaybackRate } = this.config; if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1) { return; } const targetLatency = this.targetLatency; if (targetLatency === null) { return; } const distanceFromTarget = latency - targetLatency; // Only adjust playbackRate when within one target duration of targetLatency // and more than one second from under-buffering. // Playback further than one target duration from target can be considered DVR playback. const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration); const inLiveRange = distanceFromTarget < liveMinLatencyDuration; if (levelDetails.live && inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) { const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate)); const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20; media.playbackRate = Math.min(max, Math.max(1, rate)); } else if (media.playbackRate !== 1 && media.playbackRate !== 0) { media.playbackRate = 1; } } estimateLiveEdge() { const { levelDetails } = this; if (levelDetails === null) { return null; } return levelDetails.edge + levelDetails.age; } computeLatency() { const liveEdge = this.estimateLiveEdge(); if (liveEdge === null) { return null; } return liveEdge - this.currentTime; } } const HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null]; var HlsSkip = { No: "", Yes: "YES", v2: "v2" }; function getSkipValue(details, msn) { const { canSkipUntil, canSkipDateRanges, endSN } = details; const snChangeGoal = msn !== undefined ? msn - endSN : 0; if (canSkipUntil && snChangeGoal < canSkipUntil) { if (canSkipDateRanges) { return HlsSkip.v2; } return HlsSkip.Yes; } return HlsSkip.No; } class HlsUrlParameters { constructor(msn, part, skip) { this.msn = void 0; this.part = void 0; this.skip = void 0; this.msn = msn; this.part = part; this.skip = skip; } addDirectives(uri) { const url = new self.URL(uri); if (this.msn !== undefined) { url.searchParams.set('_HLS_msn', this.msn.toString()); } if (this.part !== undefined) { url.searchParams.set('_HLS_part', this.part.toString()); } if (this.skip) { url.searchParams.set('_HLS_skip', this.skip); } return url.href; } } class Level { constructor(data) { this._attrs = void 0; this.audioCodec = void 0; this.bitrate = void 0; this.codecSet = void 0; this.height = void 0; this.id = void 0; this.name = void 0; this.videoCodec = void 0; this.width = void 0; this.unknownCodecs = void 0; this.audioGroupIds = void 0; this.details = void 0; this.fragmentError = 0; this.loadError = 0; this.loaded = void 0; this.realBitrate = 0; this.textGroupIds = void 0; this.url = void 0; this._urlId = 0; this.url = [data.url]; this._attrs = [data.attrs]; this.bitrate = data.bitrate; if (data.details) { this.details = data.details; } this.id = data.id || 0; this.name = data.name; this.width = data.width || 0; this.height = data.height || 0; this.audioCodec = data.audioCodec; this.videoCodec = data.videoCodec; this.unknownCodecs = data.unknownCodecs; this.codecSet = [data.videoCodec, data.audioCodec].filter(c => c).join(',').replace(/\.[^.,]+/g, ''); } get maxBitrate() { return Math.max(this.realBitrate, this.bitrate); } get attrs() { return this._attrs[this._urlId]; } get pathwayId() { return this.attrs['PATHWAY-ID'] || '.'; } get uri() { return this.url[this._urlId] || ''; } get urlId() { return this._urlId; } set urlId(value) { const newValue = value % this.url.length; if (this._urlId !== newValue) { this.fragmentError = 0; this.loadError = 0; this.details = undefined; this._urlId = newValue; } } get audioGroupId() { var _this$audioGroupIds; return (_this$audioGroupIds = this.audioGroupIds) == null ? void 0 : _this$audioGroupIds[this.urlId]; } get textGroupId() { var _this$textGroupIds; return (_this$textGroupIds = this.textGroupIds) == null ? void 0 : _this$textGroupIds[this.urlId]; } addFallback(data) { this.url.push(data.url); this._attrs.push(data.attrs); } } function updateFromToPTS(fragFrom, fragTo) { const fragToPTS = fragTo.startPTS; // if we know startPTS[toIdx] if (isFiniteNumber(fragToPTS)) { // update fragment duration. // it helps to fix drifts between playlist reported duration and fragment real duration let duration = 0; let frag; if (fragTo.sn > fragFrom.sn) { duration = fragToPTS - fragFrom.start; frag = fragFrom; } else { duration = fragFrom.start - fragToPTS; frag = fragTo; } if (frag.duration !== duration) { frag.duration = duration; } // we dont know startPTS[toIdx] } else if (fragTo.sn > fragFrom.sn) { const contiguous = fragFrom.cc === fragTo.cc; // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS if (contiguous && fragFrom.minEndPTS) { fragTo.start = fragFrom.start + (fragFrom.minEndPTS - fragFrom.start); } else { fragTo.start = fragFrom.start + fragFrom.duration; } } else { fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0); } } function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) { const parsedMediaDuration = endPTS - startPTS; if (parsedMediaDuration <= 0) { logger.warn('Fragment should have a positive duration', frag); endPTS = startPTS + frag.duration; endDTS = startDTS + frag.duration; } let maxStartPTS = startPTS; let minEndPTS = endPTS; const fragStartPts = frag.startPTS; const fragEndPts = frag.endPTS; if (isFiniteNumber(fragStartPts)) { // delta PTS between audio and video const deltaPTS = Math.abs(fragStartPts - startPTS); if (!isFiniteNumber(frag.deltaPTS)) { frag.deltaPTS = deltaPTS; } else { frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS); } maxStartPTS = Math.max(startPTS, fragStartPts); startPTS = Math.min(startPTS, fragStartPts); startDTS = Math.min(startDTS, frag.startDTS); minEndPTS = Math.min(endPTS, fragEndPts); endPTS = Math.max(endPTS, fragEndPts); endDTS = Math.max(endDTS, frag.endDTS); } const drift = startPTS - frag.start; if (frag.start !== 0) { frag.start = startPTS; } frag.duration = endPTS - frag.start; frag.startPTS = startPTS; frag.maxStartPTS = maxStartPTS; frag.startDTS = startDTS; frag.endPTS = endPTS; frag.minEndPTS = minEndPTS; frag.endDTS = endDTS; const sn = frag.sn; // 'initSegment' // exit if sn out of range if (!details || sn < details.startSN || sn > details.endSN) { return 0; } let i; const fragIdx = sn - details.startSN; const fragments = details.fragments; // update frag reference in fragments array // rationale is that fragments array might not contain this frag object. // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS() // if we don't update frag, we won't be able to propagate PTS info on the playlist // resulting in invalid sliding computation fragments[fragIdx] = frag; // adjust fragment PTS/duration from seqnum-1 to frag 0 for (i = fragIdx; i > 0; i--) { updateFromToPTS(fragments[i], fragments[i - 1]); } // adjust fragment PTS/duration from seqnum to last frag for (i = fragIdx; i < fragments.length - 1; i++) { updateFromToPTS(fragments[i], fragments[i + 1]); } if (details.fragmentHint) { updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint); } details.PTSKnown = details.alignedSliding = true; return drift; } function mergeDetails(oldDetails, newDetails) { // Track the last initSegment processed. Initialize it to the last one on the timeline. let currentInitSegment = null; const oldFragments = oldDetails.fragments; for (let i = oldFragments.length - 1; i >= 0; i--) { const oldInit = oldFragments[i].initSegment; if (oldInit) { currentInitSegment = oldInit; break; } } if (oldDetails.fragmentHint) { // prevent PTS and duration from being adjusted on the next hint delete oldDetails.fragmentHint.endPTS; } // check if old/new playlists have fragments in common // loop through overlapping SN and update startPTS , cc, and duration if any found let ccOffset = 0; let PTSFrag; mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => { if (oldFrag.relurl) { // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts. // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end // of the playlist. ccOffset = oldFrag.cc - newFrag.cc; } if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) { newFrag.start = newFrag.startPTS = oldFrag.startPTS; newFrag.startDTS = oldFrag.startDTS; newFrag.maxStartPTS = oldFrag.maxStartPTS; newFrag.endPTS = oldFrag.endPTS; newFrag.endDTS = oldFrag.endDTS; newFrag.minEndPTS = oldFrag.minEndPTS; newFrag.duration = oldFrag.endPTS - oldFrag.startPTS; if (newFrag.duration) { PTSFrag = newFrag; } // PTS is known when any segment has startPTS and endPTS newDetails.PTSKnown = newDetails.alignedSliding = true; } newFrag.elementaryStreams = oldFrag.elementaryStreams; newFrag.loader = oldFrag.loader; newFrag.stats = oldFrag.stats; newFrag.urlId = oldFrag.urlId; if (oldFrag.initSegment) { newFrag.initSegment = oldFrag.initSegment; currentInitSegment = oldFrag.initSegment; } }); if (currentInitSegment) { const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; fragmentsToCheck.forEach(frag => { var _currentInitSegment; if (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl)) { frag.initSegment = currentInitSegment; } }); } if (newDetails.skippedSegments) { newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag); if (newDetails.deltaUpdateFailed) { logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist'); for (let i = newDetails.skippedSegments; i--;) { newDetails.fragments.shift(); } newDetails.startSN = newDetails.fragments[0].sn; newDetails.startCC = newDetails.fragments[0].cc; } else if (newDetails.canSkipDateRanges) { newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges); } } const newFragments = newDetails.fragments; if (ccOffset) { logger.warn('discontinuity sliding from playlist, take drift into account'); for (let i = 0; i < newFragments.length; i++) { newFragments[i].cc += ccOffset; } } if (newDetails.skippedSegments) { newDetails.startCC = newDetails.fragments[0].cc; } // Merge parts mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => { newPart.elementaryStreams = oldPart.elementaryStreams; newPart.stats = oldPart.stats; }); // if at least one fragment contains PTS info, recompute PTS information for all fragments if (PTSFrag) { updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS); } else { // ensure that delta is within oldFragments range // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61]) // in that case we also need to adjust start offset of all fragments adjustSliding(oldDetails, newDetails); } if (newFragments.length) { newDetails.totalduration = newDetails.edge - newFragments[0].start; } newDetails.driftStartTime = oldDetails.driftStartTime; newDetails.driftStart = oldDetails.driftStart; const advancedDateTime = newDetails.advancedDateTime; if (newDetails.advanced && advancedDateTime) { const edge = newDetails.edge; if (!newDetails.driftStart) { newDetails.driftStartTime = advancedDateTime; newDetails.driftStart = edge; } newDetails.driftEndTime = advancedDateTime; newDetails.driftEnd = edge; } else { newDetails.driftEndTime = oldDetails.driftEndTime; newDetails.driftEnd = oldDetails.driftEnd; newDetails.advancedDateTime = oldDetails.advancedDateTime; } } function mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges) { const dateRanges = _extends({}, oldDateRanges); if (recentlyRemovedDateranges) { recentlyRemovedDateranges.forEach(id => { delete dateRanges[id]; }); } Object.keys(deltaDateRanges).forEach(id => { const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]); if (dateRange.isValid) { dateRanges[id] = dateRange; } else { logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`); } }); return dateRanges; } function mapPartIntersection(oldParts, newParts, intersectionFn) { if (oldParts && newParts) { let delta = 0; for (let i = 0, len = oldParts.length; i <= len; i++) { const oldPart = oldParts[i]; const newPart = newParts[i + delta]; if (oldPart && newPart && oldPart.index === newPart.index && oldPart.fragment.sn === newPart.fragment.sn) { intersectionFn(oldPart, newPart); } else { delta--; } } } } function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) { const skippedSegments = newDetails.skippedSegments; const start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN; const end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN; const delta = newDetails.startSN - oldDetails.startSN; const newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; const oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments; for (let i = start; i <= end; i++) { const oldFrag = oldFrags[delta + i]; let newFrag = newFrags[i]; if (skippedSegments && !newFrag && i < skippedSegments) { // Fill in skipped segments in delta playlist newFrag = newDetails.fragments[i] = oldFrag; } if (oldFrag && newFrag) { intersectionFn(oldFrag, newFrag); } } } function adjustSliding(oldDetails, newDetails) { const delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN; const oldFragments = oldDetails.fragments; if (delta < 0 || delta >= oldFragments.length) { return; } addSliding(newDetails, oldFragments[delta].start); } function addSliding(details, start) { if (start) { const fragments = details.fragments; for (let i = details.skippedSegments; i < fragments.length; i++) { fragments[i].start += start; } if (details.fragmentHint) { details.fragmentHint.start += start; } } } function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) { let reloadInterval = 1000 * newDetails.targetduration; if (newDetails.updated) { // Use last segment duration when shorter than target duration and near live edge const fragments = newDetails.fragments; const liveEdgeMaxTargetDurations = 4; if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) { const lastSegmentDuration = fragments[fragments.length - 1].duration * 1000; if (lastSegmentDuration < reloadInterval) { reloadInterval = lastSegmentDuration; } } } else { // estimate = 'miss half average'; // follow HLS Spec, If the client reloads a Playlist file and finds that it has not // changed then it MUST wait for a period of one-half the target // duration before retrying. reloadInterval /= 2; } return Math.round(reloadInterval); } function getFragmentWithSN(level, sn, fragCurrent) { if (!(level != null && level.details)) { return null; } const levelDetails = level.details; let fragment = levelDetails.fragments[sn - levelDetails.startSN]; if (fragment) { return fragment; } fragment = levelDetails.fragmentHint; if (fragment && fragment.sn === sn) { return fragment; } if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) { return fragCurrent; } return null; } function getPartWith(level, sn, partIndex) { var _level$details; if (!(level != null && level.details)) { return null; } return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex); } function findPart(partList, sn, partIndex) { if (partList) { for (let i = partList.length; i--;) { const part = partList[i]; if (part.index === partIndex && part.fragment.sn === sn) { return part; } } } return null; } function isTimeoutError(error) { switch (error.details) { case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_TIMEOUT: case ErrorDetails.LEVEL_LOAD_TIMEOUT: case ErrorDetails.MANIFEST_LOAD_TIMEOUT: return true; } return false; } function getRetryConfig(loadPolicy, error) { const isTimeout = isTimeoutError(error); return loadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`]; } function getRetryDelay(retryConfig, retryCount) { // exponential backoff capped to max retry delay const backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount); return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs); } function getLoaderConfigWithoutReties(loderConfig) { return _objectSpread2(_objectSpread2({}, loderConfig), { errorRetry: null, timeoutRetry: null }); } function shouldRetry(retryConfig, retryCount, isTimeout, httpStatus) { return !!retryConfig && retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout); } function retryForHttpStatus(httpStatus) { // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error) return httpStatus === 0 && navigator.onLine === false || !!httpStatus && (httpStatus < 400 || httpStatus > 499); } const BinarySearch = { /** * Searches for an item in an array which matches a certain condition. * This requires the condition to only match one item in the array, * and for the array to be ordered. * * @param list The array to search. * @param comparisonFn * Called and provided a candidate item as the first argument. * Should return: * > -1 if the item should be located at a lower index than the provided item. * > 1 if the item should be located at a higher index than the provided item. * > 0 if the item is the item you're looking for. * * @returns the object if found, otherwise returns null */ search: function (list, comparisonFn) { let minIndex = 0; let maxIndex = list.length - 1; let currentIndex = null; let currentElement = null; while (minIndex <= maxIndex) { currentIndex = (minIndex + maxIndex) / 2 | 0; currentElement = list[currentIndex]; const comparisonResult = comparisonFn(currentElement); if (comparisonResult > 0) { minIndex = currentIndex + 1; } else if (comparisonResult < 0) { maxIndex = currentIndex - 1; } else { return currentElement; } } return null; } }; /** * Returns first fragment whose endPdt value exceeds the given PDT, or null. * @param fragments - The array of candidate fragments * @param PDTValue - The PDT value which must be exceeded * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous */ function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) { if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) { return null; } // if less than start const startPDT = fragments[0].programDateTime; if (PDTValue < (startPDT || 0)) { return null; } const endPDT = fragments[fragments.length - 1].endProgramDateTime; if (PDTValue >= (endPDT || 0)) { return null; } maxFragLookUpTolerance = maxFragLookUpTolerance || 0; for (let seg = 0; seg < fragments.length; ++seg) { const frag = fragments[seg]; if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) { return frag; } } return null; } /** * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer. * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus * breaking any traps which would cause the same fragment to be continuously selected within a small range. * @param fragPrevious - The last frag successfully appended * @param fragments - The array of candidate fragments * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous * @returns a matching fragment or null */ function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0) { let fragNext = null; if (fragPrevious) { fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null; } else if (bufferEnd === 0 && fragments[0].start === 0) { fragNext = fragments[0]; } // Prefer the next fragment if it's within tolerance if (fragNext && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0) { return fragNext; } // We might be seeking past the tolerance so find the best match const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance)); if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) { return foundFragment; } // If no match was found return the next fragment after fragPrevious, or null return fragNext; } /** * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions. * @param candidate - The fragment to test * @param bufferEnd - The end of the current buffered range the playhead is currently within * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous * @returns 0 if it matches, 1 if too low, -1 if too high */ function fragmentWithinToleranceTest(bufferEnd = 0, maxFragLookUpTolerance = 0, candidate) { // eagerly accept an accurate match (no tolerance) if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) { return 0; } // offset should be within fragment boundary - config.maxFragLookUpTolerance // this is to cope with situations like // bufferEnd = 9.991 // frag[Ø] : [0,10] // frag[1] : [10,20] // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here // frag start frag start+duration // |-----------------------------| // <---> <---> // ...--------><-----------------------------><---------.... // previous frag matching fragment next frag // return -1 return 0 return 1 // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`); // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)); if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) { return 1; } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) { // if maxFragLookUpTolerance will have negative value then don't return -1 for first element return -1; } return 0; } /** * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions. * This function tests the candidate's program date time values, as represented in Unix time * @param candidate - The fragment to test * @param pdtBufferEnd - The Unix time representing the end of the current buffered range * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous * @returns true if contiguous, false otherwise */ function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) { const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000; // endProgramDateTime can be null, default to zero const endProgramDateTime = candidate.endProgramDateTime || 0; return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd; } function findFragWithCC(fragments, cc) { return BinarySearch.search(fragments, candidate => { if (candidate.cc < cc) { return 1; } else if (candidate.cc > cc) { return -1; } else { return 0; } }); } const RENDITION_PENALTY_DURATION_MS = 300000; var NetworkErrorAction = { DoNothing: 0, SendEndCallback: 1, SendAlternateToPenaltyBox: 2, RemoveAlternatePermanently: 3, InsertDiscontinuity: 4, RetryRequest: 5 }; var ErrorActionFlags = { None: 0, MoveAllAlternatesMatchingHost: 1, MoveAllAlternatesMatchingHDCP: 2, SwitchToSDR: 4 }; // Reserved for future use class ErrorController { constructor(hls) { this.hls = void 0; this.playlistError = 0; this.penalizedRenditions = {}; this.log = void 0; this.warn = void 0; this.error = void 0; this.hls = hls; this.log = logger.log.bind(logger, `[info]:`); this.warn = logger.warn.bind(logger, `[warning]:`); this.error = logger.error.bind(logger, `[error]:`); this.registerListeners(); } registerListeners() { const hls = this.hls; hls.on(Events.ERROR, this.onError, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } unregisterListeners() { const hls = this.hls; if (!hls) { return; } hls.off(Events.ERROR, this.onError, this); hls.off(Events.ERROR, this.onErrorOut, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } destroy() { this.unregisterListeners(); // @ts-ignore this.hls = null; this.penalizedRenditions = {}; } startLoad(startPosition) { this.playlistError = 0; } stopLoad() {} getVariantLevelIndex(frag) { return (frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN ? frag.level : this.hls.loadLevel; } onManifestLoading() { this.playlistError = 0; this.penalizedRenditions = {}; } onLevelUpdated() { this.playlistError = 0; } onError(event, data) { var _data$frag, _data$level; if (data.fatal) { return; } const hls = this.hls; const context = data.context; switch (data.details) { case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: data.errorAction = this.getFragRetryOrSwitchAction(data); return; case ErrorDetails.FRAG_PARSING_ERROR: // ignore empty segment errors marked as gap if ((_data$frag = data.frag) != null && _data$frag.gap) { data.errorAction = { action: NetworkErrorAction.DoNothing, flags: ErrorActionFlags.None }; return; } // falls through case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_DECRYPT_ERROR: { // Switch level if possible, otherwise allow retry count to reach max error retries data.errorAction = this.getFragRetryOrSwitchAction(data); data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; return; } case ErrorDetails.LEVEL_EMPTY_ERROR: case ErrorDetails.LEVEL_PARSING_ERROR: { var _data$context, _data$context$levelDe; // Only retry when empty and live const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel; if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context$levelDe = _data$context.levelDetails) != null && _data$context$levelDe.live)) { data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex); } else { // Escalate to fatal if not retrying or switching data.levelRetry = false; data.errorAction = this.getLevelSwitchAction(data, levelIndex); } } return; case ErrorDetails.LEVEL_LOAD_ERROR: case ErrorDetails.LEVEL_LOAD_TIMEOUT: if (typeof (context == null ? void 0 : context.level) === 'number') { data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level); } return; case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: case ErrorDetails.SUBTITLE_LOAD_ERROR: case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT: if (context) { const level = hls.levels[hls.loadLevel]; if (level && (context.type === PlaylistContextType.AUDIO_TRACK && context.groupId === level.audioGroupId || context.type === PlaylistContextType.SUBTITLE_TRACK && context.groupId === level.textGroupId)) { // Perform Pathway switch or Redundant failover if possible for fastest recovery // otherwise allow playlist retry count to reach max error retries data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel); data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost; return; } } return; case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED: { const level = hls.levels[hls.loadLevel]; const restrictedHdcpLevel = level == null ? void 0 : level.attrs['HDCP-LEVEL']; if (restrictedHdcpLevel) { data.errorAction = { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP, hdcpLevel: restrictedHdcpLevel }; } } return; case ErrorDetails.BUFFER_ADD_CODEC_ERROR: case ErrorDetails.REMUX_ALLOC_ERROR: data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel); return; case ErrorDetails.INTERNAL_EXCEPTION: case ErrorDetails.BUFFER_APPENDING_ERROR: case ErrorDetails.BUFFER_APPEND_ERROR: case ErrorDetails.BUFFER_FULL_ERROR: case ErrorDetails.LEVEL_SWITCH_ERROR: case ErrorDetails.BUFFER_STALLED_ERROR: case ErrorDetails.BUFFER_SEEK_OVER_HOLE: case ErrorDetails.BUFFER_NUDGE_ON_STALL: data.errorAction = { action: NetworkErrorAction.DoNothing, flags: ErrorActionFlags.None }; return; } if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) { const levelIndex = this.getVariantLevelIndex(data.frag); // Do not retry level. Escalate to fatal if switching levels fails. data.levelRetry = false; data.errorAction = this.getLevelSwitchAction(data, levelIndex); return; } } getPlaylistRetryOrSwitchAction(data, levelIndex) { var _data$response; const hls = this.hls; const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data); const retryCount = this.playlistError++; const httpStatus = (_data$response = data.response) == null ? void 0 : _data$response.code; const retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), httpStatus); if (retry) { return { action: NetworkErrorAction.RetryRequest, flags: ErrorActionFlags.None, retryConfig, retryCount }; } const errorAction = this.getLevelSwitchAction(data, levelIndex); if (retryConfig) { errorAction.retryConfig = retryConfig; errorAction.retryCount = retryCount; } return errorAction; } getFragRetryOrSwitchAction(data) { const hls = this.hls; // Share fragment error count accross media options (main, audio, subs) // This allows for level based rendition switching when media option assets fail const variantLevelIndex = this.getVariantLevelIndex(data.frag); const level = hls.levels[variantLevelIndex]; const { fragLoadPolicy, keyLoadPolicy } = hls.config; const retryConfig = getRetryConfig(data.details.startsWith('key') ? keyLoadPolicy : fragLoadPolicy, data); const fragmentErrors = hls.levels.reduce((acc, level) => acc + level.fragmentError, 0); // Switch levels when out of retried or level index out of bounds if (level) { var _data$response2; if (data.details !== ErrorDetails.FRAG_GAP) { level.fragmentError++; } const httpStatus = (_data$response2 = data.response) == null ? void 0 : _data$response2.code; const retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), httpStatus); if (retry) { return { action: NetworkErrorAction.RetryRequest, flags: ErrorActionFlags.None, retryConfig, retryCount: fragmentErrors }; } } // Reach max retry count, or Missing level reference // Switch to valid index const errorAction = this.getLevelSwitchAction(data, variantLevelIndex); // Add retry details to allow skipping of FRAG_PARSING_ERROR if (retryConfig) { errorAction.retryConfig = retryConfig; errorAction.retryCount = fragmentErrors; } return errorAction; } getLevelSwitchAction(data, levelIndex) { const hls = this.hls; if (levelIndex === null || levelIndex === undefined) { levelIndex = hls.loadLevel; } const level = this.hls.levels[levelIndex]; if (level) { level.loadError++; if (hls.autoLevelEnabled) { var _data$frag2, _data$context2; // Search for next level to retry let nextLevel = -1; const { levels, loadLevel, minAutoLevel, maxAutoLevel } = hls; const fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type; const { type: playlistErrorType, groupId: playlistErrorGroupId } = (_data$context2 = data.context) != null ? _data$context2 : {}; for (let i = levels.length; i--;) { const candidate = (i + loadLevel) % levels.length; if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) { const levelCandidate = levels[candidate]; // Skip level switch if GAP tag is found in next level at same position if (data.details === ErrorDetails.FRAG_GAP && data.frag) { const levelDetails = levels[candidate].details; if (levelDetails) { const fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start); if (fragCandidate != null && fragCandidate.gap) { continue; } } } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && playlistErrorGroupId === levelCandidate.audioGroupId || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && playlistErrorGroupId === levelCandidate.textGroupId) { // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over continue; } else if (fragErrorType === PlaylistLevelType.AUDIO && level.audioGroupId === levelCandidate.audioGroupId || fragErrorType === PlaylistLevelType.SUBTITLE && level.textGroupId === levelCandidate.textGroupId) { // For audio/subs frag errors find another group ID or fallthrough to redundant fail-over continue; } nextLevel = candidate; break; } } if (nextLevel > -1 && hls.loadLevel !== nextLevel) { data.levelRetry = true; this.playlistError = 0; return { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.None, nextAutoLevel: nextLevel }; } } } // No levels to switch / Manual level selection / Level not found // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level return { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.MoveAllAlternatesMatchingHost }; } onErrorOut(event, data) { var _data$errorAction; switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) { case NetworkErrorAction.DoNothing: break; case NetworkErrorAction.SendAlternateToPenaltyBox: this.sendAlternateToPenaltyBox(data); if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) { data.fatal = true; } break; } if (data.fatal) { this.hls.stopLoad(); return; } } sendAlternateToPenaltyBox(data) { const hls = this.hls; const errorAction = data.errorAction; if (!errorAction) { return; } const { flags, hdcpLevel, nextAutoLevel } = errorAction; switch (flags) { case ErrorActionFlags.None: this.switchLevel(data, nextAutoLevel); break; case ErrorActionFlags.MoveAllAlternatesMatchingHost: { // Handle Redundant Levels here. Pathway switching is handled by content-steering-controller if (!errorAction.resolved) { errorAction.resolved = this.redundantFailover(data); } } break; case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: if (hdcpLevel) { hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1]; errorAction.resolved = true; } this.warn(`Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`); break; } // If not resolved by previous actions try to switch to next level if (!errorAction.resolved) { this.switchLevel(data, nextAutoLevel); } } switchLevel(data, levelIndex) { if (levelIndex !== undefined && data.errorAction) { this.warn(`switching to level ${levelIndex} after ${data.details}`); this.hls.nextAutoLevel = levelIndex; data.errorAction.resolved = true; // Stream controller is responsible for this but won't switch on false start this.hls.nextLoadLevel = this.hls.nextAutoLevel; } } redundantFailover(data) { const { hls, penalizedRenditions } = this; const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel; const level = hls.levels[levelIndex]; const redundantLevels = level.url.length; const errorUrlId = data.frag ? data.frag.urlId : level.urlId; if (level.urlId === errorUrlId && (!data.frag || level.details)) { this.penalizeRendition(level, data); } for (let i = 1; i < redundantLevels; i++) { const newUrlId = (errorUrlId + i) % redundantLevels; const penalizedRendition = penalizedRenditions[newUrlId]; // Check if rendition is penalized and skip if it is a bad fit for failover if (!penalizedRendition || checkExpired(penalizedRendition, data, penalizedRenditions[errorUrlId])) { // delete penalizedRenditions[newUrlId]; // Update the url id of all levels so that we stay on the same set of variants when level switching this.warn(`Switching to Redundant Stream ${newUrlId + 1}/${redundantLevels}: "${level.url[newUrlId]}" after ${data.details}`); this.playlistError = 0; hls.levels.forEach(lv => { lv.urlId = newUrlId; }); hls.nextLoadLevel = levelIndex; return true; } } return false; } penalizeRendition(level, data) { const { penalizedRenditions } = this; const penalizedRendition = penalizedRenditions[level.urlId] || { lastErrorPerfMs: 0, errors: [], details: undefined }; penalizedRendition.lastErrorPerfMs = performance.now(); penalizedRendition.errors.push(data); penalizedRendition.details = level.details; penalizedRenditions[level.urlId] = penalizedRendition; } } function checkExpired(penalizedRendition, data, currentPenaltyState) { // Expire penalty for switching back to rendition after RENDITION_PENALTY_DURATION_MS if (performance.now() - penalizedRendition.lastErrorPerfMs > RENDITION_PENALTY_DURATION_MS) { return true; } // Expire penalty on GAP tag error if rendition has no GAP at position (does not cover media tracks) const lastErrorDetails = penalizedRendition.details; if (data.details === ErrorDetails.FRAG_GAP && lastErrorDetails && data.frag) { const position = data.frag.start; const candidateFrag = findFragmentByPTS(null, lastErrorDetails.fragments, position); if (candidateFrag && !candidateFrag.gap) { return true; } } // Expire penalty if there are more errors in currentLevel than in penalizedRendition if (currentPenaltyState && penalizedRendition.errors.length < currentPenaltyState.errors.length) { const lastCandidateError = penalizedRendition.errors[penalizedRendition.errors.length - 1]; if (lastErrorDetails && lastCandidateError.frag && data.frag && Math.abs(lastCandidateError.frag.start - data.frag.start) > lastErrorDetails.targetduration * 3) { return true; } } return false; } class BasePlaylistController { constructor(hls, logPrefix) { this.hls = void 0; this.timer = -1; this.requestScheduled = -1; this.canLoad = false; this.log = void 0; this.warn = void 0; this.log = logger.log.bind(logger, `${logPrefix}:`); this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; } destroy() { this.clearTimer(); // @ts-ignore this.hls = this.log = this.warn = null; } clearTimer() { clearTimeout(this.timer); this.timer = -1; } startLoad() { this.canLoad = true; this.requestScheduled = -1; this.loadPlaylist(); } stopLoad() { this.canLoad = false; this.clearTimer(); } switchParams(playlistUri, previous) { const renditionReports = previous == null ? void 0 : previous.renditionReports; if (renditionReports) { let foundIndex = -1; for (let i = 0; i < renditionReports.length; i++) { const attr = renditionReports[i]; let uri; try { uri = new self.URL(attr.URI, previous.url).href; } catch (error) { logger.warn(`Could not construct new URL for Rendition Report: ${error}`); uri = attr.URI || ''; } // Use exact match. Otherwise, the last partial match, if any, will be used // (Playlist URI includes a query string that the Rendition Report does not) if (uri === playlistUri) { foundIndex = i; break; } else if (uri === playlistUri.substring(0, uri.length)) { foundIndex = i; } } if (foundIndex !== -1) { const attr = renditionReports[foundIndex]; const msn = parseInt(attr['LAST-MSN']) || (previous == null ? void 0 : previous.lastPartSn); let part = parseInt(attr['LAST-PART']) || (previous == null ? void 0 : previous.lastPartIndex); if (this.hls.config.lowLatencyMode) { const currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration); if (part >= 0 && currentGoal > previous.partTarget) { part += 1; } } return new HlsUrlParameters(msn, part >= 0 ? part : undefined, HlsSkip.No); } } } loadPlaylist(hlsUrlParameters) { if (this.requestScheduled === -1) { this.requestScheduled = self.performance.now(); } // Loading is handled by the subclasses } shouldLoadPlaylist(playlist) { return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live); } shouldReloadPlaylist(playlist) { return this.timer === -1 && this.requestScheduled === -1 && this.shouldLoadPlaylist(playlist); } playlistLoaded(index, data, previousDetails) { const { details, stats } = data; // Set last updated date-time const now = self.performance.now(); const elapsed = stats.loading.first ? Math.max(0, now - stats.loading.first) : 0; details.advancedDateTime = Date.now() - elapsed; // if current playlist is a live playlist, arm a timer to reload it if (details.live || previousDetails != null && previousDetails.live) { details.reloaded(previousDetails); if (previousDetails) { this.log(`live playlist ${index} ${details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex : details.updated ? 'UPDATED' : 'MISSED'}`); } // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments if (previousDetails && details.fragments.length > 0) { mergeDetails(previousDetails, details); } if (!this.canLoad || !details.live) { return; } let deliveryDirectives; let msn = undefined; let part = undefined; if (details.canBlockReload && details.endSN && details.advanced) { // Load level with LL-HLS delivery directives const lowLatencyMode = this.hls.config.lowLatencyMode; const lastPartSn = details.lastPartSn; const endSn = details.endSN; const lastPartIndex = details.lastPartIndex; const hasParts = lastPartIndex !== -1; const lastPart = lastPartSn === endSn; // When low latency mode is disabled, we'll skip part requests once the last part index is found const nextSnStartIndex = lowLatencyMode ? 0 : lastPartIndex; if (hasParts) { msn = lastPart ? endSn + 1 : lastPartSn; part = lastPart ? nextSnStartIndex : lastPartIndex + 1; } else { msn = endSn + 1; } // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part // Update directives to obtain the Playlist that has the estimated additional duration of media const lastAdvanced = details.age; const cdnAge = lastAdvanced + details.ageHeader; let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5); if (currentGoal > 0) { if (previousDetails && currentGoal > previousDetails.tuneInGoal) { // If we attempted to get the next or latest playlist update, but currentGoal increased, // then we either can't catchup, or the "age" header cannot be trusted. this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`); currentGoal = 0; } else { const segments = Math.floor(currentGoal / details.targetduration); msn += segments; if (part !== undefined) { const parts = Math.round(currentGoal % details.targetduration / details.partTarget); part += parts; } this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`); } details.tuneInGoal = currentGoal; } deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); if (lowLatencyMode || !lastPart) { this.loadPlaylist(deliveryDirectives); return; } } else if (details.canBlockReload || details.canSkipUntil) { deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); } const bufferInfo = this.hls.mainForwardBufferInfo; const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0; const distanceToLiveEdgeMs = (details.edge - position) * 1000; const reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs); if (details.updated && now > this.requestScheduled + reloadInterval) { this.requestScheduled = stats.loading.start; } if (msn !== undefined && details.canBlockReload) { this.requestScheduled = stats.loading.first + reloadInterval - (details.partTarget * 1000 || 1000); } else if (this.requestScheduled === -1 || this.requestScheduled + reloadInterval < now) { this.requestScheduled = now; } else if (this.requestScheduled - now <= 0) { this.requestScheduled += reloadInterval; } let estimatedTimeUntilUpdate = this.requestScheduled - now; estimatedTimeUntilUpdate = Math.max(0, estimatedTimeUntilUpdate); this.log(`reload live playlist ${index} in ${Math.round(estimatedTimeUntilUpdate)} ms`); // this.log( // `live reload ${details.updated ? 'REFRESHED' : 'MISSED'} // reload in ${estimatedTimeUntilUpdate / 1000} // round trip ${(stats.loading.end - stats.loading.start) / 1000} // diff ${ // (reloadInterval - // (estimatedTimeUntilUpdate + // stats.loading.end - // stats.loading.start)) / // 1000 // } // reload interval ${reloadInterval / 1000} // target duration ${details.targetduration} // distance to edge ${distanceToLiveEdgeMs / 1000}` // ); this.timer = self.setTimeout(() => this.loadPlaylist(deliveryDirectives), estimatedTimeUntilUpdate); } else { this.clearTimer(); } } getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) { let skip = getSkipValue(details, msn); if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) { msn = previousDeliveryDirectives.msn; part = previousDeliveryDirectives.part; skip = HlsSkip.No; } return new HlsUrlParameters(msn, part, skip); } checkRetry(errorEvent) { const errorDetails = errorEvent.details; const isTimeout = isTimeoutError(errorEvent); const errorAction = errorEvent.errorAction; const { action, retryCount = 0, retryConfig } = errorAction || {}; const retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox); if (retry) { var _errorEvent$context; this.requestScheduled = -1; if (retryCount >= retryConfig.maxNumRetry) { return false; } if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) { // The LL-HLS request already timed out so retry immediately this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" without delivery-directives`); this.loadPlaylist(); } else { const delay = getRetryDelay(retryConfig, retryCount); // Schedule level/track reload this.timer = self.setTimeout(() => this.loadPlaylist(), delay); this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" in ${delay}ms`); } // `levelRetry = true` used to inform other controllers that a retry is happening errorEvent.levelRetry = true; errorAction.resolved = true; } return retry; } } let chromeOrFirefox; class LevelController extends BasePlaylistController { constructor(hls, contentSteeringController) { super(hls, '[level-controller]'); this._levels = []; this._firstLevel = -1; this._startLevel = void 0; this.currentLevel = null; this.currentLevelIndex = -1; this.manualLevelIndex = -1; this.steering = void 0; this.onParsedComplete = void 0; this.steering = contentSteeringController; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.ERROR, this.onError, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.ERROR, this.onError, this); } destroy() { this._unregisterListeners(); this.steering = null; this.resetLevels(); super.destroy(); } startLoad() { const levels = this._levels; // clean up live level details to force reload them, and reset load errors levels.forEach(level => { level.loadError = 0; level.fragmentError = 0; }); super.startLoad(); } resetLevels() { this._startLevel = undefined; this.manualLevelIndex = -1; this.currentLevelIndex = -1; this.currentLevel = null; this._levels = []; } onManifestLoading(event, data) { this.resetLevels(); } onManifestLoaded(event, data) { const levels = []; const levelSet = {}; let levelFromSet; // regroup redundant levels together data.levels.forEach(levelParsed => { var _levelParsed$audioCod; const attributes = levelParsed.attrs; // erase audio codec info if browser does not support mp4a.40.34. // demuxer will autodetect codec and fallback to mpeg/audio if (((_levelParsed$audioCod = levelParsed.audioCodec) == null ? void 0 : _levelParsed$audioCod.indexOf('mp4a.40.34')) !== -1) { chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent)); if (chromeOrFirefox) { levelParsed.audioCodec = undefined; } } const { AUDIO, CODECS, 'FRAME-RATE': FRAMERATE, 'PATHWAY-ID': PATHWAY, RESOLUTION, SUBTITLES } = attributes; const contentSteeringPrefix = `${PATHWAY || '.'}-` ; const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}`; levelFromSet = levelSet[levelKey]; if (!levelFromSet) { levelFromSet = new Level(levelParsed); levelSet[levelKey] = levelFromSet; levels.push(levelFromSet); } else { levelFromSet.addFallback(levelParsed); } addGroupId(levelFromSet, 'audio', AUDIO); addGroupId(levelFromSet, 'text', SUBTITLES); }); this.filterAndSortMediaOptions(levels, data); } filterAndSortMediaOptions(unfilteredLevels, data) { let audioTracks = []; let subtitleTracks = []; let resolutionFound = false; let videoCodecFound = false; let audioCodecFound = false; // only keep levels with supported audio/video codecs let levels = unfilteredLevels.filter(({ audioCodec, videoCodec, width, height, unknownCodecs }) => { resolutionFound || (resolutionFound = !!(width && height)); videoCodecFound || (videoCodecFound = !!videoCodec); audioCodecFound || (audioCodecFound = !!audioCodec); return !(unknownCodecs != null && unknownCodecs.length) && (!audioCodec || isCodecSupportedInMp4(audioCodec, 'audio')) && (!videoCodec || isCodecSupportedInMp4(videoCodec, 'video')); }); // remove audio-only level if we also have levels with video codecs or RESOLUTION signalled if ((resolutionFound || videoCodecFound) && audioCodecFound) { levels = levels.filter(({ videoCodec, width, height }) => !!videoCodec || !!(width && height)); } if (levels.length === 0) { // Dispatch error after MANIFEST_LOADED is done propagating Promise.resolve().then(() => { if (this.hls) { const error = new Error('no level with compatible codecs found in manifest'); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR, fatal: true, url: data.url, error, reason: error.message }); } }); return; } if (data.audioTracks) { audioTracks = data.audioTracks.filter(track => !track.audioCodec || isCodecSupportedInMp4(track.audioCodec, 'audio')); // Assign ids after filtering as array indices by group-id assignTrackIdsByGroup(audioTracks); } if (data.subtitles) { subtitleTracks = data.subtitles; assignTrackIdsByGroup(subtitleTracks); } // start bitrate is the first bitrate of the manifest const unsortedLevels = levels.slice(0); // sort levels from lowest to highest levels.sort((a, b) => { if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) { return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1; } if (a.bitrate !== b.bitrate) { return a.bitrate - b.bitrate; } if (a.attrs['FRAME-RATE'] !== b.attrs['FRAME-RATE']) { return a.attrs.decimalFloatingPoint('FRAME-RATE') - b.attrs.decimalFloatingPoint('FRAME-RATE'); } if (a.attrs.SCORE !== b.attrs.SCORE) { return a.attrs.decimalFloatingPoint('SCORE') - b.attrs.decimalFloatingPoint('SCORE'); } if (resolutionFound && a.height !== b.height) { return a.height - b.height; } return 0; }); let firstLevelInPlaylist = unsortedLevels[0]; if (this.steering) { levels = this.steering.filterParsedLevels(levels); if (levels.length !== unsortedLevels.length) { for (let i = 0; i < unsortedLevels.length; i++) { if (unsortedLevels[i].pathwayId === levels[0].pathwayId) { firstLevelInPlaylist = unsortedLevels[i]; break; } } } } this._levels = levels; // find index of first level in sorted levels for (let i = 0; i < levels.length; i++) { if (levels[i] === firstLevelInPlaylist) { this._firstLevel = i; this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelInPlaylist.bitrate}`); break; } } // Audio is only alternate if manifest include a URI along with the audio group tag, // and this is not an audio-only stream where levels contain audio-only const audioOnly = audioCodecFound && !videoCodecFound; const edata = { levels, audioTracks, subtitleTracks, sessionData: data.sessionData, sessionKeys: data.sessionKeys, firstLevel: this._firstLevel, stats: data.stats, audio: audioCodecFound, video: videoCodecFound, altAudio: !audioOnly && audioTracks.some(t => !!t.url) }; this.hls.trigger(Events.MANIFEST_PARSED, edata); // Initiate loading after all controllers have received MANIFEST_PARSED if (this.hls.config.autoStartLoad || this.hls.forceStartLoad) { this.hls.startLoad(this.hls.config.startPosition); } } get levels() { if (this._levels.length === 0) { return null; } return this._levels; } get level() { return this.currentLevelIndex; } set level(newLevel) { const levels = this._levels; if (levels.length === 0) { return; } // check if level idx is valid if (newLevel < 0 || newLevel >= levels.length) { // invalid level id given, trigger error const error = new Error('invalid level idx'); const fatal = newLevel < 0; this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.LEVEL_SWITCH_ERROR, level: newLevel, fatal, error, reason: error.message }); if (fatal) { return; } newLevel = Math.min(newLevel, levels.length - 1); } const lastLevelIndex = this.currentLevelIndex; const lastLevel = this.currentLevel; const lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined; const level = levels[newLevel]; const pathwayId = level.attrs['PATHWAY-ID']; this.currentLevelIndex = newLevel; this.currentLevel = level; if (lastLevelIndex === newLevel && level.details && lastLevel && lastPathwayId === pathwayId) { return; } this.log(`Switching to level ${newLevel}${pathwayId ? ' with Pathway ' + pathwayId : ''} from level ${lastLevelIndex}${lastPathwayId ? ' with Pathway ' + lastPathwayId : ''}`); const levelSwitchingData = _extends({}, level, { level: newLevel, maxBitrate: level.maxBitrate, attrs: level.attrs, uri: level.uri, urlId: level.urlId }); // @ts-ignore delete levelSwitchingData._attrs; // @ts-ignore delete levelSwitchingData._urlId; this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData); // check if we need to load playlist for this level const levelDetails = level.details; if (!levelDetails || levelDetails.live) { // level not retrieved yet, or live playlist we need to (re)load it const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details); this.loadPlaylist(hlsUrlParameters); } } get manualLevel() { return this.manualLevelIndex; } set manualLevel(newLevel) { this.manualLevelIndex = newLevel; if (this._startLevel === undefined) { this._startLevel = newLevel; } if (newLevel !== -1) { this.level = newLevel; } } get firstLevel() { return this._firstLevel; } set firstLevel(newLevel) { this._firstLevel = newLevel; } get startLevel() { // hls.startLevel takes precedence over config.startLevel // if none of these values are defined, fallback on this._firstLevel (first quality level appearing in variant manifest) if (this._startLevel === undefined) { const configStartLevel = this.hls.config.startLevel; if (configStartLevel !== undefined) { return configStartLevel; } else { return this._firstLevel; } } else { return this._startLevel; } } set startLevel(newLevel) { this._startLevel = newLevel; } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) { this.checkRetry(data); } } // reset errors on the successful load of a fragment onFragLoaded(event, { frag }) { if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) { const level = this._levels[frag.level]; if (level !== undefined) { level.loadError = 0; } } } onLevelLoaded(event, data) { var _data$deliveryDirecti2; const { level, details } = data; const curLevel = this._levels[level]; if (!curLevel) { var _data$deliveryDirecti; this.warn(`Invalid level index ${level}`); if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) { details.deltaUpdateFailed = true; } return; } // only process level loaded events matching with expected level if (level === this.currentLevelIndex) { // reset level load error counter on successful level loaded only if there is no issues with fragments if (curLevel.fragmentError === 0) { curLevel.loadError = 0; } this.playlistLoaded(level, data, curLevel.details); } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) { // received a delta playlist update that cannot be merged details.deltaUpdateFailed = true; } } onAudioTrackSwitched(event, data) { const currentLevel = this.currentLevel; if (!currentLevel) { return; } const audioGroupId = this.hls.audioTracks[data.id].groupId; if (currentLevel.audioGroupIds && currentLevel.audioGroupId !== audioGroupId) { let urlId = -1; for (let i = 0; i < currentLevel.audioGroupIds.length; i++) { if (currentLevel.audioGroupIds[i] === audioGroupId) { urlId = i; break; } } if (urlId !== -1 && urlId !== currentLevel.urlId) { currentLevel.urlId = urlId; if (this.canLoad) { this.startLoad(); } } } } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); const currentLevelIndex = this.currentLevelIndex; const currentLevel = this.currentLevel; if (currentLevel && this.shouldLoadPlaylist(currentLevel)) { const id = currentLevel.urlId; let url = currentLevel.uri; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } const pathwayId = currentLevel.attrs['PATHWAY-ID']; this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : ''} with${pathwayId ? ' Pathway ' + pathwayId : ''} URI ${id + 1}/${currentLevel.url.length} ${url}`); // console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId); // console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level); this.clearTimer(); this.hls.trigger(Events.LEVEL_LOADING, { url, level: currentLevelIndex, id, deliveryDirectives: hlsUrlParameters || null }); } } get nextLoadLevel() { if (this.manualLevelIndex !== -1) { return this.manualLevelIndex; } else { return this.hls.nextAutoLevel; } } set nextLoadLevel(nextLevel) { this.level = nextLevel; if (this.manualLevelIndex === -1) { this.hls.nextAutoLevel = nextLevel; } } removeLevel(levelIndex, urlId) { const filterLevelAndGroupByIdIndex = (url, id) => id !== urlId; const levels = this._levels.filter((level, index) => { if (index !== levelIndex) { return true; } if (level.url.length > 1 && urlId !== undefined) { level.url = level.url.filter(filterLevelAndGroupByIdIndex); if (level.audioGroupIds) { level.audioGroupIds = level.audioGroupIds.filter(filterLevelAndGroupByIdIndex); } if (level.textGroupIds) { level.textGroupIds = level.textGroupIds.filter(filterLevelAndGroupByIdIndex); } level.urlId = 0; return true; } if (this.steering) { this.steering.removeLevel(level); } return false; }); this.hls.trigger(Events.LEVELS_UPDATED, { levels }); } onLevelsUpdated(event, { levels }) { levels.forEach((level, index) => { const { details } = level; if (details != null && details.fragments) { details.fragments.forEach(fragment => { fragment.level = index; }); } }); this._levels = levels; } } function addGroupId(level, type, id) { if (!id) { return; } if (type === 'audio') { if (!level.audioGroupIds) { level.audioGroupIds = []; } level.audioGroupIds[level.url.length - 1] = id; } else if (type === 'text') { if (!level.textGroupIds) { level.textGroupIds = []; } level.textGroupIds[level.url.length - 1] = id; } } function assignTrackIdsByGroup(tracks) { const groups = {}; tracks.forEach(track => { const groupId = track.groupId || ''; track.id = groups[groupId] = groups[groupId] || 0; groups[groupId]++; }); } var FragmentState = { NOT_LOADED: "NOT_LOADED", APPENDING: "APPENDING", PARTIAL: "PARTIAL", OK: "OK" }; class FragmentTracker { constructor(hls) { this.activePartLists = Object.create(null); this.endListFragments = Object.create(null); this.fragments = Object.create(null); this.timeRanges = Object.create(null); this.bufferPadding = 0.2; this.hls = void 0; this.hasGaps = false; this.hls = hls; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); } destroy() { this._unregisterListeners(); // @ts-ignore this.fragments = // @ts-ignore this.activePartLists = // @ts-ignore this.endListFragments = this.timeRanges = null; } /** * Return a Fragment or Part with an appended range that matches the position and levelType * Otherwise, return null */ getAppendedFrag(position, levelType) { const activeParts = this.activePartLists[levelType]; if (activeParts) { for (let i = activeParts.length; i--;) { const activePart = activeParts[i]; if (!activePart) { break; } const appendedPTS = activePart.end; if (activePart.start <= position && appendedPTS !== null && position <= appendedPTS) { return activePart; } } } return this.getBufferedFrag(position, levelType); } /** * Return a buffered Fragment that matches the position and levelType. * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted). * If not found any Fragment, return null */ getBufferedFrag(position, levelType) { const { fragments } = this; const keys = Object.keys(fragments); for (let i = keys.length; i--;) { const fragmentEntity = fragments[keys[i]]; if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) { const frag = fragmentEntity.body; if (frag.start <= position && position <= frag.end) { return frag; } } } return null; } /** * Partial fragments effected by coded frame eviction will be removed * The browser will unload parts of the buffer to free up memory for new buffer data * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable) */ detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) { if (this.timeRanges) { this.timeRanges[elementaryStream] = timeRange; } // Check if any flagged fragments have been unloaded // excluding anything newer than appendedPartSn const appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1; Object.keys(this.fragments).forEach(key => { const fragmentEntity = this.fragments[key]; if (!fragmentEntity) { return; } if (appendedPartSn >= fragmentEntity.body.sn) { return; } if (!fragmentEntity.buffered && !fragmentEntity.loaded) { if (fragmentEntity.body.type === playlistType) { this.removeFragment(fragmentEntity.body); } return; } const esData = fragmentEntity.range[elementaryStream]; if (!esData) { return; } esData.time.some(time => { const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange); if (isNotBuffered) { // Unregister partial fragment as it needs to load again to be reused this.removeFragment(fragmentEntity.body); } return isNotBuffered; }); }); } /** * Checks if the fragment passed in is loaded in the buffer properly * Partially loaded fragments will be registered as a partial fragment */ detectPartialFragments(data) { const timeRanges = this.timeRanges; const { frag, part } = data; if (!timeRanges || frag.sn === 'initSegment') { return; } const fragKey = getFragmentKey(frag); const fragmentEntity = this.fragments[fragKey]; if (!fragmentEntity || fragmentEntity.buffered && frag.gap) { return; } const isFragHint = !frag.relurl; Object.keys(timeRanges).forEach(elementaryStream => { const streamInfo = frag.elementaryStreams[elementaryStream]; if (!streamInfo) { return; } const timeRange = timeRanges[elementaryStream]; const partial = isFragHint || streamInfo.partial === true; fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, part, partial, timeRange); }); fragmentEntity.loaded = null; if (Object.keys(fragmentEntity.range).length) { fragmentEntity.buffered = true; const endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList; if (endList) { this.endListFragments[fragmentEntity.body.type] = fragmentEntity; } if (!isPartial(fragmentEntity)) { // Remove older fragment parts from lookup after frag is tracked as buffered this.removeParts(frag.sn - 1, frag.type); } } else { // remove fragment if nothing was appended this.removeFragment(fragmentEntity.body); } } removeParts(snToKeep, levelType) { const activeParts = this.activePartLists[levelType]; if (!activeParts) { return; } this.activePartLists[levelType] = activeParts.filter(part => part.fragment.sn >= snToKeep); } fragBuffered(frag, force) { const fragKey = getFragmentKey(frag); let fragmentEntity = this.fragments[fragKey]; if (!fragmentEntity && force) { fragmentEntity = this.fragments[fragKey] = { body: frag, appendedPTS: null, loaded: null, buffered: false, range: Object.create(null) }; if (frag.gap) { this.hasGaps = true; } } if (fragmentEntity) { fragmentEntity.loaded = null; fragmentEntity.buffered = true; } } getBufferedTimes(fragment, part, partial, timeRange) { const buffered = { time: [], partial }; const startPTS = fragment.start; const endPTS = fragment.end; const minEndPTS = fragment.minEndPTS || endPTS; const maxStartPTS = fragment.maxStartPTS || startPTS; for (let i = 0; i < timeRange.length; i++) { const startTime = timeRange.start(i) - this.bufferPadding; const endTime = timeRange.end(i) + this.bufferPadding; if (maxStartPTS >= startTime && minEndPTS <= endTime) { // Fragment is entirely contained in buffer // No need to check the other timeRange times since it's completely playable buffered.time.push({ startPTS: Math.max(startPTS, timeRange.start(i)), endPTS: Math.min(endPTS, timeRange.end(i)) }); break; } else if (startPTS < endTime && endPTS > startTime) { buffered.partial = true; // Check for intersection with buffer // Get playable sections of the fragment buffered.time.push({ startPTS: Math.max(startPTS, timeRange.start(i)), endPTS: Math.min(endPTS, timeRange.end(i)) }); } else if (endPTS <= startTime) { // No need to check the rest of the timeRange as it is in order break; } } return buffered; } /** * Gets the partial fragment for a certain time */ getPartialFragment(time) { let bestFragment = null; let timePadding; let startTime; let endTime; let bestOverlap = 0; const { bufferPadding, fragments } = this; Object.keys(fragments).forEach(key => { const fragmentEntity = fragments[key]; if (!fragmentEntity) { return; } if (isPartial(fragmentEntity)) { startTime = fragmentEntity.body.start - bufferPadding; endTime = fragmentEntity.body.end + bufferPadding; if (time >= startTime && time <= endTime) { // Use the fragment that has the most padding from start and end time timePadding = Math.min(time - startTime, endTime - time); if (bestOverlap <= timePadding) { bestFragment = fragmentEntity.body; bestOverlap = timePadding; } } } }); return bestFragment; } isEndListAppended(type) { const lastFragmentEntity = this.endListFragments[type]; return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity)); } getState(fragment) { const fragKey = getFragmentKey(fragment); const fragmentEntity = this.fragments[fragKey]; if (fragmentEntity) { if (!fragmentEntity.buffered) { return FragmentState.APPENDING; } else if (isPartial(fragmentEntity)) { return FragmentState.PARTIAL; } else { return FragmentState.OK; } } return FragmentState.NOT_LOADED; } isTimeBuffered(startPTS, endPTS, timeRange) { let startTime; let endTime; for (let i = 0; i < timeRange.length; i++) { startTime = timeRange.start(i) - this.bufferPadding; endTime = timeRange.end(i) + this.bufferPadding; if (startPTS >= startTime && endPTS <= endTime) { return true; } if (endPTS <= startTime) { // No need to check the rest of the timeRange as it is in order return false; } } return false; } onFragLoaded(event, data) { const { frag, part } = data; // don't track initsegment (for which sn is not a number) // don't track frags used for bitrateTest, they're irrelevant. if (frag.sn === 'initSegment' || frag.bitrateTest) { return; } // Fragment entity `loaded` FragLoadedData is null when loading parts const loaded = part ? null : data; const fragKey = getFragmentKey(frag); this.fragments[fragKey] = { body: frag, appendedPTS: null, loaded, buffered: false, range: Object.create(null) }; } onBufferAppended(event, data) { const { frag, part, timeRanges } = data; if (frag.sn === 'initSegment') { return; } const playlistType = frag.type; if (part) { let activeParts = this.activePartLists[playlistType]; if (!activeParts) { this.activePartLists[playlistType] = activeParts = []; } activeParts.push(part); } // Store the latest timeRanges loaded in the buffer this.timeRanges = timeRanges; Object.keys(timeRanges).forEach(elementaryStream => { const timeRange = timeRanges[elementaryStream]; this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part); }); } onFragBuffered(event, data) { this.detectPartialFragments(data); } hasFragment(fragment) { const fragKey = getFragmentKey(fragment); return !!this.fragments[fragKey]; } hasParts(type) { var _this$activePartLists; return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length); } removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) { if (withGapOnly && !this.hasGaps) { return; } Object.keys(this.fragments).forEach(key => { const fragmentEntity = this.fragments[key]; if (!fragmentEntity) { return; } const frag = fragmentEntity.body; if (frag.type !== playlistType || withGapOnly && !frag.gap) { return; } if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) { this.removeFragment(frag); } }); } removeFragment(fragment) { const fragKey = getFragmentKey(fragment); fragment.stats.loaded = 0; fragment.clearElementaryStreamInfo(); const activeParts = this.activePartLists[fragment.type]; if (activeParts) { const snToRemove = fragment.sn; this.activePartLists[fragment.type] = activeParts.filter(part => part.fragment.sn !== snToRemove); } delete this.fragments[fragKey]; if (fragment.endList) { delete this.endListFragments[fragment.type]; } } removeAllFragments() { this.fragments = Object.create(null); this.endListFragments = Object.create(null); this.activePartLists = Object.create(null); this.hasGaps = false; } } function isPartial(fragmentEntity) { var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3; return fragmentEntity.buffered && (fragmentEntity.body.gap || ((_fragmentEntity$range = fragmentEntity.range.video) == null ? void 0 : _fragmentEntity$range.partial) || ((_fragmentEntity$range2 = fragmentEntity.range.audio) == null ? void 0 : _fragmentEntity$range2.partial) || ((_fragmentEntity$range3 = fragmentEntity.range.audiovideo) == null ? void 0 : _fragmentEntity$range3.partial)); } function getFragmentKey(fragment) { return `${fragment.type}_${fragment.level}_${fragment.urlId}_${fragment.sn}`; } const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb class FragmentLoader { constructor(config) { this.config = void 0; this.loader = null; this.partLoadTimeout = -1; this.config = config; } destroy() { if (this.loader) { this.loader.destroy(); this.loader = null; } } abort() { if (this.loader) { // Abort the loader for current fragment. Only one may load at any given time this.loader.abort(); } } load(frag, onProgress) { const url = frag.url; if (!url) { return Promise.reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, error: new Error(`Fragment does not have a ${url ? 'part list' : 'url'}`), networkDetails: null })); } this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap) { if (frag.tagList.some(tags => tags[0] === 'GAP')) { reject(createGapLoadError(frag)); return; } else { // Reset temporary treatment as GAP tag frag.gap = false; } } const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); const loaderContext = createLoaderContext(frag); const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE }; // Assign frag stats to the loader's stats reference frag.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); let payload = response.data; if (context.resetIV && frag.decryptdata) { frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16)); payload = payload.slice(16); } resolve({ frag, part: null, payload, networkDetails }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, response: _objectSpread2({ url, data: undefined }, response), error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats })); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, error: new Error('Aborted'), networkDetails, stats })); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats })); }, onProgress: (stats, context, data, networkDetails) => { if (onProgress) { onProgress({ frag, part: null, payload: data, networkDetails }); } } }); }); } loadPart(frag, part, onProgress) { this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap || part.gap) { reject(createGapLoadError(frag, part)); return; } const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); const loaderContext = createLoaderContext(frag, part); // Should we define another load policy for parts? const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: MIN_CHUNK_SIZE }; // Assign part stats to the loader's stats reference part.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); this.updateStatsFromPart(frag, part); const partLoadedData = { frag, part, payload: response.data, networkDetails }; onProgress(partLoadedData); resolve(partLoadedData); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, part, response: _objectSpread2({ url: loaderContext.url, data: undefined }, response), error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats })); }, onAbort: (stats, context, networkDetails) => { frag.stats.aborted = part.stats.aborted; this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, part, error: new Error('Aborted'), networkDetails, stats })); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, part, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats })); } }); }); } updateStatsFromPart(frag, part) { const fragStats = frag.stats; const partStats = part.stats; const partTotal = partStats.total; fragStats.loaded += partStats.loaded; if (partTotal) { const estTotalParts = Math.round(frag.duration / part.duration); const estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts); const estRemainingParts = estTotalParts - estLoadedParts; const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts); fragStats.total = fragStats.loaded + estRemainingBytes; } else { fragStats.total = Math.max(fragStats.loaded, fragStats.total); } const fragLoading = fragStats.loading; const partLoading = partStats.loading; if (fragLoading.start) { // add to fragment loader latency fragLoading.first += partLoading.first - partLoading.start; } else { fragLoading.start = partLoading.start; fragLoading.first = partLoading.first; } fragLoading.end = partLoading.end; } resetLoader(frag, loader) { frag.loader = null; if (this.loader === loader) { self.clearTimeout(this.partLoadTimeout); this.loader = null; } loader.destroy(); } } function createLoaderContext(frag, part = null) { const segment = part || frag; const loaderContext = { frag, part, responseType: 'arraybuffer', url: segment.url, headers: {}, rangeStart: 0, rangeEnd: 0 }; const start = segment.byteRangeStartOffset; const end = segment.byteRangeEndOffset; if (isFiniteNumber(start) && isFiniteNumber(end)) { var _frag$decryptdata; let byteRangeStart = start; let byteRangeEnd = end; if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') { // MAP segment encrypted with method 'AES-128', when served with HTTP Range, // has the unencrypted size specified in the range. // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6 const fragmentLen = end - start; if (fragmentLen % 16) { byteRangeEnd = end + (16 - fragmentLen % 16); } if (start !== 0) { loaderContext.resetIV = true; byteRangeStart = start - 16; } } loaderContext.rangeStart = byteRangeStart; loaderContext.rangeEnd = byteRangeEnd; } return loaderContext; } function createGapLoadError(frag, part) { const error = new Error(`GAP ${frag.gap ? 'tag' : 'attribute'} found`); const errorData = { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_GAP, fatal: false, frag, error, networkDetails: null }; if (part) { errorData.part = part; } (part ? part : frag).stats.aborted = true; return new LoadError(errorData); } class LoadError extends Error { constructor(data) { super(data.error.message); this.data = void 0; this.data = data; } } class KeyLoader { constructor(config) { this.config = void 0; this.keyUriToKeyInfo = {}; this.emeController = null; this.config = config; } abort(type) { for (const uri in this.keyUriToKeyInfo) { const loader = this.keyUriToKeyInfo[uri].loader; if (loader) { if (type && type !== loader.context.frag.type) { return; } loader.abort(); } } } detach() { for (const uri in this.keyUriToKeyInfo) { const keyInfo = this.keyUriToKeyInfo[uri]; // Remove cached EME keys on detach if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) { delete this.keyUriToKeyInfo[uri]; } } } destroy() { this.detach(); for (const uri in this.keyUriToKeyInfo) { const loader = this.keyUriToKeyInfo[uri].loader; if (loader) { loader.destroy(); } } this.keyUriToKeyInfo = {}; } createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) { return new LoadError({ type: ErrorTypes.NETWORK_ERROR, details, fatal: false, frag, response, error, networkDetails }); } loadClear(loadingFrag, encryptedFragments) { if (this.emeController && this.config.emeEnabled) { // access key-system with nearest key on start (loaidng frag is unencrypted) const { sn, cc } = loadingFrag; for (let i = 0; i < encryptedFragments.length; i++) { const frag = encryptedFragments[i]; if (cc <= frag.cc && (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)) { this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => { frag.setKeyFormat(keySystemFormat); }); break; } } } } load(frag) { if (!frag.decryptdata && frag.encrypted && this.emeController) { // Multiple keys, but none selected, resolve in eme-controller return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => { return this.loadInternal(frag, keySystemFormat); }); } return this.loadInternal(frag); } loadInternal(frag, keySystemFormat) { var _keyInfo, _keyInfo2; if (keySystemFormat) { frag.setKeyFormat(keySystemFormat); } const decryptdata = frag.decryptdata; if (!decryptdata) { const error = new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` : 'Missing decryption data on fragment in onKeyLoading'); return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error)); } const uri = decryptdata.uri; if (!uri) { return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`))); } let keyInfo = this.keyUriToKeyInfo[uri]; if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) { decryptdata.key = keyInfo.decryptdata.key; return Promise.resolve({ frag, keyInfo }); } // Return key load promise as long as it does not have a mediakey session with an unusable key status if ((_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) { var _keyInfo$mediaKeySess; switch ((_keyInfo$mediaKeySess = keyInfo.mediaKeySessionContext) == null ? void 0 : _keyInfo$mediaKeySess.keyStatus) { case undefined: case 'status-pending': case 'usable': case 'usable-in-future': return keyInfo.keyLoadPromise.then(keyLoadedData => { // Return the correct fragment with updated decryptdata key and loaded keyInfo decryptdata.key = keyLoadedData.keyInfo.decryptdata.key; return { frag, keyInfo }; }); } // If we have a key session and status and it is not pending or usable, continue // This will go back to the eme-controller for expired keys to get a new keyLoadPromise } // Load the key or return the loading promise keyInfo = this.keyUriToKeyInfo[uri] = { decryptdata, keyLoadPromise: null, loader: null, mediaKeySessionContext: null }; switch (decryptdata.method) { case 'ISO-23001-7': case 'SAMPLE-AES': case 'SAMPLE-AES-CENC': case 'SAMPLE-AES-CTR': if (decryptdata.keyFormat === 'identity') { // loadKeyHTTP handles http(s) and data URLs return this.loadKeyHTTP(keyInfo, frag); } return this.loadKeyEME(keyInfo, frag); case 'AES-128': return this.loadKeyHTTP(keyInfo, frag); default: return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`))); } } loadKeyEME(keyInfo, frag) { const keyLoadedData = { frag, keyInfo }; if (this.emeController && this.config.emeEnabled) { const keySessionContextPromise = this.emeController.loadKey(keyLoadedData); if (keySessionContextPromise) { return (keyInfo.keyLoadPromise = keySessionContextPromise.then(keySessionContext => { keyInfo.mediaKeySessionContext = keySessionContext; return keyLoadedData; })).catch(error => { // Remove promise for license renewal or retry keyInfo.keyLoadPromise = null; throw error; }); } } return Promise.resolve(keyLoadedData); } loadKeyHTTP(keyInfo, frag) { const config = this.config; const Loader = config.loader; const keyLoader = new Loader(config); frag.keyLoader = keyInfo.loader = keyLoader; return keyInfo.keyLoadPromise = new Promise((resolve, reject) => { const loaderContext = { keyInfo, frag, responseType: 'arraybuffer', url: keyInfo.decryptdata.uri }; // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times, // key-loader will trigger an error and rely on stream-controller to handle retry logic. // this will also align retry logic with fragment-loader const loadPolicy = config.keyLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { const { frag, keyInfo, url: uri } = context; if (!frag.decryptdata || keyInfo !== this.keyUriToKeyInfo[uri]) { return reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails)); } keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data); // detach fragment key loader on load success frag.keyLoader = null; keyInfo.loader = null; resolve({ frag, keyInfo }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({ url: loaderContext.url, data: undefined }, response))); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails)); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails)); } }; keyLoader.load(loaderContext, loaderConfig, loaderCallbacks); }); } resetLoader(context) { const { frag, keyInfo, url: uri } = context; const loader = keyInfo.loader; if (frag.keyLoader === loader) { frag.keyLoader = null; keyInfo.loader = null; } delete this.keyUriToKeyInfo[uri]; if (loader) { loader.destroy(); } } } /** * @ignore * Sub-class specialization of EventHandler base class. * * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop, * scheduled asynchroneously, avoiding recursive calls in the same tick. * * The task itself is implemented in `doTick`. It can be requested and called for single execution * using the `tick` method. * * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick", * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly. * * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`, * and cancelled with `clearNextTick`. * * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`). * * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine. * * Further explanations: * * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks. * * When the task execution (`tick` method) is called in re-entrant way this is detected and * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo). */ class TaskLoop { constructor() { this._boundTick = void 0; this._tickTimer = null; this._tickInterval = null; this._tickCallCount = 0; this._boundTick = this.tick.bind(this); } destroy() { this.onHandlerDestroying(); this.onHandlerDestroyed(); } onHandlerDestroying() { // clear all timers before unregistering from event bus this.clearNextTick(); this.clearInterval(); } onHandlerDestroyed() {} hasInterval() { return !!this._tickInterval; } hasNextTick() { return !!this._tickTimer; } /** * @param millis - Interval time (ms) * @eturns True when interval has been scheduled, false when already scheduled (no effect) */ setInterval(millis) { if (!this._tickInterval) { this._tickCallCount = 0; this._tickInterval = self.setInterval(this._boundTick, millis); return true; } return false; } /** * @returns True when interval was cleared, false when none was set (no effect) */ clearInterval() { if (this._tickInterval) { self.clearInterval(this._tickInterval); this._tickInterval = null; return true; } return false; } /** * @returns True when timeout was cleared, false when none was set (no effect) */ clearNextTick() { if (this._tickTimer) { self.clearTimeout(this._tickTimer); this._tickTimer = null; return true; } return false; } /** * Will call the subclass doTick implementation in this main loop tick * or in the next one (via setTimeout(,0)) in case it has already been called * in this tick (in case this is a re-entrant call). */ tick() { this._tickCallCount++; if (this._tickCallCount === 1) { this.doTick(); // re-entrant call to tick from previous doTick call stack // -> schedule a call on the next main loop iteration to process this task processing request if (this._tickCallCount > 1) { // make sure only one timer exists at any time at max this.tickImmediate(); } this._tickCallCount = 0; } } tickImmediate() { this.clearNextTick(); this._tickTimer = self.setTimeout(this._boundTick, 0); } /** * For subclass to implement task logic * @abstract */ doTick() {} } /** * Provides methods dealing with buffer length retrieval for example. * * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property. * * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered */ const noopBuffered = { length: 0, start: () => 0, end: () => 0 }; class BufferHelper { /** * Return true if `media`'s buffered include `position` */ static isBuffered(media, position) { try { if (media) { const buffered = BufferHelper.getBuffered(media); for (let i = 0; i < buffered.length; i++) { if (position >= buffered.start(i) && position <= buffered.end(i)) { return true; } } } } catch (error) { // this is to catch // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': // This SourceBuffer has been removed from the parent media source } return false; } static bufferInfo(media, pos, maxHoleDuration) { try { if (media) { const vbuffered = BufferHelper.getBuffered(media); const buffered = []; let i; for (i = 0; i < vbuffered.length; i++) { buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) }); } return this.bufferedInfo(buffered, pos, maxHoleDuration); } } catch (error) { // this is to catch // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': // This SourceBuffer has been removed from the parent media source } return { len: 0, start: pos, end: pos, nextStart: undefined }; } static bufferedInfo(buffered, pos, maxHoleDuration) { pos = Math.max(0, pos); // sort on buffer.start/smaller end (IE does not always return sorted buffered range) buffered.sort(function (a, b) { const diff = a.start - b.start; if (diff) { return diff; } else { return b.end - a.end; } }); let buffered2 = []; if (maxHoleDuration) { // there might be some small holes between buffer time range // consider that holes smaller than maxHoleDuration are irrelevant and build another // buffer time range representations that discards those holes for (let i = 0; i < buffered.length; i++) { const buf2len = buffered2.length; if (buf2len) { const buf2end = buffered2[buf2len - 1].end; // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative) if (buffered[i].start - buf2end < maxHoleDuration) { // merge overlapping time ranges // update lastRange.end only if smaller than item.end // e.g. [ 1, 15] with [ 2,8] => [ 1,15] (no need to modify lastRange.end) // whereas [ 1, 8] with [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15]) if (buffered[i].end > buf2end) { buffered2[buf2len - 1].end = buffered[i].end; } } else { // big hole buffered2.push(buffered[i]); } } else { // first value buffered2.push(buffered[i]); } } } else { buffered2 = buffered; } let bufferLen = 0; // bufferStartNext can possibly be undefined based on the conditional logic below let bufferStartNext; // bufferStart and bufferEnd are buffer boundaries around current video position let bufferStart = pos; let bufferEnd = pos; for (let i = 0; i < buffered2.length; i++) { const start = buffered2[i].start; const end = buffered2[i].end; // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i)); if (pos + maxHoleDuration >= start && pos < end) { // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length bufferStart = start; bufferEnd = end; bufferLen = bufferEnd - pos; } else if (pos + maxHoleDuration < start) { bufferStartNext = start; break; } } return { len: bufferLen, start: bufferStart || 0, end: bufferEnd || 0, nextStart: bufferStartNext }; } /** * Safe method to get buffered property. * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource */ static getBuffered(media) { try { return media.buffered; } catch (e) { logger.log('failed to get media.buffered', e); return noopBuffered; } } } class ChunkMetadata { constructor(level, sn, id, size = 0, part = -1, partial = false) { this.level = void 0; this.sn = void 0; this.part = void 0; this.id = void 0; this.size = void 0; this.partial = void 0; this.transmuxing = getNewPerformanceTiming(); this.buffering = { audio: getNewPerformanceTiming(), video: getNewPerformanceTiming(), audiovideo: getNewPerformanceTiming() }; this.level = level; this.sn = sn; this.id = id; this.size = size; this.part = part; this.partial = partial; } } function getNewPerformanceTiming() { return { start: 0, executeStart: 0, executeEnd: 0, end: 0 }; } function findFirstFragWithCC(fragments, cc) { let firstFrag = null; for (let i = 0, len = fragments.length; i < len; i++) { const currentFrag = fragments[i]; if (currentFrag && currentFrag.cc === cc) { firstFrag = currentFrag; break; } } return firstFrag; } function shouldAlignOnDiscontinuities(lastFrag, lastLevel, details) { if (lastLevel.details) { if (details.endCC > details.startCC || lastFrag && lastFrag.cc < details.startCC) { return true; } } return false; } // Find the first frag in the previous level which matches the CC of the first frag of the new level function findDiscontinuousReferenceFrag(prevDetails, curDetails, referenceIndex = 0) { const prevFrags = prevDetails.fragments; const curFrags = curDetails.fragments; if (!curFrags.length || !prevFrags.length) { logger.log('No fragments to align'); return; } const prevStartFrag = findFirstFragWithCC(prevFrags, curFrags[0].cc); if (!prevStartFrag || prevStartFrag && !prevStartFrag.startPTS) { logger.log('No frag in previous level to align on'); return; } return prevStartFrag; } function adjustFragmentStart(frag, sliding) { if (frag) { const start = frag.start + sliding; frag.start = frag.startPTS = start; frag.endPTS = start + frag.duration; } } function adjustSlidingStart(sliding, details) { // Update segments const fragments = details.fragments; for (let i = 0, len = fragments.length; i < len; i++) { adjustFragmentStart(fragments[i], sliding); } // Update LL-HLS parts at the end of the playlist if (details.fragmentHint) { adjustFragmentStart(details.fragmentHint, sliding); } details.alignedSliding = true; } /** * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a * contiguous stream with the last fragments. * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time * and an extra download. * @param lastFrag * @param lastLevel * @param details */ function alignStream(lastFrag, lastLevel, details) { if (!lastLevel) { return; } alignDiscontinuities(lastFrag, details, lastLevel); if (!details.alignedSliding && lastLevel.details) { // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level. // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same // discontinuity sequence. alignPDT(details, lastLevel.details); } if (!details.alignedSliding && lastLevel.details && !details.skippedSegments) { // Try to align on sn so that we pick a better start fragment. // Do not perform this on playlists with delta updates as this is only to align levels on switch // and adjustSliding only adjusts fragments after skippedSegments. adjustSliding(lastLevel.details, details); } } /** * Computes the PTS if a new level's fragments using the PTS of a fragment in the last level which shares the same * discontinuity sequence. * @param lastFrag - The last Fragment which shares the same discontinuity sequence * @param lastLevel - The details of the last loaded level * @param details - The details of the new level */ function alignDiscontinuities(lastFrag, details, lastLevel) { if (shouldAlignOnDiscontinuities(lastFrag, lastLevel, details)) { const referenceFrag = findDiscontinuousReferenceFrag(lastLevel.details, details); if (referenceFrag && isFiniteNumber(referenceFrag.start)) { logger.log(`Adjusting PTS using last level due to CC increase within current level ${details.url}`); adjustSlidingStart(referenceFrag.start, details); } } } /** * Computes the PTS of a new level's fragments using the difference in Program Date Time from the last level. * @param details - The details of the new level * @param lastDetails - The details of the last loaded level */ function alignPDT(details, lastDetails) { // This check protects the unsafe "!" usage below for null program date time access. if (!lastDetails.fragments.length || !details.hasProgramDateTime || !lastDetails.hasProgramDateTime) { return; } // if last level sliding is 1000 and its first frag PROGRAM-DATE-TIME is 2017-08-20 1:10:00 AM // and if new details first frag PROGRAM DATE-TIME is 2017-08-20 1:10:08 AM // then we can deduce that playlist B sliding is 1000+8 = 1008s const lastPDT = lastDetails.fragments[0].programDateTime; // hasProgramDateTime check above makes this safe. const newPDT = details.fragments[0].programDateTime; // date diff is in ms. frag.start is in seconds const sliding = (newPDT - lastPDT) / 1000 + lastDetails.fragments[0].start; if (sliding && isFiniteNumber(sliding)) { logger.log(`Adjusting PTS using programDateTime delta ${newPDT - lastPDT}ms, sliding:${sliding.toFixed(3)} ${details.url} `); adjustSlidingStart(sliding, details); } } /** * Ensures appropriate time-alignment between renditions based on PDT. Unlike `alignPDT`, which adjusts * the timeline based on the delta between PDTs of the 0th fragment of two playlists/`LevelDetails`, * this function assumes the timelines represented in `refDetails` are accurate, including the PDTs, * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation * times/timelines of `details` accordingly. * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks, * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should * be consistent across playlists, per the HLS spec. * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition). * @param refDetails - The details of the reference rendition with start and PDT times for alignment. */ function alignMediaPlaylistByPDT(details, refDetails) { if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) { return; } const fragments = details.fragments; const refFragments = refDetails.fragments; if (!fragments.length || !refFragments.length) { return; } // Calculate a delta to apply to all fragments according to the delta in PDT times and start times // of a fragment in the reference details, and a fragment in the target details of the same discontinuity. // If a fragment of the same discontinuity was not found use the middle fragment of both. const middleFrag = Math.round(refFragments.length / 2) - 1; const refFrag = refFragments[middleFrag]; const frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.round(fragments.length / 2) - 1]; const refPDT = refFrag.programDateTime; const targetPDT = frag.programDateTime; if (refPDT === null || targetPDT === null) { return; } const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start); adjustSlidingStart(delta, details); } class AESCrypto { constructor(subtle, iv) { this.subtle = void 0; this.aesIV = void 0; this.subtle = subtle; this.aesIV = iv; } decrypt(data, key) { return this.subtle.decrypt({ name: 'AES-CBC', iv: this.aesIV }, key, data); } } class FastAESKey { constructor(subtle, key) { this.subtle = void 0; this.key = void 0; this.subtle = subtle; this.key = key; } expandKey() { return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, ['encrypt', 'decrypt']); } } // PKCS7 function removePadding(array) { const outputBytes = array.byteLength; const paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1); if (paddingBytes) { return sliceUint8(array, 0, outputBytes - paddingBytes); } return array; } class AESDecryptor { constructor() { this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; this.sBox = new Uint32Array(256); this.invSBox = new Uint32Array(256); this.key = new Uint32Array(0); this.ksRows = 0; this.keySize = 0; this.keySchedule = void 0; this.invKeySchedule = void 0; this.initTable(); } // Using view.getUint32() also swaps the byte order. uint8ArrayToUint32Array_(arrayBuffer) { const view = new DataView(arrayBuffer); const newArray = new Uint32Array(4); for (let i = 0; i < 4; i++) { newArray[i] = view.getUint32(i * 4); } return newArray; } initTable() { const sBox = this.sBox; const invSBox = this.invSBox; const subMix = this.subMix; const subMix0 = subMix[0]; const subMix1 = subMix[1]; const subMix2 = subMix[2]; const subMix3 = subMix[3]; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; const d = new Uint32Array(256); let x = 0; let xi = 0; let i = 0; for (i = 0; i < 256; i++) { if (i < 128) { d[i] = i << 1; } else { d[i] = i << 1 ^ 0x11b; } } for (i = 0; i < 256; i++) { let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4; sx = sx >>> 8 ^ sx & 0xff ^ 0x63; sBox[x] = sx; invSBox[sx] = x; // Compute multiplication const x2 = d[x]; const x4 = d[x2]; const x8 = d[x4]; // Compute sub/invSub bytes, mix columns tables let t = d[sx] * 0x101 ^ sx * 0x1010100; subMix0[x] = t << 24 | t >>> 8; subMix1[x] = t << 16 | t >>> 16; subMix2[x] = t << 8 | t >>> 24; subMix3[x] = t; // Compute inv sub bytes, inv mix columns tables t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; invSubMix0[sx] = t << 24 | t >>> 8; invSubMix1[sx] = t << 16 | t >>> 16; invSubMix2[sx] = t << 8 | t >>> 24; invSubMix3[sx] = t; // Compute next counter if (!x) { x = xi = 1; } else { x = x2 ^ d[d[d[x8 ^ x2]]]; xi ^= d[d[xi]]; } } } expandKey(keyBuffer) { // convert keyBuffer to Uint32Array const key = this.uint8ArrayToUint32Array_(keyBuffer); let sameKey = true; let offset = 0; while (offset < key.length && sameKey) { sameKey = key[offset] === this.key[offset]; offset++; } if (sameKey) { return; } this.key = key; const keySize = this.keySize = key.length; if (keySize !== 4 && keySize !== 6 && keySize !== 8) { throw new Error('Invalid aes key size=' + keySize); } const ksRows = this.ksRows = (keySize + 6 + 1) * 4; let ksRow; let invKsRow; const keySchedule = this.keySchedule = new Uint32Array(ksRows); const invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows); const sbox = this.sBox; const rcon = this.rcon; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; let prev; let t; for (ksRow = 0; ksRow < ksRows; ksRow++) { if (ksRow < keySize) { prev = keySchedule[ksRow] = key[ksRow]; continue; } t = prev; if (ksRow % keySize === 0) { // Rot word t = t << 8 | t >>> 24; // Sub word t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; // Mix Rcon t ^= rcon[ksRow / keySize | 0] << 24; } else if (keySize > 6 && ksRow % keySize === 4) { // Sub word t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; } keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0; } for (invKsRow = 0; invKsRow < ksRows; invKsRow++) { ksRow = ksRows - invKsRow; if (invKsRow & 3) { t = keySchedule[ksRow]; } else { t = keySchedule[ksRow - 4]; } if (invKsRow < 4 || ksRow <= 4) { invKeySchedule[invKsRow] = t; } else { invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]]; } invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0; } } // Adding this as a method greatly improves performance. networkToHostOrderSwap(word) { return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; } decrypt(inputArrayBuffer, offset, aesIV) { const nRounds = this.keySize + 6; const invKeySchedule = this.invKeySchedule; const invSBOX = this.invSBox; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; const initVector = this.uint8ArrayToUint32Array_(aesIV); let initVector0 = initVector[0]; let initVector1 = initVector[1]; let initVector2 = initVector[2]; let initVector3 = initVector[3]; const inputInt32 = new Int32Array(inputArrayBuffer); const outputInt32 = new Int32Array(inputInt32.length); let t0, t1, t2, t3; let s0, s1, s2, s3; let inputWords0, inputWords1, inputWords2, inputWords3; let ksRow, i; const swapWord = this.networkToHostOrderSwap; while (offset < inputInt32.length) { inputWords0 = swapWord(inputInt32[offset]); inputWords1 = swapWord(inputInt32[offset + 1]); inputWords2 = swapWord(inputInt32[offset + 2]); inputWords3 = swapWord(inputInt32[offset + 3]); s0 = inputWords0 ^ invKeySchedule[0]; s1 = inputWords3 ^ invKeySchedule[1]; s2 = inputWords2 ^ invKeySchedule[2]; s3 = inputWords1 ^ invKeySchedule[3]; ksRow = 4; // Iterate through the rounds of decryption for (i = 1; i < nRounds; i++) { t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow]; t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; // Update state s0 = t0; s1 = t1; s2 = t2; s3 = t3; ksRow = ksRow + 4; } // Shift rows, sub bytes, add round key t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow]; t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; // Write outputInt32[offset] = swapWord(t0 ^ initVector0); outputInt32[offset + 1] = swapWord(t3 ^ initVector1); outputInt32[offset + 2] = swapWord(t2 ^ initVector2); outputInt32[offset + 3] = swapWord(t1 ^ initVector3); // reset initVector to last 4 unsigned int initVector0 = inputWords0; initVector1 = inputWords1; initVector2 = inputWords2; initVector3 = inputWords3; offset = offset + 4; } return outputInt32.buffer; } } const CHUNK_SIZE = 16; // 16 bytes, 128 bits class Decrypter { constructor(config, { removePKCS7Padding = true } = {}) { this.logEnabled = true; this.removePKCS7Padding = void 0; this.subtle = null; this.softwareDecrypter = null; this.key = null; this.fastAesKey = null; this.remainderData = null; this.currentIV = null; this.currentResult = null; this.useSoftware = void 0; this.useSoftware = config.enableSoftwareAES; this.removePKCS7Padding = removePKCS7Padding; // built in decryptor expects PKCS7 padding if (removePKCS7Padding) { try { const browserCrypto = self.crypto; if (browserCrypto) { this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; } } catch (e) { /* no-op */ } } if (this.subtle === null) { this.useSoftware = true; } } destroy() { this.subtle = null; this.softwareDecrypter = null; this.key = null; this.fastAesKey = null; this.remainderData = null; this.currentIV = null; this.currentResult = null; } isSync() { return this.useSoftware; } flush() { const { currentResult, remainderData } = this; if (!currentResult || remainderData) { this.reset(); return null; } const data = new Uint8Array(currentResult); this.reset(); if (this.removePKCS7Padding) { return removePadding(data); } return data; } reset() { this.currentResult = null; this.currentIV = null; this.remainderData = null; if (this.softwareDecrypter) { this.softwareDecrypter = null; } } decrypt(data, key, iv) { if (this.useSoftware) { return new Promise((resolve, reject) => { this.softwareDecrypt(new Uint8Array(data), key, iv); const decryptResult = this.flush(); if (decryptResult) { resolve(decryptResult.buffer); } else { reject(new Error('[softwareDecrypt] Failed to decrypt data')); } }); } return this.webCryptoDecrypt(new Uint8Array(data), key, iv); } // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached // data is handled in the flush() call softwareDecrypt(data, key, iv) { const { currentIV, currentResult, remainderData } = this; this.logOnce('JS AES decrypt'); // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached // the end on flush(), but by that time we have already received all bytes for the segment. // Progressive decryption does not work with WebCrypto if (remainderData) { data = appendUint8Array(remainderData, data); this.remainderData = null; } // Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes) const currentChunk = this.getValidChunk(data); if (!currentChunk.length) { return null; } if (currentIV) { iv = currentIV; } let softwareDecrypter = this.softwareDecrypter; if (!softwareDecrypter) { softwareDecrypter = this.softwareDecrypter = new AESDecryptor(); } softwareDecrypter.expandKey(key); const result = currentResult; this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv); this.currentIV = sliceUint8(currentChunk, -16).buffer; if (!result) { return null; } return result; } webCryptoDecrypt(data, key, iv) { const subtle = this.subtle; if (this.key !== key || !this.fastAesKey) { this.key = key; this.fastAesKey = new FastAESKey(subtle, key); } return this.fastAesKey.expandKey().then(aesKey => { // decrypt using web crypto if (!subtle) { return Promise.reject(new Error('web crypto not initialized')); } this.logOnce('WebCrypto AES decrypt'); const crypto = new AESCrypto(subtle, new Uint8Array(iv)); return crypto.decrypt(data.buffer, aesKey); }).catch(err => { logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`); return this.onWebCryptoError(data, key, iv); }); } onWebCryptoError(data, key, iv) { this.useSoftware = true; this.logEnabled = true; this.softwareDecrypt(data, key, iv); const decryptResult = this.flush(); if (decryptResult) { return decryptResult.buffer; } throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data'); } getValidChunk(data) { let currentChunk = data; const splitPoint = data.length - data.length % CHUNK_SIZE; if (splitPoint !== data.length) { currentChunk = sliceUint8(data, 0, splitPoint); this.remainderData = sliceUint8(data, splitPoint); } return currentChunk; } logOnce(msg) { if (!this.logEnabled) { return; } logger.log(`[decrypter]: ${msg}`); this.logEnabled = false; } } /** * TimeRanges to string helper */ const TimeRanges = { toString: function (r) { let log = ''; const len = r.length; for (let i = 0; i < len; i++) { log += `[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`; } return log; } }; const State = { STOPPED: 'STOPPED', IDLE: 'IDLE', KEY_LOADING: 'KEY_LOADING', FRAG_LOADING: 'FRAG_LOADING', FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY', WAITING_TRACK: 'WAITING_TRACK', PARSING: 'PARSING', PARSED: 'PARSED', ENDED: 'ENDED', ERROR: 'ERROR', WAITING_INIT_PTS: 'WAITING_INIT_PTS', WAITING_LEVEL: 'WAITING_LEVEL' }; class BaseStreamController extends TaskLoop { constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) { super(); this.hls = void 0; this.fragPrevious = null; this.fragCurrent = null; this.fragmentTracker = void 0; this.transmuxer = null; this._state = State.STOPPED; this.playlistType = void 0; this.media = null; this.mediaBuffer = null; this.config = void 0; this.bitrateTest = false; this.lastCurrentTime = 0; this.nextLoadPosition = 0; this.startPosition = 0; this.startTimeOffset = null; this.loadedmetadata = false; this.retryDate = 0; this.levels = null; this.fragmentLoader = void 0; this.keyLoader = void 0; this.levelLastLoaded = null; this.startFragRequested = false; this.decrypter = void 0; this.initPTS = []; this.onvseeking = null; this.onvended = null; this.logPrefix = ''; this.log = void 0; this.warn = void 0; this.playlistType = playlistType; this.logPrefix = logPrefix; this.log = logger.log.bind(logger, `${logPrefix}:`); this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; this.fragmentLoader = new FragmentLoader(hls.config); this.keyLoader = keyLoader; this.fragmentTracker = fragmentTracker; this.config = hls.config; this.decrypter = new Decrypter(hls.config); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } doTick() { this.onTickEnd(); } onTickEnd() {} // eslint-disable-next-line @typescript-eslint/no-unused-vars startLoad(startPosition) {} stopLoad() { this.fragmentLoader.abort(); this.keyLoader.abort(this.playlistType); const frag = this.fragCurrent; if (frag != null && frag.loader) { frag.abortRequests(); this.fragmentTracker.removeFragment(frag); } this.resetTransmuxer(); this.fragCurrent = null; this.fragPrevious = null; this.clearInterval(); this.clearNextTick(); this.state = State.STOPPED; } _streamEnded(bufferInfo, levelDetails) { // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached, // of nothing loading/loaded return false if (levelDetails.live || bufferInfo.nextStart || !bufferInfo.end || !this.media) { return false; } const partList = levelDetails.partList; // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS, // check instead if the last part is buffered. if (partList != null && partList.length) { const lastPart = partList[partList.length - 1]; // Checking the midpoint of the part for potential margin of error and related issues. // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0) // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream // part mismatches for independent audio and video playlists/segments. const lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2); return lastPartBuffered; } const playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type; return this.fragmentTracker.isEndListAppended(playlistType); } getLevelDetails() { if (this.levels && this.levelLastLoaded !== null) { var _this$levels$this$lev; return (_this$levels$this$lev = this.levels[this.levelLastLoaded]) == null ? void 0 : _this$levels$this$lev.details; } } onMediaAttached(event, data) { const media = this.media = this.mediaBuffer = data.media; this.onvseeking = this.onMediaSeeking.bind(this); this.onvended = this.onMediaEnded.bind(this); media.addEventListener('seeking', this.onvseeking); media.addEventListener('ended', this.onvended); const config = this.config; if (this.levels && config.autoStartLoad && this.state === State.STOPPED) { this.startLoad(config.startPosition); } } onMediaDetaching() { const media = this.media; if (media != null && media.ended) { this.log('MSE detaching and video ended, reset startPosition'); this.startPosition = this.lastCurrentTime = 0; } // remove video listeners if (media && this.onvseeking && this.onvended) { media.removeEventListener('seeking', this.onvseeking); media.removeEventListener('ended', this.onvended); this.onvseeking = this.onvended = null; } if (this.keyLoader) { this.keyLoader.detach(); } this.media = this.mediaBuffer = null; this.loadedmetadata = false; this.fragmentTracker.removeAllFragments(); this.stopLoad(); } onMediaSeeking() { const { config, fragCurrent, media, mediaBuffer, state } = this; const currentTime = media ? media.currentTime : 0; const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole); this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`); if (this.state === State.ENDED) { this.resetLoadingState(); } else if (fragCurrent) { // Seeking while frag load is in progress const tolerance = config.maxFragLookUpTolerance; const fragStartOffset = fragCurrent.start - tolerance; const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance; // if seeking out of buffered range or into new one if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) { const pastFragment = currentTime > fragEndOffset; // if the seek position is outside the current fragment range if (currentTime < fragStartOffset || pastFragment) { if (pastFragment && fragCurrent.loader) { this.log('seeking outside of buffer while fragment load in progress, cancel fragment load'); fragCurrent.abortRequests(); this.resetLoadingState(); } this.fragPrevious = null; } } } if (media) { // Remove gap fragments this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true); this.lastCurrentTime = currentTime; } // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target if (!this.loadedmetadata && !bufferInfo.len) { this.nextLoadPosition = this.startPosition = currentTime; } // Async tick to speed up processing this.tickImmediate(); } onMediaEnded() { // reset startPosition and lastCurrentTime to restart playback @ stream beginning this.startPosition = this.lastCurrentTime = 0; } onManifestLoaded(event, data) { this.startTimeOffset = data.startTimeOffset; this.initPTS = []; } onHandlerDestroying() { this.stopLoad(); super.onHandlerDestroying(); } onHandlerDestroyed() { this.state = State.STOPPED; if (this.fragmentLoader) { this.fragmentLoader.destroy(); } if (this.keyLoader) { this.keyLoader.destroy(); } if (this.decrypter) { this.decrypter.destroy(); } this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null; super.onHandlerDestroyed(); } loadFragment(frag, level, targetBufferTime) { this._loadFragForPlayback(frag, level, targetBufferTime); } _loadFragForPlayback(frag, level, targetBufferTime) { const progressCallback = data => { if (this.fragContextChanged(frag)) { this.warn(`Fragment ${frag.sn}${data.part ? ' p: ' + data.part.index : ''} of level ${frag.level} was dropped during download.`); this.fragmentTracker.removeFragment(frag); return; } frag.stats.chunkCount++; this._handleFragmentLoadProgress(data); }; this._doFragLoad(frag, level, targetBufferTime, progressCallback).then(data => { if (!data) { // if we're here we probably needed to backtrack or are waiting for more parts return; } const state = this.state; if (this.fragContextChanged(frag)) { if (state === State.FRAG_LOADING || !this.fragCurrent && state === State.PARSING) { this.fragmentTracker.removeFragment(frag); this.state = State.IDLE; } return; } if ('payload' in data) { this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`); this.hls.trigger(Events.FRAG_LOADED, data); } // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback this._handleFragmentLoadComplete(data); }).catch(reason => { if (this.state === State.STOPPED || this.state === State.ERROR) { return; } this.warn(reason); this.resetFragmentLoading(frag); }); } clearTrackerIfNeeded(frag) { var _this$mediaBuffer; const { fragmentTracker } = this; const fragState = fragmentTracker.getState(frag); if (fragState === FragmentState.APPENDING) { // Lower the buffer size and try again const playlistType = frag.type; const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); const minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength); if (this.reduceMaxBufferLength(minForwardBufferLength)) { fragmentTracker.removeFragment(frag); } } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) { // Stop gap for bad tracker / buffer flush behavior fragmentTracker.removeAllFragments(); } else if (fragmentTracker.hasParts(frag.type)) { // In low latency mode, remove fragments for which only some parts were buffered fragmentTracker.detectPartialFragments({ frag, part: null, stats: frag.stats, id: frag.type }); if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) { fragmentTracker.removeFragment(frag); } } } checkLiveUpdate(details) { if (details.updated && !details.live) { // Live stream ended, update fragment tracker const lastFragment = details.fragments[details.fragments.length - 1]; this.fragmentTracker.detectPartialFragments({ frag: lastFragment, part: null, stats: lastFragment.stats, id: lastFragment.type }); } if (!details.fragments[0]) { details.deltaUpdateFailed = true; } } flushMainBuffer(startOffset, endOffset, type = null) { if (!(startOffset - endOffset)) { return; } // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise, // passing a null type flushes both buffers const flushScope = { startOffset, endOffset, type }; this.hls.trigger(Events.BUFFER_FLUSHING, flushScope); } _loadInitSegment(frag, level) { this._doFragLoad(frag, level).then(data => { if (!data || this.fragContextChanged(frag) || !this.levels) { throw new Error('init load aborted'); } return data; }).then(data => { const { hls } = this; const { payload } = data; const decryptData = frag.decryptdata; // check to see if the payload needs to be decrypted if (payload && payload.byteLength > 0 && decryptData && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { const startTime = self.performance.now(); // decrypt init segment data return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => { hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_DECRYPT_ERROR, fatal: false, error: err, reason: err.message, frag }); throw err; }).then(decryptedData => { const endTime = self.performance.now(); hls.trigger(Events.FRAG_DECRYPTED, { frag, payload: decryptedData, stats: { tstart: startTime, tdecrypt: endTime } }); data.payload = decryptedData; return data; }); } return data; }).then(data => { const { fragCurrent, hls, levels } = this; if (!levels) { throw new Error('init load aborted, missing levels'); } const stats = frag.stats; this.state = State.IDLE; level.fragmentError = 0; frag.data = new Uint8Array(data.payload); stats.parsing.start = stats.buffering.start = self.performance.now(); stats.parsing.end = stats.buffering.end = self.performance.now(); // Silence FRAG_BUFFERED event if fragCurrent is null if (data.frag === fragCurrent) { hls.trigger(Events.FRAG_BUFFERED, { stats, frag: fragCurrent, part: null, id: frag.type }); } this.tick(); }).catch(reason => { if (this.state === State.STOPPED || this.state === State.ERROR) { return; } this.warn(reason); this.resetFragmentLoading(frag); }); } fragContextChanged(frag) { const { fragCurrent } = this; return !frag || !fragCurrent || frag.level !== fragCurrent.level || frag.sn !== fragCurrent.sn || frag.urlId !== fragCurrent.urlId; } fragBufferedComplete(frag, part) { var _frag$startPTS, _frag$endPTS, _this$fragCurrent, _this$fragPrevious; const media = this.mediaBuffer ? this.mediaBuffer : this.media; this.log(`Buffered ${frag.type} sn: ${frag.sn}${part ? ' part: ' + part.index : ''} of ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level} (frag:[${((_frag$startPTS = frag.startPTS) != null ? _frag$startPTS : NaN).toFixed(3)}-${((_frag$endPTS = frag.endPTS) != null ? _frag$endPTS : NaN).toFixed(3)}] > buffer:${media ? TimeRanges.toString(BufferHelper.getBuffered(media)) : '(detached)'})`); this.state = State.IDLE; if (!media) { return; } if (!this.loadedmetadata && frag.type == PlaylistLevelType.MAIN && media.buffered.length && ((_this$fragCurrent = this.fragCurrent) == null ? void 0 : _this$fragCurrent.sn) === ((_this$fragPrevious = this.fragPrevious) == null ? void 0 : _this$fragPrevious.sn)) { this.loadedmetadata = true; this.seekToStartPos(); } this.tick(); } seekToStartPos() {} _handleFragmentLoadComplete(fragLoadedEndData) { const { transmuxer } = this; if (!transmuxer) { return; } const { frag, part, partsLoaded } = fragLoadedEndData; // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data const complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(fragLoaded => !fragLoaded); const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete); transmuxer.flush(chunkMeta); } // eslint-disable-next-line @typescript-eslint/no-unused-vars _handleFragmentLoadProgress(frag) {} _doFragLoad(frag, level, targetBufferTime = null, progressCallback) { var _frag$decryptdata; const details = level == null ? void 0 : level.details; if (!this.levels || !details) { throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`); } let keyLoadingPromise = null; if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) { this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`); this.state = State.KEY_LOADING; this.fragCurrent = frag; keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => { if (!this.fragContextChanged(keyLoadedData.frag)) { this.hls.trigger(Events.KEY_LOADED, keyLoadedData); if (this.state === State.KEY_LOADING) { this.state = State.IDLE; } return keyLoadedData; } }); this.hls.trigger(Events.KEY_LOADING, { frag }); if (this.fragCurrent === null) { keyLoadingPromise = Promise.reject(new Error(`frag load aborted, context changed in KEY_LOADING`)); } } else if (!frag.encrypted && details.encryptedFragments.length) { this.keyLoader.loadClear(frag, details.encryptedFragments); } targetBufferTime = Math.max(frag.start, targetBufferTime || 0); if (this.config.lowLatencyMode && frag.sn !== 'initSegment') { const partList = details.partList; if (partList && progressCallback) { if (targetBufferTime > frag.end && details.fragmentHint) { frag = details.fragmentHint; } const partIndex = this.getNextPart(partList, frag, targetBufferTime); if (partIndex > -1) { const part = partList[partIndex]; this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`); this.nextLoadPosition = part.start + part.duration; this.state = State.FRAG_LOADING; let _result; if (keyLoadingPromise) { _result = keyLoadingPromise.then(keyLoadedData => { if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) { return null; } return this.doFragPartsLoad(frag, part, level, progressCallback); }).catch(error => this.handleFragLoadError(error)); } else { _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(error => this.handleFragLoadError(error)); } this.hls.trigger(Events.FRAG_LOADING, { frag, part, targetBufferTime }); if (this.fragCurrent === null) { return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`)); } return _result; } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) { // Fragment hint has no parts return Promise.resolve(null); } } } this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`); // Don't update nextLoadPosition for fragments which are not buffered if (isFiniteNumber(frag.sn) && !this.bitrateTest) { this.nextLoadPosition = frag.start + frag.duration; } this.state = State.FRAG_LOADING; // Load key before streaming fragment data const dataOnProgress = this.config.progressive; let result; if (dataOnProgress && keyLoadingPromise) { result = keyLoadingPromise.then(keyLoadedData => { if (!keyLoadedData || this.fragContextChanged(keyLoadedData == null ? void 0 : keyLoadedData.frag)) { return null; } return this.fragmentLoader.load(frag, progressCallback); }).catch(error => this.handleFragLoadError(error)); } else { // load unencrypted fragment data with progress event, // or handle fragment result after key and fragment are finished loading result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : undefined), keyLoadingPromise]).then(([fragLoadedData]) => { if (!dataOnProgress && fragLoadedData && progressCallback) { progressCallback(fragLoadedData); } return fragLoadedData; }).catch(error => this.handleFragLoadError(error)); } this.hls.trigger(Events.FRAG_LOADING, { frag, targetBufferTime }); if (this.fragCurrent === null) { return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`)); } return result; } doFragPartsLoad(frag, fromPart, level, progressCallback) { return new Promise((resolve, reject) => { var _level$details; const partsLoaded = []; const initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList; const loadPart = part => { this.fragmentLoader.loadPart(frag, part, progressCallback).then(partLoadedData => { partsLoaded[part.index] = partLoadedData; const loadedPart = partLoadedData.part; this.hls.trigger(Events.FRAG_LOADED, partLoadedData); const nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1); if (nextPart) { loadPart(nextPart); } else { return resolve({ frag, part: loadedPart, partsLoaded }); } }).catch(reject); }; loadPart(fromPart); }); } handleFragLoadError(error) { if ('data' in error) { const data = error.data; if (error.data && data.details === ErrorDetails.INTERNAL_ABORTED) { this.handleFragLoadAborted(data.frag, data.part); } else { this.hls.trigger(Events.ERROR, data); } } else { this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, err: error, error, fatal: true }); } return null; } _handleTransmuxerFlush(chunkMeta) { const context = this.getCurrentContext(chunkMeta); if (!context || this.state !== State.PARSING) { if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) { this.state = State.IDLE; } return; } const { frag, part, level } = context; const now = self.performance.now(); frag.stats.parsing.end = now; if (part) { part.stats.parsing.end = now; } this.updateLevelTiming(frag, part, level, chunkMeta.partial); } getCurrentContext(chunkMeta) { const { levels, fragCurrent } = this; const { level: levelIndex, sn, part: partIndex } = chunkMeta; if (!(levels != null && levels[levelIndex])) { this.warn(`Levels object was unset while buffering fragment ${sn} of level ${levelIndex}. The current chunk will not be buffered.`); return null; } const level = levels[levelIndex]; const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null; const frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent); if (!frag) { return null; } if (fragCurrent && fragCurrent !== frag) { frag.stats = fragCurrent.stats; } return { frag, part, level }; } bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) { var _buffer; if (!data || this.state !== State.PARSING) { return; } const { data1, data2 } = data; let buffer = data1; if (data1 && data2) { // Combine the moof + mdat so that we buffer with a single append buffer = appendUint8Array(data1, data2); } if (!((_buffer = buffer) != null && _buffer.length)) { return; } const segment = { type: data.type, frag, part, chunkMeta, parent: frag.type, data: buffer }; this.hls.trigger(Events.BUFFER_APPENDING, segment); if (data.dropped && data.independent && !part) { if (noBacktracking) { return; } // Clear buffer so that we reload previous segments sequentially if required this.flushBufferGap(frag); } } flushBufferGap(frag) { const media = this.media; if (!media) { return; } // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed if (!BufferHelper.isBuffered(media, media.currentTime)) { this.flushMainBuffer(0, frag.start); return; } // Remove back-buffer without interrupting playback to allow back tracking const currentTime = media.currentTime; const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const fragDuration = frag.duration; const segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25); const start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction); if (frag.start - start > segmentFraction) { this.flushMainBuffer(start, frag.start); } } getFwdBufferInfo(bufferable, type) { const pos = this.getLoadPosition(); if (!isFiniteNumber(pos)) { return null; } return this.getFwdBufferInfoAtPos(bufferable, pos, type); } getFwdBufferInfoAtPos(bufferable, pos, type) { const { config: { maxBufferHole } } = this; const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole); // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) { const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type); if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) { return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole)); } } return bufferInfo; } getMaxBufferLength(levelBitrate) { const { config } = this; let maxBufLen; if (levelBitrate) { maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength); } else { maxBufLen = config.maxBufferLength; } return Math.min(maxBufLen, config.maxMaxBufferLength); } reduceMaxBufferLength(threshold) { const config = this.config; const minLength = threshold || config.maxBufferLength; if (config.maxMaxBufferLength >= minLength) { // reduce max buffer length as it might be too high. we do this to avoid loop flushing ... config.maxMaxBufferLength /= 2; this.warn(`Reduce max buffer length to ${config.maxMaxBufferLength}s`); return true; } return false; } getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) { const fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN); if (fragOrPart && 'fragment' in fragOrPart) { return fragOrPart.fragment; } return fragOrPart; } getNextFragment(pos, levelDetails) { const fragments = levelDetails.fragments; const fragLen = fragments.length; if (!fragLen) { return null; } // find fragment index, contiguous with end of buffer position const { config } = this; const start = fragments[0].start; let frag; if (levelDetails.live) { const initialLiveManifestSize = config.initialLiveManifestSize; if (fragLen < initialLiveManifestSize) { this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`); return null; } // The real fragment start times for a live stream are only known after the PTS range for that level is known. // In order to discover the range, we load the best matching fragment for that level and demux it. // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that // we get the fragment matching that start time if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1) { frag = this.getInitialLiveFragment(levelDetails, fragments); this.startPosition = frag ? this.hls.liveSyncPosition || frag.start : pos; } } else if (pos <= start) { // VoD playlist: if loadPosition before start of playlist, load first fragment frag = fragments[0]; } // If we haven't run into any special cases already, just load the fragment most closely matching the requested position if (!frag) { const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd; frag = this.getFragmentAtPosition(pos, end, levelDetails); } return this.mapToInitFragWhenRequired(frag); } isLoopLoading(frag, targetBufferTime) { const trackerState = this.fragmentTracker.getState(frag); return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime; } getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) { const gapStart = frag.gap; const nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails); if (nextFragment === null) { return nextFragment; } frag = nextFragment; if (gapStart && frag && !frag.gap && bufferInfo.nextStart) { // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType); if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) { // Returning here might result in not finding an audio and video candiate to skip to this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`); return null; } } return frag; } mapToInitFragWhenRequired(frag) { // If an initSegment is present, it must be buffered first if (frag != null && frag.initSegment && !(frag != null && frag.initSegment.data) && !this.bitrateTest) { return frag.initSegment; } return frag; } getNextPart(partList, frag, targetBufferTime) { let nextPart = -1; let contiguous = false; let independentAttrOmitted = true; for (let i = 0, len = partList.length; i < len; i++) { const part = partList[i]; independentAttrOmitted = independentAttrOmitted && !part.independent; if (nextPart > -1 && targetBufferTime < part.start) { break; } const loaded = part.loaded; if (loaded) { nextPart = -1; } else if ((contiguous || part.independent || independentAttrOmitted) && part.fragment === frag) { nextPart = i; } contiguous = loaded; } return nextPart; } loadedEndOfParts(partList, targetBufferTime) { const lastPart = partList[partList.length - 1]; return lastPart && targetBufferTime > lastPart.start && lastPart.loaded; } /* This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real start and end times for each fragment in the playlist (after which this method will not need to be called). */ getInitialLiveFragment(levelDetails, fragments) { const fragPrevious = this.fragPrevious; let frag = null; if (fragPrevious) { if (levelDetails.hasProgramDateTime) { // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`); frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance); } if (!frag) { // SN does not need to be accurate between renditions, but depending on the packaging it may be so. const targetSN = fragPrevious.sn + 1; if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) { const fragNext = fragments[targetSN - levelDetails.startSN]; // Ensure that we're staying within the continuity range, since PTS resets upon a new range if (fragPrevious.cc === fragNext.cc) { frag = fragNext; this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`); } } // It's important to stay within the continuity range if available; otherwise the fragments in the playlist // will have the wrong start times if (!frag) { frag = findFragWithCC(fragments, fragPrevious.cc); if (frag) { this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`); } } } } else { // Find a new start fragment when fragPrevious is null const liveStart = this.hls.liveSyncPosition; if (liveStart !== null) { frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails); } } return frag; } /* This method finds the best matching fragment given the provided position. */ getFragmentAtPosition(bufferEnd, end, levelDetails) { const { config } = this; let { fragPrevious } = this; let { fragments, endSN } = levelDetails; const { fragmentHint } = levelDetails; const tolerance = config.maxFragLookUpTolerance; const partList = levelDetails.partList; const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint); if (loadingParts && fragmentHint && !this.bitrateTest) { // Include incomplete fragment with parts at end fragments = fragments.concat(fragmentHint); endSN = fragmentHint.sn; } let frag; if (bufferEnd < end) { const lookupTolerance = bufferEnd > end - tolerance ? 0 : tolerance; // Remove the tolerance if it would put the bufferEnd past the actual end of stream // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE) frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance); } else { // reach end of playlist frag = fragments[fragments.length - 1]; } if (frag) { const curSNIdx = frag.sn - levelDetails.startSN; // Move fragPrevious forward to support forcing the next fragment to load // when the buffer catches up to a previously buffered range. const fragState = this.fragmentTracker.getState(frag); if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) { fragPrevious = frag; } if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn)) { // Force the next fragment to load if the previous one was already selected. This can occasionally happen with // non-uniform fragment durations const sameLevel = fragPrevious && frag.level === fragPrevious.level; if (sameLevel) { const nextFrag = fragments[curSNIdx + 1]; if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) { frag = nextFrag; } else { frag = null; } } } } return frag; } synchronizeToLiveEdge(levelDetails) { const { config, media } = this; if (!media) { return; } const liveSyncPosition = this.hls.liveSyncPosition; const currentTime = media.currentTime; const start = levelDetails.fragments[0].start; const end = levelDetails.edge; const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end; // Continue if we can seek forward to sync position or if current time is outside of sliding window if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) { // Continue if buffer is starving or if current time is behind max latency const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) { if (!this.loadedmetadata) { this.nextLoadPosition = liveSyncPosition; } // Only seek if ready and there is not a significant forward buffer available for playback if (media.readyState) { this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); media.currentTime = liveSyncPosition; } } } } alignPlaylists(details, previousDetails) { const { levels, levelLastLoaded, fragPrevious } = this; const lastLevel = levelLastLoaded !== null ? levels[levelLastLoaded] : null; // FIXME: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc, // this could all go in level-helper mergeDetails() const length = details.fragments.length; if (!length) { this.warn(`No fragments in live playlist`); return 0; } const slidingStart = details.fragments[0].start; const firstLevelLoad = !previousDetails; const aligned = details.alignedSliding && isFiniteNumber(slidingStart); if (firstLevelLoad || !aligned && !slidingStart) { alignStream(fragPrevious, lastLevel, details); const alignedSlidingStart = details.fragments[0].start; this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN : 'na'}->${details.startSN} prev-sn: ${fragPrevious ? fragPrevious.sn : 'na'} fragments: ${length}`); return alignedSlidingStart; } return slidingStart; } waitForCdnTuneIn(details) { // Wait for Low-Latency CDN Tune-in to get an updated playlist const advancePartLimit = 3; return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit); } setStartPosition(details, sliding) { // compute start position if set to -1. use it straight away if value is defined let startPosition = this.startPosition; if (startPosition < sliding) { startPosition = -1; } if (startPosition === -1 || this.lastCurrentTime === -1) { // Use Playlist EXT-X-START:TIME-OFFSET when set // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match const offsetInMultivariantPlaylist = this.startTimeOffset !== null; const startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset; if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) { startPosition = sliding + startTimeOffset; if (startTimeOffset < 0) { startPosition += details.totalduration; } startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration); this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist, adjust startPosition to ${startPosition}`); this.startPosition = startPosition; } else if (details.live) { // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has // not been specified via the config or an as an argument to startLoad (#3736). startPosition = this.hls.liveSyncPosition || sliding; } else { this.startPosition = startPosition = 0; } this.lastCurrentTime = startPosition; } this.nextLoadPosition = startPosition; } getLoadPosition() { const { media } = this; // if we have not yet loaded any fragment, start loading from start position let pos = 0; if (this.loadedmetadata && media) { pos = media.currentTime; } else if (this.nextLoadPosition) { pos = this.nextLoadPosition; } return pos; } handleFragLoadAborted(frag, part) { if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) { this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} was aborted`); this.resetFragmentLoading(frag); } } resetFragmentLoading(frag) { if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) { this.state = State.IDLE; } } onFragmentOrKeyLoadError(filterType, data) { if (data.chunkMeta && !data.frag) { const context = this.getCurrentContext(data.chunkMeta); if (context) { data.frag = context.frag; } } const frag = data.frag; // Handle frag error related to caller's filterType if (!frag || frag.type !== filterType || !this.levels) { return; } if (this.fragContextChanged(frag)) { var _this$fragCurrent2; this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent2 = this.fragCurrent) == null ? void 0 : _this$fragCurrent2.url}`); return; } const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP; if (gapTagEncountered) { this.fragmentTracker.fragBuffered(frag, true); } // keep retrying until the limit will be reached const errorAction = data.errorAction; const { action, retryCount = 0, retryConfig } = errorAction || {}; if (errorAction && action === NetworkErrorAction.RetryRequest && retryConfig) { var _this$levelLastLoaded; this.resetStartWhenNotLoaded((_this$levelLastLoaded = this.levelLastLoaded) != null ? _this$levelLastLoaded : frag.level); const delay = getRetryDelay(retryConfig, retryCount); this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`); errorAction.resolved = true; this.retryDate = self.performance.now() + delay; this.state = State.FRAG_LOADING_WAITING_RETRY; } else if (retryConfig && errorAction) { this.resetFragmentErrors(filterType); if (retryCount < retryConfig.maxNumRetry) { // Network retry is skipped when level switch is preferred if (!gapTagEncountered) { errorAction.resolved = true; } } else { logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`); } } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) { this.state = State.WAITING_LEVEL; } else { this.state = State.ERROR; } // Perform next async tick sooner to speed up error action resolution this.tickImmediate(); } reduceLengthAndFlushBuffer(data) { // if in appending state if (this.state === State.PARSING || this.state === State.PARSED) { const playlistType = data.parent; const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); // 0.5 : tolerance needed as some browsers stalls playback before reaching buffered end // reduce max buf len if current position is buffered const buffered = bufferedInfo && bufferedInfo.len > 0.5; if (buffered) { this.reduceMaxBufferLength(bufferedInfo.len); } const flushBuffer = !buffered; if (flushBuffer) { // current position is not buffered, but browser is still complaining about buffer full error // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708 // in that case flush the whole audio buffer to recover this.warn(`Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`); } if (data.frag) { this.fragmentTracker.removeFragment(data.frag); this.nextLoadPosition = data.frag.start; } this.resetLoadingState(); return flushBuffer; } return false; } resetFragmentErrors(filterType) { if (filterType === PlaylistLevelType.AUDIO) { // Reset current fragment since audio track audio is essential and may not have a fail-over track this.fragCurrent = null; } // Fragment errors that result in a level switch or redundant fail-over // should reset the stream controller state to idle if (!this.loadedmetadata) { this.startFragRequested = false; } if (this.state !== State.STOPPED) { this.state = State.IDLE; } } afterBufferFlushed(media, bufferType, playlistType) { if (!media) { return; } // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media // (so that we will check against video.buffered ranges in case of alt audio track) const bufferedTimeRanges = BufferHelper.getBuffered(media); this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType); if (this.state === State.ENDED) { this.resetLoadingState(); } } resetLoadingState() { this.log('Reset loading state'); this.fragCurrent = null; this.fragPrevious = null; this.state = State.IDLE; } resetStartWhenNotLoaded(level) { // if loadedmetadata is not set, it means that first frag request failed // in that case, reset startFragRequested flag if (!this.loadedmetadata) { this.startFragRequested = false; const details = this.levels ? this.levels[level].details : null; if (details != null && details.live) { // Update the start position and return to IDLE to recover live start this.startPosition = -1; this.setStartPosition(details, 0); this.resetLoadingState(); } else { this.nextLoadPosition = this.startPosition; } } } resetWhenMissingContext(chunkMeta) { var _this$levelLastLoaded2; this.warn(`The loading context changed while buffering fragment ${chunkMeta.sn} of level ${chunkMeta.level}. This chunk will not be buffered.`); this.removeUnbufferedFrags(); this.resetStartWhenNotLoaded((_this$levelLastLoaded2 = this.levelLastLoaded) != null ? _this$levelLastLoaded2 : chunkMeta.level); this.resetLoadingState(); } removeUnbufferedFrags(start = 0) { this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true); } updateLevelTiming(frag, part, level, partial) { var _this$transmuxer; const details = level.details; if (!details) { this.warn('level.details undefined'); return; } const parsed = Object.keys(frag.elementaryStreams).reduce((result, type) => { const info = frag.elementaryStreams[type]; if (info) { const parsedDuration = info.endPTS - info.startPTS; if (parsedDuration <= 0) { // Destroy the transmuxer after it's next time offset failed to advance because duration was <= 0. // The new transmuxer will be configured with a time offset matching the next fragment start, // preventing the timeline from shifting. this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`); return result || false; } const drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS); this.hls.trigger(Events.LEVEL_PTS_UPDATED, { details, level, drift, type, frag, start: info.startPTS, end: info.endPTS }); return true; } return result; }, false); if (parsed) { level.fragmentError = 0; } else if (((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null) { const error = new Error(`Found no media in fragment ${frag.sn} of level ${frag.level} resetting transmuxer to fallback to playlist timing`); if (level.fragmentError === 0) { // Mark and track the odd empty segment as a gap to avoid reloading level.fragmentError++; frag.gap = true; this.fragmentTracker.removeFragment(frag); this.fragmentTracker.fragBuffered(frag, true); } this.warn(error.message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, error, frag, reason: `Found no media in msn ${frag.sn} of level "${level.url}"` }); if (!this.hls) { return; } this.resetTransmuxer(); // For this error fallthrough. Marking parsed will allow advancing to next fragment. } this.state = State.PARSED; this.hls.trigger(Events.FRAG_PARSED, { frag, part }); } resetTransmuxer() { if (this.transmuxer) { this.transmuxer.destroy(); this.transmuxer = null; } } recoverWorkerError(data) { if (data.event === 'demuxerWorker') { var _ref, _this$levelLastLoaded3, _this$fragCurrent3; this.fragmentTracker.removeAllFragments(); this.resetTransmuxer(); this.resetStartWhenNotLoaded((_ref = (_this$levelLastLoaded3 = this.levelLastLoaded) != null ? _this$levelLastLoaded3 : (_this$fragCurrent3 = this.fragCurrent) == null ? void 0 : _this$fragCurrent3.level) != null ? _ref : 0); this.resetLoadingState(); } } set state(nextState) { const previousState = this._state; if (previousState !== nextState) { this._state = nextState; this.log(`${previousState}->${nextState}`); } } get state() { return this._state; } } function getSourceBuffer() { return self.SourceBuffer || self.WebKitSourceBuffer; } /** * @ignore */ function isSupported() { const mediaSource = getMediaSource(); if (!mediaSource) { return false; } const sourceBuffer = getSourceBuffer(); const isTypeSupported = mediaSource && typeof mediaSource.isTypeSupported === 'function' && mediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'); // if SourceBuffer is exposed ensure its API is valid // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible const sourceBufferValidAPI = !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function'; return !!isTypeSupported && !!sourceBufferValidAPI; } /** * @ignore */ function changeTypeSupported() { var _sourceBuffer$prototy; const sourceBuffer = getSourceBuffer(); return typeof (sourceBuffer == null ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function'; } // ensure the worker ends up in the bundle // If the worker should not be included this gets aliased to empty.js function hasUMDWorker() { return typeof __HLS_WORKER_BUNDLE__ === 'function'; } function injectWorker() { const blob = new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], { type: 'text/javascript' }); const objectURL = self.URL.createObjectURL(blob); const worker = new self.Worker(objectURL); return { worker, objectURL }; } function loadWorker(path) { const scriptURL = new self.URL(path, self.location.href).href; const worker = new self.Worker(scriptURL); return { worker, scriptURL }; } function dummyTrack(type = '', inputTimeScale = 90000) { return { type, id: -1, pid: -1, inputTimeScale, sequenceNumber: -1, samples: [], dropped: 0 }; } class BaseAudioDemuxer { constructor() { this._audioTrack = void 0; this._id3Track = void 0; this.frameIndex = 0; this.cachedData = null; this.basePTS = null; this.initPTS = null; this.lastPTS = null; } resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { this._id3Track = { type: 'id3', id: 3, pid: -1, inputTimeScale: 90000, sequenceNumber: 0, samples: [], dropped: 0 }; } resetTimeStamp(deaultTimestamp) { this.initPTS = deaultTimestamp; this.resetContiguity(); } resetContiguity() { this.basePTS = null; this.lastPTS = null; this.frameIndex = 0; } canParse(data, offset) { return false; } appendFrame(track, data, offset) {} // feed incoming data to the front of the parsing pipeline demux(data, timeOffset) { if (this.cachedData) { data = appendUint8Array(this.cachedData, data); this.cachedData = null; } let id3Data = getID3Data(data, 0); let offset = id3Data ? id3Data.length : 0; let lastDataIndex; const track = this._audioTrack; const id3Track = this._id3Track; const timestamp = id3Data ? getTimeStamp(id3Data) : undefined; const length = data.length; if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) { this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS); this.lastPTS = this.basePTS; } if (this.lastPTS === null) { this.lastPTS = this.basePTS; } // more expressive than alternative: id3Data?.length if (id3Data && id3Data.length > 0) { id3Track.samples.push({ pts: this.lastPTS, dts: this.lastPTS, data: id3Data, type: MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); } while (offset < length) { if (this.canParse(data, offset)) { const frame = this.appendFrame(track, data, offset); if (frame) { this.frameIndex++; this.lastPTS = frame.sample.pts; offset += frame.length; lastDataIndex = offset; } else { offset = length; } } else if (canParse$2(data, offset)) { // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data id3Data = getID3Data(data, offset); id3Track.samples.push({ pts: this.lastPTS, dts: this.lastPTS, data: id3Data, type: MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); offset += id3Data.length; lastDataIndex = offset; } else { offset++; } if (offset === length && lastDataIndex !== length) { const partialData = sliceUint8(data, lastDataIndex); if (this.cachedData) { this.cachedData = appendUint8Array(this.cachedData, partialData); } else { this.cachedData = partialData; } } } return { audioTrack: track, videoTrack: dummyTrack(), id3Track, textTrack: dummyTrack() }; } demuxSampleAes(data, keyData, timeOffset) { return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`)); } flush(timeOffset) { // Parse cache in case of remaining frames. const cachedData = this.cachedData; if (cachedData) { this.cachedData = null; this.demux(cachedData, 0); } return { audioTrack: this._audioTrack, videoTrack: dummyTrack(), id3Track: this._id3Track, textTrack: dummyTrack() }; } destroy() {} } /** * Initialize PTS * <p> * use timestamp unless it is undefined, NaN or Infinity * </p> */ const initPTSFn = (timestamp, timeOffset, initPTS) => { if (isFiniteNumber(timestamp)) { return timestamp * 90; } const init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0; return timeOffset * 90000 + init90kHz; }; /** * ADTS parser helper * @link https://wiki.multimedia.cx/index.php?title=ADTS */ function getAudioConfig(observer, data, offset, audioCodec) { let adtsObjectType; let adtsExtensionSamplingIndex; let adtsChannelConfig; let config; const userAgent = navigator.userAgent.toLowerCase(); const manifestCodec = audioCodec; const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; // byte 2 adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1; const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2; if (adtsSamplingIndex > adtsSamplingRates.length - 1) { observer.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSamplingIndex}` }); return; } adtsChannelConfig = (data[offset + 2] & 0x01) << 2; // byte 3 adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6; logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`); // firefox: freq less than 24kHz = AAC SBR (HE-AAC) if (/firefox/i.test(userAgent)) { if (adtsSamplingIndex >= 6) { adtsObjectType = 5; config = new Array(4); // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSamplingIndex = adtsSamplingIndex - 3; } else { adtsObjectType = 2; config = new Array(2); adtsExtensionSamplingIndex = adtsSamplingIndex; } // Android : always use AAC } else if (userAgent.indexOf('android') !== -1) { adtsObjectType = 2; config = new Array(2); adtsExtensionSamplingIndex = adtsSamplingIndex; } else { /* for other browsers (Chrome/Vivaldi/Opera ...) always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...) */ adtsObjectType = 5; config = new Array(4); // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz) if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) { // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSamplingIndex = adtsSamplingIndex - 3; } else { // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio) // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo. if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) { adtsObjectType = 2; config = new Array(2); } adtsExtensionSamplingIndex = adtsSamplingIndex; } } /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig() Audio Profile / Audio Object Type 0: Null 1: AAC Main 2: AAC LC (Low Complexity) 3: AAC SSR (Scalable Sample Rate) 4: AAC LTP (Long Term Prediction) 5: SBR (Spectral Band Replication) 6: AAC Scalable sampling freq 0: 96000 Hz 1: 88200 Hz 2: 64000 Hz 3: 48000 Hz 4: 44100 Hz 5: 32000 Hz 6: 24000 Hz 7: 22050 Hz 8: 16000 Hz 9: 12000 Hz 10: 11025 Hz 11: 8000 Hz 12: 7350 Hz 13: Reserved 14: Reserved 15: frequency is written explictly Channel Configurations These are the channel configurations: 0: Defined in AOT Specifc Config 1: 1 channel: front-center 2: 2 channels: front-left, front-right */ // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1 config[0] = adtsObjectType << 3; // samplingFrequencyIndex config[0] |= (adtsSamplingIndex & 0x0e) >> 1; config[1] |= (adtsSamplingIndex & 0x01) << 7; // channelConfiguration config[1] |= adtsChannelConfig << 3; if (adtsObjectType === 5) { // adtsExtensionSamplingIndex config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1; config[2] = (adtsExtensionSamplingIndex & 0x01) << 7; // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ??? // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc config[2] |= 2 << 2; config[3] = 0; } return { config, samplerate: adtsSamplingRates[adtsSamplingIndex], channelCount: adtsChannelConfig, codec: 'mp4a.40.' + adtsObjectType, manifestCodec }; } function isHeaderPattern$1(data, offset) { return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0; } function getHeaderLength(data, offset) { return data[offset + 1] & 0x01 ? 7 : 9; } function getFullFrameLength(data, offset) { return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5; } function canGetFrameLength(data, offset) { return offset + 5 < data.length; } function isHeader$1(data, offset) { // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 // Layer bits (position 14 and 15) in header should be always 0 for ADTS // More info https://wiki.multimedia.cx/index.php?title=ADTS return offset + 1 < data.length && isHeaderPattern$1(data, offset); } function canParse$1(data, offset) { return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset; } function probe$1(data, offset) { // same as isHeader but we also check that ADTS frame follows last ADTS frame // or end of data is reached if (isHeader$1(data, offset)) { // ADTS header Length const headerLength = getHeaderLength(data, offset); if (offset + headerLength >= data.length) { return false; } // ADTS frame Length const frameLength = getFullFrameLength(data, offset); if (frameLength <= headerLength) { return false; } const newOffset = offset + frameLength; return newOffset === data.length || isHeader$1(data, newOffset); } return false; } function initTrackConfig(track, observer, data, offset, audioCodec) { if (!track.samplerate) { const config = getAudioConfig(observer, data, offset, audioCodec); if (!config) { return; } track.config = config.config; track.samplerate = config.samplerate; track.channelCount = config.channelCount; track.codec = config.codec; track.manifestCodec = config.manifestCodec; logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`); } } function getFrameDuration(samplerate) { return 1024 * 90000 / samplerate; } function parseFrameHeader(data, offset) { // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header const headerLength = getHeaderLength(data, offset); if (offset + headerLength <= data.length) { // retrieve frame size const frameLength = getFullFrameLength(data, offset) - headerLength; if (frameLength > 0) { // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`); return { headerLength, frameLength }; } } } function appendFrame$1(track, data, offset, pts, frameIndex) { const frameDuration = getFrameDuration(track.samplerate); const stamp = pts + frameIndex * frameDuration; const header = parseFrameHeader(data, offset); let unit; if (header) { const { frameLength, headerLength } = header; const _length = headerLength + frameLength; const missing = Math.max(0, offset + _length - data.length); // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`); if (missing) { unit = new Uint8Array(_length - headerLength); unit.set(data.subarray(offset + headerLength, data.length), 0); } else { unit = data.subarray(offset + headerLength, offset + _length); } const _sample = { unit, pts: stamp }; if (!missing) { track.samples.push(_sample); } return { sample: _sample, length: _length, missing }; } // overflow incomplete header const length = data.length - offset; unit = new Uint8Array(length); unit.set(data.subarray(offset, data.length), 0); const sample = { unit, pts: stamp }; return { sample, length, missing: -1 }; } /** * AAC demuxer */ class AACDemuxer extends BaseAudioDemuxer { constructor(observer, config) { super(); this.observer = void 0; this.config = void 0; this.observer = observer; this.config = config; } resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); this._audioTrack = { container: 'audio/adts', type: 'audio', id: 2, pid: -1, sequenceNumber: 0, segmentCodec: 'aac', samples: [], manifestCodec: audioCodec, duration: trackDuration, inputTimeScale: 90000, dropped: 0 }; } // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS static probe(data) { if (!data) { return false; } // Check for the ADTS sync word // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 // Layer bits (position 14 and 15) in header should be always 0 for ADTS // More info https://wiki.multimedia.cx/index.php?title=ADTS const id3Data = getID3Data(data, 0) || []; let offset = id3Data.length; for (let length = data.length; offset < length; offset++) { if (probe$1(data, offset)) { logger.log('ADTS sync word found !'); return true; } } return false; } canParse(data, offset) { return canParse$1(data, offset); } appendFrame(track, data, offset) { initTrackConfig(track, this.observer, data, offset, track.manifestCodec); const frame = appendFrame$1(track, data, offset, this.basePTS, this.frameIndex); if (frame && frame.missing === 0) { return frame; } } } const emsgSchemePattern = /\/emsg[-/]ID3/i; class MP4Demuxer { constructor(observer, config) { this.remainderData = null; this.timeOffset = 0; this.config = void 0; this.videoTrack = void 0; this.audioTrack = void 0; this.id3Track = void 0; this.txtTrack = void 0; this.config = config; } resetTimeStamp() {} resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { const videoTrack = this.videoTrack = dummyTrack('video', 1); const audioTrack = this.audioTrack = dummyTrack('audio', 1); const captionTrack = this.txtTrack = dummyTrack('text', 1); this.id3Track = dummyTrack('id3', 1); this.timeOffset = 0; if (!(initSegment != null && initSegment.byteLength)) { return; } const initData = parseInitSegment(initSegment); if (initData.video) { const { id, timescale, codec } = initData.video; videoTrack.id = id; videoTrack.timescale = captionTrack.timescale = timescale; videoTrack.codec = codec; } if (initData.audio) { const { id, timescale, codec } = initData.audio; audioTrack.id = id; audioTrack.timescale = timescale; audioTrack.codec = codec; } captionTrack.id = RemuxerTrackIdConfig.text; videoTrack.sampleDuration = 0; videoTrack.duration = audioTrack.duration = trackDuration; } resetContiguity() { this.remainderData = null; } static probe(data) { // ensure we find a moof box in the first 16 kB data = data.length > 16384 ? data.subarray(0, 16384) : data; return findBox(data, ['moof']).length > 0; } demux(data, timeOffset) { this.timeOffset = timeOffset; // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter let videoSamples = data; const videoTrack = this.videoTrack; const textTrack = this.txtTrack; if (this.config.progressive) { // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else. // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception. if (this.remainderData) { videoSamples = appendUint8Array(this.remainderData, data); } const segmentedData = segmentValidRange(videoSamples); this.remainderData = segmentedData.remainder; videoTrack.samples = segmentedData.valid || new Uint8Array(); } else { videoTrack.samples = videoSamples; } const id3Track = this.extractID3Track(videoTrack, timeOffset); textTrack.samples = parseSamples(timeOffset, videoTrack); return { videoTrack, audioTrack: this.audioTrack, id3Track, textTrack: this.txtTrack }; } flush() { const timeOffset = this.timeOffset; const videoTrack = this.videoTrack; const textTrack = this.txtTrack; videoTrack.samples = this.remainderData || new Uint8Array(); this.remainderData = null; const id3Track = this.extractID3Track(videoTrack, this.timeOffset); textTrack.samples = parseSamples(timeOffset, videoTrack); return { videoTrack, audioTrack: dummyTrack(), id3Track, textTrack: dummyTrack() }; } extractID3Track(videoTrack, timeOffset) { const id3Track = this.id3Track; if (videoTrack.samples.length) { const emsgs = findBox(videoTrack.samples, ['emsg']); if (emsgs) { emsgs.forEach(data => { const emsgInfo = parseEmsg(data); if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) { const pts = isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale; let duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale; // Safari takes anything <= 0.001 seconds and maps it to Infinity if (duration <= 0.001) { duration = Number.POSITIVE_INFINITY; } const payload = emsgInfo.payload; id3Track.samples.push({ data: payload, len: payload.byteLength, dts: pts, pts: pts, type: MetadataSchema.emsg, duration: duration }); } }); } } return id3Track; } demuxSampleAes(data, keyData, timeOffset) { return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption')); } destroy() {} } /** * MPEG parser helper */ let chromeVersion$1 = null; const BitratesMap = [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]; const SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000]; const SamplesCoefficients = [ // MPEG 2.5 [0, // Reserved 72, // Layer3 144, // Layer2 12 // Layer1 ], // Reserved [0, // Reserved 0, // Layer3 0, // Layer2 0 // Layer1 ], // MPEG 2 [0, // Reserved 72, // Layer3 144, // Layer2 12 // Layer1 ], // MPEG 1 [0, // Reserved 144, // Layer3 144, // Layer2 12 // Layer1 ]]; const BytesInSlot = [0, // Reserved 1, // Layer3 1, // Layer2 4 // Layer1 ]; function appendFrame(track, data, offset, pts, frameIndex) { // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference if (offset + 24 > data.length) { return; } const header = parseHeader(data, offset); if (header && offset + header.frameLength <= data.length) { const frameDuration = header.samplesPerFrame * 90000 / header.sampleRate; const stamp = pts + frameIndex * frameDuration; const sample = { unit: data.subarray(offset, offset + header.frameLength), pts: stamp, dts: stamp }; track.config = []; track.channelCount = header.channelCount; track.samplerate = header.sampleRate; track.samples.push(sample); return { sample, length: header.frameLength, missing: 0 }; } } function parseHeader(data, offset) { const mpegVersion = data[offset + 1] >> 3 & 3; const mpegLayer = data[offset + 1] >> 1 & 3; const bitRateIndex = data[offset + 2] >> 4 & 15; const sampleRateIndex = data[offset + 2] >> 2 & 3; if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) { const paddingBit = data[offset + 2] >> 1 & 1; const channelMode = data[offset + 3] >> 6; const columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4; const bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000; const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2; const sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex]; const channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono) const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer]; const bytesInSlot = BytesInSlot[mpegLayer]; const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot; const frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot; if (chromeVersion$1 === null) { const userAgent = navigator.userAgent || ''; const result = userAgent.match(/Chrome\/(\d+)/i); chromeVersion$1 = result ? parseInt(result[1]) : 0; } const needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87; if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) { // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00) data[offset + 3] = data[offset + 3] | 0x80; } return { sampleRate, channelCount, frameLength, samplesPerFrame }; } } function isHeaderPattern(data, offset) { return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00; } function isHeader(data, offset) { // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) // More info http://www.mp3-tech.org/programmer/frame_header.html return offset + 1 < data.length && isHeaderPattern(data, offset); } function canParse(data, offset) { const headerSize = 4; return isHeaderPattern(data, offset) && headerSize <= data.length - offset; } function probe(data, offset) { // same as isHeader but we also check that MPEG frame follows last MPEG frame // or end of data is reached if (offset + 1 < data.length && isHeaderPattern(data, offset)) { // MPEG header Length const headerLength = 4; // MPEG frame Length const header = parseHeader(data, offset); let frameLength = headerLength; if (header != null && header.frameLength) { frameLength = header.frameLength; } const newOffset = offset + frameLength; return newOffset === data.length || isHeader(data, newOffset); } return false; } /** * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264. */ class ExpGolomb { constructor(data) { this.data = void 0; this.bytesAvailable = void 0; this.word = void 0; this.bitsAvailable = void 0; this.data = data; // the number of bytes left to examine in this.data this.bytesAvailable = data.byteLength; // the current word being examined this.word = 0; // :uint // the number of bits left to examine in the current word this.bitsAvailable = 0; // :uint } // ():void loadWord() { const data = this.data; const bytesAvailable = this.bytesAvailable; const position = data.byteLength - bytesAvailable; const workingBytes = new Uint8Array(4); const availableBytes = Math.min(4, bytesAvailable); if (availableBytes === 0) { throw new Error('no bytes available'); } workingBytes.set(data.subarray(position, position + availableBytes)); this.word = new DataView(workingBytes.buffer).getUint32(0); // track the amount of this.data that has been processed this.bitsAvailable = availableBytes * 8; this.bytesAvailable -= availableBytes; } // (count:int):void skipBits(count) { let skipBytes; // :int count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable); if (this.bitsAvailable > count) { this.word <<= count; this.bitsAvailable -= count; } else { count -= this.bitsAvailable; skipBytes = count >> 3; count -= skipBytes << 3; this.bytesAvailable -= skipBytes; this.loadWord(); this.word <<= count; this.bitsAvailable -= count; } } // (size:int):uint readBits(size) { let bits = Math.min(this.bitsAvailable, size); // :uint const valu = this.word >>> 32 - bits; // :uint if (size > 32) { logger.error('Cannot read more than 32 bits at a time'); } this.bitsAvailable -= bits; if (this.bitsAvailable > 0) { this.word <<= bits; } else if (this.bytesAvailable > 0) { this.loadWord(); } else { throw new Error('no bits available'); } bits = size - bits; if (bits > 0 && this.bitsAvailable) { return valu << bits | this.readBits(bits); } else { return valu; } } // ():uint skipLZ() { let leadingZeroCount; // :uint for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) { if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) { // the first bit of working word is 1 this.word <<= leadingZeroCount; this.bitsAvailable -= leadingZeroCount; return leadingZeroCount; } } // we exhausted word and still have not found a 1 this.loadWord(); return leadingZeroCount + this.skipLZ(); } // ():void skipUEG() { this.skipBits(1 + this.skipLZ()); } // ():void skipEG() { this.skipBits(1 + this.skipLZ()); } // ():uint readUEG() { const clz = this.skipLZ(); // :uint return this.readBits(clz + 1) - 1; } // ():int readEG() { const valu = this.readUEG(); // :int if (0x01 & valu) { // the number is odd if the low order bit is set return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 } else { return -1 * (valu >>> 1); // divide by two then make it negative } } // Some convenience functions // :Boolean readBoolean() { return this.readBits(1) === 1; } // ():int readUByte() { return this.readBits(8); } // ():int readUShort() { return this.readBits(16); } // ():int readUInt() { return this.readBits(32); } /** * Advance the ExpGolomb decoder past a scaling list. The scaling * list is optionally transmitted as part of a sequence parameter * set and is not relevant to transmuxing. * @param count the number of entries in this scaling list * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 */ skipScalingList(count) { let lastScale = 8; let nextScale = 8; let deltaScale; for (let j = 0; j < count; j++) { if (nextScale !== 0) { deltaScale = this.readEG(); nextScale = (lastScale + deltaScale + 256) % 256; } lastScale = nextScale === 0 ? lastScale : nextScale; } } /** * Read a sequence parameter set and return some interesting video * properties. A sequence parameter set is the H264 metadata that * describes the properties of upcoming video frames. * @returns an object with configuration parsed from the * sequence parameter set, including the dimensions of the * associated video frames. */ readSPS() { let frameCropLeftOffset = 0; let frameCropRightOffset = 0; let frameCropTopOffset = 0; let frameCropBottomOffset = 0; let numRefFramesInPicOrderCntCycle; let scalingListCount; let i; const readUByte = this.readUByte.bind(this); const readBits = this.readBits.bind(this); const readUEG = this.readUEG.bind(this); const readBoolean = this.readBoolean.bind(this); const skipBits = this.skipBits.bind(this); const skipEG = this.skipEG.bind(this); const skipUEG = this.skipUEG.bind(this); const skipScalingList = this.skipScalingList.bind(this); readUByte(); const profileIdc = readUByte(); // profile_idc readBits(5); // profileCompat constraint_set[0-4]_flag, u(5) skipBits(3); // reserved_zero_3bits u(3), readUByte(); // level_idc u(8) skipUEG(); // seq_parameter_set_id // some profiles have more optional data we don't need if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) { const chromaFormatIdc = readUEG(); if (chromaFormatIdc === 3) { skipBits(1); } // separate_colour_plane_flag skipUEG(); // bit_depth_luma_minus8 skipUEG(); // bit_depth_chroma_minus8 skipBits(1); // qpprime_y_zero_transform_bypass_flag if (readBoolean()) { // seq_scaling_matrix_present_flag scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; for (i = 0; i < scalingListCount; i++) { if (readBoolean()) { // seq_scaling_list_present_flag[ i ] if (i < 6) { skipScalingList(16); } else { skipScalingList(64); } } } } } skipUEG(); // log2_max_frame_num_minus4 const picOrderCntType = readUEG(); if (picOrderCntType === 0) { readUEG(); // log2_max_pic_order_cnt_lsb_minus4 } else if (picOrderCntType === 1) { skipBits(1); // delta_pic_order_always_zero_flag skipEG(); // offset_for_non_ref_pic skipEG(); // offset_for_top_to_bottom_field numRefFramesInPicOrderCntCycle = readUEG(); for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { skipEG(); } // offset_for_ref_frame[ i ] } skipUEG(); // max_num_ref_frames skipBits(1); // gaps_in_frame_num_value_allowed_flag const picWidthInMbsMinus1 = readUEG(); const picHeightInMapUnitsMinus1 = readUEG(); const frameMbsOnlyFlag = readBits(1); if (frameMbsOnlyFlag === 0) { skipBits(1); } // mb_adaptive_frame_field_flag skipBits(1); // direct_8x8_inference_flag if (readBoolean()) { // frame_cropping_flag frameCropLeftOffset = readUEG(); frameCropRightOffset = readUEG(); frameCropTopOffset = readUEG(); frameCropBottomOffset = readUEG(); } let pixelRatio = [1, 1]; if (readBoolean()) { // vui_parameters_present_flag if (readBoolean()) { // aspect_ratio_info_present_flag const aspectRatioIdc = readUByte(); switch (aspectRatioIdc) { case 1: pixelRatio = [1, 1]; break; case 2: pixelRatio = [12, 11]; break; case 3: pixelRatio = [10, 11]; break; case 4: pixelRatio = [16, 11]; break; case 5: pixelRatio = [40, 33]; break; case 6: pixelRatio = [24, 11]; break; case 7: pixelRatio = [20, 11]; break; case 8: pixelRatio = [32, 11]; break; case 9: pixelRatio = [80, 33]; break; case 10: pixelRatio = [18, 11]; break; case 11: pixelRatio = [15, 11]; break; case 12: pixelRatio = [64, 33]; break; case 13: pixelRatio = [160, 99]; break; case 14: pixelRatio = [4, 3]; break; case 15: pixelRatio = [3, 2]; break; case 16: pixelRatio = [2, 1]; break; case 255: { pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()]; break; } } } } return { width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2), height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset), pixelRatio: pixelRatio }; } readSliceType() { // skip NALu type this.readUByte(); // discard first_mb_in_slice this.readUEG(); // return slice_type return this.readUEG(); } } /** * SAMPLE-AES decrypter */ class SampleAesDecrypter { constructor(observer, config, keyData) { this.keyData = void 0; this.decrypter = void 0; this.keyData = keyData; this.decrypter = new Decrypter(config, { removePKCS7Padding: false }); } decryptBuffer(encryptedData) { return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer); } // AAC - encrypt all full 16 bytes blocks starting from offset 16 decryptAacSample(samples, sampleIndex, callback) { const curUnit = samples[sampleIndex].unit; if (curUnit.length <= 16) { // No encrypted portion in this sample (first 16 bytes is not // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html), return; } const encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16); const encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length); this.decryptBuffer(encryptedBuffer).then(decryptedBuffer => { const decryptedData = new Uint8Array(decryptedBuffer); curUnit.set(decryptedData, 16); if (!this.decrypter.isSync()) { this.decryptAacSamples(samples, sampleIndex + 1, callback); } }); } decryptAacSamples(samples, sampleIndex, callback) { for (;; sampleIndex++) { if (sampleIndex >= samples.length) { callback(); return; } if (samples[sampleIndex].unit.length < 32) { continue; } this.decryptAacSample(samples, sampleIndex, callback); if (!this.decrypter.isSync()) { return; } } } // AVC - encrypt one 16 bytes block out of ten, starting from offset 32 getAvcEncryptedData(decodedData) { const encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16; const encryptedData = new Int8Array(encryptedDataLen); let outputPos = 0; for (let inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) { encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos); } return encryptedData; } getAvcDecryptedUnit(decodedData, decryptedData) { const uint8DecryptedData = new Uint8Array(decryptedData); let inputPos = 0; for (let outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) { decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos); } return decodedData; } decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) { const decodedData = discardEPB(curUnit.data); const encryptedData = this.getAvcEncryptedData(decodedData); this.decryptBuffer(encryptedData.buffer).then(decryptedBuffer => { curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer); if (!this.decrypter.isSync()) { this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback); } }); } decryptAvcSamples(samples, sampleIndex, unitIndex, callback) { if (samples instanceof Uint8Array) { throw new Error('Cannot decrypt samples of type Uint8Array'); } for (;; sampleIndex++, unitIndex = 0) { if (sampleIndex >= samples.length) { callback(); return; } const curUnits = samples[sampleIndex].units; for (;; unitIndex++) { if (unitIndex >= curUnits.length) { break; } const curUnit = curUnits[unitIndex]; if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) { continue; } this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit); if (!this.decrypter.isSync()) { return; } } } } } const PACKET_LENGTH = 188; class TSDemuxer { constructor(observer, config, typeSupported) { this.observer = void 0; this.config = void 0; this.typeSupported = void 0; this.sampleAes = null; this.pmtParsed = false; this.audioCodec = void 0; this.videoCodec = void 0; this._duration = 0; this._pmtId = -1; this._avcTrack = void 0; this._audioTrack = void 0; this._id3Track = void 0; this._txtTrack = void 0; this.aacOverFlow = null; this.avcSample = null; this.remainderData = null; this.observer = observer; this.config = config; this.typeSupported = typeSupported; } static probe(data) { const syncOffset = TSDemuxer.syncOffset(data); if (syncOffset > 0) { logger.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`); } return syncOffset !== -1; } static syncOffset(data) { const length = data.length; let scanwindow = Math.min(PACKET_LENGTH * 5, data.length - PACKET_LENGTH) + 1; let i = 0; while (i < scanwindow) { // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47 let foundPat = false; let packetStart = -1; let tsPackets = 0; for (let j = i; j < length; j += PACKET_LENGTH) { if (data[j] === 0x47) { tsPackets++; if (packetStart === -1) { packetStart = j; // First sync word found at offset, increase scan length (#5251) if (packetStart !== 0) { scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1; } } if (!foundPat) { foundPat = parsePID(data, j) === 0; } // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501) if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) { return packetStart; } } else if (tsPackets) { // Exit if sync word found, but does not contain contiguous packets (#5501) return -1; } else { break; } } i++; } return -1; } /** * Creates a track model internal to demuxer used to drive remuxing input */ static createTrack(type, duration) { return { container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined, type, id: RemuxerTrackIdConfig[type], pid: -1, inputTimeScale: 90000, sequenceNumber: 0, samples: [], dropped: 0, duration: type === 'audio' ? duration : undefined }; } /** * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start) * Resets all internal track instances of the demuxer. */ resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { this.pmtParsed = false; this._pmtId = -1; this._avcTrack = TSDemuxer.createTrack('video'); this._audioTrack = TSDemuxer.createTrack('audio', trackDuration); this._id3Track = TSDemuxer.createTrack('id3'); this._txtTrack = TSDemuxer.createTrack('text'); this._audioTrack.segmentCodec = 'aac'; // flush any partial content this.aacOverFlow = null; this.avcSample = null; this.remainderData = null; this.audioCodec = audioCodec; this.videoCodec = videoCodec; this._duration = trackDuration; } resetTimeStamp() {} resetContiguity() { const { _audioTrack, _avcTrack, _id3Track } = this; if (_audioTrack) { _audioTrack.pesData = null; } if (_avcTrack) { _avcTrack.pesData = null; } if (_id3Track) { _id3Track.pesData = null; } this.aacOverFlow = null; this.avcSample = null; this.remainderData = null; } demux(data, timeOffset, isSampleAes = false, flush = false) { if (!isSampleAes) { this.sampleAes = null; } let pes; const videoTrack = this._avcTrack; const audioTrack = this._audioTrack; const id3Track = this._id3Track; const textTrack = this._txtTrack; let avcId = videoTrack.pid; let avcData = videoTrack.pesData; let audioId = audioTrack.pid; let id3Id = id3Track.pid; let audioData = audioTrack.pesData; let id3Data = id3Track.pesData; let unknownPID = null; let pmtParsed = this.pmtParsed; let pmtId = this._pmtId; let len = data.length; if (this.remainderData) { data = appendUint8Array(this.remainderData, data); len = data.length; this.remainderData = null; } if (len < PACKET_LENGTH && !flush) { this.remainderData = data; return { audioTrack, videoTrack, id3Track, textTrack }; } const syncOffset = Math.max(0, TSDemuxer.syncOffset(data)); len -= (len - syncOffset) % PACKET_LENGTH; if (len < data.byteLength && !flush) { this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len); } // loop through TS packets let tsPacketErrors = 0; for (let start = syncOffset; start < len; start += PACKET_LENGTH) { if (data[start] === 0x47) { const stt = !!(data[start + 1] & 0x40); const pid = parsePID(data, start); const atf = (data[start + 3] & 0x30) >> 4; // if an adaption field is present, its length is specified by the fifth byte of the TS packet header. let offset; if (atf > 1) { offset = start + 5 + data[start + 4]; // continue if there is only adaptation field if (offset === start + PACKET_LENGTH) { continue; } } else { offset = start + 4; } switch (pid) { case avcId: if (stt) { if (avcData && (pes = parsePES(avcData))) { this.parseAVCPES(videoTrack, textTrack, pes, false); } avcData = { data: [], size: 0 }; } if (avcData) { avcData.data.push(data.subarray(offset, start + PACKET_LENGTH)); avcData.size += start + PACKET_LENGTH - offset; } break; case audioId: if (stt) { if (audioData && (pes = parsePES(audioData))) { switch (audioTrack.segmentCodec) { case 'aac': this.parseAACPES(audioTrack, pes); break; case 'mp3': this.parseMPEGPES(audioTrack, pes); break; } } audioData = { data: [], size: 0 }; } if (audioData) { audioData.data.push(data.subarray(offset, start + PACKET_LENGTH)); audioData.size += start + PACKET_LENGTH - offset; } break; case id3Id: if (stt) { if (id3Data && (pes = parsePES(id3Data))) { this.parseID3PES(id3Track, pes); } id3Data = { data: [], size: 0 }; } if (id3Data) { id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH)); id3Data.size += start + PACKET_LENGTH - offset; } break; case 0: if (stt) { offset += data[offset] + 1; } pmtId = this._pmtId = parsePAT(data, offset); // logger.log('PMT PID:' + this._pmtId); break; case pmtId: { if (stt) { offset += data[offset] + 1; } const parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes); // only update track id if track PID found while parsing PMT // this is to avoid resetting the PID to -1 in case // track PID transiently disappears from the stream // this could happen in case of transient missing audio samples for example // NOTE this is only the PID of the track as found in TS, // but we are not using this for MP4 track IDs. avcId = parsedPIDs.avc; if (avcId > 0) { videoTrack.pid = avcId; } audioId = parsedPIDs.audio; if (audioId > 0) { audioTrack.pid = audioId; audioTrack.segmentCodec = parsedPIDs.segmentCodec; } id3Id = parsedPIDs.id3; if (id3Id > 0) { id3Track.pid = id3Id; } if (unknownPID !== null && !pmtParsed) { logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`); unknownPID = null; // we set it to -188, the += 188 in the for loop will reset start to 0 start = syncOffset - 188; } pmtParsed = this.pmtParsed = true; break; } case 0x11: case 0x1fff: break; default: unknownPID = pid; break; } } else { tsPacketErrors++; } } if (tsPacketErrors > 0) { const error = new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`); this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, error, reason: error.message }); } videoTrack.pesData = avcData; audioTrack.pesData = audioData; id3Track.pesData = id3Data; const demuxResult = { audioTrack, videoTrack, id3Track, textTrack }; if (flush) { this.extractRemainingSamples(demuxResult); } return demuxResult; } flush() { const { remainderData } = this; this.remainderData = null; let result; if (remainderData) { result = this.demux(remainderData, -1, false, true); } else { result = { videoTrack: this._avcTrack, audioTrack: this._audioTrack, id3Track: this._id3Track, textTrack: this._txtTrack }; } this.extractRemainingSamples(result); if (this.sampleAes) { return this.decrypt(result, this.sampleAes); } return result; } extractRemainingSamples(demuxResult) { const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult; const avcData = videoTrack.pesData; const audioData = audioTrack.pesData; const id3Data = id3Track.pesData; // try to parse last PES packets let pes; if (avcData && (pes = parsePES(avcData))) { this.parseAVCPES(videoTrack, textTrack, pes, true); videoTrack.pesData = null; } else { // either avcData null or PES truncated, keep it for next frag parsing videoTrack.pesData = avcData; } if (audioData && (pes = parsePES(audioData))) { switch (audioTrack.segmentCodec) { case 'aac': this.parseAACPES(audioTrack, pes); break; case 'mp3': this.parseMPEGPES(audioTrack, pes); break; } audioTrack.pesData = null; } else { if (audioData != null && audioData.size) { logger.log('last AAC PES packet truncated,might overlap between fragments'); } // either audioData null or PES truncated, keep it for next frag parsing audioTrack.pesData = audioData; } if (id3Data && (pes = parsePES(id3Data))) { this.parseID3PES(id3Track, pes); id3Track.pesData = null; } else { // either id3Data null or PES truncated, keep it for next frag parsing id3Track.pesData = id3Data; } } demuxSampleAes(data, keyData, timeOffset) { const demuxResult = this.demux(data, timeOffset, true, !this.config.progressive); const sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData); return this.decrypt(demuxResult, sampleAes); } decrypt(demuxResult, sampleAes) { return new Promise(resolve => { const { audioTrack, videoTrack } = demuxResult; if (audioTrack.samples && audioTrack.segmentCodec === 'aac') { sampleAes.decryptAacSamples(audioTrack.samples, 0, () => { if (videoTrack.samples) { sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => { resolve(demuxResult); }); } else { resolve(demuxResult); } }); } else if (videoTrack.samples) { sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => { resolve(demuxResult); }); } }); } destroy() { this._duration = 0; } parseAVCPES(track, textTrack, pes, last) { const units = this.parseAVCNALu(track, pes.data); let avcSample = this.avcSample; let push; let spsfound = false; // free pes.data to save up some memory pes.data = null; // if new NAL units found and last sample still there, let's push ... // this helps parsing streams with missing AUD (only do this if AUD never found) if (avcSample && units.length && !track.audFound) { pushAccessUnit(avcSample, track); avcSample = this.avcSample = createAVCSample(false, pes.pts, pes.dts, ''); } units.forEach(unit => { var _avcSample2; switch (unit.type) { // NDR case 1: { let iskey = false; push = true; const data = unit.data; // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...) if (spsfound && data.length > 4) { // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR const sliceType = new ExpGolomb(data).readSliceType(); // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples. // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice. // I slice: A slice that is not an SI slice that is decoded using intra prediction only. // if (sliceType === 2 || sliceType === 7) { if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) { iskey = true; } } if (iskey) { var _avcSample; // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push if ((_avcSample = avcSample) != null && _avcSample.frame && !avcSample.key) { pushAccessUnit(avcSample, track); avcSample = this.avcSample = null; } } if (!avcSample) { avcSample = this.avcSample = createAVCSample(true, pes.pts, pes.dts, ''); } avcSample.frame = true; avcSample.key = iskey; break; // IDR } case 5: push = true; // handle PES not starting with AUD // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push if ((_avcSample2 = avcSample) != null && _avcSample2.frame && !avcSample.key) { pushAccessUnit(avcSample, track); avcSample = this.avcSample = null; } if (!avcSample) { avcSample = this.avcSample = createAVCSample(true, pes.pts, pes.dts, ''); } avcSample.key = true; avcSample.frame = true; break; // SEI case 6: { push = true; parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples); break; // SPS } case 7: push = true; spsfound = true; if (!track.sps) { const sps = unit.data; const expGolombDecoder = new ExpGolomb(sps); const config = expGolombDecoder.readSPS(); track.width = config.width; track.height = config.height; track.pixelRatio = config.pixelRatio; track.sps = [sps]; track.duration = this._duration; const codecarray = sps.subarray(1, 4); let codecstring = 'avc1.'; for (let i = 0; i < 3; i++) { let h = codecarray[i].toString(16); if (h.length < 2) { h = '0' + h; } codecstring += h; } track.codec = codecstring; } break; // PPS case 8: push = true; if (!track.pps) { track.pps = [unit.data]; } break; // AUD case 9: push = false; track.audFound = true; if (avcSample) { pushAccessUnit(avcSample, track); } avcSample = this.avcSample = createAVCSample(false, pes.pts, pes.dts, ''); break; // Filler Data case 12: push = true; break; default: push = false; if (avcSample) { avcSample.debug += 'unknown NAL ' + unit.type + ' '; } break; } if (avcSample && push) { const units = avcSample.units; units.push(unit); } }); // if last PES packet, push samples if (last && avcSample) { pushAccessUnit(avcSample, track); this.avcSample = null; } } getLastNalUnit(samples) { var _avcSample3; let avcSample = this.avcSample; let lastUnit; // try to fallback to previous sample if current one is empty if (!avcSample || avcSample.units.length === 0) { avcSample = samples[samples.length - 1]; } if ((_avcSample3 = avcSample) != null && _avcSample3.units) { const units = avcSample.units; lastUnit = units[units.length - 1]; } return lastUnit; } parseAVCNALu(track, array) { const len = array.byteLength; let state = track.naluState || 0; const lastState = state; const units = []; let i = 0; let value; let overflow; let unitType; let lastUnitStart = -1; let lastUnitType = 0; // logger.log('PES:' + Hex.hexDump(array)); if (state === -1) { // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet lastUnitStart = 0; // NALu type is value read from offset 0 lastUnitType = array[0] & 0x1f; state = 0; i = 1; } while (i < len) { value = array[i++]; // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case if (!state) { state = value ? 0 : 1; continue; } if (state === 1) { state = value ? 0 : 2; continue; } // here we have state either equal to 2 or 3 if (!value) { state = 3; } else if (value === 1) { if (lastUnitStart >= 0) { const unit = { data: array.subarray(lastUnitStart, i - state - 1), type: lastUnitType }; // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength); units.push(unit); } else { // lastUnitStart is undefined => this is the first start code found in this PES packet // first check if start code delimiter is overlapping between 2 PES packets, // ie it started in last packet (lastState not zero) // and ended at the beginning of this PES packet (i <= 4 - lastState) const lastUnit = this.getLastNalUnit(track.samples); if (lastUnit) { if (lastState && i <= 4 - lastState) { // start delimiter overlapping between PES packets // strip start delimiter bytes from the end of last NAL unit // check if lastUnit had a state different from zero if (lastUnit.state) { // strip last bytes lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState); } } // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit. overflow = i - state - 1; if (overflow > 0) { // logger.log('first NALU found with overflow:' + overflow); const tmp = new Uint8Array(lastUnit.data.byteLength + overflow); tmp.set(lastUnit.data, 0); tmp.set(array.subarray(0, overflow), lastUnit.data.byteLength); lastUnit.data = tmp; lastUnit.state = 0; } } } // check if we can read unit type if (i < len) { unitType = array[i] & 0x1f; // logger.log('find NALU @ offset:' + i + ',type:' + unitType); lastUnitStart = i; lastUnitType = unitType; state = 0; } else { // not enough byte to read unit type. let's read it on next PES parsing state = -1; } } else { state = 0; } } if (lastUnitStart >= 0 && state >= 0) { const unit = { data: array.subarray(lastUnitStart, len), type: lastUnitType, state: state }; units.push(unit); // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state); } // no NALu found if (units.length === 0) { // append pes.data to previous NAL unit const lastUnit = this.getLastNalUnit(track.samples); if (lastUnit) { const tmp = new Uint8Array(lastUnit.data.byteLength + array.byteLength); tmp.set(lastUnit.data, 0); tmp.set(array, lastUnit.data.byteLength); lastUnit.data = tmp; } } track.naluState = state; return units; } parseAACPES(track, pes) { let startOffset = 0; const aacOverFlow = this.aacOverFlow; let data = pes.data; if (aacOverFlow) { this.aacOverFlow = null; const frameMissingBytes = aacOverFlow.missing; const sampleLength = aacOverFlow.sample.unit.byteLength; // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`); if (frameMissingBytes === -1) { const tmp = new Uint8Array(sampleLength + data.byteLength); tmp.set(aacOverFlow.sample.unit, 0); tmp.set(data, sampleLength); data = tmp; } else { const frameOverflowBytes = sampleLength - frameMissingBytes; aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes); track.samples.push(aacOverFlow.sample); startOffset = aacOverFlow.missing; } } // look for ADTS header (0xFFFx) let offset; let len; for (offset = startOffset, len = data.length; offset < len - 1; offset++) { if (isHeader$1(data, offset)) { break; } } // if ADTS header does not start straight from the beginning of the PES payload, raise an error if (offset !== startOffset) { let reason; const recoverable = offset < len - 1; if (recoverable) { reason = `AAC PES did not start with ADTS header,offset:${offset}`; } else { reason = 'No ADTS header found in AAC PES'; } const error = new Error(reason); logger.warn(`parsing error: ${reason}`); this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, levelRetry: recoverable, error, reason }); if (!recoverable) { return; } } initTrackConfig(track, this.observer, data, offset, this.audioCodec); let pts; if (pes.pts !== undefined) { pts = pes.pts; } else if (aacOverFlow) { // if last AAC frame is overflowing, we should ensure timestamps are contiguous: // first sample PTS should be equal to last sample PTS + frameDuration const frameDuration = getFrameDuration(track.samplerate); pts = aacOverFlow.sample.pts + frameDuration; } else { logger.warn('[tsdemuxer]: AAC PES unknown PTS'); return; } // scan for aac samples let frameIndex = 0; let frame; while (offset < len) { frame = appendFrame$1(track, data, offset, pts, frameIndex); offset += frame.length; if (!frame.missing) { frameIndex++; for (; offset < len - 1; offset++) { if (isHeader$1(data, offset)) { break; } } } else { this.aacOverFlow = frame; break; } } } parseMPEGPES(track, pes) { const data = pes.data; const length = data.length; let frameIndex = 0; let offset = 0; const pts = pes.pts; if (pts === undefined) { logger.warn('[tsdemuxer]: MPEG PES unknown PTS'); return; } while (offset < length) { if (isHeader(data, offset)) { const frame = appendFrame(track, data, offset, pts, frameIndex); if (frame) { offset += frame.length; frameIndex++; } else { // logger.log('Unable to parse Mpeg audio frame'); break; } } else { // nothing found, keep looking offset++; } } } parseID3PES(id3Track, pes) { if (pes.pts === undefined) { logger.warn('[tsdemuxer]: ID3 PES unknown PTS'); return; } const id3Sample = _extends({}, pes, { type: this._avcTrack ? MetadataSchema.emsg : MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); id3Track.samples.push(id3Sample); } } function createAVCSample(key, pts, dts, debug) { return { key, frame: false, pts, dts, units: [], debug, length: 0 }; } function parsePID(data, offset) { // pid is a 13-bit field starting at the last bit of TS[1] return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2]; } function parsePAT(data, offset) { // skip the PSI header and parse the first PMT entry return (data[offset + 10] & 0x1f) << 8 | data[offset + 11]; } function parsePMT(data, offset, typeSupported, isSampleAes) { const result = { audio: -1, avc: -1, id3: -1, segmentCodec: 'aac' }; const sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2]; const tableEnd = offset + 3 + sectionLength - 4; // to determine where the table is, we have to figure out how // long the program info descriptors are const programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11]; // advance the offset to the first entry in the mapping table offset += 12 + programInfoLength; while (offset < tableEnd) { const pid = parsePID(data, offset); switch (data[offset]) { case 0xcf: // SAMPLE-AES AAC if (!isSampleAes) { logger.log('ADTS AAC with AES-128-CBC frame encryption found in unencrypted stream'); break; } /* falls through */ case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio) // logger.log('AAC PID:' + pid); if (result.audio === -1) { result.audio = pid; } break; // Packetized metadata (ID3) case 0x15: // logger.log('ID3 PID:' + pid); if (result.id3 === -1) { result.id3 = pid; } break; case 0xdb: // SAMPLE-AES AVC if (!isSampleAes) { logger.log('H.264 with AES-128-CBC slice encryption found in unencrypted stream'); break; } /* falls through */ case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video) // logger.log('AVC PID:' + pid); if (result.avc === -1) { result.avc = pid; } break; // ISO/IEC 11172-3 (MPEG-1 audio) // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio) case 0x03: case 0x04: // logger.log('MPEG PID:' + pid); if (typeSupported.mpeg !== true && typeSupported.mp3 !== true) { logger.log('MPEG audio found, not supported in this browser'); } else if (result.audio === -1) { result.audio = pid; result.segmentCodec = 'mp3'; } break; case 0x24: logger.warn('Unsupported HEVC stream type found'); break; } // move to the next table entry // skip past the elementary stream descriptors, if present offset += ((data[offset + 3] & 0x0f) << 8 | data[offset + 4]) + 5; } return result; } function parsePES(stream) { let i = 0; let frag; let pesLen; let pesHdrLen; let pesPts; let pesDts; const data = stream.data; // safety check if (!stream || stream.size === 0) { return null; } // we might need up to 19 bytes to read PES header // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes // usually only one merge is needed (and this is rare ...) while (data[0].length < 19 && data.length > 1) { const newData = new Uint8Array(data[0].length + data[1].length); newData.set(data[0]); newData.set(data[1], data[0].length); data[0] = newData; data.splice(1, 1); } // retrieve PTS/DTS from first fragment frag = data[0]; const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2]; if (pesPrefix === 1) { pesLen = (frag[4] << 8) + frag[5]; // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated // minus 6 : PES header size if (pesLen && pesLen > stream.size - 6) { return null; } const pesFlags = frag[7]; if (pesFlags & 0xc0) { /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html as PTS / DTS is 33 bit we cannot use bitwise operator in JS, as Bitwise operators treat their operands as a sequence of 32 bits */ pesPts = (frag[9] & 0x0e) * 536870912 + // 1 << 29 (frag[10] & 0xff) * 4194304 + // 1 << 22 (frag[11] & 0xfe) * 16384 + // 1 << 14 (frag[12] & 0xff) * 128 + // 1 << 7 (frag[13] & 0xfe) / 2; if (pesFlags & 0x40) { pesDts = (frag[14] & 0x0e) * 536870912 + // 1 << 29 (frag[15] & 0xff) * 4194304 + // 1 << 22 (frag[16] & 0xfe) * 16384 + // 1 << 14 (frag[17] & 0xff) * 128 + // 1 << 7 (frag[18] & 0xfe) / 2; if (pesPts - pesDts > 60 * 90000) { logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`); pesPts = pesDts; } } else { pesDts = pesPts; } } pesHdrLen = frag[8]; // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension let payloadStartOffset = pesHdrLen + 9; if (stream.size <= payloadStartOffset) { return null; } stream.size -= payloadStartOffset; // reassemble PES packet const pesData = new Uint8Array(stream.size); for (let j = 0, dataLen = data.length; j < dataLen; j++) { frag = data[j]; let len = frag.byteLength; if (payloadStartOffset) { if (payloadStartOffset > len) { // trim full frag if PES header bigger than frag payloadStartOffset -= len; continue; } else { // trim partial frag if PES header smaller than frag frag = frag.subarray(payloadStartOffset); len -= payloadStartOffset; payloadStartOffset = 0; } } pesData.set(frag, i); i += len; } if (pesLen) { // payload size : remove PES header + PES extension pesLen -= pesHdrLen + 3; } return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen }; } return null; } function pushAccessUnit(avcSample, avcTrack) { if (avcSample.units.length && avcSample.frame) { // if sample does not have PTS/DTS, patch with last sample PTS/DTS if (avcSample.pts === undefined) { const samples = avcTrack.samples; const nbSamples = samples.length; if (nbSamples) { const lastSample = samples[nbSamples - 1]; avcSample.pts = lastSample.pts; avcSample.dts = lastSample.dts; } else { // dropping samples, no timestamp found avcTrack.dropped++; return; } } avcTrack.samples.push(avcSample); } if (avcSample.debug.length) { logger.log(avcSample.pts + '/' + avcSample.dts + ':' + avcSample.debug); } } /** * MP3 demuxer */ class MP3Demuxer extends BaseAudioDemuxer { resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); this._audioTrack = { container: 'audio/mpeg', type: 'audio', id: 2, pid: -1, sequenceNumber: 0, segmentCodec: 'mp3', samples: [], manifestCodec: audioCodec, duration: trackDuration, inputTimeScale: 90000, dropped: 0 }; } static probe(data) { if (!data) { return false; } // check if data contains ID3 timestamp and MPEG sync word // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) // More info http://www.mp3-tech.org/programmer/frame_header.html const id3Data = getID3Data(data, 0) || []; let offset = id3Data.length; for (let length = data.length; offset < length; offset++) { if (probe(data, offset)) { logger.log('MPEG Audio sync word found !'); return true; } } return false; } canParse(data, offset) { return canParse(data, offset); } appendFrame(track, data, offset) { if (this.basePTS === null) { return; } return appendFrame(track, data, offset, this.basePTS, this.frameIndex); } } /** * AAC helper */ class AAC { static getSilentFrame(codec, channelCount) { switch (codec) { case 'mp4a.40.2': if (channelCount === 1) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]); } else if (channelCount === 2) { return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]); } else if (channelCount === 3) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]); } else if (channelCount === 4) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]); } else if (channelCount === 5) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]); } else if (channelCount === 6) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]); } break; // handle HE-AAC below (mp4a.40.5 / mp4a.40.29) default: if (channelCount === 1) { // ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } else if (channelCount === 2) { // ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } else if (channelCount === 3) { // ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } break; } return undefined; } } /** * Generate MP4 Box */ const UINT32_MAX = Math.pow(2, 32) - 1; class MP4 { static init() { MP4.types = { avc1: [], // codingname avcC: [], btrt: [], dinf: [], dref: [], esds: [], ftyp: [], hdlr: [], mdat: [], mdhd: [], mdia: [], mfhd: [], minf: [], moof: [], moov: [], mp4a: [], '.mp3': [], mvex: [], mvhd: [], pasp: [], sdtp: [], stbl: [], stco: [], stsc: [], stsd: [], stsz: [], stts: [], tfdt: [], tfhd: [], traf: [], trak: [], trun: [], trex: [], tkhd: [], vmhd: [], smhd: [] }; let i; for (i in MP4.types) { if (MP4.types.hasOwnProperty(i)) { MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; } } const videoHdlr = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // pre_defined 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' ]); const audioHdlr = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // pre_defined 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' ]); MP4.HDLR_TYPES = { video: videoHdlr, audio: audioHdlr }; const dref = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x01, // entry_count 0x00, 0x00, 0x00, 0x0c, // entry_size 0x75, 0x72, 0x6c, 0x20, // 'url' type 0x00, // version 0 0x00, 0x00, 0x01 // entry_flags ]); const stco = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00 // entry_count ]); MP4.STTS = MP4.STSC = MP4.STCO = stco; MP4.STSZ = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // sample_size 0x00, 0x00, 0x00, 0x00 // sample_count ]); MP4.VMHD = new Uint8Array([0x00, // version 0x00, 0x00, 0x01, // flags 0x00, 0x00, // graphicsmode 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor ]); MP4.SMHD = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, // balance 0x00, 0x00 // reserved ]); MP4.STSD = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x01]); // entry_count const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1 const minorVersion = new Uint8Array([0, 0, 0, 1]); MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand); MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref)); } static box(type, ...payload) { let size = 8; let i = payload.length; const len = i; // calculate the total size we need to allocate while (i--) { size += payload[i].byteLength; } const result = new Uint8Array(size); result[0] = size >> 24 & 0xff; result[1] = size >> 16 & 0xff; result[2] = size >> 8 & 0xff; result[3] = size & 0xff; result.set(type, 4); // copy the payload into the result for (i = 0, size = 8; i < len; i++) { // copy payload[i] array @ offset size result.set(payload[i], size); size += payload[i].byteLength; } return result; } static hdlr(type) { return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]); } static mdat(data) { return MP4.box(MP4.types.mdat, data); } static mdhd(timescale, duration) { duration *= timescale; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); return MP4.box(MP4.types.mdhd, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, // timescale upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4, // 'und' language (undetermined) 0x00, 0x00])); } static mdia(track) { return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track)); } static mfhd(sequenceNumber) { return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number ])); } static minf(track) { if (track.type === 'audio') { return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track)); } else { return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track)); } } static moof(sn, baseMediaDecodeTime, track) { return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime)); } static moov(tracks) { let i = tracks.length; const boxes = []; while (i--) { boxes[i] = MP4.trak(tracks[i]); } return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks))); } static mvex(tracks) { let i = tracks.length; const boxes = []; while (i--) { boxes[i] = MP4.trex(tracks[i]); } return MP4.box.apply(null, [MP4.types.mvex, ...boxes]); } static mvhd(timescale, duration) { duration *= timescale; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); const bytes = new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, // timescale upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00, // 1.0 rate 0x01, 0x00, // 1.0 volume 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 0xff, 0xff, 0xff, 0xff // next_track_ID ]); return MP4.box(MP4.types.mvhd, bytes); } static sdtp(track) { const samples = track.samples || []; const bytes = new Uint8Array(4 + samples.length); let i; let flags; // leave the full box header (4 bytes) all zero // write the sample table for (i = 0; i < samples.length; i++) { flags = samples[i].flags; bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; } return MP4.box(MP4.types.sdtp, bytes); } static stbl(track) { return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO)); } static avc1(track) { let sps = []; let pps = []; let i; let data; let len; // assemble the SPSs for (i = 0; i < track.sps.length; i++) { data = track.sps[i]; len = data.byteLength; sps.push(len >>> 8 & 0xff); sps.push(len & 0xff); // SPS sps = sps.concat(Array.prototype.slice.call(data)); } // assemble the PPSs for (i = 0; i < track.pps.length; i++) { data = track.pps[i]; len = data.byteLength; pps.push(len >>> 8 & 0xff); pps.push(len & 0xff); pps = pps.concat(Array.prototype.slice.call(data)); } const avcc = MP4.box(MP4.types.avcC, new Uint8Array([0x01, // version sps[3], // profile sps[4], // profile compat sps[5], // level 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes 0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets ].concat(sps).concat([track.pps.length // numOfPictureParameterSets ]).concat(pps))); // "PPS" const width = track.width; const height = track.height; const hSpacing = track.pixelRatio[0]; const vSpacing = track.pixelRatio[1]; return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // data_reference_index 0x00, 0x00, // pre_defined 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined width >> 8 & 0xff, width & 0xff, // width height >> 8 & 0xff, height & 0xff, // height 0x00, 0x48, 0x00, 0x00, // horizresolution 0x00, 0x48, 0x00, 0x00, // vertresolution 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // frame_count 0x12, 0x64, 0x61, 0x69, 0x6c, // dailymotion/hls.js 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname 0x00, 0x18, // depth = 24 0x11, 0x11]), // pre_defined = -1 avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 0x00, 0x2d, 0xc6, 0xc0])), // avgBitrate MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24, // hSpacing hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24, // vSpacing vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff]))); } static esds(track) { const configlen = track.config.length; return new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x03, // descriptor_type 0x17 + configlen, // length 0x00, 0x01, // es_id 0x00, // stream_priority 0x04, // descriptor_type 0x0f + configlen, // length 0x40, // codec : mpeg4_audio 0x15, // stream_type 0x00, 0x00, 0x00, // buffer_size 0x00, 0x00, 0x00, 0x00, // maxBitrate 0x00, 0x00, 0x00, 0x00, // avgBitrate 0x05 // descriptor_type ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor } static mp4a(track) { const samplerate = track.samplerate; return MP4.box(MP4.types.mp4a, new Uint8Array([0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // data_reference_index 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 0x00, track.channelCount, // channelcount 0x00, 0x10, // sampleSize:16bits 0x00, 0x00, 0x00, 0x00, // reserved2 samplerate >> 8 & 0xff, samplerate & 0xff, // 0x00, 0x00]), MP4.box(MP4.types.esds, MP4.esds(track))); } static mp3(track) { const samplerate = track.samplerate; return MP4.box(MP4.types['.mp3'], new Uint8Array([0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // data_reference_index 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 0x00, track.channelCount, // channelcount 0x00, 0x10, // sampleSize:16bits 0x00, 0x00, 0x00, 0x00, // reserved2 samplerate >> 8 & 0xff, samplerate & 0xff, // 0x00, 0x00])); } static stsd(track) { if (track.type === 'audio') { if (track.segmentCodec === 'mp3' && track.codec === 'mp3') { return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track)); } return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track)); } else { return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track)); } } static tkhd(track) { const id = track.id; const duration = track.duration * track.timescale; const width = track.width; const height = track.height; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); return MP4.box(MP4.types.tkhd, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x07, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, // track_ID 0x00, 0x00, 0x00, 0x00, // reserved upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, // layer 0x00, 0x00, // alternate_group 0x00, 0x00, // non-audio track volume 0x00, 0x00, // reserved 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix width >> 8 & 0xff, width & 0xff, 0x00, 0x00, // width height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height ])); } static traf(track, baseMediaDecodeTime) { const sampleDependencyTable = MP4.sdtp(track); const id = track.id; const upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1)); const lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1)); return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags upperWordBaseMediaDecodeTime >> 24, upperWordBaseMediaDecodeTime >> 16 & 0xff, upperWordBaseMediaDecodeTime >> 8 & 0xff, upperWordBaseMediaDecodeTime & 0xff, lowerWordBaseMediaDecodeTime >> 24, lowerWordBaseMediaDecodeTime >> 16 & 0xff, lowerWordBaseMediaDecodeTime >> 8 & 0xff, lowerWordBaseMediaDecodeTime & 0xff])), MP4.trun(track, sampleDependencyTable.length + 16 + // tfhd 20 + // tfdt 8 + // traf header 16 + // mfhd 8 + // moof header 8), // mdat header sampleDependencyTable); } /** * Generate a track box. * @param track a track definition */ static trak(track) { track.duration = track.duration || 0xffffffff; return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track)); } static trex(track) { const id = track.id; return MP4.box(MP4.types.trex, new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, // track_ID 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 0x00, 0x00, 0x00, 0x00, // default_sample_duration 0x00, 0x00, 0x00, 0x00, // default_sample_size 0x00, 0x01, 0x00, 0x01 // default_sample_flags ])); } static trun(track, offset) { const samples = track.samples || []; const len = samples.length; const arraylen = 12 + 16 * len; const array = new Uint8Array(arraylen); let i; let sample; let duration; let size; let flags; let cts; offset += 8 + arraylen; array.set([track.type === 'video' ? 0x01 : 0x00, // version 1 for video with signed-int sample_composition_time_offset 0x00, 0x0f, 0x01, // flags len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff, // sample_count offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset ], 0); for (i = 0; i < len; i++) { sample = samples[i]; duration = sample.duration; size = sample.size; flags = sample.flags; cts = sample.cts; array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff, // sample_duration size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff, // sample_size flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f, // sample_flags cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset ], 12 + 16 * i); } return MP4.box(MP4.types.trun, array); } static initSegment(tracks) { if (!MP4.types) { MP4.init(); } const movie = MP4.moov(tracks); const result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength); result.set(MP4.FTYP); result.set(movie, MP4.FTYP.byteLength); return result; } } MP4.types = void 0; MP4.HDLR_TYPES = void 0; MP4.STTS = void 0; MP4.STSC = void 0; MP4.STCO = void 0; MP4.STSZ = void 0; MP4.VMHD = void 0; MP4.SMHD = void 0; MP4.STSD = void 0; MP4.FTYP = void 0; MP4.DINF = void 0; const MPEG_TS_CLOCK_FREQ_HZ = 90000; function toTimescaleFromBase(baseTime, destScale, srcBase = 1, round = false) { const result = baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)` return round ? Math.round(result) : result; } function toTimescaleFromScale(baseTime, destScale, srcScale = 1, round = false) { return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round); } function toMsFromMpegTsClock(baseTime, round = false) { return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round); } function toMpegTsClockFromTimescale(baseTime, srcScale = 1) { return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale); } const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds const AAC_SAMPLES_PER_FRAME = 1024; const MPEG_AUDIO_SAMPLE_PER_FRAME = 1152; let chromeVersion = null; let safariWebkitVersion = null; class MP4Remuxer { constructor(observer, config, typeSupported, vendor = '') { this.observer = void 0; this.config = void 0; this.typeSupported = void 0; this.ISGenerated = false; this._initPTS = null; this._initDTS = null; this.nextAvcDts = null; this.nextAudioPts = null; this.videoSampleDuration = null; this.isAudioContiguous = false; this.isVideoContiguous = false; this.observer = observer; this.config = config; this.typeSupported = typeSupported; this.ISGenerated = false; if (chromeVersion === null) { const userAgent = navigator.userAgent || ''; const result = userAgent.match(/Chrome\/(\d+)/i); chromeVersion = result ? parseInt(result[1]) : 0; } if (safariWebkitVersion === null) { const result = navigator.userAgent.match(/Safari\/(\d+)/i); safariWebkitVersion = result ? parseInt(result[1]) : 0; } } destroy() {} resetTimeStamp(defaultTimeStamp) { logger.log('[mp4-remuxer]: initPTS & initDTS reset'); this._initPTS = this._initDTS = defaultTimeStamp; } resetNextTimestamp() { logger.log('[mp4-remuxer]: reset next timestamp'); this.isVideoContiguous = false; this.isAudioContiguous = false; } resetInitSegment() { logger.log('[mp4-remuxer]: ISGenerated flag reset'); this.ISGenerated = false; } getVideoStartPts(videoSamples) { let rolloverDetected = false; const startPTS = videoSamples.reduce((minPTS, sample) => { const delta = sample.pts - minPTS; if (delta < -4294967296) { // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation rolloverDetected = true; return normalizePts(minPTS, sample.pts); } else if (delta > 0) { return minPTS; } else { return sample.pts; } }, videoSamples[0].pts); if (rolloverDetected) { logger.debug('PTS rollover detected'); } return startPTS; } remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) { let video; let audio; let initSegment; let text; let id3; let independent; let audioTimeOffset = timeOffset; let videoTimeOffset = timeOffset; // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding. // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the "pid" // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list. // However, if the initSegment has already been generated, or we've reached the end of a segment (flush), // then we can remux one track without waiting for the other. const hasAudio = audioTrack.pid > -1; const hasVideo = videoTrack.pid > -1; const length = videoTrack.samples.length; const enoughAudioSamples = audioTrack.samples.length > 0; const enoughVideoSamples = flush && length > 0 || length > 1; const canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush; if (canRemuxAvc) { if (!this.ISGenerated) { initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } const isVideoContiguous = this.isVideoContiguous; let firstKeyFrameIndex = -1; let firstKeyFramePTS; if (enoughVideoSamples) { firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples); if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) { independent = true; if (firstKeyFrameIndex > 0) { logger.warn(`[mp4-remuxer]: Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`); const startPTS = this.getVideoStartPts(videoTrack.samples); videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex); videoTrack.dropped += firstKeyFrameIndex; videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale; firstKeyFramePTS = videoTimeOffset; } else if (firstKeyFrameIndex === -1) { logger.warn(`[mp4-remuxer]: No keyframe found out of ${length} video samples`); independent = false; } } } if (this.ISGenerated) { if (enoughAudioSamples && enoughVideoSamples) { // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS) // if first audio DTS is not aligned with first video DTS then we need to take that into account // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small // drift between audio and video streams const startPTS = this.getVideoStartPts(videoTrack.samples); const tsDelta = normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS; const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale; audioTimeOffset += Math.max(0, audiovideoTimestampDelta); videoTimeOffset += Math.max(0, -audiovideoTimestampDelta); } // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio. if (enoughAudioSamples) { // if initSegment was generated without audio samples, regenerate it again if (!audioTrack.samplerate) { logger.warn('[mp4-remuxer]: regenerate InitSegment as audio detected'); initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined); if (enoughVideoSamples) { const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0; // if initSegment was generated without video samples, regenerate it again if (!videoTrack.inputTimeScale) { logger.warn('[mp4-remuxer]: regenerate InitSegment as video detected'); initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength); } } else if (enoughVideoSamples) { video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0); } if (video) { video.firstKeyFrame = firstKeyFrameIndex; video.independent = firstKeyFrameIndex !== -1; video.firstKeyFramePTS = firstKeyFramePTS; } } } // Allow ID3 and text to remux, even if more audio/video samples are required if (this.ISGenerated && this._initPTS && this._initDTS) { if (id3Track.samples.length) { id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS); } if (textTrack.samples.length) { text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS); } } return { audio, video, initSegment, independent, text, id3 }; } generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) { const audioSamples = audioTrack.samples; const videoSamples = videoTrack.samples; const typeSupported = this.typeSupported; const tracks = {}; const _initPTS = this._initPTS; let computePTSDTS = !_initPTS || accurateTimeOffset; let container = 'audio/mp4'; let initPTS; let initDTS; let timescale; if (computePTSDTS) { initPTS = initDTS = Infinity; } if (audioTrack.config && audioSamples.length) { // let's use audio sampling rate as MP4 time scale. // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC) // using audio sampling rate here helps having an integer MP4 frame duration // this avoids potential rounding issue and AV sync issue audioTrack.timescale = audioTrack.samplerate; switch (audioTrack.segmentCodec) { case 'mp3': if (typeSupported.mpeg) { // Chrome and Safari container = 'audio/mpeg'; audioTrack.codec = ''; } else if (typeSupported.mp3) { // Firefox audioTrack.codec = 'mp3'; } break; } tracks.audio = { id: 'audio', container: container, codec: audioTrack.codec, initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]), metadata: { channelCount: audioTrack.channelCount } }; if (computePTSDTS) { timescale = audioTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { // remember first PTS of this demuxing context. for audio, PTS = DTS initPTS = initDTS = audioSamples[0].pts - Math.round(timescale * timeOffset); } else { computePTSDTS = false; } } } if (videoTrack.sps && videoTrack.pps && videoSamples.length) { // let's use input time scale as MP4 video timescale // we use input time scale straight away to avoid rounding issues on frame duration / cts computation videoTrack.timescale = videoTrack.inputTimeScale; tracks.video = { id: 'main', container: 'video/mp4', codec: videoTrack.codec, initSegment: MP4.initSegment([videoTrack]), metadata: { width: videoTrack.width, height: videoTrack.height } }; if (computePTSDTS) { timescale = videoTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { const startPTS = this.getVideoStartPts(videoSamples); const startOffset = Math.round(timescale * timeOffset); initDTS = Math.min(initDTS, normalizePts(videoSamples[0].dts, startPTS) - startOffset); initPTS = Math.min(initPTS, startPTS - startOffset); } else { computePTSDTS = false; } } } if (Object.keys(tracks).length) { this.ISGenerated = true; if (computePTSDTS) { this._initPTS = { baseTime: initPTS, timescale: timescale }; this._initDTS = { baseTime: initDTS, timescale: timescale }; } else { initPTS = timescale = undefined; } return { tracks, initPTS, timescale }; } } remuxVideo(track, timeOffset, contiguous, audioTrackLength) { const timeScale = track.inputTimeScale; const inputSamples = track.samples; const outputSamples = []; const nbSamples = inputSamples.length; const initPTS = this._initPTS; let nextAvcDts = this.nextAvcDts; let offset = 8; let mp4SampleDuration = this.videoSampleDuration; let firstDTS; let lastDTS; let minPTS = Number.POSITIVE_INFINITY; let maxPTS = Number.NEGATIVE_INFINITY; let sortSamples = false; // if parsed fragment is contiguous with last one, let's use last DTS value as reference if (!contiguous || nextAvcDts === null) { const pts = timeOffset * timeScale; const cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts); // if not contiguous, let's use target timeOffset nextAvcDts = pts - cts; } // PTS is coded on 33bits, and can loop from -2^32 to 2^32 // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value const initTime = initPTS.baseTime * timeScale / initPTS.timescale; for (let i = 0; i < nbSamples; i++) { const sample = inputSamples[i]; sample.pts = normalizePts(sample.pts - initTime, nextAvcDts); sample.dts = normalizePts(sample.dts - initTime, nextAvcDts); if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) { sortSamples = true; } } // sort video samples by DTS then PTS then demux id order if (sortSamples) { inputSamples.sort(function (a, b) { const deltadts = a.dts - b.dts; const deltapts = a.pts - b.pts; return deltadts || deltapts; }); } // Get first/last DTS firstDTS = inputSamples[0].dts; lastDTS = inputSamples[inputSamples.length - 1].dts; // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS // set this constant duration as being the avg delta between consecutive DTS. const inputDuration = lastDTS - firstDTS; const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30; // if fragment are contiguous, detect hole/overlapping between fragments if (contiguous) { // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole) const delta = firstDTS - nextAvcDts; const foundHole = delta > averageSampleDuration; const foundOverlap = delta < -1; if (foundHole || foundOverlap) { if (foundHole) { logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected, filling it`); } else { logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected`); } if (!foundOverlap || nextAvcDts >= inputSamples[0].pts) { firstDTS = nextAvcDts; const firstPTS = inputSamples[0].pts - delta; inputSamples[0].dts = firstDTS; inputSamples[0].pts = firstPTS; logger.log(`Video: First PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`); } } } firstDTS = Math.max(0, firstDTS); let nbNalu = 0; let naluLen = 0; for (let i = 0; i < nbSamples; i++) { // compute total/avc sample length and nb of NAL units const sample = inputSamples[i]; const units = sample.units; const nbUnits = units.length; let sampleLen = 0; for (let j = 0; j < nbUnits; j++) { sampleLen += units[j].data.length; } naluLen += sampleLen; nbNalu += nbUnits; sample.length = sampleLen; // ensure sample monotonic DTS sample.dts = Math.max(sample.dts, firstDTS); minPTS = Math.min(sample.pts, minPTS); maxPTS = Math.max(sample.pts, maxPTS); } lastDTS = inputSamples[nbSamples - 1].dts; /* concatenate the video data and construct the mdat in place (need 8 more bytes to fill length and mpdat type) */ const mdatSize = naluLen + 4 * nbNalu + 8; let mdat; try { mdat = new Uint8Array(mdatSize); } catch (err) { this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MUX_ERROR, details: ErrorDetails.REMUX_ALLOC_ERROR, fatal: false, error: err, bytes: mdatSize, reason: `fail allocating video mdat ${mdatSize}` }); return; } const view = new DataView(mdat.buffer); view.setUint32(0, mdatSize); mdat.set(MP4.types.mdat, 4); let stretchedLastFrame = false; let minDtsDelta = Number.POSITIVE_INFINITY; let minPtsDelta = Number.POSITIVE_INFINITY; let maxDtsDelta = Number.NEGATIVE_INFINITY; let maxPtsDelta = Number.NEGATIVE_INFINITY; for (let i = 0; i < nbSamples; i++) { const avcSample = inputSamples[i]; const avcSampleUnits = avcSample.units; let mp4SampleLength = 0; // convert NALU bitstream to MP4 format (prepend NALU with size field) for (let j = 0, nbUnits = avcSampleUnits.length; j < nbUnits; j++) { const unit = avcSampleUnits[j]; const unitData = unit.data; const unitDataLen = unit.data.byteLength; view.setUint32(offset, unitDataLen); offset += 4; mdat.set(unitData, offset); offset += unitDataLen; mp4SampleLength += 4 + unitDataLen; } // expected sample duration is the Decoding Timestamp diff of consecutive samples let ptsDelta; if (i < nbSamples - 1) { mp4SampleDuration = inputSamples[i + 1].dts - avcSample.dts; ptsDelta = inputSamples[i + 1].pts - avcSample.pts; } else { const config = this.config; const lastFrameDuration = i > 0 ? avcSample.dts - inputSamples[i - 1].dts : averageSampleDuration; ptsDelta = i > 0 ? avcSample.pts - inputSamples[i - 1].pts : averageSampleDuration; if (config.stretchShortVideoTrack && this.nextAudioPts !== null) { // In some cases, a segment's audio track duration may exceed the video track duration. // Since we've already remuxed audio, and we know how long the audio track is, we look to // see if the delta to the next segment is longer than maxBufferHole. // If so, playback would potentially get stuck, so we artificially inflate // the duration of the last frame to minimize any potential gap between segments. const gapTolerance = Math.floor(config.maxBufferHole * timeScale); const deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioPts) - avcSample.pts; if (deltaToFrameEnd > gapTolerance) { // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video // frame overlap. maxBufferHole should be >> lastFrameDuration anyway. mp4SampleDuration = deltaToFrameEnd - lastFrameDuration; if (mp4SampleDuration < 0) { mp4SampleDuration = lastFrameDuration; } else { stretchedLastFrame = true; } logger.log(`[mp4-remuxer]: It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`); } else { mp4SampleDuration = lastFrameDuration; } } else { mp4SampleDuration = lastFrameDuration; } } const compositionTimeOffset = Math.round(avcSample.pts - avcSample.dts); minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration); maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration); minPtsDelta = Math.min(minPtsDelta, ptsDelta); maxPtsDelta = Math.max(maxPtsDelta, ptsDelta); outputSamples.push(new Mp4Sample(avcSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset)); } if (outputSamples.length) { if (chromeVersion) { if (chromeVersion < 70) { // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue // https://code.google.com/p/chromium/issues/detail?id=229412 const flags = outputSamples[0].flags; flags.dependsOn = 2; flags.isNonSync = 0; } } else if (safariWebkitVersion) { // Fix for "CNN special report, with CC" in test-streams (Safari browser only) // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps. if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) { logger.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.'); let dts = firstDTS; for (let i = 0, len = outputSamples.length; i < len; i++) { const nextDts = dts + outputSamples[i].duration; const pts = dts + outputSamples[i].cts; if (i < len - 1) { const nextPts = nextDts + outputSamples[i + 1].cts; outputSamples[i].duration = nextPts - pts; } else { outputSamples[i].duration = i ? outputSamples[i - 1].duration : averageSampleDuration; } outputSamples[i].cts = 0; dts = nextDts; } } } } // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale) mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration; this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration; this.videoSampleDuration = mp4SampleDuration; this.isVideoContiguous = true; const moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends({}, track, { samples: outputSamples })); const type = 'video'; const data = { data1: moof, data2: mdat, startPTS: minPTS / timeScale, endPTS: (maxPTS + mp4SampleDuration) / timeScale, startDTS: firstDTS / timeScale, endDTS: nextAvcDts / timeScale, type, hasAudio: false, hasVideo: true, nb: outputSamples.length, dropped: track.dropped }; track.samples = []; track.dropped = 0; return data; } remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) { const inputTimeScale = track.inputTimeScale; const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; const scaleFactor = inputTimeScale / mp4timeScale; const mp4SampleDuration = track.segmentCodec === 'aac' ? AAC_SAMPLES_PER_FRAME : MPEG_AUDIO_SAMPLE_PER_FRAME; const inputSampleDuration = mp4SampleDuration * scaleFactor; const initPTS = this._initPTS; const rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg; const outputSamples = []; const alignedWithVideo = videoTimeOffset !== undefined; let inputSamples = track.samples; let offset = rawMPEG ? 0 : 8; let nextAudioPts = this.nextAudioPts || -1; // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]); // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs), // for sake of clarity: // consecutive fragments are frags with // - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR // - less than 20 audio frames distance // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1) // this helps ensuring audio continuity // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame const timeOffsetMpegTS = timeOffset * inputTimeScale; const initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale; this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioPts > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000 || Math.abs(normalizePts(inputSamples[0].pts - initTime, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration); // compute normalized PTS inputSamples.forEach(function (sample) { sample.pts = normalizePts(sample.pts - initTime, timeOffsetMpegTS); }); if (!contiguous || nextAudioPts < 0) { // filter out sample with negative PTS that are not playable anyway // if we don't remove these negative samples, they will shift all audio samples forward. // leading to audio overlap between current / next fragment inputSamples = inputSamples.filter(sample => sample.pts >= 0); // in case all samples have negative PTS, and have been filtered out, return now if (!inputSamples.length) { return; } if (videoTimeOffset === 0) { // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence nextAudioPts = 0; } else if (accurateTimeOffset && !alignedWithVideo) { // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS nextAudioPts = Math.max(0, timeOffsetMpegTS); } else { // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS nextAudioPts = inputSamples[0].pts; } } // If the audio track is missing samples, the frames seem to get "left-shifted" within the // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment. // In an effort to prevent this from happening, we inject frames here where there are gaps. // When possible, we inject a silent frame; when that's not possible, we duplicate the last // frame. if (track.segmentCodec === 'aac') { const maxAudioFramesDrift = this.config.maxAudioFramesDrift; for (let i = 0, nextPts = nextAudioPts; i < inputSamples.length; i++) { // First, let's see how far off this frame is from where we expect it to be const sample = inputSamples[i]; const pts = sample.pts; const delta = pts - nextPts; const duration = Math.abs(1000 * delta / inputTimeScale); // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) { if (i === 0) { logger.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps nextAudioPts by ${Math.round(1000 * delta / inputTimeScale)} ms.`); this.nextAudioPts = nextAudioPts = nextPts = pts; } } // eslint-disable-line brace-style // Insert missing frames if: // 1: We're more than maxAudioFramesDrift frame away // 2: Not more than MAX_SILENT_FRAME_DURATION away // 3: currentTime (aka nextPtsNorm) is not 0 // 4: remuxing with video (videoTimeOffset !== undefined) else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) { let missing = Math.round(delta / inputSampleDuration); // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration. nextPts = pts - missing * inputSampleDuration; if (nextPts < 0) { missing--; nextPts += inputSampleDuration; } if (i === 0) { this.nextAudioPts = nextAudioPts = nextPts; } logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`); for (let j = 0; j < missing; j++) { const newStamp = Math.max(nextPts, 0); let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); if (!fillFrame) { logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.'); fillFrame = sample.unit.subarray(); } inputSamples.splice(i, 0, { unit: fillFrame, pts: newStamp }); nextPts += inputSampleDuration; i++; } } sample.pts = nextPts; nextPts += inputSampleDuration; } } let firstPTS = null; let lastPTS = null; let mdat; let mdatSize = 0; let sampleLength = inputSamples.length; while (sampleLength--) { mdatSize += inputSamples[sampleLength].unit.byteLength; } for (let j = 0, _nbSamples = inputSamples.length; j < _nbSamples; j++) { const audioSample = inputSamples[j]; const unit = audioSample.unit; let pts = audioSample.pts; if (lastPTS !== null) { // If we have more than one sample, set the duration of the sample to the "real" duration; the PTS diff with // the previous sample const prevSample = outputSamples[j - 1]; prevSample.duration = Math.round((pts - lastPTS) / scaleFactor); } else { if (contiguous && track.segmentCodec === 'aac') { // set PTS/DTS to expected PTS/DTS pts = nextAudioPts; } // remember first PTS of our audioSamples firstPTS = pts; if (mdatSize > 0) { /* concatenate the audio data and construct the mdat in place (need 8 more bytes to fill length and mdat type) */ mdatSize += offset; try { mdat = new Uint8Array(mdatSize); } catch (err) { this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MUX_ERROR, details: ErrorDetails.REMUX_ALLOC_ERROR, fatal: false, error: err, bytes: mdatSize, reason: `fail allocating audio mdat ${mdatSize}` }); return; } if (!rawMPEG) { const view = new DataView(mdat.buffer); view.setUint32(0, mdatSize); mdat.set(MP4.types.mdat, 4); } } else { // no audio samples return; } } mdat.set(unit, offset); const unitLen = unit.byteLength; offset += unitLen; // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration // becomes the PTS diff with the previous sample outputSamples.push(new Mp4Sample(true, mp4SampleDuration, unitLen, 0)); lastPTS = pts; } // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones const nbSamples = outputSamples.length; if (!nbSamples) { return; } // The next audio sample PTS should be equal to last sample PTS + duration const lastSample = outputSamples[outputSamples.length - 1]; this.nextAudioPts = nextAudioPts = lastPTS + scaleFactor * lastSample.duration; // Set the track samples from inputSamples to outputSamples before remuxing const moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, { samples: outputSamples })); // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared track.samples = []; const start = firstPTS / inputTimeScale; const end = nextAudioPts / inputTimeScale; const type = 'audio'; const audioData = { data1: moof, data2: mdat, startPTS: start, endPTS: end, startDTS: start, endDTS: end, type, hasAudio: true, hasVideo: false, nb: nbSamples }; this.isAudioContiguous = true; return audioData; } remuxEmptyAudio(track, timeOffset, contiguous, videoData) { const inputTimeScale = track.inputTimeScale; const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; const scaleFactor = inputTimeScale / mp4timeScale; const nextAudioPts = this.nextAudioPts; // sync with video's timestamp const initDTS = this._initDTS; const init90kHz = initDTS.baseTime * 90000 / initDTS.timescale; const startDTS = (nextAudioPts !== null ? nextAudioPts : videoData.startDTS * inputTimeScale) + init90kHz; const endDTS = videoData.endDTS * inputTimeScale + init90kHz; // one sample's duration value const frameDuration = scaleFactor * AAC_SAMPLES_PER_FRAME; // samples count of this segment's duration const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration); // silent frame const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); logger.warn('[mp4-remuxer]: remux empty Audio'); // Can't remux if we can't generate a silent frame... if (!silentFrame) { logger.trace('[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec'); return; } const samples = []; for (let i = 0; i < nbSamples; i++) { const stamp = startDTS + i * frameDuration; samples.push({ unit: silentFrame, pts: stamp, dts: stamp }); } track.samples = samples; return this.remuxAudio(track, timeOffset, contiguous, false); } } function normalizePts(value, reference) { let offset; if (reference === null) { return value; } if (reference < value) { // - 2^33 offset = -8589934592; } else { // + 2^33 offset = 8589934592; } /* PTS is 33bit (from 0 to 2^33 -1) if diff between value and reference is bigger than half of the amplitude (2^32) then it means that PTS looping occured. fill the gap */ while (Math.abs(value - reference) > 4294967296) { value += offset; } return value; } function findKeyframeIndex(samples) { for (let i = 0; i < samples.length; i++) { if (samples[i].key) { return i; } } return -1; } function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) { const length = track.samples.length; if (!length) { return; } const inputTimeScale = track.inputTimeScale; for (let index = 0; index < length; index++) { const sample = track.samples[index]; // setting id3 pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; } const samples = track.samples; track.samples = []; return { samples }; } function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) { const length = track.samples.length; if (!length) { return; } const inputTimeScale = track.inputTimeScale; for (let index = 0; index < length; index++) { const sample = track.samples[index]; // setting text pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; } track.samples.sort((a, b) => a.pts - b.pts); const samples = track.samples; track.samples = []; return { samples }; } class Mp4Sample { constructor(isKeyframe, duration, size, cts) { this.size = void 0; this.duration = void 0; this.cts = void 0; this.flags = void 0; this.duration = duration; this.size = size; this.cts = cts; this.flags = new Mp4SampleFlags(isKeyframe); } } class Mp4SampleFlags { constructor(isKeyframe) { this.isLeading = 0; this.isDependedOn = 0; this.hasRedundancy = 0; this.degradPrio = 0; this.dependsOn = 1; this.isNonSync = 1; this.dependsOn = isKeyframe ? 2 : 1; this.isNonSync = isKeyframe ? 0 : 1; } } class PassThroughRemuxer { constructor() { this.emitInitSegment = false; this.audioCodec = void 0; this.videoCodec = void 0; this.initData = void 0; this.initPTS = null; this.initTracks = void 0; this.lastEndTime = null; } destroy() {} resetTimeStamp(defaultInitPTS) { this.initPTS = defaultInitPTS; this.lastEndTime = null; } resetNextTimestamp() { this.lastEndTime = null; } resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) { this.audioCodec = audioCodec; this.videoCodec = videoCodec; this.generateInitSegment(patchEncyptionData(initSegment, decryptdata)); this.emitInitSegment = true; } generateInitSegment(initSegment) { let { audioCodec, videoCodec } = this; if (!(initSegment != null && initSegment.byteLength)) { this.initTracks = undefined; this.initData = undefined; return; } const initData = this.initData = parseInitSegment(initSegment); // Get codec from initSegment or fallback to default if (!audioCodec) { audioCodec = getParsedTrackCodec(initData.audio, ElementaryStreamTypes.AUDIO); } if (!videoCodec) { videoCodec = getParsedTrackCodec(initData.video, ElementaryStreamTypes.VIDEO); } const tracks = {}; if (initData.audio && initData.video) { tracks.audiovideo = { container: 'video/mp4', codec: audioCodec + ',' + videoCodec, initSegment, id: 'main' }; } else if (initData.audio) { tracks.audio = { container: 'audio/mp4', codec: audioCodec, initSegment, id: 'audio' }; } else if (initData.video) { tracks.video = { container: 'video/mp4', codec: videoCodec, initSegment, id: 'main' }; } else { logger.warn('[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.'); } this.initTracks = tracks; } remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset) { var _initData, _initData2; let { initPTS, lastEndTime } = this; const result = { audio: undefined, video: undefined, text: textTrack, id3: id3Track, initSegment: undefined }; // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update // the media duration (which is what timeOffset is provided as) before we need to process the next chunk. if (!isFiniteNumber(lastEndTime)) { lastEndTime = this.lastEndTime = timeOffset || 0; } // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only // audio or video (or both); adding it to video was an arbitrary choice. const data = videoTrack.samples; if (!(data != null && data.length)) { return result; } const initSegment = { initPTS: undefined, timescale: 1 }; let initData = this.initData; if (!((_initData = initData) != null && _initData.length)) { this.generateInitSegment(data); initData = this.initData; } if (!((_initData2 = initData) != null && _initData2.length)) { // We can't remux if the initSegment could not be generated logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.'); return result; } if (this.emitInitSegment) { initSegment.tracks = this.initTracks; this.emitInitSegment = false; } const duration = getDuration(data, initData); const startDTS = getStartDTS(initData, data); const decodeTime = startDTS === null ? timeOffset : startDTS; if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) { initSegment.initPTS = decodeTime - timeOffset; if (initPTS && initPTS.timescale === 1) { logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`); } this.initPTS = initPTS = { baseTime: initSegment.initPTS, timescale: 1 }; } const startTime = audioTrack ? decodeTime - initPTS.baseTime / initPTS.timescale : lastEndTime; const endTime = startTime + duration; offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale); if (duration > 0) { this.lastEndTime = endTime; } else { logger.warn('Duration parsed from mp4 should be greater than zero'); this.resetNextTimestamp(); } const hasAudio = !!initData.audio; const hasVideo = !!initData.video; let type = ''; if (hasAudio) { type += 'audio'; } if (hasVideo) { type += 'video'; } const track = { data1: data, startPTS: startTime, startDTS: startTime, endPTS: endTime, endDTS: endTime, type, hasAudio, hasVideo, nb: 1, dropped: 0 }; result.audio = track.type === 'audio' ? track : undefined; result.video = track.type !== 'audio' ? track : undefined; result.initSegment = initSegment; result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS); if (textTrack.samples.length) { result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS); } return result; } } function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) { if (initPTS === null) { return true; } // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second const minDuration = Math.max(duration, 1); const startTime = startDTS - initPTS.baseTime / initPTS.timescale; return Math.abs(startTime - timeOffset) > minDuration; } function getParsedTrackCodec(track, type) { const parsedCodec = track == null ? void 0 : track.codec; if (parsedCodec && parsedCodec.length > 4) { return parsedCodec; } // Since mp4-tools cannot parse full codec string (see 'TODO: Parse codec details'... in mp4-tools) // Provide defaults based on codec type // This allows for some playback of some fmp4 playlists without CODECS defined in manifest if (parsedCodec === 'hvc1' || parsedCodec === 'hev1') { return 'hvc1.1.6.L120.90'; } if (parsedCodec === 'av01') { return 'av01.0.04M.08'; } if (parsedCodec === 'avc1' || type === ElementaryStreamTypes.VIDEO) { return 'avc1.42e01e'; } return 'mp4a.40.5'; } let now; // performance.now() not available on WebWorker, at least on Safari Desktop try { now = self.performance.now.bind(self.performance); } catch (err) { logger.debug('Unable to use Performance API on this environment'); now = typeof self !== 'undefined' && self.Date.now; } const muxConfig = [{ demux: MP4Demuxer, remux: PassThroughRemuxer }, { demux: TSDemuxer, remux: MP4Remuxer }, { demux: AACDemuxer, remux: MP4Remuxer }, { demux: MP3Demuxer, remux: MP4Remuxer }]; class Transmuxer { constructor(observer, typeSupported, config, vendor, id) { this.async = false; this.observer = void 0; this.typeSupported = void 0; this.config = void 0; this.vendor = void 0; this.id = void 0; this.demuxer = void 0; this.remuxer = void 0; this.decrypter = void 0; this.probe = void 0; this.decryptionPromise = null; this.transmuxConfig = void 0; this.currentTransmuxState = void 0; this.observer = observer; this.typeSupported = typeSupported; this.config = config; this.vendor = vendor; this.id = id; } configure(transmuxConfig) { this.transmuxConfig = transmuxConfig; if (this.decrypter) { this.decrypter.reset(); } } push(data, decryptdata, chunkMeta, state) { const stats = chunkMeta.transmuxing; stats.executeStart = now(); let uintData = new Uint8Array(data); const { currentTransmuxState, transmuxConfig } = this; if (state) { this.currentTransmuxState = state; } const { contiguous, discontinuity, trackSwitch, accurateTimeOffset, timeOffset, initSegmentChange } = state || currentTransmuxState; const { audioCodec, videoCodec, defaultInitPts, duration, initSegmentData } = transmuxConfig; const keyData = getEncryptionType(uintData, decryptdata); if (keyData && keyData.method === 'AES-128') { const decrypter = this.getDecrypter(); // Software decryption is synchronous; webCrypto is not if (decrypter.isSync()) { // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached // data is handled in the flush() call let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer); // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress const loadingParts = chunkMeta.part > -1; if (loadingParts) { decryptedData = decrypter.flush(); } if (!decryptedData) { stats.executeEnd = now(); return emptyResult(chunkMeta); } uintData = new Uint8Array(decryptedData); } else { this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => { // Calling push here is important; if flush() is called while this is still resolving, this ensures that // the decrypted data has been transmuxed const result = this.push(decryptedData, null, chunkMeta); this.decryptionPromise = null; return result; }); return this.decryptionPromise; } } const resetMuxers = this.needsProbing(discontinuity, trackSwitch); if (resetMuxers) { const error = this.configureTransmuxer(uintData); if (error) { logger.warn(`[transmuxer] ${error.message}`); this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, error, reason: error.message }); stats.executeEnd = now(); return emptyResult(chunkMeta); } } if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) { this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata); } if (discontinuity || initSegmentChange || resetMuxers) { this.resetInitialTimestamp(defaultInitPts); } if (!contiguous) { this.resetContiguity(); } const result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta); const currentState = this.currentTransmuxState; currentState.contiguous = true; currentState.discontinuity = false; currentState.trackSwitch = false; stats.executeEnd = now(); return result; } // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type) flush(chunkMeta) { const stats = chunkMeta.transmuxing; stats.executeStart = now(); const { decrypter, currentTransmuxState, decryptionPromise } = this; if (decryptionPromise) { // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore // only flushing is required for async decryption return decryptionPromise.then(() => { return this.flush(chunkMeta); }); } const transmuxResults = []; const { timeOffset } = currentTransmuxState; if (decrypter) { // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads, // or for progressive downloads with small segments) const decryptedData = decrypter.flush(); if (decryptedData) { // Push always returns a TransmuxerResult if decryptdata is null transmuxResults.push(this.push(decryptedData, null, chunkMeta)); } } const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { // If probing failed, then Hls.js has been given content its not able to handle stats.executeEnd = now(); return [emptyResult(chunkMeta)]; } const demuxResultOrPromise = demuxer.flush(timeOffset); if (isPromise(demuxResultOrPromise)) { // Decrypt final SAMPLE-AES samples return demuxResultOrPromise.then(demuxResult => { this.flushRemux(transmuxResults, demuxResult, chunkMeta); return transmuxResults; }); } this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta); return transmuxResults; } flushRemux(transmuxResults, demuxResult, chunkMeta) { const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult; const { accurateTimeOffset, timeOffset } = this.currentTransmuxState; logger.log(`[transmuxer.ts]: Flushed fragment ${chunkMeta.sn}${chunkMeta.part > -1 ? ' p: ' + chunkMeta.part : ''} of level ${chunkMeta.level}`); const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id); transmuxResults.push({ remuxResult, chunkMeta }); chunkMeta.transmuxing.executeEnd = now(); } resetInitialTimestamp(defaultInitPts) { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetTimeStamp(defaultInitPts); remuxer.resetTimeStamp(defaultInitPts); } resetContiguity() { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetContiguity(); remuxer.resetNextTimestamp(); } resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration); remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata); } destroy() { if (this.demuxer) { this.demuxer.destroy(); this.demuxer = undefined; } if (this.remuxer) { this.remuxer.destroy(); this.remuxer = undefined; } } transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) { let result; if (keyData && keyData.method === 'SAMPLE-AES') { result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta); } else { result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta); } return result; } transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) { const { audioTrack, videoTrack, id3Track, textTrack } = this.demuxer.demux(data, timeOffset, false, !this.config.progressive); const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id); return { remuxResult, chunkMeta }; } transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) { return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(demuxResult => { const remuxResult = this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id); return { remuxResult, chunkMeta }; }); } configureTransmuxer(data) { const { config, observer, typeSupported, vendor } = this; // probe for content type let mux; for (let i = 0, len = muxConfig.length; i < len; i++) { if (muxConfig[i].demux.probe(data)) { mux = muxConfig[i]; break; } } if (!mux) { return new Error('Failed to find demuxer by probing fragment data'); } // so let's check that current remuxer and demuxer are still valid const demuxer = this.demuxer; const remuxer = this.remuxer; const Remuxer = mux.remux; const Demuxer = mux.demux; if (!remuxer || !(remuxer instanceof Remuxer)) { this.remuxer = new Remuxer(observer, config, typeSupported, vendor); } if (!demuxer || !(demuxer instanceof Demuxer)) { this.demuxer = new Demuxer(observer, config, typeSupported); this.probe = Demuxer.probe; } } needsProbing(discontinuity, trackSwitch) { // in case of continuity change, or track switch // we might switch from content type (AAC container to TS container, or TS to fmp4 for example) return !this.demuxer || !this.remuxer || discontinuity || trackSwitch; } getDecrypter() { let decrypter = this.decrypter; if (!decrypter) { decrypter = this.decrypter = new Decrypter(this.config); } return decrypter; } } function getEncryptionType(data, decryptData) { let encryptionType = null; if (data.byteLength > 0 && decryptData != null && decryptData.key != null && decryptData.iv !== null && decryptData.method != null) { encryptionType = decryptData; } return encryptionType; } const emptyResult = chunkMeta => ({ remuxResult: {}, chunkMeta }); function isPromise(p) { return 'then' in p && p.then instanceof Function; } class TransmuxConfig { constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) { this.audioCodec = void 0; this.videoCodec = void 0; this.initSegmentData = void 0; this.duration = void 0; this.defaultInitPts = void 0; this.audioCodec = audioCodec; this.videoCodec = videoCodec; this.initSegmentData = initSegmentData; this.duration = duration; this.defaultInitPts = defaultInitPts || null; } } class TransmuxState { constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) { this.discontinuity = void 0; this.contiguous = void 0; this.accurateTimeOffset = void 0; this.trackSwitch = void 0; this.timeOffset = void 0; this.initSegmentChange = void 0; this.discontinuity = discontinuity; this.contiguous = contiguous; this.accurateTimeOffset = accurateTimeOffset; this.trackSwitch = trackSwitch; this.timeOffset = timeOffset; this.initSegmentChange = initSegmentChange; } } var eventemitter3 = {exports: {}}; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @private */ function Events() {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null); // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false; } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @private */ function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } /** * Add a listener for a given event. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} once Specify if the listener is a one-time listener. * @returns {EventEmitter} * @private */ function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } /** * Clear event by name. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} evt The Event name. * @private */ function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @public */ function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; /** * Return the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Array} The registered listeners. * @public */ EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; /** * Return the number of listeners listening to a given event. * * @param {(String|Symbol)} event The event name. * @returns {Number} The number of listeners. * @public */ EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; /** * Calls each of the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @public */ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; /** * Add a listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; /** * Add a one-time listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; /** * Remove the listeners of a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {*} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; /** * Remove all listeners, or those of the specified event. * * @param {(String|Symbol)} [event] The event name. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; // // Expose the prefix. // EventEmitter.prefixed = prefix; // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter; // // Expose the module. // { module.exports = EventEmitter; } } (eventemitter3)); var eventemitter3Exports = eventemitter3.exports; var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); const MediaSource$1 = getMediaSource() || { isTypeSupported: () => false }; class TransmuxerInterface { constructor(hls, id, onTransmuxComplete, onFlush) { this.error = null; this.hls = void 0; this.id = void 0; this.observer = void 0; this.frag = null; this.part = null; this.useWorker = void 0; this.workerContext = null; this.onwmsg = void 0; this.transmuxer = null; this.onTransmuxComplete = void 0; this.onFlush = void 0; const config = hls.config; this.hls = hls; this.id = id; this.useWorker = !!config.enableWorker; this.onTransmuxComplete = onTransmuxComplete; this.onFlush = onFlush; const forwardMessage = (ev, data) => { data = data || {}; data.frag = this.frag; data.id = this.id; if (ev === Events.ERROR) { this.error = data.error; } this.hls.trigger(ev, data); }; // forward events to main thread this.observer = new EventEmitter(); this.observer.on(Events.FRAG_DECRYPTED, forwardMessage); this.observer.on(Events.ERROR, forwardMessage); const typeSupported = { mp4: MediaSource$1.isTypeSupported('video/mp4'), mpeg: MediaSource$1.isTypeSupported('audio/mpeg'), mp3: MediaSource$1.isTypeSupported('audio/mp4; codecs="mp3"') }; // navigator.vendor is not always available in Web Worker // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator const vendor = navigator.vendor; if (this.useWorker && typeof Worker !== 'undefined') { const canCreateWorker = config.workerPath || hasUMDWorker(); if (canCreateWorker) { try { if (config.workerPath) { logger.log(`loading Web Worker ${config.workerPath} for "${id}"`); this.workerContext = loadWorker(config.workerPath); } else { logger.log(`injecting Web Worker for "${id}"`); this.workerContext = injectWorker(); } this.onwmsg = ev => this.onWorkerMessage(ev); const { worker } = this.workerContext; worker.addEventListener('message', this.onwmsg); worker.onerror = event => { const error = new Error(`${event.message} (${event.filename}:${event.lineno})`); config.enableWorker = false; logger.warn(`Error in "${id}" Web Worker, fallback to inline`); this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: false, event: 'demuxerWorker', error }); }; worker.postMessage({ cmd: 'init', typeSupported: typeSupported, vendor: vendor, id: id, config: JSON.stringify(config) }); } catch (err) { logger.warn(`Error setting up "${id}" Web Worker, fallback to inline`, err); this.resetWorker(); this.error = null; this.transmuxer = new Transmuxer(this.observer, typeSupported, config, vendor, id); } return; } } this.transmuxer = new Transmuxer(this.observer, typeSupported, config, vendor, id); } resetWorker() { if (this.workerContext) { const { worker, objectURL } = this.workerContext; if (objectURL) { // revoke the Object URL that was used to create transmuxer worker, so as not to leak it self.URL.revokeObjectURL(objectURL); } worker.removeEventListener('message', this.onwmsg); worker.onerror = null; worker.terminate(); this.workerContext = null; } } destroy() { if (this.workerContext) { this.resetWorker(); this.onwmsg = undefined; } else { const transmuxer = this.transmuxer; if (transmuxer) { transmuxer.destroy(); this.transmuxer = null; } } const observer = this.observer; if (observer) { observer.removeAllListeners(); } this.frag = null; // @ts-ignore this.observer = null; // @ts-ignore this.hls = null; } push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) { var _frag$initSegment, _lastFrag$initSegment; chunkMeta.transmuxing.start = self.performance.now(); const { transmuxer } = this; const timeOffset = part ? part.start : frag.start; // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones const decryptdata = frag.decryptdata; const lastFrag = this.frag; const discontinuity = !(lastFrag && frag.cc === lastFrag.cc); const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level); const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1; const partDiff = this.part ? chunkMeta.part - this.part.index : -1; const progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount); const contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0)); const now = self.performance.now(); if (trackSwitch || snDiff || frag.stats.parsing.start === 0) { frag.stats.parsing.start = now; } if (part && (partDiff || !contiguous)) { part.stats.parsing.start = now; } const initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url)); const state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange); if (!contiguous || discontinuity || initSegmentChange) { logger.log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id} discontinuity: ${discontinuity} trackSwitch: ${trackSwitch} contiguous: ${contiguous} accurateTimeOffset: ${accurateTimeOffset} timeOffset: ${timeOffset} initSegmentChange: ${initSegmentChange}`); const config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS); this.configureTransmuxer(config); } this.frag = frag; this.part = part; // Frags with sn of 'initSegment' are not transmuxed if (this.workerContext) { // post fragment payload as transferable objects for ArrayBuffer (no copy) this.workerContext.worker.postMessage({ cmd: 'demux', data, decryptdata, chunkMeta, state }, data instanceof ArrayBuffer ? [data] : []); } else if (transmuxer) { const transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state); if (isPromise(transmuxResult)) { transmuxer.async = true; transmuxResult.then(data => { this.handleTransmuxComplete(data); }).catch(error => { this.transmuxerError(error, chunkMeta, 'transmuxer-interface push error'); }); } else { transmuxer.async = false; this.handleTransmuxComplete(transmuxResult); } } } flush(chunkMeta) { chunkMeta.transmuxing.start = self.performance.now(); const { transmuxer } = this; if (this.workerContext) { this.workerContext.worker.postMessage({ cmd: 'flush', chunkMeta }); } else if (transmuxer) { let transmuxResult = transmuxer.flush(chunkMeta); const asyncFlush = isPromise(transmuxResult); if (asyncFlush || transmuxer.async) { if (!isPromise(transmuxResult)) { transmuxResult = Promise.resolve(transmuxResult); } transmuxResult.then(data => { this.handleFlushResult(data, chunkMeta); }).catch(error => { this.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error'); }); } else { this.handleFlushResult(transmuxResult, chunkMeta); } } } transmuxerError(error, chunkMeta, reason) { if (!this.hls) { return; } this.error = error; this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, chunkMeta, fatal: false, error, err: error, reason }); } handleFlushResult(results, chunkMeta) { results.forEach(result => { this.handleTransmuxComplete(result); }); this.onFlush(chunkMeta); } onWorkerMessage(ev) { const data = ev.data; const hls = this.hls; switch (data.event) { case 'init': { var _this$workerContext; const objectURL = (_this$workerContext = this.workerContext) == null ? void 0 : _this$workerContext.objectURL; if (objectURL) { // revoke the Object URL that was used to create transmuxer worker, so as not to leak it self.URL.revokeObjectURL(objectURL); } break; } case 'transmuxComplete': { this.handleTransmuxComplete(data.data); break; } case 'flush': { this.onFlush(data.data); break; } // pass logs from the worker thread to the main logger case 'workerLog': if (logger[data.data.logType]) { logger[data.data.logType](data.data.message); } break; default: { data.data = data.data || {}; data.data.frag = this.frag; data.data.id = this.id; hls.trigger(data.event, data.data); break; } } } configureTransmuxer(config) { const { transmuxer } = this; if (this.workerContext) { this.workerContext.worker.postMessage({ cmd: 'configure', config }); } else if (transmuxer) { transmuxer.configure(config); } } handleTransmuxComplete(result) { result.chunkMeta.transmuxing.end = self.performance.now(); this.onTransmuxComplete(result); } } const STALL_MINIMUM_DURATION_MS = 250; const MAX_START_GAP_JUMP = 2.0; const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1; const SKIP_BUFFER_RANGE_START = 0.05; class GapController { constructor(config, media, fragmentTracker, hls) { this.config = void 0; this.media = null; this.fragmentTracker = void 0; this.hls = void 0; this.nudgeRetry = 0; this.stallReported = false; this.stalled = null; this.moved = false; this.seeking = false; this.config = config; this.media = media; this.fragmentTracker = fragmentTracker; this.hls = hls; } destroy() { this.media = null; // @ts-ignore this.hls = this.fragmentTracker = null; } /** * Checks if the playhead is stuck within a gap, and if so, attempts to free it. * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range). * * @param lastCurrentTime - Previously read playhead position */ poll(lastCurrentTime, activeFrag) { const { config, media, stalled } = this; if (media === null) { return; } const { currentTime, seeking } = media; const seeked = this.seeking && !seeking; const beginSeek = !this.seeking && seeking; this.seeking = seeking; // The playhead is moving, no-op if (currentTime !== lastCurrentTime) { this.moved = true; if (stalled !== null) { // The playhead is now moving, but was previously stalled if (this.stallReported) { const _stalledDuration = self.performance.now() - stalled; logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`); this.stallReported = false; } this.stalled = null; this.nudgeRetry = 0; } return; } // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek if (beginSeek || seeked) { this.stalled = null; return; } // The playhead should not be moving if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) { return; } const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const isBuffered = bufferInfo.len > 0; const nextStart = bufferInfo.nextStart || 0; // There is no playable buffer (seeked, waiting for buffer) if (!isBuffered && !nextStart) { return; } if (seeking) { // Waiting for seeking in a buffered range to complete const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP; // Next buffered range is too far ahead to jump to while still seeking const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime); if (hasEnoughBuffer || noBufferGap) { return; } // Reset moved state when seeking to a point in or before a gap this.moved = false; } // Skip start gaps if we haven't played, but the last poll detected the start of a stall // The addition poll gives the browser a chance to jump the gap for us if (!this.moved && this.stalled !== null) { var _level$details; // Jump start gaps within jump threshold const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime; // When joining a live stream with audio tracks, account for live playlist window sliding by allowing // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment // that begins over 1 target duration after the video start position. const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null; const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live; const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP; const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime); if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) { this._trySkipBufferHole(partialOrGap); return; } } // Start tracking stall time const tnow = self.performance.now(); if (stalled === null) { this.stalled = tnow; return; } const stalledDuration = tnow - stalled; if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) { // Report stalling after trying to fix this._reportStall(bufferInfo); if (!this.media) { return; } } const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole); this._tryFixBufferStall(bufferedWithHoles, stalledDuration); } /** * Detects and attempts to fix known buffer stalling issues. * @param bufferInfo - The properties of the current buffer. * @param stalledDurationMs - The amount of time Hls.js has been stalling for. * @private */ _tryFixBufferStall(bufferInfo, stalledDurationMs) { const { config, fragmentTracker, media } = this; if (media === null) { return; } const currentTime = media.currentTime; const partial = fragmentTracker.getPartialFragment(currentTime); if (partial) { // Try to skip over the buffer hole caused by a partial fragment // This method isn't limited by the size of the gap between buffered ranges const targetTime = this._trySkipBufferHole(partial); // we return here in this case, meaning // the branch below only executes when we haven't seeked to a new position if (targetTime || !this.media) { return; } } // if we haven't had to skip over a buffer hole of a partial fragment // we may just have to "nudge" the playlist as the browser decoding/rendering engine // needs to cross some sort of threshold covering all source-buffers content // to start playing properly. if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) { logger.warn('Trying to nudge playhead over buffer-hole'); // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds // We only try to jump the hole if it's under the configured size // Reset stalled so to rearm watchdog timer this.stalled = null; this._tryNudgeBuffer(); } } /** * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period. * @param bufferLen - The playhead distance from the end of the current buffer segment. * @private */ _reportStall(bufferInfo) { const { hls, media, stallReported } = this; if (!stallReported && media) { // Report stalled error once this.stallReported = true; const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`); logger.warn(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, fatal: false, error, buffer: bufferInfo.len }); } } /** * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments * @param partial - The partial fragment found at the current time (where playback is stalling). * @private */ _trySkipBufferHole(partial) { const { config, hls, media } = this; if (media === null) { return 0; } // Check if currentTime is between unbuffered regions of partial fragments const currentTime = media.currentTime; const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart; if (startTime) { const bufferStarved = bufferInfo.len <= config.maxBufferHole; const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3; const gapLength = startTime - currentTime; if (gapLength > 0 && (bufferStarved || waiting)) { // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial if (gapLength > config.maxBufferHole) { const { fragmentTracker } = this; let startGap = false; if (currentTime === 0) { const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN); if (startFrag && startTime < startFrag.end) { startGap = true; } } if (!startGap) { const startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN); if (startProvisioned) { let moreToLoad = false; let pos = startProvisioned.end; while (pos < startTime) { const provisioned = fragmentTracker.getPartialFragment(pos); if (provisioned) { pos += provisioned.duration; } else { moreToLoad = true; break; } } if (moreToLoad) { return 0; } } } } const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS); logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`); this.moved = true; this.stalled = null; media.currentTime = targetTime; if (partial && !partial.gap) { const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, error, reason: error.message, frag: partial }); } return targetTime; } } return 0; } /** * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount. * @private */ _tryNudgeBuffer() { const { config, hls, media, nudgeRetry } = this; if (media === null) { return; } const currentTime = media.currentTime; this.nudgeRetry++; if (nudgeRetry < config.nudgeMaxRetry) { const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset; // playback stalled in buffered area ... let's nudge currentTime to try to overcome this const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`); logger.warn(error.message); media.currentTime = targetTime; hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_NUDGE_ON_STALL, error, fatal: false }); } else { const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`); logger.error(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, error, fatal: true }); } } } const TICK_INTERVAL$2 = 100; // how often to tick in ms class StreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN); this.audioCodecSwap = false; this.gapController = null; this.level = -1; this._forceStartLoad = false; this.altAudio = false; this.audioOnly = false; this.fragPlaying = null; this.onvplaying = null; this.onvseeked = null; this.fragLastKbps = 0; this.couldBacktrack = false; this.backtrackFragment = null; this.audioCodecSwitch = false; this.videoBuffer = null; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } onHandlerDestroying() { this._unregisterListeners(); this.onMediaDetaching(); } startLoad(startPosition) { if (this.levels) { const { lastCurrentTime, hls } = this; this.stopLoad(); this.setInterval(TICK_INTERVAL$2); this.level = -1; if (!this.startFragRequested) { // determine load level let startLevel = hls.startLevel; if (startLevel === -1) { if (hls.config.testBandwidth && this.levels.length > 1) { // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level startLevel = 0; this.bitrateTest = true; } else { startLevel = hls.nextAutoLevel; } } // set new level to playlist loader : this will trigger start level load // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded this.level = hls.nextLoadLevel = startLevel; this.loadedmetadata = false; } // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime if (lastCurrentTime > 0 && startPosition === -1) { this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`); startPosition = lastCurrentTime; } this.state = State.IDLE; this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } else { this._forceStartLoad = true; this.state = State.STOPPED; } } stopLoad() { this._forceStartLoad = false; super.stopLoad(); } doTick() { switch (this.state) { case State.WAITING_LEVEL: { var _levels$level; const { levels, level } = this; const details = levels == null ? void 0 : (_levels$level = levels[level]) == null ? void 0 : _levels$level.details; if (details && (!details.live || this.levelLastLoaded === this.level)) { if (this.waitForCdnTuneIn(details)) { break; } this.state = State.IDLE; break; } else if (this.hls.nextLoadLevel !== this.level) { this.state = State.IDLE; break; } break; } case State.FRAG_LOADING_WAITING_RETRY: { var _this$media; const now = self.performance.now(); const retryDate = this.retryDate; // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { this.resetStartWhenNotLoaded(this.level); this.state = State.IDLE; } } break; } if (this.state === State.IDLE) { this.doTickIdle(); } this.onTickEnd(); } onTickEnd() { super.onTickEnd(); this.checkBuffer(); this.checkFragmentChanged(); } doTickIdle() { const { hls, levelLastLoaded, levels, media } = this; const { config, nextLoadLevel: level } = hls; // if start level not parsed yet OR // if video not attached AND start fragment already requested OR start frag prefetch not enabled // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment if (levelLastLoaded === null || !media && (this.startFragRequested || !config.startFragPrefetch)) { return; } // If the "main" level is audio-only but we are loading an alternate track in the same group, do not load anything if (this.altAudio && this.audioOnly) { return; } if (!(levels != null && levels[level])) { return; } const levelInfo = levels[level]; // if buffer length is less than maxBufLen try to load a new fragment const bufferInfo = this.getMainFwdBufferInfo(); if (bufferInfo === null) { return; } const lastDetails = this.getLevelDetails(); if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) { const data = {}; if (this.altAudio) { data.type = 'video'; } this.hls.trigger(Events.BUFFER_EOS, data); this.state = State.ENDED; return; } // set next load level : this will trigger a playlist load if needed if (hls.loadLevel !== level && hls.manualLevel === -1) { this.log(`Adapting to level ${level} from level ${this.level}`); } this.level = hls.nextLoadLevel = level; const levelDetails = levelInfo.details; // if level info not retrieved yet, switch state and wait for level retrieval // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist) if (!levelDetails || this.state === State.WAITING_LEVEL || levelDetails.live && this.levelLastLoaded !== level) { this.level = level; this.state = State.WAITING_LEVEL; return; } const bufferLen = bufferInfo.len; // compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate); // Stay idle if we are still with buffer margins if (bufferLen >= maxBufLen) { return; } if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) { this.backtrackFragment = null; } const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end; let frag = this.getNextFragment(targetBufferTime, levelDetails); // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`) if (this.couldBacktrack && !this.fragPrevious && frag && frag.sn !== 'initSegment' && this.fragmentTracker.getState(frag) !== FragmentState.OK) { var _this$backtrackFragme; const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn; const fragIdx = backtrackSn - levelDetails.startSN; const backtrackFrag = levelDetails.fragments[fragIdx - 1]; if (backtrackFrag && frag.cc === backtrackFrag.cc) { frag = backtrackFrag; this.fragmentTracker.removeFragment(backtrackFrag); } } else if (this.backtrackFragment && bufferInfo.len) { this.backtrackFragment = null; } // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags if (frag && this.isLoopLoading(frag, targetBufferTime)) { const gapStart = frag.gap; if (!gapStart) { // Cleanup the fragment tracker before trying to find the next unbuffered fragment const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO; const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; if (mediaBuffer) { this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); } } frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); } if (!frag) { return; } if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) { frag = frag.initSegment; } this.loadFragment(frag, levelInfo, targetBufferTime); } loadFragment(frag, level, targetBufferTime) { // Check if fragment is not loaded const fragState = this.fragmentTracker.getState(frag); this.fragCurrent = frag; if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { if (frag.sn === 'initSegment') { this._loadInitSegment(frag, level); } else if (this.bitrateTest) { this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`); this._loadBitrateTestFrag(frag, level); } else { this.startFragRequested = true; super.loadFragment(frag, level, targetBufferTime); } } else { this.clearTrackerIfNeeded(frag); } } getBufferedFrag(position) { return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN); } followingBufferedFrag(frag) { if (frag) { // try to get range of next fragment (500ms after this range) return this.getBufferedFrag(frag.end + 0.5); } return null; } /* on immediate level switch : - pause playback if playing - cancel any pending load request - and trigger a buffer flush */ immediateLevelSwitch() { this.abortCurrentFrag(); this.flushMainBuffer(0, Number.POSITIVE_INFINITY); } /** * try to switch ASAP without breaking video playback: * in order to ensure smooth but quick level switching, * we need to find the next flushable buffer range * we should take into account new segment fetch time */ nextLevelSwitch() { const { levels, media } = this; // ensure that media is defined and that metadata are available (to retrieve currentTime) if (media != null && media.readyState) { let fetchdelay; const fragPlayingCurrent = this.getAppendedFrag(media.currentTime); if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { // flush buffer preceding current fragment (flush until current fragment start offset) // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ... this.flushMainBuffer(0, fragPlayingCurrent.start - 1); } const levelDetails = this.getLevelDetails(); if (levelDetails != null && levelDetails.live) { const bufferInfo = this.getMainFwdBufferInfo(); // Do not flush in live stream with low buffer if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) { return; } } if (!media.paused && levels) { // add a safety delay of 1s const nextLevelId = this.hls.nextLoadLevel; const nextLevel = levels[nextLevelId]; const fragLastKbps = this.fragLastKbps; if (fragLastKbps && this.fragCurrent) { fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1; } else { fetchdelay = 0; } } else { fetchdelay = 0; } // this.log('fetchdelay:'+fetchdelay); // find buffer range that will be reached once new fragment will be fetched const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay); if (bufferedFrag) { // we can flush buffer range following this one without stalling playback const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag); if (nextBufferedFrag) { // if we are here, we can also cancel any loading/demuxing in progress, as they are useless this.abortCurrentFrag(); // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback. const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start; const fragDuration = nextBufferedFrag.duration; const startPts = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * 0.5), fragDuration * 0.75)); this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY); } } } } abortCurrentFrag() { const fragCurrent = this.fragCurrent; this.fragCurrent = null; this.backtrackFragment = null; if (fragCurrent) { fragCurrent.abortRequests(); this.fragmentTracker.removeFragment(fragCurrent); } switch (this.state) { case State.KEY_LOADING: case State.FRAG_LOADING: case State.FRAG_LOADING_WAITING_RETRY: case State.PARSING: case State.PARSED: this.state = State.IDLE; break; } this.nextLoadPosition = this.getLoadPosition(); } flushMainBuffer(startOffset, endOffset) { super.flushMainBuffer(startOffset, endOffset, this.altAudio ? 'video' : null); } onMediaAttached(event, data) { super.onMediaAttached(event, data); const media = data.media; this.onvplaying = this.onMediaPlaying.bind(this); this.onvseeked = this.onMediaSeeked.bind(this); media.addEventListener('playing', this.onvplaying); media.addEventListener('seeked', this.onvseeked); this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls); } onMediaDetaching() { const { media } = this; if (media && this.onvplaying && this.onvseeked) { media.removeEventListener('playing', this.onvplaying); media.removeEventListener('seeked', this.onvseeked); this.onvplaying = this.onvseeked = null; this.videoBuffer = null; } this.fragPlaying = null; if (this.gapController) { this.gapController.destroy(); this.gapController = null; } super.onMediaDetaching(); } onMediaPlaying() { // tick to speed up FRAG_CHANGED triggering this.tick(); } onMediaSeeked() { const media = this.media; const currentTime = media ? media.currentTime : null; if (isFiniteNumber(currentTime)) { this.log(`Media seeked to ${currentTime.toFixed(3)}`); } // If seeked was issued before buffer was appended do not tick immediately const bufferInfo = this.getMainFwdBufferInfo(); if (bufferInfo === null || bufferInfo.len === 0) { this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`); return; } // tick to speed up FRAG_CHANGED triggering this.tick(); } onManifestLoading() { // reset buffer on manifest loading this.log('Trigger BUFFER_RESET'); this.hls.trigger(Events.BUFFER_RESET, undefined); this.fragmentTracker.removeAllFragments(); this.couldBacktrack = false; this.startPosition = this.lastCurrentTime = 0; this.levels = this.fragPlaying = this.backtrackFragment = null; this.altAudio = this.audioOnly = false; } onManifestParsed(event, data) { let aac = false; let heaac = false; let codec; data.levels.forEach(level => { // detect if we have different kind of audio codecs used amongst playlists codec = level.audioCodec; if (codec) { if (codec.indexOf('mp4a.40.2') !== -1) { aac = true; } if (codec.indexOf('mp4a.40.5') !== -1) { heaac = true; } } }); this.audioCodecSwitch = aac && heaac && !changeTypeSupported(); if (this.audioCodecSwitch) { this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC'); } this.levels = data.levels; this.startFragRequested = false; } onLevelLoading(event, data) { const { levels } = this; if (!levels || this.state !== State.IDLE) { return; } const level = levels[data.level]; if (!level.details || level.details.live && this.levelLastLoaded !== data.level || this.waitForCdnTuneIn(level.details)) { this.state = State.WAITING_LEVEL; } } onLevelLoaded(event, data) { var _curLevel$details; const { levels } = this; const newLevelId = data.level; const newDetails = data.details; const duration = newDetails.totalduration; if (!levels) { this.warn(`Levels were reset while loading level ${newLevelId}`); return; } this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`); const curLevel = levels[newLevelId]; const fragCurrent = this.fragCurrent; if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) { if ((fragCurrent.level !== data.level || fragCurrent.urlId !== curLevel.urlId) && fragCurrent.loader) { this.abortCurrentFrag(); } } let sliding = 0; if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) { this.checkLiveUpdate(newDetails); if (newDetails.deltaUpdateFailed) { return; } sliding = this.alignPlaylists(newDetails, curLevel.details); } // override level info curLevel.details = newDetails; this.levelLastLoaded = newLevelId; this.hls.trigger(Events.LEVEL_UPDATED, { details: newDetails, level: newLevelId }); // only switch back to IDLE state if we were waiting for level to start downloading a new fragment if (this.state === State.WAITING_LEVEL) { if (this.waitForCdnTuneIn(newDetails)) { // Wait for Low-Latency CDN Tune-in return; } this.state = State.IDLE; } if (!this.startFragRequested) { this.setStartPosition(newDetails, sliding); } else if (newDetails.live) { this.synchronizeToLiveEdge(newDetails); } // trigger handler right now this.tick(); } _handleFragmentLoadProgress(data) { var _frag$initSegment; const { frag, part, payload } = data; const { levels } = this; if (!levels) { this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`); return; } const currentLevel = levels[frag.level]; const details = currentLevel.details; if (!details) { this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`); this.fragmentTracker.removeFragment(frag); return; } const videoCodec = currentLevel.videoCodec; // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) const accurateTimeOffset = details.PTSKnown || !details.live; const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; const audioCodec = this._getAudioCodec(currentLevel); // transmux the MPEG-TS data to ISO-BMFF segments // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`); const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); const partIndex = part ? part.index : -1; const partial = partIndex !== -1; const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); const initPTS = this.initPTS[frag.cc]; transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); } onAudioTrackSwitching(event, data) { // if any URL found on new audio track, it is an alternate audio track const fromAltAudio = this.altAudio; const altAudio = !!data.url; // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered // don't do anything if we switch to alt audio: audio stream controller is handling it. // we will just have to change buffer scheduling on audioTrackSwitched if (!altAudio) { if (this.mediaBuffer !== this.media) { this.log('Switching on main audio, use media.buffered to schedule main fragment loading'); this.mediaBuffer = this.media; const fragCurrent = this.fragCurrent; // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch if (fragCurrent) { this.log('Switching to main audio track, cancel main fragment load'); fragCurrent.abortRequests(); this.fragmentTracker.removeFragment(fragCurrent); } // destroy transmuxer to force init segment generation (following audio switch) this.resetTransmuxer(); // switch to IDLE state to load new fragment this.resetLoadingState(); } else if (this.audioOnly) { // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off this.resetTransmuxer(); } const hls = this.hls; // If switching from alt to main audio, flush all audio and trigger track switched if (fromAltAudio) { hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: null }); this.fragmentTracker.removeAllFragments(); } hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); } } onAudioTrackSwitched(event, data) { const trackId = data.id; const altAudio = !!this.hls.audioTracks[trackId].url; if (altAudio) { const videoBuffer = this.videoBuffer; // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered if (videoBuffer && this.mediaBuffer !== videoBuffer) { this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading'); this.mediaBuffer = videoBuffer; } } this.altAudio = altAudio; this.tick(); } onBufferCreated(event, data) { const tracks = data.tracks; let mediaTrack; let name; let alternate = false; for (const type in tracks) { const track = tracks[type]; if (track.id === 'main') { name = type; mediaTrack = track; // keep video source buffer reference if (type === 'video') { const videoTrack = tracks[type]; if (videoTrack) { this.videoBuffer = videoTrack.buffer; } } } else { alternate = true; } } if (alternate && mediaTrack) { this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`); this.mediaBuffer = mediaTrack.buffer; } else { this.mediaBuffer = this.media; } } onFragBuffered(event, data) { const { frag, part } = data; if (frag && frag.type !== PlaylistLevelType.MAIN) { return; } if (this.fragContextChanged(frag)) { // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion // Avoid setting state back to IDLE, since that will interfere with a level switch this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`); if (this.state === State.PARSED) { this.state = State.IDLE; } return; } const stats = part ? part.stats : frag.stats; this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first)); if (frag.sn !== 'initSegment') { this.fragPrevious = frag; } this.fragBufferedComplete(frag, part); } onError(event, data) { var _data$context; if (data.fatal) { this.state = State.ERROR; return; } switch (data.details) { case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_PARSING_ERROR: case ErrorDetails.FRAG_DECRYPT_ERROR: case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data); break; case ErrorDetails.LEVEL_LOAD_ERROR: case ErrorDetails.LEVEL_LOAD_TIMEOUT: case ErrorDetails.LEVEL_PARSING_ERROR: // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) { this.state = State.IDLE; } break; case ErrorDetails.BUFFER_FULL_ERROR: if (!data.parent || data.parent !== 'main') { return; } if (this.reduceLengthAndFlushBuffer(data)) { this.flushMainBuffer(0, Number.POSITIVE_INFINITY); } break; case ErrorDetails.INTERNAL_EXCEPTION: this.recoverWorkerError(data); break; } } // Checks the health of the buffer and attempts to resolve playback stalls. checkBuffer() { const { media, gapController } = this; if (!media || !gapController || !media.readyState) { // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) return; } if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) { // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null; gapController.poll(this.lastCurrentTime, activeFrag); } this.lastCurrentTime = media.currentTime; } onFragLoadEmergencyAborted() { this.state = State.IDLE; // if loadedmetadata is not set, it means that we are emergency switch down on first frag // in that case, reset startFragRequested flag if (!this.loadedmetadata) { this.startFragRequested = false; this.nextLoadPosition = this.startPosition; } this.tickImmediate(); } onBufferFlushed(event, { type }) { if (type !== ElementaryStreamTypes.AUDIO || this.audioOnly && !this.altAudio) { const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); } } onLevelsUpdated(event, data) { this.levels = data.levels; } swapAudioCodec() { this.audioCodecSwap = !this.audioCodecSwap; } /** * Seeks to the set startPosition if not equal to the mediaElement's current time. */ seekToStartPos() { const { media } = this; if (!media) { return; } const currentTime = media.currentTime; let startPosition = this.startPosition; // only adjust currentTime if different from startPosition or if startPosition not buffered // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered if (startPosition >= 0 && currentTime < startPosition) { if (media.seeking) { this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`); return; } const buffered = BufferHelper.getBuffered(media); const bufferStart = buffered.length ? buffered.start(0) : 0; const delta = bufferStart - startPosition; if (delta > 0 && (delta < this.config.maxBufferHole || delta < this.config.maxFragLookUpTolerance)) { this.log(`adjusting start position by ${delta} to match buffer start`); startPosition += delta; this.startPosition = startPosition; } this.log(`seek to target start position ${startPosition} from current time ${currentTime}`); media.currentTime = startPosition; } } _getAudioCodec(currentLevel) { let audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec; if (this.audioCodecSwap && audioCodec) { this.log('Swapping audio codec'); if (audioCodec.indexOf('mp4a.40.5') !== -1) { audioCodec = 'mp4a.40.2'; } else { audioCodec = 'mp4a.40.5'; } } return audioCodec; } _loadBitrateTestFrag(frag, level) { frag.bitrateTest = true; this._doFragLoad(frag, level).then(data => { const { hls } = this; if (!data || this.fragContextChanged(frag)) { return; } level.fragmentError = 0; this.state = State.IDLE; this.startFragRequested = false; this.bitrateTest = false; const stats = frag.stats; // Bitrate tests fragments are neither parsed nor buffered stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now(); hls.trigger(Events.FRAG_LOADED, data); frag.bitrateTest = false; }); } _handleTransmuxComplete(transmuxResult) { var _id3$samples; const id = 'main'; const { hls } = this; const { remuxResult, chunkMeta } = transmuxResult; const context = this.getCurrentContext(chunkMeta); if (!context) { this.resetWhenMissingContext(chunkMeta); return; } const { frag, part, level } = context; const { video, text, id3, initSegment } = remuxResult; const { details } = level; // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track const audio = this.altAudio ? undefined : remuxResult.audio; // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. if (this.fragContextChanged(frag)) { this.fragmentTracker.removeFragment(frag); return; } this.state = State.PARSING; if (initSegment) { if (initSegment != null && initSegment.tracks) { const mapFragment = frag.initSegment || frag; this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, tracks: initSegment.tracks }); } // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038 const initPTS = initSegment.initPTS; const timescale = initSegment.timescale; if (isFiniteNumber(initPTS)) { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; hls.trigger(Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale }); } } // Avoid buffering if backtracking this fragment if (video && details && frag.sn !== 'initSegment') { const prevFrag = details.fragments[frag.sn - 1 - details.startSN]; const isFirstFragment = frag.sn === details.startSN; const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc; if (remuxResult.independent !== false) { const { startPTS, endPTS, startDTS, endDTS } = video; if (part) { part.elementaryStreams[video.type] = { startPTS, endPTS, startDTS, endDTS }; } else { if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) { this.couldBacktrack = true; } if (video.dropped && video.independent) { // Backtrack if dropped frames create a gap after currentTime const bufferInfo = this.getMainFwdBufferInfo(); const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole; const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS; if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) { this.backtrack(frag); return; } else if (isFirstInDiscontinuity) { // Mark segment with a gap to avoid loop loading frag.gap = true; } // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true); } } frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS); if (this.backtrackFragment) { this.backtrackFragment = frag; } this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity); } else if (isFirstFragment || isFirstInDiscontinuity) { // Mark segment with a gap to avoid loop loading frag.gap = true; } else { this.backtrack(frag); return; } } if (audio) { const { startPTS, endPTS, startDTS, endDTS } = audio; if (part) { part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { startPTS, endPTS, startDTS, endDTS }; } frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS); this.bufferFragmentData(audio, frag, part, chunkMeta); } if (details && id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { const emittedID3 = { id, frag, details, samples: id3.samples }; hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); } if (details && text) { const emittedText = { id, frag, details, samples: text.samples }; hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); } } _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { if (this.state !== State.PARSING) { return; } this.audioOnly = !!tracks.audio && !tracks.video; // if audio track is expected to come from audio stream controller, discard any coming from main if (this.altAudio && !this.audioOnly) { delete tracks.audio; } // include levelCodec in audio and video tracks const { audio, video, audiovideo } = tracks; if (audio) { let audioCodec = currentLevel.audioCodec; const ua = navigator.userAgent.toLowerCase(); if (this.audioCodecSwitch) { if (audioCodec) { if (audioCodec.indexOf('mp4a.40.5') !== -1) { audioCodec = 'mp4a.40.2'; } else { audioCodec = 'mp4a.40.5'; } } // In the case that AAC and HE-AAC audio codecs are signalled in manifest, // force HE-AAC, as it seems that most browsers prefers it. // don't force HE-AAC if mono stream, or in Firefox if (audio.metadata.channelCount !== 1 && ua.indexOf('firefox') === -1) { audioCodec = 'mp4a.40.5'; } } // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise if (ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') { // Exclude mpeg audio audioCodec = 'mp4a.40.2'; this.log(`Android: force audio codec to ${audioCodec}`); } if (currentLevel.audioCodec && currentLevel.audioCodec !== audioCodec) { this.log(`Swapping manifest audio codec "${currentLevel.audioCodec}" for "${audioCodec}"`); } audio.levelCodec = audioCodec; audio.id = 'main'; this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${currentLevel.audioCodec || ''}/${audio.codec}]`); } if (video) { video.levelCodec = currentLevel.videoCodec; video.id = 'main'; this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${video.codec}]`); } if (audiovideo) { this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.attrs.CODECS || ''}/${audiovideo.codec}]`); } this.hls.trigger(Events.BUFFER_CODECS, tracks); // loop through tracks that are going to be provided to bufferController Object.keys(tracks).forEach(trackName => { const track = tracks[trackName]; const initSegment = track.initSegment; if (initSegment != null && initSegment.byteLength) { this.hls.trigger(Events.BUFFER_APPENDING, { type: trackName, data: initSegment, frag, part: null, chunkMeta, parent: frag.type }); } }); // trigger handler right now this.tick(); } getMainFwdBufferInfo() { return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN); } backtrack(frag) { this.couldBacktrack = true; // Causes findFragments to backtrack through fragments to find the keyframe this.backtrackFragment = frag; this.resetTransmuxer(); this.flushBufferGap(frag); this.fragmentTracker.removeFragment(frag); this.fragPrevious = null; this.nextLoadPosition = frag.start; this.state = State.IDLE; } checkFragmentChanged() { const video = this.media; let fragPlayingCurrent = null; if (video && video.readyState > 1 && video.seeking === false) { const currentTime = video.currentTime; /* if video element is in seeked state, currentTime can only increase. (assuming that playback rate is positive ...) As sometimes currentTime jumps back to zero after a media decode error, check this, to avoid seeking back to wrong position after a media decode error */ if (BufferHelper.isBuffered(video, currentTime)) { fragPlayingCurrent = this.getAppendedFrag(currentTime); } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) { /* ensure that FRAG_CHANGED event is triggered at startup, when first video frame is displayed and playback is paused. add a tolerance of 100ms, in case current position is not buffered, check if current pos+100ms is buffered and use that buffer range for FRAG_CHANGED event reporting */ fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1); } if (fragPlayingCurrent) { this.backtrackFragment = null; const fragPlaying = this.fragPlaying; const fragCurrentLevel = fragPlayingCurrent.level; if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel || fragPlayingCurrent.urlId !== fragPlaying.urlId) { this.fragPlaying = fragPlayingCurrent; this.hls.trigger(Events.FRAG_CHANGED, { frag: fragPlayingCurrent }); if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) { this.hls.trigger(Events.LEVEL_SWITCHED, { level: fragCurrentLevel }); } } } } } get nextLevel() { const frag = this.nextBufferedFrag; if (frag) { return frag.level; } return -1; } get currentFrag() { const media = this.media; if (media) { return this.fragPlaying || this.getAppendedFrag(media.currentTime); } return null; } get currentProgramDateTime() { const media = this.media; if (media) { const currentTime = media.currentTime; const frag = this.currentFrag; if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) { const epocMs = frag.programDateTime + (currentTime - frag.start) * 1000; return new Date(epocMs); } } return null; } get currentLevel() { const frag = this.currentFrag; if (frag) { return frag.level; } return -1; } get nextBufferedFrag() { const frag = this.currentFrag; if (frag) { return this.followingBufferedFrag(frag); } return null; } get forceStartLoad() { return this._forceStartLoad; } } /* * compute an Exponential Weighted moving average * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average * - heavily inspired from shaka-player */ class EWMA { // About half of the estimated value will be from the last |halfLife| samples by weight. constructor(halfLife, estimate = 0, weight = 0) { this.halfLife = void 0; this.alpha_ = void 0; this.estimate_ = void 0; this.totalWeight_ = void 0; this.halfLife = halfLife; // Larger values of alpha expire historical data more slowly. this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0; this.estimate_ = estimate; this.totalWeight_ = weight; } sample(weight, value) { const adjAlpha = Math.pow(this.alpha_, weight); this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_; this.totalWeight_ += weight; } getTotalWeight() { return this.totalWeight_; } getEstimate() { if (this.alpha_) { const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_); if (zeroFactor) { return this.estimate_ / zeroFactor; } } return this.estimate_; } } /* * EWMA Bandwidth Estimator * - heavily inspired from shaka-player * Tracks bandwidth samples and estimates available bandwidth. * Based on the minimum of two exponentially-weighted moving averages with * different half-lives. */ class EwmaBandWidthEstimator { constructor(slow, fast, defaultEstimate, defaultTTFB = 100) { this.defaultEstimate_ = void 0; this.minWeight_ = void 0; this.minDelayMs_ = void 0; this.slow_ = void 0; this.fast_ = void 0; this.defaultTTFB_ = void 0; this.ttfb_ = void 0; this.defaultEstimate_ = defaultEstimate; this.minWeight_ = 0.001; this.minDelayMs_ = 50; this.slow_ = new EWMA(slow); this.fast_ = new EWMA(fast); this.defaultTTFB_ = defaultTTFB; this.ttfb_ = new EWMA(slow); } update(slow, fast) { const { slow_, fast_, ttfb_ } = this; if (slow_.halfLife !== slow) { this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight()); } if (fast_.halfLife !== fast) { this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight()); } if (ttfb_.halfLife !== slow) { this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight()); } } sample(durationMs, numBytes) { durationMs = Math.max(durationMs, this.minDelayMs_); const numBits = 8 * numBytes; // weight is duration in seconds const durationS = durationMs / 1000; // value is bandwidth in bits/s const bandwidthInBps = numBits / durationS; this.fast_.sample(durationS, bandwidthInBps); this.slow_.sample(durationS, bandwidthInBps); } sampleTTFB(ttfb) { // weight is frequency curve applied to TTFB in seconds // (longer times have less weight with expected input under 1 second) const seconds = ttfb / 1000; const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2); this.ttfb_.sample(weight, Math.max(ttfb, 5)); } canEstimate() { return this.fast_.getTotalWeight() >= this.minWeight_; } getEstimate() { if (this.canEstimate()) { // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate())); // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate())); // Take the minimum of these two estimates. This should have the effect of // adapting down quickly, but up more slowly. return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate()); } else { return this.defaultEstimate_; } } getEstimateTTFB() { if (this.ttfb_.getTotalWeight() >= this.minWeight_) { return this.ttfb_.getEstimate(); } else { return this.defaultTTFB_; } } destroy() {} } class AbrController { constructor(hls) { this.hls = void 0; this.lastLevelLoadSec = 0; this.lastLoadedFragLevel = 0; this._nextAutoLevel = -1; this.timer = -1; this.onCheck = this._abandonRulesCheck.bind(this); this.fragCurrent = null; this.partCurrent = null; this.bitrateTestDelay = 0; this.bwEstimator = void 0; this.hls = hls; const config = hls.config; this.bwEstimator = new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate); this.registerListeners(); } registerListeners() { const { hls } = this; hls.on(Events.FRAG_LOADING, this.onFragLoading, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); } unregisterListeners() { const { hls } = this; hls.off(Events.FRAG_LOADING, this.onFragLoading, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); } destroy() { this.unregisterListeners(); this.clearTimer(); // @ts-ignore this.hls = this.onCheck = null; this.fragCurrent = this.partCurrent = null; } onFragLoading(event, data) { var _data$part; const frag = data.frag; if (this.ignoreFragment(frag)) { return; } this.fragCurrent = frag; this.partCurrent = (_data$part = data.part) != null ? _data$part : null; this.clearTimer(); this.timer = self.setInterval(this.onCheck, 100); } onLevelSwitching(event, data) { this.clearTimer(); } getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) { const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth; const playlistLoadSec = isSwitch ? this.lastLevelLoadSec : 0; return fragLoadSec + playlistLoadSec; } onLevelLoaded(event, data) { const config = this.hls.config; const { total, bwEstimate } = data.stats; // Total is the bytelength and bwEstimate in bits/sec if (isFiniteNumber(total) && isFiniteNumber(bwEstimate)) { this.lastLevelLoadSec = 8 * total / bwEstimate; } if (data.details.live) { this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive); } else { this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD); } } /* This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load quickly enough to prevent underbuffering */ _abandonRulesCheck() { const { fragCurrent: frag, partCurrent: part, hls } = this; const { autoLevelEnabled, media } = hls; if (!frag || !media) { return; } const now = performance.now(); const stats = part ? part.stats : frag.stats; const duration = part ? part.duration : frag.duration; const timeLoading = now - stats.loading.start; // If frag loading is aborted, complete, or from lowest level, stop timer and return if (stats.aborted || stats.loaded && stats.loaded === stats.total || frag.level === 0) { this.clearTimer(); // reset forced auto level value so that next level will be selected this._nextAutoLevel = -1; return; } // This check only runs if we're in ABR mode and actually playing if (!autoLevelEnabled || media.paused || !media.playbackRate || !media.readyState) { return; } const bufferInfo = hls.mainForwardBufferInfo; if (bufferInfo === null) { return; } const ttfbEstimate = this.bwEstimator.getEstimateTTFB(); const playbackRate = Math.abs(media.playbackRate); // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) { return; } // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer const bufferStarvationDelay = bufferInfo.len / playbackRate; // Only downswitch if less than 2 fragment lengths are buffered if (bufferStarvationDelay >= 2 * duration / playbackRate) { return; } const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1; const loadedFirstByte = stats.loaded && ttfb > -1; const bwEstimate = this.bwEstimator.getEstimate(); const { levels, minAutoLevel } = hls; const level = levels[frag.level]; const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.maxBitrate / 8)); let timeStreaming = timeLoading - ttfb; if (timeStreaming < 1 && loadedFirstByte) { timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate); } const loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0; // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment const fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbEstimate / 1000; // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left if (fragLoadedDelay <= bufferStarvationDelay) { return; } const bwe = loadRate ? loadRate * 8 : bwEstimate; let fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY; let nextLoadLevel; // Iterate through lower level and try to find the largest one that avoids rebuffering for (nextLoadLevel = frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) { // compute time to load next fragment at lower level // 8 = bits per byte (bps/Bps) const levelNextBitrate = levels[nextLoadLevel].maxBitrate; fragLevelNextLoadedDelay = this.getTimeToLoadFrag(ttfbEstimate / 1000, bwe, duration * levelNextBitrate, !levels[nextLoadLevel].details); if (fragLevelNextLoadedDelay < bufferStarvationDelay) { break; } } // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing // to load the current one if (fragLevelNextLoadedDelay >= fragLoadedDelay) { return; } // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down if (fragLevelNextLoadedDelay > duration * 10) { return; } hls.nextLoadLevel = nextLoadLevel; if (loadedFirstByte) { // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded); } else { // If there has been no loading progress, sample TTFB this.bwEstimator.sampleTTFB(timeLoading); } this.clearTimer(); logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly; Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s TTFB estimate: ${ttfb} Current BW estimate: ${isFiniteNumber(bwEstimate) ? (bwEstimate / 1024).toFixed(3) : 'Unknown'} Kb/s New BW estimate: ${(this.bwEstimator.getEstimate() / 1024).toFixed(3)} Kb/s Aborting and switching to level ${nextLoadLevel}`); if (frag.loader) { this.fragCurrent = this.partCurrent = null; frag.abortRequests(); } hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, { frag, part, stats }); } onFragLoaded(event, { frag, part }) { const stats = part ? part.stats : frag.stats; if (frag.type === PlaylistLevelType.MAIN) { this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start); } if (this.ignoreFragment(frag)) { return; } // stop monitoring bw once frag loaded this.clearTimer(); // store level id after successful fragment load this.lastLoadedFragLevel = frag.level; // reset forced auto level value so that next level will be selected this._nextAutoLevel = -1; // compute level average bitrate if (this.hls.config.abrMaxWithRealBitrate) { const duration = part ? part.duration : frag.duration; const level = this.hls.levels[frag.level]; const loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded; const loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration; level.loaded = { bytes: loadedBytes, duration: loadedDuration }; level.realBitrate = Math.round(8 * loadedBytes / loadedDuration); } if (frag.bitrateTest) { const fragBufferedData = { stats, frag, part, id: frag.type }; this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData); frag.bitrateTest = false; } } onFragBuffered(event, data) { const { frag, part } = data; const stats = part != null && part.stats.loaded ? part.stats : frag.stats; if (stats.aborted) { return; } if (this.ignoreFragment(frag)) { return; } // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing; // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch // is used. If we used buffering in that case, our BW estimate sample will be very large. const processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB()); this.bwEstimator.sample(processingMs, stats.loaded); stats.bwEstimate = this.bwEstimator.getEstimate(); if (frag.bitrateTest) { this.bitrateTestDelay = processingMs / 1000; } else { this.bitrateTestDelay = 0; } } ignoreFragment(frag) { // Only count non-alt-audio frags which were actually buffered in our BW calculations return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment'; } clearTimer() { self.clearInterval(this.timer); } // return next auto level get nextAutoLevel() { const forcedAutoLevel = this._nextAutoLevel; const bwEstimator = this.bwEstimator; // in case next auto level has been forced, and bw not available or not reliable, return forced value if (forcedAutoLevel !== -1 && !bwEstimator.canEstimate()) { return forcedAutoLevel; } // compute next level using ABR logic let nextABRAutoLevel = this.getNextABRAutoLevel(); // use forced auto level when ABR selected level has errored if (forcedAutoLevel !== -1) { const levels = this.hls.levels; if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) { return forcedAutoLevel; } } // if forced auto level has been defined, use it to cap ABR computed quality level if (forcedAutoLevel !== -1) { nextABRAutoLevel = Math.min(forcedAutoLevel, nextABRAutoLevel); } return nextABRAutoLevel; } getNextABRAutoLevel() { const { fragCurrent, partCurrent, hls } = this; const { maxAutoLevel, config, minAutoLevel, media } = hls; const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as // if we're playing back at the normal rate. const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0; const avgbw = this.bwEstimator ? this.bwEstimator.getEstimate() : config.abrEwmaDefaultEstimate; // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted. const bufferInfo = hls.mainForwardBufferInfo; const bufferStarvationDelay = (bufferInfo ? bufferInfo.len : 0) / playbackRate; // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all let bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, config.abrBandWidthFactor, config.abrBandWidthUpFactor); if (bestLevel >= 0) { return bestLevel; } logger.trace(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, finding optimal quality level`); // not possible to get rid of rebuffering ... let's try to find level that will guarantee less than maxStarvationDelay of rebuffering // if no matching level found, logic will return 0 let maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay; let bwFactor = config.abrBandWidthFactor; let bwUpFactor = config.abrBandWidthUpFactor; if (!bufferStarvationDelay) { // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test const bitrateTestDelay = this.bitrateTestDelay; if (bitrateTestDelay) { // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value // max video loading delay used in automatic start level selection : // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level + // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay``` ) // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay; maxStarvationDelay = maxLoadingDelay - bitrateTestDelay; logger.trace(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`); // don't use conservative factor on bitrate test bwFactor = bwUpFactor = 1; } } bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay + maxStarvationDelay, bwFactor, bwUpFactor); return Math.max(bestLevel, 0); } findBestLevel(currentBw, minAutoLevel, maxAutoLevel, maxFetchDuration, bwFactor, bwUpFactor) { var _level$details; const { fragCurrent, partCurrent, lastLoadedFragLevel: currentLevel } = this; const { levels } = this.hls; const level = levels[currentLevel]; const live = !!(level != null && (_level$details = level.details) != null && _level$details.live); const currentCodecSet = level == null ? void 0 : level.codecSet; const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000; let levelSkippedMin = minAutoLevel; let levelSkippedMax = -1; for (let i = maxAutoLevel; i >= minAutoLevel; i--) { const levelInfo = levels[i]; if (!levelInfo || currentCodecSet && levelInfo.codecSet !== currentCodecSet) { if (levelInfo) { levelSkippedMin = Math.min(i, levelSkippedMin); levelSkippedMax = Math.max(i, levelSkippedMax); } continue; } if (levelSkippedMax !== -1) { logger.trace(`[abr] Skipped level(s) ${levelSkippedMin}-${levelSkippedMax} with CODECS:"${levels[levelSkippedMax].attrs.CODECS}"; not compatible with "${level.attrs.CODECS}"`); } const levelDetails = levelInfo.details; const avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration; let adjustedbw; // follow algorithm captured from stagefright : // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp // Pick the highest bandwidth stream below or equal to estimated bandwidth. // consider only 80% of the available bandwidth, but if we are switching up, // be even more conservative (70%) to avoid overestimating and immediately // switching back. if (i <= currentLevel) { adjustedbw = bwFactor * currentBw; } else { adjustedbw = bwUpFactor * currentBw; } const bitrate = levels[i].maxBitrate; const fetchDuration = this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === undefined); logger.trace(`[abr] level:${i} adjustedbw-bitrate:${Math.round(adjustedbw - bitrate)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)}`); // if adjusted bw is greater than level bitrate AND if (adjustedbw > bitrate && ( // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ... // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1 fetchDuration === 0 || !isFiniteNumber(fetchDuration) || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration)) { // as we are looping from highest to lowest, this will return the best achievable quality level return i; } } // not enough time budget even with quality level 0 ... rebuffering might happen return -1; } set nextAutoLevel(nextLevel) { this._nextAutoLevel = nextLevel; } } class ChunkCache { constructor() { this.chunks = []; this.dataLength = 0; } push(chunk) { this.chunks.push(chunk); this.dataLength += chunk.length; } flush() { const { chunks, dataLength } = this; let result; if (!chunks.length) { return new Uint8Array(0); } else if (chunks.length === 1) { result = chunks[0]; } else { result = concatUint8Arrays(chunks, dataLength); } this.reset(); return result; } reset() { this.chunks.length = 0; this.dataLength = 0; } } function concatUint8Arrays(chunks, dataLength) { const result = new Uint8Array(dataLength); let offset = 0; for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; result.set(chunk, offset); offset += chunk.length; } return result; } const TICK_INTERVAL$1 = 100; // how often to tick in ms class AudioStreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO); this.videoBuffer = null; this.videoTrackCC = -1; this.waitingVideoCC = -1; this.bufferedTrack = null; this.switchingTrack = null; this.trackId = -1; this.waitingData = null; this.mainDetails = null; this.bufferFlushed = false; this.cachedTrackLoadedData = null; this._registerListeners(); } onHandlerDestroying() { this._unregisterListeners(); this.mainDetails = null; this.bufferedTrack = null; this.switchingTrack = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.BUFFER_RESET, this.onBufferReset, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.BUFFER_RESET, this.onBufferReset, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value onInitPtsFound(event, { frag, id, initPTS, timescale }) { // Always update the new INIT PTS // Can change due level switch if (id === 'main') { const cc = frag.cc; this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`); this.videoTrackCC = cc; // If we are waiting, tick immediately to unblock audio fragment transmuxing if (this.state === State.WAITING_INIT_PTS) { this.tick(); } } } startLoad(startPosition) { if (!this.levels) { this.startPosition = startPosition; this.state = State.STOPPED; return; } const lastCurrentTime = this.lastCurrentTime; this.stopLoad(); this.setInterval(TICK_INTERVAL$1); if (lastCurrentTime > 0 && startPosition === -1) { this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`); startPosition = lastCurrentTime; this.state = State.IDLE; } else { this.loadedmetadata = false; this.state = State.WAITING_TRACK; } this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } doTick() { switch (this.state) { case State.IDLE: this.doTickIdle(); break; case State.WAITING_TRACK: { var _levels$trackId; const { levels, trackId } = this; const details = levels == null ? void 0 : (_levels$trackId = levels[trackId]) == null ? void 0 : _levels$trackId.details; if (details) { if (this.waitForCdnTuneIn(details)) { break; } this.state = State.WAITING_INIT_PTS; } break; } case State.FRAG_LOADING_WAITING_RETRY: { var _this$media; const now = performance.now(); const retryDate = this.retryDate; // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { this.log('RetryDate reached, switch back to IDLE state'); this.resetStartWhenNotLoaded(this.trackId); this.state = State.IDLE; } break; } case State.WAITING_INIT_PTS: { // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS const waitingData = this.waitingData; if (waitingData) { const { frag, part, cache, complete } = waitingData; if (this.initPTS[frag.cc] !== undefined) { this.waitingData = null; this.waitingVideoCC = -1; this.state = State.FRAG_LOADING; const payload = cache.flush(); const data = { frag, part, payload, networkDetails: null }; this._handleFragmentLoadProgress(data); if (complete) { super._handleFragmentLoadComplete(data); } } else if (this.videoTrackCC !== this.waitingVideoCC) { // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`); this.clearWaitingFragment(); } else { // Drop waiting fragment if an earlier fragment is needed const pos = this.getLoadPosition(); const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole); const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag); if (waitingFragmentAtPosition < 0) { this.log(`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); this.clearWaitingFragment(); } } } else { this.state = State.IDLE; } } } this.onTickEnd(); } clearWaitingFragment() { const waitingData = this.waitingData; if (waitingData) { this.fragmentTracker.removeFragment(waitingData.frag); this.waitingData = null; this.waitingVideoCC = -1; this.state = State.IDLE; } } resetLoadingState() { this.clearWaitingFragment(); super.resetLoadingState(); } onTickEnd() { const { media } = this; if (!(media != null && media.readyState)) { // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) return; } this.lastCurrentTime = media.currentTime; } doTickIdle() { const { hls, levels, media, trackId } = this; const config = hls.config; if (!(levels != null && levels[trackId])) { return; } // if video not attached AND // start fragment already requested OR start frag prefetch not enabled // exit loop // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop if (!media && (this.startFragRequested || !config.startFragPrefetch)) { return; } const levelInfo = levels[trackId]; const trackDetails = levelInfo.details; if (!trackDetails || trackDetails.live && this.levelLastLoaded !== trackId || this.waitForCdnTuneIn(trackDetails)) { this.state = State.WAITING_TRACK; return; } const bufferable = this.mediaBuffer ? this.mediaBuffer : this.media; if (this.bufferFlushed && bufferable) { this.bufferFlushed = false; this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO); } const bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO); if (bufferInfo === null) { return; } const { bufferedTrack, switchingTrack } = this; if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) { hls.trigger(Events.BUFFER_EOS, { type: 'audio' }); this.state = State.ENDED; return; } const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN); const bufferLen = bufferInfo.len; const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len); // if buffer length is less than maxBufLen try to load a new fragment if (bufferLen >= maxBufLen && !switchingTrack) { return; } const fragments = trackDetails.fragments; const start = fragments[0].start; let targetBufferTime = bufferInfo.end; if (switchingTrack && media) { const pos = this.getLoadPosition(); if (bufferedTrack && switchingTrack.attrs !== bufferedTrack.attrs) { targetBufferTime = pos; } // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime if (trackDetails.PTSKnown && pos < start) { // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start if (bufferInfo.end > start || bufferInfo.nextStart) { this.log('Alt audio track ahead of main track, seek to start of alt audio track'); media.currentTime = start + 0.05; } } } let frag = this.getNextFragment(targetBufferTime, trackDetails); let atGap = false; // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags if (frag && this.isLoopLoading(frag, targetBufferTime)) { atGap = !!frag.gap; frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); } if (!frag) { this.bufferFlushed = true; return; } // Buffer audio up to one target duration ahead of main buffer const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration; if (atBufferSyncLimit || // Or wait for main buffer after buffing some audio !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) { // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN); if (mainFrag === null) { return; } // Bridge gaps in main buffer atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0); if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) { return; } } this.loadFragment(frag, levelInfo, targetBufferTime); } getMaxBufferLength(mainBufferLength) { const maxConfigBuffer = super.getMaxBufferLength(); if (!mainBufferLength) { return maxConfigBuffer; } return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength); } onMediaDetaching() { this.videoBuffer = null; super.onMediaDetaching(); } onAudioTracksUpdated(event, { audioTracks }) { this.resetTransmuxer(); this.levels = audioTracks.map(mediaPlaylist => new Level(mediaPlaylist)); } onAudioTrackSwitching(event, data) { // if any URL found on new audio track, it is an alternate audio track const altAudio = !!data.url; this.trackId = data.id; const { fragCurrent } = this; if (fragCurrent) { fragCurrent.abortRequests(); this.removeUnbufferedFrags(fragCurrent.start); } this.resetLoadingState(); // destroy useless transmuxer when switching audio to main if (!altAudio) { this.resetTransmuxer(); } else { // switching to audio track, start timer if not already started this.setInterval(TICK_INTERVAL$1); } // should we switch tracks ? if (altAudio) { this.switchingTrack = data; // main audio track are handled by stream-controller, just do something if switching to alt audio track this.state = State.IDLE; } else { this.switchingTrack = null; this.bufferedTrack = data; this.state = State.STOPPED; } this.tick(); } onManifestLoading() { this.fragmentTracker.removeAllFragments(); this.startPosition = this.lastCurrentTime = 0; this.bufferFlushed = false; this.levels = this.mainDetails = this.waitingData = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null; this.startFragRequested = false; this.trackId = this.videoTrackCC = this.waitingVideoCC = -1; } onLevelLoaded(event, data) { this.mainDetails = data.details; if (this.cachedTrackLoadedData !== null) { this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData); this.cachedTrackLoadedData = null; } } onAudioTrackLoaded(event, data) { var _track$details; if (this.mainDetails == null) { this.cachedTrackLoadedData = data; return; } const { levels } = this; const { details: newDetails, id: trackId } = data; if (!levels) { this.warn(`Audio tracks were reset while loading level ${trackId}`); return; } this.log(`Track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`); const track = levels[trackId]; let sliding = 0; if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { this.checkLiveUpdate(newDetails); const mainDetails = this.mainDetails; if (newDetails.deltaUpdateFailed || !mainDetails) { return; } if (!track.details && newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { // Make sure our audio rendition is aligned with the "main" rendition, using // pdt as our reference times. alignMediaPlaylistByPDT(newDetails, mainDetails); sliding = newDetails.fragments[0].start; } else { sliding = this.alignPlaylists(newDetails, track.details); } } track.details = newDetails; this.levelLastLoaded = trackId; // compute start position if we are aligned with the main playlist if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { this.setStartPosition(track.details, sliding); } // only switch back to IDLE state if we were waiting for track to start downloading a new fragment if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) { this.state = State.IDLE; } // trigger handler right now this.tick(); } _handleFragmentLoadProgress(data) { var _frag$initSegment; const { frag, part, payload } = data; const { config, trackId, levels } = this; if (!levels) { this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`); return; } const track = levels[trackId]; if (!track) { this.warn('Audio track is undefined on fragment load progress'); return; } const details = track.details; if (!details) { this.warn('Audio track details undefined on fragment load progress'); this.removeUnbufferedFrags(frag.start); return; } const audioCodec = config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2'; let transmuxer = this.transmuxer; if (!transmuxer) { transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); } // Check if we have video initPTS // If not we need to wait for it const initPTS = this.initPTS[frag.cc]; const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; if (initPTS !== undefined) { // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) const accurateTimeOffset = false; // details.PTSKnown || !details.live; const partIndex = part ? part.index : -1; const partial = partIndex !== -1; const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); } else { this.log(`Unknown video PTS for cc ${frag.cc}, waiting for video PTS before demuxing audio frag ${frag.sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); const { cache } = this.waitingData = this.waitingData || { frag, part, cache: new ChunkCache(), complete: false }; cache.push(new Uint8Array(payload)); this.waitingVideoCC = this.videoTrackCC; this.state = State.WAITING_INIT_PTS; } } _handleFragmentLoadComplete(fragLoadedData) { if (this.waitingData) { this.waitingData.complete = true; return; } super._handleFragmentLoadComplete(fragLoadedData); } onBufferReset( /* event: Events.BUFFER_RESET */ ) { // reset reference to sourcebuffers this.mediaBuffer = this.videoBuffer = null; this.loadedmetadata = false; } onBufferCreated(event, data) { const audioTrack = data.tracks.audio; if (audioTrack) { this.mediaBuffer = audioTrack.buffer || null; } if (data.tracks.video) { this.videoBuffer = data.tracks.video.buffer || null; } } onFragBuffered(event, data) { const { frag, part } = data; if (frag.type !== PlaylistLevelType.AUDIO) { if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) { const bufferable = this.videoBuffer || this.media; if (bufferable) { const bufferedTimeRanges = BufferHelper.getBuffered(bufferable); if (bufferedTimeRanges.length) { this.loadedmetadata = true; } } } return; } if (this.fragContextChanged(frag)) { // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}, audioSwitch: ${this.switchingTrack ? this.switchingTrack.name : 'false'}`); return; } if (frag.sn !== 'initSegment') { this.fragPrevious = frag; const track = this.switchingTrack; if (track) { this.bufferedTrack = track; this.switchingTrack = null; this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track)); } } this.fragBufferedComplete(frag, part); } onError(event, data) { var _data$context; if (data.fatal) { this.state = State.ERROR; return; } switch (data.details) { case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_PARSING_ERROR: case ErrorDetails.FRAG_DECRYPT_ERROR: case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data); break; case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: case ErrorDetails.LEVEL_PARSING_ERROR: // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) { this.state = State.IDLE; } break; case ErrorDetails.BUFFER_FULL_ERROR: if (!data.parent || data.parent !== 'audio') { return; } if (this.reduceLengthAndFlushBuffer(data)) { this.bufferedTrack = null; super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio'); } break; case ErrorDetails.INTERNAL_EXCEPTION: this.recoverWorkerError(data); break; } } onBufferFlushed(event, { type }) { if (type === ElementaryStreamTypes.AUDIO) { this.bufferFlushed = true; if (this.state === State.ENDED) { this.state = State.IDLE; } } } _handleTransmuxComplete(transmuxResult) { var _id3$samples; const id = 'audio'; const { hls } = this; const { remuxResult, chunkMeta } = transmuxResult; const context = this.getCurrentContext(chunkMeta); if (!context) { this.resetWhenMissingContext(chunkMeta); return; } const { frag, part, level } = context; const { details } = level; const { audio, text, id3, initSegment } = remuxResult; // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. if (this.fragContextChanged(frag) || !details) { this.fragmentTracker.removeFragment(frag); return; } this.state = State.PARSING; if (this.switchingTrack && audio) { this.completeAudioSwitch(this.switchingTrack); } if (initSegment != null && initSegment.tracks) { const mapFragment = frag.initSegment || frag; this._bufferInitSegment(initSegment.tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, tracks: initSegment.tracks }); // Only flush audio from old audio tracks when PTS is known on new audio track } if (audio) { const { startPTS, endPTS, startDTS, endDTS } = audio; if (part) { part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { startPTS, endPTS, startDTS, endDTS }; } frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS); this.bufferFragmentData(audio, frag, part, chunkMeta); } if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { const emittedID3 = _extends({ id, frag, details }, id3); hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); } if (text) { const emittedText = _extends({ id, frag, details }, text); hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); } } _bufferInitSegment(tracks, frag, chunkMeta) { if (this.state !== State.PARSING) { return; } // delete any video track found on audio transmuxer if (tracks.video) { delete tracks.video; } // include levelCodec in audio and video tracks const track = tracks.audio; if (!track) { return; } track.levelCodec = track.codec; track.id = 'audio'; this.log(`Init audio buffer, container:${track.container}, codecs[parsed]=[${track.codec}]`); this.hls.trigger(Events.BUFFER_CODECS, tracks); const initSegment = track.initSegment; if (initSegment != null && initSegment.byteLength) { const segment = { type: 'audio', frag, part: null, chunkMeta, parent: frag.type, data: initSegment }; this.hls.trigger(Events.BUFFER_APPENDING, segment); } // trigger handler right now this.tick(); } loadFragment(frag, track, targetBufferTime) { // only load if fragment is not loaded or if in audio switch const fragState = this.fragmentTracker.getState(frag); this.fragCurrent = frag; // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { var _track$details2; if (frag.sn === 'initSegment') { this._loadInitSegment(frag, track); } else if ((_track$details2 = track.details) != null && _track$details2.live && !this.initPTS[frag.cc]) { this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`); this.state = State.WAITING_INIT_PTS; } else { this.startFragRequested = true; super.loadFragment(frag, track, targetBufferTime); } } else { this.clearTrackerIfNeeded(frag); } } completeAudioSwitch(switchingTrack) { const { hls, media, bufferedTrack } = this; const bufferedAttributes = bufferedTrack == null ? void 0 : bufferedTrack.attrs; const switchAttributes = switchingTrack.attrs; if (media && bufferedAttributes && (bufferedAttributes.CHANNELS !== switchAttributes.CHANNELS || bufferedAttributes.NAME !== switchAttributes.NAME || bufferedAttributes.LANGUAGE !== switchAttributes.LANGUAGE)) { this.log('Switching audio track : flushing all audio'); super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio'); } this.bufferedTrack = switchingTrack; this.switchingTrack = null; hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack)); } } class AudioTrackController extends BasePlaylistController { constructor(hls) { super(hls, '[audio-track-controller]'); this.tracks = []; this.groupId = null; this.tracksInGroup = []; this.trackId = -1; this.currentTrack = null; this.selectDefaultTrack = true; this.registerListeners(); } registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); } destroy() { this.unregisterListeners(); this.tracks.length = 0; this.tracksInGroup.length = 0; this.currentTrack = null; super.destroy(); } onManifestLoading() { this.tracks = []; this.groupId = null; this.tracksInGroup = []; this.trackId = -1; this.currentTrack = null; this.selectDefaultTrack = true; } onManifestParsed(event, data) { this.tracks = data.audioTracks || []; } onAudioTrackLoaded(event, data) { const { id, groupId, details } = data; const trackInActiveGroup = this.tracksInGroup[id]; if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) { this.warn(`Track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup.groupId}`); return; } const curDetails = trackInActiveGroup.details; trackInActiveGroup.details = data.details; this.log(`audio-track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`); if (id === this.trackId) { this.playlistLoaded(id, data, curDetails); } } onLevelLoading(event, data) { this.switchLevel(data.level); } onLevelSwitching(event, data) { this.switchLevel(data.level); } switchLevel(levelIndex) { const levelInfo = this.hls.levels[levelIndex]; if (!(levelInfo != null && levelInfo.audioGroupIds)) { return; } const audioGroupId = levelInfo.audioGroupIds[levelInfo.urlId]; if (this.groupId !== audioGroupId) { this.groupId = audioGroupId || null; const audioTracks = this.tracks.filter(track => !audioGroupId || track.groupId === audioGroupId); // Disable selectDefaultTrack if there are no default tracks if (this.selectDefaultTrack && !audioTracks.some(track => track.default)) { this.selectDefaultTrack = false; } this.tracksInGroup = audioTracks; const audioTracksUpdated = { audioTracks }; this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group:${audioGroupId}`); this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated); this.selectInitialTrack(); } else if (this.shouldReloadPlaylist(this.currentTrack)) { // Retry playlist loading if no playlist is or has been loaded yet this.setAudioTrack(this.trackId); } } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && data.context.groupId === this.groupId) { this.requestScheduled = -1; this.checkRetry(data); } } get audioTracks() { return this.tracksInGroup; } get audioTrack() { return this.trackId; } set audioTrack(newId) { // If audio track is selected from API then don't choose from the manifest default track this.selectDefaultTrack = false; this.setAudioTrack(newId); } setAudioTrack(newId) { const tracks = this.tracksInGroup; // check if level idx is valid if (newId < 0 || newId >= tracks.length) { this.warn('Invalid id passed to audio-track controller'); return; } // stopping live reloading timer if any this.clearTimer(); const lastTrack = this.currentTrack; tracks[this.trackId]; const track = tracks[newId]; const { groupId, name } = track; this.log(`Switching to audio-track ${newId} "${name}" lang:${track.lang} group:${groupId}`); this.trackId = newId; this.currentTrack = track; this.selectDefaultTrack = false; this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track)); // Do not reload track unless live if (track.details && !track.details.live) { return; } const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details); this.loadPlaylist(hlsUrlParameters); } selectInitialTrack() { const audioTracks = this.tracksInGroup; const trackId = this.findTrackId(this.currentTrack) | this.findTrackId(null); if (trackId !== -1) { this.setAudioTrack(trackId); } else { const error = new Error(`No track found for running audio group-ID: ${this.groupId} track count: ${audioTracks.length}`); this.warn(error.message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR, fatal: true, error }); } } findTrackId(currentTrack) { const audioTracks = this.tracksInGroup; for (let i = 0; i < audioTracks.length; i++) { const track = audioTracks[i]; if (!this.selectDefaultTrack || track.default) { if (!currentTrack || currentTrack.attrs['STABLE-RENDITION-ID'] !== undefined && currentTrack.attrs['STABLE-RENDITION-ID'] === track.attrs['STABLE-RENDITION-ID']) { return track.id; } if (currentTrack.name === track.name && currentTrack.lang === track.lang) { return track.id; } } } return -1; } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); const audioTrack = this.tracksInGroup[this.trackId]; if (this.shouldLoadPlaylist(audioTrack)) { const id = audioTrack.id; const groupId = audioTrack.groupId; let url = audioTrack.url; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } // track not retrieved yet, or live playlist we need to (re)load it this.log(`loading audio-track playlist ${id} "${audioTrack.name}" lang:${audioTrack.lang} group:${groupId}`); this.clearTimer(); this.hls.trigger(Events.AUDIO_TRACK_LOADING, { url, id, groupId, deliveryDirectives: hlsUrlParameters || null }); } } } function subtitleOptionsIdentical(trackList1, trackList2) { if (trackList1.length !== trackList2.length) { return false; } for (let i = 0; i < trackList1.length; i++) { if (!subtitleAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) { return false; } } return true; } function subtitleAttributesIdentical(attrs1, attrs2) { // Media options with the same rendition ID must be bit identical const stableRenditionId = attrs1['STABLE-RENDITION-ID']; if (stableRenditionId) { return stableRenditionId === attrs2['STABLE-RENDITION-ID']; } // When rendition ID is not present, compare attributes return !['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED'].some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]); } const TICK_INTERVAL = 500; // how often to tick in ms class SubtitleStreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE); this.levels = []; this.currentTrackId = -1; this.tracksBuffered = []; this.mainDetails = null; this._registerListeners(); } onHandlerDestroying() { this._unregisterListeners(); this.mainDetails = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } startLoad(startPosition) { this.stopLoad(); this.state = State.IDLE; this.setInterval(TICK_INTERVAL); this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } onManifestLoading() { this.mainDetails = null; this.fragmentTracker.removeAllFragments(); } onMediaDetaching() { this.tracksBuffered = []; super.onMediaDetaching(); } onLevelLoaded(event, data) { this.mainDetails = data.details; } onSubtitleFragProcessed(event, data) { const { frag, success } = data; this.fragPrevious = frag; this.state = State.IDLE; if (!success) { return; } const buffered = this.tracksBuffered[this.currentTrackId]; if (!buffered) { return; } // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo // so we can re-use the logic used to detect how much has been buffered let timeRange; const fragStart = frag.start; for (let i = 0; i < buffered.length; i++) { if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) { timeRange = buffered[i]; break; } } const fragEnd = frag.start + frag.duration; if (timeRange) { timeRange.end = fragEnd; } else { timeRange = { start: fragStart, end: fragEnd }; buffered.push(timeRange); } this.fragmentTracker.fragBuffered(frag); } onBufferFlushing(event, data) { const { startOffset, endOffset } = data; if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { const endOffsetSubtitles = endOffset - 1; if (endOffsetSubtitles <= 0) { return; } data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles); this.tracksBuffered.forEach(buffered => { for (let i = 0; i < buffered.length;) { if (buffered[i].end <= endOffsetSubtitles) { buffered.shift(); continue; } else if (buffered[i].start < endOffsetSubtitles) { buffered[i].start = endOffsetSubtitles; } else { break; } i++; } }); this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE); } } onFragBuffered(event, data) { if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) { var _this$media; if ((_this$media = this.media) != null && _this$media.buffered.length) { this.loadedmetadata = true; } } } // If something goes wrong, proceed to next frag, if we were processing one. onError(event, data) { const frag = data.frag; if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) { if (this.fragCurrent) { this.fragCurrent.abortRequests(); } if (this.state !== State.STOPPED) { this.state = State.IDLE; } } } // Got all new subtitle levels. onSubtitleTracksUpdated(event, { subtitleTracks }) { if (subtitleOptionsIdentical(this.levels, subtitleTracks)) { this.levels = subtitleTracks.map(mediaPlaylist => new Level(mediaPlaylist)); return; } this.tracksBuffered = []; this.levels = subtitleTracks.map(mediaPlaylist => { const level = new Level(mediaPlaylist); this.tracksBuffered[level.id] = []; return level; }); this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE); this.fragPrevious = null; this.mediaBuffer = null; } onSubtitleTrackSwitch(event, data) { this.currentTrackId = data.id; if (!this.levels.length || this.currentTrackId === -1) { this.clearInterval(); return; } // Check if track has the necessary details to load fragments const currentTrack = this.levels[this.currentTrackId]; if (currentTrack != null && currentTrack.details) { this.mediaBuffer = this.mediaBufferTimeRanges; } else { this.mediaBuffer = null; } if (currentTrack) { this.setInterval(TICK_INTERVAL); } } // Got a new set of subtitle fragments. onSubtitleTrackLoaded(event, data) { var _track$details; const { details: newDetails, id: trackId } = data; const { currentTrackId, levels } = this; if (!levels.length) { return; } const track = levels[currentTrackId]; if (trackId >= levels.length || trackId !== currentTrackId || !track) { return; } this.mediaBuffer = this.mediaBufferTimeRanges; let sliding = 0; if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { const mainDetails = this.mainDetails; if (newDetails.deltaUpdateFailed || !mainDetails) { return; } const mainSlidingStartFragment = mainDetails.fragments[0]; if (!track.details) { if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { alignMediaPlaylistByPDT(newDetails, mainDetails); sliding = newDetails.fragments[0].start; } else if (mainSlidingStartFragment) { // line up live playlist with main so that fragments in range are loaded sliding = mainSlidingStartFragment.start; addSliding(newDetails, sliding); } } else { sliding = this.alignPlaylists(newDetails, track.details); if (sliding === 0 && mainSlidingStartFragment) { // realign with main when there is no overlap with last refresh sliding = mainSlidingStartFragment.start; addSliding(newDetails, sliding); } } } track.details = newDetails; this.levelLastLoaded = trackId; if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { this.setStartPosition(track.details, sliding); } // trigger handler right now this.tick(); // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) { const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0); if (!foundFrag) { this.warn('Subtitle playlist not aligned with playback'); track.details = undefined; } } } _handleFragmentLoadComplete(fragLoadedData) { const { frag, payload } = fragLoadedData; const decryptData = frag.decryptdata; const hls = this.hls; if (this.fragContextChanged(frag)) { return; } // check to see if the payload needs to be decrypted if (payload && payload.byteLength > 0 && decryptData && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { const startTime = performance.now(); // decrypt the subtitles this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => { hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_DECRYPT_ERROR, fatal: false, error: err, reason: err.message, frag }); throw err; }).then(decryptedData => { const endTime = performance.now(); hls.trigger(Events.FRAG_DECRYPTED, { frag, payload: decryptedData, stats: { tstart: startTime, tdecrypt: endTime } }); }).catch(err => { this.warn(`${err.name}: ${err.message}`); this.state = State.IDLE; }); } } doTick() { if (!this.media) { this.state = State.IDLE; return; } if (this.state === State.IDLE) { const { currentTrackId, levels } = this; const track = levels[currentTrackId]; if (!levels.length || !track || !track.details) { return; } const { config } = this; const currentTime = this.getLoadPosition(); const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole); const { end: targetBufferTime, len: bufferLen } = bufferedInfo; const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN); const trackDetails = track.details; const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration; if (bufferLen > maxBufLen) { return; } const fragments = trackDetails.fragments; const fragLen = fragments.length; const end = trackDetails.edge; let foundFrag = null; const fragPrevious = this.fragPrevious; if (targetBufferTime < end) { const tolerance = config.maxFragLookUpTolerance; const lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance; foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance); if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) { foundFrag = fragments[0]; } } else { foundFrag = fragments[fragLen - 1]; } if (!foundFrag) { return; } foundFrag = this.mapToInitFragWhenRequired(foundFrag); if (foundFrag.sn !== 'initSegment') { // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment const curSNIdx = foundFrag.sn - trackDetails.startSN; const prevFrag = fragments[curSNIdx - 1]; if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) { foundFrag = prevFrag; } } if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) { // only load if fragment is not loaded this.loadFragment(foundFrag, track, targetBufferTime); } } } getMaxBufferLength(mainBufferLength) { const maxConfigBuffer = super.getMaxBufferLength(); if (!mainBufferLength) { return maxConfigBuffer; } return Math.max(maxConfigBuffer, mainBufferLength); } loadFragment(frag, level, targetBufferTime) { this.fragCurrent = frag; if (frag.sn === 'initSegment') { this._loadInitSegment(frag, level); } else { this.startFragRequested = true; super.loadFragment(frag, level, targetBufferTime); } } get mediaBufferTimeRanges() { return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []); } } class BufferableInstance { constructor(timeranges) { this.buffered = void 0; const getRange = (name, index, length) => { index = index >>> 0; if (index > length - 1) { throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`); } return timeranges[index][name]; }; this.buffered = { get length() { return timeranges.length; }, end(index) { return getRange('end', index, timeranges.length); }, start(index) { return getRange('start', index, timeranges.length); } }; } } class SubtitleTrackController extends BasePlaylistController { constructor(hls) { super(hls, '[subtitle-track-controller]'); this.media = null; this.tracks = []; this.groupId = null; this.tracksInGroup = []; this.trackId = -1; this.selectDefaultTrack = true; this.queuedDefaultTrack = -1; this.trackChangeListener = () => this.onTextTracksChanged(); this.asyncPollTrackChange = () => this.pollTrackChange(0); this.useTextTrackPolling = false; this.subtitlePollingInterval = -1; this._subtitleDisplay = true; this.registerListeners(); } destroy() { this.unregisterListeners(); this.tracks.length = 0; this.tracksInGroup.length = 0; this.trackChangeListener = this.asyncPollTrackChange = null; super.destroy(); } get subtitleDisplay() { return this._subtitleDisplay; } set subtitleDisplay(value) { this._subtitleDisplay = value; if (this.trackId > -1) { this.toggleTrackModes(this.trackId); } } registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); } // Listen for subtitle track change, then extract the current track ID. onMediaAttached(event, data) { this.media = data.media; if (!this.media) { return; } if (this.queuedDefaultTrack > -1) { this.subtitleTrack = this.queuedDefaultTrack; this.queuedDefaultTrack = -1; } this.useTextTrackPolling = !(this.media.textTracks && 'onchange' in this.media.textTracks); if (this.useTextTrackPolling) { this.pollTrackChange(500); } else { this.media.textTracks.addEventListener('change', this.asyncPollTrackChange); } } pollTrackChange(timeout) { self.clearInterval(this.subtitlePollingInterval); this.subtitlePollingInterval = self.setInterval(this.trackChangeListener, timeout); } onMediaDetaching() { if (!this.media) { return; } self.clearInterval(this.subtitlePollingInterval); if (!this.useTextTrackPolling) { this.media.textTracks.removeEventListener('change', this.asyncPollTrackChange); } if (this.trackId > -1) { this.queuedDefaultTrack = this.trackId; } const textTracks = filterSubtitleTracks(this.media.textTracks); // Clear loaded cues on media detachment from tracks textTracks.forEach(track => { clearCurrentCues(track); }); // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled. this.subtitleTrack = -1; this.media = null; } onManifestLoading() { this.tracks = []; this.groupId = null; this.tracksInGroup = []; this.trackId = -1; this.selectDefaultTrack = true; } // Fired whenever a new manifest is loaded. onManifestParsed(event, data) { this.tracks = data.subtitleTracks; } onSubtitleTrackLoaded(event, data) { const { id, details } = data; const { trackId } = this; const currentTrack = this.tracksInGroup[trackId]; if (!currentTrack) { this.warn(`Invalid subtitle track id ${id}`); return; } const curDetails = currentTrack.details; currentTrack.details = data.details; this.log(`subtitle track ${id} loaded [${details.startSN}-${details.endSN}]`); if (id === this.trackId) { this.playlistLoaded(id, data, curDetails); } } onLevelLoading(event, data) { this.switchLevel(data.level); } onLevelSwitching(event, data) { this.switchLevel(data.level); } switchLevel(levelIndex) { const levelInfo = this.hls.levels[levelIndex]; if (!(levelInfo != null && levelInfo.textGroupIds)) { return; } const textGroupId = levelInfo.textGroupIds[levelInfo.urlId]; const lastTrack = this.tracksInGroup ? this.tracksInGroup[this.trackId] : undefined; if (this.groupId !== textGroupId) { const subtitleTracks = this.tracks.filter(track => !textGroupId || track.groupId === textGroupId); this.tracksInGroup = subtitleTracks; const initialTrackId = this.findTrackId(lastTrack == null ? void 0 : lastTrack.name) || this.findTrackId(); this.groupId = textGroupId || null; const subtitleTracksUpdated = { subtitleTracks }; this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in "${textGroupId}" group-id`); this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated); if (initialTrackId !== -1) { this.setSubtitleTrack(initialTrackId, lastTrack); } } else if (this.shouldReloadPlaylist(lastTrack)) { // Retry playlist loading if no playlist is or has been loaded yet this.setSubtitleTrack(this.trackId, lastTrack); } } findTrackId(name) { const textTracks = this.tracksInGroup; for (let i = 0; i < textTracks.length; i++) { const track = textTracks[i]; if (!this.selectDefaultTrack || track.default) { if (!name || name === track.name) { return track.id; } } } return -1; } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && data.context.groupId === this.groupId) { this.checkRetry(data); } } /** get alternate subtitle tracks list from playlist **/ get subtitleTracks() { return this.tracksInGroup; } /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/ get subtitleTrack() { return this.trackId; } set subtitleTrack(newId) { this.selectDefaultTrack = false; const lastTrack = this.tracksInGroup ? this.tracksInGroup[this.trackId] : undefined; this.setSubtitleTrack(newId, lastTrack); } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); const currentTrack = this.tracksInGroup[this.trackId]; if (this.shouldLoadPlaylist(currentTrack)) { const id = currentTrack.id; const groupId = currentTrack.groupId; let url = currentTrack.url; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } this.log(`Loading subtitle playlist for id ${id}`); this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, { url, id, groupId, deliveryDirectives: hlsUrlParameters || null }); } } /** * Disables the old subtitleTrack and sets current mode on the next subtitleTrack. * This operates on the DOM textTracks. * A value of -1 will disable all subtitle tracks. */ toggleTrackModes(newId) { const { media, trackId } = this; if (!media) { return; } const textTracks = filterSubtitleTracks(media.textTracks); const groupTracks = textTracks.filter(track => track.groupId === this.groupId); if (newId === -1) { [].slice.call(textTracks).forEach(track => { track.mode = 'disabled'; }); } else { const oldTrack = groupTracks[trackId]; if (oldTrack) { oldTrack.mode = 'disabled'; } } const nextTrack = groupTracks[newId]; if (nextTrack) { nextTrack.mode = this.subtitleDisplay ? 'showing' : 'hidden'; } } /** * This method is responsible for validating the subtitle index and periodically reloading if live. * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track. */ setSubtitleTrack(newId, lastTrack) { var _tracks$newId; const tracks = this.tracksInGroup; // setting this.subtitleTrack will trigger internal logic // if media has not been attached yet, it will fail // we keep a reference to the default track id // and we'll set subtitleTrack when onMediaAttached is triggered if (!this.media) { this.queuedDefaultTrack = newId; return; } if (this.trackId !== newId) { this.toggleTrackModes(newId); } // exit if track id as already set or invalid if (this.trackId === newId && (newId === -1 || (_tracks$newId = tracks[newId]) != null && _tracks$newId.details) || newId < -1 || newId >= tracks.length) { return; } // stopping live reloading timer if any this.clearTimer(); const track = tracks[newId]; this.log(`Switching to subtitle-track ${newId}` + (track ? ` "${track.name}" lang:${track.lang} group:${track.groupId}` : '')); this.trackId = newId; if (track) { const { id, groupId = '', name, type, url } = track; this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id, groupId, name, type, url }); const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details); this.loadPlaylist(hlsUrlParameters); } else { // switch to -1 this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id: newId }); } } onTextTracksChanged() { if (!this.useTextTrackPolling) { self.clearInterval(this.subtitlePollingInterval); } // Media is undefined when switching streams via loadSource() if (!this.media || !this.hls.config.renderTextTracksNatively) { return; } let trackId = -1; const tracks = filterSubtitleTracks(this.media.textTracks); for (let id = 0; id < tracks.length; id++) { if (tracks[id].mode === 'hidden') { // Do not break in case there is a following track with showing. trackId = id; } else if (tracks[id].mode === 'showing') { trackId = id; break; } } // Setting current subtitleTrack will invoke code. if (this.subtitleTrack !== trackId) { this.subtitleTrack = trackId; } } } function filterSubtitleTracks(textTrackList) { const tracks = []; for (let i = 0; i < textTrackList.length; i++) { const track = textTrackList[i]; // Edge adds a track without a label; we don't want to use it if ((track.kind === 'subtitles' || track.kind === 'captions') && track.label) { tracks.push(textTrackList[i]); } } return tracks; } class BufferOperationQueue { constructor(sourceBufferReference) { this.buffers = void 0; this.queues = { video: [], audio: [], audiovideo: [] }; this.buffers = sourceBufferReference; } append(operation, type) { const queue = this.queues[type]; queue.push(operation); if (queue.length === 1 && this.buffers[type]) { this.executeNext(type); } } insertAbort(operation, type) { const queue = this.queues[type]; queue.unshift(operation); this.executeNext(type); } appendBlocker(type) { let execute; const promise = new Promise(resolve => { execute = resolve; }); const operation = { execute, onStart: () => {}, onComplete: () => {}, onError: () => {} }; this.append(operation, type); return promise; } executeNext(type) { const { buffers, queues } = this; const sb = buffers[type]; const queue = queues[type]; if (queue.length) { const operation = queue[0]; try { // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations // which do not end with this event must call _onSBUpdateEnd manually operation.execute(); } catch (e) { logger.warn('[buffer-operation-queue]: Unhandled exception executing the current operation'); operation.onError(e); // Only shift the current operation off, otherwise the updateend handler will do this for us if (!(sb != null && sb.updating)) { queue.shift(); this.executeNext(type); } } } } shiftAndExecuteNext(type) { this.queues[type].shift(); this.executeNext(type); } current(type) { return this.queues[type][0]; } } const MediaSource = getMediaSource(); const VIDEO_CODEC_PROFILE_REPACE = /([ha]vc.)(?:\.[^.,]+)+/; class BufferController { // The level details used to determine duration, target-duration and live // cache the self generated object url to detect hijack of video tag // A queue of buffer operations which require the SourceBuffer to not be updating upon execution // References to event listeners for each SourceBuffer, so that they can be referenced for event removal // The number of BUFFER_CODEC events received before any sourceBuffers are created // The total number of BUFFER_CODEC events received // A reference to the attached media element // A reference to the active media source // Last MP3 audio chunk appended // counters constructor(hls) { this.details = null; this._objectUrl = null; this.operationQueue = void 0; this.listeners = void 0; this.hls = void 0; this.bufferCodecEventsExpected = 0; this._bufferCodecEventsTotal = 0; this.media = null; this.mediaSource = null; this.lastMpegAudioChunk = null; this.appendError = 0; this.tracks = {}; this.pendingTracks = {}; this.sourceBuffer = void 0; // Keep as arrow functions so that we can directly reference these functions directly as event listeners this._onMediaSourceOpen = () => { const { media, mediaSource } = this; logger.log('[buffer-controller]: Media source opened'); if (media) { media.removeEventListener('emptied', this._onMediaEmptied); this.updateMediaElementDuration(); this.hls.trigger(Events.MEDIA_ATTACHED, { media }); } if (mediaSource) { // once received, don't listen anymore to sourceopen event mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen); } this.checkPendingTracks(); }; this._onMediaSourceClose = () => { logger.log('[buffer-controller]: Media source closed'); }; this._onMediaSourceEnded = () => { logger.log('[buffer-controller]: Media source ended'); }; this._onMediaEmptied = () => { const { media, _objectUrl } = this; if (media && media.src !== _objectUrl) { logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${media.src})`); } }; this.hls = hls; this._initSourceBuffer(); this.registerListeners(); } hasSourceTypes() { return this.getSourceBufferTypes().length > 0 || Object.keys(this.pendingTracks).length > 0; } destroy() { this.unregisterListeners(); this.details = null; this.lastMpegAudioChunk = null; } registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.BUFFER_RESET, this.onBufferReset, this); hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this); hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.on(Events.BUFFER_EOS, this.onBufferEos, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.on(Events.FRAG_PARSED, this.onFragParsed, this); hls.on(Events.FRAG_CHANGED, this.onFragChanged, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.BUFFER_RESET, this.onBufferReset, this); hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.BUFFER_EOS, this.onBufferEos, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.off(Events.FRAG_PARSED, this.onFragParsed, this); hls.off(Events.FRAG_CHANGED, this.onFragChanged, this); } _initSourceBuffer() { this.sourceBuffer = {}; this.operationQueue = new BufferOperationQueue(this.sourceBuffer); this.listeners = { audio: [], video: [], audiovideo: [] }; this.lastMpegAudioChunk = null; } onManifestLoading() { this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0; this.details = null; } onManifestParsed(event, data) { // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller // sourcebuffers will be created all at once when the expected nb of tracks will be reached // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller // it will contain the expected nb of source buffers, no need to compute it let codecEvents = 2; if (data.audio && !data.video || !data.altAudio || !true) { codecEvents = 1; } this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = codecEvents; logger.log(`${this.bufferCodecEventsExpected} bufferCodec event(s) expected`); } onMediaAttaching(event, data) { const media = this.media = data.media; if (media && MediaSource) { const ms = this.mediaSource = new MediaSource(); // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound ms.addEventListener('sourceopen', this._onMediaSourceOpen); ms.addEventListener('sourceended', this._onMediaSourceEnded); ms.addEventListener('sourceclose', this._onMediaSourceClose); // link video and media Source media.src = self.URL.createObjectURL(ms); // cache the locally generated object url this._objectUrl = media.src; media.addEventListener('emptied', this._onMediaEmptied); } } onMediaDetaching() { const { media, mediaSource, _objectUrl } = this; if (mediaSource) { logger.log('[buffer-controller]: media source detaching'); if (mediaSource.readyState === 'open') { try { // endOfStream could trigger exception if any sourcebuffer is in updating state // we don't really care about checking sourcebuffer state here, // as we are anyway detaching the MediaSource // let's just avoid this exception to propagate mediaSource.endOfStream(); } catch (err) { logger.warn(`[buffer-controller]: onMediaDetaching: ${err.message} while calling endOfStream`); } } // Clean up the SourceBuffers by invoking onBufferReset this.onBufferReset(); mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen); mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded); mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose); // Detach properly the MediaSource from the HTMLMediaElement as // suggested in https://github.com/w3c/media-source/issues/53. if (media) { media.removeEventListener('emptied', this._onMediaEmptied); if (_objectUrl) { self.URL.revokeObjectURL(_objectUrl); } // clean up video tag src only if it's our own url. some external libraries might // hijack the video tag and change its 'src' without destroying the Hls instance first if (media.src === _objectUrl) { media.removeAttribute('src'); media.load(); } else { logger.warn('[buffer-controller]: media.src was changed by a third party - skip cleanup'); } } this.mediaSource = null; this.media = null; this._objectUrl = null; this.bufferCodecEventsExpected = this._bufferCodecEventsTotal; this.pendingTracks = {}; this.tracks = {}; } this.hls.trigger(Events.MEDIA_DETACHED, undefined); } onBufferReset() { this.getSourceBufferTypes().forEach(type => { const sb = this.sourceBuffer[type]; try { if (sb) { this.removeBufferListeners(type); if (this.mediaSource) { this.mediaSource.removeSourceBuffer(sb); } // Synchronously remove the SB from the map before the next call in order to prevent an async function from // accessing it this.sourceBuffer[type] = undefined; } } catch (err) { logger.warn(`[buffer-controller]: Failed to reset the ${type} buffer`, err); } }); this._initSourceBuffer(); } onBufferCodecs(event, data) { const sourceBufferCount = this.getSourceBufferTypes().length; Object.keys(data).forEach(trackName => { if (sourceBufferCount) { // check if SourceBuffer codec needs to change const track = this.tracks[trackName]; if (track && typeof track.buffer.changeType === 'function') { const { id, codec, levelCodec, container, metadata } = data[trackName]; const currentCodec = (track.levelCodec || track.codec).replace(VIDEO_CODEC_PROFILE_REPACE, '$1'); const nextCodec = (levelCodec || codec).replace(VIDEO_CODEC_PROFILE_REPACE, '$1'); if (currentCodec !== nextCodec) { const mimeType = `${container};codecs=${levelCodec || codec}`; this.appendChangeType(trackName, mimeType); logger.log(`[buffer-controller]: switching codec ${currentCodec} to ${nextCodec}`); this.tracks[trackName] = { buffer: track.buffer, codec, container, levelCodec, metadata, id }; } } } else { // if source buffer(s) not created yet, appended buffer tracks in this.pendingTracks this.pendingTracks[trackName] = data[trackName]; } }); // if sourcebuffers already created, do nothing ... if (sourceBufferCount) { return; } this.bufferCodecEventsExpected = Math.max(this.bufferCodecEventsExpected - 1, 0); if (this.mediaSource && this.mediaSource.readyState === 'open') { this.checkPendingTracks(); } } appendChangeType(type, mimeType) { const { operationQueue } = this; const operation = { execute: () => { const sb = this.sourceBuffer[type]; if (sb) { logger.log(`[buffer-controller]: changing ${type} sourceBuffer type to ${mimeType}`); sb.changeType(mimeType); } operationQueue.shiftAndExecuteNext(type); }, onStart: () => {}, onComplete: () => {}, onError: e => { logger.warn(`[buffer-controller]: Failed to change ${type} SourceBuffer type`, e); } }; operationQueue.append(operation, type); } onBufferAppending(event, eventData) { const { hls, operationQueue, tracks } = this; const { data, type, frag, part, chunkMeta } = eventData; const chunkStats = chunkMeta.buffering[type]; const bufferAppendingStart = self.performance.now(); chunkStats.start = bufferAppendingStart; const fragBuffering = frag.stats.buffering; const partBuffering = part ? part.stats.buffering : null; if (fragBuffering.start === 0) { fragBuffering.start = bufferAppendingStart; } if (partBuffering && partBuffering.start === 0) { partBuffering.start = bufferAppendingStart; } // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended) // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset` // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos). // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486 const audioTrack = tracks.audio; let checkTimestampOffset = false; if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') { checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn; this.lastMpegAudioChunk = chunkMeta; } const fragStart = frag.start; const operation = { execute: () => { chunkStats.executeStart = self.performance.now(); if (checkTimestampOffset) { const sb = this.sourceBuffer[type]; if (sb) { const delta = fragStart - sb.timestampOffset; if (Math.abs(delta) >= 0.1) { logger.log(`[buffer-controller]: Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`); sb.timestampOffset = fragStart; } } } this.appendExecutor(data, type); }, onStart: () => { // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`); }, onComplete: () => { // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`); const end = self.performance.now(); chunkStats.executeEnd = chunkStats.end = end; if (fragBuffering.first === 0) { fragBuffering.first = end; } if (partBuffering && partBuffering.first === 0) { partBuffering.first = end; } const { sourceBuffer } = this; const timeRanges = {}; for (const type in sourceBuffer) { timeRanges[type] = BufferHelper.getBuffered(sourceBuffer[type]); } this.appendError = 0; this.hls.trigger(Events.BUFFER_APPENDED, { type, frag, part, chunkMeta, parent: frag.type, timeRanges }); }, onError: err => { // in case any error occured while appending, put back segment in segments table logger.error(`[buffer-controller]: Error encountered while trying to append to the ${type} SourceBuffer`, err); const event = { type: ErrorTypes.MEDIA_ERROR, parent: frag.type, details: ErrorDetails.BUFFER_APPEND_ERROR, frag, part, chunkMeta, error: err, err, fatal: false }; if (err.code === DOMException.QUOTA_EXCEEDED_ERR) { // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror // let's stop appending any segments, and report BUFFER_FULL_ERROR error event.details = ErrorDetails.BUFFER_FULL_ERROR; } else { this.appendError++; event.details = ErrorDetails.BUFFER_APPEND_ERROR; /* with UHD content, we could get loop of quota exceeded error until browser is able to evict some data from sourcebuffer. Retrying can help recover. */ if (this.appendError > hls.config.appendErrorMaxRetry) { logger.error(`[buffer-controller]: Failed ${hls.config.appendErrorMaxRetry} times to append segment in sourceBuffer`); event.fatal = true; } } hls.trigger(Events.ERROR, event); } }; operationQueue.append(operation, type); } onBufferFlushing(event, data) { const { operationQueue } = this; const flushOperation = type => ({ execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset), onStart: () => { // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); }, onComplete: () => { // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); this.hls.trigger(Events.BUFFER_FLUSHED, { type }); }, onError: e => { logger.warn(`[buffer-controller]: Failed to remove from ${type} SourceBuffer`, e); } }); if (data.type) { operationQueue.append(flushOperation(data.type), data.type); } else { this.getSourceBufferTypes().forEach(type => { operationQueue.append(flushOperation(type), type); }); } } onFragParsed(event, data) { const { frag, part } = data; const buffersAppendedTo = []; const elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams; if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) { buffersAppendedTo.push('audiovideo'); } else { if (elementaryStreams[ElementaryStreamTypes.AUDIO]) { buffersAppendedTo.push('audio'); } if (elementaryStreams[ElementaryStreamTypes.VIDEO]) { buffersAppendedTo.push('video'); } } const onUnblocked = () => { const now = self.performance.now(); frag.stats.buffering.end = now; if (part) { part.stats.buffering.end = now; } const stats = part ? part.stats : frag.stats; this.hls.trigger(Events.FRAG_BUFFERED, { frag, part, stats, id: frag.type }); }; if (buffersAppendedTo.length === 0) { logger.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`); } this.blockBuffers(onUnblocked, buffersAppendedTo); } onFragChanged(event, data) { this.flushBackBuffer(); } // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos() // an undefined data.type will mark all buffers as EOS. onBufferEos(event, data) { const ended = this.getSourceBufferTypes().reduce((acc, type) => { const sb = this.sourceBuffer[type]; if (sb && (!data.type || data.type === type)) { sb.ending = true; if (!sb.ended) { sb.ended = true; logger.log(`[buffer-controller]: ${type} sourceBuffer now EOS`); } } return acc && !!(!sb || sb.ended); }, true); if (ended) { logger.log(`[buffer-controller]: Queueing mediaSource.endOfStream()`); this.blockBuffers(() => { this.getSourceBufferTypes().forEach(type => { const sb = this.sourceBuffer[type]; if (sb) { sb.ending = false; } }); const { mediaSource } = this; if (!mediaSource || mediaSource.readyState !== 'open') { if (mediaSource) { logger.info(`[buffer-controller]: Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`); } return; } logger.log(`[buffer-controller]: Calling mediaSource.endOfStream()`); // Allow this to throw and be caught by the enqueueing function mediaSource.endOfStream(); }); } } onLevelUpdated(event, { details }) { if (!details.fragments.length) { return; } this.details = details; if (this.getSourceBufferTypes().length) { this.blockBuffers(this.updateMediaElementDuration.bind(this)); } else { this.updateMediaElementDuration(); } } flushBackBuffer() { const { hls, details, media, sourceBuffer } = this; if (!media || details === null) { return; } const sourceBufferTypes = this.getSourceBufferTypes(); if (!sourceBufferTypes.length) { return; } // Support for deprecated liveBackBufferLength const backBufferLength = details.live && hls.config.liveBackBufferLength !== null ? hls.config.liveBackBufferLength : hls.config.backBufferLength; if (!isFiniteNumber(backBufferLength) || backBufferLength < 0) { return; } const currentTime = media.currentTime; const targetDuration = details.levelTargetDuration; const maxBackBufferLength = Math.max(backBufferLength, targetDuration); const targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength; sourceBufferTypes.forEach(type => { const sb = sourceBuffer[type]; if (sb) { const buffered = BufferHelper.getBuffered(sb); // when target buffer start exceeds actual buffer start if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) { hls.trigger(Events.BACK_BUFFER_REACHED, { bufferEnd: targetBackBufferPosition }); // Support for deprecated event: if (details.live) { hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, { bufferEnd: targetBackBufferPosition }); } else if (sb.ended && buffered.end(buffered.length - 1) - currentTime < targetDuration * 2) { logger.info(`[buffer-controller]: Cannot flush ${type} back buffer while SourceBuffer is in ended state`); return; } hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: targetBackBufferPosition, type }); } } }); } /** * Update Media Source duration to current level duration or override to Infinity if configuration parameter * 'liveDurationInfinity` is set to `true` * More details: https://github.com/video-dev/hls.js/issues/355 */ updateMediaElementDuration() { if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') { return; } const { details, hls, media, mediaSource } = this; const levelDuration = details.fragments[0].start + details.totalduration; const mediaDuration = media.duration; const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0; if (details.live && hls.config.liveDurationInfinity) { // Override duration to Infinity logger.log('[buffer-controller]: Media Source duration is set to Infinity'); mediaSource.duration = Infinity; this.updateSeekableRange(details); } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) { // levelDuration was the last value we set. // not using mediaSource.duration as the browser may tweak this value // only update Media Source duration if its value increase, this is to avoid // flushing already buffered portion when switching between quality level logger.log(`[buffer-controller]: Updating Media Source duration to ${levelDuration.toFixed(3)}`); mediaSource.duration = levelDuration; } } updateSeekableRange(levelDetails) { const mediaSource = this.mediaSource; const fragments = levelDetails.fragments; const len = fragments.length; if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) { const start = Math.max(0, fragments[0].start); const end = Math.max(start, start + levelDetails.totalduration); mediaSource.setLiveSeekableRange(start, end); } } checkPendingTracks() { const { bufferCodecEventsExpected, operationQueue, pendingTracks } = this; // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once. // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after // data has been appended to existing ones. // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers. const pendingTracksCount = Object.keys(pendingTracks).length; if (pendingTracksCount && !bufferCodecEventsExpected || pendingTracksCount === 2) { // ok, let's create them now ! this.createSourceBuffers(pendingTracks); this.pendingTracks = {}; // append any pending segments now ! const buffers = this.getSourceBufferTypes(); if (buffers.length) { this.hls.trigger(Events.BUFFER_CREATED, { tracks: this.tracks }); buffers.forEach(type => { operationQueue.executeNext(type); }); } else { const error = new Error('could not create source buffer for media codec(s)'); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR, fatal: true, error, reason: error.message }); } } } createSourceBuffers(tracks) { const { sourceBuffer, mediaSource } = this; if (!mediaSource) { throw Error('createSourceBuffers called when mediaSource was null'); } for (const trackName in tracks) { if (!sourceBuffer[trackName]) { const track = tracks[trackName]; if (!track) { throw Error(`source buffer exists for track ${trackName}, however track does not`); } // use levelCodec as first priority const codec = track.levelCodec || track.codec; const mimeType = `${track.container};codecs=${codec}`; logger.log(`[buffer-controller]: creating sourceBuffer(${mimeType})`); try { const sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType); const sbName = trackName; this.addBufferListener(sbName, 'updatestart', this._onSBUpdateStart); this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd); this.addBufferListener(sbName, 'error', this._onSBUpdateError); this.tracks[trackName] = { buffer: sb, codec: codec, container: track.container, levelCodec: track.levelCodec, metadata: track.metadata, id: track.id }; } catch (err) { logger.error(`[buffer-controller]: error while trying to add sourceBuffer: ${err.message}`); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_ADD_CODEC_ERROR, fatal: false, error: err, mimeType: mimeType }); } } } } _onSBUpdateStart(type) { const { operationQueue } = this; const operation = operationQueue.current(type); operation.onStart(); } _onSBUpdateEnd(type) { const { operationQueue } = this; const operation = operationQueue.current(type); operation.onComplete(); operationQueue.shiftAndExecuteNext(type); } _onSBUpdateError(type, event) { const error = new Error(`${type} SourceBuffer error`); logger.error(`[buffer-controller]: ${error}`, event); // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_APPENDING_ERROR, error, fatal: false }); // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue const operation = this.operationQueue.current(type); if (operation) { operation.onError(event); } } // This method must result in an updateend event; if remove is not called, _onSBUpdateEnd must be called manually removeExecutor(type, startOffset, endOffset) { const { media, mediaSource, operationQueue, sourceBuffer } = this; const sb = sourceBuffer[type]; if (!media || !mediaSource || !sb) { logger.warn(`[buffer-controller]: Attempting to remove from the ${type} SourceBuffer, but it does not exist`); operationQueue.shiftAndExecuteNext(type); return; } const mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity; const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity; const removeStart = Math.max(0, startOffset); const removeEnd = Math.min(endOffset, mediaDuration, msDuration); if (removeEnd > removeStart && !sb.ending) { sb.ended = false; logger.log(`[buffer-controller]: Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`); sb.remove(removeStart, removeEnd); } else { // Cycle the queue operationQueue.shiftAndExecuteNext(type); } } // This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually appendExecutor(data, type) { const { operationQueue, sourceBuffer } = this; const sb = sourceBuffer[type]; if (!sb) { logger.warn(`[buffer-controller]: Attempting to append to the ${type} SourceBuffer, but it does not exist`); operationQueue.shiftAndExecuteNext(type); return; } sb.ended = false; sb.appendBuffer(data); } // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue // upon completion, since we already do it here blockBuffers(onUnblocked, buffers = this.getSourceBufferTypes()) { if (!buffers.length) { logger.log('[buffer-controller]: Blocking operation requested, but no SourceBuffers exist'); Promise.resolve().then(onUnblocked); return; } const { operationQueue } = this; // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`); const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type)); Promise.all(blockingOperations).then(() => { // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`); onUnblocked(); buffers.forEach(type => { const sb = this.sourceBuffer[type]; // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration) // While this is a workaround, it's probably useful to have around if (!(sb != null && sb.updating)) { operationQueue.shiftAndExecuteNext(type); } }); }); } getSourceBufferTypes() { return Object.keys(this.sourceBuffer); } addBufferListener(type, event, fn) { const buffer = this.sourceBuffer[type]; if (!buffer) { return; } const listener = fn.bind(this, type); this.listeners[type].push({ event, listener }); buffer.addEventListener(event, listener); } removeBufferListeners(type) { const buffer = this.sourceBuffer[type]; if (!buffer) { return; } this.listeners[type].forEach(l => { buffer.removeEventListener(l.event, l.listener); }); } } /** * * This code was ported from the dash.js project at: * https://github.com/Dash-Industry-Forum/dash.js/blob/development/externals/cea608-parser.js * https://github.com/Dash-Industry-Forum/dash.js/commit/8269b26a761e0853bb21d78780ed945144ecdd4d#diff-71bc295a2d6b6b7093a1d3290d53a4b2 * * The original copyright appears below: * * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2015-2016, DASH Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * 2. Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes */ const specialCea608CharsCodes = { 0x2a: 0xe1, // lowercase a, acute accent 0x5c: 0xe9, // lowercase e, acute accent 0x5e: 0xed, // lowercase i, acute accent 0x5f: 0xf3, // lowercase o, acute accent 0x60: 0xfa, // lowercase u, acute accent 0x7b: 0xe7, // lowercase c with cedilla 0x7c: 0xf7, // division symbol 0x7d: 0xd1, // uppercase N tilde 0x7e: 0xf1, // lowercase n tilde 0x7f: 0x2588, // Full block // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES 0x80: 0xae, // Registered symbol (R) 0x81: 0xb0, // degree sign 0x82: 0xbd, // 1/2 symbol 0x83: 0xbf, // Inverted (open) question mark 0x84: 0x2122, // Trademark symbol (TM) 0x85: 0xa2, // Cents symbol 0x86: 0xa3, // Pounds sterling 0x87: 0x266a, // Music 8'th note 0x88: 0xe0, // lowercase a, grave accent 0x89: 0x20, // transparent space (regular) 0x8a: 0xe8, // lowercase e, grave accent 0x8b: 0xe2, // lowercase a, circumflex accent 0x8c: 0xea, // lowercase e, circumflex accent 0x8d: 0xee, // lowercase i, circumflex accent 0x8e: 0xf4, // lowercase o, circumflex accent 0x8f: 0xfb, // lowercase u, circumflex accent // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F 0x90: 0xc1, // capital letter A with acute 0x91: 0xc9, // capital letter E with acute 0x92: 0xd3, // capital letter O with acute 0x93: 0xda, // capital letter U with acute 0x94: 0xdc, // capital letter U with diaresis 0x95: 0xfc, // lowercase letter U with diaeresis 0x96: 0x2018, // opening single quote 0x97: 0xa1, // inverted exclamation mark 0x98: 0x2a, // asterisk 0x99: 0x2019, // closing single quote 0x9a: 0x2501, // box drawings heavy horizontal 0x9b: 0xa9, // copyright sign 0x9c: 0x2120, // Service mark 0x9d: 0x2022, // (round) bullet 0x9e: 0x201c, // Left double quotation mark 0x9f: 0x201d, // Right double quotation mark 0xa0: 0xc0, // uppercase A, grave accent 0xa1: 0xc2, // uppercase A, circumflex 0xa2: 0xc7, // uppercase C with cedilla 0xa3: 0xc8, // uppercase E, grave accent 0xa4: 0xca, // uppercase E, circumflex 0xa5: 0xcb, // capital letter E with diaresis 0xa6: 0xeb, // lowercase letter e with diaresis 0xa7: 0xce, // uppercase I, circumflex 0xa8: 0xcf, // uppercase I, with diaresis 0xa9: 0xef, // lowercase i, with diaresis 0xaa: 0xd4, // uppercase O, circumflex 0xab: 0xd9, // uppercase U, grave accent 0xac: 0xf9, // lowercase u, grave accent 0xad: 0xdb, // uppercase U, circumflex 0xae: 0xab, // left-pointing double angle quotation mark 0xaf: 0xbb, // right-pointing double angle quotation mark // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F 0xb0: 0xc3, // Uppercase A, tilde 0xb1: 0xe3, // Lowercase a, tilde 0xb2: 0xcd, // Uppercase I, acute accent 0xb3: 0xcc, // Uppercase I, grave accent 0xb4: 0xec, // Lowercase i, grave accent 0xb5: 0xd2, // Uppercase O, grave accent 0xb6: 0xf2, // Lowercase o, grave accent 0xb7: 0xd5, // Uppercase O, tilde 0xb8: 0xf5, // Lowercase o, tilde 0xb9: 0x7b, // Open curly brace 0xba: 0x7d, // Closing curly brace 0xbb: 0x5c, // Backslash 0xbc: 0x5e, // Caret 0xbd: 0x5f, // Underscore 0xbe: 0x7c, // Pipe (vertical line) 0xbf: 0x223c, // Tilde operator 0xc0: 0xc4, // Uppercase A, umlaut 0xc1: 0xe4, // Lowercase A, umlaut 0xc2: 0xd6, // Uppercase O, umlaut 0xc3: 0xf6, // Lowercase o, umlaut 0xc4: 0xdf, // Esszett (sharp S) 0xc5: 0xa5, // Yen symbol 0xc6: 0xa4, // Generic currency sign 0xc7: 0x2503, // Box drawings heavy vertical 0xc8: 0xc5, // Uppercase A, ring 0xc9: 0xe5, // Lowercase A, ring 0xca: 0xd8, // Uppercase O, stroke 0xcb: 0xf8, // Lowercase o, strok 0xcc: 0x250f, // Box drawings heavy down and right 0xcd: 0x2513, // Box drawings heavy down and left 0xce: 0x2517, // Box drawings heavy up and right 0xcf: 0x251b // Box drawings heavy up and left }; /** * Utils */ const getCharForByte = function getCharForByte(byte) { let charCode = byte; if (specialCea608CharsCodes.hasOwnProperty(byte)) { charCode = specialCea608CharsCodes[byte]; } return String.fromCharCode(charCode); }; const NR_ROWS = 15; const NR_COLS = 100; // Tables to look up row from PAC data const rowsLowCh1 = { 0x11: 1, 0x12: 3, 0x15: 5, 0x16: 7, 0x17: 9, 0x10: 11, 0x13: 12, 0x14: 14 }; const rowsHighCh1 = { 0x11: 2, 0x12: 4, 0x15: 6, 0x16: 8, 0x17: 10, 0x13: 13, 0x14: 15 }; const rowsLowCh2 = { 0x19: 1, 0x1a: 3, 0x1d: 5, 0x1e: 7, 0x1f: 9, 0x18: 11, 0x1b: 12, 0x1c: 14 }; const rowsHighCh2 = { 0x19: 2, 0x1a: 4, 0x1d: 6, 0x1e: 8, 0x1f: 10, 0x1b: 13, 0x1c: 15 }; const backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent']; class CaptionsLogger { constructor() { this.time = null; this.verboseLevel = 0; } log(severity, msg) { if (this.verboseLevel >= severity) { const m = typeof msg === 'function' ? msg() : msg; logger.log(`${this.time} [${severity}] ${m}`); } } } const numArrayToHexArray = function numArrayToHexArray(numArray) { const hexArray = []; for (let j = 0; j < numArray.length; j++) { hexArray.push(numArray[j].toString(16)); } return hexArray; }; class PenState { constructor(foreground, underline, italics, background, flash) { this.foreground = void 0; this.underline = void 0; this.italics = void 0; this.background = void 0; this.flash = void 0; this.foreground = foreground || 'white'; this.underline = underline || false; this.italics = italics || false; this.background = background || 'black'; this.flash = flash || false; } reset() { this.foreground = 'white'; this.underline = false; this.italics = false; this.background = 'black'; this.flash = false; } setStyles(styles) { const attribs = ['foreground', 'underline', 'italics', 'background', 'flash']; for (let i = 0; i < attribs.length; i++) { const style = attribs[i]; if (styles.hasOwnProperty(style)) { this[style] = styles[style]; } } } isDefault() { return this.foreground === 'white' && !this.underline && !this.italics && this.background === 'black' && !this.flash; } equals(other) { return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash; } copy(newPenState) { this.foreground = newPenState.foreground; this.underline = newPenState.underline; this.italics = newPenState.italics; this.background = newPenState.background; this.flash = newPenState.flash; } toString() { return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash; } } /** * Unicode character with styling and background. * @constructor */ class StyledUnicodeChar { constructor(uchar, foreground, underline, italics, background, flash) { this.uchar = void 0; this.penState = void 0; this.uchar = uchar || ' '; // unicode character this.penState = new PenState(foreground, underline, italics, background, flash); } reset() { this.uchar = ' '; this.penState.reset(); } setChar(uchar, newPenState) { this.uchar = uchar; this.penState.copy(newPenState); } setPenState(newPenState) { this.penState.copy(newPenState); } equals(other) { return this.uchar === other.uchar && this.penState.equals(other.penState); } copy(newChar) { this.uchar = newChar.uchar; this.penState.copy(newChar.penState); } isEmpty() { return this.uchar === ' ' && this.penState.isDefault(); } } /** * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar. * @constructor */ class Row { constructor(logger) { this.chars = void 0; this.pos = void 0; this.currPenState = void 0; this.cueStartTime = void 0; this.logger = void 0; this.chars = []; for (let i = 0; i < NR_COLS; i++) { this.chars.push(new StyledUnicodeChar()); } this.logger = logger; this.pos = 0; this.currPenState = new PenState(); } equals(other) { let equal = true; for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].equals(other.chars[i])) { equal = false; break; } } return equal; } copy(other) { for (let i = 0; i < NR_COLS; i++) { this.chars[i].copy(other.chars[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].isEmpty()) { empty = false; break; } } return empty; } /** * Set the cursor to a valid column. */ setCursor(absPos) { if (this.pos !== absPos) { this.pos = absPos; } if (this.pos < 0) { this.logger.log(3, 'Negative cursor position ' + this.pos); this.pos = 0; } else if (this.pos > NR_COLS) { this.logger.log(3, 'Too large cursor position ' + this.pos); this.pos = NR_COLS; } } /** * Move the cursor relative to current position. */ moveCursor(relPos) { const newPos = this.pos + relPos; if (relPos > 1) { for (let i = this.pos + 1; i < newPos + 1; i++) { this.chars[i].setPenState(this.currPenState); } } this.setCursor(newPos); } /** * Backspace, move one step back and clear character. */ backSpace() { this.moveCursor(-1); this.chars[this.pos].setChar(' ', this.currPenState); } insertChar(byte) { if (byte >= 0x90) { // Extended char this.backSpace(); } const char = getCharForByte(byte); if (this.pos >= NR_COLS) { this.logger.log(0, () => 'Cannot insert ' + byte.toString(16) + ' (' + char + ') at position ' + this.pos + '. Skipping it!'); return; } this.chars[this.pos].setChar(char, this.currPenState); this.moveCursor(1); } clearFromPos(startPos) { let i; for (i = startPos; i < NR_COLS; i++) { this.chars[i].reset(); } } clear() { this.clearFromPos(0); this.pos = 0; this.currPenState.reset(); } clearToEndOfRow() { this.clearFromPos(this.pos); } getTextString() { const chars = []; let empty = true; for (let i = 0; i < NR_COLS; i++) { const char = this.chars[i].uchar; if (char !== ' ') { empty = false; } chars.push(char); } if (empty) { return ''; } else { return chars.join(''); } } setPenStyles(styles) { this.currPenState.setStyles(styles); const currChar = this.chars[this.pos]; currChar.setPenState(this.currPenState); } } /** * Keep a CEA-608 screen of 32x15 styled characters * @constructor */ class CaptionScreen { constructor(logger) { this.rows = void 0; this.currRow = void 0; this.nrRollUpRows = void 0; this.lastOutputScreen = void 0; this.logger = void 0; this.rows = []; for (let i = 0; i < NR_ROWS; i++) { this.rows.push(new Row(logger)); } // Note that we use zero-based numbering (0-14) this.logger = logger; this.currRow = NR_ROWS - 1; this.nrRollUpRows = null; this.lastOutputScreen = null; this.reset(); } reset() { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } this.currRow = NR_ROWS - 1; } equals(other) { let equal = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].equals(other.rows[i])) { equal = false; break; } } return equal; } copy(other) { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].copy(other.rows[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].isEmpty()) { empty = false; break; } } return empty; } backSpace() { const row = this.rows[this.currRow]; row.backSpace(); } clearToEndOfRow() { const row = this.rows[this.currRow]; row.clearToEndOfRow(); } /** * Insert a character (without styling) in the current row. */ insertChar(char) { const row = this.rows[this.currRow]; row.insertChar(char); } setPen(styles) { const row = this.rows[this.currRow]; row.setPenStyles(styles); } moveCursor(relPos) { const row = this.rows[this.currRow]; row.moveCursor(relPos); } setCursor(absPos) { this.logger.log(2, 'setCursor: ' + absPos); const row = this.rows[this.currRow]; row.setCursor(absPos); } setPAC(pacData) { this.logger.log(2, () => 'pacData = ' + JSON.stringify(pacData)); let newRow = pacData.row - 1; if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) { newRow = this.nrRollUpRows - 1; } // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows if (this.nrRollUpRows && this.currRow !== newRow) { // clear all rows first for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location // topRowIndex - the start of rows to copy (inclusive index) const topRowIndex = this.currRow + 1 - this.nrRollUpRows; // We only copy if the last position was already shown. // We use the cueStartTime value to check this. const lastOutputScreen = this.lastOutputScreen; if (lastOutputScreen) { const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime; const time = this.logger.time; if (prevLineTime && time !== null && prevLineTime < time) { for (let i = 0; i < this.nrRollUpRows; i++) { this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]); } } } } this.currRow = newRow; const row = this.rows[this.currRow]; if (pacData.indent !== null) { const indent = pacData.indent; const prevPos = Math.max(indent - 1, 0); row.setCursor(pacData.indent); pacData.color = row.chars[prevPos].penState.foreground; } const styles = { foreground: pacData.color, underline: pacData.underline, italics: pacData.italics, background: 'black', flash: false }; this.setPen(styles); } /** * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility). */ setBkgData(bkgData) { this.logger.log(2, () => 'bkgData = ' + JSON.stringify(bkgData)); this.backSpace(); this.setPen(bkgData); this.insertChar(0x20); // Space } setRollUpRows(nrRows) { this.nrRollUpRows = nrRows; } rollUp() { if (this.nrRollUpRows === null) { this.logger.log(3, 'roll_up but nrRollUpRows not set yet'); return; // Not properly setup } this.logger.log(1, () => this.getDisplayText()); const topRowIndex = this.currRow + 1 - this.nrRollUpRows; const topRow = this.rows.splice(topRowIndex, 1)[0]; topRow.clear(); this.rows.splice(this.currRow, 0, topRow); this.logger.log(2, 'Rolling up'); // this.logger.log(VerboseLevel.TEXT, this.get_display_text()) } /** * Get all non-empty rows with as unicode text. */ getDisplayText(asOneRow) { asOneRow = asOneRow || false; const displayText = []; let text = ''; let rowNr = -1; for (let i = 0; i < NR_ROWS; i++) { const rowText = this.rows[i].getTextString(); if (rowText) { rowNr = i + 1; if (asOneRow) { displayText.push('Row ' + rowNr + ": '" + rowText + "'"); } else { displayText.push(rowText.trim()); } } } if (displayText.length > 0) { if (asOneRow) { text = '[' + displayText.join(' | ') + ']'; } else { text = displayText.join('\n'); } } return text; } getTextAndFormat() { return this.rows; } } // var modes = ['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT']; class Cea608Channel { constructor(channelNumber, outputFilter, logger) { this.chNr = void 0; this.outputFilter = void 0; this.mode = void 0; this.verbose = void 0; this.displayedMemory = void 0; this.nonDisplayedMemory = void 0; this.lastOutputScreen = void 0; this.currRollUpRow = void 0; this.writeScreen = void 0; this.cueStartTime = void 0; this.logger = void 0; this.chNr = channelNumber; this.outputFilter = outputFilter; this.mode = null; this.verbose = 0; this.displayedMemory = new CaptionScreen(logger); this.nonDisplayedMemory = new CaptionScreen(logger); this.lastOutputScreen = new CaptionScreen(logger); this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; this.writeScreen = this.displayedMemory; this.mode = null; this.cueStartTime = null; // Keeps track of where a cue started. this.logger = logger; } reset() { this.mode = null; this.displayedMemory.reset(); this.nonDisplayedMemory.reset(); this.lastOutputScreen.reset(); this.outputFilter.reset(); this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; this.writeScreen = this.displayedMemory; this.mode = null; this.cueStartTime = null; } getHandler() { return this.outputFilter; } setHandler(newHandler) { this.outputFilter = newHandler; } setPAC(pacData) { this.writeScreen.setPAC(pacData); } setBkgData(bkgData) { this.writeScreen.setBkgData(bkgData); } setMode(newMode) { if (newMode === this.mode) { return; } this.mode = newMode; this.logger.log(2, () => 'MODE=' + newMode); if (this.mode === 'MODE_POP-ON') { this.writeScreen = this.nonDisplayedMemory; } else { this.writeScreen = this.displayedMemory; this.writeScreen.reset(); } if (this.mode !== 'MODE_ROLL-UP') { this.displayedMemory.nrRollUpRows = null; this.nonDisplayedMemory.nrRollUpRows = null; } this.mode = newMode; } insertChars(chars) { for (let i = 0; i < chars.length; i++) { this.writeScreen.insertChar(chars[i]); } const screen = this.writeScreen === this.displayedMemory ? 'DISP' : 'NON_DISP'; this.logger.log(2, () => screen + ': ' + this.writeScreen.getDisplayText(true)); if (this.mode === 'MODE_PAINT-ON' || this.mode === 'MODE_ROLL-UP') { this.logger.log(1, () => 'DISPLAYED: ' + this.displayedMemory.getDisplayText(true)); this.outputDataUpdate(); } } ccRCL() { // Resume Caption Loading (switch mode to Pop On) this.logger.log(2, 'RCL - Resume Caption Loading'); this.setMode('MODE_POP-ON'); } ccBS() { // BackSpace this.logger.log(2, 'BS - BackSpace'); if (this.mode === 'MODE_TEXT') { return; } this.writeScreen.backSpace(); if (this.writeScreen === this.displayedMemory) { this.outputDataUpdate(); } } ccAOF() { // Reserved (formerly Alarm Off) } ccAON() { // Reserved (formerly Alarm On) } ccDER() { // Delete to End of Row this.logger.log(2, 'DER- Delete to End of Row'); this.writeScreen.clearToEndOfRow(); this.outputDataUpdate(); } ccRU(nrRows) { // Roll-Up Captions-2,3,or 4 Rows this.logger.log(2, 'RU(' + nrRows + ') - Roll Up'); this.writeScreen = this.displayedMemory; this.setMode('MODE_ROLL-UP'); this.writeScreen.setRollUpRows(nrRows); } ccFON() { // Flash On this.logger.log(2, 'FON - Flash On'); this.writeScreen.setPen({ flash: true }); } ccRDC() { // Resume Direct Captioning (switch mode to PaintOn) this.logger.log(2, 'RDC - Resume Direct Captioning'); this.setMode('MODE_PAINT-ON'); } ccTR() { // Text Restart in text mode (not supported, however) this.logger.log(2, 'TR'); this.setMode('MODE_TEXT'); } ccRTD() { // Resume Text Display in Text mode (not supported, however) this.logger.log(2, 'RTD'); this.setMode('MODE_TEXT'); } ccEDM() { // Erase Displayed Memory this.logger.log(2, 'EDM - Erase Displayed Memory'); this.displayedMemory.reset(); this.outputDataUpdate(true); } ccCR() { // Carriage Return this.logger.log(2, 'CR - Carriage Return'); this.writeScreen.rollUp(); this.outputDataUpdate(true); } ccENM() { // Erase Non-Displayed Memory this.logger.log(2, 'ENM - Erase Non-displayed Memory'); this.nonDisplayedMemory.reset(); } ccEOC() { // End of Caption (Flip Memories) this.logger.log(2, 'EOC - End Of Caption'); if (this.mode === 'MODE_POP-ON') { const tmp = this.displayedMemory; this.displayedMemory = this.nonDisplayedMemory; this.nonDisplayedMemory = tmp; this.writeScreen = this.nonDisplayedMemory; this.logger.log(1, () => 'DISP: ' + this.displayedMemory.getDisplayText()); } this.outputDataUpdate(true); } ccTO(nrCols) { // Tab Offset 1,2, or 3 columns this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset'); this.writeScreen.moveCursor(nrCols); } ccMIDROW(secondByte) { // Parse MIDROW command const styles = { flash: false }; styles.underline = secondByte % 2 === 1; styles.italics = secondByte >= 0x2e; if (!styles.italics) { const colorIndex = Math.floor(secondByte / 2) - 0x10; const colors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta']; styles.foreground = colors[colorIndex]; } else { styles.foreground = 'white'; } this.logger.log(2, 'MIDROW: ' + JSON.stringify(styles)); this.writeScreen.setPen(styles); } outputDataUpdate(dispatch = false) { const time = this.logger.time; if (time === null) { return; } if (this.outputFilter) { if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) { // Start of a new cue this.cueStartTime = time; } else { if (!this.displayedMemory.equals(this.lastOutputScreen)) { this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen); if (dispatch && this.outputFilter.dispatchCue) { this.outputFilter.dispatchCue(); } this.cueStartTime = this.displayedMemory.isEmpty() ? null : time; } } this.lastOutputScreen.copy(this.displayedMemory); } } cueSplitAtTime(t) { if (this.outputFilter) { if (!this.displayedMemory.isEmpty()) { if (this.outputFilter.newCue) { this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory); } this.cueStartTime = t; } } } } // Will be 1 or 2 when parsing captions class Cea608Parser { constructor(field, out1, out2) { this.channels = void 0; this.currentChannel = 0; this.cmdHistory = void 0; this.logger = void 0; const logger = new CaptionsLogger(); this.channels = [null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)]; this.cmdHistory = createCmdHistory(); this.logger = logger; } getHandler(channel) { return this.channels[channel].getHandler(); } setHandler(channel, newHandler) { this.channels[channel].setHandler(newHandler); } /** * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs. */ addData(time, byteList) { let cmdFound; let a; let b; let charsFound = false; this.logger.time = time; for (let i = 0; i < byteList.length; i += 2) { a = byteList[i] & 0x7f; b = byteList[i + 1] & 0x7f; if (a === 0 && b === 0) { continue; } else { this.logger.log(3, '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')'); } cmdFound = this.parseCmd(a, b); if (!cmdFound) { cmdFound = this.parseMidrow(a, b); } if (!cmdFound) { cmdFound = this.parsePAC(a, b); } if (!cmdFound) { cmdFound = this.parseBackgroundAttributes(a, b); } if (!cmdFound) { charsFound = this.parseChars(a, b); if (charsFound) { const currChNr = this.currentChannel; if (currChNr && currChNr > 0) { const channel = this.channels[currChNr]; channel.insertChars(charsFound); } else { this.logger.log(2, 'No channel found yet. TEXT-MODE?'); } } } if (!cmdFound && !charsFound) { this.logger.log(2, "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]])); } } } /** * Parse Command. * @returns True if a command was found */ parseCmd(a, b) { const { cmdHistory } = this; const cond1 = (a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) && b >= 0x20 && b <= 0x2f; const cond2 = (a === 0x17 || a === 0x1f) && b >= 0x21 && b <= 0x23; if (!(cond1 || cond2)) { return false; } if (hasCmdRepeated(a, b, cmdHistory)) { setLastCmd(null, null, cmdHistory); this.logger.log(3, 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped'); return true; } const chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2; const channel = this.channels[chNr]; if (a === 0x14 || a === 0x15 || a === 0x1c || a === 0x1d) { if (b === 0x20) { channel.ccRCL(); } else if (b === 0x21) { channel.ccBS(); } else if (b === 0x22) { channel.ccAOF(); } else if (b === 0x23) { channel.ccAON(); } else if (b === 0x24) { channel.ccDER(); } else if (b === 0x25) { channel.ccRU(2); } else if (b === 0x26) { channel.ccRU(3); } else if (b === 0x27) { channel.ccRU(4); } else if (b === 0x28) { channel.ccFON(); } else if (b === 0x29) { channel.ccRDC(); } else if (b === 0x2a) { channel.ccTR(); } else if (b === 0x2b) { channel.ccRTD(); } else if (b === 0x2c) { channel.ccEDM(); } else if (b === 0x2d) { channel.ccCR(); } else if (b === 0x2e) { channel.ccENM(); } else if (b === 0x2f) { channel.ccEOC(); } } else { // a == 0x17 || a == 0x1F channel.ccTO(b - 0x20); } setLastCmd(a, b, cmdHistory); this.currentChannel = chNr; return true; } /** * Parse midrow styling command */ parseMidrow(a, b) { let chNr = 0; if ((a === 0x11 || a === 0x19) && b >= 0x20 && b <= 0x2f) { if (a === 0x11) { chNr = 1; } else { chNr = 2; } if (chNr !== this.currentChannel) { this.logger.log(0, 'Mismatch channel in midrow parsing'); return false; } const channel = this.channels[chNr]; if (!channel) { return false; } channel.ccMIDROW(b); this.logger.log(3, 'MIDROW (' + numArrayToHexArray([a, b]) + ')'); return true; } return false; } /** * Parse Preable Access Codes (Table 53). * @returns {Boolean} Tells if PAC found */ parsePAC(a, b) { let row; const cmdHistory = this.cmdHistory; const case1 = (a >= 0x11 && a <= 0x17 || a >= 0x19 && a <= 0x1f) && b >= 0x40 && b <= 0x7f; const case2 = (a === 0x10 || a === 0x18) && b >= 0x40 && b <= 0x5f; if (!(case1 || case2)) { return false; } if (hasCmdRepeated(a, b, cmdHistory)) { setLastCmd(null, null, cmdHistory); return true; // Repeated commands are dropped (once) } const chNr = a <= 0x17 ? 1 : 2; if (b >= 0x40 && b <= 0x5f) { row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a]; } else { // 0x60 <= b <= 0x7F row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a]; } const channel = this.channels[chNr]; if (!channel) { return false; } channel.setPAC(this.interpretPAC(row, b)); setLastCmd(a, b, cmdHistory); this.currentChannel = chNr; return true; } /** * Interpret the second byte of the pac, and return the information. * @returns pacData with style parameters */ interpretPAC(row, byte) { let pacIndex; const pacData = { color: null, italics: false, indent: null, underline: false, row: row }; if (byte > 0x5f) { pacIndex = byte - 0x60; } else { pacIndex = byte - 0x40; } pacData.underline = (pacIndex & 1) === 1; if (pacIndex <= 0xd) { pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)]; } else if (pacIndex <= 0xf) { pacData.italics = true; pacData.color = 'white'; } else { pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4; } return pacData; // Note that row has zero offset. The spec uses 1. } /** * Parse characters. * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise. */ parseChars(a, b) { let channelNr; let charCodes = null; let charCode1 = null; if (a >= 0x19) { channelNr = 2; charCode1 = a - 8; } else { channelNr = 1; charCode1 = a; } if (charCode1 >= 0x11 && charCode1 <= 0x13) { // Special character let oneCode; if (charCode1 === 0x11) { oneCode = b + 0x50; } else if (charCode1 === 0x12) { oneCode = b + 0x70; } else { oneCode = b + 0x90; } this.logger.log(2, "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr); charCodes = [oneCode]; } else if (a >= 0x20 && a <= 0x7f) { charCodes = b === 0 ? [a] : [a, b]; } if (charCodes) { const hexCodes = numArrayToHexArray(charCodes); this.logger.log(3, 'Char codes = ' + hexCodes.join(',')); setLastCmd(a, b, this.cmdHistory); } return charCodes; } /** * Parse extended background attributes as well as new foreground color black. * @returns True if background attributes are found */ parseBackgroundAttributes(a, b) { const case1 = (a === 0x10 || a === 0x18) && b >= 0x20 && b <= 0x2f; const case2 = (a === 0x17 || a === 0x1f) && b >= 0x2d && b <= 0x2f; if (!(case1 || case2)) { return false; } let index; const bkgData = {}; if (a === 0x10 || a === 0x18) { index = Math.floor((b - 0x20) / 2); bkgData.background = backgroundColors[index]; if (b % 2 === 1) { bkgData.background = bkgData.background + '_semi'; } } else if (b === 0x2d) { bkgData.background = 'transparent'; } else { bkgData.foreground = 'black'; if (b === 0x2f) { bkgData.underline = true; } } const chNr = a <= 0x17 ? 1 : 2; const channel = this.channels[chNr]; channel.setBkgData(bkgData); setLastCmd(a, b, this.cmdHistory); return true; } /** * Reset state of parser and its channels. */ reset() { for (let i = 0; i < Object.keys(this.channels).length; i++) { const channel = this.channels[i]; if (channel) { channel.reset(); } } this.cmdHistory = createCmdHistory(); } /** * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty. */ cueSplitAtTime(t) { for (let i = 0; i < this.channels.length; i++) { const channel = this.channels[i]; if (channel) { channel.cueSplitAtTime(t); } } } } function setLastCmd(a, b, cmdHistory) { cmdHistory.a = a; cmdHistory.b = b; } function hasCmdRepeated(a, b, cmdHistory) { return cmdHistory.a === a && cmdHistory.b === b; } function createCmdHistory() { return { a: null, b: null }; } class OutputFilter { constructor(timelineController, trackName) { this.timelineController = void 0; this.cueRanges = []; this.trackName = void 0; this.startTime = null; this.endTime = null; this.screen = null; this.timelineController = timelineController; this.trackName = trackName; } dispatchCue() { if (this.startTime === null) { return; } this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges); this.startTime = null; } newCue(startTime, endTime, screen) { if (this.startTime === null || this.startTime > startTime) { this.startTime = startTime; } this.endTime = endTime; this.screen = screen; this.timelineController.createCaptionsTrack(this.trackName); } reset() { this.cueRanges = []; this.startTime = null; } } /** * Copyright 2013 vtt.js Contributors * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var VTTCue = (function () { if (typeof self !== 'undefined' && self.VTTCue) { return self.VTTCue; } const AllowedDirections = ['', 'lr', 'rl']; const AllowedAlignments = ['start', 'middle', 'end', 'left', 'right']; function isAllowedValue(allowed, value) { if (typeof value !== 'string') { return false; } // necessary for assuring the generic conforms to the Array interface if (!Array.isArray(allowed)) { return false; } // reset the type so that the next narrowing works well const lcValue = value.toLowerCase(); // use the allow list to narrow the type to a specific subset of strings if (~allowed.indexOf(lcValue)) { return lcValue; } return false; } function findDirectionSetting(value) { return isAllowedValue(AllowedDirections, value); } function findAlignSetting(value) { return isAllowedValue(AllowedAlignments, value); } function extend(obj, ...rest) { let i = 1; for (; i < arguments.length; i++) { const cobj = arguments[i]; for (const p in cobj) { obj[p] = cobj[p]; } } return obj; } function VTTCue(startTime, endTime, text) { const cue = this; const baseObj = { enumerable: true }; /** * Shim implementation specific properties. These properties are not in * the spec. */ // Lets us know when the VTTCue's data has changed in such a way that we need // to recompute its display state. This lets us compute its display state // lazily. cue.hasBeenReset = false; /** * VTTCue and TextTrackCue properties * http://dev.w3.org/html5/webvtt/#vttcue-interface */ let _id = ''; let _pauseOnExit = false; let _startTime = startTime; let _endTime = endTime; let _text = text; let _region = null; let _vertical = ''; let _snapToLines = true; let _line = 'auto'; let _lineAlign = 'start'; let _position = 50; let _positionAlign = 'middle'; let _size = 50; let _align = 'middle'; Object.defineProperty(cue, 'id', extend({}, baseObj, { get: function () { return _id; }, set: function (value) { _id = '' + value; } })); Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, { get: function () { return _pauseOnExit; }, set: function (value) { _pauseOnExit = !!value; } })); Object.defineProperty(cue, 'startTime', extend({}, baseObj, { get: function () { return _startTime; }, set: function (value) { if (typeof value !== 'number') { throw new TypeError('Start time must be set to a number.'); } _startTime = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'endTime', extend({}, baseObj, { get: function () { return _endTime; }, set: function (value) { if (typeof value !== 'number') { throw new TypeError('End time must be set to a number.'); } _endTime = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'text', extend({}, baseObj, { get: function () { return _text; }, set: function (value) { _text = '' + value; this.hasBeenReset = true; } })); // todo: implement VTTRegion polyfill? Object.defineProperty(cue, 'region', extend({}, baseObj, { get: function () { return _region; }, set: function (value) { _region = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'vertical', extend({}, baseObj, { get: function () { return _vertical; }, set: function (value) { const setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. if (setting === false) { throw new SyntaxError('An invalid or illegal string was specified.'); } _vertical = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, { get: function () { return _snapToLines; }, set: function (value) { _snapToLines = !!value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'line', extend({}, baseObj, { get: function () { return _line; }, set: function (value) { if (typeof value !== 'number' && value !== 'auto') { throw new SyntaxError('An invalid number or illegal string was specified.'); } _line = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, { get: function () { return _lineAlign; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _lineAlign = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'position', extend({}, baseObj, { get: function () { return _position; }, set: function (value) { if (value < 0 || value > 100) { throw new Error('Position must be between 0 and 100.'); } _position = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, { get: function () { return _positionAlign; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _positionAlign = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'size', extend({}, baseObj, { get: function () { return _size; }, set: function (value) { if (value < 0 || value > 100) { throw new Error('Size must be between 0 and 100.'); } _size = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'align', extend({}, baseObj, { get: function () { return _align; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _align = setting; this.hasBeenReset = true; } })); /** * Other <track> spec defined properties */ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state cue.displayState = undefined; } /** * VTTCue methods */ VTTCue.prototype.getCueAsHTML = function () { // Assume WebVTT.convertCueToDOMTree is on the global. const WebVTT = self.WebVTT; return WebVTT.convertCueToDOMTree(self, this.text); }; // this is a polyfill hack return VTTCue; })(); /* * Source: https://github.com/mozilla/vtt.js/blob/master/dist/vtt.js */ class StringDecoder { // eslint-disable-next-line @typescript-eslint/no-unused-vars decode(data, options) { if (!data) { return ''; } if (typeof data !== 'string') { throw new Error('Error - expected string data.'); } return decodeURIComponent(encodeURIComponent(data)); } } // Try to parse input as a time stamp. function parseTimeStamp(input) { function computeSeconds(h, m, s, f) { return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f || 0); } const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/); if (!m) { return null; } if (parseFloat(m[2]) > 59) { // Timestamp takes the form of [hours]:[minutes].[milliseconds] // First position is hours as it's over 59. return computeSeconds(m[2], m[3], 0, m[4]); } // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds] return computeSeconds(m[1], m[2], m[3], m[4]); } // A settings object holds key/value pairs and will ignore anything but the first // assignment to a specific key. class Settings { constructor() { this.values = Object.create(null); } // Only accept the first assignment to any key. set(k, v) { if (!this.get(k) && v !== '') { this.values[k] = v; } } // Return the value for a key, or a default value. // If 'defaultKey' is passed then 'dflt' is assumed to be an object with // a number of possible default values as properties where 'defaultKey' is // the key of the property that will be chosen; otherwise it's assumed to be // a single value. get(k, dflt, defaultKey) { if (defaultKey) { return this.has(k) ? this.values[k] : dflt[defaultKey]; } return this.has(k) ? this.values[k] : dflt; } // Check whether we have a value for a key. has(k) { return k in this.values; } // Accept a setting if its one of the given alternatives. alt(k, v, a) { for (let n = 0; n < a.length; ++n) { if (v === a[n]) { this.set(k, v); break; } } } // Accept a setting if its a valid (signed) integer. integer(k, v) { if (/^-?\d+$/.test(v)) { // integer this.set(k, parseInt(v, 10)); } } // Accept a setting if its a valid percentage. percent(k, v) { if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) { const percent = parseFloat(v); if (percent >= 0 && percent <= 100) { this.set(k, percent); return true; } } return false; } } // Helper function to parse input into groups separated by 'groupDelim', and // interpret each group as a key/value pair separated by 'keyValueDelim'. function parseOptions(input, callback, keyValueDelim, groupDelim) { const groups = groupDelim ? input.split(groupDelim) : [input]; for (const i in groups) { if (typeof groups[i] !== 'string') { continue; } const kv = groups[i].split(keyValueDelim); if (kv.length !== 2) { continue; } const k = kv[0]; const v = kv[1]; callback(k, v); } } const defaults = new VTTCue(0, 0, ''); // 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244 // Safari doesn't yet support this change, but FF and Chrome do. const center = defaults.align === 'middle' ? 'middle' : 'center'; function parseCue(input, cue, regionList) { // Remember the original input if we need to throw an error. const oInput = input; // 4.1 WebVTT timestamp function consumeTimeStamp() { const ts = parseTimeStamp(input); if (ts === null) { throw new Error('Malformed timestamp: ' + oInput); } // Remove time stamp from input. input = input.replace(/^[^\sa-zA-Z-]+/, ''); return ts; } // 4.4.2 WebVTT cue settings function consumeCueSettings(input, cue) { const settings = new Settings(); parseOptions(input, function (k, v) { let vals; switch (k) { case 'region': // Find the last region we parsed with the same region id. for (let i = regionList.length - 1; i >= 0; i--) { if (regionList[i].id === v) { settings.set(k, regionList[i].region); break; } } break; case 'vertical': settings.alt(k, v, ['rl', 'lr']); break; case 'line': vals = v.split(','); settings.integer(k, vals[0]); if (settings.percent(k, vals[0])) { settings.set('snapToLines', false); } settings.alt(k, vals[0], ['auto']); if (vals.length === 2) { settings.alt('lineAlign', vals[1], ['start', center, 'end']); } break; case 'position': vals = v.split(','); settings.percent(k, vals[0]); if (vals.length === 2) { settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']); } break; case 'size': settings.percent(k, v); break; case 'align': settings.alt(k, v, ['start', center, 'end', 'left', 'right']); break; } }, /:/, /\s/); // Apply default values for any missing fields. cue.region = settings.get('region', null); cue.vertical = settings.get('vertical', ''); let line = settings.get('line', 'auto'); if (line === 'auto' && defaults.line === -1) { // set numeric line number for Safari line = -1; } cue.line = line; cue.lineAlign = settings.get('lineAlign', 'start'); cue.snapToLines = settings.get('snapToLines', true); cue.size = settings.get('size', 100); cue.align = settings.get('align', center); let position = settings.get('position', 'auto'); if (position === 'auto' && defaults.position === 50) { // set numeric position for Safari position = cue.align === 'start' || cue.align === 'left' ? 0 : cue.align === 'end' || cue.align === 'right' ? 100 : 50; } cue.position = position; } function skipWhitespace() { input = input.replace(/^\s+/, ''); } // 4.1 WebVTT cue timings. skipWhitespace(); cue.startTime = consumeTimeStamp(); // (1) collect cue start time skipWhitespace(); if (input.slice(0, 3) !== '-->') { // (3) next characters must match '-->' throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput); } input = input.slice(3); skipWhitespace(); cue.endTime = consumeTimeStamp(); // (5) collect cue end time // 4.1 WebVTT cue settings list. skipWhitespace(); consumeCueSettings(input, cue); } function fixLineBreaks(input) { return input.replace(/<br(?: \/)?>/gi, '\n'); } class VTTParser { constructor() { this.state = 'INITIAL'; this.buffer = ''; this.decoder = new StringDecoder(); this.regionList = []; this.cue = null; this.oncue = void 0; this.onparsingerror = void 0; this.onflush = void 0; } parse(data) { const _this = this; // If there is no data then we won't decode it, but will just try to parse // whatever is in buffer already. This may occur in circumstances, for // example when flush() is called. if (data) { // Try to decode the data that we received. _this.buffer += _this.decoder.decode(data, { stream: true }); } function collectNextLine() { let buffer = _this.buffer; let pos = 0; buffer = fixLineBreaks(buffer); while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { ++pos; } const line = buffer.slice(0, pos); // Advance the buffer early in case we fail below. if (buffer[pos] === '\r') { ++pos; } if (buffer[pos] === '\n') { ++pos; } _this.buffer = buffer.slice(pos); return line; } // 3.2 WebVTT metadata header syntax function parseHeader(input) { parseOptions(input, function (k, v) { // switch (k) { // case 'region': // 3.3 WebVTT region metadata header syntax // console.log('parse region', v); // parseRegion(v); // break; // } }, /:/); } // 5.1 WebVTT file parsing. try { let line = ''; if (_this.state === 'INITIAL') { // We can't start parsing until we have the first line. if (!/\r\n|\n/.test(_this.buffer)) { return this; } line = collectNextLine(); // strip of UTF-8 BOM if any // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 const m = line.match(/^()?WEBVTT([ \t].*)?$/); if (!(m != null && m[0])) { throw new Error('Malformed WebVTT signature.'); } _this.state = 'HEADER'; } let alreadyCollectedLine = false; while (_this.buffer) { // We can't parse a line until we have the full line. if (!/\r\n|\n/.test(_this.buffer)) { return this; } if (!alreadyCollectedLine) { line = collectNextLine(); } else { alreadyCollectedLine = false; } switch (_this.state) { case 'HEADER': // 13-18 - Allow a header (metadata) under the WEBVTT line. if (/:/.test(line)) { parseHeader(line); } else if (!line) { // An empty line terminates the header and starts the body (cues). _this.state = 'ID'; } continue; case 'NOTE': // Ignore NOTE blocks. if (!line) { _this.state = 'ID'; } continue; case 'ID': // Check for the start of NOTE blocks. if (/^NOTE($|[ \t])/.test(line)) { _this.state = 'NOTE'; break; } // 19-29 - Allow any number of line terminators, then initialize new cue values. if (!line) { continue; } _this.cue = new VTTCue(0, 0, ''); _this.state = 'CUE'; // 30-39 - Check if self line contains an optional identifier or timing data. if (line.indexOf('-->') === -1) { _this.cue.id = line; continue; } // Process line as start of a cue. /* falls through */ case 'CUE': // 40 - Collect cue timings and settings. if (!_this.cue) { _this.state = 'BADCUE'; continue; } try { parseCue(line, _this.cue, _this.regionList); } catch (e) { // In case of an error ignore rest of the cue. _this.cue = null; _this.state = 'BADCUE'; continue; } _this.state = 'CUETEXT'; continue; case 'CUETEXT': { const hasSubstring = line.indexOf('-->') !== -1; // 34 - If we have an empty line then report the cue. // 35 - If we have the special substring '-->' then report the cue, // but do not collect the line as we need to process the current // one as a new cue. if (!line || hasSubstring && (alreadyCollectedLine = true)) { // We are done parsing self cue. if (_this.oncue && _this.cue) { _this.oncue(_this.cue); } _this.cue = null; _this.state = 'ID'; continue; } if (_this.cue === null) { continue; } if (_this.cue.text) { _this.cue.text += '\n'; } _this.cue.text += line; } continue; case 'BADCUE': // 54-62 - Collect and discard the remaining cue. if (!line) { _this.state = 'ID'; } } } } catch (e) { // If we are currently parsing a cue, report what we have. if (_this.state === 'CUETEXT' && _this.cue && _this.oncue) { _this.oncue(_this.cue); } _this.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise // another exception occurred so enter BADCUE state. _this.state = _this.state === 'INITIAL' ? 'BADWEBVTT' : 'BADCUE'; } return this; } flush() { const _this = this; try { // Finish decoding the stream. // _this.buffer += _this.decoder.decode(); // Synthesize the end of the current cue or region. if (_this.cue || _this.state === 'HEADER') { _this.buffer += '\n\n'; _this.parse(); } // If we've flushed, parsed, and we're still on the INITIAL state then // that means we don't have enough of the stream to parse the first // line. if (_this.state === 'INITIAL' || _this.state === 'BADWEBVTT') { throw new Error('Malformed WebVTT signature.'); } } catch (e) { if (_this.onparsingerror) { _this.onparsingerror(e); } } if (_this.onflush) { _this.onflush(); } return this; } } const LINEBREAKS = /\r\n|\n\r|\n|\r/g; // String.prototype.startsWith is not supported in IE11 const startsWith = function startsWith(inputString, searchString, position = 0) { return inputString.slice(position, position + searchString.length) === searchString; }; const cueString2millis = function cueString2millis(timeString) { let ts = parseInt(timeString.slice(-3)); const secs = parseInt(timeString.slice(-6, -4)); const mins = parseInt(timeString.slice(-9, -7)); const hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))) : 0; if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) { throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`); } ts += 1000 * secs; ts += 60 * 1000 * mins; ts += 60 * 60 * 1000 * hours; return ts; }; // From https://github.com/darkskyapp/string-hash const hash = function hash(text) { let _hash = 5381; let i = text.length; while (i) { _hash = _hash * 33 ^ text.charCodeAt(--i); } return (_hash >>> 0).toString(); }; // Create a unique hash id for a cue based on start/end times and text. // This helps timeline-controller to avoid showing repeated captions. function generateCueId(startTime, endTime, text) { return hash(startTime.toString()) + hash(endTime.toString()) + hash(text); } const calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) { let currCC = vttCCs[cc]; let prevCC = vttCCs[currCC.prevCC]; // This is the first discontinuity or cues have been processed since the last discontinuity // Offset = current discontinuity time if (!prevCC || !prevCC.new && currCC.new) { vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start; currCC.new = false; return; } // There have been discontinuities since cues were last parsed. // Offset = time elapsed while ((_prevCC = prevCC) != null && _prevCC.new) { var _prevCC; vttCCs.ccOffset += currCC.start - prevCC.start; currCC.new = false; currCC = prevCC; prevCC = vttCCs[currCC.prevCC]; } vttCCs.presentationOffset = presentationTime; }; function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) { const parser = new VTTParser(); // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character. // Uint8Array.prototype.reduce is not implemented in IE11 const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\n').split('\n'); const cues = []; const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0; let cueTime = '00:00.000'; let timestampMapMPEGTS = 0; let timestampMapLOCAL = 0; let parsingError; let inHeader = true; parser.oncue = function (cue) { // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline. const currCC = vttCCs[cc]; let cueOffset = vttCCs.ccOffset; // Calculate subtitle PTS offset const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 90000; // Update offsets for new discontinuities if (currCC != null && currCC.new) { if (timestampMapLOCAL !== undefined) { // When local time is provided, offset = discontinuity start time - local time cueOffset = vttCCs.ccOffset = currCC.start; } else { calculateOffset(vttCCs, cc, webVttMpegTsMapOffset); } } if (webVttMpegTsMapOffset) { if (!initPTS) { parsingError = new Error('Missing initPTS for VTT MPEGTS'); return; } // If we have MPEGTS, offset = presentation time + discontinuity offset cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset; } const duration = cue.endTime - cue.startTime; const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000; cue.startTime = Math.max(startTime, 0); cue.endTime = Math.max(startTime + duration, 0); //trim trailing webvtt block whitespaces const text = cue.text.trim(); // Fix encoding of special characters cue.text = decodeURIComponent(encodeURIComponent(text)); // If the cue was not assigned an id from the VTT file (line above the content), create one. if (!cue.id) { cue.id = generateCueId(cue.startTime, cue.endTime, text); } if (cue.endTime > 0) { cues.push(cue); } }; parser.onparsingerror = function (error) { parsingError = error; }; parser.onflush = function () { if (parsingError) { errorCallBack(parsingError); return; } callBack(cues); }; // Go through contents line by line. vttLines.forEach(line => { if (inHeader) { // Look for X-TIMESTAMP-MAP in header. if (startsWith(line, 'X-TIMESTAMP-MAP=')) { // Once found, no more are allowed anyway, so stop searching. inHeader = false; // Extract LOCAL and MPEGTS. line.slice(16).split(',').forEach(timestamp => { if (startsWith(timestamp, 'LOCAL:')) { cueTime = timestamp.slice(6); } else if (startsWith(timestamp, 'MPEGTS:')) { timestampMapMPEGTS = parseInt(timestamp.slice(7)); } }); try { // Convert cue time to seconds timestampMapLOCAL = cueString2millis(cueTime) / 1000; } catch (error) { parsingError = error; } // Return without parsing X-TIMESTAMP-MAP line. return; } else if (line === '') { inHeader = false; } } // Parse line by default. parser.parse(line + '\n'); }); parser.flush(); } const IMSC1_CODEC = 'stpp.ttml.im1t'; // Time format: h:m:s:frames(.subframes) const HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/; // Time format: hours, minutes, seconds, milliseconds, frames, ticks const TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/; const textAlignToLineAlign = { left: 'start', center: 'center', right: 'end', start: 'start', end: 'end' }; function parseIMSC1(payload, initPTS, callBack, errorCallBack) { const results = findBox(new Uint8Array(payload), ['mdat']); if (results.length === 0) { errorCallBack(new Error('Could not parse IMSC1 mdat')); return; } const ttmlList = results.map(mdat => utf8ArrayToStr(mdat)); const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale); try { ttmlList.forEach(ttml => callBack(parseTTML(ttml, syncTime))); } catch (error) { errorCallBack(error); } } function parseTTML(ttml, syncTime) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(ttml, 'text/xml'); const tt = xmlDoc.getElementsByTagName('tt')[0]; if (!tt) { throw new Error('Invalid ttml'); } const defaultRateInfo = { frameRate: 30, subFrameRate: 1, frameRateMultiplier: 0, tickRate: 0 }; const rateInfo = Object.keys(defaultRateInfo).reduce((result, key) => { result[key] = tt.getAttribute(`ttp:${key}`) || defaultRateInfo[key]; return result; }, {}); const trim = tt.getAttribute('xml:space') !== 'preserve'; const styleElements = collectionToDictionary(getElementCollection(tt, 'styling', 'style')); const regionElements = collectionToDictionary(getElementCollection(tt, 'layout', 'region')); const cueElements = getElementCollection(tt, 'body', '[begin]'); return [].map.call(cueElements, cueElement => { const cueText = getTextContent(cueElement, trim); if (!cueText || !cueElement.hasAttribute('begin')) { return null; } const startTime = parseTtmlTime(cueElement.getAttribute('begin'), rateInfo); const duration = parseTtmlTime(cueElement.getAttribute('dur'), rateInfo); let endTime = parseTtmlTime(cueElement.getAttribute('end'), rateInfo); if (startTime === null) { throw timestampParsingError(cueElement); } if (endTime === null) { if (duration === null) { throw timestampParsingError(cueElement); } endTime = startTime + duration; } const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText); cue.id = generateCueId(cue.startTime, cue.endTime, cue.text); const region = regionElements[cueElement.getAttribute('region')]; const style = styleElements[cueElement.getAttribute('style')]; // Apply styles to cue const styles = getTtmlStyles(region, style, styleElements); const { textAlign } = styles; if (textAlign) { // cue.positionAlign not settable in FF~2016 const lineAlign = textAlignToLineAlign[textAlign]; if (lineAlign) { cue.lineAlign = lineAlign; } cue.align = textAlign; } _extends(cue, styles); return cue; }).filter(cue => cue !== null); } function getElementCollection(fromElement, parentName, childName) { const parent = fromElement.getElementsByTagName(parentName)[0]; if (parent) { return [].slice.call(parent.querySelectorAll(childName)); } return []; } function collectionToDictionary(elementsWithId) { return elementsWithId.reduce((dict, element) => { const id = element.getAttribute('xml:id'); if (id) { dict[id] = element; } return dict; }, {}); } function getTextContent(element, trim) { return [].slice.call(element.childNodes).reduce((str, node, i) => { var _node$childNodes; if (node.nodeName === 'br' && i) { return str + '\n'; } if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) { return getTextContent(node, trim); } else if (trim) { return str + node.textContent.trim().replace(/\s+/g, ' '); } return str + node.textContent; }, ''); } function getTtmlStyles(region, style, styleElements) { const ttsNs = 'http://www.w3.org/ns/ttml#styling'; let regionStyle = null; const styleAttributes = ['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily' // 'fontWeight', // 'lineHeight', // 'wrapOption', // 'fontStyle', // 'direction', // 'writingMode' ]; const regionStyleName = region != null && region.hasAttribute('style') ? region.getAttribute('style') : null; if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) { regionStyle = styleElements[regionStyleName]; } return styleAttributes.reduce((styles, name) => { const value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name); if (value) { styles[name] = value; } return styles; }, {}); } function getAttributeNS(element, ns, name) { if (!element) { return null; } return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null; } function timestampParsingError(node) { return new Error(`Could not parse ttml timestamp ${node}`); } function parseTtmlTime(timeAttributeValue, rateInfo) { if (!timeAttributeValue) { return null; } let seconds = parseTimeStamp(timeAttributeValue); if (seconds === null) { if (HMSF_REGEX.test(timeAttributeValue)) { seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo); } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) { seconds = parseTimeUnits(timeAttributeValue, rateInfo); } } return seconds; } function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) { const m = HMSF_REGEX.exec(timeAttributeValue); const frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate; return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate; } function parseTimeUnits(timeAttributeValue, rateInfo) { const m = TIME_UNIT_REGEX.exec(timeAttributeValue); const value = Number(m[1]); const unit = m[2]; switch (unit) { case 'h': return value * 3600; case 'm': return value * 60; case 'ms': return value * 1000; case 'f': return value / rateInfo.frameRate; case 't': return value / rateInfo.tickRate; } return value; } class TimelineController { constructor(hls) { this.hls = void 0; this.media = null; this.config = void 0; this.enabled = true; this.Cues = void 0; this.textTracks = []; this.tracks = []; this.initPTS = []; this.unparsedVttFrags = []; this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; this.cea608Parser1 = void 0; this.cea608Parser2 = void 0; this.lastSn = -1; this.lastPartIndex = -1; this.prevCC = -1; this.vttCCs = newVTTCCs(); this.captionsProperties = void 0; this.hls = hls; this.config = hls.config; this.Cues = hls.config.cueHandler; this.captionsProperties = { textTrack1: { label: this.config.captionsTextTrack1Label, languageCode: this.config.captionsTextTrack1LanguageCode }, textTrack2: { label: this.config.captionsTextTrack2Label, languageCode: this.config.captionsTextTrack2LanguageCode }, textTrack3: { label: this.config.captionsTextTrack3Label, languageCode: this.config.captionsTextTrack3LanguageCode }, textTrack4: { label: this.config.captionsTextTrack4Label, languageCode: this.config.captionsTextTrack4LanguageCode } }; if (this.config.enableCEA708Captions) { const channel1 = new OutputFilter(this, 'textTrack1'); const channel2 = new OutputFilter(this, 'textTrack2'); const channel3 = new OutputFilter(this, 'textTrack3'); const channel4 = new OutputFilter(this, 'textTrack4'); this.cea608Parser1 = new Cea608Parser(1, channel1, channel2); this.cea608Parser2 = new Cea608Parser(3, channel3, channel4); } hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.on(Events.FRAG_LOADING, this.onFragLoading, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); } destroy() { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.off(Events.FRAG_LOADING, this.onFragLoading, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); // @ts-ignore this.hls = this.config = this.cea608Parser1 = this.cea608Parser2 = null; } addCues(trackName, startTime, endTime, screen, cueRanges) { // skip cues which overlap more than 50% with previously parsed time ranges let merged = false; for (let i = cueRanges.length; i--;) { const cueRange = cueRanges[i]; const overlap = intersection(cueRange[0], cueRange[1], startTime, endTime); if (overlap >= 0) { cueRange[0] = Math.min(cueRange[0], startTime); cueRange[1] = Math.max(cueRange[1], endTime); merged = true; if (overlap / (endTime - startTime) > 0.5) { return; } } } if (!merged) { cueRanges.push([startTime, endTime]); } if (this.config.renderTextTracksNatively) { const track = this.captionsTracks[trackName]; this.Cues.newCue(track, startTime, endTime, screen); } else { const cues = this.Cues.newCue(null, startTime, endTime, screen); this.hls.trigger(Events.CUES_PARSED, { type: 'captions', cues, track: trackName }); } } // Triggered when an initial PTS is found; used for synchronisation of WebVTT. onInitPtsFound(event, { frag, id, initPTS, timescale }) { const { unparsedVttFrags } = this; if (id === 'main') { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; } // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded. // Parse any unparsed fragments upon receiving the initial PTS. if (unparsedVttFrags.length) { this.unparsedVttFrags = []; unparsedVttFrags.forEach(frag => { this.onFragLoaded(Events.FRAG_LOADED, frag); }); } } getExistingTrack(trackName) { const { media } = this; if (media) { for (let i = 0; i < media.textTracks.length; i++) { const textTrack = media.textTracks[i]; if (textTrack[trackName]) { return textTrack; } } } return null; } createCaptionsTrack(trackName) { if (this.config.renderTextTracksNatively) { this.createNativeTrack(trackName); } else { this.createNonNativeTrack(trackName); } } createNativeTrack(trackName) { if (this.captionsTracks[trackName]) { return; } const { captionsProperties, captionsTracks, media } = this; const { label, languageCode } = captionsProperties[trackName]; // Enable reuse of existing text track. const existingTrack = this.getExistingTrack(trackName); if (!existingTrack) { const textTrack = this.createTextTrack('captions', label, languageCode); if (textTrack) { // Set a special property on the track so we know it's managed by Hls.js textTrack[trackName] = true; captionsTracks[trackName] = textTrack; } } else { captionsTracks[trackName] = existingTrack; clearCurrentCues(captionsTracks[trackName]); sendAddTrackEvent(captionsTracks[trackName], media); } } createNonNativeTrack(trackName) { if (this.nonNativeCaptionsTracks[trackName]) { return; } // Create a list of a single track for the provider to consume const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } const label = trackProperties.label; const track = { _id: trackName, label, kind: 'captions', default: trackProperties.media ? !!trackProperties.media.default : false, closedCaptions: trackProperties.media }; this.nonNativeCaptionsTracks[trackName] = track; this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { tracks: [track] }); } createTextTrack(kind, label, lang) { const media = this.media; if (!media) { return; } return media.addTextTrack(kind, label, lang); } onMediaAttaching(event, data) { this.media = data.media; this._cleanTracks(); } onMediaDetaching() { const { captionsTracks } = this; Object.keys(captionsTracks).forEach(trackName => { clearCurrentCues(captionsTracks[trackName]); delete captionsTracks[trackName]; }); this.nonNativeCaptionsTracks = {}; } onManifestLoading() { this.lastSn = -1; // Detect discontinuity in fragment parsing this.lastPartIndex = -1; this.prevCC = -1; this.vttCCs = newVTTCCs(); // Detect discontinuity in subtitle manifests this._cleanTracks(); this.tracks = []; this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; this.textTracks = []; this.unparsedVttFrags = []; this.initPTS = []; if (this.cea608Parser1 && this.cea608Parser2) { this.cea608Parser1.reset(); this.cea608Parser2.reset(); } } _cleanTracks() { // clear outdated subtitles const { media } = this; if (!media) { return; } const textTracks = media.textTracks; if (textTracks) { for (let i = 0; i < textTracks.length; i++) { clearCurrentCues(textTracks[i]); } } } onSubtitleTracksUpdated(event, data) { const tracks = data.subtitleTracks || []; const hasIMSC1 = tracks.some(track => track.textCodec === IMSC1_CODEC); if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) { const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks); if (listIsIdentical) { this.tracks = tracks; return; } this.textTracks = []; this.tracks = tracks; if (this.config.renderTextTracksNatively) { const inUseTracks = this.media ? this.media.textTracks : null; this.tracks.forEach((track, index) => { let textTrack; if (inUseTracks && index < inUseTracks.length) { let inUseTrack = null; for (let i = 0; i < inUseTracks.length; i++) { if (canReuseVttTextTrack(inUseTracks[i], track)) { inUseTrack = inUseTracks[i]; break; } } // Reuse tracks with the same label, but do not reuse 608/708 tracks if (inUseTrack) { textTrack = inUseTrack; } } if (textTrack) { clearCurrentCues(textTrack); } else { const textTrackKind = this._captionsOrSubtitlesFromCharacteristics(track); textTrack = this.createTextTrack(textTrackKind, track.name, track.lang); if (textTrack) { textTrack.mode = 'disabled'; } } if (textTrack) { textTrack.groupId = track.groupId; this.textTracks.push(textTrack); } }); } else if (this.tracks.length) { // Create a list of tracks for the provider to consume const tracksList = this.tracks.map(track => { return { label: track.name, kind: track.type.toLowerCase(), default: track.default, subtitleTrack: track }; }); this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { tracks: tracksList }); } } } _captionsOrSubtitlesFromCharacteristics(track) { if (track.attrs.CHARACTERISTICS) { const transcribesSpokenDialog = /transcribes-spoken-dialog/gi.test(track.attrs.CHARACTERISTICS); const describesMusicAndSound = /describes-music-and-sound/gi.test(track.attrs.CHARACTERISTICS); if (transcribesSpokenDialog && describesMusicAndSound) { return 'captions'; } } return 'subtitles'; } onManifestLoaded(event, data) { if (this.config.enableCEA708Captions && data.captions) { data.captions.forEach(captionsTrack => { const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId); if (!instreamIdMatch) { return; } const trackName = `textTrack${instreamIdMatch[1]}`; const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } trackProperties.label = captionsTrack.name; if (captionsTrack.lang) { // optional attribute trackProperties.languageCode = captionsTrack.lang; } trackProperties.media = captionsTrack; }); } } closedCaptionsForLevel(frag) { const level = this.hls.levels[frag.level]; return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS']; } onFragLoading(event, data) { const { cea608Parser1, cea608Parser2, lastSn, lastPartIndex } = this; if (!this.enabled || !(cea608Parser1 && cea608Parser2)) { return; } // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack if (data.frag.type === PlaylistLevelType.MAIN) { var _data$part$index, _data$part; const sn = data.frag.sn; const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1; if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1)) { cea608Parser1.reset(); cea608Parser2.reset(); } this.lastSn = sn; this.lastPartIndex = partIndex; } } onFragLoaded(event, data) { const { frag, payload } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { // If fragment is subtitle type, parse as WebVTT. if (payload.byteLength) { const decryptData = frag.decryptdata; // fragment after decryption has a stats object const decrypted = ('stats' in data); // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait. if (decryptData == null || !decryptData.encrypted || decrypted) { const trackPlaylistMedia = this.tracks[frag.level]; const vttCCs = this.vttCCs; if (!vttCCs[frag.cc]) { vttCCs[frag.cc] = { start: frag.start, prevCC: this.prevCC, new: true }; this.prevCC = frag.cc; } if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) { this._parseIMSC1(frag, payload); } else { this._parseVTTs(data); } } } else { // In case there is no payload, finish unsuccessfully. this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag, error: new Error('Empty subtitle payload') }); } } } _parseIMSC1(frag, payload) { const hls = this.hls; parseIMSC1(payload, this.initPTS[frag.cc], cues => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag: frag }); }, error => { logger.log(`Failed to parse IMSC1: ${error}`); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag: frag, error }); }); } _parseVTTs(data) { var _frag$initSegment; const { frag, payload } = data; // We need an initial synchronisation PTS. Store fragments as long as none has arrived const { initPTS, unparsedVttFrags } = this; const maxAvCC = initPTS.length - 1; if (!initPTS[frag.cc] && maxAvCC === -1) { unparsedVttFrags.push(data); return; } const hls = this.hls; // Parse the WebVTT file contents. const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)) : payload; parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, cues => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag: frag }); }, error => { const missingInitPTS = error.message === 'Missing initPTS for VTT MPEGTS'; if (missingInitPTS) { unparsedVttFrags.push(data); } else { this._fallbackToIMSC1(frag, payload); } // Something went wrong while parsing. Trigger event with success false. logger.log(`Failed to parse VTT cue: ${error}`); if (missingInitPTS && maxAvCC > frag.cc) { return; } hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag: frag, error }); }); } _fallbackToIMSC1(frag, payload) { // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result const trackPlaylistMedia = this.tracks[frag.level]; if (!trackPlaylistMedia.textCodec) { parseIMSC1(payload, this.initPTS[frag.cc], () => { trackPlaylistMedia.textCodec = IMSC1_CODEC; this._parseIMSC1(frag, payload); }, () => { trackPlaylistMedia.textCodec = 'wvtt'; }); } } _appendCues(cues, fragLevel) { const hls = this.hls; if (this.config.renderTextTracksNatively) { const textTrack = this.textTracks[fragLevel]; // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled" // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null // and trying to access getCueById method of cues will throw an exception // Because we check if the mode is disabled, we can force check `cues` below. They can't be null. if (!textTrack || textTrack.mode === 'disabled') { return; } cues.forEach(cue => addCueToTrack(textTrack, cue)); } else { const currentTrack = this.tracks[fragLevel]; if (!currentTrack) { return; } const track = currentTrack.default ? 'default' : 'subtitles' + fragLevel; hls.trigger(Events.CUES_PARSED, { type: 'subtitles', cues, track }); } } onFragDecrypted(event, data) { const { frag } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { this.onFragLoaded(Events.FRAG_LOADED, data); } } onSubtitleTracksCleared() { this.tracks = []; this.captionsTracks = {}; } onFragParsingUserdata(event, data) { const { cea608Parser1, cea608Parser2 } = this; if (!this.enabled || !(cea608Parser1 && cea608Parser2)) { return; } const { frag, samples } = data; if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === 'NONE') { return; } // If the event contains captions (found in the bytes property), push all bytes into the parser immediately // It will create the proper timestamps based on the PTS value for (let i = 0; i < samples.length; i++) { const ccBytes = samples[i].bytes; if (ccBytes) { const ccdatas = this.extractCea608Data(ccBytes); cea608Parser1.addData(samples[i].pts, ccdatas[0]); cea608Parser2.addData(samples[i].pts, ccdatas[1]); } } } onBufferFlushing(event, { startOffset, endOffset, endOffsetSubtitles, type }) { const { media } = this; if (!media || media.currentTime < endOffset) { return; } // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed // Forward cues are never removed because we can loose streamed 608 content from recent fragments if (!type || type === 'video') { const { captionsTracks } = this; Object.keys(captionsTracks).forEach(trackName => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset)); } if (this.config.renderTextTracksNatively) { // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed if (startOffset === 0 && endOffsetSubtitles !== undefined) { const { textTracks } = this; Object.keys(textTracks).forEach(trackName => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles)); } } } extractCea608Data(byteArray) { const actualCCBytes = [[], []]; const count = byteArray[0] & 0x1f; let position = 2; for (let j = 0; j < count; j++) { const tmpByte = byteArray[position++]; const ccbyte1 = 0x7f & byteArray[position++]; const ccbyte2 = 0x7f & byteArray[position++]; if (ccbyte1 === 0 && ccbyte2 === 0) { continue; } const ccValid = (0x04 & tmpByte) !== 0; // Support all four channels if (ccValid) { const ccType = 0x03 & tmpByte; if (0x00 /* CEA608 field1*/ === ccType || 0x01 /* CEA608 field2*/ === ccType) { // Exclude CEA708 CC data. actualCCBytes[ccType].push(ccbyte1); actualCCBytes[ccType].push(ccbyte2); } } } return actualCCBytes; } } function canReuseVttTextTrack(inUseTrack, manifestTrack) { return !!inUseTrack && inUseTrack.label === manifestTrack.name && !(inUseTrack.textTrack1 || inUseTrack.textTrack2); } function intersection(x1, x2, y1, y2) { return Math.min(x2, y2) - Math.max(x1, y1); } function newVTTCCs() { return { ccOffset: 0, presentationOffset: 0, 0: { start: 0, prevCC: -1, new: true } }; } /* * cap stream level to media size dimension controller */ class CapLevelController { constructor(hls) { this.hls = void 0; this.autoLevelCapping = void 0; this.firstLevel = void 0; this.media = void 0; this.restrictedLevels = void 0; this.timer = void 0; this.clientRect = void 0; this.streamController = void 0; this.hls = hls; this.autoLevelCapping = Number.POSITIVE_INFINITY; this.firstLevel = -1; this.media = null; this.restrictedLevels = []; this.timer = undefined; this.clientRect = null; this.registerListeners(); } setStreamController(streamController) { this.streamController = streamController; } destroy() { this.unregisterListener(); if (this.hls.config.capLevelToPlayerSize) { this.stopCapping(); } this.media = null; this.clientRect = null; // @ts-ignore this.hls = this.streamController = null; } registerListeners() { const { hls } = this; hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } unregisterListener() { const { hls } = this; hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } onFpsDropLevelCapping(event, data) { // Don't add a restricted level more than once const level = this.hls.levels[data.droppedLevel]; if (this.isLevelAllowed(level)) { this.restrictedLevels.push({ bitrate: level.bitrate, height: level.height, width: level.width }); } } onMediaAttaching(event, data) { this.media = data.media instanceof HTMLVideoElement ? data.media : null; this.clientRect = null; } onManifestParsed(event, data) { const hls = this.hls; this.restrictedLevels = []; this.firstLevel = data.firstLevel; if (hls.config.capLevelToPlayerSize && data.video) { // Start capping immediately if the manifest has signaled video codecs this.startCapping(); } } // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted // to the first level onBufferCodecs(event, data) { const hls = this.hls; if (hls.config.capLevelToPlayerSize && data.video) { // If the manifest did not signal a video codec capping has been deferred until we're certain video is present this.startCapping(); } } onMediaDetaching() { this.stopCapping(); } detectPlayerSize() { if (this.media && this.mediaHeight > 0 && this.mediaWidth > 0) { const levels = this.hls.levels; if (levels.length) { const hls = this.hls; hls.autoLevelCapping = this.getMaxLevel(levels.length - 1); if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) { // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch // usually happen when the user go to the fullscreen mode. this.streamController.nextLevelSwitch(); } this.autoLevelCapping = hls.autoLevelCapping; } } } /* * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled) */ getMaxLevel(capLevelIndex) { const levels = this.hls.levels; if (!levels.length) { return -1; } const validLevels = levels.filter((level, index) => this.isLevelAllowed(level) && index <= capLevelIndex); this.clientRect = null; return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight); } startCapping() { if (this.timer) { // Don't reset capping if started twice; this can happen if the manifest signals a video codec return; } this.autoLevelCapping = Number.POSITIVE_INFINITY; this.hls.firstLevel = this.getMaxLevel(this.firstLevel); self.clearInterval(this.timer); this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000); this.detectPlayerSize(); } stopCapping() { this.restrictedLevels = []; this.firstLevel = -1; this.autoLevelCapping = Number.POSITIVE_INFINITY; if (this.timer) { self.clearInterval(this.timer); this.timer = undefined; } } getDimensions() { if (this.clientRect) { return this.clientRect; } const media = this.media; const boundsRect = { width: 0, height: 0 }; if (media) { const clientRect = media.getBoundingClientRect(); boundsRect.width = clientRect.width; boundsRect.height = clientRect.height; if (!boundsRect.width && !boundsRect.height) { // When the media element has no width or height (equivalent to not being in the DOM), // then use its width and height attributes (media.width, media.height) boundsRect.width = clientRect.right - clientRect.left || media.width || 0; boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0; } } this.clientRect = boundsRect; return boundsRect; } get mediaWidth() { return this.getDimensions().width * this.contentScaleFactor; } get mediaHeight() { return this.getDimensions().height * this.contentScaleFactor; } get contentScaleFactor() { let pixelRatio = 1; if (!this.hls.config.ignoreDevicePixelRatio) { try { pixelRatio = self.devicePixelRatio; } catch (e) { /* no-op */ } } return pixelRatio; } isLevelAllowed(level) { const restrictedLevels = this.restrictedLevels; return !restrictedLevels.some(restrictedLevel => { return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height; }); } static getMaxLevelByMediaSize(levels, width, height) { if (!(levels != null && levels.length)) { return -1; } // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next // to determine whether we've chosen the greatest bandwidth for the media's dimensions const atGreatestBandwidth = (curLevel, nextLevel) => { if (!nextLevel) { return true; } return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height; }; // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to // the max level let maxLevelIndex = levels.length - 1; for (let i = 0; i < levels.length; i += 1) { const level = levels[i]; if ((level.width >= width || level.height >= height) && atGreatestBandwidth(level, levels[i + 1])) { maxLevelIndex = i; break; } } return maxLevelIndex; } } class FPSController { // stream controller must be provided as a dependency! constructor(hls) { this.hls = void 0; this.isVideoPlaybackQualityAvailable = false; this.timer = void 0; this.media = null; this.lastTime = void 0; this.lastDroppedFrames = 0; this.lastDecodedFrames = 0; this.streamController = void 0; this.hls = hls; this.registerListeners(); } setStreamController(streamController) { this.streamController = streamController; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); } destroy() { if (this.timer) { clearInterval(this.timer); } this.unregisterListeners(); this.isVideoPlaybackQualityAvailable = false; this.media = null; } onMediaAttaching(event, data) { const config = this.hls.config; if (config.capLevelOnFPSDrop) { const media = data.media instanceof self.HTMLVideoElement ? data.media : null; this.media = media; if (media && typeof media.getVideoPlaybackQuality === 'function') { this.isVideoPlaybackQualityAvailable = true; } self.clearInterval(this.timer); this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod); } } checkFPS(video, decodedFrames, droppedFrames) { const currentTime = performance.now(); if (decodedFrames) { if (this.lastTime) { const currentPeriod = currentTime - this.lastTime; const currentDropped = droppedFrames - this.lastDroppedFrames; const currentDecoded = decodedFrames - this.lastDecodedFrames; const droppedFPS = 1000 * currentDropped / currentPeriod; const hls = this.hls; hls.trigger(Events.FPS_DROP, { currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames }); if (droppedFPS > 0) { // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod)); if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) { let currentLevel = hls.currentLevel; logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel); if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) { currentLevel = currentLevel - 1; hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, { level: currentLevel, droppedLevel: hls.currentLevel }); hls.autoLevelCapping = currentLevel; this.streamController.nextLevelSwitch(); } } } } this.lastTime = currentTime; this.lastDroppedFrames = droppedFrames; this.lastDecodedFrames = decodedFrames; } } checkFPSInterval() { const video = this.media; if (video) { if (this.isVideoPlaybackQualityAvailable) { const videoPlaybackQuality = video.getVideoPlaybackQuality(); this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames); } else { // HTMLVideoElement doesn't include the webkit types this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount); } } } } const LOGGER_PREFIX = '[eme]'; /** * Controller to deal with encrypted media extensions (EME) * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API * * @class * @constructor */ class EMEController { constructor(hls) { this.hls = void 0; this.config = void 0; this.media = null; this.keyFormatPromise = null; this.keySystemAccessPromises = {}; this._requestLicenseFailureCount = 0; this.mediaKeySessions = []; this.keyIdToKeySessionPromise = {}; this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : []; this.onMediaEncrypted = this._onMediaEncrypted.bind(this); this.onWaitingForKey = this._onWaitingForKey.bind(this); this.debug = logger.debug.bind(logger, LOGGER_PREFIX); this.log = logger.log.bind(logger, LOGGER_PREFIX); this.warn = logger.warn.bind(logger, LOGGER_PREFIX); this.error = logger.error.bind(logger, LOGGER_PREFIX); this.hls = hls; this.config = hls.config; this.registerListeners(); } destroy() { this.unregisterListeners(); this.onMediaDetached(); // Remove any references that could be held in config options or callbacks const config = this.config; config.requestMediaKeySystemAccessFunc = null; config.licenseXhrSetup = config.licenseResponseCallback = undefined; config.drmSystems = config.drmSystemOptions = {}; // @ts-ignore this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null; // @ts-ignore this.config = null; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } getLicenseServerUrl(keySystem) { const { drmSystems, widevineLicenseUrl } = this.config; const keySystemConfiguration = drmSystems[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.licenseUrl; } // For backward compatibility if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) { return widevineLicenseUrl; } throw new Error(`no license server URL configured for key-system "${keySystem}"`); } getServerCertificateUrl(keySystem) { const { drmSystems } = this.config; const keySystemConfiguration = drmSystems[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.serverCertificateUrl; } else { this.log(`No Server Certificate in config.drmSystems["${keySystem}"]`); } } attemptKeySystemAccess(keySystemsToAttempt) { const levels = this.hls.levels; const uniqueCodec = (value, i, a) => !!value && a.indexOf(value) === i; const audioCodecs = levels.map(level => level.audioCodec).filter(uniqueCodec); const videoCodecs = levels.map(level => level.videoCodec).filter(uniqueCodec); if (audioCodecs.length + videoCodecs.length === 0) { videoCodecs.push('avc1.42e01e'); } return new Promise((resolve, reject) => { const attempt = keySystems => { const keySystem = keySystems.shift(); this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(mediaKeys => resolve({ keySystem, mediaKeys })).catch(error => { if (keySystems.length) { attempt(keySystems); } else if (error instanceof EMEKeyError) { reject(error); } else { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_ACCESS, error, fatal: true }, error.message)); } }); }; attempt(keySystemsToAttempt); }); } requestMediaKeySystemAccess(keySystem, supportedConfigurations) { const { requestMediaKeySystemAccessFunc } = this.config; if (!(typeof requestMediaKeySystemAccessFunc === 'function')) { let errMessage = `Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`; if (requestMediaKeySystemAccess === null && self.location.protocol === 'http:') { errMessage = `navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`; } return Promise.reject(new Error(errMessage)); } return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations); } getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) { // This can throw, but is caught in event handler callpath const mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions); const keySystemAccessPromises = this.keySystemAccessPromises[keySystem]; let keySystemAccess = keySystemAccessPromises == null ? void 0 : keySystemAccessPromises.keySystemAccess; if (!keySystemAccess) { this.log(`Requesting encrypted media "${keySystem}" key-system access with config: ${JSON.stringify(mediaKeySystemConfigs)}`); keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs); const _keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = { keySystemAccess }; keySystemAccess.catch(error => { this.log(`Failed to obtain access to key-system "${keySystem}": ${error}`); }); return keySystemAccess.then(mediaKeySystemAccess => { this.log(`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`); const certificateRequest = this.fetchServerCertificate(keySystem); this.log(`Create media-keys for "${keySystem}"`); _keySystemAccessPromises.mediaKeys = mediaKeySystemAccess.createMediaKeys().then(mediaKeys => { this.log(`Media-keys created for "${keySystem}"`); return certificateRequest.then(certificate => { if (certificate) { return this.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate); } return mediaKeys; }); }); _keySystemAccessPromises.mediaKeys.catch(error => { this.error(`Failed to create media-keys for "${keySystem}"}: ${error}`); }); return _keySystemAccessPromises.mediaKeys; }); } return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys); } createMediaKeySessionContext({ decryptdata, keySystem, mediaKeys }) { this.log(`Creating key-system session "${keySystem}" keyId: ${Hex.hexDump(decryptdata.keyId || [])}`); const mediaKeysSession = mediaKeys.createSession(); const mediaKeySessionContext = { decryptdata, keySystem, mediaKeys, mediaKeysSession, keyStatus: 'status-pending' }; this.mediaKeySessions.push(mediaKeySessionContext); return mediaKeySessionContext; } renewKeySession(mediaKeySessionContext) { const decryptdata = mediaKeySessionContext.decryptdata; if (decryptdata.pssh) { const keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext); const keyId = this.getKeyIdString(decryptdata); const scheme = 'cenc'; this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'expired'); } else { this.warn(`Could not renew expired session. Missing pssh initData.`); } this.removeSession(mediaKeySessionContext); } getKeyIdString(decryptdata) { if (!decryptdata) { throw new Error('Could not read keyId of undefined decryptdata'); } if (decryptdata.keyId === null) { throw new Error('keyId is null'); } return Hex.hexDump(decryptdata.keyId); } updateKeySession(mediaKeySessionContext, data) { var _mediaKeySessionConte; const keySession = mediaKeySessionContext.mediaKeysSession; this.log(`Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump(((_mediaKeySessionConte = mediaKeySessionContext.decryptdata) == null ? void 0 : _mediaKeySessionConte.keyId) || [])} } (data length: ${data ? data.byteLength : data})`); return keySession.update(data); } selectKeySystemFormat(frag) { const keyFormats = Object.keys(frag.levelkeys || {}); if (!this.keyFormatPromise) { this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(', ')}`); this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); } return this.keyFormatPromise; } getKeyFormatPromise(keyFormats) { return new Promise((resolve, reject) => { const keySystemsInConfig = getKeySystemsForConfig(this.config); const keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter(value => !!value && keySystemsInConfig.indexOf(value) !== -1); return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({ keySystem }) => { const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem); if (keySystemFormat) { resolve(keySystemFormat); } else { reject(new Error(`Unable to find format for key-system "${keySystem}"`)); } }).catch(reject); }); } loadKey(data) { const decryptdata = data.keyInfo.decryptdata; const keyId = this.getKeyIdString(decryptdata); const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`; this.log(`Starting session for key ${keyDetails}`); let keySessionContextPromise = this.keyIdToKeySessionPromise[keyId]; if (!keySessionContextPromise) { keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] = this.getKeySystemForKeyPromise(decryptdata).then(({ keySystem, mediaKeys }) => { this.throwIfDestroyed(); this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`); return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => { this.throwIfDestroyed(); const keySessionContext = this.createMediaKeySessionContext({ keySystem, mediaKeys, decryptdata }); const scheme = 'cenc'; return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'playlist-key'); }); }); keySessionContextPromise.catch(error => this.handleError(error)); } return keySessionContextPromise; } throwIfDestroyed(message = 'Invalid state') { if (!this.hls) { throw new Error('invalid state'); } } handleError(error) { if (!this.hls) { return; } this.error(error.message); if (error instanceof EMEKeyError) { this.hls.trigger(Events.ERROR, error.data); } else { this.hls.trigger(Events.ERROR, { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_KEYS, error, fatal: true }); } } getKeySystemForKeyPromise(decryptdata) { const keyId = this.getKeyIdString(decryptdata); const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId]; if (!mediaKeySessionContext) { const keySystem = keySystemFormatToKeySystemDomain(decryptdata.keyFormat); const keySystemsToAttempt = keySystem ? [keySystem] : getKeySystemsForConfig(this.config); return this.attemptKeySystemAccess(keySystemsToAttempt); } return mediaKeySessionContext; } getKeySystemSelectionPromise(keySystemsToAttempt) { if (!keySystemsToAttempt.length) { keySystemsToAttempt = getKeySystemsForConfig(this.config); } if (keySystemsToAttempt.length === 0) { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE, fatal: true }, `Missing key-system license configuration options ${JSON.stringify({ drmSystems: this.config.drmSystems })}`); } return this.attemptKeySystemAccess(keySystemsToAttempt); } _onMediaEncrypted(event) { const { initDataType, initData } = event; this.debug(`"${event.type}" event: init data type: "${initDataType}"`); // Ignore event when initData is null if (initData === null) { return; } let keyId; let keySystemDomain; if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) { // Match sinf keyId to playlist skd://keyId= const json = bin2str(new Uint8Array(initData)); try { const sinf = base64Decode(JSON.parse(json).sinf); const tenc = parseSinf(new Uint8Array(sinf)); if (!tenc) { return; } keyId = tenc.subarray(8, 24); keySystemDomain = KeySystems.FAIRPLAY; } catch (error) { this.warn('Failed to parse sinf "encrypted" event message initData'); return; } } else { // Support clear-lead key-session creation (otherwise depend on playlist keys) const psshInfo = parsePssh(initData); if (psshInfo === null) { return; } if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) { keyId = psshInfo.data.subarray(8, 24); } keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId); } if (!keySystemDomain || !keyId) { return; } const keyIdHex = Hex.hexDump(keyId); const { keyIdToKeySessionPromise, mediaKeySessions } = this; let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; for (let i = 0; i < mediaKeySessions.length; i++) { // Match playlist key const keyContext = mediaKeySessions[i]; const decryptdata = keyContext.decryptdata; if (decryptdata.pssh || !decryptdata.keyId) { continue; } const oldKeyIdHex = Hex.hexDump(decryptdata.keyId); if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) { keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; delete keyIdToKeySessionPromise[oldKeyIdHex]; decryptdata.pssh = new Uint8Array(initData); decryptdata.keyId = keyId; keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => { return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match'); }); break; } } if (!keySessionContextPromise) { // Clear-lead key (not encountered in playlist) keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({ keySystem, mediaKeys }) => { var _keySystemToKeySystem; this.throwIfDestroyed(); const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : ''); decryptdata.pssh = new Uint8Array(initData); decryptdata.keyId = keyId; return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => { this.throwIfDestroyed(); const keySessionContext = this.createMediaKeySessionContext({ decryptdata, keySystem, mediaKeys }); return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match'); }); }); } keySessionContextPromise.catch(error => this.handleError(error)); } _onWaitingForKey(event) { this.log(`"${event.type}" event`); } attemptSetMediaKeys(keySystem, mediaKeys) { const queue = this.setMediaKeysQueue.slice(); this.log(`Setting media-keys for "${keySystem}"`); // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations // can be queued for execution for multiple key sessions. const setMediaKeysPromise = Promise.all(queue).then(() => { if (!this.media) { throw new Error('Attempted to set mediaKeys without media element attached'); } return this.media.setMediaKeys(mediaKeys); }); this.setMediaKeysQueue.push(setMediaKeysPromise); return setMediaKeysPromise.then(() => { this.log(`Media-keys set for "${keySystem}"`); queue.push(setMediaKeysPromise); this.setMediaKeysQueue = this.setMediaKeysQueue.filter(p => queue.indexOf(p) === -1); }); } generateRequestWithPreferredKeySession(context, initDataType, initData, reason) { var _this$config$drmSyste, _this$config$drmSyste2; const generateRequestFilter = (_this$config$drmSyste = this.config.drmSystems) == null ? void 0 : (_this$config$drmSyste2 = _this$config$drmSyste[context.keySystem]) == null ? void 0 : _this$config$drmSyste2.generateRequest; if (generateRequestFilter) { try { const mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context); if (!mappedInitData) { throw new Error('Invalid response from configured generateRequest filter'); } initDataType = mappedInitData.initDataType; initData = context.decryptdata.pssh = mappedInitData.initData ? new Uint8Array(mappedInitData.initData) : null; } catch (error) { var _this$hls; this.warn(error.message); if ((_this$hls = this.hls) != null && _this$hls.config.debug) { throw error; } } } if (initData === null) { this.log(`Skipping key-session request for "${reason}" (no initData)`); return Promise.resolve(context); } const keyId = this.getKeyIdString(context.decryptdata); this.log(`Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${initData ? initData.byteLength : null})`); const licenseStatus = new EventEmitter(); context.mediaKeysSession.onmessage = event => { const keySession = context.mediaKeysSession; if (!keySession) { licenseStatus.emit('error', new Error('invalid state')); return; } const { messageType, message } = event; this.log(`"${messageType}" message event for session "${keySession.sessionId}" message size: ${message.byteLength}`); if (messageType === 'license-request' || messageType === 'license-renewal') { this.renewLicense(context, message).catch(error => { this.handleError(error); licenseStatus.emit('error', error); }); } else if (messageType === 'license-release') { if (context.keySystem === KeySystems.FAIRPLAY) { this.updateKeySession(context, strToUtf8array('acknowledged')); this.removeSession(context); } } else { this.warn(`unhandled media key message type "${messageType}"`); } }; context.mediaKeysSession.onkeystatuseschange = event => { const keySession = context.mediaKeysSession; if (!keySession) { licenseStatus.emit('error', new Error('invalid state')); return; } this.onKeyStatusChange(context); const keyStatus = context.keyStatus; licenseStatus.emit('keyStatus', keyStatus); if (keyStatus === 'expired') { this.warn(`${context.keySystem} expired for key ${keyId}`); this.renewKeySession(context); } }; const keyUsablePromise = new Promise((resolve, reject) => { licenseStatus.on('error', reject); licenseStatus.on('keyStatus', keyStatus => { if (keyStatus.startsWith('usable')) { resolve(); } else if (keyStatus === 'output-restricted') { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED, fatal: false }, 'HDCP level output restricted')); } else if (keyStatus === 'internal-error') { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR, fatal: true }, `key status changed to "${keyStatus}"`)); } else if (keyStatus === 'expired') { reject(new Error('key expired while generating request')); } else { this.warn(`unhandled key status change "${keyStatus}"`); } }); }); return context.mediaKeysSession.generateRequest(initDataType, initData).then(() => { var _context$mediaKeysSes; this.log(`Request generated for key-session "${(_context$mediaKeysSes = context.mediaKeysSession) == null ? void 0 : _context$mediaKeysSes.sessionId}" keyId: ${keyId}`); }).catch(error => { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_SESSION, error, fatal: false }, `Error generating key-session request: ${error}`); }).then(() => keyUsablePromise).catch(error => { licenseStatus.removeAllListeners(); this.removeSession(context); throw error; }).then(() => { licenseStatus.removeAllListeners(); return context; }); } onKeyStatusChange(mediaKeySessionContext) { mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status, keyId) => { this.log(`key status change "${status}" for keyStatuses keyId: ${Hex.hexDump('buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId))} session keyId: ${Hex.hexDump(new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []))} uri: ${mediaKeySessionContext.decryptdata.uri}`); mediaKeySessionContext.keyStatus = status; }); } fetchServerCertificate(keySystem) { const config = this.config; const Loader = config.loader; const certLoader = new Loader(config); const url = this.getServerCertificateUrl(keySystem); if (!url) { return Promise.resolve(); } this.log(`Fetching serverCertificate for "${keySystem}"`); return new Promise((resolve, reject) => { const loaderContext = { responseType: 'arraybuffer', url }; const loadPolicy = config.certLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { resolve(response.data); }, onError: (response, contex, networkDetails, stats) => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, fatal: true, networkDetails, response: _objectSpread2({ url: loaderContext.url, data: undefined }, response) }, `"${keySystem}" certificate request failed (${url}). Status: ${response.code} (${response.text})`)); }, onTimeout: (stats, context, networkDetails) => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, fatal: true, networkDetails, response: { url: loaderContext.url, data: undefined } }, `"${keySystem}" certificate request timed out (${url})`)); }, onAbort: (stats, context, networkDetails) => { reject(new Error('aborted')); } }; certLoader.load(loaderContext, loaderConfig, loaderCallbacks); }); } setMediaKeysServerCertificate(mediaKeys, keySystem, cert) { return new Promise((resolve, reject) => { mediaKeys.setServerCertificate(cert).then(success => { this.log(`setServerCertificate ${success ? 'success' : 'not supported by CDM'} (${cert == null ? void 0 : cert.byteLength}) on "${keySystem}"`); resolve(mediaKeys); }).catch(error => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED, error, fatal: true }, error.message)); }); }); } renewLicense(context, keyMessage) { return this.requestLicense(context, new Uint8Array(keyMessage)).then(data => { return this.updateKeySession(context, new Uint8Array(data)).catch(error => { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED, error, fatal: true }, error.message); }); }); } setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) { const licenseXhrSetup = this.config.licenseXhrSetup; if (!licenseXhrSetup) { xhr.open('POST', url, true); return Promise.resolve({ xhr, licenseChallenge }); } return Promise.resolve().then(() => { if (!keysListItem.decryptdata) { throw new Error('Key removed'); } return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge); }).catch(error => { if (!keysListItem.decryptdata) { // Key session removed. Cancel license request. throw error; } // let's try to open before running setup xhr.open('POST', url, true); return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge); }).then(licenseXhrSetupResult => { // if licenseXhrSetup did not yet call open, let's do it now if (!xhr.readyState) { xhr.open('POST', url, true); } const finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge; return { xhr, licenseChallenge: finalLicenseChallenge }; }); } requestLicense(keySessionContext, licenseChallenge) { const keyLoadPolicy = this.config.keyLoadPolicy.default; return new Promise((resolve, reject) => { const url = this.getLicenseServerUrl(keySessionContext.keySystem); this.log(`Sending license request to URL: ${url}`); const xhr = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; xhr.onreadystatechange = () => { if (!this.hls || !keySessionContext.mediaKeysSession) { return reject(new Error('invalid state')); } if (xhr.readyState === 4) { if (xhr.status === 200) { this._requestLicenseFailureCount = 0; let data = xhr.response; this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength : data}`); const licenseResponseCallback = this.config.licenseResponseCallback; if (licenseResponseCallback) { try { data = licenseResponseCallback.call(this.hls, xhr, url, keySessionContext); } catch (error) { this.error(error); } } resolve(data); } else { const retryConfig = keyLoadPolicy.errorRetry; const maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0; this._requestLicenseFailureCount++; if (this._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED, fatal: true, networkDetails: xhr, response: { url, data: undefined, code: xhr.status, text: xhr.statusText } }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`)); } else { const attemptsLeft = maxNumRetry - this._requestLicenseFailureCount + 1; this.warn(`Retrying license request, ${attemptsLeft} attempts left`); this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject); } } } }; if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) { keySessionContext.licenseXhr.abort(); } keySessionContext.licenseXhr = xhr; this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({ xhr, licenseChallenge }) => { xhr.send(licenseChallenge); }); }); } onMediaAttached(event, data) { if (!this.config.emeEnabled) { return; } const media = data.media; // keep reference of media this.media = media; media.addEventListener('encrypted', this.onMediaEncrypted); media.addEventListener('waitingforkey', this.onWaitingForKey); } onMediaDetached() { const media = this.media; const mediaKeysList = this.mediaKeySessions; if (media) { media.removeEventListener('encrypted', this.onMediaEncrypted); media.removeEventListener('waitingforkey', this.onWaitingForKey); this.media = null; } this._requestLicenseFailureCount = 0; this.setMediaKeysQueue = []; this.mediaKeySessions = []; this.keyIdToKeySessionPromise = {}; LevelKey.clearKeyUriToKeyIdMap(); // Close all sessions and remove media keys from the video element. const keySessionCount = mediaKeysList.length; EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map(mediaKeySessionContext => this.removeSession(mediaKeySessionContext)).concat(media == null ? void 0 : media.setMediaKeys(null).catch(error => { this.log(`Could not clear media keys: ${error}. media.src: ${media == null ? void 0 : media.src}`); }))).then(() => { if (keySessionCount) { this.log('finished closing key sessions and clearing media keys'); mediaKeysList.length = 0; } }).catch(error => { this.log(`Could not close sessions and clear media keys: ${error}. media.src: ${media == null ? void 0 : media.src}`); }); } onManifestLoading() { this.keyFormatPromise = null; } onManifestLoaded(event, { sessionKeys }) { if (!sessionKeys || !this.config.emeEnabled) { return; } if (!this.keyFormatPromise) { const keyFormats = sessionKeys.reduce((formats, sessionKey) => { if (formats.indexOf(sessionKey.keyFormat) === -1) { formats.push(sessionKey.keyFormat); } return formats; }, []); this.log(`Selecting key-system from session-keys ${keyFormats.join(', ')}`); this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); } } removeSession(mediaKeySessionContext) { const { mediaKeysSession, licenseXhr } = mediaKeySessionContext; if (mediaKeysSession) { this.log(`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`); mediaKeysSession.onmessage = null; mediaKeysSession.onkeystatuseschange = null; if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) { licenseXhr.abort(); } mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = undefined; const index = this.mediaKeySessions.indexOf(mediaKeySessionContext); if (index > -1) { this.mediaKeySessions.splice(index, 1); } return mediaKeysSession.remove().catch(error => { this.log(`Could not remove session: ${error}`); }).then(() => { return mediaKeysSession.close(); }).catch(error => { this.log(`Could not close session: ${error}`); }); } } } EMEController.CDMCleanupPromise = void 0; class EMEKeyError extends Error { constructor(data, message) { super(message); this.data = void 0; data.error || (data.error = new Error(message)); this.data = data; data.err = data.error; } } /** * CMCD spec version */ const CMCDVersion = 1; /** * CMCD Object Type */ var CMCDObjectType = { MANIFEST: "m", AUDIO: "a", VIDEO: "v", MUXED: "av", INIT: "i", CAPTION: "c", TIMED_TEXT: "tt", KEY: "k", OTHER: "o" }; /** * CMCD Streaming Format */ const CMCDStreamingFormatHLS = 'h'; /** * CMCD Streaming Type */ /** * CMCD Headers */ /** * CMCD */ /** * Controller to deal with Common Media Client Data (CMCD) * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf */ class CMCDController { // eslint-disable-line no-restricted-globals // eslint-disable-line no-restricted-globals constructor(hls) { this.hls = void 0; this.config = void 0; this.media = void 0; this.sid = void 0; this.cid = void 0; this.useHeaders = false; this.initialized = false; this.starved = false; this.buffering = true; this.audioBuffer = void 0; this.videoBuffer = void 0; this.onWaiting = () => { if (this.initialized) { this.starved = true; } this.buffering = true; }; this.onPlaying = () => { if (!this.initialized) { this.initialized = true; } this.buffering = false; }; /** * Apply CMCD data to a manifest request. */ this.applyPlaylistData = context => { try { this.apply(context, { ot: CMCDObjectType.MANIFEST, su: !this.initialized }); } catch (error) { logger.warn('Could not generate manifest CMCD data.', error); } }; /** * Apply CMCD data to a segment request */ this.applyFragmentData = context => { try { const fragment = context.frag; const level = this.hls.levels[fragment.level]; const ot = this.getObjectType(fragment); const data = { d: fragment.duration * 1000, ot }; if (ot === CMCDObjectType.VIDEO || ot === CMCDObjectType.AUDIO || ot == CMCDObjectType.MUXED) { data.br = level.bitrate / 1000; data.tb = this.getTopBandwidth(ot) / 1000; data.bl = this.getBufferLength(ot); } this.apply(context, data); } catch (error) { logger.warn('Could not generate segment CMCD data.', error); } }; this.hls = hls; const config = this.config = hls.config; const { cmcd } = config; if (cmcd != null) { config.pLoader = this.createPlaylistLoader(); config.fLoader = this.createFragmentLoader(); this.sid = cmcd.sessionId || CMCDController.uuid(); this.cid = cmcd.contentId; this.useHeaders = cmcd.useHeaders === true; this.registerListeners(); } } registerListeners() { const hls = this.hls; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); } unregisterListeners() { const hls = this.hls; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); } destroy() { this.unregisterListeners(); this.onMediaDetached(); // @ts-ignore this.hls = this.config = this.audioBuffer = this.videoBuffer = null; } onMediaAttached(event, data) { this.media = data.media; this.media.addEventListener('waiting', this.onWaiting); this.media.addEventListener('playing', this.onPlaying); } onMediaDetached() { if (!this.media) { return; } this.media.removeEventListener('waiting', this.onWaiting); this.media.removeEventListener('playing', this.onPlaying); // @ts-ignore this.media = null; } onBufferCreated(event, data) { var _data$tracks$audio, _data$tracks$video; this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer; this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer; } /** * Create baseline CMCD data */ createData() { var _this$media; return { v: CMCDVersion, sf: CMCDStreamingFormatHLS, sid: this.sid, cid: this.cid, pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate, mtp: this.hls.bandwidthEstimate / 1000 }; } /** * Apply CMCD data to a request. */ apply(context, data = {}) { // apply baseline data _extends(data, this.createData()); const isVideo = data.ot === CMCDObjectType.INIT || data.ot === CMCDObjectType.VIDEO || data.ot === CMCDObjectType.MUXED; if (this.starved && isVideo) { data.bs = true; data.su = true; this.starved = false; } if (data.su == null) { data.su = this.buffering; } // TODO: Implement rtp, nrr, nor, dl if (this.useHeaders) { const headers = CMCDController.toHeaders(data); if (!Object.keys(headers).length) { return; } if (!context.headers) { context.headers = {}; } _extends(context.headers, headers); } else { const query = CMCDController.toQuery(data); if (!query) { return; } context.url = CMCDController.appendQueryToUri(context.url, query); } } /** * The CMCD object type. */ getObjectType(fragment) { const { type } = fragment; if (type === 'subtitle') { return CMCDObjectType.TIMED_TEXT; } if (fragment.sn === 'initSegment') { return CMCDObjectType.INIT; } if (type === 'audio') { return CMCDObjectType.AUDIO; } if (type === 'main') { if (!this.hls.audioTracks.length) { return CMCDObjectType.MUXED; } return CMCDObjectType.VIDEO; } return undefined; } /** * Get the highest bitrate. */ getTopBandwidth(type) { let bitrate = 0; let levels; const hls = this.hls; if (type === CMCDObjectType.AUDIO) { levels = hls.audioTracks; } else { const max = hls.maxAutoLevel; const len = max > -1 ? max + 1 : hls.levels.length; levels = hls.levels.slice(0, len); } for (const level of levels) { if (level.bitrate > bitrate) { bitrate = level.bitrate; } } return bitrate > 0 ? bitrate : NaN; } /** * Get the buffer length for a media type in milliseconds */ getBufferLength(type) { const media = this.hls.media; const buffer = type === CMCDObjectType.AUDIO ? this.audioBuffer : this.videoBuffer; if (!buffer || !media) { return NaN; } const info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole); return info.len * 1000; } /** * Create a playlist loader */ createPlaylistLoader() { const { pLoader } = this.config; const apply = this.applyPlaylistData; const Ctor = pLoader || this.config.loader; return class CmcdPlaylistLoader { constructor(config) { this.loader = void 0; this.loader = new Ctor(config); } get stats() { return this.loader.stats; } get context() { return this.loader.context; } destroy() { this.loader.destroy(); } abort() { this.loader.abort(); } load(context, config, callbacks) { apply(context); this.loader.load(context, config, callbacks); } }; } /** * Create a playlist loader */ createFragmentLoader() { const { fLoader } = this.config; const apply = this.applyFragmentData; const Ctor = fLoader || this.config.loader; return class CmcdFragmentLoader { constructor(config) { this.loader = void 0; this.loader = new Ctor(config); } get stats() { return this.loader.stats; } get context() { return this.loader.context; } destroy() { this.loader.destroy(); } abort() { this.loader.abort(); } load(context, config, callbacks) { apply(context); this.loader.load(context, config, callbacks); } }; } /** * Generate a random v4 UUI * * @returns {string} */ static uuid() { const url = URL.createObjectURL(new Blob()); const uuid = url.toString(); URL.revokeObjectURL(url); return uuid.slice(uuid.lastIndexOf('/') + 1); } /** * Serialize a CMCD data object according to the rules defined in the * section 3.2 of * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf). */ static serialize(data) { const results = []; const isValid = value => !Number.isNaN(value) && value != null && value !== '' && value !== false; const toRounded = value => Math.round(value); const toHundred = value => toRounded(value / 100) * 100; const toUrlSafe = value => encodeURIComponent(value); const formatters = { br: toRounded, d: toRounded, bl: toHundred, dl: toHundred, mtp: toHundred, nor: toUrlSafe, rtp: toHundred, tb: toRounded }; const keys = Object.keys(data || {}).sort(); for (const key of keys) { let value = data[key]; // ignore invalid values if (!isValid(value)) { continue; } // Version should only be reported if not equal to 1. if (key === 'v' && value === 1) { continue; } // Playback rate should only be sent if not equal to 1. if (key == 'pr' && value === 1) { continue; } // Certain values require special formatting const formatter = formatters[key]; if (formatter) { value = formatter(value); } // Serialize the key/value pair const type = typeof value; let result; if (key === 'ot' || key === 'sf' || key === 'st') { result = `${key}=${value}`; } else if (type === 'boolean') { result = key; } else if (type === 'number') { result = `${key}=${value}`; } else { result = `${key}=${JSON.stringify(value)}`; } results.push(result); } return results.join(','); } /** * Convert a CMCD data object to request headers according to the rules * defined in the section 2.1 and 3.2 of * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf). */ static toHeaders(data) { const keys = Object.keys(data); const headers = {}; const headerNames = ['Object', 'Request', 'Session', 'Status']; const headerGroups = [{}, {}, {}, {}]; const headerMap = { br: 0, d: 0, ot: 0, tb: 0, bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1, cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2, bs: 3, rtp: 3 }; for (const key of keys) { // Unmapped fields are mapped to the Request header const index = headerMap[key] != null ? headerMap[key] : 1; headerGroups[index][key] = data[key]; } for (let i = 0; i < headerGroups.length; i++) { const value = CMCDController.serialize(headerGroups[i]); if (value) { headers[`CMCD-${headerNames[i]}`] = value; } } return headers; } /** * Convert a CMCD data object to query args according to the rules * defined in the section 2.2 and 3.2 of * [CTA-5004](https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf). */ static toQuery(data) { return `CMCD=${encodeURIComponent(CMCDController.serialize(data))}`; } /** * Append query args to a uri. */ static appendQueryToUri(uri, query) { if (!query) { return uri; } const separator = uri.includes('?') ? '&' : '?'; return `${uri}${separator}${query}`; } } const PATHWAY_PENALTY_DURATION_MS = 300000; class ContentSteeringController { constructor(hls) { this.hls = void 0; this.log = void 0; this.loader = null; this.uri = null; this.pathwayId = '.'; this.pathwayPriority = null; this.timeToLoad = 300; this.reloadTimer = -1; this.updated = 0; this.started = false; this.enabled = true; this.levels = null; this.audioTracks = null; this.subtitleTracks = null; this.penalizedPathways = {}; this.hls = hls; this.log = logger.log.bind(logger, `[content-steering]:`); this.registerListeners(); } registerListeners() { const hls = this.hls; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const hls = this.hls; if (!hls) { return; } hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.ERROR, this.onError, this); } startLoad() { this.started = true; self.clearTimeout(this.reloadTimer); if (this.enabled && this.uri) { if (this.updated) { const ttl = Math.max(this.timeToLoad * 1000 - (performance.now() - this.updated), 0); this.scheduleRefresh(this.uri, ttl); } else { this.loadSteeringManifest(this.uri); } } } stopLoad() { this.started = false; if (this.loader) { this.loader.destroy(); this.loader = null; } self.clearTimeout(this.reloadTimer); } destroy() { this.unregisterListeners(); this.stopLoad(); // @ts-ignore this.hls = null; this.levels = this.audioTracks = this.subtitleTracks = null; } removeLevel(levelToRemove) { const levels = this.levels; if (levels) { this.levels = levels.filter(level => level !== levelToRemove); } } onManifestLoading() { this.stopLoad(); this.enabled = true; this.timeToLoad = 300; this.updated = 0; this.uri = null; this.pathwayId = '.'; this.levels = this.audioTracks = this.subtitleTracks = null; } onManifestLoaded(event, data) { const { contentSteering } = data; if (contentSteering === null) { return; } this.pathwayId = contentSteering.pathwayId; this.uri = contentSteering.uri; if (this.started) { this.startLoad(); } } onManifestParsed(event, data) { this.audioTracks = data.audioTracks; this.subtitleTracks = data.subtitleTracks; } onError(event, data) { const { errorAction } = data; if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) { let pathwayPriority = this.pathwayPriority; const pathwayId = this.pathwayId; if (!this.penalizedPathways[pathwayId]) { this.penalizedPathways[pathwayId] = performance.now(); } if (!pathwayPriority && this.levels) { // If PATHWAY-PRIORITY was not provided, list pathways for error handling pathwayPriority = this.levels.reduce((pathways, level) => { if (pathways.indexOf(level.pathwayId) === -1) { pathways.push(level.pathwayId); } return pathways; }, []); } if (pathwayPriority && pathwayPriority.length > 1) { this.updatePathwayPriority(pathwayPriority); errorAction.resolved = this.pathwayId !== pathwayId; } } } filterParsedLevels(levels) { // Filter levels to only include those that are in the initial pathway this.levels = levels; let pathwayLevels = this.getLevelsForPathway(this.pathwayId); if (pathwayLevels.length === 0) { const pathwayId = levels[0].pathwayId; this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to "${pathwayId}"`); pathwayLevels = this.getLevelsForPathway(pathwayId); this.pathwayId = pathwayId; } if (pathwayLevels.length !== levels.length) { this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`); return pathwayLevels; } return levels; } getLevelsForPathway(pathwayId) { if (this.levels === null) { return []; } return this.levels.filter(level => pathwayId === level.pathwayId); } updatePathwayPriority(pathwayPriority) { this.pathwayPriority = pathwayPriority; let levels; // Evaluate if we should remove the pathway from the penalized list const penalizedPathways = this.penalizedPathways; const now = performance.now(); Object.keys(penalizedPathways).forEach(pathwayId => { if (now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) { delete penalizedPathways[pathwayId]; } }); for (let i = 0; i < pathwayPriority.length; i++) { const pathwayId = pathwayPriority[i]; if (penalizedPathways[pathwayId]) { continue; } if (pathwayId === this.pathwayId) { return; } const selectedIndex = this.hls.nextLoadLevel; const selectedLevel = this.hls.levels[selectedIndex]; levels = this.getLevelsForPathway(pathwayId); if (levels.length > 0) { this.log(`Setting Pathway to "${pathwayId}"`); this.pathwayId = pathwayId; this.hls.trigger(Events.LEVELS_UPDATED, { levels }); // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed const levelAfterChange = this.hls.levels[selectedIndex]; if (selectedLevel && levelAfterChange && this.levels) { if (levelAfterChange.attrs['STABLE-VARIANT-ID'] !== selectedLevel.attrs['STABLE-VARIANT-ID'] && levelAfterChange.bitrate !== selectedLevel.bitrate) { this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`); } this.hls.nextLoadLevel = selectedIndex; } break; } } } clonePathways(pathwayClones) { const levels = this.levels; if (!levels) { return; } const audioGroupCloneMap = {}; const subtitleGroupCloneMap = {}; pathwayClones.forEach(pathwayClone => { const { ID: cloneId, 'BASE-ID': baseId, 'URI-REPLACEMENT': uriReplacement } = pathwayClone; if (levels.some(level => level.pathwayId === cloneId)) { return; } const clonedVariants = this.getLevelsForPathway(baseId).map(baseLevel => { const levelParsed = _extends({}, baseLevel); levelParsed.details = undefined; levelParsed.url = performUriReplacement(baseLevel.uri, baseLevel.attrs['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement); const attributes = new AttrList(baseLevel.attrs); attributes['PATHWAY-ID'] = cloneId; const clonedAudioGroupId = attributes.AUDIO && `${attributes.AUDIO}_clone_${cloneId}`; const clonedSubtitleGroupId = attributes.SUBTITLES && `${attributes.SUBTITLES}_clone_${cloneId}`; if (clonedAudioGroupId) { audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId; attributes.AUDIO = clonedAudioGroupId; } if (clonedSubtitleGroupId) { subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId; attributes.SUBTITLES = clonedSubtitleGroupId; } levelParsed.attrs = attributes; const clonedLevel = new Level(levelParsed); addGroupId(clonedLevel, 'audio', clonedAudioGroupId); addGroupId(clonedLevel, 'text', clonedSubtitleGroupId); return clonedLevel; }); levels.push(...clonedVariants); cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId); cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId); }); } loadSteeringManifest(uri) { const config = this.hls.config; const Loader = config.loader; if (this.loader) { this.loader.destroy(); } this.loader = new Loader(config); let url; try { url = new self.URL(uri); } catch (error) { this.enabled = false; this.log(`Failed to parse Steering Manifest URI: ${uri}`); return; } if (url.protocol !== 'data:') { const throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0; url.searchParams.set('_HLS_pathway', this.pathwayId); url.searchParams.set('_HLS_throughput', '' + throughput); } const context = { responseType: 'json', url: url.href }; const loadPolicy = config.steeringManifestLoadPolicy.default; const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: legacyRetryCompatibility.maxNumRetry || 0, retryDelay: legacyRetryCompatibility.retryDelayMs || 0, maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 }; const callbacks = { onSuccess: (response, stats, context, networkDetails) => { this.log(`Loaded steering manifest: "${url}"`); const steeringData = response.data; if (steeringData.VERSION !== 1) { this.log(`Steering VERSION ${steeringData.VERSION} not supported!`); return; } this.updated = performance.now(); this.timeToLoad = steeringData.TTL; const { 'RELOAD-URI': reloadUri, 'PATHWAY-CLONES': pathwayClones, 'PATHWAY-PRIORITY': pathwayPriority } = steeringData; if (reloadUri) { try { this.uri = new self.URL(reloadUri, url).href; } catch (error) { this.enabled = false; this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`); return; } } this.scheduleRefresh(this.uri || context.url); if (pathwayClones) { this.clonePathways(pathwayClones); } if (pathwayPriority) { this.updatePathwayPriority(pathwayPriority); } }, onError: (error, context, networkDetails, stats) => { this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context.url})`); this.stopLoad(); if (error.code === 410) { this.enabled = false; this.log(`Steering manifest ${context.url} no longer available`); return; } let ttl = this.timeToLoad * 1000; if (error.code === 429) { const loader = this.loader; if (typeof (loader == null ? void 0 : loader.getResponseHeader) === 'function') { const retryAfter = loader.getResponseHeader('Retry-After'); if (retryAfter) { ttl = parseFloat(retryAfter) * 1000; } } this.log(`Steering manifest ${context.url} rate limited`); return; } this.scheduleRefresh(this.uri || context.url, ttl); }, onTimeout: (stats, context, networkDetails) => { this.log(`Timeout loading steering manifest (${context.url})`); this.scheduleRefresh(this.uri || context.url); } }; this.log(`Requesting steering manifest: ${url}`); this.loader.load(context, loaderConfig, callbacks); } scheduleRefresh(uri, ttlMs = this.timeToLoad * 1000) { self.clearTimeout(this.reloadTimer); this.reloadTimer = self.setTimeout(() => { this.loadSteeringManifest(uri); }, ttlMs); } } function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) { if (!tracks) { return; } Object.keys(groupCloneMap).forEach(audioGroupId => { const clonedTracks = tracks.filter(track => track.groupId === audioGroupId).map(track => { const clonedTrack = _extends({}, track); clonedTrack.details = undefined; clonedTrack.attrs = new AttrList(clonedTrack.attrs); clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement); clonedTrack.groupId = clonedTrack.attrs['GROUP-ID'] = groupCloneMap[audioGroupId]; clonedTrack.attrs['PATHWAY-ID'] = cloneId; return clonedTrack; }); tracks.push(...clonedTracks); }); } function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) { const { HOST: host, PARAMS: params, [perOptionKey]: perOptionUris } = uriReplacement; let perVariantUri; if (stableId) { perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId]; if (perVariantUri) { uri = perVariantUri; } } const url = new self.URL(uri); if (host && !perVariantUri) { url.host = host; } if (params) { Object.keys(params).sort().forEach(key => { if (key) { url.searchParams.set(key, params[key]); } }); } return url.href; } const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im; class XhrLoader { constructor(config) { this.xhrSetup = void 0; this.requestTimeout = void 0; this.retryTimeout = void 0; this.retryDelay = void 0; this.config = null; this.callbacks = null; this.context = void 0; this.loader = null; this.stats = void 0; this.xhrSetup = config ? config.xhrSetup || null : null; this.stats = new LoadStats(); this.retryDelay = 0; } destroy() { this.callbacks = null; this.abortInternal(); this.loader = null; this.config = null; } abortInternal() { const loader = this.loader; self.clearTimeout(this.requestTimeout); self.clearTimeout(this.retryTimeout); if (loader) { loader.onreadystatechange = null; loader.onprogress = null; if (loader.readyState !== 4) { this.stats.aborted = true; loader.abort(); } } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.loader); } } load(context, config, callbacks) { if (this.stats.loading.start) { throw new Error('Loader can only be used once.'); } this.stats.loading.start = self.performance.now(); this.context = context; this.config = config; this.callbacks = callbacks; this.loadInternal(); } loadInternal() { const { config, context } = this; if (!config) { return; } const xhr = this.loader = new self.XMLHttpRequest(); const stats = this.stats; stats.loading.first = 0; stats.loaded = 0; stats.aborted = false; const xhrSetup = this.xhrSetup; if (xhrSetup) { Promise.resolve().then(() => { if (this.stats.aborted) return; return xhrSetup(xhr, context.url); }).catch(error => { xhr.open('GET', context.url, true); return xhrSetup(xhr, context.url); }).then(() => { if (this.stats.aborted) return; this.openAndSendXhr(xhr, context, config); }).catch(error => { // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS this.callbacks.onError({ code: xhr.status, text: error.message }, context, xhr, stats); return; }); } else { this.openAndSendXhr(xhr, context, config); } } openAndSendXhr(xhr, context, config) { if (!xhr.readyState) { xhr.open('GET', context.url, true); } const headers = this.context.headers; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; if (headers) { for (const header in headers) { xhr.setRequestHeader(header, headers[header]); } } if (context.rangeEnd) { xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1)); } xhr.onreadystatechange = this.readystatechange.bind(this); xhr.onprogress = this.loadprogress.bind(this); xhr.responseType = context.responseType; // setup timeout before we perform request self.clearTimeout(this.requestTimeout); config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout); xhr.send(); } readystatechange() { const { context, loader: xhr, stats } = this; if (!context || !xhr) { return; } const readyState = xhr.readyState; const config = this.config; // don't proceed if xhr has been aborted if (stats.aborted) { return; } // >= HEADERS_RECEIVED if (readyState >= 2) { if (stats.loading.first === 0) { stats.loading.first = Math.max(self.performance.now(), stats.loading.start); // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet if (config.timeout !== config.loadPolicy.maxLoadTimeMs) { self.clearTimeout(this.requestTimeout); config.timeout = config.loadPolicy.maxLoadTimeMs; this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start)); } } if (readyState === 4) { self.clearTimeout(this.requestTimeout); xhr.onreadystatechange = null; xhr.onprogress = null; const status = xhr.status; // http status between 200 to 299 are all successful const useResponse = xhr.responseType !== 'text'; if (status >= 200 && status < 300 && (useResponse && xhr.response || xhr.responseText !== null)) { stats.loading.end = Math.max(self.performance.now(), stats.loading.first); const data = useResponse ? xhr.response : xhr.responseText; const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length; stats.loaded = stats.total = len; stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first); if (!this.callbacks) { return; } const onProgress = this.callbacks.onProgress; if (onProgress) { onProgress(stats, context, data, xhr); } if (!this.callbacks) { return; } const response = { url: xhr.responseURL, data: data, code: status }; this.callbacks.onSuccess(response, stats, context, xhr); } else { const retryConfig = config.loadPolicy.errorRetry; const retryCount = stats.retry; // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error if (shouldRetry(retryConfig, retryCount, false, status)) { this.retry(retryConfig); } else { logger.error(`${status} while loading ${context.url}`); this.callbacks.onError({ code: status, text: xhr.statusText }, context, xhr, stats); } } } } } loadtimeout() { var _this$config; const retryConfig = (_this$config = this.config) == null ? void 0 : _this$config.loadPolicy.timeoutRetry; const retryCount = this.stats.retry; if (shouldRetry(retryConfig, retryCount, true)) { this.retry(retryConfig); } else { logger.warn(`timeout while loading ${this.context.url}`); const callbacks = this.callbacks; if (callbacks) { this.abortInternal(); callbacks.onTimeout(this.stats, this.context, this.loader); } } } retry(retryConfig) { const { context, stats } = this; this.retryDelay = getRetryDelay(retryConfig, stats.retry); stats.retry++; logger.warn(`${status ? 'HTTP Status ' + status : 'Timeout'} while loading ${context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`); // abort and reset internal state this.abortInternal(); this.loader = null; // schedule retry self.clearTimeout(this.retryTimeout); this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay); } loadprogress(event) { const stats = this.stats; stats.loaded = event.loaded; if (event.lengthComputable) { stats.total = event.total; } } getCacheAge() { let result = null; if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) { const ageHeader = this.loader.getResponseHeader('age'); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, 'im').test(this.loader.getAllResponseHeaders())) { return this.loader.getResponseHeader(name); } return null; } } function fetchSupported() { if ( // @ts-ignore self.fetch && self.AbortController && self.ReadableStream && self.Request) { try { new self.ReadableStream({}); // eslint-disable-line no-new return true; } catch (e) { /* noop */ } } return false; } const BYTERANGE = /(\d+)-(\d+)\/(\d+)/; class FetchLoader { constructor(config /* HlsConfig */) { this.fetchSetup = void 0; this.requestTimeout = void 0; this.request = void 0; this.response = void 0; this.controller = void 0; this.context = void 0; this.config = null; this.callbacks = null; this.stats = void 0; this.loader = null; this.fetchSetup = config.fetchSetup || getRequest; this.controller = new self.AbortController(); this.stats = new LoadStats(); } destroy() { this.loader = this.callbacks = null; this.abortInternal(); } abortInternal() { const response = this.response; if (!(response != null && response.ok)) { this.stats.aborted = true; this.controller.abort(); } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.response); } } load(context, config, callbacks) { const stats = this.stats; if (stats.loading.start) { throw new Error('Loader can only be used once.'); } stats.loading.start = self.performance.now(); const initParams = getRequestParameters(context, this.controller.signal); const onProgress = callbacks.onProgress; const isArrayBuffer = context.responseType === 'arraybuffer'; const LENGTH = isArrayBuffer ? 'byteLength' : 'length'; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; this.context = context; this.config = config; this.callbacks = callbacks; this.request = this.fetchSetup(context, initParams); self.clearTimeout(this.requestTimeout); config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; this.requestTimeout = self.setTimeout(() => { this.abortInternal(); callbacks.onTimeout(stats, context, this.response); }, config.timeout); self.fetch(this.request).then(response => { this.response = this.loader = response; const first = Math.max(self.performance.now(), stats.loading.start); self.clearTimeout(this.requestTimeout); config.timeout = maxLoadTimeMs; this.requestTimeout = self.setTimeout(() => { this.abortInternal(); callbacks.onTimeout(stats, context, this.response); }, maxLoadTimeMs - (first - stats.loading.start)); if (!response.ok) { const { status, statusText } = response; throw new FetchError(statusText || 'fetch, bad network response', status, response); } stats.loading.first = first; stats.total = getContentLength(response.headers) || stats.total; if (onProgress && isFiniteNumber(config.highWaterMark)) { return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress); } if (isArrayBuffer) { return response.arrayBuffer(); } if (context.responseType === 'json') { return response.json(); } return response.text(); }).then(responseData => { const { response } = this; self.clearTimeout(this.requestTimeout); stats.loading.end = Math.max(self.performance.now(), stats.loading.first); const total = responseData[LENGTH]; if (total) { stats.loaded = stats.total = total; } const loaderResponse = { url: response.url, data: responseData, code: response.status }; if (onProgress && !isFiniteNumber(config.highWaterMark)) { onProgress(stats, context, responseData, response); } callbacks.onSuccess(loaderResponse, stats, context, response); }).catch(error => { self.clearTimeout(this.requestTimeout); if (stats.aborted) { return; } // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior // when destroying, 'error' itself can be undefined const code = !error ? 0 : error.code || 0; const text = !error ? null : error.message; callbacks.onError({ code, text }, context, error ? error.details : null, stats); }); } getCacheAge() { let result = null; if (this.response) { const ageHeader = this.response.headers.get('age'); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { return this.response ? this.response.headers.get(name) : null; } loadProgressively(response, stats, context, highWaterMark = 0, onProgress) { const chunkCache = new ChunkCache(); const reader = response.body.getReader(); const pump = () => { return reader.read().then(data => { if (data.done) { if (chunkCache.dataLength) { onProgress(stats, context, chunkCache.flush(), response); } return Promise.resolve(new ArrayBuffer(0)); } const chunk = data.value; const len = chunk.length; stats.loaded += len; if (len < highWaterMark || chunkCache.dataLength) { // The current chunk is too small to to be emitted or the cache already has data // Push it to the cache chunkCache.push(chunk); if (chunkCache.dataLength >= highWaterMark) { // flush in order to join the typed arrays onProgress(stats, context, chunkCache.flush(), response); } } else { // If there's nothing cached already, and the chache is large enough // just emit the progress event onProgress(stats, context, chunk, response); } return pump(); }).catch(() => { /* aborted */ return Promise.reject(); }); }; return pump(); } } function getRequestParameters(context, signal) { const initParams = { method: 'GET', mode: 'cors', credentials: 'same-origin', signal, headers: new self.Headers(_extends({}, context.headers)) }; if (context.rangeEnd) { initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1)); } return initParams; } function getByteRangeLength(byteRangeHeader) { const result = BYTERANGE.exec(byteRangeHeader); if (result) { return parseInt(result[2]) - parseInt(result[1]) + 1; } } function getContentLength(headers) { const contentRange = headers.get('Content-Range'); if (contentRange) { const byteRangeLength = getByteRangeLength(contentRange); if (isFiniteNumber(byteRangeLength)) { return byteRangeLength; } } const contentLength = headers.get('Content-Length'); if (contentLength) { return parseInt(contentLength); } } function getRequest(context, initParams) { return new self.Request(context.url, initParams); } class FetchError extends Error { constructor(message, code, details) { super(message); this.code = void 0; this.details = void 0; this.code = code; this.details = details; } } const WHITESPACE_CHAR = /\s/; const Cues = { newCue(track, startTime, endTime, captionScreen) { const result = []; let row; // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers let cue; let indenting; let indent; let text; const Cue = self.VTTCue || self.TextTrackCue; for (let r = 0; r < captionScreen.rows.length; r++) { row = captionScreen.rows[r]; indenting = true; indent = 0; text = ''; if (!row.isEmpty()) { var _track$cues; for (let c = 0; c < row.chars.length; c++) { if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) { indent++; } else { text += row.chars[c].uchar; indenting = false; } } // To be used for cleaning-up orphaned roll-up captions row.cueStartTime = startTime; // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE if (startTime === endTime) { endTime += 0.0001; } if (indent >= 16) { indent--; } else { indent++; } const cueText = fixLineBreaks(text.trim()); const id = generateCueId(startTime, endTime, cueText); // If this cue already exists in the track do not push it if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) { cue = new Cue(startTime, endTime, cueText); cue.id = id; cue.line = r + 1; cue.align = 'left'; // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code) // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608 // Firefox throws an exception and captions break with out of bounds 0-100 values cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10); result.push(cue); } } } if (track && result.length) { // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome result.sort((cueA, cueB) => { if (cueA.line === 'auto' || cueB.line === 'auto') { return 0; } if (cueA.line > 8 && cueB.line > 8) { return cueB.line - cueA.line; } return cueA.line - cueB.line; }); result.forEach(cue => addCueToTrack(track, cue)); } return result; } }; /** * @deprecated use fragLoadPolicy.default */ /** * @deprecated use manifestLoadPolicy.default and playlistLoadPolicy.default */ const defaultLoadPolicy = { maxTimeToFirstByteMs: 8000, maxLoadTimeMs: 20000, timeoutRetry: null, errorRetry: null }; /** * @ignore * If possible, keep hlsDefaultConfig shallow * It is cloned whenever a new Hls instance is created, by keeping the config * shallow the properties are cloned, and we don't end up manipulating the default */ const hlsDefaultConfig = _objectSpread2(_objectSpread2({ autoStartLoad: true, // used by stream-controller startPosition: -1, // used by stream-controller defaultAudioCodec: undefined, // used by stream-controller debug: false, // used by logger capLevelOnFPSDrop: false, // used by fps-controller capLevelToPlayerSize: false, // used by cap-level-controller ignoreDevicePixelRatio: false, // used by cap-level-controller initialLiveManifestSize: 1, // used by stream-controller maxBufferLength: 30, // used by stream-controller backBufferLength: Infinity, // used by buffer-controller maxBufferSize: 60 * 1000 * 1000, // used by stream-controller maxBufferHole: 0.1, // used by stream-controller highBufferWatchdogPeriod: 2, // used by stream-controller nudgeOffset: 0.1, // used by stream-controller nudgeMaxRetry: 3, // used by stream-controller maxFragLookUpTolerance: 0.25, // used by stream-controller liveSyncDurationCount: 3, // used by latency-controller liveMaxLatencyDurationCount: Infinity, // used by latency-controller liveSyncDuration: undefined, // used by latency-controller liveMaxLatencyDuration: undefined, // used by latency-controller maxLiveSyncPlaybackRate: 1, // used by latency-controller liveDurationInfinity: false, // used by buffer-controller /** * @deprecated use backBufferLength */ liveBackBufferLength: null, // used by buffer-controller maxMaxBufferLength: 600, // used by stream-controller enableWorker: true, // used by transmuxer workerPath: null, // used by transmuxer enableSoftwareAES: true, // used by decrypter startLevel: undefined, // used by level-controller startFragPrefetch: false, // used by stream-controller fpsDroppedMonitoringPeriod: 5000, // used by fps-controller fpsDroppedMonitoringThreshold: 0.2, // used by fps-controller appendErrorMaxRetry: 3, // used by buffer-controller loader: XhrLoader, // loader: FetchLoader, fLoader: undefined, // used by fragment-loader pLoader: undefined, // used by playlist-loader xhrSetup: undefined, // used by xhr-loader licenseXhrSetup: undefined, // used by eme-controller licenseResponseCallback: undefined, // used by eme-controller abrController: AbrController, bufferController: BufferController, capLevelController: CapLevelController, errorController: ErrorController, fpsController: FPSController, stretchShortVideoTrack: false, // used by mp4-remuxer maxAudioFramesDrift: 1, // used by mp4-remuxer forceKeyFrameOnDiscontinuity: true, // used by ts-demuxer abrEwmaFastLive: 3, // used by abr-controller abrEwmaSlowLive: 9, // used by abr-controller abrEwmaFastVoD: 3, // used by abr-controller abrEwmaSlowVoD: 9, // used by abr-controller abrEwmaDefaultEstimate: 5e5, // 500 kbps // used by abr-controller abrBandWidthFactor: 0.95, // used by abr-controller abrBandWidthUpFactor: 0.7, // used by abr-controller abrMaxWithRealBitrate: false, // used by abr-controller maxStarvationDelay: 4, // used by abr-controller maxLoadingDelay: 4, // used by abr-controller minAutoBitrate: 0, // used by hls emeEnabled: false, // used by eme-controller widevineLicenseUrl: undefined, // used by eme-controller drmSystems: {}, // used by eme-controller drmSystemOptions: {}, // used by eme-controller requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess , // used by eme-controller testBandwidth: true, progressive: false, lowLatencyMode: true, cmcd: undefined, enableDateRangeMetadataCues: true, enableEmsgMetadataCues: true, enableID3MetadataCues: true, certLoadPolicy: { default: defaultLoadPolicy }, keyLoadPolicy: { default: { maxTimeToFirstByteMs: 8000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: 'linear' }, errorRetry: { maxNumRetry: 8, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: 'linear' } } }, manifestLoadPolicy: { default: { maxTimeToFirstByteMs: Infinity, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, playlistLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 2, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, fragLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 120000, timeoutRetry: { maxNumRetry: 4, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 6, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, steeringManifestLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, // These default settings are deprecated in favor of the above policies // and are maintained for backwards compatibility manifestLoadingTimeOut: 10000, manifestLoadingMaxRetry: 1, manifestLoadingRetryDelay: 1000, manifestLoadingMaxRetryTimeout: 64000, levelLoadingTimeOut: 10000, levelLoadingMaxRetry: 4, levelLoadingRetryDelay: 1000, levelLoadingMaxRetryTimeout: 64000, fragLoadingTimeOut: 20000, fragLoadingMaxRetry: 6, fragLoadingRetryDelay: 1000, fragLoadingMaxRetryTimeout: 64000 }, timelineConfig()), {}, { subtitleStreamController: SubtitleStreamController , subtitleTrackController: SubtitleTrackController , timelineController: TimelineController , audioStreamController: AudioStreamController , audioTrackController: AudioTrackController , emeController: EMEController , cmcdController: CMCDController , contentSteeringController: ContentSteeringController }); function timelineConfig() { return { cueHandler: Cues, // used by timeline-controller enableWebVTT: true, // used by timeline-controller enableIMSC1: true, // used by timeline-controller enableCEA708Captions: true, // used by timeline-controller captionsTextTrack1Label: 'English', // used by timeline-controller captionsTextTrack1LanguageCode: 'en', // used by timeline-controller captionsTextTrack2Label: 'Spanish', // used by timeline-controller captionsTextTrack2LanguageCode: 'es', // used by timeline-controller captionsTextTrack3Label: 'Unknown CC', // used by timeline-controller captionsTextTrack3LanguageCode: '', // used by timeline-controller captionsTextTrack4Label: 'Unknown CC', // used by timeline-controller captionsTextTrack4LanguageCode: '', // used by timeline-controller renderTextTracksNatively: true }; } /** * @ignore */ function mergeConfig(defaultConfig, userConfig) { if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) { throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration"); } if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"'); } if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"'); } const defaultsCopy = deepCpy(defaultConfig); // Backwards compatibility with deprecated config values const deprecatedSettingTypes = ['manifest', 'level', 'frag']; const deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout']; deprecatedSettingTypes.forEach(type => { const policyName = `${type === 'level' ? 'playlist' : type}LoadPolicy`; const policyNotSet = userConfig[policyName] === undefined; const report = []; deprecatedSettings.forEach(setting => { const deprecatedSetting = `${type}Loading${setting}`; const value = userConfig[deprecatedSetting]; if (value !== undefined && policyNotSet) { report.push(deprecatedSetting); const settings = defaultsCopy[policyName].default; userConfig[policyName] = { default: settings }; switch (setting) { case 'TimeOut': settings.maxLoadTimeMs = value; settings.maxTimeToFirstByteMs = value; break; case 'MaxRetry': settings.errorRetry.maxNumRetry = value; settings.timeoutRetry.maxNumRetry = value; break; case 'RetryDelay': settings.errorRetry.retryDelayMs = value; settings.timeoutRetry.retryDelayMs = value; break; case 'MaxRetryTimeout': settings.errorRetry.maxRetryDelayMs = value; settings.timeoutRetry.maxRetryDelayMs = value; break; } } }); if (report.length) { logger.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${JSON.stringify(userConfig[policyName])}`); } }); return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig); } function deepCpy(obj) { if (obj && typeof obj === 'object') { if (Array.isArray(obj)) { return obj.map(deepCpy); } return Object.keys(obj).reduce((result, key) => { result[key] = deepCpy(obj[key]); return result; }, {}); } return obj; } /** * @ignore */ function enableStreamingMode(config) { const currentLoader = config.loader; if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) { // If a developer has configured their own loader, respect that choice logger.log('[config]: Custom loader detected, cannot enable progressive streaming'); config.progressive = false; } else { const canStreamProgressively = fetchSupported(); if (canStreamProgressively) { config.loader = FetchLoader; config.progressive = true; config.enableSoftwareAES = true; logger.log('[config]: Progressive streaming enabled, using FetchLoader'); } } } /** * The `Hls` class is the core of the HLS.js library used to instantiate player instances. * @public */ class Hls { /** * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`. */ /** * The configuration object provided on player instantiation. */ /** * Get the video-dev/hls.js package version. */ static get version() { return "1.4.13"; } /** * Check if the required MediaSource Extensions are available. */ static isSupported() { return isSupported(); } static get Events() { return Events; } static get ErrorTypes() { return ErrorTypes; } static get ErrorDetails() { return ErrorDetails; } /** * Get the default configuration applied to new instances. */ static get DefaultConfig() { if (!Hls.defaultConfig) { return hlsDefaultConfig; } return Hls.defaultConfig; } /** * Replace the default configuration applied to new instances. */ static set DefaultConfig(defaultConfig) { Hls.defaultConfig = defaultConfig; } /** * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`. * @param userConfig - Configuration options applied over `Hls.DefaultConfig` */ constructor(userConfig = {}) { this.config = void 0; this.userConfig = void 0; this.coreComponents = void 0; this.networkControllers = void 0; this._emitter = new EventEmitter(); this._autoLevelCapping = void 0; this._maxHdcpLevel = null; this.abrController = void 0; this.bufferController = void 0; this.capLevelController = void 0; this.latencyController = void 0; this.levelController = void 0; this.streamController = void 0; this.audioTrackController = void 0; this.subtitleTrackController = void 0; this.emeController = void 0; this.cmcdController = void 0; this._media = null; this.url = null; enableLogs(userConfig.debug || false, 'Hls instance'); const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig); this.userConfig = userConfig; this._autoLevelCapping = -1; if (config.progressive) { enableStreamingMode(config); } // core controllers and network loaders const { abrController: ConfigAbrController, bufferController: ConfigBufferController, capLevelController: ConfigCapLevelController, errorController: ConfigErrorController, fpsController: ConfigFpsController } = config; const errorController = new ConfigErrorController(this); const abrController = this.abrController = new ConfigAbrController(this); const bufferController = this.bufferController = new ConfigBufferController(this); const capLevelController = this.capLevelController = new ConfigCapLevelController(this); const fpsController = new ConfigFpsController(this); const playListLoader = new PlaylistLoader(this); const id3TrackController = new ID3TrackController(this); const ConfigContentSteeringController = config.contentSteeringController; // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null; const levelController = this.levelController = new LevelController(this, contentSteering); // FragmentTracker must be defined before StreamController because the order of event handling is important const fragmentTracker = new FragmentTracker(this); const keyLoader = new KeyLoader(this.config); const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader); // Cap level controller uses streamController to flush the buffer capLevelController.setStreamController(streamController); // fpsController uses streamController to switch when frames are being dropped fpsController.setStreamController(streamController); const networkControllers = [playListLoader, levelController, streamController]; if (contentSteering) { networkControllers.splice(1, 0, contentSteering); } this.networkControllers = networkControllers; const coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker]; this.audioTrackController = this.createController(config.audioTrackController, networkControllers); const AudioStreamControllerClass = config.audioStreamController; if (AudioStreamControllerClass) { networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader)); } // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers); const SubtitleStreamControllerClass = config.subtitleStreamController; if (SubtitleStreamControllerClass) { networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader)); } this.createController(config.timelineController, coreComponents); keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents); this.cmcdController = this.createController(config.cmcdController, coreComponents); this.latencyController = this.createController(LatencyController, coreComponents); this.coreComponents = coreComponents; // Error controller handles errors before and after all other controllers // This listener will be invoked after all other controllers error listeners networkControllers.push(errorController); const onErrorOut = errorController.onErrorOut; if (typeof onErrorOut === 'function') { this.on(Events.ERROR, onErrorOut, errorController); } } createController(ControllerClass, components) { if (ControllerClass) { const controllerInstance = new ControllerClass(this); if (components) { components.push(controllerInstance); } return controllerInstance; } return null; } // Delegate the EventEmitter through the public API of Hls.js on(event, listener, context = this) { this._emitter.on(event, listener, context); } once(event, listener, context = this) { this._emitter.once(event, listener, context); } removeAllListeners(event) { this._emitter.removeAllListeners(event); } off(event, listener, context = this, once) { this._emitter.off(event, listener, context, once); } listeners(event) { return this._emitter.listeners(event); } emit(event, name, eventObject) { return this._emitter.emit(event, name, eventObject); } trigger(event, eventObject) { if (this.config.debug) { return this.emit(event, event, eventObject); } else { try { return this.emit(event, event, eventObject); } catch (e) { logger.error('An internal error happened while handling event ' + event + '. Error message: "' + e.message + '". Here is a stacktrace:', e); this.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: false, event: event, error: e }); } } return false; } listenerCount(event) { return this._emitter.listenerCount(event); } /** * Dispose of the instance */ destroy() { logger.log('destroy'); this.trigger(Events.DESTROYING, undefined); this.detachMedia(); this.removeAllListeners(); this._autoLevelCapping = -1; this.url = null; this.networkControllers.forEach(component => component.destroy()); this.networkControllers.length = 0; this.coreComponents.forEach(component => component.destroy()); this.coreComponents.length = 0; // Remove any references that could be held in config options or callbacks const config = this.config; config.xhrSetup = config.fetchSetup = undefined; // @ts-ignore this.userConfig = null; } /** * Attaches Hls.js to a media element */ attachMedia(media) { logger.log('attachMedia'); this._media = media; this.trigger(Events.MEDIA_ATTACHING, { media: media }); } /** * Detach Hls.js from the media */ detachMedia() { logger.log('detachMedia'); this.trigger(Events.MEDIA_DETACHING, undefined); this._media = null; } /** * Set the source URL. Can be relative or absolute. */ loadSource(url) { this.stopLoad(); const media = this.media; const loadedSource = this.url; const loadingSource = this.url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, { alwaysNormalize: true }); logger.log(`loadSource:${loadingSource}`); if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) { this.detachMedia(); this.attachMedia(media); } // when attaching to a source URL, trigger a playlist load this.trigger(Events.MANIFEST_LOADING, { url: url }); } /** * Start loading data from the stream source. * Depending on default config, client starts loading automatically when a source is set. * * @param startPosition - Set the start position to stream from. * Defaults to -1 (None: starts from earliest point) */ startLoad(startPosition = -1) { logger.log(`startLoad(${startPosition})`); this.networkControllers.forEach(controller => { controller.startLoad(startPosition); }); } /** * Stop loading of any stream data. */ stopLoad() { logger.log('stopLoad'); this.networkControllers.forEach(controller => { controller.stopLoad(); }); } /** * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1) */ swapAudioCodec() { logger.log('swapAudioCodec'); this.streamController.swapAudioCodec(); } /** * When the media-element fails, this allows to detach and then re-attach it * as one call (convenience method). * * Automatic recovery of media-errors by this process is configurable. */ recoverMediaError() { logger.log('recoverMediaError'); const media = this._media; this.detachMedia(); if (media) { this.attachMedia(media); } } removeLevel(levelIndex, urlId = 0) { this.levelController.removeLevel(levelIndex, urlId); } /** * @returns an array of levels (variants) sorted by HDCP-LEVEL, BANDWIDTH, SCORE, and RESOLUTION (height) */ get levels() { const levels = this.levelController.levels; return levels ? levels : []; } /** * Index of quality level (variant) currently played */ get currentLevel() { return this.streamController.currentLevel; } /** * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection. */ set currentLevel(newLevel) { logger.log(`set currentLevel:${newLevel}`); this.loadLevel = newLevel; this.abrController.clearTimer(); this.streamController.immediateLevelSwitch(); } /** * Index of next quality level loaded as scheduled by stream controller. */ get nextLevel() { return this.streamController.nextLevel; } /** * Set quality level index for next loaded data. * This will switch the video quality asap, without interrupting playback. * May abort current loading of data, and flush parts of buffer (outside currently played fragment region). * @param newLevel - Pass -1 for automatic level selection */ set nextLevel(newLevel) { logger.log(`set nextLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.nextLevelSwitch(); } /** * Return the quality level of the currently or last (of none is loaded currently) segment */ get loadLevel() { return this.levelController.level; } /** * Set quality level index for next loaded data in a conservative way. * This will switch the quality without flushing, but interrupt current loading. * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer. * @param newLevel - Pass -1 for automatic level selection */ set loadLevel(newLevel) { logger.log(`set loadLevel:${newLevel}`); this.levelController.manualLevel = newLevel; } /** * get next quality level loaded */ get nextLoadLevel() { return this.levelController.nextLoadLevel; } /** * Set quality level of next loaded segment in a fully "non-destructive" way. * Same as `loadLevel` but will wait for next switch (until current loading is done). */ set nextLoadLevel(level) { this.levelController.nextLoadLevel = level; } /** * Return "first level": like a default level, if not set, * falls back to index of first level referenced in manifest */ get firstLevel() { return Math.max(this.levelController.firstLevel, this.minAutoLevel); } /** * Sets "first-level", see getter. */ set firstLevel(newLevel) { logger.log(`set firstLevel:${newLevel}`); this.levelController.firstLevel = newLevel; } /** * Return start level (level of first fragment that will be played back) * if not overrided by user, first level appearing in manifest will be used as start level * if -1 : automatic start level selection, playback will start from level matching download bandwidth * (determined from download of first segment) */ get startLevel() { return this.levelController.startLevel; } /** * set start level (level of first fragment that will be played back) * if not overrided by user, first level appearing in manifest will be used as start level * if -1 : automatic start level selection, playback will start from level matching download bandwidth * (determined from download of first segment) */ set startLevel(newLevel) { logger.log(`set startLevel:${newLevel}`); // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel if (newLevel !== -1) { newLevel = Math.max(newLevel, this.minAutoLevel); } this.levelController.startLevel = newLevel; } /** * Whether level capping is enabled. * Default value is set via `config.capLevelToPlayerSize`. */ get capLevelToPlayerSize() { return this.config.capLevelToPlayerSize; } /** * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called. */ set capLevelToPlayerSize(shouldStartCapping) { const newCapLevelToPlayerSize = !!shouldStartCapping; if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) { if (newCapLevelToPlayerSize) { this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size. } else { this.capLevelController.stopCapping(); this.autoLevelCapping = -1; this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap. } this.config.capLevelToPlayerSize = newCapLevelToPlayerSize; } } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ get autoLevelCapping() { return this._autoLevelCapping; } /** * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned. */ get bandwidthEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimate(); } /** * get time to first byte estimate * @type {number} */ get ttfbEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimateTTFB(); } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ set autoLevelCapping(newLevel) { if (this._autoLevelCapping !== newLevel) { logger.log(`set autoLevelCapping:${newLevel}`); this._autoLevelCapping = newLevel; } } get maxHdcpLevel() { return this._maxHdcpLevel; } set maxHdcpLevel(value) { if (HdcpLevels.indexOf(value) > -1) { this._maxHdcpLevel = value; } } /** * True when automatic level selection enabled */ get autoLevelEnabled() { return this.levelController.manualLevel === -1; } /** * Level set manually (if any) */ get manualLevel() { return this.levelController.manualLevel; } /** * min level selectable in auto mode according to config.minAutoBitrate */ get minAutoLevel() { const { levels, config: { minAutoBitrate } } = this; if (!levels) return 0; const len = levels.length; for (let i = 0; i < len; i++) { if (levels[i].maxBitrate >= minAutoBitrate) { return i; } } return 0; } /** * max level selectable in auto mode according to autoLevelCapping */ get maxAutoLevel() { const { levels, autoLevelCapping, maxHdcpLevel } = this; let maxAutoLevel; if (autoLevelCapping === -1 && levels && levels.length) { maxAutoLevel = levels.length - 1; } else { maxAutoLevel = autoLevelCapping; } if (maxHdcpLevel) { for (let i = maxAutoLevel; i--;) { const hdcpLevel = levels[i].attrs['HDCP-LEVEL']; if (hdcpLevel && hdcpLevel <= maxHdcpLevel) { return i; } } } return maxAutoLevel; } /** * next automatically selected quality level */ get nextAutoLevel() { // ensure next auto level is between min and max auto level return Math.min(Math.max(this.abrController.nextAutoLevel, this.minAutoLevel), this.maxAutoLevel); } /** * this setter is used to force next auto level. * this is useful to force a switch down in auto mode: * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example) * forced value is valid for one fragment. upon successful frag loading at forced level, * this value will be resetted to -1 by ABR controller. */ set nextAutoLevel(nextLevel) { this.abrController.nextAutoLevel = Math.max(this.minAutoLevel, nextLevel); } /** * get the datetime value relative to media.currentTime for the active level Program Date Time if present */ get playingDate() { return this.streamController.currentProgramDateTime; } get mainForwardBufferInfo() { return this.streamController.getMainFwdBufferInfo(); } /** * Get the list of selectable audio tracks */ get audioTracks() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTracks : []; } /** * index of the selected audio track (index in audio track lists) */ get audioTrack() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTrack : -1; } /** * selects an audio track, based on its index in audio track lists */ set audioTrack(audioTrackId) { const audioTrackController = this.audioTrackController; if (audioTrackController) { audioTrackController.audioTrack = audioTrackId; } } /** * get alternate subtitle tracks list from playlist */ get subtitleTracks() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTracks : []; } /** * index of the selected subtitle track (index in subtitle track lists) */ get subtitleTrack() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1; } get media() { return this._media; } /** * select an subtitle track, based on its index in subtitle track lists */ set subtitleTrack(subtitleTrackId) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleTrack = subtitleTrackId; } } /** * Whether subtitle display is enabled or not */ get subtitleDisplay() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false; } /** * Enable/disable subtitle display rendering */ set subtitleDisplay(value) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleDisplay = value; } } /** * get mode for Low-Latency HLS loading */ get lowLatencyMode() { return this.config.lowLatencyMode; } /** * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK. */ set lowLatencyMode(mode) { this.config.lowLatencyMode = mode; } /** * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```) * @returns null prior to loading live Playlist */ get liveSyncPosition() { return this.latencyController.liveSyncPosition; } /** * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced) * @returns 0 before first playlist is loaded */ get latency() { return this.latencyController.latency; } /** * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition``` * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration``` * @returns 0 before first playlist is loaded */ get maxLatency() { return this.latencyController.maxLatency; } /** * target distance from the edge as calculated by the latency controller */ get targetLatency() { return this.latencyController.targetLatency; } /** * the rate at which the edge of the current live playlist is advancing or 1 if there is none */ get drift() { return this.latencyController.drift; } /** * set to true when startLoad is called before MANIFEST_PARSED event */ get forceStartLoad() { return this.streamController.forceStartLoad; } } Hls.defaultConfig = void 0; export { Hls as default }; //# sourceMappingURL=hls.js.map ================================================ FILE: public/assets/lib/vendor/leaflet/Control.Draw.js ================================================ /** * @class L.Control.Draw * @aka L.Draw */ L.Control.Draw = L.Control.extend({ // Options options: { position: 'topleft', draw: {}, edit: false }, // @method initialize(): void // Initializes draw control, toolbars from the options initialize: function (options) { if (L.version < '0.7') { throw new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/'); } L.Control.prototype.initialize.call(this, options); var toolbar; this._toolbars = {}; // Initialize toolbars if (L.DrawToolbar && this.options.draw) { toolbar = new L.DrawToolbar(this.options.draw); this._toolbars[L.DrawToolbar.TYPE] = toolbar; // Listen for when toolbar is enabled this._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this); } if (L.EditToolbar && this.options.edit) { toolbar = new L.EditToolbar(this.options.edit); this._toolbars[L.EditToolbar.TYPE] = toolbar; // Listen for when toolbar is enabled this._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this); } L.toolbar = this; //set global var for editing the toolbar }, // @method onAdd(): container // Adds the toolbar container to the map onAdd: function (map) { var container = L.DomUtil.create('div', 'leaflet-draw'), addedTopClass = false, topClassName = 'leaflet-draw-toolbar-top', toolbarContainer; for (var toolbarId in this._toolbars) { if (this._toolbars.hasOwnProperty(toolbarId)) { toolbarContainer = this._toolbars[toolbarId].addToolbar(map); if (toolbarContainer) { // Add class to the first toolbar to remove the margin if (!addedTopClass) { if (!L.DomUtil.hasClass(toolbarContainer, topClassName)) { L.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName); } addedTopClass = true; } container.appendChild(toolbarContainer); } } } return container; }, // @method onRemove(): void // Removes the toolbars from the map toolbar container onRemove: function () { for (var toolbarId in this._toolbars) { if (this._toolbars.hasOwnProperty(toolbarId)) { this._toolbars[toolbarId].removeToolbar(); } } }, // @method setDrawingOptions(options): void // Sets options to all toolbar instances setDrawingOptions: function (options) { for (var toolbarId in this._toolbars) { if (this._toolbars[toolbarId] instanceof L.DrawToolbar) { this._toolbars[toolbarId].setOptions(options); } } }, _toolbarEnabled: function (e) { var enabledToolbar = e.target; for (var toolbarId in this._toolbars) { if (this._toolbars[toolbarId] !== enabledToolbar) { this._toolbars[toolbarId].disable(); } } } }); L.Map.mergeOptions({ drawControlTooltips: true, drawControl: false }); L.Map.addInitHook(function () { if (this.options.drawControl) { this.drawControl = new L.Control.Draw(); this.addControl(this.drawControl); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/Leaflet.Draw.Event.js ================================================ /** * ### Events * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different * actions users can initiate. The following events will be triggered on the map: * * @class L.Draw.Event * @aka Draw.Event * * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct. * * @example * ```js * map.on(L.Draw.Event.CREATED; function (e) { * var type = e.layerType, * layer = e.layer; * * if (type === 'marker') { * // Do marker specific actions * } * * // Do whatever else you need to. (save to db; add to map etc) * map.addLayer(layer); *}); * ``` */ L.Draw.Event = {}; /** * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String * * Layer that was just created. * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker` * Triggered when a new vector or marker has been created. * */ L.Draw.Event.CREATED = 'draw:created'; /** * @event draw:edited: LayerGroup * * List of all layers just edited on the map. * * * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved. * * @example * ```js * map.on('draw:edited', function (e) { * var layers = e.layers; * layers.eachLayer(function (layer) { * //do whatever you want; most likely save back to db * }); * }); * ``` */ L.Draw.Event.EDITED = 'draw:edited'; /** * @event draw:deleted: LayerGroup * * List of all layers just removed from the map. * * Triggered when layers have been removed (and saved) from the FeatureGroup. */ L.Draw.Event.DELETED = 'draw:deleted'; /** * @event draw:drawstart: String * * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker` * * Triggered when the user has chosen to draw a particular vector or marker. */ L.Draw.Event.DRAWSTART = 'draw:drawstart'; /** * @event draw:drawstop: String * * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker` * * Triggered when the user has finished a particular vector or marker. */ L.Draw.Event.DRAWSTOP = 'draw:drawstop'; /** * @event draw:drawvertex: LayerGroup * * List of all layers just being added from the map. * * Triggered when a vertex is created on a polyline or polygon. */ L.Draw.Event.DRAWVERTEX = 'draw:drawvertex'; /** * @event draw:editstart: String * * The type of edit this is. One of: `edit` * * Triggered when the user starts edit mode by clicking the edit tool button. */ L.Draw.Event.EDITSTART = 'draw:editstart'; /** * @event draw:editmove: ILayer * * Layer that was just moved. * * Triggered as the user moves a rectangle; circle or marker. */ L.Draw.Event.EDITMOVE = 'draw:editmove'; /** * @event draw:editresize: ILayer * * Layer that was just moved. * * Triggered as the user resizes a rectangle or circle. */ L.Draw.Event.EDITRESIZE = 'draw:editresize'; /** * @event draw:editvertex: LayerGroup * * List of all layers just being edited from the map. * * Triggered when a vertex is edited on a polyline or polygon. */ L.Draw.Event.EDITVERTEX = 'draw:editvertex'; /** * @event draw:editstop: String * * The type of edit this is. One of: `edit` * * Triggered when the user has finshed editing (edit mode) and saves edits. */ L.Draw.Event.EDITSTOP = 'draw:editstop'; /** * @event draw:deletestart: String * * The type of edit this is. One of: `remove` * * Triggered when the user starts remove mode by clicking the remove tool button. */ L.Draw.Event.DELETESTART = 'draw:deletestart'; /** * @event draw:deletestop: String * * The type of edit this is. One of: `remove` * * Triggered when the user has finished removing shapes (remove mode) and saves. */ L.Draw.Event.DELETESTOP = 'draw:deletestop'; /** * @event draw:toolbaropened: String * * Triggered when a toolbar is opened. */ L.Draw.Event.TOOLBAROPENED = 'draw:toolbaropened'; /** * @event draw:toolbarclosed: String * * Triggered when a toolbar is closed. */ L.Draw.Event.TOOLBARCLOSED = 'draw:toolbarclosed'; /** * @event draw:markercontext: String * * Triggered when a marker is right clicked. */ L.Draw.Event.MARKERCONTEXT = 'draw:markercontext'; ================================================ FILE: public/assets/lib/vendor/leaflet/Leaflet.draw.js ================================================ /** * Leaflet.draw assumes that you have already included the Leaflet library. */ L.drawVersion = '0.4.2'; /** * @class L.Draw * @aka Draw * * * To add the draw toolbar set the option drawControl: true in the map options. * * @example * ```js * var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13); * * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { * attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' * }).addTo(map); * ``` * * ### Adding the edit toolbar * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map. * * ```js * var map = L.map('map').setView([51.505, -0.09], 13); * * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { * attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' * }).addTo(map); * * // FeatureGroup is to store editable layers * var drawnItems = new L.FeatureGroup(); * map.addLayer(drawnItems); * * var drawControl = new L.Control.Draw({ * edit: { * featureGroup: drawnItems * } * }); * map.addControl(drawControl); * ``` * * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon. * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon, * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons). */ L.Draw = {}; /** * @class L.drawLocal * @aka L.drawLocal * * The core toolbar class of the API — it is used to create the toolbar ui * * @example * ```js * var modifiedDraw = L.drawLocal.extend({ * draw: { * toolbar: { * buttons: { * polygon: 'Draw an awesome polygon' * } * } * } * }); * ``` * * The default state for the control is the draw toolbar just below the zoom control. * This will allow map users to draw vectors and markers. * **Please note the edit toolbar is not enabled by default.** */ L.drawLocal = { // format: { // numeric: { // delimiters: { // thousands: ',', // decimal: '.' // } // } // }, draw: { toolbar: { // #TODO: this should be reorganized where actions are nested in actions // ex: actions.undo or actions.cancel actions: { title: 'Cancel drawing', text: 'Cancel' }, finish: { title: 'Finish drawing', text: 'Finish' }, undo: { title: 'Delete last point drawn', text: 'Delete last point' }, buttons: { polyline: 'Draw a polyline', polygon: 'Draw a polygon', rectangle: 'Draw a rectangle', circle: 'Draw a circle', marker: 'Draw a marker', circlemarker: 'Draw a circlemarker' } }, handlers: { circle: { tooltip: { start: 'Click and drag to draw circle.' }, radius: 'Radius' }, circlemarker: { tooltip: { start: 'Click map to place circle marker.' } }, marker: { tooltip: { start: 'Click map to place marker.' } }, polygon: { tooltip: { start: 'Click to start drawing shape.', cont: 'Click to continue drawing shape.', end: 'Click first point to close this shape.' } }, polyline: { error: '<strong>Error:</strong> shape edges cannot cross!', tooltip: { start: 'Click to start drawing line.', cont: 'Click to continue drawing line.', end: 'Click last point to finish line.' } }, rectangle: { tooltip: { start: 'Click and drag to draw rectangle.' } }, simpleshape: { tooltip: { end: 'Release mouse to finish drawing.' } } } }, edit: { toolbar: { actions: { save: { title: 'Save changes', text: 'Save' }, cancel: { title: 'Cancel editing, discards all changes', text: 'Cancel' }, clearAll: { title: 'Clear all layers', text: 'Clear All' } }, buttons: { edit: 'Edit layers', editDisabled: 'No layers to edit', remove: 'Delete layers', removeDisabled: 'No layers to delete' } }, handlers: { edit: { tooltip: { text: 'Drag handles or markers to edit features.', subtext: 'Click cancel to undo changes.' } }, remove: { tooltip: { text: 'Click on a feature to remove.' } } } } }; ================================================ FILE: public/assets/lib/vendor/leaflet/Toolbar.js ================================================ /** * @class L.Draw.Toolbar * @aka Toolbar * * The toolbar class of the API — it is used to create the ui * This will be depreciated * * @example * * ```js * var toolbar = L.Toolbar(); * toolbar.addToolbar(map); * ``` * * ### Disabling a toolbar * * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false. * * ```js * var drawControl = new L.Control.Draw({ * draw: false, * edit: { * featureGroup: editableLayers * } * }); * ``` * * ### Disabling a toolbar item * * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and * markers. It also turns off the ability to edit layers. * * ```js * var drawControl = new L.Control.Draw({ * draw: { * polygon: false, * marker: false * }, * edit: { * featureGroup: editableLayers, * edit: false * } * }); * ``` */ L.Toolbar = L.Class.extend({ // @section Methods for modifying the toolbar // @method initialize(options): void // Toolbar constructor initialize: function (options) { L.setOptions(this, options); this._modes = {}; this._actionButtons = []; this._activeMode = null; var version = L.version.split('.'); //If Version is >= 1.2.0 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) { L.Toolbar.include(L.Evented.prototype); } else { L.Toolbar.include(L.Mixin.Events); } }, // @method enabled(): boolean // Gets a true/false of whether the toolbar is enabled enabled: function () { return this._activeMode !== null; }, // @method disable(): void // Disables the toolbar disable: function () { if (!this.enabled()) { return; } this._activeMode.handler.disable(); }, // @method addToolbar(map): L.DomUtil // Adds the toolbar to the map and returns the toolbar dom element addToolbar: function (map) { var container = L.DomUtil.create('div', 'leaflet-draw-section'), buttonIndex = 0, buttonClassPrefix = this._toolbarClass || '', modeHandlers = this.getModeHandlers(map), i; this._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar'); this._map = map; for (i = 0; i < modeHandlers.length; i++) { if (modeHandlers[i].enabled) { this._initModeHandler( modeHandlers[i].handler, this._toolbarContainer, buttonIndex++, buttonClassPrefix, modeHandlers[i].title ); } } // if no buttons were added, do not add the toolbar if (!buttonIndex) { return; } // Save button index of the last button, -1 as we would have ++ after the last button this._lastButtonIndex = --buttonIndex; // Create empty actions part of the toolbar this._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions'); // Add draw and cancel containers to the control container container.appendChild(this._toolbarContainer); container.appendChild(this._actionsContainer); return container; }, // @method removeToolbar(): void // Removes the toolbar and drops the handler event listeners removeToolbar: function () { // Dispose each handler for (var handlerId in this._modes) { if (this._modes.hasOwnProperty(handlerId)) { // Unbind handler button this._disposeButton( this._modes[handlerId].button, this._modes[handlerId].handler.enable, this._modes[handlerId].handler ); // Make sure is disabled this._modes[handlerId].handler.disable(); // Unbind handler this._modes[handlerId].handler .off('enabled', this._handlerActivated, this) .off('disabled', this._handlerDeactivated, this); } } this._modes = {}; // Dispose the actions toolbar for (var i = 0, l = this._actionButtons.length; i < l; i++) { this._disposeButton( this._actionButtons[i].button, this._actionButtons[i].callback, this ); } this._actionButtons = []; this._actionsContainer = null; }, _initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) { var type = handler.type; this._modes[type] = {}; this._modes[type].handler = handler; this._modes[type].button = this._createButton({ type: type, title: buttonTitle, className: classNamePredix + '-' + type, container: container, callback: this._modes[type].handler.enable, context: this._modes[type].handler }); this._modes[type].buttonIndex = buttonIndex; this._modes[type].handler .on('enabled', this._handlerActivated, this) .on('disabled', this._handlerDeactivated, this); }, /* Detect iOS based on browser User Agent, based on: * http://stackoverflow.com/a/9039885 */ _detectIOS: function () { var iOS = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream); return iOS; }, _createButton: function (options) { var link = L.DomUtil.create('a', options.className || '', options.container); // Screen reader tag var sr = L.DomUtil.create('span', 'sr-only', options.container); link.href = '#'; link.appendChild(sr); if (options.title) { link.title = options.title; sr.innerHTML = options.title; } if (options.text) { link.innerHTML = options.text; sr.innerHTML = options.text; } /* iOS does not use click events */ var buttonEvent = this._detectIOS() ? 'touchstart' : 'click'; L.DomEvent .on(link, 'click', L.DomEvent.stopPropagation) .on(link, 'mousedown', L.DomEvent.stopPropagation) .on(link, 'dblclick', L.DomEvent.stopPropagation) .on(link, 'touchstart', L.DomEvent.stopPropagation) .on(link, 'click', L.DomEvent.preventDefault) .on(link, buttonEvent, options.callback, options.context); return link; }, _disposeButton: function (button, callback) { /* iOS does not use click events */ var buttonEvent = this._detectIOS() ? 'touchstart' : 'click'; L.DomEvent .off(button, 'click', L.DomEvent.stopPropagation) .off(button, 'mousedown', L.DomEvent.stopPropagation) .off(button, 'dblclick', L.DomEvent.stopPropagation) .off(button, 'touchstart', L.DomEvent.stopPropagation) .off(button, 'click', L.DomEvent.preventDefault) .off(button, buttonEvent, callback); }, _handlerActivated: function (e) { // Disable active mode (if present) this.disable(); // Cache new active feature this._activeMode = this._modes[e.handler]; L.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled'); this._showActionsToolbar(); this.fire('enable'); }, _handlerDeactivated: function () { this._hideActionsToolbar(); L.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled'); this._activeMode = null; this.fire('disable'); }, _createActions: function (handler) { var container = this._actionsContainer, buttons = this.getActions(handler), l = buttons.length, li, di, dl, button; // Dispose the actions toolbar (todo: dispose only not used buttons) for (di = 0, dl = this._actionButtons.length; di < dl; di++) { this._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback); } this._actionButtons = []; // Remove all old buttons while (container.firstChild) { container.removeChild(container.firstChild); } for (var i = 0; i < l; i++) { if ('enabled' in buttons[i] && !buttons[i].enabled) { continue; } li = L.DomUtil.create('li', '', container); button = this._createButton({ title: buttons[i].title, text: buttons[i].text, container: li, callback: buttons[i].callback, context: buttons[i].context }); this._actionButtons.push({ button: button, callback: buttons[i].callback }); } }, _showActionsToolbar: function () { var buttonIndex = this._activeMode.buttonIndex, lastButtonIndex = this._lastButtonIndex, toolbarPosition = this._activeMode.button.offsetTop - 1; // Recreate action buttons on every click this._createActions(this._activeMode.handler); // Correctly position the cancel button this._actionsContainer.style.top = toolbarPosition + 'px'; if (buttonIndex === 0) { L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop'); L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top'); } if (buttonIndex === lastButtonIndex) { L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom'); L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom'); } this._actionsContainer.style.display = 'block'; this._map.fire(L.Draw.Event.TOOLBAROPENED); }, _hideActionsToolbar: function () { this._actionsContainer.style.display = 'none'; L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop'); L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom'); L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top'); L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom'); this._map.fire(L.Draw.Event.TOOLBARCLOSED); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/Tooltip.js ================================================ L.Draw = L.Draw || {}; /** * @class L.Draw.Tooltip * @aka Tooltip * * The tooltip class — it is used to display the tooltip while drawing * This will be depreciated * * @example * * ```js * var tooltip = L.Draw.Tooltip(); * ``` * */ L.Draw.Tooltip = L.Class.extend({ // @section Methods for modifying draw state // @method initialize(map): void // Tooltip constructor initialize: function (map) { this._map = map; this._popupPane = map._panes.popupPane; this._visible = false; this._container = map.options.drawControlTooltips ? L.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null; this._singleLineLabel = false; this._map.on('mouseout', this._onMouseOut, this); }, // @method dispose(): void // Remove Tooltip DOM and unbind events dispose: function () { this._map.off('mouseout', this._onMouseOut, this); if (this._container) { this._popupPane.removeChild(this._container); this._container = null; } }, // @method updateContent(labelText): this // Changes the tooltip text to string in function call updateContent: function (labelText) { if (!this._container) { return this; } labelText.subtext = labelText.subtext || ''; // update the vertical position (only if changed) if (labelText.subtext.length === 0 && !this._singleLineLabel) { L.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single'); this._singleLineLabel = true; } else if (labelText.subtext.length > 0 && this._singleLineLabel) { L.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single'); this._singleLineLabel = false; } this._container.innerHTML = (labelText.subtext.length > 0 ? '<span class="leaflet-draw-tooltip-subtext">' + labelText.subtext + '</span>' + '<br />' : '') + '<span>' + labelText.text + '</span>'; if (!labelText.text && !labelText.subtext) { this._visible = false; this._container.style.visibility = 'hidden'; } else { this._visible = true; this._container.style.visibility = 'inherit'; } return this; }, // @method updatePosition(latlng): this // Changes the location of the tooltip updatePosition: function (latlng) { var pos = this._map.latLngToLayerPoint(latlng), tooltipContainer = this._container; if (this._container) { if (this._visible) { tooltipContainer.style.visibility = 'inherit'; } L.DomUtil.setPosition(tooltipContainer, pos); } return this; }, // @method showAsError(): this // Applies error class to tooltip showAsError: function () { if (this._container) { L.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip'); } return this; }, // @method removeError(): this // Removes the error class from the tooltip removeError: function () { if (this._container) { L.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip'); } return this; }, _onMouseOut: function () { if (this._container) { this._container.style.visibility = 'hidden'; } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/DrawToolbar.js ================================================ /** * @class L.DrawToolbar * @aka Toolbar */ L.DrawToolbar = L.Toolbar.extend({ statics: { TYPE: 'draw' }, options: { polyline: {}, polygon: {}, rectangle: {}, circle: {}, marker: {}, circlemarker: {} }, // @method initialize(): void initialize: function (options) { // Ensure that the options are merged correctly since L.extend is only shallow for (var type in this.options) { if (this.options.hasOwnProperty(type)) { if (options[type]) { options[type] = L.extend({}, this.options[type], options[type]); } } } this._toolbarClass = 'leaflet-draw-draw'; L.Toolbar.prototype.initialize.call(this, options); }, // @method getModeHandlers(): object // Get mode handlers information getModeHandlers: function (map) { return [ { enabled: this.options.polyline, handler: new L.Draw.Polyline(map, this.options.polyline), title: L.drawLocal.draw.toolbar.buttons.polyline }, { enabled: this.options.polygon, handler: new L.Draw.Polygon(map, this.options.polygon), title: L.drawLocal.draw.toolbar.buttons.polygon }, { enabled: this.options.rectangle, handler: new L.Draw.Rectangle(map, this.options.rectangle), title: L.drawLocal.draw.toolbar.buttons.rectangle }, { enabled: this.options.circle, handler: new L.Draw.Circle(map, this.options.circle), title: L.drawLocal.draw.toolbar.buttons.circle }, { enabled: this.options.marker, handler: new L.Draw.Marker(map, this.options.marker), title: L.drawLocal.draw.toolbar.buttons.marker }, { enabled: this.options.circlemarker, handler: new L.Draw.CircleMarker(map, this.options.circlemarker), title: L.drawLocal.draw.toolbar.buttons.circlemarker } ]; }, // @method getActions(): object // Get action information getActions: function (handler) { return [ { enabled: handler.completeShape, title: L.drawLocal.draw.toolbar.finish.title, text: L.drawLocal.draw.toolbar.finish.text, callback: handler.completeShape, context: handler }, { enabled: handler.deleteLastVertex, title: L.drawLocal.draw.toolbar.undo.title, text: L.drawLocal.draw.toolbar.undo.text, callback: handler.deleteLastVertex, context: handler }, { title: L.drawLocal.draw.toolbar.actions.title, text: L.drawLocal.draw.toolbar.actions.text, callback: this.disable, context: this } ]; }, // @method setOptions(): void // Sets the options to the toolbar setOptions: function (options) { L.setOptions(this, options); for (var type in this._modes) { if (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) { this._modes[type].handler.setOptions(options[type]); } } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Circle.js ================================================ /** * @class L.Draw.Circle * @aka Draw.Circle * @inherits L.Draw.SimpleShape */ L.Draw.Circle = L.Draw.SimpleShape.extend({ statics: { TYPE: 'circle' }, options: { shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, fillColor: null, //same as color by default fillOpacity: 0.2, clickable: true }, showRadius: true, metric: true, // Whether to use the metric measurement system or imperial feet: true, // When not metric, use feet instead of yards for display nautic: false // When not metric, not feet use nautic mile for display }, // @method initialize(): void initialize: function (map, options) { // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.Circle.TYPE; this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start; L.Draw.SimpleShape.prototype.initialize.call(this, map, options); }, _drawShape: function (latlng) { // Calculate the distance based on the version if (L.GeometryUtil.isVersion07x()) { var distance = this._startLatLng.distanceTo(latlng); } else { var distance = this._map.distance(this._startLatLng, latlng); } if (!this._shape) { this._shape = new L.Circle(this._startLatLng, distance, this.options.shapeOptions); this._map.addLayer(this._shape); } else { this._shape.setRadius(distance); } }, _fireCreatedEvent: function () { var circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions); L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle); }, _onMouseMove: function (e) { var latlng = e.latlng, showRadius = this.options.showRadius, useMetric = this.options.metric, radius; this._tooltip.updatePosition(latlng); if (this._isDrawing) { this._drawShape(latlng); // Get the new radius (rounded to 1 dp) radius = this._shape.getRadius().toFixed(1); var subtext = ''; if (showRadius) { subtext = L.drawLocal.draw.handlers.circle.radius + ': ' + L.GeometryUtil.readableDistance(radius, useMetric, this.options.feet, this.options.nautic); } this._tooltip.updateContent({ text: this._endLabelText, subtext: subtext }); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.CircleMarker.js ================================================ /** * @class L.Draw.CircleMarker * @aka Draw.CircleMarker * @inherits L.Draw.Marker */ L.Draw.CircleMarker = L.Draw.Marker.extend({ statics: { TYPE: 'circlemarker' }, options: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, fillColor: null, //same as color by default fillOpacity: 0.2, clickable: true, zIndexOffset: 2000 // This should be > than the highest z-index any markers }, // @method initialize(): void initialize: function (map, options) { // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.CircleMarker.TYPE; this._initialLabelText = L.drawLocal.draw.handlers.circlemarker.tooltip.start; L.Draw.Feature.prototype.initialize.call(this, map, options); }, _fireCreatedEvent: function () { var circleMarker = new L.CircleMarker(this._marker.getLatLng(), this.options); L.Draw.Feature.prototype._fireCreatedEvent.call(this, circleMarker); }, _createMarker: function (latlng) { return new L.CircleMarker(latlng, this.options); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Feature.js ================================================ L.Draw = L.Draw || {}; /** * @class L.Draw.Feature * @aka Draw.Feature */ L.Draw.Feature = L.Handler.extend({ // @method initialize(): void initialize: function (map, options) { this._map = map; this._container = map._container; this._overlayPane = map._panes.overlayPane; this._popupPane = map._panes.popupPane; // Merge default shapeOptions options with custom shapeOptions if (options && options.shapeOptions) { options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions); } L.setOptions(this, options); var version = L.version.split('.'); //If Version is >= 1.2.0 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) { L.Draw.Feature.include(L.Evented.prototype); } else { L.Draw.Feature.include(L.Mixin.Events); } }, // @method enable(): void // Enables this handler enable: function () { if (this._enabled) { return; } L.Handler.prototype.enable.call(this); this.fire('enabled', {handler: this.type}); this._map.fire(L.Draw.Event.DRAWSTART, {layerType: this.type}); }, // @method disable(): void disable: function () { if (!this._enabled) { return; } L.Handler.prototype.disable.call(this); this._map.fire(L.Draw.Event.DRAWSTOP, {layerType: this.type}); this.fire('disabled', {handler: this.type}); }, // @method addHooks(): void // Add's event listeners to this handler addHooks: function () { var map = this._map; if (map) { L.DomUtil.disableTextSelection(); map.getContainer().focus(); this._tooltip = new L.Draw.Tooltip(this._map); L.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this); } }, // @method removeHooks(): void // Removes event listeners from this handler removeHooks: function () { if (this._map) { L.DomUtil.enableTextSelection(); this._tooltip.dispose(); this._tooltip = null; L.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this); } }, // @method setOptions(object): void // Sets new options to this handler setOptions: function (options) { L.setOptions(this, options); }, _fireCreatedEvent: function (layer) { this._map.fire(L.Draw.Event.CREATED, {layer: layer, layerType: this.type}); }, // Cancel drawing when the escape key is pressed _cancelDrawing: function (e) { if (e.keyCode === 27) { this._map.fire('draw:canceled', {layerType: this.type}); this.disable(); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Marker.js ================================================ /** * @class L.Draw.Marker * @aka Draw.Marker * @inherits L.Draw.Feature */ L.Draw.Marker = L.Draw.Feature.extend({ statics: { TYPE: 'marker' }, options: { icon: new L.Icon.Default(), repeatMode: false, zIndexOffset: 2000 // This should be > than the highest z-index any markers }, // @method initialize(): void initialize: function (map, options) { // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.Marker.TYPE; this._initialLabelText = L.drawLocal.draw.handlers.marker.tooltip.start; L.Draw.Feature.prototype.initialize.call(this, map, options); }, // @method addHooks(): void // Add listener hooks to this handler. addHooks: function () { L.Draw.Feature.prototype.addHooks.call(this); if (this._map) { this._tooltip.updateContent({text: this._initialLabelText}); // Same mouseMarker as in Draw.Polyline if (!this._mouseMarker) { this._mouseMarker = L.marker(this._map.getCenter(), { icon: L.divIcon({ className: 'leaflet-mouse-marker', iconAnchor: [20, 20], iconSize: [40, 40] }), opacity: 0, zIndexOffset: this.options.zIndexOffset }); } this._mouseMarker .on('click', this._onClick, this) .addTo(this._map); this._map.on('mousemove', this._onMouseMove, this); this._map.on('click', this._onTouch, this); } }, // @method removeHooks(): void // Remove listener hooks from this handler. removeHooks: function () { L.Draw.Feature.prototype.removeHooks.call(this); if (this._map) { this._map .off('click', this._onClick, this) .off('click', this._onTouch, this); if (this._marker) { this._marker.off('click', this._onClick, this); this._map .removeLayer(this._marker); delete this._marker; } this._mouseMarker.off('click', this._onClick, this); this._map.removeLayer(this._mouseMarker); delete this._mouseMarker; this._map.off('mousemove', this._onMouseMove, this); } }, _onMouseMove: function (e) { var latlng = e.latlng; this._tooltip.updatePosition(latlng); this._mouseMarker.setLatLng(latlng); if (!this._marker) { this._marker = this._createMarker(latlng); // Bind to both marker and map to make sure we get the click event. this._marker.on('click', this._onClick, this); this._map .on('click', this._onClick, this) .addLayer(this._marker); } else { latlng = this._mouseMarker.getLatLng(); this._marker.setLatLng(latlng); } }, _createMarker: function (latlng) { return new L.Marker(latlng, { icon: this.options.icon, zIndexOffset: this.options.zIndexOffset }); }, _onClick: function () { this._fireCreatedEvent(); this.disable(); if (this.options.repeatMode) { this.enable(); } }, _onTouch: function (e) { // called on click & tap, only really does any thing on tap this._onMouseMove(e); // creates & places marker this._onClick(); // permanently places marker & ends interaction }, _fireCreatedEvent: function () { var marker = new L.Marker.Touch(this._marker.getLatLng(), {icon: this.options.icon}); L.Draw.Feature.prototype._fireCreatedEvent.call(this, marker); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Polygon.js ================================================ /** * @class L.Draw.Polygon * @aka Draw.Polygon * @inherits L.Draw.Polyline */ L.Draw.Polygon = L.Draw.Polyline.extend({ statics: { TYPE: 'polygon' }, Poly: L.Polygon, options: { showArea: false, showLength: false, shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, fillColor: null, //same as color by default fillOpacity: 0.2, clickable: true }, // Whether to use the metric measurement system (truthy) or not (falsy). // Also defines the units to use for the metric system as an array of // strings (e.g. `['ha', 'm']`). metric: true, feet: true, // When not metric, to use feet instead of yards for display. nautic: false, // When not metric, not feet use nautic mile for display // Defines the precision for each type of unit (e.g. {km: 2, ft: 0} precision: {} }, // @method initialize(): void initialize: function (map, options) { L.Draw.Polyline.prototype.initialize.call(this, map, options); // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.Polygon.TYPE; }, _updateFinishHandler: function () { var markerCount = this._markers.length; // The first marker should have a click handler to close the polygon if (markerCount === 1) { this._markers[0].on('click', this._finishShape, this); } // Add and update the double click handler if (markerCount > 2) { this._markers[markerCount - 1].on('dblclick', this._finishShape, this); // Only need to remove handler if has been added before if (markerCount > 3) { this._markers[markerCount - 2].off('dblclick', this._finishShape, this); } } }, _getTooltipText: function () { var text, subtext; if (this._markers.length === 0) { text = L.drawLocal.draw.handlers.polygon.tooltip.start; } else if (this._markers.length < 3) { text = L.drawLocal.draw.handlers.polygon.tooltip.cont; subtext = this._getMeasurementString(); } else { text = L.drawLocal.draw.handlers.polygon.tooltip.end; subtext = this._getMeasurementString(); } return { text: text, subtext: subtext }; }, _getMeasurementString: function () { var area = this._area, measurementString = ''; if (!area && !this.options.showLength) { return null; } if (this.options.showLength) { measurementString = L.Draw.Polyline.prototype._getMeasurementString.call(this); } if (area) { measurementString += '<br>' + L.GeometryUtil.readableArea(area, this.options.metric, this.options.precision); } return measurementString; }, _shapeIsValid: function () { return this._markers.length >= 3; }, _vertexChanged: function (latlng, added) { var latLngs; // Check to see if we should show the area if (!this.options.allowIntersection && this.options.showArea) { latLngs = this._poly.getLatLngs(); this._area = L.GeometryUtil.geodesicArea(latLngs); } L.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added); }, _cleanUpShape: function () { var markerCount = this._markers.length; if (markerCount > 0) { this._markers[0].off('click', this._finishShape, this); if (markerCount > 2) { this._markers[markerCount - 1].off('dblclick', this._finishShape, this); } } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Polyline.js ================================================ /** * @class L.Draw.Polyline * @aka Draw.Polyline * @inherits L.Draw.Feature */ L.Draw.Polyline = L.Draw.Feature.extend({ statics: { TYPE: 'polyline' }, Poly: L.Polyline, options: { allowIntersection: true, repeatMode: false, drawError: { color: '#b00b00', timeout: 2500 }, icon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: 'leaflet-div-icon leaflet-editing-icon' }), touchIcon: new L.DivIcon({ iconSize: new L.Point(20, 20), className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon' }), guidelineDistance: 20, maxGuideLineLength: 4000, shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: false, clickable: true }, metric: true, // Whether to use the metric measurement system or imperial feet: true, // When not metric, to use feet instead of yards for display. nautic: false, // When not metric, not feet use nautic mile for display showLength: true, // Whether to display distance in the tooltip zIndexOffset: 2000, // This should be > than the highest z-index any map layers factor: 1, // To change distance calculation maxPoints: 0 // Once this number of points are placed, finish shape }, // @method initialize(): void initialize: function (map, options) { // if touch, switch to touch icon if (L.Browser.touch) { this.options.icon = this.options.touchIcon; } // Need to set this here to ensure the correct message is used. this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error; // Merge default drawError options with custom options if (options && options.drawError) { options.drawError = L.Util.extend({}, this.options.drawError, options.drawError); } // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.Polyline.TYPE; L.Draw.Feature.prototype.initialize.call(this, map, options); }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { L.Draw.Feature.prototype.addHooks.call(this); if (this._map) { this._markers = []; this._markerGroup = new L.LayerGroup(); this._map.addLayer(this._markerGroup); this._poly = new L.Polyline([], this.options.shapeOptions); this._tooltip.updateContent(this._getTooltipText()); // Make a transparent marker that will used to catch click events. These click // events will create the vertices. We need to do this so we can ensure that // we can create vertices over other map layers (markers, vector layers). We // also do not want to trigger any click handlers of objects we are clicking on // while drawing. if (!this._mouseMarker) { this._mouseMarker = L.marker(this._map.getCenter(), { icon: L.divIcon({ className: 'leaflet-mouse-marker', iconAnchor: [20, 20], iconSize: [40, 40] }), opacity: 0, zIndexOffset: this.options.zIndexOffset }); } this._mouseMarker .on('mouseout', this._onMouseOut, this) .on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter .on('mousedown', this._onMouseDown, this) .on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility .addTo(this._map); this._map .on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility .on('mousemove', this._onMouseMove, this) .on('zoomlevelschange', this._onZoomEnd, this) .on('touchstart', this._onTouch, this) .on('zoomend', this._onZoomEnd, this); } }, // @method removeHooks(): void // Remove listener hooks from this handler. removeHooks: function () { L.Draw.Feature.prototype.removeHooks.call(this); this._clearHideErrorTimeout(); this._cleanUpShape(); // remove markers from map this._map.removeLayer(this._markerGroup); delete this._markerGroup; delete this._markers; this._map.removeLayer(this._poly); delete this._poly; this._mouseMarker .off('mousedown', this._onMouseDown, this) .off('mouseout', this._onMouseOut, this) .off('mouseup', this._onMouseUp, this) .off('mousemove', this._onMouseMove, this); this._map.removeLayer(this._mouseMarker); delete this._mouseMarker; // clean up DOM this._clearGuides(); this._map .off('mouseup', this._onMouseUp, this) .off('mousemove', this._onMouseMove, this) .off('zoomlevelschange', this._onZoomEnd, this) .off('zoomend', this._onZoomEnd, this) .off('touchstart', this._onTouch, this) .off('click', this._onTouch, this); }, // @method deleteLastVertex(): void // Remove the last vertex from the polyline, removes polyline from map if only one point exists. deleteLastVertex: function () { if (this._markers.length <= 1) { return; } var lastMarker = this._markers.pop(), poly = this._poly, // Replaces .spliceLatLngs() latlngs = poly.getLatLngs(), latlng = latlngs.splice(-1, 1)[0]; this._poly.setLatLngs(latlngs); this._markerGroup.removeLayer(lastMarker); if (poly.getLatLngs().length < 2) { this._map.removeLayer(poly); } this._vertexChanged(latlng, false); }, // @method addVertex(): void // Add a vertex to the end of the polyline addVertex: function (latlng) { var markersLength = this._markers.length; // markersLength must be greater than or equal to 2 before intersections can occur if (markersLength >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) { this._showErrorTooltip(); return; } else if (this._errorShown) { this._hideErrorTooltip(); } this._markers.push(this._createMarker(latlng)); this._poly.addLatLng(latlng); if (this._poly.getLatLngs().length === 2) { this._map.addLayer(this._poly); } this._vertexChanged(latlng, true); }, // @method completeShape(): void // Closes the polyline between the first and last points completeShape: function () { if (this._markers.length <= 1 || !this._shapeIsValid()) { return; } this._fireCreatedEvent(); this.disable(); if (this.options.repeatMode) { this.enable(); } }, _finishShape: function () { var latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs(); var intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]); if ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) { this._showErrorTooltip(); return; } this._fireCreatedEvent(); this.disable(); if (this.options.repeatMode) { this.enable(); } }, // Called to verify the shape is valid when the user tries to finish it // Return false if the shape is not valid _shapeIsValid: function () { return true; }, _onZoomEnd: function () { if (this._markers !== null) { this._updateGuide(); } }, _onMouseMove: function (e) { var newPos = this._map.mouseEventToLayerPoint(e.originalEvent); var latlng = this._map.layerPointToLatLng(newPos); // Save latlng // should this be moved to _updateGuide() ? this._currentLatLng = latlng; this._updateTooltip(latlng); // Update the guide line this._updateGuide(newPos); // Update the mouse marker position this._mouseMarker.setLatLng(latlng); L.DomEvent.preventDefault(e.originalEvent); }, _vertexChanged: function (latlng, added) { this._map.fire(L.Draw.Event.DRAWVERTEX, {layers: this._markerGroup}); this._updateFinishHandler(); this._updateRunningMeasure(latlng, added); this._clearGuides(); this._updateTooltip(); }, _onMouseDown: function (e) { if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) { this._onMouseMove(e); this._clickHandled = true; this._disableNewMarkers(); var originalEvent = e.originalEvent; var clientX = originalEvent.clientX; var clientY = originalEvent.clientY; this._startPoint.call(this, clientX, clientY); } }, _startPoint: function (clientX, clientY) { this._mouseDownOrigin = L.point(clientX, clientY); }, _onMouseUp: function (e) { var originalEvent = e.originalEvent; var clientX = originalEvent.clientX; var clientY = originalEvent.clientY; this._endPoint.call(this, clientX, clientY, e); this._clickHandled = null; }, _endPoint: function (clientX, clientY, e) { if (this._mouseDownOrigin) { var dragCheckDistance = L.point(clientX, clientY) .distanceTo(this._mouseDownOrigin); var lastPtDistance = this._calculateFinishDistance(e.latlng); if (this.options.maxPoints > 1 && this.options.maxPoints == this._markers.length + 1) { this.addVertex(e.latlng); this._finishShape(); } else if (lastPtDistance < 10 && L.Browser.touch) { this._finishShape(); } else if (Math.abs(dragCheckDistance) < 9 * (window.devicePixelRatio || 1)) { this.addVertex(e.latlng); } this._enableNewMarkers(); // after a short pause, enable new markers } this._mouseDownOrigin = null; }, // ontouch prevented by clickHandled flag because some browsers fire both click/touch events, // causing unwanted behavior _onTouch: function (e) { var originalEvent = e.originalEvent; var clientX; var clientY; if (originalEvent.touches && originalEvent.touches[0] && !this._clickHandled && !this._touchHandled && !this._disableMarkers) { clientX = originalEvent.touches[0].clientX; clientY = originalEvent.touches[0].clientY; this._disableNewMarkers(); this._touchHandled = true; this._startPoint.call(this, clientX, clientY); this._endPoint.call(this, clientX, clientY, e); this._touchHandled = null; } this._clickHandled = null; }, _onMouseOut: function () { if (this._tooltip) { this._tooltip._onMouseOut.call(this._tooltip); } }, // calculate if we are currently within close enough distance // of the closing point (first point for shapes, last point for lines) // this is semi-ugly code but the only reliable way i found to get the job done // note: calculating point.distanceTo between mouseDownOrigin and last marker did NOT work _calculateFinishDistance: function (potentialLatLng) { var lastPtDistance; if (this._markers.length > 0) { var finishMarker; if (this.type === L.Draw.Polyline.TYPE) { finishMarker = this._markers[this._markers.length - 1]; } else if (this.type === L.Draw.Polygon.TYPE) { finishMarker = this._markers[0]; } else { return Infinity; } var lastMarkerPoint = this._map.latLngToContainerPoint(finishMarker.getLatLng()), potentialMarker = new L.Marker(potentialLatLng, { icon: this.options.icon, zIndexOffset: this.options.zIndexOffset * 2 }); var potentialMarkerPint = this._map.latLngToContainerPoint(potentialMarker.getLatLng()); lastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint); } else { lastPtDistance = Infinity; } return lastPtDistance; }, _updateFinishHandler: function () { var markerCount = this._markers.length; // The last marker should have a click handler to close the polyline if (markerCount > 1) { this._markers[markerCount - 1].on('click', this._finishShape, this); } // Remove the old marker click handler (as only the last point should close the polyline) if (markerCount > 2) { this._markers[markerCount - 2].off('click', this._finishShape, this); } }, _createMarker: function (latlng) { var marker = new L.Marker(latlng, { icon: this.options.icon, zIndexOffset: this.options.zIndexOffset * 2 }); this._markerGroup.addLayer(marker); return marker; }, _updateGuide: function (newPos) { var markerCount = this._markers ? this._markers.length : 0; if (markerCount > 0) { newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng); // draw the guide line this._clearGuides(); this._drawGuide( this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()), newPos ); } }, _updateTooltip: function (latLng) { var text = this._getTooltipText(); if (latLng) { this._tooltip.updatePosition(latLng); } if (!this._errorShown) { this._tooltip.updateContent(text); } }, _drawGuide: function (pointA, pointB) { var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))), guidelineDistance = this.options.guidelineDistance, maxGuideLineLength = this.options.maxGuideLineLength, // Only draw a guideline with a max length i = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance, fraction, dashPoint, dash; //create the guides container if we haven't yet if (!this._guidesContainer) { this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane); } //draw a dash every GuildeLineDistance for (; i < length; i += this.options.guidelineDistance) { //work out fraction along line we are fraction = i / length; //calculate new x,y point dashPoint = { x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)), y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y)) }; //add guide dash to guide container dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer); dash.style.backgroundColor = !this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color; L.DomUtil.setPosition(dash, dashPoint); } }, _updateGuideColor: function (color) { if (this._guidesContainer) { for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) { this._guidesContainer.childNodes[i].style.backgroundColor = color; } } }, // removes all child elements (guide dashes) from the guides container _clearGuides: function () { if (this._guidesContainer) { while (this._guidesContainer.firstChild) { this._guidesContainer.removeChild(this._guidesContainer.firstChild); } } }, _getTooltipText: function () { var showLength = this.options.showLength, labelText, distanceStr; if (this._markers.length === 0) { labelText = { text: L.drawLocal.draw.handlers.polyline.tooltip.start }; } else { distanceStr = showLength ? this._getMeasurementString() : ''; if (this._markers.length === 1) { labelText = { text: L.drawLocal.draw.handlers.polyline.tooltip.cont, subtext: distanceStr }; } else { labelText = { text: L.drawLocal.draw.handlers.polyline.tooltip.end, subtext: distanceStr }; } } return labelText; }, _updateRunningMeasure: function (latlng, added) { var markersLength = this._markers.length, previousMarkerIndex, distance; if (this._markers.length === 1) { this._measurementRunningTotal = 0; } else { previousMarkerIndex = markersLength - (added ? 2 : 1); // Calculate the distance based on the version if (L.GeometryUtil.isVersion07x()) { distance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1); } else { distance = this._map.distance(latlng, this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1); } this._measurementRunningTotal += distance * (added ? 1 : -1); } }, _getMeasurementString: function () { var currentLatLng = this._currentLatLng, previousLatLng = this._markers[this._markers.length - 1].getLatLng(), distance; // Calculate the distance from the last fixed point to the mouse position based on the version if (L.GeometryUtil.isVersion07x()) { distance = previousLatLng && currentLatLng && currentLatLng.distanceTo ? this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0; } else { distance = previousLatLng && currentLatLng ? this._measurementRunningTotal + this._map.distance(currentLatLng, previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0; } return L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet, this.options.nautic, this.options.precision); }, _showErrorTooltip: function () { this._errorShown = true; // Update tooltip this._tooltip .showAsError() .updateContent({text: this.options.drawError.message}); // Update shape this._updateGuideColor(this.options.drawError.color); this._poly.setStyle({color: this.options.drawError.color}); // Hide the error after 2 seconds this._clearHideErrorTimeout(); this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout); }, _hideErrorTooltip: function () { this._errorShown = false; this._clearHideErrorTimeout(); // Revert tooltip this._tooltip .removeError() .updateContent(this._getTooltipText()); // Revert shape this._updateGuideColor(this.options.shapeOptions.color); this._poly.setStyle({color: this.options.shapeOptions.color}); }, _clearHideErrorTimeout: function () { if (this._hideErrorTimeout) { clearTimeout(this._hideErrorTimeout); this._hideErrorTimeout = null; } }, // disable new markers temporarily; // this is to prevent duplicated touch/click events in some browsers _disableNewMarkers: function () { this._disableMarkers = true; }, // see _disableNewMarkers _enableNewMarkers: function () { setTimeout(function () { this._disableMarkers = false; }.bind(this), 50); }, _cleanUpShape: function () { if (this._markers.length > 1) { this._markers[this._markers.length - 1].off('click', this._finishShape, this); } }, _fireCreatedEvent: function () { var poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions); L.Draw.Feature.prototype._fireCreatedEvent.call(this, poly); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.Rectangle.js ================================================ /** * @class L.Draw.Rectangle * @aka Draw.Rectangle * @inherits L.Draw.SimpleShape */ L.Draw.Rectangle = L.Draw.SimpleShape.extend({ statics: { TYPE: 'rectangle' }, options: { shapeOptions: { stroke: true, color: '#3388ff', weight: 4, opacity: 0.5, fill: true, fillColor: null, //same as color by default fillOpacity: 0.2, showArea: true, clickable: true }, metric: true // Whether to use the metric measurement system or imperial }, // @method initialize(): void initialize: function (map, options) { // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.Draw.Rectangle.TYPE; this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start; L.Draw.SimpleShape.prototype.initialize.call(this, map, options); }, // @method disable(): void disable: function () { if (!this._enabled) { return; } this._isCurrentlyTwoClickDrawing = false; L.Draw.SimpleShape.prototype.disable.call(this); }, _onMouseUp: function (e) { if (!this._shape && !this._isCurrentlyTwoClickDrawing) { this._isCurrentlyTwoClickDrawing = true; return; } // Make sure closing click is on map if (this._isCurrentlyTwoClickDrawing && !_hasAncestor(e.target, 'leaflet-pane')) { return; } L.Draw.SimpleShape.prototype._onMouseUp.call(this); }, _drawShape: function (latlng) { if (!this._shape) { this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions); this._map.addLayer(this._shape); } else { this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng)); } }, _fireCreatedEvent: function () { var rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions); L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle); }, _getTooltipText: function () { var tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this), shape = this._shape, showArea = this.options.showArea, latLngs, area, subtext; if (shape) { latLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(); area = L.GeometryUtil.geodesicArea(latLngs); subtext = showArea ? L.GeometryUtil.readableArea(area, this.options.metric) : ''; } return { text: tooltipText.text, subtext: subtext }; } }); function _hasAncestor(el, cls) { while ((el = el.parentElement) && !el.classList.contains(cls)) { ; } return el; } ================================================ FILE: public/assets/lib/vendor/leaflet/draw/handler/Draw.SimpleShape.js ================================================ L.SimpleShape = {}; /** * @class L.Draw.SimpleShape * @aka Draw.SimpleShape * @inherits L.Draw.Feature */ L.Draw.SimpleShape = L.Draw.Feature.extend({ options: { repeatMode: false }, // @method initialize(): void initialize: function (map, options) { this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end; L.Draw.Feature.prototype.initialize.call(this, map, options); }, // @method addHooks(): void // Add listener hooks to this handler. addHooks: function () { L.Draw.Feature.prototype.addHooks.call(this); if (this._map) { this._mapDraggable = this._map.dragging.enabled(); if (this._mapDraggable) { this._map.dragging.disable(); } //TODO refactor: move cursor to styles this._container.style.cursor = 'crosshair'; this._tooltip.updateContent({text: this._initialLabelText}); this._map .on('mousedown', this._onMouseDown, this) .on('mousemove', this._onMouseMove, this) .on('touchstart', this._onMouseDown, this) .on('touchmove', this._onMouseMove, this); // we should prevent default, otherwise default behavior (scrolling) will fire, // and that will cause document.touchend to fire and will stop the drawing // (circle, rectangle) in touch mode. // (update): we have to send passive now to prevent scroll, because by default it is {passive: true} now, which means, // handler can't event.preventDefault // check the news https://developers.google.com/web/updates/2016/06/passive-event-listeners document.addEventListener('touchstart', L.DomEvent.preventDefault, {passive: false}); } }, // @method removeHooks(): void // Remove listener hooks from this handler. removeHooks: function () { L.Draw.Feature.prototype.removeHooks.call(this); if (this._map) { if (this._mapDraggable) { this._map.dragging.enable(); } //TODO refactor: move cursor to styles this._container.style.cursor = ''; this._map .off('mousedown', this._onMouseDown, this) .off('mousemove', this._onMouseMove, this) .off('touchstart', this._onMouseDown, this) .off('touchmove', this._onMouseMove, this); L.DomEvent.off(document, 'mouseup', this._onMouseUp, this); L.DomEvent.off(document, 'touchend', this._onMouseUp, this); document.removeEventListener('touchstart', L.DomEvent.preventDefault); // If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return if (this._shape) { this._map.removeLayer(this._shape); delete this._shape; } } this._isDrawing = false; }, _getTooltipText: function () { return { text: this._endLabelText }; }, _onMouseDown: function (e) { this._isDrawing = true; this._startLatLng = e.latlng; L.DomEvent .on(document, 'mouseup', this._onMouseUp, this) .on(document, 'touchend', this._onMouseUp, this) .preventDefault(e.originalEvent); }, _onMouseMove: function (e) { var latlng = e.latlng; this._tooltip.updatePosition(latlng); if (this._isDrawing) { this._tooltip.updateContent(this._getTooltipText()); this._drawShape(latlng); } }, _onMouseUp: function () { if (this._shape) { this._fireCreatedEvent(); } this.disable(); if (this.options.repeatMode) { this.enable(); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/EditToolbar.js ================================================ /*L.Map.mergeOptions({ editControl: true });*/ /** * @class L.EditToolbar * @aka EditToolbar */ L.EditToolbar = L.Toolbar.extend({ statics: { TYPE: 'edit' }, options: { edit: { selectedPathOptions: { dashArray: '10, 10', fill: true, fillColor: '#fe57a1', fillOpacity: 0.1, // Whether to user the existing layers color maintainColor: false } }, remove: {}, poly: null, featureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */ }, // @method intialize(): void initialize: function (options) { // Need to set this manually since null is an acceptable value here if (options.edit) { if (typeof options.edit.selectedPathOptions === 'undefined') { options.edit.selectedPathOptions = this.options.edit.selectedPathOptions; } options.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions); } if (options.remove) { options.remove = L.extend({}, this.options.remove, options.remove); } if (options.poly) { options.poly = L.extend({}, this.options.poly, options.poly); } this._toolbarClass = 'leaflet-draw-edit'; L.Toolbar.prototype.initialize.call(this, options); this._selectedFeatureCount = 0; }, // @method getModeHandlers(): object // Get mode handlers information getModeHandlers: function (map) { var featureGroup = this.options.featureGroup; return [ { enabled: this.options.edit, handler: new L.EditToolbar.Edit(map, { featureGroup: featureGroup, selectedPathOptions: this.options.edit.selectedPathOptions, poly: this.options.poly }), title: L.drawLocal.edit.toolbar.buttons.edit }, { enabled: this.options.remove, handler: new L.EditToolbar.Delete(map, { featureGroup: featureGroup }), title: L.drawLocal.edit.toolbar.buttons.remove } ]; }, // @method getActions(): object // Get actions information getActions: function (handler) { var actions = [ { title: L.drawLocal.edit.toolbar.actions.save.title, text: L.drawLocal.edit.toolbar.actions.save.text, callback: this._save, context: this }, { title: L.drawLocal.edit.toolbar.actions.cancel.title, text: L.drawLocal.edit.toolbar.actions.cancel.text, callback: this.disable, context: this } ]; if (handler.removeAllLayers) { actions.push({ title: L.drawLocal.edit.toolbar.actions.clearAll.title, text: L.drawLocal.edit.toolbar.actions.clearAll.text, callback: this._clearAllLayers, context: this }); } return actions; }, // @method addToolbar(map): L.DomUtil // Adds the toolbar to the map addToolbar: function (map) { var container = L.Toolbar.prototype.addToolbar.call(this, map); this._checkDisabled(); this.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this); return container; }, // @method removeToolbar(): void // Removes the toolbar from the map removeToolbar: function () { this.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this); L.Toolbar.prototype.removeToolbar.call(this); }, // @method disable(): void // Disables the toolbar disable: function () { if (!this.enabled()) { return; } this._activeMode.handler.revertLayers(); L.Toolbar.prototype.disable.call(this); }, _save: function () { this._activeMode.handler.save(); if (this._activeMode) { this._activeMode.handler.disable(); } }, _clearAllLayers: function () { this._activeMode.handler.removeAllLayers(); if (this._activeMode) { this._activeMode.handler.disable(); } }, _checkDisabled: function () { var featureGroup = this.options.featureGroup, hasLayers = featureGroup.getLayers().length !== 0, button; if (this.options.edit) { button = this._modes[L.EditToolbar.Edit.TYPE].button; if (hasLayers) { L.DomUtil.removeClass(button, 'leaflet-disabled'); } else { L.DomUtil.addClass(button, 'leaflet-disabled'); } button.setAttribute( 'title', hasLayers ? L.drawLocal.edit.toolbar.buttons.edit : L.drawLocal.edit.toolbar.buttons.editDisabled ); } if (this.options.remove) { button = this._modes[L.EditToolbar.Delete.TYPE].button; if (hasLayers) { L.DomUtil.removeClass(button, 'leaflet-disabled'); } else { L.DomUtil.addClass(button, 'leaflet-disabled'); } button.setAttribute( 'title', hasLayers ? L.drawLocal.edit.toolbar.buttons.remove : L.drawLocal.edit.toolbar.buttons.removeDisabled ); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.Circle.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.Circle * @aka Edit.Circle * @inherits L.Edit.CircleMarker */ L.Edit.Circle = L.Edit.CircleMarker.extend({ _createResizeMarker: function () { var center = this._shape.getLatLng(), resizemarkerPoint = this._getResizeMarkerPoint(center); this._resizeMarkers = []; this._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon)); }, _getResizeMarkerPoint: function (latlng) { // From L.shape.getBounds() var delta = this._shape._radius * Math.cos(Math.PI / 4), point = this._map.project(latlng); return this._map.unproject([point.x + delta, point.y - delta]); }, _resize: function (latlng) { var moveLatLng = this._moveMarker.getLatLng(); // Calculate the radius based on the version if (L.GeometryUtil.isVersion07x()) { radius = moveLatLng.distanceTo(latlng); } else { radius = this._map.distance(moveLatLng, latlng); } this._shape.setRadius(radius); if (this._map.editTooltip) { this._map._editTooltip.updateContent({ text: L.drawLocal.edit.handlers.edit.tooltip.subtext + '<br />' + L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.draw.handlers.circle.radius + ': ' + L.GeometryUtil.readableDistance(radius, true, this.options.feet, this.options.nautic) }); } this._shape.setRadius(radius); this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape}); } }); L.Circle.addInitHook(function () { if (L.Edit.Circle) { this.editing = new L.Edit.Circle(this); if (this.options.editable) { this.editing.enable(); } } this.on('add', function () { if (this.editing && this.editing.enabled()) { this.editing.addHooks(); } }); this.on('remove', function () { if (this.editing && this.editing.enabled()) { this.editing.removeHooks(); } }); }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.CircleMarker.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.CircleMarker * @aka Edit.Circle * @inherits L.Edit.SimpleShape */ L.Edit.CircleMarker = L.Edit.SimpleShape.extend({ _createMoveMarker: function () { var center = this._shape.getLatLng(); this._moveMarker = this._createMarker(center, this.options.moveIcon); }, _createResizeMarker: function () { // To avoid an undefined check in L.Edit.SimpleShape.removeHooks this._resizeMarkers = []; }, _move: function (latlng) { if (this._resizeMarkers.length) { var resizemarkerPoint = this._getResizeMarkerPoint(latlng); // Move the resize marker this._resizeMarkers[0].setLatLng(resizemarkerPoint); } // Move the circle this._shape.setLatLng(latlng); this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape}); }, }); L.CircleMarker.addInitHook(function () { if (L.Edit.CircleMarker) { this.editing = new L.Edit.CircleMarker(this); if (this.options.editable) { this.editing.enable(); } } this.on('add', function () { if (this.editing && this.editing.enabled()) { this.editing.addHooks(); } }); this.on('remove', function () { if (this.editing && this.editing.enabled()) { this.editing.removeHooks(); } }); }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.Marker.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.Marker * @aka Edit.Marker */ L.Edit.Marker = L.Handler.extend({ // @method initialize(): void initialize: function (marker, options) { this._marker = marker; L.setOptions(this, options); }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { var marker = this._marker; marker.dragging.enable(); marker.on('dragend', this._onDragEnd, marker); this._toggleMarkerHighlight(); }, // @method removeHooks(): void // Remove listener hooks from this handler removeHooks: function () { var marker = this._marker; marker.dragging.disable(); marker.off('dragend', this._onDragEnd, marker); this._toggleMarkerHighlight(); }, _onDragEnd: function (e) { var layer = e.target; layer.edited = true; this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer}); }, _toggleMarkerHighlight: function () { var icon = this._marker._icon; // Don't do anything if this layer is a marker but doesn't have an icon. Markers // should usually have icons. If using Leaflet.draw with Leaflet.markercluster there // is a chance that a marker doesn't. if (!icon) { return; } // This is quite naughty, but I don't see another way of doing it. (short of setting a new icon) icon.style.display = 'none'; if (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) { L.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected'); // Offset as the border will make the icon move. this._offsetMarker(icon, -4); } else { L.DomUtil.addClass(icon, 'leaflet-edit-marker-selected'); // Offset as the border will make the icon move. this._offsetMarker(icon, 4); } icon.style.display = ''; }, _offsetMarker: function (icon, offset) { var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset, iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset; icon.style.marginTop = iconMarginTop + 'px'; icon.style.marginLeft = iconMarginLeft + 'px'; } }); L.Marker.addInitHook(function () { if (L.Edit.Marker) { this.editing = new L.Edit.Marker(this); if (this.options.editable) { this.editing.enable(); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.Poly.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.Polyline * @aka L.Edit.Poly * @aka Edit.Poly */ L.Edit.Poly = L.Handler.extend({ // @method initialize(): void initialize: function (poly) { this.latlngs = [poly._latlngs]; if (poly._holes) { this.latlngs = this.latlngs.concat(poly._holes); } this._poly = poly; this._poly.on('revert-edited', this._updateLatLngs, this); }, // Compatibility method to normalize Poly* objects // between 0.7.x and 1.0+ _defaultShape: function () { if (!L.Polyline._flat) { return this._poly._latlngs; } return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0]; }, _eachVertexHandler: function (callback) { for (var i = 0; i < this._verticesHandlers.length; i++) { callback(this._verticesHandlers[i]); } }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { this._initHandlers(); this._eachVertexHandler(function (handler) { handler.addHooks(); }); }, // @method removeHooks(): void // Remove listener hooks from this handler removeHooks: function () { this._eachVertexHandler(function (handler) { handler.removeHooks(); }); }, // @method updateMarkers(): void // Fire an update for each vertex handler updateMarkers: function () { this._eachVertexHandler(function (handler) { handler.updateMarkers(); }); }, _initHandlers: function () { this._verticesHandlers = []; for (var i = 0; i < this.latlngs.length; i++) { this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this._poly.options.poly)); } }, _updateLatLngs: function (e) { this.latlngs = [e.layer._latlngs]; if (e.layer._holes) { this.latlngs = this.latlngs.concat(e.layer._holes); } } }); /** * @class L.Edit.PolyVerticesEdit * @aka Edit.PolyVerticesEdit */ L.Edit.PolyVerticesEdit = L.Handler.extend({ options: { icon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: 'leaflet-div-icon leaflet-editing-icon' }), touchIcon: new L.DivIcon({ iconSize: new L.Point(20, 20), className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon' }), drawError: { color: '#b00b00', timeout: 1000 } }, // @method intialize(): void initialize: function (poly, latlngs, options) { // if touch, switch to touch icon if (L.Browser.touch) { this.options.icon = this.options.touchIcon; } this._poly = poly; if (options && options.drawError) { options.drawError = L.Util.extend({}, this.options.drawError, options.drawError); } this._latlngs = latlngs; L.setOptions(this, options); }, // Compatibility method to normalize Poly* objects // between 0.7.x and 1.0+ _defaultShape: function () { if (!L.Polyline._flat) { return this._latlngs; } return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; }, // @method addHooks(): void // Add listener hooks to this handler. addHooks: function () { var poly = this._poly; var path = poly._path; if (!(poly instanceof L.Polygon)) { poly.options.fill = false; if (poly.options.editing) { poly.options.editing.fill = false; } } if (path) { if (poly.options.editing && poly.options.editing.className) { if (poly.options.original.className) { poly.options.original.className.split(' ').forEach(function (className) { L.DomUtil.removeClass(path, className); }); } poly.options.editing.className.split(' ').forEach(function (className) { L.DomUtil.addClass(path, className); }); } } poly.setStyle(poly.options.editing); if (this._poly._map) { this._map = this._poly._map; // Set map if (!this._markerGroup) { this._initMarkers(); } this._poly._map.addLayer(this._markerGroup); } }, // @method removeHooks(): void // Remove listener hooks from this handler. removeHooks: function () { var poly = this._poly; var path = poly._path; if (path) { if (poly.options.editing && poly.options.editing.className) { poly.options.editing.className.split(' ').forEach(function (className) { L.DomUtil.removeClass(path, className); }); if (poly.options.original.className) { poly.options.original.className.split(' ').forEach(function (className) { L.DomUtil.addClass(path, className); }); } } } poly.setStyle(poly.options.original); if (poly._map) { poly._map.removeLayer(this._markerGroup); delete this._markerGroup; delete this._markers; } }, // @method updateMarkers(): void // Clear markers and update their location updateMarkers: function () { this._markerGroup.clearLayers(); this._initMarkers(); }, _initMarkers: function () { if (!this._markerGroup) { this._markerGroup = new L.LayerGroup(); } this._markers = []; var latlngs = this._defaultShape(), i, j, len, marker; for (i = 0, len = latlngs.length; i < len; i++) { marker = this._createMarker(latlngs[i], i); marker.on('click', this._onMarkerClick, this); marker.on('contextmenu', this._onContextMenu, this); this._markers.push(marker); } var markerLeft, markerRight; for (i = 0, j = len - 1; i < len; j = i++) { if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) { continue; } markerLeft = this._markers[j]; markerRight = this._markers[i]; this._createMiddleMarker(markerLeft, markerRight); this._updatePrevNext(markerLeft, markerRight); } }, _createMarker: function (latlng, index) { // Extending L.Marker in TouchEvents.js to include touch. var marker = new L.Marker.Touch(latlng, { draggable: true, icon: this.options.icon, }); marker._origLatLng = latlng; marker._index = index; marker .on('dragstart', this._onMarkerDragStart, this) .on('drag', this._onMarkerDrag, this) .on('dragend', this._fireEdit, this) .on('touchmove', this._onTouchMove, this) .on('touchend', this._fireEdit, this) .on('MSPointerMove', this._onTouchMove, this) .on('MSPointerUp', this._fireEdit, this); this._markerGroup.addLayer(marker); return marker; }, _onMarkerDragStart: function () { this._poly.fire('editstart'); }, _spliceLatLngs: function () { var latlngs = this._defaultShape(); var removed = [].splice.apply(latlngs, arguments); this._poly._convertLatLngs(latlngs, true); this._poly.redraw(); return removed; }, _removeMarker: function (marker) { var i = marker._index; this._markerGroup.removeLayer(marker); this._markers.splice(i, 1); this._spliceLatLngs(i, 1); this._updateIndexes(i, -1); marker .off('dragstart', this._onMarkerDragStart, this) .off('drag', this._onMarkerDrag, this) .off('dragend', this._fireEdit, this) .off('touchmove', this._onMarkerDrag, this) .off('touchend', this._fireEdit, this) .off('click', this._onMarkerClick, this) .off('MSPointerMove', this._onTouchMove, this) .off('MSPointerUp', this._fireEdit, this); }, _fireEdit: function () { this._poly.edited = true; this._poly.fire('edit'); this._poly._map.fire(L.Draw.Event.EDITVERTEX, {layers: this._markerGroup, poly: this._poly}); }, _onMarkerDrag: function (e) { var marker = e.target; var poly = this._poly; L.extend(marker._origLatLng, marker._latlng); if (marker._middleLeft) { marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); } if (marker._middleRight) { marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); } if (poly.options.poly) { var tooltip = poly._map._editTooltip; // Access the tooltip // If we don't allow intersections and the polygon intersects if (!poly.options.poly.allowIntersection && poly.intersects()) { var originalColor = poly.options.color; poly.setStyle({color: this.options.drawError.color}); // Manually trigger 'dragend' behavior on marker we are about to remove // WORKAROUND: introduced in 1.0.0-rc2, may be related to #4484 if (L.version.indexOf('0.7') !== 0) { marker.dragging._draggable._onUp(e); } this._onMarkerClick(e); // Remove violating marker // FIXME: Reset the marker to it's original position (instead of remove) if (tooltip) { tooltip.updateContent({ text: L.drawLocal.draw.handlers.polyline.error }); } // Reset everything back to normal after a second setTimeout(function () { poly.setStyle({color: originalColor}); if (tooltip) { tooltip.updateContent({ text: L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext }); } }, 1000); } } //refresh the bounds when draging this._poly._bounds._southWest = L.latLng(Infinity, Infinity); this._poly._bounds._northEast = L.latLng(-Infinity, -Infinity); var latlngs = this._poly.getLatLngs(); this._poly._convertLatLngs(latlngs, true); this._poly.redraw(); this._poly.fire('editdrag'); }, _onMarkerClick: function (e) { var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3, marker = e.target; // If removing this point would create an invalid polyline/polygon don't remove if (this._defaultShape().length < minPoints) { return; } // remove the marker this._removeMarker(marker); // update prev/next links of adjacent markers this._updatePrevNext(marker._prev, marker._next); // remove ghost markers near the removed marker if (marker._middleLeft) { this._markerGroup.removeLayer(marker._middleLeft); } if (marker._middleRight) { this._markerGroup.removeLayer(marker._middleRight); } // create a ghost marker in place of the removed one if (marker._prev && marker._next) { this._createMiddleMarker(marker._prev, marker._next); } else if (!marker._prev) { marker._next._middleLeft = null; } else if (!marker._next) { marker._prev._middleRight = null; } this._fireEdit(); }, _onContextMenu: function (e) { var marker = e.target; var poly = this._poly; this._poly._map.fire(L.Draw.Event.MARKERCONTEXT, {marker: marker, layers: this._markerGroup, poly: this._poly}); L.DomEvent.stopPropagation; }, _onTouchMove: function (e) { var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]), latlng = this._map.layerPointToLatLng(layerPoint), marker = e.target; L.extend(marker._origLatLng, latlng); if (marker._middleLeft) { marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); } if (marker._middleRight) { marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); } this._poly.redraw(); this.updateMarkers(); }, _updateIndexes: function (index, delta) { this._markerGroup.eachLayer(function (marker) { if (marker._index > index) { marker._index += delta; } }); }, _createMiddleMarker: function (marker1, marker2) { var latlng = this._getMiddleLatLng(marker1, marker2), marker = this._createMarker(latlng), onClick, onDragStart, onDragEnd; marker.setOpacity(0.6); marker1._middleRight = marker2._middleLeft = marker; onDragStart = function () { marker.off('touchmove', onDragStart, this); var i = marker2._index; marker._index = i; marker .off('click', onClick, this) .on('click', this._onMarkerClick, this); latlng.lat = marker.getLatLng().lat; latlng.lng = marker.getLatLng().lng; this._spliceLatLngs(i, 0, latlng); this._markers.splice(i, 0, marker); marker.setOpacity(1); this._updateIndexes(i, 1); marker2._index++; this._updatePrevNext(marker1, marker); this._updatePrevNext(marker, marker2); this._poly.fire('editstart'); }; onDragEnd = function () { marker.off('dragstart', onDragStart, this); marker.off('dragend', onDragEnd, this); marker.off('touchmove', onDragStart, this); this._createMiddleMarker(marker1, marker); this._createMiddleMarker(marker, marker2); }; onClick = function () { onDragStart.call(this); onDragEnd.call(this); this._fireEdit(); }; marker .on('click', onClick, this) .on('dragstart', onDragStart, this) .on('dragend', onDragEnd, this) .on('touchmove', onDragStart, this); this._markerGroup.addLayer(marker); }, _updatePrevNext: function (marker1, marker2) { if (marker1) { marker1._next = marker2; } if (marker2) { marker2._prev = marker1; } }, _getMiddleLatLng: function (marker1, marker2) { var map = this._poly._map, p1 = map.project(marker1.getLatLng()), p2 = map.project(marker2.getLatLng()); return map.unproject(p1._add(p2)._divideBy(2)); } }); L.Polyline.addInitHook(function () { // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit if (this.editing) { return; } if (L.Edit.Poly) { this.editing = new L.Edit.Poly(this); if (this.options.editable) { this.editing.enable(); } } this.on('add', function () { if (this.editing && this.editing.enabled()) { this.editing.addHooks(); } }); this.on('remove', function () { if (this.editing && this.editing.enabled()) { this.editing.removeHooks(); } }); }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.Rectangle.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.Rectangle * @aka Edit.Rectangle * @inherits L.Edit.SimpleShape */ L.Edit.Rectangle = L.Edit.SimpleShape.extend({ _createMoveMarker: function () { var bounds = this._shape.getBounds(), center = bounds.getCenter(); this._moveMarker = this._createMarker(center, this.options.moveIcon); }, _createResizeMarker: function () { var corners = this._getCorners(); this._resizeMarkers = []; for (var i = 0, l = corners.length; i < l; i++) { this._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon)); // Monkey in the corner index as we will need to know this for dragging this._resizeMarkers[i]._cornerIndex = i; } }, _onMarkerDragStart: function (e) { L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e); // Save a reference to the opposite point var corners = this._getCorners(), marker = e.target, currentCornerIndex = marker._cornerIndex; this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]; this._toggleCornerMarkers(0, currentCornerIndex); }, _onMarkerDragEnd: function (e) { var marker = e.target, bounds, center; // Reset move marker position to the center if (marker === this._moveMarker) { bounds = this._shape.getBounds(); center = bounds.getCenter(); marker.setLatLng(center); } this._toggleCornerMarkers(1); this._repositionCornerMarkers(); L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e); }, _move: function (newCenter) { var latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(), bounds = this._shape.getBounds(), center = bounds.getCenter(), offset, newLatLngs = []; // Offset the latlngs to the new center for (var i = 0, l = latlngs.length; i < l; i++) { offset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng]; newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]); } this._shape.setLatLngs(newLatLngs); // Reposition the resize markers this._repositionCornerMarkers(); this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape}); }, _resize: function (latlng) { var bounds; // Update the shape based on the current position of this corner and the opposite point this._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner)); // Reposition the move marker bounds = this._shape.getBounds(); this._moveMarker.setLatLng(bounds.getCenter()); this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape}); }, _getCorners: function () { var bounds = this._shape.getBounds(), nw = bounds.getNorthWest(), ne = bounds.getNorthEast(), se = bounds.getSouthEast(), sw = bounds.getSouthWest(); return [nw, ne, se, sw]; }, _toggleCornerMarkers: function (opacity) { for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { this._resizeMarkers[i].setOpacity(opacity); } }, _repositionCornerMarkers: function () { var corners = this._getCorners(); for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { this._resizeMarkers[i].setLatLng(corners[i]); } } }); L.Rectangle.addInitHook(function () { if (L.Edit.Rectangle) { this.editing = new L.Edit.Rectangle(this); if (this.options.editable) { this.editing.enable(); } } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/Edit.SimpleShape.js ================================================ L.Edit = L.Edit || {}; /** * @class L.Edit.SimpleShape * @aka Edit.SimpleShape */ L.Edit.SimpleShape = L.Handler.extend({ options: { moveIcon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move' }), resizeIcon: new L.DivIcon({ iconSize: new L.Point(8, 8), className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize' }), touchMoveIcon: new L.DivIcon({ iconSize: new L.Point(20, 20), className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon' }), touchResizeIcon: new L.DivIcon({ iconSize: new L.Point(20, 20), className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon' }), }, // @method intialize(): void initialize: function (shape, options) { // if touch, switch to touch icon if (L.Browser.touch) { this.options.moveIcon = this.options.touchMoveIcon; this.options.resizeIcon = this.options.touchResizeIcon; } this._shape = shape; L.Util.setOptions(this, options); }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { var shape = this._shape; if (this._shape._map) { this._map = this._shape._map; shape.setStyle(shape.options.editing); if (shape._map) { this._map = shape._map; if (!this._markerGroup) { this._initMarkers(); } this._map.addLayer(this._markerGroup); } } }, // @method removeHooks(): void // Remove listener hooks from this handler removeHooks: function () { var shape = this._shape; shape.setStyle(shape.options.original); if (shape._map) { this._unbindMarker(this._moveMarker); for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { this._unbindMarker(this._resizeMarkers[i]); } this._resizeMarkers = null; this._map.removeLayer(this._markerGroup); delete this._markerGroup; } this._map = null; }, // @method updateMarkers(): void // Remove the edit markers from this layer updateMarkers: function () { this._markerGroup.clearLayers(); this._initMarkers(); }, _initMarkers: function () { if (!this._markerGroup) { this._markerGroup = new L.LayerGroup(); } // Create center marker this._createMoveMarker(); // Create edge marker this._createResizeMarker(); }, _createMoveMarker: function () { // Children override }, _createResizeMarker: function () { // Children override }, _createMarker: function (latlng, icon) { // Extending L.Marker in TouchEvents.js to include touch. var marker = new L.Marker.Touch(latlng, { draggable: true, icon: icon, zIndexOffset: 10 }); this._bindMarker(marker); this._markerGroup.addLayer(marker); return marker; }, _bindMarker: function (marker) { marker .on('dragstart', this._onMarkerDragStart, this) .on('drag', this._onMarkerDrag, this) .on('dragend', this._onMarkerDragEnd, this) .on('touchstart', this._onTouchStart, this) .on('touchmove', this._onTouchMove, this) .on('MSPointerMove', this._onTouchMove, this) .on('touchend', this._onTouchEnd, this) .on('MSPointerUp', this._onTouchEnd, this); }, _unbindMarker: function (marker) { marker .off('dragstart', this._onMarkerDragStart, this) .off('drag', this._onMarkerDrag, this) .off('dragend', this._onMarkerDragEnd, this) .off('touchstart', this._onTouchStart, this) .off('touchmove', this._onTouchMove, this) .off('MSPointerMove', this._onTouchMove, this) .off('touchend', this._onTouchEnd, this) .off('MSPointerUp', this._onTouchEnd, this); }, _onMarkerDragStart: function (e) { var marker = e.target; marker.setOpacity(0); this._shape.fire('editstart'); }, _fireEdit: function () { this._shape.edited = true; this._shape.fire('edit'); }, _onMarkerDrag: function (e) { var marker = e.target, latlng = marker.getLatLng(); if (marker === this._moveMarker) { this._move(latlng); } else { this._resize(latlng); } this._shape.redraw(); this._shape.fire('editdrag'); }, _onMarkerDragEnd: function (e) { var marker = e.target; marker.setOpacity(1); this._fireEdit(); }, _onTouchStart: function (e) { L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e); if (typeof(this._getCorners) === 'function') { // Save a reference to the opposite point var corners = this._getCorners(), marker = e.target, currentCornerIndex = marker._cornerIndex; marker.setOpacity(0); // Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart() // Latlng is null otherwise. this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]; this._toggleCornerMarkers(0, currentCornerIndex); } this._shape.fire('editstart'); }, _onTouchMove: function (e) { var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]), latlng = this._map.layerPointToLatLng(layerPoint), marker = e.target; if (marker === this._moveMarker) { this._move(latlng); } else { this._resize(latlng); } this._shape.redraw(); // prevent touchcancel in IOS // e.preventDefault(); return false; }, _onTouchEnd: function (e) { var marker = e.target; marker.setOpacity(1); this.updateMarkers(); this._fireEdit(); }, _move: function () { // Children override }, _resize: function () { // Children override } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/EditToolbar.Delete.js ================================================ /** * @class L.EditToolbar.Delete * @aka EditToolbar.Delete */ L.EditToolbar.Delete = L.Handler.extend({ statics: { TYPE: 'remove' // not delete as delete is reserved in js }, // @method intialize(): void initialize: function (map, options) { L.Handler.prototype.initialize.call(this, map); L.Util.setOptions(this, options); // Store the selectable layer group for ease of access this._deletableLayers = this.options.featureGroup; if (!(this._deletableLayers instanceof L.FeatureGroup)) { throw new Error('options.featureGroup must be a L.FeatureGroup'); } // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.EditToolbar.Delete.TYPE; var version = L.version.split('.'); //If Version is >= 1.2.0 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) { L.EditToolbar.Delete.include(L.Evented.prototype); } else { L.EditToolbar.Delete.include(L.Mixin.Events); } }, // @method enable(): void // Enable the delete toolbar enable: function () { if (this._enabled || !this._hasAvailableLayers()) { return; } this.fire('enabled', {handler: this.type}); this._map.fire(L.Draw.Event.DELETESTART, {handler: this.type}); L.Handler.prototype.enable.call(this); this._deletableLayers .on('layeradd', this._enableLayerDelete, this) .on('layerremove', this._disableLayerDelete, this); }, // @method disable(): void // Disable the delete toolbar disable: function () { if (!this._enabled) { return; } this._deletableLayers .off('layeradd', this._enableLayerDelete, this) .off('layerremove', this._disableLayerDelete, this); L.Handler.prototype.disable.call(this); this._map.fire(L.Draw.Event.DELETESTOP, {handler: this.type}); this.fire('disabled', {handler: this.type}); }, // @method addHooks(): void // Add listener hooks to this handler addHooks: function () { var map = this._map; if (map) { map.getContainer().focus(); this._deletableLayers.eachLayer(this._enableLayerDelete, this); this._deletedLayers = new L.LayerGroup(); this._tooltip = new L.Draw.Tooltip(this._map); this._tooltip.updateContent({text: L.drawLocal.edit.handlers.remove.tooltip.text}); this._map.on('mousemove', this._onMouseMove, this); } }, // @method removeHooks(): void // Remove listener hooks from this handler removeHooks: function () { if (this._map) { this._deletableLayers.eachLayer(this._disableLayerDelete, this); this._deletedLayers = null; this._tooltip.dispose(); this._tooltip = null; this._map.off('mousemove', this._onMouseMove, this); } }, // @method revertLayers(): void // Revert the deleted layers back to their prior state. revertLayers: function () { // Iterate of the deleted layers and add them back into the featureGroup this._deletedLayers.eachLayer(function (layer) { this._deletableLayers.addLayer(layer); layer.fire('revert-deleted', {layer: layer}); }, this); }, // @method save(): void // Save deleted layers save: function () { this._map.fire(L.Draw.Event.DELETED, {layers: this._deletedLayers}); }, // @method removeAllLayers(): void // Remove all delateable layers removeAllLayers: function () { // Iterate of the delateable layers and add remove them this._deletableLayers.eachLayer(function (layer) { this._removeLayer({layer: layer}); }, this); this.save(); }, _enableLayerDelete: function (e) { var layer = e.layer || e.target || e; layer.on('click', this._removeLayer, this); }, _disableLayerDelete: function (e) { var layer = e.layer || e.target || e; layer.off('click', this._removeLayer, this); // Remove from the deleted layers so we can't accidentally revert if the user presses cancel this._deletedLayers.removeLayer(layer); }, _removeLayer: function (e) { var layer = e.layer || e.target || e; this._deletableLayers.removeLayer(layer); this._deletedLayers.addLayer(layer); layer.fire('deleted'); }, _onMouseMove: function (e) { this._tooltip.updatePosition(e.latlng); }, _hasAvailableLayers: function () { return this._deletableLayers.getLayers().length !== 0; } }); ================================================ FILE: public/assets/lib/vendor/leaflet/edit/handler/EditToolbar.Edit.js ================================================ /** * @class L.EditToolbar.Edit * @aka EditToolbar.Edit */ L.EditToolbar.Edit = L.Handler.extend({ statics: { TYPE: 'edit' }, // @method intialize(): void initialize: function (map, options) { L.Handler.prototype.initialize.call(this, map); L.setOptions(this, options); // Store the selectable layer group for ease of access this._featureGroup = options.featureGroup; if (!(this._featureGroup instanceof L.FeatureGroup)) { throw new Error('options.featureGroup must be a L.FeatureGroup'); } this._uneditedLayerProps = {}; // Save the type so super can fire, need to do this as cannot do this.TYPE :( this.type = L.EditToolbar.Edit.TYPE; var version = L.version.split('.'); //If Version is >= 1.2.0 if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) { L.EditToolbar.Edit.include(L.Evented.prototype); } else { L.EditToolbar.Edit.include(L.Mixin.Events); } }, // @method enable(): void // Enable the edit toolbar enable: function () { if (this._enabled || !this._hasAvailableLayers()) { return; } this.fire('enabled', {handler: this.type}); //this disable other handlers this._map.fire(L.Draw.Event.EDITSTART, {handler: this.type}); //allow drawLayer to be updated before beginning edition. L.Handler.prototype.enable.call(this); this._featureGroup .on('layeradd', this._enableLayerEdit, this) .on('layerremove', this._disableLayerEdit, this); }, // @method disable(): void // Disable the edit toolbar disable: function () { if (!this._enabled) { return; } this._featureGroup .off('layeradd', this._enableLayerEdit, this) .off('layerremove', this._disableLayerEdit, this); L.Handler.prototype.disable.call(this); this._map.fire(L.Draw.Event.EDITSTOP, {handler: this.type}); this.fire('disabled', {handler: this.type}); }, // @method addHooks(): void // Add listener hooks for this handler addHooks: function () { var map = this._map; if (map) { map.getContainer().focus(); this._featureGroup.eachLayer(this._enableLayerEdit, this); this._tooltip = new L.Draw.Tooltip(this._map); this._tooltip.updateContent({ text: L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext }); // Quickly access the tooltip to update for intersection checking map._editTooltip = this._tooltip; this._updateTooltip(); this._map .on('mousemove', this._onMouseMove, this) .on('touchmove', this._onMouseMove, this) .on('MSPointerMove', this._onMouseMove, this) .on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this); } }, // @method removeHooks(): void // Remove listener hooks for this handler removeHooks: function () { if (this._map) { // Clean up selected layers. this._featureGroup.eachLayer(this._disableLayerEdit, this); // Clear the backups of the original layers this._uneditedLayerProps = {}; this._tooltip.dispose(); this._tooltip = null; this._map .off('mousemove', this._onMouseMove, this) .off('touchmove', this._onMouseMove, this) .off('MSPointerMove', this._onMouseMove, this) .off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this); } }, // @method revertLayers(): void // Revert each layer's geometry changes revertLayers: function () { this._featureGroup.eachLayer(function (layer) { this._revertLayer(layer); }, this); }, // @method save(): void // Save the layer geometries save: function () { var editedLayers = new L.LayerGroup(); this._featureGroup.eachLayer(function (layer) { if (layer.edited) { editedLayers.addLayer(layer); layer.edited = false; } }); this._map.fire(L.Draw.Event.EDITED, {layers: editedLayers}); }, _backupLayer: function (layer) { var id = L.Util.stamp(layer); if (!this._uneditedLayerProps[id]) { // Polyline, Polygon or Rectangle if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) { this._uneditedLayerProps[id] = { latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs()) }; } else if (layer instanceof L.Circle) { this._uneditedLayerProps[id] = { latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()), radius: layer.getRadius() }; } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker this._uneditedLayerProps[id] = { latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()) }; } } }, _getTooltipText: function () { return ({ text: L.drawLocal.edit.handlers.edit.tooltip.text, subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext }); }, _updateTooltip: function () { this._tooltip.updateContent(this._getTooltipText()); }, _revertLayer: function (layer) { var id = L.Util.stamp(layer); layer.edited = false; if (this._uneditedLayerProps.hasOwnProperty(id)) { // Polyline, Polygon or Rectangle if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) { layer.setLatLngs(this._uneditedLayerProps[id].latlngs); } else if (layer instanceof L.Circle) { layer.setLatLng(this._uneditedLayerProps[id].latlng); layer.setRadius(this._uneditedLayerProps[id].radius); } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker or CircleMarker layer.setLatLng(this._uneditedLayerProps[id].latlng); } layer.fire('revert-edited', {layer: layer}); } }, _enableLayerEdit: function (e) { var layer = e.layer || e.target || e, pathOptions, poly; // Back up this layer (if haven't before) this._backupLayer(layer); if (this.options.poly) { poly = L.Util.extend({}, this.options.poly); layer.options.poly = poly; } // Set different style for editing mode if (this.options.selectedPathOptions) { pathOptions = L.Util.extend({}, this.options.selectedPathOptions); // Use the existing color of the layer if (pathOptions.maintainColor) { pathOptions.color = layer.options.color; pathOptions.fillColor = layer.options.fillColor; } layer.options.original = L.extend({}, layer.options); layer.options.editing = pathOptions; } if (layer instanceof L.Marker) { if (layer.editing) { layer.editing.enable(); } layer.dragging.enable(); layer .on('dragend', this._onMarkerDragEnd) // #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again. .on('touchmove', this._onTouchMove, this) .on('MSPointerMove', this._onTouchMove, this) .on('touchend', this._onMarkerDragEnd, this) .on('MSPointerUp', this._onMarkerDragEnd, this); } else { layer.editing.enable(); } }, _disableLayerEdit: function (e) { var layer = e.layer || e.target || e; layer.edited = false; if (layer.editing) { layer.editing.disable(); } delete layer.options.editing; delete layer.options.original; // Reset layer styles to that of before select if (this._selectedPathOptions) { if (layer instanceof L.Marker) { this._toggleMarkerHighlight(layer); } else { // reset the layer style to what is was before being selected layer.setStyle(layer.options.previousOptions); // remove the cached options for the layer object delete layer.options.previousOptions; } } if (layer instanceof L.Marker) { layer.dragging.disable(); layer .off('dragend', this._onMarkerDragEnd, this) .off('touchmove', this._onTouchMove, this) .off('MSPointerMove', this._onTouchMove, this) .off('touchend', this._onMarkerDragEnd, this) .off('MSPointerUp', this._onMarkerDragEnd, this); } else { layer.editing.disable(); } }, _onMouseMove: function (e) { this._tooltip.updatePosition(e.latlng); }, _onMarkerDragEnd: function (e) { var layer = e.target; layer.edited = true; this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer}); }, _onTouchMove: function (e) { var touchEvent = e.originalEvent.changedTouches[0], layerPoint = this._map.mouseEventToLayerPoint(touchEvent), latlng = this._map.layerPointToLatLng(layerPoint); e.target.setLatLng(latlng); }, _hasAvailableLayers: function () { return this._featureGroup.getLayers().length !== 0; } }); ================================================ FILE: public/assets/lib/vendor/leaflet/ext/GeometryUtil.js ================================================ (function () { var defaultPrecision = { km: 2, ha: 2, m: 0, mi: 2, ac: 2, yd: 0, ft: 0, nm: 2 }; /** * @class L.GeometryUtil * @aka GeometryUtil */ L.GeometryUtil = L.extend(L.GeometryUtil || {}, { // Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270 // @method geodesicArea(): number geodesicArea: function (latLngs) { var pointsCount = latLngs.length, area = 0.0, d2r = Math.PI / 180, p1, p2; if (pointsCount > 2) { for (var i = 0; i < pointsCount; i++) { p1 = latLngs[i]; p2 = latLngs[(i + 1) % pointsCount]; area += ((p2.lng - p1.lng) * d2r) * (2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r)); } area = area * 6378137.0 * 6378137.0 / 2.0; } return Math.abs(area); }, // @method formattedNumber(n, precision): string // Returns n in specified number format (if defined) and precision formattedNumber: function (n, precision) { var formatted = parseFloat(n).toFixed(precision), format = L.drawLocal.format && L.drawLocal.format.numeric, delimiters = format && format.delimiters, thousands = delimiters && delimiters.thousands, decimal = delimiters && delimiters.decimal; if (thousands || decimal) { var splitValue = formatted.split('.'); formatted = thousands ? splitValue[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousands) : splitValue[0]; decimal = decimal || '.'; if (splitValue.length > 1) { formatted = formatted + decimal + splitValue[1]; } } return formatted; }, // @method readableArea(area, isMetric, precision): string // Returns a readable area string in yards or metric. // The value will be rounded as defined by the precision option object. readableArea: function (area, isMetric, precision) { var areaStr, units, precision = L.Util.extend({}, defaultPrecision, precision); if (isMetric) { units = ['ha', 'm']; type = typeof isMetric; if (type === 'string') { units = [isMetric]; } else if (type !== 'boolean') { units = isMetric; } if (area >= 1000000 && units.indexOf('km') !== -1) { areaStr = L.GeometryUtil.formattedNumber(area * 0.000001, precision['km']) + ' km²'; } else if (area >= 10000 && units.indexOf('ha') !== -1) { areaStr = L.GeometryUtil.formattedNumber(area * 0.0001, precision['ha']) + ' ha'; } else { areaStr = L.GeometryUtil.formattedNumber(area, precision['m']) + ' m²'; } } else { area /= 0.836127; // Square yards in 1 meter if (area >= 3097600) { //3097600 square yards in 1 square mile areaStr = L.GeometryUtil.formattedNumber(area / 3097600, precision['mi']) + ' mi²'; } else if (area >= 4840) { //4840 square yards in 1 acre areaStr = L.GeometryUtil.formattedNumber(area / 4840, precision['ac']) + ' acres'; } else { areaStr = L.GeometryUtil.formattedNumber(area, precision['yd']) + ' yd²'; } } return areaStr; }, // @method readableDistance(distance, units): string // Converts a metric distance to one of [ feet, nauticalMile, metric or yards ] string // // @alternative // @method readableDistance(distance, isMetric, useFeet, isNauticalMile, precision): string // Converts metric distance to distance string. // The value will be rounded as defined by the precision option object. readableDistance: function (distance, isMetric, isFeet, isNauticalMile, precision) { var distanceStr, units, precision = L.Util.extend({}, defaultPrecision, precision); if (isMetric) { units = typeof isMetric == 'string' ? isMetric : 'metric'; } else if (isFeet) { units = 'feet'; } else if (isNauticalMile) { units = 'nauticalMile'; } else { units = 'yards'; } switch (units) { case 'metric': // show metres when distance is < 1km, then show km if (distance > 1000) { distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['km']) + ' km'; } else { distanceStr = L.GeometryUtil.formattedNumber(distance, precision['m']) + ' m'; } break; case 'feet': distance *= 1.09361 * 3; distanceStr = L.GeometryUtil.formattedNumber(distance, precision['ft']) + ' ft'; break; case 'nauticalMile': distance *= 0.53996; distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['nm']) + ' nm'; break; case 'yards': default: distance *= 1.09361; if (distance > 1760) { distanceStr = L.GeometryUtil.formattedNumber(distance / 1760, precision['mi']) + ' miles'; } else { distanceStr = L.GeometryUtil.formattedNumber(distance, precision['yd']) + ' yd'; } break; } return distanceStr; }, // @method isVersion07x(): boolean // Returns true if the Leaflet version is 0.7.x, false otherwise. isVersion07x: function () { var version = L.version.split('.'); //If Version is == 0.7.* return parseInt(version[0], 10) === 0 && parseInt(version[1], 10) === 7; }, }); })(); ================================================ FILE: public/assets/lib/vendor/leaflet/ext/LatLngUtil.js ================================================ /** * @class L.LatLngUtil * @aka LatLngUtil */ L.LatLngUtil = { // Clones a LatLngs[], returns [][] // @method cloneLatLngs(LatLngs[]): L.LatLngs[] // Clone the latLng point or points or nested points and return an array with those points cloneLatLngs: function (latlngs) { var clone = []; for (var i = 0, l = latlngs.length; i < l; i++) { // Check for nested array (Polyline/Polygon) if (Array.isArray(latlngs[i])) { clone.push(L.LatLngUtil.cloneLatLngs(latlngs[i])); } else { clone.push(this.cloneLatLng(latlngs[i])); } } return clone; }, // @method cloneLatLng(LatLng): L.LatLng // Clone the latLng and return a new LatLng object. cloneLatLng: function (latlng) { return L.latLng(latlng.lat, latlng.lng); } }; ================================================ FILE: public/assets/lib/vendor/leaflet/ext/LineUtil.Intersect.js ================================================ /** * @class L.LineUtil * @aka Util * @aka L.Utils */ L.Util.extend(L.LineUtil, { // @method segmentsIntersect(): boolean // Checks to see if two line segments intersect. Does not handle degenerate cases. // http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) { return this._checkCounterclockwise(p, p2, p3) !== this._checkCounterclockwise(p1, p2, p3) && this._checkCounterclockwise(p, p1, p2) !== this._checkCounterclockwise(p, p1, p3); }, // check to see if points are in counterclockwise order _checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/ext/Polygon.Intersect.js ================================================ /** * @class L.Polygon * @aka Polygon */ L.Polygon.include({ // @method intersects(): boolean // Checks a polygon for any intersecting line segments. Ignores holes. intersects: function () { var polylineIntersects, points = this._getProjectedPoints(), len, firstPoint, lastPoint, maxIndex; if (this._tooFewPointsForIntersection()) { return false; } polylineIntersects = L.Polyline.prototype.intersects.call(this); // If already found an intersection don't need to check for any more. if (polylineIntersects) { return true; } len = points.length; firstPoint = points[0]; lastPoint = points[len - 1]; maxIndex = len - 2; // Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1) return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1); } }); ================================================ FILE: public/assets/lib/vendor/leaflet/ext/Polyline.Intersect.js ================================================ /** * @class L.Polyline * @aka Polyline */ L.Polyline.include({ // @method intersects(): boolean // Check to see if this polyline has any linesegments that intersect. // NOTE: does not support detecting intersection for degenerate cases. intersects: function () { var points = this._getProjectedPoints(), len = points ? points.length : 0, i, p, p1; if (this._tooFewPointsForIntersection()) { return false; } for (i = len - 1; i >= 3; i--) { p = points[i - 1]; p1 = points[i]; if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) { return true; } } return false; }, // @method newLatLngIntersects(): boolean // Check for intersection if new latlng was added to this polyline. // NOTE: does not support detecting intersection for degenerate cases. newLatLngIntersects: function (latlng, skipFirst) { // Cannot check a polyline for intersecting lats/lngs when not added to the map if (!this._map) { return false; } return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst); }, // @method newPointIntersects(): boolean // Check for intersection if new point was added to this polyline. // newPoint must be a layer point. // NOTE: does not support detecting intersection for degenerate cases. newPointIntersects: function (newPoint, skipFirst) { var points = this._getProjectedPoints(), len = points ? points.length : 0, lastPoint = points ? points[len - 1] : null, // The previous previous line segment. Previous line segment doesn't need testing. maxIndex = len - 2; if (this._tooFewPointsForIntersection(1)) { return false; } return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0); }, // Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these). // Cannot have intersection when < 3 line segments (< 4 points) _tooFewPointsForIntersection: function (extraPoints) { var points = this._getProjectedPoints(), len = points ? points.length : 0; // Increment length by extraPoints if present len += extraPoints || 0; return !points || len <= 3; }, // Checks a line segment intersections with any line segments before its predecessor. // Don't need to check the predecessor as will never intersect. _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) { var points = this._getProjectedPoints(), p2, p3; minIndex = minIndex || 0; // Check all previous line segments (beside the immediately previous) for intersections for (var j = maxIndex; j > minIndex; j--) { p2 = points[j - 1]; p3 = points[j]; if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) { return true; } } return false; }, _getProjectedPoints: function () { if (!this._defaultShape) { return this._originalPoints; } var points = [], _shape = this._defaultShape(); for (var i = 0; i < _shape.length; i++) { points.push(this._map.latLngToLayerPoint(_shape[i])); } return points; } }); ================================================ FILE: public/assets/lib/vendor/leaflet/ext/TouchEvents.js ================================================ L.Map.mergeOptions({ touchExtend: true }); /** * @class L.Map.TouchExtend * @aka TouchExtend */ L.Map.TouchExtend = L.Handler.extend({ // @method initialize(): void // Sets TouchExtend private accessor variables initialize: function (map) { this._map = map; this._container = map._container; this._pane = map._panes.overlayPane; }, // @method addHooks(): void // Adds dom listener events to the map container addHooks: function () { L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this); L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this); L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this); if (this._detectIE()) { L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this); L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this); L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this); L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this); } else { L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this); L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this); } }, // @method removeHooks(): void // Removes dom listener events from the map container removeHooks: function () { L.DomEvent.off(this._container, 'touchstart', this._onTouchStart); L.DomEvent.off(this._container, 'touchend', this._onTouchEnd); L.DomEvent.off(this._container, 'touchmove', this._onTouchMove); if (this._detectIE()) { L.DomEvent.off(this._container, 'MSPointerDowm', this._onTouchStart); L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd); L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove); L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel); } else { L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel); L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave); } }, _touchEvent: function (e, type) { // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events // _filterClick is what leaflet uses as a workaround. // This is a problem with more things than just android. Another problem is touchEnd has no touches in // its touch list. var touchEvent = {}; if (typeof e.touches !== 'undefined') { if (!e.touches.length) { return; } touchEvent = e.touches[0]; } else if (e.pointerType === 'touch') { touchEvent = e; if (!this._filterClick(e)) { return; } } else { return; } var containerPoint = this._map.mouseEventToContainerPoint(touchEvent), layerPoint = this._map.mouseEventToLayerPoint(touchEvent), latlng = this._map.layerPointToLatLng(layerPoint); this._map.fire(type, { latlng: latlng, layerPoint: layerPoint, containerPoint: containerPoint, pageX: touchEvent.pageX, pageY: touchEvent.pageY, originalEvent: e }); }, /** Borrowed from Leaflet and modified for bool ops **/ _filterClick: function (e) { var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); // are they closer together than 500ms yet more than 100ms? // Android typically triggers them ~300ms apart while multiple listeners // on the same event should be triggered far faster; // or check if click is simulated on the element, and if it is, reject any non-simulated events if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { L.DomEvent.stop(e); return false; } L.DomEvent._lastClick = timeStamp; return true; }, _onTouchStart: function (e) { if (!this._map._loaded) { return; } var type = 'touchstart'; this._touchEvent(e, type); }, _onTouchEnd: function (e) { if (!this._map._loaded) { return; } var type = 'touchend'; this._touchEvent(e, type); }, _onTouchCancel: function (e) { if (!this._map._loaded) { return; } var type = 'touchcancel'; if (this._detectIE()) { type = 'pointercancel'; } this._touchEvent(e, type); }, _onTouchLeave: function (e) { if (!this._map._loaded) { return; } var type = 'touchleave'; this._touchEvent(e, type); }, _onTouchMove: function (e) { if (!this._map._loaded) { return; } var type = 'touchmove'; this._touchEvent(e, type); }, _detectIE: function () { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0) { // IE 10 or older => return version number return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } var trident = ua.indexOf('Trident/'); if (trident > 0) { // IE 11 => return version number var rv = ua.indexOf('rv:'); return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); } var edge = ua.indexOf('Edge/'); if (edge > 0) { // IE 12 => return version number return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } // other browser return false; } }); L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend); /** * @class L.Marker.Touch * @aka Marker.Touch * * This isn't full Touch support. This is just to get markers to also support dom touch events after creation * #TODO: find a better way of getting markers to support touch. */ L.Marker.Touch = L.Marker.extend({ _initInteraction: function () { if (!this.addInteractiveTarget) { // 0.7.x support return this._initInteractionLegacy(); } // TODO this may need be updated to re-add touch events for 1.0+ return L.Marker.prototype._initInteraction.apply(this); }, // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js // with the addition of the touch events _initInteractionLegacy: function () { if (!this.options.clickable) { return; } // TODO refactor into something shared with Map/Path/etc. to DRY it up var icon = this._icon, events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu', 'touchstart', 'touchend', 'touchmove']; if (this._detectIE) { events.concat(['MSPointerDown', 'MSPointerUp', 'MSPointerMove', 'MSPointerCancel']); } else { events.concat(['touchcancel']); } L.DomUtil.addClass(icon, 'leaflet-clickable'); L.DomEvent.on(icon, 'click', this._onMouseClick, this); L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); for (var i = 0; i < events.length; i++) { L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); } if (L.Handler.MarkerDrag) { this.dragging = new L.Handler.MarkerDrag(this); if (this.options.draggable) { this.dragging.enable(); } } }, _detectIE: function () { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0) { // IE 10 or older => return version number return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } var trident = ua.indexOf('Trident/'); if (trident > 0) { // IE 11 => return version number var rv = ua.indexOf('rv:'); return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); } var edge = ua.indexOf('Edge/'); if (edge > 0) { // IE 12 => return version number return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } // other browser return false; } }); ================================================ FILE: public/assets/lib/vendor/leaflet/leaflet-measure.css ================================================ .leaflet-control-measure h3,.leaflet-measure-resultpopup h3{margin:0 0 12px;padding-bottom:10px;border-bottom:1px solid #ddd}.leaflet-control-measure p,.leaflet-measure-resultpopup p{margin:10px 0 0;line-height:1.5em}.leaflet-control-measure p:first-child,.leaflet-measure-resultpopup p:first-child{margin-top:0}.leaflet-control-measure .tasks,.leaflet-measure-resultpopup .tasks{margin:12px 0 0;padding:10px 0 0;border-top:1px solid #ddd;text-align:right;list-style:none;list-style-image:none}.leaflet-control-measure .tasks li,.leaflet-measure-resultpopup .tasks li{display:inline;margin:0 10px 0 0}.leaflet-control-measure .tasks li:last-child,.leaflet-measure-resultpopup .tasks li:last-child{margin-right:0}.leaflet-control-measure .coorddivider,.leaflet-measure-resultpopup .coorddivider{color:#999}.leaflet-control-measure{max-width:280px;background:#fff}.leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-control-measure .leaflet-control-measure-toggle:hover{background-size:14px 14px;background-image:url(assets/rulers.png);border:0;border-radius:4px;text-indent:100%;white-space:nowrap;overflow:hidden}.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle:hover{border-radius:2px}.leaflet-retina .leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-retina .leaflet-control-measure .leaflet-control-measure-toggle:hover{background-image:url(assets/rulers_@2X.png)}.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle,.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle:hover{background-size:16px 16px}.leaflet-control-measure .startprompt h3{margin-bottom:10px}.leaflet-control-measure .startprompt .tasks{margin-top:0;padding-top:0;border-top:0;text-align:left}.leaflet-control-measure .leaflet-control-measure-interaction{padding:10px 12px}.leaflet-control-measure .results .group{margin-top:10px;padding-top:10px;border-top:1px dotted #eaeaea}.leaflet-control-measure .results .group:first-child{margin-top:0;padding-top:0;border-top:0}.leaflet-control-measure .results .heading{margin-right:5px;color:#999}.leaflet-control-measure a.start{display:inline;width:auto;height:auto;padding-left:20px;margin-right:4px;line-height:1em;border:0;text-align:left;background-image:url("assets/start.png");background-repeat:no-repeat;background-position:0 50%;background-size:12px 12px;color:#5e66cc;text-decoration:none}.leaflet-control-measure a.start,.leaflet-control-measure a.start:hover{background-color:transparent}.leaflet-retina .leaflet-control-measure a.start{background-image:url("assets/start_@2X.png")}.leaflet-control-measure a.start:hover{opacity:.5;text-decoration:none}.leaflet-control-measure a.cancel{display:inline;width:auto;height:auto;padding-left:20px;margin-right:4px;line-height:1em;border:0;text-align:left;background-image:url("assets/cancel.png");background-repeat:no-repeat;background-position:0 50%;background-size:12px 12px;color:#5e66cc;text-decoration:none}.leaflet-control-measure a.cancel,.leaflet-control-measure a.cancel:hover{background-color:transparent}.leaflet-retina .leaflet-control-measure a.cancel{background-image:url("assets/cancel_@2X.png")}.leaflet-control-measure a.cancel:hover{opacity:.5;text-decoration:none}.leaflet-control-measure a.finish{display:inline;width:auto;height:auto;padding-left:20px;margin-right:4px;line-height:1em;border:0;text-align:left;background-image:url("assets/check.png");background-repeat:no-repeat;background-position:0 50%;background-size:12px 12px;color:#5e66cc;text-decoration:none}.leaflet-control-measure a.finish,.leaflet-control-measure a.finish:hover{background-color:transparent}.leaflet-retina .leaflet-control-measure a.finish{background-image:url("assets/check_@2X.png")}.leaflet-control-measure a.finish:hover{opacity:.5;text-decoration:none}.leaflet-measure-resultpopup a.zoomto{display:inline;width:auto;height:auto;padding-left:20px;margin-right:4px;line-height:1em;border:0;text-align:left;background-image:url("assets/focus.png");background-repeat:no-repeat;background-position:0 50%;background-size:12px 12px;color:#5e66cc;text-decoration:none}.leaflet-measure-resultpopup a.zoomto,.leaflet-measure-resultpopup a.zoomto:hover{background-color:transparent}.leaflet-retina .leaflet-measure-resultpopup a.zoomto{background-image:url("assets/focus_@2X.png")}.leaflet-measure-resultpopup a.zoomto:hover{opacity:.5;text-decoration:none}.leaflet-measure-resultpopup a.deletemarkup{display:inline;width:auto;height:auto;padding-left:20px;margin-right:4px;line-height:1em;border:0;text-align:left;background-image:url("assets/trash.png");background-repeat:no-repeat;background-position:0 50%;background-size:12px 12px;color:#5e66cc;text-decoration:none}.leaflet-measure-resultpopup a.deletemarkup,.leaflet-measure-resultpopup a.deletemarkup:hover{background-color:transparent}.leaflet-retina .leaflet-measure-resultpopup a.deletemarkup{background-image:url("assets/trash_@2X.png")}.leaflet-measure-resultpopup a.deletemarkup:hover{opacity:.5;text-decoration:none} ================================================ FILE: public/assets/lib/vendor/leaflet/leaflet-measure.js ================================================ !function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/dist/",t(t.s=28)}([function(e,t,r){function n(e){return null==e?void 0===e?u:a:l&&l in Object(e)?i(e):s(e)}var o=r(4),i=r(38),s=r(39),a="[object Null]",u="[object Undefined]",l=o?o.toStringTag:void 0;e.exports=n},function(e,t){function r(e){return null!=e&&"object"==typeof e}e.exports=r},function(e,t){function r(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}e.exports=r},function(e,t,r){"use strict";function n(e,t,r){if(r=r||{},!h(r))throw new Error("options is invalid");var n=r.bbox,o=r.id;if(void 0===e)throw new Error("geometry is required");if(t&&t.constructor!==Object)throw new Error("properties must be an Object");n&&d(n),o&&m(o);var i={type:"Feature"};return o&&(i.id=o),n&&(i.bbox=n),i.properties=t||{},i.geometry=e,i}function o(e,t,r){if(!e)throw new Error("coordinates is required");if(!Array.isArray(e))throw new Error("coordinates must be an Array");if(e.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!p(e[0])||!p(e[1]))throw new Error("coordinates must contain numbers");return n({type:"Point",coordinates:e},t,r)}function i(e,t,r){if(!e)throw new Error("coordinates is required");for(var o=0;o<e.length;o++){var i=e[o];if(i.length<4)throw new Error("Each LinearRing of a Polygon must have 4 or more Positions.");for(var s=0;s<i[i.length-1].length;s++){if(0===o&&0===s&&!p(i[0][0])||!p(i[0][1]))throw new Error("coordinates must contain numbers");if(i[i.length-1][s]!==i[0][s])throw new Error("First and last Position are not equivalent.")}}return n({type:"Polygon",coordinates:e},t,r)}function s(e,t,r){if(!e)throw new Error("coordinates is required");if(e.length<2)throw new Error("coordinates must be an array of two or more positions");if(!p(e[0][1])||!p(e[0][1]))throw new Error("coordinates must contain numbers");return n({type:"LineString",coordinates:e},t,r)}function a(e,t,r){if(!e)throw new Error("coordinates is required");return n({type:"MultiLineString",coordinates:e},t,r)}function u(e,t,r){if(!e)throw new Error("coordinates is required");return n({type:"MultiPoint",coordinates:e},t,r)}function l(e,t,r){if(!e)throw new Error("coordinates is required");return n({type:"MultiPolygon",coordinates:e},t,r)}function c(e,t){if(void 0===e||null===e)throw new Error("radians is required");if(t&&"string"!=typeof t)throw new Error("units must be a string");var r=y[t||"kilometers"];if(!r)throw new Error(t+" units is invalid");return e*r}function f(e){if(null===e||void 0===e)throw new Error("degrees is required");return e%360*Math.PI/180}function p(e){return!isNaN(e)&&null!==e&&!Array.isArray(e)}function h(e){return!!e&&e.constructor===Object}function d(e){if(!e)throw new Error("bbox is required");if(!Array.isArray(e))throw new Error("bbox must be an Array");if(4!==e.length&&6!==e.length)throw new Error("bbox must be an Array of 4 or 6 numbers");e.forEach(function(e){if(!p(e))throw new Error("bbox must only contain numbers")})}function m(e){if(!e)throw new Error("id is required");if(-1===["string","number"].indexOf(typeof e))throw new Error("id must be a number or a string")}r.d(t,"b",function(){return n}),r.d(t,"f",function(){return o}),r.d(t,"e",function(){return s}),r.d(t,"g",function(){return c}),r.d(t,"a",function(){return f}),r.d(t,"c",function(){return p}),r.d(t,"d",function(){return h});var y={meters:6371008.8,metres:6371008.8,millimeters:6371008800,millimetres:6371008800,centimeters:637100880,centimetres:637100880,kilometers:6371.0088,kilometres:6371.0088,miles:3958.761333810546,nauticalmiles:6371008.8/1852,inches:6371008.8*39.37,yards:6371008.8/1.0936,feet:20902260.511392,radians:1,degrees:6371008.8/111325}},function(e,t,r){var n=r(5),o=n.Symbol;e.exports=o},function(e,t,r){var n=r(11),o="object"==typeof self&&self&&self.Object===Object&&self,i=n||o||Function("return this")();e.exports=i},function(e,t){function r(e,t){return e===t||e!==e&&t!==t}e.exports=r},function(e,t,r){function n(e){return null!=e&&i(e.length)&&!o(e)}var o=r(10),i=r(16);e.exports=n},function(e,t,r){function n(e,t,r){"__proto__"==t&&o?o(e,t,{configurable:!0,enumerable:!0,value:r,writable:!0}):e[t]=r}var o=r(9);e.exports=n},function(e,t,r){var n=r(35),o=function(){try{var e=n(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=o},function(e,t,r){function n(e){if(!i(e))return!1;var t=o(e);return t==a||t==u||t==s||t==l}var o=r(0),i=r(2),s="[object AsyncFunction]",a="[object Function]",u="[object GeneratorFunction]",l="[object Proxy]";e.exports=n},function(e,t,r){(function(t){var r="object"==typeof t&&t&&t.Object===Object&&t;e.exports=r}).call(t,r(37))},function(e,t,r){function n(e,t){return s(i(e,t,o),e+"")}var o=r(13),i=r(45),s=r(46);e.exports=n},function(e,t){function r(e){return e}e.exports=r},function(e,t){function r(e,t,r){switch(r.length){case 0:return e.call(t);case 1:return e.call(t,r[0]);case 2:return e.call(t,r[0],r[1]);case 3:return e.call(t,r[0],r[1],r[2])}return e.apply(t,r)}e.exports=r},function(e,t,r){function n(e,t,r){if(!a(r))return!1;var n=typeof t;return!!("number"==n?i(r)&&s(t,r.length):"string"==n&&t in r)&&o(r[t],e)}var o=r(6),i=r(7),s=r(17),a=r(2);e.exports=n},function(e,t){function r(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=n}var n=9007199254740991;e.exports=r},function(e,t){function r(e,t){var r=typeof e;return!!(t=null==t?n:t)&&("number"==r||"symbol"!=r&&o.test(e))&&e>-1&&e%1==0&&e<t}var n=9007199254740991,o=/^(?:0|[1-9]\d*)$/;e.exports=r},function(e,t,r){function n(e,t){var r=s(e),n=!r&&i(e),c=!r&&!n&&a(e),p=!r&&!n&&!c&&l(e),h=r||n||c||p,d=h?o(e.length,String):[],m=d.length;for(var y in e)!t&&!f.call(e,y)||h&&("length"==y||c&&("offset"==y||"parent"==y)||p&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||u(y,m))||d.push(y);return d}var o=r(51),i=r(52),s=r(19),a=r(54),u=r(17),l=r(56),c=Object.prototype,f=c.hasOwnProperty;e.exports=n},function(e,t){var r=Array.isArray;e.exports=r},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t){function r(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}var n=Object.prototype;e.exports=r},function(e,t,r){function n(e){if(!i(e))return!1;var t=o(e);return t==u||t==a||"string"==typeof e.message&&"string"==typeof e.name&&!s(e)}var o=r(0),i=r(1),s=r(63),a="[object DOMException]",u="[object Error]";e.exports=n},function(e,t){function r(e,t){return function(r){return e(t(r))}}e.exports=r},function(e,t){function r(e,t){for(var r=-1,n=null==e?0:e.length,o=Array(n);++r<n;)o[r]=t(e[r],r,e);return o}e.exports=r},function(e,t){var r=/<%=([\s\S]+?)%>/g;e.exports=r},function(e,t,r){function n(e){return null==e?"":o(e)}var o=r(75);e.exports=n},function(e,t,r){"use strict";function n(e,t,r){if(null!==e)for(var o,i,s,a,u,l,c,f,p=0,h=0,d=e.type,m="FeatureCollection"===d,y="Feature"===d,v=m?e.features.length:1,g=0;g<v;g++){c=m?e.features[g].geometry:y?e.geometry:e,f=!!c&&"GeometryCollection"===c.type,u=f?c.geometries.length:1;for(var b=0;b<u;b++){var _=0,j=0;if(null!==(a=f?c.geometries[b]:c)){l=a.coordinates;var x=a.type;switch(p=!r||"Polygon"!==x&&"MultiPolygon"!==x?0:1,x){case null:break;case"Point":if(!1===t(l,h,g,_,j))return!1;h++,_++;break;case"LineString":case"MultiPoint":for(o=0;o<l.length;o++){if(!1===t(l[o],h,g,_,j))return!1;h++,"MultiPoint"===x&&_++}"LineString"===x&&_++;break;case"Polygon":case"MultiLineString":for(o=0;o<l.length;o++){for(i=0;i<l[o].length-p;i++){if(!1===t(l[o][i],h,g,_,j))return!1;h++}"MultiLineString"===x&&_++,"Polygon"===x&&j++}"Polygon"===x&&_++;break;case"MultiPolygon":for(o=0;o<l.length;o++){for("MultiPolygon"===x&&(j=0),i=0;i<l[o].length;i++){for(s=0;s<l[o][i].length-p;s++){if(!1===t(l[o][i][s],h,g,_,j))return!1;h++}j++}_++}break;case"GeometryCollection":for(o=0;o<a.geometries.length;o++)if(!1===n(a.geometries[o],t,r))return!1;break;default:throw new Error("Unknown Geometry Type")}}}}}function o(e,t){var r,n,o,i,s,a,u,l,c,f,p=0,h="FeatureCollection"===e.type,d="Feature"===e.type,m=h?e.features.length:1;for(r=0;r<m;r++){for(a=h?e.features[r].geometry:d?e.geometry:e,l=h?e.features[r].properties:d?e.properties:{},c=h?e.features[r].bbox:d?e.bbox:void 0,f=h?e.features[r].id:d?e.id:void 0,u=!!a&&"GeometryCollection"===a.type,s=u?a.geometries.length:1,o=0;o<s;o++)if(null!==(i=u?a.geometries[o]:a))switch(i.type){case"Point":case"LineString":case"MultiPoint":case"Polygon":case"MultiLineString":case"MultiPolygon":if(!1===t(i,p,l,c,f))return!1;break;case"GeometryCollection":for(n=0;n<i.geometries.length;n++)if(!1===t(i.geometries[n],p,l,c,f))return!1;break;default:throw new Error("Unknown Geometry Type")}else if(!1===t(null,p,l,c,f))return!1;p++}}function i(e,t,r){var n=r;return o(e,function(e,o,i,s,a){n=0===o&&void 0===r?e:t(n,e,o,i,s,a)}),n}function s(e,t){o(e,function(e,r,n,o,i){var s=null===e?null:e.type;switch(s){case null:case"Point":case"LineString":case"Polygon":if(!1===t(Object(l.b)(e,n,{bbox:o,id:i}),r,0))return!1;return}var a;switch(s){case"MultiPoint":a="Point";break;case"MultiLineString":a="LineString";break;case"MultiPolygon":a="Polygon"}for(var u=0;u<e.coordinates.length;u++){var c=e.coordinates[u],f={type:a,coordinates:c};if(!1===t(Object(l.b)(f,n),r,u))return!1}})}function a(e,t){s(e,function(e,r,o){var i=0;if(e.geometry){var s=e.geometry.type;if("Point"!==s&&"MultiPoint"!==s){var a;return!1!==n(e,function(n,s,u,c,f){if(void 0===a)return void(a=n);var p=Object(l.e)([a,n],e.properties);if(!1===t(p,r,o,f,i))return!1;i++,a=n})&&void 0}}})}function u(e,t,r){var n=r,o=!1;return a(e,function(e,i,s,a,u){n=!1===o&&void 0===r?e:t(n,e,i,s,a,u),o=!0}),n}r.d(t,"a",function(){return i}),r.d(t,"b",function(){return u});var l=r(3)},function(e,t,r){e.exports=r(29)},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}r(30);var o=r(31),i=n(o),s=r(79),a=n(s),u=r(80),l=n(u),c=r(85),f=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t.default=e,t}(c),p=r(86),h=n(p),d=r(87),m=r(88),y={imports:{numberFormat:d.numberFormat},interpolate:/{{([\s\S]+?)}}/g},v=(0,i.default)(m.controlTemplate,y),g=(0,i.default)(m.resultsTemplate,y),b=(0,i.default)(m.pointPopupTemplate,y),_=(0,i.default)(m.linePopupTemplate,y),j=(0,i.default)(m.areaPopupTemplate,y);L.Control.Measure=L.Control.extend({_className:"leaflet-control-measure",options:{units:{},position:"topright",primaryLengthUnit:"feet",secondaryLengthUnit:"miles",primaryAreaUnit:"acres",activeColor:"#ABE67E",completedColor:"#C8F2BE",captureZIndex:1e4,popupOptions:{className:"leaflet-measure-resultpopup",autoPanPadding:[10,10]}},initialize:function(e){L.setOptions(this,e);var t=this.options,r=t.activeColor,n=t.completedColor;this._symbols=new h.default({activeColor:r,completedColor:n}),this.options.units=L.extend({},a.default,this.options.units)},onAdd:function(e){return this._map=e,this._latlngs=[],this._initLayout(),e.on("click",this._collapse,this),this._layer=L.layerGroup().addTo(e),this._container},onRemove:function(e){e.off("click",this._collapse,this),e.removeLayer(this._layer)},_initLayout:function(){var e=this._className,t=this._container=L.DomUtil.create("div",e+" leaflet-bar");t.innerHTML=v({model:{className:e}}),t.setAttribute("aria-haspopup",!0),L.DomEvent.disableClickPropagation(t),L.DomEvent.disableScrollPropagation(t);var r=this.$toggle=(0,c.selectOne)(".js-toggle",t);this.$interaction=(0,c.selectOne)(".js-interaction",t);var n=(0,c.selectOne)(".js-start",t),o=(0,c.selectOne)(".js-cancel",t),i=(0,c.selectOne)(".js-finish",t);this.$startPrompt=(0,c.selectOne)(".js-startprompt",t),this.$measuringPrompt=(0,c.selectOne)(".js-measuringprompt",t),this.$startHelp=(0,c.selectOne)(".js-starthelp",t),this.$results=(0,c.selectOne)(".js-results",t),this.$measureTasks=(0,c.selectOne)(".js-measuretasks",t),this._collapse(),this._updateMeasureNotStarted(),L.Browser.android||(L.DomEvent.on(t,"mouseenter",this._expand,this),L.DomEvent.on(t,"mouseleave",this._collapse,this)),L.DomEvent.on(r,"click",L.DomEvent.stop),L.Browser.touch?L.DomEvent.on(r,"click",this._expand,this):L.DomEvent.on(r,"focus",this._expand,this),L.DomEvent.on(n,"click",L.DomEvent.stop),L.DomEvent.on(n,"click",this._startMeasure,this),L.DomEvent.on(o,"click",L.DomEvent.stop),L.DomEvent.on(o,"click",this._finishMeasure,this),L.DomEvent.on(i,"click",L.DomEvent.stop),L.DomEvent.on(i,"click",this._handleMeasureDoubleClick,this)},_expand:function(){f.hide(this.$toggle),f.show(this.$interaction)},_collapse:function(){this._locked||(f.hide(this.$interaction),f.show(this.$toggle))},_updateMeasureNotStarted:function(){f.hide(this.$startHelp),f.hide(this.$results),f.hide(this.$measureTasks),f.hide(this.$measuringPrompt),f.show(this.$startPrompt)},_updateMeasureStartedNoPoints:function(){f.hide(this.$results),f.show(this.$startHelp),f.show(this.$measureTasks),f.hide(this.$startPrompt),f.show(this.$measuringPrompt)},_updateMeasureStartedWithPoints:function(){f.hide(this.$startHelp),f.show(this.$results),f.show(this.$measureTasks),f.hide(this.$startPrompt),f.show(this.$measuringPrompt)},_startMeasure:function(){this._locked=!0,this._measureVertexes=L.featureGroup().addTo(this._layer),this._captureMarker=L.marker(this._map.getCenter(),{clickable:!0,zIndexOffset:this.options.captureZIndex,opacity:0}).addTo(this._layer),this._setCaptureMarkerIcon(),this._captureMarker.on("mouseout",this._handleMapMouseOut,this).on("dblclick",this._handleMeasureDoubleClick,this).on("click",this._handleMeasureClick,this),this._map.on("mousemove",this._handleMeasureMove,this).on("mouseout",this._handleMapMouseOut,this).on("move",this._centerCaptureMarker,this).on("resize",this._setCaptureMarkerIcon,this),L.DomEvent.on(this._container,"mouseenter",this._handleMapMouseOut,this),this._updateMeasureStartedNoPoints(),this._map.fire("measurestart",null,!1)},_finishMeasure:function(){var e=L.extend({},this._resultsModel,{points:this._latlngs});this._locked=!1,L.DomEvent.off(this._container,"mouseover",this._handleMapMouseOut,this),this._clearMeasure(),this._captureMarker.off("mouseout",this._handleMapMouseOut,this).off("dblclick",this._handleMeasureDoubleClick,this).off("click",this._handleMeasureClick,this),this._map.off("mousemove",this._handleMeasureMove,this).off("mouseout",this._handleMapMouseOut,this).off("move",this._centerCaptureMarker,this).off("resize",this._setCaptureMarkerIcon,this),this._layer.removeLayer(this._measureVertexes).removeLayer(this._captureMarker),this._measureVertexes=null,this._updateMeasureNotStarted(),this._collapse(),this._map.fire("measurefinish",e,!1)},_clearMeasure:function(){this._latlngs=[],this._resultsModel=null,this._measureVertexes.clearLayers(),this._measureDrag&&this._layer.removeLayer(this._measureDrag),this._measureArea&&this._layer.removeLayer(this._measureArea),this._measureBoundary&&this._layer.removeLayer(this._measureBoundary),this._measureDrag=null,this._measureArea=null,this._measureBoundary=null},_centerCaptureMarker:function(){this._captureMarker.setLatLng(this._map.getCenter())},_setCaptureMarkerIcon:function(){this._captureMarker.setIcon(L.divIcon({iconSize:this._map.getSize().multiplyBy(2)}))},_getMeasurementDisplayStrings:function(e){function t(e,t,o,i,s){if(t&&n[t]){var a=r(e,n[t],i,s);if(o&&n[o]){a=a+" ("+r(e,n[o],i,s)+")"}return a}return r(e,null,i,s)}function r(e,t,r,n){var o={acres:"Acres",feet:"Feet",kilometers:"Kilometers",hectares:"Hectares",meters:"Meters",miles:"Miles",sqfeet:"Sq Feet",sqmeters:"Sq Meters",sqmiles:"Sq Miles"},i=L.extend({factor:1,decimals:0},t);return[(0,d.numberFormat)(e*i.factor,i.decimals,r||".",n||","),o[i.display]||i.display].join(" ")}var n=this.options.units;return{lengthDisplay:t(e.length,this.options.primaryLengthUnit,this.options.secondaryLengthUnit,this.options.decPoint,this.options.thousandsSep),areaDisplay:t(e.area,this.options.primaryAreaUnit,this.options.secondaryAreaUnit,this.options.decPoint,this.options.thousandsSep)}},_updateResults:function(){var e=(0,l.default)(this._latlngs),t=this._resultsModel=L.extend({},e,this._getMeasurementDisplayStrings(e),{pointCount:this._latlngs.length});this.$results.innerHTML=g({model:t})},_handleMeasureMove:function(e){this._measureDrag?this._measureDrag.setLatLng(e.latlng):this._measureDrag=L.circleMarker(e.latlng,this._symbols.getSymbol("measureDrag")).addTo(this._layer),this._measureDrag.bringToFront()},_handleMeasureDoubleClick:function(){var e=this._latlngs,t=void 0,r=void 0;if(this._finishMeasure(),e.length){e.length>2&&e.push(e[0]);var n=(0,l.default)(e);1===e.length?(t=L.circleMarker(e[0],this._symbols.getSymbol("resultPoint")),r=b({model:n})):2===e.length?(t=L.polyline(e,this._symbols.getSymbol("resultLine")),r=_({model:L.extend({},n,this._getMeasurementDisplayStrings(n))})):(t=L.polygon(e,this._symbols.getSymbol("resultArea")),r=j({model:L.extend({},n,this._getMeasurementDisplayStrings(n))}));var o=L.DomUtil.create("div","");o.innerHTML=r;var i=(0,c.selectOne)(".js-zoomto",o);i&&(L.DomEvent.on(i,"click",L.DomEvent.stop),L.DomEvent.on(i,"click",function(){t.getBounds?this._map.fitBounds(t.getBounds(),{padding:[20,20],maxZoom:17}):t.getLatLng&&this._map.panTo(t.getLatLng())},this));var s=(0,c.selectOne)(".js-deletemarkup",o);s&&(L.DomEvent.on(s,"click",L.DomEvent.stop),L.DomEvent.on(s,"click",function(){this._layer.removeLayer(t)},this)),t.addTo(this._layer),t.bindPopup(o,this.options.popupOptions),t.getBounds?t.openPopup(t.getBounds().getCenter()):t.getLatLng&&t.openPopup(t.getLatLng())}},_handleMeasureClick:function(e){var t=this._map.mouseEventToLatLng(e.originalEvent),r=this._latlngs[this._latlngs.length-1],n=this._symbols.getSymbol("measureVertex");r&&t.equals(r)||(this._latlngs.push(t),this._addMeasureArea(this._latlngs),this._addMeasureBoundary(this._latlngs),this._measureVertexes.eachLayer(function(e){e.setStyle(n),e._path.setAttribute("class",n.className)}),this._addNewVertex(t),this._measureBoundary&&this._measureBoundary.bringToFront(),this._measureVertexes.bringToFront()),this._updateResults(),this._updateMeasureStartedWithPoints()},_handleMapMouseOut:function(){this._measureDrag&&(this._layer.removeLayer(this._measureDrag),this._measureDrag=null)},_addNewVertex:function(e){L.circleMarker(e,this._symbols.getSymbol("measureVertexActive")).addTo(this._measureVertexes)},_addMeasureArea:function(e){if(e.length<3)return void(this._measureArea&&(this._layer.removeLayer(this._measureArea),this._measureArea=null));this._measureArea?this._measureArea.setLatLngs(e):this._measureArea=L.polygon(e,this._symbols.getSymbol("measureArea")).addTo(this._layer)},_addMeasureBoundary:function(e){if(e.length<2)return void(this._measureBoundary&&(this._layer.removeLayer(this._measureBoundary),this._measureBoundary=null));this._measureBoundary?this._measureBoundary.setLatLngs(e):this._measureBoundary=L.polyline(e,this._symbols.getSymbol("measureBoundary")).addTo(this._layer)}}),L.Map.mergeOptions({measureControl:!1}),L.Map.addInitHook(function(){this.options.measureControl&&(this.measureControl=(new L.Control.Measure).addTo(this))}),L.control.measure=function(e){return new L.Control.Measure(e)}},function(e,t){},function(e,t,r){function n(e,t,r){var n=h.imports._.templateSettings||h;r&&c(e,t,r)&&(t=void 0),e=d(e),t=o({},t,n,a);var j,x,M=o({},t.imports,n.imports,a),w=f(M),L=s(M,w),O=0,P=t.interpolate||b,k="__p += '",C=RegExp((t.escape||b).source+"|"+P.source+"|"+(P===p?g:b).source+"|"+(t.evaluate||b).source+"|$","g"),E="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";e.replace(C,function(t,r,n,o,i,s){return n||(n=o),k+=e.slice(O,s).replace(_,u),r&&(j=!0,k+="' +\n__e("+r+") +\n'"),i&&(x=!0,k+="';\n"+i+";\n__p += '"),n&&(k+="' +\n((__t = ("+n+")) == null ? '' : __t) +\n'"),O=s+t.length,t}),k+="';\n";var S=t.variable;S||(k="with (obj) {\n"+k+"\n}\n"),k=(x?k.replace(m,""):k).replace(y,"$1").replace(v,"$1;"),k="function("+(S||"obj")+") {\n"+(S?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(j?", __e = _.escape":"")+(x?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+k+"return __p\n}";var A=i(function(){return Function(w,E+"return "+k).apply(void 0,L)});if(A.source=k,l(A))throw A;return A}var o=r(32),i=r(62),s=r(65),a=r(66),u=r(67),l=r(22),c=r(15),f=r(68),p=r(25),h=r(71),d=r(26),m=/\b__p \+= '';/g,y=/\b(__p \+=) '' \+/g,v=/(__e\(.*?\)|\b__t\)) \+\n'';/g,g=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,b=/($^)/,_=/['\n\r\u2028\u2029\\]/g;e.exports=n},function(e,t,r){var n=r(33),o=r(44),i=r(50),s=o(function(e,t,r,o){n(t,i(t),e,o)});e.exports=s},function(e,t,r){function n(e,t,r,n){var s=!r;r||(r={});for(var a=-1,u=t.length;++a<u;){var l=t[a],c=n?n(r[l],e[l],l,r,e):void 0;void 0===c&&(c=e[l]),s?i(r,l,c):o(r,l,c)}return r}var o=r(34),i=r(8);e.exports=n},function(e,t,r){function n(e,t,r){var n=e[t];a.call(e,t)&&i(n,r)&&(void 0!==r||t in e)||o(e,t,r)}var o=r(8),i=r(6),s=Object.prototype,a=s.hasOwnProperty;e.exports=n},function(e,t,r){function n(e,t){var r=i(e,t);return o(r)?r:void 0}var o=r(36),i=r(43);e.exports=n},function(e,t,r){function n(e){return!(!s(e)||i(e))&&(o(e)?d:l).test(a(e))}var o=r(10),i=r(40),s=r(2),a=r(42),u=/[\\^$.*+?()[\]{}|]/g,l=/^\[object .+?Constructor\]$/,c=Function.prototype,f=Object.prototype,p=c.toString,h=f.hasOwnProperty,d=RegExp("^"+p.call(h).replace(u,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=n},function(e,t){var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){function n(e){var t=s.call(e,u),r=e[u];try{e[u]=void 0;var n=!0}catch(e){}var o=a.call(e);return n&&(t?e[u]=r:delete e[u]),o}var o=r(4),i=Object.prototype,s=i.hasOwnProperty,a=i.toString,u=o?o.toStringTag:void 0;e.exports=n},function(e,t){function r(e){return o.call(e)}var n=Object.prototype,o=n.toString;e.exports=r},function(e,t,r){function n(e){return!!i&&i in e}var o=r(41),i=function(){var e=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}();e.exports=n},function(e,t,r){var n=r(5),o=n["__core-js_shared__"];e.exports=o},function(e,t){function r(e){if(null!=e){try{return o.call(e)}catch(e){}try{return e+""}catch(e){}}return""}var n=Function.prototype,o=n.toString;e.exports=r},function(e,t){function r(e,t){return null==e?void 0:e[t]}e.exports=r},function(e,t,r){function n(e){return o(function(t,r){var n=-1,o=r.length,s=o>1?r[o-1]:void 0,a=o>2?r[2]:void 0;for(s=e.length>3&&"function"==typeof s?(o--,s):void 0,a&&i(r[0],r[1],a)&&(s=o<3?void 0:s,o=1),t=Object(t);++n<o;){var u=r[n];u&&e(t,u,n,s)}return t})}var o=r(12),i=r(15);e.exports=n},function(e,t,r){function n(e,t,r){return t=i(void 0===t?e.length-1:t,0),function(){for(var n=arguments,s=-1,a=i(n.length-t,0),u=Array(a);++s<a;)u[s]=n[t+s];s=-1;for(var l=Array(t+1);++s<t;)l[s]=n[s];return l[t]=r(u),o(e,this,l)}}var o=r(14),i=Math.max;e.exports=n},function(e,t,r){var n=r(47),o=r(49),i=o(n);e.exports=i},function(e,t,r){var n=r(48),o=r(9),i=r(13),s=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:n(t),writable:!0})}:i;e.exports=s},function(e,t){function r(e){return function(){return e}}e.exports=r},function(e,t){function r(e){var t=0,r=0;return function(){var s=i(),a=o-(s-r);if(r=s,a>0){if(++t>=n)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}var n=800,o=16,i=Date.now;e.exports=r},function(e,t,r){function n(e){return s(e)?o(e,!0):i(e)}var o=r(18),i=r(60),s=r(7);e.exports=n},function(e,t){function r(e,t){for(var r=-1,n=Array(e);++r<e;)n[r]=t(r);return n}e.exports=r},function(e,t,r){var n=r(53),o=r(1),i=Object.prototype,s=i.hasOwnProperty,a=i.propertyIsEnumerable,u=n(function(){return arguments}())?n:function(e){return o(e)&&s.call(e,"callee")&&!a.call(e,"callee")};e.exports=u},function(e,t,r){function n(e){return i(e)&&o(e)==s}var o=r(0),i=r(1),s="[object Arguments]";e.exports=n},function(e,t,r){(function(e){var n=r(5),o=r(55),i="object"==typeof t&&t&&!t.nodeType&&t,s=i&&"object"==typeof e&&e&&!e.nodeType&&e,a=s&&s.exports===i,u=a?n.Buffer:void 0,l=u?u.isBuffer:void 0,c=l||o;e.exports=c}).call(t,r(20)(e))},function(e,t){function r(){return!1}e.exports=r},function(e,t,r){var n=r(57),o=r(58),i=r(59),s=i&&i.isTypedArray,a=s?o(s):n;e.exports=a},function(e,t,r){function n(e){return s(e)&&i(e.length)&&!!a[o(e)]}var o=r(0),i=r(16),s=r(1),a={};a["[object Float32Array]"]=a["[object Float64Array]"]=a["[object Int8Array]"]=a["[object Int16Array]"]=a["[object Int32Array]"]=a["[object Uint8Array]"]=a["[object Uint8ClampedArray]"]=a["[object Uint16Array]"]=a["[object Uint32Array]"]=!0,a["[object Arguments]"]=a["[object Array]"]=a["[object ArrayBuffer]"]=a["[object Boolean]"]=a["[object DataView]"]=a["[object Date]"]=a["[object Error]"]=a["[object Function]"]=a["[object Map]"]=a["[object Number]"]=a["[object Object]"]=a["[object RegExp]"]=a["[object Set]"]=a["[object String]"]=a["[object WeakMap]"]=!1,e.exports=n},function(e,t){function r(e){return function(t){return e(t)}}e.exports=r},function(e,t,r){(function(e){var n=r(11),o="object"==typeof t&&t&&!t.nodeType&&t,i=o&&"object"==typeof e&&e&&!e.nodeType&&e,s=i&&i.exports===o,a=s&&n.process,u=function(){try{return a&&a.binding&&a.binding("util")}catch(e){}}();e.exports=u}).call(t,r(20)(e))},function(e,t,r){function n(e){if(!o(e))return s(e);var t=i(e),r=[];for(var n in e)("constructor"!=n||!t&&u.call(e,n))&&r.push(n);return r}var o=r(2),i=r(21),s=r(61),a=Object.prototype,u=a.hasOwnProperty;e.exports=n},function(e,t){function r(e){var t=[];if(null!=e)for(var r in Object(e))t.push(r);return t}e.exports=r},function(e,t,r){var n=r(14),o=r(12),i=r(22),s=o(function(e,t){try{return n(e,void 0,t)}catch(e){return i(e)?e:new Error(e)}});e.exports=s},function(e,t,r){function n(e){if(!s(e)||o(e)!=a)return!1;var t=i(e);if(null===t)return!0;var r=f.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&c.call(r)==p}var o=r(0),i=r(64),s=r(1),a="[object Object]",u=Function.prototype,l=Object.prototype,c=u.toString,f=l.hasOwnProperty,p=c.call(Object);e.exports=n},function(e,t,r){var n=r(23),o=n(Object.getPrototypeOf,Object);e.exports=o},function(e,t,r){function n(e,t){return o(t,function(t){return e[t]})}var o=r(24);e.exports=n},function(e,t,r){function n(e,t,r,n){return void 0===e||o(e,i[r])&&!s.call(n,r)?t:e}var o=r(6),i=Object.prototype,s=i.hasOwnProperty;e.exports=n},function(e,t){function r(e){return"\\"+n[e]}var n={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"};e.exports=r},function(e,t,r){function n(e){return s(e)?o(e):i(e)}var o=r(18),i=r(69),s=r(7);e.exports=n},function(e,t,r){function n(e){if(!o(e))return i(e);var t=[];for(var r in Object(e))a.call(e,r)&&"constructor"!=r&&t.push(r);return t}var o=r(21),i=r(70),s=Object.prototype,a=s.hasOwnProperty;e.exports=n},function(e,t,r){var n=r(23),o=n(Object.keys,Object);e.exports=o},function(e,t,r){var n=r(72),o=r(77),i=r(78),s=r(25),a={escape:o,evaluate:i,interpolate:s,variable:"",imports:{_:{escape:n}}};e.exports=a},function(e,t,r){function n(e){return e=i(e),e&&a.test(e)?e.replace(s,o):e}var o=r(73),i=r(26),s=/[&<>"']/g,a=RegExp(s.source);e.exports=n},function(e,t,r){var n=r(74),o={"&":"&","<":"<",">":">",'"':""","'":"'"},i=n(o);e.exports=i},function(e,t){function r(e){return function(t){return null==e?void 0:e[t]}}e.exports=r},function(e,t,r){function n(e){if("string"==typeof e)return e;if(s(e))return i(e,n)+"";if(a(e))return c?c.call(e):"";var t=e+"";return"0"==t&&1/e==-u?"-0":t}var o=r(4),i=r(24),s=r(19),a=r(76),u=1/0,l=o?o.prototype:void 0,c=l?l.toString:void 0;e.exports=n},function(e,t,r){function n(e){return"symbol"==typeof e||i(e)&&o(e)==s}var o=r(0),i=r(1),s="[object Symbol]";e.exports=n},function(e,t){var r=/<%-([\s\S]+?)%>/g;e.exports=r},function(e,t){var r=/<%([\s\S]+?)%>/g;e.exports=r},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={acres:{factor:24711e-8,display:"acres",decimals:2},feet:{factor:3.2808,display:"feet",decimals:0},kilometers:{factor:.001,display:"kilometers",decimals:2},hectares:{factor:1e-4,display:"hectares",decimals:2},meters:{factor:1,display:"meters",decimals:0},miles:{factor:3.2808/5280,display:"miles",decimals:2},sqfeet:{factor:10.7639,display:"sqfeet",decimals:0},sqmeters:{factor:1,display:"sqmeters",decimals:0},sqmiles:{factor:3.86102e-7,display:"sqmiles",decimals:2}}},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function o(e){return e<10?"0"+e.toString():e.toString()}function i(e,t,r){var n=Math.abs(e),i=Math.floor(n),s=Math.floor(60*(n-i)),a=Math.round(3600*(n-i-s/60)*100)/100,u=n===e?t:r;return o(i)+"° "+o(s)+"' "+o(a)+'" '+u}function s(e){var t=e[e.length-1],r=e.map(function(e){return[e.lat,e.lng]}),n=L.polyline(r),o=L.polygon(r),s=1e3*(0,u.default)(n.toGeoJSON(),{units:"kilometers"}),a=(0,c.default)(o.toGeoJSON());return{lastCoord:{dd:{x:t.lng,y:t.lat},dms:{x:i(t.lng,"E","W"),y:i(t.lat,"N","S")}},length:s,area:a}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=s;var a=r(81),u=n(a),l=r(84),c=n(l)},function(e,t,r){"use strict";function n(e,t){if(t=t||{},!Object(s.d)(t))throw new Error("options is invalid");if(!e)throw new Error("geojson is required");return Object(i.b)(e,function(e,r){var n=r.geometry.coordinates;return e+Object(o.a)(n[0],n[1],t)},0)}Object.defineProperty(t,"__esModule",{value:!0});var o=r(82),i=r(27),s=r(3);t.default=n},function(e,t,r){"use strict";function n(e,t,r){if(r=r||{},!Object(i.d)(r))throw new Error("options is invalid");var n=r.units,s=Object(o.a)(e),a=Object(o.a)(t),u=Object(i.a)(a[1]-s[1]),l=Object(i.a)(a[0]-s[0]),c=Object(i.a)(s[1]),f=Object(i.a)(a[1]),p=Math.pow(Math.sin(u/2),2)+Math.pow(Math.sin(l/2),2)*Math.cos(c)*Math.cos(f);return Object(i.g)(2*Math.atan2(Math.sqrt(p),Math.sqrt(1-p)),n)}var o=r(83),i=r(3);t.a=n},function(e,t,r){"use strict";function n(e){if(!e)throw new Error("coord is required");if("Feature"===e.type&&null!==e.geometry&&"Point"===e.geometry.type)return e.geometry.coordinates;if("Point"===e.type)return e.coordinates;if(Array.isArray(e)&&e.length>=2&&void 0===e[0].length&&void 0===e[1].length)return e;throw new Error("coord must be GeoJSON Point or an Array of numbers")}r.d(t,"a",function(){return n});r(3)},function(e,t,r){"use strict";function n(e){return Object(u.a)(e,function(e,t){return e+o(t)},0)}function o(e){var t,r=0;switch(e.type){case"Polygon":return i(e.coordinates);case"MultiPolygon":for(t=0;t<e.coordinates.length;t++)r+=i(e.coordinates[t]);return r;case"Point":case"MultiPoint":case"LineString":case"MultiLineString":return 0;case"GeometryCollection":for(t=0;t<e.geometries.length;t++)r+=o(e.geometries[t]);return r}}function i(e){var t=0;if(e&&e.length>0){t+=Math.abs(s(e[0]));for(var r=1;r<e.length;r++)t-=Math.abs(s(e[r]))}return t}function s(e){var t,r,n,o,i,s,u,c=0,f=e.length;if(f>2){for(u=0;u<f;u++)u===f-2?(o=f-2,i=f-1,s=0):u===f-1?(o=f-1,i=0,s=1):(o=u,i=u+1,s=u+2),t=e[o],r=e[i],n=e[s],c+=(a(n[0])-a(t[0]))*Math.sin(a(r[1]));c=c*l*l/2}return c}function a(e){return e*Math.PI/180}Object.defineProperty(t,"__esModule",{value:!0});var u=r(27),l=6378137;t.default=n},function(e,t,r){"use strict";function n(e,t){return t||(t=document),t.querySelector(e)}function o(e,t){return t||(t=document),Array.prototype.slice.call(t.querySelectorAll(e))}function i(e){if(e)return e.setAttribute("style","display:none;"),e}function s(e){if(e)return e.removeAttribute("style"),e}Object.defineProperty(t,"__esModule",{value:!0}),t.selectOne=n,t.selectAll=o,t.hide=i,t.show=s},function(e,t,r){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),i={activeColor:"#ABE67E",completedColor:"#C8F2BE"},s=function(){function e(t){n(this,e),this._options=L.extend({},i,this._options,t)}return o(e,[{key:"getSymbol",value:function(e){return{measureDrag:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:.7,fillColor:this._options.activeColor,fillOpacity:.5,className:"layer-measuredrag"},measureArea:{clickable:!1,stroke:!1,fillColor:this._options.activeColor,fillOpacity:.2,className:"layer-measurearea"},measureBoundary:{clickable:!1,color:this._options.activeColor,weight:2,opacity:.9,fill:!1,className:"layer-measureboundary"},measureVertex:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:1,fillColor:this._options.activeColor,fillOpacity:.7,className:"layer-measurevertex"},measureVertexActive:{clickable:!1,radius:4,color:this._options.activeColor,weight:2,opacity:1,fillColor:this._options.activeColor,fillOpacity:1,className:"layer-measurevertex active"},resultArea:{clickable:!0,color:this._options.completedColor,weight:2,opacity:.9,fillColor:this._options.completedColor,fillOpacity:.2,className:"layer-measure-resultarea"},resultLine:{clickable:!0,color:this._options.completedColor,weight:3,opacity:.9,fill:!1,className:"layer-measure-resultline"},resultPoint:{clickable:!0,radius:4,color:this._options.completedColor,weight:2,opacity:1,fillColor:this._options.completedColor,fillOpacity:.7,className:"layer-measure-resultpoint"}}[e]}}]),e}();t.default=s},function(e,t,r){"use strict";function n(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:".",n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:",",o=e<0?"-":"",i=Math.abs(+e||0),s=parseInt(i.toFixed(t),10)+"",a=s.length>3?s.length%3:0;return[o,a?s.substr(0,a)+n:"",s.substr(a).replace(/(\d{3})(?=\d)/g,"$1"+n),t?""+r+Math.abs(i-s).toFixed(t).slice(2):""].join("")}Object.defineProperty(t,"__esModule",{value:!0}),t.numberFormat=n},function(e,t,r){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(89);Object.defineProperty(t,"controlTemplate",{enumerable:!0,get:function(){return n(o).default}});var i=r(90);Object.defineProperty(t,"resultsTemplate",{enumerable:!0,get:function(){return n(i).default}});var s=r(91);Object.defineProperty(t,"pointPopupTemplate",{enumerable:!0,get:function(){return n(s).default}});var a=r(92);Object.defineProperty(t,"linePopupTemplate",{enumerable:!0,get:function(){return n(a).default}});var u=r(93);Object.defineProperty(t,"areaPopupTemplate",{enumerable:!0,get:function(){return n(u).default}})},function(e,t,r){e.exports='<a class="{{ model.className }}-toggle js-toggle" href=# title="Measure distances and areas">Measure</a> <div class="{{ model.className }}-interaction js-interaction"> <div class="js-startprompt startprompt"> <h3>Measure distances and areas</h3> <ul class=tasks> <a href=# class="js-start start">Create a new measurement</a> </ul> </div> <div class=js-measuringprompt> <h3>Measure distances and areas</h3> <p class=js-starthelp>Start creating a measurement by adding points to the map</p> <div class="js-results results"></div> <ul class="js-measuretasks tasks"> <li><a href=# class="js-cancel cancel">Cancel</a></li> <li><a href=# class="js-finish finish">Finish measurement</a></li> </ul> </div> </div> '},function(e,t,r){e.exports='<div class=group> <p class="lastpoint heading">Last point</p> <p>{{ model.lastCoord.dms.y }} <span class=coorddivider>/</span> {{ model.lastCoord.dms.x }}</p> <p>{{ numberFormat(model.lastCoord.dd.y, 6) }} <span class=coorddivider>/</span> {{ numberFormat(model.lastCoord.dd.x, 6) }}</p> </div> <% if (model.pointCount > 1) { %> <div class=group> <p><span class=heading>Path distance</span> {{ model.lengthDisplay }}</p> </div> <% } %> <% if (model.pointCount > 2) { %> <div class=group> <p><span class=heading>Area</span> {{ model.areaDisplay }}</p> </div> <% } %> '},function(e,t,r){e.exports='<h3>Point location</h3> <p>{{ model.lastCoord.dms.y }} <span class=coorddivider>/</span> {{ model.lastCoord.dms.x }}</p> <p>{{ numberFormat(model.lastCoord.dd.y, 6) }} <span class=coorddivider>/</span> {{ numberFormat(model.lastCoord.dd.x, 6) }}</p> <ul class=tasks> <li><a href=# class="js-zoomto zoomto">Center on this location</a></li> <li><a href=# class="js-deletemarkup deletemarkup">Delete</a></li> </ul> '},function(e,t,r){e.exports='<h3>Linear measurement</h3> <p>{{ model.lengthDisplay }}</p> <ul class=tasks> <li><a href=# class="js-zoomto zoomto">Center on this line</a></li> <li><a href=# class="js-deletemarkup deletemarkup">Delete</a></li> </ul> '},function(e,t,r){e.exports='<h3>Area measurement</h3> <p>{{ model.areaDisplay }}</p> <p>{{ model.lengthDisplay }} Perimeter</p> <ul class=tasks> <li><a href=# class="js-zoomto zoomto">Center on this area</a></li> <li><a href=# class="js-deletemarkup deletemarkup">Delete</a></li> </ul> '}]); ================================================ FILE: public/assets/lib/vendor/leaflet/leaflet.css ================================================ /* required styles */ .leaflet-pane, .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow, .leaflet-tile-container, .leaflet-pane > svg, .leaflet-pane > canvas, .leaflet-zoom-box, .leaflet-image-layer, .leaflet-layer { position: absolute; left: 0; top: 0; } .leaflet-container { overflow: hidden; } .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow { -webkit-user-select: none; -moz-user-select: none; user-select: none; -webkit-user-drag: none; } /* Prevents IE11 from highlighting tiles in blue */ .leaflet-tile::selection { background: transparent; } /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ .leaflet-safari .leaflet-tile { image-rendering: -webkit-optimize-contrast; } /* hack that prevents hw layers "stretching" when loading new tiles */ .leaflet-safari .leaflet-tile-container { width: 1600px; height: 1600px; -webkit-transform-origin: 0 0; } .leaflet-marker-icon, .leaflet-marker-shadow { display: block; } /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ .leaflet-container .leaflet-overlay-pane svg { max-width: none !important; max-height: none !important; } .leaflet-container .leaflet-marker-pane img, .leaflet-container .leaflet-shadow-pane img, .leaflet-container .leaflet-tile-pane img, .leaflet-container img.leaflet-image-layer, .leaflet-container .leaflet-tile { max-width: none !important; max-height: none !important; width: auto; padding: 0; } .leaflet-container img.leaflet-tile { /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ mix-blend-mode: plus-lighter; } .leaflet-container.leaflet-touch-zoom { -ms-touch-action: pan-x pan-y; touch-action: pan-x pan-y; } .leaflet-container.leaflet-touch-drag { -ms-touch-action: pinch-zoom; /* Fallback for FF which doesn't support pinch-zoom */ touch-action: none; touch-action: pinch-zoom; } .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { -ms-touch-action: none; touch-action: none; } .leaflet-container { -webkit-tap-highlight-color: transparent; } .leaflet-container a { -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); } .leaflet-tile { filter: inherit; visibility: hidden; } .leaflet-tile-loaded { visibility: inherit; } .leaflet-zoom-box { width: 0; height: 0; -moz-box-sizing: border-box; box-sizing: border-box; z-index: 800; } /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ .leaflet-overlay-pane svg { -moz-user-select: none; } .leaflet-pane { z-index: 400; } .leaflet-tile-pane { z-index: 200; } .leaflet-overlay-pane { z-index: 400; } .leaflet-shadow-pane { z-index: 500; } .leaflet-marker-pane { z-index: 600; } .leaflet-tooltip-pane { z-index: 650; } .leaflet-popup-pane { z-index: 700; } .leaflet-map-pane canvas { z-index: 100; } .leaflet-map-pane svg { z-index: 200; } .leaflet-vml-shape { width: 1px; height: 1px; } .lvml { behavior: url(#default#VML); display: inline-block; position: absolute; } /* control positioning */ .leaflet-control { position: relative; z-index: 800; pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ pointer-events: auto; } .leaflet-top, .leaflet-bottom { position: absolute; z-index: 1000; pointer-events: none; } .leaflet-top { top: 0; } .leaflet-right { right: 0; } .leaflet-bottom { bottom: 0; } .leaflet-left { left: 0; } .leaflet-control { float: left; clear: both; } .leaflet-right .leaflet-control { float: right; } .leaflet-top .leaflet-control { margin-top: 10px; } .leaflet-bottom .leaflet-control { margin-bottom: 10px; } .leaflet-left .leaflet-control { margin-left: 10px; } .leaflet-right .leaflet-control { margin-right: 10px; } /* zoom and fade animations */ .leaflet-fade-anim .leaflet-popup { opacity: 0; -webkit-transition: opacity 0.2s linear; -moz-transition: opacity 0.2s linear; transition: opacity 0.2s linear; } .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { opacity: 1; } .leaflet-zoom-animated { -webkit-transform-origin: 0 0; -ms-transform-origin: 0 0; transform-origin: 0 0; } svg.leaflet-zoom-animated { will-change: transform; } .leaflet-zoom-anim .leaflet-zoom-animated { -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); transition: transform 0.25s cubic-bezier(0,0,0.25,1); } .leaflet-zoom-anim .leaflet-tile, .leaflet-pan-anim .leaflet-tile { -webkit-transition: none; -moz-transition: none; transition: none; } .leaflet-zoom-anim .leaflet-zoom-hide { visibility: hidden; } /* cursors */ .leaflet-interactive { cursor: pointer; } .leaflet-grab { cursor: -webkit-grab; cursor: -moz-grab; cursor: grab; } .leaflet-crosshair, .leaflet-crosshair .leaflet-interactive { cursor: crosshair; } .leaflet-popup-pane, .leaflet-control { cursor: auto; } .leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive, .leaflet-dragging .leaflet-marker-draggable { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: grabbing; } /* marker & overlays interactivity */ .leaflet-marker-icon, .leaflet-marker-shadow, .leaflet-image-layer, .leaflet-pane > svg path, .leaflet-tile-container { pointer-events: none; } .leaflet-marker-icon.leaflet-interactive, .leaflet-image-layer.leaflet-interactive, .leaflet-pane > svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ pointer-events: auto; } /* visual tweaks */ .leaflet-container { background: #ddd; outline-offset: 1px; } .leaflet-container a { color: #0078A8; } .leaflet-zoom-box { border: 2px dotted #38f; background: rgba(255,255,255,0.5); } /* general typography */ .leaflet-container { font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; font-size: 12px; font-size: 0.75rem; line-height: 1.5; } /* general toolbar styles */ .leaflet-bar { box-shadow: 0 1px 5px rgba(0,0,0,0.65); border-radius: 4px; } .leaflet-bar a { background-color: #fff; border-bottom: 1px solid #ccc; width: 26px; height: 26px; line-height: 26px; display: block; text-align: center; text-decoration: none; color: black; } .leaflet-bar a, .leaflet-control-layers-toggle { background-position: 50% 50%; background-repeat: no-repeat; display: block; } .leaflet-bar a:hover, .leaflet-bar a:focus { background-color: #f4f4f4; } .leaflet-bar a:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; } .leaflet-bar a:last-child { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-bottom: none; } .leaflet-bar a.leaflet-disabled { cursor: default; background-color: #f4f4f4; color: #bbb; } .leaflet-touch .leaflet-bar a { width: 30px; height: 30px; line-height: 30px; } .leaflet-touch .leaflet-bar a:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } .leaflet-touch .leaflet-bar a:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } /* zoom control */ .leaflet-control-zoom-in, .leaflet-control-zoom-out { font: bold 18px 'Lucida Console', Monaco, monospace; text-indent: 1px; } .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { font-size: 22px; } /* layers control */ .leaflet-control-layers { box-shadow: 0 1px 5px rgba(0,0,0,0.4); background: #fff; border-radius: 5px; } .leaflet-control-layers-toggle { background-image: url(images/layers.png); width: 36px; height: 36px; } .leaflet-retina .leaflet-control-layers-toggle { background-image: url(images/layers-2x.png); background-size: 26px 26px; } .leaflet-touch .leaflet-control-layers-toggle { width: 44px; height: 44px; } .leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { display: none; } .leaflet-control-layers-expanded .leaflet-control-layers-list { display: block; position: relative; } .leaflet-control-layers-expanded { padding: 6px 10px 6px 6px; color: #333; background: #fff; } .leaflet-control-layers-scrollbar { overflow-y: scroll; overflow-x: hidden; padding-right: 5px; } .leaflet-control-layers-selector { margin-top: 2px; position: relative; top: 1px; } .leaflet-control-layers label { display: block; font-size: 13px; font-size: 1.08333em; } .leaflet-control-layers-separator { height: 0; border-top: 1px solid #ddd; margin: 5px -10px 5px -6px; } /* Default icon URLs */ .leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ background-image: url(images/marker-icon.png); } /* attribution and scale controls */ .leaflet-container .leaflet-control-attribution { background: #fff; background: rgba(255, 255, 255, 0.8); margin: 0; } .leaflet-control-attribution, .leaflet-control-scale-line { padding: 0 5px; color: #333; line-height: 1.4; } .leaflet-control-attribution a { text-decoration: none; } .leaflet-control-attribution a:hover, .leaflet-control-attribution a:focus { text-decoration: underline; } .leaflet-attribution-flag { display: inline !important; vertical-align: baseline !important; width: 1em; height: 0.6669em; } .leaflet-left .leaflet-control-scale { margin-left: 5px; } .leaflet-bottom .leaflet-control-scale { margin-bottom: 5px; } .leaflet-control-scale-line { border: 2px solid #777; border-top: none; line-height: 1.1; padding: 2px 5px 1px; white-space: nowrap; -moz-box-sizing: border-box; box-sizing: border-box; background: rgba(255, 255, 255, 0.8); text-shadow: 1px 1px #fff; } .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; border-bottom: none; margin-top: -2px; } .leaflet-control-scale-line:not(:first-child):not(:last-child) { border-bottom: 2px solid #777; } .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { box-shadow: none; } .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { border: 2px solid rgba(0,0,0,0.2); background-clip: padding-box; } /* popup */ .leaflet-popup { position: absolute; text-align: center; margin-bottom: 20px; } .leaflet-popup-content-wrapper { padding: 1px; text-align: left; border-radius: 12px; } .leaflet-popup-content { margin: 13px 24px 13px 20px; line-height: 1.3; font-size: 13px; font-size: 1.08333em; min-height: 1px; } .leaflet-popup-content p { margin: 17px 0; margin: 1.3em 0; } .leaflet-popup-tip-container { width: 40px; height: 20px; position: absolute; left: 50%; margin-top: -1px; margin-left: -20px; overflow: hidden; pointer-events: none; } .leaflet-popup-tip { width: 17px; height: 17px; padding: 1px; margin: -10px auto 0; pointer-events: auto; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; color: #333; box-shadow: 0 3px 14px rgba(0,0,0,0.4); } .leaflet-container a.leaflet-popup-close-button { position: absolute; top: 0; right: 0; border: none; text-align: center; width: 24px; height: 24px; font: 16px/24px Tahoma, Verdana, sans-serif; color: #757575; text-decoration: none; background: transparent; } .leaflet-container a.leaflet-popup-close-button:hover, .leaflet-container a.leaflet-popup-close-button:focus { color: #585858; } .leaflet-popup-scrolled { overflow: auto; } .leaflet-oldie .leaflet-popup-content-wrapper { -ms-zoom: 1; } .leaflet-oldie .leaflet-popup-tip { width: 24px; margin: 0 auto; -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); } .leaflet-oldie .leaflet-control-zoom, .leaflet-oldie .leaflet-control-layers, .leaflet-oldie .leaflet-popup-content-wrapper, .leaflet-oldie .leaflet-popup-tip { border: 1px solid #999; } /* div icon */ .leaflet-div-icon { background: #fff; border: 1px solid #666; } /* Tooltip */ /* Base styles for the element that has a tooltip */ .leaflet-tooltip { position: absolute; padding: 6px; background-color: #fff; border: 1px solid #fff; border-radius: 3px; color: #222; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; pointer-events: none; box-shadow: 0 1px 3px rgba(0,0,0,0.4); } .leaflet-tooltip.leaflet-interactive { cursor: pointer; pointer-events: auto; } .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { position: absolute; pointer-events: none; border: 6px solid transparent; background: transparent; content: ""; } /* Directions */ .leaflet-tooltip-bottom { margin-top: 6px; } .leaflet-tooltip-top { margin-top: -6px; } .leaflet-tooltip-bottom:before, .leaflet-tooltip-top:before { left: 50%; margin-left: -6px; } .leaflet-tooltip-top:before { bottom: 0; margin-bottom: -12px; border-top-color: #fff; } .leaflet-tooltip-bottom:before { top: 0; margin-top: -12px; margin-left: -6px; border-bottom-color: #fff; } .leaflet-tooltip-left { margin-left: -6px; } .leaflet-tooltip-right { margin-left: 6px; } .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { top: 50%; margin-top: -6px; } .leaflet-tooltip-left:before { right: 0; margin-right: -12px; border-left-color: #fff; } .leaflet-tooltip-right:before { left: 0; margin-left: -12px; border-right-color: #fff; } /* Printing */ @media print { /* Prevent printers from removing background-images of controls. */ .leaflet-control { -webkit-print-color-adjust: exact; print-color-adjust: exact; } } ================================================ FILE: public/assets/lib/vendor/leaflet/leaflet.draw.css ================================================ /* ================================================================== */ /* Toolbars /* ================================================================== */ .leaflet-draw-section { position: relative; } .leaflet-draw-toolbar { margin-top: 12px; } .leaflet-draw-toolbar-top { margin-top: 0; } .leaflet-draw-toolbar-notop a:first-child { border-top-right-radius: 0; } .leaflet-draw-toolbar-nobottom a:last-child { border-bottom-right-radius: 0; } .leaflet-draw-toolbar a { background-image: url('images/spritesheet.png'); background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); background-repeat: no-repeat; background-size: 300px 30px; background-clip: padding-box; } .leaflet-retina .leaflet-draw-toolbar a { background-image: url('images/spritesheet-2x.png'); background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); } .leaflet-draw a { display: block; text-align: center; text-decoration: none; } .leaflet-draw a .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } /* ================================================================== */ /* Toolbar actions menu /* ================================================================== */ .leaflet-draw-actions { display: none; list-style: none; margin: 0; padding: 0; position: absolute; left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ top: 0; white-space: nowrap; } .leaflet-touch .leaflet-draw-actions { left: 32px; } .leaflet-right .leaflet-draw-actions { right: 26px; left: auto; } .leaflet-touch .leaflet-right .leaflet-draw-actions { right: 32px; left: auto; } .leaflet-draw-actions li { display: inline-block; } .leaflet-draw-actions li:first-child a { border-left: none; } .leaflet-draw-actions li:last-child a { -webkit-border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0; } .leaflet-right .leaflet-draw-actions li:last-child a { -webkit-border-radius: 0; border-radius: 0; } .leaflet-right .leaflet-draw-actions li:first-child a { -webkit-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; } .leaflet-draw-actions a { background-color: #919187; border-left: 1px solid #AAA; color: #FFF; font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; line-height: 28px; text-decoration: none; padding-left: 10px; padding-right: 10px; height: 28px; } .leaflet-touch .leaflet-draw-actions a { font-size: 12px; line-height: 30px; height: 30px; } .leaflet-draw-actions-bottom { margin-top: 0; } .leaflet-draw-actions-top { margin-top: 1px; } .leaflet-draw-actions-top a, .leaflet-draw-actions-bottom a { height: 27px; line-height: 27px; } .leaflet-draw-actions a:hover { background-color: #A0A098; } .leaflet-draw-actions-top.leaflet-draw-actions-bottom a { height: 26px; line-height: 26px; } /* ================================================================== */ /* Draw toolbar /* ================================================================== */ .leaflet-draw-toolbar .leaflet-draw-draw-polyline { background-position: -2px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline { background-position: 0 -1px; } .leaflet-draw-toolbar .leaflet-draw-draw-polygon { background-position: -31px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon { background-position: -29px -1px; } .leaflet-draw-toolbar .leaflet-draw-draw-rectangle { background-position: -62px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle { background-position: -60px -1px; } .leaflet-draw-toolbar .leaflet-draw-draw-circle { background-position: -92px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle { background-position: -90px -1px; } .leaflet-draw-toolbar .leaflet-draw-draw-marker { background-position: -122px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker { background-position: -120px -1px; } .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker { background-position: -273px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker { background-position: -271px -1px; } /* ================================================================== */ /* Edit toolbar /* ================================================================== */ .leaflet-draw-toolbar .leaflet-draw-edit-edit { background-position: -152px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit { background-position: -150px -1px; } .leaflet-draw-toolbar .leaflet-draw-edit-remove { background-position: -182px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove { background-position: -180px -1px; } .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { background-position: -212px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { background-position: -210px -1px; } .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { background-position: -242px -2px; } .leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { background-position: -240px -2px; } /* ================================================================== */ /* Drawing styles /* ================================================================== */ .leaflet-mouse-marker { background-color: #fff; cursor: crosshair; } .leaflet-draw-tooltip { background: rgb(54, 54, 54); background: rgba(0, 0, 0, 0.5); border: 1px solid transparent; -webkit-border-radius: 4px; border-radius: 4px; color: #fff; font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif; margin-left: 20px; margin-top: -21px; padding: 4px 8px; position: absolute; visibility: hidden; white-space: nowrap; z-index: 6; } .leaflet-draw-tooltip:before { border-right: 6px solid black; border-right-color: rgba(0, 0, 0, 0.5); border-top: 6px solid transparent; border-bottom: 6px solid transparent; content: ""; position: absolute; top: 7px; left: -7px; } .leaflet-error-draw-tooltip { background-color: #F2DEDE; border: 1px solid #E6B6BD; color: #B94A48; } .leaflet-error-draw-tooltip:before { border-right-color: #E6B6BD; } .leaflet-draw-tooltip-single { margin-top: -12px } .leaflet-draw-tooltip-subtext { color: #f8d5e4; } .leaflet-draw-guide-dash { font-size: 1%; opacity: 0.6; position: absolute; width: 5px; height: 5px; } /* ================================================================== */ /* Edit styles /* ================================================================== */ .leaflet-edit-marker-selected { background-color: rgba(254, 87, 161, 0.1); border: 4px dashed rgba(254, 87, 161, 0.6); -webkit-border-radius: 4px; border-radius: 4px; box-sizing: content-box; } .leaflet-edit-move { cursor: move; } .leaflet-edit-resize { cursor: pointer; } /* ================================================================== */ /* Old IE styles /* ================================================================== */ .leaflet-oldie .leaflet-draw-toolbar { border: 1px solid #999; } ================================================ FILE: public/assets/lib/vendor/leaflet/leaflet.js ================================================ // @ts-nocheck /* @preserve * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var e,i,n=1,o=arguments.length;n<o;n++)for(e in i=arguments[n])t[e]=i[e];return t}var R=Object.create||function(t){return N.prototype=t,new N};function N(){}function a(t,e){var i,n=Array.prototype.slice;return t.bind?t.bind.apply(t,n.call(arguments,1)):(i=n.call(arguments,2),function(){return t.apply(e,i.length?i.concat(n.call(arguments)):arguments)})}var D=0;function h(t){return"_leaflet_id"in t||(t._leaflet_id=++D),t._leaflet_id}function j(t,e,i){var n,o,s=function(){n=!1,o&&(r.apply(i,o),o=!1)},r=function(){n?o=arguments:(t.apply(i,arguments),setTimeout(s,e),n=!0)};return r}function H(t,e,i){var n=e[1],e=e[0],o=n-e;return t===n&&i?t:((t-e)%o+o)%o+e}function u(){return!1}function i(t,e){return!1===e?t:(e=Math.pow(10,void 0===e?6:e),Math.round(t*e)/e)}function W(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function F(t){return W(t).split(/\s+/)}function c(t,e){for(var i in Object.prototype.hasOwnProperty.call(t,"options")||(t.options=t.options?R(t.options):{}),e)t.options[i]=e[i];return t.options}function U(t,e,i){var n,o=[];for(n in t)o.push(encodeURIComponent(i?n.toUpperCase():n)+"="+encodeURIComponent(t[n]));return(e&&-1!==e.indexOf("?")?"&":"?")+o.join("&")}var V=/\{ *([\w_ -]+) *\}/g;function q(t,i){return t.replace(V,function(t,e){e=i[e];if(void 0===e)throw new Error("No value provided for variable "+t);return e="function"==typeof e?e(i):e})}var d=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function G(t,e){for(var i=0;i<t.length;i++)if(t[i]===e)return i;return-1}var K="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function Y(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var X=0;function J(t){var e=+new Date,i=Math.max(0,16-(e-X));return X=e+i,window.setTimeout(t,i)}var $=window.requestAnimationFrame||Y("RequestAnimationFrame")||J,Q=window.cancelAnimationFrame||Y("CancelAnimationFrame")||Y("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function x(t,e,i){if(!i||$!==J)return $.call(window,a(t,e));t.call(e)}function r(t){t&&Q.call(window,t)}var tt={__proto__:null,extend:l,create:R,bind:a,get lastId(){return D},stamp:h,throttle:j,wrapNum:H,falseFn:u,formatNum:i,trim:W,splitWords:F,setOptions:c,getParamString:U,template:q,isArray:d,indexOf:G,emptyImageUrl:K,requestFn:$,cancelFn:Q,requestAnimFrame:x,cancelAnimFrame:r};function et(){}et.extend=function(t){function e(){c(this),this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()}var i,n=e.__super__=this.prototype,o=R(n);for(i in(o.constructor=e).prototype=o,this)Object.prototype.hasOwnProperty.call(this,i)&&"prototype"!==i&&"__super__"!==i&&(e[i]=this[i]);if(t.statics&&l(e,t.statics),t.includes){var s=t.includes;if("undefined"!=typeof L&&L&&L.Mixin){s=d(s)?s:[s];for(var r=0;r<s.length;r++)s[r]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",(new Error).stack)}l.apply(null,[o].concat(t.includes))}return l(o,t),delete o.statics,delete o.includes,o.options&&(o.options=n.options?R(n.options):{},l(o.options,t.options)),o._initHooks=[],o.callInitHooks=function(){if(!this._initHooksCalled){n.callInitHooks&&n.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=o._initHooks.length;t<e;t++)o._initHooks[t].call(this)}},e},et.include=function(t){var e=this.prototype.options;return l(this.prototype,t),t.options&&(this.prototype.options=e,this.mergeOptions(t.options)),this},et.mergeOptions=function(t){return l(this.prototype.options,t),this},et.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i),this};var e={on:function(t,e,i){if("object"==typeof t)for(var n in t)this._on(n,t[n],e);else for(var o=0,s=(t=F(t)).length;o<s;o++)this._on(t[o],e,i);return this},off:function(t,e,i){if(arguments.length)if("object"==typeof t)for(var n in t)this._off(n,t[n],e);else{t=F(t);for(var o=1===arguments.length,s=0,r=t.length;s<r;s++)o?this._off(t[s]):this._off(t[s],e,i)}else delete this._events;return this},_on:function(t,e,i,n){"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1===this._listens(t,e,i)&&(e={fn:e,ctx:i=i===this?void 0:i},n&&(e.once=!0),this._events=this._events||{},this._events[t]=this._events[t]||[],this._events[t].push(e))},_off:function(t,e,i){var n,o,s;if(this._events&&(n=this._events[t]))if(1===arguments.length){if(this._firingCount)for(o=0,s=n.length;o<s;o++)n[o].fn=u;delete this._events[t]}else"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1!==(e=this._listens(t,e,i))&&(i=n[e],this._firingCount&&(i.fn=u,this._events[t]=n=n.slice()),n.splice(e,1))},fire:function(t,e,i){if(this.listens(t,i)){var n=l({},e,{type:t,target:this,sourceTarget:e&&e.sourceTarget||this});if(this._events){var o=this._events[t];if(o){this._firingCount=this._firingCount+1||1;for(var s=0,r=o.length;s<r;s++){var a=o[s],h=a.fn;a.once&&this.off(t,h,a.ctx),h.call(a.ctx||this,n)}this._firingCount--}}i&&this._propagateEvent(n)}return this},listens:function(t,e,i,n){"string"!=typeof t&&console.warn('"string" type argument expected');var o=e,s=("function"!=typeof e&&(n=!!e,i=o=void 0),this._events&&this._events[t]);if(s&&s.length&&!1!==this._listens(t,o,i))return!0;if(n)for(var r in this._eventParents)if(this._eventParents[r].listens(t,e,i,n))return!0;return!1},_listens:function(t,e,i){if(this._events){var n=this._events[t]||[];if(!e)return!!n.length;i===this&&(i=void 0);for(var o=0,s=n.length;o<s;o++)if(n[o].fn===e&&n[o].ctx===i)return o}return!1},once:function(t,e,i){if("object"==typeof t)for(var n in t)this._on(n,t[n],e,!0);else for(var o=0,s=(t=F(t)).length;o<s;o++)this._on(t[o],e,i,!0);return this},addEventParent:function(t){return this._eventParents=this._eventParents||{},this._eventParents[h(t)]=t,this},removeEventParent:function(t){return this._eventParents&&delete this._eventParents[h(t)],this},_propagateEvent:function(t){for(var e in this._eventParents)this._eventParents[e].fire(t.type,l({layer:t.target,propagatedFrom:t.target},t),!0)}},it=(e.addEventListener=e.on,e.removeEventListener=e.clearAllEventListeners=e.off,e.addOneTimeEventListener=e.once,e.fireEvent=e.fire,e.hasEventListeners=e.listens,et.extend(e));function p(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e}var nt=Math.trunc||function(t){return 0<t?Math.floor(t):Math.ceil(t)};function m(t,e,i){return t instanceof p?t:d(t)?new p(t[0],t[1]):null==t?t:"object"==typeof t&&"x"in t&&"y"in t?new p(t.x,t.y):new p(t,e,i)}function f(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;n<o;n++)this.extend(i[n])}function _(t,e){return!t||t instanceof f?t:new f(t,e)}function s(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;n<o;n++)this.extend(i[n])}function g(t,e){return t instanceof s?t:new s(t,e)}function v(t,e,i){if(isNaN(t)||isNaN(e))throw new Error("Invalid LatLng object: ("+t+", "+e+")");this.lat=+t,this.lng=+e,void 0!==i&&(this.alt=+i)}function w(t,e,i){return t instanceof v?t:d(t)&&"object"!=typeof t[0]?3===t.length?new v(t[0],t[1],t[2]):2===t.length?new v(t[0],t[1]):null:null==t?t:"object"==typeof t&&"lat"in t?new v(t.lat,"lng"in t?t.lng:t.lon,t.alt):void 0===e?null:new v(t,e,i)}p.prototype={clone:function(){return new p(this.x,this.y)},add:function(t){return this.clone()._add(m(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(m(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new p(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new p(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=nt(this.x),this.y=nt(this.y),this},distanceTo:function(t){var e=(t=m(t)).x-this.x,t=t.y-this.y;return Math.sqrt(e*e+t*t)},equals:function(t){return(t=m(t)).x===this.x&&t.y===this.y},contains:function(t){return t=m(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+i(this.x)+", "+i(this.y)+")"}},f.prototype={extend:function(t){var e,i;if(t){if(t instanceof p||"number"==typeof t[0]||"x"in t)e=i=m(t);else if(e=(t=_(t)).min,i=t.max,!e||!i)return this;this.min||this.max?(this.min.x=Math.min(e.x,this.min.x),this.max.x=Math.max(i.x,this.max.x),this.min.y=Math.min(e.y,this.min.y),this.max.y=Math.max(i.y,this.max.y)):(this.min=e.clone(),this.max=i.clone())}return this},getCenter:function(t){return m((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return m(this.min.x,this.max.y)},getTopRight:function(){return m(this.max.x,this.min.y)},getTopLeft:function(){return this.min},getBottomRight:function(){return this.max},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return(t=("number"==typeof t[0]||t instanceof p?m:_)(t))instanceof f?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=_(t);var e=this.min,i=this.max,n=t.min,t=t.max,o=t.x>=e.x&&n.x<=i.x,t=t.y>=e.y&&n.y<=i.y;return o&&t},overlaps:function(t){t=_(t);var e=this.min,i=this.max,n=t.min,t=t.max,o=t.x>e.x&&n.x<i.x,t=t.y>e.y&&n.y<i.y;return o&&t},isValid:function(){return!(!this.min||!this.max)},pad:function(t){var e=this.min,i=this.max,n=Math.abs(e.x-i.x)*t,t=Math.abs(e.y-i.y)*t;return _(m(e.x-n,e.y-t),m(i.x+n,i.y+t))},equals:function(t){return!!t&&(t=_(t),this.min.equals(t.getTopLeft())&&this.max.equals(t.getBottomRight()))}},s.prototype={extend:function(t){var e,i,n=this._southWest,o=this._northEast;if(t instanceof v)i=e=t;else{if(!(t instanceof s))return t?this.extend(w(t)||g(t)):this;if(e=t._southWest,i=t._northEast,!e||!i)return this}return n||o?(n.lat=Math.min(e.lat,n.lat),n.lng=Math.min(e.lng,n.lng),o.lat=Math.max(i.lat,o.lat),o.lng=Math.max(i.lng,o.lng)):(this._southWest=new v(e.lat,e.lng),this._northEast=new v(i.lat,i.lng)),this},pad:function(t){var e=this._southWest,i=this._northEast,n=Math.abs(e.lat-i.lat)*t,t=Math.abs(e.lng-i.lng)*t;return new s(new v(e.lat-n,e.lng-t),new v(i.lat+n,i.lng+t))},getCenter:function(){return new v((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new v(this.getNorth(),this.getWest())},getSouthEast:function(){return new v(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t=("number"==typeof t[0]||t instanceof v||"lat"in t?w:g)(t);var e,i,n=this._southWest,o=this._northEast;return t instanceof s?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=n.lat&&i.lat<=o.lat&&e.lng>=n.lng&&i.lng<=o.lng},intersects:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=e.lat&&n.lat<=i.lat,t=t.lng>=e.lng&&n.lng<=i.lng;return o&&t},overlaps:function(t){t=g(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>e.lat&&n.lat<i.lat,t=t.lng>e.lng&&n.lng<i.lng;return o&&t},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t,e){return!!t&&(t=g(t),this._southWest.equals(t.getSouthWest(),e)&&this._northEast.equals(t.getNorthEast(),e))},isValid:function(){return!(!this._southWest||!this._northEast)}};var ot={latLngToPoint:function(t,e){t=this.projection.project(t),e=this.scale(e);return this.transformation._transform(t,e)},pointToLatLng:function(t,e){e=this.scale(e),t=this.transformation.untransform(t,e);return this.projection.unproject(t)},project:function(t){return this.projection.project(t)},unproject:function(t){return this.projection.unproject(t)},scale:function(t){return 256*Math.pow(2,t)},zoom:function(t){return Math.log(t/256)/Math.LN2},getProjectedBounds:function(t){var e;return this.infinite?null:(e=this.projection.bounds,t=this.scale(t),new f(this.transformation.transform(e.min,t),this.transformation.transform(e.max,t)))},infinite:!(v.prototype={equals:function(t,e){return!!t&&(t=w(t),Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng))<=(void 0===e?1e-9:e))},toString:function(t){return"LatLng("+i(this.lat,t)+", "+i(this.lng,t)+")"},distanceTo:function(t){return st.distance(this,w(t))},wrap:function(){return st.wrapLatLng(this)},toBounds:function(t){var t=180*t/40075017,e=t/Math.cos(Math.PI/180*this.lat);return g([this.lat-t,this.lng-e],[this.lat+t,this.lng+e])},clone:function(){return new v(this.lat,this.lng,this.alt)}}),wrapLatLng:function(t){var e=this.wrapLng?H(t.lng,this.wrapLng,!0):t.lng;return new v(this.wrapLat?H(t.lat,this.wrapLat,!0):t.lat,e,t.alt)},wrapLatLngBounds:function(t){var e=t.getCenter(),i=this.wrapLatLng(e),n=e.lat-i.lat,e=e.lng-i.lng;return 0==n&&0==e?t:(i=t.getSouthWest(),t=t.getNorthEast(),new s(new v(i.lat-n,i.lng-e),new v(t.lat-n,t.lng-e)))}},st=l({},ot,{wrapLng:[-180,180],R:6371e3,distance:function(t,e){var i=Math.PI/180,n=t.lat*i,o=e.lat*i,s=Math.sin((e.lat-t.lat)*i/2),e=Math.sin((e.lng-t.lng)*i/2),t=s*s+Math.cos(n)*Math.cos(o)*e*e,i=2*Math.atan2(Math.sqrt(t),Math.sqrt(1-t));return this.R*i}}),rt=6378137,rt={R:rt,MAX_LATITUDE:85.0511287798,project:function(t){var e=Math.PI/180,i=this.MAX_LATITUDE,i=Math.max(Math.min(i,t.lat),-i),i=Math.sin(i*e);return new p(this.R*t.lng*e,this.R*Math.log((1+i)/(1-i))/2)},unproject:function(t){var e=180/Math.PI;return new v((2*Math.atan(Math.exp(t.y/this.R))-Math.PI/2)*e,t.x*e/this.R)},bounds:new f([-(rt=rt*Math.PI),-rt],[rt,rt])};function at(t,e,i,n){d(t)?(this._a=t[0],this._b=t[1],this._c=t[2],this._d=t[3]):(this._a=t,this._b=e,this._c=i,this._d=n)}function ht(t,e,i,n){return new at(t,e,i,n)}at.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return t.x=(e=e||1)*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return new p((t.x/(e=e||1)-this._b)/this._a,(t.y/e-this._d)/this._c)}};var lt=l({},st,{code:"EPSG:3857",projection:rt,transformation:ht(lt=.5/(Math.PI*rt.R),.5,-lt,.5)}),ut=l({},lt,{code:"EPSG:900913"});function ct(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function dt(t,e){for(var i,n,o,s,r="",a=0,h=t.length;a<h;a++){for(i=0,n=(o=t[a]).length;i<n;i++)r+=(i?"L":"M")+(s=o[i]).x+" "+s.y;r+=e?b.svg?"z":"x":""}return r||"M0 0"}var _t=document.documentElement.style,pt="ActiveXObject"in window,mt=pt&&!document.addEventListener,n="msLaunchUri"in navigator&&!("documentMode"in document),ft=y("webkit"),gt=y("android"),vt=y("android 2")||y("android 3"),yt=parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1],10),yt=gt&&y("Google")&&yt<537&&!("AudioNode"in window),xt=!!window.opera,wt=!n&&y("chrome"),bt=y("gecko")&&!ft&&!xt&&!pt,Pt=!wt&&y("safari"),Lt=y("phantom"),o="OTransition"in _t,Tt=0===navigator.platform.indexOf("Win"),Mt=pt&&"transition"in _t,zt="WebKitCSSMatrix"in window&&"m11"in new window.WebKitCSSMatrix&&!vt,_t="MozPerspective"in _t,Ct=!window.L_DISABLE_3D&&(Mt||zt||_t)&&!o&&!Lt,Zt="undefined"!=typeof orientation||y("mobile"),St=Zt&&ft,Et=Zt&&zt,kt=!window.PointerEvent&&window.MSPointerEvent,Ot=!(!window.PointerEvent&&!kt),At="ontouchstart"in window||!!window.TouchEvent,Bt=!window.L_NO_TOUCH&&(At||Ot),It=Zt&&xt,Rt=Zt&&bt,Nt=1<(window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI),Dt=function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("testPassiveEventSupport",u,e),window.removeEventListener("testPassiveEventSupport",u,e)}catch(t){}return t}(),jt=!!document.createElement("canvas").getContext,Ht=!(!document.createElementNS||!ct("svg").createSVGRect),Wt=!!Ht&&((Wt=document.createElement("div")).innerHTML="<svg/>","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var b={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:bt,safari:Pt,phantom:Lt,opera12:o,win:Tt,ie3d:Mt,webkit3d:zt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:Et,msPointer:kt,pointer:Ot,touch:Bt,touchNative:At,mobileOpera:It,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),e=(t.innerHTML='<v:shape adj="1"/>',t.firstChild);return e.style.behavior="url(#default#VML)",e&&"object"==typeof e.adj}catch(t){return!1}}(),inlineSvg:Wt,mac:0===navigator.platform.indexOf("Mac"),linux:0===navigator.platform.indexOf("Linux")},Ft=b.msPointer?"MSPointerDown":"pointerdown",Ut=b.msPointer?"MSPointerMove":"pointermove",Vt=b.msPointer?"MSPointerUp":"pointerup",qt=b.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,e){e.MSPOINTER_TYPE_TOUCH&&e.pointerType===e.MSPOINTER_TYPE_TOUCH&&O(e);ee(t,e)},touchmove:ee,touchend:ee,touchcancel:ee},Yt={},Xt=!1;function Jt(t,e,i){return"touchstart"!==e||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,te,!0),document.addEventListener(qt,te,!0),Xt=!0),Kt[e]?(i=Kt[e].bind(this,i),t.addEventListener(Gt[e],i,!1),i):(console.warn("wrong event specified:",e),u)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function te(t){delete Yt[t.pointerId]}function ee(t,e){if(e.pointerType!==(e.MSPOINTER_TYPE_MOUSE||"mouse")){for(var i in e.touches=[],Yt)e.touches.push(Yt[i]);e.changedTouches=[e],t(e)}}var ie=200;function ne(t,i){t.addEventListener("dblclick",i);var n,o=0;function e(t){var e;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((e=Ne(t)).some(function(t){return t instanceof HTMLLabelElement&&t.attributes.for})&&!e.some(function(t){return t instanceof HTMLInputElement||t instanceof HTMLSelectElement})||((e=Date.now())-o<=ie?2===++n&&i(function(t){var e,i,n={};for(i in t)e=t[i],n[i]=e&&e.bind?e.bind(t):e;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=e))}return t.addEventListener("click",e),{dblclick:i,simDblclick:e}}var oe,se,re,ae,he,le,ue=we(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ce=we(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),de="webkitTransition"===ce||"OTransition"===ce?ce+"End":"transitionend";function _e(t){return"string"==typeof t?document.getElementById(t):t}function pe(t,e){var i=t.style[e]||t.currentStyle&&t.currentStyle[e];return"auto"===(i=i&&"auto"!==i||!document.defaultView?i:(t=document.defaultView.getComputedStyle(t,null))?t[e]:null)?null:i}function P(t,e,i){t=document.createElement(t);return t.className=e||"",i&&i.appendChild(t),t}function T(t){var e=t.parentNode;e&&e.removeChild(t)}function me(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fe(t){var e=t.parentNode;e&&e.lastChild!==t&&e.appendChild(t)}function ge(t){var e=t.parentNode;e&&e.firstChild!==t&&e.insertBefore(t,e.firstChild)}function ve(t,e){return void 0!==t.classList?t.classList.contains(e):0<(t=xe(t)).length&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(t)}function M(t,e){var i;if(void 0!==t.classList)for(var n=F(e),o=0,s=n.length;o<s;o++)t.classList.add(n[o]);else ve(t,e)||ye(t,((i=xe(t))?i+" ":"")+e)}function z(t,e){void 0!==t.classList?t.classList.remove(e):ye(t,W((" "+xe(t)+" ").replace(" "+e+" "," ")))}function ye(t,e){void 0===t.className.baseVal?t.className=e:t.className.baseVal=e}function xe(t){return void 0===(t=t.correspondingElement?t.correspondingElement:t).className.baseVal?t.className:t.className.baseVal}function C(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(t){if(1===e)return}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}}function we(t){for(var e=document.documentElement.style,i=0;i<t.length;i++)if(t[i]in e)return t[i];return!1}function be(t,e,i){e=e||new p(0,0);t.style[ue]=(b.ie3d?"translate("+e.x+"px,"+e.y+"px)":"translate3d("+e.x+"px,"+e.y+"px,0)")+(i?" scale("+i+")":"")}function Z(t,e){t._leaflet_pos=e,b.any3d?be(t,e):(t.style.left=e.x+"px",t.style.top=e.y+"px")}function Pe(t){return t._leaflet_pos||new p(0,0)}function Le(){S(window,"dragstart",O)}function Te(){k(window,"dragstart",O)}function Me(t){for(;-1===t.tabIndex;)t=t.parentNode;t.style&&(ze(),le=(he=t).style.outlineStyle,t.style.outlineStyle="none",S(window,"keydown",ze))}function ze(){he&&(he.style.outlineStyle=le,le=he=void 0,k(window,"keydown",ze))}function Ce(t){for(;!((t=t.parentNode).offsetWidth&&t.offsetHeight||t===document.body););return t}function Ze(t){var e=t.getBoundingClientRect();return{x:e.width/t.offsetWidth||1,y:e.height/t.offsetHeight||1,boundingClientRect:e}}ae="onselectstart"in document?(re=function(){S(window,"selectstart",O)},function(){k(window,"selectstart",O)}):(se=we(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]),re=function(){var t;se&&(t=document.documentElement.style,oe=t[se],t[se]="none")},function(){se&&(document.documentElement.style[se]=oe,oe=void 0)});pt={__proto__:null,TRANSFORM:ue,TRANSITION:ce,TRANSITION_END:de,get:_e,getStyle:pe,create:P,remove:T,empty:me,toFront:fe,toBack:ge,hasClass:ve,addClass:M,removeClass:z,setClass:ye,getClass:xe,setOpacity:C,testProp:we,setTransform:be,setPosition:Z,getPosition:Pe,get disableTextSelection(){return re},get enableTextSelection(){return ae},disableImageDrag:Le,enableImageDrag:Te,preventOutline:Me,restoreOutline:ze,getSizedParentNode:Ce,getScale:Ze};function S(t,e,i,n){if(e&&"object"==typeof e)for(var o in e)ke(t,o,e[o],i);else for(var s=0,r=(e=F(e)).length;s<r;s++)ke(t,e[s],i,n);return this}var E="_leaflet_events";function k(t,e,i,n){if(1===arguments.length)Se(t),delete t[E];else if(e&&"object"==typeof e)for(var o in e)Oe(t,o,e[o],i);else if(e=F(e),2===arguments.length)Se(t,function(t){return-1!==G(e,t)});else for(var s=0,r=e.length;s<r;s++)Oe(t,e[s],i,n);return this}function Se(t,e){for(var i in t[E]){var n=i.split(/\d/)[0];e&&!e(n)||Oe(t,n,null,null,i)}}var Ee={mouseenter:"mouseover",mouseleave:"mouseout",wheel:!("onwheel"in window)&&"mousewheel"};function ke(e,t,i,n){var o,s,r=t+h(i)+(n?"_"+h(n):"");e[E]&&e[E][r]||(s=o=function(t){return i.call(n||e,t||window.event)},!b.touchNative&&b.pointer&&0===t.indexOf("touch")?o=Jt(e,t,o):b.touch&&"dblclick"===t?o=ne(e,o):"addEventListener"in e?"touchstart"===t||"touchmove"===t||"wheel"===t||"mousewheel"===t?e.addEventListener(Ee[t]||t,o,!!b.passiveEvents&&{passive:!1}):"mouseenter"===t||"mouseleave"===t?e.addEventListener(Ee[t],o=function(t){t=t||window.event,We(e,t)&&s(t)},!1):e.addEventListener(t,s,!1):e.attachEvent("on"+t,o),e[E]=e[E]||{},e[E][r]=o)}function Oe(t,e,i,n,o){o=o||e+h(i)+(n?"_"+h(n):"");var s,r,i=t[E]&&t[E][o];i&&(!b.touchNative&&b.pointer&&0===e.indexOf("touch")?(n=t,r=i,Gt[s=e]?n.removeEventListener(Gt[s],r,!1):console.warn("wrong event specified:",s)):b.touch&&"dblclick"===e?(n=i,(r=t).removeEventListener("dblclick",n.dblclick),r.removeEventListener("click",n.simDblclick)):"removeEventListener"in t?t.removeEventListener(Ee[e]||e,i,!1):t.detachEvent("on"+e,i),t[E][o]=null)}function Ae(t){return t.stopPropagation?t.stopPropagation():t.originalEvent?t.originalEvent._stopped=!0:t.cancelBubble=!0,this}function Be(t){return ke(t,"wheel",Ae),this}function Ie(t){return S(t,"mousedown touchstart dblclick contextmenu",Ae),t._leaflet_disable_click=!0,this}function O(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this}function Re(t){return O(t),Ae(t),this}function Ne(t){if(t.composedPath)return t.composedPath();for(var e=[],i=t.target;i;)e.push(i),i=i.parentNode;return e}function De(t,e){var i,n;return e?(n=(i=Ze(e)).boundingClientRect,new p((t.clientX-n.left)/i.x-e.clientLeft,(t.clientY-n.top)/i.y-e.clientTop)):new p(t.clientX,t.clientY)}var je=b.linux&&b.chrome?window.devicePixelRatio:b.mac?3*window.devicePixelRatio:0<window.devicePixelRatio?2*window.devicePixelRatio:1;function He(t){return b.edge?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.deltaY/je:t.deltaY&&1===t.deltaMode?20*-t.deltaY:t.deltaY&&2===t.deltaMode?60*-t.deltaY:t.deltaX||t.deltaZ?0:t.wheelDelta?(t.wheelDeltaY||t.wheelDelta)/2:t.detail&&Math.abs(t.detail)<32765?20*-t.detail:t.detail?t.detail/-32765*60:0}function We(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(t){return!1}return i!==t}var mt={__proto__:null,on:S,off:k,stopPropagation:Ae,disableScrollPropagation:Be,disableClickPropagation:Ie,preventDefault:O,stop:Re,getPropagationPath:Ne,getMousePosition:De,getWheelDelta:He,isExternalTarget:We,addListener:S,removeListener:k},Fe=it.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=Pe(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=x(this._animate,this),this._step()},_step:function(t){var e=+new Date-this._startTime,i=1e3*this._duration;e<i?this._runFrame(this._easeOut(e/i),t):(this._runFrame(1),this._complete())},_runFrame:function(t,e){t=this._startPos.add(this._offset.multiplyBy(t));e&&t._round(),Z(this._el,t),this.fire("step")},_complete:function(){r(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),A=it.extend({options:{crs:lt,center:void 0,zoom:void 0,minZoom:void 0,maxZoom:void 0,layers:[],maxBounds:void 0,renderer:void 0,zoomAnimation:!0,zoomAnimationThreshold:4,fadeAnimation:!0,markerZoomAnimation:!0,transform3DLimit:8388608,zoomSnap:1,zoomDelta:1,trackResize:!0},initialize:function(t,e){e=c(this,e),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._sizeChanged=!0,this._initContainer(t),this._initLayout(),this._onResize=a(this._onResize,this),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),void 0!==e.zoom&&(this._zoom=this._limitZoom(e.zoom)),e.center&&void 0!==e.zoom&&this.setView(w(e.center),e.zoom,{reset:!0}),this.callInitHooks(),this._zoomAnimated=ce&&b.any3d&&!b.mobileOpera&&this.options.zoomAnimation,this._zoomAnimated&&(this._createAnimProxy(),S(this._proxy,de,this._catchTransitionEnd,this)),this._addLayers(this.options.layers)},setView:function(t,e,i){if((e=void 0===e?this._zoom:this._limitZoom(e),t=this._limitCenter(w(t),e,this.options.maxBounds),i=i||{},this._stop(),this._loaded&&!i.reset&&!0!==i)&&(void 0!==i.animate&&(i.zoom=l({animate:i.animate},i.zoom),i.pan=l({animate:i.animate,duration:i.duration},i.pan)),this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,i.zoom):this._tryAnimatedPan(t,i.pan)))return clearTimeout(this._sizeTimer),this;return this._resetView(t,e,i.pan&&i.pan.noMoveStart),this},setZoom:function(t,e){return this._loaded?this.setView(this.getCenter(),t,{zoom:e}):(this._zoom=t,this)},zoomIn:function(t,e){return t=t||(b.any3d?this.options.zoomDelta:1),this.setZoom(this._zoom+t,e)},zoomOut:function(t,e){return t=t||(b.any3d?this.options.zoomDelta:1),this.setZoom(this._zoom-t,e)},setZoomAround:function(t,e,i){var n=this.getZoomScale(e),o=this.getSize().divideBy(2),t=(t instanceof p?t:this.latLngToContainerPoint(t)).subtract(o).multiplyBy(1-1/n),n=this.containerPointToLatLng(o.add(t));return this.setView(n,e,{zoom:i})},_getBoundsCenterZoom:function(t,e){e=e||{},t=t.getBounds?t.getBounds():g(t);var i=m(e.paddingTopLeft||e.padding||[0,0]),n=m(e.paddingBottomRight||e.padding||[0,0]),o=this.getBoundsZoom(t,!1,i.add(n));return(o="number"==typeof e.maxZoom?Math.min(e.maxZoom,o):o)===1/0?{center:t.getCenter(),zoom:o}:(e=n.subtract(i).divideBy(2),n=this.project(t.getSouthWest(),o),i=this.project(t.getNorthEast(),o),{center:this.unproject(n.add(i).divideBy(2).add(e),o),zoom:o})},fitBounds:function(t,e){if((t=g(t)).isValid())return t=this._getBoundsCenterZoom(t,e),this.setView(t.center,t.zoom,e);throw new Error("Bounds are not valid.")},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,e){return this.setView(t,this._zoom,{pan:e})},panBy:function(t,e){var i;return e=e||{},(t=m(t).round()).x||t.y?(!0===e.animate||this.getSize().contains(t)?(this._panAnim||(this._panAnim=new Fe,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),!1!==e.animate?(M(this._mapPane,"leaflet-pan-anim"),i=this._getMapPanePos().subtract(t).round(),this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)):(this._rawPanBy(t),this.fire("move").fire("moveend"))):this._resetView(this.unproject(this.project(this.getCenter()).add(t)),this.getZoom()),this):this.fire("moveend")},flyTo:function(n,o,t){if(!1===(t=t||{}).animate||!b.any3d)return this.setView(n,o,t);this._stop();var s=this.project(this.getCenter()),r=this.project(n),e=this.getSize(),a=this._zoom,h=(n=w(n),o=void 0===o?a:o,Math.max(e.x,e.y)),i=h*this.getZoomScale(a,o),l=r.distanceTo(s)||1,u=1.42,c=u*u;function d(t){t=(i*i-h*h+(t?-1:1)*c*c*l*l)/(2*(t?i:h)*c*l),t=Math.sqrt(t*t+1)-t;return t<1e-9?-18:Math.log(t)}function _(t){return(Math.exp(t)-Math.exp(-t))/2}function p(t){return(Math.exp(t)+Math.exp(-t))/2}var m=d(0);function f(t){return h*(p(m)*(_(t=m+u*t)/p(t))-_(m))/c}var g=Date.now(),v=(d(1)-m)/u,y=t.duration?1e3*t.duration:1e3*v*.8;return this._moveStart(!0,t.noMoveStart),function t(){var e=(Date.now()-g)/y,i=(1-Math.pow(1-e,1.5))*v;e<=1?(this._flyToFrame=x(t,this),this._move(this.unproject(s.add(r.subtract(s).multiplyBy(f(i)/l)),a),this.getScaleZoom(h/(e=i,h*(p(m)/p(m+u*e))),a),{flyTo:!0})):this._move(n,o)._moveEnd(!0)}.call(this),this},flyToBounds:function(t,e){t=this._getBoundsCenterZoom(t,e);return this.flyTo(t.center,t.zoom,e)},setMaxBounds:function(t){return t=g(t),this.listens("moveend",this._panInsideMaxBounds)&&this.off("moveend",this._panInsideMaxBounds),t.isValid()?(this.options.maxBounds=t,this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds)):(this.options.maxBounds=null,this)},setMinZoom:function(t){var e=this.options.minZoom;return this.options.minZoom=t,this._loaded&&e!==t&&(this.fire("zoomlevelschange"),this.getZoom()<this.options.minZoom)?this.setZoom(t):this},setMaxZoom:function(t){var e=this.options.maxZoom;return this.options.maxZoom=t,this._loaded&&e!==t&&(this.fire("zoomlevelschange"),this.getZoom()>this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,e){this._enforcingBounds=!0;var i=this.getCenter(),t=this._limitCenter(i,this._zoom,g(t));return i.equals(t)||this.panTo(t,e),this._enforcingBounds=!1,this},panInside:function(t,e){var i=m((e=e||{}).paddingTopLeft||e.padding||[0,0]),n=m(e.paddingBottomRight||e.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),i=_([s.min.add(i),s.max.subtract(n)]),s=i.getSize();return i.contains(t)||(this._enforcingBounds=!0,n=t.subtract(i.getCenter()),i=i.extend(t).getSize().subtract(s),o.x+=n.x<0?-i.x:i.x,o.y+=n.y<0?-i.y:i.y,this.panTo(this.unproject(o),e),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var e=this.getSize(),i=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=e.divideBy(2).round(),o=i.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){var e,i;return t=this._locateOptions=l({timeout:1e4,watch:!1},t),"geolocation"in navigator?(e=a(this._handleGeolocationResponse,this),i=a(this._handleGeolocationError,this),t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t)):this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e;this._container._leaflet_id&&(e=t.code,t=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var e,i,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(e=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(e,s.maxZoom):e)),{latlng:n,bounds:o,timestamp:t.timestamp});for(i in t.coords)"number"==typeof t.coords[i]&&(r[i]=t.coords[i]);this.fire("locationfound",r)}},addHandler:function(t,e){return e&&(e=this[t]=new e(this),this._handlers.push(e),this.options[t]&&e.enable()),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,e){e=P("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),e||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter.clone():this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=g(t),i=m(i||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),i=this.getSize().subtract(i),t=_(this.project(t,n),this.project(r,n)).getSize(),r=b.any3d?this.options.zoomSnap:1,a=i.x/t.x,i=i.y/t.y,t=e?Math.max(a,i):Math.min(a,i),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=e?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,e){t=this._getTopLeftPoint(t,e);return new f(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,e){var i=this.options.crs;return e=void 0===e?this._zoom:e,i.scale(t)/i.scale(e)},getScaleZoom:function(t,e){var i=this.options.crs,t=(e=void 0===e?this._zoom:e,i.zoom(t*i.scale(e)));return isNaN(t)?1/0:t},project:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.latLngToPoint(w(t),e)},unproject:function(t,e){return e=void 0===e?this._zoom:e,this.options.crs.pointToLatLng(m(t),e)},layerPointToLatLng:function(t){t=m(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,e){return this.options.crs.distance(w(t),w(e))},containerPointToLayerPoint:function(t){return m(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return m(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(m(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return De(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_e(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,e=(this._fadeAnimated=this.options.fadeAnimation&&b.any3d,M(t,"leaflet-container"+(b.touch?" leaflet-touch":"")+(b.retina?" leaflet-retina":"")+(b.ielt9?" leaflet-oldie":"")+(b.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pe(t,"position"));"absolute"!==e&&"relative"!==e&&"fixed"!==e&&"sticky"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(M(t.markerPane,"leaflet-zoom-hide"),M(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,e,i){Z(this._mapPane,new p(0,0));var n=!this._loaded,o=(this._loaded=!0,e=this._limitZoom(e),this.fire("viewprereset"),this._zoom!==e);this._moveStart(o,i)._move(t,e)._moveEnd(o),this.fire("viewreset"),n&&this.fire("load")},_moveStart:function(t,e){return t&&this.fire("zoomstart"),e||this.fire("movestart"),this},_move:function(t,e,i,n){void 0===e&&(e=this._zoom);var o=this._zoom!==e;return this._zoom=e,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?i&&i.pinch&&this.fire("zoom",i):((o||i&&i.pinch)&&this.fire("zoom",i),this.fire("move",i)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var e=t?k:S;e((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&e(window,"resize",this._onResize,this),b.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,e){for(var i,n=[],o="mouseout"===e||"mouseover"===e,s=t.target||t.srcElement,r=!1;s;){if((i=this._targets[h(s)])&&("click"===e||"preclick"===e)&&this._draggableMoved(i)){r=!0;break}if(i&&i.listens(e,!0)){if(o&&!We(s,t))break;if(n.push(i),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(e,!0)?n:[this]},_isClickDisabled:function(t){for(;t&&t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var e,i=t.target||t.srcElement;!this._loaded||i._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(i)||("mousedown"===(e=t.type)&&Me(i),this._fireDOMEvent(t,e))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,i){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,i));var n=this._findEventTargets(t,e);if(i){for(var o=[],s=0;s<i.length;s++)i[s].listens(e,!0)&&o.push(i[s]);n=o.concat(n)}if(n.length){"contextmenu"===e&&O(t);var r,a=n[0],h={originalEvent:t};for("keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type&&(r=a.getLatLng&&(!a._radius||a._radius<=10),h.containerPoint=r?this.latLngToContainerPoint(a.getLatLng()):this.mouseEventToContainerPoint(t),h.layerPoint=this.containerPointToLayerPoint(h.containerPoint),h.latlng=r?a.getLatLng():this.layerPointToLatLng(h.layerPoint)),s=0;s<n.length;s++)if(n[s].fire(e,h,!0),h.originalEvent._stopped||!1===n[s].options.bubblingMouseEvents&&-1!==G(this._mouseEvents,e))return}},_draggableMoved:function(t){return(t=t.dragging&&t.dragging.enabled()?t:this).dragging&&t.dragging.moved()||this.boxZoom&&this.boxZoom.moved()},_clearHandlers:function(){for(var t=0,e=this._handlers.length;t<e;t++)this._handlers[t].disable()},whenReady:function(t,e){return this._loaded?t.call(e||this,{target:this}):this.on("load",t,e),this},_getMapPanePos:function(){return Pe(this._mapPane)||new p(0,0)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(t,e){return(t&&void 0!==e?this._getNewPixelOrigin(t,e):this.getPixelOrigin()).subtract(this._getMapPanePos())},_getNewPixelOrigin:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._add(this._getMapPanePos())._round()},_latLngToNewLayerPoint:function(t,e,i){i=this._getNewPixelOrigin(i,e);return this.project(t,e)._subtract(i)},_latLngBoundsToNewLayerBounds:function(t,e,i){i=this._getNewPixelOrigin(i,e);return _([this.project(t.getSouthWest(),e)._subtract(i),this.project(t.getNorthWest(),e)._subtract(i),this.project(t.getSouthEast(),e)._subtract(i),this.project(t.getNorthEast(),e)._subtract(i)])},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,e,i){var n,o;return!i||(n=this.project(t,e),o=this.getSize().divideBy(2),o=new f(n.subtract(o),n.add(o)),o=this._getBoundsOffset(o,i,e),Math.abs(o.x)<=1&&Math.abs(o.y)<=1)?t:this.unproject(n.add(o),e)},_limitOffset:function(t,e){var i;return e?(i=new f((i=this.getPixelBounds()).min.add(t),i.max.add(t)),t.add(this._getBoundsOffset(i,e))):t},_getBoundsOffset:function(t,e,i){e=_(this.project(e.getNorthEast(),i),this.project(e.getSouthWest(),i)),i=e.min.subtract(t.min),e=e.max.subtract(t.max);return new p(this._rebound(i.x,-e.x),this._rebound(i.y,-e.y))},_rebound:function(t,e){return 0<t+e?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom(),n=b.any3d?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(e,Math.min(i,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){z(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){t=this._getCenterOffset(t)._trunc();return!(!0!==(e&&e.animate)&&!this.getSize().contains(t))&&(this.panBy(t,e),!0)},_createAnimProxy:function(){var t=this._proxy=P("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var e=ue,i=this._proxy.style[e];be(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),i===this._proxy.style[e]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",this._animMoveEnd,this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){T(this._proxy),this.off("load moveend",this._animMoveEnd,this),delete this._proxy},_animMoveEnd:function(){var t=this.getCenter(),e=this.getZoom();be(this._proxy,this.project(t,e),this.getZoomScale(e,1))},_catchTransitionEnd:function(t){this._animatingZoom&&0<=t.propertyName.indexOf("transform")&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(!this._animatingZoom){if(i=i||{},!this._zoomAnimated||!1===i.animate||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),n=this._getCenterOffset(t)._divideBy(1-1/n);if(!0!==i.animate&&!this.getSize().contains(n))return!1;x(function(){this._moveStart(!0,i.noMoveStart||!1)._animateZoom(t,e,!0)},this)}return!0},_animateZoom:function(t,e,i,n){this._mapPane&&(i&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=e,M(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:e,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&z(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Ue(t){return new B(t)}var B=et.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),t=t._controlCorners[i];return M(e,"leaflet-control"),-1!==i.indexOf("bottom")?t.insertBefore(e,t.firstChild):t.appendChild(e),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0<t.screenX&&0<t.screenY&&this._map.getContainer().focus()}}),Ve=(A.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){var i=this._controlCorners={},n="leaflet-",o=this._controlContainer=P("div",n+"control-container",this._container);function t(t,e){i[t+e]=P("div",n+t+" "+n+e,o)}t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)T(this._controlCorners[t]);T(this._controlContainer),delete this._controlCorners,delete this._controlContainer}}),B.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,e,i,n){return i<n?-1:n<i?1:0}},initialize:function(t,e,i){for(var n in c(this,i),this._layerControlInputs=[],this._layers=[],this._lastZIndex=0,this._handlingClick=!1,this._preventClick=!1,t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){this._initLayout(),this._update(),(this._map=t).on("zoomend",this._checkDisabledLayers,this);for(var e=0;e<this._layers.length;e++)this._layers[e].layer.on("add remove",this._onLayerChange,this);return this._container},addTo:function(t){return B.prototype.addTo.call(this,t),this._expandIfNotCollapsed()},onRemove:function(){this._map.off("zoomend",this._checkDisabledLayers,this);for(var t=0;t<this._layers.length;t++)this._layers[t].layer.off("add remove",this._onLayerChange,this)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._map?this._update():this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._map?this._update():this},removeLayer:function(t){t.off("add remove",this._onLayerChange,this);t=this._getLayer(h(t));return t&&this._layers.splice(this._layers.indexOf(t),1),this._map?this._update():this},expand:function(){M(this._container,"leaflet-control-layers-expanded"),this._section.style.height=null;var t=this._map.getSize().y-(this._container.offsetTop+50);return t<this._section.clientHeight?(M(this._section,"leaflet-control-layers-scrollbar"),this._section.style.height=t+"px"):z(this._section,"leaflet-control-layers-scrollbar"),this._checkDisabledLayers(),this},collapse:function(){return z(this._container,"leaflet-control-layers-expanded"),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=P("div",t),i=this.options.collapsed,n=(e.setAttribute("aria-haspopup",!0),Ie(e),Be(e),this._section=P("section",t+"-list")),o=(i&&(this._map.on("click",this.collapse,this),S(e,{mouseenter:this._expandSafely,mouseleave:this.collapse},this)),this._layersLink=P("a",t+"-toggle",e));o.href="#",o.title="Layers",o.setAttribute("role","button"),S(o,{keydown:function(t){13===t.keyCode&&this._expandSafely()},click:function(t){O(t),this._expandSafely()}},this),i||this.expand(),this._baseLayersList=P("div",t+"-base",n),this._separator=P("div",t+"-separator",n),this._overlaysList=P("div",t+"-overlays",n),e.appendChild(n)},_getLayer:function(t){for(var e=0;e<this._layers.length;e++)if(this._layers[e]&&h(this._layers[e].layer)===t)return this._layers[e]},_addLayer:function(t,e,i){this._map&&t.on("add remove",this._onLayerChange,this),this._layers.push({layer:t,name:e,overlay:i}),this.options.sortLayers&&this._layers.sort(a(function(t,e){return this.options.sortFunction(t.layer,e.layer,t.name,e.name)},this)),this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex)),this._expandIfNotCollapsed()},_update:function(){if(this._container){me(this._baseLayersList),me(this._overlaysList),this._layerControlInputs=[];for(var t,e,i,n=0,o=0;o<this._layers.length;o++)i=this._layers[o],this._addItem(i),e=e||i.overlay,t=t||!i.overlay,n+=i.overlay?0:1;this.options.hideSingleBase&&(this._baseLayersList.style.display=(t=t&&1<n)?"":"none"),this._separator.style.display=e&&t?"":"none"}return this},_onLayerChange:function(t){this._handlingClick||this._update();var e=this._getLayer(h(t.target)),t=e.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;t&&this._map.fire(t,e)},_createRadioElement:function(t,e){t='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"'+(e?' checked="checked"':"")+"/>",e=document.createElement("div");return e.innerHTML=t,e.firstChild},_addItem:function(t){var e,i=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((e=document.createElement("input")).type="checkbox",e.className="leaflet-control-layers-selector",e.defaultChecked=n):e=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(e),e.layerId=h(t.layer),S(e,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return i.appendChild(o),o.appendChild(e),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(i),this._checkDisabledLayers(),i},_onInputClick:function(){if(!this._preventClick){var t,e,i=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=i.length-1;0<=s;s--)t=i[s],e=this._getLayer(t.layerId).layer,t.checked?n.push(e):t.checked||o.push(e);for(s=0;s<o.length;s++)this._map.hasLayer(o[s])&&this._map.removeLayer(o[s]);for(s=0;s<n.length;s++)this._map.hasLayer(n[s])||this._map.addLayer(n[s]);this._handlingClick=!1,this._refocusOnMap()}},_checkDisabledLayers:function(){for(var t,e,i=this._layerControlInputs,n=this._map.getZoom(),o=i.length-1;0<=o;o--)t=i[o],e=this._getLayer(t.layerId).layer,t.disabled=void 0!==e.options.minZoom&&n<e.options.minZoom||void 0!==e.options.maxZoom&&n>e.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expandSafely:function(){var t=this._section,e=(this._preventClick=!0,S(t,"click",O),this.expand(),this);setTimeout(function(){k(t,"click",O),e._preventClick=!1})}})),qe=B.extend({options:{position:"topleft",zoomInText:'<span aria-hidden="true">+</span>',zoomInTitle:"Zoom in",zoomOutText:'<span aria-hidden="true">−</span>',zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=P("div",e+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,e+"-in",i,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,e+"-out",i,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoom<this._map.getMaxZoom()&&this._map.zoomIn(this._map.options.zoomDelta*(t.shiftKey?3:1))},_zoomOut:function(t){!this._disabled&&this._map._zoom>this._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,e,i,n,o){i=P("a",i,n);return i.innerHTML=t,i.href="#",i.title=e,i.setAttribute("role","button"),i.setAttribute("aria-label",e),Ie(i),S(i,"click",Re),S(i,"click",o,this),S(i,"click",this._refocusOnMap,this),i},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";z(this._zoomInButton,e),z(this._zoomOutButton,e),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(M(this._zoomOutButton,e),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(M(this._zoomInButton,e),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Ge=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qe,this.addControl(this.zoomControl))}),B.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var e="leaflet-control-scale",i=P("div",e),n=this.options;return this._addScales(n,e+"-line",i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=P("div",e,i)),t.imperial&&(this._iScale=P("div",e,i))},_update:function(){var t=this._map,e=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,e]),t.containerPointToLatLng([this.options.maxWidth,e]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var e=this._getRoundNum(t);this._updateScale(this._mScale,e<1e3?e+" m":e/1e3+" km",e/t)},_updateImperial:function(t){var e,i,t=3.2808399*t;5280<t?(i=this._getRoundNum(e=t/5280),this._updateScale(this._iScale,i+" mi",i/e)):(i=this._getRoundNum(t),this._updateScale(this._iScale,i+" ft",i/t))},_updateScale:function(t,e,i){t.style.width=Math.round(this.options.maxWidth*i)+"px",t.innerHTML=e},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),t=t/e;return e*(t=10<=t?10:5<=t?5:3<=t?3:2<=t?2:1)}})),Ke=B.extend({options:{position:"bottomright",prefix:'<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">'+(b.inlineSvg?'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg> ':"")+"Leaflet</a>"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var e in(t.attributionControl=this)._container=P("div","leaflet-control-attribution"),Ie(this._container),t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,e=[];for(t in this._attributions)this._attributions[t]&&e.push(t);var i=[];this.options.prefix&&i.push(this.options.prefix),e.length&&i.push(e.join(", ")),this._container.innerHTML=i.join(' <span aria-hidden="true">|</span> ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ke).addTo(this)}),B.Layers=Ve,B.Zoom=qe,B.Scale=Ge,B.Attribution=Ke,Ue.layers=function(t,e,i){return new Ve(t,e,i)},Ue.zoom=function(t){return new qe(t)},Ue.scale=function(t){return new Ge(t)},Ue.attribution=function(t){return new Ke(t)},et.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,e){return t.addHandler(e,this),this},{Events:e}),Ye=b.touch?"touchstart mousedown":"mousedown",Xe=it.extend({options:{clickTolerance:3},initialize:function(t,e,i,n){c(this,n),this._element=t,this._dragStartTarget=e||t,this._preventOutline=i},enable:function(){this._enabled||(S(this._dragStartTarget,Ye,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xe._dragging===this&&this.finishDrag(!0),k(this._dragStartTarget,Ye,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var e,i;this._enabled&&(this._moved=!1,ve(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xe._dragging===this&&this.finishDrag():Xe._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xe._dragging=this)._preventOutline&&Me(this._element),Le(),re(),this._moving||(this.fire("down"),i=t.touches?t.touches[0]:t,e=Ce(this._element),this._startPoint=new p(i.clientX,i.clientY),this._startPos=Pe(this._element),this._parentScale=Ze(e),i="mousedown"===t.type,S(document,i?"mousemove":"touchmove",this._onMove,this),S(document,i?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var e;this._enabled&&(t.touches&&1<t.touches.length?this._moved=!0:!(e=new p((e=t.touches&&1===t.touches.length?t.touches[0]:t).clientX,e.clientY)._subtract(this._startPoint)).x&&!e.y||Math.abs(e.x)+Math.abs(e.y)<this.options.clickTolerance||(e.x/=this._parentScale.x,e.y/=this._parentScale.y,O(t),this._moved||(this.fire("dragstart"),this._moved=!0,M(document.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,window.SVGElementInstance&&this._lastTarget instanceof window.SVGElementInstance&&(this._lastTarget=this._lastTarget.correspondingUseElement),M(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(e),this._moving=!0,this._lastEvent=t,this._updatePosition()))},_updatePosition:function(){var t={originalEvent:this._lastEvent};this.fire("predrag",t),Z(this._element,this._newPos),this.fire("drag",t)},_onUp:function(){this._enabled&&this.finishDrag()},finishDrag:function(t){z(document.body,"leaflet-dragging"),this._lastTarget&&(z(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null),k(document,"mousemove touchmove",this._onMove,this),k(document,"mouseup touchend touchcancel",this._onUp,this),Te(),ae();var e=this._moved&&this._moving;this._moving=!1,Xe._dragging=!1,e&&this.fire("dragend",{noInertia:t,distance:this._newPos.distanceTo(this._startPos)})}});function Je(t,e,i){for(var n,o,s,r,a,h,l,u=[1,4,2,8],c=0,d=t.length;c<d;c++)t[c]._code=si(t[c],e);for(s=0;s<4;s++){for(h=u[s],n=[],c=0,o=(d=t.length)-1;c<d;o=c++)r=t[c],a=t[o],r._code&h?a._code&h||((l=oi(a,r,h,e,i))._code=si(l,e),n.push(l)):(a._code&h&&((l=oi(a,r,h,e,i))._code=si(l,e),n.push(l)),n.push(r));t=n}return t}function $e(t,e){var i,n,o,s,r,a,h;if(!t||0===t.length)throw new Error("latlngs not passed");I(t)||(console.warn("latlngs are not flat! Only the first ring will be used"),t=t[0]);for(var l=w([0,0]),u=g(t),c=(u.getNorthWest().distanceTo(u.getSouthWest())*u.getNorthEast().distanceTo(u.getNorthWest())<1700&&(l=Qe(t)),t.length),d=[],_=0;_<c;_++){var p=w(t[_]);d.push(e.project(w([p.lat-l.lat,p.lng-l.lng])))}for(_=r=a=h=0,i=c-1;_<c;i=_++)n=d[_],o=d[i],s=n.y*o.x-o.y*n.x,a+=(n.x+o.x)*s,h+=(n.y+o.y)*s,r+=3*s;u=0===r?d[0]:[a/r,h/r],u=e.unproject(m(u));return w([u.lat+l.lat,u.lng+l.lng])}function Qe(t){for(var e=0,i=0,n=0,o=0;o<t.length;o++){var s=w(t[o]);e+=s.lat,i+=s.lng,n++}return w([e/n,i/n])}var ti,gt={__proto__:null,clipPolygon:Je,polygonCenter:$e,centroid:Qe};function ei(t,e){if(e&&t.length){var i=t=function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;n<s;n++)(function(t,e){var i=e.x-t.x,e=e.y-t.y;return i*i+e*e})(t[n],t[o])>e&&(i.push(t[n]),o=n);o<s-1&&i.push(t[s-1]);return i}(t,e=e*e),n=i.length,o=new(typeof Uint8Array!=void 0+""?Uint8Array:Array)(n);o[0]=o[n-1]=1,function t(e,i,n,o,s){var r,a,h,l=0;for(a=o+1;a<=s-1;a++)h=ri(e[a],e[o],e[s],!0),l<h&&(r=a,l=h);n<l&&(i[r]=1,t(e,i,n,o,r),t(e,i,n,r,s))}(i,o,e,0,n-1);var s,r=[];for(s=0;s<n;s++)o[s]&&r.push(i[s]);return r}return t.slice()}function ii(t,e,i){return Math.sqrt(ri(t,e,i,!0))}function ni(t,e,i,n,o){var s,r,a,h=n?ti:si(t,i),l=si(e,i);for(ti=l;;){if(!(h|l))return[t,e];if(h&l)return!1;a=si(r=oi(t,e,s=h||l,i,o),i),s===h?(t=r,h=a):(e=r,l=a)}}function oi(t,e,i,n,o){var s,r,a=e.x-t.x,e=e.y-t.y,h=n.min,n=n.max;return 8&i?(s=t.x+a*(n.y-t.y)/e,r=n.y):4&i?(s=t.x+a*(h.y-t.y)/e,r=h.y):2&i?(s=n.x,r=t.y+e*(n.x-t.x)/a):1&i&&(s=h.x,r=t.y+e*(h.x-t.x)/a),new p(s,r,o)}function si(t,e){var i=0;return t.x<e.min.x?i|=1:t.x>e.max.x&&(i|=2),t.y<e.min.y?i|=4:t.y>e.max.y&&(i|=8),i}function ri(t,e,i,n){var o=e.x,e=e.y,s=i.x-o,r=i.y-e,a=s*s+r*r;return 0<a&&(1<(a=((t.x-o)*s+(t.y-e)*r)/a)?(o=i.x,e=i.y):0<a&&(o+=s*a,e+=r*a)),s=t.x-o,r=t.y-e,n?s*s+r*r:new p(o,e)}function I(t){return!d(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function ai(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),I(t)}function hi(t,e){var i,n,o,s,r,a;if(!t||0===t.length)throw new Error("latlngs not passed");I(t)||(console.warn("latlngs are not flat! Only the first ring will be used"),t=t[0]);for(var h=w([0,0]),l=g(t),u=(l.getNorthWest().distanceTo(l.getSouthWest())*l.getNorthEast().distanceTo(l.getNorthWest())<1700&&(h=Qe(t)),t.length),c=[],d=0;d<u;d++){var _=w(t[d]);c.push(e.project(w([_.lat-h.lat,_.lng-h.lng])))}for(i=d=0;d<u-1;d++)i+=c[d].distanceTo(c[d+1])/2;if(0===i)a=c[0];else for(n=d=0;d<u-1;d++)if(o=c[d],s=c[d+1],i<(n+=r=o.distanceTo(s))){a=[s.x-(r=(n-i)/r)*(s.x-o.x),s.y-r*(s.y-o.y)];break}l=e.unproject(m(a));return w([l.lat+h.lat,l.lng+h.lng])}var vt={__proto__:null,simplify:ei,pointToSegmentDistance:ii,closestPointOnSegment:function(t,e,i){return ri(t,e,i)},clipSegment:ni,_getEdgeIntersection:oi,_getBitCode:si,_sqClosestPointOnSegment:ri,isFlat:I,_flat:ai,polylineCenter:hi},yt={project:function(t){return new p(t.lng,t.lat)},unproject:function(t){return new v(t.y,t.x)},bounds:new f([-180,-90],[180,90])},xt={R:6378137,R_MINOR:6356752.314245179,bounds:new f([-20037508.34279,-15496570.73972],[20037508.34279,18764656.23138]),project:function(t){var e=Math.PI/180,i=this.R,n=t.lat*e,o=this.R_MINOR/i,o=Math.sqrt(1-o*o),s=o*Math.sin(n),s=Math.tan(Math.PI/4-n/2)/Math.pow((1-s)/(1+s),o/2),n=-i*Math.log(Math.max(s,1e-10));return new p(t.lng*e*i,n)},unproject:function(t){for(var e,i=180/Math.PI,n=this.R,o=this.R_MINOR/n,s=Math.sqrt(1-o*o),r=Math.exp(-t.y/n),a=Math.PI/2-2*Math.atan(r),h=0,l=.1;h<15&&1e-7<Math.abs(l);h++)e=s*Math.sin(a),e=Math.pow((1-e)/(1+e),s/2),a+=l=Math.PI/2-2*Math.atan(r*e)-a;return new v(a*i,t.x*i/n)}},wt={__proto__:null,LonLat:yt,Mercator:xt,SphericalMercator:rt},Pt=l({},st,{code:"EPSG:3395",projection:xt,transformation:ht(bt=.5/(Math.PI*xt.R),.5,-bt,.5)}),li=l({},st,{code:"EPSG:4326",projection:yt,transformation:ht(1/180,1,-1/180,.5)}),Lt=l({},ot,{projection:yt,transformation:ht(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,e){var i=e.lng-t.lng,e=e.lat-t.lat;return Math.sqrt(i*i+e*e)},infinite:!0}),o=(ot.Earth=st,ot.EPSG3395=Pt,ot.EPSG3857=lt,ot.EPSG900913=ut,ot.EPSG4326=li,ot.Simple=Lt,it.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[h(t)]=this},removeInteractiveTarget:function(t){return delete this._map._targets[h(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var e,i=t.target;i.hasLayer(this)&&(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents&&(e=this.getEvents(),i.on(e,this),this.once("remove",function(){i.off(e,this)},this)),this.onAdd(i),this.fire("add"),i.fire("layeradd",{layer:this}))}})),ui=(A.include({addLayer:function(t){var e;if(t._layerAdd)return e=h(t),this._layers[e]||((this._layers[e]=t)._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t)),this;throw new Error("The provided object is not a Layer.")},removeLayer:function(t){var e=h(t);return this._layers[e]&&(this._loaded&&t.onRemove(this),delete this._layers[e],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null),this},hasLayer:function(t){return h(t)in this._layers},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},_addLayers:function(t){for(var e=0,i=(t=t?d(t)?t:[t]:[]).length;e<i;e++)this.addLayer(t[e])},_addZoomLimit:function(t){isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[h(t)]=t,this._updateZoomLevels())},_removeZoomLimit:function(t){t=h(t);this._zoomBoundLayers[t]&&(delete this._zoomBoundLayers[t],this._updateZoomLevels())},_updateZoomLevels:function(){var t,e=1/0,i=-1/0,n=this._getZoomSpan();for(t in this._zoomBoundLayers)var o=this._zoomBoundLayers[t].options,e=void 0===o.minZoom?e:Math.min(e,o.minZoom),i=void 0===o.maxZoom?i:Math.max(i,o.maxZoom);this._layersMaxZoom=i===-1/0?void 0:i,this._layersMinZoom=e===1/0?void 0:e,n!==this._getZoomSpan()&&this.fire("zoomlevelschange"),void 0===this.options.maxZoom&&this._layersMaxZoom&&this.getZoom()>this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()<this._layersMinZoom&&this.setZoom(this._layersMinZoom)}}),o.extend({initialize:function(t,e){var i,n;if(c(this,e),this._layers={},t)for(i=0,n=t.length;i<n;i++)this.addLayer(t[i])},addLayer:function(t){var e=this.getLayerId(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){t=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[t]&&this._map.removeLayer(this._layers[t]),delete this._layers[t],this},hasLayer:function(t){return("number"==typeof t?t:this.getLayerId(t))in this._layers},clearLayers:function(){return this.eachLayer(this.removeLayer,this)},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)(i=this._layers[e])[t]&&i[t].apply(i,n);return this},onAdd:function(t){this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t)},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];return this.eachLayer(t.push,t),t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:h})),ci=ui.extend({addLayer:function(t){return this.hasLayer(t)?this:(t.addEventParent(this),ui.prototype.addLayer.call(this,t),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?((t=t in this._layers?this._layers[t]:t).removeEventParent(this),ui.prototype.removeLayer.call(this,t),this.fire("layerremove",{layer:t})):this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t,e=new s;for(t in this._layers){var i=this._layers[t];e.extend(i.getBounds?i.getBounds():i.getLatLng())}return e}}),di=et.extend({options:{popupAnchor:[0,0],tooltipAnchor:[0,0],crossOrigin:!1},initialize:function(t){c(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(i)return i=this._createImg(i,e&&"IMG"===e.tagName?e:null),this._setIconStyles(i,t),!this.options.crossOrigin&&""!==this.options.crossOrigin||(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),i;if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null},_setIconStyles:function(t,e){var i=this.options,n=i[e+"Size"],n=m(n="number"==typeof n?[n,n]:n),o=m("shadow"===e&&i.shadowAnchor||i.iconAnchor||n&&n.divideBy(2,!0));t.className="leaflet-marker-"+e+" "+(i.className||""),o&&(t.style.marginLeft=-o.x+"px",t.style.marginTop=-o.y+"px"),n&&(t.style.width=n.x+"px",t.style.height=n.y+"px")},_createImg:function(t,e){return(e=e||document.createElement("img")).src=t,e},_getIconUrl:function(t){return b.retina&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}});var _i=di.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return"string"!=typeof _i.imagePath&&(_i.imagePath=this._detectIconPath()),(this.options.imagePath||_i.imagePath)+di.prototype._getIconUrl.call(this,t)},_stripUrl:function(t){function e(t,e,i){return(e=e.exec(t))&&e[i]}return(t=e(t,/^url\((['"])?(.+)\1\)$/,2))&&e(t,/^(.*)marker-icon\.png$/,1)},_detectIconPath:function(){var t=P("div","leaflet-default-icon-path",document.body),e=pe(t,"background-image")||pe(t,"backgroundImage");return document.body.removeChild(t),(e=this._stripUrl(e))?e:(t=document.querySelector('link[href$="leaflet.css"]'))?t.href.substring(0,t.href.length-"leaflet.css".length-1):""}}),pi=n.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new Xe(t,t,!0)),this._draggable.on({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).enable(),M(t,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).disable(),this._marker._icon&&z(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_adjustPan:function(t){var e=this._marker,i=e._map,n=this._marker.options.autoPanSpeed,o=this._marker.options.autoPanPadding,s=Pe(e._icon),r=i.getPixelBounds(),a=i.getPixelOrigin(),a=_(r.min._subtract(a).add(o),r.max._subtract(a).subtract(o));a.contains(s)||(o=m((Math.max(a.max.x,s.x)-a.max.x)/(r.max.x-a.max.x)-(Math.min(a.min.x,s.x)-a.min.x)/(r.min.x-a.min.x),(Math.max(a.max.y,s.y)-a.max.y)/(r.max.y-a.max.y)-(Math.min(a.min.y,s.y)-a.min.y)/(r.min.y-a.min.y)).multiplyBy(n),i.panBy(o,{animate:!1}),this._draggable._newPos._add(o),this._draggable._startPos._add(o),Z(e._icon,this._draggable._newPos),this._onDrag(t),this._panRequest=x(this._adjustPan.bind(this,t)))},_onDragStart:function(){this._oldLatLng=this._marker.getLatLng(),this._marker.closePopup&&this._marker.closePopup(),this._marker.fire("movestart").fire("dragstart")},_onPreDrag:function(t){this._marker.options.autoPan&&(r(this._panRequest),this._panRequest=x(this._adjustPan.bind(this,t)))},_onDrag:function(t){var e=this._marker,i=e._shadow,n=Pe(e._icon),o=e._map.layerPointToLatLng(n);i&&Z(i,n),e._latlng=o,t.latlng=o,t.oldLatLng=this._oldLatLng,e.fire("move",t).fire("drag",t)},_onDragEnd:function(t){r(this._panRequest),delete this._oldLatLng,this._marker.fire("moveend").fire("dragend",t)}}),mi=o.extend({options:{icon:new _i,interactive:!0,keyboard:!0,title:"",alt:"Marker",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",shadowPane:"shadowPane",bubblingMouseEvents:!1,autoPanOnFocus:!0,draggable:!1,autoPan:!1,autoPanPadding:[50,50],autoPanSpeed:10},initialize:function(t,e){c(this,e),this._latlng=w(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),delete this.dragging,this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var e=this._latlng;return this._latlng=w(t),this.update(),this.fire("move",{oldLatLng:e,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},getIcon:function(){return this.options.icon},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){var t;return this._icon&&this._map&&(t=this._map.latLngToLayerPoint(this._latlng).round(),this._setPos(t)),this},_initIcon:function(){var t=this.options,e="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),i=t.icon.createIcon(this._icon),n=!1,i=(i!==this._icon&&(this._icon&&this._removeIcon(),n=!0,t.title&&(i.title=t.title),"IMG"===i.tagName&&(i.alt=t.alt||"")),M(i,e),t.keyboard&&(i.tabIndex="0",i.setAttribute("role","button")),this._icon=i,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex}),this.options.autoPanOnFocus&&S(i,"focus",this._panOnFocus,this),t.icon.createShadow(this._shadow)),o=!1;i!==this._shadow&&(this._removeShadow(),o=!0),i&&(M(i,e),i.alt=""),this._shadow=i,t.opacity<1&&this._updateOpacity(),n&&this.getPane().appendChild(this._icon),this._initInteraction(),i&&o&&this.getPane(t.shadowPane).appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),this.options.autoPanOnFocus&&k(this._icon,"focus",this._panOnFocus,this),T(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&T(this._shadow),this._shadow=null},_setPos:function(t){this._icon&&Z(this._icon,t),this._shadow&&Z(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon&&(this._icon.style.zIndex=this._zIndex+t)},_animateZoom:function(t){t=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(t)},_initInteraction:function(){var t;this.options.interactive&&(M(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),pi&&(t=this.options.draggable,this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new pi(this),t&&this.dragging.enable()))},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;this._icon&&C(this._icon,t),this._shadow&&C(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_panOnFocus:function(){var t,e,i=this._map;i&&(t=(e=this.options.icon.options).iconSize?m(e.iconSize):m(0,0),e=e.iconAnchor?m(e.iconAnchor):m(0,0),i.panInside(this._latlng,{paddingTopLeft:e,paddingBottomRight:t.subtract(e)}))},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor}});var fi=o.extend({options:{stroke:!0,color:"#3388ff",weight:3,opacity:1,lineCap:"round",lineJoin:"round",dashArray:null,dashOffset:null,fill:!1,fillColor:null,fillOpacity:.2,fillRule:"evenodd",interactive:!0,bubblingMouseEvents:!0},beforeAdd:function(t){this._renderer=t.getRenderer(this)},onAdd:function(){this._renderer._initPath(this),this._reset(),this._renderer._addPath(this)},onRemove:function(){this._renderer._removePath(this)},redraw:function(){return this._map&&this._renderer._updatePath(this),this},setStyle:function(t){return c(this,t),this._renderer&&(this._renderer._updateStyle(this),this.options.stroke&&t&&Object.prototype.hasOwnProperty.call(t,"weight")&&this._updateBounds()),this},bringToFront:function(){return this._renderer&&this._renderer._bringToFront(this),this},bringToBack:function(){return this._renderer&&this._renderer._bringToBack(this),this},getElement:function(){return this._path},_reset:function(){this._project(),this._update()},_clickTolerance:function(){return(this.options.stroke?this.options.weight/2:0)+(this._renderer.options.tolerance||0)}}),gi=fi.extend({options:{fill:!0,radius:10},initialize:function(t,e){c(this,e),this._latlng=w(t),this._radius=this.options.radius},setLatLng:function(t){var e=this._latlng;return this._latlng=w(t),this.redraw(),this.fire("move",{oldLatLng:e,latlng:this._latlng})},getLatLng:function(){return this._latlng},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius},setStyle:function(t){var e=t&&t.radius||this._radius;return fi.prototype.setStyle.call(this,t),this.setRadius(e),this},_project:function(){this._point=this._map.latLngToLayerPoint(this._latlng),this._updateBounds()},_updateBounds:function(){var t=this._radius,e=this._radiusY||t,i=this._clickTolerance(),t=[t+i,e+i];this._pxBounds=new f(this._point.subtract(t),this._point.add(t))},_update:function(){this._map&&this._updatePath()},_updatePath:function(){this._renderer._updateCircle(this)},_empty:function(){return this._radius&&!this._renderer._bounds.intersects(this._pxBounds)},_containsPoint:function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()}});var vi=gi.extend({initialize:function(t,e,i){if(c(this,e="number"==typeof e?l({},i,{radius:e}):e),this._latlng=w(t),isNaN(this.options.radius))throw new Error("Circle radius cannot be NaN");this._mRadius=this.options.radius},setRadius:function(t){return this._mRadius=t,this.redraw()},getRadius:function(){return this._mRadius},getBounds:function(){var t=[this._radius,this._radiusY||this._radius];return new s(this._map.layerPointToLatLng(this._point.subtract(t)),this._map.layerPointToLatLng(this._point.add(t)))},setStyle:fi.prototype.setStyle,_project:function(){var t,e,i,n,o,s=this._latlng.lng,r=this._latlng.lat,a=this._map,h=a.options.crs;h.distance===st.distance?(n=Math.PI/180,o=this._mRadius/st.R/n,t=a.project([r+o,s]),e=a.project([r-o,s]),e=t.add(e).divideBy(2),i=a.unproject(e).lat,n=Math.acos((Math.cos(o*n)-Math.sin(r*n)*Math.sin(i*n))/(Math.cos(r*n)*Math.cos(i*n)))/n,!isNaN(n)&&0!==n||(n=o/Math.cos(Math.PI/180*r)),this._point=e.subtract(a.getPixelOrigin()),this._radius=isNaN(n)?0:e.x-a.project([i,s-n]).x,this._radiusY=e.y-t.y):(o=h.unproject(h.project(this._latlng).subtract([this._mRadius,0])),this._point=a.latLngToLayerPoint(this._latlng),this._radius=this._point.x-a.latLngToLayerPoint(o).x),this._updateBounds()}});var yi=fi.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,e){c(this,e),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var e=1/0,i=null,n=ri,o=0,s=this._parts.length;o<s;o++)for(var r=this._parts[o],a=1,h=r.length;a<h;a++){var l,u,c=n(t,l=r[a-1],u=r[a],!0);c<e&&(e=c,i=n(t,l,u))}return i&&(i.distance=Math.sqrt(e)),i},getCenter:function(){if(this._map)return hi(this._defaultShape(),this._map.options.crs);throw new Error("Must add layer to map before using getCenter()")},getBounds:function(){return this._bounds},addLatLng:function(t,e){return e=e||this._defaultShape(),t=w(t),e.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new s,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return I(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var e=[],i=I(t),n=0,o=t.length;n<o;n++)i?(e[n]=w(t[n]),this._bounds.extend(e[n])):e[n]=this._convertLatLngs(t[n]);return e},_project:function(){var t=new f;this._rings=[],this._projectLatlngs(this._latlngs,this._rings,t),this._bounds.isValid()&&t.isValid()&&(this._rawPxBounds=t,this._updateBounds())},_updateBounds:function(){var t=this._clickTolerance(),t=new p(t,t);this._rawPxBounds&&(this._pxBounds=new f([this._rawPxBounds.min.subtract(t),this._rawPxBounds.max.add(t)]))},_projectLatlngs:function(t,e,i){var n,o,s=t[0]instanceof v,r=t.length;if(s){for(o=[],n=0;n<r;n++)o[n]=this._map.latLngToLayerPoint(t[n]),i.extend(o[n]);e.push(o)}else for(n=0;n<r;n++)this._projectLatlngs(t[n],e,i)},_clipPoints:function(){var t=this._renderer._bounds;if(this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var e,i,n,o,s=this._parts,r=0,a=0,h=this._rings.length;r<h;r++)for(e=0,i=(o=this._rings[r]).length;e<i-1;e++)(n=ni(o[e],o[e+1],t,e,!0))&&(s[a]=s[a]||[],s[a].push(n[0]),n[1]===o[e+1]&&e!==i-2||(s[a].push(n[1]),a++))},_simplifyPoints:function(){for(var t=this._parts,e=this.options.smoothFactor,i=0,n=t.length;i<n;i++)t[i]=ei(t[i],e)},_update:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),this._updatePath())},_updatePath:function(){this._renderer._updatePoly(this)},_containsPoint:function(t,e){var i,n,o,s,r,a,h=this._clickTolerance();if(this._pxBounds&&this._pxBounds.contains(t))for(i=0,s=this._parts.length;i<s;i++)for(n=0,o=(r=(a=this._parts[i]).length)-1;n<r;o=n++)if((e||0!==n)&&ii(t,a[o],a[n])<=h)return!0;return!1}});yi._flat=ai;var xi=yi.extend({options:{fill:!0},isEmpty:function(){return!this._latlngs.length||!this._latlngs[0].length},getCenter:function(){if(this._map)return $e(this._defaultShape(),this._map.options.crs);throw new Error("Must add layer to map before using getCenter()")},_convertLatLngs:function(t){var t=yi.prototype._convertLatLngs.call(this,t),e=t.length;return 2<=e&&t[0]instanceof v&&t[0].equals(t[e-1])&&t.pop(),t},_setLatLngs:function(t){yi.prototype._setLatLngs.call(this,t),I(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return(I(this._latlngs[0])?this._latlngs:this._latlngs[0])[0]},_clipPoints:function(){var t=this._renderer._bounds,e=this.options.weight,e=new p(e,e),t=new f(t.min.subtract(e),t.max.add(e));if(this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var i,n=0,o=this._rings.length;n<o;n++)(i=Je(this._rings[n],t,!0)).length&&this._parts.push(i)},_updatePath:function(){this._renderer._updatePoly(this,!0)},_containsPoint:function(t){var e,i,n,o,s,r,a,h,l=!1;if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(o=0,a=this._parts.length;o<a;o++)for(s=0,r=(h=(e=this._parts[o]).length)-1;s<h;r=s++)i=e[s],n=e[r],i.y>t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(l=!l);return l||yi.prototype._containsPoint.call(this,t,!0)}});var wi=ci.extend({initialize:function(t,e){c(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,o=d(t)?t:t.features;if(o){for(e=0,i=o.length;e<i;e++)((n=o[e]).geometries||n.geometry||n.features||n.coordinates)&&this.addData(n);return this}var s,r=this.options;return(!r.filter||r.filter(t))&&(s=bi(t,r))?(s.feature=Zi(t),s.defaultOptions=s.options,this.resetStyle(s),r.onEachFeature&&r.onEachFeature(t,s),this.addLayer(s)):this},resetStyle:function(t){return void 0===t?this.eachLayer(this.resetStyle,this):(t.options=l({},t.defaultOptions),this._setLayerStyle(t,this.options.style),this)},setStyle:function(e){return this.eachLayer(function(t){this._setLayerStyle(t,e)},this)},_setLayerStyle:function(t,e){t.setStyle&&("function"==typeof e&&(e=e(t.feature)),t.setStyle(e))}});function bi(t,e){var i,n,o,s,r="Feature"===t.type?t.geometry:t,a=r?r.coordinates:null,h=[],l=e&&e.pointToLayer,u=e&&e.coordsToLatLng||Li;if(!a&&!r)return null;switch(r.type){case"Point":return Pi(l,t,i=u(a),e);case"MultiPoint":for(o=0,s=a.length;o<s;o++)i=u(a[o]),h.push(Pi(l,t,i,e));return new ci(h);case"LineString":case"MultiLineString":return n=Ti(a,"LineString"===r.type?0:1,u),new yi(n,e);case"Polygon":case"MultiPolygon":return n=Ti(a,"Polygon"===r.type?1:2,u),new xi(n,e);case"GeometryCollection":for(o=0,s=r.geometries.length;o<s;o++){var c=bi({geometry:r.geometries[o],type:"Feature",properties:t.properties},e);c&&h.push(c)}return new ci(h);case"FeatureCollection":for(o=0,s=r.features.length;o<s;o++){var d=bi(r.features[o],e);d&&h.push(d)}return new ci(h);default:throw new Error("Invalid GeoJSON object.")}}function Pi(t,e,i,n){return t?t(e,i):new mi(i,n&&n.markersInheritOptions&&n)}function Li(t){return new v(t[1],t[0],t[2])}function Ti(t,e,i){for(var n,o=[],s=0,r=t.length;s<r;s++)n=e?Ti(t[s],e-1,i):(i||Li)(t[s]),o.push(n);return o}function Mi(t,e){return void 0!==(t=w(t)).alt?[i(t.lng,e),i(t.lat,e),i(t.alt,e)]:[i(t.lng,e),i(t.lat,e)]}function zi(t,e,i,n){for(var o=[],s=0,r=t.length;s<r;s++)o.push(e?zi(t[s],I(t[s])?0:e-1,i,n):Mi(t[s],n));return!e&&i&&0<o.length&&o.push(o[0].slice()),o}function Ci(t,e){return t.feature?l({},t.feature,{geometry:e}):Zi(e)}function Zi(t){return"Feature"===t.type||"FeatureCollection"===t.type?t:{type:"Feature",properties:{},geometry:t}}Tt={toGeoJSON:function(t){return Ci(this,{type:"Point",coordinates:Mi(this.getLatLng(),t)})}};function Si(t,e){return new wi(t,e)}mi.include(Tt),vi.include(Tt),gi.include(Tt),yi.include({toGeoJSON:function(t){var e=!I(this._latlngs);return Ci(this,{type:(e?"Multi":"")+"LineString",coordinates:zi(this._latlngs,e?1:0,!1,t)})}}),xi.include({toGeoJSON:function(t){var e=!I(this._latlngs),i=e&&!I(this._latlngs[0]),t=zi(this._latlngs,i?2:e?1:0,!0,t);return Ci(this,{type:(i?"Multi":"")+"Polygon",coordinates:t=e?t:[t]})}}),ui.include({toMultiPoint:function(e){var i=[];return this.eachLayer(function(t){i.push(t.toGeoJSON(e).geometry.coordinates)}),Ci(this,{type:"MultiPoint",coordinates:i})},toGeoJSON:function(e){var i,n,t=this.feature&&this.feature.geometry&&this.feature.geometry.type;return"MultiPoint"===t?this.toMultiPoint(e):(i="GeometryCollection"===t,n=[],this.eachLayer(function(t){t.toGeoJSON&&(t=t.toGeoJSON(e),i?n.push(t.geometry):"FeatureCollection"===(t=Zi(t)).type?n.push.apply(n,t.features):n.push(t))}),i?Ci(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n})}});var Mt=Si,Ei=o.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1,errorOverlayUrl:"",zIndex:1,className:""},initialize:function(t,e,i){this._url=t,this._bounds=g(e),c(this,i)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(M(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){T(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&fe(this._image),this},bringToBack:function(){return this._map&&ge(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=g(t),this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t="IMG"===this._url.tagName,e=this._image=t?this._url:P("img");M(e,"leaflet-image-layer"),this._zoomAnimated&&M(e,"leaflet-zoom-animated"),this.options.className&&M(e,this.options.className),e.onselectstart=u,e.onmousemove=u,e.onload=a(this.fire,this,"load"),e.onerror=a(this._overlayOnError,this,"error"),!this.options.crossOrigin&&""!==this.options.crossOrigin||(e.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),this.options.zIndex&&this._updateZIndex(),t?this._url=e.src:(e.src=this._url,e.alt=this.options.alt)},_animateZoom:function(t){var e=this._map.getZoomScale(t.zoom),t=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;be(this._image,t,e)},_reset:function(){var t=this._image,e=new f(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),i=e.getSize();Z(t,e.min),t.style.width=i.x+"px",t.style.height=i.y+"px"},_updateOpacity:function(){C(this._image,this.options.opacity)},_updateZIndex:function(){this._image&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._image.style.zIndex=this.options.zIndex)},_overlayOnError:function(){this.fire("error");var t=this.options.errorOverlayUrl;t&&this._url!==t&&(this._url=t,this._image.src=t)},getCenter:function(){return this._bounds.getCenter()}}),ki=Ei.extend({options:{autoplay:!0,loop:!0,keepAspectRatio:!0,muted:!1,playsInline:!0},_initImage:function(){var t="VIDEO"===this._url.tagName,e=this._image=t?this._url:P("video");if(M(e,"leaflet-image-layer"),this._zoomAnimated&&M(e,"leaflet-zoom-animated"),this.options.className&&M(e,this.options.className),e.onselectstart=u,e.onmousemove=u,e.onloadeddata=a(this.fire,this,"load"),t){for(var i=e.getElementsByTagName("source"),n=[],o=0;o<i.length;o++)n.push(i[o].src);this._url=0<i.length?n:[e.src]}else{d(this._url)||(this._url=[this._url]),!this.options.keepAspectRatio&&Object.prototype.hasOwnProperty.call(e.style,"objectFit")&&(e.style.objectFit="fill"),e.autoplay=!!this.options.autoplay,e.loop=!!this.options.loop,e.muted=!!this.options.muted,e.playsInline=!!this.options.playsInline;for(var s=0;s<this._url.length;s++){var r=P("source");r.src=this._url[s],e.appendChild(r)}}}});var Oi=Ei.extend({_initImage:function(){var t=this._image=this._url;M(t,"leaflet-image-layer"),this._zoomAnimated&&M(t,"leaflet-zoom-animated"),this.options.className&&M(t,this.options.className),t.onselectstart=u,t.onmousemove=u}});var Ai=o.extend({options:{interactive:!1,offset:[0,0],className:"",pane:void 0,content:""},initialize:function(t,e){t&&(t instanceof v||d(t))?(this._latlng=w(t),c(this,e)):(c(this,t),this._source=e),this.options.content&&(this._content=this.options.content)},openOn:function(t){return(t=arguments.length?t:this._source._map).hasLayer(this)||t.addLayer(this),this},close:function(){return this._map&&this._map.removeLayer(this),this},toggle:function(t){return this._map?this.close():(arguments.length?this._source=t:t=this._source,this._prepareOpen(),this.openOn(t._map)),this},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&C(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&C(this._container,1),this.bringToFront(),this.options.interactive&&(M(this._container,"leaflet-interactive"),this.addInteractiveTarget(this._container))},onRemove:function(t){t._fadeAnimated?(C(this._container,0),this._removeTimeout=setTimeout(a(T,void 0,this._container),200)):T(this._container),this.options.interactive&&(z(this._container,"leaflet-interactive"),this.removeInteractiveTarget(this._container))},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=w(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&fe(this._container),this},bringToBack:function(){return this._map&&ge(this._container),this},_prepareOpen:function(t){if(!(i=this._source)._map)return!1;if(i instanceof ci){var e,i=null,n=this._source._layers;for(e in n)if(n[e]._map){i=n[e];break}if(!i)return!1;this._source=i}if(!t)if(i.getCenter)t=i.getCenter();else if(i.getLatLng)t=i.getLatLng();else{if(!i.getBounds)throw new Error("Unable to get source layer LatLng.");t=i.getBounds().getCenter()}return this.setLatLng(t),this._map&&this.update(),!0},_updateContent:function(){if(this._content){var t=this._contentNode,e="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof e)t.innerHTML=e;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(e)}this.fire("contentupdate")}},_updatePosition:function(){var t,e,i;this._map&&(e=this._map.latLngToLayerPoint(this._latlng),t=m(this.options.offset),i=this._getAnchor(),this._zoomAnimated?Z(this._container,e.add(i)):t=t.add(e).add(i),e=this._containerBottom=-t.y,i=this._containerLeft=-Math.round(this._containerWidth/2)+t.x,this._container.style.bottom=e+"px",this._container.style.left=i+"px")},_getAnchor:function(){return[0,0]}}),Bi=(A.include({_initOverlay:function(t,e,i,n){var o=e;return o instanceof t||(o=new t(n).setContent(e)),i&&o.setLatLng(i),o}}),o.include({_initOverlay:function(t,e,i,n){var o=i;return o instanceof t?(c(o,n),o._source=this):(o=e&&!n?e:new t(n,this)).setContent(i),o}}),Ai.extend({options:{pane:"popupPane",offset:[0,7],maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,closeOnEscapeKey:!0,className:""},openOn:function(t){return!(t=arguments.length?t:this._source._map).hasLayer(this)&&t._popup&&t._popup.options.autoClose&&t.removeLayer(t._popup),t._popup=this,Ai.prototype.openOn.call(this,t)},onAdd:function(t){Ai.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof fi||this._source.on("preclick",Ae))},onRemove:function(t){Ai.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof fi||this._source.off("preclick",Ae))},getEvents:function(){var t=Ai.prototype.getEvents.call(this);return(void 0!==this.options.closeOnClick?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this.close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_initLayout:function(){var t="leaflet-popup",e=this._container=P("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated"),i=this._wrapper=P("div",t+"-content-wrapper",e);this._contentNode=P("div",t+"-content",i),Ie(e),Be(this._contentNode),S(e,"contextmenu",Ae),this._tipContainer=P("div",t+"-tip-container",e),this._tip=P("div",t+"-tip",this._tipContainer),this.options.closeButton&&((i=this._closeButton=P("a",t+"-close-button",e)).setAttribute("role","button"),i.setAttribute("aria-label","Close popup"),i.href="#close",i.innerHTML='<span aria-hidden="true">×</span>',S(i,"click",function(t){O(t),this.close()},this))},_updateLayout:function(){var t=this._contentNode,e=t.style,i=(e.width="",e.whiteSpace="nowrap",t.offsetWidth),i=Math.min(i,this.options.maxWidth),i=(i=Math.max(i,this.options.minWidth),e.width=i+1+"px",e.whiteSpace="",e.height="",t.offsetHeight),n=this.options.maxHeight,o="leaflet-popup-scrolled";(n&&n<i?(e.height=n+"px",M):z)(t,o),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var t=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();Z(this._container,t.add(e))},_adjustPan:function(){var t,e,i,n,o,s,r,a;this.options.autoPan&&(this._map._panAnim&&this._map._panAnim.stop(),this._autopanning?this._autopanning=!1:(t=this._map,e=parseInt(pe(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+e,a=this._containerWidth,(i=new p(this._containerLeft,-e-this._containerBottom))._add(Pe(this._container)),i=t.layerPointToContainerPoint(i),o=m(this.options.autoPanPadding),n=m(this.options.autoPanPaddingTopLeft||o),o=m(this.options.autoPanPaddingBottomRight||o),s=t.getSize(),r=0,i.x+a+o.x>s.x&&(r=i.x+a-s.x+o.x),i.x-r-n.x<(a=0)&&(r=i.x-n.x),i.y+e+o.y>s.y&&(a=i.y+e-s.y+o.y),i.y-a-n.y<0&&(a=i.y-n.y),(r||a)&&(this.options.keepInView&&(this._autopanning=!0),t.fire("autopanstart").panBy([r,a]))))},_getAnchor:function(){return m(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ii=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,e,i){return this._initOverlay(Bi,t,e,i).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,e){return this._popup=this._initOverlay(Bi,this._popup,t,e),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&(this instanceof ci||(this._popup._source=this),this._popup._prepareOpen(t||this._latlng)&&this._popup.openOn(this._map)),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var e;this._popup&&this._map&&(Re(t),e=t.layer||t.target,this._popup._source!==e||e instanceof fi?(this._popup._source=e,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),Ai.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){Ai.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){Ai.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=Ai.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=P("div",t),this._container.setAttribute("role","tooltip"),this._container.setAttribute("id","leaflet-tooltip-"+h(this))},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var e,i=this._map,n=this._container,o=i.latLngToContainerPoint(i.getCenter()),i=i.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=m(this.options.offset),l=this._getAnchor(),i="top"===s?(e=r/2,a):"bottom"===s?(e=r/2,0):(e="center"===s?r/2:"right"===s?0:"left"===s?r:i.x<o.x?(s="right",0):(s="left",r+2*(h.x+l.x)),a/2);t=t.subtract(m(e,i,!0)).add(h).add(l),z(n,"leaflet-tooltip-right"),z(n,"leaflet-tooltip-left"),z(n,"leaflet-tooltip-top"),z(n,"leaflet-tooltip-bottom"),M(n,"leaflet-tooltip-"+s),Z(n,t)},_updatePosition:function(){var t=this._map.latLngToLayerPoint(this._latlng);this._setPosition(t)},setOpacity:function(t){this.options.opacity=t,this._container&&C(this._container,t)},_animateZoom:function(t){t=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPosition(t)},_getAnchor:function(){return m(this._source&&this._source._getTooltipAnchor&&!this.options.sticky?this._source._getTooltipAnchor():[0,0])}})),Ri=(A.include({openTooltip:function(t,e,i){return this._initOverlay(Ii,t,e,i).openOn(this),this},closeTooltip:function(t){return t.close(),this}}),o.include({bindTooltip:function(t,e){return this._tooltip&&this.isTooltipOpen()&&this.unbindTooltip(),this._tooltip=this._initOverlay(Ii,this._tooltip,t,e),this._initTooltipInteractions(),this._tooltip.options.permanent&&this._map&&this._map.hasLayer(this)&&this.openTooltip(),this},unbindTooltip:function(){return this._tooltip&&(this._initTooltipInteractions(!0),this.closeTooltip(),this._tooltip=null),this},_initTooltipInteractions:function(t){var e,i;!t&&this._tooltipHandlersAdded||(e=t?"off":"on",i={remove:this.closeTooltip,move:this._moveTooltip},this._tooltip.options.permanent?i.add=this._openTooltip:(i.mouseover=this._openTooltip,i.mouseout=this.closeTooltip,i.click=this._openTooltip,this._map?this._addFocusListeners():i.add=this._addFocusListeners),this._tooltip.options.sticky&&(i.mousemove=this._moveTooltip),this[e](i),this._tooltipHandlersAdded=!t)},openTooltip:function(t){return this._tooltip&&(this instanceof ci||(this._tooltip._source=this),this._tooltip._prepareOpen(t)&&(this._tooltip.openOn(this._map),this.getElement?this._setAriaDescribedByOnLayer(this):this.eachLayer&&this.eachLayer(this._setAriaDescribedByOnLayer,this))),this},closeTooltip:function(){if(this._tooltip)return this._tooltip.close()},toggleTooltip:function(){return this._tooltip&&this._tooltip.toggle(this),this},isTooltipOpen:function(){return this._tooltip.isOpen()},setTooltipContent:function(t){return this._tooltip&&this._tooltip.setContent(t),this},getTooltip:function(){return this._tooltip},_addFocusListeners:function(){this.getElement?this._addFocusListenersOnLayer(this):this.eachLayer&&this.eachLayer(this._addFocusListenersOnLayer,this)},_addFocusListenersOnLayer:function(t){var e="function"==typeof t.getElement&&t.getElement();e&&(S(e,"focus",function(){this._tooltip._source=t,this.openTooltip()},this),S(e,"blur",this.closeTooltip,this))},_setAriaDescribedByOnLayer:function(t){t="function"==typeof t.getElement&&t.getElement();t&&t.setAttribute("aria-describedby",this._tooltip._container.id)},_openTooltip:function(t){var e;this._tooltip&&this._map&&(this._map.dragging&&this._map.dragging.moving()&&!this._openOnceFlag?(this._openOnceFlag=!0,(e=this)._map.once("moveend",function(){e._openOnceFlag=!1,e._openTooltip(t)})):(this._tooltip._source=t.layer||t.target,this.openTooltip(this._tooltip.options.sticky?t.latlng:void 0)))},_moveTooltip:function(t){var e=t.latlng;this._tooltip.options.sticky&&t.originalEvent&&(t=this._map.mouseEventToContainerPoint(t.originalEvent),t=this._map.containerPointToLayerPoint(t),e=this._map.layerPointToLatLng(t)),this._tooltip.setLatLng(e)}}),di.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var t=t&&"DIV"===t.tagName?t:document.createElement("div"),e=this.options;return e.html instanceof Element?(me(t),t.appendChild(e.html)):t.innerHTML=!1!==e.html?e.html:"",e.bgPos&&(e=m(e.bgPos),t.style.backgroundPosition=-e.x+"px "+-e.y+"px"),this._setIconStyles(t,"icon"),t},createShadow:function(){return null}}));di.Default=_i;var Ni=o.extend({options:{tileSize:256,opacity:1,updateWhenIdle:b.mobile,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:void 0,maxNativeZoom:void 0,minNativeZoom:void 0,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){c(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),T(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=void 0},bringToFront:function(){return this._map&&(fe(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(ge(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){var t;return this._map&&(this._removeAllTiles(),(t=this._clampZoom(this._map.getZoom()))!==this._tileZoom&&(this._tileZoom=t,this._updateLevels()),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=j(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return document.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof p?t:new p(t,t)},_updateZIndex:function(){this._container&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var e,i=this.getPane().children,n=-t(-1/0,1/0),o=0,s=i.length;o<s;o++)e=i[o].style.zIndex,i[o]!==this._container&&e&&(n=t(n,+e));isFinite(n)&&(this.options.zIndex=n+t(-1,1),this._updateZIndex())},_updateOpacity:function(){if(this._map&&!b.ielt9){C(this._container,this.options.opacity);var t,e=+new Date,i=!1,n=!1;for(t in this._tiles){var o,s=this._tiles[t];s.current&&s.loaded&&(o=Math.min(1,(e-s.loaded)/200),C(s.el,o),o<1?i=!0:(s.active?n=!0:this._onOpaqueTile(s),s.active=!0))}n&&!this._noPrune&&this._pruneTiles(),i&&(r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this))}},_onOpaqueTile:u,_initContainer:function(){this._container||(this._container=P("div","leaflet-layer "+(this.options.className||"")),this._updateZIndex(),this.options.opacity<1&&this._updateOpacity(),this.getPane().appendChild(this._container))},_updateLevels:function(){var t=this._tileZoom,e=this.options.maxZoom;if(void 0!==t){for(var i in this._levels)i=Number(i),this._levels[i].el.children.length||i===t?(this._levels[i].el.style.zIndex=e-Math.abs(t-i),this._onUpdateLevel(i)):(T(this._levels[i].el),this._removeTilesAtZoom(i),this._onRemoveLevel(i),delete this._levels[i]);var n=this._levels[t],o=this._map;return n||((n=this._levels[t]={}).el=P("div","leaflet-tile-container leaflet-zoom-animated",this._container),n.el.style.zIndex=e,n.origin=o.project(o.unproject(o.getPixelOrigin()),t).round(),n.zoom=t,this._setZoomTransform(n,o.getCenter(),o.getZoom()),u(n.el.offsetWidth),this._onCreateLevel(n)),this._level=n}},_onUpdateLevel:u,_onRemoveLevel:u,_onCreateLevel:u,_pruneTiles:function(){if(this._map){var t,e,i,n=this._map.getZoom();if(n>this.options.maxZoom||n<this.options.minZoom)this._removeAllTiles();else{for(t in this._tiles)(i=this._tiles[t]).retain=i.current;for(t in this._tiles)(i=this._tiles[t]).current&&!i.active&&(e=i.coords,this._retainParent(e.x,e.y,e.z,e.z-5)||this._retainChildren(e.x,e.y,e.z,e.z+2));for(t in this._tiles)this._tiles[t].retain||this._removeTile(t)}}},_removeTilesAtZoom:function(t){for(var e in this._tiles)this._tiles[e].coords.z===t&&this._removeTile(e)},_removeAllTiles:function(){for(var t in this._tiles)this._removeTile(t)},_invalidateAll:function(){for(var t in this._levels)T(this._levels[t].el),this._onRemoveLevel(Number(t)),delete this._levels[t];this._removeAllTiles(),this._tileZoom=void 0},_retainParent:function(t,e,i,n){var t=Math.floor(t/2),e=Math.floor(e/2),i=i-1,o=new p(+t,+e),o=(o.z=i,this._tileCoordsToKey(o)),o=this._tiles[o];return o&&o.active?o.retain=!0:(o&&o.loaded&&(o.retain=!0),n<i&&this._retainParent(t,e,i,n))},_retainChildren:function(t,e,i,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*e;s<2*e+2;s++){var r=new p(o,s),r=(r.z=i+1,this._tileCoordsToKey(r)),r=this._tiles[r];r&&r.active?r.retain=!0:(r&&r.loaded&&(r.retain=!0),i+1<n&&this._retainChildren(o,s,i+1,n))}},_resetView:function(t){t=t&&(t.pinch||t.flyTo);this._setView(this._map.getCenter(),this._map.getZoom(),t,t)},_animateZoom:function(t){this._setView(t.center,t.zoom,!0,t.noUpdate)},_clampZoom:function(t){var e=this.options;return void 0!==e.minNativeZoom&&t<e.minNativeZoom?e.minNativeZoom:void 0!==e.maxNativeZoom&&e.maxNativeZoom<t?e.maxNativeZoom:t},_setView:function(t,e,i,n){var o=Math.round(e),o=void 0!==this.options.maxZoom&&o>this.options.maxZoom||void 0!==this.options.minZoom&&o<this.options.minZoom?void 0:this._clampZoom(o),s=this.options.updateWhenZooming&&o!==this._tileZoom;n&&!s||(this._tileZoom=o,this._abortLoading&&this._abortLoading(),this._updateLevels(),this._resetGrid(),void 0!==o&&this._update(t),i||this._pruneTiles(),this._noPrune=!!i),this._setZoomTransforms(t,e)},_setZoomTransforms:function(t,e){for(var i in this._levels)this._setZoomTransform(this._levels[i],t,e)},_setZoomTransform:function(t,e,i){var n=this._map.getZoomScale(i,t.zoom),e=t.origin.multiplyBy(n).subtract(this._map._getNewPixelOrigin(e,i)).round();b.any3d?be(t.el,e,n):Z(t.el,e)},_resetGrid:function(){var t=this._map,e=t.options.crs,i=this._tileSize=this.getTileSize(),n=this._tileZoom,o=this._map.getPixelWorldBounds(this._tileZoom);o&&(this._globalTileRange=this._pxBoundsToTileRange(o)),this._wrapX=e.wrapLng&&!this.options.noWrap&&[Math.floor(t.project([0,e.wrapLng[0]],n).x/i.x),Math.ceil(t.project([0,e.wrapLng[1]],n).x/i.y)],this._wrapY=e.wrapLat&&!this.options.noWrap&&[Math.floor(t.project([e.wrapLat[0],0],n).y/i.x),Math.ceil(t.project([e.wrapLat[1],0],n).y/i.y)]},_onMoveEnd:function(){this._map&&!this._map._animatingZoom&&this._update()},_getTiledPixelBounds:function(t){var e=this._map,i=e._animatingZoom?Math.max(e._animateToZoom,e.getZoom()):e.getZoom(),i=e.getZoomScale(i,this._tileZoom),t=e.project(t,this._tileZoom).floor(),e=e.getSize().divideBy(2*i);return new f(t.subtract(e),t.add(e))},_update:function(t){var e=this._map;if(e){var i=this._clampZoom(e.getZoom());if(void 0===t&&(t=e.getCenter()),void 0!==this._tileZoom){var n,e=this._getTiledPixelBounds(t),o=this._pxBoundsToTileRange(e),s=o.getCenter(),r=[],e=this.options.keepBuffer,a=new f(o.getBottomLeft().subtract([e,-e]),o.getTopRight().add([e,-e]));if(!(isFinite(o.min.x)&&isFinite(o.min.y)&&isFinite(o.max.x)&&isFinite(o.max.y)))throw new Error("Attempted to load an infinite number of tiles");for(n in this._tiles){var h=this._tiles[n].coords;h.z===this._tileZoom&&a.contains(new p(h.x,h.y))||(this._tiles[n].current=!1)}if(1<Math.abs(i-this._tileZoom))this._setView(t,i);else{for(var l=o.min.y;l<=o.max.y;l++)for(var u=o.min.x;u<=o.max.x;u++){var c,d=new p(u,l);d.z=this._tileZoom,this._isValidTile(d)&&((c=this._tiles[this._tileCoordsToKey(d)])?c.current=!0:r.push(d))}if(r.sort(function(t,e){return t.distanceTo(s)-e.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));for(var _=document.createDocumentFragment(),u=0;u<r.length;u++)this._addTile(r[u],_);this._level.el.appendChild(_)}}}}},_isValidTile:function(t){var e=this._map.options.crs;if(!e.infinite){var i=this._globalTileRange;if(!e.wrapLng&&(t.x<i.min.x||t.x>i.max.x)||!e.wrapLat&&(t.y<i.min.y||t.y>i.max.y))return!1}return!this.options.bounds||(e=this._tileCoordsToBounds(t),g(this.options.bounds).overlaps(e))},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var e=this._map,i=this.getTileSize(),n=t.scaleBy(i),i=n.add(i);return[e.unproject(n,t.z),e.unproject(i,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),e=new p(+t[0],+t[1]);return e.z=+t[2],e},_removeTile:function(t){var e=this._tiles[t];e&&(T(e.el),delete this._tiles[t],this.fire("tileunload",{tile:e.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){M(t,"leaflet-tile");var e=this.getTileSize();t.style.width=e.x+"px",t.style.height=e.y+"px",t.onselectstart=u,t.onmousemove=u,b.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,e){var i=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,i),this._tiles[n]={el:o,coords:t,current:!0},e.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,e,i){e&&this.fire("tileerror",{error:e,tile:i,coords:t});var n=this._tileCoordsToKey(t);(i=this._tiles[n])&&(i.loaded=+new Date,this._map._fadeAnimated?(C(i.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(i.active=!0,this._pruneTiles()),e||(M(i.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:i.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),b.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var e=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return e.z=t.z,e},_pxBoundsToTileRange:function(t){var e=this.getTileSize();return new f(t.min.unscaleBy(e).floor(),t.max.unscaleBy(e).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Di=Ni.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,e){this._url=t,(e=c(this,e)).detectRetina&&b.retina&&0<e.maxZoom?(e.tileSize=Math.floor(e.tileSize/2),e.zoomReverse?(e.zoomOffset--,e.minZoom=Math.min(e.maxZoom,e.minZoom+1)):(e.zoomOffset++,e.maxZoom=Math.max(e.minZoom,e.maxZoom-1)),e.minZoom=Math.max(0,e.minZoom)):e.zoomReverse?e.minZoom=Math.min(e.maxZoom,e.minZoom):e.maxZoom=Math.max(e.minZoom,e.maxZoom),"string"==typeof e.subdomains&&(e.subdomains=e.subdomains.split("")),this.on("tileunload",this._onTileRemove)},setUrl:function(t,e){return this._url===t&&void 0===e&&(e=!0),this._url=t,e||this.redraw(),this},createTile:function(t,e){var i=document.createElement("img");return S(i,"load",a(this._tileOnLoad,this,e,i)),S(i,"error",a(this._tileOnError,this,e,i)),!this.options.crossOrigin&&""!==this.options.crossOrigin||(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),"string"==typeof this.options.referrerPolicy&&(i.referrerPolicy=this.options.referrerPolicy),i.alt="",i.src=this.getTileUrl(t),i},getTileUrl:function(t){var e={r:b.retina?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};return this._map&&!this._map.options.crs.infinite&&(t=this._globalTileRange.max.y-t.y,this.options.tms&&(e.y=t),e["-y"]=t),q(this._url,l(e,this.options))},_tileOnLoad:function(t,e){b.ielt9?setTimeout(a(t,this,null,e),0):t(null,e)},_tileOnError:function(t,e,i){var n=this.options.errorTileUrl;n&&e.getAttribute("src")!==n&&(e.src=n),t(i,e)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,e=this.options.maxZoom;return(t=this.options.zoomReverse?e-t:t)+this.options.zoomOffset},_getSubdomain:function(t){t=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[t]},_abortLoading:function(){var t,e,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=u,i.onerror=u,i.complete||(i.src=K,e=this._tiles[t].coords,T(i),delete this._tiles[t],this.fire("tileabort",{tile:i,coords:e})))},_removeTile:function(t){var e=this._tiles[t];if(e)return e.el.setAttribute("src",K),Ni.prototype._removeTile.call(this,t)},_tileReady:function(t,e,i){if(this._map&&(!i||i.getAttribute("src")!==K))return Ni.prototype._tileReady.call(this,t,e,i)}});function ji(t,e){return new Di(t,e)}var Hi=Di.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var i,n=l({},this.defaultWmsParams);for(i in e)i in this.options||(n[i]=e[i]);var t=(e=c(this,e)).detectRetina&&b.retina?2:1,o=this.getTileSize();n.width=o.x*t,n.height=o.y*t,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=1.3<=this._wmsVersion?"crs":"srs";this.wmsParams[e]=this._crs.code,Di.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._tileCoordsToNwSe(t),i=this._crs,i=_(i.project(e[0]),i.project(e[1])),e=i.min,i=i.max,e=(1.3<=this._wmsVersion&&this._crs===li?[e.y,e.x,i.y,i.x]:[e.x,e.y,i.x,i.y]).join(","),i=Di.prototype.getTileUrl.call(this,t);return i+U(this.wmsParams,i,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+e},setParams:function(t,e){return l(this.wmsParams,t),e||this.redraw(),this}});Di.WMS=Hi,ji.wms=function(t,e){return new Hi(t,e)};var Wi=o.extend({options:{padding:.1},initialize:function(t){c(this,t),h(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),M(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,e){var i=this._map.getZoomScale(e,this._zoom),n=this._map.getSize().multiplyBy(.5+this.options.padding),o=this._map.project(this._center,e),n=n.multiplyBy(-i).add(o).subtract(this._map._getNewPixelOrigin(t,e));b.any3d?be(this._container,n,i):Z(this._container,n)},_reset:function(){for(var t in this._update(),this._updateTransform(this._center,this._zoom),this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,e=this._map.getSize(),i=this._map.containerPointToLayerPoint(e.multiplyBy(-t)).round();this._bounds=new f(i,i.add(e.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),Fi=Wi.extend({options:{tolerance:0},getEvents:function(){var t=Wi.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){Wi.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");S(t,"mousemove",this._onMouseMove,this),S(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),S(t,"mouseout",this._handleMouseOut,this),t._leaflet_disable_events=!0,this._ctx=t.getContext("2d")},_destroyContainer:function(){r(this._redrawRequest),delete this._ctx,T(this._container),k(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){for(var t in this._redrawBounds=null,this._layers)this._layers[t]._update();this._redraw()}},_update:function(){var t,e,i,n;this._map._animatingZoom&&this._bounds||(Wi.prototype._update.call(this),t=this._bounds,e=this._container,i=t.getSize(),n=b.retina?2:1,Z(e,t.min),e.width=n*i.x,e.height=n*i.y,e.style.width=i.x+"px",e.style.height=i.y+"px",b.retina&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update"))},_reset:function(){Wi.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t);t=(this._layers[h(t)]=t)._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=t),this._drawLast=t,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var e=t._order,i=e.next,e=e.prev;i?i.prev=e:this._drawLast=e,e?e.next=i:this._drawFirst=i,delete t._order,delete this._layers[h(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){for(var e,i=t.options.dashArray.split(/[, ]+/),n=[],o=0;o<i.length;o++){if(e=Number(i[o]),isNaN(e))return;n.push(e)}t.options._dashArray=n}else t.options._dashArray=t.options.dashArray},_requestRedraw:function(t){this._map&&(this._extendRedrawBounds(t),this._redrawRequest=this._redrawRequest||x(this._redraw,this))},_extendRedrawBounds:function(t){var e;t._pxBounds&&(e=(t.options.weight||0)+1,this._redrawBounds=this._redrawBounds||new f,this._redrawBounds.extend(t._pxBounds.min.subtract([e,e])),this._redrawBounds.extend(t._pxBounds.max.add([e,e])))},_redraw:function(){this._redrawRequest=null,this._redrawBounds&&(this._redrawBounds.min._floor(),this._redrawBounds.max._ceil()),this._clear(),this._draw(),this._redrawBounds=null},_clear:function(){var t,e=this._redrawBounds;e?(t=e.getSize(),this._ctx.clearRect(e.min.x,e.min.y,t.x,t.y)):(this._ctx.save(),this._ctx.setTransform(1,0,0,1,0,0),this._ctx.clearRect(0,0,this._container.width,this._container.height),this._ctx.restore())},_draw:function(){var t,e,i=this._redrawBounds;this._ctx.save(),i&&(e=i.getSize(),this._ctx.beginPath(),this._ctx.rect(i.min.x,i.min.y,e.x,e.y),this._ctx.clip()),this._drawing=!0;for(var n=this._drawFirst;n;n=n.next)t=n.layer,(!i||t._pxBounds&&t._pxBounds.intersects(i))&&t._updatePath();this._drawing=!1,this._ctx.restore()},_updatePoly:function(t,e){if(this._drawing){var i,n,o,s,r=t._parts,a=r.length,h=this._ctx;if(a){for(h.beginPath(),i=0;i<a;i++){for(n=0,o=r[i].length;n<o;n++)s=r[i][n],h[n?"lineTo":"moveTo"](s.x,s.y);e&&h.closePath()}this._fillStroke(h,t)}}},_updateCircle:function(t){var e,i,n,o;this._drawing&&!t._empty()&&(e=t._point,i=this._ctx,n=Math.max(Math.round(t._radius),1),1!=(o=(Math.max(Math.round(t._radiusY),1)||n)/n)&&(i.save(),i.scale(1,o)),i.beginPath(),i.arc(e.x,e.y/o,n,0,2*Math.PI,!1),1!=o&&i.restore(),this._fillStroke(i,t))},_fillStroke:function(t,e){var i=e.options;i.fill&&(t.globalAlpha=i.fillOpacity,t.fillStyle=i.fillColor||i.color,t.fill(i.fillRule||"evenodd")),i.stroke&&0!==i.weight&&(t.setLineDash&&t.setLineDash(e.options&&e.options._dashArray||[]),t.globalAlpha=i.opacity,t.lineWidth=i.weight,t.strokeStyle=i.color,t.lineCap=i.lineCap,t.lineJoin=i.lineJoin,t.stroke())},_onClick:function(t){for(var e,i,n=this._map.mouseEventToLayerPoint(t),o=this._drawFirst;o;o=o.next)(e=o.layer).options.interactive&&e._containsPoint(n)&&(("click"===t.type||"preclick"===t.type)&&this._map._draggableMoved(e)||(i=e));this._fireEvent(!!i&&[i],t)},_onMouseMove:function(t){var e;!this._map||this._map.dragging.moving()||this._map._animatingZoom||(e=this._map.mouseEventToLayerPoint(t),this._handleMouseHover(t,e))},_handleMouseOut:function(t){var e=this._hoveredLayer;e&&(z(this._container,"leaflet-interactive"),this._fireEvent([e],t,"mouseout"),this._hoveredLayer=null,this._mouseHoverThrottled=!1)},_handleMouseHover:function(t,e){if(!this._mouseHoverThrottled){for(var i,n,o=this._drawFirst;o;o=o.next)(i=o.layer).options.interactive&&i._containsPoint(e)&&(n=i);n!==this._hoveredLayer&&(this._handleMouseOut(t),n&&(M(this._container,"leaflet-interactive"),this._fireEvent([n],t,"mouseover"),this._hoveredLayer=n)),this._fireEvent(!!this._hoveredLayer&&[this._hoveredLayer],t),this._mouseHoverThrottled=!0,setTimeout(a(function(){this._mouseHoverThrottled=!1},this),32)}},_fireEvent:function(t,e,i){this._map._fireDOMEvent(e,i||e.type,t)},_bringToFront:function(t){var e,i,n=t._order;n&&(e=n.next,i=n.prev,e&&((e.prev=i)?i.next=e:e&&(this._drawFirst=e),n.prev=this._drawLast,(this._drawLast.next=n).next=null,this._drawLast=n,this._requestRedraw(t)))},_bringToBack:function(t){var e,i,n=t._order;n&&(e=n.next,(i=n.prev)&&((i.next=e)?e.prev=i:i&&(this._drawLast=i),n.prev=null,n.next=this._drawFirst,this._drawFirst.prev=n,this._drawFirst=n,this._requestRedraw(t)))}});function Ui(t){return b.canvas?new Fi(t):null}var Vi=function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return document.createElement("<lvml:"+t+' class="lvml">')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),zt={_initContainer:function(){this._container=P("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Wi.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var e=t._container=Vi("shape");M(e,"leaflet-vml-shape "+(this.options.className||"")),e.coordsize="1 1",t._path=Vi("path"),e.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var e=t._container;this._container.appendChild(e),t.options.interactive&&t.addInteractiveTarget(e)},_removePath:function(t){var e=t._container;T(e),t.removeInteractiveTarget(e),delete this._layers[h(t)]},_updateStyle:function(t){var e=t._stroke,i=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(e=e||(t._stroke=Vi("stroke")),o.appendChild(e),e.weight=n.weight+"px",e.color=n.color,e.opacity=n.opacity,n.dashArray?e.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):e.dashStyle="",e.endcap=n.lineCap.replace("butt","flat"),e.joinstyle=n.lineJoin):e&&(o.removeChild(e),t._stroke=null),n.fill?(i=i||(t._fill=Vi("fill")),o.appendChild(i),i.color=n.fillColor||n.color,i.opacity=n.fillOpacity):i&&(o.removeChild(i),t._fill=null)},_updateCircle:function(t){var e=t._point.round(),i=Math.round(t._radius),n=Math.round(t._radiusY||i);this._setPath(t,t._empty()?"M0 0":"AL "+e.x+","+e.y+" "+i+","+n+" 0,23592600")},_setPath:function(t,e){t._path.v=e},_bringToFront:function(t){fe(t._container)},_bringToBack:function(t){ge(t._container)}},qi=b.vml?Vi:ct,Gi=Wi.extend({_initContainer:function(){this._container=qi("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=qi("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),k(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,e,i;this._map._animatingZoom&&this._bounds||(Wi.prototype._update.call(this),e=(t=this._bounds).getSize(),i=this._container,this._svgSize&&this._svgSize.equals(e)||(this._svgSize=e,i.setAttribute("width",e.x),i.setAttribute("height",e.y)),Z(i,t.min),i.setAttribute("viewBox",[t.min.x,t.min.y,e.x,e.y].join(" ")),this.fire("update"))},_initPath:function(t){var e=t._path=qi("path");t.options.className&&M(e,t.options.className),t.options.interactive&&M(e,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var e=t._path,t=t.options;e&&(t.stroke?(e.setAttribute("stroke",t.color),e.setAttribute("stroke-opacity",t.opacity),e.setAttribute("stroke-width",t.weight),e.setAttribute("stroke-linecap",t.lineCap),e.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?e.setAttribute("stroke-dasharray",t.dashArray):e.removeAttribute("stroke-dasharray"),t.dashOffset?e.setAttribute("stroke-dashoffset",t.dashOffset):e.removeAttribute("stroke-dashoffset")):e.setAttribute("stroke","none"),t.fill?(e.setAttribute("fill",t.fillColor||t.color),e.setAttribute("fill-opacity",t.fillOpacity),e.setAttribute("fill-rule",t.fillRule||"evenodd")):e.setAttribute("fill","none"))},_updatePoly:function(t,e){this._setPath(t,dt(t._parts,e))},_updateCircle:function(t){var e=t._point,i=Math.max(Math.round(t._radius),1),n="a"+i+","+(Math.max(Math.round(t._radiusY),1)||i)+" 0 1,0 ",e=t._empty()?"M0 0":"M"+(e.x-i)+","+e.y+n+2*i+",0 "+n+2*-i+",0 ";this._setPath(t,e)},_setPath:function(t,e){t._path.setAttribute("d",e)},_bringToFront:function(t){fe(t._path)},_bringToBack:function(t){ge(t._path)}});function Ki(t){return b.svg||b.vml?new Gi(t):null}b.vml&&Gi.include(zt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){var e;return"overlayPane"!==t&&void 0!==t&&(void 0===(e=this._paneRenderers[t])&&(e=this._createRenderer({pane:t}),this._paneRenderers[t]=e),e)},_createRenderer:function(t){return this.options.preferCanvas&&Ui(t)||Ki(t)}});var Yi=xi.extend({initialize:function(t,e){xi.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Gi.create=qi,Gi.pointsToPath=dt,wi.geometryToLayer=bi,wi.coordsToLatLng=Li,wi.coordsToLatLngs=Ti,wi.latLngToCoords=Mi,wi.latLngsToCoords=zi,wi.getFeature=Ci,wi.asFeature=Zi,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){k(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),re(),Le(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Re,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=P("div","leaflet-zoom-box",this._container),M(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new f(this._point,this._startPoint),e=t.getSize();Z(this._box,t.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(T(this._box),z(this._container,"leaflet-crosshair")),ae(),Te(),k(document,{contextmenu:Re,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom(),n=e.options.zoomDelta,i=t.originalEvent.shiftKey?i-n:i+n;"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xe(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),M(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){z(this._map._container,"leaflet-grab"),z(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,e=this._map;e._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=_(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,e.fire("movestart").fire("dragstart"),e.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var e,i;this._map.options.inertia&&(e=this._lastTime=+new Date,i=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(i),this._times.push(e),this._prunePositions(e)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1<this._positions.length&&50<t-this._times[0];)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,e){return t-(t-e)*this._viscosity},_onPreDragLimit:function(){var t,e;this._viscosity&&this._offsetLimit&&(t=this._draggable._newPos.subtract(this._draggable._startPos),e=this._offsetLimit,t.x<e.min.x&&(t.x=this._viscousLimit(t.x,e.min.x)),t.y<e.min.y&&(t.y=this._viscousLimit(t.y,e.min.y)),t.x>e.max.x&&(t.x=this._viscousLimit(t.x,e.max.x)),t.y>e.max.y&&(t.y=this._viscousLimit(t.y,e.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,n=(n+e+i)%t-e-i,t=Math.abs(o+i)<Math.abs(n+i)?o:n;this._draggable._absPos=this._draggable._newPos.clone(),this._draggable._newPos.x=t},_onDragEnd:function(t){var e,i,n,o,s=this._map,r=s.options,a=!r.inertia||t.noInertia||this._times.length<2;s.fire("dragend",t),!a&&(this._prunePositions(+new Date),t=this._lastPos.subtract(this._positions[0]),a=(this._lastTime-this._times[0])/1e3,e=r.easeLinearity,a=(t=t.multiplyBy(e/a)).distanceTo([0,0]),i=Math.min(r.inertiaMaxSpeed,a),t=t.multiplyBy(i/a),n=i/(r.inertiaDeceleration*e),(o=t.multiplyBy(-n/2).round()).x||o.y)?(o=s._limitOffset(o,s.options.maxBounds),x(function(){s.panBy(o,{duration:n,easeLinearity:e,noMoveStart:!0,animate:!0})})):s.fire("moveend")}})),St=(A.addInitHook("addHandler","dragging",Zt),A.mergeOptions({keyboard:!0,keyboardPanDelta:80}),n.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),S(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),k(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){var t,e,i;this._focused||(i=document.body,t=document.documentElement,e=i.scrollTop||t.scrollTop,i=i.scrollLeft||t.scrollLeft,this._map._container.focus(),window.scrollTo(i,e))},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){for(var e=this._panKeys={},i=this.keyCodes,n=0,o=i.left.length;n<o;n++)e[i.left[n]]=[-1*t,0];for(n=0,o=i.right.length;n<o;n++)e[i.right[n]]=[t,0];for(n=0,o=i.down.length;n<o;n++)e[i.down[n]]=[0,t];for(n=0,o=i.up.length;n<o;n++)e[i.up[n]]=[0,-1*t]},_setZoomDelta:function(t){for(var e=this._zoomKeys={},i=this.keyCodes,n=0,o=i.zoomIn.length;n<o;n++)e[i.zoomIn[n]]=t;for(n=0,o=i.zoomOut.length;n<o;n++)e[i.zoomOut[n]]=-t},_addHooks:function(){S(document,"keydown",this._onKeyDown,this)},_removeHooks:function(){k(document,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){if(!(t.altKey||t.ctrlKey||t.metaKey)){var e,i,n=t.keyCode,o=this._map;if(n in this._panKeys)o._panAnim&&o._panAnim._inProgress||(i=this._panKeys[n],t.shiftKey&&(i=m(i).multiplyBy(3)),o.options.maxBounds&&(i=o._limitOffset(m(i),o.options.maxBounds)),o.options.worldCopyJump?(e=o.wrapLatLng(o.unproject(o.project(o.getCenter()).add(i))),o.panTo(e)):o.panBy(i));else if(n in this._zoomKeys)o.setZoom(o.getZoom()+(t.shiftKey?3:1)*this._zoomKeys[n]);else{if(27!==n||!o._popup||!o._popup.options.closeOnEscapeKey)return;o.closePopup()}Re(t)}}})),Et=(A.addInitHook("addHandler","keyboard",St),A.mergeOptions({scrollWheelZoom:!0,wheelDebounceTime:40,wheelPxPerZoomLevel:60}),n.extend({addHooks:function(){S(this._map._container,"wheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){k(this._map._container,"wheel",this._onWheelScroll,this)},_onWheelScroll:function(t){var e=He(t),i=this._map.options.wheelDebounceTime,e=(this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date),Math.max(i-(+new Date-this._startTime),0));clearTimeout(this._timer),this._timer=setTimeout(a(this._performZoom,this),e),Re(t)},_performZoom:function(){var t=this._map,e=t.getZoom(),i=this._map.options.zoomSnap||0,n=(t._stop(),this._delta/(4*this._map.options.wheelPxPerZoomLevel)),n=4*Math.log(2/(1+Math.exp(-Math.abs(n))))/Math.LN2,i=i?Math.ceil(n/i)*i:n,n=t._limitZoom(e+(0<this._delta?i:-i))-e;this._delta=0,this._startTime=null,n&&("center"===t.options.scrollWheelZoom?t.setZoom(e+n):t.setZoomAround(this._lastMousePos,e+n))}})),kt=(A.addInitHook("addHandler","scrollWheelZoom",Et),A.mergeOptions({tapHold:b.touchNative&&b.safari&&b.mobile,tapTolerance:15}),n.extend({addHooks:function(){S(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){k(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){var e;clearTimeout(this._holdTimeout),1===t.touches.length&&(e=t.touches[0],this._startPos=this._newPos=new p(e.clientX,e.clientY),this._holdTimeout=setTimeout(a(function(){this._cancel(),this._isTapValid()&&(S(document,"touchend",O),S(document,"touchend touchcancel",this._cancelClickPrevent),this._simulateEvent("contextmenu",e))},this),600),S(document,"touchend touchcancel contextmenu",this._cancel,this),S(document,"touchmove",this._onMove,this))},_cancelClickPrevent:function t(){k(document,"touchend",O),k(document,"touchend touchcancel",t)},_cancel:function(){clearTimeout(this._holdTimeout),k(document,"touchend touchcancel contextmenu",this._cancel,this),k(document,"touchmove",this._onMove,this)},_onMove:function(t){t=t.touches[0];this._newPos=new p(t.clientX,t.clientY)},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_simulateEvent:function(t,e){t=new MouseEvent(t,{bubbles:!0,cancelable:!0,view:window,screenX:e.screenX,screenY:e.screenY,clientX:e.clientX,clientY:e.clientY});t._simulated=!0,e.target.dispatchEvent(t)}})),Ot=(A.addInitHook("addHandler","tapHold",kt),A.mergeOptions({touchZoom:b.touch,bounceAtZoomLimits:!0}),n.extend({addHooks:function(){M(this._map._container,"leaflet-touch-zoom"),S(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){z(this._map._container,"leaflet-touch-zoom"),k(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var e,i,n=this._map;!t.touches||2!==t.touches.length||n._animatingZoom||this._zooming||(e=n.mouseEventToContainerPoint(t.touches[0]),i=n.mouseEventToContainerPoint(t.touches[1]),this._centerPoint=n.getSize()._divideBy(2),this._startLatLng=n.containerPointToLatLng(this._centerPoint),"center"!==n.options.touchZoom&&(this._pinchStartLatLng=n.containerPointToLatLng(e.add(i)._divideBy(2))),this._startDist=e.distanceTo(i),this._startZoom=n.getZoom(),this._moved=!1,this._zooming=!0,n._stop(),S(document,"touchmove",this._onTouchMove,this),S(document,"touchend touchcancel",this._onTouchEnd,this),O(t))},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var e=this._map,i=e.mouseEventToContainerPoint(t.touches[0]),n=e.mouseEventToContainerPoint(t.touches[1]),o=i.distanceTo(n)/this._startDist;if(this._zoom=e.getScaleZoom(o,this._startZoom),!e.options.bounceAtZoomLimits&&(this._zoom<e.getMinZoom()&&o<1||this._zoom>e.getMaxZoom()&&1<o)&&(this._zoom=e._limitZoom(this._zoom)),"center"===e.options.touchZoom){if(this._center=this._startLatLng,1==o)return}else{i=i._add(n)._divideBy(2)._subtract(this._centerPoint);if(1==o&&0===i.x&&0===i.y)return;this._center=e.unproject(e.project(this._pinchStartLatLng,this._zoom).subtract(i),this._zoom)}this._moved||(e._moveStart(!0,!1),this._moved=!0),r(this._animRequest);n=a(e._move,e,this._center,this._zoom,{pinch:!0,round:!1},void 0);this._animRequest=x(n,this,!0),O(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,r(this._animRequest),k(document,"touchmove",this._onTouchMove,this),k(document,"touchend touchcancel",this._onTouchEnd,this),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}})),Xi=(A.addInitHook("addHandler","touchZoom",Ot),A.BoxZoom=_t,A.DoubleClickZoom=Ct,A.Drag=Zt,A.Keyboard=St,A.ScrollWheelZoom=Et,A.TapHold=kt,A.TouchZoom=Ot,t.Bounds=f,t.Browser=b,t.CRS=ot,t.Canvas=Fi,t.Circle=vi,t.CircleMarker=gi,t.Class=et,t.Control=B,t.DivIcon=Ri,t.DivOverlay=Ai,t.DomEvent=mt,t.DomUtil=pt,t.Draggable=Xe,t.Evented=it,t.FeatureGroup=ci,t.GeoJSON=wi,t.GridLayer=Ni,t.Handler=n,t.Icon=di,t.ImageOverlay=Ei,t.LatLng=v,t.LatLngBounds=s,t.Layer=o,t.LayerGroup=ui,t.LineUtil=vt,t.Map=A,t.Marker=mi,t.Mixin=ft,t.Path=fi,t.Point=p,t.PolyUtil=gt,t.Polygon=xi,t.Polyline=yi,t.Popup=Bi,t.PosAnimation=Fe,t.Projection=wt,t.Rectangle=Yi,t.Renderer=Wi,t.SVG=Gi,t.SVGOverlay=Oi,t.TileLayer=Di,t.Tooltip=Ii,t.Transformation=at,t.Util=tt,t.VideoOverlay=ki,t.bind=a,t.bounds=_,t.canvas=Ui,t.circle=function(t,e,i){return new vi(t,e,i)},t.circleMarker=function(t,e){return new gi(t,e)},t.control=Ue,t.divIcon=function(t){return new Ri(t)},t.extend=l,t.featureGroup=function(t,e){return new ci(t,e)},t.geoJSON=Si,t.geoJson=Mt,t.gridLayer=function(t){return new Ni(t)},t.icon=function(t){return new di(t)},t.imageOverlay=function(t,e,i){return new Ei(t,e,i)},t.latLng=w,t.latLngBounds=g,t.layerGroup=function(t,e){return new ui(t,e)},t.map=function(t,e){return new A(t,e)},t.marker=function(t,e){return new mi(t,e)},t.point=m,t.polygon=function(t,e){return new xi(t,e)},t.polyline=function(t,e){return new yi(t,e)},t.popup=function(t,e){return new Bi(t,e)},t.rectangle=function(t,e){return new Yi(t,e)},t.setOptions=c,t.stamp=h,t.svg=Ki,t.svgOverlay=function(t,e,i){return new Oi(t,e,i)},t.tileLayer=ji,t.tooltip=function(t,e){return new Ii(t,e)},t.transformation=ht,t.version="1.9.4",t.videoOverlay=function(t,e,i){return new ki(t,e,i)},window.L);t.noConflict=function(){return window.L=Xi,this},window.L=t}); //# sourceMappingURL=leaflet.js.map ================================================ FILE: public/assets/lib/vendor/leaflet/shp.esm.js ================================================ function globals(defs) { defs('EPSG:4326', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees"); defs('EPSG:4269', "+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees"); defs('EPSG:3857', "+title=WGS 84 / Pseudo-Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"); defs.WGS84 = defs['EPSG:4326']; defs['EPSG:3785'] = defs['EPSG:3857']; // maintain backward compat, official code is 3857 defs.GOOGLE = defs['EPSG:3857']; defs['EPSG:900913'] = defs['EPSG:3857']; defs['EPSG:102113'] = defs['EPSG:3857']; } var PJD_3PARAM = 1; var PJD_7PARAM = 2; var PJD_GRIDSHIFT = 3; var PJD_WGS84 = 4; // WGS84 or equivalent var PJD_NODATUM = 5; // WGS84 or equivalent var SRS_WGS84_SEMIMAJOR = 6378137.0; // only used in grid shift transforms var SRS_WGS84_SEMIMINOR = 6356752.314; // only used in grid shift transforms var SRS_WGS84_ESQUARED = 0.0066943799901413165; // only used in grid shift transforms var SEC_TO_RAD = 4.84813681109535993589914102357e-6; var HALF_PI = Math.PI/2; // ellipoid pj_set_ell.c var SIXTH = 0.1666666666666666667; /* 1/6 */ var RA4 = 0.04722222222222222222; /* 17/360 */ var RA6 = 0.02215608465608465608; var EPSLN = 1.0e-10; // you'd think you could use Number.EPSILON above but that makes // Mollweide get into an infinate loop. var D2R$1 = 0.01745329251994329577; var R2D = 57.29577951308232088; var FORTPI = Math.PI/4; var TWO_PI = Math.PI * 2; // SPI is slightly greater than Math.PI, so values that exceed the -180..180 // degree range by a tiny amount don't get wrapped. This prevents points that // have drifted from their original location along the 180th meridian (due to // floating point error) from changing their sign. var SPI = 3.14159265359; var exports$2 = {}; exports$2.greenwich = 0.0; //"0dE", exports$2.lisbon = -9.131906111111; //"9d07'54.862\"W", exports$2.paris = 2.337229166667; //"2d20'14.025\"E", exports$2.bogota = -74.080916666667; //"74d04'51.3\"W", exports$2.madrid = -3.687938888889; //"3d41'16.58\"W", exports$2.rome = 12.452333333333; //"12d27'8.4\"E", exports$2.bern = 7.439583333333; //"7d26'22.5\"E", exports$2.jakarta = 106.807719444444; //"106d48'27.79\"E", exports$2.ferro = -17.666666666667; //"17d40'W", exports$2.brussels = 4.367975; //"4d22'4.71\"E", exports$2.stockholm = 18.058277777778; //"18d3'29.8\"E", exports$2.athens = 23.7163375; //"23d42'58.815\"E", exports$2.oslo = 10.722916666667; //"10d43'22.5\"E" var units = { ft: {to_meter: 0.3048}, 'us-ft': {to_meter: 1200 / 3937} }; var ignoredChar = /[\s_\-\/\(\)]/g; function match(obj, key) { if (obj[key]) { return obj[key]; } var keys = Object.keys(obj); var lkey = key.toLowerCase().replace(ignoredChar, ''); var i = -1; var testkey, processedKey; while (++i < keys.length) { testkey = keys[i]; processedKey = testkey.toLowerCase().replace(ignoredChar, ''); if (processedKey === lkey) { return obj[testkey]; } } } function projStr(defData) { var self = {}; var paramObj = defData.split('+').map(function(v) { return v.trim(); }).filter(function(a) { return a; }).reduce(function(p, a) { var split = a.split('='); split.push(true); p[split[0].toLowerCase()] = split[1]; return p; }, {}); var paramName, paramVal, paramOutname; var params = { proj: 'projName', datum: 'datumCode', rf: function(v) { self.rf = parseFloat(v); }, lat_0: function(v) { self.lat0 = v * D2R$1; }, lat_1: function(v) { self.lat1 = v * D2R$1; }, lat_2: function(v) { self.lat2 = v * D2R$1; }, lat_ts: function(v) { self.lat_ts = v * D2R$1; }, lon_0: function(v) { self.long0 = v * D2R$1; }, lon_1: function(v) { self.long1 = v * D2R$1; }, lon_2: function(v) { self.long2 = v * D2R$1; }, alpha: function(v) { self.alpha = parseFloat(v) * D2R$1; }, gamma: function(v) { self.rectified_grid_angle = parseFloat(v); }, lonc: function(v) { self.longc = v * D2R$1; }, x_0: function(v) { self.x0 = parseFloat(v); }, y_0: function(v) { self.y0 = parseFloat(v); }, k_0: function(v) { self.k0 = parseFloat(v); }, k: function(v) { self.k0 = parseFloat(v); }, a: function(v) { self.a = parseFloat(v); }, b: function(v) { self.b = parseFloat(v); }, r_a: function() { self.R_A = true; }, zone: function(v) { self.zone = parseInt(v, 10); }, south: function() { self.utmSouth = true; }, towgs84: function(v) { self.datum_params = v.split(",").map(function(a) { return parseFloat(a); }); }, to_meter: function(v) { self.to_meter = parseFloat(v); }, units: function(v) { self.units = v; var unit = match(units, v); if (unit) { self.to_meter = unit.to_meter; } }, from_greenwich: function(v) { self.from_greenwich = v * D2R$1; }, pm: function(v) { var pm = match(exports$2, v); self.from_greenwich = (pm ? pm : parseFloat(v)) * D2R$1; }, nadgrids: function(v) { if (v === '@null') { self.datumCode = 'none'; } else { self.nadgrids = v; } }, axis: function(v) { var legalAxis = "ewnsud"; if (v.length === 3 && legalAxis.indexOf(v.substr(0, 1)) !== -1 && legalAxis.indexOf(v.substr(1, 1)) !== -1 && legalAxis.indexOf(v.substr(2, 1)) !== -1) { self.axis = v; } }, approx: function() { self.approx = true; } }; for (paramName in paramObj) { paramVal = paramObj[paramName]; if (paramName in params) { paramOutname = params[paramName]; if (typeof paramOutname === 'function') { paramOutname(paramVal); } else { self[paramOutname] = paramVal; } } else { self[paramName] = paramVal; } } if(typeof self.datumCode === 'string' && self.datumCode !== "WGS84"){ self.datumCode = self.datumCode.toLowerCase(); } return self; } var NEUTRAL = 1; var KEYWORD = 2; var NUMBER = 3; var QUOTED = 4; var AFTERQUOTE = 5; var ENDED = -1; var whitespace = /\s/; var latin = /[A-Za-z]/; var keyword = /[A-Za-z84_]/; var endThings = /[,\]]/; var digets = /[\d\.E\-\+]/; // const ignoredChar = /[\s_\-\/\(\)]/g; function Parser(text) { if (typeof text !== 'string') { throw new Error('not a string'); } this.text = text.trim(); this.level = 0; this.place = 0; this.root = null; this.stack = []; this.currentObject = null; this.state = NEUTRAL; } Parser.prototype.readCharicter = function() { var char = this.text[this.place++]; if (this.state !== QUOTED) { while (whitespace.test(char)) { if (this.place >= this.text.length) { return; } char = this.text[this.place++]; } } switch (this.state) { case NEUTRAL: return this.neutral(char); case KEYWORD: return this.keyword(char) case QUOTED: return this.quoted(char); case AFTERQUOTE: return this.afterquote(char); case NUMBER: return this.number(char); case ENDED: return; } }; Parser.prototype.afterquote = function(char) { if (char === '"') { this.word += '"'; this.state = QUOTED; return; } if (endThings.test(char)) { this.word = this.word.trim(); this.afterItem(char); return; } throw new Error('havn\'t handled "' +char + '" in afterquote yet, index ' + this.place); }; Parser.prototype.afterItem = function(char) { if (char === ',') { if (this.word !== null) { this.currentObject.push(this.word); } this.word = null; this.state = NEUTRAL; return; } if (char === ']') { this.level--; if (this.word !== null) { this.currentObject.push(this.word); this.word = null; } this.state = NEUTRAL; this.currentObject = this.stack.pop(); if (!this.currentObject) { this.state = ENDED; } return; } }; Parser.prototype.number = function(char) { if (digets.test(char)) { this.word += char; return; } if (endThings.test(char)) { this.word = parseFloat(this.word); this.afterItem(char); return; } throw new Error('havn\'t handled "' +char + '" in number yet, index ' + this.place); }; Parser.prototype.quoted = function(char) { if (char === '"') { this.state = AFTERQUOTE; return; } this.word += char; return; }; Parser.prototype.keyword = function(char) { if (keyword.test(char)) { this.word += char; return; } if (char === '[') { var newObjects = []; newObjects.push(this.word); this.level++; if (this.root === null) { this.root = newObjects; } else { this.currentObject.push(newObjects); } this.stack.push(this.currentObject); this.currentObject = newObjects; this.state = NEUTRAL; return; } if (endThings.test(char)) { this.afterItem(char); return; } throw new Error('havn\'t handled "' +char + '" in keyword yet, index ' + this.place); }; Parser.prototype.neutral = function(char) { if (latin.test(char)) { this.word = char; this.state = KEYWORD; return; } if (char === '"') { this.word = ''; this.state = QUOTED; return; } if (digets.test(char)) { this.word = char; this.state = NUMBER; return; } if (endThings.test(char)) { this.afterItem(char); return; } throw new Error('havn\'t handled "' +char + '" in neutral yet, index ' + this.place); }; Parser.prototype.output = function() { while (this.place < this.text.length) { this.readCharicter(); } if (this.state === ENDED) { return this.root; } throw new Error('unable to parse string "' +this.text + '". State is ' + this.state); }; function parseString(txt) { var parser = new Parser(txt); return parser.output(); } function mapit(obj, key, value) { if (Array.isArray(key)) { value.unshift(key); key = null; } var thing = key ? {} : obj; var out = value.reduce(function(newObj, item) { sExpr(item, newObj); return newObj }, thing); if (key) { obj[key] = out; } } function sExpr(v, obj) { if (!Array.isArray(v)) { obj[v] = true; return; } var key = v.shift(); if (key === 'PARAMETER') { key = v.shift(); } if (v.length === 1) { if (Array.isArray(v[0])) { obj[key] = {}; sExpr(v[0], obj[key]); return; } obj[key] = v[0]; return; } if (!v.length) { obj[key] = true; return; } if (key === 'TOWGS84') { obj[key] = v; return; } if (key === 'AXIS') { if (!(key in obj)) { obj[key] = []; } obj[key].push(v); return; } if (!Array.isArray(key)) { obj[key] = {}; } var i; switch (key) { case 'UNIT': case 'PRIMEM': case 'VERT_DATUM': obj[key] = { name: v[0].toLowerCase(), convert: v[1] }; if (v.length === 3) { sExpr(v[2], obj[key]); } return; case 'SPHEROID': case 'ELLIPSOID': obj[key] = { name: v[0], a: v[1], rf: v[2] }; if (v.length === 4) { sExpr(v[3], obj[key]); } return; case 'PROJECTEDCRS': case 'PROJCRS': case 'GEOGCS': case 'GEOCCS': case 'PROJCS': case 'LOCAL_CS': case 'GEODCRS': case 'GEODETICCRS': case 'GEODETICDATUM': case 'EDATUM': case 'ENGINEERINGDATUM': case 'VERT_CS': case 'VERTCRS': case 'VERTICALCRS': case 'COMPD_CS': case 'COMPOUNDCRS': case 'ENGINEERINGCRS': case 'ENGCRS': case 'FITTED_CS': case 'LOCAL_DATUM': case 'DATUM': v[0] = ['name', v[0]]; mapit(obj, key, v); return; default: i = -1; while (++i < v.length) { if (!Array.isArray(v[i])) { return sExpr(v, obj[key]); } } return mapit(obj, key, v); } } var D2R = 0.01745329251994329577; function rename(obj, params) { var outName = params[0]; var inName = params[1]; if (!(outName in obj) && (inName in obj)) { obj[outName] = obj[inName]; if (params.length === 3) { obj[outName] = params[2](obj[outName]); } } } function d2r(input) { return input * D2R; } function cleanWKT(wkt) { if (wkt.type === 'GEOGCS') { wkt.projName = 'longlat'; } else if (wkt.type === 'LOCAL_CS') { wkt.projName = 'identity'; wkt.local = true; } else { if (typeof wkt.PROJECTION === 'object') { wkt.projName = Object.keys(wkt.PROJECTION)[0]; } else { wkt.projName = wkt.PROJECTION; } } if (wkt.AXIS) { var axisOrder = ''; for (var i = 0, ii = wkt.AXIS.length; i < ii; ++i) { var axis = [wkt.AXIS[i][0].toLowerCase(), wkt.AXIS[i][1].toLowerCase()]; if (axis[0].indexOf('north') !== -1 || ((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'north')) { axisOrder += 'n'; } else if (axis[0].indexOf('south') !== -1 || ((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'south')) { axisOrder += 's'; } else if (axis[0].indexOf('east') !== -1 || ((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'east')) { axisOrder += 'e'; } else if (axis[0].indexOf('west') !== -1 || ((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'west')) { axisOrder += 'w'; } } if (axisOrder.length === 2) { axisOrder += 'u'; } if (axisOrder.length === 3) { wkt.axis = axisOrder; } } if (wkt.UNIT) { wkt.units = wkt.UNIT.name.toLowerCase(); if (wkt.units === 'metre') { wkt.units = 'meter'; } if (wkt.UNIT.convert) { if (wkt.type === 'GEOGCS') { if (wkt.DATUM && wkt.DATUM.SPHEROID) { wkt.to_meter = wkt.UNIT.convert*wkt.DATUM.SPHEROID.a; } } else { wkt.to_meter = wkt.UNIT.convert; } } } var geogcs = wkt.GEOGCS; if (wkt.type === 'GEOGCS') { geogcs = wkt; } if (geogcs) { //if(wkt.GEOGCS.PRIMEM&&wkt.GEOGCS.PRIMEM.convert){ // wkt.from_greenwich=wkt.GEOGCS.PRIMEM.convert*D2R; //} if (geogcs.DATUM) { wkt.datumCode = geogcs.DATUM.name.toLowerCase(); } else { wkt.datumCode = geogcs.name.toLowerCase(); } if (wkt.datumCode.slice(0, 2) === 'd_') { wkt.datumCode = wkt.datumCode.slice(2); } if (wkt.datumCode === 'new_zealand_geodetic_datum_1949' || wkt.datumCode === 'new_zealand_1949') { wkt.datumCode = 'nzgd49'; } if (wkt.datumCode === 'wgs_1984' || wkt.datumCode === 'world_geodetic_system_1984') { if (wkt.PROJECTION === 'Mercator_Auxiliary_Sphere') { wkt.sphere = true; } wkt.datumCode = 'wgs84'; } if (wkt.datumCode.slice(-6) === '_ferro') { wkt.datumCode = wkt.datumCode.slice(0, - 6); } if (wkt.datumCode.slice(-8) === '_jakarta') { wkt.datumCode = wkt.datumCode.slice(0, - 8); } if (~wkt.datumCode.indexOf('belge')) { wkt.datumCode = 'rnb72'; } if (geogcs.DATUM && geogcs.DATUM.SPHEROID) { wkt.ellps = geogcs.DATUM.SPHEROID.name.replace('_19', '').replace(/[Cc]larke\_18/, 'clrk'); if (wkt.ellps.toLowerCase().slice(0, 13) === 'international') { wkt.ellps = 'intl'; } wkt.a = geogcs.DATUM.SPHEROID.a; wkt.rf = parseFloat(geogcs.DATUM.SPHEROID.rf, 10); } if (geogcs.DATUM && geogcs.DATUM.TOWGS84) { wkt.datum_params = geogcs.DATUM.TOWGS84; } if (~wkt.datumCode.indexOf('osgb_1936')) { wkt.datumCode = 'osgb36'; } if (~wkt.datumCode.indexOf('osni_1952')) { wkt.datumCode = 'osni52'; } if (~wkt.datumCode.indexOf('tm65') || ~wkt.datumCode.indexOf('geodetic_datum_of_1965')) { wkt.datumCode = 'ire65'; } if (wkt.datumCode === 'ch1903+') { wkt.datumCode = 'ch1903'; } if (~wkt.datumCode.indexOf('israel')) { wkt.datumCode = 'isr93'; } } if (wkt.b && !isFinite(wkt.b)) { wkt.b = wkt.a; } function toMeter(input) { var ratio = wkt.to_meter || 1; return input * ratio; } var renamer = function(a) { return rename(wkt, a); }; var list = [ ['standard_parallel_1', 'Standard_Parallel_1'], ['standard_parallel_1', 'Latitude of 1st standard parallel'], ['standard_parallel_2', 'Standard_Parallel_2'], ['standard_parallel_2', 'Latitude of 2nd standard parallel'], ['false_easting', 'False_Easting'], ['false_easting', 'False easting'], ['false-easting', 'Easting at false origin'], ['false_northing', 'False_Northing'], ['false_northing', 'False northing'], ['false_northing', 'Northing at false origin'], ['central_meridian', 'Central_Meridian'], ['central_meridian', 'Longitude of natural origin'], ['central_meridian', 'Longitude of false origin'], ['latitude_of_origin', 'Latitude_Of_Origin'], ['latitude_of_origin', 'Central_Parallel'], ['latitude_of_origin', 'Latitude of natural origin'], ['latitude_of_origin', 'Latitude of false origin'], ['scale_factor', 'Scale_Factor'], ['k0', 'scale_factor'], ['latitude_of_center', 'Latitude_Of_Center'], ['latitude_of_center', 'Latitude_of_center'], ['lat0', 'latitude_of_center', d2r], ['longitude_of_center', 'Longitude_Of_Center'], ['longitude_of_center', 'Longitude_of_center'], ['longc', 'longitude_of_center', d2r], ['x0', 'false_easting', toMeter], ['y0', 'false_northing', toMeter], ['long0', 'central_meridian', d2r], ['lat0', 'latitude_of_origin', d2r], ['lat0', 'standard_parallel_1', d2r], ['lat1', 'standard_parallel_1', d2r], ['lat2', 'standard_parallel_2', d2r], ['azimuth', 'Azimuth'], ['alpha', 'azimuth', d2r], ['srsCode', 'name'] ]; list.forEach(renamer); if (!wkt.long0 && wkt.longc && (wkt.projName === 'Albers_Conic_Equal_Area' || wkt.projName === 'Lambert_Azimuthal_Equal_Area')) { wkt.long0 = wkt.longc; } if (!wkt.lat_ts && wkt.lat1 && (wkt.projName === 'Stereographic_South_Pole' || wkt.projName === 'Polar Stereographic (variant B)')) { wkt.lat0 = d2r(wkt.lat1 > 0 ? 90 : -90); wkt.lat_ts = wkt.lat1; } else if (!wkt.lat_ts && wkt.lat0 && wkt.projName === 'Polar_Stereographic') { wkt.lat_ts = wkt.lat0; wkt.lat0 = d2r(wkt.lat0 > 0 ? 90 : -90); } } function wkt(wkt) { var lisp = parseString(wkt); var type = lisp.shift(); var name = lisp.shift(); lisp.unshift(['name', name]); lisp.unshift(['type', type]); var obj = {}; sExpr(lisp, obj); cleanWKT(obj); return obj; } function defs(name) { /*global console*/ var that = this; if (arguments.length === 2) { var def = arguments[1]; if (typeof def === 'string') { if (def.charAt(0) === '+') { defs[name] = projStr(arguments[1]); } else { defs[name] = wkt(arguments[1]); } } else { defs[name] = def; } } else if (arguments.length === 1) { if (Array.isArray(name)) { return name.map(function(v) { if (Array.isArray(v)) { defs.apply(that, v); } else { defs(v); } }); } else if (typeof name === 'string') { if (name in defs) { return defs[name]; } } else if ('EPSG' in name) { defs['EPSG:' + name.EPSG] = name; } else if ('ESRI' in name) { defs['ESRI:' + name.ESRI] = name; } else if ('IAU2000' in name) { defs['IAU2000:' + name.IAU2000] = name; } else { console.log(name); } return; } } globals(defs); function testObj(code){ return typeof code === 'string'; } function testDef(code){ return code in defs; } var codeWords = ['PROJECTEDCRS', 'PROJCRS', 'GEOGCS','GEOCCS','PROJCS','LOCAL_CS', 'GEODCRS', 'GEODETICCRS', 'GEODETICDATUM', 'ENGCRS', 'ENGINEERINGCRS']; function testWKT(code){ return codeWords.some(function (word) { return code.indexOf(word) > -1; }); } var codes = ['3857', '900913', '3785', '102113']; function checkMercator(item) { var auth = match(item, 'authority'); if (!auth) { return; } var code = match(auth, 'epsg'); return code && codes.indexOf(code) > -1; } function checkProjStr(item) { var ext = match(item, 'extension'); if (!ext) { return; } return match(ext, 'proj4'); } function testProj(code){ return code[0] === '+'; } function parse(code){ if (testObj(code)) { //check to see if this is a WKT string if (testDef(code)) { return defs[code]; } if (testWKT(code)) { var out = wkt(code); // test of spetial case, due to this being a very common and often malformed if (checkMercator(out)) { return defs['EPSG:3857']; } var maybeProjStr = checkProjStr(out); if (maybeProjStr) { return projStr(maybeProjStr); } return out; } if (testProj(code)) { return projStr(code); } }else { return code; } } function extend(destination, source) { destination = destination || {}; var value, property; if (!source) { return destination; } for (property in source) { value = source[property]; if (value !== undefined) { destination[property] = value; } } return destination; } function msfnz(eccent, sinphi, cosphi) { var con = eccent * sinphi; return cosphi / (Math.sqrt(1 - con * con)); } function sign(x) { return x<0 ? -1 : 1; } function adjust_lon(x) { return (Math.abs(x) <= SPI) ? x : (x - (sign(x) * TWO_PI)); } function tsfnz(eccent, phi, sinphi) { var con = eccent * sinphi; var com = 0.5 * eccent; con = Math.pow(((1 - con) / (1 + con)), com); return (Math.tan(0.5 * (HALF_PI - phi)) / con); } function phi2z(eccent, ts) { var eccnth = 0.5 * eccent; var con, dphi; var phi = HALF_PI - 2 * Math.atan(ts); for (var i = 0; i <= 15; i++) { con = eccent * Math.sin(phi); dphi = HALF_PI - 2 * Math.atan(ts * (Math.pow(((1 - con) / (1 + con)), eccnth))) - phi; phi += dphi; if (Math.abs(dphi) <= 0.0000000001) { return phi; } } //console.log("phi2z has NoConvergence"); return -9999; } function init$v() { var con = this.b / this.a; this.es = 1 - con * con; if(!('x0' in this)){ this.x0 = 0; } if(!('y0' in this)){ this.y0 = 0; } this.e = Math.sqrt(this.es); if (this.lat_ts) { if (this.sphere) { this.k0 = Math.cos(this.lat_ts); } else { this.k0 = msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts)); } } else { if (!this.k0) { if (this.k) { this.k0 = this.k; } else { this.k0 = 1; } } } } /* Mercator forward equations--mapping lat,long to x,y --------------------------------------------------*/ function forward$u(p) { var lon = p.x; var lat = p.y; // convert to radians if (lat * R2D > 90 && lat * R2D < -90 && lon * R2D > 180 && lon * R2D < -180) { return null; } var x, y; if (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN) { return null; } else { if (this.sphere) { x = this.x0 + this.a * this.k0 * adjust_lon(lon - this.long0); y = this.y0 + this.a * this.k0 * Math.log(Math.tan(FORTPI + 0.5 * lat)); } else { var sinphi = Math.sin(lat); var ts = tsfnz(this.e, lat, sinphi); x = this.x0 + this.a * this.k0 * adjust_lon(lon - this.long0); y = this.y0 - this.a * this.k0 * Math.log(ts); } p.x = x; p.y = y; return p; } } /* Mercator inverse equations--mapping x,y to lat/long --------------------------------------------------*/ function inverse$u(p) { var x = p.x - this.x0; var y = p.y - this.y0; var lon, lat; if (this.sphere) { lat = HALF_PI - 2 * Math.atan(Math.exp(-y / (this.a * this.k0))); } else { var ts = Math.exp(-y / (this.a * this.k0)); lat = phi2z(this.e, ts); if (lat === -9999) { return null; } } lon = adjust_lon(this.long0 + x / (this.a * this.k0)); p.x = lon; p.y = lat; return p; } var names$w = ["Mercator", "Popular Visualisation Pseudo Mercator", "Mercator_1SP", "Mercator_Auxiliary_Sphere", "merc"]; var merc = { init: init$v, forward: forward$u, inverse: inverse$u, names: names$w }; function init$u() { //no-op for longlat } function identity(pt) { return pt; } var names$v = ["longlat", "identity"]; var longlat = { init: init$u, forward: identity, inverse: identity, names: names$v }; var projs = [merc, longlat]; var names$u = {}; var projStore = []; function add(proj, i) { var len = projStore.length; if (!proj.names) { console.log(i); return true; } projStore[len] = proj; proj.names.forEach(function(n) { names$u[n.toLowerCase()] = len; }); return this; } function get(name) { if (!name) { return false; } var n = name.toLowerCase(); if (typeof names$u[n] !== 'undefined' && projStore[names$u[n]]) { return projStore[names$u[n]]; } } function start() { projs.forEach(add); } var projections = { start: start, add: add, get: get }; var exports$1 = {}; exports$1.MERIT = { a: 6378137.0, rf: 298.257, ellipseName: "MERIT 1983" }; exports$1.SGS85 = { a: 6378136.0, rf: 298.257, ellipseName: "Soviet Geodetic System 85" }; exports$1.GRS80 = { a: 6378137.0, rf: 298.257222101, ellipseName: "GRS 1980(IUGG, 1980)" }; exports$1.IAU76 = { a: 6378140.0, rf: 298.257, ellipseName: "IAU 1976" }; exports$1.airy = { a: 6377563.396, b: 6356256.910, ellipseName: "Airy 1830" }; exports$1.APL4 = { a: 6378137, rf: 298.25, ellipseName: "Appl. Physics. 1965" }; exports$1.NWL9D = { a: 6378145.0, rf: 298.25, ellipseName: "Naval Weapons Lab., 1965" }; exports$1.mod_airy = { a: 6377340.189, b: 6356034.446, ellipseName: "Modified Airy" }; exports$1.andrae = { a: 6377104.43, rf: 300.0, ellipseName: "Andrae 1876 (Den., Iclnd.)" }; exports$1.aust_SA = { a: 6378160.0, rf: 298.25, ellipseName: "Australian Natl & S. Amer. 1969" }; exports$1.GRS67 = { a: 6378160.0, rf: 298.2471674270, ellipseName: "GRS 67(IUGG 1967)" }; exports$1.bessel = { a: 6377397.155, rf: 299.1528128, ellipseName: "Bessel 1841" }; exports$1.bess_nam = { a: 6377483.865, rf: 299.1528128, ellipseName: "Bessel 1841 (Namibia)" }; exports$1.clrk66 = { a: 6378206.4, b: 6356583.8, ellipseName: "Clarke 1866" }; exports$1.clrk80 = { a: 6378249.145, rf: 293.4663, ellipseName: "Clarke 1880 mod." }; exports$1.clrk80ign = { a: 6378249.2, b: 6356515, rf: 293.4660213, ellipseName: "Clarke 1880 (IGN)" }; exports$1.clrk58 = { a: 6378293.645208759, rf: 294.2606763692654, ellipseName: "Clarke 1858" }; exports$1.CPM = { a: 6375738.7, rf: 334.29, ellipseName: "Comm. des Poids et Mesures 1799" }; exports$1.delmbr = { a: 6376428.0, rf: 311.5, ellipseName: "Delambre 1810 (Belgium)" }; exports$1.engelis = { a: 6378136.05, rf: 298.2566, ellipseName: "Engelis 1985" }; exports$1.evrst30 = { a: 6377276.345, rf: 300.8017, ellipseName: "Everest 1830" }; exports$1.evrst48 = { a: 6377304.063, rf: 300.8017, ellipseName: "Everest 1948" }; exports$1.evrst56 = { a: 6377301.243, rf: 300.8017, ellipseName: "Everest 1956" }; exports$1.evrst69 = { a: 6377295.664, rf: 300.8017, ellipseName: "Everest 1969" }; exports$1.evrstSS = { a: 6377298.556, rf: 300.8017, ellipseName: "Everest (Sabah & Sarawak)" }; exports$1.fschr60 = { a: 6378166.0, rf: 298.3, ellipseName: "Fischer (Mercury Datum) 1960" }; exports$1.fschr60m = { a: 6378155.0, rf: 298.3, ellipseName: "Fischer 1960" }; exports$1.fschr68 = { a: 6378150.0, rf: 298.3, ellipseName: "Fischer 1968" }; exports$1.helmert = { a: 6378200.0, rf: 298.3, ellipseName: "Helmert 1906" }; exports$1.hough = { a: 6378270.0, rf: 297.0, ellipseName: "Hough" }; exports$1.intl = { a: 6378388.0, rf: 297.0, ellipseName: "International 1909 (Hayford)" }; exports$1.kaula = { a: 6378163.0, rf: 298.24, ellipseName: "Kaula 1961" }; exports$1.lerch = { a: 6378139.0, rf: 298.257, ellipseName: "Lerch 1979" }; exports$1.mprts = { a: 6397300.0, rf: 191.0, ellipseName: "Maupertius 1738" }; exports$1.new_intl = { a: 6378157.5, b: 6356772.2, ellipseName: "New International 1967" }; exports$1.plessis = { a: 6376523.0, rf: 6355863.0, ellipseName: "Plessis 1817 (France)" }; exports$1.krass = { a: 6378245.0, rf: 298.3, ellipseName: "Krassovsky, 1942" }; exports$1.SEasia = { a: 6378155.0, b: 6356773.3205, ellipseName: "Southeast Asia" }; exports$1.walbeck = { a: 6376896.0, b: 6355834.8467, ellipseName: "Walbeck" }; exports$1.WGS60 = { a: 6378165.0, rf: 298.3, ellipseName: "WGS 60" }; exports$1.WGS66 = { a: 6378145.0, rf: 298.25, ellipseName: "WGS 66" }; exports$1.WGS7 = { a: 6378135.0, rf: 298.26, ellipseName: "WGS 72" }; var WGS84 = exports$1.WGS84 = { a: 6378137.0, rf: 298.257223563, ellipseName: "WGS 84" }; exports$1.sphere = { a: 6370997.0, b: 6370997.0, ellipseName: "Normal Sphere (r=6370997)" }; function eccentricity(a, b, rf, R_A) { var a2 = a * a; // used in geocentric var b2 = b * b; // used in geocentric var es = (a2 - b2) / a2; // e ^ 2 var e = 0; if (R_A) { a *= 1 - es * (SIXTH + es * (RA4 + es * RA6)); a2 = a * a; es = 0; } else { e = Math.sqrt(es); // eccentricity } var ep2 = (a2 - b2) / b2; // used in geocentric return { es: es, e: e, ep2: ep2 }; } function sphere(a, b, rf, ellps, sphere) { if (!a) { // do we have an ellipsoid? var ellipse = match(exports$1, ellps); if (!ellipse) { ellipse = WGS84; } a = ellipse.a; b = ellipse.b; rf = ellipse.rf; } if (rf && !b) { b = (1.0 - 1.0 / rf) * a; } if (rf === 0 || Math.abs(a - b) < EPSLN) { sphere = true; b = a; } return { a: a, b: b, rf: rf, sphere: sphere }; } var exports = {}; exports.wgs84 = { towgs84: "0,0,0", ellipse: "WGS84", datumName: "WGS84" }; exports.ch1903 = { towgs84: "674.374,15.056,405.346", ellipse: "bessel", datumName: "swiss" }; exports.ggrs87 = { towgs84: "-199.87,74.79,246.62", ellipse: "GRS80", datumName: "Greek_Geodetic_Reference_System_1987" }; exports.nad83 = { towgs84: "0,0,0", ellipse: "GRS80", datumName: "North_American_Datum_1983" }; exports.nad27 = { nadgrids: "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", ellipse: "clrk66", datumName: "North_American_Datum_1927" }; exports.potsdam = { towgs84: "598.1,73.7,418.2,0.202,0.045,-2.455,6.7", ellipse: "bessel", datumName: "Potsdam Rauenberg 1950 DHDN" }; exports.carthage = { towgs84: "-263.0,6.0,431.0", ellipse: "clark80", datumName: "Carthage 1934 Tunisia" }; exports.hermannskogel = { towgs84: "577.326,90.129,463.919,5.137,1.474,5.297,2.4232", ellipse: "bessel", datumName: "Hermannskogel" }; exports.militargeographische_institut = { towgs84: "577.326,90.129,463.919,5.137,1.474,5.297,2.4232", ellipse: "bessel", datumName: "Militar-Geographische Institut" }; exports.osni52 = { towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "airy", datumName: "Irish National" }; exports.ire65 = { towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "mod_airy", datumName: "Ireland 1965" }; exports.rassadiran = { towgs84: "-133.63,-157.5,-158.62", ellipse: "intl", datumName: "Rassadiran" }; exports.nzgd49 = { towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", ellipse: "intl", datumName: "New Zealand Geodetic Datum 1949" }; exports.osgb36 = { towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", ellipse: "airy", datumName: "Airy 1830" }; exports.s_jtsk = { towgs84: "589,76,480", ellipse: 'bessel', datumName: 'S-JTSK (Ferro)' }; exports.beduaram = { towgs84: '-106,-87,188', ellipse: 'clrk80', datumName: 'Beduaram' }; exports.gunung_segara = { towgs84: '-403,684,41', ellipse: 'bessel', datumName: 'Gunung Segara Jakarta' }; exports.rnb72 = { towgs84: "106.869,-52.2978,103.724,-0.33657,0.456955,-1.84218,1", ellipse: "intl", datumName: "Reseau National Belge 1972" }; function datum(datumCode, datum_params, a, b, es, ep2, nadgrids) { var out = {}; if (datumCode === undefined || datumCode === 'none') { out.datum_type = PJD_NODATUM; } else { out.datum_type = PJD_WGS84; } if (datum_params) { out.datum_params = datum_params.map(parseFloat); if (out.datum_params[0] !== 0 || out.datum_params[1] !== 0 || out.datum_params[2] !== 0) { out.datum_type = PJD_3PARAM; } if (out.datum_params.length > 3) { if (out.datum_params[3] !== 0 || out.datum_params[4] !== 0 || out.datum_params[5] !== 0 || out.datum_params[6] !== 0) { out.datum_type = PJD_7PARAM; out.datum_params[3] *= SEC_TO_RAD; out.datum_params[4] *= SEC_TO_RAD; out.datum_params[5] *= SEC_TO_RAD; out.datum_params[6] = (out.datum_params[6] / 1000000.0) + 1.0; } } } if (nadgrids) { out.datum_type = PJD_GRIDSHIFT; out.grids = nadgrids; } out.a = a; //datum object also uses these values out.b = b; out.es = es; out.ep2 = ep2; return out; } /** * Resources for details of NTv2 file formats: * - https://web.archive.org/web/20140127204822if_/http://www.mgs.gov.on.ca:80/stdprodconsume/groups/content/@mgs/@iandit/documents/resourcelist/stel02_047447.pdf * - http://mimaka.com/help/gs/html/004_NTV2%20Data%20Format.htm */ var loadedNadgrids = {}; /** * Load a binary NTv2 file (.gsb) to a key that can be used in a proj string like +nadgrids=<key>. Pass the NTv2 file * as an ArrayBuffer. */ function nadgrid(key, data) { var view = new DataView(data); var isLittleEndian = detectLittleEndian(view); var header = readHeader(view, isLittleEndian); var subgrids = readSubgrids(view, header, isLittleEndian); var nadgrid = {header: header, subgrids: subgrids}; loadedNadgrids[key] = nadgrid; return nadgrid; } /** * Given a proj4 value for nadgrids, return an array of loaded grids */ function getNadgrids(nadgrids) { // Format details: http://proj.maptools.org/gen_parms.html if (nadgrids === undefined) { return null; } var grids = nadgrids.split(','); return grids.map(parseNadgridString); } function parseNadgridString(value) { if (value.length === 0) { return null; } var optional = value[0] === '@'; if (optional) { value = value.slice(1); } if (value === 'null') { return {name: 'null', mandatory: !optional, grid: null, isNull: true}; } return { name: value, mandatory: !optional, grid: loadedNadgrids[value] || null, isNull: false }; } function secondsToRadians(seconds) { return (seconds / 3600) * Math.PI / 180; } function detectLittleEndian(view) { var nFields = view.getInt32(8, false); if (nFields === 11) { return false; } nFields = view.getInt32(8, true); if (nFields !== 11) { console.warn('Failed to detect nadgrid endian-ness, defaulting to little-endian'); } return true; } function readHeader(view, isLittleEndian) { return { nFields: view.getInt32(8, isLittleEndian), nSubgridFields: view.getInt32(24, isLittleEndian), nSubgrids: view.getInt32(40, isLittleEndian), shiftType: decodeString(view, 56, 56 + 8).trim(), fromSemiMajorAxis: view.getFloat64(120, isLittleEndian), fromSemiMinorAxis: view.getFloat64(136, isLittleEndian), toSemiMajorAxis: view.getFloat64(152, isLittleEndian), toSemiMinorAxis: view.getFloat64(168, isLittleEndian), }; } function decodeString(view, start, end) { return String.fromCharCode.apply(null, new Uint8Array(view.buffer.slice(start, end))); } function readSubgrids(view, header, isLittleEndian) { var gridOffset = 176; var grids = []; for (var i = 0; i < header.nSubgrids; i++) { var subHeader = readGridHeader(view, gridOffset, isLittleEndian); var nodes = readGridNodes(view, gridOffset, subHeader, isLittleEndian); var lngColumnCount = Math.round( 1 + (subHeader.upperLongitude - subHeader.lowerLongitude) / subHeader.longitudeInterval); var latColumnCount = Math.round( 1 + (subHeader.upperLatitude - subHeader.lowerLatitude) / subHeader.latitudeInterval); // Proj4 operates on radians whereas the coordinates are in seconds in the grid grids.push({ ll: [secondsToRadians(subHeader.lowerLongitude), secondsToRadians(subHeader.lowerLatitude)], del: [secondsToRadians(subHeader.longitudeInterval), secondsToRadians(subHeader.latitudeInterval)], lim: [lngColumnCount, latColumnCount], count: subHeader.gridNodeCount, cvs: mapNodes(nodes) }); gridOffset += 176 + subHeader.gridNodeCount * 16; } return grids; } function mapNodes(nodes) { return nodes.map(function (r) {return [secondsToRadians(r.longitudeShift), secondsToRadians(r.latitudeShift)];}); } function readGridHeader(view, offset, isLittleEndian) { return { name: decodeString(view, offset + 8, offset + 16).trim(), parent: decodeString(view, offset + 24, offset + 24 + 8).trim(), lowerLatitude: view.getFloat64(offset + 72, isLittleEndian), upperLatitude: view.getFloat64(offset + 88, isLittleEndian), lowerLongitude: view.getFloat64(offset + 104, isLittleEndian), upperLongitude: view.getFloat64(offset + 120, isLittleEndian), latitudeInterval: view.getFloat64(offset + 136, isLittleEndian), longitudeInterval: view.getFloat64(offset + 152, isLittleEndian), gridNodeCount: view.getInt32(offset + 168, isLittleEndian) }; } function readGridNodes(view, offset, gridHeader, isLittleEndian) { var nodesOffset = offset + 176; var gridRecordLength = 16; var gridShiftRecords = []; for (var i = 0; i < gridHeader.gridNodeCount; i++) { var record = { latitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength, isLittleEndian), longitudeShift: view.getFloat32(nodesOffset + i * gridRecordLength + 4, isLittleEndian), latitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 8, isLittleEndian), longitudeAccuracy: view.getFloat32(nodesOffset + i * gridRecordLength + 12, isLittleEndian), }; gridShiftRecords.push(record); } return gridShiftRecords; } function Projection(srsCode,callback) { if (!(this instanceof Projection)) { return new Projection(srsCode); } callback = callback || function(error){ if(error){ throw error; } }; var json = parse(srsCode); if(typeof json !== 'object'){ callback(srsCode); return; } var ourProj = Projection.projections.get(json.projName); if(!ourProj){ callback(srsCode); return; } if (json.datumCode && json.datumCode !== 'none') { var datumDef = match(exports, json.datumCode); if (datumDef) { json.datum_params = json.datum_params || (datumDef.towgs84 ? datumDef.towgs84.split(',') : null); json.ellps = datumDef.ellipse; json.datumName = datumDef.datumName ? datumDef.datumName : json.datumCode; } } json.k0 = json.k0 || 1.0; json.axis = json.axis || 'enu'; json.ellps = json.ellps || 'wgs84'; json.lat1 = json.lat1 || json.lat0; // Lambert_Conformal_Conic_1SP, for example, needs this var sphere_ = sphere(json.a, json.b, json.rf, json.ellps, json.sphere); var ecc = eccentricity(sphere_.a, sphere_.b, sphere_.rf, json.R_A); var nadgrids = getNadgrids(json.nadgrids); var datumObj = json.datum || datum(json.datumCode, json.datum_params, sphere_.a, sphere_.b, ecc.es, ecc.ep2, nadgrids); extend(this, json); // transfer everything over from the projection because we don't know what we'll need extend(this, ourProj); // transfer all the methods from the projection // copy the 4 things over we calculated in deriveConstants.sphere this.a = sphere_.a; this.b = sphere_.b; this.rf = sphere_.rf; this.sphere = sphere_.sphere; // copy the 3 things we calculated in deriveConstants.eccentricity this.es = ecc.es; this.e = ecc.e; this.ep2 = ecc.ep2; // add in the datum object this.datum = datumObj; // init the projection this.init(); // legecy callback from back in the day when it went to spatialreference.org callback(null, this); } Projection.projections = projections; Projection.projections.start(); function compareDatums(source, dest) { if (source.datum_type !== dest.datum_type) { return false; // false, datums are not equal } else if (source.a !== dest.a || Math.abs(source.es - dest.es) > 0.000000000050) { // the tolerance for es is to ensure that GRS80 and WGS84 // are considered identical return false; } else if (source.datum_type === PJD_3PARAM) { return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2]); } else if (source.datum_type === PJD_7PARAM) { return (source.datum_params[0] === dest.datum_params[0] && source.datum_params[1] === dest.datum_params[1] && source.datum_params[2] === dest.datum_params[2] && source.datum_params[3] === dest.datum_params[3] && source.datum_params[4] === dest.datum_params[4] && source.datum_params[5] === dest.datum_params[5] && source.datum_params[6] === dest.datum_params[6]); } else { return true; // datums are equal } } // cs_compare_datums() /* * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z), * according to the current ellipsoid parameters. * * Latitude : Geodetic latitude in radians (input) * Longitude : Geodetic longitude in radians (input) * Height : Geodetic height, in meters (input) * X : Calculated Geocentric X coordinate, in meters (output) * Y : Calculated Geocentric Y coordinate, in meters (output) * Z : Calculated Geocentric Z coordinate, in meters (output) * */ function geodeticToGeocentric(p, es, a) { var Longitude = p.x; var Latitude = p.y; var Height = p.z ? p.z : 0; //Z value not always supplied var Rn; /* Earth radius at location */ var Sin_Lat; /* Math.sin(Latitude) */ var Sin2_Lat; /* Square of Math.sin(Latitude) */ var Cos_Lat; /* Math.cos(Latitude) */ /* ** Don't blow up if Latitude is just a little out of the value ** range as it may just be a rounding issue. Also removed longitude ** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001. */ if (Latitude < -HALF_PI && Latitude > -1.001 * HALF_PI) { Latitude = -HALF_PI; } else if (Latitude > HALF_PI && Latitude < 1.001 * HALF_PI) { Latitude = HALF_PI; } else if (Latitude < -HALF_PI) { /* Latitude out of range */ //..reportError('geocent:lat out of range:' + Latitude); return { x: -Infinity, y: -Infinity, z: p.z }; } else if (Latitude > HALF_PI) { /* Latitude out of range */ return { x: Infinity, y: Infinity, z: p.z }; } if (Longitude > Math.PI) { Longitude -= (2 * Math.PI); } Sin_Lat = Math.sin(Latitude); Cos_Lat = Math.cos(Latitude); Sin2_Lat = Sin_Lat * Sin_Lat; Rn = a / (Math.sqrt(1.0e0 - es * Sin2_Lat)); return { x: (Rn + Height) * Cos_Lat * Math.cos(Longitude), y: (Rn + Height) * Cos_Lat * Math.sin(Longitude), z: ((Rn * (1 - es)) + Height) * Sin_Lat }; } // cs_geodetic_to_geocentric() function geocentricToGeodetic(p, es, a, b) { /* local defintions and variables */ /* end-criterium of loop, accuracy of sin(Latitude) */ var genau = 1e-12; var genau2 = (genau * genau); var maxiter = 30; var P; /* distance between semi-minor axis and location */ var RR; /* distance between center and location */ var CT; /* sin of geocentric latitude */ var ST; /* cos of geocentric latitude */ var RX; var RK; var RN; /* Earth radius at location */ var CPHI0; /* cos of start or old geodetic latitude in iterations */ var SPHI0; /* sin of start or old geodetic latitude in iterations */ var CPHI; /* cos of searched geodetic latitude */ var SPHI; /* sin of searched geodetic latitude */ var SDPHI; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ var iter; /* # of continous iteration, max. 30 is always enough (s.a.) */ var X = p.x; var Y = p.y; var Z = p.z ? p.z : 0.0; //Z value not always supplied var Longitude; var Latitude; var Height; P = Math.sqrt(X * X + Y * Y); RR = Math.sqrt(X * X + Y * Y + Z * Z); /* special cases for latitude and longitude */ if (P / a < genau) { /* special case, if P=0. (X=0., Y=0.) */ Longitude = 0.0; /* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis * of ellipsoid (=center of mass), Latitude becomes PI/2 */ if (RR / a < genau) { Latitude = HALF_PI; Height = -b; return { x: p.x, y: p.y, z: p.z }; } } else { /* ellipsoidal (geodetic) longitude * interval: -PI < Longitude <= +PI */ Longitude = Math.atan2(Y, X); } /* -------------------------------------------------------------- * Following iterative algorithm was developped by * "Institut for Erdmessung", University of Hannover, July 1988. * Internet: www.ife.uni-hannover.de * Iterative computation of CPHI,SPHI and Height. * Iteration of CPHI and SPHI to 10**-12 radian resp. * 2*10**-7 arcsec. * -------------------------------------------------------------- */ CT = Z / RR; ST = P / RR; RX = 1.0 / Math.sqrt(1.0 - es * (2.0 - es) * ST * ST); CPHI0 = ST * (1.0 - es) * RX; SPHI0 = CT * RX; iter = 0; /* loop to find sin(Latitude) resp. Latitude * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ do { iter++; RN = a / Math.sqrt(1.0 - es * SPHI0 * SPHI0); /* ellipsoidal (geodetic) height */ Height = P * CPHI0 + Z * SPHI0 - RN * (1.0 - es * SPHI0 * SPHI0); RK = es * RN / (RN + Height); RX = 1.0 / Math.sqrt(1.0 - RK * (2.0 - RK) * ST * ST); CPHI = ST * (1.0 - RK) * RX; SPHI = CT * RX; SDPHI = SPHI * CPHI0 - CPHI * SPHI0; CPHI0 = CPHI; SPHI0 = SPHI; } while (SDPHI * SDPHI > genau2 && iter < maxiter); /* ellipsoidal (geodetic) latitude */ Latitude = Math.atan(SPHI / Math.abs(CPHI)); return { x: Longitude, y: Latitude, z: Height }; } // cs_geocentric_to_geodetic() /****************************************************************/ // pj_geocentic_to_wgs84( p ) // p = point to transform in geocentric coordinates (x,y,z) /** point object, nothing fancy, just allows values to be passed back and forth by reference rather than by value. Other point classes may be used as long as they have x and y properties, which will get modified in the transform method. */ function geocentricToWgs84(p, datum_type, datum_params) { if (datum_type === PJD_3PARAM) { // if( x[io] === HUGE_VAL ) // continue; return { x: p.x + datum_params[0], y: p.y + datum_params[1], z: p.z + datum_params[2], }; } else if (datum_type === PJD_7PARAM) { var Dx_BF = datum_params[0]; var Dy_BF = datum_params[1]; var Dz_BF = datum_params[2]; var Rx_BF = datum_params[3]; var Ry_BF = datum_params[4]; var Rz_BF = datum_params[5]; var M_BF = datum_params[6]; // if( x[io] === HUGE_VAL ) // continue; return { x: M_BF * (p.x - Rz_BF * p.y + Ry_BF * p.z) + Dx_BF, y: M_BF * (Rz_BF * p.x + p.y - Rx_BF * p.z) + Dy_BF, z: M_BF * (-Ry_BF * p.x + Rx_BF * p.y + p.z) + Dz_BF }; } } // cs_geocentric_to_wgs84 /****************************************************************/ // pj_geocentic_from_wgs84() // coordinate system definition, // point to transform in geocentric coordinates (x,y,z) function geocentricFromWgs84(p, datum_type, datum_params) { if (datum_type === PJD_3PARAM) { //if( x[io] === HUGE_VAL ) // continue; return { x: p.x - datum_params[0], y: p.y - datum_params[1], z: p.z - datum_params[2], }; } else if (datum_type === PJD_7PARAM) { var Dx_BF = datum_params[0]; var Dy_BF = datum_params[1]; var Dz_BF = datum_params[2]; var Rx_BF = datum_params[3]; var Ry_BF = datum_params[4]; var Rz_BF = datum_params[5]; var M_BF = datum_params[6]; var x_tmp = (p.x - Dx_BF) / M_BF; var y_tmp = (p.y - Dy_BF) / M_BF; var z_tmp = (p.z - Dz_BF) / M_BF; //if( x[io] === HUGE_VAL ) // continue; return { x: x_tmp + Rz_BF * y_tmp - Ry_BF * z_tmp, y: -Rz_BF * x_tmp + y_tmp + Rx_BF * z_tmp, z: Ry_BF * x_tmp - Rx_BF * y_tmp + z_tmp }; } //cs_geocentric_from_wgs84() } function checkParams(type) { return (type === PJD_3PARAM || type === PJD_7PARAM); } function datum_transform(source, dest, point) { // Short cut if the datums are identical. if (compareDatums(source, dest)) { return point; // in this case, zero is sucess, // whereas cs_compare_datums returns 1 to indicate TRUE // confusing, should fix this } // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest if (source.datum_type === PJD_NODATUM || dest.datum_type === PJD_NODATUM) { return point; } // If this datum requires grid shifts, then apply it to geodetic coordinates. var source_a = source.a; var source_es = source.es; if (source.datum_type === PJD_GRIDSHIFT) { var gridShiftCode = applyGridShift(source, false, point); if (gridShiftCode !== 0) { return undefined; } source_a = SRS_WGS84_SEMIMAJOR; source_es = SRS_WGS84_ESQUARED; } var dest_a = dest.a; var dest_b = dest.b; var dest_es = dest.es; if (dest.datum_type === PJD_GRIDSHIFT) { dest_a = SRS_WGS84_SEMIMAJOR; dest_b = SRS_WGS84_SEMIMINOR; dest_es = SRS_WGS84_ESQUARED; } // Do we need to go through geocentric coordinates? if (source_es === dest_es && source_a === dest_a && !checkParams(source.datum_type) && !checkParams(dest.datum_type)) { return point; } // Convert to geocentric coordinates. point = geodeticToGeocentric(point, source_es, source_a); // Convert between datums if (checkParams(source.datum_type)) { point = geocentricToWgs84(point, source.datum_type, source.datum_params); } if (checkParams(dest.datum_type)) { point = geocentricFromWgs84(point, dest.datum_type, dest.datum_params); } point = geocentricToGeodetic(point, dest_es, dest_a, dest_b); if (dest.datum_type === PJD_GRIDSHIFT) { var destGridShiftResult = applyGridShift(dest, true, point); if (destGridShiftResult !== 0) { return undefined; } } return point; } function applyGridShift(source, inverse, point) { if (source.grids === null || source.grids.length === 0) { console.log('Grid shift grids not found'); return -1; } var input = {x: -point.x, y: point.y}; var output = {x: Number.NaN, y: Number.NaN}; var attemptedGrids = []; outer: for (var i = 0; i < source.grids.length; i++) { var grid = source.grids[i]; attemptedGrids.push(grid.name); if (grid.isNull) { output = input; break; } grid.mandatory; if (grid.grid === null) { if (grid.mandatory) { console.log("Unable to find mandatory grid '" + grid.name + "'"); return -1; } continue; } var subgrids = grid.grid.subgrids; for (var j = 0, jj = subgrids.length; j < jj; j++) { var subgrid = subgrids[j]; // skip tables that don't match our point at all var epsilon = (Math.abs(subgrid.del[1]) + Math.abs(subgrid.del[0])) / 10000.0; var minX = subgrid.ll[0] - epsilon; var minY = subgrid.ll[1] - epsilon; var maxX = subgrid.ll[0] + (subgrid.lim[0] - 1) * subgrid.del[0] + epsilon; var maxY = subgrid.ll[1] + (subgrid.lim[1] - 1) * subgrid.del[1] + epsilon; if (minY > input.y || minX > input.x || maxY < input.y || maxX < input.x ) { continue; } output = applySubgridShift(input, inverse, subgrid); if (!isNaN(output.x)) { break outer; } } } if (isNaN(output.x)) { console.log("Failed to find a grid shift table for location '"+ -input.x * R2D + " " + input.y * R2D + " tried: '" + attemptedGrids + "'"); return -1; } point.x = -output.x; point.y = output.y; return 0; } function applySubgridShift(pin, inverse, ct) { var val = {x: Number.NaN, y: Number.NaN}; if (isNaN(pin.x)) { return val; } var tb = {x: pin.x, y: pin.y}; tb.x -= ct.ll[0]; tb.y -= ct.ll[1]; tb.x = adjust_lon(tb.x - Math.PI) + Math.PI; var t = nadInterpolate(tb, ct); if (inverse) { if (isNaN(t.x)) { return val; } t.x = tb.x - t.x; t.y = tb.y - t.y; var i = 9, tol = 1e-12; var dif, del; do { del = nadInterpolate(t, ct); if (isNaN(del.x)) { console.log("Inverse grid shift iteration failed, presumably at grid edge. Using first approximation."); break; } dif = {x: tb.x - (del.x + t.x), y: tb.y - (del.y + t.y)}; t.x += dif.x; t.y += dif.y; } while (i-- && Math.abs(dif.x) > tol && Math.abs(dif.y) > tol); if (i < 0) { console.log("Inverse grid shift iterator failed to converge."); return val; } val.x = adjust_lon(t.x + ct.ll[0]); val.y = t.y + ct.ll[1]; } else { if (!isNaN(t.x)) { val.x = pin.x + t.x; val.y = pin.y + t.y; } } return val; } function nadInterpolate(pin, ct) { var t = {x: pin.x / ct.del[0], y: pin.y / ct.del[1]}; var indx = {x: Math.floor(t.x), y: Math.floor(t.y)}; var frct = {x: t.x - 1.0 * indx.x, y: t.y - 1.0 * indx.y}; var val= {x: Number.NaN, y: Number.NaN}; var inx; if (indx.x < 0 || indx.x >= ct.lim[0]) { return val; } if (indx.y < 0 || indx.y >= ct.lim[1]) { return val; } inx = (indx.y * ct.lim[0]) + indx.x; var f00 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; inx++; var f10= {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; inx += ct.lim[0]; var f11 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; inx--; var f01 = {x: ct.cvs[inx][0], y: ct.cvs[inx][1]}; var m11 = frct.x * frct.y, m10 = frct.x * (1.0 - frct.y), m00 = (1.0 - frct.x) * (1.0 - frct.y), m01 = (1.0 - frct.x) * frct.y; val.x = (m00 * f00.x + m10 * f10.x + m01 * f01.x + m11 * f11.x); val.y = (m00 * f00.y + m10 * f10.y + m01 * f01.y + m11 * f11.y); return val; } function adjust_axis(crs, denorm, point) { var xin = point.x, yin = point.y, zin = point.z || 0.0; var v, t, i; var out = {}; for (i = 0; i < 3; i++) { if (denorm && i === 2 && point.z === undefined) { continue; } if (i === 0) { v = xin; if ("ew".indexOf(crs.axis[i]) !== -1) { t = 'x'; } else { t = 'y'; } } else if (i === 1) { v = yin; if ("ns".indexOf(crs.axis[i]) !== -1) { t = 'y'; } else { t = 'x'; } } else { v = zin; t = 'z'; } switch (crs.axis[i]) { case 'e': out[t] = v; break; case 'w': out[t] = -v; break; case 'n': out[t] = v; break; case 's': out[t] = -v; break; case 'u': if (point[t] !== undefined) { out.z = v; } break; case 'd': if (point[t] !== undefined) { out.z = -v; } break; default: //console.log("ERROR: unknow axis ("+crs.axis[i]+") - check definition of "+crs.projName); return null; } } return out; } function common (array){ var out = { x: array[0], y: array[1] }; if (array.length>2) { out.z = array[2]; } if (array.length>3) { out.m = array[3]; } return out; } function checkSanity (point) { checkCoord(point.x); checkCoord(point.y); } function checkCoord(num) { if (typeof Number.isFinite === 'function') { if (Number.isFinite(num)) { return; } throw new TypeError('coordinates must be finite numbers'); } if (typeof num !== 'number' || num !== num || !isFinite(num)) { throw new TypeError('coordinates must be finite numbers'); } } function checkNotWGS(source, dest) { return ( (source.datum.datum_type === PJD_3PARAM || source.datum.datum_type === PJD_7PARAM || source.datum.datum_type === PJD_GRIDSHIFT) && dest.datumCode !== 'WGS84') || ((dest.datum.datum_type === PJD_3PARAM || dest.datum.datum_type === PJD_7PARAM || dest.datum.datum_type === PJD_GRIDSHIFT) && source.datumCode !== 'WGS84'); } function transform(source, dest, point, enforceAxis) { var wgs84; if (Array.isArray(point)) { point = common(point); } else { // Clone the point object so inputs don't get modified point = { x: point.x, y: point.y, z: point.z, m: point.m }; } var hasZ = point.z !== undefined; checkSanity(point); // Workaround for datum shifts towgs84, if either source or destination projection is not wgs84 if (source.datum && dest.datum && checkNotWGS(source, dest)) { wgs84 = new Projection('WGS84'); point = transform(source, wgs84, point, enforceAxis); source = wgs84; } // DGR, 2010/11/12 if (enforceAxis && source.axis !== 'enu') { point = adjust_axis(source, false, point); } // Transform source points to long/lat, if they aren't already. if (source.projName === 'longlat') { point = { x: point.x * D2R$1, y: point.y * D2R$1, z: point.z || 0 }; } else { if (source.to_meter) { point = { x: point.x * source.to_meter, y: point.y * source.to_meter, z: point.z || 0 }; } point = source.inverse(point); // Convert Cartesian to longlat if (!point) { return; } } // Adjust for the prime meridian if necessary if (source.from_greenwich) { point.x += source.from_greenwich; } // Convert datums if needed, and if possible. point = datum_transform(source.datum, dest.datum, point); if (!point) { return; } // Adjust for the prime meridian if necessary if (dest.from_greenwich) { point = { x: point.x - dest.from_greenwich, y: point.y, z: point.z || 0 }; } if (dest.projName === 'longlat') { // convert radians to decimal degrees point = { x: point.x * R2D, y: point.y * R2D, z: point.z || 0 }; } else { // else project point = dest.forward(point); if (dest.to_meter) { point = { x: point.x / dest.to_meter, y: point.y / dest.to_meter, z: point.z || 0 }; } } // DGR, 2010/11/12 if (enforceAxis && dest.axis !== 'enu') { return adjust_axis(dest, true, point); } if (point && !hasZ) { delete point.z; } return point; } var wgs84 = Projection('WGS84'); function transformer(from, to, coords, enforceAxis) { var transformedArray, out, keys; if (Array.isArray(coords)) { transformedArray = transform(from, to, coords, enforceAxis) || {x: NaN, y: NaN}; if (coords.length > 2) { if ((typeof from.name !== 'undefined' && from.name === 'geocent') || (typeof to.name !== 'undefined' && to.name === 'geocent')) { if (typeof transformedArray.z === 'number') { return [transformedArray.x, transformedArray.y, transformedArray.z].concat(coords.splice(3)); } else { return [transformedArray.x, transformedArray.y, coords[2]].concat(coords.splice(3)); } } else { return [transformedArray.x, transformedArray.y].concat(coords.splice(2)); } } else { return [transformedArray.x, transformedArray.y]; } } else { out = transform(from, to, coords, enforceAxis); keys = Object.keys(coords); if (keys.length === 2) { return out; } keys.forEach(function (key) { if ((typeof from.name !== 'undefined' && from.name === 'geocent') || (typeof to.name !== 'undefined' && to.name === 'geocent')) { if (key === 'x' || key === 'y' || key === 'z') { return; } } else { if (key === 'x' || key === 'y') { return; } } out[key] = coords[key]; }); return out; } } function checkProj(item) { if (item instanceof Projection) { return item; } if (item.oProj) { return item.oProj; } return Projection(item); } function proj4(fromProj, toProj, coord) { fromProj = checkProj(fromProj); var single = false; var obj; if (typeof toProj === 'undefined') { toProj = fromProj; fromProj = wgs84; single = true; } else if (typeof toProj.x !== 'undefined' || Array.isArray(toProj)) { coord = toProj; toProj = fromProj; fromProj = wgs84; single = true; } toProj = checkProj(toProj); if (coord) { return transformer(fromProj, toProj, coord); } else { obj = { forward: function (coords, enforceAxis) { return transformer(fromProj, toProj, coords, enforceAxis); }, inverse: function (coords, enforceAxis) { return transformer(toProj, fromProj, coords, enforceAxis); } }; if (single) { obj.oProj = toProj; } return obj; } } /** * UTM zones are grouped, and assigned to one of a group of 6 * sets. * * {int} @private */ var NUM_100K_SETS = 6; /** * The column letters (for easting) of the lower left value, per * set. * * {string} @private */ var SET_ORIGIN_COLUMN_LETTERS = 'AJSAJS'; /** * The row letters (for northing) of the lower left value, per * set. * * {string} @private */ var SET_ORIGIN_ROW_LETTERS = 'AFAFAF'; var A$1 = 65; // A var I = 73; // I var O = 79; // O var V = 86; // V var Z = 90; // Z var mgrs = { forward: forward$t, inverse: inverse$t, toPoint: toPoint }; /** * Conversion of lat/lon to MGRS. * * @param {object} ll Object literal with lat and lon properties on a * WGS84 ellipsoid. * @param {int} accuracy Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for * 100 m, 2 for 1000 m or 1 for 10000 m). Optional, default is 5. * @return {string} the MGRS string for the given location and accuracy. */ function forward$t(ll, accuracy) { accuracy = accuracy || 5; // default accuracy 1m return encode(LLtoUTM({ lat: ll[1], lon: ll[0] }), accuracy); } /** * Conversion of MGRS to lat/lon. * * @param {string} mgrs MGRS string. * @return {array} An array with left (longitude), bottom (latitude), right * (longitude) and top (latitude) values in WGS84, representing the * bounding box for the provided MGRS reference. */ function inverse$t(mgrs) { var bbox = UTMtoLL(decode(mgrs.toUpperCase())); if (bbox.lat && bbox.lon) { return [bbox.lon, bbox.lat, bbox.lon, bbox.lat]; } return [bbox.left, bbox.bottom, bbox.right, bbox.top]; } function toPoint(mgrs) { var bbox = UTMtoLL(decode(mgrs.toUpperCase())); if (bbox.lat && bbox.lon) { return [bbox.lon, bbox.lat]; } return [(bbox.left + bbox.right) / 2, (bbox.top + bbox.bottom) / 2]; }/** * Conversion from degrees to radians. * * @private * @param {number} deg the angle in degrees. * @return {number} the angle in radians. */ function degToRad(deg) { return (deg * (Math.PI / 180.0)); } /** * Conversion from radians to degrees. * * @private * @param {number} rad the angle in radians. * @return {number} the angle in degrees. */ function radToDeg(rad) { return (180.0 * (rad / Math.PI)); } /** * Converts a set of Longitude and Latitude co-ordinates to UTM * using the WGS84 ellipsoid. * * @private * @param {object} ll Object literal with lat and lon properties * representing the WGS84 coordinate to be converted. * @return {object} Object literal containing the UTM value with easting, * northing, zoneNumber and zoneLetter properties, and an optional * accuracy property in digits. Returns null if the conversion failed. */ function LLtoUTM(ll) { var Lat = ll.lat; var Long = ll.lon; var a = 6378137.0; //ellip.radius; var eccSquared = 0.00669438; //ellip.eccsq; var k0 = 0.9996; var LongOrigin; var eccPrimeSquared; var N, T, C, A, M; var LatRad = degToRad(Lat); var LongRad = degToRad(Long); var LongOriginRad; var ZoneNumber; // (int) ZoneNumber = Math.floor((Long + 180) / 6) + 1; //Make sure the longitude 180.00 is in Zone 60 if (Long === 180) { ZoneNumber = 60; } // Special zone for Norway if (Lat >= 56.0 && Lat < 64.0 && Long >= 3.0 && Long < 12.0) { ZoneNumber = 32; } // Special zones for Svalbard if (Lat >= 72.0 && Lat < 84.0) { if (Long >= 0.0 && Long < 9.0) { ZoneNumber = 31; } else if (Long >= 9.0 && Long < 21.0) { ZoneNumber = 33; } else if (Long >= 21.0 && Long < 33.0) { ZoneNumber = 35; } else if (Long >= 33.0 && Long < 42.0) { ZoneNumber = 37; } } LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3; //+3 puts origin // in middle of // zone LongOriginRad = degToRad(LongOrigin); eccPrimeSquared = (eccSquared) / (1 - eccSquared); N = a / Math.sqrt(1 - eccSquared * Math.sin(LatRad) * Math.sin(LatRad)); T = Math.tan(LatRad) * Math.tan(LatRad); C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad); A = Math.cos(LatRad) * (LongRad - LongOriginRad); M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(2 * LatRad) + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(4 * LatRad) - (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.sin(6 * LatRad)); var UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6.0 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120.0) + 500000.0); var UTMNorthing = (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24.0 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720.0))); if (Lat < 0.0) { UTMNorthing += 10000000.0; //10000000 meter offset for // southern hemisphere } return { northing: Math.round(UTMNorthing), easting: Math.round(UTMEasting), zoneNumber: ZoneNumber, zoneLetter: getLetterDesignator(Lat) }; } /** * Converts UTM coords to lat/long, using the WGS84 ellipsoid. This is a convenience * class where the Zone can be specified as a single string eg."60N" which * is then broken down into the ZoneNumber and ZoneLetter. * * @private * @param {object} utm An object literal with northing, easting, zoneNumber * and zoneLetter properties. If an optional accuracy property is * provided (in meters), a bounding box will be returned instead of * latitude and longitude. * @return {object} An object literal containing either lat and lon values * (if no accuracy was provided), or top, right, bottom and left values * for the bounding box calculated according to the provided accuracy. * Returns null if the conversion failed. */ function UTMtoLL(utm) { var UTMNorthing = utm.northing; var UTMEasting = utm.easting; var zoneLetter = utm.zoneLetter; var zoneNumber = utm.zoneNumber; // check the ZoneNummber is valid if (zoneNumber < 0 || zoneNumber > 60) { return null; } var k0 = 0.9996; var a = 6378137.0; //ellip.radius; var eccSquared = 0.00669438; //ellip.eccsq; var eccPrimeSquared; var e1 = (1 - Math.sqrt(1 - eccSquared)) / (1 + Math.sqrt(1 - eccSquared)); var N1, T1, C1, R1, D, M; var LongOrigin; var mu, phi1Rad; // remove 500,000 meter offset for longitude var x = UTMEasting - 500000.0; var y = UTMNorthing; // We must know somehow if we are in the Northern or Southern // hemisphere, this is the only time we use the letter So even // if the Zone letter isn't exactly correct it should indicate // the hemisphere correctly if (zoneLetter < 'N') { y -= 10000000.0; // remove 10,000,000 meter offset used // for southern hemisphere } // There are 60 zones with zone 1 being at West -180 to -174 LongOrigin = (zoneNumber - 1) * 6 - 180 + 3; // +3 puts origin // in middle of // zone eccPrimeSquared = (eccSquared) / (1 - eccSquared); M = y / k0; mu = M / (a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256)); phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu); // double phi1 = ProjMath.radToDeg(phi1Rad); N1 = a / Math.sqrt(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad)); T1 = Math.tan(phi1Rad) * Math.tan(phi1Rad); C1 = eccPrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad); R1 = a * (1 - eccSquared) / Math.pow(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5); D = x / (N1 * k0); var lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720); lat = radToDeg(lat); var lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad); lon = LongOrigin + radToDeg(lon); var result; if (utm.accuracy) { var topRight = UTMtoLL({ northing: utm.northing + utm.accuracy, easting: utm.easting + utm.accuracy, zoneLetter: utm.zoneLetter, zoneNumber: utm.zoneNumber }); result = { top: topRight.lat, right: topRight.lon, bottom: lat, left: lon }; } else { result = { lat: lat, lon: lon }; } return result; } /** * Calculates the MGRS letter designator for the given latitude. * * @private * @param {number} lat The latitude in WGS84 to get the letter designator * for. * @return {char} The letter designator. */ function getLetterDesignator(lat) { //This is here as an error flag to show that the Latitude is //outside MGRS limits var LetterDesignator = 'Z'; if ((84 >= lat) && (lat >= 72)) { LetterDesignator = 'X'; } else if ((72 > lat) && (lat >= 64)) { LetterDesignator = 'W'; } else if ((64 > lat) && (lat >= 56)) { LetterDesignator = 'V'; } else if ((56 > lat) && (lat >= 48)) { LetterDesignator = 'U'; } else if ((48 > lat) && (lat >= 40)) { LetterDesignator = 'T'; } else if ((40 > lat) && (lat >= 32)) { LetterDesignator = 'S'; } else if ((32 > lat) && (lat >= 24)) { LetterDesignator = 'R'; } else if ((24 > lat) && (lat >= 16)) { LetterDesignator = 'Q'; } else if ((16 > lat) && (lat >= 8)) { LetterDesignator = 'P'; } else if ((8 > lat) && (lat >= 0)) { LetterDesignator = 'N'; } else if ((0 > lat) && (lat >= -8)) { LetterDesignator = 'M'; } else if ((-8 > lat) && (lat >= -16)) { LetterDesignator = 'L'; } else if ((-16 > lat) && (lat >= -24)) { LetterDesignator = 'K'; } else if ((-24 > lat) && (lat >= -32)) { LetterDesignator = 'J'; } else if ((-32 > lat) && (lat >= -40)) { LetterDesignator = 'H'; } else if ((-40 > lat) && (lat >= -48)) { LetterDesignator = 'G'; } else if ((-48 > lat) && (lat >= -56)) { LetterDesignator = 'F'; } else if ((-56 > lat) && (lat >= -64)) { LetterDesignator = 'E'; } else if ((-64 > lat) && (lat >= -72)) { LetterDesignator = 'D'; } else if ((-72 > lat) && (lat >= -80)) { LetterDesignator = 'C'; } return LetterDesignator; } /** * Encodes a UTM location as MGRS string. * * @private * @param {object} utm An object literal with easting, northing, * zoneLetter, zoneNumber * @param {number} accuracy Accuracy in digits (1-5). * @return {string} MGRS string for the given UTM location. */ function encode(utm, accuracy) { // prepend with leading zeroes var seasting = "00000" + utm.easting, snorthing = "00000" + utm.northing; return utm.zoneNumber + utm.zoneLetter + get100kID(utm.easting, utm.northing, utm.zoneNumber) + seasting.substr(seasting.length - 5, accuracy) + snorthing.substr(snorthing.length - 5, accuracy); } /** * Get the two letter 100k designator for a given UTM easting, * northing and zone number value. * * @private * @param {number} easting * @param {number} northing * @param {number} zoneNumber * @return the two letter 100k designator for the given UTM location. */ function get100kID(easting, northing, zoneNumber) { var setParm = get100kSetForZone(zoneNumber); var setColumn = Math.floor(easting / 100000); var setRow = Math.floor(northing / 100000) % 20; return getLetter100kID(setColumn, setRow, setParm); } /** * Given a UTM zone number, figure out the MGRS 100K set it is in. * * @private * @param {number} i An UTM zone number. * @return {number} the 100k set the UTM zone is in. */ function get100kSetForZone(i) { var setParm = i % NUM_100K_SETS; if (setParm === 0) { setParm = NUM_100K_SETS; } return setParm; } /** * Get the two-letter MGRS 100k designator given information * translated from the UTM northing, easting and zone number. * * @private * @param {number} column the column index as it relates to the MGRS * 100k set spreadsheet, created from the UTM easting. * Values are 1-8. * @param {number} row the row index as it relates to the MGRS 100k set * spreadsheet, created from the UTM northing value. Values * are from 0-19. * @param {number} parm the set block, as it relates to the MGRS 100k set * spreadsheet, created from the UTM zone. Values are from * 1-60. * @return two letter MGRS 100k code. */ function getLetter100kID(column, row, parm) { // colOrigin and rowOrigin are the letters at the origin of the set var index = parm - 1; var colOrigin = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(index); var rowOrigin = SET_ORIGIN_ROW_LETTERS.charCodeAt(index); // colInt and rowInt are the letters to build to return var colInt = colOrigin + column - 1; var rowInt = rowOrigin + row; var rollover = false; if (colInt > Z) { colInt = colInt - Z + A$1 - 1; rollover = true; } if (colInt === I || (colOrigin < I && colInt > I) || ((colInt > I || colOrigin < I) && rollover)) { colInt++; } if (colInt === O || (colOrigin < O && colInt > O) || ((colInt > O || colOrigin < O) && rollover)) { colInt++; if (colInt === I) { colInt++; } } if (colInt > Z) { colInt = colInt - Z + A$1 - 1; } if (rowInt > V) { rowInt = rowInt - V + A$1 - 1; rollover = true; } else { rollover = false; } if (((rowInt === I) || ((rowOrigin < I) && (rowInt > I))) || (((rowInt > I) || (rowOrigin < I)) && rollover)) { rowInt++; } if (((rowInt === O) || ((rowOrigin < O) && (rowInt > O))) || (((rowInt > O) || (rowOrigin < O)) && rollover)) { rowInt++; if (rowInt === I) { rowInt++; } } if (rowInt > V) { rowInt = rowInt - V + A$1 - 1; } var twoLetter = String.fromCharCode(colInt) + String.fromCharCode(rowInt); return twoLetter; } /** * Decode the UTM parameters from a MGRS string. * * @private * @param {string} mgrsString an UPPERCASE coordinate string is expected. * @return {object} An object literal with easting, northing, zoneLetter, * zoneNumber and accuracy (in meters) properties. */ function decode(mgrsString) { if (mgrsString && mgrsString.length === 0) { throw ("MGRSPoint coverting from nothing"); } var length = mgrsString.length; var hunK = null; var sb = ""; var testChar; var i = 0; // get Zone number while (!(/[A-Z]/).test(testChar = mgrsString.charAt(i))) { if (i >= 2) { throw ("MGRSPoint bad conversion from: " + mgrsString); } sb += testChar; i++; } var zoneNumber = parseInt(sb, 10); if (i === 0 || i + 3 > length) { // A good MGRS string has to be 4-5 digits long, // ##AAA/#AAA at least. throw ("MGRSPoint bad conversion from: " + mgrsString); } var zoneLetter = mgrsString.charAt(i++); // Should we check the zone letter here? Why not. if (zoneLetter <= 'A' || zoneLetter === 'B' || zoneLetter === 'Y' || zoneLetter >= 'Z' || zoneLetter === 'I' || zoneLetter === 'O') { throw ("MGRSPoint zone letter " + zoneLetter + " not handled: " + mgrsString); } hunK = mgrsString.substring(i, i += 2); var set = get100kSetForZone(zoneNumber); var east100k = getEastingFromChar(hunK.charAt(0), set); var north100k = getNorthingFromChar(hunK.charAt(1), set); // We have a bug where the northing may be 2000000 too low. // How // do we know when to roll over? while (north100k < getMinNorthing(zoneLetter)) { north100k += 2000000; } // calculate the char index for easting/northing separator var remainder = length - i; if (remainder % 2 !== 0) { throw ("MGRSPoint has to have an even number \nof digits after the zone letter and two 100km letters - front \nhalf for easting meters, second half for \nnorthing meters" + mgrsString); } var sep = remainder / 2; var sepEasting = 0.0; var sepNorthing = 0.0; var accuracyBonus, sepEastingString, sepNorthingString, easting, northing; if (sep > 0) { accuracyBonus = 100000.0 / Math.pow(10, sep); sepEastingString = mgrsString.substring(i, i + sep); sepEasting = parseFloat(sepEastingString) * accuracyBonus; sepNorthingString = mgrsString.substring(i + sep); sepNorthing = parseFloat(sepNorthingString) * accuracyBonus; } easting = sepEasting + east100k; northing = sepNorthing + north100k; return { easting: easting, northing: northing, zoneLetter: zoneLetter, zoneNumber: zoneNumber, accuracy: accuracyBonus }; } /** * Given the first letter from a two-letter MGRS 100k zone, and given the * MGRS table set for the zone number, figure out the easting value that * should be added to the other, secondary easting value. * * @private * @param {char} e The first letter from a two-letter MGRS 100´k zone. * @param {number} set The MGRS table set for the zone number. * @return {number} The easting value for the given letter and set. */ function getEastingFromChar(e, set) { // colOrigin is the letter at the origin of the set for the // column var curCol = SET_ORIGIN_COLUMN_LETTERS.charCodeAt(set - 1); var eastingValue = 100000.0; var rewindMarker = false; while (curCol !== e.charCodeAt(0)) { curCol++; if (curCol === I) { curCol++; } if (curCol === O) { curCol++; } if (curCol > Z) { if (rewindMarker) { throw ("Bad character: " + e); } curCol = A$1; rewindMarker = true; } eastingValue += 100000.0; } return eastingValue; } /** * Given the second letter from a two-letter MGRS 100k zone, and given the * MGRS table set for the zone number, figure out the northing value that * should be added to the other, secondary northing value. You have to * remember that Northings are determined from the equator, and the vertical * cycle of letters mean a 2000000 additional northing meters. This happens * approx. every 18 degrees of latitude. This method does *NOT* count any * additional northings. You have to figure out how many 2000000 meters need * to be added for the zone letter of the MGRS coordinate. * * @private * @param {char} n Second letter of the MGRS 100k zone * @param {number} set The MGRS table set number, which is dependent on the * UTM zone number. * @return {number} The northing value for the given letter and set. */ function getNorthingFromChar(n, set) { if (n > 'V') { throw ("MGRSPoint given invalid Northing " + n); } // rowOrigin is the letter at the origin of the set for the // column var curRow = SET_ORIGIN_ROW_LETTERS.charCodeAt(set - 1); var northingValue = 0.0; var rewindMarker = false; while (curRow !== n.charCodeAt(0)) { curRow++; if (curRow === I) { curRow++; } if (curRow === O) { curRow++; } // fixing a bug making whole application hang in this loop // when 'n' is a wrong character if (curRow > V) { if (rewindMarker) { // making sure that this loop ends throw ("Bad character: " + n); } curRow = A$1; rewindMarker = true; } northingValue += 100000.0; } return northingValue; } /** * The function getMinNorthing returns the minimum northing value of a MGRS * zone. * * Ported from Geotrans' c Lattitude_Band_Value structure table. * * @private * @param {char} zoneLetter The MGRS zone to get the min northing for. * @return {number} */ function getMinNorthing(zoneLetter) { var northing; switch (zoneLetter) { case 'C': northing = 1100000.0; break; case 'D': northing = 2000000.0; break; case 'E': northing = 2800000.0; break; case 'F': northing = 3700000.0; break; case 'G': northing = 4600000.0; break; case 'H': northing = 5500000.0; break; case 'J': northing = 6400000.0; break; case 'K': northing = 7300000.0; break; case 'L': northing = 8200000.0; break; case 'M': northing = 9100000.0; break; case 'N': northing = 0.0; break; case 'P': northing = 800000.0; break; case 'Q': northing = 1700000.0; break; case 'R': northing = 2600000.0; break; case 'S': northing = 3500000.0; break; case 'T': northing = 4400000.0; break; case 'U': northing = 5300000.0; break; case 'V': northing = 6200000.0; break; case 'W': northing = 7000000.0; break; case 'X': northing = 7900000.0; break; default: northing = -1.0; } if (northing >= 0.0) { return northing; } else { throw ("Invalid zone letter: " + zoneLetter); } } function Point(x, y, z) { if (!(this instanceof Point)) { return new Point(x, y, z); } if (Array.isArray(x)) { this.x = x[0]; this.y = x[1]; this.z = x[2] || 0.0; } else if(typeof x === 'object') { this.x = x.x; this.y = x.y; this.z = x.z || 0.0; } else if (typeof x === 'string' && typeof y === 'undefined') { var coords = x.split(','); this.x = parseFloat(coords[0], 10); this.y = parseFloat(coords[1], 10); this.z = parseFloat(coords[2], 10) || 0.0; } else { this.x = x; this.y = y; this.z = z || 0.0; } console.warn('proj4.Point will be removed in version 3, use proj4.toPoint'); } Point.fromMGRS = function(mgrsStr) { return new Point(toPoint(mgrsStr)); }; Point.prototype.toMGRS = function(accuracy) { return forward$t([this.x, this.y], accuracy); }; var C00 = 1; var C02 = 0.25; var C04 = 0.046875; var C06 = 0.01953125; var C08 = 0.01068115234375; var C22 = 0.75; var C44 = 0.46875; var C46 = 0.01302083333333333333; var C48 = 0.00712076822916666666; var C66 = 0.36458333333333333333; var C68 = 0.00569661458333333333; var C88 = 0.3076171875; function pj_enfn(es) { var en = []; en[0] = C00 - es * (C02 + es * (C04 + es * (C06 + es * C08))); en[1] = es * (C22 - es * (C04 + es * (C06 + es * C08))); var t = es * es; en[2] = t * (C44 - es * (C46 + es * C48)); t *= es; en[3] = t * (C66 - es * C68); en[4] = t * es * C88; return en; } function pj_mlfn(phi, sphi, cphi, en) { cphi *= sphi; sphi *= sphi; return (en[0] * phi - cphi * (en[1] + sphi * (en[2] + sphi * (en[3] + sphi * en[4])))); } var MAX_ITER$3 = 20; function pj_inv_mlfn(arg, es, en) { var k = 1 / (1 - es); var phi = arg; for (var i = MAX_ITER$3; i; --i) { /* rarely goes over 2 iterations */ var s = Math.sin(phi); var t = 1 - es * s * s; //t = this.pj_mlfn(phi, s, Math.cos(phi), en) - arg; //phi -= t * (t * Math.sqrt(t)) * k; t = (pj_mlfn(phi, s, Math.cos(phi), en) - arg) * (t * Math.sqrt(t)) * k; phi -= t; if (Math.abs(t) < EPSLN) { return phi; } } //..reportError("cass:pj_inv_mlfn: Convergence error"); return phi; } // Heavily based on this tmerc projection implementation // https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/tmerc.js function init$t() { this.x0 = this.x0 !== undefined ? this.x0 : 0; this.y0 = this.y0 !== undefined ? this.y0 : 0; this.long0 = this.long0 !== undefined ? this.long0 : 0; this.lat0 = this.lat0 !== undefined ? this.lat0 : 0; if (this.es) { this.en = pj_enfn(this.es); this.ml0 = pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en); } } /** Transverse Mercator Forward - long/lat to x/y long/lat in radians */ function forward$s(p) { var lon = p.x; var lat = p.y; var delta_lon = adjust_lon(lon - this.long0); var con; var x, y; var sin_phi = Math.sin(lat); var cos_phi = Math.cos(lat); if (!this.es) { var b = cos_phi * Math.sin(delta_lon); if ((Math.abs(Math.abs(b) - 1)) < EPSLN) { return (93); } else { x = 0.5 * this.a * this.k0 * Math.log((1 + b) / (1 - b)) + this.x0; y = cos_phi * Math.cos(delta_lon) / Math.sqrt(1 - Math.pow(b, 2)); b = Math.abs(y); if (b >= 1) { if ((b - 1) > EPSLN) { return (93); } else { y = 0; } } else { y = Math.acos(y); } if (lat < 0) { y = -y; } y = this.a * this.k0 * (y - this.lat0) + this.y0; } } else { var al = cos_phi * delta_lon; var als = Math.pow(al, 2); var c = this.ep2 * Math.pow(cos_phi, 2); var cs = Math.pow(c, 2); var tq = Math.abs(cos_phi) > EPSLN ? Math.tan(lat) : 0; var t = Math.pow(tq, 2); var ts = Math.pow(t, 2); con = 1 - this.es * Math.pow(sin_phi, 2); al = al / Math.sqrt(con); var ml = pj_mlfn(lat, sin_phi, cos_phi, this.en); x = this.a * (this.k0 * al * (1 + als / 6 * (1 - t + c + als / 20 * (5 - 18 * t + ts + 14 * c - 58 * t * c + als / 42 * (61 + 179 * ts - ts * t - 479 * t))))) + this.x0; y = this.a * (this.k0 * (ml - this.ml0 + sin_phi * delta_lon * al / 2 * (1 + als / 12 * (5 - t + 9 * c + 4 * cs + als / 30 * (61 + ts - 58 * t + 270 * c - 330 * t * c + als / 56 * (1385 + 543 * ts - ts * t - 3111 * t)))))) + this.y0; } p.x = x; p.y = y; return p; } /** Transverse Mercator Inverse - x/y to long/lat */ function inverse$s(p) { var con, phi; var lat, lon; var x = (p.x - this.x0) * (1 / this.a); var y = (p.y - this.y0) * (1 / this.a); if (!this.es) { var f = Math.exp(x / this.k0); var g = 0.5 * (f - 1 / f); var temp = this.lat0 + y / this.k0; var h = Math.cos(temp); con = Math.sqrt((1 - Math.pow(h, 2)) / (1 + Math.pow(g, 2))); lat = Math.asin(con); if (y < 0) { lat = -lat; } if ((g === 0) && (h === 0)) { lon = 0; } else { lon = adjust_lon(Math.atan2(g, h) + this.long0); } } else { // ellipsoidal form con = this.ml0 + y / this.k0; phi = pj_inv_mlfn(con, this.es, this.en); if (Math.abs(phi) < HALF_PI) { var sin_phi = Math.sin(phi); var cos_phi = Math.cos(phi); var tan_phi = Math.abs(cos_phi) > EPSLN ? Math.tan(phi) : 0; var c = this.ep2 * Math.pow(cos_phi, 2); var cs = Math.pow(c, 2); var t = Math.pow(tan_phi, 2); var ts = Math.pow(t, 2); con = 1 - this.es * Math.pow(sin_phi, 2); var d = x * Math.sqrt(con) / this.k0; var ds = Math.pow(d, 2); con = con * tan_phi; lat = phi - (con * ds / (1 - this.es)) * 0.5 * (1 - ds / 12 * (5 + 3 * t - 9 * c * t + c - 4 * cs - ds / 30 * (61 + 90 * t - 252 * c * t + 45 * ts + 46 * c - ds / 56 * (1385 + 3633 * t + 4095 * ts + 1574 * ts * t)))); lon = adjust_lon(this.long0 + (d * (1 - ds / 6 * (1 + 2 * t + c - ds / 20 * (5 + 28 * t + 24 * ts + 8 * c * t + 6 * c - ds / 42 * (61 + 662 * t + 1320 * ts + 720 * ts * t)))) / cos_phi)); } else { lat = HALF_PI * sign(y); lon = 0; } } p.x = lon; p.y = lat; return p; } var names$t = ["Fast_Transverse_Mercator", "Fast Transverse Mercator"]; var tmerc = { init: init$t, forward: forward$s, inverse: inverse$s, names: names$t }; function sinh(x) { var r = Math.exp(x); r = (r - 1 / r) / 2; return r; } function hypot(x, y) { x = Math.abs(x); y = Math.abs(y); var a = Math.max(x, y); var b = Math.min(x, y) / (a ? a : 1); return a * Math.sqrt(1 + Math.pow(b, 2)); } function log1py(x) { var y = 1 + x; var z = y - 1; return z === 0 ? x : x * Math.log(y) / z; } function asinhy(x) { var y = Math.abs(x); y = log1py(y * (1 + y / (hypot(1, y) + 1))); return x < 0 ? -y : y; } function gatg(pp, B) { var cos_2B = 2 * Math.cos(2 * B); var i = pp.length - 1; var h1 = pp[i]; var h2 = 0; var h; while (--i >= 0) { h = -h2 + cos_2B * h1 + pp[i]; h2 = h1; h1 = h; } return (B + h * Math.sin(2 * B)); } function clens(pp, arg_r) { var r = 2 * Math.cos(arg_r); var i = pp.length - 1; var hr1 = pp[i]; var hr2 = 0; var hr; while (--i >= 0) { hr = -hr2 + r * hr1 + pp[i]; hr2 = hr1; hr1 = hr; } return Math.sin(arg_r) * hr; } function cosh(x) { var r = Math.exp(x); r = (r + 1 / r) / 2; return r; } function clens_cmplx(pp, arg_r, arg_i) { var sin_arg_r = Math.sin(arg_r); var cos_arg_r = Math.cos(arg_r); var sinh_arg_i = sinh(arg_i); var cosh_arg_i = cosh(arg_i); var r = 2 * cos_arg_r * cosh_arg_i; var i = -2 * sin_arg_r * sinh_arg_i; var j = pp.length - 1; var hr = pp[j]; var hi1 = 0; var hr1 = 0; var hi = 0; var hr2; var hi2; while (--j >= 0) { hr2 = hr1; hi2 = hi1; hr1 = hr; hi1 = hi; hr = -hr2 + r * hr1 - i * hi1 + pp[j]; hi = -hi2 + i * hr1 + r * hi1; } r = sin_arg_r * cosh_arg_i; i = cos_arg_r * sinh_arg_i; return [r * hr - i * hi, r * hi + i * hr]; } // Heavily based on this etmerc projection implementation // https://github.com/mbloch/mapshaper-proj/blob/master/src/projections/etmerc.js function init$s() { if (!this.approx && (isNaN(this.es) || this.es <= 0)) { throw new Error('Incorrect elliptical usage. Try using the +approx option in the proj string, or PROJECTION["Fast_Transverse_Mercator"] in the WKT.'); } if (this.approx) { // When '+approx' is set, use tmerc instead tmerc.init.apply(this); this.forward = tmerc.forward; this.inverse = tmerc.inverse; } this.x0 = this.x0 !== undefined ? this.x0 : 0; this.y0 = this.y0 !== undefined ? this.y0 : 0; this.long0 = this.long0 !== undefined ? this.long0 : 0; this.lat0 = this.lat0 !== undefined ? this.lat0 : 0; this.cgb = []; this.cbg = []; this.utg = []; this.gtu = []; var f = this.es / (1 + Math.sqrt(1 - this.es)); var n = f / (2 - f); var np = n; this.cgb[0] = n * (2 + n * (-2 / 3 + n * (-2 + n * (116 / 45 + n * (26 / 45 + n * (-2854 / 675 )))))); this.cbg[0] = n * (-2 + n * ( 2 / 3 + n * ( 4 / 3 + n * (-82 / 45 + n * (32 / 45 + n * (4642 / 4725)))))); np = np * n; this.cgb[1] = np * (7 / 3 + n * (-8 / 5 + n * (-227 / 45 + n * (2704 / 315 + n * (2323 / 945))))); this.cbg[1] = np * (5 / 3 + n * (-16 / 15 + n * ( -13 / 9 + n * (904 / 315 + n * (-1522 / 945))))); np = np * n; this.cgb[2] = np * (56 / 15 + n * (-136 / 35 + n * (-1262 / 105 + n * (73814 / 2835)))); this.cbg[2] = np * (-26 / 15 + n * (34 / 21 + n * (8 / 5 + n * (-12686 / 2835)))); np = np * n; this.cgb[3] = np * (4279 / 630 + n * (-332 / 35 + n * (-399572 / 14175))); this.cbg[3] = np * (1237 / 630 + n * (-12 / 5 + n * ( -24832 / 14175))); np = np * n; this.cgb[4] = np * (4174 / 315 + n * (-144838 / 6237)); this.cbg[4] = np * (-734 / 315 + n * (109598 / 31185)); np = np * n; this.cgb[5] = np * (601676 / 22275); this.cbg[5] = np * (444337 / 155925); np = Math.pow(n, 2); this.Qn = this.k0 / (1 + n) * (1 + np * (1 / 4 + np * (1 / 64 + np / 256))); this.utg[0] = n * (-0.5 + n * ( 2 / 3 + n * (-37 / 96 + n * ( 1 / 360 + n * (81 / 512 + n * (-96199 / 604800)))))); this.gtu[0] = n * (0.5 + n * (-2 / 3 + n * (5 / 16 + n * (41 / 180 + n * (-127 / 288 + n * (7891 / 37800)))))); this.utg[1] = np * (-1 / 48 + n * (-1 / 15 + n * (437 / 1440 + n * (-46 / 105 + n * (1118711 / 3870720))))); this.gtu[1] = np * (13 / 48 + n * (-3 / 5 + n * (557 / 1440 + n * (281 / 630 + n * (-1983433 / 1935360))))); np = np * n; this.utg[2] = np * (-17 / 480 + n * (37 / 840 + n * (209 / 4480 + n * (-5569 / 90720 )))); this.gtu[2] = np * (61 / 240 + n * (-103 / 140 + n * (15061 / 26880 + n * (167603 / 181440)))); np = np * n; this.utg[3] = np * (-4397 / 161280 + n * (11 / 504 + n * (830251 / 7257600))); this.gtu[3] = np * (49561 / 161280 + n * (-179 / 168 + n * (6601661 / 7257600))); np = np * n; this.utg[4] = np * (-4583 / 161280 + n * (108847 / 3991680)); this.gtu[4] = np * (34729 / 80640 + n * (-3418889 / 1995840)); np = np * n; this.utg[5] = np * (-20648693 / 638668800); this.gtu[5] = np * (212378941 / 319334400); var Z = gatg(this.cbg, this.lat0); this.Zb = -this.Qn * (Z + clens(this.gtu, 2 * Z)); } function forward$r(p) { var Ce = adjust_lon(p.x - this.long0); var Cn = p.y; Cn = gatg(this.cbg, Cn); var sin_Cn = Math.sin(Cn); var cos_Cn = Math.cos(Cn); var sin_Ce = Math.sin(Ce); var cos_Ce = Math.cos(Ce); Cn = Math.atan2(sin_Cn, cos_Ce * cos_Cn); Ce = Math.atan2(sin_Ce * cos_Cn, hypot(sin_Cn, cos_Cn * cos_Ce)); Ce = asinhy(Math.tan(Ce)); var tmp = clens_cmplx(this.gtu, 2 * Cn, 2 * Ce); Cn = Cn + tmp[0]; Ce = Ce + tmp[1]; var x; var y; if (Math.abs(Ce) <= 2.623395162778) { x = this.a * (this.Qn * Ce) + this.x0; y = this.a * (this.Qn * Cn + this.Zb) + this.y0; } else { x = Infinity; y = Infinity; } p.x = x; p.y = y; return p; } function inverse$r(p) { var Ce = (p.x - this.x0) * (1 / this.a); var Cn = (p.y - this.y0) * (1 / this.a); Cn = (Cn - this.Zb) / this.Qn; Ce = Ce / this.Qn; var lon; var lat; if (Math.abs(Ce) <= 2.623395162778) { var tmp = clens_cmplx(this.utg, 2 * Cn, 2 * Ce); Cn = Cn + tmp[0]; Ce = Ce + tmp[1]; Ce = Math.atan(sinh(Ce)); var sin_Cn = Math.sin(Cn); var cos_Cn = Math.cos(Cn); var sin_Ce = Math.sin(Ce); var cos_Ce = Math.cos(Ce); Cn = Math.atan2(sin_Cn * cos_Ce, hypot(sin_Ce, cos_Ce * cos_Cn)); Ce = Math.atan2(sin_Ce, cos_Ce * cos_Cn); lon = adjust_lon(Ce + this.long0); lat = gatg(this.cgb, Cn); } else { lon = Infinity; lat = Infinity; } p.x = lon; p.y = lat; return p; } var names$s = ["Extended_Transverse_Mercator", "Extended Transverse Mercator", "etmerc", "Transverse_Mercator", "Transverse Mercator", "Gauss Kruger", "Gauss_Kruger", "tmerc"]; var etmerc = { init: init$s, forward: forward$r, inverse: inverse$r, names: names$s }; function adjust_zone(zone, lon) { if (zone === undefined) { zone = Math.floor((adjust_lon(lon) + Math.PI) * 30 / Math.PI) + 1; if (zone < 0) { return 0; } else if (zone > 60) { return 60; } } return zone; } var dependsOn = 'etmerc'; function init$r() { var zone = adjust_zone(this.zone, this.long0); if (zone === undefined) { throw new Error('unknown utm zone'); } this.lat0 = 0; this.long0 = ((6 * Math.abs(zone)) - 183) * D2R$1; this.x0 = 500000; this.y0 = this.utmSouth ? 10000000 : 0; this.k0 = 0.9996; etmerc.init.apply(this); this.forward = etmerc.forward; this.inverse = etmerc.inverse; } var names$r = ["Universal Transverse Mercator System", "utm"]; var utm = { init: init$r, names: names$r, dependsOn: dependsOn }; function srat(esinp, exp) { return (Math.pow((1 - esinp) / (1 + esinp), exp)); } var MAX_ITER$2 = 20; function init$q() { var sphi = Math.sin(this.lat0); var cphi = Math.cos(this.lat0); cphi *= cphi; this.rc = Math.sqrt(1 - this.es) / (1 - this.es * sphi * sphi); this.C = Math.sqrt(1 + this.es * cphi * cphi / (1 - this.es)); this.phic0 = Math.asin(sphi / this.C); this.ratexp = 0.5 * this.C * this.e; this.K = Math.tan(0.5 * this.phic0 + FORTPI) / (Math.pow(Math.tan(0.5 * this.lat0 + FORTPI), this.C) * srat(this.e * sphi, this.ratexp)); } function forward$q(p) { var lon = p.x; var lat = p.y; p.y = 2 * Math.atan(this.K * Math.pow(Math.tan(0.5 * lat + FORTPI), this.C) * srat(this.e * Math.sin(lat), this.ratexp)) - HALF_PI; p.x = this.C * lon; return p; } function inverse$q(p) { var DEL_TOL = 1e-14; var lon = p.x / this.C; var lat = p.y; var num = Math.pow(Math.tan(0.5 * lat + FORTPI) / this.K, 1 / this.C); for (var i = MAX_ITER$2; i > 0; --i) { lat = 2 * Math.atan(num * srat(this.e * Math.sin(p.y), - 0.5 * this.e)) - HALF_PI; if (Math.abs(lat - p.y) < DEL_TOL) { break; } p.y = lat; } /* convergence failed */ if (!i) { return null; } p.x = lon; p.y = lat; return p; } var names$q = ["gauss"]; var gauss = { init: init$q, forward: forward$q, inverse: inverse$q, names: names$q }; function init$p() { gauss.init.apply(this); if (!this.rc) { return; } this.sinc0 = Math.sin(this.phic0); this.cosc0 = Math.cos(this.phic0); this.R2 = 2 * this.rc; if (!this.title) { this.title = "Oblique Stereographic Alternative"; } } function forward$p(p) { var sinc, cosc, cosl, k; p.x = adjust_lon(p.x - this.long0); gauss.forward.apply(this, [p]); sinc = Math.sin(p.y); cosc = Math.cos(p.y); cosl = Math.cos(p.x); k = this.k0 * this.R2 / (1 + this.sinc0 * sinc + this.cosc0 * cosc * cosl); p.x = k * cosc * Math.sin(p.x); p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl); p.x = this.a * p.x + this.x0; p.y = this.a * p.y + this.y0; return p; } function inverse$p(p) { var sinc, cosc, lon, lat, rho; p.x = (p.x - this.x0) / this.a; p.y = (p.y - this.y0) / this.a; p.x /= this.k0; p.y /= this.k0; if ((rho = hypot(p.x, p.y))) { var c = 2 * Math.atan2(rho, this.R2); sinc = Math.sin(c); cosc = Math.cos(c); lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho); lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc); } else { lat = this.phic0; lon = 0; } p.x = lon; p.y = lat; gauss.inverse.apply(this, [p]); p.x = adjust_lon(p.x + this.long0); return p; } var names$p = ["Stereographic_North_Pole", "Oblique_Stereographic", "sterea","Oblique Stereographic Alternative","Double_Stereographic"]; var sterea = { init: init$p, forward: forward$p, inverse: inverse$p, names: names$p }; function ssfn_(phit, sinphi, eccen) { sinphi *= eccen; return (Math.tan(0.5 * (HALF_PI + phit)) * Math.pow((1 - sinphi) / (1 + sinphi), 0.5 * eccen)); } function init$o() { // setting default parameters this.x0 = this.x0 || 0; this.y0 = this.y0 || 0; this.lat0 = this.lat0 || 0; this.long0 = this.long0 || 0; this.coslat0 = Math.cos(this.lat0); this.sinlat0 = Math.sin(this.lat0); if (this.sphere) { if (this.k0 === 1 && !isNaN(this.lat_ts) && Math.abs(this.coslat0) <= EPSLN) { this.k0 = 0.5 * (1 + sign(this.lat0) * Math.sin(this.lat_ts)); } } else { if (Math.abs(this.coslat0) <= EPSLN) { if (this.lat0 > 0) { //North pole //trace('stere:north pole'); this.con = 1; } else { //South pole //trace('stere:south pole'); this.con = -1; } } this.cons = Math.sqrt(Math.pow(1 + this.e, 1 + this.e) * Math.pow(1 - this.e, 1 - this.e)); if (this.k0 === 1 && !isNaN(this.lat_ts) && Math.abs(this.coslat0) <= EPSLN && Math.abs(Math.cos(this.lat_ts)) > EPSLN) { // When k0 is 1 (default value) and lat_ts is a vaild number and lat0 is at a pole and lat_ts is not at a pole // Recalculate k0 using formula 21-35 from p161 of Snyder, 1987 this.k0 = 0.5 * this.cons * msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts)) / tsfnz(this.e, this.con * this.lat_ts, this.con * Math.sin(this.lat_ts)); } this.ms1 = msfnz(this.e, this.sinlat0, this.coslat0); this.X0 = 2 * Math.atan(this.ssfn_(this.lat0, this.sinlat0, this.e)) - HALF_PI; this.cosX0 = Math.cos(this.X0); this.sinX0 = Math.sin(this.X0); } } // Stereographic forward equations--mapping lat,long to x,y function forward$o(p) { var lon = p.x; var lat = p.y; var sinlat = Math.sin(lat); var coslat = Math.cos(lat); var A, X, sinX, cosX, ts, rh; var dlon = adjust_lon(lon - this.long0); if (Math.abs(Math.abs(lon - this.long0) - Math.PI) <= EPSLN && Math.abs(lat + this.lat0) <= EPSLN) { //case of the origine point //trace('stere:this is the origin point'); p.x = NaN; p.y = NaN; return p; } if (this.sphere) { //trace('stere:sphere case'); A = 2 * this.k0 / (1 + this.sinlat0 * sinlat + this.coslat0 * coslat * Math.cos(dlon)); p.x = this.a * A * coslat * Math.sin(dlon) + this.x0; p.y = this.a * A * (this.coslat0 * sinlat - this.sinlat0 * coslat * Math.cos(dlon)) + this.y0; return p; } else { X = 2 * Math.atan(this.ssfn_(lat, sinlat, this.e)) - HALF_PI; cosX = Math.cos(X); sinX = Math.sin(X); if (Math.abs(this.coslat0) <= EPSLN) { ts = tsfnz(this.e, lat * this.con, this.con * sinlat); rh = 2 * this.a * this.k0 * ts / this.cons; p.x = this.x0 + rh * Math.sin(lon - this.long0); p.y = this.y0 - this.con * rh * Math.cos(lon - this.long0); //trace(p.toString()); return p; } else if (Math.abs(this.sinlat0) < EPSLN) { //Eq //trace('stere:equateur'); A = 2 * this.a * this.k0 / (1 + cosX * Math.cos(dlon)); p.y = A * sinX; } else { //other case //trace('stere:normal case'); A = 2 * this.a * this.k0 * this.ms1 / (this.cosX0 * (1 + this.sinX0 * sinX + this.cosX0 * cosX * Math.cos(dlon))); p.y = A * (this.cosX0 * sinX - this.sinX0 * cosX * Math.cos(dlon)) + this.y0; } p.x = A * cosX * Math.sin(dlon) + this.x0; } //trace(p.toString()); return p; } //* Stereographic inverse equations--mapping x,y to lat/long function inverse$o(p) { p.x -= this.x0; p.y -= this.y0; var lon, lat, ts, ce, Chi; var rh = Math.sqrt(p.x * p.x + p.y * p.y); if (this.sphere) { var c = 2 * Math.atan(rh / (2 * this.a * this.k0)); lon = this.long0; lat = this.lat0; if (rh <= EPSLN) { p.x = lon; p.y = lat; return p; } lat = Math.asin(Math.cos(c) * this.sinlat0 + p.y * Math.sin(c) * this.coslat0 / rh); if (Math.abs(this.coslat0) < EPSLN) { if (this.lat0 > 0) { lon = adjust_lon(this.long0 + Math.atan2(p.x, - 1 * p.y)); } else { lon = adjust_lon(this.long0 + Math.atan2(p.x, p.y)); } } else { lon = adjust_lon(this.long0 + Math.atan2(p.x * Math.sin(c), rh * this.coslat0 * Math.cos(c) - p.y * this.sinlat0 * Math.sin(c))); } p.x = lon; p.y = lat; return p; } else { if (Math.abs(this.coslat0) <= EPSLN) { if (rh <= EPSLN) { lat = this.lat0; lon = this.long0; p.x = lon; p.y = lat; //trace(p.toString()); return p; } p.x *= this.con; p.y *= this.con; ts = rh * this.cons / (2 * this.a * this.k0); lat = this.con * phi2z(this.e, ts); lon = this.con * adjust_lon(this.con * this.long0 + Math.atan2(p.x, - 1 * p.y)); } else { ce = 2 * Math.atan(rh * this.cosX0 / (2 * this.a * this.k0 * this.ms1)); lon = this.long0; if (rh <= EPSLN) { Chi = this.X0; } else { Chi = Math.asin(Math.cos(ce) * this.sinX0 + p.y * Math.sin(ce) * this.cosX0 / rh); lon = adjust_lon(this.long0 + Math.atan2(p.x * Math.sin(ce), rh * this.cosX0 * Math.cos(ce) - p.y * this.sinX0 * Math.sin(ce))); } lat = -1 * phi2z(this.e, Math.tan(0.5 * (HALF_PI + Chi))); } } p.x = lon; p.y = lat; //trace(p.toString()); return p; } var names$o = ["stere", "Stereographic_South_Pole", "Polar Stereographic (variant B)", "Polar_Stereographic"]; var stere = { init: init$o, forward: forward$o, inverse: inverse$o, names: names$o, ssfn_: ssfn_ }; /* references: Formules et constantes pour le Calcul pour la projection cylindrique conforme à axe oblique et pour la transformation entre des systèmes de référence. http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf */ function init$n() { var phy0 = this.lat0; this.lambda0 = this.long0; var sinPhy0 = Math.sin(phy0); var semiMajorAxis = this.a; var invF = this.rf; var flattening = 1 / invF; var e2 = 2 * flattening - Math.pow(flattening, 2); var e = this.e = Math.sqrt(e2); this.R = this.k0 * semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2)); this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4)); this.b0 = Math.asin(sinPhy0 / this.alpha); var k1 = Math.log(Math.tan(Math.PI / 4 + this.b0 / 2)); var k2 = Math.log(Math.tan(Math.PI / 4 + phy0 / 2)); var k3 = Math.log((1 + e * sinPhy0) / (1 - e * sinPhy0)); this.K = k1 - this.alpha * k2 + this.alpha * e / 2 * k3; } function forward$n(p) { var Sa1 = Math.log(Math.tan(Math.PI / 4 - p.y / 2)); var Sa2 = this.e / 2 * Math.log((1 + this.e * Math.sin(p.y)) / (1 - this.e * Math.sin(p.y))); var S = -this.alpha * (Sa1 + Sa2) + this.K; // spheric latitude var b = 2 * (Math.atan(Math.exp(S)) - Math.PI / 4); // spheric longitude var I = this.alpha * (p.x - this.lambda0); // psoeudo equatorial rotation var rotI = Math.atan(Math.sin(I) / (Math.sin(this.b0) * Math.tan(b) + Math.cos(this.b0) * Math.cos(I))); var rotB = Math.asin(Math.cos(this.b0) * Math.sin(b) - Math.sin(this.b0) * Math.cos(b) * Math.cos(I)); p.y = this.R / 2 * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB))) + this.y0; p.x = this.R * rotI + this.x0; return p; } function inverse$n(p) { var Y = p.x - this.x0; var X = p.y - this.y0; var rotI = Y / this.R; var rotB = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4); var b = Math.asin(Math.cos(this.b0) * Math.sin(rotB) + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI)); var I = Math.atan(Math.sin(rotI) / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0) * Math.tan(rotB))); var lambda = this.lambda0 + I / this.alpha; var S = 0; var phy = b; var prevPhy = -1000; var iteration = 0; while (Math.abs(phy - prevPhy) > 0.0000001) { if (++iteration > 20) { //...reportError("omercFwdInfinity"); return; } //S = Math.log(Math.tan(Math.PI / 4 + phy / 2)); S = 1 / this.alpha * (Math.log(Math.tan(Math.PI / 4 + b / 2)) - this.K) + this.e * Math.log(Math.tan(Math.PI / 4 + Math.asin(this.e * Math.sin(phy)) / 2)); prevPhy = phy; phy = 2 * Math.atan(Math.exp(S)) - Math.PI / 2; } p.x = lambda; p.y = phy; return p; } var names$n = ["somerc"]; var somerc = { init: init$n, forward: forward$n, inverse: inverse$n, names: names$n }; var TOL = 1e-7; function isTypeA(P) { var typeAProjections = ['Hotine_Oblique_Mercator','Hotine_Oblique_Mercator_Azimuth_Natural_Origin']; var projectionName = typeof P.PROJECTION === "object" ? Object.keys(P.PROJECTION)[0] : P.PROJECTION; return 'no_uoff' in P || 'no_off' in P || typeAProjections.indexOf(projectionName) !== -1; } /* Initialize the Oblique Mercator projection ------------------------------------------*/ function init$m() { var con, com, cosph0, D, F, H, L, sinph0, p, J, gamma = 0, gamma0, lamc = 0, lam1 = 0, lam2 = 0, phi1 = 0, phi2 = 0, alpha_c = 0; // only Type A uses the no_off or no_uoff property // https://github.com/OSGeo/proj.4/issues/104 this.no_off = isTypeA(this); this.no_rot = 'no_rot' in this; var alp = false; if ("alpha" in this) { alp = true; } var gam = false; if ("rectified_grid_angle" in this) { gam = true; } if (alp) { alpha_c = this.alpha; } if (gam) { gamma = (this.rectified_grid_angle * D2R$1); } if (alp || gam) { lamc = this.longc; } else { lam1 = this.long1; phi1 = this.lat1; lam2 = this.long2; phi2 = this.lat2; if (Math.abs(phi1 - phi2) <= TOL || (con = Math.abs(phi1)) <= TOL || Math.abs(con - HALF_PI) <= TOL || Math.abs(Math.abs(this.lat0) - HALF_PI) <= TOL || Math.abs(Math.abs(phi2) - HALF_PI) <= TOL) { throw new Error(); } } var one_es = 1.0 - this.es; com = Math.sqrt(one_es); if (Math.abs(this.lat0) > EPSLN) { sinph0 = Math.sin(this.lat0); cosph0 = Math.cos(this.lat0); con = 1 - this.es * sinph0 * sinph0; this.B = cosph0 * cosph0; this.B = Math.sqrt(1 + this.es * this.B * this.B / one_es); this.A = this.B * this.k0 * com / con; D = this.B * com / (cosph0 * Math.sqrt(con)); F = D * D -1; if (F <= 0) { F = 0; } else { F = Math.sqrt(F); if (this.lat0 < 0) { F = -F; } } this.E = F += D; this.E *= Math.pow(tsfnz(this.e, this.lat0, sinph0), this.B); } else { this.B = 1 / com; this.A = this.k0; this.E = D = F = 1; } if (alp || gam) { if (alp) { gamma0 = Math.asin(Math.sin(alpha_c) / D); if (!gam) { gamma = alpha_c; } } else { gamma0 = gamma; alpha_c = Math.asin(D * Math.sin(gamma0)); } this.lam0 = lamc - Math.asin(0.5 * (F - 1 / F) * Math.tan(gamma0)) / this.B; } else { H = Math.pow(tsfnz(this.e, phi1, Math.sin(phi1)), this.B); L = Math.pow(tsfnz(this.e, phi2, Math.sin(phi2)), this.B); F = this.E / H; p = (L - H) / (L + H); J = this.E * this.E; J = (J - L * H) / (J + L * H); con = lam1 - lam2; if (con < -Math.pi) { lam2 -=TWO_PI; } else if (con > Math.pi) { lam2 += TWO_PI; } this.lam0 = adjust_lon(0.5 * (lam1 + lam2) - Math.atan(J * Math.tan(0.5 * this.B * (lam1 - lam2)) / p) / this.B); gamma0 = Math.atan(2 * Math.sin(this.B * adjust_lon(lam1 - this.lam0)) / (F - 1 / F)); gamma = alpha_c = Math.asin(D * Math.sin(gamma0)); } this.singam = Math.sin(gamma0); this.cosgam = Math.cos(gamma0); this.sinrot = Math.sin(gamma); this.cosrot = Math.cos(gamma); this.rB = 1 / this.B; this.ArB = this.A * this.rB; this.BrA = 1 / this.ArB; this.A * this.B; if (this.no_off) { this.u_0 = 0; } else { this.u_0 = Math.abs(this.ArB * Math.atan(Math.sqrt(D * D - 1) / Math.cos(alpha_c))); if (this.lat0 < 0) { this.u_0 = - this.u_0; } } F = 0.5 * gamma0; this.v_pole_n = this.ArB * Math.log(Math.tan(FORTPI - F)); this.v_pole_s = this.ArB * Math.log(Math.tan(FORTPI + F)); } /* Oblique Mercator forward equations--mapping lat,long to x,y ----------------------------------------------------------*/ function forward$m(p) { var coords = {}; var S, T, U, V, W, temp, u, v; p.x = p.x - this.lam0; if (Math.abs(Math.abs(p.y) - HALF_PI) > EPSLN) { W = this.E / Math.pow(tsfnz(this.e, p.y, Math.sin(p.y)), this.B); temp = 1 / W; S = 0.5 * (W - temp); T = 0.5 * (W + temp); V = Math.sin(this.B * p.x); U = (S * this.singam - V * this.cosgam) / T; if (Math.abs(Math.abs(U) - 1.0) < EPSLN) { throw new Error(); } v = 0.5 * this.ArB * Math.log((1 - U)/(1 + U)); temp = Math.cos(this.B * p.x); if (Math.abs(temp) < TOL) { u = this.A * p.x; } else { u = this.ArB * Math.atan2((S * this.cosgam + V * this.singam), temp); } } else { v = p.y > 0 ? this.v_pole_n : this.v_pole_s; u = this.ArB * p.y; } if (this.no_rot) { coords.x = u; coords.y = v; } else { u -= this.u_0; coords.x = v * this.cosrot + u * this.sinrot; coords.y = u * this.cosrot - v * this.sinrot; } coords.x = (this.a * coords.x + this.x0); coords.y = (this.a * coords.y + this.y0); return coords; } function inverse$m(p) { var u, v, Qp, Sp, Tp, Vp, Up; var coords = {}; p.x = (p.x - this.x0) * (1.0 / this.a); p.y = (p.y - this.y0) * (1.0 / this.a); if (this.no_rot) { v = p.y; u = p.x; } else { v = p.x * this.cosrot - p.y * this.sinrot; u = p.y * this.cosrot + p.x * this.sinrot + this.u_0; } Qp = Math.exp(-this.BrA * v); Sp = 0.5 * (Qp - 1 / Qp); Tp = 0.5 * (Qp + 1 / Qp); Vp = Math.sin(this.BrA * u); Up = (Vp * this.cosgam + Sp * this.singam) / Tp; if (Math.abs(Math.abs(Up) - 1) < EPSLN) { coords.x = 0; coords.y = Up < 0 ? -HALF_PI : HALF_PI; } else { coords.y = this.E / Math.sqrt((1 + Up) / (1 - Up)); coords.y = phi2z(this.e, Math.pow(coords.y, 1 / this.B)); if (coords.y === Infinity) { throw new Error(); } coords.x = -this.rB * Math.atan2((Sp * this.cosgam - Vp * this.singam), Math.cos(this.BrA * u)); } coords.x += this.lam0; return coords; } var names$m = ["Hotine_Oblique_Mercator", "Hotine Oblique Mercator", "Hotine_Oblique_Mercator_Azimuth_Natural_Origin", "Hotine_Oblique_Mercator_Two_Point_Natural_Origin", "Hotine_Oblique_Mercator_Azimuth_Center", "Oblique_Mercator", "omerc"]; var omerc = { init: init$m, forward: forward$m, inverse: inverse$m, names: names$m }; function init$l() { //double lat0; /* the reference latitude */ //double long0; /* the reference longitude */ //double lat1; /* first standard parallel */ //double lat2; /* second standard parallel */ //double r_maj; /* major axis */ //double r_min; /* minor axis */ //double false_east; /* x offset in meters */ //double false_north; /* y offset in meters */ //the above value can be set with proj4.defs //example: proj4.defs("EPSG:2154","+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"); if (!this.lat2) { this.lat2 = this.lat1; } //if lat2 is not defined if (!this.k0) { this.k0 = 1; } this.x0 = this.x0 || 0; this.y0 = this.y0 || 0; // Standard Parallels cannot be equal and on opposite sides of the equator if (Math.abs(this.lat1 + this.lat2) < EPSLN) { return; } var temp = this.b / this.a; this.e = Math.sqrt(1 - temp * temp); var sin1 = Math.sin(this.lat1); var cos1 = Math.cos(this.lat1); var ms1 = msfnz(this.e, sin1, cos1); var ts1 = tsfnz(this.e, this.lat1, sin1); var sin2 = Math.sin(this.lat2); var cos2 = Math.cos(this.lat2); var ms2 = msfnz(this.e, sin2, cos2); var ts2 = tsfnz(this.e, this.lat2, sin2); var ts0 = tsfnz(this.e, this.lat0, Math.sin(this.lat0)); if (Math.abs(this.lat1 - this.lat2) > EPSLN) { this.ns = Math.log(ms1 / ms2) / Math.log(ts1 / ts2); } else { this.ns = sin1; } if (isNaN(this.ns)) { this.ns = sin1; } this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns)); this.rh = this.a * this.f0 * Math.pow(ts0, this.ns); if (!this.title) { this.title = "Lambert Conformal Conic"; } } // Lambert Conformal conic forward equations--mapping lat,long to x,y // ----------------------------------------------------------------- function forward$l(p) { var lon = p.x; var lat = p.y; // singular cases : if (Math.abs(2 * Math.abs(lat) - Math.PI) <= EPSLN) { lat = sign(lat) * (HALF_PI - 2 * EPSLN); } var con = Math.abs(Math.abs(lat) - HALF_PI); var ts, rh1; if (con > EPSLN) { ts = tsfnz(this.e, lat, Math.sin(lat)); rh1 = this.a * this.f0 * Math.pow(ts, this.ns); } else { con = lat * this.ns; if (con <= 0) { return null; } rh1 = 0; } var theta = this.ns * adjust_lon(lon - this.long0); p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0; p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0; return p; } // Lambert Conformal Conic inverse equations--mapping x,y to lat/long // ----------------------------------------------------------------- function inverse$l(p) { var rh1, con, ts; var lat, lon; var x = (p.x - this.x0) / this.k0; var y = (this.rh - (p.y - this.y0) / this.k0); if (this.ns > 0) { rh1 = Math.sqrt(x * x + y * y); con = 1; } else { rh1 = -Math.sqrt(x * x + y * y); con = -1; } var theta = 0; if (rh1 !== 0) { theta = Math.atan2((con * x), (con * y)); } if ((rh1 !== 0) || (this.ns > 0)) { con = 1 / this.ns; ts = Math.pow((rh1 / (this.a * this.f0)), con); lat = phi2z(this.e, ts); if (lat === -9999) { return null; } } else { lat = -HALF_PI; } lon = adjust_lon(theta / this.ns + this.long0); p.x = lon; p.y = lat; return p; } var names$l = [ "Lambert Tangential Conformal Conic Projection", "Lambert_Conformal_Conic", "Lambert_Conformal_Conic_1SP", "Lambert_Conformal_Conic_2SP", "lcc", "Lambert Conic Conformal (1SP)", "Lambert Conic Conformal (2SP)" ]; var lcc = { init: init$l, forward: forward$l, inverse: inverse$l, names: names$l }; function init$k() { this.a = 6377397.155; this.es = 0.006674372230614; this.e = Math.sqrt(this.es); if (!this.lat0) { this.lat0 = 0.863937979737193; } if (!this.long0) { this.long0 = 0.7417649320975901 - 0.308341501185665; } /* if scale not set default to 0.9999 */ if (!this.k0) { this.k0 = 0.9999; } this.s45 = 0.785398163397448; /* 45 */ this.s90 = 2 * this.s45; this.fi0 = this.lat0; this.e2 = this.es; this.e = Math.sqrt(this.e2); this.alfa = Math.sqrt(1 + (this.e2 * Math.pow(Math.cos(this.fi0), 4)) / (1 - this.e2)); this.uq = 1.04216856380474; this.u0 = Math.asin(Math.sin(this.fi0) / this.alfa); this.g = Math.pow((1 + this.e * Math.sin(this.fi0)) / (1 - this.e * Math.sin(this.fi0)), this.alfa * this.e / 2); this.k = Math.tan(this.u0 / 2 + this.s45) / Math.pow(Math.tan(this.fi0 / 2 + this.s45), this.alfa) * this.g; this.k1 = this.k0; this.n0 = this.a * Math.sqrt(1 - this.e2) / (1 - this.e2 * Math.pow(Math.sin(this.fi0), 2)); this.s0 = 1.37008346281555; this.n = Math.sin(this.s0); this.ro0 = this.k1 * this.n0 / Math.tan(this.s0); this.ad = this.s90 - this.uq; } /* ellipsoid */ /* calculate xy from lat/lon */ /* Constants, identical to inverse transform function */ function forward$k(p) { var gfi, u, deltav, s, d, eps, ro; var lon = p.x; var lat = p.y; var delta_lon = adjust_lon(lon - this.long0); /* Transformation */ gfi = Math.pow(((1 + this.e * Math.sin(lat)) / (1 - this.e * Math.sin(lat))), (this.alfa * this.e / 2)); u = 2 * (Math.atan(this.k * Math.pow(Math.tan(lat / 2 + this.s45), this.alfa) / gfi) - this.s45); deltav = -delta_lon * this.alfa; s = Math.asin(Math.cos(this.ad) * Math.sin(u) + Math.sin(this.ad) * Math.cos(u) * Math.cos(deltav)); d = Math.asin(Math.cos(u) * Math.sin(deltav) / Math.cos(s)); eps = this.n * d; ro = this.ro0 * Math.pow(Math.tan(this.s0 / 2 + this.s45), this.n) / Math.pow(Math.tan(s / 2 + this.s45), this.n); p.y = ro * Math.cos(eps) / 1; p.x = ro * Math.sin(eps) / 1; if (!this.czech) { p.y *= -1; p.x *= -1; } return (p); } /* calculate lat/lon from xy */ function inverse$k(p) { var u, deltav, s, d, eps, ro, fi1; var ok; /* Transformation */ /* revert y, x*/ var tmp = p.x; p.x = p.y; p.y = tmp; if (!this.czech) { p.y *= -1; p.x *= -1; } ro = Math.sqrt(p.x * p.x + p.y * p.y); eps = Math.atan2(p.y, p.x); d = eps / Math.sin(this.s0); s = 2 * (Math.atan(Math.pow(this.ro0 / ro, 1 / this.n) * Math.tan(this.s0 / 2 + this.s45)) - this.s45); u = Math.asin(Math.cos(this.ad) * Math.sin(s) - Math.sin(this.ad) * Math.cos(s) * Math.cos(d)); deltav = Math.asin(Math.cos(s) * Math.sin(d) / Math.cos(u)); p.x = this.long0 - deltav / this.alfa; fi1 = u; ok = 0; var iter = 0; do { p.y = 2 * (Math.atan(Math.pow(this.k, - 1 / this.alfa) * Math.pow(Math.tan(u / 2 + this.s45), 1 / this.alfa) * Math.pow((1 + this.e * Math.sin(fi1)) / (1 - this.e * Math.sin(fi1)), this.e / 2)) - this.s45); if (Math.abs(fi1 - p.y) < 0.0000000001) { ok = 1; } fi1 = p.y; iter += 1; } while (ok === 0 && iter < 15); if (iter >= 15) { return null; } return (p); } var names$k = ["Krovak", "krovak"]; var krovak = { init: init$k, forward: forward$k, inverse: inverse$k, names: names$k }; function mlfn(e0, e1, e2, e3, phi) { return (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi)); } function e0fn(x) { return (1 - 0.25 * x * (1 + x / 16 * (3 + 1.25 * x))); } function e1fn(x) { return (0.375 * x * (1 + 0.25 * x * (1 + 0.46875 * x))); } function e2fn(x) { return (0.05859375 * x * x * (1 + 0.75 * x)); } function e3fn(x) { return (x * x * x * (35 / 3072)); } function gN(a, e, sinphi) { var temp = e * sinphi; return a / Math.sqrt(1 - temp * temp); } function adjust_lat(x) { return (Math.abs(x) < HALF_PI) ? x : (x - (sign(x) * Math.PI)); } function imlfn(ml, e0, e1, e2, e3) { var phi; var dphi; phi = ml / e0; for (var i = 0; i < 15; i++) { dphi = (ml - (e0 * phi - e1 * Math.sin(2 * phi) + e2 * Math.sin(4 * phi) - e3 * Math.sin(6 * phi))) / (e0 - 2 * e1 * Math.cos(2 * phi) + 4 * e2 * Math.cos(4 * phi) - 6 * e3 * Math.cos(6 * phi)); phi += dphi; if (Math.abs(dphi) <= 0.0000000001) { return phi; } } //..reportError("IMLFN-CONV:Latitude failed to converge after 15 iterations"); return NaN; } function init$j() { if (!this.sphere) { this.e0 = e0fn(this.es); this.e1 = e1fn(this.es); this.e2 = e2fn(this.es); this.e3 = e3fn(this.es); this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); } } /* Cassini forward equations--mapping lat,long to x,y -----------------------------------------------------------------------*/ function forward$j(p) { /* Forward equations -----------------*/ var x, y; var lam = p.x; var phi = p.y; lam = adjust_lon(lam - this.long0); if (this.sphere) { x = this.a * Math.asin(Math.cos(phi) * Math.sin(lam)); y = this.a * (Math.atan2(Math.tan(phi), Math.cos(lam)) - this.lat0); } else { //ellipsoid var sinphi = Math.sin(phi); var cosphi = Math.cos(phi); var nl = gN(this.a, this.e, sinphi); var tl = Math.tan(phi) * Math.tan(phi); var al = lam * Math.cos(phi); var asq = al * al; var cl = this.es * cosphi * cosphi / (1 - this.es); var ml = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi); x = nl * al * (1 - asq * tl * (1 / 6 - (8 - tl + 8 * cl) * asq / 120)); y = ml - this.ml0 + nl * sinphi / cosphi * asq * (0.5 + (5 - tl + 6 * cl) * asq / 24); } p.x = x + this.x0; p.y = y + this.y0; return p; } /* Inverse equations -----------------*/ function inverse$j(p) { p.x -= this.x0; p.y -= this.y0; var x = p.x / this.a; var y = p.y / this.a; var phi, lam; if (this.sphere) { var dd = y + this.lat0; phi = Math.asin(Math.sin(dd) * Math.cos(x)); lam = Math.atan2(Math.tan(x), Math.cos(dd)); } else { /* ellipsoid */ var ml1 = this.ml0 / this.a + y; var phi1 = imlfn(ml1, this.e0, this.e1, this.e2, this.e3); if (Math.abs(Math.abs(phi1) - HALF_PI) <= EPSLN) { p.x = this.long0; p.y = HALF_PI; if (y < 0) { p.y *= -1; } return p; } var nl1 = gN(this.a, this.e, Math.sin(phi1)); var rl1 = nl1 * nl1 * nl1 / this.a / this.a * (1 - this.es); var tl1 = Math.pow(Math.tan(phi1), 2); var dl = x * this.a / nl1; var dsq = dl * dl; phi = phi1 - nl1 * Math.tan(phi1) / rl1 * dl * dl * (0.5 - (1 + 3 * tl1) * dl * dl / 24); lam = dl * (1 - dsq * (tl1 / 3 + (1 + 3 * tl1) * tl1 * dsq / 15)) / Math.cos(phi1); } p.x = adjust_lon(lam + this.long0); p.y = adjust_lat(phi); return p; } var names$j = ["Cassini", "Cassini_Soldner", "cass"]; var cass = { init: init$j, forward: forward$j, inverse: inverse$j, names: names$j }; function qsfnz(eccent, sinphi) { var con; if (eccent > 1.0e-7) { con = eccent * sinphi; return ((1 - eccent * eccent) * (sinphi / (1 - con * con) - (0.5 / eccent) * Math.log((1 - con) / (1 + con)))); } else { return (2 * sinphi); } } /* reference "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. */ var S_POLE = 1; var N_POLE = 2; var EQUIT = 3; var OBLIQ = 4; /* Initialize the Lambert Azimuthal Equal Area projection ------------------------------------------------------*/ function init$i() { var t = Math.abs(this.lat0); if (Math.abs(t - HALF_PI) < EPSLN) { this.mode = this.lat0 < 0 ? this.S_POLE : this.N_POLE; } else if (Math.abs(t) < EPSLN) { this.mode = this.EQUIT; } else { this.mode = this.OBLIQ; } if (this.es > 0) { var sinphi; this.qp = qsfnz(this.e, 1); this.mmf = 0.5 / (1 - this.es); this.apa = authset(this.es); switch (this.mode) { case this.N_POLE: this.dd = 1; break; case this.S_POLE: this.dd = 1; break; case this.EQUIT: this.rq = Math.sqrt(0.5 * this.qp); this.dd = 1 / this.rq; this.xmf = 1; this.ymf = 0.5 * this.qp; break; case this.OBLIQ: this.rq = Math.sqrt(0.5 * this.qp); sinphi = Math.sin(this.lat0); this.sinb1 = qsfnz(this.e, sinphi) / this.qp; this.cosb1 = Math.sqrt(1 - this.sinb1 * this.sinb1); this.dd = Math.cos(this.lat0) / (Math.sqrt(1 - this.es * sinphi * sinphi) * this.rq * this.cosb1); this.ymf = (this.xmf = this.rq) / this.dd; this.xmf *= this.dd; break; } } else { if (this.mode === this.OBLIQ) { this.sinph0 = Math.sin(this.lat0); this.cosph0 = Math.cos(this.lat0); } } } /* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y -----------------------------------------------------------------------*/ function forward$i(p) { /* Forward equations -----------------*/ var x, y, coslam, sinlam, sinphi, q, sinb, cosb, b, cosphi; var lam = p.x; var phi = p.y; lam = adjust_lon(lam - this.long0); if (this.sphere) { sinphi = Math.sin(phi); cosphi = Math.cos(phi); coslam = Math.cos(lam); if (this.mode === this.OBLIQ || this.mode === this.EQUIT) { y = (this.mode === this.EQUIT) ? 1 + cosphi * coslam : 1 + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam; if (y <= EPSLN) { return null; } y = Math.sqrt(2 / y); x = y * cosphi * Math.sin(lam); y *= (this.mode === this.EQUIT) ? sinphi : this.cosph0 * sinphi - this.sinph0 * cosphi * coslam; } else if (this.mode === this.N_POLE || this.mode === this.S_POLE) { if (this.mode === this.N_POLE) { coslam = -coslam; } if (Math.abs(phi + this.lat0) < EPSLN) { return null; } y = FORTPI - phi * 0.5; y = 2 * ((this.mode === this.S_POLE) ? Math.cos(y) : Math.sin(y)); x = y * Math.sin(lam); y *= coslam; } } else { sinb = 0; cosb = 0; b = 0; coslam = Math.cos(lam); sinlam = Math.sin(lam); sinphi = Math.sin(phi); q = qsfnz(this.e, sinphi); if (this.mode === this.OBLIQ || this.mode === this.EQUIT) { sinb = q / this.qp; cosb = Math.sqrt(1 - sinb * sinb); } switch (this.mode) { case this.OBLIQ: b = 1 + this.sinb1 * sinb + this.cosb1 * cosb * coslam; break; case this.EQUIT: b = 1 + cosb * coslam; break; case this.N_POLE: b = HALF_PI + phi; q = this.qp - q; break; case this.S_POLE: b = phi - HALF_PI; q = this.qp + q; break; } if (Math.abs(b) < EPSLN) { return null; } switch (this.mode) { case this.OBLIQ: case this.EQUIT: b = Math.sqrt(2 / b); if (this.mode === this.OBLIQ) { y = this.ymf * b * (this.cosb1 * sinb - this.sinb1 * cosb * coslam); } else { y = (b = Math.sqrt(2 / (1 + cosb * coslam))) * sinb * this.ymf; } x = this.xmf * b * cosb * sinlam; break; case this.N_POLE: case this.S_POLE: if (q >= 0) { x = (b = Math.sqrt(q)) * sinlam; y = coslam * ((this.mode === this.S_POLE) ? b : -b); } else { x = y = 0; } break; } } p.x = this.a * x + this.x0; p.y = this.a * y + this.y0; return p; } /* Inverse equations -----------------*/ function inverse$i(p) { p.x -= this.x0; p.y -= this.y0; var x = p.x / this.a; var y = p.y / this.a; var lam, phi, cCe, sCe, q, rho, ab; if (this.sphere) { var cosz = 0, rh, sinz = 0; rh = Math.sqrt(x * x + y * y); phi = rh * 0.5; if (phi > 1) { return null; } phi = 2 * Math.asin(phi); if (this.mode === this.OBLIQ || this.mode === this.EQUIT) { sinz = Math.sin(phi); cosz = Math.cos(phi); } switch (this.mode) { case this.EQUIT: phi = (Math.abs(rh) <= EPSLN) ? 0 : Math.asin(y * sinz / rh); x *= sinz; y = cosz * rh; break; case this.OBLIQ: phi = (Math.abs(rh) <= EPSLN) ? this.lat0 : Math.asin(cosz * this.sinph0 + y * sinz * this.cosph0 / rh); x *= sinz * this.cosph0; y = (cosz - Math.sin(phi) * this.sinph0) * rh; break; case this.N_POLE: y = -y; phi = HALF_PI - phi; break; case this.S_POLE: phi -= HALF_PI; break; } lam = (y === 0 && (this.mode === this.EQUIT || this.mode === this.OBLIQ)) ? 0 : Math.atan2(x, y); } else { ab = 0; if (this.mode === this.OBLIQ || this.mode === this.EQUIT) { x /= this.dd; y *= this.dd; rho = Math.sqrt(x * x + y * y); if (rho < EPSLN) { p.x = this.long0; p.y = this.lat0; return p; } sCe = 2 * Math.asin(0.5 * rho / this.rq); cCe = Math.cos(sCe); x *= (sCe = Math.sin(sCe)); if (this.mode === this.OBLIQ) { ab = cCe * this.sinb1 + y * sCe * this.cosb1 / rho; q = this.qp * ab; y = rho * this.cosb1 * cCe - y * this.sinb1 * sCe; } else { ab = y * sCe / rho; q = this.qp * ab; y = rho * cCe; } } else if (this.mode === this.N_POLE || this.mode === this.S_POLE) { if (this.mode === this.N_POLE) { y = -y; } q = (x * x + y * y); if (!q) { p.x = this.long0; p.y = this.lat0; return p; } ab = 1 - q / this.qp; if (this.mode === this.S_POLE) { ab = -ab; } } lam = Math.atan2(x, y); phi = authlat(Math.asin(ab), this.apa); } p.x = adjust_lon(this.long0 + lam); p.y = phi; return p; } /* determine latitude from authalic latitude */ var P00 = 0.33333333333333333333; var P01 = 0.17222222222222222222; var P02 = 0.10257936507936507936; var P10 = 0.06388888888888888888; var P11 = 0.06640211640211640211; var P20 = 0.01641501294219154443; function authset(es) { var t; var APA = []; APA[0] = es * P00; t = es * es; APA[0] += t * P01; APA[1] = t * P10; t *= es; APA[0] += t * P02; APA[1] += t * P11; APA[2] = t * P20; return APA; } function authlat(beta, APA) { var t = beta + beta; return (beta + APA[0] * Math.sin(t) + APA[1] * Math.sin(t + t) + APA[2] * Math.sin(t + t + t)); } var names$i = ["Lambert Azimuthal Equal Area", "Lambert_Azimuthal_Equal_Area", "laea"]; var laea = { init: init$i, forward: forward$i, inverse: inverse$i, names: names$i, S_POLE: S_POLE, N_POLE: N_POLE, EQUIT: EQUIT, OBLIQ: OBLIQ }; function asinz(x) { if (Math.abs(x) > 1) { x = (x > 1) ? 1 : -1; } return Math.asin(x); } function init$h() { if (Math.abs(this.lat1 + this.lat2) < EPSLN) { return; } this.temp = this.b / this.a; this.es = 1 - Math.pow(this.temp, 2); this.e3 = Math.sqrt(this.es); this.sin_po = Math.sin(this.lat1); this.cos_po = Math.cos(this.lat1); this.t1 = this.sin_po; this.con = this.sin_po; this.ms1 = msfnz(this.e3, this.sin_po, this.cos_po); this.qs1 = qsfnz(this.e3, this.sin_po); this.sin_po = Math.sin(this.lat2); this.cos_po = Math.cos(this.lat2); this.t2 = this.sin_po; this.ms2 = msfnz(this.e3, this.sin_po, this.cos_po); this.qs2 = qsfnz(this.e3, this.sin_po); this.sin_po = Math.sin(this.lat0); this.cos_po = Math.cos(this.lat0); this.t3 = this.sin_po; this.qs0 = qsfnz(this.e3, this.sin_po); if (Math.abs(this.lat1 - this.lat2) > EPSLN) { this.ns0 = (this.ms1 * this.ms1 - this.ms2 * this.ms2) / (this.qs2 - this.qs1); } else { this.ns0 = this.con; } this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1; this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0) / this.ns0; } /* Albers Conical Equal Area forward equations--mapping lat,long to x,y -------------------------------------------------------------------*/ function forward$h(p) { var lon = p.x; var lat = p.y; this.sin_phi = Math.sin(lat); this.cos_phi = Math.cos(lat); var qs = qsfnz(this.e3, this.sin_phi); var rh1 = this.a * Math.sqrt(this.c - this.ns0 * qs) / this.ns0; var theta = this.ns0 * adjust_lon(lon - this.long0); var x = rh1 * Math.sin(theta) + this.x0; var y = this.rh - rh1 * Math.cos(theta) + this.y0; p.x = x; p.y = y; return p; } function inverse$h(p) { var rh1, qs, con, theta, lon, lat; p.x -= this.x0; p.y = this.rh - p.y + this.y0; if (this.ns0 >= 0) { rh1 = Math.sqrt(p.x * p.x + p.y * p.y); con = 1; } else { rh1 = -Math.sqrt(p.x * p.x + p.y * p.y); con = -1; } theta = 0; if (rh1 !== 0) { theta = Math.atan2(con * p.x, con * p.y); } con = rh1 * this.ns0 / this.a; if (this.sphere) { lat = Math.asin((this.c - con * con) / (2 * this.ns0)); } else { qs = (this.c - con * con) / this.ns0; lat = this.phi1z(this.e3, qs); } lon = adjust_lon(theta / this.ns0 + this.long0); p.x = lon; p.y = lat; return p; } /* Function to compute phi1, the latitude for the inverse of the Albers Conical Equal-Area projection. -------------------------------------------*/ function phi1z(eccent, qs) { var sinphi, cosphi, con, com, dphi; var phi = asinz(0.5 * qs); if (eccent < EPSLN) { return phi; } var eccnts = eccent * eccent; for (var i = 1; i <= 25; i++) { sinphi = Math.sin(phi); cosphi = Math.cos(phi); con = eccent * sinphi; com = 1 - con * con; dphi = 0.5 * com * com / cosphi * (qs / (1 - eccnts) - sinphi / com + 0.5 / eccent * Math.log((1 - con) / (1 + con))); phi = phi + dphi; if (Math.abs(dphi) <= 1e-7) { return phi; } } return null; } var names$h = ["Albers_Conic_Equal_Area", "Albers", "aea"]; var aea = { init: init$h, forward: forward$h, inverse: inverse$h, names: names$h, phi1z: phi1z }; /* reference: Wolfram Mathworld "Gnomonic Projection" http://mathworld.wolfram.com/GnomonicProjection.html Accessed: 12th November 2009 */ function init$g() { /* Place parameters in static storage for common use -------------------------------------------------*/ this.sin_p14 = Math.sin(this.lat0); this.cos_p14 = Math.cos(this.lat0); // Approximation for projecting points to the horizon (infinity) this.infinity_dist = 1000 * this.a; this.rc = 1; } /* Gnomonic forward equations--mapping lat,long to x,y ---------------------------------------------------*/ function forward$g(p) { var sinphi, cosphi; /* sin and cos value */ var dlon; /* delta longitude value */ var coslon; /* cos of longitude */ var ksp; /* scale factor */ var g; var x, y; var lon = p.x; var lat = p.y; /* Forward equations -----------------*/ dlon = adjust_lon(lon - this.long0); sinphi = Math.sin(lat); cosphi = Math.cos(lat); coslon = Math.cos(dlon); g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon; ksp = 1; if ((g > 0) || (Math.abs(g) <= EPSLN)) { x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon) / g; y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon) / g; } else { // Point is in the opposing hemisphere and is unprojectable // We still need to return a reasonable point, so we project // to infinity, on a bearing // equivalent to the northern hemisphere equivalent // This is a reasonable approximation for short shapes and lines that // straddle the horizon. x = this.x0 + this.infinity_dist * cosphi * Math.sin(dlon); y = this.y0 + this.infinity_dist * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon); } p.x = x; p.y = y; return p; } function inverse$g(p) { var rh; /* Rho */ var sinc, cosc; var c; var lon, lat; /* Inverse equations -----------------*/ p.x = (p.x - this.x0) / this.a; p.y = (p.y - this.y0) / this.a; p.x /= this.k0; p.y /= this.k0; if ((rh = Math.sqrt(p.x * p.x + p.y * p.y))) { c = Math.atan2(rh, this.rc); sinc = Math.sin(c); cosc = Math.cos(c); lat = asinz(cosc * this.sin_p14 + (p.y * sinc * this.cos_p14) / rh); lon = Math.atan2(p.x * sinc, rh * this.cos_p14 * cosc - p.y * this.sin_p14 * sinc); lon = adjust_lon(this.long0 + lon); } else { lat = this.phic0; lon = 0; } p.x = lon; p.y = lat; return p; } var names$g = ["gnom"]; var gnom = { init: init$g, forward: forward$g, inverse: inverse$g, names: names$g }; function iqsfnz(eccent, q) { var temp = 1 - (1 - eccent * eccent) / (2 * eccent) * Math.log((1 - eccent) / (1 + eccent)); if (Math.abs(Math.abs(q) - temp) < 1.0E-6) { if (q < 0) { return (-1 * HALF_PI); } else { return HALF_PI; } } //var phi = 0.5* q/(1-eccent*eccent); var phi = Math.asin(0.5 * q); var dphi; var sin_phi; var cos_phi; var con; for (var i = 0; i < 30; i++) { sin_phi = Math.sin(phi); cos_phi = Math.cos(phi); con = eccent * sin_phi; dphi = Math.pow(1 - con * con, 2) / (2 * cos_phi) * (q / (1 - eccent * eccent) - sin_phi / (1 - con * con) + 0.5 / eccent * Math.log((1 - con) / (1 + con))); phi += dphi; if (Math.abs(dphi) <= 0.0000000001) { return phi; } } //console.log("IQSFN-CONV:Latitude failed to converge after 30 iterations"); return NaN; } /* reference: "Cartographic Projection Procedures for the UNIX Environment- A User's Manual" by Gerald I. Evenden, USGS Open File Report 90-284and Release 4 Interim Reports (2003) */ function init$f() { //no-op if (!this.sphere) { this.k0 = msfnz(this.e, Math.sin(this.lat_ts), Math.cos(this.lat_ts)); } } /* Cylindrical Equal Area forward equations--mapping lat,long to x,y ------------------------------------------------------------*/ function forward$f(p) { var lon = p.x; var lat = p.y; var x, y; /* Forward equations -----------------*/ var dlon = adjust_lon(lon - this.long0); if (this.sphere) { x = this.x0 + this.a * dlon * Math.cos(this.lat_ts); y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts); } else { var qs = qsfnz(this.e, Math.sin(lat)); x = this.x0 + this.a * this.k0 * dlon; y = this.y0 + this.a * qs * 0.5 / this.k0; } p.x = x; p.y = y; return p; } /* Cylindrical Equal Area inverse equations--mapping x,y to lat/long ------------------------------------------------------------*/ function inverse$f(p) { p.x -= this.x0; p.y -= this.y0; var lon, lat; if (this.sphere) { lon = adjust_lon(this.long0 + (p.x / this.a) / Math.cos(this.lat_ts)); lat = Math.asin((p.y / this.a) * Math.cos(this.lat_ts)); } else { lat = iqsfnz(this.e, 2 * p.y * this.k0 / this.a); lon = adjust_lon(this.long0 + p.x / (this.a * this.k0)); } p.x = lon; p.y = lat; return p; } var names$f = ["cea"]; var cea = { init: init$f, forward: forward$f, inverse: inverse$f, names: names$f }; function init$e() { this.x0 = this.x0 || 0; this.y0 = this.y0 || 0; this.lat0 = this.lat0 || 0; this.long0 = this.long0 || 0; this.lat_ts = this.lat_ts || 0; this.title = this.title || "Equidistant Cylindrical (Plate Carre)"; this.rc = Math.cos(this.lat_ts); } // forward equations--mapping lat,long to x,y // ----------------------------------------------------------------- function forward$e(p) { var lon = p.x; var lat = p.y; var dlon = adjust_lon(lon - this.long0); var dlat = adjust_lat(lat - this.lat0); p.x = this.x0 + (this.a * dlon * this.rc); p.y = this.y0 + (this.a * dlat); return p; } // inverse equations--mapping x,y to lat/long // ----------------------------------------------------------------- function inverse$e(p) { var x = p.x; var y = p.y; p.x = adjust_lon(this.long0 + ((x - this.x0) / (this.a * this.rc))); p.y = adjust_lat(this.lat0 + ((y - this.y0) / (this.a))); return p; } var names$e = ["Equirectangular", "Equidistant_Cylindrical", "eqc"]; var eqc = { init: init$e, forward: forward$e, inverse: inverse$e, names: names$e }; var MAX_ITER$1 = 20; function init$d() { /* Place parameters in static storage for common use -------------------------------------------------*/ this.temp = this.b / this.a; this.es = 1 - Math.pow(this.temp, 2); // devait etre dans tmerc.js mais n y est pas donc je commente sinon retour de valeurs nulles this.e = Math.sqrt(this.es); this.e0 = e0fn(this.es); this.e1 = e1fn(this.es); this.e2 = e2fn(this.es); this.e3 = e3fn(this.es); this.ml0 = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); //si que des zeros le calcul ne se fait pas } /* Polyconic forward equations--mapping lat,long to x,y ---------------------------------------------------*/ function forward$d(p) { var lon = p.x; var lat = p.y; var x, y, el; var dlon = adjust_lon(lon - this.long0); el = dlon * Math.sin(lat); if (this.sphere) { if (Math.abs(lat) <= EPSLN) { x = this.a * dlon; y = -1 * this.a * this.lat0; } else { x = this.a * Math.sin(el) / Math.tan(lat); y = this.a * (adjust_lat(lat - this.lat0) + (1 - Math.cos(el)) / Math.tan(lat)); } } else { if (Math.abs(lat) <= EPSLN) { x = this.a * dlon; y = -1 * this.ml0; } else { var nl = gN(this.a, this.e, Math.sin(lat)) / Math.tan(lat); x = nl * Math.sin(el); y = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, lat) - this.ml0 + nl * (1 - Math.cos(el)); } } p.x = x + this.x0; p.y = y + this.y0; return p; } /* Inverse equations -----------------*/ function inverse$d(p) { var lon, lat, x, y, i; var al, bl; var phi, dphi; x = p.x - this.x0; y = p.y - this.y0; if (this.sphere) { if (Math.abs(y + this.a * this.lat0) <= EPSLN) { lon = adjust_lon(x / this.a + this.long0); lat = 0; } else { al = this.lat0 + y / this.a; bl = x * x / this.a / this.a + al * al; phi = al; var tanphi; for (i = MAX_ITER$1; i; --i) { tanphi = Math.tan(phi); dphi = -1 * (al * (phi * tanphi + 1) - phi - 0.5 * (phi * phi + bl) * tanphi) / ((phi - al) / tanphi - 1); phi += dphi; if (Math.abs(dphi) <= EPSLN) { lat = phi; break; } } lon = adjust_lon(this.long0 + (Math.asin(x * Math.tan(phi) / this.a)) / Math.sin(lat)); } } else { if (Math.abs(y + this.ml0) <= EPSLN) { lat = 0; lon = adjust_lon(this.long0 + x / this.a); } else { al = (this.ml0 + y) / this.a; bl = x * x / this.a / this.a + al * al; phi = al; var cl, mln, mlnp, ma; var con; for (i = MAX_ITER$1; i; --i) { con = this.e * Math.sin(phi); cl = Math.sqrt(1 - con * con) * Math.tan(phi); mln = this.a * mlfn(this.e0, this.e1, this.e2, this.e3, phi); mlnp = this.e0 - 2 * this.e1 * Math.cos(2 * phi) + 4 * this.e2 * Math.cos(4 * phi) - 6 * this.e3 * Math.cos(6 * phi); ma = mln / this.a; dphi = (al * (cl * ma + 1) - ma - 0.5 * cl * (ma * ma + bl)) / (this.es * Math.sin(2 * phi) * (ma * ma + bl - 2 * al * ma) / (4 * cl) + (al - ma) * (cl * mlnp - 2 / Math.sin(2 * phi)) - mlnp); phi -= dphi; if (Math.abs(dphi) <= EPSLN) { lat = phi; break; } } //lat=phi4z(this.e,this.e0,this.e1,this.e2,this.e3,al,bl,0,0); cl = Math.sqrt(1 - this.es * Math.pow(Math.sin(lat), 2)) * Math.tan(lat); lon = adjust_lon(this.long0 + Math.asin(x * cl / this.a) / Math.sin(lat)); } } p.x = lon; p.y = lat; return p; } var names$d = ["Polyconic", "poly"]; var poly = { init: init$d, forward: forward$d, inverse: inverse$d, names: names$d }; function init$c() { this.A = []; this.A[1] = 0.6399175073; this.A[2] = -0.1358797613; this.A[3] = 0.063294409; this.A[4] = -0.02526853; this.A[5] = 0.0117879; this.A[6] = -0.0055161; this.A[7] = 0.0026906; this.A[8] = -0.001333; this.A[9] = 0.00067; this.A[10] = -0.00034; this.B_re = []; this.B_im = []; this.B_re[1] = 0.7557853228; this.B_im[1] = 0; this.B_re[2] = 0.249204646; this.B_im[2] = 0.003371507; this.B_re[3] = -0.001541739; this.B_im[3] = 0.041058560; this.B_re[4] = -0.10162907; this.B_im[4] = 0.01727609; this.B_re[5] = -0.26623489; this.B_im[5] = -0.36249218; this.B_re[6] = -0.6870983; this.B_im[6] = -1.1651967; this.C_re = []; this.C_im = []; this.C_re[1] = 1.3231270439; this.C_im[1] = 0; this.C_re[2] = -0.577245789; this.C_im[2] = -0.007809598; this.C_re[3] = 0.508307513; this.C_im[3] = -0.112208952; this.C_re[4] = -0.15094762; this.C_im[4] = 0.18200602; this.C_re[5] = 1.01418179; this.C_im[5] = 1.64497696; this.C_re[6] = 1.9660549; this.C_im[6] = 2.5127645; this.D = []; this.D[1] = 1.5627014243; this.D[2] = 0.5185406398; this.D[3] = -0.03333098; this.D[4] = -0.1052906; this.D[5] = -0.0368594; this.D[6] = 0.007317; this.D[7] = 0.01220; this.D[8] = 0.00394; this.D[9] = -0.0013; } /** New Zealand Map Grid Forward - long/lat to x/y long/lat in radians */ function forward$c(p) { var n; var lon = p.x; var lat = p.y; var delta_lat = lat - this.lat0; var delta_lon = lon - this.long0; // 1. Calculate d_phi and d_psi ... // and d_lambda // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians. var d_phi = delta_lat / SEC_TO_RAD * 1E-5; var d_lambda = delta_lon; var d_phi_n = 1; // d_phi^0 var d_psi = 0; for (n = 1; n <= 10; n++) { d_phi_n = d_phi_n * d_phi; d_psi = d_psi + this.A[n] * d_phi_n; } // 2. Calculate theta var th_re = d_psi; var th_im = d_lambda; // 3. Calculate z var th_n_re = 1; var th_n_im = 0; // theta^0 var th_n_re1; var th_n_im1; var z_re = 0; var z_im = 0; for (n = 1; n <= 6; n++) { th_n_re1 = th_n_re * th_re - th_n_im * th_im; th_n_im1 = th_n_im * th_re + th_n_re * th_im; th_n_re = th_n_re1; th_n_im = th_n_im1; z_re = z_re + this.B_re[n] * th_n_re - this.B_im[n] * th_n_im; z_im = z_im + this.B_im[n] * th_n_re + this.B_re[n] * th_n_im; } // 4. Calculate easting and northing p.x = (z_im * this.a) + this.x0; p.y = (z_re * this.a) + this.y0; return p; } /** New Zealand Map Grid Inverse - x/y to long/lat */ function inverse$c(p) { var n; var x = p.x; var y = p.y; var delta_x = x - this.x0; var delta_y = y - this.y0; // 1. Calculate z var z_re = delta_y / this.a; var z_im = delta_x / this.a; // 2a. Calculate theta - first approximation gives km accuracy var z_n_re = 1; var z_n_im = 0; // z^0 var z_n_re1; var z_n_im1; var th_re = 0; var th_im = 0; for (n = 1; n <= 6; n++) { z_n_re1 = z_n_re * z_re - z_n_im * z_im; z_n_im1 = z_n_im * z_re + z_n_re * z_im; z_n_re = z_n_re1; z_n_im = z_n_im1; th_re = th_re + this.C_re[n] * z_n_re - this.C_im[n] * z_n_im; th_im = th_im + this.C_im[n] * z_n_re + this.C_re[n] * z_n_im; } // 2b. Iterate to refine the accuracy of the calculation // 0 iterations gives km accuracy // 1 iteration gives m accuracy -- good enough for most mapping applications // 2 iterations bives mm accuracy for (var i = 0; i < this.iterations; i++) { var th_n_re = th_re; var th_n_im = th_im; var th_n_re1; var th_n_im1; var num_re = z_re; var num_im = z_im; for (n = 2; n <= 6; n++) { th_n_re1 = th_n_re * th_re - th_n_im * th_im; th_n_im1 = th_n_im * th_re + th_n_re * th_im; th_n_re = th_n_re1; th_n_im = th_n_im1; num_re = num_re + (n - 1) * (this.B_re[n] * th_n_re - this.B_im[n] * th_n_im); num_im = num_im + (n - 1) * (this.B_im[n] * th_n_re + this.B_re[n] * th_n_im); } th_n_re = 1; th_n_im = 0; var den_re = this.B_re[1]; var den_im = this.B_im[1]; for (n = 2; n <= 6; n++) { th_n_re1 = th_n_re * th_re - th_n_im * th_im; th_n_im1 = th_n_im * th_re + th_n_re * th_im; th_n_re = th_n_re1; th_n_im = th_n_im1; den_re = den_re + n * (this.B_re[n] * th_n_re - this.B_im[n] * th_n_im); den_im = den_im + n * (this.B_im[n] * th_n_re + this.B_re[n] * th_n_im); } // Complex division var den2 = den_re * den_re + den_im * den_im; th_re = (num_re * den_re + num_im * den_im) / den2; th_im = (num_im * den_re - num_re * den_im) / den2; } // 3. Calculate d_phi ... // and d_lambda var d_psi = th_re; var d_lambda = th_im; var d_psi_n = 1; // d_psi^0 var d_phi = 0; for (n = 1; n <= 9; n++) { d_psi_n = d_psi_n * d_psi; d_phi = d_phi + this.D[n] * d_psi_n; } // 4. Calculate latitude and longitude // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians. var lat = this.lat0 + (d_phi * SEC_TO_RAD * 1E5); var lon = this.long0 + d_lambda; p.x = lon; p.y = lat; return p; } var names$c = ["New_Zealand_Map_Grid", "nzmg"]; var nzmg = { init: init$c, forward: forward$c, inverse: inverse$c, names: names$c }; /* reference "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. */ /* Initialize the Miller Cylindrical projection -------------------------------------------*/ function init$b() { //no-op } /* Miller Cylindrical forward equations--mapping lat,long to x,y ------------------------------------------------------------*/ function forward$b(p) { var lon = p.x; var lat = p.y; /* Forward equations -----------------*/ var dlon = adjust_lon(lon - this.long0); var x = this.x0 + this.a * dlon; var y = this.y0 + this.a * Math.log(Math.tan((Math.PI / 4) + (lat / 2.5))) * 1.25; p.x = x; p.y = y; return p; } /* Miller Cylindrical inverse equations--mapping x,y to lat/long ------------------------------------------------------------*/ function inverse$b(p) { p.x -= this.x0; p.y -= this.y0; var lon = adjust_lon(this.long0 + p.x / this.a); var lat = 2.5 * (Math.atan(Math.exp(0.8 * p.y / this.a)) - Math.PI / 4); p.x = lon; p.y = lat; return p; } var names$b = ["Miller_Cylindrical", "mill"]; var mill = { init: init$b, forward: forward$b, inverse: inverse$b, names: names$b }; var MAX_ITER = 20; function init$a() { /* Place parameters in static storage for common use -------------------------------------------------*/ if (!this.sphere) { this.en = pj_enfn(this.es); } else { this.n = 1; this.m = 0; this.es = 0; this.C_y = Math.sqrt((this.m + 1) / this.n); this.C_x = this.C_y / (this.m + 1); } } /* Sinusoidal forward equations--mapping lat,long to x,y -----------------------------------------------------*/ function forward$a(p) { var x, y; var lon = p.x; var lat = p.y; /* Forward equations -----------------*/ lon = adjust_lon(lon - this.long0); if (this.sphere) { if (!this.m) { lat = this.n !== 1 ? Math.asin(this.n * Math.sin(lat)) : lat; } else { var k = this.n * Math.sin(lat); for (var i = MAX_ITER; i; --i) { var V = (this.m * lat + Math.sin(lat) - k) / (this.m + Math.cos(lat)); lat -= V; if (Math.abs(V) < EPSLN) { break; } } } x = this.a * this.C_x * lon * (this.m + Math.cos(lat)); y = this.a * this.C_y * lat; } else { var s = Math.sin(lat); var c = Math.cos(lat); y = this.a * pj_mlfn(lat, s, c, this.en); x = this.a * lon * c / Math.sqrt(1 - this.es * s * s); } p.x = x; p.y = y; return p; } function inverse$a(p) { var lat, temp, lon, s; p.x -= this.x0; lon = p.x / this.a; p.y -= this.y0; lat = p.y / this.a; if (this.sphere) { lat /= this.C_y; lon = lon / (this.C_x * (this.m + Math.cos(lat))); if (this.m) { lat = asinz((this.m * lat + Math.sin(lat)) / this.n); } else if (this.n !== 1) { lat = asinz(Math.sin(lat) / this.n); } lon = adjust_lon(lon + this.long0); lat = adjust_lat(lat); } else { lat = pj_inv_mlfn(p.y / this.a, this.es, this.en); s = Math.abs(lat); if (s < HALF_PI) { s = Math.sin(lat); temp = this.long0 + p.x * Math.sqrt(1 - this.es * s * s) / (this.a * Math.cos(lat)); //temp = this.long0 + p.x / (this.a * Math.cos(lat)); lon = adjust_lon(temp); } else if ((s - EPSLN) < HALF_PI) { lon = this.long0; } } p.x = lon; p.y = lat; return p; } var names$a = ["Sinusoidal", "sinu"]; var sinu = { init: init$a, forward: forward$a, inverse: inverse$a, names: names$a }; function init$9() {} /* Mollweide forward equations--mapping lat,long to x,y ----------------------------------------------------*/ function forward$9(p) { /* Forward equations -----------------*/ var lon = p.x; var lat = p.y; var delta_lon = adjust_lon(lon - this.long0); var theta = lat; var con = Math.PI * Math.sin(lat); /* Iterate using the Newton-Raphson method to find theta -----------------------------------------------------*/ while (true) { var delta_theta = -(theta + Math.sin(theta) - con) / (1 + Math.cos(theta)); theta += delta_theta; if (Math.abs(delta_theta) < EPSLN) { break; } } theta /= 2; /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting" this is done here because of precision problems with "cos(theta)" --------------------------------------------------------------------------*/ if (Math.PI / 2 - Math.abs(lat) < EPSLN) { delta_lon = 0; } var x = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0; var y = 1.4142135623731 * this.a * Math.sin(theta) + this.y0; p.x = x; p.y = y; return p; } function inverse$9(p) { var theta; var arg; /* Inverse equations -----------------*/ p.x -= this.x0; p.y -= this.y0; arg = p.y / (1.4142135623731 * this.a); /* Because of division by zero problems, 'arg' can not be 1. Therefore a number very close to one is used instead. -------------------------------------------------------------------*/ if (Math.abs(arg) > 0.999999999999) { arg = 0.999999999999; } theta = Math.asin(arg); var lon = adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta)))); if (lon < (-Math.PI)) { lon = -Math.PI; } if (lon > Math.PI) { lon = Math.PI; } arg = (2 * theta + Math.sin(2 * theta)) / Math.PI; if (Math.abs(arg) > 1) { arg = 1; } var lat = Math.asin(arg); p.x = lon; p.y = lat; return p; } var names$9 = ["Mollweide", "moll"]; var moll = { init: init$9, forward: forward$9, inverse: inverse$9, names: names$9 }; function init$8() { /* Place parameters in static storage for common use -------------------------------------------------*/ // Standard Parallels cannot be equal and on opposite sides of the equator if (Math.abs(this.lat1 + this.lat2) < EPSLN) { return; } this.lat2 = this.lat2 || this.lat1; this.temp = this.b / this.a; this.es = 1 - Math.pow(this.temp, 2); this.e = Math.sqrt(this.es); this.e0 = e0fn(this.es); this.e1 = e1fn(this.es); this.e2 = e2fn(this.es); this.e3 = e3fn(this.es); this.sinphi = Math.sin(this.lat1); this.cosphi = Math.cos(this.lat1); this.ms1 = msfnz(this.e, this.sinphi, this.cosphi); this.ml1 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat1); if (Math.abs(this.lat1 - this.lat2) < EPSLN) { this.ns = this.sinphi; } else { this.sinphi = Math.sin(this.lat2); this.cosphi = Math.cos(this.lat2); this.ms2 = msfnz(this.e, this.sinphi, this.cosphi); this.ml2 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2); this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1); } this.g = this.ml1 + this.ms1 / this.ns; this.ml0 = mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); this.rh = this.a * (this.g - this.ml0); } /* Equidistant Conic forward equations--mapping lat,long to x,y -----------------------------------------------------------*/ function forward$8(p) { var lon = p.x; var lat = p.y; var rh1; /* Forward equations -----------------*/ if (this.sphere) { rh1 = this.a * (this.g - lat); } else { var ml = mlfn(this.e0, this.e1, this.e2, this.e3, lat); rh1 = this.a * (this.g - ml); } var theta = this.ns * adjust_lon(lon - this.long0); var x = this.x0 + rh1 * Math.sin(theta); var y = this.y0 + this.rh - rh1 * Math.cos(theta); p.x = x; p.y = y; return p; } /* Inverse equations -----------------*/ function inverse$8(p) { p.x -= this.x0; p.y = this.rh - p.y + this.y0; var con, rh1, lat, lon; if (this.ns >= 0) { rh1 = Math.sqrt(p.x * p.x + p.y * p.y); con = 1; } else { rh1 = -Math.sqrt(p.x * p.x + p.y * p.y); con = -1; } var theta = 0; if (rh1 !== 0) { theta = Math.atan2(con * p.x, con * p.y); } if (this.sphere) { lon = adjust_lon(this.long0 + theta / this.ns); lat = adjust_lat(this.g - rh1 / this.a); p.x = lon; p.y = lat; return p; } else { var ml = this.g - rh1 / this.a; lat = imlfn(ml, this.e0, this.e1, this.e2, this.e3); lon = adjust_lon(this.long0 + theta / this.ns); p.x = lon; p.y = lat; return p; } } var names$8 = ["Equidistant_Conic", "eqdc"]; var eqdc = { init: init$8, forward: forward$8, inverse: inverse$8, names: names$8 }; /* Initialize the Van Der Grinten projection ----------------------------------------*/ function init$7() { //this.R = 6370997; //Radius of earth this.R = this.a; } function forward$7(p) { var lon = p.x; var lat = p.y; /* Forward equations -----------------*/ var dlon = adjust_lon(lon - this.long0); var x, y; if (Math.abs(lat) <= EPSLN) { x = this.x0 + this.R * dlon; y = this.y0; } var theta = asinz(2 * Math.abs(lat / Math.PI)); if ((Math.abs(dlon) <= EPSLN) || (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN)) { x = this.x0; if (lat >= 0) { y = this.y0 + Math.PI * this.R * Math.tan(0.5 * theta); } else { y = this.y0 + Math.PI * this.R * -Math.tan(0.5 * theta); } // return(OK); } var al = 0.5 * Math.abs((Math.PI / dlon) - (dlon / Math.PI)); var asq = al * al; var sinth = Math.sin(theta); var costh = Math.cos(theta); var g = costh / (sinth + costh - 1); var gsq = g * g; var m = g * (2 / sinth - 1); var msq = m * m; var con = Math.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq); if (dlon < 0) { con = -con; } x = this.x0 + con; //con = Math.abs(con / (Math.PI * this.R)); var q = asq + g; con = Math.PI * this.R * (m * q - al * Math.sqrt((msq + asq) * (asq + 1) - q * q)) / (msq + asq); if (lat >= 0) { //y = this.y0 + Math.PI * this.R * Math.sqrt(1 - con * con - 2 * al * con); y = this.y0 + con; } else { //y = this.y0 - Math.PI * this.R * Math.sqrt(1 - con * con - 2 * al * con); y = this.y0 - con; } p.x = x; p.y = y; return p; } /* Van Der Grinten inverse equations--mapping x,y to lat/long ---------------------------------------------------------*/ function inverse$7(p) { var lon, lat; var xx, yy, xys, c1, c2, c3; var a1; var m1; var con; var th1; var d; /* inverse equations -----------------*/ p.x -= this.x0; p.y -= this.y0; con = Math.PI * this.R; xx = p.x / con; yy = p.y / con; xys = xx * xx + yy * yy; c1 = -Math.abs(yy) * (1 + xys); c2 = c1 - 2 * yy * yy + xx * xx; c3 = -2 * c1 + 1 + 2 * yy * yy + xys * xys; d = yy * yy / c3 + (2 * c2 * c2 * c2 / c3 / c3 / c3 - 9 * c1 * c2 / c3 / c3) / 27; a1 = (c1 - c2 * c2 / 3 / c3) / c3; m1 = 2 * Math.sqrt(-a1 / 3); con = ((3 * d) / a1) / m1; if (Math.abs(con) > 1) { if (con >= 0) { con = 1; } else { con = -1; } } th1 = Math.acos(con) / 3; if (p.y >= 0) { lat = (-m1 * Math.cos(th1 + Math.PI / 3) - c2 / 3 / c3) * Math.PI; } else { lat = -(-m1 * Math.cos(th1 + Math.PI / 3) - c2 / 3 / c3) * Math.PI; } if (Math.abs(xx) < EPSLN) { lon = this.long0; } else { lon = adjust_lon(this.long0 + Math.PI * (xys - 1 + Math.sqrt(1 + 2 * (xx * xx - yy * yy) + xys * xys)) / 2 / xx); } p.x = lon; p.y = lat; return p; } var names$7 = ["Van_der_Grinten_I", "VanDerGrinten", "vandg"]; var vandg = { init: init$7, forward: forward$7, inverse: inverse$7, names: names$7 }; function init$6() { this.sin_p12 = Math.sin(this.lat0); this.cos_p12 = Math.cos(this.lat0); } function forward$6(p) { var lon = p.x; var lat = p.y; var sinphi = Math.sin(p.y); var cosphi = Math.cos(p.y); var dlon = adjust_lon(lon - this.long0); var e0, e1, e2, e3, Mlp, Ml, tanphi, Nl1, Nl, psi, Az, G, H, GH, Hs, c, kp, cos_c, s, s2, s3, s4, s5; if (this.sphere) { if (Math.abs(this.sin_p12 - 1) <= EPSLN) { //North Pole case p.x = this.x0 + this.a * (HALF_PI - lat) * Math.sin(dlon); p.y = this.y0 - this.a * (HALF_PI - lat) * Math.cos(dlon); return p; } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { //South Pole case p.x = this.x0 + this.a * (HALF_PI + lat) * Math.sin(dlon); p.y = this.y0 + this.a * (HALF_PI + lat) * Math.cos(dlon); return p; } else { //default case cos_c = this.sin_p12 * sinphi + this.cos_p12 * cosphi * Math.cos(dlon); c = Math.acos(cos_c); kp = c ? c / Math.sin(c) : 1; p.x = this.x0 + this.a * kp * cosphi * Math.sin(dlon); p.y = this.y0 + this.a * kp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * Math.cos(dlon)); return p; } } else { e0 = e0fn(this.es); e1 = e1fn(this.es); e2 = e2fn(this.es); e3 = e3fn(this.es); if (Math.abs(this.sin_p12 - 1) <= EPSLN) { //North Pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); Ml = this.a * mlfn(e0, e1, e2, e3, lat); p.x = this.x0 + (Mlp - Ml) * Math.sin(dlon); p.y = this.y0 - (Mlp - Ml) * Math.cos(dlon); return p; } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { //South Pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); Ml = this.a * mlfn(e0, e1, e2, e3, lat); p.x = this.x0 + (Mlp + Ml) * Math.sin(dlon); p.y = this.y0 + (Mlp + Ml) * Math.cos(dlon); return p; } else { //Default case tanphi = sinphi / cosphi; Nl1 = gN(this.a, this.e, this.sin_p12); Nl = gN(this.a, this.e, sinphi); psi = Math.atan((1 - this.es) * tanphi + this.es * Nl1 * this.sin_p12 / (Nl * cosphi)); Az = Math.atan2(Math.sin(dlon), this.cos_p12 * Math.tan(psi) - this.sin_p12 * Math.cos(dlon)); if (Az === 0) { s = Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi)); } else if (Math.abs(Math.abs(Az) - Math.PI) <= EPSLN) { s = -Math.asin(this.cos_p12 * Math.sin(psi) - this.sin_p12 * Math.cos(psi)); } else { s = Math.asin(Math.sin(dlon) * Math.cos(psi) / Math.sin(Az)); } G = this.e * this.sin_p12 / Math.sqrt(1 - this.es); H = this.e * this.cos_p12 * Math.cos(Az) / Math.sqrt(1 - this.es); GH = G * H; Hs = H * H; s2 = s * s; s3 = s2 * s; s4 = s3 * s; s5 = s4 * s; c = Nl1 * s * (1 - s2 * Hs * (1 - Hs) / 6 + s3 / 8 * GH * (1 - 2 * Hs) + s4 / 120 * (Hs * (4 - 7 * Hs) - 3 * G * G * (1 - 7 * Hs)) - s5 / 48 * GH); p.x = this.x0 + c * Math.sin(Az); p.y = this.y0 + c * Math.cos(Az); return p; } } } function inverse$6(p) { p.x -= this.x0; p.y -= this.y0; var rh, z, sinz, cosz, lon, lat, con, e0, e1, e2, e3, Mlp, M, N1, psi, Az, cosAz, tmp, A, B, D, Ee, F, sinpsi; if (this.sphere) { rh = Math.sqrt(p.x * p.x + p.y * p.y); if (rh > (2 * HALF_PI * this.a)) { return; } z = rh / this.a; sinz = Math.sin(z); cosz = Math.cos(z); lon = this.long0; if (Math.abs(rh) <= EPSLN) { lat = this.lat0; } else { lat = asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh); con = Math.abs(this.lat0) - HALF_PI; if (Math.abs(con) <= EPSLN) { if (this.lat0 >= 0) { lon = adjust_lon(this.long0 + Math.atan2(p.x, - p.y)); } else { lon = adjust_lon(this.long0 - Math.atan2(-p.x, p.y)); } } else { /*con = cosz - this.sin_p12 * Math.sin(lat); if ((Math.abs(con) < EPSLN) && (Math.abs(p.x) < EPSLN)) { //no-op, just keep the lon value as is } else { var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh)); lon = adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh))); }*/ lon = adjust_lon(this.long0 + Math.atan2(p.x * sinz, rh * this.cos_p12 * cosz - p.y * this.sin_p12 * sinz)); } } p.x = lon; p.y = lat; return p; } else { e0 = e0fn(this.es); e1 = e1fn(this.es); e2 = e2fn(this.es); e3 = e3fn(this.es); if (Math.abs(this.sin_p12 - 1) <= EPSLN) { //North pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); rh = Math.sqrt(p.x * p.x + p.y * p.y); M = Mlp - rh; lat = imlfn(M / this.a, e0, e1, e2, e3); lon = adjust_lon(this.long0 + Math.atan2(p.x, - 1 * p.y)); p.x = lon; p.y = lat; return p; } else if (Math.abs(this.sin_p12 + 1) <= EPSLN) { //South pole case Mlp = this.a * mlfn(e0, e1, e2, e3, HALF_PI); rh = Math.sqrt(p.x * p.x + p.y * p.y); M = rh - Mlp; lat = imlfn(M / this.a, e0, e1, e2, e3); lon = adjust_lon(this.long0 + Math.atan2(p.x, p.y)); p.x = lon; p.y = lat; return p; } else { //default case rh = Math.sqrt(p.x * p.x + p.y * p.y); Az = Math.atan2(p.x, p.y); N1 = gN(this.a, this.e, this.sin_p12); cosAz = Math.cos(Az); tmp = this.e * this.cos_p12 * cosAz; A = -tmp * tmp / (1 - this.es); B = 3 * this.es * (1 - A) * this.sin_p12 * this.cos_p12 * cosAz / (1 - this.es); D = rh / N1; Ee = D - A * (1 + A) * Math.pow(D, 3) / 6 - B * (1 + 3 * A) * Math.pow(D, 4) / 24; F = 1 - A * Ee * Ee / 2 - D * Ee * Ee * Ee / 6; psi = Math.asin(this.sin_p12 * Math.cos(Ee) + this.cos_p12 * Math.sin(Ee) * cosAz); lon = adjust_lon(this.long0 + Math.asin(Math.sin(Az) * Math.sin(Ee) / Math.cos(psi))); sinpsi = Math.sin(psi); lat = Math.atan2((sinpsi - this.es * F * this.sin_p12) * Math.tan(psi), sinpsi * (1 - this.es)); p.x = lon; p.y = lat; return p; } } } var names$6 = ["Azimuthal_Equidistant", "aeqd"]; var aeqd = { init: init$6, forward: forward$6, inverse: inverse$6, names: names$6 }; function init$5() { //double temp; /* temporary variable */ /* Place parameters in static storage for common use -------------------------------------------------*/ this.sin_p14 = Math.sin(this.lat0); this.cos_p14 = Math.cos(this.lat0); } /* Orthographic forward equations--mapping lat,long to x,y ---------------------------------------------------*/ function forward$5(p) { var sinphi, cosphi; /* sin and cos value */ var dlon; /* delta longitude value */ var coslon; /* cos of longitude */ var ksp; /* scale factor */ var g, x, y; var lon = p.x; var lat = p.y; /* Forward equations -----------------*/ dlon = adjust_lon(lon - this.long0); sinphi = Math.sin(lat); cosphi = Math.cos(lat); coslon = Math.cos(dlon); g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon; ksp = 1; if ((g > 0) || (Math.abs(g) <= EPSLN)) { x = this.a * ksp * cosphi * Math.sin(dlon); y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon); } p.x = x; p.y = y; return p; } function inverse$5(p) { var rh; /* height above ellipsoid */ var z; /* angle */ var sinz, cosz; /* sin of z and cos of z */ var con; var lon, lat; /* Inverse equations -----------------*/ p.x -= this.x0; p.y -= this.y0; rh = Math.sqrt(p.x * p.x + p.y * p.y); z = asinz(rh / this.a); sinz = Math.sin(z); cosz = Math.cos(z); lon = this.long0; if (Math.abs(rh) <= EPSLN) { lat = this.lat0; p.x = lon; p.y = lat; return p; } lat = asinz(cosz * this.sin_p14 + (p.y * sinz * this.cos_p14) / rh); con = Math.abs(this.lat0) - HALF_PI; if (Math.abs(con) <= EPSLN) { if (this.lat0 >= 0) { lon = adjust_lon(this.long0 + Math.atan2(p.x, - p.y)); } else { lon = adjust_lon(this.long0 - Math.atan2(-p.x, p.y)); } p.x = lon; p.y = lat; return p; } lon = adjust_lon(this.long0 + Math.atan2((p.x * sinz), rh * this.cos_p14 * cosz - p.y * this.sin_p14 * sinz)); p.x = lon; p.y = lat; return p; } var names$5 = ["ortho"]; var ortho = { init: init$5, forward: forward$5, inverse: inverse$5, names: names$5 }; // QSC projection rewritten from the original PROJ4 // https://github.com/OSGeo/proj.4/blob/master/src/PJ_qsc.c /* constants */ var FACE_ENUM = { FRONT: 1, RIGHT: 2, BACK: 3, LEFT: 4, TOP: 5, BOTTOM: 6 }; var AREA_ENUM = { AREA_0: 1, AREA_1: 2, AREA_2: 3, AREA_3: 4 }; function init$4() { this.x0 = this.x0 || 0; this.y0 = this.y0 || 0; this.lat0 = this.lat0 || 0; this.long0 = this.long0 || 0; this.lat_ts = this.lat_ts || 0; this.title = this.title || "Quadrilateralized Spherical Cube"; /* Determine the cube face from the center of projection. */ if (this.lat0 >= HALF_PI - FORTPI / 2.0) { this.face = FACE_ENUM.TOP; } else if (this.lat0 <= -(HALF_PI - FORTPI / 2.0)) { this.face = FACE_ENUM.BOTTOM; } else if (Math.abs(this.long0) <= FORTPI) { this.face = FACE_ENUM.FRONT; } else if (Math.abs(this.long0) <= HALF_PI + FORTPI) { this.face = this.long0 > 0.0 ? FACE_ENUM.RIGHT : FACE_ENUM.LEFT; } else { this.face = FACE_ENUM.BACK; } /* Fill in useful values for the ellipsoid <-> sphere shift * described in [LK12]. */ if (this.es !== 0) { this.one_minus_f = 1 - (this.a - this.b) / this.a; this.one_minus_f_squared = this.one_minus_f * this.one_minus_f; } } // QSC forward equations--mapping lat,long to x,y // ----------------------------------------------------------------- function forward$4(p) { var xy = {x: 0, y: 0}; var lat, lon; var theta, phi; var t, mu; /* nu; */ var area = {value: 0}; // move lon according to projection's lon p.x -= this.long0; /* Convert the geodetic latitude to a geocentric latitude. * This corresponds to the shift from the ellipsoid to the sphere * described in [LK12]. */ if (this.es !== 0) {//if (P->es != 0) { lat = Math.atan(this.one_minus_f_squared * Math.tan(p.y)); } else { lat = p.y; } /* Convert the input lat, lon into theta, phi as used by QSC. * This depends on the cube face and the area on it. * For the top and bottom face, we can compute theta and phi * directly from phi, lam. For the other faces, we must use * unit sphere cartesian coordinates as an intermediate step. */ lon = p.x; //lon = lp.lam; if (this.face === FACE_ENUM.TOP) { phi = HALF_PI - lat; if (lon >= FORTPI && lon <= HALF_PI + FORTPI) { area.value = AREA_ENUM.AREA_0; theta = lon - HALF_PI; } else if (lon > HALF_PI + FORTPI || lon <= -(HALF_PI + FORTPI)) { area.value = AREA_ENUM.AREA_1; theta = (lon > 0.0 ? lon - SPI : lon + SPI); } else if (lon > -(HALF_PI + FORTPI) && lon <= -FORTPI) { area.value = AREA_ENUM.AREA_2; theta = lon + HALF_PI; } else { area.value = AREA_ENUM.AREA_3; theta = lon; } } else if (this.face === FACE_ENUM.BOTTOM) { phi = HALF_PI + lat; if (lon >= FORTPI && lon <= HALF_PI + FORTPI) { area.value = AREA_ENUM.AREA_0; theta = -lon + HALF_PI; } else if (lon < FORTPI && lon >= -FORTPI) { area.value = AREA_ENUM.AREA_1; theta = -lon; } else if (lon < -FORTPI && lon >= -(HALF_PI + FORTPI)) { area.value = AREA_ENUM.AREA_2; theta = -lon - HALF_PI; } else { area.value = AREA_ENUM.AREA_3; theta = (lon > 0.0 ? -lon + SPI : -lon - SPI); } } else { var q, r, s; var sinlat, coslat; var sinlon, coslon; if (this.face === FACE_ENUM.RIGHT) { lon = qsc_shift_lon_origin(lon, +HALF_PI); } else if (this.face === FACE_ENUM.BACK) { lon = qsc_shift_lon_origin(lon, +SPI); } else if (this.face === FACE_ENUM.LEFT) { lon = qsc_shift_lon_origin(lon, -HALF_PI); } sinlat = Math.sin(lat); coslat = Math.cos(lat); sinlon = Math.sin(lon); coslon = Math.cos(lon); q = coslat * coslon; r = coslat * sinlon; s = sinlat; if (this.face === FACE_ENUM.FRONT) { phi = Math.acos(q); theta = qsc_fwd_equat_face_theta(phi, s, r, area); } else if (this.face === FACE_ENUM.RIGHT) { phi = Math.acos(r); theta = qsc_fwd_equat_face_theta(phi, s, -q, area); } else if (this.face === FACE_ENUM.BACK) { phi = Math.acos(-q); theta = qsc_fwd_equat_face_theta(phi, s, -r, area); } else if (this.face === FACE_ENUM.LEFT) { phi = Math.acos(-r); theta = qsc_fwd_equat_face_theta(phi, s, q, area); } else { /* Impossible */ phi = theta = 0; area.value = AREA_ENUM.AREA_0; } } /* Compute mu and nu for the area of definition. * For mu, see Eq. (3-21) in [OL76], but note the typos: * compare with Eq. (3-14). For nu, see Eq. (3-38). */ mu = Math.atan((12 / SPI) * (theta + Math.acos(Math.sin(theta) * Math.cos(FORTPI)) - HALF_PI)); t = Math.sqrt((1 - Math.cos(phi)) / (Math.cos(mu) * Math.cos(mu)) / (1 - Math.cos(Math.atan(1 / Math.cos(theta))))); /* Apply the result to the real area. */ if (area.value === AREA_ENUM.AREA_1) { mu += HALF_PI; } else if (area.value === AREA_ENUM.AREA_2) { mu += SPI; } else if (area.value === AREA_ENUM.AREA_3) { mu += 1.5 * SPI; } /* Now compute x, y from mu and nu */ xy.x = t * Math.cos(mu); xy.y = t * Math.sin(mu); xy.x = xy.x * this.a + this.x0; xy.y = xy.y * this.a + this.y0; p.x = xy.x; p.y = xy.y; return p; } // QSC inverse equations--mapping x,y to lat/long // ----------------------------------------------------------------- function inverse$4(p) { var lp = {lam: 0, phi: 0}; var mu, nu, cosmu, tannu; var tantheta, theta, cosphi, phi; var t; var area = {value: 0}; /* de-offset */ p.x = (p.x - this.x0) / this.a; p.y = (p.y - this.y0) / this.a; /* Convert the input x, y to the mu and nu angles as used by QSC. * This depends on the area of the cube face. */ nu = Math.atan(Math.sqrt(p.x * p.x + p.y * p.y)); mu = Math.atan2(p.y, p.x); if (p.x >= 0.0 && p.x >= Math.abs(p.y)) { area.value = AREA_ENUM.AREA_0; } else if (p.y >= 0.0 && p.y >= Math.abs(p.x)) { area.value = AREA_ENUM.AREA_1; mu -= HALF_PI; } else if (p.x < 0.0 && -p.x >= Math.abs(p.y)) { area.value = AREA_ENUM.AREA_2; mu = (mu < 0.0 ? mu + SPI : mu - SPI); } else { area.value = AREA_ENUM.AREA_3; mu += HALF_PI; } /* Compute phi and theta for the area of definition. * The inverse projection is not described in the original paper, but some * good hints can be found here (as of 2011-12-14): * http://fits.gsfc.nasa.gov/fitsbits/saf.93/saf.9302 * (search for "Message-Id: <9302181759.AA25477 at fits.cv.nrao.edu>") */ t = (SPI / 12) * Math.tan(mu); tantheta = Math.sin(t) / (Math.cos(t) - (1 / Math.sqrt(2))); theta = Math.atan(tantheta); cosmu = Math.cos(mu); tannu = Math.tan(nu); cosphi = 1 - cosmu * cosmu * tannu * tannu * (1 - Math.cos(Math.atan(1 / Math.cos(theta)))); if (cosphi < -1) { cosphi = -1; } else if (cosphi > +1) { cosphi = +1; } /* Apply the result to the real area on the cube face. * For the top and bottom face, we can compute phi and lam directly. * For the other faces, we must use unit sphere cartesian coordinates * as an intermediate step. */ if (this.face === FACE_ENUM.TOP) { phi = Math.acos(cosphi); lp.phi = HALF_PI - phi; if (area.value === AREA_ENUM.AREA_0) { lp.lam = theta + HALF_PI; } else if (area.value === AREA_ENUM.AREA_1) { lp.lam = (theta < 0.0 ? theta + SPI : theta - SPI); } else if (area.value === AREA_ENUM.AREA_2) { lp.lam = theta - HALF_PI; } else /* area.value == AREA_ENUM.AREA_3 */ { lp.lam = theta; } } else if (this.face === FACE_ENUM.BOTTOM) { phi = Math.acos(cosphi); lp.phi = phi - HALF_PI; if (area.value === AREA_ENUM.AREA_0) { lp.lam = -theta + HALF_PI; } else if (area.value === AREA_ENUM.AREA_1) { lp.lam = -theta; } else if (area.value === AREA_ENUM.AREA_2) { lp.lam = -theta - HALF_PI; } else /* area.value == AREA_ENUM.AREA_3 */ { lp.lam = (theta < 0.0 ? -theta - SPI : -theta + SPI); } } else { /* Compute phi and lam via cartesian unit sphere coordinates. */ var q, r, s; q = cosphi; t = q * q; if (t >= 1) { s = 0; } else { s = Math.sqrt(1 - t) * Math.sin(theta); } t += s * s; if (t >= 1) { r = 0; } else { r = Math.sqrt(1 - t); } /* Rotate q,r,s into the correct area. */ if (area.value === AREA_ENUM.AREA_1) { t = r; r = -s; s = t; } else if (area.value === AREA_ENUM.AREA_2) { r = -r; s = -s; } else if (area.value === AREA_ENUM.AREA_3) { t = r; r = s; s = -t; } /* Rotate q,r,s into the correct cube face. */ if (this.face === FACE_ENUM.RIGHT) { t = q; q = -r; r = t; } else if (this.face === FACE_ENUM.BACK) { q = -q; r = -r; } else if (this.face === FACE_ENUM.LEFT) { t = q; q = r; r = -t; } /* Now compute phi and lam from the unit sphere coordinates. */ lp.phi = Math.acos(-s) - HALF_PI; lp.lam = Math.atan2(r, q); if (this.face === FACE_ENUM.RIGHT) { lp.lam = qsc_shift_lon_origin(lp.lam, -HALF_PI); } else if (this.face === FACE_ENUM.BACK) { lp.lam = qsc_shift_lon_origin(lp.lam, -SPI); } else if (this.face === FACE_ENUM.LEFT) { lp.lam = qsc_shift_lon_origin(lp.lam, +HALF_PI); } } /* Apply the shift from the sphere to the ellipsoid as described * in [LK12]. */ if (this.es !== 0) { var invert_sign; var tanphi, xa; invert_sign = (lp.phi < 0 ? 1 : 0); tanphi = Math.tan(lp.phi); xa = this.b / Math.sqrt(tanphi * tanphi + this.one_minus_f_squared); lp.phi = Math.atan(Math.sqrt(this.a * this.a - xa * xa) / (this.one_minus_f * xa)); if (invert_sign) { lp.phi = -lp.phi; } } lp.lam += this.long0; p.x = lp.lam; p.y = lp.phi; return p; } /* Helper function for forward projection: compute the theta angle * and determine the area number. */ function qsc_fwd_equat_face_theta(phi, y, x, area) { var theta; if (phi < EPSLN) { area.value = AREA_ENUM.AREA_0; theta = 0.0; } else { theta = Math.atan2(y, x); if (Math.abs(theta) <= FORTPI) { area.value = AREA_ENUM.AREA_0; } else if (theta > FORTPI && theta <= HALF_PI + FORTPI) { area.value = AREA_ENUM.AREA_1; theta -= HALF_PI; } else if (theta > HALF_PI + FORTPI || theta <= -(HALF_PI + FORTPI)) { area.value = AREA_ENUM.AREA_2; theta = (theta >= 0.0 ? theta - SPI : theta + SPI); } else { area.value = AREA_ENUM.AREA_3; theta += HALF_PI; } } return theta; } /* Helper function: shift the longitude. */ function qsc_shift_lon_origin(lon, offset) { var slon = lon + offset; if (slon < -SPI) { slon += TWO_PI; } else if (slon > +SPI) { slon -= TWO_PI; } return slon; } var names$4 = ["Quadrilateralized Spherical Cube", "Quadrilateralized_Spherical_Cube", "qsc"]; var qsc = { init: init$4, forward: forward$4, inverse: inverse$4, names: names$4 }; // Robinson projection // Based on https://github.com/OSGeo/proj.4/blob/master/src/PJ_robin.c // Polynomial coeficients from http://article.gmane.org/gmane.comp.gis.proj-4.devel/6039 var COEFS_X = [ [1.0000, 2.2199e-17, -7.15515e-05, 3.1103e-06], [0.9986, -0.000482243, -2.4897e-05, -1.3309e-06], [0.9954, -0.00083103, -4.48605e-05, -9.86701e-07], [0.9900, -0.00135364, -5.9661e-05, 3.6777e-06], [0.9822, -0.00167442, -4.49547e-06, -5.72411e-06], [0.9730, -0.00214868, -9.03571e-05, 1.8736e-08], [0.9600, -0.00305085, -9.00761e-05, 1.64917e-06], [0.9427, -0.00382792, -6.53386e-05, -2.6154e-06], [0.9216, -0.00467746, -0.00010457, 4.81243e-06], [0.8962, -0.00536223, -3.23831e-05, -5.43432e-06], [0.8679, -0.00609363, -0.000113898, 3.32484e-06], [0.8350, -0.00698325, -6.40253e-05, 9.34959e-07], [0.7986, -0.00755338, -5.00009e-05, 9.35324e-07], [0.7597, -0.00798324, -3.5971e-05, -2.27626e-06], [0.7186, -0.00851367, -7.01149e-05, -8.6303e-06], [0.6732, -0.00986209, -0.000199569, 1.91974e-05], [0.6213, -0.010418, 8.83923e-05, 6.24051e-06], [0.5722, -0.00906601, 0.000182, 6.24051e-06], [0.5322, -0.00677797, 0.000275608, 6.24051e-06] ]; var COEFS_Y = [ [-5.20417e-18, 0.0124, 1.21431e-18, -8.45284e-11], [0.0620, 0.0124, -1.26793e-09, 4.22642e-10], [0.1240, 0.0124, 5.07171e-09, -1.60604e-09], [0.1860, 0.0123999, -1.90189e-08, 6.00152e-09], [0.2480, 0.0124002, 7.10039e-08, -2.24e-08], [0.3100, 0.0123992, -2.64997e-07, 8.35986e-08], [0.3720, 0.0124029, 9.88983e-07, -3.11994e-07], [0.4340, 0.0123893, -3.69093e-06, -4.35621e-07], [0.4958, 0.0123198, -1.02252e-05, -3.45523e-07], [0.5571, 0.0121916, -1.54081e-05, -5.82288e-07], [0.6176, 0.0119938, -2.41424e-05, -5.25327e-07], [0.6769, 0.011713, -3.20223e-05, -5.16405e-07], [0.7346, 0.0113541, -3.97684e-05, -6.09052e-07], [0.7903, 0.0109107, -4.89042e-05, -1.04739e-06], [0.8435, 0.0103431, -6.4615e-05, -1.40374e-09], [0.8936, 0.00969686, -6.4636e-05, -8.547e-06], [0.9394, 0.00840947, -0.000192841, -4.2106e-06], [0.9761, 0.00616527, -0.000256, -4.2106e-06], [1.0000, 0.00328947, -0.000319159, -4.2106e-06] ]; var FXC = 0.8487; var FYC = 1.3523; var C1 = R2D/5; // rad to 5-degree interval var RC1 = 1/C1; var NODES = 18; var poly3_val = function(coefs, x) { return coefs[0] + x * (coefs[1] + x * (coefs[2] + x * coefs[3])); }; var poly3_der = function(coefs, x) { return coefs[1] + x * (2 * coefs[2] + x * 3 * coefs[3]); }; function newton_rapshon(f_df, start, max_err, iters) { var x = start; for (; iters; --iters) { var upd = f_df(x); x -= upd; if (Math.abs(upd) < max_err) { break; } } return x; } function init$3() { this.x0 = this.x0 || 0; this.y0 = this.y0 || 0; this.long0 = this.long0 || 0; this.es = 0; this.title = this.title || "Robinson"; } function forward$3(ll) { var lon = adjust_lon(ll.x - this.long0); var dphi = Math.abs(ll.y); var i = Math.floor(dphi * C1); if (i < 0) { i = 0; } else if (i >= NODES) { i = NODES - 1; } dphi = R2D * (dphi - RC1 * i); var xy = { x: poly3_val(COEFS_X[i], dphi) * lon, y: poly3_val(COEFS_Y[i], dphi) }; if (ll.y < 0) { xy.y = -xy.y; } xy.x = xy.x * this.a * FXC + this.x0; xy.y = xy.y * this.a * FYC + this.y0; return xy; } function inverse$3(xy) { var ll = { x: (xy.x - this.x0) / (this.a * FXC), y: Math.abs(xy.y - this.y0) / (this.a * FYC) }; if (ll.y >= 1) { // pathologic case ll.x /= COEFS_X[NODES][0]; ll.y = xy.y < 0 ? -HALF_PI : HALF_PI; } else { // find table interval var i = Math.floor(ll.y * NODES); if (i < 0) { i = 0; } else if (i >= NODES) { i = NODES - 1; } for (;;) { if (COEFS_Y[i][0] > ll.y) { --i; } else if (COEFS_Y[i+1][0] <= ll.y) { ++i; } else { break; } } // linear interpolation in 5 degree interval var coefs = COEFS_Y[i]; var t = 5 * (ll.y - coefs[0]) / (COEFS_Y[i+1][0] - coefs[0]); // find t so that poly3_val(coefs, t) = ll.y t = newton_rapshon(function(x) { return (poly3_val(coefs, x) - ll.y) / poly3_der(coefs, x); }, t, EPSLN, 100); ll.x /= poly3_val(COEFS_X[i], t); ll.y = (5 * i + t) * D2R$1; if (xy.y < 0) { ll.y = -ll.y; } } ll.x = adjust_lon(ll.x + this.long0); return ll; } var names$3 = ["Robinson", "robin"]; var robin = { init: init$3, forward: forward$3, inverse: inverse$3, names: names$3 }; function init$2() { this.name = 'geocent'; } function forward$2(p) { var point = geodeticToGeocentric(p, this.es, this.a); return point; } function inverse$2(p) { var point = geocentricToGeodetic(p, this.es, this.a, this.b); return point; } var names$2 = ["Geocentric", 'geocentric', "geocent", "Geocent"]; var geocent = { init: init$2, forward: forward$2, inverse: inverse$2, names: names$2 }; var mode = { N_POLE: 0, S_POLE: 1, EQUIT: 2, OBLIQ: 3 }; var params = { h: { def: 100000, num: true }, // default is Karman line, no default in PROJ.7 azi: { def: 0, num: true, degrees: true }, // default is North tilt: { def: 0, num: true, degrees: true }, // default is Nadir long0: { def: 0, num: true }, // default is Greenwich, conversion to rad is automatic lat0: { def: 0, num: true } // default is Equator, conversion to rad is automatic }; function init$1() { Object.keys(params).forEach(function (p) { if (typeof this[p] === "undefined") { this[p] = params[p].def; } else if (params[p].num && isNaN(this[p])) { throw new Error("Invalid parameter value, must be numeric " + p + " = " + this[p]); } else if (params[p].num) { this[p] = parseFloat(this[p]); } if (params[p].degrees) { this[p] = this[p] * D2R$1; } }.bind(this)); if (Math.abs((Math.abs(this.lat0) - HALF_PI)) < EPSLN) { this.mode = this.lat0 < 0 ? mode.S_POLE : mode.N_POLE; } else if (Math.abs(this.lat0) < EPSLN) { this.mode = mode.EQUIT; } else { this.mode = mode.OBLIQ; this.sinph0 = Math.sin(this.lat0); this.cosph0 = Math.cos(this.lat0); } this.pn1 = this.h / this.a; // Normalize relative to the Earth's radius if (this.pn1 <= 0 || this.pn1 > 1e10) { throw new Error("Invalid height"); } this.p = 1 + this.pn1; this.rp = 1 / this.p; this.h1 = 1 / this.pn1; this.pfact = (this.p + 1) * this.h1; this.es = 0; var omega = this.tilt; var gamma = this.azi; this.cg = Math.cos(gamma); this.sg = Math.sin(gamma); this.cw = Math.cos(omega); this.sw = Math.sin(omega); } function forward$1(p) { p.x -= this.long0; var sinphi = Math.sin(p.y); var cosphi = Math.cos(p.y); var coslam = Math.cos(p.x); var x, y; switch (this.mode) { case mode.OBLIQ: y = this.sinph0 * sinphi + this.cosph0 * cosphi * coslam; break; case mode.EQUIT: y = cosphi * coslam; break; case mode.S_POLE: y = -sinphi; break; case mode.N_POLE: y = sinphi; break; } y = this.pn1 / (this.p - y); x = y * cosphi * Math.sin(p.x); switch (this.mode) { case mode.OBLIQ: y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam; break; case mode.EQUIT: y *= sinphi; break; case mode.N_POLE: y *= -(cosphi * coslam); break; case mode.S_POLE: y *= cosphi * coslam; break; } // Tilt var yt, ba; yt = y * this.cg + x * this.sg; ba = 1 / (yt * this.sw * this.h1 + this.cw); x = (x * this.cg - y * this.sg) * this.cw * ba; y = yt * ba; p.x = x * this.a; p.y = y * this.a; return p; } function inverse$1(p) { p.x /= this.a; p.y /= this.a; var r = { x: p.x, y: p.y }; // Un-Tilt var bm, bq, yt; yt = 1 / (this.pn1 - p.y * this.sw); bm = this.pn1 * p.x * yt; bq = this.pn1 * p.y * this.cw * yt; p.x = bm * this.cg + bq * this.sg; p.y = bq * this.cg - bm * this.sg; var rh = hypot(p.x, p.y); if (Math.abs(rh) < EPSLN) { r.x = 0; r.y = p.y; } else { var cosz, sinz; sinz = 1 - rh * rh * this.pfact; sinz = (this.p - Math.sqrt(sinz)) / (this.pn1 / rh + rh / this.pn1); cosz = Math.sqrt(1 - sinz * sinz); switch (this.mode) { case mode.OBLIQ: r.y = Math.asin(cosz * this.sinph0 + p.y * sinz * this.cosph0 / rh); p.y = (cosz - this.sinph0 * Math.sin(r.y)) * rh; p.x *= sinz * this.cosph0; break; case mode.EQUIT: r.y = Math.asin(p.y * sinz / rh); p.y = cosz * rh; p.x *= sinz; break; case mode.N_POLE: r.y = Math.asin(cosz); p.y = -p.y; break; case mode.S_POLE: r.y = -Math.asin(cosz); break; } r.x = Math.atan2(p.x, p.y); } p.x = r.x + this.long0; p.y = r.y; return p; } var names$1 = ["Tilted_Perspective", "tpers"]; var tpers = { init: init$1, forward: forward$1, inverse: inverse$1, names: names$1 }; function init() { this.flip_axis = (this.sweep === 'x' ? 1 : 0); this.h = Number(this.h); this.radius_g_1 = this.h / this.a; if (this.radius_g_1 <= 0 || this.radius_g_1 > 1e10) { throw new Error(); } this.radius_g = 1.0 + this.radius_g_1; this.C = this.radius_g * this.radius_g - 1.0; if (this.es !== 0.0) { var one_es = 1.0 - this.es; var rone_es = 1 / one_es; this.radius_p = Math.sqrt(one_es); this.radius_p2 = one_es; this.radius_p_inv2 = rone_es; this.shape = 'ellipse'; // Use as a condition in the forward and inverse functions. } else { this.radius_p = 1.0; this.radius_p2 = 1.0; this.radius_p_inv2 = 1.0; this.shape = 'sphere'; // Use as a condition in the forward and inverse functions. } if (!this.title) { this.title = "Geostationary Satellite View"; } } function forward(p) { var lon = p.x; var lat = p.y; var tmp, v_x, v_y, v_z; lon = lon - this.long0; if (this.shape === 'ellipse') { lat = Math.atan(this.radius_p2 * Math.tan(lat)); var r = this.radius_p / hypot(this.radius_p * Math.cos(lat), Math.sin(lat)); v_x = r * Math.cos(lon) * Math.cos(lat); v_y = r * Math.sin(lon) * Math.cos(lat); v_z = r * Math.sin(lat); if (((this.radius_g - v_x) * v_x - v_y * v_y - v_z * v_z * this.radius_p_inv2) < 0.0) { p.x = Number.NaN; p.y = Number.NaN; return p; } tmp = this.radius_g - v_x; if (this.flip_axis) { p.x = this.radius_g_1 * Math.atan(v_y / hypot(v_z, tmp)); p.y = this.radius_g_1 * Math.atan(v_z / tmp); } else { p.x = this.radius_g_1 * Math.atan(v_y / tmp); p.y = this.radius_g_1 * Math.atan(v_z / hypot(v_y, tmp)); } } else if (this.shape === 'sphere') { tmp = Math.cos(lat); v_x = Math.cos(lon) * tmp; v_y = Math.sin(lon) * tmp; v_z = Math.sin(lat); tmp = this.radius_g - v_x; if (this.flip_axis) { p.x = this.radius_g_1 * Math.atan(v_y / hypot(v_z, tmp)); p.y = this.radius_g_1 * Math.atan(v_z / tmp); } else { p.x = this.radius_g_1 * Math.atan(v_y / tmp); p.y = this.radius_g_1 * Math.atan(v_z / hypot(v_y, tmp)); } } p.x = p.x * this.a; p.y = p.y * this.a; return p; } function inverse(p) { var v_x = -1.0; var v_y = 0.0; var v_z = 0.0; var a, b, det, k; p.x = p.x / this.a; p.y = p.y / this.a; if (this.shape === 'ellipse') { if (this.flip_axis) { v_z = Math.tan(p.y / this.radius_g_1); v_y = Math.tan(p.x / this.radius_g_1) * hypot(1.0, v_z); } else { v_y = Math.tan(p.x / this.radius_g_1); v_z = Math.tan(p.y / this.radius_g_1) * hypot(1.0, v_y); } var v_zp = v_z / this.radius_p; a = v_y * v_y + v_zp * v_zp + v_x * v_x; b = 2 * this.radius_g * v_x; det = (b * b) - 4 * a * this.C; if (det < 0.0) { p.x = Number.NaN; p.y = Number.NaN; return p; } k = (-b - Math.sqrt(det)) / (2.0 * a); v_x = this.radius_g + k * v_x; v_y *= k; v_z *= k; p.x = Math.atan2(v_y, v_x); p.y = Math.atan(v_z * Math.cos(p.x) / v_x); p.y = Math.atan(this.radius_p_inv2 * Math.tan(p.y)); } else if (this.shape === 'sphere') { if (this.flip_axis) { v_z = Math.tan(p.y / this.radius_g_1); v_y = Math.tan(p.x / this.radius_g_1) * Math.sqrt(1.0 + v_z * v_z); } else { v_y = Math.tan(p.x / this.radius_g_1); v_z = Math.tan(p.y / this.radius_g_1) * Math.sqrt(1.0 + v_y * v_y); } a = v_y * v_y + v_z * v_z + v_x * v_x; b = 2 * this.radius_g * v_x; det = (b * b) - 4 * a * this.C; if (det < 0.0) { p.x = Number.NaN; p.y = Number.NaN; return p; } k = (-b - Math.sqrt(det)) / (2.0 * a); v_x = this.radius_g + k * v_x; v_y *= k; v_z *= k; p.x = Math.atan2(v_y, v_x); p.y = Math.atan(v_z * Math.cos(p.x) / v_x); } p.x = p.x + this.long0; return p; } var names = ["Geostationary Satellite View", "Geostationary_Satellite", "geos"]; var geos = { init: init, forward: forward, inverse: inverse, names: names, }; function includedProjections(proj4){ proj4.Proj.projections.add(tmerc); proj4.Proj.projections.add(etmerc); proj4.Proj.projections.add(utm); proj4.Proj.projections.add(sterea); proj4.Proj.projections.add(stere); proj4.Proj.projections.add(somerc); proj4.Proj.projections.add(omerc); proj4.Proj.projections.add(lcc); proj4.Proj.projections.add(krovak); proj4.Proj.projections.add(cass); proj4.Proj.projections.add(laea); proj4.Proj.projections.add(aea); proj4.Proj.projections.add(gnom); proj4.Proj.projections.add(cea); proj4.Proj.projections.add(eqc); proj4.Proj.projections.add(poly); proj4.Proj.projections.add(nzmg); proj4.Proj.projections.add(mill); proj4.Proj.projections.add(sinu); proj4.Proj.projections.add(moll); proj4.Proj.projections.add(eqdc); proj4.Proj.projections.add(vandg); proj4.Proj.projections.add(aeqd); proj4.Proj.projections.add(ortho); proj4.Proj.projections.add(qsc); proj4.Proj.projections.add(robin); proj4.Proj.projections.add(geocent); proj4.Proj.projections.add(tpers); proj4.Proj.projections.add(geos); } proj4.defaultDatum = 'WGS84'; //default datum proj4.Proj = Projection; proj4.WGS84 = new proj4.Proj('WGS84'); proj4.Point = Point; proj4.toPoint = common; proj4.defs = defs; proj4.nadgrid = nadgrid; proj4.transform = transform; proj4.mgrs = mgrs; proj4.version = '__VERSION__'; includedProjections(proj4); var f,m="deflate-raw",x=self.DecompressionStream;try{new x(m),f=async t=>{let n=new x(m),e=n.writable.getWriter(),i=n.readable.getReader();e.write(t),e.close();let c,o=[],s=0,a=0,l;for(;!(l=await i.read()).done;)c=l.value,o.push(c),s+=c.length;return o.length-1?(c=new Uint8Array(s),o.map(r=>{c.set(r,a),a+=r.length;}),c):o[0]};}catch{}var _=new TextDecoder,h=t=>{throw new Error("but-unzip~"+t)},E=t=>_.decode(t),A=t=>{let n=t.length-20,e=Math.max(n-65516,2);for(;(n=t.lastIndexOf(80,n-1))!==-1&&!(t[n+1]===75&&t[n+2]===5&&t[n+3]===6)&&n>e;);return n};function*C(t,n=f){let e=A(t);e===-1&&h(2);let i=(r,d)=>t.subarray(e+=r,e+=d),c=new DataView(t.buffer,t.byteOffset),o=r=>c.getUint16(r+e,!0),s=r=>c.getUint32(r+e,!0),a=o(10);for(a!==o(8)&&h(3),e=s(16);a--;){let r=o(10),d=o(28),g=o(30),y=o(32),b=s(20),w=s(42),p=E(i(46,d)),D=E(i(g,y)),L=e,u;e=w,u=i(30+o(26)+o(28),b),yield {filename:p,comment:D,read:()=>r&8?n(u):r?h(1):u},e=L;}} const regex$1 = /.+\.(shp|dbf|json|prj|cpg)$/i; var unzip = async (buffer) => { const files = {}; const proms = []; for (const entry of C(buffer)) { if (!regex$1.test(entry.filename)) { continue; } proms.push(Promise.resolve(entry.read()).then(bytes => files[entry.filename] = bytes)); } await Promise.all(proms); const out = {}; const decoder = new TextDecoder(); for (const [key, value] of Object.entries(files)) { if (key.slice(-3).toLowerCase() === 'shp' || key.slice(-3).toLowerCase() === 'dbf') { out[key] = new DataView(value.buffer, value.byteOffset, value.byteLength); } else { out[key] = decoder.decode(value); } } return out; }; const URL$1 = globalThis.URL; var combine$1 = (base, type) => { if (!type) { return base; } const url = new URL$1(base); url.pathname = `${url.pathname}.${type}`; return url.href; }; async function binaryAjax(_url, type) { const url = combine$1(_url, type); const isOptionalTxt = type === 'prj' || type === 'cpg'; try { const resp = await fetch(url); if (resp.status > 399) { throw new Error(resp.statusText); } if (isOptionalTxt) { return resp.text(); } const parsed = await resp.arrayBuffer(); return new DataView(parsed) } catch (e) { if (isOptionalTxt || type === 'dbf') { return false; } throw e; } } function isClockWise(array) { let sum = 0; let i = 1; const len = array.length; let prev, cur; const bbox = [array[0][0], array[0][1], array[0][0], array[0][1]]; while (i < len) { prev = cur || array[0]; cur = array[i]; sum += ((cur[0] - prev[0]) * (cur[1] + prev[1])); i++; if (cur[0] < bbox[0]) { bbox[0] = cur[0]; } if (cur[1] < bbox[1]) { bbox[1] = cur[1]; } if (cur[0] > bbox[2]) { bbox[2] = cur[0]; } if (cur[1] > bbox[3]) { bbox[3] = cur[1]; } } return { ring: array, clockWise: sum > 0, bbox, children: [] } } function contains(outer, inner) { if (outer.bbox[0] > inner.bbox[0]) { return false; } if (outer.bbox[1] > inner.bbox[1]) { return false; } if (outer.bbox[2] < inner.bbox[2]) { return false; } if (outer.bbox[3] < inner.bbox[3]) { return false; } return true; } function handleRings(rings) { const outers = []; const inners = []; for (const ring of rings) { const proccessed = isClockWise(ring); if (proccessed.clockWise) { outers.push(proccessed); } else { inners.push(proccessed); } } // this is an optimization, // but it would also put in weird bad rings that would otherwise get left out // if (outers.length === 1) { // const out = [outers[0].ring] // for (const inner of inners) { // out.push(inner.ring); // } // return [out]; // } for (const inner of inners) { for (const outer of outers) { if (contains(outer, inner)) { outer.children.push(inner.ring); break; } } } const out = []; for (const outer of outers) { out.push([outer.ring].concat(outer.children)); } return out; } ParseShp.prototype.parsePoint = function (data) { return { type: 'Point', coordinates: this.parseCoord(data, 0) }; }; ParseShp.prototype.parseZPoint = function (data) { const pointXY = this.parsePoint(data); pointXY.coordinates.push(data.getFloat64(16, true)); return pointXY; }; ParseShp.prototype.parsePointArray = function (data, offset, num) { const out = []; let done = 0; while (done < num) { out.push(this.parseCoord(data, offset)); offset += 16; done++; } return out; }; ParseShp.prototype.parseZPointArray = function (data, zOffset, num, coordinates) { let i = 0; while (i < num) { coordinates[i].push(data.getFloat64(zOffset, true)); i++; zOffset += 8; } return coordinates; }; ParseShp.prototype.parseArrayGroup = function (data, offset, partOffset, num, tot) { const out = []; let done = 0; let curNum; let nextNum = 0; let pointNumber; while (done < num) { done++; partOffset += 4; curNum = nextNum; if (done === num) { nextNum = tot; } else { nextNum = data.getInt32(partOffset, true); } pointNumber = nextNum - curNum; if (!pointNumber) { continue; } out.push(this.parsePointArray(data, offset, pointNumber)); offset += (pointNumber << 4); } return out; }; ParseShp.prototype.parseZArrayGroup = function (data, zOffset, num, coordinates) { let i = 0; while (i < num) { coordinates[i] = this.parseZPointArray(data, zOffset, coordinates[i].length, coordinates[i]); zOffset += (coordinates[i].length << 3); i++; } return coordinates; }; ParseShp.prototype.parseMultiPoint = function (data) { const out = {}; const num = data.getInt32(32, true); if (!num) { return null; } const mins = this.parseCoord(data, 0); const maxs = this.parseCoord(data, 16); out.bbox = [ mins[0], mins[1], maxs[0], maxs[1] ]; const offset = 36; if (num === 1) { out.type = 'Point'; out.coordinates = this.parseCoord(data, offset); } else { out.type = 'MultiPoint'; out.coordinates = this.parsePointArray(data, offset, num); } return out; }; ParseShp.prototype.parseZMultiPoint = function (data) { const geoJson = this.parseMultiPoint(data); if (!geoJson) { return null; } let num; if (geoJson.type === 'Point') { geoJson.coordinates.push(data.getFloat64(72, true)); return geoJson; } else { num = geoJson.coordinates.length; } const zOffset = 52 + (num << 4); geoJson.coordinates = this.parseZPointArray(data, zOffset, num, geoJson.coordinates); return geoJson; }; ParseShp.prototype.parsePolyline = function (data) { const out = {}; const numParts = data.getInt32(32, true); if (!numParts) { return null; } const mins = this.parseCoord(data, 0); const maxs = this.parseCoord(data, 16); out.bbox = [ mins[0], mins[1], maxs[0], maxs[1] ]; const num = data.getInt32(36, true); let offset, partOffset; if (numParts === 1) { out.type = 'LineString'; offset = 44; out.coordinates = this.parsePointArray(data, offset, num); } else { out.type = 'MultiLineString'; offset = 40 + (numParts << 2); partOffset = 40; out.coordinates = this.parseArrayGroup(data, offset, partOffset, numParts, num); } return out; }; ParseShp.prototype.parseZPolyline = function (data) { const geoJson = this.parsePolyline(data); if (!geoJson) { return null; } const num = geoJson.coordinates.length; let zOffset; if (geoJson.type === 'LineString') { zOffset = 60 + (num << 4); geoJson.coordinates = this.parseZPointArray(data, zOffset, num, geoJson.coordinates); return geoJson; } else { const totalPoints = geoJson.coordinates.reduce(function (a, v) { return a + v.length; }, 0); zOffset = 56 + (totalPoints << 4) + (num << 2); geoJson.coordinates = this.parseZArrayGroup(data, zOffset, num, geoJson.coordinates); return geoJson; } }; ParseShp.prototype.polyFuncs = function (out) { if (!out) { return out; } if (out.type === 'LineString') { out.type = 'Polygon'; out.coordinates = [out.coordinates]; return out; } else { out.coordinates = handleRings(out.coordinates); if (out.coordinates.length === 1) { out.type = 'Polygon'; out.coordinates = out.coordinates[0]; return out; } else { out.type = 'MultiPolygon'; return out; } } }; ParseShp.prototype.parsePolygon = function (data) { return this.polyFuncs(this.parsePolyline(data)); }; ParseShp.prototype.parseZPolygon = function (data) { return this.polyFuncs(this.parseZPolyline(data)); }; const shpFuncObj = { 1: 'parsePoint', 3: 'parsePolyline', 5: 'parsePolygon', 8: 'parseMultiPoint', 11: 'parseZPoint', 13: 'parseZPolyline', 15: 'parseZPolygon', 18: 'parseZMultiPoint' }; function makeParseCoord(trans) { if (trans) { return function (data, offset) { const args = [data.getFloat64(offset, true), data.getFloat64(offset + 8, true)]; return trans.inverse(args); }; } else { return function (data, offset) { return [data.getFloat64(offset, true), data.getFloat64(offset + 8, true)]; }; } } function ParseShp(buffer, trans) { if (!(this instanceof ParseShp)) { return new ParseShp(buffer, trans); } this.buffer = buffer; this.headers = this.parseHeader(); this.shpFuncs(trans); this.rows = this.getRows(); } ParseShp.prototype.shpFuncs = function (tran) { let num = this.headers.shpCode; if (num > 20) { num -= 20; } if (!(num in shpFuncObj)) { throw new Error(`I don't know shp type "${num}"`); } this.parseFunc = this[shpFuncObj[num]]; this.parseCoord = makeParseCoord(tran); }; ParseShp.prototype.getShpCode = function () { return this.parseHeader().shpCode; }; ParseShp.prototype.parseHeader = function () { const view = this.buffer; return { length: view.getInt32(6 << 2) << 1, version: view.getInt32(7 << 2, true), shpCode: view.getInt32(8 << 2, true), bbox: [ view.getFloat64(9 << 2, true), view.getFloat64(11 << 2, true), view.getFloat64(13 << 2, true), view.getFloat64(15 << 2, true) ] }; }; ParseShp.prototype.getRows = function () { let offset = 100; const len = this.buffer.byteLength - 8; const out = []; let current; while (offset <= len) { current = this.getRow(offset); if (!current) { break; } offset += 8; offset += current.len; if (current.type) { out.push(this.parseFunc(current.data)); } else { out.push(null); } } return out; }; ParseShp.prototype.getRow = function (offset) { const id = this.buffer.getInt32(offset); const len = this.buffer.getInt32(offset + 4) << 1; if (len === 0) { return { id: id, len: len, type: 0 }; } if (offset + len + 8 > this.buffer.byteLength) { return; } return { id: id, len: len, data: new DataView(this.buffer.buffer, this.buffer.byteOffset + offset + 12, len - 4), type: this.buffer.getInt32(offset + 8, true) }; }; function parseShp (buffer, trans) { return new ParseShp(buffer, trans).rows; } var regex = /^(?:ANSI\s)?(\d+)$/m; function createDecoder(encoding, second) { if (!encoding) { return browserDecoder; } try { new TextDecoder(encoding.trim()); } catch (e) { var match = regex.exec(encoding); if (match && !second) { return createDecoder('windows-' + match[1], true); } else { encoding = undefined; return browserDecoder; } } return browserDecoder; function browserDecoder(buffer) { var decoder = new TextDecoder(encoding ? encoding : undefined); var out = decoder.decode(buffer, { stream: true }) + decoder.decode(); return out.replace(/\0/g, '').trim(); } } function dbfHeader(data) { var out = {}; out.lastUpdated = new Date(data.getUint8(1) + 1900, data.getUint8(2), data.getUint8(3)); out.records = data.getUint32(4, true); out.headerLen = data.getUint16(8, true); out.recLen = data.getUint16(10, true); return out; } function dbfRowHeader(data, headerLen, decoder) { var out = []; var offset = 32; while (offset < headerLen) { out.push({ name: decoder(new Uint8Array(data.buffer.slice(data.byteOffset + offset, data.byteOffset + offset + 11))), dataType: String.fromCharCode(data.getUint8(offset + 11)), len: data.getUint8(offset + 16), decimal: data.getUint8(offset + 17) }); if (data.getUint8(offset + 32) === 13) { break; } else { offset += 32; } } return out; } function rowFuncs(buffer, offset, len, type, decoder) { const data = new Uint8Array(buffer.buffer.slice(buffer.byteOffset + offset, buffer.byteOffset + offset + len)); var textData = decoder(data); switch (type) { case 'N': case 'F': case 'O': return parseFloat(textData, 10); case 'D': return new Date(textData.slice(0, 4), parseInt(textData.slice(4, 6), 10) - 1, textData.slice(6, 8)); case 'L': return textData.toLowerCase() === 'y' || textData.toLowerCase() === 't'; default: return textData; } } function parseRow(buffer, offset, rowHeaders, decoder) { var out = {}; var i = 0; var len = rowHeaders.length; var field; var header; while (i < len) { header = rowHeaders[i]; field = rowFuncs(buffer, offset, header.len, header.dataType, decoder); offset += header.len; if (typeof field !== 'undefined') { out[header.name] = field; } i++; } return out; } function parseDbf (buffer, encoding) { var decoder = createDecoder(encoding); var header = dbfHeader(buffer); var rowHeaders = dbfRowHeader(buffer, header.headerLen - 1, decoder); var offset = ((rowHeaders.length + 1) << 5) + 2; var recLen = header.recLen; var records = header.records; var out = []; while (records) { out.push(parseRow(buffer, offset, rowHeaders, decoder)); offset += recLen; records--; } return out; } const URL = globalThis.URL; const toUitn8Arr = b => { if (!b) { throw new Error('forgot to pass buffer'); } if (isArrayBuffer(b)) { return new Uint8Array(b); } if (isArrayBuffer(b.buffer)) { if (b.BYTES_PER_ELEMENT === 1) { return b; } return new Uint8Array(b.buffer, b.byteOffset, b.byteLength); } throw new Error('invalid buffer like object') }; const txtDecoder = new TextDecoder(); const toString = (possibleString) => { if (!possibleString) { return; } if (typeof possibleString === 'string') { return possibleString; } if (isArrayBuffer(possibleString) || ArrayBuffer.isView(possibleString) || isDataView(possibleString)) { return txtDecoder.decode(possibleString); } }; const toDataView = b => { if (!b) { throw new Error('forgot to pass buffer'); } if (isDataView(b)) { return b; } if (isArrayBuffer(b)) { return new DataView(b); } if (isArrayBuffer(b.buffer)) { return new DataView(b.buffer, b.byteOffset, b.byteLength); } throw new Error('invalid buffer like object') }; function isArrayBuffer(subject) { return subject instanceof globalThis.ArrayBuffer || Object.prototype.toString.call(subject) === '[object ArrayBuffer]'; } function isDataView(subject) { return subject instanceof globalThis.DataView || Object.prototype.toString.call(subject) === '[object DataView]' } const combine = function ([shp, dbf]) { const out = {}; out.type = 'FeatureCollection'; out.features = []; let i = 0; const len = shp.length; if (!dbf) { dbf = []; } while (i < len) { out.features.push({ type: 'Feature', geometry: shp[i], properties: dbf[i] || {} }); i++; } return out; }; const parseZip = async function (buffer, whiteList) { let key; buffer = toUitn8Arr(buffer); const zip = await unzip(buffer); const names = []; whiteList = whiteList || []; for (key in zip) { if (key.indexOf('__MACOSX') !== -1) { continue; } if (key.slice(-4).toLowerCase() === '.shp') { names.push(key.slice(0, -4)); zip[key.slice(0, -3) + key.slice(-3).toLowerCase()] = zip[key]; } else if (key.slice(-4).toLowerCase() === '.prj') { zip[key.slice(0, -3) + key.slice(-3).toLowerCase()] = proj4(zip[key]); } else if (key.slice(-5).toLowerCase() === '.json' || whiteList.indexOf(key.split('.').pop()) > -1) { names.push(key.slice(0, -3) + key.slice(-3).toLowerCase()); } else if (key.slice(-4).toLowerCase() === '.dbf' || key.slice(-4).toLowerCase() === '.cpg') { zip[key.slice(0, -3) + key.slice(-3).toLowerCase()] = zip[key]; } } if (!names.length) { throw new Error('no layers founds'); } const geojson = names.map(function (name) { let parsed, dbf; const lastDotIdx = name.lastIndexOf('.'); if (lastDotIdx > -1 && name.slice(lastDotIdx).indexOf('json') > -1) { parsed = JSON.parse(zip[name]); parsed.fileName = name.slice(0, lastDotIdx); } else if (whiteList.indexOf(name.slice(lastDotIdx + 1)) > -1) { parsed = zip[name]; parsed.fileName = name; } else { if (zip[name + '.dbf']) { dbf = parseDbf(zip[name + '.dbf'], zip[name + '.cpg']); } parsed = combine([parseShp(zip[name + '.shp'], zip[name + '.prj']), dbf]); parsed.fileName = name; } return parsed; }); if (geojson.length === 1) { return geojson[0]; } else { return geojson; } }; async function getZip(base, whiteList) { const a = await binaryAjax(base); return parseZip(a, whiteList); } const handleShp = async (base) => { const args = await Promise.all([ binaryAjax(base, 'shp'), binaryAjax(base, 'prj') ]); let prj = false; try { if (args[1]) { prj = proj4(args[1]); } } catch (e) { prj = false; } return parseShp(args[0], prj); }; const handleDbf = async (base) => { const [dbf, cpg] = await Promise.all([ binaryAjax(base, 'dbf'), binaryAjax(base, 'cpg') ]); if (!dbf) { return; } return parseDbf(dbf, cpg); }; const checkSuffix = (base, suffix) => { const url = new URL(base, globalThis?.document?.location); return url.pathname.slice(-4).toLowerCase() === suffix; }; const fromObject = ({ shp, dbf, cpg, prj }) => { const things = [ _parseShp(shp, prj) ]; if (dbf) { things.push(_parseDbf(dbf, cpg)); } return combine(things); }; const getShapefile = async function (base, whiteList) { if (typeof base !== 'string') { if (isArrayBuffer(base) || ArrayBuffer.isView(base) || isDataView(base)) { return parseZip(base); } if (base.shp) { return fromObject(base); } throw new TypeError('must be a string, some sort of Buffer, or an object with at least a .shp property') } if (checkSuffix(base, '.zip')) { return getZip(base, whiteList); } if (checkSuffix(base, '.shp')) { base = base.slice(0, -4); } const results = await Promise.all([ handleShp(base), handleDbf(base) ]); return combine(results); }; const _parseShp = function (shp, prj) { shp = toDataView(shp); prj = toString(prj); if (typeof prj === 'string') { try { prj = proj4(prj); } catch (e) { prj = false; } } return parseShp(shp, prj); }; const _parseDbf = function (dbf, cpg) { dbf = toDataView(dbf); cpg = toString(cpg); return parseDbf(dbf, cpg); }; export { combine, getShapefile as default, getShapefile, _parseDbf as parseDbf, _parseShp as parseShp, parseZip }; ================================================ FILE: public/assets/lib/vendor/pdfjs/pdf.js ================================================ /** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2023 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ /******/ var __webpack_modules__ = ({ /***/ 640: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { // EXPORTS __webpack_require__.d(__webpack_exports__, { AnnotationLayer: () => (/* binding */ AnnotationLayer), FreeTextAnnotationElement: () => (/* binding */ FreeTextAnnotationElement), InkAnnotationElement: () => (/* binding */ InkAnnotationElement), StampAnnotationElement: () => (/* binding */ StampAnnotationElement) }); // EXTERNAL MODULE: ./src/shared/util.js var util = __webpack_require__(266); // EXTERNAL MODULE: ./src/display/display_utils.js var display_utils = __webpack_require__(473); // EXTERNAL MODULE: ./src/display/annotation_storage.js var annotation_storage = __webpack_require__(780); ;// CONCATENATED MODULE: ./src/shared/scripting_utils.js function makeColorComp(n) { return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0"); } function scaleAndClamp(x) { return Math.max(0, Math.min(255, 255 * x)); } class ColorConverters { static CMYK_G([c, y, m, k]) { return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)]; } static G_CMYK([g]) { return ["CMYK", 0, 0, 0, 1 - g]; } static G_RGB([g]) { return ["RGB", g, g, g]; } static G_rgb([g]) { g = scaleAndClamp(g); return [g, g, g]; } static G_HTML([g]) { const G = makeColorComp(g); return `#${G}${G}${G}`; } static RGB_G([r, g, b]) { return ["G", 0.3 * r + 0.59 * g + 0.11 * b]; } static RGB_rgb(color) { return color.map(scaleAndClamp); } static RGB_HTML(color) { return `#${color.map(makeColorComp).join("")}`; } static T_HTML() { return "#00000000"; } static T_rgb() { return [null]; } static CMYK_RGB([c, y, m, k]) { return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)]; } static CMYK_rgb([c, y, m, k]) { return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))]; } static CMYK_HTML(components) { const rgb = this.CMYK_RGB(components).slice(1); return this.RGB_HTML(rgb); } static RGB_CMYK([r, g, b]) { const c = 1 - r; const m = 1 - g; const y = 1 - b; const k = Math.min(c, m, y); return ["CMYK", c, m, y, k]; } } // EXTERNAL MODULE: ./src/display/xfa_layer.js var xfa_layer = __webpack_require__(160); ;// CONCATENATED MODULE: ./src/display/annotation_layer.js const DEFAULT_TAB_INDEX = 1000; const DEFAULT_FONT_SIZE = 9; const GetElementsByNameSet = new WeakSet(); function getRectDims(rect) { return { width: rect[2] - rect[0], height: rect[3] - rect[1] }; } class AnnotationElementFactory { static create(parameters) { const subtype = parameters.data.annotationType; switch (subtype) { case util.AnnotationType.LINK: return new LinkAnnotationElement(parameters); case util.AnnotationType.TEXT: return new TextAnnotationElement(parameters); case util.AnnotationType.WIDGET: const fieldType = parameters.data.fieldType; switch (fieldType) { case "Tx": return new TextWidgetAnnotationElement(parameters); case "Btn": if (parameters.data.radioButton) { return new RadioButtonWidgetAnnotationElement(parameters); } else if (parameters.data.checkBox) { return new CheckboxWidgetAnnotationElement(parameters); } return new PushButtonWidgetAnnotationElement(parameters); case "Ch": return new ChoiceWidgetAnnotationElement(parameters); case "Sig": return new SignatureWidgetAnnotationElement(parameters); } return new WidgetAnnotationElement(parameters); case util.AnnotationType.POPUP: return new PopupAnnotationElement(parameters); case util.AnnotationType.FREETEXT: return new FreeTextAnnotationElement(parameters); case util.AnnotationType.LINE: return new LineAnnotationElement(parameters); case util.AnnotationType.SQUARE: return new SquareAnnotationElement(parameters); case util.AnnotationType.CIRCLE: return new CircleAnnotationElement(parameters); case util.AnnotationType.POLYLINE: return new PolylineAnnotationElement(parameters); case util.AnnotationType.CARET: return new CaretAnnotationElement(parameters); case util.AnnotationType.INK: return new InkAnnotationElement(parameters); case util.AnnotationType.POLYGON: return new PolygonAnnotationElement(parameters); case util.AnnotationType.HIGHLIGHT: return new HighlightAnnotationElement(parameters); case util.AnnotationType.UNDERLINE: return new UnderlineAnnotationElement(parameters); case util.AnnotationType.SQUIGGLY: return new SquigglyAnnotationElement(parameters); case util.AnnotationType.STRIKEOUT: return new StrikeOutAnnotationElement(parameters); case util.AnnotationType.STAMP: return new StampAnnotationElement(parameters); case util.AnnotationType.FILEATTACHMENT: return new FileAttachmentAnnotationElement(parameters); default: return new AnnotationElement(parameters); } } } class AnnotationElement { #hasBorder = false; constructor(parameters, { isRenderable = false, ignoreBorder = false, createQuadrilaterals = false } = {}) { this.isRenderable = isRenderable; this.data = parameters.data; this.layer = parameters.layer; this.linkService = parameters.linkService; this.downloadManager = parameters.downloadManager; this.imageResourcesPath = parameters.imageResourcesPath; this.renderForms = parameters.renderForms; this.svgFactory = parameters.svgFactory; this.annotationStorage = parameters.annotationStorage; this.enableScripting = parameters.enableScripting; this.hasJSActions = parameters.hasJSActions; this._fieldObjects = parameters.fieldObjects; this.parent = parameters.parent; if (isRenderable) { this.container = this._createContainer(ignoreBorder); } if (createQuadrilaterals) { this._createQuadrilaterals(); } } static _hasPopupData({ titleObj, contentsObj, richText }) { return !!(titleObj?.str || contentsObj?.str || richText?.str); } get hasPopupData() { return AnnotationElement._hasPopupData(this.data); } _createContainer(ignoreBorder) { const { data, parent: { page, viewport } } = this; const container = document.createElement("section"); container.setAttribute("data-annotation-id", data.id); if (!(this instanceof WidgetAnnotationElement)) { container.tabIndex = DEFAULT_TAB_INDEX; } container.style.zIndex = this.parent.zIndex++; if (this.data.popupRef) { container.setAttribute("aria-haspopup", "dialog"); } if (data.noRotate) { container.classList.add("norotate"); } const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; if (!data.rect || this instanceof PopupAnnotationElement) { const { rotation } = data; if (!data.hasOwnCanvas && rotation !== 0) { this.setRotation(rotation, container); } return container; } const { width, height } = getRectDims(data.rect); const rect = util.Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]); if (!ignoreBorder && data.borderStyle.width > 0) { container.style.borderWidth = `${data.borderStyle.width}px`; const horizontalRadius = data.borderStyle.horizontalCornerRadius; const verticalRadius = data.borderStyle.verticalCornerRadius; if (horizontalRadius > 0 || verticalRadius > 0) { const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`; container.style.borderRadius = radius; } else if (this instanceof RadioButtonWidgetAnnotationElement) { const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`; container.style.borderRadius = radius; } switch (data.borderStyle.style) { case util.AnnotationBorderStyleType.SOLID: container.style.borderStyle = "solid"; break; case util.AnnotationBorderStyleType.DASHED: container.style.borderStyle = "dashed"; break; case util.AnnotationBorderStyleType.BEVELED: (0,util.warn)("Unimplemented border style: beveled"); break; case util.AnnotationBorderStyleType.INSET: (0,util.warn)("Unimplemented border style: inset"); break; case util.AnnotationBorderStyleType.UNDERLINE: container.style.borderBottomStyle = "solid"; break; default: break; } const borderColor = data.borderColor || null; if (borderColor) { this.#hasBorder = true; container.style.borderColor = util.Util.makeHexColor(borderColor[0] | 0, borderColor[1] | 0, borderColor[2] | 0); } else { container.style.borderWidth = 0; } } container.style.left = `${100 * (rect[0] - pageX) / pageWidth}%`; container.style.top = `${100 * (rect[1] - pageY) / pageHeight}%`; const { rotation } = data; if (data.hasOwnCanvas || rotation === 0) { container.style.width = `${100 * width / pageWidth}%`; container.style.height = `${100 * height / pageHeight}%`; } else { this.setRotation(rotation, container); } return container; } setRotation(angle, container = this.container) { if (!this.data.rect) { return; } const { pageWidth, pageHeight } = this.parent.viewport.rawDims; const { width, height } = getRectDims(this.data.rect); let elementWidth, elementHeight; if (angle % 180 === 0) { elementWidth = 100 * width / pageWidth; elementHeight = 100 * height / pageHeight; } else { elementWidth = 100 * height / pageWidth; elementHeight = 100 * width / pageHeight; } container.style.width = `${elementWidth}%`; container.style.height = `${elementHeight}%`; container.setAttribute("data-main-rotation", (360 - angle) % 360); } get _commonActions() { const setColor = (jsName, styleName, event) => { const color = event.detail[jsName]; const colorType = color[0]; const colorArray = color.slice(1); event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray); this.annotationStorage.setValue(this.data.id, { [styleName]: ColorConverters[`${colorType}_rgb`](colorArray) }); }; return (0,util.shadow)(this, "_commonActions", { display: event => { const { display } = event.detail; const hidden = display % 2 === 1; this.container.style.visibility = hidden ? "hidden" : "visible"; this.annotationStorage.setValue(this.data.id, { noView: hidden, noPrint: display === 1 || display === 2 }); }, print: event => { this.annotationStorage.setValue(this.data.id, { noPrint: !event.detail.print }); }, hidden: event => { const { hidden } = event.detail; this.container.style.visibility = hidden ? "hidden" : "visible"; this.annotationStorage.setValue(this.data.id, { noPrint: hidden, noView: hidden }); }, focus: event => { setTimeout(() => event.target.focus({ preventScroll: false }), 0); }, userName: event => { event.target.title = event.detail.userName; }, readonly: event => { event.target.disabled = event.detail.readonly; }, required: event => { this._setRequired(event.target, event.detail.required); }, bgColor: event => { setColor("bgColor", "backgroundColor", event); }, fillColor: event => { setColor("fillColor", "backgroundColor", event); }, fgColor: event => { setColor("fgColor", "color", event); }, textColor: event => { setColor("textColor", "color", event); }, borderColor: event => { setColor("borderColor", "borderColor", event); }, strokeColor: event => { setColor("strokeColor", "borderColor", event); }, rotation: event => { const angle = event.detail.rotation; this.setRotation(angle); this.annotationStorage.setValue(this.data.id, { rotation: angle }); } }); } _dispatchEventFromSandbox(actions, jsEvent) { const commonActions = this._commonActions; for (const name of Object.keys(jsEvent.detail)) { const action = actions[name] || commonActions[name]; action?.(jsEvent); } } _setDefaultPropertiesFromJS(element) { if (!this.enableScripting) { return; } const storedData = this.annotationStorage.getRawValue(this.data.id); if (!storedData) { return; } const commonActions = this._commonActions; for (const [actionName, detail] of Object.entries(storedData)) { const action = commonActions[actionName]; if (action) { const eventProxy = { detail: { [actionName]: detail }, target: element }; action(eventProxy); delete storedData[actionName]; } } } _createQuadrilaterals() { if (!this.container) { return; } const { quadPoints } = this.data; if (!quadPoints) { return; } const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect; if (quadPoints.length === 1) { const [, { x: trX, y: trY }, { x: blX, y: blY }] = quadPoints[0]; if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) { return; } } const { style } = this.container; let svgBuffer; if (this.#hasBorder) { const { borderColor, borderWidth } = style; style.borderWidth = 0; svgBuffer = ["url('data:image/svg+xml;utf8,", `<svg xmlns="http://www.w3.org/2000/svg"`, ` preserveAspectRatio="none" viewBox="0 0 1 1">`, `<g fill="transparent" stroke="${borderColor}" stroke-width="${borderWidth}">`]; this.container.classList.add("hasBorder"); } const width = rectTrX - rectBlX; const height = rectTrY - rectBlY; const { svgFactory } = this; const svg = svgFactory.createElement("svg"); svg.classList.add("quadrilateralsContainer"); svg.setAttribute("width", 0); svg.setAttribute("height", 0); const defs = svgFactory.createElement("defs"); svg.append(defs); const clipPath = svgFactory.createElement("clipPath"); const id = `clippath_${this.data.id}`; clipPath.setAttribute("id", id); clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); defs.append(clipPath); for (const [, { x: trX, y: trY }, { x: blX, y: blY }] of quadPoints) { const rect = svgFactory.createElement("rect"); const x = (blX - rectBlX) / width; const y = (rectTrY - trY) / height; const rectWidth = (trX - blX) / width; const rectHeight = (trY - blY) / height; rect.setAttribute("x", x); rect.setAttribute("y", y); rect.setAttribute("width", rectWidth); rect.setAttribute("height", rectHeight); clipPath.append(rect); svgBuffer?.push(`<rect vector-effect="non-scaling-stroke" x="${x}" y="${y}" width="${rectWidth}" height="${rectHeight}"/>`); } if (this.#hasBorder) { svgBuffer.push(`</g></svg>')`); style.backgroundImage = svgBuffer.join(""); } this.container.append(svg); this.container.style.clipPath = `url(#${id})`; } _createPopup() { const { container, data } = this; container.setAttribute("aria-haspopup", "dialog"); const popup = new PopupAnnotationElement({ data: { color: data.color, titleObj: data.titleObj, modificationDate: data.modificationDate, contentsObj: data.contentsObj, richText: data.richText, parentRect: data.rect, borderStyle: 0, id: `popup_${data.id}`, rotation: data.rotation }, parent: this.parent, elements: [this] }); this.parent.div.append(popup.render()); } render() { (0,util.unreachable)("Abstract method `AnnotationElement.render` called"); } _getElementsByName(name, skipId = null) { const fields = []; if (this._fieldObjects) { const fieldObj = this._fieldObjects[name]; if (fieldObj) { for (const { page, id, exportValues } of fieldObj) { if (page === -1) { continue; } if (id === skipId) { continue; } const exportValue = typeof exportValues === "string" ? exportValues : null; const domElement = document.querySelector(`[data-element-id="${id}"]`); if (domElement && !GetElementsByNameSet.has(domElement)) { (0,util.warn)(`_getElementsByName - element not allowed: ${id}`); continue; } fields.push({ id, exportValue, domElement }); } } return fields; } for (const domElement of document.getElementsByName(name)) { const { exportValue } = domElement; const id = domElement.getAttribute("data-element-id"); if (id === skipId) { continue; } if (!GetElementsByNameSet.has(domElement)) { continue; } fields.push({ id, exportValue, domElement }); } return fields; } show() { if (this.container) { this.container.hidden = false; } this.popup?.maybeShow(); } hide() { if (this.container) { this.container.hidden = true; } this.popup?.forceHide(); } getElementsToTriggerPopup() { return this.container; } addHighlightArea() { const triggers = this.getElementsToTriggerPopup(); if (Array.isArray(triggers)) { for (const element of triggers) { element.classList.add("highlightArea"); } } else { triggers.classList.add("highlightArea"); } } get _isEditable() { return false; } _editOnDoubleClick() { if (!this._isEditable) { return; } const { annotationEditorType: mode, data: { id: editId } } = this; this.container.addEventListener("dblclick", () => { this.linkService.eventBus?.dispatch("switchannotationeditormode", { source: this, mode, editId }); }); } } class LinkAnnotationElement extends AnnotationElement { constructor(parameters, options = null) { super(parameters, { isRenderable: true, ignoreBorder: !!options?.ignoreBorder, createQuadrilaterals: true }); this.isTooltipOnly = parameters.data.isTooltipOnly; } render() { const { data, linkService } = this; const link = document.createElement("a"); link.setAttribute("data-element-id", data.id); let isBound = false; if (data.url) { linkService.addLinkAttributes(link, data.url, data.newWindow); isBound = true; } else if (data.action) { this._bindNamedAction(link, data.action); isBound = true; } else if (data.attachment) { this.#bindAttachment(link, data.attachment, data.attachmentDest); isBound = true; } else if (data.setOCGState) { this.#bindSetOCGState(link, data.setOCGState); isBound = true; } else if (data.dest) { this._bindLink(link, data.dest); isBound = true; } else { if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) { this._bindJSAction(link, data); isBound = true; } if (data.resetForm) { this._bindResetFormAction(link, data.resetForm); isBound = true; } else if (this.isTooltipOnly && !isBound) { this._bindLink(link, ""); isBound = true; } } this.container.classList.add("linkAnnotation"); if (isBound) { this.container.append(link); } return this.container; } #setInternalLink() { this.container.setAttribute("data-internal-link", ""); } _bindLink(link, destination) { link.href = this.linkService.getDestinationHash(destination); link.onclick = () => { if (destination) { this.linkService.goToDestination(destination); } return false; }; if (destination || destination === "") { this.#setInternalLink(); } } _bindNamedAction(link, action) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.linkService.executeNamedAction(action); return false; }; this.#setInternalLink(); } #bindAttachment(link, attachment, dest = null) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.downloadManager?.openOrDownloadData(attachment.content, attachment.filename, dest); return false; }; this.#setInternalLink(); } #bindSetOCGState(link, action) { link.href = this.linkService.getAnchorUrl(""); link.onclick = () => { this.linkService.executeSetOCGState(action); return false; }; this.#setInternalLink(); } _bindJSAction(link, data) { link.href = this.linkService.getAnchorUrl(""); const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]); for (const name of Object.keys(data.actions)) { const jsName = map.get(name); if (!jsName) { continue; } link[jsName] = () => { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: data.id, name } }); return false; }; } if (!link.onclick) { link.onclick = () => false; } this.#setInternalLink(); } _bindResetFormAction(link, resetForm) { const otherClickAction = link.onclick; if (!otherClickAction) { link.href = this.linkService.getAnchorUrl(""); } this.#setInternalLink(); if (!this._fieldObjects) { (0,util.warn)(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided."); if (!otherClickAction) { link.onclick = () => false; } return; } link.onclick = () => { otherClickAction?.(); const { fields: resetFormFields, refs: resetFormRefs, include } = resetForm; const allFields = []; if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) { const fieldIds = new Set(resetFormRefs); for (const fieldName of resetFormFields) { const fields = this._fieldObjects[fieldName] || []; for (const { id } of fields) { fieldIds.add(id); } } for (const fields of Object.values(this._fieldObjects)) { for (const field of fields) { if (fieldIds.has(field.id) === include) { allFields.push(field); } } } } else { for (const fields of Object.values(this._fieldObjects)) { allFields.push(...fields); } } const storage = this.annotationStorage; const allIds = []; for (const field of allFields) { const { id } = field; allIds.push(id); switch (field.type) { case "text": { const value = field.defaultValue || ""; storage.setValue(id, { value }); break; } case "checkbox": case "radiobutton": { const value = field.defaultValue === field.exportValues; storage.setValue(id, { value }); break; } case "combobox": case "listbox": { const value = field.defaultValue || ""; storage.setValue(id, { value }); break; } default: continue; } const domElement = document.querySelector(`[data-element-id="${id}"]`); if (!domElement) { continue; } else if (!GetElementsByNameSet.has(domElement)) { (0,util.warn)(`_bindResetFormAction - element not allowed: ${id}`); continue; } domElement.dispatchEvent(new Event("resetform")); } if (this.enableScripting) { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: "app", ids: allIds, name: "ResetForm" } }); } return false; }; } } class TextAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true }); } render() { this.container.classList.add("textAnnotation"); const image = document.createElement("img"); image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg"; image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type"); image.setAttribute("data-l10n-args", JSON.stringify({ type: this.data.name })); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.append(image); return this.container; } } class WidgetAnnotationElement extends AnnotationElement { render() { if (this.data.alternativeText) { this.container.title = this.data.alternativeText; } return this.container; } showElementAndHideCanvas(element) { if (this.data.hasOwnCanvas) { if (element.previousSibling?.nodeName === "CANVAS") { element.previousSibling.hidden = true; } element.hidden = false; } } _getKeyModifier(event) { return util.FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey; } _setEventListener(element, elementData, baseName, eventName, valueGetter) { if (baseName.includes("mouse")) { element.addEventListener(baseName, event => { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: this.data.id, name: eventName, value: valueGetter(event), shift: event.shiftKey, modifier: this._getKeyModifier(event) } }); }); } else { element.addEventListener(baseName, event => { if (baseName === "blur") { if (!elementData.focused || !event.relatedTarget) { return; } elementData.focused = false; } else if (baseName === "focus") { if (elementData.focused) { return; } elementData.focused = true; } if (!valueGetter) { return; } this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id: this.data.id, name: eventName, value: valueGetter(event) } }); }); } } _setEventListeners(element, elementData, names, getter) { for (const [baseName, eventName] of names) { if (eventName === "Action" || this.data.actions?.[eventName]) { if (eventName === "Focus" || eventName === "Blur") { elementData ||= { focused: false }; } this._setEventListener(element, elementData, baseName, eventName, getter); if (eventName === "Focus" && !this.data.actions?.Blur) { this._setEventListener(element, elementData, "blur", "Blur", null); } else if (eventName === "Blur" && !this.data.actions?.Focus) { this._setEventListener(element, elementData, "focus", "Focus", null); } } } } _setBackgroundColor(element) { const color = this.data.backgroundColor || null; element.style.backgroundColor = color === null ? "transparent" : util.Util.makeHexColor(color[0], color[1], color[2]); } _setTextStyle(element) { const TEXT_ALIGNMENT = ["left", "center", "right"]; const { fontColor } = this.data.defaultAppearanceData; const fontSize = this.data.defaultAppearanceData.fontSize || DEFAULT_FONT_SIZE; const style = element.style; let computedFontSize; const BORDER_SIZE = 2; const roundToOneDecimal = x => Math.round(10 * x) / 10; if (this.data.multiLine) { const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); const numberOfLines = Math.round(height / (util.LINE_FACTOR * fontSize)) || 1; const lineHeight = height / numberOfLines; computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / util.LINE_FACTOR)); } else { const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE); computedFontSize = Math.min(fontSize, roundToOneDecimal(height / util.LINE_FACTOR)); } style.fontSize = `calc(${computedFontSize}px * var(--scale-factor))`; style.color = util.Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]); if (this.data.textAlignment !== null) { style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; } } _setRequired(element, isRequired) { if (isRequired) { element.setAttribute("required", true); } else { element.removeAttribute("required"); } element.setAttribute("aria-required", isRequired); } } class TextWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue; super(parameters, { isRenderable }); } setPropertyOnSiblings(base, key, value, keyInStorage) { const storage = this.annotationStorage; for (const element of this._getElementsByName(base.name, base.id)) { if (element.domElement) { element.domElement[key] = value; } storage.setValue(element.id, { [keyInStorage]: value }); } } render() { const storage = this.annotationStorage; const id = this.data.id; this.container.classList.add("textWidgetAnnotation"); let element = null; if (this.renderForms) { const storedData = storage.getValue(id, { value: this.data.fieldValue }); let textContent = storedData.value || ""; const maxLen = storage.getValue(id, { charLimit: this.data.maxLen }).charLimit; if (maxLen && textContent.length > maxLen) { textContent = textContent.slice(0, maxLen); } let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null; if (fieldFormattedValues && this.data.comb) { fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, ""); } const elementData = { userValue: textContent, formattedValue: fieldFormattedValues, lastCommittedValue: null, commitKey: 1, focused: false }; if (this.data.multiLine) { element = document.createElement("textarea"); element.textContent = fieldFormattedValues ?? textContent; if (this.data.doNotScroll) { element.style.overflowY = "hidden"; } } else { element = document.createElement("input"); element.type = "text"; element.setAttribute("value", fieldFormattedValues ?? textContent); if (this.data.doNotScroll) { element.style.overflowX = "hidden"; } } if (this.data.hasOwnCanvas) { element.hidden = true; } GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = this.data.readOnly; element.name = this.data.fieldName; element.tabIndex = DEFAULT_TAB_INDEX; this._setRequired(element, this.data.required); if (maxLen) { element.maxLength = maxLen; } element.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); this.setPropertyOnSiblings(element, "value", event.target.value, "value"); elementData.formattedValue = null; }); element.addEventListener("resetform", event => { const defaultValue = this.data.defaultFieldValue ?? ""; element.value = elementData.userValue = defaultValue; elementData.formattedValue = null; }); let blurListener = event => { const { formattedValue } = elementData; if (formattedValue !== null && formattedValue !== undefined) { event.target.value = formattedValue; } event.target.scrollLeft = 0; }; if (this.enableScripting && this.hasJSActions) { element.addEventListener("focus", event => { if (elementData.focused) { return; } const { target } = event; if (elementData.userValue) { target.value = elementData.userValue; } elementData.lastCommittedValue = target.value; elementData.commitKey = 1; if (!this.data.actions?.Focus) { elementData.focused = true; } }); element.addEventListener("updatefromsandbox", jsEvent => { this.showElementAndHideCanvas(jsEvent.target); const actions = { value(event) { elementData.userValue = event.detail.value ?? ""; storage.setValue(id, { value: elementData.userValue.toString() }); event.target.value = elementData.userValue; }, formattedValue(event) { const { formattedValue } = event.detail; elementData.formattedValue = formattedValue; if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) { event.target.value = formattedValue; } storage.setValue(id, { formattedValue }); }, selRange(event) { event.target.setSelectionRange(...event.detail.selRange); }, charLimit: event => { const { charLimit } = event.detail; const { target } = event; if (charLimit === 0) { target.removeAttribute("maxLength"); return; } target.setAttribute("maxLength", charLimit); let value = elementData.userValue; if (!value || value.length <= charLimit) { return; } value = value.slice(0, charLimit); target.value = elementData.userValue = value; storage.setValue(id, { value }); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey: 1, selStart: target.selectionStart, selEnd: target.selectionEnd } }); } }; this._dispatchEventFromSandbox(actions, jsEvent); }); element.addEventListener("keydown", event => { elementData.commitKey = 1; let commitKey = -1; if (event.key === "Escape") { commitKey = 0; } else if (event.key === "Enter" && !this.data.multiLine) { commitKey = 2; } else if (event.key === "Tab") { elementData.commitKey = 3; } if (commitKey === -1) { return; } const { value } = event.target; if (elementData.lastCommittedValue === value) { return; } elementData.lastCommittedValue = value; elementData.userValue = value; this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey, selStart: event.target.selectionStart, selEnd: event.target.selectionEnd } }); }); const _blurListener = blurListener; blurListener = null; element.addEventListener("blur", event => { if (!elementData.focused || !event.relatedTarget) { return; } if (!this.data.actions?.Blur) { elementData.focused = false; } const { value } = event.target; elementData.userValue = value; if (elementData.lastCommittedValue !== value) { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, willCommit: true, commitKey: elementData.commitKey, selStart: event.target.selectionStart, selEnd: event.target.selectionEnd } }); } _blurListener(event); }); if (this.data.actions?.Keystroke) { element.addEventListener("beforeinput", event => { elementData.lastCommittedValue = null; const { data, target } = event; const { value, selectionStart, selectionEnd } = target; let selStart = selectionStart, selEnd = selectionEnd; switch (event.inputType) { case "deleteWordBackward": { const match = value.substring(0, selectionStart).match(/\w*[^\w]*$/); if (match) { selStart -= match[0].length; } break; } case "deleteWordForward": { const match = value.substring(selectionStart).match(/^[^\w]*\w*/); if (match) { selEnd += match[0].length; } break; } case "deleteContentBackward": if (selectionStart === selectionEnd) { selStart -= 1; } break; case "deleteContentForward": if (selectionStart === selectionEnd) { selEnd += 1; } break; } event.preventDefault(); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value, change: data || "", willCommit: false, selStart, selEnd } }); }); } this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value); } if (blurListener) { element.addEventListener("blur", blurListener); } if (this.data.comb) { const fieldWidth = this.data.rect[2] - this.data.rect[0]; const combWidth = fieldWidth / maxLen; element.classList.add("comb"); element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`; } } else { element = document.createElement("div"); element.textContent = this.data.fieldValue; element.style.verticalAlign = "middle"; element.style.display = "table-cell"; if (this.data.hasOwnCanvas) { element.hidden = true; } } this._setTextStyle(element); this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class SignatureWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: !!parameters.data.hasOwnCanvas }); } } class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { const storage = this.annotationStorage; const data = this.data; const id = data.id; let value = storage.getValue(id, { value: data.exportValue === data.fieldValue }).value; if (typeof value === "string") { value = value !== "Off"; storage.setValue(id, { value }); } this.container.classList.add("buttonWidgetAnnotation", "checkBox"); const element = document.createElement("input"); GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = data.readOnly; this._setRequired(element, this.data.required); element.type = "checkbox"; element.name = data.fieldName; if (value) { element.setAttribute("checked", true); } element.setAttribute("exportValue", data.exportValue); element.tabIndex = DEFAULT_TAB_INDEX; element.addEventListener("change", event => { const { name, checked } = event.target; for (const checkbox of this._getElementsByName(name, id)) { const curChecked = checked && checkbox.exportValue === data.exportValue; if (checkbox.domElement) { checkbox.domElement.checked = curChecked; } storage.setValue(checkbox.id, { value: curChecked }); } storage.setValue(id, { value: checked }); }); element.addEventListener("resetform", event => { const defaultValue = data.defaultFieldValue || "Off"; event.target.checked = defaultValue === data.exportValue; }); if (this.enableScripting && this.hasJSActions) { element.addEventListener("updatefromsandbox", jsEvent => { const actions = { value(event) { event.target.checked = event.detail.value !== "Off"; storage.setValue(id, { value: event.target.checked }); } }; this._dispatchEventFromSandbox(actions, jsEvent); }); this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked); } this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { this.container.classList.add("buttonWidgetAnnotation", "radioButton"); const storage = this.annotationStorage; const data = this.data; const id = data.id; let value = storage.getValue(id, { value: data.fieldValue === data.buttonValue }).value; if (typeof value === "string") { value = value !== data.buttonValue; storage.setValue(id, { value }); } if (value) { for (const radio of this._getElementsByName(data.fieldName, id)) { storage.setValue(radio.id, { value: false }); } } const element = document.createElement("input"); GetElementsByNameSet.add(element); element.setAttribute("data-element-id", id); element.disabled = data.readOnly; this._setRequired(element, this.data.required); element.type = "radio"; element.name = data.fieldName; if (value) { element.setAttribute("checked", true); } element.tabIndex = DEFAULT_TAB_INDEX; element.addEventListener("change", event => { const { name, checked } = event.target; for (const radio of this._getElementsByName(name, id)) { storage.setValue(radio.id, { value: false }); } storage.setValue(id, { value: checked }); }); element.addEventListener("resetform", event => { const defaultValue = data.defaultFieldValue; event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue; }); if (this.enableScripting && this.hasJSActions) { const pdfButtonValue = data.buttonValue; element.addEventListener("updatefromsandbox", jsEvent => { const actions = { value: event => { const checked = pdfButtonValue === event.detail.value; for (const radio of this._getElementsByName(event.target.name)) { const curChecked = checked && radio.id === id; if (radio.domElement) { radio.domElement.checked = curChecked; } storage.setValue(radio.id, { value: curChecked }); } } }; this._dispatchEventFromSandbox(actions, jsEvent); }); this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked); } this._setBackgroundColor(element); this._setDefaultPropertiesFromJS(element); this.container.append(element); return this.container; } } class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { constructor(parameters) { super(parameters, { ignoreBorder: parameters.data.hasAppearance }); } render() { const container = super.render(); container.classList.add("buttonWidgetAnnotation", "pushButton"); if (this.data.alternativeText) { container.title = this.data.alternativeText; } const linkElement = container.lastChild; if (this.enableScripting && this.hasJSActions && linkElement) { this._setDefaultPropertiesFromJS(linkElement); linkElement.addEventListener("updatefromsandbox", jsEvent => { this._dispatchEventFromSandbox({}, jsEvent); }); } return container; } } class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { constructor(parameters) { super(parameters, { isRenderable: parameters.renderForms }); } render() { this.container.classList.add("choiceWidgetAnnotation"); const storage = this.annotationStorage; const id = this.data.id; const storedData = storage.getValue(id, { value: this.data.fieldValue }); const selectElement = document.createElement("select"); GetElementsByNameSet.add(selectElement); selectElement.setAttribute("data-element-id", id); selectElement.disabled = this.data.readOnly; this._setRequired(selectElement, this.data.required); selectElement.name = this.data.fieldName; selectElement.tabIndex = DEFAULT_TAB_INDEX; let addAnEmptyEntry = this.data.combo && this.data.options.length > 0; if (!this.data.combo) { selectElement.size = this.data.options.length; if (this.data.multiSelect) { selectElement.multiple = true; } } selectElement.addEventListener("resetform", event => { const defaultValue = this.data.defaultFieldValue; for (const option of selectElement.options) { option.selected = option.value === defaultValue; } }); for (const option of this.data.options) { const optionElement = document.createElement("option"); optionElement.textContent = option.displayValue; optionElement.value = option.exportValue; if (storedData.value.includes(option.exportValue)) { optionElement.setAttribute("selected", true); addAnEmptyEntry = false; } selectElement.append(optionElement); } let removeEmptyEntry = null; if (addAnEmptyEntry) { const noneOptionElement = document.createElement("option"); noneOptionElement.value = " "; noneOptionElement.setAttribute("hidden", true); noneOptionElement.setAttribute("selected", true); selectElement.prepend(noneOptionElement); removeEmptyEntry = () => { noneOptionElement.remove(); selectElement.removeEventListener("input", removeEmptyEntry); removeEmptyEntry = null; }; selectElement.addEventListener("input", removeEmptyEntry); } const getValue = isExport => { const name = isExport ? "value" : "textContent"; const { options, multiple } = selectElement; if (!multiple) { return options.selectedIndex === -1 ? null : options[options.selectedIndex][name]; } return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]); }; let selectedValues = getValue(false); const getItems = event => { const options = event.target.options; return Array.prototype.map.call(options, option => { return { displayValue: option.textContent, exportValue: option.value }; }); }; if (this.enableScripting && this.hasJSActions) { selectElement.addEventListener("updatefromsandbox", jsEvent => { const actions = { value(event) { removeEmptyEntry?.(); const value = event.detail.value; const values = new Set(Array.isArray(value) ? value : [value]); for (const option of selectElement.options) { option.selected = values.has(option.value); } storage.setValue(id, { value: getValue(true) }); selectedValues = getValue(false); }, multipleSelection(event) { selectElement.multiple = true; }, remove(event) { const options = selectElement.options; const index = event.detail.remove; options[index].selected = false; selectElement.remove(index); if (options.length > 0) { const i = Array.prototype.findIndex.call(options, option => option.selected); if (i === -1) { options[0].selected = true; } } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, clear(event) { while (selectElement.length !== 0) { selectElement.remove(0); } storage.setValue(id, { value: null, items: [] }); selectedValues = getValue(false); }, insert(event) { const { index, displayValue, exportValue } = event.detail.insert; const selectChild = selectElement.children[index]; const optionElement = document.createElement("option"); optionElement.textContent = displayValue; optionElement.value = exportValue; if (selectChild) { selectChild.before(optionElement); } else { selectElement.append(optionElement); } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, items(event) { const { items } = event.detail; while (selectElement.length !== 0) { selectElement.remove(0); } for (const item of items) { const { displayValue, exportValue } = item; const optionElement = document.createElement("option"); optionElement.textContent = displayValue; optionElement.value = exportValue; selectElement.append(optionElement); } if (selectElement.options.length > 0) { selectElement.options[0].selected = true; } storage.setValue(id, { value: getValue(true), items: getItems(event) }); selectedValues = getValue(false); }, indices(event) { const indices = new Set(event.detail.indices); for (const option of event.target.options) { option.selected = indices.has(option.index); } storage.setValue(id, { value: getValue(true) }); selectedValues = getValue(false); }, editable(event) { event.target.disabled = !event.detail.editable; } }; this._dispatchEventFromSandbox(actions, jsEvent); }); selectElement.addEventListener("input", event => { const exportValue = getValue(true); storage.setValue(id, { value: exportValue }); event.preventDefault(); this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", value: selectedValues, changeEx: exportValue, willCommit: false, commitKey: 1, keyDown: false } }); }); this._setEventListeners(selectElement, null, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"], ["input", "Action"], ["input", "Validate"]], event => event.target.value); } else { selectElement.addEventListener("input", function (event) { storage.setValue(id, { value: getValue(true) }); }); } if (this.data.combo) { this._setTextStyle(selectElement); } else {} this._setBackgroundColor(selectElement); this._setDefaultPropertiesFromJS(selectElement); this.container.append(selectElement); return this.container; } } class PopupAnnotationElement extends AnnotationElement { constructor(parameters) { const { data, elements } = parameters; super(parameters, { isRenderable: AnnotationElement._hasPopupData(data) }); this.elements = elements; } render() { this.container.classList.add("popupAnnotation"); const popup = new PopupElement({ container: this.container, color: this.data.color, titleObj: this.data.titleObj, modificationDate: this.data.modificationDate, contentsObj: this.data.contentsObj, richText: this.data.richText, rect: this.data.rect, parentRect: this.data.parentRect || null, parent: this.parent, elements: this.elements, open: this.data.open }); const elementIds = []; for (const element of this.elements) { element.popup = popup; elementIds.push(element.data.id); element.addHighlightArea(); } this.container.setAttribute("aria-controls", elementIds.map(id => `${util.AnnotationPrefix}${id}`).join(",")); return this.container; } } class PopupElement { #boundKeyDown = this.#keyDown.bind(this); #boundHide = this.#hide.bind(this); #boundShow = this.#show.bind(this); #boundToggle = this.#toggle.bind(this); #color = null; #container = null; #contentsObj = null; #dateObj = null; #elements = null; #parent = null; #parentRect = null; #pinned = false; #popup = null; #rect = null; #richText = null; #titleObj = null; #wasVisible = false; constructor({ container, color, elements, titleObj, modificationDate, contentsObj, richText, parent, rect, parentRect, open }) { this.#container = container; this.#titleObj = titleObj; this.#contentsObj = contentsObj; this.#richText = richText; this.#parent = parent; this.#color = color; this.#rect = rect; this.#parentRect = parentRect; this.#elements = elements; this.#dateObj = display_utils.PDFDateString.toDateObject(modificationDate); this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); for (const element of this.trigger) { element.addEventListener("click", this.#boundToggle); element.addEventListener("mouseenter", this.#boundShow); element.addEventListener("mouseleave", this.#boundHide); element.classList.add("popupTriggerArea"); } for (const element of elements) { element.container?.addEventListener("keydown", this.#boundKeyDown); } this.#container.hidden = true; if (open) { this.#toggle(); } } render() { if (this.#popup) { return; } const { page: { view }, viewport: { rawDims: { pageWidth, pageHeight, pageX, pageY } } } = this.#parent; const popup = this.#popup = document.createElement("div"); popup.className = "popup"; if (this.#color) { const baseColor = popup.style.outlineColor = util.Util.makeHexColor(...this.#color); if (CSS.supports("background-color", "color-mix(in srgb, red 30%, white)")) { popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`; } else { const BACKGROUND_ENLIGHT = 0.7; popup.style.backgroundColor = util.Util.makeHexColor(...this.#color.map(c => Math.floor(BACKGROUND_ENLIGHT * (255 - c) + c))); } } const header = document.createElement("span"); header.className = "header"; const title = document.createElement("h1"); header.append(title); ({ dir: title.dir, str: title.textContent } = this.#titleObj); popup.append(header); if (this.#dateObj) { const modificationDate = document.createElement("span"); modificationDate.classList.add("popupDate"); modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-string"); modificationDate.setAttribute("data-l10n-args", JSON.stringify({ date: this.#dateObj.toLocaleDateString(), time: this.#dateObj.toLocaleTimeString() })); header.append(modificationDate); } const contentsObj = this.#contentsObj; const richText = this.#richText; if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) { xfa_layer.XfaLayer.render({ xfaHtml: richText.html, intent: "richText", div: popup }); popup.lastChild.classList.add("richText", "popupContent"); } else { const contents = this._formatContents(contentsObj); popup.append(contents); } let useParentRect = !!this.#parentRect; let rect = useParentRect ? this.#parentRect : this.#rect; for (const element of this.#elements) { if (!rect || util.Util.intersect(element.data.rect, rect) !== null) { rect = element.data.rect; useParentRect = true; break; } } const normalizedRect = util.Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]); const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5; const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0; const popupLeft = normalizedRect[0] + parentWidth; const popupTop = normalizedRect[1]; const { style } = this.#container; style.left = `${100 * (popupLeft - pageX) / pageWidth}%`; style.top = `${100 * (popupTop - pageY) / pageHeight}%`; this.#container.append(popup); } _formatContents({ str, dir }) { const p = document.createElement("p"); p.classList.add("popupContent"); p.dir = dir; const lines = str.split(/(?:\r\n?|\n)/); for (let i = 0, ii = lines.length; i < ii; ++i) { const line = lines[i]; p.append(document.createTextNode(line)); if (i < ii - 1) { p.append(document.createElement("br")); } } return p; } #keyDown(event) { if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) { return; } if (event.key === "Enter" || event.key === "Escape" && this.#pinned) { this.#toggle(); } } #toggle() { this.#pinned = !this.#pinned; if (this.#pinned) { this.#show(); this.#container.addEventListener("click", this.#boundToggle); this.#container.addEventListener("keydown", this.#boundKeyDown); } else { this.#hide(); this.#container.removeEventListener("click", this.#boundToggle); this.#container.removeEventListener("keydown", this.#boundKeyDown); } } #show() { if (!this.#popup) { this.render(); } if (!this.isVisible) { this.#container.hidden = false; this.#container.style.zIndex = parseInt(this.#container.style.zIndex) + 1000; } else if (this.#pinned) { this.#container.classList.add("focused"); } } #hide() { this.#container.classList.remove("focused"); if (this.#pinned || !this.isVisible) { return; } this.#container.hidden = true; this.#container.style.zIndex = parseInt(this.#container.style.zIndex) - 1000; } forceHide() { this.#wasVisible = this.isVisible; if (!this.#wasVisible) { return; } this.#container.hidden = true; } maybeShow() { if (!this.#wasVisible) { return; } this.#wasVisible = false; this.#container.hidden = false; } get isVisible() { return this.#container.hidden === false; } } class FreeTextAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.textContent = parameters.data.textContent; this.textPosition = parameters.data.textPosition; this.annotationEditorType = util.AnnotationEditorType.FREETEXT; } render() { this.container.classList.add("freeTextAnnotation"); if (this.textContent) { const content = document.createElement("div"); content.classList.add("annotationTextContent"); content.setAttribute("role", "comment"); for (const line of this.textContent) { const lineSpan = document.createElement("span"); lineSpan.textContent = line; content.append(lineSpan); } this.container.append(content); } if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this._editOnDoubleClick(); return this.container; } get _isEditable() { return this.data.hasOwnCanvas; } } class LineAnnotationElement extends AnnotationElement { #line = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("lineAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const line = this.#line = this.svgFactory.createElement("svg:line"); line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]); line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]); line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]); line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]); line.setAttribute("stroke-width", data.borderStyle.width || 1); line.setAttribute("stroke", "transparent"); line.setAttribute("fill", "transparent"); svg.append(line); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#line; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class SquareAnnotationElement extends AnnotationElement { #square = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("squareAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const borderWidth = data.borderStyle.width; const square = this.#square = this.svgFactory.createElement("svg:rect"); square.setAttribute("x", borderWidth / 2); square.setAttribute("y", borderWidth / 2); square.setAttribute("width", width - borderWidth); square.setAttribute("height", height - borderWidth); square.setAttribute("stroke-width", borderWidth || 1); square.setAttribute("stroke", "transparent"); square.setAttribute("fill", "transparent"); svg.append(square); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#square; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class CircleAnnotationElement extends AnnotationElement { #circle = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("circleAnnotation"); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); const borderWidth = data.borderStyle.width; const circle = this.#circle = this.svgFactory.createElement("svg:ellipse"); circle.setAttribute("cx", width / 2); circle.setAttribute("cy", height / 2); circle.setAttribute("rx", width / 2 - borderWidth / 2); circle.setAttribute("ry", height / 2 - borderWidth / 2); circle.setAttribute("stroke-width", borderWidth || 1); circle.setAttribute("stroke", "transparent"); circle.setAttribute("fill", "transparent"); svg.append(circle); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#circle; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class PolylineAnnotationElement extends AnnotationElement { #polyline = null; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.containerClassName = "polylineAnnotation"; this.svgElementName = "svg:polyline"; } render() { this.container.classList.add(this.containerClassName); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); let points = []; for (const coordinate of data.vertices) { const x = coordinate.x - data.rect[0]; const y = data.rect[3] - coordinate.y; points.push(x + "," + y); } points = points.join(" "); const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName); polyline.setAttribute("points", points); polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("fill", "transparent"); svg.append(polyline); this.container.append(svg); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } getElementsToTriggerPopup() { return this.#polyline; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class PolygonAnnotationElement extends PolylineAnnotationElement { constructor(parameters) { super(parameters); this.containerClassName = "polygonAnnotation"; this.svgElementName = "svg:polygon"; } } class CaretAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("caretAnnotation"); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } } class InkAnnotationElement extends AnnotationElement { #polylines = []; constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); this.containerClassName = "inkAnnotation"; this.svgElementName = "svg:polyline"; this.annotationEditorType = util.AnnotationEditorType.INK; } render() { this.container.classList.add(this.containerClassName); const data = this.data; const { width, height } = getRectDims(data.rect); const svg = this.svgFactory.create(width, height, true); for (const inkList of data.inkLists) { let points = []; for (const coordinate of inkList) { const x = coordinate.x - data.rect[0]; const y = data.rect[3] - coordinate.y; points.push(`${x},${y}`); } points = points.join(" "); const polyline = this.svgFactory.createElement(this.svgElementName); this.#polylines.push(polyline); polyline.setAttribute("points", points); polyline.setAttribute("stroke-width", data.borderStyle.width || 1); polyline.setAttribute("stroke", "transparent"); polyline.setAttribute("fill", "transparent"); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } svg.append(polyline); } this.container.append(svg); return this.container; } getElementsToTriggerPopup() { return this.#polylines; } addHighlightArea() { this.container.classList.add("highlightArea"); } } class HighlightAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("highlightAnnotation"); return this.container; } } class UnderlineAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("underlineAnnotation"); return this.container; } } class SquigglyAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("squigglyAnnotation"); return this.container; } } class StrikeOutAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true, createQuadrilaterals: true }); } render() { if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } this.container.classList.add("strikeoutAnnotation"); return this.container; } } class StampAnnotationElement extends AnnotationElement { constructor(parameters) { super(parameters, { isRenderable: true, ignoreBorder: true }); } render() { this.container.classList.add("stampAnnotation"); if (!this.data.popupRef && this.hasPopupData) { this._createPopup(); } return this.container; } } class FileAttachmentAnnotationElement extends AnnotationElement { #trigger = null; constructor(parameters) { super(parameters, { isRenderable: true }); const { filename, content } = this.data.file; this.filename = (0,display_utils.getFilenameFromUrl)(filename, true); this.content = content; this.linkService.eventBus?.dispatch("fileattachmentannotation", { source: this, filename, content }); } render() { this.container.classList.add("fileAttachmentAnnotation"); const { container, data } = this; let trigger; if (data.hasAppearance || data.fillAlpha === 0) { trigger = document.createElement("div"); } else { trigger = document.createElement("img"); trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`; if (data.fillAlpha && data.fillAlpha < 1) { trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`; } } trigger.addEventListener("dblclick", this.#download.bind(this)); this.#trigger = trigger; const { isMac } = util.FeatureTest.platform; container.addEventListener("keydown", evt => { if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) { this.#download(); } }); if (!data.popupRef && this.hasPopupData) { this._createPopup(); } else { trigger.classList.add("popupTriggerArea"); } container.append(trigger); return container; } getElementsToTriggerPopup() { return this.#trigger; } addHighlightArea() { this.container.classList.add("highlightArea"); } #download() { this.downloadManager?.openOrDownloadData(this.content, this.filename); } } class AnnotationLayer { #accessibilityManager = null; #annotationCanvasMap = null; #editableAnnotations = new Map(); constructor({ div, accessibilityManager, annotationCanvasMap, page, viewport }) { this.div = div; this.#accessibilityManager = accessibilityManager; this.#annotationCanvasMap = annotationCanvasMap; this.page = page; this.viewport = viewport; this.zIndex = 0; } #appendElement(element, id) { const contentElement = element.firstChild || element; contentElement.id = `${util.AnnotationPrefix}${id}`; this.div.append(element); this.#accessibilityManager?.moveElementInDOM(this.div, element, contentElement, false); } async render(params) { const { annotations } = params; const layer = this.div; (0,display_utils.setLayerDimensions)(layer, this.viewport); const popupToElements = new Map(); const elementParams = { data: null, layer, linkService: params.linkService, downloadManager: params.downloadManager, imageResourcesPath: params.imageResourcesPath || "", renderForms: params.renderForms !== false, svgFactory: new display_utils.DOMSVGFactory(), annotationStorage: params.annotationStorage || new annotation_storage.AnnotationStorage(), enableScripting: params.enableScripting === true, hasJSActions: params.hasJSActions, fieldObjects: params.fieldObjects, parent: this, elements: null }; for (const data of annotations) { if (data.noHTML) { continue; } const isPopupAnnotation = data.annotationType === util.AnnotationType.POPUP; if (!isPopupAnnotation) { const { width, height } = getRectDims(data.rect); if (width <= 0 || height <= 0) { continue; } } else { const elements = popupToElements.get(data.id); if (!elements) { continue; } elementParams.elements = elements; } elementParams.data = data; const element = AnnotationElementFactory.create(elementParams); if (!element.isRenderable) { continue; } if (!isPopupAnnotation && data.popupRef) { const elements = popupToElements.get(data.popupRef); if (!elements) { popupToElements.set(data.popupRef, [element]); } else { elements.push(element); } } if (element.annotationEditorType > 0) { this.#editableAnnotations.set(element.data.id, element); } const rendered = element.render(); if (data.hidden) { rendered.style.visibility = "hidden"; } this.#appendElement(rendered, data.id); } this.#setAnnotationCanvasMap(); } update({ viewport }) { const layer = this.div; this.viewport = viewport; (0,display_utils.setLayerDimensions)(layer, { rotation: viewport.rotation }); this.#setAnnotationCanvasMap(); layer.hidden = false; } #setAnnotationCanvasMap() { if (!this.#annotationCanvasMap) { return; } const layer = this.div; for (const [id, canvas] of this.#annotationCanvasMap) { const element = layer.querySelector(`[data-annotation-id="${id}"]`); if (!element) { continue; } const { firstChild } = element; if (!firstChild) { element.append(canvas); } else if (firstChild.nodeName === "CANVAS") { firstChild.replaceWith(canvas); } else { firstChild.before(canvas); } } this.#annotationCanvasMap.clear(); } getEditableAnnotations() { return Array.from(this.#editableAnnotations.values()); } getEditableAnnotation(id) { return this.#editableAnnotations.get(id); } } /***/ }), /***/ 780: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AnnotationStorage: () => (/* binding */ AnnotationStorage), /* harmony export */ PrintAnnotationStorage: () => (/* binding */ PrintAnnotationStorage), /* harmony export */ SerializableEmpty: () => (/* binding */ SerializableEmpty) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _editor_editor_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(796); /* harmony import */ var _shared_murmurhash3_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(825); const SerializableEmpty = Object.freeze({ map: null, hash: "", transfer: undefined }); class AnnotationStorage { #modified = false; #storage = new Map(); constructor() { this.onSetModified = null; this.onResetModified = null; this.onAnnotationEditor = null; } getValue(key, defaultValue) { const value = this.#storage.get(key); if (value === undefined) { return defaultValue; } return Object.assign(defaultValue, value); } getRawValue(key) { return this.#storage.get(key); } remove(key) { this.#storage.delete(key); if (this.#storage.size === 0) { this.resetModified(); } if (typeof this.onAnnotationEditor === "function") { for (const value of this.#storage.values()) { if (value instanceof _editor_editor_js__WEBPACK_IMPORTED_MODULE_1__.AnnotationEditor) { return; } } this.onAnnotationEditor(null); } } setValue(key, value) { const obj = this.#storage.get(key); let modified = false; if (obj !== undefined) { for (const [entry, val] of Object.entries(value)) { if (obj[entry] !== val) { modified = true; obj[entry] = val; } } } else { modified = true; this.#storage.set(key, value); } if (modified) { this.#setModified(); } if (value instanceof _editor_editor_js__WEBPACK_IMPORTED_MODULE_1__.AnnotationEditor && typeof this.onAnnotationEditor === "function") { this.onAnnotationEditor(value.constructor._type); } } has(key) { return this.#storage.has(key); } getAll() { return this.#storage.size > 0 ? (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.objectFromMap)(this.#storage) : null; } setAll(obj) { for (const [key, val] of Object.entries(obj)) { this.setValue(key, val); } } get size() { return this.#storage.size; } #setModified() { if (!this.#modified) { this.#modified = true; if (typeof this.onSetModified === "function") { this.onSetModified(); } } } resetModified() { if (this.#modified) { this.#modified = false; if (typeof this.onResetModified === "function") { this.onResetModified(); } } } get print() { return new PrintAnnotationStorage(this); } get serializable() { if (this.#storage.size === 0) { return SerializableEmpty; } const map = new Map(), hash = new _shared_murmurhash3_js__WEBPACK_IMPORTED_MODULE_2__.MurmurHash3_64(), transfer = []; const context = Object.create(null); let hasBitmap = false; for (const [key, val] of this.#storage) { const serialized = val instanceof _editor_editor_js__WEBPACK_IMPORTED_MODULE_1__.AnnotationEditor ? val.serialize(false, context) : val; if (serialized) { map.set(key, serialized); hash.update(`${key}:${JSON.stringify(serialized)}`); hasBitmap ||= !!serialized.bitmap; } } if (hasBitmap) { for (const value of map.values()) { if (value.bitmap) { transfer.push(value.bitmap); } } } return map.size > 0 ? { map, hash: hash.hexdigest(), transfer } : SerializableEmpty; } } class PrintAnnotationStorage extends AnnotationStorage { #serializable; constructor(parent) { super(); const { map, hash, transfer } = parent.serializable; const clone = structuredClone(map, transfer ? { transfer } : null); this.#serializable = { map: clone, hash, transfer }; } get print() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Should not call PrintAnnotationStorage.print"); } get serializable() { return this.#serializable; } } /***/ }), /***/ 406: /***/ ((__webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ PDFDataRangeTransport: () => (/* binding */ PDFDataRangeTransport), /* harmony export */ PDFWorker: () => (/* binding */ PDFWorker), /* harmony export */ build: () => (/* binding */ build), /* harmony export */ getDocument: () => (/* binding */ getDocument), /* harmony export */ version: () => (/* binding */ version) /* harmony export */ }); /* unused harmony exports DefaultCanvasFactory, DefaultCMapReaderFactory, DefaultFilterFactory, DefaultStandardFontDataFactory, LoopbackPort, PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy, PDFWorkerUtil, RenderTask */ /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _annotation_storage_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(780); /* harmony import */ var _display_utils_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(473); /* harmony import */ var _font_loader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(742); /* harmony import */ var display_node_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(738); /* harmony import */ var _canvas_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(250); /* harmony import */ var _worker_options_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(368); /* harmony import */ var _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(694); /* harmony import */ var _metadata_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(472); /* harmony import */ var _optional_content_config_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(890); /* harmony import */ var _transport_stream_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(92); /* harmony import */ var display_fetch_stream__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(171); /* harmony import */ var display_network__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(474); /* harmony import */ var display_node_stream__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(498); /* harmony import */ var _xfa_text_js__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(521); var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([display_node_utils__WEBPACK_IMPORTED_MODULE_4__, display_node_stream__WEBPACK_IMPORTED_MODULE_13__]); ([display_node_utils__WEBPACK_IMPORTED_MODULE_4__, display_node_stream__WEBPACK_IMPORTED_MODULE_13__] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__); const DEFAULT_RANGE_CHUNK_SIZE = 65536; const RENDERING_CANCELLED_TIMEOUT = 100; const DELAYED_CLEANUP_TIMEOUT = 5000; const DefaultCanvasFactory = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS ? display_node_utils__WEBPACK_IMPORTED_MODULE_4__.NodeCanvasFactory : _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMCanvasFactory; const DefaultCMapReaderFactory = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS ? display_node_utils__WEBPACK_IMPORTED_MODULE_4__.NodeCMapReaderFactory : _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMCMapReaderFactory; const DefaultFilterFactory = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS ? display_node_utils__WEBPACK_IMPORTED_MODULE_4__.NodeFilterFactory : _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMFilterFactory; const DefaultStandardFontDataFactory = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS ? display_node_utils__WEBPACK_IMPORTED_MODULE_4__.NodeStandardFontDataFactory : _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMStandardFontDataFactory; function getDocument(src) { if (typeof src === "string" || src instanceof URL) { src = { url: src }; } else if ((0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isArrayBuffer)(src)) { src = { data: src }; } if (typeof src !== "object") { throw new Error("Invalid parameter in getDocument, need parameter object."); } if (!src.url && !src.data && !src.range) { throw new Error("Invalid parameter object: need either .data, .range or .url"); } const task = new PDFDocumentLoadingTask(); const { docId } = task; const url = src.url ? getUrlProp(src.url) : null; const data = src.data ? getDataProp(src.data) : null; const httpHeaders = src.httpHeaders || null; const withCredentials = src.withCredentials === true; const password = src.password ?? null; const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null; const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE; let worker = src.worker instanceof PDFWorker ? src.worker : null; const verbosity = src.verbosity; const docBaseUrl = typeof src.docBaseUrl === "string" && !(0,_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isDataScheme)(src.docBaseUrl) ? src.docBaseUrl : null; const cMapUrl = typeof src.cMapUrl === "string" ? src.cMapUrl : null; const cMapPacked = src.cMapPacked !== false; const CMapReaderFactory = src.CMapReaderFactory || DefaultCMapReaderFactory; const standardFontDataUrl = typeof src.standardFontDataUrl === "string" ? src.standardFontDataUrl : null; const StandardFontDataFactory = src.StandardFontDataFactory || DefaultStandardFontDataFactory; const ignoreErrors = src.stopAtErrors !== true; const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1; const isEvalSupported = src.isEvalSupported !== false; const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS; const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1; const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS; const fontExtraProperties = src.fontExtraProperties === true; const enableXfa = src.enableXfa === true; const ownerDocument = src.ownerDocument || globalThis.document; const disableRange = src.disableRange === true; const disableStream = src.disableStream === true; const disableAutoFetch = src.disableAutoFetch === true; const pdfBug = src.pdfBug === true; const length = rangeTransport ? rangeTransport.length : src.length ?? NaN; const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS && !disableFontFace; const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : CMapReaderFactory === _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMCMapReaderFactory && StandardFontDataFactory === _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMStandardFontDataFactory && cMapUrl && standardFontDataUrl && (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isValidFetchUrl)(cMapUrl, document.baseURI) && (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isValidFetchUrl)(standardFontDataUrl, document.baseURI); const canvasFactory = src.canvasFactory || new DefaultCanvasFactory({ ownerDocument }); const filterFactory = src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument }); const styleElement = null; (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.setVerbosityLevel)(verbosity); const transportFactory = { canvasFactory, filterFactory }; if (!useWorkerFetch) { transportFactory.cMapReaderFactory = new CMapReaderFactory({ baseUrl: cMapUrl, isCompressed: cMapPacked }); transportFactory.standardFontDataFactory = new StandardFontDataFactory({ baseUrl: standardFontDataUrl }); } if (!worker) { const workerParams = { verbosity, port: _worker_options_js__WEBPACK_IMPORTED_MODULE_6__.GlobalWorkerOptions.workerPort }; worker = workerParams.port ? PDFWorker.fromPort(workerParams) : new PDFWorker(workerParams); task._worker = worker; } const fetchDocParams = { docId, apiVersion: '4.0.269', data, password, disableAutoFetch, rangeChunkSize, length, docBaseUrl, enableXfa, evaluatorOptions: { maxImageSize, disableFontFace, ignoreErrors, isEvalSupported, isOffscreenCanvasSupported, canvasMaxAreaInBytes, fontExtraProperties, useSystemFonts, cMapUrl: useWorkerFetch ? cMapUrl : null, standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null } }; const transportParams = { ignoreErrors, isEvalSupported, disableFontFace, fontExtraProperties, enableXfa, ownerDocument, disableAutoFetch, pdfBug, styleElement }; worker.promise.then(function () { if (task.destroyed) { throw new Error("Loading aborted"); } const workerIdPromise = _fetchDocument(worker, fetchDocParams); const networkStreamPromise = new Promise(function (resolve) { let networkStream; if (rangeTransport) { networkStream = new _transport_stream_js__WEBPACK_IMPORTED_MODULE_10__.PDFDataTransportStream({ length, initialData: rangeTransport.initialData, progressiveDone: rangeTransport.progressiveDone, contentDispositionFilename: rangeTransport.contentDispositionFilename, disableRange, disableStream }, rangeTransport); } else if (!data) { const createPDFNetworkStream = params => { if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS) { return new display_node_stream__WEBPACK_IMPORTED_MODULE_13__.PDFNodeStream(params); } return (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isValidFetchUrl)(params.url) ? new display_fetch_stream__WEBPACK_IMPORTED_MODULE_11__.PDFFetchStream(params) : new display_network__WEBPACK_IMPORTED_MODULE_12__.PDFNetworkStream(params); }; networkStream = createPDFNetworkStream({ url, length, httpHeaders, withCredentials, rangeChunkSize, disableRange, disableStream }); } resolve(networkStream); }); return Promise.all([workerIdPromise, networkStreamPromise]).then(function ([workerId, networkStream]) { if (task.destroyed) { throw new Error("Loading aborted"); } const messageHandler = new _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__.MessageHandler(docId, workerId, worker.port); const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory); task._transport = transport; messageHandler.send("Ready", null); }); }).catch(task._capability.reject); return task; } async function _fetchDocument(worker, source) { if (worker.destroyed) { throw new Error("Worker was destroyed"); } const workerId = await worker.messageHandler.sendWithPromise("GetDocRequest", source, source.data ? [source.data.buffer] : null); if (worker.destroyed) { throw new Error("Worker was destroyed"); } return workerId; } function getUrlProp(val) { if (val instanceof URL) { return val.href; } try { return new URL(val, window.location).href; } catch { if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS && typeof val === "string") { return val; } } throw new Error("Invalid PDF url data: " + "either string or URL-object is expected in the url property."); } function getDataProp(val) { if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS && typeof Buffer !== "undefined" && val instanceof Buffer) { throw new Error("Please provide binary data as `Uint8Array`, rather than `Buffer`."); } if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { return val; } if (typeof val === "string") { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.stringToBytes)(val); } if (typeof val === "object" && !isNaN(val?.length) || (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isArrayBuffer)(val)) { return new Uint8Array(val); } throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property."); } class PDFDocumentLoadingTask { static #docId = 0; constructor() { this._capability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._transport = null; this._worker = null; this.docId = `d${PDFDocumentLoadingTask.#docId++}`; this.destroyed = false; this.onPassword = null; this.onProgress = null; } get promise() { return this._capability.promise; } async destroy() { this.destroyed = true; try { if (this._worker?.port) { this._worker._pendingDestroy = true; } await this._transport?.destroy(); } catch (ex) { if (this._worker?.port) { delete this._worker._pendingDestroy; } throw ex; } this._transport = null; if (this._worker) { this._worker.destroy(); this._worker = null; } } } class PDFDataRangeTransport { constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) { this.length = length; this.initialData = initialData; this.progressiveDone = progressiveDone; this.contentDispositionFilename = contentDispositionFilename; this._rangeListeners = []; this._progressListeners = []; this._progressiveReadListeners = []; this._progressiveDoneListeners = []; this._readyCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); } addRangeListener(listener) { this._rangeListeners.push(listener); } addProgressListener(listener) { this._progressListeners.push(listener); } addProgressiveReadListener(listener) { this._progressiveReadListeners.push(listener); } addProgressiveDoneListener(listener) { this._progressiveDoneListeners.push(listener); } onDataRange(begin, chunk) { for (const listener of this._rangeListeners) { listener(begin, chunk); } } onDataProgress(loaded, total) { this._readyCapability.promise.then(() => { for (const listener of this._progressListeners) { listener(loaded, total); } }); } onDataProgressiveRead(chunk) { this._readyCapability.promise.then(() => { for (const listener of this._progressiveReadListeners) { listener(chunk); } }); } onDataProgressiveDone() { this._readyCapability.promise.then(() => { for (const listener of this._progressiveDoneListeners) { listener(); } }); } transportReady() { this._readyCapability.resolve(); } requestDataRange(begin, end) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Abstract method PDFDataRangeTransport.requestDataRange"); } abort() {} } class PDFDocumentProxy { constructor(pdfInfo, transport) { this._pdfInfo = pdfInfo; this._transport = transport; } get annotationStorage() { return this._transport.annotationStorage; } get filterFactory() { return this._transport.filterFactory; } get numPages() { return this._pdfInfo.numPages; } get fingerprints() { return this._pdfInfo.fingerprints; } get isPureXfa() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "isPureXfa", !!this._transport._htmlForXfa); } get allXfaHtml() { return this._transport._htmlForXfa; } getPage(pageNumber) { return this._transport.getPage(pageNumber); } getPageIndex(ref) { return this._transport.getPageIndex(ref); } getDestinations() { return this._transport.getDestinations(); } getDestination(id) { return this._transport.getDestination(id); } getPageLabels() { return this._transport.getPageLabels(); } getPageLayout() { return this._transport.getPageLayout(); } getPageMode() { return this._transport.getPageMode(); } getViewerPreferences() { return this._transport.getViewerPreferences(); } getOpenAction() { return this._transport.getOpenAction(); } getAttachments() { return this._transport.getAttachments(); } getJSActions() { return this._transport.getDocJSActions(); } getOutline() { return this._transport.getOutline(); } getOptionalContentConfig() { return this._transport.getOptionalContentConfig(); } getPermissions() { return this._transport.getPermissions(); } getMetadata() { return this._transport.getMetadata(); } getMarkInfo() { return this._transport.getMarkInfo(); } getData() { return this._transport.getData(); } saveDocument() { return this._transport.saveDocument(); } getDownloadInfo() { return this._transport.downloadInfoCapability.promise; } cleanup(keepLoadedFonts = false) { return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa); } destroy() { return this.loadingTask.destroy(); } get loadingParams() { return this._transport.loadingParams; } get loadingTask() { return this._transport.loadingTask; } getFieldObjects() { return this._transport.getFieldObjects(); } hasJSActions() { return this._transport.hasJSActions(); } getCalculationOrderIds() { return this._transport.getCalculationOrderIds(); } } class PDFPageProxy { #delayedCleanupTimeout = null; #pendingCleanup = false; constructor(pageIndex, pageInfo, transport, pdfBug = false) { this._pageIndex = pageIndex; this._pageInfo = pageInfo; this._transport = transport; this._stats = pdfBug ? new _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.StatTimer() : null; this._pdfBug = pdfBug; this.commonObjs = transport.commonObjs; this.objs = new PDFObjects(); this._maybeCleanupAfterRender = false; this._intentStates = new Map(); this.destroyed = false; } get pageNumber() { return this._pageIndex + 1; } get rotate() { return this._pageInfo.rotate; } get ref() { return this._pageInfo.ref; } get userUnit() { return this._pageInfo.userUnit; } get view() { return this._pageInfo.view; } getViewport({ scale, rotation = this.rotate, offsetX = 0, offsetY = 0, dontFlip = false } = {}) { return new _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.PageViewport({ viewBox: this.view, scale, rotation, offsetX, offsetY, dontFlip }); } getAnnotations({ intent = "display" } = {}) { const intentArgs = this._transport.getRenderingIntent(intent); return this._transport.getAnnotations(this._pageIndex, intentArgs.renderingIntent); } getJSActions() { return this._transport.getPageJSActions(this._pageIndex); } get filterFactory() { return this._transport.filterFactory; } get isPureXfa() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "isPureXfa", !!this._transport._htmlForXfa); } async getXfa() { return this._transport._htmlForXfa?.children[this._pageIndex] || null; } render({ canvasContext, viewport, intent = "display", annotationMode = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE, transform = null, background = null, optionalContentConfigPromise = null, annotationCanvasMap = null, pageColors = null, printAnnotationStorage = null }) { this._stats?.time("Overall"); const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage); this.#pendingCleanup = false; this.#abortDelayedCleanup(); if (!optionalContentConfigPromise) { optionalContentConfigPromise = this._transport.getOptionalContentConfig(); } let intentState = this._intentStates.get(intentArgs.cacheKey); if (!intentState) { intentState = Object.create(null); this._intentStates.set(intentArgs.cacheKey, intentState); } if (intentState.streamReaderCancelTimeout) { clearTimeout(intentState.streamReaderCancelTimeout); intentState.streamReaderCancelTimeout = null; } const intentPrint = !!(intentArgs.renderingIntent & _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.PRINT); if (!intentState.displayReadyCapability) { intentState.displayReadyCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); intentState.operatorList = { fnArray: [], argsArray: [], lastChunk: false, separateAnnots: null }; this._stats?.time("Page Request"); this._pumpOperatorList(intentArgs); } const complete = error => { intentState.renderTasks.delete(internalRenderTask); if (this._maybeCleanupAfterRender || intentPrint) { this.#pendingCleanup = true; } this.#tryCleanup(!intentPrint); if (error) { internalRenderTask.capability.reject(error); this._abortOperatorList({ intentState, reason: error instanceof Error ? error : new Error(error) }); } else { internalRenderTask.capability.resolve(); } this._stats?.timeEnd("Rendering"); this._stats?.timeEnd("Overall"); }; const internalRenderTask = new InternalRenderTask({ callback: complete, params: { canvasContext, viewport, transform, background }, objs: this.objs, commonObjs: this.commonObjs, annotationCanvasMap, operatorList: intentState.operatorList, pageIndex: this._pageIndex, canvasFactory: this._transport.canvasFactory, filterFactory: this._transport.filterFactory, useRequestAnimationFrame: !intentPrint, pdfBug: this._pdfBug, pageColors }); (intentState.renderTasks ||= new Set()).add(internalRenderTask); const renderTask = internalRenderTask.task; Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => { if (this.destroyed) { complete(); return; } this._stats?.time("Rendering"); internalRenderTask.initializeGraphics({ transparency, optionalContentConfig }); internalRenderTask.operatorListChanged(); }).catch(complete); return renderTask; } getOperatorList({ intent = "display", annotationMode = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE, printAnnotationStorage = null } = {}) { function operatorListChanged() { if (intentState.operatorList.lastChunk) { intentState.opListReadCapability.resolve(intentState.operatorList); intentState.renderTasks.delete(opListTask); } } const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, true); let intentState = this._intentStates.get(intentArgs.cacheKey); if (!intentState) { intentState = Object.create(null); this._intentStates.set(intentArgs.cacheKey, intentState); } let opListTask; if (!intentState.opListReadCapability) { opListTask = Object.create(null); opListTask.operatorListChanged = operatorListChanged; intentState.opListReadCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); (intentState.renderTasks ||= new Set()).add(opListTask); intentState.operatorList = { fnArray: [], argsArray: [], lastChunk: false, separateAnnots: null }; this._stats?.time("Page Request"); this._pumpOperatorList(intentArgs); } return intentState.opListReadCapability.promise; } streamTextContent({ includeMarkedContent = false, disableNormalization = false } = {}) { const TEXT_CONTENT_CHUNK_SIZE = 100; return this._transport.messageHandler.sendWithStream("GetTextContent", { pageIndex: this._pageIndex, includeMarkedContent: includeMarkedContent === true, disableNormalization: disableNormalization === true }, { highWaterMark: TEXT_CONTENT_CHUNK_SIZE, size(textContent) { return textContent.items.length; } }); } getTextContent(params = {}) { if (this._transport._htmlForXfa) { return this.getXfa().then(xfa => { return _xfa_text_js__WEBPACK_IMPORTED_MODULE_14__.XfaText.textContent(xfa); }); } const readableStream = this.streamTextContent(params); return new Promise(function (resolve, reject) { function pump() { reader.read().then(function ({ value, done }) { if (done) { resolve(textContent); return; } Object.assign(textContent.styles, value.styles); textContent.items.push(...value.items); pump(); }, reject); } const reader = readableStream.getReader(); const textContent = { items: [], styles: Object.create(null) }; pump(); }); } getStructTree() { return this._transport.getStructTree(this._pageIndex); } _destroy() { this.destroyed = true; const waitOn = []; for (const intentState of this._intentStates.values()) { this._abortOperatorList({ intentState, reason: new Error("Page was destroyed."), force: true }); if (intentState.opListReadCapability) { continue; } for (const internalRenderTask of intentState.renderTasks) { waitOn.push(internalRenderTask.completed); internalRenderTask.cancel(); } } this.objs.clear(); this.#pendingCleanup = false; this.#abortDelayedCleanup(); return Promise.all(waitOn); } cleanup(resetStats = false) { this.#pendingCleanup = true; const success = this.#tryCleanup(false); if (resetStats && success) { this._stats &&= new _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.StatTimer(); } return success; } #tryCleanup(delayed = false) { this.#abortDelayedCleanup(); if (!this.#pendingCleanup || this.destroyed) { return false; } if (delayed) { this.#delayedCleanupTimeout = setTimeout(() => { this.#delayedCleanupTimeout = null; this.#tryCleanup(false); }, DELAYED_CLEANUP_TIMEOUT); return false; } for (const { renderTasks, operatorList } of this._intentStates.values()) { if (renderTasks.size > 0 || !operatorList.lastChunk) { return false; } } this._intentStates.clear(); this.objs.clear(); this.#pendingCleanup = false; return true; } #abortDelayedCleanup() { if (this.#delayedCleanupTimeout) { clearTimeout(this.#delayedCleanupTimeout); this.#delayedCleanupTimeout = null; } } _startRenderPage(transparency, cacheKey) { const intentState = this._intentStates.get(cacheKey); if (!intentState) { return; } this._stats?.timeEnd("Page Request"); intentState.displayReadyCapability?.resolve(transparency); } _renderPageChunk(operatorListChunk, intentState) { for (let i = 0, ii = operatorListChunk.length; i < ii; i++) { intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); } intentState.operatorList.lastChunk = operatorListChunk.lastChunk; intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; for (const internalRenderTask of intentState.renderTasks) { internalRenderTask.operatorListChanged(); } if (operatorListChunk.lastChunk) { this.#tryCleanup(true); } } _pumpOperatorList({ renderingIntent, cacheKey, annotationStorageSerializable }) { const { map, transfer } = annotationStorageSerializable; const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", { pageIndex: this._pageIndex, intent: renderingIntent, cacheKey, annotationStorage: map }, transfer); const reader = readableStream.getReader(); const intentState = this._intentStates.get(cacheKey); intentState.streamReader = reader; const pump = () => { reader.read().then(({ value, done }) => { if (done) { intentState.streamReader = null; return; } if (this._transport.destroyed) { return; } this._renderPageChunk(value, intentState); pump(); }, reason => { intentState.streamReader = null; if (this._transport.destroyed) { return; } if (intentState.operatorList) { intentState.operatorList.lastChunk = true; for (const internalRenderTask of intentState.renderTasks) { internalRenderTask.operatorListChanged(); } this.#tryCleanup(true); } if (intentState.displayReadyCapability) { intentState.displayReadyCapability.reject(reason); } else if (intentState.opListReadCapability) { intentState.opListReadCapability.reject(reason); } else { throw reason; } }); }; pump(); } _abortOperatorList({ intentState, reason, force = false }) { if (!intentState.streamReader) { return; } if (intentState.streamReaderCancelTimeout) { clearTimeout(intentState.streamReaderCancelTimeout); intentState.streamReaderCancelTimeout = null; } if (!force) { if (intentState.renderTasks.size > 0) { return; } if (reason instanceof _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.RenderingCancelledException) { let delay = RENDERING_CANCELLED_TIMEOUT; if (reason.extraDelay > 0 && reason.extraDelay < 1000) { delay += reason.extraDelay; } intentState.streamReaderCancelTimeout = setTimeout(() => { intentState.streamReaderCancelTimeout = null; this._abortOperatorList({ intentState, reason, force: true }); }, delay); return; } } intentState.streamReader.cancel(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException(reason.message)).catch(() => {}); intentState.streamReader = null; if (this._transport.destroyed) { return; } for (const [curCacheKey, curIntentState] of this._intentStates) { if (curIntentState === intentState) { this._intentStates.delete(curCacheKey); break; } } this.cleanup(); } get stats() { return this._stats; } } class LoopbackPort { #listeners = new Set(); #deferred = Promise.resolve(); postMessage(obj, transfer) { const event = { data: structuredClone(obj, transfer ? { transfer } : null) }; this.#deferred.then(() => { for (const listener of this.#listeners) { listener.call(this, event); } }); } addEventListener(name, listener) { this.#listeners.add(listener); } removeEventListener(name, listener) { this.#listeners.delete(listener); } terminate() { this.#listeners.clear(); } } const PDFWorkerUtil = { isWorkerDisabled: false, fakeWorkerId: 0 }; { if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS) { PDFWorkerUtil.isWorkerDisabled = true; _worker_options_js__WEBPACK_IMPORTED_MODULE_6__.GlobalWorkerOptions.workerSrc ||= "./pdf.worker.mjs"; } PDFWorkerUtil.isSameOrigin = function (baseUrl, otherUrl) { let base; try { base = new URL(baseUrl); if (!base.origin || base.origin === "null") { return false; } } catch { return false; } const other = new URL(otherUrl, base); return base.origin === other.origin; }; PDFWorkerUtil.createCDNWrapper = function (url) { const wrapper = `await import("${url}");`; return URL.createObjectURL(new Blob([wrapper], { type: "text/javascript" })); }; } class PDFWorker { static #workerPorts; constructor({ name = null, port = null, verbosity = (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.getVerbosityLevel)() } = {}) { this.name = name; this.destroyed = false; this.verbosity = verbosity; this._readyCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._port = null; this._webWorker = null; this._messageHandler = null; if (port) { if (PDFWorker.#workerPorts?.has(port)) { throw new Error("Cannot use more than one PDFWorker per port."); } (PDFWorker.#workerPorts ||= new WeakMap()).set(port, this); this._initializeFromPort(port); return; } this._initialize(); } get promise() { return this._readyCapability.promise; } get port() { return this._port; } get messageHandler() { return this._messageHandler; } _initializeFromPort(port) { this._port = port; this._messageHandler = new _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__.MessageHandler("main", "worker", port); this._messageHandler.on("ready", function () {}); this._readyCapability.resolve(); this._messageHandler.send("configure", { verbosity: this.verbosity }); } _initialize() { if (!PDFWorkerUtil.isWorkerDisabled && !PDFWorker.#mainThreadWorkerMessageHandler) { let { workerSrc } = PDFWorker; try { if (!PDFWorkerUtil.isSameOrigin(window.location.href, workerSrc)) { workerSrc = PDFWorkerUtil.createCDNWrapper(new URL(workerSrc, window.location).href); } const worker = new Worker(workerSrc, { type: "module" }); const messageHandler = new _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__.MessageHandler("main", "worker", worker); const terminateEarly = () => { worker.removeEventListener("error", onWorkerError); messageHandler.destroy(); worker.terminate(); if (this.destroyed) { this._readyCapability.reject(new Error("Worker was destroyed")); } else { this._setupFakeWorker(); } }; const onWorkerError = () => { if (!this._webWorker) { terminateEarly(); } }; worker.addEventListener("error", onWorkerError); messageHandler.on("test", data => { worker.removeEventListener("error", onWorkerError); if (this.destroyed) { terminateEarly(); return; } if (data) { this._messageHandler = messageHandler; this._port = worker; this._webWorker = worker; this._readyCapability.resolve(); messageHandler.send("configure", { verbosity: this.verbosity }); } else { this._setupFakeWorker(); messageHandler.destroy(); worker.terminate(); } }); messageHandler.on("ready", data => { worker.removeEventListener("error", onWorkerError); if (this.destroyed) { terminateEarly(); return; } try { sendTest(); } catch { this._setupFakeWorker(); } }); const sendTest = () => { const testObj = new Uint8Array(); messageHandler.send("test", testObj, [testObj.buffer]); }; sendTest(); return; } catch { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.info)("The worker has been disabled."); } } this._setupFakeWorker(); } _setupFakeWorker() { if (!PDFWorkerUtil.isWorkerDisabled) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)("Setting up fake worker."); PDFWorkerUtil.isWorkerDisabled = true; } PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => { if (this.destroyed) { this._readyCapability.reject(new Error("Worker was destroyed")); return; } const port = new LoopbackPort(); this._port = port; const id = `fake${PDFWorkerUtil.fakeWorkerId++}`; const workerHandler = new _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__.MessageHandler(id + "_worker", id, port); WorkerMessageHandler.setup(workerHandler, port); const messageHandler = new _shared_message_handler_js__WEBPACK_IMPORTED_MODULE_7__.MessageHandler(id, id + "_worker", port); this._messageHandler = messageHandler; this._readyCapability.resolve(); messageHandler.send("configure", { verbosity: this.verbosity }); }).catch(reason => { this._readyCapability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`)); }); } destroy() { this.destroyed = true; if (this._webWorker) { this._webWorker.terminate(); this._webWorker = null; } PDFWorker.#workerPorts?.delete(this._port); this._port = null; if (this._messageHandler) { this._messageHandler.destroy(); this._messageHandler = null; } } static fromPort(params) { if (!params?.port) { throw new Error("PDFWorker.fromPort - invalid method signature."); } const cachedPort = this.#workerPorts?.get(params.port); if (cachedPort) { if (cachedPort._pendingDestroy) { throw new Error("PDFWorker.fromPort - the worker is being destroyed.\n" + "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls."); } return cachedPort; } return new PDFWorker(params); } static get workerSrc() { if (_worker_options_js__WEBPACK_IMPORTED_MODULE_6__.GlobalWorkerOptions.workerSrc) { return _worker_options_js__WEBPACK_IMPORTED_MODULE_6__.GlobalWorkerOptions.workerSrc; } throw new Error('No "GlobalWorkerOptions.workerSrc" specified.'); } static get #mainThreadWorkerMessageHandler() { try { return globalThis.pdfjsWorker?.WorkerMessageHandler || null; } catch { return null; } } static get _setupFakeWorkerGlobal() { const loader = async () => { if (this.#mainThreadWorkerMessageHandler) { return this.#mainThreadWorkerMessageHandler; } const worker = await import(/* webpackIgnore: true */ this.workerSrc); return worker.WorkerMessageHandler; }; return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "_setupFakeWorkerGlobal", loader()); } } class WorkerTransport { #methodPromises = new Map(); #pageCache = new Map(); #pagePromises = new Map(); #passwordCapability = null; constructor(messageHandler, loadingTask, networkStream, params, factory) { this.messageHandler = messageHandler; this.loadingTask = loadingTask; this.commonObjs = new PDFObjects(); this.fontLoader = new _font_loader_js__WEBPACK_IMPORTED_MODULE_3__.FontLoader({ ownerDocument: params.ownerDocument, styleElement: params.styleElement }); this._params = params; this.canvasFactory = factory.canvasFactory; this.filterFactory = factory.filterFactory; this.cMapReaderFactory = factory.cMapReaderFactory; this.standardFontDataFactory = factory.standardFontDataFactory; this.destroyed = false; this.destroyCapability = null; this._networkStream = networkStream; this._fullReader = null; this._lastProgress = null; this.downloadInfoCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.setupMessageHandler(); } #cacheSimpleMethod(name, data = null) { const cachedPromise = this.#methodPromises.get(name); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise(name, data); this.#methodPromises.set(name, promise); return promise; } get annotationStorage() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "annotationStorage", new _annotation_storage_js__WEBPACK_IMPORTED_MODULE_1__.AnnotationStorage()); } getRenderingIntent(intent, annotationMode = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE, printAnnotationStorage = null, isOpList = false) { let renderingIntent = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.DISPLAY; let annotationStorageSerializable = _annotation_storage_js__WEBPACK_IMPORTED_MODULE_1__.SerializableEmpty; switch (intent) { case "any": renderingIntent = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.ANY; break; case "display": break; case "print": renderingIntent = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.PRINT; break; default: (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`getRenderingIntent - invalid intent: ${intent}`); } switch (annotationMode) { case _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.DISABLE: renderingIntent += _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.ANNOTATIONS_DISABLE; break; case _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE: break; case _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE_FORMS: renderingIntent += _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.ANNOTATIONS_FORMS; break; case _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode.ENABLE_STORAGE: renderingIntent += _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.ANNOTATIONS_STORAGE; const annotationStorage = renderingIntent & _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.PRINT && printAnnotationStorage instanceof _annotation_storage_js__WEBPACK_IMPORTED_MODULE_1__.PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage; annotationStorageSerializable = annotationStorage.serializable; break; default: (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`getRenderingIntent - invalid annotationMode: ${annotationMode}`); } if (isOpList) { renderingIntent += _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.RenderingIntentFlag.OPLIST; } return { renderingIntent, cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`, annotationStorageSerializable }; } destroy() { if (this.destroyCapability) { return this.destroyCapability.promise; } this.destroyed = true; this.destroyCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback")); const waitOn = []; for (const page of this.#pageCache.values()) { waitOn.push(page._destroy()); } this.#pageCache.clear(); this.#pagePromises.clear(); if (this.hasOwnProperty("annotationStorage")) { this.annotationStorage.resetModified(); } const terminated = this.messageHandler.sendWithPromise("Terminate", null); waitOn.push(terminated); Promise.all(waitOn).then(() => { this.commonObjs.clear(); this.fontLoader.clear(); this.#methodPromises.clear(); this.filterFactory.destroy(); this._networkStream?.cancelAllRequests(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException("Worker was terminated.")); if (this.messageHandler) { this.messageHandler.destroy(); this.messageHandler = null; } this.destroyCapability.resolve(); }, this.destroyCapability.reject); return this.destroyCapability.promise; } setupMessageHandler() { const { messageHandler, loadingTask } = this; messageHandler.on("GetReader", (data, sink) => { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(this._networkStream, "GetReader - no `IPDFStream` instance available."); this._fullReader = this._networkStream.getFullReader(); this._fullReader.onProgress = evt => { this._lastProgress = { loaded: evt.loaded, total: evt.total }; }; sink.onPull = () => { this._fullReader.read().then(function ({ value, done }) { if (done) { sink.close(); return; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer."); sink.enqueue(new Uint8Array(value), 1, [value]); }).catch(reason => { sink.error(reason); }); }; sink.onCancel = reason => { this._fullReader.cancel(reason); sink.ready.catch(readyReason => { if (this.destroyed) { return; } throw readyReason; }); }; }); messageHandler.on("ReaderHeadersReady", data => { const headersCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); const fullReader = this._fullReader; fullReader.headersReady.then(() => { if (!fullReader.isStreamingSupported || !fullReader.isRangeSupported) { if (this._lastProgress) { loadingTask.onProgress?.(this._lastProgress); } fullReader.onProgress = evt => { loadingTask.onProgress?.({ loaded: evt.loaded, total: evt.total }); }; } headersCapability.resolve({ isStreamingSupported: fullReader.isStreamingSupported, isRangeSupported: fullReader.isRangeSupported, contentLength: fullReader.contentLength }); }, headersCapability.reject); return headersCapability.promise; }); messageHandler.on("GetRangeReader", (data, sink) => { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(this._networkStream, "GetRangeReader - no `IPDFStream` instance available."); const rangeReader = this._networkStream.getRangeReader(data.begin, data.end); if (!rangeReader) { sink.close(); return; } sink.onPull = () => { rangeReader.read().then(function ({ value, done }) { if (done) { sink.close(); return; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer."); sink.enqueue(new Uint8Array(value), 1, [value]); }).catch(reason => { sink.error(reason); }); }; sink.onCancel = reason => { rangeReader.cancel(reason); sink.ready.catch(readyReason => { if (this.destroyed) { return; } throw readyReason; }); }; }); messageHandler.on("GetDoc", ({ pdfInfo }) => { this._numPages = pdfInfo.numPages; this._htmlForXfa = pdfInfo.htmlForXfa; delete pdfInfo.htmlForXfa; loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this)); }); messageHandler.on("DocException", function (ex) { let reason; switch (ex.name) { case "PasswordException": reason = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PasswordException(ex.message, ex.code); break; case "InvalidPDFException": reason = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.InvalidPDFException(ex.message); break; case "MissingPDFException": reason = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException(ex.message); break; case "UnexpectedResponseException": reason = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.UnexpectedResponseException(ex.message, ex.status); break; case "UnknownErrorException": reason = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.UnknownErrorException(ex.message, ex.details); break; default: (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("DocException - expected a valid Error."); } loadingTask._capability.reject(reason); }); messageHandler.on("PasswordRequest", exception => { this.#passwordCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); if (loadingTask.onPassword) { const updatePassword = password => { if (password instanceof Error) { this.#passwordCapability.reject(password); } else { this.#passwordCapability.resolve({ password }); } }; try { loadingTask.onPassword(updatePassword, exception.code); } catch (ex) { this.#passwordCapability.reject(ex); } } else { this.#passwordCapability.reject(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PasswordException(exception.message, exception.code)); } return this.#passwordCapability.promise; }); messageHandler.on("DataLoaded", data => { loadingTask.onProgress?.({ loaded: data.length, total: data.length }); this.downloadInfoCapability.resolve(data); }); messageHandler.on("StartRenderPage", data => { if (this.destroyed) { return; } const page = this.#pageCache.get(data.pageIndex); page._startRenderPage(data.transparency, data.cacheKey); }); messageHandler.on("commonobj", ([id, type, exportedData]) => { if (this.destroyed) { return; } if (this.commonObjs.has(id)) { return; } switch (type) { case "Font": const params = this._params; if ("error" in exportedData) { const exportedError = exportedData.error; (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Error during font loading: ${exportedError}`); this.commonObjs.resolve(id, exportedError); break; } const inspectFont = params.pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null; const font = new _font_loader_js__WEBPACK_IMPORTED_MODULE_3__.FontFaceObject(exportedData, { isEvalSupported: params.isEvalSupported, disableFontFace: params.disableFontFace, ignoreErrors: params.ignoreErrors, inspectFont }); this.fontLoader.bind(font).catch(reason => { return messageHandler.sendWithPromise("FontFallback", { id }); }).finally(() => { if (!params.fontExtraProperties && font.data) { font.data = null; } this.commonObjs.resolve(id, font); }); break; case "FontPath": case "Image": case "Pattern": this.commonObjs.resolve(id, exportedData); break; default: throw new Error(`Got unknown common object type ${type}`); } }); messageHandler.on("obj", ([id, pageIndex, type, imageData]) => { if (this.destroyed) { return; } const pageProxy = this.#pageCache.get(pageIndex); if (pageProxy.objs.has(id)) { return; } if (pageProxy._intentStates.size === 0) { imageData?.bitmap?.close(); return; } switch (type) { case "Image": pageProxy.objs.resolve(id, imageData); if (imageData) { let length; if (imageData.bitmap) { const { width, height } = imageData; length = width * height * 4; } else { length = imageData.data?.length || 0; } if (length > _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MAX_IMAGE_SIZE_TO_CACHE) { pageProxy._maybeCleanupAfterRender = true; } } break; case "Pattern": pageProxy.objs.resolve(id, imageData); break; default: throw new Error(`Got unknown object type ${type}`); } }); messageHandler.on("DocProgress", data => { if (this.destroyed) { return; } loadingTask.onProgress?.({ loaded: data.loaded, total: data.total }); }); messageHandler.on("FetchBuiltInCMap", data => { if (this.destroyed) { return Promise.reject(new Error("Worker was destroyed.")); } if (!this.cMapReaderFactory) { return Promise.reject(new Error("CMapReaderFactory not initialized, see the `useWorkerFetch` parameter.")); } return this.cMapReaderFactory.fetch(data); }); messageHandler.on("FetchStandardFontData", data => { if (this.destroyed) { return Promise.reject(new Error("Worker was destroyed.")); } if (!this.standardFontDataFactory) { return Promise.reject(new Error("StandardFontDataFactory not initialized, see the `useWorkerFetch` parameter.")); } return this.standardFontDataFactory.fetch(data); }); } getData() { return this.messageHandler.sendWithPromise("GetData", null); } saveDocument() { if (this.annotationStorage.size <= 0) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead."); } const { map, transfer } = this.annotationStorage.serializable; return this.messageHandler.sendWithPromise("SaveDocument", { isPureXfa: !!this._htmlForXfa, numPages: this._numPages, annotationStorage: map, filename: this._fullReader?.filename ?? null }, transfer).finally(() => { this.annotationStorage.resetModified(); }); } getPage(pageNumber) { if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) { return Promise.reject(new Error("Invalid page request.")); } const pageIndex = pageNumber - 1, cachedPromise = this.#pagePromises.get(pageIndex); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise("GetPage", { pageIndex }).then(pageInfo => { if (this.destroyed) { throw new Error("Transport destroyed"); } const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug); this.#pageCache.set(pageIndex, page); return page; }); this.#pagePromises.set(pageIndex, promise); return promise; } getPageIndex(ref) { if (typeof ref !== "object" || ref === null || !Number.isInteger(ref.num) || ref.num < 0 || !Number.isInteger(ref.gen) || ref.gen < 0) { return Promise.reject(new Error("Invalid pageIndex request.")); } return this.messageHandler.sendWithPromise("GetPageIndex", { num: ref.num, gen: ref.gen }); } getAnnotations(pageIndex, intent) { return this.messageHandler.sendWithPromise("GetAnnotations", { pageIndex, intent }); } getFieldObjects() { return this.#cacheSimpleMethod("GetFieldObjects"); } hasJSActions() { return this.#cacheSimpleMethod("HasJSActions"); } getCalculationOrderIds() { return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null); } getDestinations() { return this.messageHandler.sendWithPromise("GetDestinations", null); } getDestination(id) { if (typeof id !== "string") { return Promise.reject(new Error("Invalid destination request.")); } return this.messageHandler.sendWithPromise("GetDestination", { id }); } getPageLabels() { return this.messageHandler.sendWithPromise("GetPageLabels", null); } getPageLayout() { return this.messageHandler.sendWithPromise("GetPageLayout", null); } getPageMode() { return this.messageHandler.sendWithPromise("GetPageMode", null); } getViewerPreferences() { return this.messageHandler.sendWithPromise("GetViewerPreferences", null); } getOpenAction() { return this.messageHandler.sendWithPromise("GetOpenAction", null); } getAttachments() { return this.messageHandler.sendWithPromise("GetAttachments", null); } getDocJSActions() { return this.#cacheSimpleMethod("GetDocJSActions"); } getPageJSActions(pageIndex) { return this.messageHandler.sendWithPromise("GetPageJSActions", { pageIndex }); } getStructTree(pageIndex) { return this.messageHandler.sendWithPromise("GetStructTree", { pageIndex }); } getOutline() { return this.messageHandler.sendWithPromise("GetOutline", null); } getOptionalContentConfig() { return this.messageHandler.sendWithPromise("GetOptionalContentConfig", null).then(results => { return new _optional_content_config_js__WEBPACK_IMPORTED_MODULE_9__.OptionalContentConfig(results); }); } getPermissions() { return this.messageHandler.sendWithPromise("GetPermissions", null); } getMetadata() { const name = "GetMetadata", cachedPromise = this.#methodPromises.get(name); if (cachedPromise) { return cachedPromise; } const promise = this.messageHandler.sendWithPromise(name, null).then(results => { return { info: results[0], metadata: results[1] ? new _metadata_js__WEBPACK_IMPORTED_MODULE_8__.Metadata(results[1]) : null, contentDispositionFilename: this._fullReader?.filename ?? null, contentLength: this._fullReader?.contentLength ?? null }; }); this.#methodPromises.set(name, promise); return promise; } getMarkInfo() { return this.messageHandler.sendWithPromise("GetMarkInfo", null); } async startCleanup(keepLoadedFonts = false) { if (this.destroyed) { return; } await this.messageHandler.sendWithPromise("Cleanup", null); for (const page of this.#pageCache.values()) { const cleanupSuccessful = page.cleanup(); if (!cleanupSuccessful) { throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`); } } this.commonObjs.clear(); if (!keepLoadedFonts) { this.fontLoader.clear(); } this.#methodPromises.clear(); this.filterFactory.destroy(true); } get loadingParams() { const { disableAutoFetch, enableXfa } = this._params; return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "loadingParams", { disableAutoFetch, enableXfa }); } } class PDFObjects { #objs = Object.create(null); #ensureObj(objId) { return this.#objs[objId] ||= { capability: new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(), data: null }; } get(objId, callback = null) { if (callback) { const obj = this.#ensureObj(objId); obj.capability.promise.then(() => callback(obj.data)); return null; } const obj = this.#objs[objId]; if (!obj?.capability.settled) { throw new Error(`Requesting object that isn't resolved yet ${objId}.`); } return obj.data; } has(objId) { const obj = this.#objs[objId]; return obj?.capability.settled || false; } resolve(objId, data = null) { const obj = this.#ensureObj(objId); obj.data = data; obj.capability.resolve(); } clear() { for (const objId in this.#objs) { const { data } = this.#objs[objId]; data?.bitmap?.close(); } this.#objs = Object.create(null); } } class RenderTask { #internalRenderTask = null; constructor(internalRenderTask) { this.#internalRenderTask = internalRenderTask; this.onContinue = null; } get promise() { return this.#internalRenderTask.capability.promise; } cancel(extraDelay = 0) { this.#internalRenderTask.cancel(null, extraDelay); } get separateAnnots() { const { separateAnnots } = this.#internalRenderTask.operatorList; if (!separateAnnots) { return false; } const { annotationCanvasMap } = this.#internalRenderTask; return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0; } } class InternalRenderTask { static #canvasInUse = new WeakSet(); constructor({ callback, params, objs, commonObjs, annotationCanvasMap, operatorList, pageIndex, canvasFactory, filterFactory, useRequestAnimationFrame = false, pdfBug = false, pageColors = null }) { this.callback = callback; this.params = params; this.objs = objs; this.commonObjs = commonObjs; this.annotationCanvasMap = annotationCanvasMap; this.operatorListIdx = null; this.operatorList = operatorList; this._pageIndex = pageIndex; this.canvasFactory = canvasFactory; this.filterFactory = filterFactory; this._pdfBug = pdfBug; this.pageColors = pageColors; this.running = false; this.graphicsReadyCallback = null; this.graphicsReady = false; this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined"; this.cancelled = false; this.capability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.task = new RenderTask(this); this._cancelBound = this.cancel.bind(this); this._continueBound = this._continue.bind(this); this._scheduleNextBound = this._scheduleNext.bind(this); this._nextBound = this._next.bind(this); this._canvas = params.canvasContext.canvas; } get completed() { return this.capability.promise.catch(function () {}); } initializeGraphics({ transparency = false, optionalContentConfig }) { if (this.cancelled) { return; } if (this._canvas) { if (InternalRenderTask.#canvasInUse.has(this._canvas)) { throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed."); } InternalRenderTask.#canvasInUse.add(this._canvas); } if (this._pdfBug && globalThis.StepperManager?.enabled) { this.stepper = globalThis.StepperManager.create(this._pageIndex); this.stepper.init(this.operatorList); this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); } const { canvasContext, viewport, transform, background } = this.params; this.gfx = new _canvas_js__WEBPACK_IMPORTED_MODULE_5__.CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { optionalContentConfig }, this.annotationCanvasMap, this.pageColors); this.gfx.beginDrawing({ transform, viewport, transparency, background }); this.operatorListIdx = 0; this.graphicsReady = true; this.graphicsReadyCallback?.(); } cancel(error = null, extraDelay = 0) { this.running = false; this.cancelled = true; this.gfx?.endDrawing(); InternalRenderTask.#canvasInUse.delete(this._canvas); this.callback(error || new _display_utils_js__WEBPACK_IMPORTED_MODULE_2__.RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay)); } operatorListChanged() { if (!this.graphicsReady) { this.graphicsReadyCallback ||= this._continueBound; return; } this.stepper?.updateOperatorList(this.operatorList); if (this.running) { return; } this._continue(); } _continue() { this.running = true; if (this.cancelled) { return; } if (this.task.onContinue) { this.task.onContinue(this._scheduleNextBound); } else { this._scheduleNext(); } } _scheduleNext() { if (this._useRequestAnimationFrame) { window.requestAnimationFrame(() => { this._nextBound().catch(this._cancelBound); }); } else { Promise.resolve().then(this._nextBound).catch(this._cancelBound); } } async _next() { if (this.cancelled) { return; } this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper); if (this.operatorListIdx === this.operatorList.argsArray.length) { this.running = false; if (this.operatorList.lastChunk) { this.gfx.endDrawing(); InternalRenderTask.#canvasInUse.delete(this._canvas); this.callback(); } } } } const version = '4.0.269'; const build = 'f4b396f6c'; __webpack_async_result__(); } catch(e) { __webpack_async_result__(e); } }); /***/ }), /***/ 822: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ BaseCMapReaderFactory: () => (/* binding */ BaseCMapReaderFactory), /* harmony export */ BaseCanvasFactory: () => (/* binding */ BaseCanvasFactory), /* harmony export */ BaseFilterFactory: () => (/* binding */ BaseFilterFactory), /* harmony export */ BaseSVGFactory: () => (/* binding */ BaseSVGFactory), /* harmony export */ BaseStandardFontDataFactory: () => (/* binding */ BaseStandardFontDataFactory) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); class BaseFilterFactory { constructor() { if (this.constructor === BaseFilterFactory) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Cannot initialize BaseFilterFactory."); } } addFilter(maps) { return "none"; } addHCMFilter(fgColor, bgColor) { return "none"; } addHighlightHCMFilter(fgColor, bgColor, newFgColor, newBgColor) { return "none"; } destroy(keepHCM = false) {} } class BaseCanvasFactory { constructor() { if (this.constructor === BaseCanvasFactory) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Cannot initialize BaseCanvasFactory."); } } create(width, height) { if (width <= 0 || height <= 0) { throw new Error("Invalid canvas size"); } const canvas = this._createCanvas(width, height); return { canvas, context: canvas.getContext("2d") }; } reset(canvasAndContext, width, height) { if (!canvasAndContext.canvas) { throw new Error("Canvas is not specified"); } if (width <= 0 || height <= 0) { throw new Error("Invalid canvas size"); } canvasAndContext.canvas.width = width; canvasAndContext.canvas.height = height; } destroy(canvasAndContext) { if (!canvasAndContext.canvas) { throw new Error("Canvas is not specified"); } canvasAndContext.canvas.width = 0; canvasAndContext.canvas.height = 0; canvasAndContext.canvas = null; canvasAndContext.context = null; } _createCanvas(width, height) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Abstract method `_createCanvas` called."); } } class BaseCMapReaderFactory { constructor({ baseUrl = null, isCompressed = true }) { if (this.constructor === BaseCMapReaderFactory) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Cannot initialize BaseCMapReaderFactory."); } this.baseUrl = baseUrl; this.isCompressed = isCompressed; } async fetch({ name }) { if (!this.baseUrl) { throw new Error('The CMap "baseUrl" parameter must be specified, ensure that ' + 'the "cMapUrl" and "cMapPacked" API parameters are provided.'); } if (!name) { throw new Error("CMap name must be specified."); } const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : ""); const compressionType = this.isCompressed ? _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.CMapCompressionType.BINARY : _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.CMapCompressionType.NONE; return this._fetchData(url, compressionType).catch(reason => { throw new Error(`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`); }); } _fetchData(url, compressionType) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Abstract method `_fetchData` called."); } } class BaseStandardFontDataFactory { constructor({ baseUrl = null }) { if (this.constructor === BaseStandardFontDataFactory) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Cannot initialize BaseStandardFontDataFactory."); } this.baseUrl = baseUrl; } async fetch({ filename }) { if (!this.baseUrl) { throw new Error('The standard font "baseUrl" parameter must be specified, ensure that ' + 'the "standardFontDataUrl" API parameter is provided.'); } if (!filename) { throw new Error("Font filename must be specified."); } const url = `${this.baseUrl}${filename}`; return this._fetchData(url).catch(reason => { throw new Error(`Unable to load font data at: ${url}`); }); } _fetchData(url) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Abstract method `_fetchData` called."); } } class BaseSVGFactory { constructor() { if (this.constructor === BaseSVGFactory) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Cannot initialize BaseSVGFactory."); } } create(width, height, skipDimensions = false) { if (width <= 0 || height <= 0) { throw new Error("Invalid SVG dimensions"); } const svg = this._createSVG("svg:svg"); svg.setAttribute("version", "1.1"); if (!skipDimensions) { svg.setAttribute("width", `${width}px`); svg.setAttribute("height", `${height}px`); } svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("viewBox", `0 0 ${width} ${height}`); return svg; } createElement(type) { if (typeof type !== "string") { throw new Error("Invalid SVG element type"); } return this._createSVG(type); } _createSVG(type) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Abstract method `_createSVG` called."); } } /***/ }), /***/ 250: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { // EXPORTS __webpack_require__.d(__webpack_exports__, { CanvasGraphics: () => (/* binding */ CanvasGraphics) }); // EXTERNAL MODULE: ./src/shared/util.js var util = __webpack_require__(266); // EXTERNAL MODULE: ./src/display/display_utils.js var display_utils = __webpack_require__(473); ;// CONCATENATED MODULE: ./src/display/pattern_helper.js const PathType = { FILL: "Fill", STROKE: "Stroke", SHADING: "Shading" }; function applyBoundingBox(ctx, bbox) { if (!bbox) { return; } const width = bbox[2] - bbox[0]; const height = bbox[3] - bbox[1]; const region = new Path2D(); region.rect(bbox[0], bbox[1], width, height); ctx.clip(region); } class BaseShadingPattern { constructor() { if (this.constructor === BaseShadingPattern) { (0,util.unreachable)("Cannot initialize BaseShadingPattern."); } } getPattern() { (0,util.unreachable)("Abstract method `getPattern` called."); } } class RadialAxialShadingPattern extends BaseShadingPattern { constructor(IR) { super(); this._type = IR[1]; this._bbox = IR[2]; this._colorStops = IR[3]; this._p0 = IR[4]; this._p1 = IR[5]; this._r0 = IR[6]; this._r1 = IR[7]; this.matrix = null; } _createGradient(ctx) { let grad; if (this._type === "axial") { grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]); } else if (this._type === "radial") { grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1); } for (const colorStop of this._colorStops) { grad.addColorStop(colorStop[0], colorStop[1]); } return grad; } getPattern(ctx, owner, inverse, pathType) { let pattern; if (pathType === PathType.STROKE || pathType === PathType.FILL) { const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, (0,display_utils.getCurrentTransform)(ctx)) || [0, 0, 0, 0]; const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1; const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1; const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height, true); const tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); tmpCtx.beginPath(); tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height); tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]); inverse = util.Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]); tmpCtx.transform(...owner.baseTransform); if (this.matrix) { tmpCtx.transform(...this.matrix); } applyBoundingBox(tmpCtx, this._bbox); tmpCtx.fillStyle = this._createGradient(tmpCtx); tmpCtx.fill(); pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat"); const domMatrix = new DOMMatrix(inverse); pattern.setTransform(domMatrix); } else { applyBoundingBox(ctx, this._bbox); pattern = this._createGradient(ctx); } return pattern; } } function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) { const coords = context.coords, colors = context.colors; const bytes = data.data, rowSize = data.width * 4; let tmp; if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } if (coords[p2 + 1] > coords[p3 + 1]) { tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; } if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } const x1 = (coords[p1] + context.offsetX) * context.scaleX; const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; const x2 = (coords[p2] + context.offsetX) * context.scaleX; const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; const x3 = (coords[p3] + context.offsetX) * context.scaleX; const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; if (y1 >= y3) { return; } const c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; const c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; const c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; const minY = Math.round(y1), maxY = Math.round(y3); let xa, car, cag, cab; let xb, cbr, cbg, cbb; for (let y = minY; y <= maxY; y++) { if (y < y2) { const k = y < y1 ? 0 : (y1 - y) / (y1 - y2); xa = x1 - (x1 - x2) * k; car = c1r - (c1r - c2r) * k; cag = c1g - (c1g - c2g) * k; cab = c1b - (c1b - c2b) * k; } else { let k; if (y > y3) { k = 1; } else if (y2 === y3) { k = 0; } else { k = (y2 - y) / (y2 - y3); } xa = x2 - (x2 - x3) * k; car = c2r - (c2r - c3r) * k; cag = c2g - (c2g - c3g) * k; cab = c2b - (c2b - c3b) * k; } let k; if (y < y1) { k = 0; } else if (y > y3) { k = 1; } else { k = (y1 - y) / (y1 - y3); } xb = x1 - (x1 - x3) * k; cbr = c1r - (c1r - c3r) * k; cbg = c1g - (c1g - c3g) * k; cbb = c1b - (c1b - c3b) * k; const x1_ = Math.round(Math.min(xa, xb)); const x2_ = Math.round(Math.max(xa, xb)); let j = rowSize * y + x1_ * 4; for (let x = x1_; x <= x2_; x++) { k = (xa - x) / (xa - xb); if (k < 0) { k = 0; } else if (k > 1) { k = 1; } bytes[j++] = car - (car - cbr) * k | 0; bytes[j++] = cag - (cag - cbg) * k | 0; bytes[j++] = cab - (cab - cbb) * k | 0; bytes[j++] = 255; } } } function drawFigure(data, figure, context) { const ps = figure.coords; const cs = figure.colors; let i, ii; switch (figure.type) { case "lattice": const verticesPerRow = figure.verticesPerRow; const rows = Math.floor(ps.length / verticesPerRow) - 1; const cols = verticesPerRow - 1; for (i = 0; i < rows; i++) { let q = i * verticesPerRow; for (let j = 0; j < cols; j++, q++) { drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]); drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]); } } break; case "triangles": for (i = 0, ii = ps.length; i < ii; i += 3) { drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]); } break; default: throw new Error("illegal figure"); } } class MeshShadingPattern extends BaseShadingPattern { constructor(IR) { super(); this._coords = IR[2]; this._colors = IR[3]; this._figures = IR[4]; this._bounds = IR[5]; this._bbox = IR[7]; this._background = IR[8]; this.matrix = null; } _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) { const EXPECTED_SCALE = 1.1; const MAX_PATTERN_SIZE = 3000; const BORDER_SIZE = 2; const offsetX = Math.floor(this._bounds[0]); const offsetY = Math.floor(this._bounds[1]); const boundsWidth = Math.ceil(this._bounds[2]) - offsetX; const boundsHeight = Math.ceil(this._bounds[3]) - offsetY; const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); const scaleX = boundsWidth / width; const scaleY = boundsHeight / height; const context = { coords: this._coords, colors: this._colors, offsetX: -offsetX, offsetY: -offsetY, scaleX: 1 / scaleX, scaleY: 1 / scaleY }; const paddedWidth = width + BORDER_SIZE * 2; const paddedHeight = height + BORDER_SIZE * 2; const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight, false); const tmpCtx = tmpCanvas.context; const data = tmpCtx.createImageData(width, height); if (backgroundColor) { const bytes = data.data; for (let i = 0, ii = bytes.length; i < ii; i += 4) { bytes[i] = backgroundColor[0]; bytes[i + 1] = backgroundColor[1]; bytes[i + 2] = backgroundColor[2]; bytes[i + 3] = 255; } } for (const figure of this._figures) { drawFigure(data, figure, context); } tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE); const canvas = tmpCanvas.canvas; return { canvas, offsetX: offsetX - BORDER_SIZE * scaleX, offsetY: offsetY - BORDER_SIZE * scaleY, scaleX, scaleY }; } getPattern(ctx, owner, inverse, pathType) { applyBoundingBox(ctx, this._bbox); let scale; if (pathType === PathType.SHADING) { scale = util.Util.singularValueDecompose2dScale((0,display_utils.getCurrentTransform)(ctx)); } else { scale = util.Util.singularValueDecompose2dScale(owner.baseTransform); if (this.matrix) { const matrixScale = util.Util.singularValueDecompose2dScale(this.matrix); scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]]; } } const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases); if (pathType !== PathType.SHADING) { ctx.setTransform(...owner.baseTransform); if (this.matrix) { ctx.transform(...this.matrix); } } ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat"); } } class DummyShadingPattern extends BaseShadingPattern { getPattern() { return "hotpink"; } } function getShadingPattern(IR) { switch (IR[0]) { case "RadialAxial": return new RadialAxialShadingPattern(IR); case "Mesh": return new MeshShadingPattern(IR); case "Dummy": return new DummyShadingPattern(); } throw new Error(`Unknown IR type: ${IR[0]}`); } const PaintType = { COLORED: 1, UNCOLORED: 2 }; class TilingPattern { static MAX_PATTERN_SIZE = 3000; constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) { this.operatorList = IR[2]; this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; this.bbox = IR[4]; this.xstep = IR[5]; this.ystep = IR[6]; this.paintType = IR[7]; this.tilingType = IR[8]; this.color = color; this.ctx = ctx; this.canvasGraphicsFactory = canvasGraphicsFactory; this.baseTransform = baseTransform; } createPatternCanvas(owner) { const operatorList = this.operatorList; const bbox = this.bbox; const xstep = this.xstep; const ystep = this.ystep; const paintType = this.paintType; const tilingType = this.tilingType; const color = this.color; const canvasGraphicsFactory = this.canvasGraphicsFactory; (0,util.info)("TilingType: " + tilingType); const x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; const matrixScale = util.Util.singularValueDecompose2dScale(this.matrix); const curMatrixScale = util.Util.singularValueDecompose2dScale(this.baseTransform); const combinedScale = [matrixScale[0] * curMatrixScale[0], matrixScale[1] * curMatrixScale[1]]; const dimx = this.getSizeAndScale(xstep, this.ctx.canvas.width, combinedScale[0]); const dimy = this.getSizeAndScale(ystep, this.ctx.canvas.height, combinedScale[1]); const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size, true); const tmpCtx = tmpCanvas.context; const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx); graphics.groupLevel = owner.groupLevel; this.setFillAndStrokeStyleToContext(graphics, paintType, color); let adjustedX0 = x0; let adjustedY0 = y0; let adjustedX1 = x1; let adjustedY1 = y1; if (x0 < 0) { adjustedX0 = 0; adjustedX1 += Math.abs(x0); } if (y0 < 0) { adjustedY0 = 0; adjustedY1 += Math.abs(y0); } tmpCtx.translate(-(dimx.scale * adjustedX0), -(dimy.scale * adjustedY0)); graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0); tmpCtx.save(); this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1); graphics.baseTransform = (0,display_utils.getCurrentTransform)(graphics.ctx); graphics.executeOperatorList(operatorList); graphics.endDrawing(); return { canvas: tmpCanvas.canvas, scaleX: dimx.scale, scaleY: dimy.scale, offsetX: adjustedX0, offsetY: adjustedY0 }; } getSizeAndScale(step, realOutputSize, scale) { step = Math.abs(step); const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize); let size = Math.ceil(step * scale); if (size >= maxSize) { size = maxSize; } else { scale = size / step; } return { scale, size }; } clipBbox(graphics, x0, y0, x1, y1) { const bboxWidth = x1 - x0; const bboxHeight = y1 - y0; graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight); graphics.current.updateRectMinMax((0,display_utils.getCurrentTransform)(graphics.ctx), [x0, y0, x1, y1]); graphics.clip(); graphics.endPath(); } setFillAndStrokeStyleToContext(graphics, paintType, color) { const context = graphics.ctx, current = graphics.current; switch (paintType) { case PaintType.COLORED: const ctx = this.ctx; context.fillStyle = ctx.fillStyle; context.strokeStyle = ctx.strokeStyle; current.fillColor = ctx.fillStyle; current.strokeColor = ctx.strokeStyle; break; case PaintType.UNCOLORED: const cssColor = util.Util.makeHexColor(color[0], color[1], color[2]); context.fillStyle = cssColor; context.strokeStyle = cssColor; current.fillColor = cssColor; current.strokeColor = cssColor; break; default: throw new util.FormatError(`Unsupported paint type: ${paintType}`); } } getPattern(ctx, owner, inverse, pathType) { let matrix = inverse; if (pathType !== PathType.SHADING) { matrix = util.Util.transform(matrix, owner.baseTransform); if (this.matrix) { matrix = util.Util.transform(matrix, this.matrix); } } const temporaryPatternCanvas = this.createPatternCanvas(owner); let domMatrix = new DOMMatrix(matrix); domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY); domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY); const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat"); pattern.setTransform(domMatrix); return pattern; } } ;// CONCATENATED MODULE: ./src/shared/image_utils.js function convertToRGBA(params) { switch (params.kind) { case ImageKind.GRAYSCALE_1BPP: return convertBlackAndWhiteToRGBA(params); case ImageKind.RGB_24BPP: return convertRGBToRGBA(params); } return null; } function convertBlackAndWhiteToRGBA({ src, srcPos = 0, dest, width, height, nonBlackColor = 0xffffffff, inverseDecode = false }) { const black = util.FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; const widthInSource = width >> 3; const widthRemainder = width & 7; const srcLength = src.length; dest = new Uint32Array(dest.buffer); let destPos = 0; for (let i = 0; i < height; i++) { for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { const elem = srcPos < srcLength ? src[srcPos] : 255; dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; } if (widthRemainder === 0) { continue; } const elem = srcPos < srcLength ? src[srcPos++] : 255; for (let j = 0; j < widthRemainder; j++) { dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping; } } return { srcPos, destPos }; } function convertRGBToRGBA({ src, srcPos = 0, dest, destPos = 0, width, height }) { let i = 0; const len32 = src.length >> 2; const src32 = new Uint32Array(src.buffer, srcPos, len32); if (FeatureTest.isLittleEndian) { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff000000; dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000; dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000; dest[destPos + 3] = s3 >>> 8 | 0xff000000; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000; } } else { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff; dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff; dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff; dest[destPos + 3] = s3 << 8 | 0xff; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff; } } return { srcPos, destPos }; } function grayToRGBA(src, dest) { if (FeatureTest.isLittleEndian) { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x10101 | 0xff000000; } } else { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x1010100 | 0x000000ff; } } } ;// CONCATENATED MODULE: ./src/display/canvas.js const MIN_FONT_SIZE = 16; const MAX_FONT_SIZE = 100; const MAX_GROUP_SIZE = 4096; const EXECUTION_TIME = 15; const EXECUTION_STEPS = 10; const MAX_SIZE_TO_COMPILE = 1000; const FULL_CHUNK_HEIGHT = 16; function mirrorContextOperations(ctx, destCtx) { if (ctx._removeMirroring) { throw new Error("Context is already forwarding operations."); } ctx.__originalSave = ctx.save; ctx.__originalRestore = ctx.restore; ctx.__originalRotate = ctx.rotate; ctx.__originalScale = ctx.scale; ctx.__originalTranslate = ctx.translate; ctx.__originalTransform = ctx.transform; ctx.__originalSetTransform = ctx.setTransform; ctx.__originalResetTransform = ctx.resetTransform; ctx.__originalClip = ctx.clip; ctx.__originalMoveTo = ctx.moveTo; ctx.__originalLineTo = ctx.lineTo; ctx.__originalBezierCurveTo = ctx.bezierCurveTo; ctx.__originalRect = ctx.rect; ctx.__originalClosePath = ctx.closePath; ctx.__originalBeginPath = ctx.beginPath; ctx._removeMirroring = () => { ctx.save = ctx.__originalSave; ctx.restore = ctx.__originalRestore; ctx.rotate = ctx.__originalRotate; ctx.scale = ctx.__originalScale; ctx.translate = ctx.__originalTranslate; ctx.transform = ctx.__originalTransform; ctx.setTransform = ctx.__originalSetTransform; ctx.resetTransform = ctx.__originalResetTransform; ctx.clip = ctx.__originalClip; ctx.moveTo = ctx.__originalMoveTo; ctx.lineTo = ctx.__originalLineTo; ctx.bezierCurveTo = ctx.__originalBezierCurveTo; ctx.rect = ctx.__originalRect; ctx.closePath = ctx.__originalClosePath; ctx.beginPath = ctx.__originalBeginPath; delete ctx._removeMirroring; }; ctx.save = function ctxSave() { destCtx.save(); this.__originalSave(); }; ctx.restore = function ctxRestore() { destCtx.restore(); this.__originalRestore(); }; ctx.translate = function ctxTranslate(x, y) { destCtx.translate(x, y); this.__originalTranslate(x, y); }; ctx.scale = function ctxScale(x, y) { destCtx.scale(x, y); this.__originalScale(x, y); }; ctx.transform = function ctxTransform(a, b, c, d, e, f) { destCtx.transform(a, b, c, d, e, f); this.__originalTransform(a, b, c, d, e, f); }; ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { destCtx.setTransform(a, b, c, d, e, f); this.__originalSetTransform(a, b, c, d, e, f); }; ctx.resetTransform = function ctxResetTransform() { destCtx.resetTransform(); this.__originalResetTransform(); }; ctx.rotate = function ctxRotate(angle) { destCtx.rotate(angle); this.__originalRotate(angle); }; ctx.clip = function ctxRotate(rule) { destCtx.clip(rule); this.__originalClip(rule); }; ctx.moveTo = function (x, y) { destCtx.moveTo(x, y); this.__originalMoveTo(x, y); }; ctx.lineTo = function (x, y) { destCtx.lineTo(x, y); this.__originalLineTo(x, y); }; ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); }; ctx.rect = function (x, y, width, height) { destCtx.rect(x, y, width, height); this.__originalRect(x, y, width, height); }; ctx.closePath = function () { destCtx.closePath(); this.__originalClosePath(); }; ctx.beginPath = function () { destCtx.beginPath(); this.__originalBeginPath(); }; } class CachedCanvases { constructor(canvasFactory) { this.canvasFactory = canvasFactory; this.cache = Object.create(null); } getCanvas(id, width, height) { let canvasEntry; if (this.cache[id] !== undefined) { canvasEntry = this.cache[id]; this.canvasFactory.reset(canvasEntry, width, height); } else { canvasEntry = this.canvasFactory.create(width, height); this.cache[id] = canvasEntry; } return canvasEntry; } delete(id) { delete this.cache[id]; } clear() { for (const id in this.cache) { const canvasEntry = this.cache[id]; this.canvasFactory.destroy(canvasEntry); delete this.cache[id]; } } } function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) { const [a, b, c, d, tx, ty] = (0,display_utils.getCurrentTransform)(ctx); if (b === 0 && c === 0) { const tlX = destX * a + tx; const rTlX = Math.round(tlX); const tlY = destY * d + ty; const rTlY = Math.round(tlY); const brX = (destX + destW) * a + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destY + destH) * d + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight); ctx.setTransform(a, b, c, d, tx, ty); return [rWidth, rHeight]; } if (a === 0 && d === 0) { const tlX = destY * c + tx; const rTlX = Math.round(tlX); const tlY = destX * b + ty; const rTlY = Math.round(tlY); const brX = (destY + destH) * c + tx; const rWidth = Math.abs(Math.round(brX) - rTlX) || 1; const brY = (destX + destW) * b + ty; const rHeight = Math.abs(Math.round(brY) - rTlY) || 1; ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY); ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth); ctx.setTransform(a, b, c, d, tx, ty); return [rHeight, rWidth]; } ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH); const scaleX = Math.hypot(a, b); const scaleY = Math.hypot(c, d); return [scaleX * destW, scaleY * destH]; } function compileType3Glyph(imgData) { const { width, height } = imgData; if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) { return null; } const POINT_TO_PROCESS_LIMIT = 1000; const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); const width1 = width + 1; let points = new Uint8Array(width1 * (height + 1)); let i, j, j0; const lineSize = width + 7 & ~7; let data = new Uint8Array(lineSize * height), pos = 0; for (const elem of imgData.data) { let mask = 128; while (mask > 0) { data[pos++] = elem & mask ? 0 : 255; mask >>= 1; } } let count = 0; pos = 0; if (data[pos] !== 0) { points[0] = 1; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j] = data[pos] ? 2 : 1; ++count; } pos++; } if (data[pos] !== 0) { points[j] = 2; ++count; } for (i = 1; i < height; i++) { pos = i * lineSize; j0 = i * width1; if (data[pos - lineSize] !== data[pos]) { points[j0] = data[pos] ? 1 : 8; ++count; } let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); for (j = 1; j < width; j++) { sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0); if (POINT_TYPES[sum]) { points[j0 + j] = POINT_TYPES[sum]; ++count; } pos++; } if (data[pos - lineSize] !== data[pos]) { points[j0 + j] = data[pos] ? 2 : 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } } pos = lineSize * (height - 1); j0 = i * width1; if (data[pos] !== 0) { points[j0] = 8; ++count; } for (j = 1; j < width; j++) { if (data[pos] !== data[pos + 1]) { points[j0 + j] = data[pos] ? 4 : 8; ++count; } pos++; } if (data[pos] !== 0) { points[j0 + j] = 4; ++count; } if (count > POINT_TO_PROCESS_LIMIT) { return null; } const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); const path = new Path2D(); for (i = 0; count && i <= height; i++) { let p = i * width1; const end = p + width; while (p < end && !points[p]) { p++; } if (p === end) { continue; } path.moveTo(p % width1, i); const p0 = p; let type = points[p]; do { const step = steps[type]; do { p += step; } while (!points[p]); const pp = points[p]; if (pp !== 5 && pp !== 10) { type = pp; points[p] = 0; } else { type = pp & 0x33 * type >> 4; points[p] &= type >> 2 | type << 2; } path.lineTo(p % width1, p / width1 | 0); if (!points[p]) { --count; } } while (p0 !== p); --i; } data = null; points = null; const drawOutline = function (c) { c.save(); c.scale(1 / width, -1 / height); c.translate(0, -height); c.fill(path); c.beginPath(); c.restore(); }; return drawOutline; } class CanvasExtraState { constructor(width, height) { this.alphaIsShape = false; this.fontSize = 0; this.fontSizeScale = 1; this.textMatrix = util.IDENTITY_MATRIX; this.textMatrixScale = 1; this.fontMatrix = util.FONT_IDENTITY_MATRIX; this.leading = 0; this.x = 0; this.y = 0; this.lineX = 0; this.lineY = 0; this.charSpacing = 0; this.wordSpacing = 0; this.textHScale = 1; this.textRenderingMode = util.TextRenderingMode.FILL; this.textRise = 0; this.fillColor = "#000000"; this.strokeColor = "#000000"; this.patternFill = false; this.fillAlpha = 1; this.strokeAlpha = 1; this.lineWidth = 1; this.activeSMask = null; this.transferMaps = "none"; this.startNewPathAndClipBox([0, 0, width, height]); } clone() { const clone = Object.create(this); clone.clipBox = this.clipBox.slice(); return clone; } setCurrentPoint(x, y) { this.x = x; this.y = y; } updatePathMinMax(transform, x, y) { [x, y] = util.Util.applyTransform([x, y], transform); this.minX = Math.min(this.minX, x); this.minY = Math.min(this.minY, y); this.maxX = Math.max(this.maxX, x); this.maxY = Math.max(this.maxY, y); } updateRectMinMax(transform, rect) { const p1 = util.Util.applyTransform(rect, transform); const p2 = util.Util.applyTransform(rect.slice(2), transform); const p3 = util.Util.applyTransform([rect[0], rect[3]], transform); const p4 = util.Util.applyTransform([rect[2], rect[1]], transform); this.minX = Math.min(this.minX, p1[0], p2[0], p3[0], p4[0]); this.minY = Math.min(this.minY, p1[1], p2[1], p3[1], p4[1]); this.maxX = Math.max(this.maxX, p1[0], p2[0], p3[0], p4[0]); this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]); } updateScalingPathMinMax(transform, minMax) { util.Util.scaleMinMax(transform, minMax); this.minX = Math.min(this.minX, minMax[0]); this.maxX = Math.max(this.maxX, minMax[1]); this.minY = Math.min(this.minY, minMax[2]); this.maxY = Math.max(this.maxY, minMax[3]); } updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) { const box = util.Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3); if (minMax) { minMax[0] = Math.min(minMax[0], box[0], box[2]); minMax[1] = Math.max(minMax[1], box[0], box[2]); minMax[2] = Math.min(minMax[2], box[1], box[3]); minMax[3] = Math.max(minMax[3], box[1], box[3]); return; } this.updateRectMinMax(transform, box); } getPathBoundingBox(pathType = PathType.FILL, transform = null) { const box = [this.minX, this.minY, this.maxX, this.maxY]; if (pathType === PathType.STROKE) { if (!transform) { (0,util.unreachable)("Stroke bounding box must include transform."); } const scale = util.Util.singularValueDecompose2dScale(transform); const xStrokePad = scale[0] * this.lineWidth / 2; const yStrokePad = scale[1] * this.lineWidth / 2; box[0] -= xStrokePad; box[1] -= yStrokePad; box[2] += xStrokePad; box[3] += yStrokePad; } return box; } updateClipFromPath() { const intersect = util.Util.intersect(this.clipBox, this.getPathBoundingBox()); this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]); } isEmptyClip() { return this.minX === Infinity; } startNewPathAndClipBox(box) { this.clipBox = box; this.minX = Infinity; this.minY = Infinity; this.maxX = 0; this.maxY = 0; } getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) { return util.Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform)); } } function putBinaryImageData(ctx, imgData) { if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { ctx.putImageData(imgData, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0, destPos; const src = imgData.data; const dest = chunkImgData.data; let i, j, thisChunkHeight, elemsInThisChunk; if (imgData.kind === util.ImageKind.GRAYSCALE_1BPP) { const srcLength = src.byteLength; const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); const dest32DataLength = dest32.length; const fullSrcDiff = width + 7 >> 3; const white = 0xffffffff; const black = util.FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; for (i = 0; i < totalChunks; i++) { thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { const srcDiff = srcLength - srcPos; let k = 0; const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; const kEndUnrolled = kEnd & ~7; let mask = 0; let srcByte = 0; for (; k < kEndUnrolled; k += 8) { srcByte = src[srcPos++]; dest32[destPos++] = srcByte & 128 ? white : black; dest32[destPos++] = srcByte & 64 ? white : black; dest32[destPos++] = srcByte & 32 ? white : black; dest32[destPos++] = srcByte & 16 ? white : black; dest32[destPos++] = srcByte & 8 ? white : black; dest32[destPos++] = srcByte & 4 ? white : black; dest32[destPos++] = srcByte & 2 ? white : black; dest32[destPos++] = srcByte & 1 ? white : black; } for (; k < kEnd; k++) { if (mask === 0) { srcByte = src[srcPos++]; mask = 128; } dest32[destPos++] = srcByte & mask ? white : black; mask >>= 1; } } while (destPos < dest32DataLength) { dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else if (imgData.kind === util.ImageKind.RGBA_32BPP) { j = 0; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; ctx.putImageData(chunkImgData, 0, j); j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === util.ImageKind.RGB_24BPP) { thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { if (i >= fullChunks) { thisChunkHeight = partialChunkHeight; elemsInThisChunk = width * thisChunkHeight; } destPos = 0; for (j = elemsInThisChunk; j--;) { dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { throw new Error(`bad image kind: ${imgData.kind}`); } } function putBinaryImageMask(ctx, imgData) { if (imgData.bitmap) { ctx.drawImage(imgData.bitmap, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0; const src = imgData.data; const dest = chunkImgData.data; for (let i = 0; i < totalChunks; i++) { const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; ({ srcPos } = convertBlackAndWhiteToRGBA({ src, srcPos, dest, width, height: thisChunkHeight, nonBlackColor: 0 })); ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } function copyCtxState(sourceCtx, destCtx) { const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"]; for (const property of properties) { if (sourceCtx[property] !== undefined) { destCtx[property] = sourceCtx[property]; } } if (sourceCtx.setLineDash !== undefined) { destCtx.setLineDash(sourceCtx.getLineDash()); destCtx.lineDashOffset = sourceCtx.lineDashOffset; } } function resetCtxToDefault(ctx) { ctx.strokeStyle = ctx.fillStyle = "#000000"; ctx.fillRule = "nonzero"; ctx.globalAlpha = 1; ctx.lineWidth = 1; ctx.lineCap = "butt"; ctx.lineJoin = "miter"; ctx.miterLimit = 10; ctx.globalCompositeOperation = "source-over"; ctx.font = "10px sans-serif"; if (ctx.setLineDash !== undefined) { ctx.setLineDash([]); ctx.lineDashOffset = 0; } if (!util.isNodeJS) { const { filter } = ctx; if (filter !== "none" && filter !== "") { ctx.filter = "none"; } } } function composeSMaskBackdrop(bytes, r0, g0, b0) { const length = bytes.length; for (let i = 3; i < length; i += 4) { const alpha = bytes[i]; if (alpha === 0) { bytes[i - 3] = r0; bytes[i - 2] = g0; bytes[i - 1] = b0; } else if (alpha < 255) { const alpha_ = 255 - alpha; bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8; bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8; bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8; } } } function composeSMaskAlpha(maskData, layerData, transferMap) { const length = maskData.length; const scale = 1 / 255; for (let i = 3; i < length; i += 4) { const alpha = transferMap ? transferMap[maskData[i]] : maskData[i]; layerData[i] = layerData[i] * alpha * scale | 0; } } function composeSMaskLuminosity(maskData, layerData, transferMap) { const length = maskData.length; for (let i = 3; i < length; i += 4) { const y = maskData[i - 3] * 77 + maskData[i - 2] * 152 + maskData[i - 1] * 28; layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16; } } function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) { const hasBackdrop = !!backdrop; const r0 = hasBackdrop ? backdrop[0] : 0; const g0 = hasBackdrop ? backdrop[1] : 0; const b0 = hasBackdrop ? backdrop[2] : 0; const composeFn = subtype === "Luminosity" ? composeSMaskLuminosity : composeSMaskAlpha; const PIXELS_TO_PROCESS = 1048576; const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); for (let row = 0; row < height; row += chunkSize) { const chunkHeight = Math.min(chunkSize, height - row); const maskData = maskCtx.getImageData(layerOffsetX - maskOffsetX, row + (layerOffsetY - maskOffsetY), width, chunkHeight); const layerData = layerCtx.getImageData(layerOffsetX, row + layerOffsetY, width, chunkHeight); if (hasBackdrop) { composeSMaskBackdrop(maskData.data, r0, g0, b0); } composeFn(maskData.data, layerData.data, transferMap); layerCtx.putImageData(layerData, layerOffsetX, row + layerOffsetY); } } function composeSMask(ctx, smask, layerCtx, layerBox) { const layerOffsetX = layerBox[0]; const layerOffsetY = layerBox[1]; const layerWidth = layerBox[2] - layerOffsetX; const layerHeight = layerBox[3] - layerOffsetY; if (layerWidth === 0 || layerHeight === 0) { return; } genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY); ctx.save(); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(layerCtx.canvas, 0, 0); ctx.restore(); } function getImageSmoothingEnabled(transform, interpolate) { const scale = util.Util.singularValueDecompose2dScale(transform); scale[0] = Math.fround(scale[0]); scale[1] = Math.fround(scale[1]); const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * display_utils.PixelsPerInch.PDF_TO_CSS_UNITS); if (interpolate !== undefined) { return interpolate; } else if (scale[0] <= actualScale || scale[1] <= actualScale) { return true; } return false; } const LINE_CAP_STYLES = ["butt", "round", "square"]; const LINE_JOIN_STYLES = ["miter", "round", "bevel"]; const NORMAL_CLIP = {}; const EO_CLIP = {}; class CanvasGraphics { constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, { optionalContentConfig, markedContentStack = null }, annotationCanvasMap, pageColors) { this.ctx = canvasCtx; this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); this.stateStack = []; this.pendingClip = null; this.pendingEOFill = false; this.res = null; this.xobjs = null; this.commonObjs = commonObjs; this.objs = objs; this.canvasFactory = canvasFactory; this.filterFactory = filterFactory; this.groupStack = []; this.processingType3 = null; this.baseTransform = null; this.baseTransformStack = []; this.groupLevel = 0; this.smaskStack = []; this.smaskCounter = 0; this.tempSMask = null; this.suspendedCtx = null; this.contentVisible = true; this.markedContentStack = markedContentStack || []; this.optionalContentConfig = optionalContentConfig; this.cachedCanvases = new CachedCanvases(this.canvasFactory); this.cachedPatterns = new Map(); this.annotationCanvasMap = annotationCanvasMap; this.viewportScale = 1; this.outputScaleX = 1; this.outputScaleY = 1; this.pageColors = pageColors; this._cachedScaleForStroking = [-1, 0]; this._cachedGetSinglePixelWidth = null; this._cachedBitmapsMap = new Map(); } getObject(data, fallback = null) { if (typeof data === "string") { return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data); } return fallback; } beginDrawing({ transform, viewport, transparency = false, background = null }) { const width = this.ctx.canvas.width; const height = this.ctx.canvas.height; const savedFillStyle = this.ctx.fillStyle; this.ctx.fillStyle = background || "#ffffff"; this.ctx.fillRect(0, 0, width, height); this.ctx.fillStyle = savedFillStyle; if (transparency) { const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height); this.compositeCtx = this.ctx; this.transparentCanvas = transparentCanvas.canvas; this.ctx = transparentCanvas.context; this.ctx.save(); this.ctx.transform(...(0,display_utils.getCurrentTransform)(this.compositeCtx)); } this.ctx.save(); resetCtxToDefault(this.ctx); if (transform) { this.ctx.transform(...transform); this.outputScaleX = transform[0]; this.outputScaleY = transform[0]; } this.ctx.transform(...viewport.transform); this.viewportScale = viewport.scale; this.baseTransform = (0,display_utils.getCurrentTransform)(this.ctx); } executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) { const argsArray = operatorList.argsArray; const fnArray = operatorList.fnArray; let i = executionStartIdx || 0; const argsArrayLen = argsArray.length; if (argsArrayLen === i) { return i; } const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function"; const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0; let steps = 0; const commonObjs = this.commonObjs; const objs = this.objs; let fnId; while (true) { if (stepper !== undefined && i === stepper.nextBreakPoint) { stepper.breakIt(i, continueCallback); return i; } fnId = fnArray[i]; if (fnId !== util.OPS.dependency) { this[fnId].apply(this, argsArray[i]); } else { for (const depObjId of argsArray[i]) { const objsPool = depObjId.startsWith("g_") ? commonObjs : objs; if (!objsPool.has(depObjId)) { objsPool.get(depObjId, continueCallback); return i; } } } i++; if (i === argsArrayLen) { return i; } if (chunkOperations && ++steps > EXECUTION_STEPS) { if (Date.now() > endTime) { continueCallback(); return i; } steps = 0; } } } #restoreInitialState() { while (this.stateStack.length || this.inSMaskMode) { this.restore(); } this.ctx.restore(); if (this.transparentCanvas) { this.ctx = this.compositeCtx; this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.drawImage(this.transparentCanvas, 0, 0); this.ctx.restore(); this.transparentCanvas = null; } } endDrawing() { this.#restoreInitialState(); this.cachedCanvases.clear(); this.cachedPatterns.clear(); for (const cache of this._cachedBitmapsMap.values()) { for (const canvas of cache.values()) { if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { canvas.width = canvas.height = 0; } } cache.clear(); } this._cachedBitmapsMap.clear(); this.#drawFilter(); } #drawFilter() { if (this.pageColors) { const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background); if (hcmFilterId !== "none") { const savedFilter = this.ctx.filter; this.ctx.filter = hcmFilterId; this.ctx.drawImage(this.ctx.canvas, 0, 0); this.ctx.filter = savedFilter; } } } _scaleImage(img, inverseTransform) { const width = img.width; const height = img.height; let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1); let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1); let paintWidth = width, paintHeight = height; let tmpCanvasId = "prescale1"; let tmpCanvas, tmpCtx; while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) { let newWidth = paintWidth, newHeight = paintHeight; if (widthScale > 2 && paintWidth > 1) { newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2); widthScale /= paintWidth / newWidth; } if (heightScale > 2 && paintHeight > 1) { newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2; heightScale /= paintHeight / newHeight; } tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight); tmpCtx = tmpCanvas.context; tmpCtx.clearRect(0, 0, newWidth, newHeight); tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight); img = tmpCanvas.canvas; paintWidth = newWidth; paintHeight = newHeight; tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1"; } return { img, paintWidth, paintHeight }; } _createMaskCanvas(img) { const ctx = this.ctx; const { width, height } = img; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; const currentTransform = (0,display_utils.getCurrentTransform)(ctx); let cache, cacheKey, scaled, maskCanvas; if ((img.bitmap || img.data) && img.count > 1) { const mainKey = img.bitmap || img.data.buffer; cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]); cache = this._cachedBitmapsMap.get(mainKey); if (!cache) { cache = new Map(); this._cachedBitmapsMap.set(mainKey, cache); } const cachedImage = cache.get(cacheKey); if (cachedImage && !isPatternFill) { const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]); const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]); return { canvas: cachedImage, offsetX, offsetY }; } scaled = cachedImage; } if (!scaled) { maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); putBinaryImageMask(maskCanvas.context, img); } let maskToCanvas = util.Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]); maskToCanvas = util.Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]); const [minX, minY, maxX, maxY] = util.Util.getAxialAlignedBoundingBox([0, 0, width, height], maskToCanvas); const drawnWidth = Math.round(maxX - minX) || 1; const drawnHeight = Math.round(maxY - minY) || 1; const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight); const fillCtx = fillCanvas.context; const offsetX = minX; const offsetY = minY; fillCtx.translate(-offsetX, -offsetY); fillCtx.transform(...maskToCanvas); if (!scaled) { scaled = this._scaleImage(maskCanvas.canvas, (0,display_utils.getCurrentTransformInverse)(fillCtx)); scaled = scaled.img; if (cache && isPatternFill) { cache.set(cacheKey, scaled); } } fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled((0,display_utils.getCurrentTransform)(fillCtx), img.interpolate); drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height); fillCtx.globalCompositeOperation = "source-in"; const inverse = util.Util.transform((0,display_utils.getCurrentTransformInverse)(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]); fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL) : fillColor; fillCtx.fillRect(0, 0, width, height); if (cache && !isPatternFill) { this.cachedCanvases.delete("fillCanvas"); cache.set(cacheKey, fillCanvas.canvas); } return { canvas: fillCanvas.canvas, offsetX: Math.round(offsetX), offsetY: Math.round(offsetY) }; } setLineWidth(width) { if (width !== this.current.lineWidth) { this._cachedScaleForStroking[0] = -1; } this.current.lineWidth = width; this.ctx.lineWidth = width; } setLineCap(style) { this.ctx.lineCap = LINE_CAP_STYLES[style]; } setLineJoin(style) { this.ctx.lineJoin = LINE_JOIN_STYLES[style]; } setMiterLimit(limit) { this.ctx.miterLimit = limit; } setDash(dashArray, dashPhase) { const ctx = this.ctx; if (ctx.setLineDash !== undefined) { ctx.setLineDash(dashArray); ctx.lineDashOffset = dashPhase; } } setRenderingIntent(intent) {} setFlatness(flatness) {} setGState(states) { for (const [key, value] of states) { switch (key) { case "LW": this.setLineWidth(value); break; case "LC": this.setLineCap(value); break; case "LJ": this.setLineJoin(value); break; case "ML": this.setMiterLimit(value); break; case "D": this.setDash(value[0], value[1]); break; case "RI": this.setRenderingIntent(value); break; case "FL": this.setFlatness(value); break; case "Font": this.setFont(value[0], value[1]); break; case "CA": this.current.strokeAlpha = value; break; case "ca": this.current.fillAlpha = value; this.ctx.globalAlpha = value; break; case "BM": this.ctx.globalCompositeOperation = value; break; case "SMask": this.current.activeSMask = value ? this.tempSMask : null; this.tempSMask = null; this.checkSMaskState(); break; case "TR": this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value); break; } } } get inSMaskMode() { return !!this.suspendedCtx; } checkSMaskState() { const inSMaskMode = this.inSMaskMode; if (this.current.activeSMask && !inSMaskMode) { this.beginSMaskMode(); } else if (!this.current.activeSMask && inSMaskMode) { this.endSMaskMode(); } } beginSMaskMode() { if (this.inSMaskMode) { throw new Error("beginSMaskMode called while already in smask mode"); } const drawnWidth = this.ctx.canvas.width; const drawnHeight = this.ctx.canvas.height; const cacheId = "smaskGroupAt" + this.groupLevel; const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); this.suspendedCtx = this.ctx; this.ctx = scratchCanvas.context; const ctx = this.ctx; ctx.setTransform(...(0,display_utils.getCurrentTransform)(this.suspendedCtx)); copyCtxState(this.suspendedCtx, ctx); mirrorContextOperations(ctx, this.suspendedCtx); this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]); } endSMaskMode() { if (!this.inSMaskMode) { throw new Error("endSMaskMode called while not in smask mode"); } this.ctx._removeMirroring(); copyCtxState(this.ctx, this.suspendedCtx); this.ctx = this.suspendedCtx; this.suspendedCtx = null; } compose(dirtyBox) { if (!this.current.activeSMask) { return; } if (!dirtyBox) { dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height]; } else { dirtyBox[0] = Math.floor(dirtyBox[0]); dirtyBox[1] = Math.floor(dirtyBox[1]); dirtyBox[2] = Math.ceil(dirtyBox[2]); dirtyBox[3] = Math.ceil(dirtyBox[3]); } const smask = this.current.activeSMask; const suspendedCtx = this.suspendedCtx; composeSMask(suspendedCtx, smask, this.ctx, dirtyBox); this.ctx.save(); this.ctx.setTransform(1, 0, 0, 1, 0, 0); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); this.ctx.restore(); } save() { if (this.inSMaskMode) { copyCtxState(this.ctx, this.suspendedCtx); this.suspendedCtx.save(); } else { this.ctx.save(); } const old = this.current; this.stateStack.push(old); this.current = old.clone(); } restore() { if (this.stateStack.length === 0 && this.inSMaskMode) { this.endSMaskMode(); } if (this.stateStack.length !== 0) { this.current = this.stateStack.pop(); if (this.inSMaskMode) { this.suspendedCtx.restore(); copyCtxState(this.suspendedCtx, this.ctx); } else { this.ctx.restore(); } this.checkSMaskState(); this.pendingClip = null; this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; } } transform(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; } constructPath(ops, args, minMax) { const ctx = this.ctx; const current = this.current; let x = current.x, y = current.y; let startX, startY; const currentTransform = (0,display_utils.getCurrentTransform)(ctx); const isScalingMatrix = currentTransform[0] === 0 && currentTransform[3] === 0 || currentTransform[1] === 0 && currentTransform[2] === 0; const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null; for (let i = 0, j = 0, ii = ops.length; i < ii; i++) { switch (ops[i] | 0) { case util.OPS.rectangle: x = args[j++]; y = args[j++]; const width = args[j++]; const height = args[j++]; const xw = x + width; const yh = y + height; ctx.moveTo(x, y); if (width === 0 || height === 0) { ctx.lineTo(xw, yh); } else { ctx.lineTo(xw, y); ctx.lineTo(xw, yh); ctx.lineTo(x, yh); } if (!isScalingMatrix) { current.updateRectMinMax(currentTransform, [x, y, xw, yh]); } ctx.closePath(); break; case util.OPS.moveTo: x = args[j++]; y = args[j++]; ctx.moveTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case util.OPS.lineTo: x = args[j++]; y = args[j++]; ctx.lineTo(x, y); if (!isScalingMatrix) { current.updatePathMinMax(currentTransform, x, y); } break; case util.OPS.curveTo: startX = x; startY = y; x = args[j + 4]; y = args[j + 5]; ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y, minMaxForBezier); j += 6; break; case util.OPS.curveTo2: startX = x; startY = y; ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]); current.updateCurvePathMinMax(currentTransform, startX, startY, x, y, args[j], args[j + 1], args[j + 2], args[j + 3], minMaxForBezier); x = args[j + 2]; y = args[j + 3]; j += 4; break; case util.OPS.curveTo3: startX = x; startY = y; x = args[j + 2]; y = args[j + 3]; ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y); current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], x, y, x, y, minMaxForBezier); j += 4; break; case util.OPS.closePath: ctx.closePath(); break; } } if (isScalingMatrix) { current.updateScalingPathMinMax(currentTransform, minMaxForBezier); } current.setCurrentPoint(x, y); } closePath() { this.ctx.closePath(); } stroke(consumePath = true) { const ctx = this.ctx; const strokeColor = this.current.strokeColor; ctx.globalAlpha = this.current.strokeAlpha; if (this.contentVisible) { if (typeof strokeColor === "object" && strokeColor?.getPattern) { ctx.save(); ctx.strokeStyle = strokeColor.getPattern(ctx, this, (0,display_utils.getCurrentTransformInverse)(ctx), PathType.STROKE); this.rescaleAndStroke(false); ctx.restore(); } else { this.rescaleAndStroke(true); } } if (consumePath) { this.consumePath(this.current.getClippedPathBoundingBox()); } ctx.globalAlpha = this.current.fillAlpha; } closeStroke() { this.closePath(); this.stroke(); } fill(consumePath = true) { const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; let needRestore = false; if (isPatternFill) { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx, this, (0,display_utils.getCurrentTransformInverse)(ctx), PathType.FILL); needRestore = true; } const intersect = this.current.getClippedPathBoundingBox(); if (this.contentVisible && intersect !== null) { if (this.pendingEOFill) { ctx.fill("evenodd"); this.pendingEOFill = false; } else { ctx.fill(); } } if (needRestore) { ctx.restore(); } if (consumePath) { this.consumePath(intersect); } } eoFill() { this.pendingEOFill = true; this.fill(); } fillStroke() { this.fill(false); this.stroke(false); this.consumePath(); } eoFillStroke() { this.pendingEOFill = true; this.fillStroke(); } closeFillStroke() { this.closePath(); this.fillStroke(); } closeEOFillStroke() { this.pendingEOFill = true; this.closePath(); this.fillStroke(); } endPath() { this.consumePath(); } clip() { this.pendingClip = NORMAL_CLIP; } eoClip() { this.pendingClip = EO_CLIP; } beginText() { this.current.textMatrix = util.IDENTITY_MATRIX; this.current.textMatrixScale = 1; this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } endText() { const paths = this.pendingTextPaths; const ctx = this.ctx; if (paths === undefined) { ctx.beginPath(); return; } ctx.save(); ctx.beginPath(); for (const path of paths) { ctx.setTransform(...path.transform); ctx.translate(path.x, path.y); path.addToPath(ctx, path.fontSize); } ctx.restore(); ctx.clip(); ctx.beginPath(); delete this.pendingTextPaths; } setCharSpacing(spacing) { this.current.charSpacing = spacing; } setWordSpacing(spacing) { this.current.wordSpacing = spacing; } setHScale(scale) { this.current.textHScale = scale / 100; } setLeading(leading) { this.current.leading = -leading; } setFont(fontRefName, size) { const fontObj = this.commonObjs.get(fontRefName); const current = this.current; if (!fontObj) { throw new Error(`Can't find font for ${fontRefName}`); } current.fontMatrix = fontObj.fontMatrix || util.FONT_IDENTITY_MATRIX; if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) { (0,util.warn)("Invalid font matrix for font " + fontRefName); } if (size < 0) { size = -size; current.fontDirection = -1; } else { current.fontDirection = 1; } this.current.font = fontObj; this.current.fontSize = size; if (fontObj.isType3Font) { return; } const name = fontObj.loadedName || "sans-serif"; const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`; let bold = "normal"; if (fontObj.black) { bold = "900"; } else if (fontObj.bold) { bold = "bold"; } const italic = fontObj.italic ? "italic" : "normal"; let browserFontSize = size; if (size < MIN_FONT_SIZE) { browserFontSize = MIN_FONT_SIZE; } else if (size > MAX_FONT_SIZE) { browserFontSize = MAX_FONT_SIZE; } this.current.fontSizeScale = size / browserFontSize; this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`; } setTextRenderingMode(mode) { this.current.textRenderingMode = mode; } setTextRise(rise) { this.current.textRise = rise; } moveText(x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; } setLeadingMoveText(x, y) { this.setLeading(-y); this.moveText(x, y); } setTextMatrix(a, b, c, d, e, f) { this.current.textMatrix = [a, b, c, d, e, f]; this.current.textMatrixScale = Math.hypot(a, b); this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; } nextLine() { this.moveText(0, this.current.leading); } paintChar(character, x, y, patternTransform) { const ctx = this.ctx; const current = this.current; const font = current.font; const textRenderingMode = current.textRenderingMode; const fontSize = current.fontSize / current.fontSizeScale; const fillStrokeMode = textRenderingMode & util.TextRenderingMode.FILL_STROKE_MASK; const isAddToPathSet = !!(textRenderingMode & util.TextRenderingMode.ADD_TO_PATH_FLAG); const patternFill = current.patternFill && !font.missingFile; let addToPath; if (font.disableFontFace || isAddToPathSet || patternFill) { addToPath = font.getPathGenerator(this.commonObjs, character); } if (font.disableFontFace || patternFill) { ctx.save(); ctx.translate(x, y); ctx.beginPath(); addToPath(ctx, fontSize); if (patternTransform) { ctx.setTransform(...patternTransform); } if (fillStrokeMode === util.TextRenderingMode.FILL || fillStrokeMode === util.TextRenderingMode.FILL_STROKE) { ctx.fill(); } if (fillStrokeMode === util.TextRenderingMode.STROKE || fillStrokeMode === util.TextRenderingMode.FILL_STROKE) { ctx.stroke(); } ctx.restore(); } else { if (fillStrokeMode === util.TextRenderingMode.FILL || fillStrokeMode === util.TextRenderingMode.FILL_STROKE) { ctx.fillText(character, x, y); } if (fillStrokeMode === util.TextRenderingMode.STROKE || fillStrokeMode === util.TextRenderingMode.FILL_STROKE) { ctx.strokeText(character, x, y); } } if (isAddToPathSet) { const paths = this.pendingTextPaths ||= []; paths.push({ transform: (0,display_utils.getCurrentTransform)(ctx), x, y, fontSize, addToPath }); } } get isFontSubpixelAAEnabled() { const { context: ctx } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10); ctx.scale(1.5, 1); ctx.fillText("I", 0, 10); const data = ctx.getImageData(0, 0, 10, 10).data; let enabled = false; for (let i = 3; i < data.length; i += 4) { if (data[i] > 0 && data[i] < 255) { enabled = true; break; } } return (0,util.shadow)(this, "isFontSubpixelAAEnabled", enabled); } showText(glyphs) { const current = this.current; const font = current.font; if (font.isType3Font) { return this.showType3Text(glyphs); } const fontSize = current.fontSize; if (fontSize === 0) { return undefined; } const ctx = this.ctx; const fontSizeScale = current.fontSizeScale; const charSpacing = current.charSpacing; const wordSpacing = current.wordSpacing; const fontDirection = current.fontDirection; const textHScale = current.textHScale * fontDirection; const glyphsLength = glyphs.length; const vertical = font.vertical; const spacingDir = vertical ? 1 : -1; const defaultVMetrics = font.defaultVMetrics; const widthAdvanceScale = fontSize * current.fontMatrix[0]; const simpleFillText = current.textRenderingMode === util.TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill; ctx.save(); ctx.transform(...current.textMatrix); ctx.translate(current.x, current.y + current.textRise); if (fontDirection > 0) { ctx.scale(textHScale, -1); } else { ctx.scale(textHScale, 1); } let patternTransform; if (current.patternFill) { ctx.save(); const pattern = current.fillColor.getPattern(ctx, this, (0,display_utils.getCurrentTransformInverse)(ctx), PathType.FILL); patternTransform = (0,display_utils.getCurrentTransform)(ctx); ctx.restore(); ctx.fillStyle = pattern; } let lineWidth = current.lineWidth; const scale = current.textMatrixScale; if (scale === 0 || lineWidth === 0) { const fillStrokeMode = current.textRenderingMode & util.TextRenderingMode.FILL_STROKE_MASK; if (fillStrokeMode === util.TextRenderingMode.STROKE || fillStrokeMode === util.TextRenderingMode.FILL_STROKE) { lineWidth = this.getSinglePixelWidth(); } } else { lineWidth /= scale; } if (fontSizeScale !== 1.0) { ctx.scale(fontSizeScale, fontSizeScale); lineWidth /= fontSizeScale; } ctx.lineWidth = lineWidth; if (font.isInvalidPDFjsFont) { const chars = []; let width = 0; for (const glyph of glyphs) { chars.push(glyph.unicode); width += glyph.width; } ctx.fillText(chars.join(""), 0, 0); current.x += width * widthAdvanceScale * textHScale; ctx.restore(); this.compose(); return undefined; } let x = 0, i; for (i = 0; i < glyphsLength; ++i) { const glyph = glyphs[i]; if (typeof glyph === "number") { x += spacingDir * glyph * fontSize / 1000; continue; } let restoreNeeded = false; const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; const character = glyph.fontChar; const accent = glyph.accent; let scaledX, scaledY; let width = glyph.width; if (vertical) { const vmetric = glyph.vmetric || defaultVMetrics; const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale; const vy = vmetric[2] * widthAdvanceScale; width = vmetric ? -vmetric[0] : width; scaledX = vx / fontSizeScale; scaledY = (x + vy) / fontSizeScale; } else { scaledX = x / fontSizeScale; scaledY = 0; } if (font.remeasure && width > 0) { const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale; if (width < measuredWidth && this.isFontSubpixelAAEnabled) { const characterScaleX = width / measuredWidth; restoreNeeded = true; ctx.save(); ctx.scale(characterScaleX, 1); scaledX /= characterScaleX; } else if (width !== measuredWidth) { scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale; } } if (this.contentVisible && (glyph.isInFont || font.missingFile)) { if (simpleFillText && !accent) { ctx.fillText(character, scaledX, scaledY); } else { this.paintChar(character, scaledX, scaledY, patternTransform); if (accent) { const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale; const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale; this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform); } } } const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection; x += charWidth; if (restoreNeeded) { ctx.restore(); } } if (vertical) { current.y -= x; } else { current.x += x * textHScale; } ctx.restore(); this.compose(); return undefined; } showType3Text(glyphs) { const ctx = this.ctx; const current = this.current; const font = current.font; const fontSize = current.fontSize; const fontDirection = current.fontDirection; const spacingDir = font.vertical ? 1 : -1; const charSpacing = current.charSpacing; const wordSpacing = current.wordSpacing; const textHScale = current.textHScale * fontDirection; const fontMatrix = current.fontMatrix || util.FONT_IDENTITY_MATRIX; const glyphsLength = glyphs.length; const isTextInvisible = current.textRenderingMode === util.TextRenderingMode.INVISIBLE; let i, glyph, width, spacingLength; if (isTextInvisible || fontSize === 0) { return; } this._cachedScaleForStroking[0] = -1; this._cachedGetSinglePixelWidth = null; ctx.save(); ctx.transform(...current.textMatrix); ctx.translate(current.x, current.y); ctx.scale(textHScale, fontDirection); for (i = 0; i < glyphsLength; ++i) { glyph = glyphs[i]; if (typeof glyph === "number") { spacingLength = spacingDir * glyph * fontSize / 1000; this.ctx.translate(spacingLength, 0); current.x += spacingLength * textHScale; continue; } const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing; const operatorList = font.charProcOperatorList[glyph.operatorListId]; if (!operatorList) { (0,util.warn)(`Type3 character "${glyph.operatorListId}" is not available.`); continue; } if (this.contentVisible) { this.processingType3 = glyph; this.save(); ctx.scale(fontSize, fontSize); ctx.transform(...fontMatrix); this.executeOperatorList(operatorList); this.restore(); } const transformed = util.Util.applyTransform([glyph.width, 0], fontMatrix); width = transformed[0] * fontSize + spacing; ctx.translate(width, 0); current.x += width * textHScale; } ctx.restore(); this.processingType3 = null; } setCharWidth(xWidth, yWidth) {} setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) { this.ctx.rect(llx, lly, urx - llx, ury - lly); this.ctx.clip(); this.endPath(); } getColorN_Pattern(IR) { let pattern; if (IR[0] === "TilingPattern") { const color = IR[1]; const baseTransform = this.baseTransform || (0,display_utils.getCurrentTransform)(this.ctx); const canvasGraphicsFactory = { createCanvasGraphics: ctx => { return new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, { optionalContentConfig: this.optionalContentConfig, markedContentStack: this.markedContentStack }); } }; pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform); } else { pattern = this._getPattern(IR[1], IR[2]); } return pattern; } setStrokeColorN() { this.current.strokeColor = this.getColorN_Pattern(arguments); } setFillColorN() { this.current.fillColor = this.getColorN_Pattern(arguments); this.current.patternFill = true; } setStrokeRGBColor(r, g, b) { const color = util.Util.makeHexColor(r, g, b); this.ctx.strokeStyle = color; this.current.strokeColor = color; } setFillRGBColor(r, g, b) { const color = util.Util.makeHexColor(r, g, b); this.ctx.fillStyle = color; this.current.fillColor = color; this.current.patternFill = false; } _getPattern(objId, matrix = null) { let pattern; if (this.cachedPatterns.has(objId)) { pattern = this.cachedPatterns.get(objId); } else { pattern = getShadingPattern(this.getObject(objId)); this.cachedPatterns.set(objId, pattern); } if (matrix) { pattern.matrix = matrix; } return pattern; } shadingFill(objId) { if (!this.contentVisible) { return; } const ctx = this.ctx; this.save(); const pattern = this._getPattern(objId); ctx.fillStyle = pattern.getPattern(ctx, this, (0,display_utils.getCurrentTransformInverse)(ctx), PathType.SHADING); const inv = (0,display_utils.getCurrentTransformInverse)(ctx); if (inv) { const { width, height } = ctx.canvas; const [x0, y0, x1, y1] = util.Util.getAxialAlignedBoundingBox([0, 0, width, height], inv); this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); } else { this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); } this.compose(this.current.getClippedPathBoundingBox()); this.restore(); } beginInlineImage() { (0,util.unreachable)("Should not call beginInlineImage"); } beginImageData() { (0,util.unreachable)("Should not call beginImageData"); } paintFormXObjectBegin(matrix, bbox) { if (!this.contentVisible) { return; } this.save(); this.baseTransformStack.push(this.baseTransform); if (Array.isArray(matrix) && matrix.length === 6) { this.transform(...matrix); } this.baseTransform = (0,display_utils.getCurrentTransform)(this.ctx); if (bbox) { const width = bbox[2] - bbox[0]; const height = bbox[3] - bbox[1]; this.ctx.rect(bbox[0], bbox[1], width, height); this.current.updateRectMinMax((0,display_utils.getCurrentTransform)(this.ctx), bbox); this.clip(); this.endPath(); } } paintFormXObjectEnd() { if (!this.contentVisible) { return; } this.restore(); this.baseTransform = this.baseTransformStack.pop(); } beginGroup(group) { if (!this.contentVisible) { return; } this.save(); if (this.inSMaskMode) { this.endSMaskMode(); this.current.activeSMask = null; } const currentCtx = this.ctx; if (!group.isolated) { (0,util.info)("TODO: Support non-isolated groups."); } if (group.knockout) { (0,util.warn)("Knockout groups not supported."); } const currentTransform = (0,display_utils.getCurrentTransform)(currentCtx); if (group.matrix) { currentCtx.transform(...group.matrix); } if (!group.bbox) { throw new Error("Bounding box is required."); } let bounds = util.Util.getAxialAlignedBoundingBox(group.bbox, (0,display_utils.getCurrentTransform)(currentCtx)); const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height]; bounds = util.Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; const offsetX = Math.floor(bounds[0]); const offsetY = Math.floor(bounds[1]); let drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); let drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); let scaleX = 1, scaleY = 1; if (drawnWidth > MAX_GROUP_SIZE) { scaleX = drawnWidth / MAX_GROUP_SIZE; drawnWidth = MAX_GROUP_SIZE; } if (drawnHeight > MAX_GROUP_SIZE) { scaleY = drawnHeight / MAX_GROUP_SIZE; drawnHeight = MAX_GROUP_SIZE; } this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]); let cacheId = "groupAt" + this.groupLevel; if (group.smask) { cacheId += "_smask_" + this.smaskCounter++ % 2; } const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight); const groupCtx = scratchCanvas.context; groupCtx.scale(1 / scaleX, 1 / scaleY); groupCtx.translate(-offsetX, -offsetY); groupCtx.transform(...currentTransform); if (group.smask) { this.smaskStack.push({ canvas: scratchCanvas.canvas, context: groupCtx, offsetX, offsetY, scaleX, scaleY, subtype: group.smask.subtype, backdrop: group.smask.backdrop, transferMap: group.smask.transferMap || null, startTransformInverse: null }); } else { currentCtx.setTransform(1, 0, 0, 1, 0, 0); currentCtx.translate(offsetX, offsetY); currentCtx.scale(scaleX, scaleY); currentCtx.save(); } copyCtxState(currentCtx, groupCtx); this.ctx = groupCtx; this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]); this.groupStack.push(currentCtx); this.groupLevel++; } endGroup(group) { if (!this.contentVisible) { return; } this.groupLevel--; const groupCtx = this.ctx; const ctx = this.groupStack.pop(); this.ctx = ctx; this.ctx.imageSmoothingEnabled = false; if (group.smask) { this.tempSMask = this.smaskStack.pop(); this.restore(); } else { this.ctx.restore(); const currentMtx = (0,display_utils.getCurrentTransform)(this.ctx); this.restore(); this.ctx.save(); this.ctx.setTransform(...currentMtx); const dirtyBox = util.Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx); this.ctx.drawImage(groupCtx.canvas, 0, 0); this.ctx.restore(); this.compose(dirtyBox); } } beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) { this.#restoreInitialState(); resetCtxToDefault(this.ctx); this.ctx.save(); this.save(); if (this.baseTransform) { this.ctx.setTransform(...this.baseTransform); } if (Array.isArray(rect) && rect.length === 4) { const width = rect[2] - rect[0]; const height = rect[3] - rect[1]; if (hasOwnCanvas && this.annotationCanvasMap) { transform = transform.slice(); transform[4] -= rect[0]; transform[5] -= rect[1]; rect = rect.slice(); rect[0] = rect[1] = 0; rect[2] = width; rect[3] = height; const [scaleX, scaleY] = util.Util.singularValueDecompose2dScale((0,display_utils.getCurrentTransform)(this.ctx)); const { viewportScale } = this; const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale); const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale); this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight); const { canvas, context } = this.annotationCanvas; this.annotationCanvasMap.set(id, canvas); this.annotationCanvas.savedCtx = this.ctx; this.ctx = context; this.ctx.save(); this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY); resetCtxToDefault(this.ctx); } else { resetCtxToDefault(this.ctx); this.ctx.rect(rect[0], rect[1], width, height); this.ctx.clip(); this.endPath(); } } this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height); this.transform(...transform); this.transform(...matrix); } endAnnotation() { if (this.annotationCanvas) { this.ctx.restore(); this.#drawFilter(); this.ctx = this.annotationCanvas.savedCtx; delete this.annotationCanvas.savedCtx; delete this.annotationCanvas; } } paintImageMaskXObject(img) { if (!this.contentVisible) { return; } const count = img.count; img = this.getObject(img.data, img); img.count = count; const ctx = this.ctx; const glyph = this.processingType3; if (glyph) { if (glyph.compiled === undefined) { glyph.compiled = compileType3Glyph(img); } if (glyph.compiled) { glyph.compiled(ctx); return; } } const mask = this._createMaskCanvas(img); const maskCanvas = mask.canvas; ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY); ctx.restore(); this.compose(); } paintImageMaskXObjectRepeat(img, scaleX, skewX = 0, skewY = 0, scaleY, positions) { if (!this.contentVisible) { return; } img = this.getObject(img.data, img); const ctx = this.ctx; ctx.save(); const currentTransform = (0,display_utils.getCurrentTransform)(ctx); ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0); const mask = this._createMaskCanvas(img); ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]); for (let i = 0, ii = positions.length; i < ii; i += 2) { const trans = util.Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]); const [x, y] = util.Util.applyTransform([0, 0], trans); ctx.drawImage(mask.canvas, x, y); } ctx.restore(); this.compose(); } paintImageMaskXObjectGroup(images) { if (!this.contentVisible) { return; } const ctx = this.ctx; const fillColor = this.current.fillColor; const isPatternFill = this.current.patternFill; for (const image of images) { const { data, width, height, transform } = image; const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height); const maskCtx = maskCanvas.context; maskCtx.save(); const img = this.getObject(data, image); putBinaryImageMask(maskCtx, img); maskCtx.globalCompositeOperation = "source-in"; maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, (0,display_utils.getCurrentTransformInverse)(ctx), PathType.FILL) : fillColor; maskCtx.fillRect(0, 0, width, height); maskCtx.restore(); ctx.save(); ctx.transform(...transform); ctx.scale(1, -1); drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1); ctx.restore(); } this.compose(); } paintImageXObject(objId) { if (!this.contentVisible) { return; } const imgData = this.getObject(objId); if (!imgData) { (0,util.warn)("Dependent image isn't ready yet"); return; } this.paintInlineImageXObject(imgData); } paintImageXObjectRepeat(objId, scaleX, scaleY, positions) { if (!this.contentVisible) { return; } const imgData = this.getObject(objId); if (!imgData) { (0,util.warn)("Dependent image isn't ready yet"); return; } const width = imgData.width; const height = imgData.height; const map = []; for (let i = 0, ii = positions.length; i < ii; i += 2) { map.push({ transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]], x: 0, y: 0, w: width, h: height }); } this.paintInlineImageXObjectGroup(imgData, map); } applyTransferMapsToCanvas(ctx) { if (this.current.transferMaps !== "none") { ctx.filter = this.current.transferMaps; ctx.drawImage(ctx.canvas, 0, 0); ctx.filter = "none"; } return ctx.canvas; } applyTransferMapsToBitmap(imgData) { if (this.current.transferMaps === "none") { return imgData.bitmap; } const { bitmap, width, height } = imgData; const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); const tmpCtx = tmpCanvas.context; tmpCtx.filter = this.current.transferMaps; tmpCtx.drawImage(bitmap, 0, 0); tmpCtx.filter = "none"; return tmpCanvas.canvas; } paintInlineImageXObject(imgData) { if (!this.contentVisible) { return; } const width = imgData.width; const height = imgData.height; const ctx = this.ctx; this.save(); if (!util.isNodeJS) { const { filter } = ctx; if (filter !== "none" && filter !== "") { ctx.filter = "none"; } } ctx.scale(1 / width, -1 / height); let imgToPaint; if (imgData.bitmap) { imgToPaint = this.applyTransferMapsToBitmap(imgData); } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) { imgToPaint = imgData; } else { const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height); const tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); } const scaled = this._scaleImage(imgToPaint, (0,display_utils.getCurrentTransformInverse)(ctx)); ctx.imageSmoothingEnabled = getImageSmoothingEnabled((0,display_utils.getCurrentTransform)(ctx), imgData.interpolate); drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height); this.compose(); this.restore(); } paintInlineImageXObjectGroup(imgData, map) { if (!this.contentVisible) { return; } const ctx = this.ctx; let imgToPaint; if (imgData.bitmap) { imgToPaint = imgData.bitmap; } else { const w = imgData.width; const h = imgData.height; const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h); const tmpCtx = tmpCanvas.context; putBinaryImageData(tmpCtx, imgData); imgToPaint = this.applyTransferMapsToCanvas(tmpCtx); } for (const entry of map) { ctx.save(); ctx.transform(...entry.transform); ctx.scale(1, -1); drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1); ctx.restore(); } this.compose(); } paintSolidColorImageMask() { if (!this.contentVisible) { return; } this.ctx.fillRect(0, 0, 1, 1); this.compose(); } markPoint(tag) {} markPointProps(tag, properties) {} beginMarkedContent(tag) { this.markedContentStack.push({ visible: true }); } beginMarkedContentProps(tag, properties) { if (tag === "OC") { this.markedContentStack.push({ visible: this.optionalContentConfig.isVisible(properties) }); } else { this.markedContentStack.push({ visible: true }); } this.contentVisible = this.isContentVisible(); } endMarkedContent() { this.markedContentStack.pop(); this.contentVisible = this.isContentVisible(); } beginCompat() {} endCompat() {} consumePath(clipBox) { const isEmpty = this.current.isEmptyClip(); if (this.pendingClip) { this.current.updateClipFromPath(); } if (!this.pendingClip) { this.compose(clipBox); } const ctx = this.ctx; if (this.pendingClip) { if (!isEmpty) { if (this.pendingClip === EO_CLIP) { ctx.clip("evenodd"); } else { ctx.clip(); } } this.pendingClip = null; } this.current.startNewPathAndClipBox(this.current.clipBox); ctx.beginPath(); } getSinglePixelWidth() { if (!this._cachedGetSinglePixelWidth) { const m = (0,display_utils.getCurrentTransform)(this.ctx); if (m[1] === 0 && m[2] === 0) { this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3])); } else { const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]); const normX = Math.hypot(m[0], m[2]); const normY = Math.hypot(m[1], m[3]); this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet; } } return this._cachedGetSinglePixelWidth; } getScaleForStroking() { if (this._cachedScaleForStroking[0] === -1) { const { lineWidth } = this.current; const { a, b, c, d } = this.ctx.getTransform(); let scaleX, scaleY; if (b === 0 && c === 0) { const normX = Math.abs(a); const normY = Math.abs(d); if (normX === normY) { if (lineWidth === 0) { scaleX = scaleY = 1 / normX; } else { const scaledLineWidth = normX * lineWidth; scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1; } } else if (lineWidth === 0) { scaleX = 1 / normX; scaleY = 1 / normY; } else { const scaledXLineWidth = normX * lineWidth; const scaledYLineWidth = normY * lineWidth; scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1; scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1; } } else { const absDet = Math.abs(a * d - b * c); const normX = Math.hypot(a, b); const normY = Math.hypot(c, d); if (lineWidth === 0) { scaleX = normY / absDet; scaleY = normX / absDet; } else { const baseArea = lineWidth * absDet; scaleX = normY > baseArea ? normY / baseArea : 1; scaleY = normX > baseArea ? normX / baseArea : 1; } } this._cachedScaleForStroking[0] = scaleX; this._cachedScaleForStroking[1] = scaleY; } return this._cachedScaleForStroking; } rescaleAndStroke(saveRestore) { const { ctx } = this; const { lineWidth } = this.current; const [scaleX, scaleY] = this.getScaleForStroking(); ctx.lineWidth = lineWidth || 1; if (scaleX === 1 && scaleY === 1) { ctx.stroke(); return; } const dashes = ctx.getLineDash(); if (saveRestore) { ctx.save(); } ctx.scale(scaleX, scaleY); if (dashes.length > 0) { const scale = Math.max(scaleX, scaleY); ctx.setLineDash(dashes.map(x => x / scale)); ctx.lineDashOffset /= scale; } ctx.stroke(); if (saveRestore) { ctx.restore(); } } isContentVisible() { for (let i = this.markedContentStack.length - 1; i >= 0; i--) { if (!this.markedContentStack[i].visible) { return false; } } return true; } } for (const op in util.OPS) { if (CanvasGraphics.prototype[op] !== undefined) { CanvasGraphics.prototype[util.OPS[op]] = CanvasGraphics.prototype[op]; } } /***/ }), /***/ 473: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ DOMCMapReaderFactory: () => (/* binding */ DOMCMapReaderFactory), /* harmony export */ DOMCanvasFactory: () => (/* binding */ DOMCanvasFactory), /* harmony export */ DOMFilterFactory: () => (/* binding */ DOMFilterFactory), /* harmony export */ DOMSVGFactory: () => (/* binding */ DOMSVGFactory), /* harmony export */ DOMStandardFontDataFactory: () => (/* binding */ DOMStandardFontDataFactory), /* harmony export */ PDFDateString: () => (/* binding */ PDFDateString), /* harmony export */ PageViewport: () => (/* binding */ PageViewport), /* harmony export */ PixelsPerInch: () => (/* binding */ PixelsPerInch), /* harmony export */ RenderingCancelledException: () => (/* binding */ RenderingCancelledException), /* harmony export */ StatTimer: () => (/* binding */ StatTimer), /* harmony export */ fetchData: () => (/* binding */ fetchData), /* harmony export */ getColorValues: () => (/* binding */ getColorValues), /* harmony export */ getCurrentTransform: () => (/* binding */ getCurrentTransform), /* harmony export */ getCurrentTransformInverse: () => (/* binding */ getCurrentTransformInverse), /* harmony export */ getFilenameFromUrl: () => (/* binding */ getFilenameFromUrl), /* harmony export */ getPdfFilenameFromUrl: () => (/* binding */ getPdfFilenameFromUrl), /* harmony export */ getRGB: () => (/* binding */ getRGB), /* harmony export */ getXfaPageViewport: () => (/* binding */ getXfaPageViewport), /* harmony export */ isDataScheme: () => (/* binding */ isDataScheme), /* harmony export */ isPdfFile: () => (/* binding */ isPdfFile), /* harmony export */ isValidFetchUrl: () => (/* binding */ isValidFetchUrl), /* harmony export */ noContextMenu: () => (/* binding */ noContextMenu), /* harmony export */ setLayerDimensions: () => (/* binding */ setLayerDimensions) /* harmony export */ }); /* unused harmony export deprecated */ /* harmony import */ var _base_factory_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(822); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(266); const SVG_NS = "http://www.w3.org/2000/svg"; class PixelsPerInch { static CSS = 96.0; static PDF = 72.0; static PDF_TO_CSS_UNITS = this.CSS / this.PDF; } class DOMFilterFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseFilterFactory { #_cache; #_defs; #docId; #document; #hcmFilter; #hcmKey; #hcmUrl; #hcmHighlightFilter; #hcmHighlightKey; #hcmHighlightUrl; #id = 0; constructor({ docId, ownerDocument = globalThis.document } = {}) { super(); this.#docId = docId; this.#document = ownerDocument; } get #cache() { return this.#_cache ||= new Map(); } get #defs() { if (!this.#_defs) { const div = this.#document.createElement("div"); const { style } = div; style.visibility = "hidden"; style.contain = "strict"; style.width = style.height = 0; style.position = "absolute"; style.top = style.left = 0; style.zIndex = -1; const svg = this.#document.createElementNS(SVG_NS, "svg"); svg.setAttribute("width", 0); svg.setAttribute("height", 0); this.#_defs = this.#document.createElementNS(SVG_NS, "defs"); div.append(svg); svg.append(this.#_defs); this.#document.body.append(div); } return this.#_defs; } addFilter(maps) { if (!maps) { return "none"; } let value = this.#cache.get(maps); if (value) { return value; } let tableR, tableG, tableB, key; if (maps.length === 1) { const mapR = maps[0]; const buffer = new Array(256); for (let i = 0; i < 256; i++) { buffer[i] = mapR[i] / 255; } key = tableR = tableG = tableB = buffer.join(","); } else { const [mapR, mapG, mapB] = maps; const bufferR = new Array(256); const bufferG = new Array(256); const bufferB = new Array(256); for (let i = 0; i < 256; i++) { bufferR[i] = mapR[i] / 255; bufferG[i] = mapG[i] / 255; bufferB[i] = mapB[i] / 255; } tableR = bufferR.join(","); tableG = bufferG.join(","); tableB = bufferB.join(","); key = `${tableR}${tableG}${tableB}`; } value = this.#cache.get(key); if (value) { this.#cache.set(maps, value); return value; } const id = `g_${this.#docId}_transfer_map_${this.#id++}`; const url = `url(#${id})`; this.#cache.set(maps, url); this.#cache.set(key, url); const filter = this.#createFilter(id); this.#addTransferMapConversion(tableR, tableG, tableB, filter); return url; } addHCMFilter(fgColor, bgColor) { const key = `${fgColor}-${bgColor}`; if (this.#hcmKey === key) { return this.#hcmUrl; } this.#hcmKey = key; this.#hcmUrl = "none"; this.#hcmFilter?.remove(); if (!fgColor || !bgColor) { return this.#hcmUrl; } const fgRGB = this.#getRGB(fgColor); fgColor = _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.makeHexColor(...fgRGB); const bgRGB = this.#getRGB(bgColor); bgColor = _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.makeHexColor(...bgRGB); this.#defs.style.color = ""; if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) { return this.#hcmUrl; } const map = new Array(256); for (let i = 0; i <= 255; i++) { const x = i / 255; map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; } const table = map.join(","); const id = `g_${this.#docId}_hcm_filter`; const filter = this.#hcmHighlightFilter = this.#createFilter(id); this.#addTransferMapConversion(table, table, table, filter); this.#addGrayConversion(filter); const getSteps = (c, n) => { const start = fgRGB[c] / 255; const end = bgRGB[c] / 255; const arr = new Array(n + 1); for (let i = 0; i <= n; i++) { arr[i] = start + i / n * (end - start); } return arr.join(","); }; this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter); this.#hcmUrl = `url(#${id})`; return this.#hcmUrl; } addHighlightHCMFilter(fgColor, bgColor, newFgColor, newBgColor) { const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`; if (this.#hcmHighlightKey === key) { return this.#hcmHighlightUrl; } this.#hcmHighlightKey = key; this.#hcmHighlightUrl = "none"; this.#hcmHighlightFilter?.remove(); if (!fgColor || !bgColor) { return this.#hcmHighlightUrl; } const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this)); let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]); let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]); let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this)); if (bgGray < fgGray) { [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB]; } this.#defs.style.color = ""; const getSteps = (fg, bg, n) => { const arr = new Array(256); const step = (bgGray - fgGray) / n; const newStart = fg / 255; const newStep = (bg - fg) / (255 * n); let prev = 0; for (let i = 0; i <= n; i++) { const k = Math.round(fgGray + i * step); const value = newStart + i * newStep; for (let j = prev; j <= k; j++) { arr[j] = value; } prev = k + 1; } for (let i = prev; i < 256; i++) { arr[i] = arr[prev - 1]; } return arr.join(","); }; const id = `g_${this.#docId}_hcm_highlight_filter`; const filter = this.#hcmHighlightFilter = this.#createFilter(id); this.#addGrayConversion(filter); this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter); this.#hcmHighlightUrl = `url(#${id})`; return this.#hcmHighlightUrl; } destroy(keepHCM = false) { if (keepHCM && (this.#hcmUrl || this.#hcmHighlightUrl)) { return; } if (this.#_defs) { this.#_defs.parentNode.parentNode.remove(); this.#_defs = null; } if (this.#_cache) { this.#_cache.clear(); this.#_cache = null; } this.#id = 0; } #addGrayConversion(filter) { const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix"); feColorMatrix.setAttribute("type", "matrix"); feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"); filter.append(feColorMatrix); } #createFilter(id) { const filter = this.#document.createElementNS(SVG_NS, "filter"); filter.setAttribute("color-interpolation-filters", "sRGB"); filter.setAttribute("id", id); this.#defs.append(filter); return filter; } #appendFeFunc(feComponentTransfer, func, table) { const feFunc = this.#document.createElementNS(SVG_NS, func); feFunc.setAttribute("type", "discrete"); feFunc.setAttribute("tableValues", table); feComponentTransfer.append(feFunc); } #addTransferMapConversion(rTable, gTable, bTable, filter) { const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer"); filter.append(feComponentTransfer); this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable); this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable); this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable); } #getRGB(color) { this.#defs.style.color = color; return getRGB(getComputedStyle(this.#defs).getPropertyValue("color")); } } class DOMCanvasFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseCanvasFactory { constructor({ ownerDocument = globalThis.document } = {}) { super(); this._document = ownerDocument; } _createCanvas(width, height) { const canvas = this._document.createElement("canvas"); canvas.width = width; canvas.height = height; return canvas; } } async function fetchData(url, type = "text") { if (isValidFetchUrl(url, document.baseURI)) { const response = await fetch(url); if (!response.ok) { throw new Error(response.statusText); } switch (type) { case "arraybuffer": return response.arrayBuffer(); case "blob": return response.blob(); case "json": return response.json(); } return response.text(); } return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); request.open("GET", url, true); request.responseType = type; request.onreadystatechange = () => { if (request.readyState !== XMLHttpRequest.DONE) { return; } if (request.status === 200 || request.status === 0) { let data; switch (type) { case "arraybuffer": case "blob": case "json": data = request.response; break; default: data = request.responseText; break; } if (data) { resolve(data); return; } } reject(new Error(request.statusText)); }; request.send(null); }); } class DOMCMapReaderFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseCMapReaderFactory { _fetchData(url, compressionType) { return fetchData(url, this.isCompressed ? "arraybuffer" : "text").then(data => { return { cMapData: data instanceof ArrayBuffer ? new Uint8Array(data) : (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.stringToBytes)(data), compressionType }; }); } } class DOMStandardFontDataFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseStandardFontDataFactory { _fetchData(url) { return fetchData(url, "arraybuffer").then(data => { return new Uint8Array(data); }); } } class DOMSVGFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseSVGFactory { _createSVG(type) { return document.createElementNS(SVG_NS, type); } } class PageViewport { constructor({ viewBox, scale, rotation, offsetX = 0, offsetY = 0, dontFlip = false }) { this.viewBox = viewBox; this.scale = scale; this.rotation = rotation; this.offsetX = offsetX; this.offsetY = offsetY; const centerX = (viewBox[2] + viewBox[0]) / 2; const centerY = (viewBox[3] + viewBox[1]) / 2; let rotateA, rotateB, rotateC, rotateD; rotation %= 360; if (rotation < 0) { rotation += 360; } switch (rotation) { case 180: rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; break; case 90: rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; break; case 270: rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; break; case 0: rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; break; default: throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees."); } if (dontFlip) { rotateC = -rotateC; rotateD = -rotateD; } let offsetCanvasX, offsetCanvasY; let width, height; if (rotateA === 0) { offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; width = (viewBox[3] - viewBox[1]) * scale; height = (viewBox[2] - viewBox[0]) * scale; } else { offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; width = (viewBox[2] - viewBox[0]) * scale; height = (viewBox[3] - viewBox[1]) * scale; } this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY]; this.width = width; this.height = height; } get rawDims() { const { viewBox } = this; return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.shadow)(this, "rawDims", { pageWidth: viewBox[2] - viewBox[0], pageHeight: viewBox[3] - viewBox[1], pageX: viewBox[0], pageY: viewBox[1] }); } clone({ scale = this.scale, rotation = this.rotation, offsetX = this.offsetX, offsetY = this.offsetY, dontFlip = false } = {}) { return new PageViewport({ viewBox: this.viewBox.slice(), scale, rotation, offsetX, offsetY, dontFlip }); } convertToViewportPoint(x, y) { return _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.applyTransform([x, y], this.transform); } convertToViewportRectangle(rect) { const topLeft = _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.applyTransform([rect[0], rect[1]], this.transform); const bottomRight = _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.applyTransform([rect[2], rect[3]], this.transform); return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]]; } convertToPdfPoint(x, y) { return _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.Util.applyInverseTransform([x, y], this.transform); } } class RenderingCancelledException extends _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.BaseException { constructor(msg, extraDelay = 0) { super(msg, "RenderingCancelledException"); this.extraDelay = extraDelay; } } function isDataScheme(url) { const ii = url.length; let i = 0; while (i < ii && url[i].trim() === "") { i++; } return url.substring(i, i + 5).toLowerCase() === "data:"; } function isPdfFile(filename) { return typeof filename === "string" && /\.pdf$/i.test(filename); } function getFilenameFromUrl(url, onlyStripPath = false) { if (!onlyStripPath) { [url] = url.split(/[#?]/, 1); } return url.substring(url.lastIndexOf("/") + 1); } function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") { if (typeof url !== "string") { return defaultFilename; } if (isDataScheme(url)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.warn)('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.'); return defaultFilename; } const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i; const splitURI = reURI.exec(url); let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]); if (suggestedFilename) { suggestedFilename = suggestedFilename[0]; if (suggestedFilename.includes("%")) { try { suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0]; } catch {} } } return suggestedFilename || defaultFilename; } class StatTimer { started = Object.create(null); times = []; time(name) { if (name in this.started) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.warn)(`Timer is already running for ${name}`); } this.started[name] = Date.now(); } timeEnd(name) { if (!(name in this.started)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.warn)(`Timer has not been started for ${name}`); } this.times.push({ name, start: this.started[name], end: Date.now() }); delete this.started[name]; } toString() { const outBuf = []; let longest = 0; for (const { name } of this.times) { longest = Math.max(name.length, longest); } for (const { name, start, end } of this.times) { outBuf.push(`${name.padEnd(longest)} ${end - start}ms\n`); } return outBuf.join(""); } } function isValidFetchUrl(url, baseUrl) { try { const { protocol } = baseUrl ? new URL(url, baseUrl) : new URL(url); return protocol === "http:" || protocol === "https:"; } catch { return false; } } function noContextMenu(e) { e.preventDefault(); } function deprecated(details) { console.log("Deprecated API usage: " + details); } let pdfDateStringRegex; class PDFDateString { static toDateObject(input) { if (!input || typeof input !== "string") { return null; } pdfDateStringRegex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?"); const matches = pdfDateStringRegex.exec(input); if (!matches) { return null; } const year = parseInt(matches[1], 10); let month = parseInt(matches[2], 10); month = month >= 1 && month <= 12 ? month - 1 : 0; let day = parseInt(matches[3], 10); day = day >= 1 && day <= 31 ? day : 1; let hour = parseInt(matches[4], 10); hour = hour >= 0 && hour <= 23 ? hour : 0; let minute = parseInt(matches[5], 10); minute = minute >= 0 && minute <= 59 ? minute : 0; let second = parseInt(matches[6], 10); second = second >= 0 && second <= 59 ? second : 0; const universalTimeRelation = matches[7] || "Z"; let offsetHour = parseInt(matches[8], 10); offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0; let offsetMinute = parseInt(matches[9], 10) || 0; offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0; if (universalTimeRelation === "-") { hour += offsetHour; minute += offsetMinute; } else if (universalTimeRelation === "+") { hour -= offsetHour; minute -= offsetMinute; } return new Date(Date.UTC(year, month, day, hour, minute, second)); } } function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) { const { width, height } = xfaPage.attributes.style; const viewBox = [0, 0, parseInt(width), parseInt(height)]; return new PageViewport({ viewBox, scale, rotation }); } function getRGB(color) { if (color.startsWith("#")) { const colorRGB = parseInt(color.slice(1), 16); return [(colorRGB & 0xff0000) >> 16, (colorRGB & 0x00ff00) >> 8, colorRGB & 0x0000ff]; } if (color.startsWith("rgb(")) { return color.slice(4, -1).split(",").map(x => parseInt(x)); } if (color.startsWith("rgba(")) { return color.slice(5, -1).split(",").map(x => parseInt(x)).slice(0, 3); } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.warn)(`Not a valid color format: "${color}"`); return [0, 0, 0]; } function getColorValues(colors) { const span = document.createElement("span"); span.style.visibility = "hidden"; document.body.append(span); for (const name of colors.keys()) { span.style.color = name; const computedColor = window.getComputedStyle(span).color; colors.set(name, getRGB(computedColor)); } span.remove(); } function getCurrentTransform(ctx) { const { a, b, c, d, e, f } = ctx.getTransform(); return [a, b, c, d, e, f]; } function getCurrentTransformInverse(ctx) { const { a, b, c, d, e, f } = ctx.getTransform().invertSelf(); return [a, b, c, d, e, f]; } function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) { if (viewport instanceof PageViewport) { const { pageWidth, pageHeight } = viewport.rawDims; const { style } = div; const useRound = _shared_util_js__WEBPACK_IMPORTED_MODULE_1__.FeatureTest.isCSSRoundSupported; const w = `var(--scale-factor) * ${pageWidth}px`, h = `var(--scale-factor) * ${pageHeight}px`; const widthStr = useRound ? `round(${w}, 1px)` : `calc(${w})`, heightStr = useRound ? `round(${h}, 1px)` : `calc(${h})`; if (!mustFlip || viewport.rotation % 180 === 0) { style.width = widthStr; style.height = heightStr; } else { style.width = heightStr; style.height = widthStr; } } if (mustRotate) { div.setAttribute("data-main-rotation", viewport.rotation); } } /***/ }), /***/ 423: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ DrawLayer: () => (/* binding */ DrawLayer) /* harmony export */ }); /* harmony import */ var _display_utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(473); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(266); class DrawLayer { #parent = null; #id = 0; #mapping = new Map(); constructor({ pageIndex }) { this.pageIndex = pageIndex; } setParent(parent) { if (!this.#parent) { this.#parent = parent; return; } if (this.#parent !== parent) { if (this.#mapping.size > 0) { for (const root of this.#mapping.values()) { root.remove(); parent.append(root); } } this.#parent = parent; } } static get _svgFactory() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.shadow)(this, "_svgFactory", new _display_utils_js__WEBPACK_IMPORTED_MODULE_0__.DOMSVGFactory()); } static #setBox(element, { x, y, width, height }) { const { style } = element; style.top = `${100 * y}%`; style.left = `${100 * x}%`; style.width = `${100 * width}%`; style.height = `${100 * height}%`; } #createSVG(box) { const svg = DrawLayer._svgFactory.create(1, 1, true); this.#parent.append(svg); DrawLayer.#setBox(svg, box); return svg; } highlight({ outlines, box }, color, opacity) { const id = this.#id++; const root = this.#createSVG(box); root.classList.add("highlight"); const defs = DrawLayer._svgFactory.createElement("defs"); root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); const pathId = `path_p${this.pageIndex}_${id}`; path.setAttribute("id", pathId); path.setAttribute("d", DrawLayer.#extractPathFromHighlightOutlines(outlines)); const clipPath = DrawLayer._svgFactory.createElement("clipPath"); defs.append(clipPath); const clipPathId = `clip_${pathId}`; clipPath.setAttribute("id", clipPathId); clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); const clipPathUse = DrawLayer._svgFactory.createElement("use"); clipPath.append(clipPathUse); clipPathUse.setAttribute("href", `#${pathId}`); clipPathUse.classList.add("clip"); const use = DrawLayer._svgFactory.createElement("use"); root.append(use); root.setAttribute("fill", color); root.setAttribute("fill-opacity", opacity); use.setAttribute("href", `#${pathId}`); this.#mapping.set(id, root); return { id, clipPathId: `url(#${clipPathId})` }; } highlightOutline({ outlines, box }) { const id = this.#id++; const root = this.#createSVG(box); root.classList.add("highlightOutline"); const defs = DrawLayer._svgFactory.createElement("defs"); root.append(defs); const path = DrawLayer._svgFactory.createElement("path"); defs.append(path); const pathId = `path_p${this.pageIndex}_${id}`; path.setAttribute("id", pathId); path.setAttribute("d", DrawLayer.#extractPathFromHighlightOutlines(outlines)); path.setAttribute("vector-effect", "non-scaling-stroke"); const use1 = DrawLayer._svgFactory.createElement("use"); root.append(use1); use1.setAttribute("href", `#${pathId}`); const use2 = use1.cloneNode(); root.append(use2); use1.classList.add("mainOutline"); use2.classList.add("secondaryOutline"); this.#mapping.set(id, root); return id; } static #extractPathFromHighlightOutlines(polygons) { const buffer = []; for (const polygon of polygons) { let [prevX, prevY] = polygon; buffer.push(`M${prevX} ${prevY}`); for (let i = 2; i < polygon.length; i += 2) { const x = polygon[i]; const y = polygon[i + 1]; if (x === prevX) { buffer.push(`V${y}`); prevY = y; } else if (y === prevY) { buffer.push(`H${x}`); prevX = x; } } buffer.push("Z"); } return buffer.join(" "); } updateBox(id, box) { DrawLayer.#setBox(this.#mapping.get(id), box); } rotate(id, angle) { this.#mapping.get(id).setAttribute("data-main-rotation", angle); } changeColor(id, color) { this.#mapping.get(id).setAttribute("fill", color); } changeOpacity(id, opacity) { this.#mapping.get(id).setAttribute("fill-opacity", opacity); } addClass(id, className) { this.#mapping.get(id).classList.add(className); } removeClass(id, className) { this.#mapping.get(id).classList.remove(className); } remove(id) { this.#mapping.get(id).remove(); this.#mapping.delete(id); } destroy() { this.#parent = null; for (const root of this.#mapping.values()) { root.remove(); } this.#mapping.clear(); } } /***/ }), /***/ 331: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { // EXPORTS __webpack_require__.d(__webpack_exports__, { AnnotationEditorLayer: () => (/* binding */ AnnotationEditorLayer) }); // EXTERNAL MODULE: ./src/shared/util.js var util = __webpack_require__(266); // EXTERNAL MODULE: ./src/display/editor/editor.js + 1 modules var editor_editor = __webpack_require__(796); // EXTERNAL MODULE: ./src/display/editor/tools.js var tools = __webpack_require__(812); // EXTERNAL MODULE: ./src/display/annotation_layer.js + 1 modules var annotation_layer = __webpack_require__(640); ;// CONCATENATED MODULE: ./src/display/editor/freetext.js class FreeTextEditor extends editor_editor.AnnotationEditor { #boundEditorDivBlur = this.editorDivBlur.bind(this); #boundEditorDivFocus = this.editorDivFocus.bind(this); #boundEditorDivInput = this.editorDivInput.bind(this); #boundEditorDivKeydown = this.editorDivKeydown.bind(this); #color; #content = ""; #editorDivId = `${this.id}-editor`; #fontSize; #initialData = null; static _freeTextDefaultContent = ""; static _internalPadding = 0; static _defaultColor = null; static _defaultFontSize = 10; static get _keyboardManager() { const proto = FreeTextEditor.prototype; const arrowChecker = self => self.isEmpty(); const small = tools.AnnotationEditorUIManager.TRANSLATE_SMALL; const big = tools.AnnotationEditorUIManager.TRANSLATE_BIG; return (0,util.shadow)(this, "_keyboardManager", new tools.KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, { bubbles: true }], [["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"], proto.commitOrRemove], [["ArrowLeft", "mac+ArrowLeft"], proto._translateEmpty, { args: [-small, 0], checker: arrowChecker }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, { args: [-big, 0], checker: arrowChecker }], [["ArrowRight", "mac+ArrowRight"], proto._translateEmpty, { args: [small, 0], checker: arrowChecker }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, { args: [big, 0], checker: arrowChecker }], [["ArrowUp", "mac+ArrowUp"], proto._translateEmpty, { args: [0, -small], checker: arrowChecker }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, { args: [0, -big], checker: arrowChecker }], [["ArrowDown", "mac+ArrowDown"], proto._translateEmpty, { args: [0, small], checker: arrowChecker }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, { args: [0, big], checker: arrowChecker }]])); } static _type = "freetext"; static _editorType = util.AnnotationEditorType.FREETEXT; constructor(params) { super({ ...params, name: "freeTextEditor" }); this.#color = params.color || FreeTextEditor._defaultColor || editor_editor.AnnotationEditor._defaultLineColor; this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize; } static initialize(l10n) { editor_editor.AnnotationEditor.initialize(l10n, { strings: ["pdfjs-free-text-default-content"] }); const style = getComputedStyle(document.documentElement); this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding")); } static updateDefaultParams(type, value) { switch (type) { case util.AnnotationEditorParamsType.FREETEXT_SIZE: FreeTextEditor._defaultFontSize = value; break; case util.AnnotationEditorParamsType.FREETEXT_COLOR: FreeTextEditor._defaultColor = value; break; } } updateParams(type, value) { switch (type) { case util.AnnotationEditorParamsType.FREETEXT_SIZE: this.#updateFontSize(value); break; case util.AnnotationEditorParamsType.FREETEXT_COLOR: this.#updateColor(value); break; } } static get defaultPropertiesToUpdate() { return [[util.AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [util.AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || editor_editor.AnnotationEditor._defaultLineColor]]; } get propertiesToUpdate() { return [[util.AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [util.AnnotationEditorParamsType.FREETEXT_COLOR, this.#color]]; } #updateFontSize(fontSize) { const setFontsize = size => { this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`; this.translate(0, -(size - this.#fontSize) * this.parentScale); this.#fontSize = size; this.#setEditorDimensions(); }; const savedFontsize = this.#fontSize; this.addCommands({ cmd: () => { setFontsize(fontSize); }, undo: () => { setFontsize(savedFontsize); }, mustExec: true, type: util.AnnotationEditorParamsType.FREETEXT_SIZE, overwriteIfSameType: true, keepUndo: true }); } #updateColor(color) { const savedColor = this.#color; this.addCommands({ cmd: () => { this.#color = this.editorDiv.style.color = color; }, undo: () => { this.#color = this.editorDiv.style.color = savedColor; }, mustExec: true, type: util.AnnotationEditorParamsType.FREETEXT_COLOR, overwriteIfSameType: true, keepUndo: true }); } _translateEmpty(x, y) { this._uiManager.translateSelectedEditors(x, y, true); } getInitialTranslation() { const scale = this.parentScale; return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale]; } rebuild() { if (!this.parent) { return; } super.rebuild(); if (this.div === null) { return; } if (!this.isAttachedToDOM) { this.parent.add(this); } } enableEditMode() { if (this.isInEditMode()) { return; } this.parent.setEditingState(false); this.parent.updateToolbar(util.AnnotationEditorType.FREETEXT); super.enableEditMode(); this.overlayDiv.classList.remove("enabled"); this.editorDiv.contentEditable = true; this._isDraggable = false; this.div.removeAttribute("aria-activedescendant"); this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown); this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.addEventListener("input", this.#boundEditorDivInput); } disableEditMode() { if (!this.isInEditMode()) { return; } this.parent.setEditingState(true); super.disableEditMode(); this.overlayDiv.classList.add("enabled"); this.editorDiv.contentEditable = false; this.div.setAttribute("aria-activedescendant", this.#editorDivId); this._isDraggable = true; this.editorDiv.removeEventListener("keydown", this.#boundEditorDivKeydown); this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.removeEventListener("input", this.#boundEditorDivInput); this.div.focus({ preventScroll: true }); this.isEditing = false; this.parent.div.classList.add("freetextEditing"); } focusin(event) { if (!this._focusEventsAllowed) { return; } super.focusin(event); if (event.target !== this.editorDiv) { this.editorDiv.focus(); } } onceAdded() { if (this.width) { this.#cheatInitialRect(); return; } this.enableEditMode(); this.editorDiv.focus(); if (this._initialOptions?.isCentered) { this.center(); } this._initialOptions = null; } isEmpty() { return !this.editorDiv || this.editorDiv.innerText.trim() === ""; } remove() { this.isEditing = false; if (this.parent) { this.parent.setEditingState(true); this.parent.div.classList.add("freetextEditing"); } super.remove(); } #extractText() { const divs = this.editorDiv.getElementsByTagName("div"); if (divs.length === 0) { return this.editorDiv.innerText; } const buffer = []; for (const div of divs) { buffer.push(div.innerText.replace(/\r\n?|\n/, "")); } return buffer.join("\n"); } #setEditorDimensions() { const [parentWidth, parentHeight] = this.parentDimensions; let rect; if (this.isAttachedToDOM) { rect = this.div.getBoundingClientRect(); } else { const { currentLayer, div } = this; const savedDisplay = div.style.display; div.style.display = "hidden"; currentLayer.div.append(this.div); rect = div.getBoundingClientRect(); div.remove(); div.style.display = savedDisplay; } if (this.rotation % 180 === this.parentRotation % 180) { this.width = rect.width / parentWidth; this.height = rect.height / parentHeight; } else { this.width = rect.height / parentWidth; this.height = rect.width / parentHeight; } this.fixAndSetPosition(); } commit() { if (!this.isInEditMode()) { return; } super.commit(); this.disableEditMode(); const savedText = this.#content; const newText = this.#content = this.#extractText().trimEnd(); if (savedText === newText) { return; } const setText = text => { this.#content = text; if (!text) { this.remove(); return; } this.#setContent(); this._uiManager.rebuild(this); this.#setEditorDimensions(); }; this.addCommands({ cmd: () => { setText(newText); }, undo: () => { setText(savedText); }, mustExec: false }); this.#setEditorDimensions(); } shouldGetKeyboardEvents() { return this.isInEditMode(); } enterInEditMode() { this.enableEditMode(); this.editorDiv.focus(); } dblclick(event) { this.enterInEditMode(); } keydown(event) { if (event.target === this.div && event.key === "Enter") { this.enterInEditMode(); event.preventDefault(); } } editorDivKeydown(event) { FreeTextEditor._keyboardManager.exec(this, event); } editorDivFocus(event) { this.isEditing = true; } editorDivBlur(event) { this.isEditing = false; } editorDivInput(event) { this.parent.div.classList.toggle("freetextEditing", this.isEmpty()); } disableEditing() { this.editorDiv.setAttribute("role", "comment"); this.editorDiv.removeAttribute("aria-multiline"); } enableEditing() { this.editorDiv.setAttribute("role", "textbox"); this.editorDiv.setAttribute("aria-multiline", true); } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.editorDiv = document.createElement("div"); this.editorDiv.className = "internal"; this.editorDiv.setAttribute("id", this.#editorDivId); this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text"); this.enableEditing(); editor_editor.AnnotationEditor._l10nPromise.get("pdfjs-free-text-default-content").then(msg => this.editorDiv?.setAttribute("default-content", msg)); this.editorDiv.contentEditable = true; const { style } = this.editorDiv; style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`; style.color = this.#color; this.div.append(this.editorDiv); this.overlayDiv = document.createElement("div"); this.overlayDiv.classList.add("overlay", "enabled"); this.div.append(this.overlayDiv); (0,tools.bindEvents)(this, this.div, ["dblclick", "keydown"]); if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; if (this.annotationElementId) { const { position } = this.#initialData; let [tx, ty] = this.getInitialTranslation(); [tx, ty] = this.pageTranslationToScreen(tx, ty); const [pageWidth, pageHeight] = this.pageDimensions; const [pageX, pageY] = this.pageTranslation; let posX, posY; switch (this.rotation) { case 0: posX = baseX + (position[0] - pageX) / pageWidth; posY = baseY + this.height - (position[1] - pageY) / pageHeight; break; case 90: posX = baseX + (position[0] - pageX) / pageWidth; posY = baseY - (position[1] - pageY) / pageHeight; [tx, ty] = [ty, -tx]; break; case 180: posX = baseX - this.width + (position[0] - pageX) / pageWidth; posY = baseY - (position[1] - pageY) / pageHeight; [tx, ty] = [-tx, -ty]; break; case 270: posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth; posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight; [tx, ty] = [-ty, tx]; break; } this.setAt(posX * parentWidth, posY * parentHeight, tx, ty); } else { this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); } this.#setContent(); this._isDraggable = true; this.editorDiv.contentEditable = false; } else { this._isDraggable = false; this.editorDiv.contentEditable = true; } return this.div; } #setContent() { this.editorDiv.replaceChildren(); if (!this.#content) { return; } for (const line of this.#content.split("\n")) { const div = document.createElement("div"); div.append(line ? document.createTextNode(line) : document.createElement("br")); this.editorDiv.append(div); } } get contentDiv() { return this.editorDiv; } static deserialize(data, parent, uiManager) { let initialData = null; if (data instanceof annotation_layer.FreeTextAnnotationElement) { const { data: { defaultAppearanceData: { fontSize, fontColor }, rect, rotation, id }, textContent, textPosition, parent: { page: { pageNumber } } } = data; if (!textContent || textContent.length === 0) { return null; } initialData = data = { annotationType: util.AnnotationEditorType.FREETEXT, color: Array.from(fontColor), fontSize, value: textContent.join("\n"), position: textPosition, pageIndex: pageNumber - 1, rect, rotation, id, deleted: false }; } const editor = super.deserialize(data, parent, uiManager); editor.#fontSize = data.fontSize; editor.#color = util.Util.makeHexColor(...data.color); editor.#content = data.value; editor.annotationElementId = data.id || null; editor.#initialData = initialData; return editor; } serialize(isForCopying = false) { if (this.isEmpty()) { return null; } if (this.deleted) { return { pageIndex: this.pageIndex, id: this.annotationElementId, deleted: true }; } const padding = FreeTextEditor._internalPadding * this.parentScale; const rect = this.getRect(padding, padding); const color = editor_editor.AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.#color); const serialized = { annotationType: util.AnnotationEditorType.FREETEXT, color, fontSize: this.#fontSize, value: this.#content, pageIndex: this.pageIndex, rect, rotation: this.rotation, structTreeParentId: this._structTreeParentId }; if (isForCopying) { return serialized; } if (this.annotationElementId && !this.#hasElementChanged(serialized)) { return null; } serialized.id = this.annotationElementId; return serialized; } #hasElementChanged(serialized) { const { value, fontSize, color, rect, pageIndex } = this.#initialData; return serialized.value !== value || serialized.fontSize !== fontSize || serialized.rect.some((x, i) => Math.abs(x - rect[i]) >= 1) || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex; } #cheatInitialRect(delayed = false) { if (!this.annotationElementId) { return; } this.#setEditorDimensions(); if (!delayed && (this.width === 0 || this.height === 0)) { setTimeout(() => this.#cheatInitialRect(true), 0); return; } const padding = FreeTextEditor._internalPadding * this.parentScale; this.#initialData.rect = this.getRect(padding, padding); } } // EXTERNAL MODULE: ./src/display/display_utils.js var display_utils = __webpack_require__(473); ;// CONCATENATED MODULE: ./src/display/editor/ink.js class InkEditor extends editor_editor.AnnotationEditor { #baseHeight = 0; #baseWidth = 0; #boundCanvasPointermove = this.canvasPointermove.bind(this); #boundCanvasPointerleave = this.canvasPointerleave.bind(this); #boundCanvasPointerup = this.canvasPointerup.bind(this); #boundCanvasPointerdown = this.canvasPointerdown.bind(this); #canvasContextMenuTimeoutId = null; #currentPath2D = new Path2D(); #disableEditing = false; #hasSomethingToDraw = false; #isCanvasInitialized = false; #observer = null; #realWidth = 0; #realHeight = 0; #requestFrameCallback = null; static _defaultColor = null; static _defaultOpacity = 1; static _defaultThickness = 1; static _type = "ink"; static _editorType = util.AnnotationEditorType.INK; constructor(params) { super({ ...params, name: "inkEditor" }); this.color = params.color || null; this.thickness = params.thickness || null; this.opacity = params.opacity || null; this.paths = []; this.bezierPath2D = []; this.allRawPaths = []; this.currentPath = []; this.scaleFactor = 1; this.translationX = this.translationY = 0; this.x = 0; this.y = 0; this._willKeepAspectRatio = true; } static initialize(l10n) { editor_editor.AnnotationEditor.initialize(l10n); } static updateDefaultParams(type, value) { switch (type) { case util.AnnotationEditorParamsType.INK_THICKNESS: InkEditor._defaultThickness = value; break; case util.AnnotationEditorParamsType.INK_COLOR: InkEditor._defaultColor = value; break; case util.AnnotationEditorParamsType.INK_OPACITY: InkEditor._defaultOpacity = value / 100; break; } } updateParams(type, value) { switch (type) { case util.AnnotationEditorParamsType.INK_THICKNESS: this.#updateThickness(value); break; case util.AnnotationEditorParamsType.INK_COLOR: this.#updateColor(value); break; case util.AnnotationEditorParamsType.INK_OPACITY: this.#updateOpacity(value); break; } } static get defaultPropertiesToUpdate() { return [[util.AnnotationEditorParamsType.INK_THICKNESS, InkEditor._defaultThickness], [util.AnnotationEditorParamsType.INK_COLOR, InkEditor._defaultColor || editor_editor.AnnotationEditor._defaultLineColor], [util.AnnotationEditorParamsType.INK_OPACITY, Math.round(InkEditor._defaultOpacity * 100)]]; } get propertiesToUpdate() { return [[util.AnnotationEditorParamsType.INK_THICKNESS, this.thickness || InkEditor._defaultThickness], [util.AnnotationEditorParamsType.INK_COLOR, this.color || InkEditor._defaultColor || editor_editor.AnnotationEditor._defaultLineColor], [util.AnnotationEditorParamsType.INK_OPACITY, Math.round(100 * (this.opacity ?? InkEditor._defaultOpacity))]]; } #updateThickness(thickness) { const savedThickness = this.thickness; this.addCommands({ cmd: () => { this.thickness = thickness; this.#fitToContent(); }, undo: () => { this.thickness = savedThickness; this.#fitToContent(); }, mustExec: true, type: util.AnnotationEditorParamsType.INK_THICKNESS, overwriteIfSameType: true, keepUndo: true }); } #updateColor(color) { const savedColor = this.color; this.addCommands({ cmd: () => { this.color = color; this.#redraw(); }, undo: () => { this.color = savedColor; this.#redraw(); }, mustExec: true, type: util.AnnotationEditorParamsType.INK_COLOR, overwriteIfSameType: true, keepUndo: true }); } #updateOpacity(opacity) { opacity /= 100; const savedOpacity = this.opacity; this.addCommands({ cmd: () => { this.opacity = opacity; this.#redraw(); }, undo: () => { this.opacity = savedOpacity; this.#redraw(); }, mustExec: true, type: util.AnnotationEditorParamsType.INK_OPACITY, overwriteIfSameType: true, keepUndo: true }); } rebuild() { if (!this.parent) { return; } super.rebuild(); if (this.div === null) { return; } if (!this.canvas) { this.#createCanvas(); this.#createObserver(); } if (!this.isAttachedToDOM) { this.parent.add(this); this.#setCanvasDims(); } this.#fitToContent(); } remove() { if (this.canvas === null) { return; } if (!this.isEmpty()) { this.commit(); } this.canvas.width = this.canvas.height = 0; this.canvas.remove(); this.canvas = null; if (this.#canvasContextMenuTimeoutId) { clearTimeout(this.#canvasContextMenuTimeoutId); this.#canvasContextMenuTimeoutId = null; } this.#observer.disconnect(); this.#observer = null; super.remove(); } setParent(parent) { if (!this.parent && parent) { this._uiManager.removeShouldRescale(this); } else if (this.parent && parent === null) { this._uiManager.addShouldRescale(this); } super.setParent(parent); } onScaleChanging() { const [parentWidth, parentHeight] = this.parentDimensions; const width = this.width * parentWidth; const height = this.height * parentHeight; this.setDimensions(width, height); } enableEditMode() { if (this.#disableEditing || this.canvas === null) { return; } super.enableEditMode(); this._isDraggable = false; this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); } disableEditMode() { if (!this.isInEditMode() || this.canvas === null) { return; } super.disableEditMode(); this._isDraggable = !this.isEmpty(); this.div.classList.remove("editing"); this.canvas.removeEventListener("pointerdown", this.#boundCanvasPointerdown); } onceAdded() { this._isDraggable = !this.isEmpty(); } isEmpty() { return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0; } #getInitialBBox() { const { parentRotation, parentDimensions: [width, height] } = this; switch (parentRotation) { case 90: return [0, height, height, width]; case 180: return [width, height, width, height]; case 270: return [width, 0, height, width]; default: return [0, 0, width, height]; } } #setStroke() { const { ctx, color, opacity, thickness, parentScale, scaleFactor } = this; ctx.lineWidth = thickness * parentScale / scaleFactor; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.miterLimit = 10; ctx.strokeStyle = `${color}${(0,tools.opacityToHex)(opacity)}`; } #startDrawing(x, y) { this.canvas.addEventListener("contextmenu", display_utils.noContextMenu); this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave); this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove); this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup); this.canvas.removeEventListener("pointerdown", this.#boundCanvasPointerdown); this.isEditing = true; if (!this.#isCanvasInitialized) { this.#isCanvasInitialized = true; this.#setCanvasDims(); this.thickness ||= InkEditor._defaultThickness; this.color ||= InkEditor._defaultColor || editor_editor.AnnotationEditor._defaultLineColor; this.opacity ??= InkEditor._defaultOpacity; } this.currentPath.push([x, y]); this.#hasSomethingToDraw = false; this.#setStroke(); this.#requestFrameCallback = () => { this.#drawPoints(); if (this.#requestFrameCallback) { window.requestAnimationFrame(this.#requestFrameCallback); } }; window.requestAnimationFrame(this.#requestFrameCallback); } #draw(x, y) { const [lastX, lastY] = this.currentPath.at(-1); if (this.currentPath.length > 1 && x === lastX && y === lastY) { return; } const currentPath = this.currentPath; let path2D = this.#currentPath2D; currentPath.push([x, y]); this.#hasSomethingToDraw = true; if (currentPath.length <= 2) { path2D.moveTo(...currentPath[0]); path2D.lineTo(x, y); return; } if (currentPath.length === 3) { this.#currentPath2D = path2D = new Path2D(); path2D.moveTo(...currentPath[0]); } this.#makeBezierCurve(path2D, ...currentPath.at(-3), ...currentPath.at(-2), x, y); } #endPath() { if (this.currentPath.length === 0) { return; } const lastPoint = this.currentPath.at(-1); this.#currentPath2D.lineTo(...lastPoint); } #stopDrawing(x, y) { this.#requestFrameCallback = null; x = Math.min(Math.max(x, 0), this.canvas.width); y = Math.min(Math.max(y, 0), this.canvas.height); this.#draw(x, y); this.#endPath(); let bezier; if (this.currentPath.length !== 1) { bezier = this.#generateBezierPoints(); } else { const xy = [x, y]; bezier = [[xy, xy.slice(), xy.slice(), xy]]; } const path2D = this.#currentPath2D; const currentPath = this.currentPath; this.currentPath = []; this.#currentPath2D = new Path2D(); const cmd = () => { this.allRawPaths.push(currentPath); this.paths.push(bezier); this.bezierPath2D.push(path2D); this.rebuild(); }; const undo = () => { this.allRawPaths.pop(); this.paths.pop(); this.bezierPath2D.pop(); if (this.paths.length === 0) { this.remove(); } else { if (!this.canvas) { this.#createCanvas(); this.#createObserver(); } this.#fitToContent(); } }; this.addCommands({ cmd, undo, mustExec: true }); } #drawPoints() { if (!this.#hasSomethingToDraw) { return; } this.#hasSomethingToDraw = false; const thickness = Math.ceil(this.thickness * this.parentScale); const lastPoints = this.currentPath.slice(-3); const x = lastPoints.map(xy => xy[0]); const y = lastPoints.map(xy => xy[1]); const xMin = Math.min(...x) - thickness; const xMax = Math.max(...x) + thickness; const yMin = Math.min(...y) - thickness; const yMax = Math.max(...y) + thickness; const { ctx } = this; ctx.save(); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); for (const path of this.bezierPath2D) { ctx.stroke(path); } ctx.stroke(this.#currentPath2D); ctx.restore(); } #makeBezierCurve(path2D, x0, y0, x1, y1, x2, y2) { const prevX = (x0 + x1) / 2; const prevY = (y0 + y1) / 2; const x3 = (x1 + x2) / 2; const y3 = (y1 + y2) / 2; path2D.bezierCurveTo(prevX + 2 * (x1 - prevX) / 3, prevY + 2 * (y1 - prevY) / 3, x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3, x3, y3); } #generateBezierPoints() { const path = this.currentPath; if (path.length <= 2) { return [[path[0], path[0], path.at(-1), path.at(-1)]]; } const bezierPoints = []; let i; let [x0, y0] = path[0]; for (i = 1; i < path.length - 2; i++) { const [x1, y1] = path[i]; const [x2, y2] = path[i + 1]; const x3 = (x1 + x2) / 2; const y3 = (y1 + y2) / 2; const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3]; const control2 = [x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3]; bezierPoints.push([[x0, y0], control1, control2, [x3, y3]]); [x0, y0] = [x3, y3]; } const [x1, y1] = path[i]; const [x2, y2] = path[i + 1]; const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3]; const control2 = [x2 + 2 * (x1 - x2) / 3, y2 + 2 * (y1 - y2) / 3]; bezierPoints.push([[x0, y0], control1, control2, [x2, y2]]); return bezierPoints; } #redraw() { if (this.isEmpty()) { this.#updateTransform(); return; } this.#setStroke(); const { canvas, ctx } = this; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); this.#updateTransform(); for (const path of this.bezierPath2D) { ctx.stroke(path); } } commit() { if (this.#disableEditing) { return; } super.commit(); this.isEditing = false; this.disableEditMode(); this.setInForeground(); this.#disableEditing = true; this.div.classList.add("disabled"); this.#fitToContent(true); this.select(); this.parent.addInkEditorIfNeeded(true); this.moveInDOM(); this.div.focus({ preventScroll: true }); } focusin(event) { if (!this._focusEventsAllowed) { return; } super.focusin(event); this.enableEditMode(); } canvasPointerdown(event) { if (event.button !== 0 || !this.isInEditMode() || this.#disableEditing) { return; } this.setInForeground(); event.preventDefault(); if (event.pointerType !== "mouse" && !this.div.contains(document.activeElement)) { this.div.focus({ preventScroll: true }); } this.#startDrawing(event.offsetX, event.offsetY); } canvasPointermove(event) { event.preventDefault(); this.#draw(event.offsetX, event.offsetY); } canvasPointerup(event) { event.preventDefault(); this.#endDrawing(event); } canvasPointerleave(event) { this.#endDrawing(event); } #endDrawing(event) { this.canvas.removeEventListener("pointerleave", this.#boundCanvasPointerleave); this.canvas.removeEventListener("pointermove", this.#boundCanvasPointermove); this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup); this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); if (this.#canvasContextMenuTimeoutId) { clearTimeout(this.#canvasContextMenuTimeoutId); } this.#canvasContextMenuTimeoutId = setTimeout(() => { this.#canvasContextMenuTimeoutId = null; this.canvas.removeEventListener("contextmenu", display_utils.noContextMenu); }, 10); this.#stopDrawing(event.offsetX, event.offsetY); this.addToAnnotationStorage(); this.setInBackground(); } #createCanvas() { this.canvas = document.createElement("canvas"); this.canvas.width = this.canvas.height = 0; this.canvas.className = "inkEditorCanvas"; this.canvas.setAttribute("data-l10n-id", "pdfjs-ink-canvas"); this.div.append(this.canvas); this.ctx = this.canvas.getContext("2d"); } #createObserver() { this.#observer = new ResizeObserver(entries => { const rect = entries[0].contentRect; if (rect.width && rect.height) { this.setDimensions(rect.width, rect.height); } }); this.#observer.observe(this.div); } get isResizable() { return !this.isEmpty() && this.#disableEditing; } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.div.setAttribute("data-l10n-id", "pdfjs-ink"); const [x, y, w, h] = this.#getInitialBBox(); this.setAt(x, y, 0, 0); this.setDims(w, h); this.#createCanvas(); if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; this.setAspectRatio(this.width * parentWidth, this.height * parentHeight); this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); this.#isCanvasInitialized = true; this.#setCanvasDims(); this.setDims(this.width * parentWidth, this.height * parentHeight); this.#redraw(); this.div.classList.add("disabled"); } else { this.div.classList.add("editing"); this.enableEditMode(); } this.#createObserver(); return this.div; } #setCanvasDims() { if (!this.#isCanvasInitialized) { return; } const [parentWidth, parentHeight] = this.parentDimensions; this.canvas.width = Math.ceil(this.width * parentWidth); this.canvas.height = Math.ceil(this.height * parentHeight); this.#updateTransform(); } setDimensions(width, height) { const roundedWidth = Math.round(width); const roundedHeight = Math.round(height); if (this.#realWidth === roundedWidth && this.#realHeight === roundedHeight) { return; } this.#realWidth = roundedWidth; this.#realHeight = roundedHeight; this.canvas.style.visibility = "hidden"; const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.fixAndSetPosition(); if (this.#disableEditing) { this.#setScaleFactor(width, height); } this.#setCanvasDims(); this.#redraw(); this.canvas.style.visibility = "visible"; this.fixDims(); } #setScaleFactor(width, height) { const padding = this.#getPadding(); const scaleFactorW = (width - padding) / this.#baseWidth; const scaleFactorH = (height - padding) / this.#baseHeight; this.scaleFactor = Math.min(scaleFactorW, scaleFactorH); } #updateTransform() { const padding = this.#getPadding() / 2; this.ctx.setTransform(this.scaleFactor, 0, 0, this.scaleFactor, this.translationX * this.scaleFactor + padding, this.translationY * this.scaleFactor + padding); } static #buildPath2D(bezier) { const path2D = new Path2D(); for (let i = 0, ii = bezier.length; i < ii; i++) { const [first, control1, control2, second] = bezier[i]; if (i === 0) { path2D.moveTo(...first); } path2D.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], second[0], second[1]); } return path2D; } static #toPDFCoordinates(points, rect, rotation) { const [blX, blY, trX, trY] = rect; switch (rotation) { case 0: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] += blX; points[i + 1] = trY - points[i + 1]; } break; case 90: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = points[i + 1] + blX; points[i + 1] = x + blY; } break; case 180: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] = trX - points[i]; points[i + 1] += blY; } break; case 270: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = trX - points[i + 1]; points[i + 1] = trY - x; } break; default: throw new Error("Invalid rotation"); } return points; } static #fromPDFCoordinates(points, rect, rotation) { const [blX, blY, trX, trY] = rect; switch (rotation) { case 0: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] -= blX; points[i + 1] = trY - points[i + 1]; } break; case 90: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = points[i + 1] - blY; points[i + 1] = x - blX; } break; case 180: for (let i = 0, ii = points.length; i < ii; i += 2) { points[i] = trX - points[i]; points[i + 1] -= blY; } break; case 270: for (let i = 0, ii = points.length; i < ii; i += 2) { const x = points[i]; points[i] = trY - points[i + 1]; points[i + 1] = trX - x; } break; default: throw new Error("Invalid rotation"); } return points; } #serializePaths(s, tx, ty, rect) { const paths = []; const padding = this.thickness / 2; const shiftX = s * tx + padding; const shiftY = s * ty + padding; for (const bezier of this.paths) { const buffer = []; const points = []; for (let j = 0, jj = bezier.length; j < jj; j++) { const [first, control1, control2, second] = bezier[j]; const p10 = s * first[0] + shiftX; const p11 = s * first[1] + shiftY; const p20 = s * control1[0] + shiftX; const p21 = s * control1[1] + shiftY; const p30 = s * control2[0] + shiftX; const p31 = s * control2[1] + shiftY; const p40 = s * second[0] + shiftX; const p41 = s * second[1] + shiftY; if (j === 0) { buffer.push(p10, p11); points.push(p10, p11); } buffer.push(p20, p21, p30, p31, p40, p41); points.push(p20, p21); if (j === jj - 1) { points.push(p40, p41); } } paths.push({ bezier: InkEditor.#toPDFCoordinates(buffer, rect, this.rotation), points: InkEditor.#toPDFCoordinates(points, rect, this.rotation) }); } return paths; } #getBbox() { let xMin = Infinity; let xMax = -Infinity; let yMin = Infinity; let yMax = -Infinity; for (const path of this.paths) { for (const [first, control1, control2, second] of path) { const bbox = util.Util.bezierBoundingBox(...first, ...control1, ...control2, ...second); xMin = Math.min(xMin, bbox[0]); yMin = Math.min(yMin, bbox[1]); xMax = Math.max(xMax, bbox[2]); yMax = Math.max(yMax, bbox[3]); } } return [xMin, yMin, xMax, yMax]; } #getPadding() { return this.#disableEditing ? Math.ceil(this.thickness * this.parentScale) : 0; } #fitToContent(firstTime = false) { if (this.isEmpty()) { return; } if (!this.#disableEditing) { this.#redraw(); return; } const bbox = this.#getBbox(); const padding = this.#getPadding(); this.#baseWidth = Math.max(editor_editor.AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]); this.#baseHeight = Math.max(editor_editor.AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]); const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor); const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor); const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.setAspectRatio(width, height); const prevTranslationX = this.translationX; const prevTranslationY = this.translationY; this.translationX = -bbox[0]; this.translationY = -bbox[1]; this.#setCanvasDims(); this.#redraw(); this.#realWidth = width; this.#realHeight = height; this.setDims(width, height); const unscaledPadding = firstTime ? padding / this.scaleFactor / 2 : 0; this.translate(prevTranslationX - this.translationX - unscaledPadding, prevTranslationY - this.translationY - unscaledPadding); } static deserialize(data, parent, uiManager) { if (data instanceof annotation_layer.InkAnnotationElement) { return null; } const editor = super.deserialize(data, parent, uiManager); editor.thickness = data.thickness; editor.color = util.Util.makeHexColor(...data.color); editor.opacity = data.opacity; const [pageWidth, pageHeight] = editor.pageDimensions; const width = editor.width * pageWidth; const height = editor.height * pageHeight; const scaleFactor = editor.parentScale; const padding = data.thickness / 2; editor.#disableEditing = true; editor.#realWidth = Math.round(width); editor.#realHeight = Math.round(height); const { paths, rect, rotation } = data; for (let { bezier } of paths) { bezier = InkEditor.#fromPDFCoordinates(bezier, rect, rotation); const path = []; editor.paths.push(path); let p0 = scaleFactor * (bezier[0] - padding); let p1 = scaleFactor * (bezier[1] - padding); for (let i = 2, ii = bezier.length; i < ii; i += 6) { const p10 = scaleFactor * (bezier[i] - padding); const p11 = scaleFactor * (bezier[i + 1] - padding); const p20 = scaleFactor * (bezier[i + 2] - padding); const p21 = scaleFactor * (bezier[i + 3] - padding); const p30 = scaleFactor * (bezier[i + 4] - padding); const p31 = scaleFactor * (bezier[i + 5] - padding); path.push([[p0, p1], [p10, p11], [p20, p21], [p30, p31]]); p0 = p30; p1 = p31; } const path2D = this.#buildPath2D(path); editor.bezierPath2D.push(path2D); } const bbox = editor.#getBbox(); editor.#baseWidth = Math.max(editor_editor.AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]); editor.#baseHeight = Math.max(editor_editor.AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]); editor.#setScaleFactor(width, height); return editor; } serialize() { if (this.isEmpty()) { return null; } const rect = this.getRect(0, 0); const color = editor_editor.AnnotationEditor._colorManager.convert(this.ctx.strokeStyle); return { annotationType: util.AnnotationEditorType.INK, color, thickness: this.thickness, opacity: this.opacity, paths: this.#serializePaths(this.scaleFactor / this.parentScale, this.translationX, this.translationY, rect), pageIndex: this.pageIndex, rect, rotation: this.rotation, structTreeParentId: this._structTreeParentId }; } } ;// CONCATENATED MODULE: ./src/display/editor/stamp.js class StampEditor extends editor_editor.AnnotationEditor { #bitmap = null; #bitmapId = null; #bitmapPromise = null; #bitmapUrl = null; #bitmapFile = null; #bitmapFileName = ""; #canvas = null; #observer = null; #resizeTimeoutId = null; #isSvg = false; #hasBeenAddedInUndoStack = false; static _type = "stamp"; static _editorType = util.AnnotationEditorType.STAMP; constructor(params) { super({ ...params, name: "stampEditor" }); this.#bitmapUrl = params.bitmapUrl; this.#bitmapFile = params.bitmapFile; } static initialize(l10n) { editor_editor.AnnotationEditor.initialize(l10n); } static get supportedTypes() { const types = ["apng", "avif", "bmp", "gif", "jpeg", "png", "svg+xml", "webp", "x-icon"]; return (0,util.shadow)(this, "supportedTypes", types.map(type => `image/${type}`)); } static get supportedTypesStr() { return (0,util.shadow)(this, "supportedTypesStr", this.supportedTypes.join(",")); } static isHandlingMimeForPasting(mime) { return this.supportedTypes.includes(mime); } static paste(item, parent) { parent.pasteEditor(util.AnnotationEditorType.STAMP, { bitmapFile: item.getAsFile() }); } #getBitmapFetched(data, fromId = false) { if (!data) { this.remove(); return; } this.#bitmap = data.bitmap; if (!fromId) { this.#bitmapId = data.id; this.#isSvg = data.isSvg; } if (data.file) { this.#bitmapFileName = data.file.name; } this.#createCanvas(); } #getBitmapDone() { this.#bitmapPromise = null; this._uiManager.enableWaiting(false); if (this.#canvas) { this.div.focus(); } } #getBitmap() { if (this.#bitmapId) { this._uiManager.enableWaiting(true); this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone()); return; } if (this.#bitmapUrl) { const url = this.#bitmapUrl; this.#bitmapUrl = null; this._uiManager.enableWaiting(true); this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); return; } if (this.#bitmapFile) { const file = this.#bitmapFile; this.#bitmapFile = null; this._uiManager.enableWaiting(true); this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone()); return; } const input = document.createElement("input"); input.type = "file"; input.accept = StampEditor.supportedTypesStr; this.#bitmapPromise = new Promise(resolve => { input.addEventListener("change", async () => { if (!input.files || input.files.length === 0) { this.remove(); } else { this._uiManager.enableWaiting(true); const data = await this._uiManager.imageManager.getFromFile(input.files[0]); this.#getBitmapFetched(data); } resolve(); }); input.addEventListener("cancel", () => { this.remove(); resolve(); }); }).finally(() => this.#getBitmapDone()); input.click(); } remove() { if (this.#bitmapId) { this.#bitmap = null; this._uiManager.imageManager.deleteId(this.#bitmapId); this.#canvas?.remove(); this.#canvas = null; this.#observer?.disconnect(); this.#observer = null; if (this.#resizeTimeoutId) { clearTimeout(this.#resizeTimeoutId); this.#resizeTimeoutId = null; } } super.remove(); } rebuild() { if (!this.parent) { if (this.#bitmapId) { this.#getBitmap(); } return; } super.rebuild(); if (this.div === null) { return; } if (this.#bitmapId) { this.#getBitmap(); } if (!this.isAttachedToDOM) { this.parent.add(this); } } onceAdded() { this._isDraggable = true; this.div.focus(); } isEmpty() { return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile); } get isResizable() { return true; } render() { if (this.div) { return this.div; } let baseX, baseY; if (this.width) { baseX = this.x; baseY = this.y; } super.render(); this.div.hidden = true; if (this.#bitmap) { this.#createCanvas(); } else { this.#getBitmap(); } if (this.width) { const [parentWidth, parentHeight] = this.parentDimensions; this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight); } return this.div; } #createCanvas() { const { div } = this; let { width, height } = this.#bitmap; const [pageWidth, pageHeight] = this.pageDimensions; const MAX_RATIO = 0.75; if (this.width) { width = this.width * pageWidth; height = this.height * pageHeight; } else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) { const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height); width *= factor; height *= factor; } const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(width * parentWidth / pageWidth, height * parentHeight / pageHeight); this._uiManager.enableWaiting(false); const canvas = this.#canvas = document.createElement("canvas"); div.append(canvas); div.hidden = false; this.#drawBitmap(width, height); this.#createObserver(); if (!this.#hasBeenAddedInUndoStack) { this.parent.addUndoableEditor(this); this.#hasBeenAddedInUndoStack = true; } this._uiManager._eventBus.dispatch("reporttelemetry", { source: this, details: { type: "editing", subtype: this.editorType, data: { action: "inserted_image" } } }); this.addAltTextButton(); if (this.#bitmapFileName) { canvas.setAttribute("aria-label", this.#bitmapFileName); } } #setDimensions(width, height) { const [parentWidth, parentHeight] = this.parentDimensions; this.width = width / parentWidth; this.height = height / parentHeight; this.setDims(width, height); if (this._initialOptions?.isCentered) { this.center(); } else { this.fixAndSetPosition(); } this._initialOptions = null; if (this.#resizeTimeoutId !== null) { clearTimeout(this.#resizeTimeoutId); } const TIME_TO_WAIT = 200; this.#resizeTimeoutId = setTimeout(() => { this.#resizeTimeoutId = null; this.#drawBitmap(width, height); }, TIME_TO_WAIT); } #scaleBitmap(width, height) { const { width: bitmapWidth, height: bitmapHeight } = this.#bitmap; let newWidth = bitmapWidth; let newHeight = bitmapHeight; let bitmap = this.#bitmap; while (newWidth > 2 * width || newHeight > 2 * height) { const prevWidth = newWidth; const prevHeight = newHeight; if (newWidth > 2 * width) { newWidth = newWidth >= 16384 ? Math.floor(newWidth / 2) - 1 : Math.ceil(newWidth / 2); } if (newHeight > 2 * height) { newHeight = newHeight >= 16384 ? Math.floor(newHeight / 2) - 1 : Math.ceil(newHeight / 2); } const offscreen = new OffscreenCanvas(newWidth, newHeight); const ctx = offscreen.getContext("2d"); ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight); bitmap = offscreen.transferToImageBitmap(); } return bitmap; } #drawBitmap(width, height) { width = Math.ceil(width); height = Math.ceil(height); const canvas = this.#canvas; if (!canvas || canvas.width === width && canvas.height === height) { return; } canvas.width = width; canvas.height = height; const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(width, height); const ctx = canvas.getContext("2d"); ctx.filter = this._uiManager.hcmFilter; ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height); } getImageForAltText() { return this.#canvas; } #serializeBitmap(toUrl) { if (toUrl) { if (this.#isSvg) { const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId); if (url) { return url; } } const canvas = document.createElement("canvas"); ({ width: canvas.width, height: canvas.height } = this.#bitmap); const ctx = canvas.getContext("2d"); ctx.drawImage(this.#bitmap, 0, 0); return canvas.toDataURL(); } if (this.#isSvg) { const [pageWidth, pageHeight] = this.pageDimensions; const width = Math.round(this.width * pageWidth * display_utils.PixelsPerInch.PDF_TO_CSS_UNITS); const height = Math.round(this.height * pageHeight * display_utils.PixelsPerInch.PDF_TO_CSS_UNITS); const offscreen = new OffscreenCanvas(width, height); const ctx = offscreen.getContext("2d"); ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height); return offscreen.transferToImageBitmap(); } return structuredClone(this.#bitmap); } #createObserver() { this.#observer = new ResizeObserver(entries => { const rect = entries[0].contentRect; if (rect.width && rect.height) { this.#setDimensions(rect.width, rect.height); } }); this.#observer.observe(this.div); } static deserialize(data, parent, uiManager) { if (data instanceof annotation_layer.StampAnnotationElement) { return null; } const editor = super.deserialize(data, parent, uiManager); const { rect, bitmapUrl, bitmapId, isSvg, accessibilityData } = data; if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) { editor.#bitmapId = bitmapId; } else { editor.#bitmapUrl = bitmapUrl; } editor.#isSvg = isSvg; const [parentWidth, parentHeight] = editor.pageDimensions; editor.width = (rect[2] - rect[0]) / parentWidth; editor.height = (rect[3] - rect[1]) / parentHeight; if (accessibilityData) { editor.altTextData = accessibilityData; } return editor; } serialize(isForCopying = false, context = null) { if (this.isEmpty()) { return null; } const serialized = { annotationType: util.AnnotationEditorType.STAMP, bitmapId: this.#bitmapId, pageIndex: this.pageIndex, rect: this.getRect(0, 0), rotation: this.rotation, isSvg: this.#isSvg, structTreeParentId: this._structTreeParentId }; if (isForCopying) { serialized.bitmapUrl = this.#serializeBitmap(true); serialized.accessibilityData = this.altTextData; return serialized; } const { decorative, altText } = this.altTextData; if (!decorative && altText) { serialized.accessibilityData = { type: "Figure", alt: altText }; } if (context === null) { return serialized; } context.stamps ||= new Map(); const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null; if (!context.stamps.has(this.#bitmapId)) { context.stamps.set(this.#bitmapId, { area, serialized }); serialized.bitmap = this.#serializeBitmap(false); } else if (this.#isSvg) { const prevData = context.stamps.get(this.#bitmapId); if (area > prevData.area) { prevData.area = area; prevData.serialized.bitmap.close(); prevData.serialized.bitmap = this.#serializeBitmap(false); } } return serialized; } } ;// CONCATENATED MODULE: ./src/display/editor/annotation_editor_layer.js class AnnotationEditorLayer { #accessibilityManager; #allowClick = false; #annotationLayer = null; #boundPointerup = this.pointerup.bind(this); #boundPointerdown = this.pointerdown.bind(this); #editorFocusTimeoutId = null; #editors = new Map(); #hadPointerDown = false; #isCleaningUp = false; #isDisabling = false; #uiManager; static _initialized = false; static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor].map(type => [type._editorType, type])); constructor({ uiManager, pageIndex, div, accessibilityManager, annotationLayer, viewport, l10n }) { const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()]; if (!AnnotationEditorLayer._initialized) { AnnotationEditorLayer._initialized = true; for (const editorType of editorTypes) { editorType.initialize(l10n); } } uiManager.registerEditorTypes(editorTypes); this.#uiManager = uiManager; this.pageIndex = pageIndex; this.div = div; this.#accessibilityManager = accessibilityManager; this.#annotationLayer = annotationLayer; this.viewport = viewport; this.#uiManager.addLayer(this); } get isEmpty() { return this.#editors.size === 0; } updateToolbar(mode) { this.#uiManager.updateToolbar(mode); } updateMode(mode = this.#uiManager.getMode()) { this.#cleanup(); if (mode === util.AnnotationEditorType.INK) { this.addInkEditorIfNeeded(false); this.disableClick(); } else { this.enableClick(); } if (mode !== util.AnnotationEditorType.NONE) { const { classList } = this.div; for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType); } this.div.hidden = false; } } addInkEditorIfNeeded(isCommitting) { if (!isCommitting && this.#uiManager.getMode() !== util.AnnotationEditorType.INK) { return; } if (!isCommitting) { for (const editor of this.#editors.values()) { if (editor.isEmpty()) { editor.setInBackground(); return; } } } const editor = this.#createAndAddNewEditor({ offsetX: 0, offsetY: 0 }, false); editor.setInBackground(); } setEditingState(isEditing) { this.#uiManager.setEditingState(isEditing); } addCommands(params) { this.#uiManager.addCommands(params); } togglePointerEvents(enabled = false) { this.div.classList.toggle("disabled", !enabled); } enable() { this.togglePointerEvents(true); const annotationElementIds = new Set(); for (const editor of this.#editors.values()) { editor.enableEditing(); if (editor.annotationElementId) { annotationElementIds.add(editor.annotationElementId); } } if (!this.#annotationLayer) { return; } const editables = this.#annotationLayer.getEditableAnnotations(); for (const editable of editables) { editable.hide(); if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) { continue; } if (annotationElementIds.has(editable.data.id)) { continue; } const editor = this.deserialize(editable); if (!editor) { continue; } this.addOrRebuild(editor); editor.enableEditing(); } } disable() { this.#isDisabling = true; this.togglePointerEvents(false); const hiddenAnnotationIds = new Set(); for (const editor of this.#editors.values()) { editor.disableEditing(); if (!editor.annotationElementId || editor.serialize() !== null) { hiddenAnnotationIds.add(editor.annotationElementId); continue; } this.getEditableAnnotation(editor.annotationElementId)?.show(); editor.remove(); } if (this.#annotationLayer) { const editables = this.#annotationLayer.getEditableAnnotations(); for (const editable of editables) { const { id } = editable.data; if (hiddenAnnotationIds.has(id) || this.#uiManager.isDeletedAnnotationElement(id)) { continue; } editable.show(); } } this.#cleanup(); if (this.isEmpty) { this.div.hidden = true; } const { classList } = this.div; for (const editorType of AnnotationEditorLayer.#editorTypes.values()) { classList.remove(`${editorType._type}Editing`); } this.#isDisabling = false; } getEditableAnnotation(id) { return this.#annotationLayer?.getEditableAnnotation(id) || null; } setActiveEditor(editor) { const currentActive = this.#uiManager.getActive(); if (currentActive === editor) { return; } this.#uiManager.setActiveEditor(editor); } enableClick() { this.div.addEventListener("pointerdown", this.#boundPointerdown); this.div.addEventListener("pointerup", this.#boundPointerup); } disableClick() { this.div.removeEventListener("pointerdown", this.#boundPointerdown); this.div.removeEventListener("pointerup", this.#boundPointerup); } attach(editor) { this.#editors.set(editor.id, editor); const { annotationElementId } = editor; if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) { this.#uiManager.removeDeletedAnnotationElement(editor); } } detach(editor) { this.#editors.delete(editor.id); this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); if (!this.#isDisabling && editor.annotationElementId) { this.#uiManager.addDeletedAnnotationElement(editor); } } remove(editor) { this.detach(editor); this.#uiManager.removeEditor(editor); editor.div.remove(); editor.isAttachedToDOM = false; if (!this.#isCleaningUp) { this.addInkEditorIfNeeded(false); } } changeParent(editor) { if (editor.parent === this) { return; } if (editor.annotationElementId) { this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId); editor_editor.AnnotationEditor.deleteAnnotationElement(editor); editor.annotationElementId = null; } this.attach(editor); editor.parent?.detach(editor); editor.setParent(this); if (editor.div && editor.isAttachedToDOM) { editor.div.remove(); this.div.append(editor.div); } } add(editor) { this.changeParent(editor); this.#uiManager.addEditor(editor); this.attach(editor); if (!editor.isAttachedToDOM) { const div = editor.render(); this.div.append(div); editor.isAttachedToDOM = true; } editor.fixAndSetPosition(); editor.onceAdded(); this.#uiManager.addToAnnotationStorage(editor); } moveEditorInDOM(editor) { if (!editor.isAttachedToDOM) { return; } const { activeElement } = document; if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) { editor._focusEventsAllowed = false; this.#editorFocusTimeoutId = setTimeout(() => { this.#editorFocusTimeoutId = null; if (!editor.div.contains(document.activeElement)) { editor.div.addEventListener("focusin", () => { editor._focusEventsAllowed = true; }, { once: true }); activeElement.focus(); } else { editor._focusEventsAllowed = true; } }, 0); } editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true); } addOrRebuild(editor) { if (editor.needsToBeRebuilt()) { editor.parent ||= this; editor.rebuild(); } else { this.add(editor); } } addUndoableEditor(editor) { const cmd = () => editor._uiManager.rebuild(editor); const undo = () => { editor.remove(); }; this.addCommands({ cmd, undo, mustExec: false }); } getNextId() { return this.#uiManager.getId(); } #createNewEditor(params) { const editorType = AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode()); return editorType ? new editorType.prototype.constructor(params) : null; } pasteEditor(mode, params) { this.#uiManager.updateToolbar(mode); this.#uiManager.updateMode(mode); const { offsetX, offsetY } = this.#getCenterPoint(); const id = this.getNextId(); const editor = this.#createNewEditor({ parent: this, id, x: offsetX, y: offsetY, uiManager: this.#uiManager, isCentered: true, ...params }); if (editor) { this.add(editor); } } deserialize(data) { return AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager) || null; } #createAndAddNewEditor(event, isCentered) { const id = this.getNextId(); const editor = this.#createNewEditor({ parent: this, id, x: event.offsetX, y: event.offsetY, uiManager: this.#uiManager, isCentered }); if (editor) { this.add(editor); } return editor; } #getCenterPoint() { const { x, y, width, height } = this.div.getBoundingClientRect(); const tlX = Math.max(0, x); const tlY = Math.max(0, y); const brX = Math.min(window.innerWidth, x + width); const brY = Math.min(window.innerHeight, y + height); const centerX = (tlX + brX) / 2 - x; const centerY = (tlY + brY) / 2 - y; const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX]; return { offsetX, offsetY }; } addNewEditor() { this.#createAndAddNewEditor(this.#getCenterPoint(), true); } setSelected(editor) { this.#uiManager.setSelected(editor); } toggleSelected(editor) { this.#uiManager.toggleSelected(editor); } isSelected(editor) { return this.#uiManager.isSelected(editor); } unselect(editor) { this.#uiManager.unselect(editor); } pointerup(event) { const { isMac } = util.FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } if (event.target !== this.div) { return; } if (!this.#hadPointerDown) { return; } this.#hadPointerDown = false; if (!this.#allowClick) { this.#allowClick = true; return; } if (this.#uiManager.getMode() === util.AnnotationEditorType.STAMP) { this.#uiManager.unselectAll(); return; } this.#createAndAddNewEditor(event, false); } pointerdown(event) { if (this.#hadPointerDown) { this.#hadPointerDown = false; return; } const { isMac } = util.FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } if (event.target !== this.div) { return; } this.#hadPointerDown = true; const editor = this.#uiManager.getActive(); this.#allowClick = !editor || editor.isEmpty(); } findNewParent(editor, x, y) { const layer = this.#uiManager.findParent(x, y); if (layer === null || layer === this) { return false; } layer.changeParent(editor); return true; } destroy() { if (this.#uiManager.getActive()?.parent === this) { this.#uiManager.commitOrRemove(); this.#uiManager.setActiveEditor(null); } if (this.#editorFocusTimeoutId) { clearTimeout(this.#editorFocusTimeoutId); this.#editorFocusTimeoutId = null; } for (const editor of this.#editors.values()) { this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv); editor.setParent(null); editor.isAttachedToDOM = false; editor.div.remove(); } this.div = null; this.#editors.clear(); this.#uiManager.removeLayer(this); } #cleanup() { this.#isCleaningUp = true; for (const editor of this.#editors.values()) { if (editor.isEmpty()) { editor.remove(); } } this.#isCleaningUp = false; } render({ viewport }) { this.viewport = viewport; (0,display_utils.setLayerDimensions)(this.div, viewport); for (const editor of this.#uiManager.getEditors(this.pageIndex)) { this.add(editor); } this.updateMode(); } update({ viewport }) { this.#uiManager.commitOrRemove(); this.viewport = viewport; (0,display_utils.setLayerDimensions)(this.div, { rotation: viewport.rotation }); this.updateMode(); } get pageDimensions() { const { pageWidth, pageHeight } = this.viewport.rawDims; return [pageWidth, pageHeight]; } } /***/ }), /***/ 796: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { // EXPORTS __webpack_require__.d(__webpack_exports__, { AnnotationEditor: () => (/* binding */ AnnotationEditor) }); // EXTERNAL MODULE: ./src/display/editor/tools.js var tools = __webpack_require__(812); // EXTERNAL MODULE: ./src/shared/util.js var util = __webpack_require__(266); // EXTERNAL MODULE: ./src/display/display_utils.js var display_utils = __webpack_require__(473); ;// CONCATENATED MODULE: ./src/display/editor/toolbar.js class EditorToolbar { #toolbar = null; #editor; #buttons = null; constructor(editor) { this.#editor = editor; } render() { const editToolbar = this.#toolbar = document.createElement("div"); editToolbar.className = "editToolbar"; editToolbar.addEventListener("contextmenu", display_utils.noContextMenu); editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown); const buttons = this.#buttons = document.createElement("div"); buttons.className = "buttons"; editToolbar.append(buttons); this.#addDeleteButton(); return editToolbar; } static #pointerDown(e) { e.stopPropagation(); } #focusIn(e) { this.#editor._focusEventsAllowed = false; e.preventDefault(); e.stopPropagation(); } #focusOut(e) { this.#editor._focusEventsAllowed = true; e.preventDefault(); e.stopPropagation(); } #addListenersToElement(element) { element.addEventListener("focusin", this.#focusIn.bind(this), { capture: true }); element.addEventListener("focusout", this.#focusOut.bind(this), { capture: true }); element.addEventListener("contextmenu", display_utils.noContextMenu); } hide() { this.#toolbar.classList.add("hidden"); } show() { this.#toolbar.classList.remove("hidden"); } #addDeleteButton() { const button = document.createElement("button"); button.className = "delete"; button.tabIndex = 0; button.setAttribute("data-l10n-id", "pdfjs-editor-remove-button"); this.#addListenersToElement(button); button.addEventListener("click", e => { this.#editor._uiManager.delete(); }); this.#buttons.append(button); } remove() { this.#toolbar.remove(); } } ;// CONCATENATED MODULE: ./src/display/editor/editor.js class AnnotationEditor { #allResizerDivs = null; #altText = ""; #altTextDecorative = false; #altTextButton = null; #altTextTooltip = null; #altTextTooltipTimeout = null; #altTextWasFromKeyBoard = false; #keepAspectRatio = false; #resizersDiv = null; #savedDimensions = null; #boundFocusin = this.focusin.bind(this); #boundFocusout = this.focusout.bind(this); #editToolbar = null; #focusedResizerName = ""; #hasBeenClicked = false; #isEditing = false; #isInEditMode = false; #isResizerEnabledForKeyboard = false; #moveInDOMTimeout = null; _initialOptions = Object.create(null); _uiManager = null; _focusEventsAllowed = true; _l10nPromise = null; #isDraggable = false; #zIndex = AnnotationEditor._zIndex++; static _borderLineWidth = -1; static _colorManager = new tools.ColorManager(); static _zIndex = 1; static SMALL_EDITOR_SIZE = 0; static get _resizerKeyboardManager() { const resize = AnnotationEditor.prototype._resizeWithKeyboard; const small = tools.AnnotationEditorUIManager.TRANSLATE_SMALL; const big = tools.AnnotationEditorUIManager.TRANSLATE_BIG; return (0,util.shadow)(this, "_resizerKeyboardManager", new tools.KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], resize, { args: [-small, 0] }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, { args: [-big, 0] }], [["ArrowRight", "mac+ArrowRight"], resize, { args: [small, 0] }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, { args: [big, 0] }], [["ArrowUp", "mac+ArrowUp"], resize, { args: [0, -small] }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, { args: [0, -big] }], [["ArrowDown", "mac+ArrowDown"], resize, { args: [0, small] }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, { args: [0, big] }], [["Escape", "mac+Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]])); } constructor(parameters) { if (this.constructor === AnnotationEditor) { (0,util.unreachable)("Cannot initialize AnnotationEditor."); } this.parent = parameters.parent; this.id = parameters.id; this.width = this.height = null; this.pageIndex = parameters.parent.pageIndex; this.name = parameters.name; this.div = null; this._uiManager = parameters.uiManager; this.annotationElementId = null; this._willKeepAspectRatio = false; this._initialOptions.isCentered = parameters.isCentered; this._structTreeParentId = null; const { rotation, rawDims: { pageWidth, pageHeight, pageX, pageY } } = this.parent.viewport; this.rotation = rotation; this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360; this.pageDimensions = [pageWidth, pageHeight]; this.pageTranslation = [pageX, pageY]; const [width, height] = this.parentDimensions; this.x = parameters.x / width; this.y = parameters.y / height; this.isAttachedToDOM = false; this.deleted = false; } get editorType() { return Object.getPrototypeOf(this).constructor._type; } static get _defaultLineColor() { return (0,util.shadow)(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText")); } static deleteAnnotationElement(editor) { const fakeEditor = new FakeEditor({ id: editor.parent.getNextId(), parent: editor.parent, uiManager: editor._uiManager }); fakeEditor.annotationElementId = editor.annotationElementId; fakeEditor.deleted = true; fakeEditor._uiManager.addToAnnotationStorage(fakeEditor); } static initialize(l10n, options = null) { AnnotationEditor._l10nPromise ||= new Map(["pdfjs-editor-alt-text-button-label", "pdfjs-editor-alt-text-edit-button-label", "pdfjs-editor-alt-text-decorative-tooltip", "pdfjs-editor-resizer-label-topLeft", "pdfjs-editor-resizer-label-topMiddle", "pdfjs-editor-resizer-label-topRight", "pdfjs-editor-resizer-label-middleRight", "pdfjs-editor-resizer-label-bottomRight", "pdfjs-editor-resizer-label-bottomMiddle", "pdfjs-editor-resizer-label-bottomLeft", "pdfjs-editor-resizer-label-middleLeft"].map(str => [str, l10n.get(str.replaceAll(/([A-Z])/g, c => `-${c.toLowerCase()}`))])); if (options?.strings) { for (const str of options.strings) { AnnotationEditor._l10nPromise.set(str, l10n.get(str)); } } if (AnnotationEditor._borderLineWidth !== -1) { return; } const style = getComputedStyle(document.documentElement); AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0; } static updateDefaultParams(_type, _value) {} static get defaultPropertiesToUpdate() { return []; } static isHandlingMimeForPasting(mime) { return false; } static paste(item, parent) { (0,util.unreachable)("Not implemented"); } get propertiesToUpdate() { return []; } get _isDraggable() { return this.#isDraggable; } set _isDraggable(value) { this.#isDraggable = value; this.div?.classList.toggle("draggable", value); } get isEnterHandled() { return true; } center() { const [pageWidth, pageHeight] = this.pageDimensions; switch (this.parentRotation) { case 90: this.x -= this.height * pageHeight / (pageWidth * 2); this.y += this.width * pageWidth / (pageHeight * 2); break; case 180: this.x += this.width / 2; this.y += this.height / 2; break; case 270: this.x += this.height * pageHeight / (pageWidth * 2); this.y -= this.width * pageWidth / (pageHeight * 2); break; default: this.x -= this.width / 2; this.y -= this.height / 2; break; } this.fixAndSetPosition(); } addCommands(params) { this._uiManager.addCommands(params); } get currentLayer() { return this._uiManager.currentLayer; } setInBackground() { this.div.style.zIndex = 0; } setInForeground() { this.div.style.zIndex = this.#zIndex; } setParent(parent) { if (parent !== null) { this.pageIndex = parent.pageIndex; this.pageDimensions = parent.pageDimensions; } else { this.#stopResizing(); } this.parent = parent; } focusin(event) { if (!this._focusEventsAllowed) { return; } if (!this.#hasBeenClicked) { this.parent.setSelected(this); } else { this.#hasBeenClicked = false; } } focusout(event) { if (!this._focusEventsAllowed) { return; } if (!this.isAttachedToDOM) { return; } const target = event.relatedTarget; if (target?.closest(`#${this.id}`)) { return; } event.preventDefault(); if (!this.parent?.isMultipleSelection) { this.commitOrRemove(); } } commitOrRemove() { if (this.isEmpty()) { this.remove(); } else { this.commit(); } } commit() { this.addToAnnotationStorage(); } addToAnnotationStorage() { this._uiManager.addToAnnotationStorage(this); } setAt(x, y, tx, ty) { const [width, height] = this.parentDimensions; [tx, ty] = this.screenToPageTranslation(tx, ty); this.x = (x + tx) / width; this.y = (y + ty) / height; this.fixAndSetPosition(); } #translate([width, height], x, y) { [x, y] = this.screenToPageTranslation(x, y); this.x += x / width; this.y += y / height; this.fixAndSetPosition(); } translate(x, y) { this.#translate(this.parentDimensions, x, y); } translateInPage(x, y) { this.#translate(this.pageDimensions, x, y); this.div.scrollIntoView({ block: "nearest" }); } drag(tx, ty) { const [parentWidth, parentHeight] = this.parentDimensions; this.x += tx / parentWidth; this.y += ty / parentHeight; if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) { const { x, y } = this.div.getBoundingClientRect(); if (this.parent.findNewParent(this, x, y)) { this.x -= Math.floor(this.x); this.y -= Math.floor(this.y); } } let { x, y } = this; const [bx, by] = this.#getBaseTranslation(); x += bx; y += by; this.div.style.left = `${(100 * x).toFixed(2)}%`; this.div.style.top = `${(100 * y).toFixed(2)}%`; this.div.scrollIntoView({ block: "nearest" }); } #getBaseTranslation() { const [parentWidth, parentHeight] = this.parentDimensions; const { _borderLineWidth } = AnnotationEditor; const x = _borderLineWidth / parentWidth; const y = _borderLineWidth / parentHeight; switch (this.rotation) { case 90: return [-x, y]; case 180: return [x, y]; case 270: return [x, -y]; default: return [-x, -y]; } } fixAndSetPosition() { const [pageWidth, pageHeight] = this.pageDimensions; let { x, y, width, height } = this; width *= pageWidth; height *= pageHeight; x *= pageWidth; y *= pageHeight; switch (this.rotation) { case 0: x = Math.max(0, Math.min(pageWidth - width, x)); y = Math.max(0, Math.min(pageHeight - height, y)); break; case 90: x = Math.max(0, Math.min(pageWidth - height, x)); y = Math.min(pageHeight, Math.max(width, y)); break; case 180: x = Math.min(pageWidth, Math.max(width, x)); y = Math.min(pageHeight, Math.max(height, y)); break; case 270: x = Math.min(pageWidth, Math.max(height, x)); y = Math.max(0, Math.min(pageHeight - width, y)); break; } this.x = x /= pageWidth; this.y = y /= pageHeight; const [bx, by] = this.#getBaseTranslation(); x += bx; y += by; const { style } = this.div; style.left = `${(100 * x).toFixed(2)}%`; style.top = `${(100 * y).toFixed(2)}%`; this.moveInDOM(); } static #rotatePoint(x, y, angle) { switch (angle) { case 90: return [y, -x]; case 180: return [-x, -y]; case 270: return [-y, x]; default: return [x, y]; } } screenToPageTranslation(x, y) { return AnnotationEditor.#rotatePoint(x, y, this.parentRotation); } pageTranslationToScreen(x, y) { return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation); } #getRotationMatrix(rotation) { switch (rotation) { case 90: { const [pageWidth, pageHeight] = this.pageDimensions; return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0]; } case 180: return [-1, 0, 0, -1]; case 270: { const [pageWidth, pageHeight] = this.pageDimensions; return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0]; } default: return [1, 0, 0, 1]; } } get parentScale() { return this._uiManager.viewParameters.realScale; } get parentRotation() { return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360; } get parentDimensions() { const { parentScale, pageDimensions: [pageWidth, pageHeight] } = this; const scaledWidth = pageWidth * parentScale; const scaledHeight = pageHeight * parentScale; return util.FeatureTest.isCSSRoundSupported ? [Math.round(scaledWidth), Math.round(scaledHeight)] : [scaledWidth, scaledHeight]; } setDims(width, height) { const [parentWidth, parentHeight] = this.parentDimensions; this.div.style.width = `${(100 * width / parentWidth).toFixed(2)}%`; if (!this.#keepAspectRatio) { this.div.style.height = `${(100 * height / parentHeight).toFixed(2)}%`; } this.#altTextButton?.classList.toggle("small", width < AnnotationEditor.SMALL_EDITOR_SIZE || height < AnnotationEditor.SMALL_EDITOR_SIZE); } fixDims() { const { style } = this.div; const { height, width } = style; const widthPercent = width.endsWith("%"); const heightPercent = !this.#keepAspectRatio && height.endsWith("%"); if (widthPercent && heightPercent) { return; } const [parentWidth, parentHeight] = this.parentDimensions; if (!widthPercent) { style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`; } if (!this.#keepAspectRatio && !heightPercent) { style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`; } } getInitialTranslation() { return [0, 0]; } #createResizers() { if (this.#resizersDiv) { return; } this.#resizersDiv = document.createElement("div"); this.#resizersDiv.classList.add("resizers"); const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"]; for (const name of classes) { const div = document.createElement("div"); this.#resizersDiv.append(div); div.classList.add("resizer", name); div.setAttribute("data-resizer-name", name); div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name)); div.addEventListener("contextmenu", display_utils.noContextMenu); div.tabIndex = -1; } this.div.prepend(this.#resizersDiv); } #resizerPointerdown(name, event) { event.preventDefault(); const { isMac } = util.FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { return; } this.#toggleAltTextButton(false); const boundResizerPointermove = this.#resizerPointermove.bind(this, name); const savedDraggable = this._isDraggable; this._isDraggable = false; const pointerMoveOptions = { passive: true, capture: true }; this.parent.togglePointerEvents(false); window.addEventListener("pointermove", boundResizerPointermove, pointerMoveOptions); const savedX = this.x; const savedY = this.y; const savedWidth = this.width; const savedHeight = this.height; const savedParentCursor = this.parent.div.style.cursor; const savedCursor = this.div.style.cursor; this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor; const pointerUpCallback = () => { this.parent.togglePointerEvents(true); this.#toggleAltTextButton(true); this._isDraggable = savedDraggable; window.removeEventListener("pointerup", pointerUpCallback); window.removeEventListener("blur", pointerUpCallback); window.removeEventListener("pointermove", boundResizerPointermove, pointerMoveOptions); this.parent.div.style.cursor = savedParentCursor; this.div.style.cursor = savedCursor; this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight); }; window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("blur", pointerUpCallback); } #addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) { const newX = this.x; const newY = this.y; const newWidth = this.width; const newHeight = this.height; if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) { return; } this.addCommands({ cmd: () => { this.width = newWidth; this.height = newHeight; this.x = newX; this.y = newY; const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(parentWidth * newWidth, parentHeight * newHeight); this.fixAndSetPosition(); }, undo: () => { this.width = savedWidth; this.height = savedHeight; this.x = savedX; this.y = savedY; const [parentWidth, parentHeight] = this.parentDimensions; this.setDims(parentWidth * savedWidth, parentHeight * savedHeight); this.fixAndSetPosition(); }, mustExec: true }); } #resizerPointermove(name, event) { const [parentWidth, parentHeight] = this.parentDimensions; const savedX = this.x; const savedY = this.y; const savedWidth = this.width; const savedHeight = this.height; const minWidth = AnnotationEditor.MIN_SIZE / parentWidth; const minHeight = AnnotationEditor.MIN_SIZE / parentHeight; const round = x => Math.round(x * 10000) / 10000; const rotationMatrix = this.#getRotationMatrix(this.rotation); const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y]; const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation); const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y]; let getPoint; let getOpposite; let isDiagonal = false; let isHorizontal = false; switch (name) { case "topLeft": isDiagonal = true; getPoint = (w, h) => [0, 0]; getOpposite = (w, h) => [w, h]; break; case "topMiddle": getPoint = (w, h) => [w / 2, 0]; getOpposite = (w, h) => [w / 2, h]; break; case "topRight": isDiagonal = true; getPoint = (w, h) => [w, 0]; getOpposite = (w, h) => [0, h]; break; case "middleRight": isHorizontal = true; getPoint = (w, h) => [w, h / 2]; getOpposite = (w, h) => [0, h / 2]; break; case "bottomRight": isDiagonal = true; getPoint = (w, h) => [w, h]; getOpposite = (w, h) => [0, 0]; break; case "bottomMiddle": getPoint = (w, h) => [w / 2, h]; getOpposite = (w, h) => [w / 2, 0]; break; case "bottomLeft": isDiagonal = true; getPoint = (w, h) => [0, h]; getOpposite = (w, h) => [w, 0]; break; case "middleLeft": isHorizontal = true; getPoint = (w, h) => [0, h / 2]; getOpposite = (w, h) => [w, h / 2]; break; } const point = getPoint(savedWidth, savedHeight); const oppositePoint = getOpposite(savedWidth, savedHeight); let transfOppositePoint = transf(...oppositePoint); const oppositeX = round(savedX + transfOppositePoint[0]); const oppositeY = round(savedY + transfOppositePoint[1]); let ratioX = 1; let ratioY = 1; let [deltaX, deltaY] = this.screenToPageTranslation(event.movementX, event.movementY); [deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight); if (isDiagonal) { const oldDiag = Math.hypot(savedWidth, savedHeight); ratioX = ratioY = Math.max(Math.min(Math.hypot(oppositePoint[0] - point[0] - deltaX, oppositePoint[1] - point[1] - deltaY) / oldDiag, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight); } else if (isHorizontal) { ratioX = Math.max(minWidth, Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))) / savedWidth; } else { ratioY = Math.max(minHeight, Math.min(1, Math.abs(oppositePoint[1] - point[1] - deltaY))) / savedHeight; } const newWidth = round(savedWidth * ratioX); const newHeight = round(savedHeight * ratioY); transfOppositePoint = transf(...getOpposite(newWidth, newHeight)); const newX = oppositeX - transfOppositePoint[0]; const newY = oppositeY - transfOppositePoint[1]; this.width = newWidth; this.height = newHeight; this.x = newX; this.y = newY; this.setDims(parentWidth * newWidth, parentHeight * newHeight); this.fixAndSetPosition(); } async addAltTextButton() { if (this.#altTextButton) { return; } const altText = this.#altTextButton = document.createElement("button"); altText.className = "altText"; const msg = await AnnotationEditor._l10nPromise.get("pdfjs-editor-alt-text-button-label"); altText.textContent = msg; altText.setAttribute("aria-label", msg); altText.tabIndex = "0"; altText.addEventListener("contextmenu", display_utils.noContextMenu); altText.addEventListener("pointerdown", event => event.stopPropagation()); const onClick = event => { this.#altTextButton.hidden = true; event.preventDefault(); this._uiManager.editAltText(this); }; altText.addEventListener("click", onClick, { capture: true }); altText.addEventListener("keydown", event => { if (event.target === altText && event.key === "Enter") { this.#altTextWasFromKeyBoard = true; onClick(event); } }); this.#setAltTextButtonState(); this.div.append(altText); if (!AnnotationEditor.SMALL_EDITOR_SIZE) { const PERCENT = 40; AnnotationEditor.SMALL_EDITOR_SIZE = Math.min(128, Math.round(altText.getBoundingClientRect().width * (1 + PERCENT / 100))); } } async #setAltTextButtonState() { const button = this.#altTextButton; if (!button) { return; } if (!this.#altText && !this.#altTextDecorative) { button.classList.remove("done"); this.#altTextTooltip?.remove(); return; } button.classList.add("done"); AnnotationEditor._l10nPromise.get("pdfjs-editor-alt-text-edit-button-label").then(msg => { button.setAttribute("aria-label", msg); }); let tooltip = this.#altTextTooltip; if (!tooltip) { this.#altTextTooltip = tooltip = document.createElement("span"); tooltip.className = "tooltip"; tooltip.setAttribute("role", "tooltip"); const id = tooltip.id = `alt-text-tooltip-${this.id}`; button.setAttribute("aria-describedby", id); const DELAY_TO_SHOW_TOOLTIP = 100; button.addEventListener("mouseenter", () => { this.#altTextTooltipTimeout = setTimeout(() => { this.#altTextTooltipTimeout = null; this.#altTextTooltip.classList.add("show"); this._uiManager._eventBus.dispatch("reporttelemetry", { source: this, details: { type: "editing", subtype: this.editorType, data: { action: "alt_text_tooltip" } } }); }, DELAY_TO_SHOW_TOOLTIP); }); button.addEventListener("mouseleave", () => { if (this.#altTextTooltipTimeout) { clearTimeout(this.#altTextTooltipTimeout); this.#altTextTooltipTimeout = null; } this.#altTextTooltip?.classList.remove("show"); }); } tooltip.innerText = this.#altTextDecorative ? await AnnotationEditor._l10nPromise.get("pdfjs-editor-alt-text-decorative-tooltip") : this.#altText; if (!tooltip.parentNode) { button.append(tooltip); } const element = this.getImageForAltText(); element?.setAttribute("aria-describedby", tooltip.id); } #toggleAltTextButton(enabled = false) { if (!this.#altTextButton) { return; } if (!enabled && this.#altTextTooltipTimeout) { clearTimeout(this.#altTextTooltipTimeout); this.#altTextTooltipTimeout = null; } this.#altTextButton.disabled = !enabled; } altTextFinish() { if (!this.#altTextButton) { return; } this.#altTextButton.hidden = false; this.#altTextButton.focus({ focusVisible: this.#altTextWasFromKeyBoard }); this.#altTextWasFromKeyBoard = false; } addEditToolbar() { if (this.#editToolbar || this.#isInEditMode) { return; } this.#editToolbar = new EditorToolbar(this); this.div.append(this.#editToolbar.render()); } removeEditToolbar() { if (!this.#editToolbar) { return; } this.#editToolbar.remove(); this.#editToolbar = null; } getClientDimensions() { return this.div.getBoundingClientRect(); } get altTextData() { return { altText: this.#altText, decorative: this.#altTextDecorative }; } set altTextData({ altText, decorative }) { if (this.#altText === altText && this.#altTextDecorative === decorative) { return; } this.#altText = altText; this.#altTextDecorative = decorative; this.#setAltTextButtonState(); } render() { this.div = document.createElement("div"); this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); this.div.className = this.name; this.div.setAttribute("id", this.id); this.div.setAttribute("tabIndex", 0); this.setInForeground(); this.div.addEventListener("focusin", this.#boundFocusin); this.div.addEventListener("focusout", this.#boundFocusout); const [parentWidth, parentHeight] = this.parentDimensions; if (this.parentRotation % 180 !== 0) { this.div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`; this.div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`; } const [tx, ty] = this.getInitialTranslation(); this.translate(tx, ty); (0,tools.bindEvents)(this, this.div, ["pointerdown"]); return this.div; } pointerdown(event) { const { isMac } = util.FeatureTest.platform; if (event.button !== 0 || event.ctrlKey && isMac) { event.preventDefault(); return; } this.#hasBeenClicked = true; this.#setUpDragSession(event); } #setUpDragSession(event) { if (!this._isDraggable) { return; } const isSelected = this._uiManager.isSelected(this); this._uiManager.setUpDragSession(); let pointerMoveOptions, pointerMoveCallback; if (isSelected) { pointerMoveOptions = { passive: true, capture: true }; pointerMoveCallback = e => { const [tx, ty] = this.screenToPageTranslation(e.movementX, e.movementY); this._uiManager.dragSelectedEditors(tx, ty); }; window.addEventListener("pointermove", pointerMoveCallback, pointerMoveOptions); } const pointerUpCallback = () => { window.removeEventListener("pointerup", pointerUpCallback); window.removeEventListener("blur", pointerUpCallback); if (isSelected) { window.removeEventListener("pointermove", pointerMoveCallback, pointerMoveOptions); } this.#hasBeenClicked = false; if (!this._uiManager.endDragSession()) { const { isMac } = util.FeatureTest.platform; if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) { this.parent.toggleSelected(this); } else { this.parent.setSelected(this); } } }; window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("blur", pointerUpCallback); } moveInDOM() { if (this.#moveInDOMTimeout) { clearTimeout(this.#moveInDOMTimeout); } this.#moveInDOMTimeout = setTimeout(() => { this.#moveInDOMTimeout = null; this.parent?.moveEditorInDOM(this); }, 0); } _setParentAndPosition(parent, x, y) { parent.changeParent(this); this.x = x; this.y = y; this.fixAndSetPosition(); } getRect(tx, ty) { const scale = this.parentScale; const [pageWidth, pageHeight] = this.pageDimensions; const [pageX, pageY] = this.pageTranslation; const shiftX = tx / scale; const shiftY = ty / scale; const x = this.x * pageWidth; const y = this.y * pageHeight; const width = this.width * pageWidth; const height = this.height * pageHeight; switch (this.rotation) { case 0: return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY]; case 90: return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY]; case 180: return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY]; case 270: return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY]; default: throw new Error("Invalid rotation"); } } getRectInCurrentCoords(rect, pageHeight) { const [x1, y1, x2, y2] = rect; const width = x2 - x1; const height = y2 - y1; switch (this.rotation) { case 0: return [x1, pageHeight - y2, width, height]; case 90: return [x1, pageHeight - y1, height, width]; case 180: return [x2, pageHeight - y1, width, height]; case 270: return [x2, pageHeight - y2, height, width]; default: throw new Error("Invalid rotation"); } } onceAdded() {} isEmpty() { return false; } enableEditMode() { this.#isInEditMode = true; } disableEditMode() { this.#isInEditMode = false; } isInEditMode() { return this.#isInEditMode; } shouldGetKeyboardEvents() { return this.#isResizerEnabledForKeyboard; } needsToBeRebuilt() { return this.div && !this.isAttachedToDOM; } rebuild() { this.div?.addEventListener("focusin", this.#boundFocusin); this.div?.addEventListener("focusout", this.#boundFocusout); } serialize(isForCopying = false, context = null) { (0,util.unreachable)("An editor must be serializable"); } static deserialize(data, parent, uiManager) { const editor = new this.prototype.constructor({ parent, id: parent.getNextId(), uiManager }); editor.rotation = data.rotation; const [pageWidth, pageHeight] = editor.pageDimensions; const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight); editor.x = x / pageWidth; editor.y = y / pageHeight; editor.width = width / pageWidth; editor.height = height / pageHeight; return editor; } remove() { this.div.removeEventListener("focusin", this.#boundFocusin); this.div.removeEventListener("focusout", this.#boundFocusout); if (!this.isEmpty()) { this.commit(); } if (this.parent) { this.parent.remove(this); } else { this._uiManager.removeEditor(this); } this.#altTextButton?.remove(); this.#altTextButton = null; this.#altTextTooltip = null; if (this.#moveInDOMTimeout) { clearTimeout(this.#moveInDOMTimeout); this.#moveInDOMTimeout = null; } this.#stopResizing(); this.removeEditToolbar(); } get isResizable() { return false; } makeResizable() { if (this.isResizable) { this.#createResizers(); this.#resizersDiv.classList.remove("hidden"); (0,tools.bindEvents)(this, this.div, ["keydown"]); } } keydown(event) { if (!this.isResizable || event.target !== this.div || event.key !== "Enter") { return; } this._uiManager.setSelected(this); this.#savedDimensions = { savedX: this.x, savedY: this.y, savedWidth: this.width, savedHeight: this.height }; const children = this.#resizersDiv.children; if (!this.#allResizerDivs) { this.#allResizerDivs = Array.from(children); const boundResizerKeydown = this.#resizerKeydown.bind(this); const boundResizerBlur = this.#resizerBlur.bind(this); for (const div of this.#allResizerDivs) { const name = div.getAttribute("data-resizer-name"); div.setAttribute("role", "spinbutton"); div.addEventListener("keydown", boundResizerKeydown); div.addEventListener("blur", boundResizerBlur); div.addEventListener("focus", this.#resizerFocus.bind(this, name)); AnnotationEditor._l10nPromise.get(`pdfjs-editor-resizer-label-${name}`).then(msg => div.setAttribute("aria-label", msg)); } } const first = this.#allResizerDivs[0]; let firstPosition = 0; for (const div of children) { if (div === first) { break; } firstPosition++; } const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4); if (nextFirstPosition !== firstPosition) { if (nextFirstPosition < firstPosition) { for (let i = 0; i < firstPosition - nextFirstPosition; i++) { this.#resizersDiv.append(this.#resizersDiv.firstChild); } } else if (nextFirstPosition > firstPosition) { for (let i = 0; i < nextFirstPosition - firstPosition; i++) { this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild); } } let i = 0; for (const child of children) { const div = this.#allResizerDivs[i++]; const name = div.getAttribute("data-resizer-name"); AnnotationEditor._l10nPromise.get(`pdfjs-editor-resizer-label-${name}`).then(msg => child.setAttribute("aria-label", msg)); } } this.#setResizerTabIndex(0); this.#isResizerEnabledForKeyboard = true; this.#resizersDiv.firstChild.focus({ focusVisible: true }); event.preventDefault(); event.stopImmediatePropagation(); } #resizerKeydown(event) { AnnotationEditor._resizerKeyboardManager.exec(this, event); } #resizerBlur(event) { if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) { this.#stopResizing(); } } #resizerFocus(name) { this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : ""; } #setResizerTabIndex(value) { if (!this.#allResizerDivs) { return; } for (const div of this.#allResizerDivs) { div.tabIndex = value; } } _resizeWithKeyboard(x, y) { if (!this.#isResizerEnabledForKeyboard) { return; } this.#resizerPointermove(this.#focusedResizerName, { movementX: x, movementY: y }); } #stopResizing() { this.#isResizerEnabledForKeyboard = false; this.#setResizerTabIndex(-1); if (this.#savedDimensions) { const { savedX, savedY, savedWidth, savedHeight } = this.#savedDimensions; this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight); this.#savedDimensions = null; } } _stopResizingWithKeyboard() { this.#stopResizing(); this.div.focus(); } select() { this.makeResizable(); this.div?.classList.add("selectedEditor"); this.addEditToolbar(); this.#editToolbar?.show(); } unselect() { this.#resizersDiv?.classList.add("hidden"); this.div?.classList.remove("selectedEditor"); if (this.div?.contains(document.activeElement)) { this._uiManager.currentLayer.div.focus(); } this.#editToolbar?.hide(); } updateParams(type, value) {} disableEditing() { if (this.#altTextButton) { this.#altTextButton.hidden = true; } } enableEditing() { if (this.#altTextButton) { this.#altTextButton.hidden = false; } } enterInEditMode() {} getImageForAltText() { return null; } get contentDiv() { return this.div; } get isEditing() { return this.#isEditing; } set isEditing(value) { this.#isEditing = value; if (!this.parent) { return; } if (value) { this.parent.setSelected(this); this.parent.setActiveEditor(this); } else { this.parent.setActiveEditor(null); } } setAspectRatio(width, height) { this.#keepAspectRatio = true; const aspectRatio = width / height; const { style } = this.div; style.aspectRatio = aspectRatio; style.height = "auto"; } static get MIN_SIZE() { return 16; } } class FakeEditor extends AnnotationEditor { constructor(params) { super(params); this.annotationElementId = params.annotationElementId; this.deleted = true; } serialize() { return { id: this.annotationElementId, deleted: true, pageIndex: this.pageIndex }; } } /***/ }), /***/ 405: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Outliner: () => (/* binding */ Outliner) /* harmony export */ }); class Outliner { #box; #verticalEdges = []; #intervals = []; constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) { let minX = Infinity; let maxX = -Infinity; let minY = Infinity; let maxY = -Infinity; const NUMBER_OF_DIGITS = 4; const EPSILON = 10 ** -NUMBER_OF_DIGITS; for (const { x, y, width, height } of boxes) { const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON; const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON; const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON; const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON; const left = [x1, y1, y2, true]; const right = [x2, y1, y2, false]; this.#verticalEdges.push(left, right); minX = Math.min(minX, x1); maxX = Math.max(maxX, x2); minY = Math.min(minY, y1); maxY = Math.max(maxY, y2); } const bboxWidth = maxX - minX + 2 * innerMargin; const bboxHeight = maxY - minY + 2 * innerMargin; const shiftedMinX = minX - innerMargin; const shiftedMinY = minY - innerMargin; const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2); const lastPoint = [lastEdge[0], lastEdge[2]]; for (const edge of this.#verticalEdges) { const [x, y1, y2] = edge; edge[0] = (x - shiftedMinX) / bboxWidth; edge[1] = (y1 - shiftedMinY) / bboxHeight; edge[2] = (y2 - shiftedMinY) / bboxHeight; } this.#box = { x: shiftedMinX, y: shiftedMinY, width: bboxWidth, height: bboxHeight, lastPoint }; } getOutlines() { this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]); const outlineVerticalEdges = []; for (const edge of this.#verticalEdges) { if (edge[3]) { outlineVerticalEdges.push(...this.#breakEdge(edge)); this.#insert(edge); } else { this.#remove(edge); outlineVerticalEdges.push(...this.#breakEdge(edge)); } } return this.#getOutlines(outlineVerticalEdges); } #getOutlines(outlineVerticalEdges) { const edges = []; const allEdges = new Set(); for (const edge of outlineVerticalEdges) { const [x, y1, y2] = edge; edges.push([x, y1, edge], [x, y2, edge]); } edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]); for (let i = 0, ii = edges.length; i < ii; i += 2) { const edge1 = edges[i][2]; const edge2 = edges[i + 1][2]; edge1.push(edge2); edge2.push(edge1); allEdges.add(edge1); allEdges.add(edge2); } const outlines = []; let outline; while (allEdges.size > 0) { const edge = allEdges.values().next().value; let [x, y1, y2, edge1, edge2] = edge; allEdges.delete(edge); let lastPointX = x; let lastPointY = y1; outline = [x, y2]; outlines.push(outline); while (true) { let e; if (allEdges.has(edge1)) { e = edge1; } else if (allEdges.has(edge2)) { e = edge2; } else { break; } allEdges.delete(e); [x, y1, y2, edge1, edge2] = e; if (lastPointX !== x) { outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2); lastPointX = x; } lastPointY = lastPointY === y1 ? y2 : y1; } outline.push(lastPointX, lastPointY); } return { outlines, box: this.#box }; } #binarySearch(y) { const array = this.#intervals; let start = 0; let end = array.length - 1; while (start <= end) { const middle = start + end >> 1; const y1 = array[middle][0]; if (y1 === y) { return middle; } if (y1 < y) { start = middle + 1; } else { end = middle - 1; } } return end + 1; } #insert([, y1, y2]) { const index = this.#binarySearch(y1); this.#intervals.splice(index, 0, [y1, y2]); } #remove([, y1, y2]) { const index = this.#binarySearch(y1); for (let i = index; i < this.#intervals.length; i++) { const [start, end] = this.#intervals[i]; if (start !== y1) { break; } if (start === y1 && end === y2) { this.#intervals.splice(i, 1); return; } } for (let i = index - 1; i >= 0; i--) { const [start, end] = this.#intervals[i]; if (start !== y1) { break; } if (start === y1 && end === y2) { this.#intervals.splice(i, 1); return; } } } #breakEdge(edge) { const [x, y1, y2] = edge; const results = [[x, y1, y2]]; const index = this.#binarySearch(y2); for (let i = 0; i < index; i++) { const [start, end] = this.#intervals[i]; for (let j = 0, jj = results.length; j < jj; j++) { const [, y3, y4] = results[j]; if (end <= y3 || y4 <= start) { continue; } if (y3 >= start) { if (y4 > end) { results[j][1] = end; } else { if (jj === 1) { return []; } results.splice(j, 1); j--; jj--; } continue; } results[j][2] = start; if (y4 > end) { results.push([x, end, y4]); } } } return results; } } /***/ }), /***/ 812: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AnnotationEditorUIManager: () => (/* binding */ AnnotationEditorUIManager), /* harmony export */ ColorManager: () => (/* binding */ ColorManager), /* harmony export */ KeyboardManager: () => (/* binding */ KeyboardManager), /* harmony export */ bindEvents: () => (/* binding */ bindEvents), /* harmony export */ opacityToHex: () => (/* binding */ opacityToHex) /* harmony export */ }); /* unused harmony export CommandManager */ /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _display_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(473); function bindEvents(obj, element, names) { for (const name of names) { element.addEventListener(name, obj[name].bind(obj)); } } function opacityToHex(opacity) { return Math.round(Math.min(255, Math.max(1, 255 * opacity))).toString(16).padStart(2, "0"); } class IdManager { #id = 0; getId() { return `${_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorPrefix}${this.#id++}`; } } class ImageManager { #baseId = (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.getUuid)(); #id = 0; #cache = null; static get _isSVGFittingCanvas() { const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" style="fill:red;"/></svg>`; const canvas = new OffscreenCanvas(1, 3); const ctx = canvas.getContext("2d"); const image = new Image(); image.src = svg; const promise = image.decode().then(() => { ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3); return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0; }); return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "_isSVGFittingCanvas", promise); } async #get(key, rawData) { this.#cache ||= new Map(); let data = this.#cache.get(key); if (data === null) { return null; } if (data?.bitmap) { data.refCounter += 1; return data; } try { data ||= { bitmap: null, id: `image_${this.#baseId}_${this.#id++}`, refCounter: 0, isSvg: false }; let image; if (typeof rawData === "string") { data.url = rawData; image = await (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.fetchData)(rawData, "blob"); } else { image = data.file = rawData; } if (image.type === "image/svg+xml") { const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas; const fileReader = new FileReader(); const imageElement = new Image(); const imagePromise = new Promise((resolve, reject) => { imageElement.onload = () => { data.bitmap = imageElement; data.isSvg = true; resolve(); }; fileReader.onload = async () => { const url = data.svgUrl = fileReader.result; imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url; }; imageElement.onerror = fileReader.onerror = reject; }); fileReader.readAsDataURL(image); await imagePromise; } else { data.bitmap = await createImageBitmap(image); } data.refCounter = 1; } catch (e) { console.error(e); data = null; } this.#cache.set(key, data); if (data) { this.#cache.set(data.id, data); } return data; } async getFromFile(file) { const { lastModified, name, size, type } = file; return this.#get(`${lastModified}_${name}_${size}_${type}`, file); } async getFromUrl(url) { return this.#get(url, url); } async getFromId(id) { this.#cache ||= new Map(); const data = this.#cache.get(id); if (!data) { return null; } if (data.bitmap) { data.refCounter += 1; return data; } if (data.file) { return this.getFromFile(data.file); } return this.getFromUrl(data.url); } getSvgUrl(id) { const data = this.#cache.get(id); if (!data?.isSvg) { return null; } return data.svgUrl; } deleteId(id) { this.#cache ||= new Map(); const data = this.#cache.get(id); if (!data) { return; } data.refCounter -= 1; if (data.refCounter !== 0) { return; } data.bitmap = null; } isValidId(id) { return id.startsWith(`image_${this.#baseId}_`); } } class CommandManager { #commands = []; #locked = false; #maxSize; #position = -1; constructor(maxSize = 128) { this.#maxSize = maxSize; } add({ cmd, undo, mustExec, type = NaN, overwriteIfSameType = false, keepUndo = false }) { if (mustExec) { cmd(); } if (this.#locked) { return; } const save = { cmd, undo, type }; if (this.#position === -1) { if (this.#commands.length > 0) { this.#commands.length = 0; } this.#position = 0; this.#commands.push(save); return; } if (overwriteIfSameType && this.#commands[this.#position].type === type) { if (keepUndo) { save.undo = this.#commands[this.#position].undo; } this.#commands[this.#position] = save; return; } const next = this.#position + 1; if (next === this.#maxSize) { this.#commands.splice(0, 1); } else { this.#position = next; if (next < this.#commands.length) { this.#commands.splice(next); } } this.#commands.push(save); } undo() { if (this.#position === -1) { return; } this.#locked = true; this.#commands[this.#position].undo(); this.#locked = false; this.#position -= 1; } redo() { if (this.#position < this.#commands.length - 1) { this.#position += 1; this.#locked = true; this.#commands[this.#position].cmd(); this.#locked = false; } } hasSomethingToUndo() { return this.#position !== -1; } hasSomethingToRedo() { return this.#position < this.#commands.length - 1; } destroy() { this.#commands = null; } } class KeyboardManager { constructor(callbacks) { this.buffer = []; this.callbacks = new Map(); this.allKeys = new Set(); const { isMac } = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.FeatureTest.platform; for (const [keys, callback, options = {}] of callbacks) { for (const key of keys) { const isMacKey = key.startsWith("mac+"); if (isMac && isMacKey) { this.callbacks.set(key.slice(4), { callback, options }); this.allKeys.add(key.split("+").at(-1)); } else if (!isMac && !isMacKey) { this.callbacks.set(key, { callback, options }); this.allKeys.add(key.split("+").at(-1)); } } } } #serialize(event) { if (event.altKey) { this.buffer.push("alt"); } if (event.ctrlKey) { this.buffer.push("ctrl"); } if (event.metaKey) { this.buffer.push("meta"); } if (event.shiftKey) { this.buffer.push("shift"); } this.buffer.push(event.key); const str = this.buffer.join("+"); this.buffer.length = 0; return str; } exec(self, event) { if (!this.allKeys.has(event.key)) { return; } const info = this.callbacks.get(this.#serialize(event)); if (!info) { return; } const { callback, options: { bubbles = false, args = [], checker = null } } = info; if (checker && !checker(self, event)) { return; } callback.bind(self, ...args)(); if (!bubbles) { event.stopPropagation(); event.preventDefault(); } } } class ColorManager { static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]); get _colors() { const colors = new Map([["CanvasText", null], ["Canvas", null]]); (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.getColorValues)(colors); return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "_colors", colors); } convert(color) { const rgb = (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.getRGB)(color); if (!window.matchMedia("(forced-colors: active)").matches) { return rgb; } for (const [name, RGB] of this._colors) { if (RGB.every((x, i) => x === rgb[i])) { return ColorManager._colorsMapping.get(name); } } return rgb; } getHexCode(name) { const rgb = this._colors.get(name); if (!rgb) { return name; } return _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.Util.makeHexColor(...rgb); } } class AnnotationEditorUIManager { #activeEditor = null; #allEditors = new Map(); #allLayers = new Map(); #altTextManager = null; #annotationStorage = null; #commandManager = new CommandManager(); #currentPageIndex = 0; #deletedAnnotationsElementIds = new Set(); #draggingEditors = null; #editorTypes = null; #editorsToRescale = new Set(); #filterFactory = null; #focusMainContainerTimeoutId = null; #idManager = new IdManager(); #isEnabled = false; #isWaiting = false; #lastActiveElement = null; #mode = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorType.NONE; #selectedEditors = new Set(); #pageColors = null; #boundBlur = this.blur.bind(this); #boundFocus = this.focus.bind(this); #boundCopy = this.copy.bind(this); #boundCut = this.cut.bind(this); #boundPaste = this.paste.bind(this); #boundKeydown = this.keydown.bind(this); #boundOnEditingAction = this.onEditingAction.bind(this); #boundOnPageChanging = this.onPageChanging.bind(this); #boundOnScaleChanging = this.onScaleChanging.bind(this); #boundOnRotationChanging = this.onRotationChanging.bind(this); #previousStates = { isEditing: false, isEmpty: true, hasSomethingToUndo: false, hasSomethingToRedo: false, hasSelectedEditor: false }; #translation = [0, 0]; #translationTimeoutId = null; #container = null; #viewer = null; static TRANSLATE_SMALL = 1; static TRANSLATE_BIG = 10; static get _keyboardManager() { const proto = AnnotationEditorUIManager.prototype; const arrowChecker = self => { return self.#container.contains(document.activeElement) && self.hasSomethingToControl(); }; const textInputChecker = (_self, { target: el }) => { if (el instanceof HTMLInputElement) { const { type } = el; return type !== "text" && type !== "number"; } return true; }; const small = this.TRANSLATE_SMALL; const big = this.TRANSLATE_BIG; return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, { checker: textInputChecker }], [["ctrl+z", "mac+meta+z"], proto.undo, { checker: textInputChecker }], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, { checker: textInputChecker }], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, { checker: textInputChecker }], [["Enter", "mac+Enter"], proto.addNewEditorFromKeyboard, { checker: (self, { target: el }) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled }], [[" ", "mac+ "], proto.addNewEditorFromKeyboard, { checker: self => self.#container.contains(document.activeElement) }], [["Escape", "mac+Escape"], proto.unselectAll], [["ArrowLeft", "mac+ArrowLeft"], proto.translateSelectedEditors, { args: [-small, 0], checker: arrowChecker }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, { args: [-big, 0], checker: arrowChecker }], [["ArrowRight", "mac+ArrowRight"], proto.translateSelectedEditors, { args: [small, 0], checker: arrowChecker }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, { args: [big, 0], checker: arrowChecker }], [["ArrowUp", "mac+ArrowUp"], proto.translateSelectedEditors, { args: [0, -small], checker: arrowChecker }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, { args: [0, -big], checker: arrowChecker }], [["ArrowDown", "mac+ArrowDown"], proto.translateSelectedEditors, { args: [0, small], checker: arrowChecker }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, { args: [0, big], checker: arrowChecker }]])); } constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors) { this.#container = container; this.#viewer = viewer; this.#altTextManager = altTextManager; this._eventBus = eventBus; this._eventBus._on("editingaction", this.#boundOnEditingAction); this._eventBus._on("pagechanging", this.#boundOnPageChanging); this._eventBus._on("scalechanging", this.#boundOnScaleChanging); this._eventBus._on("rotationchanging", this.#boundOnRotationChanging); this.#annotationStorage = pdfDocument.annotationStorage; this.#filterFactory = pdfDocument.filterFactory; this.#pageColors = pageColors; this.viewParameters = { realScale: _display_utils_js__WEBPACK_IMPORTED_MODULE_1__.PixelsPerInch.PDF_TO_CSS_UNITS, rotation: 0 }; } destroy() { this.#removeKeyboardManager(); this.#removeFocusManager(); this._eventBus._off("editingaction", this.#boundOnEditingAction); this._eventBus._off("pagechanging", this.#boundOnPageChanging); this._eventBus._off("scalechanging", this.#boundOnScaleChanging); this._eventBus._off("rotationchanging", this.#boundOnRotationChanging); for (const layer of this.#allLayers.values()) { layer.destroy(); } this.#allLayers.clear(); this.#allEditors.clear(); this.#editorsToRescale.clear(); this.#activeEditor = null; this.#selectedEditors.clear(); this.#commandManager.destroy(); this.#altTextManager.destroy(); if (this.#focusMainContainerTimeoutId) { clearTimeout(this.#focusMainContainerTimeoutId); this.#focusMainContainerTimeoutId = null; } if (this.#translationTimeoutId) { clearTimeout(this.#translationTimeoutId); this.#translationTimeoutId = null; } } get hcmFilter() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none"); } get direction() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "direction", getComputedStyle(this.#container).direction); } editAltText(editor) { this.#altTextManager?.editAltText(this, editor); } onPageChanging({ pageNumber }) { this.#currentPageIndex = pageNumber - 1; } focusMainContainer() { this.#container.focus(); } findParent(x, y) { for (const layer of this.#allLayers.values()) { const { x: layerX, y: layerY, width, height } = layer.div.getBoundingClientRect(); if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) { return layer; } } return null; } disableUserSelect(value = false) { this.#viewer.classList.toggle("noUserSelect", value); } addShouldRescale(editor) { this.#editorsToRescale.add(editor); } removeShouldRescale(editor) { this.#editorsToRescale.delete(editor); } onScaleChanging({ scale }) { this.commitOrRemove(); this.viewParameters.realScale = scale * _display_utils_js__WEBPACK_IMPORTED_MODULE_1__.PixelsPerInch.PDF_TO_CSS_UNITS; for (const editor of this.#editorsToRescale) { editor.onScaleChanging(); } } onRotationChanging({ pagesRotation }) { this.commitOrRemove(); this.viewParameters.rotation = pagesRotation; } addToAnnotationStorage(editor) { if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) { this.#annotationStorage.setValue(editor.id, editor); } } #addFocusManager() { window.addEventListener("focus", this.#boundFocus); window.addEventListener("blur", this.#boundBlur); } #removeFocusManager() { window.removeEventListener("focus", this.#boundFocus); window.removeEventListener("blur", this.#boundBlur); } blur() { if (!this.hasSelection) { return; } const { activeElement } = document; for (const editor of this.#selectedEditors) { if (editor.div.contains(activeElement)) { this.#lastActiveElement = [editor, activeElement]; editor._focusEventsAllowed = false; break; } } } focus() { if (!this.#lastActiveElement) { return; } const [lastEditor, lastActiveElement] = this.#lastActiveElement; this.#lastActiveElement = null; lastActiveElement.addEventListener("focusin", () => { lastEditor._focusEventsAllowed = true; }, { once: true }); lastActiveElement.focus(); } #addKeyboardManager() { window.addEventListener("keydown", this.#boundKeydown); } #removeKeyboardManager() { window.removeEventListener("keydown", this.#boundKeydown); } #addCopyPasteListeners() { document.addEventListener("copy", this.#boundCopy); document.addEventListener("cut", this.#boundCut); document.addEventListener("paste", this.#boundPaste); } #removeCopyPasteListeners() { document.removeEventListener("copy", this.#boundCopy); document.removeEventListener("cut", this.#boundCut); document.removeEventListener("paste", this.#boundPaste); } addEditListeners() { this.#addKeyboardManager(); this.#addCopyPasteListeners(); } removeEditListeners() { this.#removeKeyboardManager(); this.#removeCopyPasteListeners(); } copy(event) { event.preventDefault(); this.#activeEditor?.commitOrRemove(); if (!this.hasSelection) { return; } const editors = []; for (const editor of this.#selectedEditors) { const serialized = editor.serialize(true); if (serialized) { editors.push(serialized); } } if (editors.length === 0) { return; } event.clipboardData.setData("application/pdfjs", JSON.stringify(editors)); } cut(event) { this.copy(event); this.delete(); } paste(event) { event.preventDefault(); const { clipboardData } = event; for (const item of clipboardData.items) { for (const editorType of this.#editorTypes) { if (editorType.isHandlingMimeForPasting(item.type)) { editorType.paste(item, this.currentLayer); return; } } } let data = clipboardData.getData("application/pdfjs"); if (!data) { return; } try { data = JSON.parse(data); } catch (ex) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`paste: "${ex.message}".`); return; } if (!Array.isArray(data)) { return; } this.unselectAll(); const layer = this.currentLayer; try { const newEditors = []; for (const editor of data) { const deserializedEditor = layer.deserialize(editor); if (!deserializedEditor) { return; } newEditors.push(deserializedEditor); } const cmd = () => { for (const editor of newEditors) { this.#addEditorToLayer(editor); } this.#selectEditors(newEditors); }; const undo = () => { for (const editor of newEditors) { editor.remove(); } }; this.addCommands({ cmd, undo, mustExec: true }); } catch (ex) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`paste: "${ex.message}".`); } } keydown(event) { if (!this.isEditorHandlingKeyboard) { AnnotationEditorUIManager._keyboardManager.exec(this, event); } } onEditingAction(details) { if (["undo", "redo", "delete", "selectAll"].includes(details.name)) { this[details.name](); } } #dispatchUpdateStates(details) { const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value); if (hasChanged) { this._eventBus.dispatch("annotationeditorstateschanged", { source: this, details: Object.assign(this.#previousStates, details) }); } } #dispatchUpdateUI(details) { this._eventBus.dispatch("annotationeditorparamschanged", { source: this, details }); } setEditingState(isEditing) { if (isEditing) { this.#addFocusManager(); this.#addKeyboardManager(); this.#addCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: this.#mode !== _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorType.NONE, isEmpty: this.#isEmpty(), hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), hasSelectedEditor: false }); } else { this.#removeFocusManager(); this.#removeKeyboardManager(); this.#removeCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: false }); this.disableUserSelect(false); } } registerEditorTypes(types) { if (this.#editorTypes) { return; } this.#editorTypes = types; for (const editorType of this.#editorTypes) { this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate); } } getId() { return this.#idManager.getId(); } get currentLayer() { return this.#allLayers.get(this.#currentPageIndex); } getLayer(pageIndex) { return this.#allLayers.get(pageIndex); } get currentPageIndex() { return this.#currentPageIndex; } addLayer(layer) { this.#allLayers.set(layer.pageIndex, layer); if (this.#isEnabled) { layer.enable(); } else { layer.disable(); } } removeLayer(layer) { this.#allLayers.delete(layer.pageIndex); } updateMode(mode, editId = null, isFromKeyboard = false) { if (this.#mode === mode) { return; } this.#mode = mode; if (mode === _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorType.NONE) { this.setEditingState(false); this.#disableAll(); return; } this.setEditingState(true); this.#enableAll(); this.unselectAll(); for (const layer of this.#allLayers.values()) { layer.updateMode(mode); } if (!editId && isFromKeyboard) { this.addNewEditorFromKeyboard(); return; } if (!editId) { return; } for (const editor of this.#allEditors.values()) { if (editor.annotationElementId === editId) { this.setSelected(editor); editor.enterInEditMode(); break; } } } addNewEditorFromKeyboard() { this.currentLayer.addNewEditor(); } updateToolbar(mode) { if (mode === this.#mode) { return; } this._eventBus.dispatch("switchannotationeditormode", { source: this, mode }); } updateParams(type, value) { if (!this.#editorTypes) { return; } if (type === _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorParamsType.CREATE) { this.currentLayer.addNewEditor(); return; } for (const editor of this.#selectedEditors) { editor.updateParams(type, value); } for (const editorType of this.#editorTypes) { editorType.updateDefaultParams(type, value); } } enableWaiting(mustWait = false) { if (this.#isWaiting === mustWait) { return; } this.#isWaiting = mustWait; for (const layer of this.#allLayers.values()) { if (mustWait) { layer.disableClick(); } else { layer.enableClick(); } layer.div.classList.toggle("waiting", mustWait); } } #enableAll() { if (!this.#isEnabled) { this.#isEnabled = true; for (const layer of this.#allLayers.values()) { layer.enable(); } } } #disableAll() { this.unselectAll(); if (this.#isEnabled) { this.#isEnabled = false; for (const layer of this.#allLayers.values()) { layer.disable(); } } } getEditors(pageIndex) { const editors = []; for (const editor of this.#allEditors.values()) { if (editor.pageIndex === pageIndex) { editors.push(editor); } } return editors; } getEditor(id) { return this.#allEditors.get(id); } addEditor(editor) { this.#allEditors.set(editor.id, editor); } removeEditor(editor) { if (editor.div.contains(document.activeElement)) { if (this.#focusMainContainerTimeoutId) { clearTimeout(this.#focusMainContainerTimeoutId); } this.#focusMainContainerTimeoutId = setTimeout(() => { this.focusMainContainer(); this.#focusMainContainerTimeoutId = null; }, 0); } this.#allEditors.delete(editor.id); this.unselect(editor); if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) { this.#annotationStorage?.remove(editor.id); } } addDeletedAnnotationElement(editor) { this.#deletedAnnotationsElementIds.add(editor.annotationElementId); editor.deleted = true; } isDeletedAnnotationElement(annotationElementId) { return this.#deletedAnnotationsElementIds.has(annotationElementId); } removeDeletedAnnotationElement(editor) { this.#deletedAnnotationsElementIds.delete(editor.annotationElementId); editor.deleted = false; } #addEditorToLayer(editor) { const layer = this.#allLayers.get(editor.pageIndex); if (layer) { layer.addOrRebuild(editor); } else { this.addEditor(editor); } } setActiveEditor(editor) { if (this.#activeEditor === editor) { return; } this.#activeEditor = editor; if (editor) { this.#dispatchUpdateUI(editor.propertiesToUpdate); } } toggleSelected(editor) { if (this.#selectedEditors.has(editor)) { this.#selectedEditors.delete(editor); editor.unselect(); this.#dispatchUpdateStates({ hasSelectedEditor: this.hasSelection }); return; } this.#selectedEditors.add(editor); editor.select(); this.#dispatchUpdateUI(editor.propertiesToUpdate); this.#dispatchUpdateStates({ hasSelectedEditor: true }); } setSelected(editor) { for (const ed of this.#selectedEditors) { if (ed !== editor) { ed.unselect(); } } this.#selectedEditors.clear(); this.#selectedEditors.add(editor); editor.select(); this.#dispatchUpdateUI(editor.propertiesToUpdate); this.#dispatchUpdateStates({ hasSelectedEditor: true }); } isSelected(editor) { return this.#selectedEditors.has(editor); } get firstSelectedEditor() { return this.#selectedEditors.values().next().value; } unselect(editor) { editor.unselect(); this.#selectedEditors.delete(editor); this.#dispatchUpdateStates({ hasSelectedEditor: this.hasSelection }); } get hasSelection() { return this.#selectedEditors.size !== 0; } get isEnterHandled() { return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled; } undo() { this.#commandManager.undo(); this.#dispatchUpdateStates({ hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(), hasSomethingToRedo: true, isEmpty: this.#isEmpty() }); } redo() { this.#commandManager.redo(); this.#dispatchUpdateStates({ hasSomethingToUndo: true, hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(), isEmpty: this.#isEmpty() }); } addCommands(params) { this.#commandManager.add(params); this.#dispatchUpdateStates({ hasSomethingToUndo: true, hasSomethingToRedo: false, isEmpty: this.#isEmpty() }); } #isEmpty() { if (this.#allEditors.size === 0) { return true; } if (this.#allEditors.size === 1) { for (const editor of this.#allEditors.values()) { return editor.isEmpty(); } } return false; } delete() { this.commitOrRemove(); if (!this.hasSelection) { return; } const editors = [...this.#selectedEditors]; const cmd = () => { for (const editor of editors) { editor.remove(); } }; const undo = () => { for (const editor of editors) { this.#addEditorToLayer(editor); } }; this.addCommands({ cmd, undo, mustExec: true }); } commitOrRemove() { this.#activeEditor?.commitOrRemove(); } hasSomethingToControl() { return this.#activeEditor || this.hasSelection; } #selectEditors(editors) { this.#selectedEditors.clear(); for (const editor of editors) { if (editor.isEmpty()) { continue; } this.#selectedEditors.add(editor); editor.select(); } this.#dispatchUpdateStates({ hasSelectedEditor: true }); } selectAll() { for (const editor of this.#selectedEditors) { editor.commit(); } this.#selectEditors(this.#allEditors.values()); } unselectAll() { if (this.#activeEditor) { this.#activeEditor.commitOrRemove(); return; } if (!this.hasSelection) { return; } for (const editor of this.#selectedEditors) { editor.unselect(); } this.#selectedEditors.clear(); this.#dispatchUpdateStates({ hasSelectedEditor: false }); } translateSelectedEditors(x, y, noCommit = false) { if (!noCommit) { this.commitOrRemove(); } if (!this.hasSelection) { return; } this.#translation[0] += x; this.#translation[1] += y; const [totalX, totalY] = this.#translation; const editors = [...this.#selectedEditors]; const TIME_TO_WAIT = 1000; if (this.#translationTimeoutId) { clearTimeout(this.#translationTimeoutId); } this.#translationTimeoutId = setTimeout(() => { this.#translationTimeoutId = null; this.#translation[0] = this.#translation[1] = 0; this.addCommands({ cmd: () => { for (const editor of editors) { if (this.#allEditors.has(editor.id)) { editor.translateInPage(totalX, totalY); } } }, undo: () => { for (const editor of editors) { if (this.#allEditors.has(editor.id)) { editor.translateInPage(-totalX, -totalY); } } }, mustExec: false }); }, TIME_TO_WAIT); for (const editor of editors) { editor.translateInPage(x, y); } } setUpDragSession() { if (!this.hasSelection) { return; } this.disableUserSelect(true); this.#draggingEditors = new Map(); for (const editor of this.#selectedEditors) { this.#draggingEditors.set(editor, { savedX: editor.x, savedY: editor.y, savedPageIndex: editor.pageIndex, newX: 0, newY: 0, newPageIndex: -1 }); } } endDragSession() { if (!this.#draggingEditors) { return false; } this.disableUserSelect(false); const map = this.#draggingEditors; this.#draggingEditors = null; let mustBeAddedInUndoStack = false; for (const [{ x, y, pageIndex }, value] of map) { value.newX = x; value.newY = y; value.newPageIndex = pageIndex; mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex; } if (!mustBeAddedInUndoStack) { return false; } const move = (editor, x, y, pageIndex) => { if (this.#allEditors.has(editor.id)) { const parent = this.#allLayers.get(pageIndex); if (parent) { editor._setParentAndPosition(parent, x, y); } else { editor.pageIndex = pageIndex; editor.x = x; editor.y = y; } } }; this.addCommands({ cmd: () => { for (const [editor, { newX, newY, newPageIndex }] of map) { move(editor, newX, newY, newPageIndex); } }, undo: () => { for (const [editor, { savedX, savedY, savedPageIndex }] of map) { move(editor, savedX, savedY, savedPageIndex); } }, mustExec: true }); return true; } dragSelectedEditors(tx, ty) { if (!this.#draggingEditors) { return; } for (const editor of this.#draggingEditors.keys()) { editor.drag(tx, ty); } } rebuild(editor) { if (editor.parent === null) { const parent = this.getLayer(editor.pageIndex); if (parent) { parent.changeParent(editor); parent.addOrRebuild(editor); } else { this.addEditor(editor); this.addToAnnotationStorage(editor); editor.rebuild(); } } else { editor.parent.addOrRebuild(editor); } } get isEditorHandlingKeyboard() { return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents(); } isActive(editor) { return this.#activeEditor === editor; } getActive() { return this.#activeEditor; } getMode() { return this.#mode; } get imageManager() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "imageManager", new ImageManager()); } } /***/ }), /***/ 171: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ PDFFetchStream: () => (/* binding */ PDFFetchStream) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _network_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(253); ; function createFetchOptions(headers, withCredentials, abortController) { return { method: "GET", headers, signal: abortController.signal, mode: "cors", credentials: withCredentials ? "include" : "same-origin", redirect: "follow" }; } function createHeaders(httpHeaders) { const headers = new Headers(); for (const property in httpHeaders) { const value = httpHeaders[property]; if (value === undefined) { continue; } headers.append(property, value); } return headers; } function getArrayBuffer(val) { if (val instanceof Uint8Array) { return val.buffer; } if (val instanceof ArrayBuffer) { return val; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`getArrayBuffer - unexpected data format: ${val}`); return new Uint8Array(val).buffer; } class PDFFetchStream { constructor(source) { this.source = source; this.isHttp = /^https?:/i.test(source.url); this.httpHeaders = this.isHttp && source.httpHeaders || {}; this._fullRequestReader = null; this._rangeRequestReaders = []; } get _progressiveDataLength() { return this._fullRequestReader?._loaded ?? 0; } getFullReader() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!this._fullRequestReader, "PDFFetchStream.getFullReader can only be called once."); this._fullRequestReader = new PDFFetchStreamReader(this); return this._fullRequestReader; } getRangeReader(begin, end) { if (end <= this._progressiveDataLength) { return null; } const reader = new PDFFetchStreamRangeReader(this, begin, end); this._rangeRequestReaders.push(reader); return reader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeRequestReaders.slice(0)) { reader.cancel(reason); } } } class PDFFetchStreamReader { constructor(stream) { this._stream = stream; this._reader = null; this._loaded = 0; this._filename = null; const source = stream.source; this._withCredentials = source.withCredentials || false; this._contentLength = source.length; this._headersCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._disableRange = source.disableRange || false; this._rangeChunkSize = source.rangeChunkSize; if (!this._rangeChunkSize && !this._disableRange) { this._disableRange = true; } this._abortController = new AbortController(); this._isStreamingSupported = !source.disableStream; this._isRangeSupported = !source.disableRange; this._headers = createHeaders(this._stream.httpHeaders); const url = source.url; fetch(url, createFetchOptions(this._headers, this._withCredentials, this._abortController)).then(response => { if (!(0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.validateResponseStatus)(response.status)) { throw (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.createResponseStatusError)(response.status, url); } this._reader = response.body.getReader(); this._headersCapability.resolve(); const getResponseHeader = name => { return response.headers.get(name); }; const { allowRangeRequests, suggestedLength } = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.validateRangeRequestCapabilities)({ getResponseHeader, isHttp: this._stream.isHttp, rangeChunkSize: this._rangeChunkSize, disableRange: this._disableRange }); this._isRangeSupported = allowRangeRequests; this._contentLength = suggestedLength || this._contentLength; this._filename = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.extractFilenameFromHeader)(getResponseHeader); if (!this._isStreamingSupported && this._isRangeSupported) { this.cancel(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException("Streaming is disabled.")); } }).catch(this._headersCapability.reject); this.onProgress = null; } get headersReady() { return this._headersCapability.promise; } get filename() { return this._filename; } get contentLength() { return this._contentLength; } get isRangeSupported() { return this._isRangeSupported; } get isStreamingSupported() { return this._isStreamingSupported; } async read() { await this._headersCapability.promise; const { value, done } = await this._reader.read(); if (done) { return { value, done }; } this._loaded += value.byteLength; this.onProgress?.({ loaded: this._loaded, total: this._contentLength }); return { value: getArrayBuffer(value), done: false }; } cancel(reason) { this._reader?.cancel(reason); this._abortController.abort(); } } class PDFFetchStreamRangeReader { constructor(stream, begin, end) { this._stream = stream; this._reader = null; this._loaded = 0; const source = stream.source; this._withCredentials = source.withCredentials || false; this._readCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._isStreamingSupported = !source.disableStream; this._abortController = new AbortController(); this._headers = createHeaders(this._stream.httpHeaders); this._headers.append("Range", `bytes=${begin}-${end - 1}`); const url = source.url; fetch(url, createFetchOptions(this._headers, this._withCredentials, this._abortController)).then(response => { if (!(0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.validateResponseStatus)(response.status)) { throw (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.createResponseStatusError)(response.status, url); } this._readCapability.resolve(); this._reader = response.body.getReader(); }).catch(this._readCapability.reject); this.onProgress = null; } get isStreamingSupported() { return this._isStreamingSupported; } async read() { await this._readCapability.promise; const { value, done } = await this._reader.read(); if (done) { return { value, done }; } this._loaded += value.byteLength; this.onProgress?.({ loaded: this._loaded }); return { value: getArrayBuffer(value), done: false }; } cancel(reason) { this._reader?.cancel(reason); this._abortController.abort(); } } /***/ }), /***/ 742: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ FontFaceObject: () => (/* binding */ FontFaceObject), /* harmony export */ FontLoader: () => (/* binding */ FontLoader) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); class FontLoader { #systemFonts = new Set(); constructor({ ownerDocument = globalThis.document, styleElement = null }) { this._document = ownerDocument; this.nativeFontFaces = new Set(); this.styleElement = null; this.loadingRequests = []; this.loadTestFontId = 0; } addNativeFontFace(nativeFontFace) { this.nativeFontFaces.add(nativeFontFace); this._document.fonts.add(nativeFontFace); } removeNativeFontFace(nativeFontFace) { this.nativeFontFaces.delete(nativeFontFace); this._document.fonts.delete(nativeFontFace); } insertRule(rule) { if (!this.styleElement) { this.styleElement = this._document.createElement("style"); this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement); } const styleSheet = this.styleElement.sheet; styleSheet.insertRule(rule, styleSheet.cssRules.length); } clear() { for (const nativeFontFace of this.nativeFontFaces) { this._document.fonts.delete(nativeFontFace); } this.nativeFontFaces.clear(); this.#systemFonts.clear(); if (this.styleElement) { this.styleElement.remove(); this.styleElement = null; } } async loadSystemFont({ systemFontInfo: info, _inspectFont }) { if (!info || this.#systemFonts.has(info.loadedName)) { return; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!this.disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set."); if (this.isFontLoadingAPISupported) { const { loadedName, src, style } = info; const fontFace = new FontFace(loadedName, src, style); this.addNativeFontFace(fontFace); try { await fontFace.load(); this.#systemFonts.add(loadedName); _inspectFont?.(info); } catch { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`); this.removeNativeFontFace(fontFace); } return; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Not implemented: loadSystemFont without the Font Loading API."); } async bind(font) { if (font.attached || font.missingFile && !font.systemFontInfo) { return; } font.attached = true; if (font.systemFontInfo) { await this.loadSystemFont(font); return; } if (this.isFontLoadingAPISupported) { const nativeFontFace = font.createNativeFontFace(); if (nativeFontFace) { this.addNativeFontFace(nativeFontFace); try { await nativeFontFace.loaded; } catch (ex) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Failed to load font '${nativeFontFace.family}': '${ex}'.`); font.disableFontFace = true; throw ex; } } return; } const rule = font.createFontFaceRule(); if (rule) { this.insertRule(rule); if (this.isSyncFontLoadingSupported) { return; } await new Promise(resolve => { const request = this._queueLoadingCallback(resolve); this._prepareFontLoadEvent(font, request); }); } } get isFontLoadingAPISupported() { const hasFonts = !!this._document?.fonts; return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "isFontLoadingAPISupported", hasFonts); } get isSyncFontLoadingSupported() { let supported = false; if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS) { supported = true; } else if (typeof navigator !== "undefined" && typeof navigator?.userAgent === "string" && /Mozilla\/5.0.*?rv:\d+.*? Gecko/.test(navigator.userAgent)) { supported = true; } return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "isSyncFontLoadingSupported", supported); } _queueLoadingCallback(callback) { function completeRequest() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!request.done, "completeRequest() cannot be called twice."); request.done = true; while (loadingRequests.length > 0 && loadingRequests[0].done) { const otherRequest = loadingRequests.shift(); setTimeout(otherRequest.callback, 0); } } const { loadingRequests } = this; const request = { done: false, complete: completeRequest, callback }; loadingRequests.push(request); return request; } get _loadTestFont() { const testFont = atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQA" + "FQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAA" + "ALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgA" + "AAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1" + "AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD" + "6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACM" + "AooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4D" + "IP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAA" + "AAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUA" + "AQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgAB" + "AAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABY" + "AAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAA" + "AC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAA" + "AAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQAC" + "AQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3" + "Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTj" + "FQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA=="); return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow)(this, "_loadTestFont", testFont); } _prepareFontLoadEvent(font, request) { function int32(data, offset) { return data.charCodeAt(offset) << 24 | data.charCodeAt(offset + 1) << 16 | data.charCodeAt(offset + 2) << 8 | data.charCodeAt(offset + 3) & 0xff; } function spliceString(s, offset, remove, insert) { const chunk1 = s.substring(0, offset); const chunk2 = s.substring(offset + remove); return chunk1 + insert + chunk2; } let i, ii; const canvas = this._document.createElement("canvas"); canvas.width = 1; canvas.height = 1; const ctx = canvas.getContext("2d"); let called = 0; function isFontReady(name, callback) { if (++called > 30) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)("Load test font never loaded."); callback(); return; } ctx.font = "30px " + name; ctx.fillText(".", 0, 20); const imageData = ctx.getImageData(0, 0, 1, 1); if (imageData.data[3] > 0) { callback(); return; } setTimeout(isFontReady.bind(null, name, callback)); } const loadTestFontId = `lt${Date.now()}${this.loadTestFontId++}`; let data = this._loadTestFont; const COMMENT_OFFSET = 976; data = spliceString(data, COMMENT_OFFSET, loadTestFontId.length, loadTestFontId); const CFF_CHECKSUM_OFFSET = 16; const XXXX_VALUE = 0x58585858; let checksum = int32(data, CFF_CHECKSUM_OFFSET); for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) { checksum = checksum - XXXX_VALUE + int32(loadTestFontId, i) | 0; } if (i < loadTestFontId.length) { checksum = checksum - XXXX_VALUE + int32(loadTestFontId + "XXX", i) | 0; } data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.string32)(checksum)); const url = `url(data:font/opentype;base64,${btoa(data)});`; const rule = `@font-face {font-family:"${loadTestFontId}";src:${url}}`; this.insertRule(rule); const div = this._document.createElement("div"); div.style.visibility = "hidden"; div.style.width = div.style.height = "10px"; div.style.position = "absolute"; div.style.top = div.style.left = "0px"; for (const name of [font.loadedName, loadTestFontId]) { const span = this._document.createElement("span"); span.textContent = "Hi"; span.style.fontFamily = name; div.append(span); } this._document.body.append(div); isFontReady(loadTestFontId, () => { div.remove(); request.complete(); }); } } class FontFaceObject { constructor(translatedData, { isEvalSupported = true, disableFontFace = false, ignoreErrors = false, inspectFont = null }) { this.compiledGlyphs = Object.create(null); for (const i in translatedData) { this[i] = translatedData[i]; } this.isEvalSupported = isEvalSupported !== false; this.disableFontFace = disableFontFace === true; this.ignoreErrors = ignoreErrors === true; this._inspectFont = inspectFont; } createNativeFontFace() { if (!this.data || this.disableFontFace) { return null; } let nativeFontFace; if (!this.cssFontInfo) { nativeFontFace = new FontFace(this.loadedName, this.data, {}); } else { const css = { weight: this.cssFontInfo.fontWeight }; if (this.cssFontInfo.italicAngle) { css.style = `oblique ${this.cssFontInfo.italicAngle}deg`; } nativeFontFace = new FontFace(this.cssFontInfo.fontFamily, this.data, css); } this._inspectFont?.(this); return nativeFontFace; } createFontFaceRule() { if (!this.data || this.disableFontFace) { return null; } const data = (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.bytesToString)(this.data); const url = `url(data:${this.mimetype};base64,${btoa(data)});`; let rule; if (!this.cssFontInfo) { rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`; } else { let css = `font-weight: ${this.cssFontInfo.fontWeight};`; if (this.cssFontInfo.italicAngle) { css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`; } rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`; } this._inspectFont?.(this, url); return rule; } getPathGenerator(objs, character) { if (this.compiledGlyphs[character] !== undefined) { return this.compiledGlyphs[character]; } let cmds; try { cmds = objs.get(this.loadedName + "_path_" + character); } catch (ex) { if (!this.ignoreErrors) { throw ex; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`getPathGenerator - ignoring character: "${ex}".`); return this.compiledGlyphs[character] = function (c, size) {}; } if (this.isEvalSupported && _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.FeatureTest.isEvalSupported) { const jsBuf = []; for (const current of cmds) { const args = current.args !== undefined ? current.args.join(",") : ""; jsBuf.push("c.", current.cmd, "(", args, ");\n"); } return this.compiledGlyphs[character] = new Function("c", "size", jsBuf.join("")); } return this.compiledGlyphs[character] = function (c, size) { for (const current of cmds) { if (current.cmd === "scale") { current.args = [size, -size]; } c[current.cmd].apply(c, current.args); } }; } } /***/ }), /***/ 472: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Metadata: () => (/* binding */ Metadata) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); class Metadata { #metadataMap; #data; constructor({ parsedData, rawData }) { this.#metadataMap = parsedData; this.#data = rawData; } getRaw() { return this.#data; } get(name) { return this.#metadataMap.get(name) ?? null; } getAll() { return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.objectFromMap)(this.#metadataMap); } has(name) { return this.#metadataMap.has(name); } } /***/ }), /***/ 474: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ PDFNetworkStream: () => (/* binding */ PDFNetworkStream) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _network_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(253); ; const OK_RESPONSE = 200; const PARTIAL_CONTENT_RESPONSE = 206; function getArrayBuffer(xhr) { const data = xhr.response; if (typeof data !== "string") { return data; } return (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.stringToBytes)(data).buffer; } class NetworkManager { constructor(url, args = {}) { this.url = url; this.isHttp = /^https?:/i.test(url); this.httpHeaders = this.isHttp && args.httpHeaders || Object.create(null); this.withCredentials = args.withCredentials || false; this.currXhrId = 0; this.pendingRequests = Object.create(null); } requestRange(begin, end, listeners) { const args = { begin, end }; for (const prop in listeners) { args[prop] = listeners[prop]; } return this.request(args); } requestFull(listeners) { return this.request(listeners); } request(args) { const xhr = new XMLHttpRequest(); const xhrId = this.currXhrId++; const pendingRequest = this.pendingRequests[xhrId] = { xhr }; xhr.open("GET", this.url); xhr.withCredentials = this.withCredentials; for (const property in this.httpHeaders) { const value = this.httpHeaders[property]; if (value === undefined) { continue; } xhr.setRequestHeader(property, value); } if (this.isHttp && "begin" in args && "end" in args) { xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`); pendingRequest.expectedStatus = PARTIAL_CONTENT_RESPONSE; } else { pendingRequest.expectedStatus = OK_RESPONSE; } xhr.responseType = "arraybuffer"; if (args.onError) { xhr.onerror = function (evt) { args.onError(xhr.status); }; } xhr.onreadystatechange = this.onStateChange.bind(this, xhrId); xhr.onprogress = this.onProgress.bind(this, xhrId); pendingRequest.onHeadersReceived = args.onHeadersReceived; pendingRequest.onDone = args.onDone; pendingRequest.onError = args.onError; pendingRequest.onProgress = args.onProgress; xhr.send(null); return xhrId; } onProgress(xhrId, evt) { const pendingRequest = this.pendingRequests[xhrId]; if (!pendingRequest) { return; } pendingRequest.onProgress?.(evt); } onStateChange(xhrId, evt) { const pendingRequest = this.pendingRequests[xhrId]; if (!pendingRequest) { return; } const xhr = pendingRequest.xhr; if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) { pendingRequest.onHeadersReceived(); delete pendingRequest.onHeadersReceived; } if (xhr.readyState !== 4) { return; } if (!(xhrId in this.pendingRequests)) { return; } delete this.pendingRequests[xhrId]; if (xhr.status === 0 && this.isHttp) { pendingRequest.onError?.(xhr.status); return; } const xhrStatus = xhr.status || OK_RESPONSE; const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE; if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) { pendingRequest.onError?.(xhr.status); return; } const chunk = getArrayBuffer(xhr); if (xhrStatus === PARTIAL_CONTENT_RESPONSE) { const rangeHeader = xhr.getResponseHeader("Content-Range"); const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader); pendingRequest.onDone({ begin: parseInt(matches[1], 10), chunk }); } else if (chunk) { pendingRequest.onDone({ begin: 0, chunk }); } else { pendingRequest.onError?.(xhr.status); } } getRequestXhr(xhrId) { return this.pendingRequests[xhrId].xhr; } isPendingRequest(xhrId) { return xhrId in this.pendingRequests; } abortRequest(xhrId) { const xhr = this.pendingRequests[xhrId].xhr; delete this.pendingRequests[xhrId]; xhr.abort(); } } class PDFNetworkStream { constructor(source) { this._source = source; this._manager = new NetworkManager(source.url, { httpHeaders: source.httpHeaders, withCredentials: source.withCredentials }); this._rangeChunkSize = source.rangeChunkSize; this._fullRequestReader = null; this._rangeRequestReaders = []; } _onRangeRequestReaderClosed(reader) { const i = this._rangeRequestReaders.indexOf(reader); if (i >= 0) { this._rangeRequestReaders.splice(i, 1); } } getFullReader() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!this._fullRequestReader, "PDFNetworkStream.getFullReader can only be called once."); this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._source); return this._fullRequestReader; } getRangeReader(begin, end) { const reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end); reader.onClosed = this._onRangeRequestReaderClosed.bind(this); this._rangeRequestReaders.push(reader); return reader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeRequestReaders.slice(0)) { reader.cancel(reason); } } } class PDFNetworkStreamFullRequestReader { constructor(manager, source) { this._manager = manager; const args = { onHeadersReceived: this._onHeadersReceived.bind(this), onDone: this._onDone.bind(this), onError: this._onError.bind(this), onProgress: this._onProgress.bind(this) }; this._url = source.url; this._fullRequestId = manager.requestFull(args); this._headersReceivedCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._disableRange = source.disableRange || false; this._contentLength = source.length; this._rangeChunkSize = source.rangeChunkSize; if (!this._rangeChunkSize && !this._disableRange) { this._disableRange = true; } this._isStreamingSupported = false; this._isRangeSupported = false; this._cachedChunks = []; this._requests = []; this._done = false; this._storedError = undefined; this._filename = null; this.onProgress = null; } _onHeadersReceived() { const fullRequestXhrId = this._fullRequestId; const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId); const getResponseHeader = name => { return fullRequestXhr.getResponseHeader(name); }; const { allowRangeRequests, suggestedLength } = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.validateRangeRequestCapabilities)({ getResponseHeader, isHttp: this._manager.isHttp, rangeChunkSize: this._rangeChunkSize, disableRange: this._disableRange }); if (allowRangeRequests) { this._isRangeSupported = true; } this._contentLength = suggestedLength || this._contentLength; this._filename = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.extractFilenameFromHeader)(getResponseHeader); if (this._isRangeSupported) { this._manager.abortRequest(fullRequestXhrId); } this._headersReceivedCapability.resolve(); } _onDone(data) { if (data) { if (this._requests.length > 0) { const requestCapability = this._requests.shift(); requestCapability.resolve({ value: data.chunk, done: false }); } else { this._cachedChunks.push(data.chunk); } } this._done = true; if (this._cachedChunks.length > 0) { return; } for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; } _onError(status) { this._storedError = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.createResponseStatusError)(status, this._url); this._headersReceivedCapability.reject(this._storedError); for (const requestCapability of this._requests) { requestCapability.reject(this._storedError); } this._requests.length = 0; this._cachedChunks.length = 0; } _onProgress(evt) { this.onProgress?.({ loaded: evt.loaded, total: evt.lengthComputable ? evt.total : this._contentLength }); } get filename() { return this._filename; } get isRangeSupported() { return this._isRangeSupported; } get isStreamingSupported() { return this._isStreamingSupported; } get contentLength() { return this._contentLength; } get headersReady() { return this._headersReceivedCapability.promise; } async read() { if (this._storedError) { throw this._storedError; } if (this._cachedChunks.length > 0) { const chunk = this._cachedChunks.shift(); return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; this._headersReceivedCapability.reject(reason); for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; if (this._manager.isPendingRequest(this._fullRequestId)) { this._manager.abortRequest(this._fullRequestId); } this._fullRequestReader = null; } } class PDFNetworkStreamRangeRequestReader { constructor(manager, begin, end) { this._manager = manager; const args = { onDone: this._onDone.bind(this), onError: this._onError.bind(this), onProgress: this._onProgress.bind(this) }; this._url = manager.url; this._requestId = manager.requestRange(begin, end, args); this._requests = []; this._queuedChunk = null; this._done = false; this._storedError = undefined; this.onProgress = null; this.onClosed = null; } _close() { this.onClosed?.(this); } _onDone(data) { const chunk = data.chunk; if (this._requests.length > 0) { const requestCapability = this._requests.shift(); requestCapability.resolve({ value: chunk, done: false }); } else { this._queuedChunk = chunk; } this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; this._close(); } _onError(status) { this._storedError = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.createResponseStatusError)(status, this._url); for (const requestCapability of this._requests) { requestCapability.reject(this._storedError); } this._requests.length = 0; this._queuedChunk = null; } _onProgress(evt) { if (!this.isStreamingSupported) { this.onProgress?.({ loaded: evt.loaded }); } } get isStreamingSupported() { return false; } async read() { if (this._storedError) { throw this._storedError; } if (this._queuedChunk !== null) { const chunk = this._queuedChunk; this._queuedChunk = null; return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; if (this._manager.isPendingRequest(this._requestId)) { this._manager.abortRequest(this._requestId); } this._close(); } } /***/ }), /***/ 253: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { // EXPORTS __webpack_require__.d(__webpack_exports__, { createResponseStatusError: () => (/* binding */ createResponseStatusError), extractFilenameFromHeader: () => (/* binding */ extractFilenameFromHeader), validateRangeRequestCapabilities: () => (/* binding */ validateRangeRequestCapabilities), validateResponseStatus: () => (/* binding */ validateResponseStatus) }); // EXTERNAL MODULE: ./src/shared/util.js var util = __webpack_require__(266); ;// CONCATENATED MODULE: ./src/display/content_disposition.js function getFilenameFromContentDispositionHeader(contentDisposition) { let needsEncodingFixup = true; let tmp = toParamRegExp("filename\\*", "i").exec(contentDisposition); if (tmp) { tmp = tmp[1]; let filename = rfc2616unquote(tmp); filename = unescape(filename); filename = rfc5987decode(filename); filename = rfc2047decode(filename); return fixupEncoding(filename); } tmp = rfc2231getparam(contentDisposition); if (tmp) { const filename = rfc2047decode(tmp); return fixupEncoding(filename); } tmp = toParamRegExp("filename", "i").exec(contentDisposition); if (tmp) { tmp = tmp[1]; let filename = rfc2616unquote(tmp); filename = rfc2047decode(filename); return fixupEncoding(filename); } function toParamRegExp(attributePattern, flags) { return new RegExp("(?:^|;)\\s*" + attributePattern + "\\s*=\\s*" + "(" + '[^";\\s][^;\\s]*' + "|" + '"(?:[^"\\\\]|\\\\"?)+"?' + ")", flags); } function textdecode(encoding, value) { if (encoding) { if (!/^[\x00-\xFF]+$/.test(value)) { return value; } try { const decoder = new TextDecoder(encoding, { fatal: true }); const buffer = (0,util.stringToBytes)(value); value = decoder.decode(buffer); needsEncodingFixup = false; } catch {} } return value; } function fixupEncoding(value) { if (needsEncodingFixup && /[\x80-\xff]/.test(value)) { value = textdecode("utf-8", value); if (needsEncodingFixup) { value = textdecode("iso-8859-1", value); } } return value; } function rfc2231getparam(contentDispositionStr) { const matches = []; let match; const iter = toParamRegExp("filename\\*((?!0\\d)\\d+)(\\*?)", "ig"); while ((match = iter.exec(contentDispositionStr)) !== null) { let [, n, quot, part] = match; n = parseInt(n, 10); if (n in matches) { if (n === 0) { break; } continue; } matches[n] = [quot, part]; } const parts = []; for (let n = 0; n < matches.length; ++n) { if (!(n in matches)) { break; } let [quot, part] = matches[n]; part = rfc2616unquote(part); if (quot) { part = unescape(part); if (n === 0) { part = rfc5987decode(part); } } parts.push(part); } return parts.join(""); } function rfc2616unquote(value) { if (value.startsWith('"')) { const parts = value.slice(1).split('\\"'); for (let i = 0; i < parts.length; ++i) { const quotindex = parts[i].indexOf('"'); if (quotindex !== -1) { parts[i] = parts[i].slice(0, quotindex); parts.length = i + 1; } parts[i] = parts[i].replaceAll(/\\(.)/g, "$1"); } value = parts.join('"'); } return value; } function rfc5987decode(extvalue) { const encodingend = extvalue.indexOf("'"); if (encodingend === -1) { return extvalue; } const encoding = extvalue.slice(0, encodingend); const langvalue = extvalue.slice(encodingend + 1); const value = langvalue.replace(/^[^']*'/, ""); return textdecode(encoding, value); } function rfc2047decode(value) { if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) { return value; } return value.replaceAll(/=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g, function (matches, charset, encoding, text) { if (encoding === "q" || encoding === "Q") { text = text.replaceAll("_", " "); text = text.replaceAll(/=([0-9a-fA-F]{2})/g, function (match, hex) { return String.fromCharCode(parseInt(hex, 16)); }); return textdecode(charset, text); } try { text = atob(text); } catch {} return textdecode(charset, text); }); } return ""; } // EXTERNAL MODULE: ./src/display/display_utils.js var display_utils = __webpack_require__(473); ;// CONCATENATED MODULE: ./src/display/network_utils.js function validateRangeRequestCapabilities({ getResponseHeader, isHttp, rangeChunkSize, disableRange }) { const returnValues = { allowRangeRequests: false, suggestedLength: undefined }; const length = parseInt(getResponseHeader("Content-Length"), 10); if (!Number.isInteger(length)) { return returnValues; } returnValues.suggestedLength = length; if (length <= 2 * rangeChunkSize) { return returnValues; } if (disableRange || !isHttp) { return returnValues; } if (getResponseHeader("Accept-Ranges") !== "bytes") { return returnValues; } const contentEncoding = getResponseHeader("Content-Encoding") || "identity"; if (contentEncoding !== "identity") { return returnValues; } returnValues.allowRangeRequests = true; return returnValues; } function extractFilenameFromHeader(getResponseHeader) { const contentDisposition = getResponseHeader("Content-Disposition"); if (contentDisposition) { let filename = getFilenameFromContentDispositionHeader(contentDisposition); if (filename.includes("%")) { try { filename = decodeURIComponent(filename); } catch {} } if ((0,display_utils.isPdfFile)(filename)) { return filename; } } return null; } function createResponseStatusError(status, url) { if (status === 404 || status === 0 && url.startsWith("file:")) { return new util.MissingPDFException('Missing PDF "' + url + '".'); } return new util.UnexpectedResponseException(`Unexpected server response (${status}) while retrieving PDF "${url}".`, status); } function validateResponseStatus(status) { return status === 200 || status === 206; } /***/ }), /***/ 498: /***/ ((__webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ PDFNodeStream: () => (/* binding */ PDFNodeStream) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _network_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(253); ; let fs, http, https, url; if (_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.isNodeJS) { fs = await import(/* webpackIgnore: true */ "fs"); http = await import(/* webpackIgnore: true */ "http"); https = await import(/* webpackIgnore: true */ "https"); url = await import(/* webpackIgnore: true */ "url"); } const fileUriRegex = /^file:\/\/\/[a-zA-Z]:\//; function parseUrl(sourceUrl) { const parsedUrl = url.parse(sourceUrl); if (parsedUrl.protocol === "file:" || parsedUrl.host) { return parsedUrl; } if (/^[a-z]:[/\\]/i.test(sourceUrl)) { return url.parse(`file:///${sourceUrl}`); } if (!parsedUrl.host) { parsedUrl.protocol = "file:"; } return parsedUrl; } class PDFNodeStream { constructor(source) { this.source = source; this.url = parseUrl(source.url); this.isHttp = this.url.protocol === "http:" || this.url.protocol === "https:"; this.isFsUrl = this.url.protocol === "file:"; this.httpHeaders = this.isHttp && source.httpHeaders || {}; this._fullRequestReader = null; this._rangeRequestReaders = []; } get _progressiveDataLength() { return this._fullRequestReader?._loaded ?? 0; } getFullReader() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!this._fullRequestReader, "PDFNodeStream.getFullReader can only be called once."); this._fullRequestReader = this.isFsUrl ? new PDFNodeStreamFsFullReader(this) : new PDFNodeStreamFullReader(this); return this._fullRequestReader; } getRangeReader(start, end) { if (end <= this._progressiveDataLength) { return null; } const rangeReader = this.isFsUrl ? new PDFNodeStreamFsRangeReader(this, start, end) : new PDFNodeStreamRangeReader(this, start, end); this._rangeRequestReaders.push(rangeReader); return rangeReader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeRequestReaders.slice(0)) { reader.cancel(reason); } } } class BaseFullReader { constructor(stream) { this._url = stream.url; this._done = false; this._storedError = null; this.onProgress = null; const source = stream.source; this._contentLength = source.length; this._loaded = 0; this._filename = null; this._disableRange = source.disableRange || false; this._rangeChunkSize = source.rangeChunkSize; if (!this._rangeChunkSize && !this._disableRange) { this._disableRange = true; } this._isStreamingSupported = !source.disableStream; this._isRangeSupported = !source.disableRange; this._readableStream = null; this._readCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._headersCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); } get headersReady() { return this._headersCapability.promise; } get filename() { return this._filename; } get contentLength() { return this._contentLength; } get isRangeSupported() { return this._isRangeSupported; } get isStreamingSupported() { return this._isStreamingSupported; } async read() { await this._readCapability.promise; if (this._done) { return { value: undefined, done: true }; } if (this._storedError) { throw this._storedError; } const chunk = this._readableStream.read(); if (chunk === null) { this._readCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); return this.read(); } this._loaded += chunk.length; this.onProgress?.({ loaded: this._loaded, total: this._contentLength }); const buffer = new Uint8Array(chunk).buffer; return { value: buffer, done: false }; } cancel(reason) { if (!this._readableStream) { this._error(reason); return; } this._readableStream.destroy(reason); } _error(reason) { this._storedError = reason; this._readCapability.resolve(); } _setReadableStream(readableStream) { this._readableStream = readableStream; readableStream.on("readable", () => { this._readCapability.resolve(); }); readableStream.on("end", () => { readableStream.destroy(); this._done = true; this._readCapability.resolve(); }); readableStream.on("error", reason => { this._error(reason); }); if (!this._isStreamingSupported && this._isRangeSupported) { this._error(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException("streaming is disabled")); } if (this._storedError) { this._readableStream.destroy(this._storedError); } } } class BaseRangeReader { constructor(stream) { this._url = stream.url; this._done = false; this._storedError = null; this.onProgress = null; this._loaded = 0; this._readableStream = null; this._readCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); const source = stream.source; this._isStreamingSupported = !source.disableStream; } get isStreamingSupported() { return this._isStreamingSupported; } async read() { await this._readCapability.promise; if (this._done) { return { value: undefined, done: true }; } if (this._storedError) { throw this._storedError; } const chunk = this._readableStream.read(); if (chunk === null) { this._readCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); return this.read(); } this._loaded += chunk.length; this.onProgress?.({ loaded: this._loaded }); const buffer = new Uint8Array(chunk).buffer; return { value: buffer, done: false }; } cancel(reason) { if (!this._readableStream) { this._error(reason); return; } this._readableStream.destroy(reason); } _error(reason) { this._storedError = reason; this._readCapability.resolve(); } _setReadableStream(readableStream) { this._readableStream = readableStream; readableStream.on("readable", () => { this._readCapability.resolve(); }); readableStream.on("end", () => { readableStream.destroy(); this._done = true; this._readCapability.resolve(); }); readableStream.on("error", reason => { this._error(reason); }); if (this._storedError) { this._readableStream.destroy(this._storedError); } } } function createRequestOptions(parsedUrl, headers) { return { protocol: parsedUrl.protocol, auth: parsedUrl.auth, host: parsedUrl.hostname, port: parsedUrl.port, path: parsedUrl.path, method: "GET", headers }; } class PDFNodeStreamFullReader extends BaseFullReader { constructor(stream) { super(stream); const handleResponse = response => { if (response.statusCode === 404) { const error = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException(`Missing PDF "${this._url}".`); this._storedError = error; this._headersCapability.reject(error); return; } this._headersCapability.resolve(); this._setReadableStream(response); const getResponseHeader = name => { return this._readableStream.headers[name.toLowerCase()]; }; const { allowRangeRequests, suggestedLength } = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.validateRangeRequestCapabilities)({ getResponseHeader, isHttp: stream.isHttp, rangeChunkSize: this._rangeChunkSize, disableRange: this._disableRange }); this._isRangeSupported = allowRangeRequests; this._contentLength = suggestedLength || this._contentLength; this._filename = (0,_network_utils_js__WEBPACK_IMPORTED_MODULE_1__.extractFilenameFromHeader)(getResponseHeader); }; this._request = null; if (this._url.protocol === "http:") { this._request = http.request(createRequestOptions(this._url, stream.httpHeaders), handleResponse); } else { this._request = https.request(createRequestOptions(this._url, stream.httpHeaders), handleResponse); } this._request.on("error", reason => { this._storedError = reason; this._headersCapability.reject(reason); }); this._request.end(); } } class PDFNodeStreamRangeReader extends BaseRangeReader { constructor(stream, start, end) { super(stream); this._httpHeaders = {}; for (const property in stream.httpHeaders) { const value = stream.httpHeaders[property]; if (value === undefined) { continue; } this._httpHeaders[property] = value; } this._httpHeaders.Range = `bytes=${start}-${end - 1}`; const handleResponse = response => { if (response.statusCode === 404) { const error = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException(`Missing PDF "${this._url}".`); this._storedError = error; return; } this._setReadableStream(response); }; this._request = null; if (this._url.protocol === "http:") { this._request = http.request(createRequestOptions(this._url, this._httpHeaders), handleResponse); } else { this._request = https.request(createRequestOptions(this._url, this._httpHeaders), handleResponse); } this._request.on("error", reason => { this._storedError = reason; }); this._request.end(); } } class PDFNodeStreamFsFullReader extends BaseFullReader { constructor(stream) { super(stream); let path = decodeURIComponent(this._url.path); if (fileUriRegex.test(this._url.href)) { path = path.replace(/^\//, ""); } fs.lstat(path, (error, stat) => { if (error) { if (error.code === "ENOENT") { error = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException(`Missing PDF "${path}".`); } this._storedError = error; this._headersCapability.reject(error); return; } this._contentLength = stat.size; this._setReadableStream(fs.createReadStream(path)); this._headersCapability.resolve(); }); } } class PDFNodeStreamFsRangeReader extends BaseRangeReader { constructor(stream, start, end) { super(stream); let path = decodeURIComponent(this._url.path); if (fileUriRegex.test(this._url.href)) { path = path.replace(/^\//, ""); } this._setReadableStream(fs.createReadStream(path, { start, end: end - 1 })); } } __webpack_async_result__(); } catch(e) { __webpack_async_result__(e); } }, 1); /***/ }), /***/ 738: /***/ ((__webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ NodeCMapReaderFactory: () => (/* binding */ NodeCMapReaderFactory), /* harmony export */ NodeCanvasFactory: () => (/* binding */ NodeCanvasFactory), /* harmony export */ NodeFilterFactory: () => (/* binding */ NodeFilterFactory), /* harmony export */ NodeStandardFontDataFactory: () => (/* binding */ NodeStandardFontDataFactory) /* harmony export */ }); /* harmony import */ var _base_factory_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(822); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(266); ; let fs, canvas, path2d_polyfill; if (_shared_util_js__WEBPACK_IMPORTED_MODULE_1__.isNodeJS) { fs = await import(/* webpackIgnore: true */ "fs"); try { canvas = await import(/* webpackIgnore: true */ "canvas"); } catch {} try { path2d_polyfill = await import(/* webpackIgnore: true */ "path2d-polyfill"); } catch {} } ; const fetchData = function (url) { return new Promise((resolve, reject) => { fs.readFile(url, (error, data) => { if (error || !data) { reject(new Error(error)); return; } resolve(new Uint8Array(data)); }); }); }; class NodeFilterFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseFilterFactory {} class NodeCanvasFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseCanvasFactory { _createCanvas(width, height) { return canvas.createCanvas(width, height); } } class NodeCMapReaderFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseCMapReaderFactory { _fetchData(url, compressionType) { return fetchData(url).then(data => { return { cMapData: data, compressionType }; }); } } class NodeStandardFontDataFactory extends _base_factory_js__WEBPACK_IMPORTED_MODULE_0__.BaseStandardFontDataFactory { _fetchData(url) { return fetchData(url); } } __webpack_async_result__(); } catch(e) { __webpack_async_result__(e); } }, 1); /***/ }), /***/ 890: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ OptionalContentConfig: () => (/* binding */ OptionalContentConfig) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _shared_murmurhash3_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(825); const INTERNAL = Symbol("INTERNAL"); class OptionalContentGroup { #visible = true; constructor(name, intent) { this.name = name; this.intent = intent; } get visible() { return this.#visible; } _setVisible(internal, visible) { if (internal !== INTERNAL) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)("Internal method `_setVisible` called."); } this.#visible = visible; } } class OptionalContentConfig { #cachedGetHash = null; #groups = new Map(); #initialHash = null; #order = null; constructor(data) { this.name = null; this.creator = null; if (data === null) { return; } this.name = data.name; this.creator = data.creator; this.#order = data.order; for (const group of data.groups) { this.#groups.set(group.id, new OptionalContentGroup(group.name, group.intent)); } if (data.baseState === "OFF") { for (const group of this.#groups.values()) { group._setVisible(INTERNAL, false); } } for (const on of data.on) { this.#groups.get(on)._setVisible(INTERNAL, true); } for (const off of data.off) { this.#groups.get(off)._setVisible(INTERNAL, false); } this.#initialHash = this.getHash(); } #evaluateVisibilityExpression(array) { const length = array.length; if (length < 2) { return true; } const operator = array[0]; for (let i = 1; i < length; i++) { const element = array[i]; let state; if (Array.isArray(element)) { state = this.#evaluateVisibilityExpression(element); } else if (this.#groups.has(element)) { state = this.#groups.get(element).visible; } else { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${element}`); return true; } switch (operator) { case "And": if (!state) { return false; } break; case "Or": if (state) { return true; } break; case "Not": return !state; default: return true; } } return operator === "And"; } isVisible(group) { if (this.#groups.size === 0) { return true; } if (!group) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)("Optional content group not defined."); return true; } if (group.type === "OCG") { if (!this.#groups.has(group.id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${group.id}`); return true; } return this.#groups.get(group.id).visible; } else if (group.type === "OCMD") { if (group.expression) { return this.#evaluateVisibilityExpression(group.expression); } if (!group.policy || group.policy === "AnyOn") { for (const id of group.ids) { if (!this.#groups.has(id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${id}`); return true; } if (this.#groups.get(id).visible) { return true; } } return false; } else if (group.policy === "AllOn") { for (const id of group.ids) { if (!this.#groups.has(id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${id}`); return true; } if (!this.#groups.get(id).visible) { return false; } } return true; } else if (group.policy === "AnyOff") { for (const id of group.ids) { if (!this.#groups.has(id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${id}`); return true; } if (!this.#groups.get(id).visible) { return true; } } return false; } else if (group.policy === "AllOff") { for (const id of group.ids) { if (!this.#groups.has(id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${id}`); return true; } if (this.#groups.get(id).visible) { return false; } } return true; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Unknown optional content policy ${group.policy}.`); return true; } (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Unknown group type ${group.type}.`); return true; } setVisibility(id, visible = true) { if (!this.#groups.has(id)) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.warn)(`Optional content group not found: ${id}`); return; } this.#groups.get(id)._setVisible(INTERNAL, !!visible); this.#cachedGetHash = null; } get hasInitialVisibility() { return this.#initialHash === null || this.getHash() === this.#initialHash; } getOrder() { if (!this.#groups.size) { return null; } if (this.#order) { return this.#order.slice(); } return [...this.#groups.keys()]; } getGroups() { return this.#groups.size > 0 ? (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.objectFromMap)(this.#groups) : null; } getGroup(id) { return this.#groups.get(id) || null; } getHash() { if (this.#cachedGetHash !== null) { return this.#cachedGetHash; } const hash = new _shared_murmurhash3_js__WEBPACK_IMPORTED_MODULE_1__.MurmurHash3_64(); for (const [id, group] of this.#groups) { hash.update(`${id}:${group.visible}`); } return this.#cachedGetHash = hash.hexdigest(); } } /***/ }), /***/ 739: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ renderTextLayer: () => (/* binding */ renderTextLayer), /* harmony export */ updateTextLayer: () => (/* binding */ updateTextLayer) /* harmony export */ }); /* unused harmony export TextLayerRenderTask */ /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _display_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(473); const MAX_TEXT_DIVS_TO_RENDER = 100000; const DEFAULT_FONT_SIZE = 30; const DEFAULT_FONT_ASCENT = 0.8; const ascentCache = new Map(); function getCtx(size, isOffscreenCanvasSupported) { let ctx; if (isOffscreenCanvasSupported && _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.FeatureTest.isOffscreenCanvasSupported) { ctx = new OffscreenCanvas(size, size).getContext("2d", { alpha: false }); } else { const canvas = document.createElement("canvas"); canvas.width = canvas.height = size; ctx = canvas.getContext("2d", { alpha: false }); } return ctx; } function getAscent(fontFamily, isOffscreenCanvasSupported) { const cachedAscent = ascentCache.get(fontFamily); if (cachedAscent) { return cachedAscent; } const ctx = getCtx(DEFAULT_FONT_SIZE, isOffscreenCanvasSupported); ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`; const metrics = ctx.measureText(""); let ascent = metrics.fontBoundingBoxAscent; let descent = Math.abs(metrics.fontBoundingBoxDescent); if (ascent) { const ratio = ascent / (ascent + descent); ascentCache.set(fontFamily, ratio); ctx.canvas.width = ctx.canvas.height = 0; return ratio; } ctx.strokeStyle = "red"; ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE); ctx.strokeText("g", 0, 0); let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data; descent = 0; for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) { if (pixels[i] > 0) { descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE); break; } } ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE); ctx.strokeText("A", 0, DEFAULT_FONT_SIZE); pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data; ascent = 0; for (let i = 0, ii = pixels.length; i < ii; i += 4) { if (pixels[i] > 0) { ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE); break; } } ctx.canvas.width = ctx.canvas.height = 0; if (ascent) { const ratio = ascent / (ascent + descent); ascentCache.set(fontFamily, ratio); return ratio; } ascentCache.set(fontFamily, DEFAULT_FONT_ASCENT); return DEFAULT_FONT_ASCENT; } function appendText(task, geom, styles) { const textDiv = document.createElement("span"); const textDivProperties = { angle: 0, canvasWidth: 0, hasText: geom.str !== "", hasEOL: geom.hasEOL, fontSize: 0 }; task._textDivs.push(textDiv); const tx = _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.Util.transform(task._transform, geom.transform); let angle = Math.atan2(tx[1], tx[0]); const style = styles[geom.fontName]; if (style.vertical) { angle += Math.PI / 2; } const fontFamily = task._fontInspectorEnabled && style.fontSubstitution || style.fontFamily; const fontHeight = Math.hypot(tx[2], tx[3]); const fontAscent = fontHeight * getAscent(fontFamily, task._isOffscreenCanvasSupported); let left, top; if (angle === 0) { left = tx[4]; top = tx[5] - fontAscent; } else { left = tx[4] + fontAscent * Math.sin(angle); top = tx[5] - fontAscent * Math.cos(angle); } const scaleFactorStr = "calc(var(--scale-factor)*"; const divStyle = textDiv.style; if (task._container === task._rootContainer) { divStyle.left = `${(100 * left / task._pageWidth).toFixed(2)}%`; divStyle.top = `${(100 * top / task._pageHeight).toFixed(2)}%`; } else { divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`; divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`; } divStyle.fontSize = `${scaleFactorStr}${fontHeight.toFixed(2)}px)`; divStyle.fontFamily = fontFamily; textDivProperties.fontSize = fontHeight; textDiv.setAttribute("role", "presentation"); textDiv.textContent = geom.str; textDiv.dir = geom.dir; if (task._fontInspectorEnabled) { textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName; } if (angle !== 0) { textDivProperties.angle = angle * (180 / Math.PI); } let shouldScaleText = false; if (geom.str.length > 1) { shouldScaleText = true; } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) { const absScaleX = Math.abs(geom.transform[0]), absScaleY = Math.abs(geom.transform[3]); if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) { shouldScaleText = true; } } if (shouldScaleText) { textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width; } task._textDivProperties.set(textDiv, textDivProperties); if (task._isReadableStream) { task._layoutText(textDiv); } } function layout(params) { const { div, scale, properties, ctx, prevFontSize, prevFontFamily } = params; const { style } = div; let transform = ""; if (properties.canvasWidth !== 0 && properties.hasText) { const { fontFamily } = style; const { canvasWidth, fontSize } = properties; if (prevFontSize !== fontSize || prevFontFamily !== fontFamily) { ctx.font = `${fontSize * scale}px ${fontFamily}`; params.prevFontSize = fontSize; params.prevFontFamily = fontFamily; } const { width } = ctx.measureText(div.textContent); if (width > 0) { transform = `scaleX(${canvasWidth * scale / width})`; } } if (properties.angle !== 0) { transform = `rotate(${properties.angle}deg) ${transform}`; } if (transform.length > 0) { style.transform = transform; } } function render(task) { if (task._canceled) { return; } const textDivs = task._textDivs; const capability = task._capability; const textDivsLength = textDivs.length; if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { capability.resolve(); return; } if (!task._isReadableStream) { for (const textDiv of textDivs) { task._layoutText(textDiv); } } capability.resolve(); } class TextLayerRenderTask { constructor({ textContentSource, container, viewport, textDivs, textDivProperties, textContentItemsStr, isOffscreenCanvasSupported }) { this._textContentSource = textContentSource; this._isReadableStream = textContentSource instanceof ReadableStream; this._container = this._rootContainer = container; this._textDivs = textDivs || []; this._textContentItemsStr = textContentItemsStr || []; this._isOffscreenCanvasSupported = isOffscreenCanvasSupported; this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled; this._reader = null; this._textDivProperties = textDivProperties || new WeakMap(); this._canceled = false; this._capability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._layoutTextParams = { prevFontSize: null, prevFontFamily: null, div: null, scale: viewport.scale * (globalThis.devicePixelRatio || 1), properties: null, ctx: getCtx(0, isOffscreenCanvasSupported) }; const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; this._transform = [1, 0, 0, -1, -pageX, pageY + pageHeight]; this._pageWidth = pageWidth; this._pageHeight = pageHeight; (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.setLayerDimensions)(container, viewport); this._capability.promise.finally(() => { this._layoutTextParams = null; }).catch(() => {}); } get promise() { return this._capability.promise; } cancel() { this._canceled = true; if (this._reader) { this._reader.cancel(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException("TextLayer task cancelled.")).catch(() => {}); this._reader = null; } this._capability.reject(new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException("TextLayer task cancelled.")); } _processItems(items, styleCache) { for (const item of items) { if (item.str === undefined) { if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") { const parent = this._container; this._container = document.createElement("span"); this._container.classList.add("markedContent"); if (item.id !== null) { this._container.setAttribute("id", `${item.id}`); } parent.append(this._container); } else if (item.type === "endMarkedContent") { this._container = this._container.parentNode; } continue; } this._textContentItemsStr.push(item.str); appendText(this, item, styleCache); } } _layoutText(textDiv) { const textDivProperties = this._layoutTextParams.properties = this._textDivProperties.get(textDiv); this._layoutTextParams.div = textDiv; layout(this._layoutTextParams); if (textDivProperties.hasText) { this._container.append(textDiv); } if (textDivProperties.hasEOL) { const br = document.createElement("br"); br.setAttribute("role", "presentation"); this._container.append(br); } } _render() { const capability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); let styleCache = Object.create(null); if (this._isReadableStream) { const pump = () => { this._reader.read().then(({ value, done }) => { if (done) { capability.resolve(); return; } Object.assign(styleCache, value.styles); this._processItems(value.items, styleCache); pump(); }, capability.reject); }; this._reader = this._textContentSource.getReader(); pump(); } else if (this._textContentSource) { const { items, styles } = this._textContentSource; this._processItems(items, styles); capability.resolve(); } else { throw new Error('No "textContentSource" parameter specified.'); } capability.promise.then(() => { styleCache = null; render(this); }, this._capability.reject); } } function renderTextLayer(params) { const task = new TextLayerRenderTask(params); task._render(); return task; } function updateTextLayer({ container, viewport, textDivs, textDivProperties, isOffscreenCanvasSupported, mustRotate = true, mustRescale = true }) { if (mustRotate) { (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.setLayerDimensions)(container, { rotation: viewport.rotation }); } if (mustRescale) { const ctx = getCtx(0, isOffscreenCanvasSupported); const scale = viewport.scale * (globalThis.devicePixelRatio || 1); const params = { prevFontSize: null, prevFontFamily: null, div: null, scale, properties: null, ctx }; for (const div of textDivs) { params.properties = textDivProperties.get(div); params.div = div; layout(params); } } } /***/ }), /***/ 92: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ PDFDataTransportStream: () => (/* binding */ PDFDataTransportStream) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _display_utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(473); class PDFDataTransportStream { constructor({ length, initialData, progressiveDone = false, contentDispositionFilename = null, disableRange = false, disableStream = false }, pdfDataRangeTransport) { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(pdfDataRangeTransport, 'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.'); this._queuedChunks = []; this._progressiveDone = progressiveDone; this._contentDispositionFilename = contentDispositionFilename; if (initialData?.length > 0) { const buffer = initialData instanceof Uint8Array && initialData.byteLength === initialData.buffer.byteLength ? initialData.buffer : new Uint8Array(initialData).buffer; this._queuedChunks.push(buffer); } this._pdfDataRangeTransport = pdfDataRangeTransport; this._isStreamingSupported = !disableStream; this._isRangeSupported = !disableRange; this._contentLength = length; this._fullRequestReader = null; this._rangeReaders = []; this._pdfDataRangeTransport.addRangeListener((begin, chunk) => { this._onReceiveData({ begin, chunk }); }); this._pdfDataRangeTransport.addProgressListener((loaded, total) => { this._onProgress({ loaded, total }); }); this._pdfDataRangeTransport.addProgressiveReadListener(chunk => { this._onReceiveData({ chunk }); }); this._pdfDataRangeTransport.addProgressiveDoneListener(() => { this._onProgressiveDone(); }); this._pdfDataRangeTransport.transportReady(); } _onReceiveData({ begin, chunk }) { const buffer = chunk instanceof Uint8Array && chunk.byteLength === chunk.buffer.byteLength ? chunk.buffer : new Uint8Array(chunk).buffer; if (begin === undefined) { if (this._fullRequestReader) { this._fullRequestReader._enqueue(buffer); } else { this._queuedChunks.push(buffer); } } else { const found = this._rangeReaders.some(function (rangeReader) { if (rangeReader._begin !== begin) { return false; } rangeReader._enqueue(buffer); return true; }); (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(found, "_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."); } } get _progressiveDataLength() { return this._fullRequestReader?._loaded ?? 0; } _onProgress(evt) { if (evt.total === undefined) { this._rangeReaders[0]?.onProgress?.({ loaded: evt.loaded }); } else { this._fullRequestReader?.onProgress?.({ loaded: evt.loaded, total: evt.total }); } } _onProgressiveDone() { this._fullRequestReader?.progressiveDone(); this._progressiveDone = true; } _removeRangeReader(reader) { const i = this._rangeReaders.indexOf(reader); if (i >= 0) { this._rangeReaders.splice(i, 1); } } getFullReader() { (0,_shared_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(!this._fullRequestReader, "PDFDataTransportStream.getFullReader can only be called once."); const queuedChunks = this._queuedChunks; this._queuedChunks = null; return new PDFDataTransportStreamReader(this, queuedChunks, this._progressiveDone, this._contentDispositionFilename); } getRangeReader(begin, end) { if (end <= this._progressiveDataLength) { return null; } const reader = new PDFDataTransportStreamRangeReader(this, begin, end); this._pdfDataRangeTransport.requestDataRange(begin, end); this._rangeReaders.push(reader); return reader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeReaders.slice(0)) { reader.cancel(reason); } this._pdfDataRangeTransport.abort(); } } class PDFDataTransportStreamReader { constructor(stream, queuedChunks, progressiveDone = false, contentDispositionFilename = null) { this._stream = stream; this._done = progressiveDone || false; this._filename = (0,_display_utils_js__WEBPACK_IMPORTED_MODULE_1__.isPdfFile)(contentDispositionFilename) ? contentDispositionFilename : null; this._queuedChunks = queuedChunks || []; this._loaded = 0; for (const chunk of this._queuedChunks) { this._loaded += chunk.byteLength; } this._requests = []; this._headersReady = Promise.resolve(); stream._fullRequestReader = this; this.onProgress = null; } _enqueue(chunk) { if (this._done) { return; } if (this._requests.length > 0) { const requestCapability = this._requests.shift(); requestCapability.resolve({ value: chunk, done: false }); } else { this._queuedChunks.push(chunk); } this._loaded += chunk.byteLength; } get headersReady() { return this._headersReady; } get filename() { return this._filename; } get isRangeSupported() { return this._stream._isRangeSupported; } get isStreamingSupported() { return this._stream._isStreamingSupported; } get contentLength() { return this._stream._contentLength; } async read() { if (this._queuedChunks.length > 0) { const chunk = this._queuedChunks.shift(); return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; } progressiveDone() { if (this._done) { return; } this._done = true; } } class PDFDataTransportStreamRangeReader { constructor(stream, begin, end) { this._stream = stream; this._begin = begin; this._end = end; this._queuedChunk = null; this._requests = []; this._done = false; this.onProgress = null; } _enqueue(chunk) { if (this._done) { return; } if (this._requests.length === 0) { this._queuedChunk = chunk; } else { const requestsCapability = this._requests.shift(); requestsCapability.resolve({ value: chunk, done: false }); for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; } this._done = true; this._stream._removeRangeReader(this); } get isStreamingSupported() { return false; } async read() { if (this._queuedChunk) { const chunk = this._queuedChunk; this._queuedChunk = null; return { value: chunk, done: false }; } if (this._done) { return { value: undefined, done: true }; } const requestCapability = new _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this._requests.push(requestCapability); return requestCapability.promise; } cancel(reason) { this._done = true; for (const requestCapability of this._requests) { requestCapability.resolve({ value: undefined, done: true }); } this._requests.length = 0; this._stream._removeRangeReader(this); } } /***/ }), /***/ 368: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ GlobalWorkerOptions: () => (/* binding */ GlobalWorkerOptions) /* harmony export */ }); const GlobalWorkerOptions = Object.create(null); GlobalWorkerOptions.workerPort = null; GlobalWorkerOptions.workerSrc = ""; /***/ }), /***/ 160: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ XfaLayer: () => (/* binding */ XfaLayer) /* harmony export */ }); /* harmony import */ var _xfa_text_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(521); class XfaLayer { static setupStorage(html, id, element, storage, intent) { const storedData = storage.getValue(id, { value: null }); switch (element.name) { case "textarea": if (storedData.value !== null) { html.textContent = storedData.value; } if (intent === "print") { break; } html.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); }); break; case "input": if (element.attributes.type === "radio" || element.attributes.type === "checkbox") { if (storedData.value === element.attributes.xfaOn) { html.setAttribute("checked", true); } else if (storedData.value === element.attributes.xfaOff) { html.removeAttribute("checked"); } if (intent === "print") { break; } html.addEventListener("change", event => { storage.setValue(id, { value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff") }); }); } else { if (storedData.value !== null) { html.setAttribute("value", storedData.value); } if (intent === "print") { break; } html.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); }); } break; case "select": if (storedData.value !== null) { html.setAttribute("value", storedData.value); for (const option of element.children) { if (option.attributes.value === storedData.value) { option.attributes.selected = true; } else if (option.attributes.hasOwnProperty("selected")) { delete option.attributes.selected; } } } html.addEventListener("input", event => { const options = event.target.options; const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value; storage.setValue(id, { value }); }); break; } } static setAttributes({ html, element, storage = null, intent, linkService }) { const { attributes } = element; const isHTMLAnchorElement = html instanceof HTMLAnchorElement; if (attributes.type === "radio") { attributes.name = `${attributes.name}-${intent}`; } for (const [key, value] of Object.entries(attributes)) { if (value === null || value === undefined) { continue; } switch (key) { case "class": if (value.length) { html.setAttribute(key, value.join(" ")); } break; case "dataId": break; case "id": html.setAttribute("data-element-id", value); break; case "style": Object.assign(html.style, value); break; case "textContent": html.textContent = value; break; default: if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") { html.setAttribute(key, value); } } } if (isHTMLAnchorElement) { linkService.addLinkAttributes(html, attributes.href, attributes.newWindow); } if (storage && attributes.dataId) { this.setupStorage(html, attributes.dataId, element, storage); } } static render(parameters) { const storage = parameters.annotationStorage; const linkService = parameters.linkService; const root = parameters.xfaHtml; const intent = parameters.intent || "display"; const rootHtml = document.createElement(root.name); if (root.attributes) { this.setAttributes({ html: rootHtml, element: root, intent, linkService }); } const isNotForRichText = intent !== "richText"; const rootDiv = parameters.div; rootDiv.append(rootHtml); if (parameters.viewport) { const transform = `matrix(${parameters.viewport.transform.join(",")})`; rootDiv.style.transform = transform; } if (isNotForRichText) { rootDiv.setAttribute("class", "xfaLayer xfaFont"); } const textDivs = []; if (root.children.length === 0) { if (root.value) { const node = document.createTextNode(root.value); rootHtml.append(node); if (isNotForRichText && _xfa_text_js__WEBPACK_IMPORTED_MODULE_0__.XfaText.shouldBuildText(root.name)) { textDivs.push(node); } } return { textDivs }; } const stack = [[root, -1, rootHtml]]; while (stack.length > 0) { const [parent, i, html] = stack.at(-1); if (i + 1 === parent.children.length) { stack.pop(); continue; } const child = parent.children[++stack.at(-1)[1]]; if (child === null) { continue; } const { name } = child; if (name === "#text") { const node = document.createTextNode(child.value); textDivs.push(node); html.append(node); continue; } const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name); html.append(childHtml); if (child.attributes) { this.setAttributes({ html: childHtml, element: child, storage, intent, linkService }); } if (child.children?.length > 0) { stack.push([child, -1, childHtml]); } else if (child.value) { const node = document.createTextNode(child.value); if (isNotForRichText && _xfa_text_js__WEBPACK_IMPORTED_MODULE_0__.XfaText.shouldBuildText(name)) { textDivs.push(node); } childHtml.append(node); } } for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) { el.setAttribute("readOnly", true); } return { textDivs }; } static update(parameters) { const transform = `matrix(${parameters.viewport.transform.join(",")})`; parameters.div.style.transform = transform; parameters.div.hidden = false; } } /***/ }), /***/ 521: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ XfaText: () => (/* binding */ XfaText) /* harmony export */ }); class XfaText { static textContent(xfa) { const items = []; const output = { items, styles: Object.create(null) }; function walk(node) { if (!node) { return; } let str = null; const name = node.name; if (name === "#text") { str = node.value; } else if (!XfaText.shouldBuildText(name)) { return; } else if (node?.attributes?.textContent) { str = node.attributes.textContent; } else if (node.value) { str = node.value; } if (str !== null) { items.push({ str }); } if (!node.children) { return; } for (const child of node.children) { walk(child); } } walk(xfa); return output; } static shouldBuildText(name) { return !(name === "textarea" || name === "input" || name === "option" || name === "select"); } } /***/ }), /***/ 907: /***/ ((__webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.a(__webpack_module__, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AbortException: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException), /* harmony export */ AnnotationEditorLayer: () => (/* reexport safe */ _display_editor_annotation_editor_layer_js__WEBPACK_IMPORTED_MODULE_4__.AnnotationEditorLayer), /* harmony export */ AnnotationEditorParamsType: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorParamsType), /* harmony export */ AnnotationEditorType: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationEditorType), /* harmony export */ AnnotationEditorUIManager: () => (/* reexport safe */ _display_editor_tools_js__WEBPACK_IMPORTED_MODULE_5__.AnnotationEditorUIManager), /* harmony export */ AnnotationLayer: () => (/* reexport safe */ _display_annotation_layer_js__WEBPACK_IMPORTED_MODULE_6__.AnnotationLayer), /* harmony export */ AnnotationMode: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.AnnotationMode), /* harmony export */ CMapCompressionType: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.CMapCompressionType), /* harmony export */ DOMSVGFactory: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.DOMSVGFactory), /* harmony export */ DrawLayer: () => (/* reexport safe */ _display_draw_layer_js__WEBPACK_IMPORTED_MODULE_7__.DrawLayer), /* harmony export */ FeatureTest: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.FeatureTest), /* harmony export */ GlobalWorkerOptions: () => (/* reexport safe */ _display_worker_options_js__WEBPACK_IMPORTED_MODULE_8__.GlobalWorkerOptions), /* harmony export */ ImageKind: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.ImageKind), /* harmony export */ InvalidPDFException: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.InvalidPDFException), /* harmony export */ MissingPDFException: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException), /* harmony export */ OPS: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.OPS), /* harmony export */ Outliner: () => (/* reexport safe */ _display_editor_outliner_js__WEBPACK_IMPORTED_MODULE_9__.Outliner), /* harmony export */ PDFDataRangeTransport: () => (/* reexport safe */ _display_api_js__WEBPACK_IMPORTED_MODULE_1__.PDFDataRangeTransport), /* harmony export */ PDFDateString: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.PDFDateString), /* harmony export */ PDFWorker: () => (/* reexport safe */ _display_api_js__WEBPACK_IMPORTED_MODULE_1__.PDFWorker), /* harmony export */ PasswordResponses: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PasswordResponses), /* harmony export */ PermissionFlag: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PermissionFlag), /* harmony export */ PixelsPerInch: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.PixelsPerInch), /* harmony export */ PromiseCapability: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability), /* harmony export */ RenderingCancelledException: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.RenderingCancelledException), /* harmony export */ UnexpectedResponseException: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.UnexpectedResponseException), /* harmony export */ Util: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.Util), /* harmony export */ VerbosityLevel: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.VerbosityLevel), /* harmony export */ XfaLayer: () => (/* reexport safe */ _display_xfa_layer_js__WEBPACK_IMPORTED_MODULE_10__.XfaLayer), /* harmony export */ build: () => (/* reexport safe */ _display_api_js__WEBPACK_IMPORTED_MODULE_1__.build), /* harmony export */ createValidAbsoluteUrl: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.createValidAbsoluteUrl), /* harmony export */ fetchData: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.fetchData), /* harmony export */ getDocument: () => (/* reexport safe */ _display_api_js__WEBPACK_IMPORTED_MODULE_1__.getDocument), /* harmony export */ getFilenameFromUrl: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.getFilenameFromUrl), /* harmony export */ getPdfFilenameFromUrl: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.getPdfFilenameFromUrl), /* harmony export */ getXfaPageViewport: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.getXfaPageViewport), /* harmony export */ isDataScheme: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isDataScheme), /* harmony export */ isPdfFile: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.isPdfFile), /* harmony export */ noContextMenu: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.noContextMenu), /* harmony export */ normalizeUnicode: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.normalizeUnicode), /* harmony export */ renderTextLayer: () => (/* reexport safe */ _display_text_layer_js__WEBPACK_IMPORTED_MODULE_3__.renderTextLayer), /* harmony export */ setLayerDimensions: () => (/* reexport safe */ _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__.setLayerDimensions), /* harmony export */ shadow: () => (/* reexport safe */ _shared_util_js__WEBPACK_IMPORTED_MODULE_0__.shadow), /* harmony export */ updateTextLayer: () => (/* reexport safe */ _display_text_layer_js__WEBPACK_IMPORTED_MODULE_3__.updateTextLayer), /* harmony export */ version: () => (/* reexport safe */ _display_api_js__WEBPACK_IMPORTED_MODULE_1__.version) /* harmony export */ }); /* harmony import */ var _shared_util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); /* harmony import */ var _display_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(406); /* harmony import */ var _display_display_utils_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(473); /* harmony import */ var _display_text_layer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(739); /* harmony import */ var _display_editor_annotation_editor_layer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(331); /* harmony import */ var _display_editor_tools_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(812); /* harmony import */ var _display_annotation_layer_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(640); /* harmony import */ var _display_draw_layer_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(423); /* harmony import */ var _display_worker_options_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(368); /* harmony import */ var _display_editor_outliner_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(405); /* harmony import */ var _display_xfa_layer_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(160); var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([_display_api_js__WEBPACK_IMPORTED_MODULE_1__]); _display_api_js__WEBPACK_IMPORTED_MODULE_1__ = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0]; const pdfjsVersion = '4.0.269'; const pdfjsBuild = 'f4b396f6c'; __webpack_async_result__(); } catch(e) { __webpack_async_result__(e); } }); /***/ }), /***/ 694: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ MessageHandler: () => (/* binding */ MessageHandler) /* harmony export */ }); /* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); const CallbackKind = { UNKNOWN: 0, DATA: 1, ERROR: 2 }; const StreamKind = { UNKNOWN: 0, CANCEL: 1, CANCEL_COMPLETE: 2, CLOSE: 3, ENQUEUE: 4, ERROR: 5, PULL: 6, PULL_COMPLETE: 7, START_COMPLETE: 8 }; function wrapReason(reason) { if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) { (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.unreachable)('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); } switch (reason.name) { case "AbortException": return new _util_js__WEBPACK_IMPORTED_MODULE_0__.AbortException(reason.message); case "MissingPDFException": return new _util_js__WEBPACK_IMPORTED_MODULE_0__.MissingPDFException(reason.message); case "PasswordException": return new _util_js__WEBPACK_IMPORTED_MODULE_0__.PasswordException(reason.message, reason.code); case "UnexpectedResponseException": return new _util_js__WEBPACK_IMPORTED_MODULE_0__.UnexpectedResponseException(reason.message, reason.status); case "UnknownErrorException": return new _util_js__WEBPACK_IMPORTED_MODULE_0__.UnknownErrorException(reason.message, reason.details); default: return new _util_js__WEBPACK_IMPORTED_MODULE_0__.UnknownErrorException(reason.message, reason.toString()); } } class MessageHandler { constructor(sourceName, targetName, comObj) { this.sourceName = sourceName; this.targetName = targetName; this.comObj = comObj; this.callbackId = 1; this.streamId = 1; this.streamSinks = Object.create(null); this.streamControllers = Object.create(null); this.callbackCapabilities = Object.create(null); this.actionHandler = Object.create(null); this._onComObjOnMessage = event => { const data = event.data; if (data.targetName !== this.sourceName) { return; } if (data.stream) { this.#processStreamMessage(data); return; } if (data.callback) { const callbackId = data.callbackId; const capability = this.callbackCapabilities[callbackId]; if (!capability) { throw new Error(`Cannot resolve callback ${callbackId}`); } delete this.callbackCapabilities[callbackId]; if (data.callback === CallbackKind.DATA) { capability.resolve(data.data); } else if (data.callback === CallbackKind.ERROR) { capability.reject(wrapReason(data.reason)); } else { throw new Error("Unexpected callback case"); } return; } const action = this.actionHandler[data.action]; if (!action) { throw new Error(`Unknown action from worker: ${data.action}`); } if (data.callbackId) { const cbSourceName = this.sourceName; const cbTargetName = data.sourceName; new Promise(function (resolve) { resolve(action(data.data)); }).then(function (result) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.DATA, callbackId: data.callbackId, data: result }); }, function (reason) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.ERROR, callbackId: data.callbackId, reason: wrapReason(reason) }); }); return; } if (data.streamId) { this.#createStreamSink(data); return; } action(data.data); }; comObj.addEventListener("message", this._onComObjOnMessage); } on(actionName, handler) { const ah = this.actionHandler; if (ah[actionName]) { throw new Error(`There is already an actionName called "${actionName}"`); } ah[actionName] = handler; } send(actionName, data, transfers) { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, data }, transfers); } sendWithPromise(actionName, data, transfers) { const callbackId = this.callbackId++; const capability = new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.callbackCapabilities[callbackId] = capability; try { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, callbackId, data }, transfers); } catch (ex) { capability.reject(ex); } return capability.promise; } sendWithStream(actionName, data, queueingStrategy, transfers) { const streamId = this.streamId++, sourceName = this.sourceName, targetName = this.targetName, comObj = this.comObj; return new ReadableStream({ start: controller => { const startCapability = new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.streamControllers[streamId] = { controller, startCall: startCapability, pullCall: null, cancelCall: null, isClosed: false }; comObj.postMessage({ sourceName, targetName, action: actionName, streamId, data, desiredSize: controller.desiredSize }, transfers); return startCapability.promise; }, pull: controller => { const pullCapability = new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.streamControllers[streamId].pullCall = pullCapability; comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL, streamId, desiredSize: controller.desiredSize }); return pullCapability.promise; }, cancel: reason => { (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(reason instanceof Error, "cancel must have a valid reason"); const cancelCapability = new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.streamControllers[streamId].cancelCall = cancelCapability; this.streamControllers[streamId].isClosed = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL, streamId, reason: wrapReason(reason) }); return cancelCapability.promise; } }, queueingStrategy); } #createStreamSink(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const self = this, action = this.actionHandler[data.action]; const streamSink = { enqueue(chunk, size = 1, transfers) { if (this.isCancelled) { return; } const lastDesiredSize = this.desiredSize; this.desiredSize -= size; if (lastDesiredSize > 0 && this.desiredSize <= 0) { this.sinkCapability = new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(); this.ready = this.sinkCapability.promise; } comObj.postMessage({ sourceName, targetName, stream: StreamKind.ENQUEUE, streamId, chunk }, transfers); }, close() { if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CLOSE, streamId }); delete self.streamSinks[streamId]; }, error(reason) { (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(reason instanceof Error, "error must have a valid reason"); if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.ERROR, streamId, reason: wrapReason(reason) }); }, sinkCapability: new _util_js__WEBPACK_IMPORTED_MODULE_0__.PromiseCapability(), onPull: null, onCancel: null, isCancelled: false, desiredSize: data.desiredSize, ready: null }; streamSink.sinkCapability.resolve(); streamSink.ready = streamSink.sinkCapability.promise; this.streamSinks[streamId] = streamSink; new Promise(function (resolve) { resolve(action(data.data, streamSink)); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, reason: wrapReason(reason) }); }); } #processStreamMessage(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const streamController = this.streamControllers[streamId], streamSink = this.streamSinks[streamId]; switch (data.stream) { case StreamKind.START_COMPLETE: if (data.success) { streamController.startCall.resolve(); } else { streamController.startCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL_COMPLETE: if (data.success) { streamController.pullCall.resolve(); } else { streamController.pullCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL: if (!streamSink) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); break; } if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { streamSink.sinkCapability.resolve(); } streamSink.desiredSize = data.desiredSize; new Promise(function (resolve) { resolve(streamSink.onPull?.()); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, reason: wrapReason(reason) }); }); break; case StreamKind.ENQUEUE: (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(streamController, "enqueue should have stream controller"); if (streamController.isClosed) { break; } streamController.controller.enqueue(data.chunk); break; case StreamKind.CLOSE: (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(streamController, "close should have stream controller"); if (streamController.isClosed) { break; } streamController.isClosed = true; streamController.controller.close(); this.#deleteStreamController(streamController, streamId); break; case StreamKind.ERROR: (0,_util_js__WEBPACK_IMPORTED_MODULE_0__.assert)(streamController, "error should have stream controller"); streamController.controller.error(wrapReason(data.reason)); this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL_COMPLETE: if (data.success) { streamController.cancelCall.resolve(); } else { streamController.cancelCall.reject(wrapReason(data.reason)); } this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL: if (!streamSink) { break; } new Promise(function (resolve) { resolve(streamSink.onCancel?.(wrapReason(data.reason))); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, reason: wrapReason(reason) }); }); streamSink.sinkCapability.reject(wrapReason(data.reason)); streamSink.isCancelled = true; delete this.streamSinks[streamId]; break; default: throw new Error("Unexpected stream case"); } } async #deleteStreamController(streamController, streamId) { await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); delete this.streamControllers[streamId]; } destroy() { this.comObj.removeEventListener("message", this._onComObjOnMessage); } } /***/ }), /***/ 825: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ MurmurHash3_64: () => (/* binding */ MurmurHash3_64) /* harmony export */ }); /* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(266); const SEED = 0xc3d2e1f0; const MASK_HIGH = 0xffff0000; const MASK_LOW = 0xffff; class MurmurHash3_64 { constructor(seed) { this.h1 = seed ? seed & 0xffffffff : SEED; this.h2 = seed ? seed & 0xffffffff : SEED; } update(input) { let data, length; if (typeof input === "string") { data = new Uint8Array(input.length * 2); length = 0; for (let i = 0, ii = input.length; i < ii; i++) { const code = input.charCodeAt(i); if (code <= 0xff) { data[length++] = code; } else { data[length++] = code >>> 8; data[length++] = code & 0xff; } } } else if ((0,_util_js__WEBPACK_IMPORTED_MODULE_0__.isArrayBuffer)(input)) { data = input.slice(); length = data.byteLength; } else { throw new Error("Wrong data format in MurmurHash3_64_update. " + "Input must be a string or array."); } const blockCounts = length >> 2; const tailLength = length - blockCounts * 4; const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts); let k1 = 0, k2 = 0; let h1 = this.h1, h2 = this.h2; const C1 = 0xcc9e2d51, C2 = 0x1b873593; const C1_LOW = C1 & MASK_LOW, C2_LOW = C2 & MASK_LOW; for (let i = 0; i < blockCounts; i++) { if (i & 1) { k1 = dataUint32[i]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; h1 ^= k1; h1 = h1 << 13 | h1 >>> 19; h1 = h1 * 5 + 0xe6546b64; } else { k2 = dataUint32[i]; k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW; k2 = k2 << 15 | k2 >>> 17; k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW; h2 ^= k2; h2 = h2 << 13 | h2 >>> 19; h2 = h2 * 5 + 0xe6546b64; } } k1 = 0; switch (tailLength) { case 3: k1 ^= data[blockCounts * 4 + 2] << 16; case 2: k1 ^= data[blockCounts * 4 + 1] << 8; case 1: k1 ^= data[blockCounts * 4]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; if (blockCounts & 1) { h1 ^= k1; } else { h2 ^= k1; } } this.h1 = h1; this.h2 = h2; } hexdigest() { let h1 = this.h1, h2 = this.h2; h1 ^= h2 >>> 1; h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW; h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW; h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0"); } } /***/ }), /***/ 266: /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AbortException: () => (/* binding */ AbortException), /* harmony export */ AnnotationBorderStyleType: () => (/* binding */ AnnotationBorderStyleType), /* harmony export */ AnnotationEditorParamsType: () => (/* binding */ AnnotationEditorParamsType), /* harmony export */ AnnotationEditorPrefix: () => (/* binding */ AnnotationEditorPrefix), /* harmony export */ AnnotationEditorType: () => (/* binding */ AnnotationEditorType), /* harmony export */ AnnotationMode: () => (/* binding */ AnnotationMode), /* harmony export */ AnnotationPrefix: () => (/* binding */ AnnotationPrefix), /* harmony export */ AnnotationType: () => (/* binding */ AnnotationType), /* harmony export */ BaseException: () => (/* binding */ BaseException), /* harmony export */ CMapCompressionType: () => (/* binding */ CMapCompressionType), /* harmony export */ FONT_IDENTITY_MATRIX: () => (/* binding */ FONT_IDENTITY_MATRIX), /* harmony export */ FeatureTest: () => (/* binding */ FeatureTest), /* harmony export */ FormatError: () => (/* binding */ FormatError), /* harmony export */ IDENTITY_MATRIX: () => (/* binding */ IDENTITY_MATRIX), /* harmony export */ ImageKind: () => (/* binding */ ImageKind), /* harmony export */ InvalidPDFException: () => (/* binding */ InvalidPDFException), /* harmony export */ LINE_FACTOR: () => (/* binding */ LINE_FACTOR), /* harmony export */ MAX_IMAGE_SIZE_TO_CACHE: () => (/* binding */ MAX_IMAGE_SIZE_TO_CACHE), /* harmony export */ MissingPDFException: () => (/* binding */ MissingPDFException), /* harmony export */ OPS: () => (/* binding */ OPS), /* harmony export */ PasswordException: () => (/* binding */ PasswordException), /* harmony export */ PasswordResponses: () => (/* binding */ PasswordResponses), /* harmony export */ PermissionFlag: () => (/* binding */ PermissionFlag), /* harmony export */ PromiseCapability: () => (/* binding */ PromiseCapability), /* harmony export */ RenderingIntentFlag: () => (/* binding */ RenderingIntentFlag), /* harmony export */ TextRenderingMode: () => (/* binding */ TextRenderingMode), /* harmony export */ UnexpectedResponseException: () => (/* binding */ UnexpectedResponseException), /* harmony export */ UnknownErrorException: () => (/* binding */ UnknownErrorException), /* harmony export */ Util: () => (/* binding */ Util), /* harmony export */ VerbosityLevel: () => (/* binding */ VerbosityLevel), /* harmony export */ assert: () => (/* binding */ assert), /* harmony export */ bytesToString: () => (/* binding */ bytesToString), /* harmony export */ createValidAbsoluteUrl: () => (/* binding */ createValidAbsoluteUrl), /* harmony export */ getUuid: () => (/* binding */ getUuid), /* harmony export */ getVerbosityLevel: () => (/* binding */ getVerbosityLevel), /* harmony export */ info: () => (/* binding */ info), /* harmony export */ isArrayBuffer: () => (/* binding */ isArrayBuffer), /* harmony export */ isNodeJS: () => (/* binding */ isNodeJS), /* harmony export */ normalizeUnicode: () => (/* binding */ normalizeUnicode), /* harmony export */ objectFromMap: () => (/* binding */ objectFromMap), /* harmony export */ setVerbosityLevel: () => (/* binding */ setVerbosityLevel), /* harmony export */ shadow: () => (/* binding */ shadow), /* harmony export */ string32: () => (/* binding */ string32), /* harmony export */ stringToBytes: () => (/* binding */ stringToBytes), /* harmony export */ unreachable: () => (/* binding */ unreachable), /* harmony export */ warn: () => (/* binding */ warn) /* harmony export */ }); /* unused harmony exports AnnotationActionEventType, AnnotationFieldFlag, AnnotationFlag, AnnotationReplyType, BASELINE_FACTOR, DocumentActionEventType, getModificationDate, isArrayEqual, LINE_DESCENT_FACTOR, objectSize, PageActionEventType, stringToPDFString, stringToUTF8String, utf8StringToString */ const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser"); const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; const MAX_IMAGE_SIZE_TO_CACHE = 10e6; const LINE_FACTOR = 1.35; const LINE_DESCENT_FACTOR = 0.35; const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR; const RenderingIntentFlag = { ANY: 0x01, DISPLAY: 0x02, PRINT: 0x04, SAVE: 0x08, ANNOTATIONS_FORMS: 0x10, ANNOTATIONS_STORAGE: 0x20, ANNOTATIONS_DISABLE: 0x40, OPLIST: 0x100 }; const AnnotationMode = { DISABLE: 0, ENABLE: 1, ENABLE_FORMS: 2, ENABLE_STORAGE: 3 }; const AnnotationEditorPrefix = "pdfjs_internal_editor_"; const AnnotationEditorType = { DISABLE: -1, NONE: 0, FREETEXT: 3, HIGHLIGHT: 9, STAMP: 13, INK: 15 }; const AnnotationEditorParamsType = { RESIZE: 1, CREATE: 2, FREETEXT_SIZE: 11, FREETEXT_COLOR: 12, FREETEXT_OPACITY: 13, INK_COLOR: 21, INK_THICKNESS: 22, INK_OPACITY: 23 }; const PermissionFlag = { PRINT: 0x04, MODIFY_CONTENTS: 0x08, COPY: 0x10, MODIFY_ANNOTATIONS: 0x20, FILL_INTERACTIVE_FORMS: 0x100, COPY_FOR_ACCESSIBILITY: 0x200, ASSEMBLE: 0x400, PRINT_HIGH_QUALITY: 0x800 }; const TextRenderingMode = { FILL: 0, STROKE: 1, FILL_STROKE: 2, INVISIBLE: 3, FILL_ADD_TO_PATH: 4, STROKE_ADD_TO_PATH: 5, FILL_STROKE_ADD_TO_PATH: 6, ADD_TO_PATH: 7, FILL_STROKE_MASK: 3, ADD_TO_PATH_FLAG: 4 }; const ImageKind = { GRAYSCALE_1BPP: 1, RGB_24BPP: 2, RGBA_32BPP: 3 }; const AnnotationType = { TEXT: 1, LINK: 2, FREETEXT: 3, LINE: 4, SQUARE: 5, CIRCLE: 6, POLYGON: 7, POLYLINE: 8, HIGHLIGHT: 9, UNDERLINE: 10, SQUIGGLY: 11, STRIKEOUT: 12, STAMP: 13, CARET: 14, INK: 15, POPUP: 16, FILEATTACHMENT: 17, SOUND: 18, MOVIE: 19, WIDGET: 20, SCREEN: 21, PRINTERMARK: 22, TRAPNET: 23, WATERMARK: 24, THREED: 25, REDACT: 26 }; const AnnotationReplyType = { GROUP: "Group", REPLY: "R" }; const AnnotationFlag = { INVISIBLE: 0x01, HIDDEN: 0x02, PRINT: 0x04, NOZOOM: 0x08, NOROTATE: 0x10, NOVIEW: 0x20, READONLY: 0x40, LOCKED: 0x80, TOGGLENOVIEW: 0x100, LOCKEDCONTENTS: 0x200 }; const AnnotationFieldFlag = { READONLY: 0x0000001, REQUIRED: 0x0000002, NOEXPORT: 0x0000004, MULTILINE: 0x0001000, PASSWORD: 0x0002000, NOTOGGLETOOFF: 0x0004000, RADIO: 0x0008000, PUSHBUTTON: 0x0010000, COMBO: 0x0020000, EDIT: 0x0040000, SORT: 0x0080000, FILESELECT: 0x0100000, MULTISELECT: 0x0200000, DONOTSPELLCHECK: 0x0400000, DONOTSCROLL: 0x0800000, COMB: 0x1000000, RICHTEXT: 0x2000000, RADIOSINUNISON: 0x2000000, COMMITONSELCHANGE: 0x4000000 }; const AnnotationBorderStyleType = { SOLID: 1, DASHED: 2, BEVELED: 3, INSET: 4, UNDERLINE: 5 }; const AnnotationActionEventType = { E: "Mouse Enter", X: "Mouse Exit", D: "Mouse Down", U: "Mouse Up", Fo: "Focus", Bl: "Blur", PO: "PageOpen", PC: "PageClose", PV: "PageVisible", PI: "PageInvisible", K: "Keystroke", F: "Format", V: "Validate", C: "Calculate" }; const DocumentActionEventType = { WC: "WillClose", WS: "WillSave", DS: "DidSave", WP: "WillPrint", DP: "DidPrint" }; const PageActionEventType = { O: "PageOpen", C: "PageClose" }; const VerbosityLevel = { ERRORS: 0, WARNINGS: 1, INFOS: 5 }; const CMapCompressionType = { NONE: 0, BINARY: 1 }; const OPS = { dependency: 1, setLineWidth: 2, setLineCap: 3, setLineJoin: 4, setMiterLimit: 5, setDash: 6, setRenderingIntent: 7, setFlatness: 8, setGState: 9, save: 10, restore: 11, transform: 12, moveTo: 13, lineTo: 14, curveTo: 15, curveTo2: 16, curveTo3: 17, closePath: 18, rectangle: 19, stroke: 20, closeStroke: 21, fill: 22, eoFill: 23, fillStroke: 24, eoFillStroke: 25, closeFillStroke: 26, closeEOFillStroke: 27, endPath: 28, clip: 29, eoClip: 30, beginText: 31, endText: 32, setCharSpacing: 33, setWordSpacing: 34, setHScale: 35, setLeading: 36, setFont: 37, setTextRenderingMode: 38, setTextRise: 39, moveText: 40, setLeadingMoveText: 41, setTextMatrix: 42, nextLine: 43, showText: 44, showSpacedText: 45, nextLineShowText: 46, nextLineSetSpacingShowText: 47, setCharWidth: 48, setCharWidthAndBounds: 49, setStrokeColorSpace: 50, setFillColorSpace: 51, setStrokeColor: 52, setStrokeColorN: 53, setFillColor: 54, setFillColorN: 55, setStrokeGray: 56, setFillGray: 57, setStrokeRGBColor: 58, setFillRGBColor: 59, setStrokeCMYKColor: 60, setFillCMYKColor: 61, shadingFill: 62, beginInlineImage: 63, beginImageData: 64, endInlineImage: 65, paintXObject: 66, markPoint: 67, markPointProps: 68, beginMarkedContent: 69, beginMarkedContentProps: 70, endMarkedContent: 71, beginCompat: 72, endCompat: 73, paintFormXObjectBegin: 74, paintFormXObjectEnd: 75, beginGroup: 76, endGroup: 77, beginAnnotation: 80, endAnnotation: 81, paintImageMaskXObject: 83, paintImageMaskXObjectGroup: 84, paintImageXObject: 85, paintInlineImageXObject: 86, paintInlineImageXObjectGroup: 87, paintImageXObjectRepeat: 88, paintImageMaskXObjectRepeat: 89, paintSolidColorImageMask: 90, constructPath: 91 }; const PasswordResponses = { NEED_PASSWORD: 1, INCORRECT_PASSWORD: 2 }; let verbosity = VerbosityLevel.WARNINGS; function setVerbosityLevel(level) { if (Number.isInteger(level)) { verbosity = level; } } function getVerbosityLevel() { return verbosity; } function info(msg) { if (verbosity >= VerbosityLevel.INFOS) { console.log(`Info: ${msg}`); } } function warn(msg) { if (verbosity >= VerbosityLevel.WARNINGS) { console.log(`Warning: ${msg}`); } } function unreachable(msg) { throw new Error(msg); } function assert(cond, msg) { if (!cond) { unreachable(msg); } } function _isValidProtocol(url) { switch (url?.protocol) { case "http:": case "https:": case "ftp:": case "mailto:": case "tel:": return true; default: return false; } } function createValidAbsoluteUrl(url, baseUrl = null, options = null) { if (!url) { return null; } try { if (options && typeof url === "string") { if (options.addDefaultProtocol && url.startsWith("www.")) { const dots = url.match(/\./g); if (dots?.length >= 2) { url = `http://${url}`; } } if (options.tryConvertEncoding) { try { url = stringToUTF8String(url); } catch {} } } const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url); if (_isValidProtocol(absoluteUrl)) { return absoluteUrl; } } catch {} return null; } function shadow(obj, prop, value, nonSerializable = false) { Object.defineProperty(obj, prop, { value, enumerable: !nonSerializable, configurable: true, writable: false }); return value; } const BaseException = function BaseExceptionClosure() { function BaseException(message, name) { if (this.constructor === BaseException) { unreachable("Cannot initialize BaseException."); } this.message = message; this.name = name; } BaseException.prototype = new Error(); BaseException.constructor = BaseException; return BaseException; }(); class PasswordException extends BaseException { constructor(msg, code) { super(msg, "PasswordException"); this.code = code; } } class UnknownErrorException extends BaseException { constructor(msg, details) { super(msg, "UnknownErrorException"); this.details = details; } } class InvalidPDFException extends BaseException { constructor(msg) { super(msg, "InvalidPDFException"); } } class MissingPDFException extends BaseException { constructor(msg) { super(msg, "MissingPDFException"); } } class UnexpectedResponseException extends BaseException { constructor(msg, status) { super(msg, "UnexpectedResponseException"); this.status = status; } } class FormatError extends BaseException { constructor(msg) { super(msg, "FormatError"); } } class AbortException extends BaseException { constructor(msg) { super(msg, "AbortException"); } } function bytesToString(bytes) { if (typeof bytes !== "object" || bytes?.length === undefined) { unreachable("Invalid argument for bytesToString"); } const length = bytes.length; const MAX_ARGUMENT_COUNT = 8192; if (length < MAX_ARGUMENT_COUNT) { return String.fromCharCode.apply(null, bytes); } const strBuf = []; for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) { const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); const chunk = bytes.subarray(i, chunkEnd); strBuf.push(String.fromCharCode.apply(null, chunk)); } return strBuf.join(""); } function stringToBytes(str) { if (typeof str !== "string") { unreachable("Invalid argument for stringToBytes"); } const length = str.length; const bytes = new Uint8Array(length); for (let i = 0; i < length; ++i) { bytes[i] = str.charCodeAt(i) & 0xff; } return bytes; } function string32(value) { return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff); } function objectSize(obj) { return Object.keys(obj).length; } function objectFromMap(map) { const obj = Object.create(null); for (const [key, value] of map) { obj[key] = value; } return obj; } function isLittleEndian() { const buffer8 = new Uint8Array(4); buffer8[0] = 1; const view32 = new Uint32Array(buffer8.buffer, 0, 1); return view32[0] === 1; } function isEvalSupported() { try { new Function(""); return true; } catch { return false; } } class FeatureTest { static get isLittleEndian() { return shadow(this, "isLittleEndian", isLittleEndian()); } static get isEvalSupported() { return shadow(this, "isEvalSupported", isEvalSupported()); } static get isOffscreenCanvasSupported() { return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined"); } static get platform() { if (typeof navigator !== "undefined" && typeof navigator?.platform === "string") { return shadow(this, "platform", { isMac: navigator.platform.includes("Mac") }); } return shadow(this, "platform", { isMac: false }); } static get isCSSRoundSupported() { return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)")); } } const hexNumbers = [...Array(256).keys()].map(n => n.toString(16).padStart(2, "0")); class Util { static makeHexColor(r, g, b) { return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`; } static scaleMinMax(transform, minMax) { let temp; if (transform[0]) { if (transform[0] < 0) { temp = minMax[0]; minMax[0] = minMax[1]; minMax[1] = temp; } minMax[0] *= transform[0]; minMax[1] *= transform[0]; if (transform[3] < 0) { temp = minMax[2]; minMax[2] = minMax[3]; minMax[3] = temp; } minMax[2] *= transform[3]; minMax[3] *= transform[3]; } else { temp = minMax[0]; minMax[0] = minMax[2]; minMax[2] = temp; temp = minMax[1]; minMax[1] = minMax[3]; minMax[3] = temp; if (transform[1] < 0) { temp = minMax[2]; minMax[2] = minMax[3]; minMax[3] = temp; } minMax[2] *= transform[1]; minMax[3] *= transform[1]; if (transform[2] < 0) { temp = minMax[0]; minMax[0] = minMax[1]; minMax[1] = temp; } minMax[0] *= transform[2]; minMax[1] *= transform[2]; } minMax[0] += transform[4]; minMax[1] += transform[4]; minMax[2] += transform[5]; minMax[3] += transform[5]; } static transform(m1, m2) { return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]]; } static applyTransform(p, m) { const xt = p[0] * m[0] + p[1] * m[2] + m[4]; const yt = p[0] * m[1] + p[1] * m[3] + m[5]; return [xt, yt]; } static applyInverseTransform(p, m) { const d = m[0] * m[3] - m[1] * m[2]; const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; return [xt, yt]; } static getAxialAlignedBoundingBox(r, m) { const p1 = this.applyTransform(r, m); const p2 = this.applyTransform(r.slice(2, 4), m); const p3 = this.applyTransform([r[0], r[3]], m); const p4 = this.applyTransform([r[2], r[1]], m); return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])]; } static inverseTransform(m) { const d = m[0] * m[3] - m[1] * m[2]; return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; } static singularValueDecompose2dScale(m) { const transpose = [m[0], m[2], m[1], m[3]]; const a = m[0] * transpose[0] + m[1] * transpose[2]; const b = m[0] * transpose[1] + m[1] * transpose[3]; const c = m[2] * transpose[0] + m[3] * transpose[2]; const d = m[2] * transpose[1] + m[3] * transpose[3]; const first = (a + d) / 2; const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2; const sx = first + second || 1; const sy = first - second || 1; return [Math.sqrt(sx), Math.sqrt(sy)]; } static normalizeRect(rect) { const r = rect.slice(0); if (rect[0] > rect[2]) { r[0] = rect[2]; r[2] = rect[0]; } if (rect[1] > rect[3]) { r[1] = rect[3]; r[3] = rect[1]; } return r; } static intersect(rect1, rect2) { const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2])); const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2])); if (xLow > xHigh) { return null; } const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3])); const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3])); if (yLow > yHigh) { return null; } return [xLow, yLow, xHigh, yHigh]; } static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) { const tvalues = [], bounds = [[], []]; let a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (let i = 0; i < 2; ++i) { if (i === 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (Math.abs(a) < 1e-12) { if (Math.abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = Math.sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } let j = tvalues.length, mt; const jlen = j; while (j--) { t = tvalues[j]; mt = 1 - t; bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3; bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; bounds[0].length = bounds[1].length = jlen + 2; return [Math.min(...bounds[0]), Math.min(...bounds[1]), Math.max(...bounds[0]), Math.max(...bounds[1])]; } } const PDFStringTranslateTable = (/* unused pure expression or super */ null && ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 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, 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, 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, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac])); function stringToPDFString(str) { if (str[0] >= "\xEF") { let encoding; if (str[0] === "\xFE" && str[1] === "\xFF") { encoding = "utf-16be"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xFF" && str[1] === "\xFE") { encoding = "utf-16le"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") { encoding = "utf-8"; } if (encoding) { try { const decoder = new TextDecoder(encoding, { fatal: true }); const buffer = stringToBytes(str); const decoded = decoder.decode(buffer); if (!decoded.includes("\x1b")) { return decoded; } return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, ""); } catch (ex) { warn(`stringToPDFString: "${ex}".`); } } } const strBuf = []; for (let i = 0, ii = str.length; i < ii; i++) { const charCode = str.charCodeAt(i); if (charCode === 0x1b) { while (++i < ii && str.charCodeAt(i) !== 0x1b) {} continue; } const code = PDFStringTranslateTable[charCode]; strBuf.push(code ? String.fromCharCode(code) : str.charAt(i)); } return strBuf.join(""); } function stringToUTF8String(str) { return decodeURIComponent(escape(str)); } function utf8StringToString(str) { return unescape(encodeURIComponent(str)); } function isArrayBuffer(v) { return typeof v === "object" && v?.byteLength !== undefined; } function isArrayEqual(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0, ii = arr1.length; i < ii; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true; } function getModificationDate(date = new Date()) { const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")]; return buffer.join(""); } class PromiseCapability { #settled = false; constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = data => { this.#settled = true; resolve(data); }; this.reject = reason => { this.#settled = true; reject(reason); }; }); } get settled() { return this.#settled; } } let NormalizeRegex = null; let NormalizationMap = null; function normalizeUnicode(str) { if (!NormalizeRegex) { NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu; NormalizationMap = new Map([["ſt", "ſt"]]); } return str.replaceAll(NormalizeRegex, (_, p1, p2) => { return p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2); }); } function getUuid() { if (typeof crypto !== "undefined" && typeof crypto?.randomUUID === "function") { return crypto.randomUUID(); } const buf = new Uint8Array(32); if (typeof crypto !== "undefined" && typeof crypto?.getRandomValues === "function") { crypto.getRandomValues(buf); } else { for (let i = 0; i < 32; i++) { buf[i] = Math.floor(Math.random() * 255); } } return bytesToString(buf); } const AnnotationPrefix = "pdfjs_internal_id_"; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/async module */ /******/ (() => { /******/ var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__"; /******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__"; /******/ var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__"; /******/ var resolveQueue = (queue) => { /******/ if(queue && queue.d < 1) { /******/ queue.d = 1; /******/ queue.forEach((fn) => (fn.r--)); /******/ queue.forEach((fn) => (fn.r-- ? fn.r++ : fn())); /******/ } /******/ } /******/ var wrapDeps = (deps) => (deps.map((dep) => { /******/ if(dep !== null && typeof dep === "object") { /******/ if(dep[webpackQueues]) return dep; /******/ if(dep.then) { /******/ var queue = []; /******/ queue.d = 0; /******/ dep.then((r) => { /******/ obj[webpackExports] = r; /******/ resolveQueue(queue); /******/ }, (e) => { /******/ obj[webpackError] = e; /******/ resolveQueue(queue); /******/ }); /******/ var obj = {}; /******/ obj[webpackQueues] = (fn) => (fn(queue)); /******/ return obj; /******/ } /******/ } /******/ var ret = {}; /******/ ret[webpackQueues] = x => {}; /******/ ret[webpackExports] = dep; /******/ return ret; /******/ })); /******/ __webpack_require__.a = (module, body, hasAwait) => { /******/ var queue; /******/ hasAwait && ((queue = []).d = -1); /******/ var depQueues = new Set(); /******/ var exports = module.exports; /******/ var currentDeps; /******/ var outerResolve; /******/ var reject; /******/ var promise = new Promise((resolve, rej) => { /******/ reject = rej; /******/ outerResolve = resolve; /******/ }); /******/ promise[webpackExports] = exports; /******/ promise[webpackQueues] = (fn) => (queue && fn(queue), depQueues.forEach(fn), promise["catch"](x => {})); /******/ module.exports = promise; /******/ body((deps) => { /******/ currentDeps = wrapDeps(deps); /******/ var fn; /******/ var getResult = () => (currentDeps.map((d) => { /******/ if(d[webpackError]) throw d[webpackError]; /******/ return d[webpackExports]; /******/ })) /******/ var promise = new Promise((resolve) => { /******/ fn = () => (resolve(getResult)); /******/ fn.r = 0; /******/ var fnQueue = (q) => (q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))); /******/ currentDeps.map((dep) => (dep[webpackQueues](fnQueue))); /******/ }); /******/ return fn.r ? promise : getResult(); /******/ }, (err) => ((err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue))); /******/ queue && queue.d < 0 && (queue.d = 0); /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module used 'module' so it can't be inlined /******/ var __webpack_exports__ = __webpack_require__(907); /******/ __webpack_exports__ = globalThis.pdfjsLib = await (globalThis.pdfjsLibPromise = __webpack_exports__); /******/ var __webpack_exports__AbortException = __webpack_exports__.AbortException; /******/ var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer; /******/ var __webpack_exports__AnnotationEditorParamsType = __webpack_exports__.AnnotationEditorParamsType; /******/ var __webpack_exports__AnnotationEditorType = __webpack_exports__.AnnotationEditorType; /******/ var __webpack_exports__AnnotationEditorUIManager = __webpack_exports__.AnnotationEditorUIManager; /******/ var __webpack_exports__AnnotationLayer = __webpack_exports__.AnnotationLayer; /******/ var __webpack_exports__AnnotationMode = __webpack_exports__.AnnotationMode; /******/ var __webpack_exports__CMapCompressionType = __webpack_exports__.CMapCompressionType; /******/ var __webpack_exports__DOMSVGFactory = __webpack_exports__.DOMSVGFactory; /******/ var __webpack_exports__DrawLayer = __webpack_exports__.DrawLayer; /******/ var __webpack_exports__FeatureTest = __webpack_exports__.FeatureTest; /******/ var __webpack_exports__GlobalWorkerOptions = __webpack_exports__.GlobalWorkerOptions; /******/ var __webpack_exports__ImageKind = __webpack_exports__.ImageKind; /******/ var __webpack_exports__InvalidPDFException = __webpack_exports__.InvalidPDFException; /******/ var __webpack_exports__MissingPDFException = __webpack_exports__.MissingPDFException; /******/ var __webpack_exports__OPS = __webpack_exports__.OPS; /******/ var __webpack_exports__Outliner = __webpack_exports__.Outliner; /******/ var __webpack_exports__PDFDataRangeTransport = __webpack_exports__.PDFDataRangeTransport; /******/ var __webpack_exports__PDFDateString = __webpack_exports__.PDFDateString; /******/ var __webpack_exports__PDFWorker = __webpack_exports__.PDFWorker; /******/ var __webpack_exports__PasswordResponses = __webpack_exports__.PasswordResponses; /******/ var __webpack_exports__PermissionFlag = __webpack_exports__.PermissionFlag; /******/ var __webpack_exports__PixelsPerInch = __webpack_exports__.PixelsPerInch; /******/ var __webpack_exports__PromiseCapability = __webpack_exports__.PromiseCapability; /******/ var __webpack_exports__RenderingCancelledException = __webpack_exports__.RenderingCancelledException; /******/ var __webpack_exports__UnexpectedResponseException = __webpack_exports__.UnexpectedResponseException; /******/ var __webpack_exports__Util = __webpack_exports__.Util; /******/ var __webpack_exports__VerbosityLevel = __webpack_exports__.VerbosityLevel; /******/ var __webpack_exports__XfaLayer = __webpack_exports__.XfaLayer; /******/ var __webpack_exports__build = __webpack_exports__.build; /******/ var __webpack_exports__createValidAbsoluteUrl = __webpack_exports__.createValidAbsoluteUrl; /******/ var __webpack_exports__fetchData = __webpack_exports__.fetchData; /******/ var __webpack_exports__getDocument = __webpack_exports__.getDocument; /******/ var __webpack_exports__getFilenameFromUrl = __webpack_exports__.getFilenameFromUrl; /******/ var __webpack_exports__getPdfFilenameFromUrl = __webpack_exports__.getPdfFilenameFromUrl; /******/ var __webpack_exports__getXfaPageViewport = __webpack_exports__.getXfaPageViewport; /******/ var __webpack_exports__isDataScheme = __webpack_exports__.isDataScheme; /******/ var __webpack_exports__isPdfFile = __webpack_exports__.isPdfFile; /******/ var __webpack_exports__noContextMenu = __webpack_exports__.noContextMenu; /******/ var __webpack_exports__normalizeUnicode = __webpack_exports__.normalizeUnicode; /******/ var __webpack_exports__renderTextLayer = __webpack_exports__.renderTextLayer; /******/ var __webpack_exports__setLayerDimensions = __webpack_exports__.setLayerDimensions; /******/ var __webpack_exports__shadow = __webpack_exports__.shadow; /******/ var __webpack_exports__updateTextLayer = __webpack_exports__.updateTextLayer; /******/ var __webpack_exports__version = __webpack_exports__.version; /******/ export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__CMapCompressionType as CMapCompressionType, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__Outliner as Outliner, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__PromiseCapability as PromiseCapability, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__UnexpectedResponseException as UnexpectedResponseException, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__renderTextLayer as renderTextLayer, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__updateTextLayer as updateTextLayer, __webpack_exports__version as version }; /******/ //# sourceMappingURL=pdf.mjs.map ================================================ FILE: public/assets/lib/vendor/pdfjs/pdf.sandbox.js ================================================ /** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2023 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = globalThis.pdfjsSandbox = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { QuickJSSandbox: () => (/* binding */ QuickJSSandbox) }); ;// CONCATENATED MODULE: ./external/quickjs/quickjs-eval.js var Module=(()=>{var _scriptDir=typeof document!=='undefined'&&document.currentScript?document.currentScript.src:undefined;return function(Module){Module=Module||{};var c;c||(c=typeof Module!=='undefined'?Module:{});var h,n;c.ready=new Promise(function(a,b){h=a;n=b;});var r=Object.assign({},c),t="";"undefined"!=typeof document&&document.currentScript&&(t=document.currentScript.src);_scriptDir&&(t=_scriptDir);0!==t.indexOf("blob:")?t=t.substr(0,t.replace(/[?#].*/,"").lastIndexOf("/")+1):t="";var aa=c.print||console.log.bind(console),u=c.printErr||console.warn.bind(console);Object.assign(c,r);r=null;var v;c.wasmBinary&&(v=c.wasmBinary);var noExitRuntime=c.noExitRuntime||!0;"object"!=typeof WebAssembly&&w("no native wasm support detected");var x,y=!1;function z(a,b,d,e){var f={string:function(l){var p=0;if(null!==l&&void 0!==l&&0!==l){var S=(l.length<<2)+1;p=A(S);B(l,C,p,S);}return p;},array:function(l){var p=A(l.length);D.set(l,p);return p;}};a=c["_"+a];var g=[],k=0;if(e)for(var m=0;m<e.length;m++){var q=f[d[m]];q?(0===k&&(k=E()),g[m]=q(e[m])):g[m]=e[m];}d=a.apply(null,g);return d=function(l){0!==k&&F(k);return"string"===b?G(l):"boolean"===b?!!l:l;}(d);}var H="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function I(a,b){for(var d=b+NaN,e=b;a[e]&&!(e>=d);)++e;if(16<e-b&&a.buffer&&H)return H.decode(a.subarray(b,e));for(d="";b<e;){var f=a[b++];if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var k=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|k:(f&7)<<18|g<<12|k<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023));}}else d+=String.fromCharCode(f);}return d;}function G(a){return a?I(C,a):"";}function B(a,b,d,e){if(0<e){e=d+e-1;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var k=a.charCodeAt(++f);g=65536+((g&1023)<<10)|k&1023;}if(127>=g){if(d>=e)break;b[d++]=g;}else{if(2047>=g){if(d+1>=e)break;b[d++]=192|g>>6;}else{if(65535>=g){if(d+2>=e)break;b[d++]=224|g>>12;}else{if(d+3>=e)break;b[d++]=240|g>>18;b[d++]=128|g>>12&63;}b[d++]=128|g>>6&63;}b[d++]=128|g&63;}}b[d]=0;}}function J(a){for(var b=0,d=0;d<a.length;++d){var e=a.charCodeAt(d);55296<=e&&57343>=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++d)&1023);127>=e?++b:b=2047>=e?b+2:65535>=e?b+3:b+4;}return b;}function K(a){var b=J(a)+1,d=L(b);d&&B(a,D,d,b);return d;}var M,D,C,N;function ba(){var a=x.buffer;M=a;c.HEAP8=D=new Int8Array(a);c.HEAP16=new Int16Array(a);c.HEAP32=N=new Int32Array(a);c.HEAPU8=C=new Uint8Array(a);c.HEAPU16=new Uint16Array(a);c.HEAPU32=new Uint32Array(a);c.HEAPF32=new Float32Array(a);c.HEAPF64=new Float64Array(a);}var O,ca=[],da=[],ea=[];function fa(){var a=c.preRun.shift();ca.unshift(a);}var P=0,Q=null,R=null;function w(a){if(c.onAbort)c.onAbort(a);a="Aborted("+a+")";u(a);y=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");n(a);throw a;}function T(a){return a.startsWith("data:application/octet-stream;base64,");}var U;U="data:application/octet-stream;base64,AGFzbQEAAAAByQZtYAR/fn9/AX5gA39/fwF/YAJ/fwF/YAJ/fwBgAX8Bf2AFf35/f38BfmADf39/AGABfwBgAXwBfGACf34BfmAEf39/fwF/YAJ/fgBgAn9/AX5gAn9+AX9gA39/fgF/YAN/fn8BfmABfgF/YAN/fn8AYAZ/fn9/f38BfmADf35/AX9gBX9/f39/AX9gBn9+fn9/fwF+YAN/fn4BfmAEf39/fwBgBH9/fn8Bf2ADf39/AX5gBH9/f38BfmAGf39/f39/AX9gBX9+fn5+AGABfwF+YAN/fn4Bf2ACfHwBfGABfgF+YAV/fn9+fwF/YAV/fn5/fwF+YAd/fn9+fn5/AX9gAABgBX9/f39/AGAEf35+fwBgBX9+fn5/AX9gB39/f39/f38Bf2AGf35/fn5/AX9gBH9+f34BfmACfn8Bf2AEf35+fwF/YAJ+fwBgCX9/f39/f39/fwF/YAR/fn5/AX5gBn9/f39/fwF+YAN/fn4AYAR/fn9/AX9gBX9+fn9/AGACfn4BfmAHf35/f39/fwF+YAF/AXxgA39/fgBgBH9+f38AYAR/fn9+AX9gBH9+fn4Bf2AEf39/fgF/YAh/f39/f39/fwF/YAd/f39/f39/AGACfH8BfGADfn9/AX9gA3x8fwF8YAR/f35/AGAEf35+fgF+YAABf2AGf3x/f39/AX9gAAF8YAV/fn9+fwF+YAF8AX5gAX4BfGAFf39+f38Bf2AGf39+fn5+AX9gBn9/f39/fwBgAn98AGAEf39+fwF+YAR/fn9+AGAFf39/f34BfmAHf35+fn9/fwF+YAR+fn5+AX9gCn9/f39/f39/f38Bf2AHf39/f39/fgF+YAd8f39/f39/AGADf39+AX5gBX9/f39/AX5gBX9+f39/AGAFf3x/f38BfmAGf35+fn5/AX9gBH98f38Bf2AGf35/f39/AX9gBX9/fn5/AX9gBX9+f39/AX9gBn9/fn5/fwF/YAV/fn5+fgF/YAJ/fwF8YAR/f35+AX9gBX9/fn5+AX5gB39/f35+fn8Bf2AEf39/fgF+YAJ8fwF/YAJ8fAF/YAh/fn5+fn9+fgF+YAN/fnwBfmAAAX5gAn5+AXxgA35+fgF/YAN/f3wAAkkMAWEBYQAXAWEBYgAkAWEBYwAKAWEBZABFAWEBZQADAWEBZgAGAWEBZwADAWEB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QgBSDwsgAactAAUhAiAAIAEQDCACQX9zQYABcUEHdg8LIANBB2tBbU0EQCABEEkiBUQAAAAAAAAAAGIgBb1C////////////AINCgYCAgICAgPj/AFRxDwsgACABEAxBASECCyACCwsAIAAgAUEAEKAECxkAIAAoAhAgARDoASIBRQRAIAAQyQELIAELPwEBfyMAQRBrIgIkAAJ/IAEgACgCEEcEQCACIAE2AgAgAEG8/QAgAhAVQX8MAQsgABARCyEAIAJBEGokACAACygBAX8jAEEQayICJAAgAiABOwEOIAAgAkEOakECEIoBGiACQRBqJAALCwAgACABQQEQ4gULxQoCBX8PfiMAQeAAayIFJAAgBEL///////8/gyEMIAIgBIVCgICAgICAgICAf4MhCiACQv///////z+DIg1CIIghDiAEQjCIp0H//wFxIQcCQAJAIAJCMIinQf//AXEiCUH//wFrQYKAfk8EQCAHQf//AWtBgYB+Sw0BCyABUCACQv///////////wCDIgtCgICAgICAwP//AFQgC0KAgICAgIDA//8AURtFBEAgAkKAgICAgIAghCEKDAILIANQIARC////////////AIMiAkKAgICAgIDA//8AVCACQoCAgICAgMD//wBRG0UEQCAEQoCAgICAgCCEIQogAyEBDAILIAEgC0KAgICAgIDA//8AhYRQBEAgAiADhFAEQEKAgICAgIDg//8AIQpCACEBDAMLIApCgICAgICAwP//AIQhCkIAIQEMAgsgAyACQoCAgICAgMD//wCFhFAEQCABIAuEIQJCACEBIAJQBEBCgICAgICA4P//ACEKDAMLIApCgICAgICAwP//AIQhCgwCCyABIAuEUARAQgAhAQwCCyACIAOEUARAQgAhAQwCCyALQv///////z9YBEAgBUHQAGogASANIAEgDSANUCIGG3kgBkEGdK18pyIGQQ9rEHNBECAGayEGIAUpA1giDUIgiCEOIAUpA1AhAQsgAkL///////8/Vg0AIAVBQGsgAyAMIAMgDCAMUCIIG3kgCEEGdK18pyIIQQ9rEHMgBiAIa0EQaiEGIAUpA0ghDCAFKQNAIQMLIANCD4YiC0KAgP7/D4MiAiABQiCIIgR+IhAgC0IgiCITIAFC/////w+DIgF+fCIPQiCGIhEgASACfnwiCyARVK0gAiANQv////8PgyINfiIVIAQgE358IhEgDEIPhiADQjGIhCISQv////8PgyIDIAF+fCIUIA8gEFStQiCGIA9CIIiEfCIPIAIgDkKAgASEIgx+IhYgDSATfnwiDiASQiCIQoCAgIAIhCICIAF+fCIQIAMgBH58IhJCIIZ8Ihd8IQEgByAJaiAGakH//wBrIQYCQCACIAR+IhggDCATfnwiBCAYVK0gBCAEIAMgDX58IgRWrXwgAiAMfnwgBCAEIBEgFVStIBEgFFatfHwiBFatfCADIAx+IgMgAiANfnwiAiADVK1CIIYgAkIgiIR8IAQgAkIghnwiAiAEVK18IAIgAiAQIBJWrSAOIBZUrSAOIBBWrXx8QiCGIBJCIIiEfCICVq18IAIgAiAPIBRUrSAPIBdWrXx8IgJWrXwiBEKAgICAgIDAAINQRQRAIAZBAWohBgwBCyALQj+IIQMgBEIBhiACQj+IhCEEIAJCAYYgAUI/iIQhAiALQgGGIQsgAyABQgGGhCEBCyAGQf//AU4EQCAKQoCAgICAgMD//wCEIQpCACEBDAELAn4gBkEATARAQQEgBmsiB0GAAU8EQEIAIQEMAwsgBUEwaiALIAEgBkH/AGoiBhBzIAVBIGogAiAEIAYQcyAFQRBqIAsgASAHEKECIAUgAiAEIAcQoQIgBSkDMCAFKQM4hEIAUq0gBSkDICAFKQMQhIQhCyAFKQMoIAUpAxiEIQEgBSkDACECIAUpAwgMAQsgBEL///////8/gyAGrUIwhoQLIAqEIQogC1AgAUIAWSABQoCAgICAgICAgH9RG0UEQCAKIAJCAXwiASACVK18IQoMAQsgCyABQoCAgICAgICAgH+FhFBFBEAgAiEBDAELIAogAiACQgGDfCIBIAJUrXwhCgsgACABNwMAIAAgCjcDCCAFQeAAaiQAC2oBAn8CQCAAKALYAiIDRQ0AIAAoAuACIgQgACgC3AJODQAgACgC6AIgAUsNACAAKALkAiACRg0AIAMgBEEDdGoiAyACNgIEIAMgATYCACAAIAE2AugCIAAgBEEBajYC4AIgACACNgLkAgsLDAAgACgCQEF/ENADCyEAIAAgASACQoCAgIAwIAMgBEECEOMBIQIgACABEAwgAgsZACABBEAgACABQRBrrUKAgICAkH+EEAwLC28BAn8gAUIgiKciAyABpyICQQBIckUEQCACEJUBDwsgA0F4RgRAIAAgACgCECACENYCEBkPC0EAIQIgACABEJgEIgEQDQR/QQAFIAFCgICAgHCDQoCAgICAf1EEQCAAIAEQmAIPCyAAIAGnEKUECwvrAQICfwF+QoCAgIDgACEDIAAoAhQEfkKAgICA4AAFIAAoAgQhASAAKAIIIgJFBEAgACgCACABEBogAEEANgIEIAAoAgBBLxAyDwsgACgCDCACSgRAIAAoAgAoAhAgASACIAAoAhAiAXQgAWtBEWoQ5wEiAUUEQCAAKAIEIQELIAAgATYCBAsgASAAKAIQIgIEfyACBSABIAAoAghqQQA6ABAgACgCEAtBH3StIAEpAgRC/////3eDhCIDNwIEIAEgA0KAgICAeIMgADUCCEL/////B4OENwIEIABBADYCBCABrUKAgICAkH+ECwsPACAAKAJAQYACaiABEB4LSwECfyABQoCAgIBwWgR/IAGnIgMvAQYiAkENRgRAQQEPCyACQSlGBEAgAygCIC0AEA8LIAAoAhAoAkQgAkEYbGooAhBBAEcFQQALCxAAIAAgACgCKCkDCEEBEFMLFAEBfiAAIAEQLiECIAAgARAMIAILcgEBfwJ/IAAoAggiAiAAKAIMTgRAQX8gACACQQFqIAEQ1QINARoLAkAgACgCEARAIAAgACgCCCICQQFqNgIIIAAoAgQgAkEBdGogATsBEAwBCyAAIAAoAggiAkEBajYCCCACIAAoAgRqIAE6ABALQQALCywBAX8jAEEQayIDJAAgAyACNgIMIABB3ABqQYABIAEgAhDZAhogA0EQaiQACygBAX8CQCAAQoCAgIBwVA0AIAEgAKciAS8BBkcNACABKAIgIQILIAILKAAgACACQTAgAkEAEBQiAhANBEAgAUIANwMAQX8PCyAAIAEgAhCwAQsNACAAIAEgAkEAEKoDC38BA38gACEBAkAgAEEDcQRAA0AgAS0AAEUNAiABQQFqIgFBA3ENAAsLA0AgASICQQRqIQEgAigCACIDQX9zIANBgYKECGtxQYCBgoR4cUUNAAsgA0H/AXFFBEAgAiAAaw8LA0AgAi0AASEDIAJBAWoiASECIAMNAAsLIAEgAGsLFQAgACgCACAAKAIEEBogAEEANgIECwoAIABBMGtBCkkLIwECfyAAKAIAIgEgACgC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EBAPCyAAIAJBOWtB/wFxEBAPCyAAIAJBNWtB/wFxEBAPCyAAIAJBMWtB/wFxEBAPCyAAIAJBLWtB/wFxEBAPCyAAIAJBKWtB/wFxEBAPCyAAIAJBJWtB/wFxEBAPCyAAIAJBIWtB/wFxEBAPCyAAIAJBHWtB/wFxEBAPCyACQf8BSw0BAkACQAJAIAFB2ABrDgMAAQIECyAAQcABEBAMBQsgAEHBARAQDAQLIABBwgEQEAwDCyABQSJGDQELIAAgAUH/AXEQECAAIAJB//8DcRAxDwsgACACQRRrQf8BcRAQDwsgACACQf8BcRAQCxsBAX8gACABEDsEf0EABSAAQak2QQAQFkF/CwsZAQF/IAEgAhBAIgNFBEAgACACEJwDCyADCyYBAX8jAEEQayICJAAgAkEANgIMIABBASABQQAQqwMgAkEQaiQACxkAIAAoAhAgARCcAiIBRQRAIAAQyQELIAELbQEBfyMAQYACayIFJAAgBEGAwARxIAIgA0xyRQRAIAUgAUH/AXEgAiADayICQYACIAJBgAJJIgEbEEsaIAFFBEADQCAAIAVBgAIQZyACQYACayICQf8BSw0ACwsgACAFIAIQZwsgBUGAAmokAAsPACAAKAJAQYACaiABEBALbwIBfgF/IAAhBAJAAkAgARASDQAgACABQTsgAUEAEBQiAxANBEAgAw8LIAMQIg0BIAAgAxAMIAAgARCPAyIEDQBCgICAgOAADwsgBCgCKCACQQN0aikDABAPIQMLIAAgAyACEFMhASAAIAMQDCABCzEAIAAgASACQoCAgIAIfEL/////D1gEfiACQv////8PgwUgArkQFwsgA0GHgAEQzQILEAAgACAANgIEIAAgADYCAAt1AQF+IAAgASAEfiACIAN+fCADQiCIIgIgAUIgiCIEfnwgA0L/////D4MiAyABQv////8PgyIBfiIFQiCIIAMgBH58IgNCIIh8IAEgAn4gA0L/////D4N8IgFCIIh8NwMIIAAgBUL/////D4MgAUIghoQ3AwALUAEBfgJAIANBwABxBEAgASADQUBqrYYhAkIAIQEMAQsgA0UNACACIAOtIgSGIAFBwAAgA2utiIQhAiABIASGIQELIAAgATcDACAAIAI3AwgLYgACQAJAIAFBAEgNACAAKAKsAiABTA0AIAAoAqQCIAFBFGxqIgAg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GnZxIAAgBnFyEJ8DGyEHDAILIAAgBkHG0QAQeSEHDAELQX8hBwsgCkEQaiQAIAcLTAECfyMAQRBrIgMkAAJAIAFBgIABcUUEQCABQYCAAnFFDQEgABD7AUUNAQsgA0EANgIMIABBBCACQQAQqwNBfyEECyADQRBqJAAgBAvMAQECfwJAIAFCgICAgHBaBEAgAachAwNAAkAgAy0ABUEEcUUNACAAKAIQKAJEIAMvAQZBGGxqKAIUIgRFDQAgBCgCEEUNACAAIAOtQoCAgIBwhBAPIgEgAiAEKAIQERMAIQIgACABEAwgAg8LIAOtQoCAgIBwhBAPIQEgAEEAIAMgAhBPIQQgACABEAwgBA0CAkAgAy8BBkEVa0H//wNxQQhLDQAgACACEKUDIgRFDQAgBEEfdQ8LIAMoAhAoAiwiAw0ACwtBACEECyAECxoAIAAgASACQQBOBH4gAq0FIAK4EBcLEKEBCwsAIABB/////wdxC8cJAgR+BH8jAEHwAGsiCiQAIARC////////////AIMhBQJAAkAgAVAiCSACQv///////////wCDIgZCgICAgICAwP//AH1CgICAgICAwICAf1QgBlAbRQRAIANCAFIgBUKAgICAgIDA//8AfSIIQoCAgICAgMCAgH9WIAhCgICAgICAwICAf1EbDQELIAkgBkKAgICAgIDA//8AVCAGQoCAgICAgMD//wBRG0UEQCACQoCAgICAgCCEIQQgASEDDAILIANQIAVCgICAgICAwP//AFQgBUKAgICAgIDA//8AURtFBEAgBEKAgICAgIAghCEEDAILIAEgBkKAgICAgIDA//8AhYRQBEBCgICAgICA4P//ACACIAEgA4UgAiAEhUKAgICAgICAgIB/hYRQIgkbIQRCACABIAkbIQMMAgsgAyAFQoCAgICAgMD//wCFhFANASABIAaEUARAIAMgBYRCAFINAiABIAODIQMgAiAEgyEEDAILIAMgBYRQRQ0AIAEhAyACIQQMAQsgAyABIAEgA1QgBSAGViAFIAZRGyIMGyEFIAQgAiAMGyIIQv///////z+DIQYgAiAEIAwbIgdCMIinQf//AXEhCyAIQjCIp0H//wFxIglFBEAgCkHgAGogBSAGIAUgBiAGUCIJG3kgCUEGdK18pyIJQQ9rEHMgCikDaCEGIAopA2AhBUEQIAlrIQkLIAEgAyAMGyEDIAdC////////P4MhBCALRQRAIApB0ABqIAMgBCADIAQgBFAiCxt5IAtBBnStfKciC0EPaxBzQRAgC2shCyAKKQNYIQQgCikDUCEDCyAEQgOGIANCPYiEQoCAgICAgIAEhCECIAZCA4YgBUI9iIQhBCADQgOGIQEgByAIhSEDAkAgCSALRg0AIAkgC2siC0H/AEsEQEIAIQJCASEBDAELIApBQGsgASACQYABIAtrEHMgCkEwaiABIAIgCxChAiAKKQMwIAopA0AgCikDSIRCAFKthCEBIAopAzghAgsgBEKAgICAgICABIQhByAFQgOGIQYCQCADQgBTBEBCACEDQgAhBCABIAaFIAIgB4WEUA0CIAYgAX0hBSAHIAJ9IAEgBlatfSIEQv////////8DVg0BIApBIGogBSAEIAUgBCAEUCILG3kgC0EGdK18p0EMayILEHMgCSALayEJIAopAyghBCAKKQMgIQUMAQsgASAGfCIFIAFUrSACIAd8fCIEQoCAgICAgIAIg1ANACAFQgGDIARCP4YgBUIBiISEIQUgCUEBaiEJIARCAYghBAsgCEKAgICAgICAgIB/gyEBIAlB//8BTgRAIAFCgICAgICAwP//AIQhBEIAIQMMAQtBACELAkAgCUEASgRAIAkhCwwBCyAKQRBqIAUgBCAJQf8AahBzIAogBSAEQQEgCWsQoQIgCikDACAKKQMQIAopAxiEQgBSrYQhBSAKKQMIIQQLIARCPYYgBUIDiIQiAiAFp0EHcSIJQQRLrXwiAyACVK0gBEIDiEL///////8/gyALrUIwhoQgAYR8IQQCQCAJQQRGBEAgBCADQgGDIgEgA3wiAyABVK18IQQMAQsgCUUNAQsLIAAgAzcDACAAIAQ3AwggCkHwAGokAAvJBQEFfyMAQeAAayIDJAAgAyABNgJcAkACQAJAAkACQAJAAkACQAJAAkACQANAIAJBFGwiBCADakEUayEFA0ACQCADIAMoAlwiAUEEajYCXAJAAkACQAJAAkAgASgCACIGDggAAQIDAwMECAULIAJBBE4NECADIAFBCGo2AlwgASgCBCEBIAMgBGoiBCAAKAIMIAAoAhAQiAEgAkEBaiECIAQgARC2BEUNBgwJCyACQQRODQ4gAyABQQhqNgJcIAEoAgQhASADIARqIgQg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A0EIaiACEI4ERQRAQQAhBCABIAMpAwgiAkKAgICAgICAEFoEfiAAQb8OEGtBfyEEQgAFIAILNwMACyADQRBqJAAgBAsNACAAIAEgAhAPEM4FC8wBAgF/AXwCfwNAAkACQAJ/AkACQCACEFYOCAAAAAAEBAQBBAsgAqcMAQsgAhBJIgS9IgJCNIinQf8PcSIDQZ0ISw0BIASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyEAQQAMAwtBACEAQQAgA0HSCEsNAhpBACACQv////////8Hg0KAgICAgICACIQgA0GTCGuthkIgiKciAGsgACACQgBTGyEAQQAMAgsgACACEKABIgIQDUUNAAtBACEAQX8LIQMgASAANgIAIAMLCwAgACABIAIQkwILLwEBfyMAQdAAayIDJAAgAyAAIANBEGogARCJATYCACAAIAIgAxAWIANB0ABqJAALLAEBfyAAKAIQIgEtAIgBRQRAIAFBAToAiAEgAEHaC0EAEFAgAUEAOgCIAQsLDQAgACABIAEQQxCtAwsWACAAIAEgAiADIAQgBSAAKQMwEIsCCxsAIAAgAUH/AXEQECAAIAIgACgCBGtBBGsQHguOAQECfyMAQRBrIgIkAAJ/IAEEQCAAQSBqIAAgAEHBAGtBGkkbIABB/wBNDQEaIAJBBGogAEECELcDGiACKAIEDAELIABBIGsgACAAQeEAa0EaSRsgAEH/AE0NABogAkEEaiAAQQAQtwMhASACKAIEIgMgACADQf8ASxsgACABQQFGGwshACACQRBqJAAgAAtmAQF/An9BACAAKAIIIgIgAU8NABpBfyAAKAIMDQAaIAAoAhQgACgCACACQQNsQQF2IgIgASABIAJJGyIBIAAoAhARAQAiAkUEQCAAQQE2AgxBfw8LIAAgATYCCCAAIAI2AgBBAAsLVQECfwJAIAFCgICAgHBUDQAgAaciAy8BBiIEQQpLQQEgBHRB8AlxRXINACAAIAMpAyAQDCADIAI3AyAPCyAAIAIQDCABEA1FBEAgAEGszABBABAWCwsnACAAIAApA8ABIAIgARAPIgFBAxDsARogACABIAMQ7gUgACABEAwLIAEBfiAAIAAgAiABIANBBEEAEMsBIgUgASAEENABIAULjgIBAn8jAEEwayIFJAACfyACIAEoAgBPBEAgBSACNgIkIAUg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ARAOIAAgBi0AOBBuIABBDhAOIAAoAhBBPUcNACAAQREQDiAAQQYQDiAAQasBEA4gAEHpAEF/EB0hBCAAEBENBiAAQQ4QDiAAEGINBiAGKAIwIghBtgFHIAhBPEdxRQRAIAAgBigCNBCtAQsgACAEECALIAAgBigCMCAGKAIsIAYoAjQgBigCPEEBIAwQ1AELIAAoAhBB3QBGDQAgB0Glf0YEQEHOzAAhCAwECyAAQSwQMEUNAQwFCwsgAEGDARAOIAAoAkAQqgEgABARDQMLAkAgBUUNACAAKAIQQT1HDQBBfyEHIABB6wBBfxAdIQEgABARDQQgACAKECAgAwRAIABBDhAOCyAAEGINBCAAQesAIAsQHRogACABECBBASEHDAQLIANFBEAgAEGAOUEAEBUMAwsgACgCQCgCgAIgDWpBsQEgDiANaxBLGiAAKAJAKAKkAiAKQRRsaiIAIAAoAgBBAWs2AgBBACEHDAMLIAAgCEEAEBUMAQsgACgCACAGKAI0EBMLQX8hBwsgBkFAayQAIAcLKwAgACgCQCgCpAFBAE4EQCAAQQYQDiAAQdkAEA4gACAAKAJALwGkARAYCwsSACAAQYN/RiAAQdUAakEuSXILEwAgACABIAIgAyAEQQBBABCKAgusAQIBfwF+IAApAgQiBKdB/////wdxIQMCQAJAIARCgICAgAiDUEUEQCACIAMgAiADShshAyAAQRBqIQADQCACIANGDQIgACACQQF0ai8BACABRg0DIAJBAWohAgwACwALIAFB/wFLDQAgAiADIAIgA0obIQMgAEEQaiEAIAFB/wFxIQEDQCACIANGDQEgACACai0AACABRg0CIAJBAWohAgwACwALQX8hAgsgAguNAQEBfyMAQRBrIgMkACADIAI3AwgCQCAAIAFBhgEgAUEAEBQiAhANDQAgACACEDsEQCAAIAIgAUEBIANBCGoQNiICEA0NASACECINASACECgNASAAIAIQDCAAQco8QQAQFkKAgICA4AAhAgwBCyAAIAIQDCAAIAFBASADQQhqEJAFIQILIANBEGokACACC6MBAgN/AX4gAEEQaiECIAEoAgAiBEEBaiEDAkAgACkCBCIFQoCAgIAIg1BFBEAgAiAEQQF0ai8BACIAQYD4A3FBgLADRyADIAWnQf////8HcU5yDQEgAiADQQF0ai8B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BiAHQQhrIggpAwA3AwAMyQELIAkgB0EYayIGKQMAEAwgBiAHQRBrIgYpAwA3AwAgBiAHQQhrIggpAwA3AwAMyAELIAcgB0EIaykDABAPNwMAIAdBCGohCAzHAQsgByAHQRBrKQMAEA83AwAgByAHQQhrKQMAEA83AwggB0EQaiEIDMYBCyAHIAdBGGspAwAQDzcDACAHIAdBEGspAwAQDzcDCCAHIAdBCGspAwAQDzcDECAHQRhqIQgMxQELIAcgB0EIayIGKQMANwMAIAYgB0EQaykDABAPNwMAIAdBCGohCAzEAQsgByAHQQhrIgYpAwAiATcDACAGIAdBEGsiBikDADcDACAGIAEQDzcDACAHQQhqIQgMwwELIAcgB0EIayIGKQMAIgE3AwAgB0EQayIIKQMAIScgCCAHQRhrIggpAwA3AwAgBiAnNwMAIAggARAPNwMAIAdBCGohCAzCAQsgByAHQQhrIgYpAwAiATcDACAHQRBrIggpAwAhJyAIIAdBGGsiCCkDADcDACAGICc3AwAgCCAHQSBrIgYpAwA3AwAgBiABEA83AwAgB0EIaiEIDMEBCyAHQRBrIgYpAwAhASAGIAdBGGsiBikDADcDACAGIAE3AwAMvwELIAdBGGsiBikDACEBIAYgB0EQayIGKQMANwMAIAdBCGsiCCkDACEnIAggATcDACAGICc3AwAMvgELIAdBIGsiBikDACEBIAYgB0EYayIGKQMANwMAIAdBEGsiCCkDACEnIAggB0EIayIIKQMANwMAIAYgJzcDACAIIAE3AwAMvQELIAdBKGsiBikDACEBIAYgB0EgayIGKQMANwMAIAdBGGsiCCkDACEnIAggB0EQayIIKQMANwMAIAYgJzcDACAIIAdBCGsiBikDADcDACAGIAE3AwAMvAELIAdBCGsiBikDACEBIAYgB0EQayIGKQMANwMAIAdBGGsiCCkDACEnIAggATcDACAGICc3AwAMuwELIAdBEGsiBikDACEBIAYgB0EYayIGKQMANwMAIAdBIGsiCCkDACEnIAggATcDACAGICc3AwAMugELIAdBEGsiBikDACEBIAYgB0EYayIGKQMANwMAIAdBIGsiCCkDACEnIAggB0EoayIIKQMANwMAIAYgJzcDACAIIAE3AwAMuQELIAdBCGsiBikDACEBIAYgB0EQayIG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gIB4RiABQv////8PVnJFBEAgCCABQgF9Qv////8PgzcDAAwoCyARIAEQDzcDGCAJICFBjgEQkgINLCAJIAggESkDGBAfDCcLIAdBCGsiCCkDACIBQv////8PWARAIAggAUL/////D4U3AwAgByEIDCgLIAchCCMAQRBrIgokACAJIApBDGogB0EIayINKQMAEMYBIQwgDUKAgICAMCAKNQIMQv////8PhSAMGzcDACAKQRBqJABBf0EAIAwbRQ0nDCsLIAdBCGsiCCkDACIBIAdBEGsiCikDACInhEL/////D1gEQCAKICenIAGndK03AwAMJwsgCSAHQaABEMgCRQ0mDCoLIAdBCGsiCCkDACIBIAdBEGsiCikDACInhEL/////D1gEQCAKAn4gJ6cgAad2IgZBAE4EQCAGrQwBCyAGuBAXCzcDAAwmCyMAQRBrIgokACAHQQhrIg0pAwAhAQJ/AkAgCSAKQQxqIAdBEGsiDCkDABDpAwRAIAkgARAMDAELIAkgCkEIaiABEOkDDQAgDAJ+IAooAgwgCigCCHYiDEEATgRAIAytDAELIAy4EBcLNwMAQQAMAQsgDEKAgICAMDcDACANQoCAgIAwNwMAQX8LIQwgCkEQaiQAIAxFDSUMKQsgB0EIayIIKQMAIgEgB0EQayIKKQMAIieEQv////8PWARAIAogJ6cgAad1rTcDAAwlCyAJIAdBoQEQyAJFDSQMKAsgB0EIayIIKQMAIgEgB0EQayIKKQMAIieEQv////8PWARAIAogASAngzcDAAwkCyAJIAdBrQEQyAJFDSMMJwsgB0EIayIIKQMAIAdBEGsiCikDAIQiAUL/////D1gEQCAKIAE3AwAMIwsgCSAHQa8BEMgCRQ0iDCYLIAdBCGsiCCkDACIBIAdBEGsiCikDACInhEL/////D1gEQCAKIAEgJ4VC/////w+DNwMADCILIAkgB0GuARDIAkUNIQwlCyAHQQhrIggpAwAiASAHQRBrIgopAwAiJ4RC/////w9YBEAgCiAnpyABp0itQoCAgIAQhDcDAAwhCyAJIAdBowEQlgNFDSAMJAsgB0EIayIIKQMAIgEgB0EQayIKKQMAIieEQv////8PWARAIAogJ6cgAadMrUKAgICAEIQ3AwAMIAsgCSAHQaQBEJYDRQ0fDCMLIAdBCGsiCCkDACIB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ACACAn9BACACKAIQIgFBO0YNABpBACABQf0ARg0AGkEAIAIoAjANABogAhCZAQ0dQQELEPYCIAIQvQENHAweCyACEBENGyACKAIwBEAgAkGIEEEAEBUMHAsgAhCZAQ0bIAJBLxAOIAIQvQFFDRwMGwsgAhARDRogAhCFARogAhDWASACEIgCDRogAkHpAEF/EB0hACACIAIoAkAtAG5Bf3NBAXEiARDxAQ0aAkAgAigCEEGvf0cEQCAAIQMMAQsgAkHrAEF/EB0hAyACEBENGyACIAAQICACIAEQ8QENGwsgAiADECAMFwsgAhA1IQAgAhA1IQEgAigCQCAHQRBqIAsgASAAQQAQqwEgAhARDRkgAhDWASACIAAQICACEIgCDRkgAkHpACABEB0aIAIQuAINGSACQesAIAAQHRogAiABECAgAigCQBCqAQwaCyACEDUhACACEDUhASACEDUhAyACKAJAIAdBEGogCyABIABBABCrASACEBENGCACIAMQICACENYBIAIQuAINGCACIAAQICACQbp/EDANGCACEIgCDRggAigCEEE7RgRAIAIQEQ0ZCyACQeoAIAMQHRogAiABECAgAigCQBCqAQwZCyACEBENFyACENYBQQAhASAHQQA2AgwCQCACKAIQIgBBWEcEQCAAQShHDQEgAiAHQQxqQQAQqQEaDAELIAIoAkAtAGxBAnFFBEAgAkHwIUEAEBUMGQsgAhARDRhBASEBCyACQSgQMA0XIActAAxBAXFFBEAgASEEIwBBQGoiBSQAIAIoAgAhDSACKAJAIg4oArwBIRAgAhA1IQMgAhA1IREgAhA1IRIgAhA1IRMgAhCFARpBASEIIAIoAkAgBUEQaiALIBIgA0EBEKsBIAUgEDYCKCACQesAQX8QHSEUIAIoAkAoAoQCIQkgAiATECAgAigCECEAQVEhAUF/IQoCQAJAAkACQAJAAkACQAJAAkAgAkEEENYDDgIAAQgLIABBSUYhDCAAQVFGIgEhCCABIABBsX9GckUgAEFJR3ENASAAIQELIAIQEQ0EAkAgAigCECIAQfsARiAAQdsARnJFBEAgAEGDf0YEQCACKAIoRQ0CCyACQZLfAEEAEBUMBgtBASEIIAIgAUEAQQFBf0EAENUBQQBIDQcgBUEANgI8DAMLIAUgDSACKAIgEBkiBjYCPCACEBEEQCACKAIAIAYQEwwFCyACIAYgARC3AkUNASACKAIAIAYQEwwECwJAAkAgAigCEEEgckH7AEcNACACIAVBDGpBABCpASIBQVlHIAFBt39HcQ0AIAJBAEEAQQEgBSgCDEECcUEBENUBQQBODQEMBQsgAhC0Ag0EIAIgBUE4aiAFQTRqIAVBPGogBUEIakEAQQBBu38QvAENBCACIAUoAjggBSgCNCAFKAI8IAUoAghBBEEAENQBCyAFQQA2AjxBACEIDAILIAJBuwFBuwFBtwEgCBsgDBsQDiACIAYQHCACIA4vAbwBEBhBACEICyABIQALIAJB6wAgERAdGiACKAJAKAKEAiEKIAIgFBAgAkAgAigCECIMQT1HDQACQCACEBFFBEAgAkEAELsBRQ0BCyANIAYQEwwCCyAGRQ0AIAJBtwEQDiACIAYQHCACIA4vAbwBEBgLIA0gBhATAkACQAJAIAJBwwAQVCIBBEAgBUEBNgIsIAUgBSgCIEECajYCIEGDxgAhBiAMQT1GDQEMAwsgAigCEEG3f0cNASAEBEAgAkGS/QBBABAVDAQLIAxBPUcNAkG/OiEGIABBsX9HDQAgDi0AbkEBcUUgCEF/c3ENAgsgBSAGNgIAIAJBuiwgBRAVDAILIAJB0DhBABAVDAELIAIQEQ0AAkAgAQRAIAIQYkUNAQwCCyACEJkBDQELIAIgAigCQCgCvAEgEBC2AiACQf4AQf0AIAQbQfwAIAEbEA4gAkHrACADEB0aIAJBKRAwRQ0BC0F/IQoMAQsgAigCQCIAQYACaiIIIAAoAoQCIg0gCiAJayIGahDOARogCCAAKAKAAiAJaiAGEIoBGiAAKAKAAiAJakGxASAGEEsaIAIoAkAiBiAAKAKEAkEFazYCmAIgAyAGKAKsAiIAIAAgA0gbIQ4gDSAJayENIAMhAANAIAAgDkcEQCAGKAKkAiAAQRRsaiIMKAIEIgggCUggCCAKTnJFBEAgDCAIIA1qNgIECyAAQQFqIQAMAQsLIAIgERAgQX8hCiACELgCDQAgAiACKAJAKAK8ASAQELYCIAIgAxAgAn8gAQRAIAQEQCACQRQQDiACQQ4QDiACQSQQDiACQQAQGCACQYsBEA4gAkGCARAOQYMBDAILIAJBgAEQDiACQQAQbkGDAQwBCyACQf8AEA5BDgshACACQekAIBMQHRogAkEOEA4gAiASECAgAiAAEA4gAigCQBCqASACEO8BQQAhCgsgBUFAayQAIApFDRkMGAsgAigCQCgCvAEhBiACEIUBGiACKAIQIgBBO0YNE0FRIQMCQCACQQQQ1gMOAgASGAsgAEGxf0YgAEFRRnINECAAIgNBSUYNESACQQAQ9QQNFyACQQ4QDgwSCyACEBENFiADQcQAaiEBQQAhAAJAIAIoAjANACACKAIQQYN/Rw0AIAIoAigNACACKAIgIQALAn8gAigCQCIEQbACaiEDIAQoArwBIQUCQANAIAMoAgAiAwRAIAIgBSADKAIYELYCIAMoAhghBQJAIAEEQCADKAIMIgRBf0YNASAABEAgAygCBCAARw0CCwwECyADKAIIIgRBf0YNACAABEAgAygCBCAARw0BCwwDCyADKAIcBH8gAkGDARAOQQMFQQALIQQDQCADKAIQIARKBEAgAkEOEA4gBEEBaiEEDAELCyADKAIUQX9GDQEgAkEGEA4gAkHtACADKAIUEB0aIAJBDhAODAELCwJAIABFBEAgAQRAIAJB+jNBABAVDAILIAJByMIAQQAQFQwBCyACQYnaAEEAEBULQX8MAQsgAkHrACAEEB0aQQALDRYgAARAIAIQEQ0XCyACEL0BRQ0XDBYLIAIQEQ0VIAIQ1gEgAhCIAg0VIAIQhQEaIAIQNSEEQX8hASACKAJAIAdBEGogCyAEQX9BARCrASACQfsAEDANFUF/IQMCQANAAkACQAJAIAIoAhAiAEHBAGoOAgABAgsgAUEASAR/QX8FIAJB6wBBfxAdCyEAIAIgARAgA0AgAhARDRogAkEREA4gAhCZAQ0aIAJBOhAwDRogAkGrARAOIAIoAhBBv39GBEAgAkHqACAAEB0hAAwBCwsgAkHpAEF/EB0hASACIAAQIAwCCyACEBENGCACQToQMA0YIANBAE4EQCACQZkZQQAQFQwZCyABQQBIBEAgAkHrAEF/EB0hAQsgAkG0ARAOIAJBABA6IAIoAkAoAoQCQQRrIQMMAQsCQAJAIABB/QBHBEAgAUEATg0BIAJB9xhBABAVDBoLIAJB/QAQMA0ZIANBAEgNASACKAJAKAKAAiADaiABEF0gAigCQCgCpAIgAUEUbGogA0EEajYCBAwDCyACQQcQ8QFFDQEMGAsLIAIgARAgCyACIAQQICACQQ4QDiACKAJAEKoBDBILIAIQ1gEgAhARDRQgAhA1IQEgAhA1IQAgAhA1IQMgAhA1IQQgAkHsACABEB0aIAIoAkAgB0EQakEAQX9Bf0EBEKsBIAcgAzYCJCACEPcCDRQgAigCQBCqASACEPUCBEAgAkEOEA4gAkEGEA4gAkHtACADEB0aIAJBDhAOIAJB6wAgBBAdGgsCQAJAAkAgAigCEEE9ag4CAA8BCyACEBENFiACEIUBGiACIAEQICACKAIQQfsARgRAIAJBDhAODA4LIAJBKBAwDRYgAigCECIBQfsARiABQdsARnINAQJAIAFBg39GBEAgAigCKEUNAQsgAkGn3gBBABAVDBcLIA8gAigCIBAZIQECQCACEBFFBEAgAiABQUMQtwJBAE4NAQsgDyABEBMMFwsgAkG3ARAOIAIgARA6IAIgAigCQC8BvAEQGAwMCyACQbwMQQAQFQwVCyACQVFBAEEBQX9BARDVAUEATg0KDBQLIAIQEUUNFAwTCyACKAJALQBuQQFxBEAgAkGGwQBBABAVDBMLIAIQEQ0SIAIQiAINEiACEIUBGiACIAIoAkBB1ABBABCsASIAQQBIDRIgAkHvABAOIAJB2QAQDiACIABB//8DcRAYIAIQ1gEgAhC4Ag0SDA8LIAFBAXFFDQEgAUEEcQ0GIAJBABCLAUEqRg0BDAYLIAIoAigEQCACEPABDBELQVEhAwJAIAIgARDWAw4CAA8RCyACQYUBEFRFDQMgAkEBEIsBQUVHDQMgAUEEcQ0FCyACQbIRQQAQFQwPCyABQQRxRQRAIAJB9hBBABAVDA8LQX8hAUEAIQAgAkEAQQAQ+gJFDRAMEQsgAhARDQ0gAhC9AUUNDgwNCyACEJkBDQwCQCACKAJAKAKkAUEATgRAIAJB2QAQDiACIAIoAkAvAaQBEBgMAQsgAkEOEA4LIAIQvQFFDQ0MDAsgAigCICEBIwBB0ABrIgAkACAAIAIoAgAgAEEQaiABEIkBNgIAIAJBvSggABAVIABB0ABqJAAMCwtBACEAIAJBAUEAIAIoAhggAigCFBDYAQ0KDAwLIAJBKRAwDQkLIAJB7AAgABAdGiACEIUBGiACKAJAIAdBEGpBAEF/QX9BARCrASAHIAM2AiQgAhD3Ag0IIAIoAkAQqgEgAhDvASACEO8BIAIQ9QIEQCACQQ4QDiACQQYQDiACQe0AIAMQHRogAkEOEA4gAkHrACAEEB0aCyAAIQELIAIgARAgIAJB7QAgAxAdGiACQS8QDiACIAMQICACKAIQQURGBEAgAhARDQhBACEAIAIoAkAgB0EQakEAQX9Bf0ECEKsBIAIoAkAiASgCpAFBAE4EQCACKAIAIAFB0QAQWCIAQQBIDQkgAkHYABAOIAIgAigCQC8BpAEQGCACQdkAEA4gAiAAQf//A3EQGCACENYBCyACEPcCDQggAigCQCIBKAKkAUEATgR/IAJB2AAQDiACIABB//8DcRAYIAJB2QAQDiACIAIoAkAvAaQBEBggAigCQAUgAQsQqgELIAJB7gAQDiACIAQQIAwICyAAIQMLIAIQEQ0FIAJBACADQQAQ2AMNBQsgAiACKAJAKAK8ASAGELYCCyACQTsQMA0DIAIQNSEEIAIQNSEAIAIQNSEDIAIQNSEFIAIoAkAgB0EQaiALIAUgAEEAEKsBIAMhASACKAIQQTtHBEAgAiAEECAgAhCZAQ0EIAJB6QAgBRAdGiAEIQELIAJBOxAwDQMCQCACKAIQQSlGBEAgByABNgIcQQAhBCABIQAMAQsgAkHrACADEB0aIAIoAkAoAoQCIQQgAiAAECAgAhCZAQ0EIAJBDhAOIAEgA0YNACACQesAIAEQHRoLIAJBKRAwDQMgAigCQCgChAIhCCACIAMQICACELgCDQMgAiACKAJAKAK8ASAGELYCAkAgASADRiAAIAFGckUEQCACKAJAIgFBgAJqIgYgASgChAIiCSAIIARrIgNqEM4BGiAGIAEoAoACIARqIAMQigEaIAEoAoACIARqQbEBIAMQSxogAigCQCIDIAEoAoQCQQVrNgKYAiAAIAMoAqwCIgEgACABShshBiAJIARrIQkDQCAAIAZGDQIgAygCpAIgAEEUbGoiCigCBCIBIARIIAEgCE5yRQRAIAogASAJajYCBAsgAEEBaiEADAALAAsgAkHrACAAEB0aCyACIAUQICACKAJAEKoBCyACEO8BDAMLIAFBBHENACACQfERQQAQFQwBCyACEBENAEEAIQAgAkEBIANBABDYAw0AIAIQvQFFDQILQX8hAAwBC0EAIQALIA8gCxATIAAhAQsgB0EwaiQAIAELCAAgAEHPAUgLmAEBAX4CQAJAAkAgARAiRQ0AIAAgAUE8IAFBABAUIgEQDQ0CAkAgARASDQAgARAiRQRAIAAgARAMDAILIAAgAUHMASABQQAQFCEDIAAgARAMAkAgAxANDQAgAxASDQEgAxAoDQEgAxC1AQ0AIAAgAxAMIABB3ylBABAWDAMLIAMPCyACEA8PCyAAECkLQoCAgIDgACEBCyABCxIAIAEQ8gFFBEAgACABEIQFCwsNACAAQRpBJEEZEOsFC60CAQN+AkACQCACBEAgACABQc4BIAFBABAUIgMQDQ0CIAMQEkUEQCADEChFDQILIAAgAUHDASABQQAQFCIDEA0NAiAAIAEgAxDoAyEBIAAgAxAMIAEQDQRAIAEPCwJ+QoCAgIDgACEDIAAgAUHqACABQQAQFCIEEA1FBEAgAEEwEKQBIgMQDQRAIAAgBBAMIAMMAgsgAEEQEGwiAkUEQCAAIAMQDCAAIAQQDEKAgICA4AAMAgsgARAPIQUgAiAENwMIIAIgBTcDACADIAIQjQELIAMLIQMgACABEAwgAw8LIAAgAUHDASABQQAQFCIDEA0NAQsgACADEDtFBEAgACADEAwgAEHj0QBBABAWQoCAgIDgAA8LIAAgASADEOgDIQEgACADEAwgASEDCyADCykBAX8gAEKAgICAcINCgICAgJB/UQR/IACnKAIEQf////8HcQVBAQtFCy0BAX9BASEBAkACQAJAIABBDWsOBAIBAQIACyAAQS1GDQELIABBMUYhAQsgAQsKACAAIAEQDxAtC2kBAX8CQAJAIAFFDQAgASgCACICQQBMDQEgASACQQFrIgI2AgAgAg0AAkAgAS0ABUEBcQRAIAAgASkDGBAnIAEQnwIMAQsgAUEIahBGCyAAIAEQIQsPC0Go8wBBvuMAQfQoQcTGABAAAAscACAAKAIQKAKMASIARQRAQQAPCyAAKAIoQQFxC5sCAgN/An4gAUKAgICAcFoEQCABpyICLwEGQSlGBEAjAEEQayIDJABCgICAgOAAIQUCQCAAIANBCGogAUHfABCHASICRQ0AIAMpAwgiARASBEAgACACKQMAEPwBIQUMAQsCQCAAIAEgAikDCEEBIAIQNiIBEA0NAAJAAkACQCABQiCIp0EBag4EAAEBAAELIAAgAikDABCiASIEQQBIDQEgBA0CIAAgAikDABD8ASIGEA0NASAAIAYQDCAGpyABp0YNAgsgACABEAwgAEHpywBBABAWDAILIAAgARAMDAELIAEhBQsgA0EQaiQAIAUPCyACKAIQKAIsIgBFBEBCgICAgCAPCyAArUKAgICAcIQQDw8LIAAgARCdBBAPCxsAIAAoAhAgASACEOEFIgFFBEAgABDJAQsgAQvyAgIEfwF+IwBBIGsiBCQAIAEgAmohBSABIQMDQAJAIAMgBU8NACADLAAAQQBIDQAgA0EBaiEDDAELCwJ+AkAgAyABayIGQYCAgIAETwRAIABBmsMAQQAQUAwBCyADIAVGBEAgACABIAIQ2AIMAgsgACAEIAIQQkUEQCAEIAEgBhCdAhoDQCADIAVJBEAgAywAACIAQQBOBEAgBCAAQf8BcRA+GiADQQFqIQMMAgUCQCADIAUgA2sgBEEcahBhIgFB//8DTQRAIAQoAhwhAwwBCyABQf//wwBNBEAgBCgCHCEDIAQgAUGAgARrQQp2QYCwA2oQlgEaIAFB/wdxQYC4A3IhAQwBCwNAQf3/AyEBIAMgBU8NASADLAAAQb9/TARAIANBAWohAwwBCwsDQCADQQFqIgMgBU8NASADLAAAQUBIDQALCyAEIAEQlgEaDAILAAsLIAQQOQwCCyAEEEQLQoCAgIDgAAshByAEQSBqJAAgBwvbAQIBfwJ+QQEhBAJAIABCAFIgAUL///////////8AgyIFQoCAgICAgMD//wBWIAVCgICAgICAwP//AFEbDQAgAkIAUiADQv///////////wCDIgZCgICAgICAwP//AFYgBkKAgICAgIDA//8AURsNACAAIAKEIAUgBoSEUARAQQAPCyABIAODQgBZBEBBfyEEIAAgAlQgASADUyABIANRGw0BIAAgAoUgASADhYRCAFIPC0F/IQQgACACViABIANVIAEgA1EbDQAgACAChSABIAOFhEIAUiEECyAEC1IBAn9BpLMEKAIAIgEgAEEDakF8cSICaiEAAkAgAkEAIAAgAU0bDQAgAD8AQRB0SwRAIAAQCUUNAQtBpLMEIAA2AgAgAQ8LQcSzBEEwNgIAQX8LRwAgACABSQRAIAAgASACECUaDwsgAgRAIAAgAmohACABIAJqIQEDQCAAQQFrIgAgAUEBayIBLQAAOgAAIAJBAWsiAg0ACwsLIgAgACABQTsgAhAPIgIgAxAbGiAAIAJBPCABEA8gBBAbGgvhBAEGfyAAKAIAIgRBAWohAkEIIQMCQAJAAkAgBC0AACIGQTBrIgdBCE8EQEF+IQUCQAJAAkACQAJAAkAgBkHuAGsOCwEJCQkCCQMFBAkFAAsCQCAGQeIAaw4FCAkJCQAJC0EMIQMMBwtBCiEDDAYLQQ0hAwwFC0EJIQMMBAtBCyEDDAMLAkAgAUUNACACLQAAQfsARw0AIARBAmohAiAELQACIQRBACEDA0AgAiEBQX8hBSAEEOsCIgJBAEgNBSACIANBBHRyIgNB///DAEsNBSABQQFqIgItAAAiBEH9AEcNAAsgAUECaiECDAMLIARBAkEEIAZB+ABGGyIHakEBaiEEQQAhA0EAIQUDQCAFIAdHBEAgAi0AABDrAiIGQQBIBEBBfw8FIAVBAWohBSACQQFqIQIgBiADQQR0ciEDDAILAAsLIAFBAkcgA0GAeHFBgLADR3INASAELQAAQdwARw0BIAQtAAFB9QBHDQFBACECQQAhBQNAAkAgAkEERg0AIAIgBGotAAIQ6wIiAUEASA0AIAJBAWohAiABIAVBBHRyIQUMAQsLIAJBBEcgBUGAuANJciAFQf+/A0tyDQEgA0EKdEGA+D9xIAVB/wdxckGAgARqIQMgBEEGaiECDAILIAFBAkYEQEF/IQUgBw0DIAItAAAQRQ0DQQAhAwwCCyACLQAAQTBrIgFBB0sEQCAHIQMMAgsgBEECaiECIAEgB0EDdHIiA0EfSw0BIAQtAAJBMGsiAUEHSw0BIARBA2ohAiABIANBA3RyIQMMAQsgBCECCyAAIAI2AgAgAyEFCyAFC4sBAQN/IwBBkAFrIgMkACADIAI2AowBAn8gA0GAASABIAIQ2QIiBEH/AE0EQCAAIAMgBBCKAQwBC0F/IAAgBCAAKAIEakEBahDOAQ0AGiADIAI2AowBIAAoAgQiBSAAKAIAaiAAKAIIIAVrIAEgAhDZAhogACAAKAIEIARqNgIEQQALGiADQZABaiQAC5wBAQR/IwBBEGsiAiQAIAJBJToACkEBIQMgAUGAAk4EQCACQfUAOgALIAIgAUEIdkEPcUHL7ABqLQAAOgANIAIgAUEMdkEPcUHL7ABqLQAAOgAMQQQhAwsgAkEKaiIEIANqIgUgAUEPcUHL7ABqLQAAOgABIAUgAUEEdkEPcUHL7ABqLQAAOgAAIAAgBCADQQJyEJ0CGiACQRBqJAALtgEBAn8CQCACIAEoAgQiCkYEQCADIQsMAQsgACAKIAIgAyAEIAUgBiAHIAggCRCGAiIFQQBODQBBfw8LQQAhAiABKALAAiIDQQAgA0EAShshAwJAA0AgAiADRwRAAkAgBSABKALIAiACQQN0aiIKLwECRw0AIAotAAAiCkEBdkEBcSAERw0AIAsgCkEBcUYNAwsgAkEBaiECDAELCyAAIAEgCyAEIAUgBiAHIAggCRDLAyECCyACC0cBAn8gACgCfCECAkADQCACQQBKBEAgACgCdCACQQFrIgJBBHRqIgMoAgAgAUcNASADKAIEDQEMAgsLIAAgARDyBCECCyACCykBAX9BfyEBAkAgAEEoEDANACAAEJkBDQBBf0EAIABBKRAwGyEBCyABC9EBAQJ/IAAoAgAhBSMAQdAAayIGJAACQCABIAMQvwUEQAJAIAAEQCAGIAUgBkEQaiADEIkBNgIAIABBgPsAIAYQFQwBCyAFIANBgPsAEJUDC0EAIQAMAQtBACEAIAUgAUEcakEUIAFBJGogASgCIEEBahCAAQ0AIAEgASgCICIAQQFqNgIgIAEoAhwgAEEUbGoiAEIANwIAIABBADYCECAAQgA3AgggACAFIAIQGTYCDCAFIAMQGSEBIAAgBDYCCCAAIAE2AhALIAZB0ABqJAAgAAvcFQEKfyMAQRBrIg4kACAAKAJAIQcgACgCACELAkACQAJAAkAgAUECTQRAAkAgAg0AQQAhAiAAQYUBEFRFDQAgAEEBEIsBQQpGDQBBfyEIIAAQEQ0FQQIhAgtBfyEIIAAQEQ0EIAAoAhAiCkEqRgRAIAAQEQ0FIAAoAhAhCiACQQFyIQILAkACQAJAAkAgCkEpag4CAQIACyAKQYN/Rw0EAkAgACgCKA0AIAJBAXFFIAFBAkdyRSAAKAIgIgpBLUZxDQAgAkECcUUgAUECR3IgCkEuR3INAwsgABDwAQwHCyABQQJHDQMgBy0AbkEBcUUNAQwDCyABQQJHDQIgACgCRA0CCyALIAAoAiAQGSEKIAAQEUUNAgwDCyABQQNGDQEgC0EAEBkaDAELQQAhCiABQQJGIAVBAkZyDQAgAEH73gBBABAVDAILAkACQAJAIAcoAiAiCEUgAUEBS3INACAHKAIkQQFHDQAgByAKELUCIglFDQAgCSgCCCAHKAK8AUcNACAAQfvVAEEAEBUMAQtBfyEPAkAgAUEBRwRADAELAkAgAg0AIActAG5BAXENACAHIAogBygCwAFBABDVA0EATg0AIAcgChCHAkGAgICAenFBgICAgAJGDQAgCkHNAEYEQCAHKAJIDQELQQEhDQsCQCAIRQ0AIAcoAiRBAUsNACAHKAK8ASIIIAcoAvABRw0AIAcgChC1AiIJRQ0BIAkoAgggCEcNASAAQaAwQQAQFQwCC0F/IQggACAHIApBBEEDIAIbEKwBIg9BAEgNAwsgCyAHQQAgAUEBSyAAKAIMIAQQ9wMiBw0BCyALIAoQE0F/IQgMAgsgBgRAIAYgBzYCAAsgACAHNgJAIAcgCjYCcCAHIAFBCEYiBDYCYCAHIAFBA0ciCDYCTCAHIAg2AkggByACRSABQQNJcTYCNCAHIAFBBGtBBUkiCTYCMEEBIQxBASEQIAhFBEAgBygCBCIIKAJcIRAgCCgCWCEJIAgoAlAhDCAIKAJUIQQLIAcgEDYCXCAHIAk2AlggByAENgJUIAcgDDYCUCAHIAJB/wFxIAFBCHRyOwFsIAFBB2tBAU0EQCAAQSsQDgsgAUEHRgRAIAAQ9AQLIAdCATcCOAJAAkACQAJAIAFBA0cgACgCECIEQYN/R3JFBEAgACgCKA0DIAsgByAAKAIgENQDQQBIDQQgB0EBNgKMAQwBCwJAIARBKEYEQCAAIA5BDGpBABCpARogDi0ADEEEcQRAIAdBATYCPAsgABARRQ0BDAULIABBKBAwDQQLIAcoAjwEQEF/IQggB0F/NgK8ASAAEIUBQQBIDQYLQQAhCQJAA0AgACgCECIIQSlGDQEgCEGlf0ciDEUEQCAHQQA2AjggABARDQYgACgCECEICwJAAkACQAJAIAhBg39HBEAgCEH7AEcgCEHbAEdxDQQgB0EANgI4AkAgDEUEQCAAQQ0QDiAHKAKIASEIDAELIAsgB0EAENQDIQggAEHbABAOCyAAIAhB//8DcRAYIABBUUGxfyAHKAI8G0EBQQFBf0EBENUBIgRBAEgNCiAEIAlyIQRBASEJIARFBEAgByAHKAKMAUEBajYCjAFBACEJCyAMRQ0BDAMLIAAoAigNCCAAKAIgIgRBLUYEQCAHLQBsQQFGDQkLIAcoAjwEQCAAIAcgBEEBEKwBQQBIDQoLIAsgByAEENQDIghBAEgNCSAAEBENCSAMDQEgAEENEA4gACAIQf//A3EiCBAYIAcoAjwEQCAAQREQDiAAQbsBEA4gACAEEBwgACAHLwG8ARAYCyAAQdwAEA4gACAIEBggB0EANgI4CyAAKAIQQSlGDQQgAEEpEDAaDAgLAkAgACgCEEE9RgRAIAdBADYCOCAAEBENCSAAEDUhCSAAQdsAEA4gACAIQf//A3EiCBAYIABBERAOIABBBhAOIABBqwEQDiAAQekAIAkQHRogAEEOEA4gABBiDQkgACAEEK0BIABBERAOIABB3AAQDiAAIAgQGCAAIAkQIEEBIQkMAQsgCUUEQCAHIAcoAowBQQFqNgKMAQsgBygCPEUNASAAQdsAEA4gACAIQf//A3EQGAsgAEG7ARAOIAAgBBAcIAAgBy8BvAEQGAsgACgCEEEpRg0CIABBLBAwRQ0BDAYLCyAAQZYuQQAQFQwECwJAAkAgAUEEaw4CAQACCyAHKAKIAUEBRg0BDAILIAcoAogBDQELIAcoAjwEQCAHKALMASAHKAK8AUEDdGpBBGohCANAAkAgCCgCACIEQQBIDQAgBygCdCIIIARBBHQiBGoiCSgCBCAHKAK8AUcNACAHIAkoAgAiCRCHAkEASARAIAsgByAJEFhBAEgNBiAHKAJ0IQggAEG2ARAOIAAgBCAIaiIJKAIAEBwgACAHLwG8ARAYIABBtwEQDiAAIAkoAgAQHCAAQQAQGAsgBCAIakEIaiEIDAELCyAAQbMBEA4gACAHLwG8ARAYIAdBADYCvAEgByAHKALMASgCBDYCwAELIAAQEQ0CIAJBfXFBAUYEQCAAQYcBEA4LIAdBATYCZCAAEIUBGiAHIAcoArwBNgLwAQJAAkAgACgCEEGkf0cNACAAEBENBCAAKAIQQfsARg0AIAAgByAKEPMEDQQgABBiDQQgAEEuQSggAhsQDiAHLQBuQQJxDQEgByAAKAI0IANrIgI2ApADIAcgCyADIAIQowMiAjYCjAMgAg0BDAQLIABB+wAQMA0DIAAQ+QQNAyAAIAcgChDzBA0DA0AgACgCEEH9AEcEQCAAEPgERQ0BDAULCyAHLQBuQQJxRQRAIAcgACgCOCADayICNgKQAyAHIAsgAyACEKMDIgI2AowDIAJFDQQLIAAQEQ0DIAAQ9QJFDQAgAEEAEPYCCyAAIAcoAgQ2AkAgBygCcCECIAcgAEKAgICAIBDTAyIDNgIIIAFBAk8EQEEAIQggAUEJa0F9Sw0FIABBAxAOIAAgAxA6IAINBSAAQc0AEA4gAEEAEDoMBQsgAUEBRgRAIABBAxAOIAAgAxA6IA0EQAJAIAAoAkAiASgCKARAIAsgASACEPQCIgFFDQYgAUEANgIIIAEgAS0ABEH+AXEgACgCQC0AbkEBcXI6AAQMAQsgASACEIcCQQBODQAgCyABIAIQWEEASA0FCyAAQREQDiAAQbcBEA4gACACEBwgAEEAEBgLQQAhCCAPQQBOBEAgACgCQCgCdCAPQQR0aiIBIAEoAgxB/4CAgHhxIANBB3RBgP///wdxcjYCDCAAQQ4QDgwGCyAAQbsBEA4gACACEBwgACAAKAJALwG8ARAYDAULAkACQCAAKAJAIgEoAihFBEAgACABIAJBBhCsASIBQQBIDQUgACgCQCEAIAFBgICAgAJxBEAgACgCgAEgAUEEdGoiACAAKAIMQf+AgIB4cSADQQd0QYD///8HcXI2AgwMAgsgACgCdCABQQR0aiIAIAAoAgxB/4CAgHhxIANBB3RBgP///wdxcjYCDAwBCyALIAEgAkH8ACACGyIBEPQCIgJFDQQgAiADNgIAIAUNAQtBACEIDAULQQAhCCAAIAAoAkAoApQDIAEgAUEWIAVBAUYbQQAQiQINBAwCCyAAQcAtQQAQFQwBCyAAEPABCyAAIAcoAgQ2AkAgCyAHEI0DQX8hCCAGRQ0BIAZBADYCAAwBCyALIAoQEwsgDkEQaiQAIAgLegEBfyAAIAZBDBBTIgYQDUUEQCAGpyIHIAAQoAIiADYCICAHIAU7ASogByAEOgApIAcgAzoAKCAHIAE2AiQgByAHLQAFQe8BcSAEQQJrQQRJQQR0cjoABSAAIAYgACACQdyDASACGxDKASIBIAMQqQMgACABEBMLIAYL0AECAX4BfyMAQRBrIgIkAAJAIAEQIkUEQCAAEClCgICAgOAAIQUMAQsCQCAEDQAgAykDACIFQSoQQEUNACAAIAVBPCAFQQAQFCIFEA0NASAAIAUgARBaIQYgACAFEAwgBkUNACADKQMAEA8hBQwBCyAAIAIgARDDAiIBEA1FBEAgACACIARBA3RqKQMAQoCAgIAwQQEgAxAkIQUgACACKQMAEAwgACACKQMIEAwgBRANBEAgACABEAwMAgsgACAFEAwLIAEhBQsgAkEQaiQAIAULDAAgACABEAwgARANC0QBAn8CQCAAQoCAgIBwVA0AIACnIgMvAQZBAkcNACADLQAFQQhxRQ0AIAIgAygCKDYCACABIAMoAiQ2AgBBASEECyAEC3gBAX8CQAJAAkACQAJAIAEoAgAiAkH/AGoOBAAAAwECCyAAKAIAIAEpAxAQDA8LIAAoAgAgASkDEBAMIAAoAgAgASkDGBAMDwsgAkGpf0cNAQsgACgCACABKAIQEBMPCyACQdUAakEtTQRAIAAoAgAgASgCEBATCwsNACAAIAEgAkEAEKEECw4AIAEgACgCEEErEOcCC9MBAwF/AX4BfCMAQRBrIgMkAAJ/IAAgA0EIaiABQQhrIgEpAwAQWwRAQoCAgIAwIQRBfwwBCwJ8AkACQAJAAkACQCACQYwBaw4EAgQBAAMLIAMrAwhEAAAAAAAA8D+gDAQLIAMrAwhEAAAAAAAA8L+gDAMLIAMrAwiaDAILEAEACyADKwMICyIFvQJ/IAWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyIAt71RBEAgAK0hBEEADAELIAUQFyEEQQALIQAgASAENwMAIANBEGokACAACw0AIAAgASACEA8QxgELSQECfyACQv////8HWARAIAAgASACpxCVAUGAgAEQ3gEPCyAAIAIQnQMiA0UEQEF/DwsgACABIANBgIABEN4BIQQgACADEBMgBAtKAQF/AkAgACABIAAoAgRB/////wdxIgIgASgCBEH/////B3EiARC0ARDkBSIADQBBACEAIAEgAkYNAEF/QQEgASACSxshAAsgAAsgACAAIAEgAkEATgR+IAKtBSACuBAXCyADQYCAARDhAQvNCgIHfwF+IwBBIGsiCSQAAkACQAJAAkACQAJAAn8CQAJAAkACQAJAIAFCIIinQQFqDgUDAgIAAQILIAAgAxAMIAAgAkGmPRDIAUF/IQUMCgsgACADEAwgACACQZrgABDIAUF/IQUMCQsgACABEJ0EpyEGDAELIAGnIQYCQAJAA0AgBigCECIHIAcoAhggAnFBf3NBAnRqKAIAIQUgBxAqIQgDQCAGIQcgBUUNAyACIAggBUEBa0EDdCIHaiIFKAIERwRAIAUoAgBB////H3EhBQwBCwsgBSgCACIIQRp2IQogBigCFCAHaiEHIAhBgICAwH5xQYCAgMAARgRAIAAgByADEB8MBgsCQCAIQYCAgIACcQRAIAYvAQZBAkcNASACQTBHDQMgACAGIAMgBBDVBSEFDAwLIApBMHEiCEEwRwRAIAhBIEcEQCAIQRBHDQkgACAHKAIEIAEgAyAEEKIDIQUMDQsgBi8BBkELRg0IIAAgBygCACgCECADEB8MBwsgACAGIAIgByAFENECRQ0BDAoLC0G/5wBBvuMAQY/CAEHBPxAAAAtBsMEAQb7jAEGQwgBBwT8QAAALQQAMAQtBAQshBQNAAkACQCAFRQRAAkAgBi0ABSIFQQRxRQ0AAkAgBUEIcQRAIAIQXgRAIAIQfCIFIAYoAihPDQIgBiAHRw0FIAAgASAFrSADIAQQ4QEhBQwNCyAGLwEGQRVrQf//A3FBCEsNAiAAIAIQpQMiCEUNAkF/IQUgCEEATg0JDAoLIAAoAhAoAkQgBi8BBkEYbGooAhQiBUUNASAFKAIYBEAgACAGrUKAgICAcIQQDyIMIAIgAyABIAQgBSgCGBEpACEFIAAgDBAMDAoLIAUoAgBFDQEgACAJIAatQoCAgIBwhBAPIgwgAiAFKAIAERgAIQUgACAMEAwgBUEASA0JIAVFDQEgCS0AAEEQcQRAIABBACAJKQMYIgynIAwQEhsgASADIAQQogMhBSAAIAkpAxAQDCAAIAkpAxgQDAwMCyAAIAkpAwgQDCAJLQAAQQJxRQ0HIAYgB0cNAyAAIAEgAiADQoCAgIAwQoCAgIAwQYDAABB4IQUMCQsgBi8BBkEVa0H//wNxQQlJDQcLIAYoAhAoAiwhBkEBIQUMAwsgBkUNAANAIAYoAhAiCCAIKAIYIAJxQX9zQQJ0aigCACEFIAgQKiEKA0AgBUUNAyACIAogBUEBa0EDdCIFaiIIKAIERwRAIAgoAgBB////H3EhBQwBCwsgBigCFCAFaiEKAkAgCCgCACIFQRp2QTBxIgtBMEcEQCALQRBHDQEgACAKKAIEIAEgAyAEEKIDIQUMCwtBfyEFIAAgBiACIAogCBDRAkUNAQwKCwsgBUGAgIDAAHENAQwECyAEQYCABHEEQCAAIAMQDCAAIAIQ0AJBfyEFDAgLIAdFBEAgACADEAwgACAEQfQcEHkhBQwICyAHLQAFIgZBAXFFBEAgACADEAwgACAEQdzQABB5IQUMCAsgBkEEcQRAAkAgBkEIcUUgBy8BBkECR3INACACEF5FDQAgAhB8IAcoAihHDQAgACAHIAMgBBCXBCEFDAkLIAAgByACIANCgICAgDBCgICAgDAgBEGHzgByEJYEIQUMBgsgACAHIAJBBxCDASICRQ0GIAIgAzcDAAwCC0EAIQUMAAsAC0EBIQUMBAsgACADEAwgACAEIAIQ4AEhBQwDCyAAIAAgAxCgASIBEAxBfyEFIAEQDQ0CIAAgBEHTDhB5IQUMAgsgACADEAwMAQsgACADEAxBfyEFCyAJQSBqJAAgBQsNACAAKAIQIAGnENYCCxUBAX4gACABEPwBIQIgACABEAwgAgshACAAKAIQIAEgAhDnASIBIAJFcgR/IAEFIAAQyQFBAAsL8QMCA38BfgJAAkAgAwRAIAFCgICAgGCDQoCAgIAgUg0BDAILIAFCgICAgHBUDQELQQEhBAJAAkAgAkIgiKdBAWoOBAACAgECCyACpyEFCwJAAkAgAUL/////b1hBACADGw0AIAGnIgYvAQZBKUYEQCMAQSBrIgQkAAJAAkAgACAEQRhqIAFB4AAQhwEiBUUNACAFKQMAIQEgBCkDGCIHEBIEQCAAIAEgAiADEJsCIQMMAgsgBCACNwMIIAQgATcDACAAIAcgBSkDCEECIAQQNiIBEA0NACAAIAEQLUUEQCADRQRAQQAhAwwDCyAAQYfMAEEAEBYMAQsgACAFKQMAEKIBIgZBAEgNAEEBIQMgBg0BIAAgBSkDABD8ASIBEA0NACAAIAEQDCACpyABp0YNASAAQenLAEEAEBYLQX8hAwsgBEEgaiQAIAMPCyAGKAIQKAIsIAVGDQAgBi0ABUEBcUUEQCADRQ0CIABB3NAAQQAQFkF/DwsgBQRAIAUhBANAIAQgBkYEQCADRQ0EIABBqTpBABAWQX8PCyAEKAIQKAIsIgQNAAsgAhAPGgtBfyEEIAAgBkEAEOQBDQAgBigCECIDKAIsIgQEQCAAIAStQoCAgIBwhBAMCyADIAU2AixBASEECyAEDwtBAA8LIAAQKUF/CxkAIAAgARDoASIABEAgAEEAIAEQSxoLIAALkwEBAn8CfyAAKAIIIAJqIgQgACgCDEoEQEF/IAAgBEEAENUCDQEaCwJAIAAoAhAEQCACQQAgAkEAShshBANAIAMgBEYNAiAAKAIEIAAoAgggA2pBAXRqIAEgA2otAAA7ARAgA0EBaiEDDAALAAsgACgCBCAAKAIIakEQaiABIAIQJRoLIAAgACgCCCACajYCCEEACwuiAQECfyABIAEoAgAiAkEBazYCACACQQFMBEACQCABKAIARQRAIAEtABAEQCAAIAEQkQQLIAEoAiwiAgRAIAAgAq1CgICAgHCEECcLQQAhAiABECohAwNAIAEoAiAgAksEQCAAIAMoAgQQ9AEgAkEBaiECIANBCGohAwwBCwsgARCfAiAAIAEQwQIQIQwBC0Hg9ABBvuMAQcMiQf3yABAAAAsLCwkAIABBCGoQRgsRACAAIAAoAgBBAWo2AgAgAAtQAQF+AkAgA0HAAHEEQCACIANBQGqtiCEBQgAhAgwBCyADRQ0AIAJBwAAgA2uthiABIAOtIgSIhCEBIAIgBIghAgsgACABNwMAIAAgAjcDCAtjAgF/AX4jAEEQayICJAAgAAJ+IAFFBEBCAAwBCyACIAGtQgAgAWciAUHRAGoQcyACKQMIQoCAgICAgMAAhUGegAEgAWutQjCGfCEDIAIpAwALNwMAIAAgAzcDCCACQRBqJAALiS4BC38jAEEQayILJAACQAJAAkACQAJAAkACQAJAAkACQAJAIABB9AFNBEBBmL0EKAIAIgZBECAAQQtqQXhxIABBC0kbIgdBA3YiAnYiAUEDcQRAAkAgAUF/c0EBcSACaiICQQN0IgBBwL0EaiIBIABByL0EaigCACIDKAIIIgBGBEBBmL0EIAZBfiACd3E2AgAMAQsgACABNgIMIAEgADYCCAsgAyACQQN0IgBBA3I2AgQgACADaiIAIAAoAgRBAXI2AgQgA0EIaiEADAwLIAdBoL0EKAIAIgpNDQEgAQRAAkBBAiACdCIAQQAgAGtyIAEgAnRxIgBBACAAa3FBAWsiACAAQQx2QRBxIgJ2IgFBBXZBCHEiACACciABIAB2IgFBAnZBBHEiAHIgASAAdiIBQQF2QQJxIgByIAEgAHYiAUEBdkEBcSIAciABIAB2aiICQQN0IgBBwL0EaiIBIABByL0EaigCACIEKAIIIgBGBEBBmL0EIAZBfiACd3EiBjYCAAwBCyAAIAE2AgwgASAANgIICyAEIAdBA3I2AgQgBCAHaiIBIAJBA3QiACAHayICQQFyNgIEIAAgBGogAjYCACAKBEAgCkEDdiIAQQN0QcC9BGohBUGsvQQoAgAhAwJ/IAZBASAAdCIAcUUEQEGYvQQgACAGcjYCACAFDAELIAUoAggLIQAgBSADNgIIIAAgAzYCDCADIAU2AgwgAyAANgIIC0GsvQQgATYCAEGgvQQgAjYCACAEQQhqIQAMDAtBnL0EKAIAIglFDQEgCUEAIAlrcUEBayIAIABBDHZBEHEiAnYiAUEFdkEIcSIAIAJyIAEgAHYiAUECdkEEcSIAciABIAB2IgFBAXZBAnEiAHIgASAAdiIBQQF2QQFxIgByIAEgAHZqQQJ0Qci/BGooAgAiASgCBEF4cSAHayEDIAEhAgNAAkAgAigCECIARQRAIAIoAhQiAEUNAQsgACgCBEF4cSAHayICIAMgAiADSSICGyEDIAAgASACGyEBIAAhAgwBCwsgASgCGCEIIAEgASgCDCIFRwRAIAEoAggiAEGovQQoAgBJGiAAIAU2AgwgBSAANgIIDAsLIAFBFGoiAigCACIARQRAIAEoAhAiAEUNAyABQRBqIQILA0AgAiEEIAAiBUEUaiICKAIAIgANACAFQRBqIQIgBSgCECIADQALIARBADYCAAwKC0F/IQcgAEG/f0sNACAAQQtqIgBBeHEhB0GcvQQoAgAiCUUNAEEAIAdrIQMCQAJAAkACf0EAIAdBgAJJDQAaQR8gB0H///8HSw0AGiAAQQh2IgAgAEGA/j9qQRB2QQhxIgJ0IgAgAEGA4B9qQRB2QQRxIgF0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAEgAnIgAHJrIgBBAXQgByAAQRVqdkEBcXJBHGoLIgZBAnRByL8EaigCACICRQRAQQAhAAwBC0EAIQAgB0EAQRkgBkEBdmsgBkEfRht0IQEDQAJAIAIoAgRBeHEgB2siBCADTw0AIAIhBSAEIgMNAEEAIQMgAiEADAMLIAAgAigCFCIEIAQgAiABQR12QQRxaigCECICRhsgACAEGyEAIAFBAXQhASACDQALCyAAIAVyRQRAQQAhBUECIAZ0IgBBACAAa3IgCXEiAEUNAyAAQQAgAGtxQQFrIgAgAEEMdkEQcSICdiIBQQV2QQhxIgAgAnIgASAAdiIBQQJ2QQRxIgByIAEgAHYiAUEBdkECcSIAciABIAB2IgFBAXZBAXEiAHIgASAAdmpBAnRByL8EaigCACEACyAARQ0BCwNAIAAoAgRBeHEgB2siASADSSECIAEgAyACGyEDIAAgBSACGyEFIAAoAhAiAQR/IAEFIAAoAhQLIgANAAsLIAVFDQAgA0GgvQQoAgAgB2tPDQAgBSgCGCEGIAUgBSgCDCIBRwRAIAUoAggiAEGovQQoAgBJGiAAIAE2AgwgASAANgIIDAkLIAVBFGoiAigCACIARQRAIAUoAhAiAEUNAyAFQRBqIQILA0AgAiEEIAAiAUEUaiICKAIAIgANACABQRBqIQIgASgCECIADQALIARBADYCAAwICyAHQaC9BCgCACICTQRAQay9BCgCACEDAkAgAiAHayIBQRBPBEBBoL0EIAE2AgBBrL0EIAMgB2oiADYCACAAIAFBAXI2AgQgAiADaiABNgIAIAMgB0EDcjYCBAwBC0GsvQRBADYCAEGgvQRBADYCACADIAJBA3I2AgQgAiADaiIAIAAoAgRBAXI2AgQLIANBCGohAAwKCyAHQaS9BCgCACIISQRAQaS9BCAIIAdrIgE2AgBBsL0EQbC9BCgCACICIAdqIgA2AgAgACABQQFyNgIEIAIgB0EDcjYCBCACQQhqIQAMCgtBACEAIAdBL2oiCQJ/QfDABCgCAARAQfjABCgCAAwBC0H8wARCfzcCAEH0wARCgKCAgICABDcCAEHwwAQgC0EMakFwcUHYqtWqBXM2AgBBhMEEQQA2AgBB1MAEQQA2AgBBgCALIgFqIgZBACABayIEcSICIAdNDQlB0MAEKAIAIgUEQEHIwAQoAgAiAyACaiIBIANNIAEgBUtyDQoLQdTABC0AAEEEcQ0EAkACQEGwvQQoAgAiAwRAQdjABCEAA0AgAyAAKAIAIgFPBEAgASAAKAIEaiADSw0DCyAAKAIIIgANAAsLQQAQgAIiAUF/Rg0FIAIhBkH0wAQoAgAiA0EBayIAIAFxBEAgAiABayAAIAFqQQAgA2txaiEGCyAGIAdNIAZB/v///wdLcg0FQdDABCgCACIFBEBByMAEKAIAIgMgBmoiACADTSAAIAVLcg0GCyAGEIACIgAgAUcNAQwHCyAGIAhrIARxIgZB/v///wdLDQQgBhCAAiIBIAAoAgAgACgCBGpGDQMgASEACyAAQX9GIAdBMGogBk1yRQRAQfjABCgCACIBIAkgBmtqQQAgAWtxIgFB/v///wdLBEAgACEBDAcLIAEQgAJBf0cEQCABIAZqIQYgACEBDAcLQQAgBmsQgAIaDAQLIAAiAUF/Rw0FDAMLQQAhBQwHC0EAIQEMBQsgAUF/Rw0CC0HUwARB1MAEKAIAQQRyNgIACyACQf7///8HSw0BIAIQgAIiAUF/RkEAEIACIgBBf0ZyIAAgAU1yDQEgACABayIGIAdBKGpNDQELQcjABEHIwAQoAgAgBmoiADYCAEHMwAQoAgAgAEkEQEHMwAQgADYCAAsCQAJAAkBBsL0EKAIAIgQEQEHYwAQhAANAIAEgACgCACIDIAAoAgQiAmpGDQIgACgCCCIADQALDAILQai9BCgCACIAQQAgACABTRtFBEBBqL0EIAE2AgALQQAhAEHcwAQgBjYCAEHYwAQgATYCAEG4vQRBfzYCAEG8vQRB8MAEKAIANgIAQeTABEEANgIAA0AgAEEDdCIDQci9BGogA0HAvQRqIgI2AgAgA0HMvQRqIAI2AgAgAEEBaiIAQSBHDQALQaS9BCAGQShrIgNBeCABa0EHcUEAIAFBCGpBB3EbIgBrIgI2AgBBsL0EIAAgAWoiADYCACAAIAJBAXI2AgQgASADakEoNgIEQbS9BEGAwQQoAgA2AgAMAgsgAC0ADEEIcSADIARLciABIARNcg0AIAAgAiAGajYCBEGwvQQgBEF4IARrQQdxQQAgBEEIakEHcRsiAGoiAjYCAEGkvQRBpL0EKAIAIAZqIgEgAGsiADYCACACIABBAXI2AgQgASAEakEoNgIEQbS9BEGAwQQoAgA2AgAMAQtBqL0EKAIAIAFLBEBBqL0EIAE2AgALIAEgBmohAkHYwAQhAAJAAkACQAJAAkACQANAIAIgACgCAEcEQCAAKAIIIgANAQwCCwsgAC0ADEEIcUUNAQtB2MAEIQADQCAEIAAoAgAiAk8EQCACIAAoAgRqIgUgBEsNAwsgACgCCCEADAALAAsgACABNgIAIAAgACgCBCAGajYCBCABQXggAWtBB3FBACABQQhqQQdxG2oiCSAHQQNyNgIEIAJBeCACa0EHcUEAIAJBCGpBB3EbaiIGIAcgCWoiCGshAiAEIAZGBEBBsL0EIAg2AgBBpL0EQaS9BCgCACACaiIANgIAIAggAEEBcjYCBAwDC0GsvQQoAgAgBkYEQEGsvQQgCDYCAEGgvQRBoL0EKAIAIAJqIgA2AgAgCCAAQQFyNgIEIAAgCGogADYCAAwDCyAGKAIEIgBBA3FBAUYEQCAAQXhxIQQCQCAAQf8BTQRAIAYoAggiAyAAQQN2IgBBA3RBwL0EakYaIAMgBigCDCIBRgRAQZi9BEGYvQQoAgBBfiAAd3E2AgAMAgsgAyABNgIMIAEgAzYCCAwBCyAGKAIYIQcCQCAGIAYoAgwiAUcEQCAGKAIIIgAgATYCDCABIAA2AggMAQsCQCAGQRRqIgAoAgAiAw0AIAZBEGoiACgCACIDDQBBACEBDAELA0AgACEFIAMiAUEUaiIAKAIAIgMNACABQRBqIQAgASgCECIDDQALIAVBADYCAAsgB0UNAAJAIAYoAhwiA0ECdEHIvwRqIgAoAgAgBkYEQCAAIAE2AgAgAQ0BQZy9BEGcvQQoAgBBfiADd3E2AgAMAgsgB0EQQRQgBygCECAGRhtqIAE2AgAgAUUNAQsgASAHNgIYIAYoAhAiAARAIAEgADYCECAAIAE2AhgLIAYoAhQiAEUNACABIAA2AhQgACABNgIYCyACIARqIQIgBCAGaiIGKAIEIQALIAYgAEF+cTYCBCAIIAJBAXI2AgQgAiAIaiACNgIAIAJB/wFNBEAgAkEDdiIAQQN0QcC9BGohAgJ/QZi9BCgCACIBQQEgAHQiAHFFBEBBmL0EIAAgAXI2AgAgAgwBCyACKAIICyEAIAIgCDYCCCAAIAg2AgwgCCACNgIMIAggADYCCAwDC0EfIQAgAkH///8HTQRAIAJBCHYiACAAQYD+P2pBEHZBCHEiA3QiACAAQYDgH2pBEHZBBHEiAXQiACAAQYCAD2pBEHZBAnEiAHRBD3YgASADciAAcmsiAEEBdCACIABBFWp2QQFxckEcaiEACyAIIAA2AhwgCEIANwIQIABBAnRByL8EaiEFAkBBnL0EKAIAIgNBASAAdCIBcUUEQEGcvQQgASADcjYCACAFIAg2AgAgCCAFNgIYDAELIAJBAEEZIABBAXZrIABBH0YbdCEAIAUoAgAhAQNAIAEiAygCBEF4cSACRg0DIABBHXYhASAAQQF0IQAgAyABQQRxaiIFKAIQIgENAAsgBSAINgIQIAggAzYCGAsgCCAINgIMIAggCDYCCAwCC0GkvQQgBkEoayIDQXggAWtBB3FBACABQQhqQQdxGyIAayICNgIAQbC9BCAAIAFqIgA2AgAgACACQQFyNgIEIAEgA2pBKDYCBEG0vQRBgMEEKAIANgIAIAQgBUEnIAVrQQdxQQAgBUEna0EHcRtqQS9rIgAgACAEQRBqSRsiAkEbNgIEIAJB4MAEKQIANwIQIAJB2MAEKQIANwIIQeDABCACQQhqNgIAQdzABCAGNgIAQdjABCABNgIAQeTABEEANgIAIAJBGGohAANAIABBBzYCBCAAQQhqIQEgAEEEaiEAIAEgBUkNAAsgAiAERg0DIAIgAigCBEF+cTYCBCAEIAIgBGsiBUEBcjYCBCACIAU2AgAgBUH/AU0EQCAFQQN2IgBBA3RBwL0EaiECAn9BmL0EKAIAIgFBASAAdCIAcUUEQEGYvQQgACABcjYCACACDAELIAIoAggLIQAgAiAENgIIIAAgBDYCDCAEIAI2AgwgBCAANgIIDAQLQR8hACAFQf///wdNBEAgBUEIdiIAIABBgP4/akEQdkEIcSICdCIAIABBgOAfakEQdkEEcSIBdCIAIABBgIAPakEQdkECcSIAdEEPdiABIAJyIAByayIAQQF0IAUgAEEVanZBAXFyQRxqIQALIAQgADYCHCAEQgA3AhAgAEECdEHIvwRqIQMCQEGcvQQoAgAiAkEBIAB0IgFxRQRAQZy9BCABIAJyNgIAIAMgBDYCACAEIAM2AhgMAQsgBUEAQRkgAEEBdmsgAEEfRht0IQAgAygCACEBA0AgASICKAIEQXhxIAVGDQQgAEEddiEBIABBAXQhACACIAFBBHFqIgMoAhAiAQ0ACyADIAQ2AhAgBCACNgIYCyAEIAQ2AgwgBCAENgIIDAMLIAMoAggiACAINgIMIAMgCDYCCCAIQQA2AhggCCADNgIMIAggADYCCAsgCUEIaiEADAULIAIoAggiACAENgIMIAIgBDYCCCAEQQA2AhggBCACNgIMIAQgADYCCAtBpL0EKAIAIgAgB00NAEGkvQQgACAHayIBNgIAQbC9BEGwvQQoAgAiAiAHaiIANgIAIAAgAUEBcjYCBCACIAdBA3I2AgQgAkEIaiEADAMLQcSzBEEwNgIAQQAhAAwCCwJAIAZFDQACQCAFKAIcIgJBAnRByL8EaiIAKAIAIAVGBEAgACABNgIAIAENAUGcvQQgCUF+IAJ3cSIJNgIADAILIAZBEEEUIAYoAhAgBUYbaiABNgIAIAFFDQELIAEgBjYCGCAFKAIQIgAEQCABIAA2AhAgACABNgIYCyAFKAIUIgBFDQAgASAANgIUIAAgATYCGAsCQCADQQ9NBEAgBSADIAdqIgBBA3I2AgQgACAFaiIAIAAoAgRBAXI2AgQMAQsgBSAHQQNyNgIEIAUgB2oiBCADQQFyNgIEIAMgBGogAzYCACADQf8BTQRAIANBA3YiAEEDdEHAvQRqIQICf0GYvQQoAgAiAUEBIAB0IgBxRQRAQZi9BCAAIAFyNgIAIAIMAQsgAigCCAshACACIAQ2AgggACAENgIMIAQgAjYCDCAEIAA2AggMAQtBHyEAIANB////B00EQCADQQh2IgAgAEGA/j9qQRB2QQhxIgJ0IgAgAEGA4B9qQRB2QQRxIgF0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAEgAnIgAHJrIgBBAXQgAyAAQRVqdkEBcXJBHGohAAsgBCAANgIcIARCADcCECAAQQJ0Qci/BGohAQJAAkAgCUEBIAB0IgJxRQRAQZy9BCACIAlyNgIAIAEgBDYCAAwBCyADQQBBGSAAQQF2ayAAQR9GG3QhACABKAIAIQcDQCAHIgEoAgRBeHEgA0YNAiAAQR12IQIgAEEBdCEAIAEgAkEEcWoiAigCECIHDQALIAIgBDYCEAsgBCABNgIYIAQgBDYCDCAEIAQ2AggMAQsgASgCCCIAIAQ2AgwgASAENgIIIARBADYCGCAEIAE2AgwgBCAANgIICyAFQQhqIQAMAQsCQCAIRQ0AAkAgASgCHCICQQJ0Qci/BGoiACgCACABRgRAIAAgBTYCACAFDQFBnL0EIAlBfiACd3E2AgAMAgsgCEEQQRQgCCgCECABRhtqIAU2AgAgBUUNAQsgBSAINgIYIAEoAhAiAARAIAUgADYCECAAIAU2AhgLIAEoAhQiAEUNACAFIAA2AhQgACAFNgIYCwJAIANBD00EQCABIAMgB2oiAEEDcjYCBCAAIAFqIgAgACgCBEEBcjYCBAwBCyABIAdBA3I2AgQgASAHaiICIANBAXI2AgQgAiADaiADNgIAIAoEQCAKQQN2IgBBA3RBwL0EaiEEQay9BCgCACEFAn9BASAAdCIAIAZxRQRAQZi9BCAAIAZyNgIAIAQMAQsgBCgCCAshACAEIAU2AgggACAFNgIMIAUgBDYCDCAFIAA2AggLQay9BCACNgIAQaC9BCADNgIACyABQQhqIQALIAtBEGokACAAC4MBAgN/AX4CQCAAQoCAgIAQVARAIAAhBQwBCwNAIAFBAWsiASAAIABCCoAiBUIKfn2nQTByOgAAIABC/////58BViECIAUhACACDQALCyAFpyICBEADQCABQQFrIgEgAiACQQpuIgNBCmxrQTByOgAAIAJBCUshBCADIQIgBA0ACwsgAQvjAQECfyACQQBHIQMCQAJAAkAgAEEDcUUgAkVyDQAgAUH/AXEhBANAIAAtAAAgBEYNAiACQQFrIgJBAEchAyAAQQFqIgBBA3FFDQEgAg0ACwsgA0UNAQsCQCAALQAAIAFB/wFxRiACQQRJckUEQCABQf8BcUGBgoQIbCEDA0AgACgCACADcyIEQX9zIARBgYKECGtxQYCBgoR4cQ0CIABBBGohACACQQRrIgJBA0sNAAsLIAJFDQELIAFB/wFxIQEDQCABIAAtAABGBEAgAA8LIABBAWohACACQQFrIgINAAsLQQAL5QUDBHwBfwF+AkACQAJAAnwCQCAAvSIGQiCIp0H/////B3EiBUH60I2CBE8EQCAAvUL///////////8Ag0KAgICAgICA+P8AVg0FIAZCAFMEQEQAAAAAAADwvw8LIABE7zn6/kIuhkBkRQ0BIABEAAAAAAAA4H+iDwsgBUHD3Nj+A0kNAiAFQbHFwv8DSw0AIAZCAFkEQEEBIQVEdjx5Ne856j0hASAARAAA4P5CLua/oAwCC0F/IQVEdjx5Ne856r0hASAARAAA4P5CLuY/oAwBCwJ/IABE/oIrZUcV9z+iRAAAAAAAAOA/IACmoCIBmUQAAAAAAADgQWMEQCABqgwBC0GAgICAeAsiBbciAkR2PHk17znqPaIhASAAIAJEAADg/kIu5r+ioAsiACAAIAGhIgChIAGhIQEMAQsgBUGAgMDkA0kNAUEAIQULIAAgAEQAAAAAAADgP6IiA6IiAiACIAIgAiACIAJELcMJbrf9ir6iRDlS5obKz9A+oKJEt9uqnhnOFL+gokSFVf4ZoAFaP6CiRPQQEREREaG/oKJEAAAAAAAA8D+gIgREAAAAAAAACEAgBCADoqEiA6FEAAAAAAAAGEAgACADoqGjoiEDIAVFBEAgACAAIAOiIAKhoQ8LIAAgAyABoaIgAaEgAqEhAQJAAkACQCAFQQFqDgMAAgECCyAAIAGhRAAAAAAAAOA/okQAAAAAAADgv6APCyAARAAAAAAAANC/YwRAIAEgAEQAAAAAAADgP6ChRAAAAAAAAADAog8LIAAgAaEiACAAoEQAAAAAAADwP6APCyAFQf8Haq1CNIa/IQIgBUE5TwRAIAAgAaFEAAAAAAAA8D+gIgAgAKBEAAAAAAAA4H+iIAAgAqIgBUGACEYbRAAAAAAAAPC/oA8LRAAAAAAAAPA/Qf8HIAVrrUI0hr8iA6EgACABoaAgACABIAOgoUQAAAAAAADwP6AgBUETTRsgAqIhAAsgAAuNAQAgACAAIAAgACAARAn3/Q3hPQI/okSIsgF14O9JP6CiRDuPaLUogqS/oKJEVUSIDlXByT+gokR9b+sDEtbUv6CiRFVVVVVVVcU/oCAAoiAAIAAgACAARIKSLrHFuLM/okRZAY0bbAbmv6CiRMiKWZzlKgBAoKJESy2KHCc6A8CgokQAAAAAAADwP6CjC4QCAQZ/IwBBEGsiBCQAAkAgBEEMaiAAQYCtA0EcELwEIgFBAEgNACABQeCtA2ohAQNAAn8gAUEBaiABLQAAIgZBP3EiAkEwSQ0AGiACQQh0IQMgAkE3TQRAIAMgAS0AAWpB0N8AayECIAFBAmoMAQsgAS0AAiADQYDwAGsgAS0AAUEIdHJqQbAQaiECIAFBA2oLIQMgAyAGQX9zQYABcUEHdmohAQJAIAAgAiAEKAIMIgNqQQFqIgJJBEACQAJAIAZBBnYOAwMABQELIAFBAWstAAAgACADa2ohBQwEC0HmASEFDAMLIAQgAjYCDAwBCwsgAUEBay0AACEFCyAEQRBqJAAgBQtZAQN/QX8hASAAIAAoAgAiAkECaiIDEOACBH9BfwUgACgCCCIBQQRqIAEgAkECdCICEIECIAAoAggiAUEANgIAIAEgAmpBfzYCBCAAIAM2AgAgABC6BEEACwvyAQEEfwJAA0ACQAJAAkACfyACIAdMIgggBCAGTHJFBEAgASAHQQJ0aigCACIJIAMgBkECdGooAgAiCEkEQCAJDAILIAggCUcNAyAGQQFqIQYgB0EBaiEHIAkhCAwECyAIDQEgASAHQQJ0aigCAAshCCAHQQFqIQcMAgsgBCAGTA0DIAMgBkECdGooAgAhCAsgBkEBaiEGCwJ/AkACQAJAAkAgBQ4DAwABAgsgBiAHcUEBcQwDCyAGIAdzQQFxDAILEAEACyAGIAdyQQFxCyEJIAkgACgCAEEBcUYNACAAIAgQvgRFDQALQX8PCyAAELoEQQALawIBfgJ/IAAoAgAhAwNAIAMtAAAiBEE6a0H/AXFB9gFPBEAgAkIKfiAErUL/AYN8QjB9IgJC/////wdUIgQgAXIEQCACQv////8HIAQbIQIgA0EBaiEDDAIFQX8PCwALCyAAIAM2AgAgAqcLCwAgAEHaC0EAED8LFgAgACABQf8BcRAQIAAgAkH/AXEQEAuKCAEPfyMAQeAEayIMJAAgACACEL4DIQ0gACACQYABchC+AyESAkAgAkUgAUECSXINACAMIAE2AgQgDCAANgIAIAxBADYCCEEAIAJrIRAgDEEMciELA0AgCyAMTQ0BIAtBDGsiCygCCCIOQTIgDkEyShshEyALKAIEIQkgCygCACEFA0ACQCAFIAlBB08EfyAOIBNHDQEgAiAJbCIGIAJrIQggCUEBdiACbCEKIAUgAhC+AyEJA0ACQCAKRQRAA0AgBiACayIGRQ0CIAUgBSAGaiACIAkRBgAgBiACayEIQQAhAANAIABBAXQgAmoiASAGTw0BIAEgCEkEQCABQQAgAiABIAVqIgcgAiAHaiAEIAMRAQBBAEobaiEBCyAAIAVqIgcgASAFaiIAIAQgAxEBAEEASg0BIAcgACACIAkRBgAgASEADAALAAsACyAKIAJrIgohAANAIABBAXQgAmoiASAGTw0CIAEgCEkEQCABQQAgAiABIAVqIgcgAiAHaiAEIAMRAQBBAEobaiEBCyAAIAVqIgcgASAFaiIAIAQgAxEBAEEASg0CIAcgACACIAkRBgAgASEADAALAAsLQQAFIAkLIAJsaiEIIAUhBwNAIAIgB2oiByEAIAcgCE8NAwNAIAAgBU0NASAAIBBqIgEgACAEIAMRAQBBAEwNASAAIAEgAiANEQYAIAEhAAwACwALAAsgDkEBaiEOQQEhByAFAn8gBSAJQQJ2IAJsIgFqIgYgBSABQQF0aiIIIAQgAxEBACEAIAggBSABQQNsaiIKIAQgAxEBACEBAkAgAEEASARAIAFBAEgNASAKIAYgBiAKIAQgAxEBAEEASBsMAgsgAUEASg0AIAYgCiAGIAogBCADEQEAQQBIGyEICyAICyACIA0RBgAgBSACIAlsaiIKIQEgCiEIIAIgBWoiDyEAQQEhEQNAAkACQCAAIAFPDQAgBSAAIAQgAxEBACIGQQBIDQAgBg0BIA8gACACIA0RBgAgAiAPaiEPIBFBAWohEQwBCwJAA0AgACABIBBqIgFPDQEgBSABIAQgAxEBACIGQQBMBEAgBg0BIAggEGoiCCABIAIgDREGACAJQQFrIQkMAQsLIAAgASACIA0RBgAMAQsgBSAAIA8gBWsiBiAAIA9rIgEgASAGSxsiAWsgASASEQYAIAAgCiAKIAhrIgEgCCAAayIGIAEgBkkbIgBrIAAgEhEGACAJIAdrIQggCiAGayEBAkAgCCAHIBFrIglJBEAgBSEHIAkhACABIQUgCCEJDAELIAEhByAIIQALIAsgDjYCCCALIAA2AgQgCyAHNgIAIAtBDGohCwwCCyAAIAJqIQAgB0EBaiEHDAALAAsACwALIAxB4ARqJAALTgEBfyABEJABBEAgARAPDwsCQCABQoCAgIBwVA0AIAGnIgIvAQZBBEcNACACKQMgIgEQkAFFDQAgARAPDwsgAEGkMkEAEBZCgICAgOAAC40CAQJ/IwBBEGsiAyQAIAMgAjcDCEKAgICA4AAhAgJAIAAgARDCASIEQQBIDQAgBEUEQCAAQoCAgIAwQQEgA0EIahDpAiECDAELIAAgAUE8IAFBABAUIgEQDQRAIAEhAgwBCwJAAkAgARC1AUUNACAAIAEQjwMiBEUNASAAIARGDQAgACABIAQpA0AQWkUNACAAIAEQDEKAgICAMCEBCyABECIEQCAAIAFBzAEgAUEAEBQhAiAAIAEQDCACEA0NAkKAgICAMCACIAIQKBshAQsgARASBEAgAEKAgICAMEEBIANBCGoQ6QIhAgwCCyAAIAFBASADQQhqELIBIQILIAAgARAMCyADQRBqJAAgAgsaACAAQd4AQdgAIAEbEBAgACACQf//A3EQMQvwAQEDfwNAAkAgAiADTA0AIAEgA2oiBS0AACIGQQJ0IQcCQAJAIAZBtAFHBEAgBkHAAUcNASAEIAUoAAE2AgAMAgsgACAFKAABIgVBABB0QQBKDQIgACgCpAIgBUEUbGooAhBFDQFBguoAQb7jAEGI8AFBotUAEAAACyAHQbOaAWotAAAiBkEcSw0AQQEgBnQiBkGAgIAccUUEQCAGQYCAgOAAcUUEQCAGQYCAgIIBcUUNAiAAIAUoAAFBfxB0GgwCCyAAIAUoAAVBfxB0GgsgACgCACAFKAABEBMLIAMgB0GwmgFqLQAAaiEDDAELCyADC7kDAQV/IAFFBEAgACACQQRxQQhyEO4BDwtBfyEDAkACQAJAIAAgAUEBayIEIAIQswINACAEQQdLDQEgAkF7cSEFIAJBAXEhBiABQQFrIQcDQCAAKAIQIQECQAJAAkACQAJAAkACQAJAAkACQCAHDgcAAQIDBAUGBwsgAUElRwRAQZoBIQIgAUEqRg0JIAFBL0cNDUGbASECDAkLQZwBIQIMCAtBnQEhAkEAIQMCQCABQStrDgMICgAKC0GeASECDAcLIAFB6gBqIgFBA08NCiABQeAAayECDAYLQQAhAwJAAkACQAJAIAFB5gBqDgMBCwIACwJAIAFByQBqDgIIAwALQaMBIQICQCABQTxrDgMJCwALC0GlASECDAgLQaQBIQIMBwtBpgEhAgwGC0GnASECDAULIAFB4wBqIgFBBE8NCEGp16rleiABQQN0diECDAQLQa0BIQIgAUEmRw0HDAMLQa4BIQIgAUHeAEcNBgwCC0GvASECIAFB/ABHDQUMAQtBqAEhAiAGRQ0CC0F/IQMgABARDQEgACAEIAUQswINASAAIAJB/wFxEA4MAAsACyADDwsQAQALQQALCQAgAEECEM8DC1MBBH8gACgC9AEiAkEAIAJBAEobIQRBACECAkADQCACIARGDQEgASAAKAL8ASIFIAJBBHRqKAIMRwRAIAJBAWohAgwBCwsgBSACQQR0aiEDCyADCzYAA0AgASACTEUEQCAAQbMBEA4gACABQf//A3EQGCAAKAJAKALMASABQQN0aigCACEBDAELCwvZAQEBfyAAIAAoAkAiAyABAn8CQAJAAkACQAJAIAFBJ0YNACABQc0ARiABQTpGckUEQCABQcUARg0BIAFBLUcNAiADLQBsQQFHDQIgAEHKMEEAEBVBfw8LIAMtAG5BAXEEQCAAQcTTAEEAEBVBfw8LIAFBxQBHDQELIAJBsX9GDQMgAkFDRg0BIAJBUUcgAkFJR3ENAiAAQdHPAEEAEBVBfw8LIAJBsX9GDQIgAkFDRg0AQQEgAkFRRg0DGiACQUlHDQFBAgwDC0EFDAILEAEAC0EGCxCsAUEfdQsJACAAQQAQ8QELQAEBfwJAIAJCgICAgHBUDQAgAqciAy8BBkEKRw0AIAMpAyAiAhCQAUUNACAAIAEgAhBHDwsgAEGhHUEAEBZBfwsbAQF+IAAgASACIAMgBBDGAiEFIAAgARAMIAUL2gMCBn8BfiMAQTBrIgUkACABQSoQQCEGIAVCADcCKAJAA0AgB0ECRwRAQQAhBCAAQSAQbCIIBEADQCAEQQJGRQRAIAggBEEDdCIJaiADIAlqKQMAEA83AwggBEEBaiEEDAELCyAIIAIgB0EDdGopAwAiCkKAgICAMCAAIAoQOxsQDzcDGCAFQShqIAdBAnRqIAg2AgAgB0EBaiEHDAIFQX8hBCAHQQFHDQMgACgCECAFKAIoELwCDAMLAAsLAkAgBigCACIERQRAQQAhBANAIARBAkYNAiAFQShqIARBAnRqKAIAIAYgBEEDdGpBBGoQTCAEQQFqIQQMAAsACwJAIARBAkcNAEECIQQgBigCFA0AIAAoAhAiAigCmAEiA0UNACAAIAEgBikDGEEBIAIoApwBIAMRMwAgBigCACEECyAFIAVBKGogBEEBayIDQQJ0aigCACICKQMINwMAIAUgAikDEDcDCCAFIAIpAxg3AxBBACEEIAUgA0EAR61CgICAgBCENwMYIAUgBikDGDcDICAAQS1BBSAFEIMDA0AgBEECRg0BIAAoAhAgBUEoaiAEQQJ0aigCABC8AiAEQQFqIQQMAAsACyAGQQE2AhRBACEECyAFQTBqJAAgBAsjACAAIAEpAwgQJyAAIAEpAxAQJyAAIAEpAxgQJyAAIAEQIQsMACAAIAEgACABUxsLhgIBAX8jAEEQayIHJAAgByAAOQMIIAcgAUEBayIFNgIAIAZBgAFBzNgAIAcQVxogAyAGLQAAQS1GNgIAIAQgBi0AAToAACABQQJOBEAgBEEBaiAGQQNqIAUQJRoLIAEgBGpBADoAACACAn8gASAGaiABQQFKakECaiECQQAhA0EAIQQDQCACIgFBAWohAiABLAAAEIMGDQALAkACQAJAIAEsAAAiBUEraw4DAQIAAgtBASEECyACLAAAIQUgAiEBCyAFEEUEQANAIANBCmwgASwAAGtBMGohAyABLAABIQIgAUEBaiEBIAIQRQ0ACwsgA0EAIANrIAQbQQFqCzYCACAHQRBqJAALCgAgACABQQJ0agsOACAAIAFqQYGA3PF5bAsQACAAIAAoAhhBf3NBAnRqCyEAIAAgAa0gASkDAEKAgICAMCABKAIIIAEoAiBBBBDjAQuWAgIFfwF+IwBBEGsiBCQAIwBBEGsiAyQAIANCgICAgDA3AwggA0KAgICAMDcDACAAQSxBAkEAQQIgAxDmASEIIANBEGokACAEIAg3AwggCBANRQRAAn4CfiACEBIEQCAAIAJBASAEQQhqEOoFDAELIAAgAkEBIARBCGoQsgELIggQDUUEQCAEKQMIQQ8QQCEHA0AgBUECRgRAA0AgBkECRwRAIAEgBkEDdCIDaiADIAdqKQMIEA83AwAgBkEBaiEGDAELCyAEKQMIIQIgCAwDCyAFQQN0IQMgBUEBaiEFIAAgAyAHaikDCBBpRQ0ACwsgACAEKQMIEAwgCCECQoCAgIDgAAshCCAAIAIQDAsgBEEQaiQAIAgLkwwDCX8DfgF8IwBB0ABrIggkACAIIAE2AkxB3wBBgAIgBEEgcRshCQJAAkACQAJAAkACQAJAAkAgAS0AACIHQStrDgMBAgACC0EBIQwLIAggAUEBaiIBNgJMIARBgAhxRQ0BIAEtAAAhBwsgB0EwRw0AAn8CQAJAAkACQAJAAkAgAS0AASIHQfgARwRAIAdB7wBGDQIgB0HYAEcNAQsgA0FvcQ0KIAggAUECaiIGNgJMQRAMBgsgA0UgB0HPAEZxDQEgB0HiAEYNAiADRSAHQcIARnENAyADIAdBMGtB/wFxQQlLcg0HIARBEHFFDQggAUEBaiEGQQEhBQNAIAdB+AFxQTBHDQUgASAFQQFqIgVqLQAAIQcMAAsACyADDQgLIARBBHFFDQYgCCABQQJqIgY2AkxBCAwDCyADDQYLIARBBHFFDQQgCCABQQJqIgY2AkxBAgwBC0GAAiEJIAdB/gFxQThGDQMgCCAGNgJMQQgLIQNCgICAgMB+IQ4gBi0AABD1ASADSA0DDAQLIARBgQFxDQACfyAIQcwAaiEHQdELIQUDQCAFLQAAIgYEQCAGIAEtAABHBEBBAAwDBSAFQQFqIQUgAUEBaiEBDAILAAsLIAcEQCAHIAE2AgALQQELRQ0ARAAAAAAAAPD/RAAAAAAAAPB/IAwbIhG9An8gEZlEAAAAAAAA4EFjBEAgEaoMAQtBgICAgHgLIgC3vVEEQCAArSEODAQLIBEQFyEODAMLIAMNAQtBCiEDCyAIKAJMIgpBAWohB0EAIQEgA0EKRyELAkADQAJAIAEgCmoiBS0AACIGQRh0QRh1IQ0gBhD1ASADTgRAIAkgDUcNASALIAFBAUdyRQRAIAotAABBMEYNBAsgBS0AARD1ASADTg0BCyAIIAogAUEBaiIBajYCTAwBCwsgBSEHC0EAIQsCQCAEQQFxDQACQCAGQS5HDQAgByAKTQRAIActAAEQ9QEgA04NAgsgCCAHQQFqIgU2AkxCgICAgMB+IQ4gCSAHLAABIgZGDQIDQCAGQf8BcRD1ASADTgRAQQEhCyAJIAZBGHRBGHVHDQIgBS0AARD1ASADTg0CCyAIIAVBAWoiATYCTCAFLQABIQYgASEFDAALAAsgBSAKTQ0AAkAgBkH/AXFB5QBHBEAgA0EKRiAGQf8BcUHFAEZxDQEgBkEgckH/AXFB8ABHIANBEEtyDQJBASADdEGEggRxDQEMAgsgA0EKRw0BC0EBIQsgBUEBaiEBAkACQAJAIAUtAAFBK2sOAwACAQILIAVBAmohAQwBCyAFQQJqIQELIAEtAAAQRUUNACABIQUDQCAIIAUiAUEBaiIFNgJMIAEtAAEiBEEYdEEYdSEHIAQQRQ0AIAcgCUcNASABLQACEEUNAAsLIAUgCkYEQEKAgICAwH4hDgwBCyAIIQkCQCAFIAprIgRBAmoiB0HBAE8EQCAAKAIQIAcQ6AEiCUUNAQtBACEBQQAhBiAMBEAgCUEtOgAAQQEhBgsgBEEAIARBAEobIQQDQCABIARHBEAgASAKai0AACIFQd8ARwRAIAYgCWogBToAACAGQQFqIQYLIAFBAWohAQwBCwsgBiAJakEAOgAAAn4gA0EKRwRAQoCAgIDAfiALDQEaCwJ8QgAhDiALIANBCkZxRQRAIAkgCS0AACIGQS1GaiEBA0AgASIEQQFqIQEgBC0AACIFQTBGDQALAn4gA0EKRgRAQgohD0KYs+bMmbPmzBkMAQtBACADa6wgA6wiD4ALIRBBACEBA0ACQCAFRQ0AIAUQ9QEiBSADTg0AIA4gBawgDiAPfnwgDiAQViIFGyEOIAEgBWohASAELQABIQUgBEEBaiEEDAELCyAOuiERIAEEQCADtyABtxCCBiARoiERCyARmiARIAZBLUYbDAELIAkQ+gULIhG9An8gEZlEAAAAAAAA4EFjBEAgEaoMAQtBgICAgHgLIgG3vVEEQCABrQwBCyAREBcLIQ4gB0HBAEkNASAAKAIQIAkQIQwBCyAAEMkBQoCAgIDgACEOCyACBEAgAiAIKAJMNgIACyAIQdAAaiQAIA4LKwAgAEH/AE0EQCAAQQN2Qfz///8BcUHA4AFqKAIAIAB2QQFxDwsgABC5BAsmAQF+IAAgASACIAFBABAUIgUQDQR+IAUFIAAgBSABIAMgBBA2CwuzBwIMfwF+IwBB4ABrIgUkACAAIAVByABqEJECAkAgAgRAIAUgAjYCQCAFQcgAakGqKCAFQUBrEIQCIANBf0cEQCAFIAM2AjAgBUHIAGpBgeMAIAVBMGoQhAILIAVByABqQQoQECAAIAFBMSAAIAIQdkEDEBsaIAAgAUEyIAOtQQMQGxogBEECcQ0BCyAEQQFxIQ0gACgCEEGMAWohAgNAIAIoAgAiAkUNASANBEBBACENDAELQQAhBAJAIAIpAwgiEUKAgICAcFQNACARpyIIKAIQIgYgBigCGEF/c0ECdEGkfnJqKAIAIQMgBhAqIQYDQCADRQ0BIAYgA0EBayIJQQN0aiIHKAIAIQMgBygCBEE2RwRAIANB////H3EhAwwBCwsgA0H/////A0sNACAIKAIUIAlBA3RqKQMAIhFCgICAgHCDQoCAgICQf1INACAAIBEQpgEhBAsgBSAEBH8gBEHA7wAgBC0AABsFQcDvAAs2AiAgBUHIAGpBqiggBUEgahCEAiAAIAQQNwJAIAIoAggiAy8BBhD4AQRAIAMoAiAiCC8AESIEQQt2QQFxIQMgBEGACHFFDQEgAigCICAIKAIUQX9zaiEQQQAhDiMAQRBrIgkkAEF/IQQCQCAILQASQQRxRQ0AIAgoAlAiB0UNACAHIAgoAkxqIQsgCCgCRCEGA0AgBiEEIAcgC08NASAHQQFqIQoCfyAHLQAAIgZFBEACQCAJQQhqIAogCxCTBSIMQQBIDQAgCSgCCCEPQQAhByMAQRBrIgYkAAJAIAZBDGogCiAMaiIMIAsQkwUiCkEASARAQX8hCgwBCyAGKAIMIgdBAXZBACAHQQFxa3MhBwsgCSAHNgIMIAZBEGokACAKIgdBAEgNACAJKAIMIARqIQYgByAMagwCCyAIKAJEIQQMAwsgBCAGQQFrIgYgBkH/AXFBBW4iD0EFbGtB/wFxakEBayEGIAoLIQcgDiAPaiIOIBBNDQALCyAJQRBqJAAgBSAAIAgoAkAQogQiBkHt7wAgBhs2AhAgBUHIAGpBlyggBUEQahCEAiAAIAYQNyAEQX9HBEAgBSAENgIAIAVByABqQYHjACAFEIQCCyAFQcgAakEpEBAMAQtBACEDIAVByABqQcP3AEEAEIQCCyAFQcgAakEKEBAgA0UNAAsLIAVByABqQQAQEEKAgICAICERIAUoAlRFBEAgACAFKAJIEHYhEQsgBUHIAGoQlwEgACABQTUgEUEDEBsaIAVB4ABqJAAL7AECAn8BfiMAQRBrIgMkACABQQhrIgQpAwAhBQJ/AkAgACADQQxqIAFBEGsiASkDABDGAQRAIAAgBRAMDAELIAAgA0EIaiAFEMYBDQAgAQJ/AkACQAJAAkACQAJAIAJBrQFrDgMBAwIACwJAIAJBoAFrDgIFAAQLIAMoAgwgAygCCHUMBQsgAygCCCADKAIMcQwECyADKAIIIAMoAgxyDAMLIAMoAgggAygCDHMMAgsQAQALIAMoAgwgAygCCHQLrTcDAEEADAELIAFCgICAgDA3AwAgBEKAgICAMDcDAEF/CyEAIANBEGokACAAC+oEAgd/An4CQCABQoCAgIBwg0KAgICAkH9SBEBCgICAgOAAIQogACABED0iARANDQELAkAgAkKAgICAcINCgICAgJB/UQ0AQoCAgIDgACEKIAAgAhA9IgIQDUUNACABIQIMAQsCQCACpyIFKQIEIgpC/////weDUA0AAkAgAaciAygCAEEBRw0AIAMpAgQgCoVCgICAgAiDQgBSDQAgACgCECADEKMEIAUoAgQiBkH/////B3EiCCADKAIEIgRB/////wdxIgdqIAZBH3Z0IARBH3YiCWtBEWpJDQAgA0EQaiEEIAkEQCAEIAdBAXRqIAVBEGogBkEBdBAlGiADIAMpAgQiCiAFKQIEfEL/////B4MgCkKAgICAeIOENwIEDAILIAQgB2ogBUEQaiAIECUaIAMgAykCBCIKIAUpAgR8Qv////8HgyILIApCgICAgHiDhDcCBCAEIAunakEAOgAADAELAn4CQAJAIAUpAgQiCqdB/////wdxIAMpAgQiC6dB/////wdxaiIGQYCAgIAETwRAIABBmsMAQQAQUAwBCyAAIAYgCiALhKciCEEfdhD9ASIHDQELQoCAgIDgAAwBCyAHQRBqIQQCQCAIQQBOBEAgBCADQRBqIAMoAgRB/////wdxECUiBCADKAIEQf////8HcWogBUEQaiAFKAIEQf////8HcRAlGiAEIAZqQQA6AAAMAQsgBCADIAMoAgRB/////wdxEJQFIAQgAygCBEEBdGogBSAFKAIEQf////8HcRCUBQsgB61CgICAgJB/hAshCiAAIAEQDAwBCyABIQoLIAAgAhAMIAoLQAAgAAJ/An8gAwRAIAEoAiQgAkEDdGpBBGoMAQtBACABKAIgIgNFDQEaIAMgAS8BKCACakEEdGoLKAIACxDiAQsLACAAQZ8JQQAQFguBDAINfwR+IwBBgAFrIgskACALIQUjAEHgAWsiCCQAAkAgAb0iEkKAgICAgICA+P8Ag0KAgICAgICA+P8AUQRAIBJC////////////AINCgYCAgICAgPj/AFoEQCAFQc7CuQI2AAAMAgsgAUQAAAAAAAAAAGMEQCAFQS06AAAgBUEBaiEFCyAFQdkLLQAAOgAIIAVB0QspAAA3AAAMAQsCQCAERQRAAn4gAZlEAAAAAAAA4ENjBEAgAbAMAQtCgICAgICAgICAfwsiE0KAgICAgICAEH1CgYCAgICAgGBUIBO5IAFicg0BIAhB1QFqIgNBADoAACATIBNCP4ciEoUgEn0hEiACrSEUA0AgAyICQQFrIgNBMEHXACASIBIgFIAiFSAUfn2nIgRBCkgbIARqOgAAIBIgFFohBCAVIRIgBA0ACyATQgBTBEAgAkECayIDQS06AAALIAUgAxCBBgwCC0QAAAAAAAAAACABIAFEAAAAAAAAAABhGyEBIARBAkcNACMAQYACayICJAACQCACQYABaiABIANBAWoiBEEAEIcDIAJqLQB/QTVHDQAgAkGAAWogASAEQYAIEIcDIgYgAiABIARBgBAQhwNHDQAgAkGAAWogAiAGEHcNAEGACEGAECACLQCAAUEtRhshCQsgBSABIAMgCRCHAxogAkGAAmokAAwBCyADIQIgCEEIaiENIAhBDGohDiAIQRBqIQwjAEGQA2siByQAAkAgBEEDcUEBRiIPRQRAQREhAkEBIQoDQCACIApNBEBBACEJDAMLIAEgAiAKakEBdiIJIA0gDiAMQQAgB0GQAmoiBhC+AiAGEPoFIAFhBEAg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AgAiAEECdGooAgAiAiABRg0BIAJBDGohAiAADQALQdX1AEG+4wBB+BRBrQ4QAAALIAAL8wYCBn8BfgJAAkACQAJ/IAJBAkwEQCACIAEpAgQiCUI+iKdGBEAgACABENYCIgMQ8gFFDQUgASABKAIAQQFrNgIAIAMPCyAAKAI0IAAoAiRBAWsgASACEOUFQf////8DcSIHcSIIQQJ0aiEDIAmnQf////8HcSEFA0AgAiADKAIAIgNFDQIaAkAgACgCOCADQQJ0aigCACIEKQIEIglCIIinQf////8DcSAHRyAJQj6IpyACR3IgCadB/////wdxIAVHcg0AIAQgASAFEOQFDQAgAxDyAQ0EIAQgBCgCAEEBajYCAAwECyAEQQxqIQMMAAsACyACQQNHIQdBAwshBQJAIAAoAjwNAEEAIQNB0wEgACgCLEEDbEECbRBKIgRB/////wNLDQEgACAAKAI4IARBAnQQ5wEiBkUNASAAKAIsIgJFBEAgAEEQEJwCIgJFBEAgACAGECEMAwsgAkEBNgIAIAIgAikCBEKAgICAgICAgECENwIEIAYgAjYCACAAIAAoAihBAWo2AihBASECCyAAIAI2AjwgACAGNgI4IAAgBDYCLCAEQQFrIQYDQCACIARPDQEgACgCOCACQQJ0akEAIAJBAWoiAyACIAZGGxDjBTYCACADIQIMAAsACwJAIAEEQCABKQIEIglC//////////8/WARAIAEgCSAFrUI+hoQ3AgQMAgsgACAJpyICQR91IAJB/////wdxIAJBH3Z0akERahDoASICRQRAQQAhAwwECyACQQE2AgAgAiACKQIEQv////93gyABKQIEQoCAgIAIg4QiCTcCBCACIAlCgICAgHiDIAEpAgRC/////weDhDcCBCACQRBqIAFBEGogASgCBCIDQR91IANB/////wdxIANBH3Z0akEBahAlGiAAIAEQpAQgAiEBDAELIABBEBDoASIBRQRAQQAPCyABQoGAgICAgICAgH83AgALIAAgACgCOCAAKAI8IgNBAnRqIgIoAgBBAXY2AjwgAiABNgIAIAEgAzYCDCABIAE1AgQgB61CIIaEIAWtQj6GhDcCBCAAIAAoAihBAWo2AiggBUEDRg0CIAEgACgCNCAIQQJ0aiIBKAIANgIMIAEgAzYCACAAKAIoIAAoAjBIDQIg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AgseACAAQTBrQQpJIABBX3FBwQBrQRpJciAAQd8ARnILrQEBA38gACgCQBoCQCAAKAIEIQMgACABEMYEDQBBBSADayEEA0AgACgCGCICLQAAQfwARwRAQQAPCyAAIAJBAWo2AhggACgCBCECIAAgA0EFEOsBBEAgABCsAkF/DwsgACgCACADakEJOgAAIAAoAgAgA2pBAWogAiAEahBdIABBB0EAELoBIQIgACABEMYEDQEgACgCACACaiAAKAIEIAJrQQRrEF0MAAsAC0F/C0gBAn8CQANAIAFBCkYNASABQQJ0QZLgAWovAQAgAEoNASABQQF0IQIgAUEBaiEBIAJBAXRBlOABai8BACAATA0AC0EBDwtBAAukAgEBfwJ/An8gAUH/AE0EQCAAIAE6AAAgAEEBagwBCwJAIAFB/w9NBEAgACABQQZ2QcABcjoAACAAIQIMAQsCfyABQf//A00EQCAAIAFBDHZB4AFyOgAAIABBAWoMAQsCQCABQf///wBNBEAgACABQRJ2QfABcjoAACAAIQIMAQsCfyABQf///x9NBEAgACABQRh2QfgBcjoAACAAQQFqDAELQQAgAUEASA0FGiAAIAFBHnZB/AFyOgAAIAAgAUEYdkE/cUGAAXI6AAEgAEECagsiAiABQRJ2QT9xQYABcjoAAAsgAiABQQx2QT9xQYABcjoAASACQQJqCyICIAFBBnZBP3FBgAFyOgAACyACIAFBP3FBgAFyOgABIAJBAmoLIABrCwskACAAQgA3AgAgACABNgIUIABCADcCCCAAIAJB4QIgAhs2AhALJwECfwJAIAAgAUEAEJsBIgMEQCADEJoBRQ0BIAAQdQtBfyECCyACC8kBAgN/AX4jAEEQayIFJAACQCAAIAFBAhBvIgEQDQ0AAkACQCACQQFHDQAgAykDACIHEJABRQ0AIAAgBUEMaiAHEA9BARDOAg0BIAAgAUEwAn4gBSgCDCICQQBOBEAgAq0MAQsgArgQFwsQSEEASA0BDAILIAJBACACQQBKGyECA0AgAiAERg0CIAAgASAEIAMgBEEDdGopAwAQDxCWAiEGIARBAWohBCAGQQBODQALCyAAIAEQDEKAgICA4AAhAQsgBUEQaiQAIAELEQAgACABIAIgAyAEIAUQywELDQAgAEEGQX9BBRDrBQt8AgJ+AX8gACACKQMA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CgIBBgYGBgYCAwAECyAApyEBDAULIACnQQAQ5QUhAQwECyAAp0HbGGwhAQwDCyAAp0HbGGy3DAELIAJBB0cNAUQAAAAAAAD4fyAAEEkiAyADvUL///////////8Ag0KAgICAgICA+P8AVhsLvSIAQiCIIACFp0HbGGwhAQsgASACcwsHACAAQQFxCy4BAX8gACAAIAEgACACEMoBIgIgAUEAEBQiARCmASEDIAAgARAMIAAgAhATIAMLEgAgAEEIdCAAQQh2ckH//wNxC1ABAX8gAEEgEC8iAgRAIAJBATYCACACQoCAgIDAAEKAgICAMCABGzcDGCACIAJBGGo2AhAgAiACLQAFQQFyOgAFIAAoAhAgAkEDEL4BCyACCwoAIAAoAgQgAEYLMgACQCAAIAIgAUEAQQAQJCICEA0NACACECINACAAIAIQDCAAEClCgICAgOAAIQILIAILCwAgACABIAIQxgELCwAgAEG2PEEAEBYLdQECfyMAQZABayIEJABB3PsAIQUCQAJAAkACQCABQQFqDgUDAgIAAQILQZ37ACEFDAELQdseIQULIAAgBEHQAGogAxCJASEBIAQgACAEQRBqIAIoAgQQiQE2AgQgBCABNgIAIAAgBSAEENMCCyAEQZABaiQAC2kBAn8jAEEQayIFJAAgBUEANgIIIAVCADcDACAAIAEgAiADIAQgBRChBSECA0AgBSgCACEBIAYgBSgCCE5FBEAgACABIAZBA3RqKAIEEBMgBkEBaiEGDAELCyAAIAEQGiAFQRBqJAAgAgseACABKAIAQQRHBEAgACABQQhqEIsDIAFBBDYCAAsLpQEBBX8jAEEQayIDJABBfyECAkAgACgCFA0AIAAoAgAgACgCBCABQQF0QRBqIANBDGoQtwEiBEUEQCAAEIoDDAELIARBEGohBSAAKAIIIQIgAygCDCEGA0AgAkEATEUEQCAFIAJBAWsiAkEBdGogAiAFai0AADsBAAwBCwsgAEEBNgIQIAAgBDYCBCAAIAZBAXYgAWo2AgxBACECCyADQRBqJAAgAgtUAQJ/IAAgASkDGCACECMgACABKQMAIAIQIwJAIAEoAjwiBEUNACABKAIgIQMDQCADIARPDQEgACADKQMAIAIQIyADQQhqIQMgASgCPCEEDAALAAsLGgEBfyABpygCICIDBEAgACADKQMA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BCQAAkACQCAAIARBCGogAUHiABCHASICRQ0AIAQpAwgiARASBEAgACACKQMAEJkEIQMMAgsgACABIAIpAwhBASACEDYiARANDQAgACABEC0iA0UEQEEAIQMMAgsgACACKQMAEKIBIgJBAEgNACACRQ0BIABB6iJBABAWC0F/IQMLIARBEGokACADDwsgAiACLQAFQf4BcToABUEBCy4BAX8gAKcpAyAiAEKAgICAcINCgICAgJB/UQR/IACnKAIEQf////8HcQVBAAsLCgAgACgCAEF8cQszACAAIAJBARD9ASIARQRAQoCAgIDgAA8LIABBEGogASACQQF0ECUaIACtQoCAgICQf4QLZQICfwF+QQQhAkKAgICAICEEAkACQAJAAkACQAJAIAEQViIDQQhqDgoDAgUFBQUFBQQBAAsgA0EHRg0DDAQLQQYhAgwCC0EFIQIMAQtBByECCyAAKAIoIAJBA3RqKQMAIQQLIAQLYAEBfCAAKQIEQv//////////P1gEQCABIAErAwhEAAAAAAAA8D8gACgCALciAqOgOQMIIAEgASsDECAAKAIEIgBBH3UgAEH/////B3EgAEEfdnRqQRFquCACo6A5AxALC+kGAQV/AkACQAJAAkACQAJAAkACQAJAIAEtAARBD3EOBgABBAIDBgULIAAgASgCECIHIAIRAwAgBxAqIQUDQCAHKAIgIANKBEACQCAFKAIERQ0AIAEoAhQgA0EDdGohBAJAAkACQAJAIAUoAgBBHnZBAWsOAwABAgMLIAQoAgAiBgRAIAAgBiACEQMACyAEKAIEIgRFDQMgACAEIAIRAwAMAwsgBCgCACIELQAFQQFxRQ0CIAAgBCACEQMADAILIAAgBBCbBCACEQMADAELIAAgBCkDACACECMLIANBAWohAyAFQQhqIQUMAQsLIAEvAQYiA0EBRg0GIAAoAkQgA0EYbGooAgwiA0UNBiAAIAGtQoCAgIBwhCACIAMREQAPCwNAIAEoAjggA0oEQCAAIAEoAjQgA0EDdGopAwAgAhAjIANBAWohAwwBCwsgASgCMCIBRQ0FIAAgASACEQMADwsgAS0ABUEBcUUNBSAAIAEoAhApAwAgAhAjDwsgASgCIARAIAAgAUEoaiACEO8DCyAAIAEpAxAgAhAjIAAgASkDGCACECMPCyABKAIsIgFFDQIgACABIAIRAwAPCxABAAsgAUHkAWohAyABQeABaiEHA0AgByADKAIAIgVHBEAgBUEIayEDQQAhBANAIAMoAiAgBEoEQAJAIAMoAhwgBEEUbGoiBigCCA0AIAYoAgQiBkUNACAAIAYgAhEDAAsgBEEBaiEEDAELCyAAIAMpA0AgAhAjIAAgAykDSCACECMgACADKQNgIAIQIyAAIAMpA2ggAhAjIAVBBGohAwwBCwsgACABKQPAASACECMgACABKQPIASACECMgACABKQOwASACECMgACABKQO4ASACECMgACABKQOoASACECNBACEDA0AgA0EIRgRAQQAhAwNAIAAoAkAgA0oEQCAAIAEoAiggA0EDdGopAwAgAhAjIANBAWohAwwBCwsgACABKQOYASACECMgACABKQOgASACECMgACABKQNQIAIQIyAAIAEpA0AgAhAjIAAgASkDSCACECMgACABKQM4IAIQIyAAIAEpAzAgAhAjIAEoAiQiAQRAIAAgASACEQMACwUgACABIANBA3RqKQNYIAIQIyADQQFqIQMMAQsLCw8LQZniAEG+4wBBjixB0joQAAALiAICAX4CfyMAQTBrIgQkAEHK5gAhBUKAgICA4AAhAwJAAkACQAJAAkACQAJAAkACQAJAAkACQCABEFZBCGoOEAUGCQkJCQoEAAECAwkJCwgJCyAEIAE+AgAgBEEQaiIFQSBBnOMAIAQQVxoMCQsgAEEDQQIgAacbEDIhAwwJCyAAQQEQMiEDDAgLIABBxQAQMiEDDAcLIAAgAUEAEJsDIgEQDQRAIAEhAwwHCyAAIAEgAhCgBCEDIAAgARAMDAYLIAJFDQELIAEQDyEDDAQLIABBw8MAQQAQFgwDCyAAIAEQSUEKQQBBABDMAiEDDAILQbfmACEFCyAAIAUQdiEDCyAEQTBqJAAgAwutBAIIfwF+AkACQAJAAkACQCACQoCAgIBwg0KAgICAkH9SBEAgACACEC4iAhANRQ0BDAILIAIQDyECCyACpyIEQRBqIQcgBCkCBCIMp0H/////B3EhBgJAIAxCgICAgAiDUARAQQAhBANAIAQgBkZFBEAgBSAEIAdqLQAAQQd2aiEFIARBAWohBAwBCwsgBUUEQCAHIQQgAQ0EDAYLIAAgBSAGakEAEP0BIghFDQIgCEEQaiEEQQAhBQNAIAUgBkYN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C1QAIwBBEGsiAiQAIAAgAkEIaiADKQMAEEcEfkKAgICA4AAFIAIpAwhC////////////AINCgICAgICAgPj/AFatQoCAgIAQhAshASACQRBqJAAgAQv4AgIDfwN+IwBBMGsiCCQAIANCACADQgBVGyENIAVBAWshCiAFQQBMIQVCACEDA0ACQCADIA1RBEAgBCEMDAELQn8hDCAAIAIgAyAIQShqEIwBIglBAEgNAAJAIAlFDQAgBhASRQRAIAggCCkDKDcDACADIQsgA0KAgICACFoEQCADuRAXIQsLIAggAjcDECAIIAs3AwggCCAAIAYgB0EDIAgQJCILNwMoIAAgCCkDABAMIAAgCCkDCBAMIAsQDQ0CCwJAAkACQCAFDQAgACAIKQMoIgsQwgEiCUEASA0BIAlFDQAgACAIQSBqIAsQQUEASA0BIAAgASALIAgpAyAgBCAKQoCAgIAwQoCAgIAwENQEIgRCAFMNASAAIAsQDAwDCyAEQv////////8PUw0BIABBi8MAQQAQFiAIKQMoIQsLIAAgCxAMDAILIAAgASAEIAgpAygQcEEASA0BIARCAXwhBAsgA0IBfCEDDAELCyAIQTBqJAAgDAsMACAAQgAgAEIAVRsLKAACQCABEBJFBEAgARAoRQ0BCyAAIAEQPQ8LIAAgAUE4QQBBABC6AgujAgIGfwF+IwBBMGsiAiQAAkACQCADKQMAIgEQIkUNAEKAgICA4AAhCyAAIAEQmQQiA0EASA0BIANFBEAgAEHdygBBABAWDAILIAAgAkEsaiACQShqIAGnIglBAxCSAQ0BIAIoAiwhBiACKAIoIQdBACEDAkADQCADIAdHBEAgBiADQQN0aigCBCEIQYCCASEFAkAgBEUNACAAIAJBCGogCSAIEE8iCkEASA0DIApFDQAgAigCCCEFIAAgAkEIahBOQYCGAUGAggEgBUECcRshBQsgACABIAhCgICAgDBCgICAgDBCgICAgDAgBRB4QQBIDQIgA0EBaiEDDAELCyAAIAYgBxBmDAELIAAgBiAHEGYMAQsgARAPIQsLIAJBMGokACALC+sBAQF+AkACQCABECgEQCAAQek9EHYhBAwBCyABEBIEQCAAQf/gABB2IQQMAQsgACABECsiARANDQEgACABEMIBIgNBAEgEQCAAIAEQDEKAgICA4AAPCwJ/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RQRAIAUgAigCDCABGyEDIAYhAQwCCyADQQZrDgICAAYLIABBiNgAQQAQFQwECyACQShqIAQQwAFFDQEMAwsLIABBnzNBABAVDAELIABBxDNBABAVCyACQShqEEQgAkEQahBECyACQUBrJAAgCAszAQF/A0ACQCABQQBOBH8gASACRw0BQQEFQQALDwsgACgCzAEgAUEDdGooAgAhAQwACwALQwECfyAAKAKIASECQX8hAwJAA0AgAkEATA0BIAAoAoABIAJBAWsiAkEEdGooAgAgAUcNAAsgAkGAgICAAnIhAwsgAwuDAwEGfyABKAI4IQMCQAJAAkAgAS0AbkEBcQRAIANFBEBBry4hAyABKAJADQMLQdbVACEDIAJBOkYgAkHNAEZyDQJBACECIAEoAogBIgNBACADQQBKGyEEA0AgAiAERg0CQbHVACEDIAEoAoABIAJBBHRqKAIAIgZBOkYgBkHNAEZyDQMgAkEBaiECDAALAAsgA0UNACABLwFsIgJBggxGDQAgAkEIdkEDaw4EAAICAAILQQAhBCABKAKIASICQQAgAkEAShshCEEAIQMDQCADIAhGDQJBACECAkAgASgCgAEiBSADQQR0aigCACIGRQ0AA0ACQCACIANGBEBBACECIAEoAnwiBUEAIAVBAEobIQUDQCACIAVGDQQgBiABKAJ0IAJBBHRqIgcoAgBGBEAgBygCBEUNAwsgAkEBaiECDAALAAsgAkEEdCEHIAJBAWohAiAFIAdqKAIAIAZHDQELC0GvEiEDDAILIANBAWohAwwACwALIAAgA0EAEBVBfyEECyAEC2EBAX8gAEG2ARAOIABB9gAQHCAAIAAoAkAvAbwBEBggAEEREA4gAEHpAEF/EB0hASAAQbYBEA4gAEEIEBwgAEEAEBggAEEbEA4gAEEkEA4gAEEAEBggACABECAgAEEOEA4LUQECf0F/IQJBASEDA0ACQCAAIAEQuwENACADRQRAIAAoAkBBfzYCmAILIAAoAhBBLEcEQEEAIQIMAQsgABARDQAgAEEOEA5BACEDDAELCyACCykBAX4gACABEMoBIgFFBEBCgICAgOAADwsgACABEDIhAiAAIAEQEyACC9sCAQR/IwBBoAFrIgUkACABKAIAIQcgBUGAATYCCCAFIAVBEGo2AgwgBAR/IAVBIzoAEEEBBUEACyEE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DEEHEBsLIQogACAFEBMgB0EBaiEHIApBAE4NAAsMAQsgACAGIAgQZkEAIQYgACACEGAiDBANDQAgBCALNwMYIAQgDDcDECAAIAMgAUECIARBEGoQJCEBIAAgDBAMIAAgCxAMDAELIAAgBiAEKAIMEGYgACALEAxCgICAgOAAIQELIARBIGokACABC+sCAQN/IwBBQGoiAyQAAkAgACABEGMiARANDQACQCAAIANBIGogAaciBCgCBEH/////B3FBAmoQQg0AIANBIGpBIhA+DQAgA0EANgI8A0AgBCgCBEH/////B3EgAkoEQAJAAkACQAJAAkACQAJAAkACQAJAIAQgA0E8ahDbASICQQhrDgYFAgQBBgMACyACQSJGIAJB3ABGcg0GCyACQYBwcUGAsANHIAJBIE9xDQYgAyACNgIAIANBEGoiAkEQQcAPIAMQVxogA0EgaiACEI4BDQoMBwtB9AAhAgwEC0HyACECDAMLQe4AIQIMAgtB4gAhAgwBC0HmACECCyADQSBqQdwAED4NBCADQSBqIAIQPkUNAQwECyADQSBqIAIQwAENAwsgAygCPCECDAELCyADQSBqQSIQPg0AIAAgARAMIANBIGoQOSEBDAELIAAgARAMIANBIGoQREKAgICA4AAhAQsgA0FAayQAIAELbgEEf0F/IQZBfyACKAIAIgRBAXYgBGogBEGp1arVeksbIQUCQAJAIAMgASgCACIHRgRAIAAgBRAvIgBFDQIgACADIAQQJRoMAQsgACAHIAUQmgIiAEUNAQsgASAANgIAIAIgBTYCAEEAIQYLIAYLYQECfwNAIAAoAigiAUEATEUEQCAAIAFBAWsiATYCKCAAKAIAIAAoAgQgAUEDdGopAwAQDAwBCwsgACgCBCIBIABBCGoiAkcEQCAAKAIAIAEQGgsgAEEENgIsIAAgAjYCBAukBQILfwV+IwBBMGsiAiQAIAEpAyAhDyABKQMYIQ4gASkDCCENIAEpAwAhEAJ+AkACQCABKQMoIhEQngEEQCANEJ4BDQELIABB/MMAQQAQFgwBCyAAIAJBCGpBABBCGiACQQA2AiQCQCAOEBJFBEAgACACQSRqIA4Q3AENAQsgACACQShqIBAQ3AENACAAIAJBLGogASkDEBDHAUEASA0AIA2nIQcgAigCLCIKIAIoAihqIQsgEaciBCgC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CwsNACAAIAEgAkEAEN8BC0MAAnwgAb1CgICAgICAgPj/AINCgICAgICAgPj/AFEEQEQAAAAAAAD4fyAAmUQAAAAAAADwP2ENARoLIAAgARCCBgsLewEBfgJAAn4gBEEEcQRAQSYhAiAAIAEQYwwBC0ElIQIgACABECsLIgEQDQ0AIAAgAhCkASIFEA0NACAAQRAQLyICBEAgAkEANgIMIAIgBEEDcTYCCCACIAE3AwAgBSACEI0BIAUPCyAAIAUQDAsgACABEAxCgICAgOAAC5UBAgJ+AX8gACABEDIhAwJAIAEQXg0AQQAgACgCECgCOCABQQJ0aigCACkCBCICQoCAgICAgICAQINCgICAgICAgICAf1IgAkKAgICA8P///z+DUCACQv//////////v39WcRsNACACp0F/c0EfdkEBIAJC/////weDUBshBAsgBAR+IABBhOcAIANBgucAEL8BBSADCwvcAwEFfyAAQeAAaiIEEHEgAEHQAGohBSAAKAJUIQECQAJAA0AgASAFRwRAIAFBBGstAABBEE8NAiABKAIEIQIgACABQQhrIgNBBhCfBCADIAMtAARBD3FBEHI6AAQgAiEBIAMoAgANASADQQhqIgIQRiACIAQQTAwBCwsMAQtB+vQAQb7jAEHELEG6xgAQAAALIABB1ABqIQEgAEHQAGohAwJAAkADQCADIAEoAgAiAkcEQCACQQhrIgEoAgBBAEwNAiABIAEtAARBD3E6AAQgACABQQcQnwQgAkEEaiEBDAELCyAAQeQAaiEBIABB4ABqIQIDQCACIAEoAgAiAUcEQCAAIAFBCGtBCBCfBCABQQRqIQEMAQsLDAELQZfzAEG+4wBB5yxB7DsQAAALIAAiAkECOgBoIABB2ABqIQMgAEHgAGohBANAIAQgAigCZCIARwRAIABBCGshASAAQQRrLQAAQQ5xBEAgAUEIaiIAEEYgACADEEwFIAIgARDeBQsMAQsLIAJBADoAaCACKAJcIQACQAJAA0AgACADRwRAIABBBGstAABBDnENAiAAKAIEIQEgAiAAQQhrECEgASEADAELCyADEHEMAQtBv+0AQb7jAEGdLUGwJRAAAAsLpwEBBX8gAKciAygCECIBIAEoAhhBf3NBAnRBpH5yaigCACECIAEQKiEBA0AgAkUEQEEADwsgASACQQFrIgRB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AyACNgLIBQsgA0KOgICAcDcDgAEgA0GIBWogAygCkAUgA0GAAWoQLARAIAMoApQFIgJBAEgNFyADIAI2AsgFDBcLIAEgAygCtAUgAygCyAUQNCADQbAFakEAIAdrEMcDDBYLIANCjoCAgHA3A3AgA0GIBWogBCADQfAAahAsBEAgAygClAUiAkEASA0WIAMgAjYCyAUMFgsgA0Lp1IGAcDcDYCADQYgFaiAEIANB4ABqECwEQCAHQQBHIQgMDQsgASADKAK0BSADKALIBRA0IANBsAVqIAcQxwMgBCECDBkLIAkoAAEiB0H/AUoNDyABIAMoArQFIAMoAsgFEDQgA0GwBWoiAiAFQcUAa0H/AXEQECACIAdB/wFxEBAgBCECDBgLIAkoAAEhAiADQo6AgIBwNwOgASADQYgFaiAEIANBoAFqECwEQCAAIAIQEyADKAKUBSICQQBIDRQgAyACNgLIBQwUCyACQS9HDQ4gAEEvEBMgASADKAK0BSADKALIBRA0IANBsAVqQb8BEBAgBCECDBcLIANCyYCAgHA3A9gBIANC2Lb5gnA3A9ABIANBiAVqIAQiAiADQdABahAsDRYgA0F/NgLIASADQoGEkICQCTcDwAEgA0GIBWogAiADQcABahAsDRYgA0F/NgK4ASADQoaOqMiQCTcDsAEgA0GIBWogBCADQbABahAsDRYMDQsgA0KOgICAcDcDoAIgA0GIBWogBCADQaACahAsBEAgAygClAUiAkEASA0SIAMgAjYCyAUMEgsgA0KogICAcDcDkAIgA0GIBWogBCADQZACahAsBEACQCADKAKUBSICQQBIBEAgAygCyAUhAgwBCyADIAI2AsgFCyABIAMoArQFIAIQNCADQbAFakEpEBAMEgsgA0Lp1IGAcDcDgAJBACEIIANBiAVqIAQgA0GAAmoQLA0IIANCq4GAgHA3A/ABIANBiAVqIAQgA0HwAWoQLARAAkAgAygClAUiAkEASARAIAMoAsgFIQIMAQsgAyACNgLIBQsgASADKAK0BSACEDQgA0GwBWpB8AEQEAwSCyADQX82AugBIANCrIGAgJDNGjcD4AEgA0GIBWogBCADQeABahAsRQ0MAkAgAygClAUiBUEASARAIAMoAsgFIQUMAQsgAyAFNgLIBQsgASADKAK0BSAFEDQgA0GwBWpB8AEQECADKAKYBUEDcyEFDBILIANBfzYCuAIgA0LD9oCA4AE3A7ACIANBiAVqIAQgA0GwAmoQLEUNCwJAIAMoApQFIgJBAEgEQCADKALIBSECDAELIAMgAjYCyAULIAEgAygCtAUgAhA0IANBsAVqIgIgAy0AmAUQECACIAMoAqgFEB4MEAsgA0F/NgLoAiADQtm4/YJwNwPgAiADQYgFaiAEIANB4AJqECxFDQogAygClAUiAkEATgRAIAMgAjYCyAULIANCjoCAgHA3A9ACIAMoApgFIgVBAWohBwJAIANBiAVqIAMoApAFIgIgA0HQAmoQLAR/IAMoApQFIgJBAE4EQCADIAI2AsgFCyADIAMoApwFNgLEAkF/IQQgA0F/NgLIAiADIAVBAWs2AsACIANBiAVqIAMoApAFIgIgA0HAAmoQLEUNASADKAKQBSECIAMoApQFBUF/CyEEIAchBQsgASADKAK0BSADKALIBRA0IANBsAVqIAUgAygCnAUQaCAEQQBIDRMgAyAENgLIBQwTCyAJLwABIgdB/wFLDQkgA0KOgICAcDcC/AMgAyAHNgL4AyADQpCjgoCQCzcD8AMCQCADQYgFaiAEIANB8ANqECxFBEAgA0KOgICAcDcD4AMgAyAHNgLcAyADQdkANgLYAyADQo6fgoCQAjcD0AMgA0GIBWogBCADQdADahAsRQ0BCwJAIAMoApQFIgVBAEgEQCADKALIBSEFDAELIAMgBTYCyAULIAEgAygCtAUgBRA0IANBsAVqIgRBkwFBkwFBkgEgAygCmAUiAkGRAUYbIAJBjwFGGxAQIAQgB0H/AXEQEAwPCyADQo6AgIBwNwLEAyADIAc2AsADIANCkYCAgJALNwO4AyADQoSAgIDQEzcDsAMgA0GIBWogBCADQbADahAsBEACQCADKAKUBSIFQQBIBEAgAygCyAUhBQwBCyADIAU2AsgFCyABIAMoArQFIAUQNAJAIAMoAqgFQS9GBEAgAEEvEBMgA0GwBWpBvwEQEAwBCyADQbAFaiICQQQQECACIAMoAqgFEB4LIANBsAVqIgJBlAEQECACIAdB/wFxEBAMDwsgA0KOgICAcDcCpAMgAyAHNgKgAyADQpGAgICQCzcDmAMgA0KBgICA0BM3A5ADIANBiAVqIAQgA0GQA2oQLARAAkAgAygClAUiBUEASARAIAMoAsgFIQUMAQsgAyAFNgLIBQsgASADKAK0BSAFEDQgA0GwBWoiAiADKAKgBRDHAyACQZQBEBAgAiAHQf8BcRAQDA8LIANCjoCAgHA3A4gDIAMgBzYChAMgA0HZADYCgAMgA0KdgYCAkAI3A/gCIANC2Lb5gnA3A/ACIANBiAVqIAQgA0HwAmoQLARAAkAgAygClAUiBUEASARAIAMoAsgFIQUMAQsgAyAFNgLIBQsgASADKAK0BSAFEDQgA0GwBWoiAiADKAKYBSADKAKcBRBoIAJBlAEQECACIAdB/wFxEBAMDwsgASADKAK0BSADKALIBRA0IANBsAVqQdgAIAcQaCAEIQIMEgsgCS8AASECIAEgAygCtAUgAygCyAUQNCADQbAFaiAFIAIQaCAEIQIMEQsgAyAJLwABIgI2ApQEIANBfzYCmAQgAyAFQQFrNgKQBCADQYgFaiAEIANBkARqECwEQAJAIAMoApQFIgRBAEgEQCADKALIBSEEDAELIAMgBDYCyAULIAEgAygCtAUgBBA0IANBsAVqIAVBAWogAhBoDA0LIAEgAygCtAUgAygCyAUQNCADQbAFaiAFIAIQaCAEIQIMEAsgASALIA4gBCADQcgFahCyAiEEDAYLIAEoAtQCIQ4gASgCzAIhB0EAIQhBACEPA0ACQCAIIA5IBEBBAyEFIAcoAgAiBEHpAGtBA08EQCAEQesBRw0CQQEhBQsCQCABKAKkAiAHKAIMQRRsaigCDCAHKAIIIgtrIgJBgH9IIAIgBUH/AGpKckUEQCAHQQE2AgQgBEHrAUYEQEHqASECIAdB6gE2AgAMAgsgByAEQf8AaiICNgIADAELIARB6wBHIAJBgIACakH//wNLcg0CIAdC64GAgCA3AgBBAiEFQesBIQILIAsgAygCsAVqQQFrIAI6AAAgBygCBCIEIAMoArAFIAtqaiICIAIgBWogAygCtAUgBSALaiAEamsQgQIgAyADKAK0BSAFazYCtAVBACEEIAEoAqwCIgJBACACQQBKGyEKIAEoAqQCIQIDQCAEIApGBEAgASgC1AIhDiAHIQYgCCEEA0ACQCAOIARBAWoiBEwEQEEAIQIgASgC4AIiBEEAIARBAEobIQoDQCACIApGDQIgCyABKALYAiACQQN0aiIGKAIAIgRJBEAgBiAEIAVrNgIACyACQQFqIQIMAAsACyAGIgJBEGohBiACKAIYIgogC0wNASACIAogBWs2AhgMAQsLIA9BAWohDwwDCyALIAIoAgwiBkgEQCACIAYgBWs2AgwLIAJBFGohAiAEQQFqIQQMAAsACwJAIA9FDQAgASgCzAIhAkEAIQUDQCAFIA5ODQEgASgCpAIgAigCDEEUbGooAgwgAigCCCIHayEEAkACQAJAAkAgAigCBEEBaw4EAAEDAgMLIAMoArAFIAdqIARB/wFxEOEEDAILIAMoArAFIAdqIARB//8DcRCGAwwBCyADKAKwBSAHaiAEEF0LIAJBEGohAiAFQQFqIQUgASgC1AIhDgwACwALIAAgASgCzAIQGiABQQA2AswCIAAgASgCpAIQGiABQQA2AqQCQQAhCkEAIQQCQCABLQBuQQJxDQAgASgC2AJFDQAgASgC8AIhBiABKAIAIAFB9AJqIgUQkQIDQCAKIAEoAuACTg0BAkAgASgC2AIgCkEDdGoiAigCBCIHQQBIIAYgB0ZyDQAgAigCACICIARrIghBAEgNAAJAIAcgBmsiBkEBaiIEQQRLIAhBMktyRQRAIAUgBCAIQQVsakEBakH/AXEQEAwBCyAFQQAQECAFIAgQkQUgBSAGQQF0IAZBH3VzEJEFCyACIQQgByEGCyAKQQFqIQoMAAsACyAAIAEoAtgCEBogAUEANgLYAiANEJcBIA0gAykDwAU3AhAgDSADKQO4BTcCCCANIAMpA7AFNwIAIAFBATYCoAJBACANKAIMRQ0SGiAAEMkBDBELIAdBEGohByAIQQFqIQgMAAsAC0HfFkG+4wBBrPcBQesjEAAACyADKAKUBSIEQQBOBEAgAyAENgLIBQsgAygCoAUhBSADKAKQBSEHIAMoApgFQekAayAIRg0BIAEgBUF/EHQaIAchAgwMCyAEIQcMCQsgA0F/NgKEBSADQYgFaiAHIAEgBSADQcwFaiADQYQFahDJAyIGEMgDBEAgASAGQX8QdBogByECDAsLIAMoAswFIghBKGsiBEEHS0EBIAR0QYMBcUVyRQRAIAEgBkF/EHQaIAEgAygCtAUgAygCyAUQNCADQbAFaiAIQf8BcRAQIAEgCyAOIAcgA0HIBWoQsgIhAgwLC0HrACEFDAgLAkAgBUGQAWtBAk8EQCAFQZcBRg0BIAVBtAFHBEAgBUHAAUcNAyADIAkoAAE2AsgFIAQhAgwMCyAJKAABIgJBAEgNAyACIAEoAqwCTg0DIA8gAkEUbGoiCCgCDEF/Rw0EIAggAygCtAU2AgwgCCgCECEGA0AgBiICBEAgCCgCDCACKAIEIgdrIQUgAigCACEGAkACQAJAAkAgAigCCEEBaw4EAgEDAAMLIAMoArAFIAdqIAUQXQwCCyAFQYCAAmpBgIAETw0JIAMoArAFIAdqIAVB//8DcRCGAwwBCyAFQYABakGAAk8NCSADKAKwBSAHaiAFQf8BcRDhBAsgACACEBoMAQsLIAhBADYCECAEIQIMCwsgA0KOgICAcDcD2AQgA0LZuP2CcDcD0AQgA0GIBWogBCADQdAEahAsBEAgAygClAUiAkEATgRAIAMgAjYCyAULIAMgAygCnAUiBjYCxAQgA0F/NgLIBCADIAMoApgFIgRBAWs2AsAEIANBiAVqIAMoApAFIgIgA0HABGoQLARAIAMoApQFIgJBAE4EQCADIAI2AsgFCyAEQQFqIQQgAygCkAUhAgsgASADKAK0BSADKALIBRA0IANBsAVqIgcgBUECa0H/AXEQECAHIAQgBhBoDAsLIANCjoCAgHA3A7gEIANCmICAgLDoDjcDsAQgA0GIBWogBCADQbAEahAsBEACQCADKAKUBSICQQBIBEAgAygCyAUhAgwBCyADIAI2AsgFCyABIAMoArQFIAIQNCADQbAFaiICIAVBAmtB/wFxEBAgAiADLQCYBRAQIAIgAygCqAUQHgwHCyADQo6AgIBwNwOoBCADQpmAgICQCTcDoAQgA0GIBWogBCADQaAEahAsRQ0BAkAgAygClAUiAkEASARAIAMoAsgFIQIMAQsgAyACNgLIBQsgASADKAK0BSACEDQgA0GwBWoiAiAFQQJrQf8BcRAQIAJByQAQEAwGCyADQX82AvgEIANChICAgLCV69SqfzcD8AQgA0GIBWogBCADQfAEahAsRQ0AIAMoApQFIgdBAE4EQCADIAc2AsgFCyADKAKYBSEGIAMoAqgFIgdBxQBGBH9B8gEFIAdBG0cNAUHzAQshByAGQX1xQakBRgRAIAEgAygCtAUgAygCyAUQNCADQbAFaiAHEBAgACADKAKoBRATDAYLIANC6YCAgHA3A+AEIANBiAVqIAMoApAFIANB4ARqECxFDQACQCADKAKUBSIFQQBIBEAgAygCyAUhBQwBCyADIAU2AsgFCyABIAMoArQFIAUQNCADQbAFaiAHEBAgACADKAKoBRATQeoAIQUMBgsgASADKAK0BSADKALIBRA0IANBsAVqIAkgChCKARogBCECDAgLQd8WQb7jAEHj9QFB6yMQAAALQbDyAEG+4wBB5fUBQesjEAAAC0GfxgBBvuMAQfD1AUHrIxAAAAtBisYAQb7jAEH09QFB6yMQAAALIAMoApAFIQIMAwsgAygCoAUhBiADKAKQBSEHCyABIAMoArQFIAMoAsgFEDQgBUHrAEciCkUEQCABIAsgDiAHIANByAVqELICIQcLIAZBAEgNBCAGIAEoAqwCTg0EIAEgASgC1AIiBEEBajYC1AIgASgCzAIgBEEEdGoiEUEENgIEIBEgBTYCACADKAK0BSEIIBEgBjYCDCARIAhBAWo2AggCQCAPIAZBFGxqIgkoAgwiBEF/RgRAIAkoAgggAkF/c2oiAkH/AEogBUHpAGtBAktyRQRAIBFBATYCBCARIAVB/wBqIgQ2AgAgA0GwBWoiAiAEQf8BcRAQIAJBABAQIAchAiAAIAkgAygCtAVBAWtBARDuAg0EDAMLIAJB//8BSiAKcg0BIBFBAjYCBCARQesBNgIAIANBsAVqIgJB6wEQECACQQAQMSAHIQIgACAJIAMoArQFQQJrQQIQ7gINAwwCCyAEIAhBf3NqIgZBgAFqQf8BSyAFQekAa0ECS3JFBEAgEUEBNgIEIBEgBUH/AGoiBDYCACADQbAFaiICIARB/wFxEBAgAiAGQf8BcRAQIAchAgwDCyAGQYCAAmpB//8DSyAKcg0AIBFBAjYCBCARQesBNgIAIANBsAVqIgJB6wEQECACIAZB//8DcRAxIAchAgwCCyADQbAFaiICIAVB/wFxEBAgAiAJKAIMIAMoArQFaxAeIAchAiAJKAIMQX9HDQEgACAJIAMoArQFQQRrQQQQ7gINAQsLIANBsAVqEJcBC0F/CyECIANB0AVqJAAgAgwBC0HfFkG+4wBB5fYBQesjEAAACw0BQQAhCiMAQSBrIgkkACABKAKAAiEPIAkgASgChAIiAjYCCCAJIAAgAkEBdBAvIgc2AhACQCAHRQRAQX8hBAwBC0EAIQQgAkEAIAJBAEobIQIDQCACIARHBEAgByAEQQF0akH//wM7AQAgBEEBaiEEDAELCyAJQQA2AhwgCUIANwIUIAlBADYCDAJ/AkAgACAJQQhqQQBBAEEAENIBDQADQAJAAkACQCAJKAIYIgJBAEoEQCAJIAJBAWsiAjYCGCAPIAkoAhQgAkECdGooAgAiDWoiDi0AACILQQxqQf8BcUENSQRAQfz4ACEGDAQLIA0gC0EPaiALIAtBsQFLGyIFQQJ0IgdBsJoBai0AAGoiCCAJKAIISgRAQZf4ACEGDAQLIAkoAhAgDUEBdGovAQAhBCAHQbGaAWotAAAhBgJAIAVBIWsiAkEQS0EBIAJ0Qb+ABHFFckUEQCAGIA4vAAFqIQYMAQsgBUH7AWtBA0sNACAGIAtqQewBayEGCyAEIAZIBEBB3fgAIQYMBAsCQCAHQbKaAWotAAAgBmsgBGoiBCAJKAIMTA0AIAkgBDYCDCAEQf7/A0wNAEG/+AAhBgwECwJAAkACQAJAAkACQAJAIAtB6QBrDg8CAgECAwsJCQkEBgQFBQUACyALQSNrIgJBDUsNB0EBIAJ0QeXwAHENCgwHCyANIA4oAAFqQQFqIQgMBwsgACAJQQhqIA0gDigAAWpBAWogCyAEENIBDQkMBgsgACAJQQhqIA0gDigAAWpBAWogCyAEQQFqENIBDQgMBQsgACAJQQhqIA0gDigABWpBBWogCyAEQQFqENIBDQcMBAsgACAJQQhqIA0gDigABWpBBWogCyAEQQJqENIBRQ0DDAYLIAAgCUEIaiANIA4oAAVqQQVqIAsgBEEBaxDSAQ0FDAILIAAgCSgCEBAaIAAgCSgCFBAaIAkoAgwhCkEADAULAkACQAJAIAtB6AFrDgQCAgEAAwsgDSAOLgABakEBaiEIDAILIA1BAWoiAiACIA9qLAAAaiEIDAELIAAgCUEIaiANQQFqIgIgAiAPaiwAAGogCyAEENIBDQMLIAAgCUEIaiAIIAsgBBDSAUUNAQwCCwsgCSANNgIEIAkgCzYCACAAIAYgCRBQCyAAIAkoAhAQGiAAIAkoAhQQGkF/CyEEIBQgCjYCDAsgCUEgaiQAIARBAEgNAUHAAEHYACABLQBuQQJxIgIbIgggASgCuAJBA3RqIQUgAAJ/IAIEQCAFIAEoAkRFDQEaCyABKAJ8IAEoAogBakEEdCAFagsiBiABKALAAkEDdGoiAiABKAKEAmoQbCIKRQ0BIApBATYCACAKIAIgCmoiBDYCFCAKIAEoAoQCIgI2AhggBCABKAKAAiACECUaIAAgASgCgAIQGiABQQA2AoACIAogASgCcDYCHCABKAJ8IgcgASgCiAEiBGpBAEwNBiABLQBuQQJxRQ0EIAEoAkQNBEEAIQIDQCACIAdOBEBBACECA0AgASgCiAEgAkwEQEEAIQIDQCACIAEoAsACTg0KIAAgAkEDdCIEIAEoAsgCaigCBBATIAEoAsgCIARqQQA2AgQgAkEBaiECDAALAAUgACABKAKAASACQQR0aigCABATIAJBAWohAgwBCwALAAUgACABKAJ0IAJBBHRqKAIAEBMgAkEBaiECIAEoAnwhBwwBCwALAAtB1fMAQb7jAEGD/gFBizYQAAALBSABKAJ0IAJBBHRqIgQgASgCzAEgBCgCBEEDdGoiBCgCBDYCCCAEIAI2AgQgAkEBaiECDAELCyAAIAEQjQNCgICAgOAADAMLIAogBSAKaiICNgIgIAIgASgCgAEgBEEEdBAlGiAKKAIgIAEoAogBQQR0aiABKAJ0IAEoAnxBBHQQJRoLIAogASgCfDsBKiAKIAEoAogBOwEoIAogASgCjAE7ASwgACABKAKAARAaIAAgASgCdBAaCyAKIAEoArgCIgQ2AjggBARAIAogCCAKaiICNgI0IAIgASgCtAIgBEEDdBAlGgsgACABKAK0AhAaIAFBADYCtAIgCiAUKAIMOwEuAkAgAS0AbkECcQRAIAAgASgC7AIQEyABQfQCahCXAQwBCyAKIAovABFBgAhyOwARIAogASgC7AI2AkAgCiABKALwAjYCRCAKIAAgASgC9AIgASgC+AIQmgIiAjYCUCACRQRAIAogASgC9AI2AlALIAogASgC+AI2AkwgCiABKAKMAzYCVCAKIAEoApADNgJICyABKALMASICIAFB0AFqRwRAIAAgAhAaCyAKIAEoAsACIgQ2AjwgBARAIAogBiAKaiICNgIkIAIgASgCyAIgBEEDdBAlGgsgACABKALIAhAaIAFBADYCyAIgCiAKLwARQX5xIAEvATRBAXFyIgI7ABEgCiABLwE4QQF0QQJxIAJBfXFyIgI7ABEgCiABLQBuOgAQIAogAS8BYEECdEEEcSACQXtxciICOwARIAogAkFPcSABLwFsQQR0QTBxciICOwARIAogASgCtAFBAEgE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DAELCyAGQQEgA2tBA3RqIgNBBGohByADKAIIIQQDQCAEIAdGDQEgBCgCBCEDIAQQRiAAKAIQIAQQvAIgAyEEDAALAAsgBUEwaiQAC8ECAgN+An8jAEEQayICJABCgICAgDAhBQJAAkAgACACQQhqIAAgARArIgEQQQ0AAkAgAikDCCIHQgBXBEAMAQsgB0IBfSEGAkACQAJAAkAgASACQQRqIAIQjgJFDQAgByACKAIAIgitUg0AIAGnIQkgAigCBCEDIARFDQEgAykDACEFIAMgA0EIaiAIQQN0QQhrEIECDAILAkAgBARAIAAgAUIAEGQiBRANDQYgACABQgBCASAGQQEQggNFDQEMBgsgACABIAYQZCIFEA0NBQsgACABIAYQlAJBAE4NAgwECyAIQQN0IANqQQhrKQMAIQULIAkgCSgCKEEBazYCKAsgB0KBgICACFQNACAGuRAXIQYLIAAgAUEwIAYQSEEATg0BCyAAIAUQDEKAgICA4AAhBQsgACABEAwgAkEQaiQAIAULEAAgACADKQMAQREgBBCBAwuuAgIFfgF/IwBBEGsiCiQAAn4CQCAAIApBCGogACABECsiBRBBDQAgCikDCCIBIAKsIgh8IgZCgICAgICAgBBZBEAgAEGqwwBBABAWDAELAkAgBEUgAkEATHJFBEAgACAFIAhCACABQX8QggMNAgwBCyABIQcLIAJBACACQQBKG60hCEIAIQEDQCABIAhSBEAgASAHfCEJIAGnIQIgAUIBfCEBIAAgBSAJIAMgAkEDdGopAwAQDxCRAUEATg0BDAILCyAAIAVBMCAGQoCAgIAIfCIHQv////8PWAR+IAZC/////w+DBSAGuRAXCxBIQQBIDQAgACAFEAwgBkL/////D4MgB0L/////D1gNARogBrkQFwwBCyAAIAUQDEKAgICA4AALIQEgCkEQaiQAIAELPAAgAUEAQdAAEEsiASAENgIMIAEgADYCACABIAIgA2o2AjwgASACNgI4IAFBATYCCCABQqCAgIAQNwMQC38BBH8gAS0AAEHbAEYEQCABQQFqIgMQQ0EBayECIAAoAhAoAjghBEHCASEBA0AgAUHPAUcEQAJAIAQgAUECdGooAgAiBSgCBEH/////B3EgAkcNACAFQRBqIAMgAhB3DQAgACABEBkPCyABQQFqIQEMAQsLEAEACyAAIAEQygELFwAgACAAKQPAASABIAIgA0EAQX8QtwULNQEBfyAAKALsASIHRQRAIABB2d0AQQAQFkKAgICA4AAPCyAAIAEgAiADIAQgBSAGIAcRNQALxgICAn4Cf0KAgICAMCECAkACQCABKQJUIgNCGIZCOIenDQAgA0IghkI4h6cEQCADQhCGQjiHp0UNASAAIAEpA2AQDxCUAUKAgICA4AAPCyABIANC/////49gg0KAgICAEIQ3AlQDQCABKAIUIARKBEAgASgCECAEQQN0aigCBCIFKQJUQhiGQjiHp0UEQCAAIAUQuAUiAhANDQQgACACEAwLIARBAWohBAwBCwsCQCABKAJQIgQEQEKAgICA4ABCgICAgDAgACABIAQRAgBBAEgbIQIMAQsgACABKQNIQoCAgIAwQQBBABA2IQIgAUKAgICAMDcDSAsgAhANBEAgAUEBOgBZIAEgACgCECkDgAEQDzcDYAsgASABKQJUQv///4eAYINCgICACIQ3AlQLIAIPCyABIAEpAlRC/////49ggzcCVCACC8AFAgd/AX4jAEEQayIFJAACQCABKQJUIglCKIZCOIenDQAgASAJQv//g3iDQoCABIQ3AlQDQAJAIAEoAhQgA0wEQEEAIQMDQCABKAIgIANKBEACQCABKAIcIgQgA0EUbGoiAigCCEEBRw0AIAIoAgwiB0H9AEYNACAAIAVBCGogBUEMaiABKAIQIAIoAgBBA3RqKAIEIAcQ7AMiAkUNACAAIAIgASAEIANBFGxqKAIQEOsDDAQLIANBAWohAwwBCwtBACECIAEoAlANAyABKAJIKAIkIQhBACEDQQAhBANAAkAgASgCOCAETARAA0AgAyABKAIgTg0CIAEoAhwgA0EUbGoiAigCCEUEQCAIIAIoAgBBAnRqKAIAIgQgBCgCAEEBajYCACACIAQ2AgQLIANBAWohAwwACwALIAEoAhAgASgCNCAEQQxsaiIHKAIIQQN0aigCBCECAkACQCAHKAIEIgZB/QBGBEAgACACEIkDIgkQDUUNAQwGCyAAIAVBCGogBUEMaiACIAYQ7AMiBgRAIAAgBiACIAcoAgQQ6wMMBgsCQCAFKAIMIgYoAgxB/QBGBEAgACAFKAIIKAIQIAYoAgBBA3RqKAIEEIkDIgkQDQ0HIABBARDmAyICRQRAIAAgCRAMDAgLIAAgAkEYaiAJEB8MAQsgBigCBCICRQRAIAUoAggoAkgoAiQgBigCAEECdGooAgAhAgsgAiACKAIAQQFqNgIACyAIIAcoAgBBAnRqIAI2AgAMAQsgACAIIAcoAgBBAnRqKAIAQRhqIAkQHwsgBEEBaiEEDAELC0F/IQIgACABKQNIQoGAgIAQQQBBABAkIgkQDQ0DIAAgCRAMQQAhAgwDCyADQQN0IQRBfyECIANBAWohAyAAIAQgASgCEGooAgQQuQVBAE4NAQwCCwtBfyECCyAFQRBqJAAgAgv/AgIGfwJ+AkAgASkCVEIwhkI4h6cNAAJAIAEoAlAEQANAIAIgASgCIE4NAiABKAIcIAJBFGxqIgMoAghFBEAgAEEAEOYDIgRFBEBBfw8LIAMgBDYCBAsgAkEBaiECDAALAAtBfyEEIAEpA0ghCEF/IQcgACAAKQMwQQ0QUyIJEA1FBEAgCaciAyAIpyICNgIgIAIgAigCAEEBajYCACADQgA3AiQCQAJAAkAgAigCPCIFRQ0AIAAgBUECdBBsIgVFDQEgAyAFNgIkQQAhAwNAIAMgAigCPE4NASACKAIkIANBA3RqLQAAIgZBAXEEQCAAIAZBA3ZBAXEQ5gMiBkUNAyAFIANBAnRqIAY2AgALIANBAWohAwwACwALIAEgCTcDSEEAIQcMAQsgCSEICyAAIAgQDAsgBw0BCyABQQE6AFVBACECA0AgASgCFCACTARAQQAPCyACQQN0IQNBfyEEIAJBAWohAiAAIAMgASgCEGooAgQQugVBAE4NAAsLIAQLiwEAAkACQAJAAkACQCABQiCIp0EDag4CAQACCyAAIAAgASADIAQQjQQgAkEAQQAQNg8LIAAgARAMAkAgACABpyIDELoFQQBIDQAgACADELkFQQBIDQAgACADELgFIgEQDUUNAwsgAEECEKYEDAELIAAgARAMIABBu94AQQAQFgtCgICAgOAAIQELIAELQAECfyAAQeQBaiECIABB4AFqIQMDQCADIAIoAgAiAEYEQEEADwsgAEEEaiECIABBBGsoAgAgAUcNAAsgAEEIawuoAwEEfyMAQRBrIgUkAAJ/IAAoAhAiBigCqAEiA0UEQAJ/IAItAABBLkcEQCAAIAIgAhBDEKMDDAELIAEQ/wUhAyAAIAIQQyADIAFrQQAgAxsiA2pBAmoQLyIEBH8gAyAEIAEgAxAlIgFqQQA6AAACQANAAkAgAi0AAEEuRw0AQQIhAwJAAkAgAi0AAUEuaw4CAAECCyACLQACQS9HDQEgAS0AAEUNAyABEP8FIgNBAWogASADGyIDQZL2ABCsBEUNASADQZH2ABCsBEUNASADIAEgA0lrQQA6AABBAyEDCyACIANqIQIMAQsLIAEtAABFDQAgARBDIAFqQS87AAALIAEQQyABaiACEIEGIAEFQQALCwwBCyAAIAEgAiAGKAKwASADEQoACyEDQQAhAgJAIANFDQACQCAAIAMQygEiBEUNACAAIAQQvAUiAQRAIAAgAxAaIAAgBBATIAEhAgwCCyAAIAQQEyAGKAKsASIBRQRAIAUgAzYCACAAQYb8ACAFENICDAELIAAgAyAGKAKwASABEQEAIQILIAAgAxAaCyAFQRBqJAAgAgtvAgN/AX4CQCAAKAIQKAKMASICRQ0AA0AgAUEASgRAIAFBAWshASACKAIAIgINAQwCCwsgAikDCCIEQoCAgIBwVA0AIASnIgEvAQYQ+AFFDQAgASgCICIBLQASQQRxRQ0AIAAgASgCQBAZIQMLIAMLUgEEfyAAKAIgIgJBACACQQBKGyEEQQAhAgNAAkAgAiAERwR/IAAoAhwiBSACQRRsaigCECABRw0BIAUgAkEUbGoFQQALDwsgAkEBaiECDAALAAvZAQEHf0F/IQIgASABQQFrcUUEQCAAIAFBAnQQnAIiBQR/IAFB/////wNqQf////8DcSEHIAAoAjQhBgNAIAMgACgCJE9FBEAgBiADQQJ0aigCACECA0AgAgRAIAAoAjggAkECdGooAgAiBCgCDCEIIAQgBSAHIAQoAghxQQJ0aiIEKAIANgIMIAQgAjYCACAIIQIMAQsLIANBAWohAwwBCwsgACAGECEgACABQQF0NgIwIAAgATYCJCAAIAU2AjRBAAVBfwsPC0Gq9QBBvuMAQYAUQarCABAAAAuCAQIEfwF+IAFBGGohBCABKAIcIQIDQCACIARGRQRAIAIoAgQhBSABQRBBFCACQQNrIgMtAABBAnEbaigCACACQQJrLwEAQQN0aikDABAPIQYgAiACQRBqNgIIIAIgBjcDECADIAMtAABBAXI6AAAgACACQQhrQQMQvgEgBSECDAELCwsrAQF/IAFBEGsiAyAAIAMpAwAgAUEIaykDABCYBSACR61CgICAgBCENwMAC5kEAgV/An4jAEEQayIFJAAgAUEIayIHKQMAIQggAUEQayIGKQMAIQkCfwJAAkACQAJAAkADQCAIEFYhAQJAA0BBASABRSAJEFYiBEEHRnEgASAERnIgBEUgAUEHRnEbBEAgACAJIAgQmAUhAwwGC0EBIQMgBEECRiABQQNGcSABQQJGIARBA0Zxcg0FAkACQAJAAkACQAJAAkACQCAEQXlGBEAgASIDQQFqDgkKAQUNDQ0NDQENCyABQXlHDQFBeSEDIARBAWoOCQYAAgwMDAwMAAwLIAAgBUEIaiAJEFsNDSAAIAUgCBBbDQ4gBSsDCCAFKwMAYSEDDAwLIARBAUcNAQsgCUL/////D4MhCQwFCyABQQFHDQELIAhC/////w+DIQgMBQsgBEF/Rw0BIAFBCGoiA0EPS0EBIAN0QYGCAnFFcg0FCyAAIAlBAhDDASIJEA1FDQEMBwsLIAEiA0F/Rw0DQX8hAyAEQQhqIgFBD0tBASABdEGDggJxRXINAwsgACAIQQIQwwEiCBANRQ0ACyAAIAkQDAwECyABIQMLAn8gCRCXBQRAQQEgA0F+cUECRg0BGgsgBEF+cUECRiAIEJcFQQBHcQshAyAAIAkQDCAAIAgQDAsgBiACIANHrUKAgICAEIQ3AwBBAAwCCyAAIAgQDAsgBkKAgICAMDcDACAHQoCAgIAwNwMAQX8LIQEgBUEQaiQAIAEL2wIBBX8jAEEQayIDJAAgACAAKQOAARAnIABBoAFqIQQgACgCpAEhAQNAIAEgBEZFBEAgASgCBCEFQQAhAgNAIAIgASgCEE5FBEAgACABIAJBA3RqKQMYECcgAkEBaiECDAELCyAAIAEQISAFIQEMAQsLIAQQcSAAEJwFIABB0ABqEOcDBEBBACECA0ACQCAAKAJEIQEgAiAAKAJATg0AIAEgAkEYbGoiASgCAARAIAAgASgCBBD0AQsgAkEBaiECDAELCyAAIAEQIUEAIQIDQAJAIAAoAjghASACIAAoAixODQAgASACQQJ0aigCACIBEOMDRQRAIAAgARAhCyACQQFqIQIMAQsLIAAgARAhIAAgACgCNBAhIAAgACgC1AEQISADIAApAhg3AwggAyAAKQIQNwMAIAMgACAAKAIEEQMAIANBEGokAA8LQan2AEG+4wBBvw9Bic0AEAAAC8wCAwJ+A38BfCMAQRBrIgQkACABQQhrIgYpAwAhAgJ/AkACQAJAAkAgAUEQayIFKQMAIgNCIIinIgFBACABQQdrQW1LG0UEQCACQiCIpyIBRSABQQdrQW5Jcg0BCyAAIANBAhDDASIDEA0NAiAAIAJBAhDDASICEA0EQCADIQIMAwsgA0KAgICAcINCgICAgJB/UiACQoCAgIBwg0KAgICAkH9ScQ0AIAUgACADIAIQyQIiAzcDACADEA0NAwwBCyAAIARBCGogAxBbDQEgACAEIAIQWw0CIAUCfiAEKwMIIAQrAwCgIge9An8gB5lEAAAAAAAA4EFjBEAgB6oMAQtBgICAgHgLIgC3vVEEQCAArQwBCyAHEBcLNwMAC0EADAILIAAgAhAMCyAFQoCAgIAwNwMAIAZCgICAgDA3AwBBfwshACAEQRBqJAAgAAuDAwEJfyMAQTBrIgckAAJAIAJCgICAgHBUDQBBEyEFAkAgAqciCi0ABUEEcUUNACAAKAIQKAJEIAovAQZBGGxqKAIUIghFDQBBA0ETIAgoAgQbIQULQX8hCSAAIAdBLGogB0EoaiAKIAUQkgENACADp0EAIANC/////29WGyEMIAVBEHEhDSAHKAIsIQggBygCKCELQQAhBQJAA0AgBSALRwRAAkACQCAMRQ0AIABBACAMIAggBUEDdGooAgQQTyIGRQ0AIAZBAE4NAQwECyANRQRAIAAgB0EIaiAKIAggBUEDdGooAgQQTyIGQQBIDQQgBkUNASAHKAIIIQYgACAHQQhqEE4gBkEEcUUNAQsgACACIAggBUEDdGoiBigCBCACQQAQFCIDEA0NAyAGKAIEIQYCfyAEBEAgACABIAYgAxBIDAELIAAgASAGIANBBxAbC0EASA0DCyAFQQFqIQUMAQsLIAAgCCALEGZBACEJDAELIAAgCCALEGYLIAdBMGokACAJC1MBAn8CQAJAIAEQIkUNACABEJ0FDQBBfyEDIAAgAhA4IgRFDQEgACAEEJsFIQIgACAEEBMgAhANDQEgACABQTYgAkEBEBtBAEgNAQtBACEDCyADCzIAAkAgAkUNACABECJFDQAgARCdBQ0AIAAgAUE2IAAgAhAyQQEQG0EATg0AQX8PC0EAC2gBAX8gACgCECECAkAgARBeRQRAIAIoAiwgAU0NASACKAI4IAFBAnRqKAIAIgGtQoCAgICQf4QQDxogACABQQQQ9QMPC0GO9wBBvuMAQc4XQYs8EAAAC0GXyABBvuMAQc8XQYs8EAAAC9YBAQR/IAAoAsgBIgYoAhAiBCAEKAIYIAFxQX9zQQJ0aigCACEFIAQQKiEEAkADQCAFRQ0BIAEgBCAFQQFrIgdBA3RqIgUoAgRHBEAgBSgCAEH///8fcSEFDAELCyAGKAIUIAdBA3RqIQQCQCADQQFGDQAgBCkDABCGAQRAIAAgAhAMIAAgBSgCBBDiAUF/DwsgBS0AA0EIcQ0AIAAgAhAMIABBgIABIAEQ4AEPCyAAIAQgAhAfQQAPCyAAIAApA8ABIAEgAkGAgAZBgIACIAAQ+wEbEJcCC30BAX8CQCACQoCAgIBwg0KAgICAkH9RIANCgICAgHCDQoCAgICQf1FxRQRAIABB1t4AQQAQFgwBCyAAIAFBEhBvIgEQDQ0AIAGnIgQgAj4CICAEIAM+AiQgACABQdUAQgBBAhAbGiABDwsgACADEAwgACACEAxCgICAgOAACw0AIAAgAUHq+gAQlQML0gEDAX4BfAF/A0ACQEF/IQUCQAJAAkAgAhBWDggAAAAAAgIDAQILIAJCIIZCIIchA0EAIQUMAgtBACEFIAIQSSIEvUL///////////8Ag0KAgICAgICA+P8AVg0BQoCAgICAgICAgH8hAyAERAAAAAAAAODDYw0BQv///////////wAhAyAERAAAAAAAAOBDZA0BIASZRAAAAAAAAOBDYwRAIASwIQMMAgtCgICAgICAgICAfyEDDAELIAAgAhCgASICEA1FDQELCyABIAM3AwAgBQu8AQICfwF8A0ACQEF/IQQCQAJAAkAgAhBWDggAAAAAAgIDAQILIAKnIQNBACEEDAILQQAhBCACEEkiBb1C////////////AINCgICAgICAgPj/AFYNAUGAgICAeCEDIAVEAAAAAAAA4MFjDQFB/////wchAyAFRAAAwP///99BZA0BIAWZRAAAAAAAAOBBYwRAIAWqIQMMAgtBgICAgHghAwwBCyAAIAIQoAEiAhANRQ0BCwsgASADNgIAIAQLbQACQAJAAkACQAJAIAJBBHZBA3FBAWsOAwABAgMLIAEoAgAiAgRAIAAgAq1CgICAgHCEECcLIAEoAgQiAUUNAyAAIAGtQoCAgIBwhBAnDwsgACABKAIAEPoBDwsgARDYBQ8LIAAgASkDABAnCwsLACAAIAEQDxCgAQuZAwEGfyADIAEoAgAiBSgCHEEDbEECbRBKIQYCQCACBEAgACACKAIUIAZBA3QQmgIiA0UNASACIAM2AhQLIAUoAhhBAWoiBCEDA0AgAyICQQF0IQMgAiAGSQ0ACwJAIAIgBEcEQCAAIAIgBhDlARAvIgNFDQIgAyACEL8CIQcgBUEIahBGIAcgBSAFKAIgQQN0QTBqECUiBEEIaiAAKAIQQdAAahBMIAQgAkEBayIJNgIYQQAhAyAEIAJBAnQiAmtBACACEEsaIARBMGohAgNAIAMgBCgCIE9FBEACQCACKAIEIghFBEAgA0EBaiEDDAELIAIgAigCAEGAgIBgcSAEIAggCXFBf3NBAnRqIggoAgBB////H3FyNgIAIAggA0EBaiIDNgIACyACQQhqIQIMAQsLIAAgBRDBAhAaDAELIAVBCGoiAhBGIAAgBRDBAiAEIAYQ5QEQmgIiA0UEQCACIAAoAhBB0ABqEEwMAgsgAyAEEL8CIgdBCGogACgCEEHQAGoQTAsgASAHNgIAIAcgBjYCHEEADwtBfwugAQEDfwJAIAAgASgCGEEBaiICIAEoAhwQ5QEiAxAvIgRFBEBBACECDAELIAQgARDBAiADECUgAhC/AiICQQE2AgAgACgCECACQQIQvgFBACEBIAJBADoAECACKAIsIgMEQCADrUKAgICAcIQQDxoLIAIQKiEDA0AgASACKAIgTw0BIAAgAygCBBAZGiADQQhqIQMgAUEBaiEBDAALAAsgAgtfAgF/AXwjAEEQayICJAACf0EAIAEQkAFFDQAaQX8gACACQQhqIAEQRw0AGiACKwMIIgO9QoCAgICAgID4/wCDQoCAgICAgID4/wBSIAOcIANhcQshACACQRBqJAAgAAu7AQEBfCABAn8CfwNAAkACQAJAIAIQVg4IAAAAAAICAgECC0EAIQBBAEH/ASACpxC0ARBKDAQLQQAiACACEEkiA71C////////////AINCgICAgICAgPj/AFYgA0QAAAAAAAAAAGNyDQIaQf8BIANEAAAAAADgb0BkDQMaAn8gA54iA5lEAAAAAAAA4EFjBEAgA6oMAQtBgICAgHgLDAMLIAAgAhCgASICEA1FDQALQX8LIQBBAAs2AgAgAAvBBAEIfyMAQRBrIgYkAAJ/QX8gACAGQQxqIAJBABDOAg0AGiABKAIQLQAzQQhxRQRAIAAgA0EwEOABDAELIAEtAAVBCHEEQCAGKAIMIgMgASgCKCIFSQRAIAMhBANAIAQgBUZFBEAgACABKAIkIARBA3RqKQMAEAwgBEEBaiEEDAELCyABIAM2AigLIANBAE4EfiADrQUgA7gQFwshAiABKAIUIAI3AwBBAQwBCyAAIAZBBGogASgCFCkDABDHARoCQCAGKAIEIgcgBigCDCIJSwRAIAEoAhAiCygCICIEIAcgCWtPBEADQCAJIAciBUkEQCAAIAEgACAFQQFrIgcQ5gUiChCUBCEEIAAgChATIAQNAQsLIAYgBTYCBAwCCyAJIQUgCxAqIgchCANAIAQgCkwEQCAGIAU2AgRBACEIA0AgBCAITA0EAkAgBygCBCIERQ0AIAAgBkEIaiAEELYBRQ0AIAYoAgggBUkNACAAIAEgBygCBBCUBBogASgCECILECogCEEDdGohBwsgB0EIaiEHIAhBAWohCCALKAIgIQQMAAsABQJAIAgoAgQiBEUNACAAIAZBCGogBBC2AUUNACAGKAIIIgQgBUkNACAFIARBAWogCC0AA0EEcRshBQsgCEEIaiEIIApBAWohCiALKAIgIQQMAQsACwALIAYgCTYCBCAJIQULIAAgASgCFCAFQQBOBH4gBa0FIAW4EBcLEB9BASAFIAlNDQAaIAAgA0HS0QAQeQshBCAGQRBqJAAgBAupBAEJfyMAQRBrIgIkACACQQA2AgwgAkIANwMAIAJBfzYCCAJAIAJB4AFBlIgBKAIAEQIAIgQEQCAEQQBB4AEQSyIAQZyIASkCADcCCCAAQZSIASkCADcCACAAKAIMRQRAIABBATYCDAsgACACKQMANwMQIAAgAikDCDcDGCAAQYCAEDYCbCAAQcgAahBxIABB0ABqEHEgAEHYAGoQcSAAQQA6AGggAEGgAWoQcSAAQQA2AjQgAEIANwIkIABBADYCPCAAQQA2AixBfyEGAkAgAEGAAhDABQ0AQZCLASEBQQEhAwNAIANBzwFGBEBBACEGDAILQQRBA0EBIANBwQFLGyADQcEBRhshCCAAIAEQQyIFQQAQ4QUiBwR/IAdBEGogASAFECUgBWpBADoAACAAIAcgCBDXAgVBAAtFDQEgA0EBaiEDIAEgBWpBAWohAQwACwALAkAgBg0AIABB4IMBQQFBKBCQBEEASA0AIAAoAkQiAUECNgL4AiABQQM2ArACIAFB+IcBNgKcAiABQdyHATYCjAEgAUHAhwE2AtQBIAFBBDYCkAMgAUEFNgLgAiAAQQA2AtABIABChICAgIACNwPIASAAIABBwAAQnAIiATYC1AFBAEF/IAEbDQAgAEGAgBA2AnAgAEEANgJ0IAAgACgCcCIBBH8gACgCdCABawVBAAs2AnggAEKAgICAIDcDgAEMAgsgABDEBQtBACEECyACQRBqJAAgBAuoAwIEfwJ+IAAoAhAhAiABEF4EQCABEHytDwsCQCABIAIoAixJBEACQCACKAI4IAFBAnRqKAIAIgUpAgQiBkKAgICAgICAgECDQoCAgICAgICAwABSDQAgBUEQaiEBIAanQf////8HcSEEAkACQAJAIAZCgICAgAiDUEUEQCAERQ0EIAEhAgJAIAEvAQAiA0EtRw0AIAFBAmohAiABLwECIgNBMEcNACAEQQJGDQILIAMQRQ0DIANByQBHIAEgBEEBdGogAmtBEEdyDQQgAkECakHIogFBDhB3RQ0DDAQLIARFDQMgASECIAEtAAAiA0EtRw0BIAFBAWohAiABLQABIgNBMEcgBEECR3INAQtEAAAAAAAAAIAQFw8LIAMQRQ0AIANByQBHIAEgBGogAmtBCEdyDQEgAkEBakHSC0EHEHcNAQsgACAFrUKAgICAkH+EENAFIgYQDQ0CIAAgBhAuIgcQDQRAIAAgBhAMIAcPCyAFIAenEJUCIQEgACAHEAwgAUUNAiAAIAYQDAtCgICAgDAPC0GtyABBvuMAQdkYQYryABAAAAsgBgsKACAAEJsEEK4DC/gBAQN/AkAgACACEDtFDQAgAqciBC8BBkEORgRAIAAgASAEKAIgKQMAENoFDwsgAUKAgICAcFQNAAJAIAAgAkE7IAJBABAUIgJC/////29YBEBBfyEDIAIQDQ0BIABBuhxBABAWDAELIAGnIQMgAqchBQJAA0ACQCADKAIQKAIsIgRFBEAgAy8BBkEpRw0DIAOtQoCAgIBwhBAPIQEDQEF/IQMgACABEJkCIgEQDQ0FIAEQKA0EIAGnIAVGBEAgACABEAwMAwsgABCCAUUNAAsgACABEAwMBAsgBCIDIAVHDQELC0EBIQMMAQtBACEDCyAAIAIQDAsgAwuHAQIBfwF+IwBBEGsiAyQAIAMgATcDCAJ/AkAgAhAiBEBBfyAAIAJBywEgAkEAEBQiBBANDQIaAkAgBBAoDQAgBBASDQAgACAAIAQgAkEBIANBCGoQNhAtDAMLIAAgAhA7DQELIABBx9sAQQAQFkF/DAELIAAgASACENkFCyEAIANBEGokACAAC3QCAX4BfyMAQYACayIGJAAgBkGAAiACIAMQ2QIaAkAgACAAIAFBA3RqKQNYQQMQUyIFEA0EQEKAgICAICEFDAELIAAgBUEzIAAgBhB2QQMQGxoLIAQEQCAAIAVBAEEAQQAQxwILIAAgBRCUASAGQYACaiQAC58DAgR/AX4jAEEQayIGJAACQAJAAkACQCACEF4EQCAGIAIQfDYCACABQcAAQfMQIAYQVxoMAQsgACgCLCACTQ0CIAJFBEAgAUHw7wAoAAA2AAMgAUHt7wAoAAA2AAAMAQsgACgCOCACQQJ0aigCACIEEOMDDQMgASECAkAgBEUNACAEKQIEIgdCgICAgAiDUARAIARBEGohAyAHp0H/////B3EhBUEAIQJBACEAA0AgAiAFRkUEQCAAIAIgA2otAAByIQAgAkEBaiECDAELCyAAQYABSA0DCyAEQRBqIQVBACEAIAEhAgNAIAAgB6dB/////wdxTw0BAn8gB0KAgICACINQRQRAIAUgAEEBdGovAQAMAQsgACAFai0AAAshAyACIAFrQTlKDQECfyADQf8ATQRAIAIgAzoAACACQQFqDAELIAIgAxDmAiACagshAiAAQQFqIQAgBCkCBCEHDAALAAsgAkEAOgAACyABIQMLIAZBEGokACADDwtBrcgAQb7jAEHfF0GH6AAQAAALQav3AEG+4wBB6RdBh+gAEAAACxwAIAAQIkUEQEEADwsgAKctAAVBAXZBf3NBAXELswUBBH8CQAJAAkAgAS0ABEEPcQ4CAgABCyAAIAEoAhQgASgCGEEBEKMFAkAgASgCIEUNAANAIAIgAS8BKiABLwEoak8NASAAIAEoAiAgAkEEdGooAgAQ9AEgAkEBaiECDAALAAtBACECA0AgASgCOCACTARAAkBBACECA0AgASgCPCACSgRAIAAgASgCJCACQQN0aigCBBD0ASACQQFqIQIMAQsLIAEoAjAiAgRAIAIQrgMLIAAgASgCHBD0ASABLQASQQRxBEAgACABKAJAEPQBIAAgASgCUBAhIAAgASgCVBAhCyABEJ8CAkAgAC0AaEECRw0AIAEoAgBFDQAgAUEIaiAAQdgAahBMDAELIAAgARAhCwUgACABKAI0IAJBA3RqKQMAECcgAkEBaiECDAELCw8LEAEACyABIAEtAAVBAnI6AAUgASgCECIEECohAwNAIAEoAhQhBSAEKAIgIAJKBEAgACAFIAJBA3RqIAMoAgBBGnYQzwUgAkEBaiECIANBCGohAwwBCwsgACAFECEgACAEEJ4CIAFCADcDECABKAIYBEACQCABQRhqIQICQAJAA0AgAigCACICBEAgAigCCCgCAEUNAiACKAIEDQMgAkEYahBGIAJBEGoQRiACQQxqIQIMAQsLIAEoAhghAgNAIAIEQCACKAIMIQMgACACKQMoECcgACACECEgAyECDAELCyABQQA2AhgMAgtBz8AAQb7jAEHu5QJB8MYAEAAAC0G9C0G+4wBB7+UCQfDGABAAAAsLIAAoAkQgAS8BBkEYbGooAggiAgRAIAAgAa1CgICAgHCEIAIRCwALIAFCADcDICABQQA7AQYgAUEANgIoIAEQnwICQAJAIAAtAGhBAkcNACABKAIARQ0AIAFBCGogAEHYAGoQTAwBCyAAIAEQIQsLCQBBASAAEMACC4gDAQJ/IAAoAhAiAygCbCADKAIUQTBqSQRAIAMQnAUgAyADKAIUIgNBAXYgA2o2AmwLAkAgAEEwEC8iAwRAIANBADYCICADQQA2AhggA0EBOgAFIAMgAjsBBiADIAE2AhAgAyAAIAEoAhxBA3QQLyIENgIUIAQNASAAIAMQGgsgACgCECABEJ4CQoCAgIDgAA8LAkACQAJAAkACQAJAAkACQCACQQFrDh4HAAYEBAQEAgYEBgEGBgYGBgUGBgICAgICAgICAgMGCyADQQA2AiggA0IANwMgIAMgAy0ABUEMcjoABSABIAAoAiRHBH8gACADQTBBChCDAQUgBAtCADcDAAwGCyAEQoCAgIAwNwMADAULIANCADcCJCADIAMtAAVBDHI6AAUMBAsgA0IANwIkDAMLIANCgICAgDA3AyAMAQsgA0IANwMgCyAAKAIQKAJEIAJBGGxqKAIURQ0AIAMgAy0ABUEEcjoABQsgA0EBNgIAIAAoAhAgA0EAEL4BIAOtQoCAgIBwhAs8ACAAIAEgAnQgAmtBEWoQ6AEiAARAIABBADYCDCAAQQE2AgAgACABQf////8HcSACQR90cq03AgQLIAAL2QECAX8BfiMAQdAAayIDJAACQAJ+IAEQXgRAIAMgARB8NgIAIANBEGoiAUHAAEHzECADEFcaIAAgARB2DAELIAAoAhAiACgCLCABTQ0BAkACQCAAKAI4IgAgAUECdGooAgAiASkCBCIEQoCAgICAgICAQINCgICAgICAgIDAAFENACACRQ0BIASnQYCAgIB4Rw0AIAAoArwBIQELIAGtQoCAgICQf4QQDwwBCyABrUKAgICAgH+EEA8LIQQgA0HQAGokACAEDwtBrcgAQb7jAEGYGEHsyQAQAAALCgAgAEEBdEEBcgupAQICfwF+IAEpAgRCgICAgAiDIQUgAC0AB0GAAXFFBEAgBVAEQCAAQRBqIAFBEGogAhB3DwtBACABQRBqIABBEGogAhCkBWsPCyABQRBqIQEgAEEQaiEAIAVQBEAgACABIAIQpAUPCwJ/IAJBACACQQBKGyEEA0BBACADIARGDQEaIANBAXQhAiADQQFqIQMgACACai8BACABIAJqLwEAayICRQ0ACyACCwtgAgJ/AX4gAEEQaiEDIAApAgQiBKdB/////wdxIQAgBEKAgICACINQRQRAA0AgACACRwRAIAMgAkEBdGovAQAgAUGHAmxqIQEgAkEBaiECDAELCyABDwsgAyAAIAEQ6AULXwICfwF+IwBBEGsiAiQAAkAgAUEATgRAIAEQlQEhAwwBCyACIAE2AgAgAkEFaiIBQQtB8xAgAhBXGiAAIAEQdiIEEA0NACAAKAIQIASnQQEQ1wIhAwsgAkEQaiQAIAML1QECBX8BfgJAIAEpAgQiB6dB/////wdxIgRBC2tBdkkNAAJ/IAdCgICAgAiDUCIGRQRAIAEvARAMAQsgAS0AEAsiAhBFRQ0AAn8CQCACQTBGBEBBACAEQQFHDQIaDAELIAFBEGohBSACQTBrIQNBASEBA0AgASAERg0BAn8gBkUEQCAFIAFBAXRqLwEADAELIAEgBWotAAALIgIQRUUNAyACQTBrrCADrUIKfnwiB6chAyABQQFqIQEgB0KAgICAEFQNAAsMAgsgACADNgIAQQELDwtBAAssAQF/A0AgASADRkUEQCAAIANqLQAAIAJBhwJsaiECIANBAWohAwwBCwsgAguNAgECfyAAIAEoAgQQEwNAIAEoAhAhAyACIAEoAhRORQRAIAAgAyACQQN0aigCABATIAJBAWohAgwBCwsgACADEBpBACECA0ACQCABKAIcIQMgAiABKAIgTg0AIAMgAkEUbGoiAygCCEUEQCAAKAIQIAMoAgQQ+gELIAAgAygCEBATIAAgAygCDBATIAJBAWohAgwBCwsgACADEBogACABKAIoEBpBACECA0AgASgCNCEDIAIgASgCOE5FBEAgACADIAJBDGxqKAIEEBMgAkEBaiECDAELCyAAIAMQGiAAIAEpA0AQDCAAIAEpA0gQDCAAIAEpA2AQDCAAIAEpA2gQDCABQQhqEEYgACABEBoLqgICAX8DfiMAQSBrIgIkAEKAgICA4AAhBgJAIAAgAykDACIFEGkNACAAIAFBKhBvIgEQDQ0AIAACfgJAIABBIBBsIgRFDQBBACEDIARBADYCFCAEQQA2AgADQCADQQJGRQRAIAQgA0EDdGpBBGoQcSADQQFqIQMMAQsLIARCgICAgDA3AxggASAEEI0BIAAgAkEQaiABEKwFDQACQCAAIAVCgICAgDBBAiACQRBqECQiBxANBEAgAiAAEJMBNwMIIAAgAikDGEKAgICAMEEBIAJBCGoQJCEFIAAgAikDCBAMIAUQDQ0BIAAgBRAMCyAAIAcQDCAAIAIpAxAQDCABIQYgAikDGAwCCyAAIAIpAxAQDCAAIAIpAxgQDAsgAQsQDAsgAkEgaiQAIAYLOAEBfyAAQTBrIgRBCk8EfyAAQcEAayADTQRAIABBN2sPCyAAQdcAayACIABB4QBrIAFJGwUgBAsLuAkCBX4EfyMAQRBrIgIkACAEQeWKAWotAAAiC60hBQJAAkAgAykDACIGQv////9vWARAQoCAgIDgACEHIAAgAkEIaiAGEMQBDQIgAEKAgICAMCACKQMIIgggBYYQjAMiBRANDQJCACEGIAJCADcDAAwBCwJAAkAgBqciCi8BBiIMQRNrQf//A3FBAU0EQCAKKAIgIQpCgICAgOAAIQcgACACIAMpAwgQxAENBCAKLQAEDQICQCACKQMAIgZBfyALdEF/cyILrINQBEAgBiAKKAIAIgysIghYDQELIABB7BkQawwFCwJAIAMpAxAiCRASBEAgCyAMcQ0BIAIgCCAGfSAFiCIINwMIDAMLIAAgAkEIaiAJEMQBDQUgCi0ABA0DIAo0AgAgAikDCCIIIAWGIAZ8Wg0CCyAAQfjBABBrDAQLIAxBFWtB//8DcUEITQRAAn4CQAJAIAAgASAEEG8iARANDQACQAJAIAanIgMQmgFFBEAgAygCKCEKQoCAgIAwIQUgAygCICIMKAIMIgsoAiAiDS0ABUUEQCAAIAutQoCAgIBwhEKAgICAMBDzASIFEA0NAwsgACAFIAqtIgggBEHligFqMQAAhhCMAyEHIAAgBRAMIAcQDQ0CIAMQmgFFDQEgACAHEAwLIAAQdQwBCyAHQRMQQCELIAAgASAHQgAgCBDzAw0AIAMvAQYgBEYNAkEAIQMDQCADIApGDQIgACAGIAMQeyIFEA0NASAAIAEgAyAFEJYCIQQgA0EBaiEDIARBAE4NAAsLIAAgARAMQoCAgIDgACEBCyABDAELIAsoAgggDSgCCCAMKAIQaiALKAIAECUaIAELIQcMBAsjAEEQayIDJABCgICAgOAAIQUgACABIAQQbyIHEA1FBEBCgICAgDAhAQJ+AkAgACAGQcMBIAZBABAUIgUQDQ0AAkACQCAFEBINACAFECgNAEEAIQojAEEQayILJAAgA0EANgIEAkAgABBRIggQDQ0AQoCAgIAwIQkCQCAAIAYgBRDoAyIBEA0NACAAIAFB6gAgAUEAEBQiCRANDQADQCAAIAEgCSALQQxqEK8BIgYQDQ0BIAsoAgwEQCAAIAYQDCAAIAkQDCAAIAEQDCADIAo2AgQMAwsgACAIIAqtIAZBgIABEK4BQQBIDQEgCkEBaiEKDAALAAsgACAJEAwgACABEAwgACAIEAxCgICAgOAAIQgLIAtBEGokACAIIQEgACAFEAwgARANDQIgAyADNQIEIgU3AwgMAQsgACADQQhqIAYQQQ0BIAYQDyEBIAMpAwghBQsgAEKAgICAMCAFIARB5YoBajEAAIYQjAMiBhANDQAgACAHIAZCACAFEPMDDQBBACEEA0AgByAErSAFWQ0CGiAAIAEgBBB7IgYQDQ0BIAAgByAEIAYQlgIhCiAEQQFqIQQgCkEATg0ACwsgACABEAwgByEBQoCAgIDgAAshBSAAIAEQDAsgA0EQaiQAIAUhBwwDCyADKQMAEA8hBQwBCyAAEHUMAQsCQCAAIAEgBBBvIgcQDQRAIAAgBRAMDAELIAAgByAFIAYgCBDzA0UNASAAIAcQDAtCgICAgOAAIQcLIAJBEGokACAHC9IDAgJ+An8jAEEgayIEJAACQCABQv///////////wCDIgNCgICAgICAwIA8fSADQoCAgICAgMD/wwB9VARAIAFCBIYgAEI8iIQhAyAAQv//////////D4MiAEKBgICAgICAgAhaBEAgA0KBgICAgICAgMAAfCECDAILIANCgICAgICAgIBAfSECIABCgICAgICAgIAIUg0BIAIgA0IBg3whAgwBCyAAUCADQoCAgICAgMD//wBUIANCgICAgICAwP//AFEbRQRAIAFCBIYgAEI8iIRC/////////wODQoCAgICAgID8/wCEIQIMAQtCgICAgICAgPj/ACECIANC////////v//DAFYNAEIAIQIgA0IwiKciBUGR9wBJDQAgBEEQaiAAIAFC////////P4NCgICAgICAwACEIgIgBUGB9wBrEHMgBCAAIAJBgfgAIAVrEKECIAQpAwhCBIYgBCkDACIAQjyIhCECIAQpAxAgBCkDGIRCAFKtIABC//////////8Pg4QiAEKBgICAgICAgAhaBEAgAkIBfCECDAELIABCgICAgICAgIAIUg0AIAJCAYMgAnwhAgsgBEEgaiQAIAIgAUKAgICAgICAgIB/g4S/Cw8AIAAgASACQQBBAxCCAguiDwIFfw5+IwBB0AJrIgUkACAEQv///////z+DIQogAkL///////8/gyEMIAIgBIVCgICAgICAgICAf4MhDSAEQjCIp0H//wFxIQgCQAJAIAJCMIinQf//AXEiCUH//wFrQYKAfk8EQCAIQf//AWtBgYB+Sw0BCyABUCACQv///////////wCDIg9CgICAgICAwP//AFQgD0KAgICAgIDA//8AURtFBEAgAkKAgICAgIAghCENDAILIANQIARC////////////AIMiAkKAgICAgIDA//8AVCACQoCAgICAgMD//wBRG0UEQCAEQoCAgICAgCCEIQ0gAyEBDAILIAEgD0KAgICAgIDA//8AhYRQBEAgAyACQoCAgICAgMD//wCFhFAEQEIAIQFCgICAgICA4P//ACENDAMLIA1CgICAgICAwP//AIQhDUIAIQEMAgsgAyACQoCAgICAgMD//wCFhFAEQEIAIQEMAgsgASAPhFAEQEKAgICAgIDg//8AIA0gAiADhFAbIQ1CACEBDAILIAIgA4RQBEAgDUKAgICAgIDA//8AhCENQgAhAQwCCyAPQv///////z9YBEAgBUHAAmogASAMIAEgDCAMUCIGG3kgBkEGdK18pyIGQQ9rEHNBECAGayEGIAUpA8gCIQwgBSkDwAIhAQsgAkL///////8/Vg0AIAVBsAJqIAMgCiADIAogClAiBxt5IAdBBnStfKciB0EPaxBzIAYgB2pBEGshBiAFKQO4AiEKIAUpA7ACIQMLIAVBoAJqIApCgICAgICAwACEIhJCD4YgA0IxiIQiAkIAQoCAgICw5ryC9QAgAn0iBEIAEHIgBUGQAmpCACAFKQOoAn1CACAEQgAQciAFQYACaiAFKQOYAkIBhiAFKQOQAkI/iIQiBEIAIAJCABByIAVB8AFqIARCAEIAIAUpA4gCfUIAEHIgBUHgAWogBSkD+AFCAYYgBSkD8AFCP4iEIgRCACACQgAQciAFQdABaiAEQgBCACAFKQPoAX1CABByIAVBwAFqIAUpA9gBQgGGIAUpA9ABQj+IhCIEQgAgAkIAEHIgBUGwAWogBEIAQgAgBSkDyAF9QgAQciAFQaABaiACQgAgBSkDuAFCAYYgBSkDsAFCP4iEQgF9IgJCABByIAVBkAFqIANCD4ZCACACQgAQciAFQfAAaiACQgBCACAFKQOoASAFKQOgASIPIAUpA5gBfCIEIA9UrXwgBEIBVq18fUIAEHIgBUGAAWpCASAEfUIAIAJCABByIAYgCSAIa2ohBgJ/IAUpA3AiEEIBhiIUIAUpA4gBIg5CAYYgBSkDgAFCP4iEfCILQufsAH0iFUIgiCICIAxCgICAgICAwACEIhZCAYYgAUI/iIQiDEIgiCIEfiIRIAFCAYYiD0IgiCIKIAsgFVatIAsgFFStIAUpA3hCAYYgEEI/iIQgDkI/iHx8fEIBfSIQQiCIIgt+fCIOIBFUrSAOIA4gEEL/////D4MiECAMQv////8PgyIUfnwiDlatfCAEIAt+fCAEIBB+IhMgCyAUfnwiESATVK1CIIYgEUIgiIR8IA4gDiARQiCGfCIOVq18IA4gDiAVQv////8PgyIVIBR+IhMgAiAKfnwiESATVK0gESARIBAgD0L+////D4MiE358IhFWrXx8Ig5WrXwgDiAEIBV+IhcgCyATfnwiBCACIBR+fCILIAogEH58IhBCIIggCyAQVq0gBCAXVK0gBCALVq18fEIghoR8IgQgDlStfCAEIBEgAiATfiICIAogFX58IgpCIIggAiAKVq1CIIaEfCICIBFUrSACIBBCIIZ8IAJUrXx8IgIgBFStfCIEQv////////8AWARAIAVB0ABqIAIgBCADIBIQciABQjGGIAUpA1h9IAUpA1AiAUIAUq19IQtCACABfSEKIAZB/v8AagwBCyAFQeAAaiAEQj+GIAJCAYiEIgIgBEIBiCIEIAMgEhByIAFCMIYgBSkDaH0gBSkDYCIMQgBSrX0hC0IAIAx9IQogASEPIBYhDCAGQf//AGoLIgZB//8BTgRAIA1CgICAgICAwP//AIQhDUIAIQEMAQsCfiAGQQBKBEAgC0IBhiAKQj+IhCELIARC////////P4MgBq1CMIaEIQwgCkIBhgwBCyAGQY9/TARAQgAhAQwCCyAFQUBrIAIgBEEBIAZrEKECIAVBMGogDyAMIAZB8ABqEHMgBUEgaiADIBIgBSkDQCICIAUpA0giDBByIAUpAzggBSkDKEIBhiAFKQMgIgFCP4iEfSAFKQMwIgQgAUIBhiIBVK19IQsgBCABfQshBCAFQRBqIAMgEkIDQgAQciAFIAMgEkIFQgAQciAMIAIgAiADIAJCAYMiASAEfCIDVCALIAEgA1atfCIBIBJWIAEgElEbrXwiAlatfCIEIAIgAiAEQoCAgICAgMD//wBUIAMgBSkDEFYgASAFKQMYIgRWIAEgBFEbca18IgJWrXwiBCACIARCgICAgICAwP//AFQgAyAFKQMAViABIAUpAwgiA1YgASADURtxrXwiASACVK18IA2EIQ0LIAAgATcDACAAIA03AwggBUHQAmokAAvEAQIBfwJ+QX8hAwJAIABCAFIgAUL///////////8AgyIEQoCAgICAgMD//wBWIARCgICAgICAwP//AFEbDQBBACACQv///////////wCDIgVCgICAgICAwP//AFYgBUKAgICAgIDA//8AURsNACAAIAQgBYSEUARAQQAPCyABIAKDQgBZBEBBACABIAJTIAEgAlEbDQEgACABIAKFhEIAUg8LIABCAFIgASAC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QT9xQYABcjoAASAAIAFBBnZBwAFyOgAAQQIMBAsgAUGAQHFBgMADRyABQYCwA09xRQRAIAAgAUE/cUGAAXI6AAIgACABQQx2QeABcjoAACAAIAFBBnZBP3FBgAFyOgABQQMMBAsgAUGAgARrQf//P00EQCAAIAFBP3FBgAFyOgADIAAgAUESdkHwAXI6AAAgACABQQZ2QT9xQYABcjoAAiAAIAFBDHZBP3FBgAFyOgABQQQMBAsLQcSzBEEZNgIAQX8FQQELDAELIAAgAToAAEEBCwsWACAARQRAQQAPC0HEswQgADYCAEF/C8QCAAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAFBCWsOEgAKCwwKCwIDBAUMCwwMCgsHCAkLIAIgAigCACIBQQRqNgIAIAAgASgCADYCAA8LAAsgAiACKAIAIgFBBGo2AgAgACABMgEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMwEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMAAANwMADwsgAiACKAIAIgFBBGo2AgAgACABMQAANwMADwsACyACIAIoAgBBB2pBeHEiAUEIajYCACAAIAErAwA5AwAPCyAAIAIgAxEDAAsPCyACIAIoAgAiAUEEajYCACAAIAE0AgA3AwAPCyACIAIoAgAiAUEEajYCACAAIAE1AgA3AwAPCyACIAIoAgBBB2pBeHEiAUEIajYCACAAIAEpAwA3AwALawEEfyAAKAIALAAAEEVFBEBBAA8LA0AgACgCACEDQX8hASACQcyZs+YATQRAQX8gAywAAEEwayIEIAJBCmwiAWogBEH/////ByABa0obIQELIAAgA0EBajYCACABIQIgAywAARBFDQALIAIL8RICEX8BfiMAQdAAayIHJAAgByABNgJMIAdBN2ohFiAHQThqIRJBACEBAkACQAJAAkADQCABQf////8HIA1rSg0BIAEgDWohDSAHKAJMIgwhAQJAAkACQCAMLQAAIgsEQANAAkACQCALQf8BcSIIRQRAIAEhCwwBCyAIQSVHDQEgASELA0AgAS0AAUElRw0BIAcgAUECaiIINgJMIAtBAWohCyABLQACIQogCCEBIApBJUYNAAsLIAsgDGsiAUH/////ByANayIXSg0HIAAEQCAAIAwgARBnCyABDQZBfyEQQQEhCCAHKAJMLAABEEUhASAHKAJMIQoCQCABRQ0AIAotAAJBJEcNACAKLAABQTBrIRBBASEUQQMhCAsgByAIIApqIgE2AkxBACEOAkAgASwAACITQSBrIgpBH0sEQCABIQgMAQsgASEIQQEgCnQiCUGJ0QRxRQ0AA0AgByABQQFqIgg2AkwgCSAOciEOIAEsAAEiE0EgayIKQSBPDQEgCCEBQQEgCnQiCUGJ0QRxDQALCwJAIBNBKkYEQCAHAn8CQCAILAABEEVFDQAgBygCTCIBLQACQSRHDQAgASwAAUECdCAEakHAAWtBCjYCACABLAABQQN0IANqQYADaygCACEPQQEhFCABQQNqDAELIBQNBkEAIRRBACEPIAAEQCACIAIoAgAiAUEEajYCACABKAIAIQ8LIAcoAkxBAWoLIgE2AkwgD0EATg0BQQAgD2shDyAOQYDAAHIhDgwBCyAHQcwAahD2BSIPQQBIDQggBygCTCEBC0EAIQhBfyEJAn9BACABLQAAQS5HDQAaIAEtAAFBKkYEQCAHAn8CQCABLAACEEVFDQAgBygCTCIBLQADQSRHDQAgASwAAkECdCAEakHAAWtBCjYCACABLAACQQN0IANqQYADaygCACEJIAFBBGoMAQsgFA0GIAAEfyACIAIoAgAiAUEEajYCACABKAIABUEACyEJIAcoAkxBAmoLIgE2AkwgCUF/c0EfdgwBCyAHIAFBAWo2AkwgB0HMAGoQ9gUhCSAHKAJMIQFBAQshFQNAIAghEUEcIQsgASwAAEH7AGtBRkkNCSAHIAFBAWoiEzYCTCABLAAAIQggEyEBIAggEUE6bGpB36wEai0AACIIQQFrQQhJDQALAkACQCAIQRtHBEAgCEUNCyAQQQBOBEAgBCAQQQJ0aiAINgIAIAcgAyAQQQN0aikDADcDQAwCCyAARQ0IIAdBQGsgCCACIAYQ9QUgBygCTCETDAILIBBBAE4NCgtBACEBIABFDQcLIA5B//97cSIKIA4gDkGAwABxGyEIQQAhDkHrDyEQIBIhCwJAAkACQAJ/AkACQAJAAkACfwJAAkACQAJAAkACQAJAIBNBAWssAAAiAUFfcSABIAFBD3FBA0YbIAEgERsiAUHYAGsOIQQUFBQUFBQUFA4UDwYODg4UBhQUFBQCBQMUFAkUARQUBAALAkAgAUHBAGsOBw4UCxQODg4ACyABQdMARg0JDBMLIAcpA0AhGEHrDwwFC0EAIQECQAJAAkACQAJAAkACQCARQf8BcQ4IAAECAwQaBQYaCyAHKAJAIA02AgAMGQsgBygCQCANNgIADBgLIAcoAkAgDaw3AwAMFwsgBygCQCANOwEADBYLIAcoAkAgDToAAAwVCyAHKAJAIA02AgAMFAsgBygCQCANrDcDAAwTCyAJQQggCUEISxshCSAIQQhyIQhB+AAhAQsgEiEKIAFBIHEhESAHKQNAIhhQRQRAA0AgCkEBayIKIBinQQ9xQfCwBGotAAAgEXI6AAAgGEIPViEMIBhCBIghGCAMDQALCyAKIQwgCEEIcUUgBykDQFByDQMgAUEEdkHrD2ohEEECIQ4MAwsgEiEBIAcpA0AiGFBFBEADQCABQQFrIgEgGKdBB3FBMHI6AAAgGEIHViEKIBhCA4ghGCAKDQALCyABIQwgCEEIcUUNAiAJIBIgDGsiAUEBaiABIAlIGyEJDAILIAcpA0AiGEIAUwRAIAdCACAYfSIYNwNAQQEhDkHrDwwBCyAIQYAQcQRAQQEhDkHsDwwBC0HtD0HrDyAIQQFxIg4bCyEQIBggEhCkAiEMCyAVQQAgCUEASBsNDiAIQf//e3EgCCAVGyEIIAcpA0AiGEIAUiAJckUEQCASIgwhC0EAIQkMDAsgCSAYUCASIAxraiIBIAEgCUgbIQkMCwsgBygCQCIBQbz3ACABGyIMIAlB/////wcgCUH/////B0kbEIAGIgEgDGohCyAJQQBOBEAgCiEIIAEhCQwLCyAKIQggASEJIAstAAANDQwKCyAJBEAgBygCQAwCC0EAIQEgAEEgIA9BACAIEG0MAgsgB0EANgIMIAcgBykDQD4CCCAHIAdBCGoiATYCQEF/IQkgAQshC0EAIQECQANAIAsoAgAiCkUNASAHQQRqIAoQ8wUiDEEASCIKIAwgCSABa0tyRQRAIAtBBGohCyAJIAEgDGoiAUsNAQwCCwsgCg0NC0E9IQsgAUEASA0LIABBICAPIAEgCBBtIAFFBEBBACEBDAELQQAhCSAHKAJAIQsDQCALKAIAIgpFDQEgB0EEaiAKEPMFIgogCWoiCSABSw0BIAAgB0EEaiAKEGcgC0EEaiELIAEgCUsNAAsLIABBICAPIAEgCEGAwABzEG0gDyABIAEgD0gbIQEMCAsgFUEAIAlBAEgbDQhBPSELIAAgBysDQCAPIAkgCCABIAURRAAiAUEATg0HDAkLIAcgBykDQDwAN0EBIQkgFiEMIAohCAwECyAHIAFBAWoiCDYCTCABLQABIQsgCCEBDAALAAsgAA0HIBRFDQJBASEBA0AgBCABQQJ0aigCACIABEAgAyABQQN0aiAAIAIgBhD1BUEBIQ0gAUEBaiIBQQpHDQEMCQsLQQEhDSABQQpPDQcDQCAEIAFBAnRqKAIADQEgAUEBaiIBQQpHDQALDAcLQRwhCwwECyAJIAsgDGsiESAJIBFKGyIKQf////8HIA5rSg0CQT0hCyAPIAogDmoiCSAJIA9IGyIBIBdKDQMgAEEgIAEgCSAIEG0gACAQIA4QZyAAQTAgASAJIAhBgIAEcxBtIABBMCAKIBFBABBtIAAgDCAREGcgAEEgIAEgCSAIQYDAAHMQbQwBCwtBACENDAMLQT0hCwtBxLMEIAs2AgALQX8hDQsgB0HQAGokACANC38CAX8BfiAAvSIDQjSIp0H/D3EiAkH/D0cEfCACRQRAIAEgAEQAAAAAAAAAAGEEf0EABSAARAAAAAAAAPBDoiABEPgFIQAgASgCAEFAags2AgAgAA8LIAEgAkH+B2s2AgAgA0L/////////h4B/g0KAgICAgICA8D+EvwUgAAsLqAMDAnwDfwF+IAC9IghCIIinIgVB+P///wdxQaiolv8DSSIGRQRARBgtRFT7Iek/IAAgAJogCEIAWSIHG6FEB1wUMyamgTwgASABmiAHG6GgIQAgBUEfdiEFRAAAAAAAAAAAIQELIAAgACAAIACiIgSiIgNEY1VVVVVV1T+iIAQgAyAEIASiIgMgAyADIAMgA0RzU2Dby3XzvqJEppI3oIh+FD+gokQBZfLy2ERDP6CiRCgDVskibW0/oKJEN9YGhPRklj+gokR6/hARERHBP6AgBCADIAMgAyADIANE1Hq/dHAq+z6iROmn8DIPuBI/oKJEaBCNGvcmMD+gokQVg+D+yNtXP6CiRJOEbunjJoI/oKJE/kGzG7qhqz+goqCiIAGgoiABoKAiA6AhASAGRQRAQQEgAkEBdGu3IgQgACADIAEgAaIgASAEoKOhoCIAIACgoSIAmiAAIAUbDwsgAgR8RAAAAAAAAPC/IAGjIgQgBL1CgICAgHCDvyIEIAMgAb1CgICAgHCDvyIBIAChoaIgBCABokQAAAAAAADwP6CgoiAEoAUgAQsL0DIDFH8HfgF8IwBBEGsiDyQAIwBBoAFrIgMkACADIAA2AjwgAyAANgIUIANBfzYCGCADQRBqIgAQqwQgAyEQIwBBMGsiDCQAQZCtBCgCACEOQYStBCgCACENA0ACfyAAKAIEIgMgACgCaEcEQCAAIANBAWo2AgQgAy0AAAwBCyAAEFwLIgIQgwYNAAtBASEDAkACQCACQStrDgMAAQABC0F/QQEgAkEtRhshAyAAKAIEIgIgACgCaEcEQCAAIAJBAWo2AgQgAi0AACECDAELIAAQXCECCwJAAkACQANAIARByAtqLAAAIAJBIHJGBEACQCAEQQZLDQAgACgCBCICIAAoAmhHBEAgACACQQFqNgIEIAItAAAhAgwBCyAAEFwhAgsgBEEBaiIEQQhHDQEMAgsLIARBA0cEQCAEQQhGDQEgBEEESQ0CIARBCEYNAQsgACkDcCIVQgBZBEAgACAAKAIEQQFrNgIECyAEQQRJDQAgFUIAUyECA0AgAkUEQCAAIAAoAgRBAWs2AgQLIARBAWsiBEEDSw0ACwtCACEVIwBBEGsiAiQAAn4gA7JDAACAf5S8IgNB/////wdxIgBBgICABGtB////9wdNBEAgAK1CGYZCgICAgICAgMA/fAwBCyADrUIZhkKAgICAgIDA//8AhCAAQYCAgPwHTw0AGkIAIABFDQAaIAIgAK1CACAAZyIAQdEAahBzIAIpAwAhFSACKQMIQoCAgICAgMAAhUGJ/wAgAGutQjCGhAshFiAMIBU3AwAgDCAWIANBgICAgHhxrUIghoQ3AwggAkEQaiQAIAwpAwghFSAMKQMAIRYMAQsCQAJAAkAgBA0AQQAhBANAIARB0jtqLAAAIAJBIHJHDQECQCAEQQFLDQAgACgCBCICIAAoAmhHBEAgACACQQFqNgIEIAItAAAhAgwBCyAAEFwhAgsgBEEBaiIEQQNHDQALDAELAkACQCAEDgQAAQECAQsCQCACQTBHDQACfyAAKAIEIgEgACgCaEcEQCAAIAFBAWo2AgQgAS0AAAwBCyAAEFwLQV9xQdgARgRAIwBBsANrIgIkAAJ/IAAoAgQiASAAKAJoRwRAIAAgAUEBajYCBCABLQAADAELIAAQXAshBAJAAn8DQCAEQTBHBEACQCAEQS5HDQQgACgCBCIBIAAoAmhGDQAgACABQQFqNgIEIAEtAAAMAwsFIAAoAgQiASAAKAJoRwR/QQEhBiAAIAFBAWo2AgQgAS0AAAVBASEGIAAQXAshBAwBCwsgABBcCyEEQQEhCSAEQTBHDQADQCAYQgF9IRgCfyAAKAIEIgYgACgCaEcEQCAAIAZBAWo2AgQgBi0AAAwBCyAAEFwLIgRBMEYNAAtBASEGC0KAgICAgIDA/z8hFgJAA0ACQCAEQSByIQECQAJAIARBMGsiB0EKSQ0AIARBLkcgAUHhAGtBBk9xDQQgBEEuRw0AIAkNAkEBIQkgFSEYDAELIAFB1wBrIAcgBEE5ShshBgJAIBVCB1cEQCAGIAVBBHRqIQUMAQsgFUIcWARAIAJBMGogBhCEASACQSBqIBogFkIAQoCAgICAgMD9PxAzIAJBEGogAikDMCACKQM4IAIpAyAiGiACKQMoIhYQMyACIAIpAxAgAikDGCAXIBkQfSACKQMIIRkgAikDACEXDAELIAZFIAtyDQAgAkHQAGogGiAWQgBCgICAgICAgP8/EDMgAkFAayACKQNQIAIpA1ggFyAZEH0gAikDSCEZQQEhCyACKQNAIRcLIBVCAXwhFUEBIQYLIAAoAgQiASAAKAJoRwR/IAAgAUEBajYCBCABLQAABSAAEFwLIQQMAQsLQS4hBAsCfiAGRQRAIAApA3BCAFkEQAJAIAAgACgCBCIFQQFrNgIEIAAgBUECazYCBCAJRQ0AIAAgBUEDazYCBAsLIAJB4ABqIAO3RAAAAAAAAAAAohC4ASACKQNgIRcgAikDaAwBCyAVQgdXBEAgFSEWA0AgBUEEdCEFIBZCAXwiFkIIUg0ACwsCQAJAAkAgBEFfcUHQAEYEQCAAEPsFIhZCgICAgICAgICAf1INAyAAKQNwQgBZDQEMAgtCACEWIAApA3BCAFMNAgsgACAAKAIEQQFrNgIEC0IAIRYLIAVFBEAgAkHwAGogA7dEAAAAAAAAAACiELgBIAIpA3AhFyACKQN4DAELIBggFSAJG0IChiAWfEIgfSIVQQAgDmutVQRAQcSzBEHEADYCACACQaABaiADEIQBIAJBkAFqIAIpA6ABIAIpA6gBQn9C////////v///ABAzIAJBgAFqIAIpA5ABIAIpA5gBQn9C////////v///ABAzIAIpA4ABIRcgAikDiAEMAQsgDkHiAWusIBVXBEAgBUEATgRAA0AgAkGgA2ogFyAZQgBCgICAgICAwP+/fxB9IBcgGUKAgICAgICA/z8Q8AUhACACQZADaiAXIBkgFyACKQOgAyAAQQBIIgYbIBkgAikDqAMgBhsQfSAVQgF9IRUgAikDmAMhGSACKQOQAyEXIAVBAXQgAEEATnIiBUEATg0ACwsCfiAVIA6sfUIgfCIWpyIAQQAgAEEAShsgDSAWIA2tUxsiAEHxAE4EQCACQYADaiADEIQBIAIpA4gDIRggAikDgAMhGkIADAELIAJB4AJqRAAAAAAAAPA/QZABIABrEOoBELgBIAJB0AJqIAMQhAEgAkHwAmogAikD4AIgAikD6AIgAikD0AIiGiACKQPYAiIYEP4FIAIpA/gCIRsgAikD8AILIRYgAkHAAmogBSAFQQFxRSAXIBlCAEIAEP8BQQBHIABBIEhxcSIAahCiAiACQbACaiAaIBggAikDwAIgAikDyAIQMyACQZACaiACKQOwAiACKQO4AiAWIBsQfSACQaACaiAaIBhCACAXIAAbQgAgGSAAGxAzIAJBgAJqIAIpA6ACIAIpA6gCIAIpA5ACIAIpA5gCEH0gAkHwAWogAikDgAIgAikDiAIgFiAbEKcEIAIpA/ABIhYgAikD+AEiGEIAQgAQ/wFFBEBBxLMEQcQANgIACyACQeABaiAWIBggFacQ/QUgAikD4AEhFyACKQPoAQwBC0HEswRBxAA2AgAgAkHQAWogAxCEASACQcABaiACKQPQASACKQPYAUIAQoCAgICAgMAAEDMgAkGwAWogAikDwAEgAikDyAFCAEKAgICAgIDAABAzIAIpA7ABIRcgAikDuAELIRUgDCAXNwMQIAwgFTcDGCACQbADaiQAIAwpAxghFSAMKQMQIRYMBQsgACkDcEIAUw0AIAAgACgCBEEBazYCBAsgACEFIAIhACADIQtBACECIwBBkMYAayIBJABBACANIA5qIhNrIRQCQAJ/A0AgAEEwRwRAAkAgAEEuRw0EIAUoAgQiACAFKAJoRg0AIAUgAEEBajYCBCAALQAADAMLBSAFKAIEIgAgBSgCaEcEf0EBIQIgBSAAQQFqNgIEIAAtAAAFQQEhAiAFEFwLIQAMAQsLIAUQXAshAEEBIQcgAEEwRw0AA0AgFUIBfSEVAn8gBSgCBCIAIAUoAmhHBEAgBSAAQQFqNgIEIAAtAAAMAQsgBRBcCyIAQTBGDQALQQEhAgsgAUEANgKQBiAMAn4CQAJAAkACQCAAQS5GIgMgAEEwayIIQQlNcgRAA0ACQCADQQFxBEAgB0UEQCAWIRVBASEHDAILIAJFIQMMBAsgFkIBfCEWIAZB/A9MBEAgCSAWpyAAQTBGGyEJIAFBkAZqIAZBAnRqIgMgCgR/IAAgAygCAEEKbGpBMGsFIAgLNgIAQQEhAkEAIApBAWoiACAAQQlGIgAbIQogACAGaiEGDAELIABBMEYNACABIAEoAoBGQQFyNgKARkHcjwEhCQsCfyAFKAIEIgAgBSgCaEcEQCAFIABBAWo2AgQgAC0AAAwBCyAFEFwLIgBBLkYiAyAAQTBrIghBCklyDQALCyAVIBYgBxshFSACRSAAQV9xQcUAR3JFBEACQCAFEPsFIhdCgICAgICAgICAf1INAEIAIRcgBSkDcEIAUw0AIAUgBSgCBEEBazYCBAsgAkUNAyAVIBd8IRUMBAsgAkUhAyAAQQBIDQELIAUpA3BCAFMNACAFIAUoAgRBAWs2AgQLIANFDQELQcSzBEEcNgIAQgAhFiAFEKsEQgAMAQsgASgCkAYiAEUEQCABIAu3RAAAAAAAAAAAohC4ASABKQMAIRYgASkDCAwBCyAVIBZSIBZCCVVyIA1BHkxBACAAIA12G3JFBEAgAUEwaiALEIQBIAFBIGogABCiAiABQRBqIAEpAzAgASkDOCABKQMgIAEpAygQMyABKQMQIRYgASkDGAwBCyAOQX5trSAVUwRAQcSzBEHEADYCACABQeAAaiALEIQBIAFB0ABqIAEpA2AgASkDaEJ/Qv///////7///wAQMyABQUBrIAEpA1AgASkDWEJ/Qv///////7///wAQMyABKQNAIRYgASkDSAwBCyAOQeIBa6wgFVUEQEHEswRBxAA2AgAgAUGQAWogCxCEASABQYABaiABKQOQASABKQOYAUIAQoCAgICAgMAAEDMgAUHwAGogASkDgAEgASkDiAFCAEKAgICAgIDAABAzIAEpA3AhFiABKQN4DAELIAoEQCAKQQhMBEAgAUGQBmogBkECdGoiACgCACEEA0AgBEEKbCEEIApBAWoiCkEJRw0ACyAAIAQ2AgALIAZBAWohBgsCQCAJIBWnIgdKIAlBCU5yIAdBEUpyDQAgB0EJRgRAIAFBwAFqIAsQhAEgAUGwAWogASgCkAYQogIgAUGgAWogASkDwAEgASkDyAEgASkDsAEgASkDuAEQMyABKQOgASEWIAEpA6gBDAILIAdBCEwEQCABQZACaiALEIQBIAFBgAJqIAEoApAGEKICIAFB8AFqIAEpA5ACIAEpA5gCIAEpA4ACIAEpA4gCEDMgAUHgAWpBACAHa0ECdEGArQRqKAIAEIQBIAFB0AFqIAEpA/ABIAEpA/gBIAEpA+ABIAEpA+gBEO8FIAEpA9ABIRYgASkD2AEMAgsgDSAHQX1sakEbaiIAQR5MQQAgASgCkAYiAyAAdhsNACABQeACaiALEIQBIAFB0AJqIAMQogIgAUHAAmogASkD4AIgASkD6AIgASkD0AIgASkD2AIQMyABQbACaiAHQQJ0QbisBGooAgAQhAEgAUGgAmogASkDwAIgASkDyAIgASkDsAIgASkDuAIQMyABKQOgAiEWIAEpA6gCDAELA0AgAUGQBmogBiIAQQFrIgZBAnRqKAIARQ0AC0EAIQoCQCAHQQlvIgJFBEBBACEDDAELQQAhAyACQQlqIAIgB0EASBshAgJAIABFBEBBACEADAELQYCU69wDQQAgAmtBAnRBgK0EaigCACIFbSEGQQAhCEEAIQQDQCABQZAGaiAEQQJ0aiIJIAggCSgCACIJIAVuIhFqIgg2AgAgA0EBakH/D3EgAyAIRSADIARGcSIIGyEDIAdBCWsgByAIGyEHIAYgCSAFIBFsa2whCCAEQQFqIgQgAEcNAAsgCEUNACABQZAGaiAAQQJ0aiAINgIAIABBAWohAAsgByACa0EJaiEHCwNAIAFBkAZqIANBAnRqIQYCQANAIAdBJE4EQCAHQSRHDQIgBigCAEHR6fkETw0CCyAAQf8PaiECQQAhCANAIAitIAFBkAZqIAJB/w9xIgVBAnRqIgI1AgBCHYZ8IhVCgZTr3ANUBH9BAAUgFSAVQoCU69wDgCIWQoCU69wDfn0hFSAWpwshCCACIBWnIgI2AgAgACAAIAAgBSACGyADIAVGGyAFIABBAWtB/w9xRxshACAFQQFrIQIgAyAFRw0ACyAKQR1rIQogCEUNAAsgACADQQFrQf8PcSIDRgRAIAFBkAZqIgIgAEH+D2pB/w9xQQJ0aiIFIAUoAgAgAEEBa0H/D3EiAEECdCACaigCAHI2AgALIAdBCWohByABQZAGaiADQQJ0aiAINgIADAELCwJAA0AgAEEBakH/D3EhBSABQZAGaiAAQQFrQf8PcUECdGohCANAQQlBASAHQS1KGyEGAkADQCADIQJBACEEAkADQAJAIAIgBGpB/w9xIgMgAEYNACABQZAGaiADQQJ0aigCACIDIARBAnRB0KwEaigCACIJSQ0AIAMgCUsNAiAEQQFqIgRBBEcNAQsLIAdBJEcNAEIAIRVBACEEQgAhFgNAIAAgAiAEakH/D3EiA0YEQCAAQQFqQf8PcSIAQQJ0IAFqQQA2AowGCyABQYAGaiABQZAGaiADQQJ0aigCABCiAiABQfAFaiAVIBZCAEKAgICA5Zq3jsAAEDMgAUHgBWogASkD8AUgASkD+AUgASkDgAYgASkDiAYQfSABKQPoBSEWIAEpA+AFIRUgBEEBaiIEQQRHDQALIAFB0AVqIAsQhAEgAUHABWogFSAWIAEpA9AFIAEpA9gFEDMgASkDyAUhFkIAIRUgASkDwAUhFyAKQfEAaiIHIA5rIgVBACAFQQBKGyANIAUgDUgiBhsiA0HwAEwNAgwFCyAGIApqIQogACEDIAAgAkYNAAtBgJTr3AMgBnYhCUF/IAZ0QX9zIRFBACEEIAIhAwNAIAFBkAZqIAJBAnRqIhIgBCASKAIAIhIgBnZqIgQ2AgAgA0EBakH/D3EgAyAERSACIANGcSIEGyEDIAdBCWsgByAEGyEHIBEgEnEgCWwhBCACQQFqQf8PcSICIABHDQALIARFDQEgAyAFRwRAIAFBkAZqIABBAnRqIAQ2AgAgBSEADAMLIAggCCgCAEEBcjYCAAwBCwsLIAFBkAVqRAAAAAAAAPA/QeEBIANrEOoBELgBIAFBsAVqIAEpA5AFIAEpA5gFIBcgFhD+BSABKQO4BSEZIAEpA7AFIRogAUGABWpEAAAAAAAA8D9B8QAgA2sQ6gEQuAEgAUGgBWogFyAWIAEpA4AFIAEpA4gFEPwFIAFB8ARqIBcgFiABKQOgBSIVIAEpA6gFIhgQpwQgAUHgBGogGiAZIAEpA/AEIAEpA/gEEH0gASkD6AQhFiABKQPgBCEXCwJAIAJBBGpB/w9xIgQgAEYNAAJAIAFBkAZqIARBAnRqKAIAIgRB/8m17gFNBEAgBEUgAkEFakH/D3EgAEZxDQEgAUHwA2ogC7dEAAAAAAAA0D+iELgBIAFB4ANqIBUgGCABKQPwAyABKQP4AxB9IAEpA+gDIRggASkD4AMhFQwBCyAEQYDKte4BRwRAIAFB0ARqIAu3RAAAAAAAAOg/ohC4ASABQcAEaiAVIBggASkD0AQgASkD2AQQfSABKQPIBCEYIAEpA8AEIRUMAQsgC7chHCAAIAJBBWpB/w9xRgRAIAFBkARqIBxEAAAAAAAA4D+iELgBIAFBgARqIBUgGCABKQOQBCABKQOYBBB9IAEpA4gEIRggASkDgAQhFQwBCyABQbAEaiAcRAAAAAAAAOg/ohC4ASABQaAEaiAVIBggASkDsAQgASkDuAQQfSABKQOoBCEYIAEpA6AEIRULIANB7wBKDQAgAUHQA2ogFSAYQgBCgICAgICAwP8/EPwFIAEpA9ADIAEpA9gDQgBCABD/AQ0AIAFBwANqIBUgGEIAQoCAgICAgMD/PxB9IAEpA8gDIRggASkDwAMhFQsgAUGwA2ogFyAWIBUgGBB9IAFBoANqIAEpA7ADIAEpA7gDIBogGRCnBCABKQOoAyEWIAEpA6ADIRcCQEF+IBNrIAdB/////wdxTg0AIAEgFkL///////////8AgzcDmAMgASAXNwOQAyABQYADaiAXIBZCAEKAgICAgICA/z8QMyABKQOQAyABKQOYA0KAgICAgICAuMAAEPAFIQAgFiABKQOIAyAAQQBIIgIbIRYgFyABKQOAAyACGyEXQQAgFCAKIABBAE5qIgpB7gBqTiAVIBhCAEIAEP8BQQBHIAYgBiADIAVHcSACG3EbDQBBxLMEQcQANgIACyABQfACaiAXIBYgChD9BSABKQPwAiEWIAEpA/gCCzcDKCAMIBY3AyAgAUGQxgBqJAAgDCkDKCEVIAwpAyAhFgwDCyAAKQNwQgBZBEAgACAAKAIEQQFrNgIEC0HEswRBHDYCAAwBCwJAAn8gACgCBCIDIAAoAmhHBEAgACADQQFqNgIEIAMtAAAMAQsgABBcC0EoRgRAQQEhBAwBC0KAgICAgIDg//8AIRUgACkDcEIAUw0CIAAgACgCBEEBazYCBAwCCwNAAn8gACgCBCIDIAAoAmhHBEAgACADQQFqNgIEIAMtAAAMAQsgABBcCyIDQTBrQQpJIANBwQBrQRpJciADQd8ARnJFIANB4QBrQRpPcUUEQCAEQQFqIQQMAQsLQoCAgICAgOD//wAhFSADQSlGDQEgACkDcCIYQgBZBEAgACAAKAIEQQFrNgIECyAERQ0BA0AgBEEBayEEIBhCAFkEQCAAIAAoAgRBAWs2AgQLIAQNAAsMAQsgABCrBAsgECAWNwMAIBAgFTcDCCAMQTBqJAAgECkDACEVIA8gECkDCDcDCCAPIBU3AwAgEEGgAWokACAPKQMAIA8pAwgQ7QUhHCAPQRBqJAAgHAv8AwIEfwF+AkACQAJ/AkACQAJ/IAAoAgQiASAAKAJoRwRAIAAgAUEBajYCBCABLQAADAELIAAQXAsiAUEraw4DAAEAAQsgAUEtRgJ/IAAoAgQiASAAKAJoRwRAIAAgAUEBajYCBCABLQAADAELIAAQXAsiAUE6ayICQXVLDQEaIAApA3BCAFMNAiAAIAAoAgRBAWs2AgQMAgsgAUE6ayECQQALIQQgAkF2SQ0AIAFBMGsiAkEKSQRAA0AgASADQQpsakEwayIDQcyZs+YASAJ/IAAoAgQiASAAKAJoRwRAIAAgAUEBajYCBCABLQAADAELIAAQXAsiAUEwayICQQlNcQ0ACyADrCEFCwJAIAJBCk8NAANAIAGtIAVCCn58QjB9IQUCfyAAKAIEIgEgACgCaEcEQCAAIAFBAWo2AgQgAS0AAAwBCyAAEFwLIgFBMGsiAkEJSw0BIAVCro+F18fC66MBUw0ACwsgAkEKSQRAA0ACfyAAKAIEIgEgACgCaEcEQCAAIAFBAWo2AgQgAS0AAAwBCyAAEFwLQTBrQQpJDQALCyAAKQNwQgBZBEAgACAAKAIEQQFrNgIEC0IAIAV9IAUgBBshBQwBC0KAgICAgICAgIB/IQUgACkDcEIAUw0AIAAgACgCBEEBazYCBEKAgICAgICAgIB/DwsgBQvQBgIEfwN+IwBBgAFrIgUkAAJAAkACQCADIARCAEIAEP8BRQ0AAn8gBEL///////8/gyEJAn8gBEIwiKdB//8BcSIGQf//AUcEQEEEIAYNARpBAkEDIAMgCYRQGwwCCyADIAmEUAsLIQcgAkIwiKciCEH//wFxIgZB//8BRg0AIAcNAQsgBUEQaiABIAIgAyAEEDMgBSAFKQMQIgEgBSkDGCICIAEgAhDvBSAFKQMIIQIgBSkDACEEDAELIAEgAkL///////8/gyAGrUIwhoQiCiADIARC////////P4MgBEIwiKdB//8BcSIHrUIwhoQiCRD/AUEATARAIAEgCiADIAkQ/wEEQCABIQQMAgsgBUHwAGogASACQgBCABAzIAUpA3ghAiAFKQNwIQQMAQsgBgR+IAEFIAVB4ABqIAEgCkIAQoCAgICAgMC7wAAQMyAFKQNoIgpCMIinQfgAayEGIAUpA2ALIQQgB0UEQCAFQdAAaiADIAlCAEKAgICAgIDAu8AAEDMgBSkDWCIJQjCIp0H4AGshByAFKQNQIQMLIAlC////////P4NCgICAgICAwACEIQkgCkL///////8/g0KAgICAgIDAAIQhCiAGIAdKBEADQAJ+IAogCX0gAyAEVq19IgtCAFkEQCALIAQgA30iBIRQBEAgBUEgaiABIAJCAEIAEDMgBSkDKCECIAUpAyAhBAwFCyALQgGGIARCP4iEDAELIApCAYYgBEI/iIQLIQogBEIBhiEEIAZBAWsiBiAHSg0ACyAHIQYLAkAgCiAJfSADIARWrX0iCUIAUwRAIAohCQwBCyAJIAQgA30iBIRCAFINACAFQTBqIAEgAkIAQgAQMyAFKQM4IQIgBSkDMCEEDAELIAlC////////P1gEQANAIARCP4ghASAGQQFrIQYgBEIBhiEEIAEgCUIBhoQiCUKAgICAgIDAAFQNAAsLIAhBgIACcSEHIAZBAEwEQCAFQUBrIAQgCUL///////8/gyAGQfgAaiAHcq1CMIaEQgBCgICAgICAwMM/EDMgBSkDSCECIAUpA0AhBAwBCyAJQv///////z+DIAYgB3KtQjCGhCECCyAAIAQ3AwAgACACNwMIIAVBgAFqJAALvwIBAX8jAEHQAGsiBCQAAkAgA0GAgAFOBEAgBEEgaiABIAJCAEKAgICAgICA//8AEDMgBCkDKCECIAQpAyAhASADQf//AUkEQCADQf//AGshAwwCCyAEQRBqIAEgAkIAQoCAgICAgID//wAQMyADQf3/AiADQf3/AkgbQf7/AWshAyAEKQMYIQIgBCkDECEBDAELIANBgYB/Sg0AIARBQGsgASACQgBCgICAgICAgDkQMyAEKQNIIQIgBCkDQCEBIANB9IB+SwRAIANBjf8AaiEDDAELIARB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AykDCBA4IgJFDQAgACABIAIgBhAPIARBABCIBCEDIAAgAhATIANBAEgNACADQQBHrUKAgICAEIQhBQsgBQsqACADKQMAIgFC/////29YBEAgABApQoCAgIDgAA8LIAAgAUEDQQAQgQMLYwEBfiADKQMAIgRC/////29YBEAgABApQoCAgIDgAA8LQoCAgIDgACEBAkAgACADKQMIEDgiAkUNACAAIAQgAhB6IQMgACACEBMgA0EASA0AIANBAEetQoCAgIAQhCEBCyABC2MBAn4CQAJAIAMpAwAiAUL/////b1gEQCAAECkMAQsgAykDCCEFIAEhBCACQQNOBEAgAykDECEECyAAIAUQOCICDQELQoCAgIDgAA8LIAAgASACIARBABAUIQEgACACEBMgAQtmAQF+IAMpAwAiBEL/////b1gEQCAAEClCgICAgOAADwtCgICAgOAAIQECQCAAIAMpAwgQOCICRQ0AIAAgBCACQQAQ3gEhAyAAIAIQEyADQQBIDQAgA0EAR61CgICAgBCEIQELIAELigECAX8CfiMAQRBrIgQkACADKQMIIQUgAykDACIGIQECQAJAAkACQCACQQNIDQAgAykDECIBELUBDQAgAEHfKUEAEBYMAQsgACAEQQxqIAUQiwQiAg0BC0KAgICA4AAhAQwBCyAAIAYgASAEKAIMIgMgAhCOAyEBIAAgAiADEJgDCyAEQRBqJAAgAQscACAAIAMpAwBBACACQQFrEEogA0EIakECEJoDC0MAIwBBEGsiAiQAAn5CgICAgOAAIAAgAkEMaiADKQMAEMcBDQAaQiAgAigCDCIARQ0AGiAAZ60LIQEgAkEQaiQAIAELUAAjAEEQayICJABCgICAgOAAIQECQCAAIAJBDGogAykDABCTAg0AIAAgAkEIaiADKQMIEJMCDQAgAigCCCACKAIMbK0hAQsgAkEQaiQAIAELBgAgALa7C1AAIAAgACkD0AEiAUIMiCABhSIBQhmGIAGFIgFCG4ggAYUiATcD0AEgAUKdurP7lJL9oiV+QgyIQoCAgICAgID4P4S/RAAAAAAAAPC/oBAXC/UDAwN8BX8DfiMAQRBrIggkACAIQgA3AwgCQAJAIAJBAEwNAEKAgICA4AAhASAAIAhBCGogAykDABBHDQFBASEJIAgrAwghBCACQQFHBEADQCACIAlGDQIgACAIIAMgCUEDdGopAwAQRw0DIAlBAWohCSAIKwMAIQUjAEEgayIHJAAgBL1C////////////AIMiDSAFvUL///////////8AgyIMIAwgDVYbIg6/IQQCQCAOQjSIpyIKQf8PRg0AIA0gDCAMIA1UGyIMvyEFAkAgDlANACAMQjSIpyILQf8PRg0AIAsgCmtBwQBOBEAgBSAEoCEEDAILAnwgC0H+C08EQCAERAAAAAAAADAUoiEEIAVEAAAAAAAAMBSiIQVEAAAAAAAAsGsMAQtEAAAAAAAA8D8gCkG8BEsNABogBEQAAAAAAACwa6IhBCAFRAAAAAAAALBroiEFRAAAAAAAADAUCyEGIAdBGGogB0EQaiAFEIQGIAdBCGogByAEEIQGIAYgBysDACAHKwMQoCAHKwMIoCAHKwMYoJ+iIQQMAQsgBSEECyAHQSBqJAAMAAsACyAEmSEECyAEvQJ/IASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyIAt71RBEAgAK0hAQwBCyAEEBchAQsgCEEQaiQAIAELTgAgACAARAAAAAAAAPC/RAAAAAAAAPA/IABEAAAAAAAAAABjGyAAvUL///////////8Ag0KAgICAgICA+P8AVhsgAEQAAAAAAAAAAGEbC4MBAgJ+AX8gAL0iAUI0iKdB/w9xIgNB/gdNBEAgAUKAgICAgICAgIB/gyECIANB/gdHIAFCgICAgICAgPC/f1FyRQRAIAJCgICAgICAgPg/hL8PCyACvw8LIANBsghNBHwgAUI/hyABfEIBQbMIIANrrYYiAUIBiHxCACABfYO/BSAACwvdBAICfAV/IwBBEGsiCCQAAn4gAkUEQEQAAAAAAADw/0QAAAAAAADwfyAEGxAXDAELAnwgAykDACIBQv////8PWARAIAJBASACQQFKGyELIAGnIQlBASEHA0AgByALRwRAIAm3IAMgB0EDdGopAwAiAUKAgICAEFoNAxogAachCgJ/IAQEQCAJIAoQSgwBCyAJIAoQtAELIQkgB0EBaiEHDAELCyAJrQwCC0KAgICA4AAgACAIQQhqIAEQRw0BGkEBIQcgCCsDCAshBSAHIAIgAiAHSBshAgNAIAIgB0cEQEKAgICA4AAgACAIIAMgB0EDdGopAwAQRw0CGgJAIAW9Qv///////////wCDQoCAgICAgID4/wBWDQAgCCsDACIGvUL///////////8Ag0KAgICAgICA+P8AVgRAIAYhBQwBCyAEBEAgBSAFIAalIAa9Qv///////////wCDQoCAgICAgID4/wBWGyAGIAW9Qv///////////wCDQoCAgICAgID4/wBYGyAGvSAFvYO/IAVEAAAAAAAAAABiIAZEAAAAAAAAAABichshBQwBCyAFIAUgBqQgBr1C////////////AINCgICAgICAgPj/AFYbIAYgBb1C////////////AINCgICAgICAgPj/AFgbIAa9IAW9hL8gBUQAAAAAAAAAAGIgBkQAAAAAAAAAAGJyGyEFCyAHQQFqIQcMAQsLIAW9An8gBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIgC3vVEEQCAArQwBCyAFEBcLIQEgCEEQaiQAIAEL0AEBAn8jAEEQayICJAACfiAAIAFBJhBqIgNFBEAgBEEANgIAQoCAgIDgAAwBCwJAIAMpAwAiARASRQRAIAIgAygCDCIFNgIMIAUgAaciBigCBEH/////B3FJDQEgACABEAwgA0KAgICAMDcDAAsgBEEBNgIAQoCAgIAwDAELIAYgAkEMahDbASEHIAMgAigCDDYCDCAEQQA2AgAgB0H//wNNBEAgACAHQf//A3EQpgMMAQsgACAGIAVBAXRqQRBqQQIQnAQLIQEgAkEQaiQAIAELxwICAn8CfiMAQSBrIgIkAEKAgICA4AAhBwJAIAAgARBjIgEQDQ0AIAAgAkEIaiIFQQcQQhogBUE8ED4aIAUgBEEDdCIFQfDKAWooAgAiBhCOARpBnj0gBHZBAXFFBEAgAkEIaiIEQSAQPhogBCAFQfTKAWooAgAQjgEaIARByv4AEI4BGiAAIAMpAwAQYyIIEA0EQCAAIAEQDCACQQhqEEQMAgsgCKchA0EAIQQDQCAEIAMoAgRB/////wdxT0UEQAJAIAMgBBBNIgVBIkYEQCACQQhqQfTvABCOARoMAQsgAkEIaiAFEJYBGgsgBEEBaiEEDAELCyAAIAgQDCACQQhqQSIQPhoLIAJBCGoiAEE+ED4aIAAgARCPARogAEH29QAQjgEaIAAgBhCOARogAkEIakE+ED4aIAAQOSEHCyACQSBqJAAgBwu7BAEIfyMAQTBrIgIkAAJAIAAgARBjIgEQ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IAQgCSkDCBBwQQBIDQcLIARCAXwhBCABQgF8IQEMAAsACyAEQv7///////8PVQ0DIAAgBSAEIAYQDxBwQQBIDQQgBEIBfCEECyAKQQFqIQoMAQsLIAAgBUEwIARCgICAgAh8Qv////8PWAR+IARC/////w+DBSAEuRAXCxBIQQBIDQEMAgsgAEGqwwBBABAWCyAAIAUQDEKAgICA4AAhBQsgACAIEAwgCUEQaiQAIAULLQEBfkKAgICAMCECAkAgARCoAyIARQ0AIAAtABJBBHFFDQAgADUCRCECCyACCzMCAX4Bf0KAgICAMCECAkAgARCoAyIDRQ0AIAMtABJBBHFFDQAgACADKAJAEDIhAgsgAgsoAEKAgICA4AAgACADKQMAIAEQ2QUiAEEAR61CgICAgBCEIABBAEgbC6sBAgF+An9CgICAgOAAIQQgACABEGkEfkKAgICA4AAFQeb+ACECAkAgAaciAy8BBhD4AUUNAAJAIAMoAiAiAy8AESIFQYAIcUUNACADKAJUIgZFDQAgACAGIAMoAkgQ/gEPCyAFQQR2QQNxQQFrIgNBAksNACADQQJ0QaDdAWooAgAhAgsgACACIAAgAUE2IAFBABAUIgEQEgR+IABBLxAyBSABC0GeCBC/AQsLhwQDA34EfwN8AkAgACABEGkNACAAIAApAzBBDhBTIgUQDQ0AIAWnIgkgARC1AUEEdEEQcSAJLQAFQe8BcXI6AAUCQCAAQQAgAkEBaxBKIgJBA3RBGGoQLyIHRQ0AIAcgARAPIgE3AwAgAykDABAPIQQgByACNgIQIAcgBDcDCCACQQAgAkEAShshCgNAIAggCkcEQCAHIAhBA3RqIAMgCEEBaiIIQQN0aikDABAPNwMYDAELCyAJIAc2AiACfyABQv////9vWARAIAAQKUF/DAELIABBACABp0EwEE8LIgNBAEgNAAJAIANFDQAgACABQTAgAUEAEBQiBBANDQEgBEL/////D1gEQCAEpyIDIAJrQQAgAiADSButIQYMAQsgBBBWQQdGBEACQCAEEEkiDL1C////////////AINCgICAgICAgPj/AFYNACAMnSIMIAK3Ig1lDQAgDCANoSELCyALvQJ/IAuZRAAAAAAAAOBBYwRAIAuqDAELQYCAgIB4CyICt71RBEAgAq0hBgwCCyALEBchBgwBCyAAIAQQDAsgACAFQTAgBkEBEBsaIAAgAUE2IAFBABAUIgEQDQ0AIABB8P4AIAEQngEEfiABBSAAIAEQDCAAQS8QMgtB3IMBEL8BIgEQDQ0AIAAgBUE2IAFBARAbGiAFDwsgACAFEAwLQoCAgIDgAAswACACQQBMBEAgACABQoCAgIAwQQBBABAkDwsgACABIAMpAwAgAkEBayADQQhqECQLvwECAX4BfyMAQSBrIgIkAEKAgICA4AAhBQJAAkAgACABECsiARANDQAgACADKQMAEDgiA0UNAANAIAAgAiABpyADEE8iBkEASA0CIAYEQEKAgICAMCEFIAItAABBEHEEQCACQRhBECAEG2opAwAQDyEFCyAAIAIQTgwDCyAAIAEQmQIiARANDQIgARAoBEBCgICAgDAhBQwDCyAAEIIBRQ0ACwwBC0EAIQMLIAAgAxATIAAgARAMIAJBIGokACAFC6QBAQN+IAMpAwghBSADKQMAIQZCgICAgOAAIQcCQCAAIAEQKyIBEA0EfkKAgICA4AAFIAAgBRBpDQEgACAGEDgiAkUNASAAIAEgAkKAgICAMEKAgICAMCAFIAQbIAVCgICAgDAgBBtBhaoBQYWaASAEGxB4IQMgACABEAwgACACEBNCgICAgOAAQoCAgIAwIANBAEgbCw8LIAAgARAMQoCAgIDgAAtSAAJAIAEQEkUEQCABEChFDQELIAAQKUKAgICA4AAPCwJAIAIQIg0AIAIQKA0AQoCAgIAwDwtCgICAgOAAQoCAgIAwIAAgASACQQEQmwJBAEgbCyUBAX4gACABECsiARANBEAgAQ8LIAAgARD8ASECIAAgARAMIAILkwECAX4BfyMAQSBrIgIkAEKAgICA4AAhBAJAAkAgACABECsiARANDQAgACADKQMAEDgiA0UNACAAIAIgAacgAxBPIgVBAEgNASAFRQRAQoCAgIAQIQQMAgsgAjUCACEEIAAgAhBOIARCAohCAYNCgICAgBCEIQQMAQtBACEDCyAAIAMQEyAAIAEQDCACQSBqJAAgBAuIAQECfiADKQMAIgUQIkUEQEKAgICAEA8LAkAgACABECsiBBANRQRAIASnIQIgBRAPIQEDQCAAIAEQmQIiARANRQRAIAEQKCIDIAIgAadGcg0DIAAQggFFDQELCyAAIAEQDCAAIAQQDAtCgICAgOAADwsgACABEAwgACAEEAwgA0WtQoCAgIAQhAtlAQF+QoCAgIDgACEEAkAgACADKQMAEDgiAkUNACAAIAEQKyIBEA0EQCAAIAIQEyABDwsgAEEAIAGnIAIQTyEDIAAgAhATIAAgARAMIANBAEgNACADQQBHrUKAgICAEIQhBAsgBAtAAAJ+AkAgARCoAyICRQ0AIAItABBBAXENAEKAgICAMCACLQARQQFxDQEaCyAAIAFBAEEAENsEGkKAgICA4AALCwgAIAAgARArCw8AIAAgAUE3QQBBABDGAgtnACAAIAMpAwAQKyIBEA0EfiABBQJAAkAgACADKQMIEDgiAkUEQCAAIAEQDAwBCyAAQQAgAacgAhBPIQMgACACEBMgACABEAwgA0EATg0BC0KAgICA4AAPCyADQQBHrUKAgICAEIQLC5wCAQV+IwBBEGsiAiQAIAMpAwAhBQJAIAAQPCIBEA0EQCABIQUMAQtCgICAgDAhBwJAAkAgACAFQQAQ9gEiBBANDQAgACAEQeoAIARBABAUIgcQDQ0AA0AgACAEIAcgAkEMahCvASIGEA0NASACKAIMBEAgASEFDAMLAkACQCAGECJFBEAgABApDAELIAAgBkEAEHsiCBANDQAgACAGQQEQeyIFEA0EQCAAIAgQDAwBCyAAIAEgCCAFQYeAARDNAkEATg0BCyAAIAYQDAwCCyAAIAYQDAwACwALQoCAgIDgACEFIAQQIgRAIAAgBEEBELMBGgsgByEGIAQhByABIQQLIAAgBhAMIAAgBxAMIAAgBBAMCyACQRBqJAAgBQtIAEEvIQIgACADKQMAIgEQVkF/RgR/IAGnLwEGIgJBKUYEQEENQSkgACABEDsbIQILIAAoAhAoAkQgAkEYbGooAgQFQS8LEDIL8QECBH8BfiMAQTBrIgIkAAJAIAMpAwAiCRAiRQRAQoGAgIAQIQEMAQtCgICAgOAAIQEgACACQSxqIAJBKGogCaciCEEDEJIBDQAgAigCLCEGIAIoAighB0EAIQMCQANAIAMgB0cEQCAAIAJBCGogCCAGIANBA3RqKAIEEE8iBUEASA0CAkAgBUUNACAAIAJBCGoQTiACKAIIIgVBAXFFIARFIAVBAnFFcnENAEKAgICAECEBDAMLIANBAWohAwwBCwsgACAJEKIBIgNBAEgNASADQQFHrUKAgICAEIQhAQsgACAGIAcQZgsgAkEwaiQAIAELnQECAX4Bf0KAgICAMCEBAkACQCAAIAMpAwAQKyIEEA0NACACQQEgAkEBShshBUEBIQIDQCACIAVGDQICQCADIAJBA3RqKQMAIgEQKA0AIAEQEg0AIAAgARArIgEQDQ0CIAAgBCABQoCAgIAwQQEQxgUNAiAAIAEQDAsgAkEBaiECDAALAAsgACAEEAwgACABEAxCgICAgOAAIQQLIAQLGAAgACADKQMAIAMpAwgQWq1CgICAgBCEC5sCAgN+A38jAEEgayICJABCgICAgOAAIQQgACADKQMAECsiBRANRQRAQoCAgIAwIQECfgJAIAAgAkEcaiACQRhqIAWnQQMQkgENACAAEDwiARANDQAgAigCHCEHIAIoAhghCEEAIQMDQCADIAhHBEAgACAHIANBA3RqIgkoAgQQYCIGEA0NAiACIAY3AwggAiAFNwMAIABCgICAgDBBAiACQQAQ2QQhBCAAIAYQDCAEEA0NAiAEEBJFBEAgACABIAkoAgQgBEGHgAEQG0EASA0DCyADQQFqIQMMAQsLIAAgByAIEGYgAQwBCyAAIAIoAhwgAigCGBBmIAAgBRAMIAEhBUKAgICA4AALIQQgACAFEAwLIAJBIGokACAEC20AAn4CQCADKQMAIgFC/////29YBEAgBEUNASAAEClCgICAgOAADwtCgICAgOAAIAAgARCZBCICQQBIDQEaIAQEQCACQQBHrUKAgICAEIQPCyACDQAgAEHdygBBABAWQoCAgIDgAA8LIAEQDwsLTwACQAJAIAMpAwAiAUL/////b1gEQCAERQRAQoCAgIAQDwsgABApDAELIAAgARCiASIAQQBODQELQoCAgIDgAA8LIABBAEetQoCAgIAQhAsQACAAIAMpAwBBAkEAEIEDCxAAIAAgAykDAEEBQQAQgQMLLQEBfkKAgICA4AAhASAAIAMpAwAiBCADKQMIENwEBH5CgICAgOAABSAEEA8LC30BAn4gAykDACIBQv////9vWARAIAAQKUKAgICA4AAPCyADKQMQIQZCgICAgOAAIQUCQCAAIAMpAwgQOCICRQ0AIAAgASACIAYgBEVBDnQQ2gQhAyAAIAIQEyADQQBIDQAgBARAIANBAEetQoCAgIAQhA8LIAEQDyEFCyAFCycAIAAgAykDACIB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QRBqJAAgAAtGAQJ+IAIgACgCABAyIQNBACEAIAIgASgCABAyIQQCQCADEA0NACAEEA0NACADpyAEpxCVAiEACyACIAMQDCACIAQQDCAAC2sBAX4CQAJAAkACQAJAIAMtAAUiAQ4EAwICAAELIAAgAygCCBD2BA8LIAFBCEYNAgsQAQALIAAgAygCDCADKAIAIAMtAAggAy0ACSADLgEGEMsBDwsgACAAEDwiBCADKAIIIAMoAgwQJiAECwkAIAAgAxCJAws8AQF+IAAQPCIEEA1FBEAgACAEQTwgAa1CgICAgHCEEA9BAxAbQQBOBEAgBA8LIAAgBBAMC0KAgICA4AALXwEBfwJAIAFFBEAgAkUNASAAIAIQpQUPCyACRQRAIAAgACgCAEEBazYCACAAIAAoAgRBCGs2AgQgARDpAQwBCyAAKAIIIAAoAgQgAmpPBH8gASACEPIFBUEACw8LQQALJgAgAQRAIAAgACgCAEEBazYCACAAIAAoAgRBCGs2AgQgARDpAQsLKAEBfwJAIAGnKAIgIgNFDQAgAygCAEEERg0AIAAgA0EIaiACEO8DCwscAQF/IAFBKBBAIgIEQCAAIAIQ7QMgACACECELCyUBAX8gAacoAiAiAwRAIAAgAykDACACECMgACADKQMIIAIQIwsLJwEBfyABpygCICICBEAgACACKQMAECcgACACKQMIECcgACACECELCx4BAX8gAacoAiAiAgRAIAAgAikDABAnIAAgAhAhCwtDAQJ/IAGnKAIgIgIEQAJAIAIpAwAiARDdBUUNACACKAIMIgNFDQAgACADEPEDIAIpAwAhAQsgACABECcgACACECELC1gBA38CQCABpygCICIERQ0AIARBCGohAyAEQQRqIQUDQCADKAIAIgMgBUYNASAEKAIARQRAIAAgAykDECACECMLIAAgAykDGCACECMgA0EEaiEDDAALAAsLgQEBBX8gAacoAiAiAgRAIAJBBGohBSACKAIIIQMDQCADIAVHBEAgAygCBCEGIANBEGshBCADQQxrKAIARQRAAkAgAigCAARAIAQQpgUMAQsgACAEKQMgECcLIAAgBCkDKBAnCyAAIAQQISAGIQMMAQsLIAAgAigCEBAhIAAgAhAhCwshAQF/IAGnKAIgIgMEQCAAIAM1AgxCgICAgHCEIAIQIwsLQAEBfyABpygCICICBEAgACACNQIMQoCAgIBwhCIBEN0FBH4gAhBGIAI1AgxCgICAgHCEBSABCxAnIAAgAhAhCwtbAQJ/IAGnKAIgIgIEQAJAAkAgAi0ABUUNACAAKAK8ASIDRQ0AIAAoAsQBIAIoAgggAxEDAAwBCyACKAIYIgNFDQAgACACKAIUIAIoAgggAxEGAAsgACACECELCykBAX8gACABpyICNQIkQoCAgICQf4QQJyAAIAI1AiBCgICAgJB/hBAnCxEAIAAgAacoAiApAwAgAhAjCxkBAX8gACABpygCICICKQMAECcgACACECELOgECfwJAIAFBDxBAIgRFDQADQCADIAQtAAVPDQEgACAEIANBA3RqKQMIIAIQIyADQQFqIQMMAAsACws8AQJ/IAFBDxBAIgMEQANAIAIgAy0ABU9FBEAgACADIAJBA3RqKQMIECcgAkEBaiECDAELCyAAIAMQIQsLSQECfyAAIAGnKAIgIgQpAwAgAhAjIAAgBCkDCCACECMDQCADIAQoAhBORQRAIAAgBCADQQN0aikDGCACECMgA0EBaiEDDAELCwtJAQJ/IAAgAacoAiAiAikDABAnIAAgAikDCBAnA0AgAyACKAIQTkUEQCAAIAIgA0EDdGopAxgQJyADQQFqIQMMAQsLIAAgAhAhC44BAQR/IAGnIgMoAiQhBSADKAIgIQQgAygCKCIDBEAgACADrUKAgICAcIQgAhAjCyAEBEACQCAFRQ0AQQAhAwNAIAMgBCgCPE4NAQJAIAUgA0ECdGooAgAiBkUNACAGLQAFQQFxRQ0AIAAgBiACEQMACyADQQFqIQMMAAsACyAAIAStQoCAgIBghCACECMLC3MBA38gAaciAigCKCIDBEAgACADrUKAgICAcIQQJwsgAigCICIDBEAgAigCJCIEBEBBACECA0AgAiADKAI8TkUEQCAAIAQgAkECdGooAgAQ+gEgAkEBaiECDAELCyAAIAQQIQsgACADrUKAgICAYIQQJwsLEgAgAacoAiAiAARAIAAQrgMLCw4AIAAgAacpAyAgAhAjCxkAIAAgAaciACkDIBAnIABCgICAgDA3AyALNQECfyABpyEEA0AgAyAEKAIoT0UEQCAAIAQoAiQgA0EDdGopAwAgAhAjIANBAWohAwwBCwsLPQEDfyABpyEDA0AgAygCJCEEIAIg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KQwBCyABIQgLIAAgBhAMIAAgBxAMCyAKQSBqJAAgCCEGDAELQoCAgIDgACEGIAAgCUEYaiABQdoAEIcBIgVFDQAgCSkDGCEHIAUtABBFBEAgACAHEAwgAEGpNkEAEBYMAQsgBxASBEAgACAFKQMAIAIgAyAEECQhBgwBCyAAIAMgBBCRAyIIEA1FBEAgBSkDACEBIAkgCDcDECAJIAI3AwggCSABNwMAIAAgByAFKQMIQQMgCRAkIQYLIAAgBxAMIAAgCBAMCyAJQSBqJAAgBgsFACAAmwuDAgMCfAJ/AX4gAL0iBUIgiKdB/////wdxIgNBgIDA/wdPBEAgACAAoA8LQZPx/dQCIQQCQCADQf//P00EQEGT8f3LAiEEIABEAAAAAAAAUEOivSIFQiCIp0H/////B3EiA0UNAQsgBUKAgICAgICAgIB/gyADQQNuIARqrUIghoS/IgIgAqIgAiAAo6IiASABIAGioiABRNft5NQAsMI/okTZUee+y0Tov6CiIAEgAUTC1klKYPH5P6JEICTwkuAo/r+gokSS5mEP5gP+P6CgIAKivUKAgICACHxCgICAgHyDvyIBIAAgASABoqMiACABoSABIAGgIACgo6IgAaAhAAsgAAuHAQMBfgF/AXwgAL0iAUL///////////8Ag78hAAJAAnwgAUI0iKdB/w9xIgJB/QdNBEAgAkHfB0kNAiAAIACgIgMgAyAAokQAAAAAAADwPyAAoaOgDAELIABEAAAAAAAA8D8gAKGjIgAgAKALELMDRAAAAAAAAOA/oiEACyAAmiAAIAFCAFMbC6gDAgV/AX4gAL1C////////////AINCgYCAgICAgPj/AFQgAb1C////////////AINCgICAgICAgPj/AFhxRQRAIAAgAaAPCyABvSIHQiCIpyICQYCAwP8DayAHpyIFckUEQCAAELIEDwsgAkEedkECcSIGIAC9IgdCP4inciEDAkAgB0IgiKdB/////wdxIgQgB6dyRQRAAkACQCADQQJrDgIAAQMLRBgtRFT7IQlADwtEGC1EVPshCcAPCyACQf////8HcSICIAVyRQRARBgtRFT7Ifk/IACmDwsCQCACQYCAwP8HRgRAIARBgIDA/wdHDQEgA0EDdEHQhARqKwMADwsgBEGAgMD/B0cgAkGAgIAgaiAET3FFBEBEGC1EVPsh+T8gAKYPCwJ8IAYEQEQAAAAAAAAAACAEQYCAgCBqIAJJDQEaCyAAIAGjmRCyBAshAAJAAkACQCADDgMEAAECCyAAmg8LRBgtRFT7IQlAIABEB1wUMyamobygoQ8LIABEB1wUMyamobygRBgtRFT7IQnAoA8LIANBA3RB8IQEaisDACEACyAAC7IBAwF+AX8BfCAAvSIBQv///////////wCDvyEAAkAgAUI0iKdB/w9xIgJBmQhPBEAgABDaAkTvOfr+Qi7mP6AhAAwBCyACQYAITwRAIAAgAKBEAAAAAAAA8D8gACAAokQAAAAAAADwP6CfIACgo6AQ2gIhAAwBCyACQeUHSQ0AIAAgAKIiAyADRAAAAAAAAPA/oJ9EAAAAAAAA8D+goyAAoBCzAyEACyAAmiAAIAFCAFMbC7kCAwF/A3wBfiAAvSIFQiCIp0H/////B3EiAUGAgMD/A08EQCAFpyABQYCAwP8Da3JFBEAgAEQYLURU+yH5P6JEAAAAAAAAcDigDwtEAAAAAAAAAAAgACAAoaMPCwJAIAFB/////gNNBEAgAUGAgEBqQYCAgPIDSQ0BIAAgACAAohCnAqIgAKAPC0QAAAAAAADwPyAAmaFEAAAAAAAA4D+iIgOfIQAgAxCnAiEEAnwgAUGz5rz/A08EQEQYLURU+yH5PyAAIASiIACgIgAgAKBEB1wUMyamkbygoQwBC0QYLURU+yHpPyAAvUKAgICAcIO/IgIgAqChIAAgAKAgBKJEB1wUMyamkTwgAyACIAKioSAAIAKgoyIAIACgoaGhRBgtRFT7Iek/oAsiAJogACAFQgBTGyEACyAAC3YBAX8gAL1CNIinQf8PcSIBQf8HTQRAIABEAAAAAAAA8L+gIgAgACAAoiAAIACgoJ+gELMDDwsgAUGYCE0EQCAAIACgRAAAAAAAAPC/IAAgAKJEAAAAAAAA8L+gnyAAoKOgENoCDwsgABDaAkTvOfr+Qi7mP6ALWgIBfwF+AkBBsLMEKAIABEBBtLMEKAIAIQIMAQtBsLMEENYFIgI2AgBBtLMEIAIQ4AQiAjYCAAsgAiAAIAAQQ0Gt7wAQtgUiAyABEKcDGkG0swQoAgAgAxAMCwuvpgRRAEGACAvheygpe30AKCl7c3VwZXIoLi4uYXJndW1lbnRzKTt9ACgpIHsKICAgIFtuYXRpdmUgY29kZV0KfQBjYW5ub3QgbWl4ID8/IHdpdGggJiYgb3IgfHwAcHJveHk6IHByb3BlcnR5IG5vdCBwcmVzZW50IGluIHRhcmdldCB3ZXJlIHJldHVybmVkIGJ5IG5vbiBleHRlbnNpYmxlIHByb3h5AHJldm9rZWQgcHJveHkAUHJveHkAYWRkX3Byb3BlcnR5AHByb3h5OiBjYW5ub3Qgc2V0IHByb3BlcnR5AG5vIHNldHRlciBmb3IgcHJvcGVydHkAdmFsdWUgaGFzIG5vIHByb3BlcnR5AGNvdWxkIG5vdCBkZWxldGUgcHJvcGVydHkAcHJveHk6IGR1cGxpY2F0ZSBwcm9wZXJ0eQBKU19EZWZpbmVBdXRvSW5pdFByb3BlcnR5AGhhc093blByb3BlcnR5AHByb3h5OiBpbmNvbnNpc3RlbnQgZGVsZXRlUHJvcGVydHkAcHJveHk6IGluY29uc2lzdGVudCBkZWZpbmVQcm9wZXJ0eQBKU19EZWZpbmVQcm9wZXJ0eQAhbXItPmVtcHR5AGluZmluaXR5AEluZmluaXR5AG91dCBvZiBtZW1vcnkAdW5rbm93biB1bmljb2RlIGdlbmVyYWwgY2F0ZWdvcnkAR2VuZXJhbF9DYXRlZ29yeQBldmVyeQBhbnkAYXBwbHkAJyVzJyBpcyByZWFkLW9ubHkAZXhwZWN0aW5nIGNhdGNoIG9yIGZpbmFsbHkAc3RpY2t5AHN0cmluZ2lmeQBzdWJhcnJheQBlbXB0eSBhcnJheQBub24gaW50ZWdlciBpbmRleCBpbiB0eXBlZCBhcnJheQBuZWdhdGl2ZSBpbmRleCBpbiB0eXBlZCBhcnJheQBvdXQtb2YtYm91bmQgaW5kZXggaW4gdHlwZWQgYXJyYXkAY2Fubm90IGNyZWF0ZSBudW1lcmljIGluZGV4IGluIHR5cGVkIGFycmF5AGlzQXJyYXkAVHlwZWRBcnJheQBnZXREYXkAZ2V0VVRDRGF5AGpzX2dldF9hdG9tX2luZGV4AGludmFsaWQgYXJyYXkgaW5kZXgAb3V0LW9mLWJvdW5kIG51bWVyaWMgaW5kZXgASlNfQXRvbUlzQXJyYXlJbmRleABmaW5kSW5kZXgAaW52YWxpZCBleHBvcnQgc3ludGF4AGludmFsaWQgYXNzaWdubWVudCBzeW50YXgAbWF4AFx1JTA0eABpbnZhbGlkIG9wY29kZTogcGM9JXUgb3Bjb2RlPTB4JTAyeAAtKyAgIDBYMHgALTBYKzBYIDBYLTB4KzB4IDB4AGxpbmUgdGVybWluYXRvciBub3QgYWxsb3dlZCBhZnRlciB0aHJvdwBwb3cAbm93AHN0YWNrIG92ZXJmbG93AG11c3QgYmUgY2FsbGVkIHdpdGggbmV3AGlzVmlldwBEYXRhVmlldwByYXcAJXUAY2xhc3MgZGVjbGFyYXRpb25zIGNhbid0IGFwcGVhciBpbiBzaW5nbGUtc3RhdGVtZW50IGNvbnRleHQAZnVuY3Rpb24gZGVjbGFyYXRpb25zIGNhbid0IGFwcGVhciBpbiBzaW5nbGUtc3RhdGVtZW50IGNvbnRleHQAbGV4aWNhbCBkZWNsYXJhdGlvbnMgY2FuJ3QgYXBwZWFyIGluIHNpbmdsZS1zdGF0ZW1lbnQgY29udGV4dABkdXBsaWNhdGUgYXJndW1lbnQgbmFtZXMgbm90IGFsbG93ZWQgaW4gdGhpcyBjb250ZXh0AGR1cGxpY2F0ZSBwYXJhbWV0ZXIgbmFtZXMgbm90IGFsbG93ZWQgaW4gdGhpcyBjb250ZXh0AGltcG9ydC5tZXRhIG5vdCBzdXBwb3J0ZWQgaW4gdGhpcyBjb250ZXh0AEpTX0ZyZWVDb250ZXh0AEpTQ29udGV4dABqc19tYXBfaXRlcmF0b3JfbmV4dABqc19hc3luY19nZW5lcmF0b3JfcmVzdW1lX25leHQAdW5leHBlY3RlZCBlbmQgb2YgaW5wdXQAdHQAZXhwb3J0ZWQgdmFyaWFibGUgJyVzJyBkb2VzIG5vdCBleGlzdABwcml2YXRlIGNsYXNzIGZpZWxkICclcycgZG9lcyBub3QgZXhpc3QAdGVzdABhc3NpZ25tZW50IHJlc3QgcHJvcGVydHkgbXVzdCBiZSBsYXN0AHNxcnQAc29ydABjYnJ0AHRyaW1TdGFydABwYWRTdGFydAB1bmtub3duIHVuaWNvZGUgc2NyaXB0AFNjcmlwdABoeXBvdABmcmVlX3plcm9fcmVmY291bnQAc3RyX2luZGV4ID09IG51bV9rZXlzX2NvdW50ICsgc3RyX2tleXNfY291bnQAbnVtX2luZGV4ID09IG51bV9rZXlzX2NvdW50AHN5bV9pbmRleCA9PSBhdG9tX2NvdW50AGxhYmVsID49IDAgJiYgbGFiZWwgPCBzLT5sYWJlbF9jb3VudABsYWIxID49IDAgJiYgbGFiMSA8IHMtPmxhYmVsX2NvdW50AHZhbCA8IHMtPmNhcHR1cmVfY291bnQAdmFsMiA8IHMtPmNhcHR1cmVfY291bnQAaW52YWxpZCByZXBlYXQgY291bnQAaW52YWxpZCByZXBldGl0aW9uIGNvdW50AGZvbnQAaW52YWxpZCBjb2RlIHBvaW50AGZyb21Db2RlUG9pbnQAaW52YWxpZCBoaW50AGVuY29kZVVSSUNvbXBvbmVudABkZWNvZGVVUklDb21wb25lbnQAdW5leHBlY3RlZCBlbmQgb2YgY29tbWVudABpbnZhbGlkIHN3aXRjaCBzdGF0ZW1lbnQAcGFyc2VJbnQAZHVwbGljYXRlIGRlZmF1bHQAc3BsaXQAZXhwZWN0aW5nIGhleCBkaWdpdAB0cmltUmlnaHQAcmVkdWNlUmlnaHQAdW5zaGlmdAB0cmltTGVmdABpbnZhbGlkIG9mZnNldABpbnZhbGlkIGJ5dGVPZmZzZXQAZ2V0VGltZXpvbmVPZmZzZXQAcmVzb2x2aW5nIGZ1bmN0aW9uIGFscmVhZHkgc2V0AHByb3h5OiBpbmNvbnNpc3RlbnQgc2V0AGZpbmRfanVtcF90YXJnZXQAZXhwZWN0aW5nIHRhcmdldABpbnZhbGlkIGRlc3RydWN0dXJpbmcgdGFyZ2V0AHByb3h5OiBpbmNvbnNpc3RlbnQgZ2V0AFdlYWtTZXQAY29uc3RydWN0AEpTX0ZyZWVBdG9tU3RydWN0AHVzZSBzdHJpY3QAUmVmbGVjdAByZWplY3QAbm90IGFuIEFzeW5jR2VuZXJhdG9yIG9iamVjdABjYW5ub3QgY29udmVydCB0byBvYmplY3QAaW52YWxpZCBicmFuZCBvbiBvYmplY3QAb3BlcmFuZCAncHJvdG90eXBlJyBwcm9wZXJ0eSBpcyBub3QgYW4gb2JqZWN0AHJlY2VpdmVyIGlzIG5vdCBhbiBvYmplY3QAaXRlcmF0b3IgbXVzdCByZXR1cm4gYW4gb2JqZWN0AG5vdCBhIERhdGUgb2JqZWN0AG5vdCBhIG9iamVjdABKU09iamVjdABwYXJzZUZsb2F0AGZsYXQAbm90aGluZyB0byByZXBlYXQAY29uY2F0AGNvZGVQb2ludEF0AGNoYXJBdABjaGFyQ29kZUF0AGtleXMAcHJveHk6IHRhcmdldCBwcm9wZXJ0eSBtdXN0IGJlIHByZXNlbnQgaW4gcHJveHkgb3duS2V5cwAgIGZhc3QgYXJyYXlzAGV4cG9ydCAnJXMnIGluIG1vZHVsZSAnJXMnIGlzIGFtYmlndW91cwBwcml2YXRlIGNsYXNzIGZpZWxkICclcycgYWxyZWFkeSBleGlzdHMAdG9vIG1hbnkgYXJndW1lbnRzAFRvbyBtYW55IGNhbGwgYXJndW1lbnRzACAgZWxlbWVudHMAaW52YWxpZCBudW1iZXIgb2YgZGlnaXRzAGJpbmFyeSBvYmplY3RzAGludmFsaWQgcHJvcGVydHkgYWNjZXNzAGpzX29wX2RlZmluZV9jbGFzcwBmZC0+Ynl0ZV9jb2RlLmJ1ZltkZWZpbmVfY2xhc3NfcG9zXSA9PSBPUF9kZWZpbmVfY2xhc3MAX19nZXRDbGFzcwBzZXRIb3VycwBnZXRIb3VycwBzZXRVVENIb3VycwBnZXRVVENIb3VycwBnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzAHRvbyBtYW55IGltYnJpY2F0ZWQgcXVhbnRpZmllcnMAdW5pY29kZV9wcm9wX29wcwBhY29zAGZvciBhd2FpdCBpcyBvbmx5IHZhbGlkIGluIGFzeW5jaHJvbm91cyBmdW5jdGlvbnMAbmV3LnRhcmdldCBvbmx5IGFsbG93ZWQgd2l0aGluIGZ1bmN0aW9ucwBieXRlY29kZSBmdW5jdGlvbnMAQyBmdW5jdGlvbnMAcHJveHk6IGluY29uc2lzdGVudCBwcmV2ZW50RXh0ZW5zaW9ucwBTY3JpcHRfRXh0ZW5zaW9ucwBhdG9tcwBwcm94eTogcHJvcGVydGllcyBtdXN0IGJlIHN0cmluZ3Mgb3Igc3ltYm9scwBnZXRPd25Qcm9wZXJ0eVN5bWJvbHMAcmVzb2x2ZV9sYWJlbHMASlNfRXZhbFRoaXMAc3RyaW5ncwBpbnZhbGlkIGRlc2NyaXB0b3IgZmxhZ3MAaW52YWxpZCByZWd1bGFyIGV4cHJlc3Npb24gZmxhZ3MAdmFsdWVzAHNldE1pbnV0ZXMAZ2V0TWludXRlcwBzZXRVVENNaW51dGVzAGdldFVUQ01pbnV0ZXMAdG9vIG1hbnkgY2FwdHVyZXMAICBzaGFwZXMAZ2V0T3duUHJvcGVydHlOYW1lcwBnY19mcmVlX2N5Y2xlcwBhZGRfZXZhbF92YXJpYWJsZXMAcmVzb2x2ZV92YXJpYWJsZXMAdG9vIG1hbnkgbG9jYWwgdmFyaWFibGVzAHRvbyBtYW55IGNsb3N1cmUgdmFyaWFibGVzAGNvbXBhY3RfcHJvcGVydGllcwAgIHByb3BlcnRpZXMAZGVmaW5lUHJvcGVydGllcwBlbnRyaWVzAGZyb21FbnRyaWVzAHRvbyBtYW55IHJhbmdlcwBpbmNsdWRlcwBzZXRNaWxsaXNlY29uZHMAZ2V0TWlsbGlzZWNvbmRzAHNldFVUQ01pbGxpc2Vjb25kcwBnZXRVVENNaWxsaXNlY29uZHMAc2V0U2Vjb25kcwBnZXRTZWNvbmRzAHNldFVUQ1NlY29uZHMAZ2V0VVRDU2Vjb25kcwBpdGFsaWNzAGFicwBwcm94eTogaW5jb25zaXN0ZW50IGhhcwAlLipzACAoJXMAc2V0ICVzAGdldCAlcwAgICAgYXQgJXMAbm90IGEgJXMAdW5zdXBwb3J0ZWQga2V5d29yZDogJXMAc3Vic3RyAHByb3h5OiBpbmNvbnNpc3RlbnQgZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yAHN1cGVyKCkgaXMgb25seSB2YWxpZCBpbiBhIGRlcml2ZWQgY2xhc3MgY29uc3RydWN0b3IAcGFyZW50IGNsYXNzIG11c3QgYmUgY29uc3RydWN0b3IAbm90IGEgY29uc3RydWN0b3IAQXJyYXkgSXRlcmF0b3IAU2V0IEl0ZXJhdG9yAE1hcCBJdGVyYXRvcgBSZWdFeHAgU3RyaW5nIEl0ZXJhdG9yAG5vdCBhbiBBc3luYy1mcm9tLVN5bmMgSXRlcmF0b3IAY2Fubm90IGludm9rZSBhIHJ1bm5pbmcgZ2VuZXJhdG9yAG5vdCBhIGdlbmVyYXRvcgBBc3luY0dlbmVyYXRvcgBzeW50YXggZXJyb3IAU3ludGF4RXJyb3IARXZhbEVycm9yAEludGVybmFsRXJyb3IAQWdncmVnYXRlRXJyb3IAVHlwZUVycm9yAFJhbmdlRXJyb3IAUmVmZXJlbmNlRXJyb3IAVVJJRXJyb3IAZmxvb3IAZm9udGNvbG9yAGFuY2hvcgBmb3IAa2V5Rm9yAGV4cGVjdGluZyBzdXJyb2dhdGUgcGFpcgBhIGRlY2xhcmF0aW9uIGluIHRoZSBoZWFkIG9mIGEgZm9yLSVzIGxvb3AgY2FuJ3QgaGF2ZSBhbiBpbml0aWFsaXplcgAnYXJndW1lbnRzJyBpZGVudGlmaWVyIGlzIG5vdCBhbGxvd2VkIGluIGNsYXNzIGZpZWxkIGluaXRpYWxpemVyAGludmFsaWQgbnVtYmVyIG9mIGFyZ3VtZW50cyBmb3IgZ2V0dGVyIG9yIHNldHRlcgBpbnZhbGlkIHNldHRlcgBpbnZhbGlkIGdldHRlcgBmaWx0ZXIAbWlzc2luZyBmb3JtYWwgcGFyYW1ldGVyACJ1c2Ugc3RyaWN0IiBub3QgYWxsb3dlZCBpbiBmdW5jdGlvbiB3aXRoIGRlZmF1bHQgb3IgZGVzdHJ1Y3R1cmluZyBwYXJhbWV0ZXIAaW52YWxpZCBjaGFyYWN0ZXIAdW5leHBlY3RlZCBjaGFyYWN0ZXIAcHJpdmF0ZSBjbGFzcyBmaWVsZCBmb3JiaWRkZW4gYWZ0ZXIgc3VwZXIAaW52YWxpZCByZWRlZmluaXRpb24gb2YgbGV4aWNhbCBpZGVudGlmaWVyACdsZXQnIGlzIG5vdCBhIHZhbGlkIGxleGljYWwgaWRlbnRpZmllcgBpbnZhbGlkIHJlZGVmaW5pdGlvbiBvZiBnbG9iYWwgaWRlbnRpZmllcgB5aWVsZCBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAJyVzJyBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAb3RoZXIAYXRvbTFfaXNfaW50ZWdlciAmJiBhdG9tMl9pc19pbnRlZ2VyAGlzSW50ZWdlcgBpc1NhZmVJbnRlZ2VyAGJ1ZmZlcgBTaGFyZWRBcnJheUJ1ZmZlcgBjYW5ub3QgdXNlIGlkZW50aWNhbCBBcnJheUJ1ZmZlcgBjYW5ub3QgY29udmVydCBzeW1ib2wgdG8gbnVtYmVyAG5vdCBhIG51bWJlcgBsaW5lTnVtYmVyAG1hbGZvcm1lZCB1bmljb2RlIGNoYXIAY2xlYXIAc2V0WWVhcgBnZXRZZWFyAHNldEZ1bGxZZWFyAGdldEZ1bGxZZWFyAHNldFVUQ0Z1bGxZZWFyAGdldFVUQ0Z1bGxZZWFyAHVuZXhwZWN0ZWQgbGluZSB0ZXJtaW5hdG9yIGluIHJlZ2V4cAB1bmV4cGVjdGVkIGVuZCBvZiByZWdleHAAUmVnRXhwAHN1cABpbnZhbGlkIGdyb3VwAHBvcABjb250aW51ZSBtdXN0IGJlIGluc2lkZSBsb29wAGR1bXAAbnVtX2tleXNfY21wAHVzZSBzdHJpcABtYXAAZmxhdE1hcABXZWFrTWFwAGV4cGVjdGluZyAneycgYWZ0ZXIgXHAAbG9nMXAAaGFzT3duAGl0ZXJhdG9yX2Nsb3NlX3JldHVybgBwcm9taXNlIHNlbGYgcmVzb2x1dGlvbgBvdXQgb2YgbWVtb3J5IGluIHJlZ2V4cCBleGVjdXRpb24AZGVzY3JpcHRpb24AcHJveHk6IGRlZmluZVByb3BlcnR5IGV4Y2VwdGlvbgBqc19hc3luY19nZW5lcmF0b3JfcmVzb2x2ZV9mdW5jdGlvbgBqc19jcmVhdGVfZnVuY3Rpb24Ac2V0L2FkZCBpcyBub3QgYSBmdW5jdGlvbgByZXR1cm4gbm90IGluIGEgZnVuY3Rpb24AQXN5bmNHZW5lcmF0b3JGdW5jdGlvbgBjYWxsRXh0ZXJuYWxGdW5jdGlvbgBBc3luY0Z1bmN0aW9uAGF3YWl0IGluIGRlZmF1bHQgZXhwcmVzc2lvbgB5aWVsZCBpbiBkZWZhdWx0IGV4cHJlc3Npb24AaW52YWxpZCBkZWNpbWFsIGVzY2FwZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AYmFjayByZWZlcmVuY2Ugb3V0IG9mIHJhbmdlIGluIHJlZ3VsYXIgZXhwcmVzc2lvbgBpbnZhbGlkIGVzY2FwZSBzZXF1ZW5jZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AZXhwZWN0ZWQgJ29mJyBvciAnaW4nIGluIGZvciBjb250cm9sIGV4cHJlc3Npb24AdG9vIGNvbXBsaWNhdGVkIGRlc3RydWN0dXJpbmcgZXhwcmVzc2lvbgBleHBlY3RlZCAnfScgYWZ0ZXIgdGVtcGxhdGUgZXhwcmVzc2lvbgB0b1ByZWNpc2lvbgBhc2luAGpvaW4AbWluAGNvcHlXaXRoaW4AdGVtcGxhdGUgbGl0ZXJhbCBjYW5ub3QgYXBwZWFyIGluIGFuIG9wdGlvbmFsIGNoYWluAGNpcmN1bGFyIHByb3RvdHlwZSBjaGFpbgBhc3NpZ24AaXNGcm96ZW4AbWFya19jaGlsZHJlbgAocG9zICsgbGVuKSA8PSBiY19idWZfbGVuAHVuZXhwZWN0ZWQgZWxsaXBzaXMgdG9rZW4AdGhlbgBzZXR0ZXIgaXMgZm9yYmlkZGVuAG51bGwgb3IgdW5kZWZpbmVkIGFyZSBmb3JiaWRkZW4AYXRhbgBuYW4Abm90IGEgYm9vbGVhbgBCb29sZWFuAGdjX3NjYW4AYmFkIG5vcm1hbGl6YXRpb24gZm9ybQBKU19OZXdTeW1ib2xGcm9tQXRvbQBmcm9tAHJhbmRvbQB0cmltAGltdWwAbm90IGEgc3ltYm9sAFN5bWJvbABSZWdFeHAgZXhlYyBtZXRob2QgbXVzdCByZXR1cm4gYW4gb2JqZWN0IG9yIG51bGwAcGFyZW50IHByb3RvdHlwZSBtdXN0IGJlIGFuIG9iamVjdCBvciBudWxsAGNhbm5vdCBzZXQgcHJvcGVydHkgJyVzJyBvZiBudWxsAGNhbm5vdCByZWFkIHByb3BlcnR5ICclcycgb2YgbnVsbABOdWxsAGZpbGwAbmV3IEFycmF5QnVmZmVyIGlzIHRvbyBzbWFsbABUeXBlZEFycmF5IGxlbmd0aCBpcyB0b28gc21hbGwAY2FsbABkb3RBbGwAbWF0Y2hBbGwAcmVwbGFjZUFsbABjZWlsAHVwZGF0ZV9sYWJlbABiY19idWZbcG9zXSA9PSBPUF9sYWJlbABldmFsAGludmFsaWQgbnVtYmVyIGxpdGVyYWwAbWFsZm9ybWVkIGVzY2FwZSBzZXF1ZW5jZSBpbiBzdHJpbmcgbGl0ZXJhbABKU19TZXRQcm9wZXJ0eUludGVybmFsAEpTX0dldE93blByb3BlcnR5TmFtZXNJbnRlcm5hbABfX0pTX0V2YWxJbnRlcm5hbAB0b0V4cG9uZW50aWFsAHNlYWwAZ2xvYmFsAGJsaW5rAF9fZGF0ZV9jbG9jawBzdGFjawBscmVfZXhlY19iYWNrdHJhY2sAcy0+aXNfd2VhawBpAHNldE1vbnRoAGdldE1vbnRoAHNldFVUQ01vbnRoAGdldFVUQ01vbnRoAGludmFsaWQga2V5d29yZDogd2l0aABzdGFydHNXaXRoAGVuZHNXaXRoAHByb3AgPT0gSlNfQVRPTV9sZW5ndGgAaW52YWxpZCBhcnJheSBsZW5ndGgAaW52YWxpZCBhcnJheSBidWZmZXIgbGVuZ3RoAGludmFsaWQgbGVuZ3RoAGludmFsaWQgYnl0ZUxlbmd0aABNYXRoAHB1c2gAYWNvc2gASlNfUmVzaXplQXRvbUhhc2gAYXNpbmgAYXRhbmgAYnJlYWsgbXVzdCBiZSBpbnNpZGUgbG9vcCBvciBzd2l0Y2gAbWF0Y2gAY2F0Y2gAc2VhcmNoAGZvckVhY2gAbG9nAEFycmF5IHRvbyBsb25nAHN0cmluZyB0b28gbG9uZwBBcnJheSBsb28gbG9uZwBzdWJzdHJpbmcAY2Fubm90IGNvbnZlcnQgc3ltYm9sIHRvIHN0cmluZwB1bmV4cGVjdGVkIGVuZCBvZiBzdHJpbmcAbm90IGEgc3RyaW5nAGludmFsaWQgY2hhcmFjdGVyIGluIGEgSlNPTiBzdHJpbmcAdG9TdHJpbmcAdG9EYXRlU3RyaW5nAHRvTG9jYWxlRGF0ZVN0cmluZwB0b1RpbWVTdHJpbmcAdG9Mb2NhbGVUaW1lU3RyaW5nAHRvTG9jYWxlU3RyaW5nAHRvR01UU3RyaW5nAEpTU3RyaW5nAHRvSVNPU3RyaW5nAHRvVVRDU3RyaW5nAGR1cGxpY2F0ZSBpbXBvcnQgYmluZGluZwBpbnZhbGlkIGltcG9ydCBiaW5kaW5nAGJpZwByZWdleHAgbXVzdCBoYXZlIHRoZSAnZycgZmxhZwBvZgBpbmYAZGlmZiA9PSAoaW50OF90KWRpZmYAZGlmZiA9PSAoaW50MTZfdClkaWZmAGhyZWYAZ2NfZGVjcmVmAGZyZWVfdmFyX3JlZgBvcHRpbWl6ZV9zY29wZV9tYWtlX2dsb2JhbF9yZWYAcmVzZXRfd2Vha19yZWYAZGVsZXRlX3dlYWtfcmVmAG9wdGltaXplX3Njb3BlX21ha2VfcmVmAGluZGV4T2YAbGFzdEluZGV4T2YAdmFsdWVPZgBzZXRQcm90b3R5cGVPZgBnZXRQcm90b3R5cGVPZgBpc1Byb3RvdHlwZU9mACUuKmYAZm9udHNpemUAbmV3X3NpemUgPD0gc2gtPnByb3Bfc2l6ZQBkZXNjciA8IHJ0LT5hdG9tX3NpemUAYXRvbSA8IHJ0LT5hdG9tX3NpemUAY29tcHV0ZV9zdGFja19zaXplAG4gPCBidWZfc2l6ZQBub3JtYWxpemUAZnJlZXplAHJlc29sdmUAdG9QcmltaXRpdmUAcHV0X2x2YWx1ZQB1bmtub3duIHVuaWNvZGUgcHJvcGVydHkgdmFsdWUAcmVzdCBlbGVtZW50IGNhbm5vdCBoYXZlIGEgZGVmYXVsdCB2YWx1ZQBpbnZhbGlkIHJldCB2YWx1ZQBfX0pTX0F0b21Ub1ZhbHVlAF9fcXVvdGUAaXNGaW5pdGUAZGVsZXRlAGNyZWF0ZQBzZXREYXRlAGdldERhdGUAc2V0VVRDRGF0ZQBnZXRVVENEYXRlAEludmFsaWQgRGF0ZQByZXZlcnNlAHBhcnNlAHByb3h5IHByZXZlbnRFeHRlbnNpb25zIGhhbmRsZXIgcmV0dXJuZWQgZmFsc2UAUHJvbWlzZQB0b0xvd2VyQ2FzZQB0b0xvY2FsZUxvd2VyQ2FzZQB0b1VwcGVyQ2FzZQB0b0xvY2FsZVVwcGVyQ2FzZQBpZ25vcmVDYXNlAGxvY2FsZUNvbXBhcmUAcHJveHk6IGluY29uc2lzdGVudCBwcm90b3R5cGUAcHJveHk6IGJhZCBwcm90b3R5cGUAbm90IGEgcHJvdG90eXBlAGludmFsaWQgb2JqZWN0IHR5cGUAdW5lc2NhcGUAbm9uZQByZXN0IGVsZW1lbnQgbXVzdCBiZSB0aGUgbGFzdCBvbmUAbXVsdGlsaW5lACAgcGMybGluZQBzb21lAEpTX0ZyZWVSdW50aW1lAEpTUnVudGltZQBzZXRUaW1lAGdldFRpbWUAc2V0X29iamVjdF9uYW1lAGV4cGVjdGluZyBwcm9wZXJ0eSBuYW1lAHVua25vd24gdW5pY29kZSBwcm9wZXJ0eSBuYW1lAGludmFsaWQgcHJvcGVydHkgbmFtZQBkdXBsaWNhdGUgX19wcm90b19fIHByb3BlcnR5IG5hbWUAaW52YWxpZCByZWRlZmluaXRpb24gb2YgcGFyYW1ldGVyIG5hbWUAZXhwZWN0aW5nIGdyb3VwIG5hbWUAZHVwbGljYXRlIGdyb3VwIG5hbWUAaW52YWxpZCBncm91cCBuYW1lAGR1cGxpY2F0ZSBsYWJlbCBuYW1lAGludmFsaWQgZmlyc3QgY2hhcmFjdGVyIG9mIHByaXZhdGUgbmFtZQBpbnZhbGlkIGxleGljYWwgdmFyaWFibGUgbmFtZQBpbnZhbGlkIG1ldGhvZCBuYW1lAGV4cGVjdGluZyBmaWVsZCBuYW1lAGludmFsaWQgZmllbGQgbmFtZQBjbGFzcyBzdGF0ZW1lbnQgcmVxdWlyZXMgYSBuYW1lAGZpbGVOYW1lAGNvbXBpbGUAb2JqZWN0IGlzIG5vdCBleHRlbnNpYmxlAHByb3h5OiBpbmNvbnNpc3RlbnQgaXNFeHRlbnNpYmxlAGNhbm5vdCBoYXZlIHNldHRlci9nZXR0ZXIgYW5kIHZhbHVlIG9yIHdyaXRhYmxlAHByb3BlcnR5IGlzIG5vdCBjb25maWd1cmFibGUAdmFsdWUgaXMgbm90IGl0ZXJhYmxlAHByb3BlcnR5SXNFbnVtZXJhYmxlAG1pc3NpbmcgaW5pdGlhbGl6ZXIgZm9yIGNvbnN0IHZhcmlhYmxlAGxleGljYWwgdmFyaWFibGUAaW52YWxpZCByZWRlZmluaXRpb24gb2YgYSB2YXJpYWJsZQByZXZvY2FibGUAc3RyaWtlAGludmFsaWQgY2xhc3MgcmFuZ2UAbWVzc2FnZQBhc3luY19mdW5jX2ZyZWUAaW52YWxpZCBsdmFsdWUgaW4gc3RyaWN0IG1vZGUAaW52YWxpZCB2YXJpYWJsZSBuYW1lIGluIHN0cmljdCBtb2RlAGNhbm5vdCBkZWxldGUgYSBkaXJlY3QgcmVmZXJlbmNlIGluIHN0cmljdCBtb2RlAG9jdGFsIGVzY2FwZSBzZXF1ZW5jZXMgYXJlIG5vdCBhbGxvd2VkIGluIHN0cmljdCBtb2RlAG9jdGFsIGxpdGVyYWxzIGFyZSBkZXByZWNhdGVkIGluIHN0cmljdCBtb2RlAHVuaWNvZGUAICBieXRlY29kZQBKU0Z1bmN0aW9uQnl0ZWNvZGUAc2tpcF9kZWFkX2NvZGUAaW52YWxpZCBhcmd1bWVudCBuYW1lIGluIHN0cmljdCBjb2RlAGludmFsaWQgZnVuY3Rpb24gbmFtZSBpbiBzdHJpY3QgY29kZQBpbnZhbGlkIHJlZGVmaW5pdGlvbiBvZiBnbG9iYWwgaWRlbnRpZmllciBpbiBtb2R1bGUgY29kZQBpbXBvcnQubWV0YSBvbmx5IHZhbGlkIGluIG1vZHVsZSBjb2RlAGZyb21DaGFyQ29kZQBpbnZhbGlkIGZvciBpbi9vZiBsZWZ0IGhhbmQtc2lkZQBpbnZhbGlkIGFzc2lnbm1lbnQgbGVmdC1oYW5kIHNpZGUAcmVkdWNlAHNvdXJjZQAndGhpcycgY2FuIGJlIGluaXRpYWxpemVkIG9ubHkgb25jZQBwcm9wZXJ0eSBjb25zdHJ1Y3RvciBhcHBlYXJzIG1vcmUgdGhhbiBvbmNlAGludmFsaWQgVVRGLTggc2VxdWVuY2UAY2lyY3VsYXIgcmVmZXJlbmNlAHNsaWNlAHNwbGljZQByYWNlAHJlcGxhY2UAJSsuKmUAdW5leHBlY3RlZCAnYXdhaXQnIGtleXdvcmQAdW5leHBlY3RlZCAneWllbGQnIGtleXdvcmQAbWFwX2RlY3JlZl9yZWNvcmQAaXRlcmF0b3IgZG9lcyBub3QgaGF2ZSBhIHRocm93IG1ldGhvZABvYmplY3QgbmVlZHMgdG9JU09TdHJpbmcgbWV0aG9kACdzdXBlcicgaXMgb25seSB2YWxpZCBpbiBhIG1ldGhvZABmcm91bmQAYnJlYWsvY29udGludWUgbGFiZWwgbm90IGZvdW5kAG91dCBvZiBib3VuZABmaW5kAGJpbmQAaW52YWxpZCBpbmRleCBmb3IgYXBwZW5kAGV4dHJhbmVvdXMgY2hhcmFjdGVycyBhdCB0aGUgZW5kAHVuZXhwZWN0ZWQgZGF0YSBhdCB0aGUgZW5kAHVuZXhwZWN0ZWQgZW5kAGludmFsaWQgaW5jcmVtZW50L2RlY3JlbWVudCBvcGVyYW5kAGludmFsaWQgJ2luc3RhbmNlb2YnIHJpZ2h0IG9wZXJhbmQAaW52YWxpZCAnaW4nIG9wZXJhbmQAdHJpbUVuZABwYWRFbmQAYm9sZAAlbGxkAGdjX2RlY3JlZl9jaGlsZAByZXNvbHZlX3Njb3BlX3ByaXZhdGVfZmllbGQAY2Fubm90IGRlbGV0ZSBhIHByaXZhdGUgY2xhc3MgZmllbGQAZXhwZWN0aW5nIDxicmFuZD4gcHJpdmF0ZSBmaWVsZAAlcyBpcyBub3QgaW5pdGlhbGl6ZWQAZml4ZWQAdG9GaXhlZABzZXRfb2JqZWN0X25hbWVfY29tcHV0ZWQAcmVnZXggbm90IHN1cHBvcnRlZABldmFsIGlzIG5vdCBzdXBwb3J0ZWQAUmVnRXhwIGFyZSBub3Qgc3VwcG9ydGVkAGludGVycnVwdGVkACVzIG9iamVjdCBleHBlY3RlZABpZGVudGlmaWVyIGV4cGVjdGVkAGJ5dGVjb2RlIGZ1bmN0aW9uIGV4cGVjdGVkAHN0cmluZyBleHBlY3RlZABmcm9tIGNsYXVzZSBleHBlY3RlZABmdW5jdGlvbiBuYW1lIGV4cGVjdGVkAHZhcmlhYmxlIG5hbWUgZXhwZWN0ZWQAbWV0YSBleHBlY3RlZAByZWplY3RlZABtZW1vcnkgYWxsb2NhdGVkAG1lbW9yeSB1c2VkAGRlcml2ZWQgY2xhc3MgY29uc3RydWN0b3IgbXVzdCByZXR1cm4gYW4gb2JqZWN0IG9yIHVuZGVmaW5lZABjYW5ub3Qgc2V0IHByb3BlcnR5ICclcycgb2YgdW5kZWZpbmVkAGNhbm5vdCByZWFkIHByb3BlcnR5ICclcycgb2YgdW5kZWZpbmVkAGZsYWdzIG11c3QgYmUgdW5kZWZpbmVkAFVuZGVmaW5lZABwcml2YXRlIGNsYXNzIGZpZWxkIGlzIGFscmVhZHkgZGVmaW5lZAAnJXMnIGlzIG5vdCBkZWZpbmVkAGdyb3VwIG5hbWUgbm90IGRlZmluZWQAYWxsU2V0dGxlZABmdWxmaWxsZWQAY2Fubm90IGJlIGNhbGxlZABpc1NlYWxlZAAhc2gtPmlzX2hhc2hlZAB2YXJfcmVmLT5pc19kZXRhY2hlZABBcnJheUJ1ZmZlciBpcyBkZXRhY2hlZABhZGQAJSswN2QAJTA0ZAAlMDJkJTAyZAAlMDJkLyUwMmQvJTAqZAAlLjNzICUuM3MgJTAyZCAlMCpkADolZABpbnZhbGlkIHRocm93IHZhciB0eXBlICVkAHNjAGpzX2RlZl9tYWxsb2MAdHJ1bmMAZ2MAZXhlYwAvdG1wL3F1aWNranMvcXVpY2tqcy5jAC90bXAvcXVpY2tqcy9saWJyZWdleHAuYwAvdG1wL3F1aWNranMvbGlidW5pY29kZS5jAHN1YgBwcm9taXNlX3JlYWN0aW9uX2pvYgBqc19wcm9taXNlX3Jlc29sdmVfdGhlbmFibGVfam9iAHJ3YQBfX2xvb2t1cFNldHRlcl9fAF9fZGVmaW5lU2V0dGVyX18AX19sb29rdXBHZXR0ZXJfXwBfX2RlZmluZUdldHRlcl9fAF9fcHJvdG9fXwBbU3ltYm9sLnNwbGl0XQBbU3ltYm9sLnNwZWNpZXNdAFtTeW1ib2wuaXRlcmF0b3JdAFtTeW1ib2wuYXN5bmNJdGVyYXRvcl0AW1N5bWJvbC5tYXRjaEFsbF0AW1N5bWJvbC5tYXRjaF0AW1N5bWJvbC5zZWFyY2hdAFtTeW1ib2wudG9TdHJpbmdUYWddAFtTeW1ib2wudG9QcmltaXRpdmVdAFt1bnN1cHBvcnRlZCB0eXBlXQBbZnVuY3Rpb24gYnl0ZWNvZGVdAFtTeW1ib2wuaGFzSW5zdGFuY2VdAFtTeW1ib2wucmVwbGFjZV0AWwAlMDJkOiUwMmQ6JTAyZC4lMDNkWgBQT1NJVElWRV9JTkZJTklUWQBORUdBVElWRV9JTkZJTklUWQBwLT5jbGFzc19pZCA9PSBKU19DTEFTU19BUlJBWQBzdGFja19sZW4gPCBQT1BfU1RBQ0tfTEVOX01BWAAtJTAyZC0lMDJkVABKU19BdG9tR2V0U3RyUlQAb3Bjb2RlIDwgUkVPUF9DT1VOVABCWVRFU19QRVJfRUxFTUVOVAAlMDJkOiUwMmQ6JTAyZCBHTVQASlNfVkFMVUVfR0VUX1RBRyhzZi0+Y3VyX2Z1bmMpID09IEpTX1RBR19PQkpFQ1QAdmFyX2tpbmQgPT0gSlNfVkFSX1BSSVZBVEVfU0VUVEVSAE1BWF9TQUZFX0lOVEVHRVIATUlOX1NBRkVfSU5URUdFUgBpc05hTgBEYXRlIHZhbHVlIGlzIE5hTgB0b0pTT04ARVBTSUxPTgBOQU4AJTAyZDolMDJkOiUwMmQgJWNNAHMtPmxhYmVsX3Nsb3RzW2xhYmVsXS5maXJzdF9yZWxvYyA9PSBOVUxMAGxhYmVsX3Nsb3RzW2ldLmZpcnN0X3JlbG9jID09IE5VTEwAcHJzICE9IE5VTEwAc2YtPmN1cl9zcCAhPSBOVUxMAHNmICE9IE5VTEwAbXIxICE9IE5VTEwAdmFyX2tpbmQgIT0gSlNfVkFSX05PUk1BTABiLT5mdW5jX2tpbmQgPT0gSlNfRlVOQ19OT1JNQUwAZW5jb2RlVVJJAGRlY29kZVVSSQBQSQBzcGVjaWFsID09IFBVVF9MVkFMVUVfTk9LRUVQIHx8IHNwZWNpYWwgPT0gUFVUX0xWQUxVRV9OT0tFRVBfREVQVEgAcy0+c3RhdGUgPT0gSlNfQVNZTkNfR0VORVJBVE9SX1NUQVRFX0VYRUNVVElORwBJTkYAMDEyMzQ1Njc4OUFCQ0RFRgBTSVpFAE1BWF9WQUxVRQBNSU5fVkFMVUUATkFNRQBldmFsX3R5cGUgPT0gSlNfRVZBTF9UWVBFX0dMT0JBTCB8fCBldmFsX3R5cGUgPT0gSlNfRVZBTF9UWVBFX01PRFVMRQBwLT5nY19vYmpfdHlwZSA9PSBKU19HQ19PQkpfVFlQRV9KU19PQkpFQ1QgfHwgcC0+Z2Nfb2JqX3R5cGUgPT0gSlNfR0NfT0JKX1RZUEVfRlVOQ1RJT05fQllURUNPREUATE9HMkUATE9HMTBFAHMtPnN0YXRlID09IEpTX0FTWU5DX0dFTkVSQVRPUl9TVEFURV9BV0FJVElOR19SRVRVUk4gfHwgcy0+c3RhdGUgPT0gSlNfQVNZTkNfR0VORVJBVE9SX1NUQVRFX0NPTVBMRVRFRABVVEMAPGlucHV0PgA8aW5pdFNjcmlwdD4APGV2YWxTY3JpcHQ+ADxzZXQ+ADxhbm9ueW1vdXM+ADxjb21tRnVuPgA8Y2FsbEV4dGVybmFsRnVuY3Rpb24+ADxudWxsPgAmcXVvdDsAc2V0VWludDgAZ2V0VWludDgAc2V0SW50OABnZXRJbnQ4AG1hbGZvcm1lZCBVVEYtOAByYWRpeCBtdXN0IGJlIGJldHdlZW4gMiBhbmQgMzYAc2V0VWludDE2AGdldFVpbnQxNgBzZXRJbnQxNgBnZXRJbnQxNgBhcmdjID09IDUAc2V0RmxvYXQ2NABnZXRGbG9hdDY0AGFyZ2MgPT0gMwBhdGFuMgBsb2cyAFNRUlQxXzIAU1FSVDIATE4yAGNsejMyAHNldFVpbnQzMgBnZXRVaW50MzIAc2V0SW50MzIAZ2V0SW50MzIAc2V0RmxvYXQzMgBnZXRGbG9hdDMyAHN0YWNrX2xlbiA+PSAyAEpTX0F0b21Jc051bWVyaWNJbmRleDEAanNfZmN2dDEAZXhwbTEAbHMtPmFkZHIgPT0gLTEAc3RhY2tfbGVuID49IDEAcC0+c2hhcGUtPmhlYWRlci5yZWZfY291bnQgPT0gMQBzdGFja19sZW4gPT0gMQBqc19mcmVlX3NoYXBlMABsb2cxMABMTjEwAHAtPnJlZl9jb3VudCA+IDAAdmFyX3JlZi0+aGVhZGVyLnJlZl9jb3VudCA+IDAAc3RhY2tfc2l6ZSA+IDAAY3Bvb2xfaWR4ID49IDAAcnQtPmF0b21fY291bnQgPj0gMABscy0+cmVmX2NvdW50ID49IDAAcy0+aXNfZXZhbCB8fCBzLT5jbG9zdXJlX3Zhcl9jb3VudCA9PSAwAHAtPnJlZl9jb3VudCA9PSAwAGN0eC0+aGVhZGVyLnJlZl9jb3VudCA9PSAwAHNoLT5oZWFkZXIucmVmX2NvdW50ID09IDAAcC0+bWFyayA9PSAwAChwci0+dS5pbml0LnJlYWxtX2FuZF9pZCAmIDMpID09IDAAKG5ld19oYXNoX3NpemUgJiAobmV3X2hhc2hfc2l6ZSAtIDEpKSA9PSAwAGkgIT0gMABzaXplICE9IDAAXiRcLiorPygpW117fXwvADwvAG1pc3NpbmcgYmluZGluZyBwYXR0ZXJuLi4uAGFzeW5jIGZ1bmN0aW9uICoACn0pAGxpc3RfZW1wdHkoJnJ0LT5nY19vYmpfbGlzdCkAaiA9PSAoc2gtPnByb3BfY291bnQgLSBzaC0+ZGVsZXRlZF9wcm9wX2NvdW50KQBKU19Jc1VuZGVmaW5lZChmdW5jX3JldCkAIV9fSlNfQXRvbUlzVGFnZ2VkSW50KGRlc2NyKQAhYXRvbV9pc19mcmVlKHApAChudWxsKQAgKG5hdGl2ZSkAanNfY2xhc3NfaGFzX2J5dGVjb2RlKHAtPmNsYXNzX2lkKQB1bmNvbnNpc3RlbnQgc3RhY2sgc2l6ZTogJWQgJWQgKHBjPSVkKQBieXRlY29kZSBidWZmZXIgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgdW5kZXJmbG93IChvcD0lZCwgcGM9JWQpAGludmFsaWQgb3Bjb2RlIChvcD0lZCwgcGM9JWQpACg/OikAbm8gZnVuY3Rpb24gZmlsZW5hbWUgZm9yIGltcG9ydCgpAC1fLiF+KicoKQAgYW5vbnltb3VzKABTeW1ib2woAGV4cGVjdGluZyAnfScAY2xhc3MgY29uc3RydWN0b3JzIG11c3QgYmUgaW52b2tlZCB3aXRoICduZXcnAGV4cGVjdGluZyAnYXMnAHVuZXhwZWN0ZWQgdG9rZW4gaW4gZXhwcmVzc2lvbjogJyUuKnMnAHVuZXhwZWN0ZWQgdG9rZW46ICclLipzJwByZWRlY2xhcmF0aW9uIG9mICclcycAZHVwbGljYXRlIGV4cG9ydGVkIG5hbWUgJyVzJwBjaXJjdWxhciByZWZlcmVuY2Ugd2hlbiBsb29raW5nIGZvciBleHBvcnQgJyVzJyBpbiBtb2R1bGUgJyVzJwBDb3VsZCBub3QgZmluZCBleHBvcnQgJyVzJyBpbiBtb2R1bGUgJyVzJwBjb3VsZCBub3QgbG9hZCBtb2R1bGUgJyVzJwBjYW5ub3QgZGVmaW5lIHZhcmlhYmxlICclcycAdW5kZWZpbmVkIHByaXZhdGUgZmllbGQgJyVzJwB1bnN1cHBvcnRlZCByZWZlcmVuY2UgdG8gJ3N1cGVyJwBpbnZhbGlkIHVzZSBvZiAnc3VwZXInACdmb3IgYXdhaXQnIGxvb3Agc2hvdWxkIGJlIHVzZWQgd2l0aCAnb2YnAGV4cGVjdGluZyAnJWMnAHVucGFyZW50aGVzaXplZCB1bmFyeSBleHByZXNzaW9uIGNhbid0IGFwcGVhciBvbiB0aGUgbGVmdC1oYW5kIHNpZGUgb2YgJyoqJwBpbnZhbGlkIHVzZSBvZiAnaW1wb3J0KCknAGV4cGVjdGluZyAlJQA7Lz86QCY9KyQsIwA9IgBzZXQgAGdldCAAW29iamVjdCAAYXN5bmMgZnVuY3Rpb24gAGJvdW5kIAAlLjNzLCAlMDJkICUuM3MgJTAqZCAAYXN5bmMgADogACAgICAgICAgICAACikgewoACkpTT2JqZWN0IGNsYXNzZXMKACUtMjBzICU4cyAlOHMKACAgJTVkICAlMi4wZCAlcwoAICAlM3UgKyAlLTJ1ICAlcwoAICBt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YWJsZQB2YWx1ZQBnZXQAc2V0AG9mAF9fcHJvdG9fXwB1bmRlZmluZWQAbnVtYmVyAGJvb2xlYW4Ac3RyaW5nAG9iamVjdABzeW1ib2wAaW50ZWdlcgB1bmtub3duAGFyZ3VtZW50cwBjYWxsZWUAY2FsbGVyADxldmFsPgA8cmV0PgA8dmFyPgA8YXJnX3Zhcj4APHdpdGg+AGxhc3RJbmRleAB0YXJnZXQAaW5kZXgAaW5wdXQAZGVmaW5lUHJvcGVydGllcwBhcHBseQBqb2luAGNvbmNhdABzcGxpdABjb25zdHJ1Y3QAZ2V0UHJvdG90eXBlT2YAc2V0UHJvdG90eXBlT2YAaXNFeHRlbnNpYmxlAHByZXZlbnRFeHRlbnNpb25zAGhhcwBkZWxldGVQcm9wZXJ0eQBkZWZpbmVQcm9wZXJ0eQBnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IAb3duS2V5cwBhZGQAZG9uZQBuZXh0AHZhbHVlcwBzb3VyY2UAZmxhZ3MAZ2xvYmFsAHVuaWNvZGUAcmF3AG5ldy50YXJnZXQAdGhpcy5hY3RpdmVfZnVuYwA8aG9tZV9vYmplY3Q+ADxjb21wdXRlZF9maWVsZD4APHN0YXRpY19jb21wdXRlZF9maWVsZD4APGNsYXNzX2ZpZWxkc19pbml0PgA8YnJhbmQ+ACNjb25zdHJ1Y3RvcgBhcwBmcm9tAG1ldGEAKmRlZmF1bHQqACoATW9kdWxlAHRoZW4AcmVzb2x2ZQByZWplY3QAcHJvbWlzZQBwcm94eQByZXZva2UAYXN5bmMAZXhlYwBncm91cHMAc3RhdHVzAHJlYXNvbgBnbG9iYWxUaGlzAHRvSlNPTgBPYmplY3QAQXJyYXkARXJyb3IATnVtYmVyAFN0cmluZwBCb29sZWFuAFN5bWJvbABBcmd1bWVudHMATWF0aABKU09OAERhdGUARnVuY3Rpb24AR2VuZXJhdG9yRnVuY3Rpb24ARm9ySW5JdGVyYXRvcgBSZWdFeHAAQXJyYXlCdWZmZXIAU2hhcmVkQXJyYXlCdWZmZXIAVWludDhDbGFtcGVkQXJyYXkASW50OEFycmF5AFVpbnQ4QXJyYXkASW50MTZBcnJheQBVaW50MTZBcnJheQBJbnQzMkFycmF5AFVpbnQzMkFycmF5AEZsb2F0MzJBcnJheQBGbG9hdDY0QXJyYXkARGF0YVZpZXcATWFwAFNldABXZWFrTWFwAFdlYWtTZXQATWFwIEl0ZXJhdG9yAFNldCBJdGVyYXRvcgBBcnJheSBJdGVyYXRvcgBTdHJpbmcgSXRlcmF0b3IAUmVnRXhwIFN0cmluZyBJdGVyYXRvcgBHZW5lcmF0b3IAUHJveHkAUHJvbWlzZQBQcm9taXNlUmVzb2x2ZUZ1bmN0aW9uAFByb21pc2VSZWplY3RGdW5jdGlvbgBBc3luY0Z1bmN0aW9uAEFzeW5jRnVuY3Rpb25SZXNvbHZlAEFzeW5jRnVuY3Rpb25SZWplY3QAQXN5bmNHZW5lcmF0b3JGdW5jdGlvbgBBc3luY0dlbmVyYXRvcgBFdmFsRXJyb3IAUmFuZ2VFcnJvcgBSZWZlcmVuY2VFcnJvcgBTeW50YXhFcnJvcgBUeXBlRXJyb3IAVVJJRXJyb3IASW50ZXJuYWxFcnJvcgA8YnJhbmQ+AFN5bWJvbC50b1ByaW1pdGl2ZQBTeW1ib2wuaXRlcmF0b3IAU3ltYm9sLm1hdGNoAFN5bWJvbC5tYXRjaEFsbABTeW1ib2wucmVwbGFjZQBTeW1ib2wuc2VhcmNoAFN5bWJvbC5zcGxpdABTeW1ib2wudG9TdHJpbmdUYWcAU3ltYm9sLmlzQ29uY2F0U3ByZWFkYWJsZQBTeW1ib2wuaGFzSW5zdGFuY2UAU3ltYm9sLnNwZWNpZXMAU3ltYm9sLnVuc2NvcGFibGVzAFN5bWJvbC5hc3luY0l0ZXJhdG9yAAAAAAABAAAABQABFAUAARUFAAEVBQABFwUAARcBAAEAAQABAAEAAQABAAEAAQABAAEAAQACAAEFAwABCgEBAAABAgEAAQMCAAEBAgABAgMAAQIEAAEDBgABAgMAAQMEAAEEBQABAwMAAQQEAAEFBQABAgIAAQQEAAEDAwABAwMAAQQEAAEFBQADAgENAwEBDQMBAA0DAgENAwIADQMAAQ0DAwEKAQEAAAEAAAABAQIAAQAAAAECAgABAgAAAQEAAAEBAAAGAAAYBQEBDwMCAQoBAgEAAQEBAAEBAQAFAAEXBQABFwUAARcFAQAXBQEAFwUCABcBAgMAAQMAAAYAABgGAAAYBgEAGAUBARcFAQIXBQIAFwECAQABAwAAAQMB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AFCD4wBQA+UAQJDmAACB7gBAEu8AtAH4AFCD+ABAAvoAMAH7ADCB+wBAKPwAMAEQAUASEQExAR0BQIIdATCBHgExAR8BAYIfAUCCIAEwgSEBMAEiATCBIgFACiMBAQEoAQGBKAEBASkBAIEpAQABKgEAAisBAIEsAQCBLQEBAS4BAAEwAQGBMAEAgTEBAYEyAQEBMwEAATQBAIE0AQEBNQEBgTUBAQE2AQCBNwEBgTgBAAE5AQCBOgEBgT4BAAFAAQEBQQEAgUEBAYFDAQABRAEAgUQBAAJFAQABRgEAAUkBAYFOAQEBTwFzgaIBQAS4AUACuwEAg70BMIG/ATABwwEwA8QBMAHGATACxwHQAcgBMJHIATCJ0QEAAdYBAIPWAdMB2AEAkdgBcwHhAQCJ4QEAAeYBAILmATCB5wFzAegBc4HoAXOB6gFzAesBAIHrAUAY7AFzAfgBc4H4AQAB+QEAgfkBoAH6AXOB+gFAgvsBMIH8AUAC/QEwg/4BMBAAAjAgCAIAIBgCABAoAkAiMAJANkUCMAFgAkCOYAIAgWcCQGBoAjCmmAIAprACtYHDAjEmUAgxgWMIMYFmCAAraAgAg34IEVDQCRAG+AkgBvwJdAFADnSBQA50AUEOdIFBDnQBQg50gUIOdAFDDoCBQw6AAUQOMCtIDjCDXg4BgbwOAYG+DgEBxw5AfgAPQBg/D7UBSw+2gUsPtgFMD7aBTA+3AU0PgIFNDzABTw9AYFAPAAiADzAIhA8ABogPMAaMDwAIkA8wCJQPAAiYDzAInA8ABqAPMAakD7ABqA8AgagP0wGpDwCBqQ/TAaoPAIGqD9MBqw8AgasPMIGsDzCBrQ8wga4PMIGvDwAIsA8wCLQPAAK4DwAEuQ8AArsPAQK8DwECvQ8BAr4PtwjAD2cIxA+4CMgPaAjMD7gI0A9oCNQPAALYD7kB2Q+xgdkPuQHaD7EB2w/XgdsPMALcDzAC3Q9hAd4PcwHfD7kB4Q+ygeEPugHiD7IB4w/YgeMPMATkD2IB5g8AAugP0AHpD9CB6Q+wAesP0IHrDzAC7A8wAu0PAQLwD9MB8Q/TgfEPugHyDwGB8g+wAfMP04HzDzAC9A8wAvUPMQH2D7oB+Q+ygfkPuwH6D7IB+w/ZgfsPMAL8DzAC/Q9iAf4PoAGTEKABlRCggZUQMQGZEAEBpxAxELAQARC4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iacwH4CIiq2PQZQ4h4+Jt5WAjfkqAAgwB4mvIAgniUFIg2BLaInViaWEuoaYiUP0ALYz0ICKgWBMqoFSYK2BlkIdIi85hp2DQJOCRYixQf+2g7E4jYCVII5FTzCQDgEEQQSGiIlBY4C8jUXVhuw0iVKViWwFBUDv+gYAcAkA8ApAVwwA8A1Axw8A6hcgRRsgVSAgDKhgN6oAUP4AOg0BgxEBxBQhRBkhWh1Bn7xhsNoh8AEOAAAAAEFkbGFtLEFkbG0AQWhvbSxBaG9tAEFuYXRvbGlhbl9IaWVyb2dseXBocyxIbHV3AEFyYWJpYyxBcmFiAEFybWVuaWFuLEFybW4AQXZlc3RhbixBdnN0AEJhbGluZXNlLEJhbGkAQmFtdW0sQmFtdQBCYXNzYV9WYWgsQmFzcwBCYXRhayxCYXRrAEJlbmdhbGksQmVuZwBCaGFpa3N1a2ksQmhrcwBCb3BvbW9mbyxCb3BvAEJyYWhtaSxCcmFoAEJyYWlsbGUsQnJhaQBCdWdpbmVzZSxCdWdpAEJ1aGlkLEJ1aGQAQ2FuYWRpYW5fQWJvcmlnaW5hbCxDYW5zAENhcmlhbixDYXJpAENhdWNhc2lhbl9BbGJhbmlhbixBZ2hiAENoYWttYSxDYWttAENoYW0sQ2hhbQBDaGVyb2tlZSxDaGVyAENob3Jhc21pYW4sQ2hycwBDb21tb24sWnl5eQBDb3B0aWMsQ29wdCxRYWFjAEN1bmVpZm9ybSxYc3V4AEN5cHJpb3QsQ3BydABDeXJpbGxpYyxDeXJsAEN5cHJvX01pbm9hbixDcG1uAERlc2VyZXQsRHNydABEZXZhbmFnYXJpLERldmEARGl2ZXNfQWt1cnUsRGlhawBEb2dyYSxEb2dyAER1cGxveWFuLER1cGwARWd5cHRpYW5fSGllcm9nbHlwaHMsRWd5cABFbGJhc2FuLEVsYmEARWx5bWFpYyxFbHltAEV0aGlvcGljLEV0aGkAR2VvcmdpYW4sR2VvcgBHbGFnb2xpdGljLEdsYWcAR290aGljLEdvdGgAR3JhbnRoYSxHcmFuAEdyZWVrLEdyZWsAR3VqYXJhdGksR3VqcgBHdW5qYWxhX0dvbmRpLEdvbmcAR3VybXVraGksR3VydQBIYW4sSGFuaQBIYW5ndWwsSGFuZwBIYW5pZmlfUm9oaW5neWEsUm9oZwBIYW51bm9vLEhhbm8ASGF0cmFuLEhhdHIASGVicmV3LEhlYnIASGlyYWdhbmEsSGlyYQBJbXBlcmlhbF9BcmFtYWljLEFybWkASW5oZXJpdGVkLFppbmgsUWFhaQBJbnNjcmlwdGlvbmFsX1BhaGxhdmksUGhsaQBJbnNjcmlwdGlvbmFsX1BhcnRoaWFuLFBydGkASmF2YW5lc2UsSmF2YQBLYWl0aGksS3RoaQBLYW5uYWRhLEtuZGEAS2F0YWthbmEsS2FuYQBLYXlhaF9MaSxLYWxpAEtoYXJvc2h0aGksS2hhcgBLaG1lcixLaG1yAEtob2praSxLaG9qAEtoaXRhbl9TbWFsbF9TY3JpcHQsS2l0cwBLaHVkYXdhZGksU2luZABMYW8sTGFvbwBMYXRpbixMYXRuAExlcGNoYSxMZXBjAExpbWJ1LExpbWIATGluZWFyX0EsTGluYQBMaW5lYXJfQixMaW5iAExpc3UsTGlzdQBMeWNpYW4sTHljaQBMeWRpYW4sTHlkaQBNYWthc2FyLE1ha2EATWFoYWphbmksTWFoagBNYWxheWFsYW0sTWx5bQBNYW5kYWljLE1hbmQATWFuaWNoYWVhbixNYW5pAE1hcmNoZW4sTWFyYwBNYXNhcmFtX0dvbmRpLEdvbm0ATWVkZWZhaWRyaW4sTWVkZgBNZWV0ZWlfTWF5ZWssTXRlaQBNZW5kZV9LaWtha3VpLE1lbmQATWVyb2l0aWNfQ3Vyc2l2ZSxNZXJjAE1lcm9pdGljX0hpZXJvZ2x5cGhzLE1lcm8ATWlhbyxQbHJkAE1vZGksTW9kaQBNb25nb2xpYW4sTW9uZwBNcm8sTXJvbwBNdWx0YW5pLE11bHQATXlhbm1hcixNeW1yAE5hYmF0YWVhbixOYmF0AE5hbmRpbmFnYXJpLE5hbmQATmV3X1RhaV9MdWUsVGFsdQBOZXdhLE5ld2EATmtvLE5rb28ATnVzaHUsTnNodQBOeWlha2VuZ19QdWFjaHVlX0htb25nLEhtbnAAT2doYW0sT2dhbQBPbF9DaGlraSxPbGNrAE9sZF9IdW5nYXJpYW4sSHVuZwBPbGRfSXRhbGljLEl0YWwAT2xkX05vcnRoX0FyYWJpYW4sTmFyYgBPbGRfUGVybWljLFBlcm0AT2xkX1BlcnNpYW4sWHBlbwBPbGRfU29nZGlhbixTb2dvAE9sZF9Tb3V0aF9BcmFiaWFuLFNhcmIAT2xkX1R1cmtpYyxPcmtoAE9sZF9VeWdodXIsT3VncgBPcml5YSxPcnlhAE9zYWdlLE9zZ2UAT3NtYW55YSxPc21hAFBhaGF3aF9IbW9uZyxIbW5nAFBhbG15cmVuZSxQYWxtAFBhdV9DaW5fSGF1LFBhdWMAUGhhZ3NfUGEsUGhhZwBQaG9lbmljaWFuLFBobngAUHNhbHRlcl9QYWhsYXZpLFBobHAAUmVqYW5nLFJqbmcAUnVuaWMsUnVucgBTYW1hcml0YW4sU2FtcgBTYXVyYXNodHJhLFNhdXIAU2hhcmFkYSxTaHJkAFNoYXZpYW4sU2hhdwBTaWRkaGFtLFNpZGQAU2lnbldyaXRpbmcsU2dudwBTaW5oYWxhLFNpbmgAU29nZGlhbixTb2dkAFNvcmFfU29tcGVuZyxTb3JhAFNveW9tYm8sU295bwBTdW5kYW5lc2UsU3VuZABTeWxvdGlfTmFncmksU3lsbwBTeXJpYWMsU3lyYwBUYWdhbG9nLFRnbGcAVGFnYmFud2EsVGFnYgBUYWlfTGUsVGFsZQBUYWlfVGhhbSxMYW5hAFRhaV9WaWV0LFRhdnQAVGFrcmksVGFrcgBUYW1pbCxUYW1sAFRhbmd1dCxUYW5nAFRlbHVndSxUZWx1AFRoYWFuYSxUaGFhAFRoYWksVGhhaQBUaWJldGFuLFRpYnQAVGlmaW5hZ2gsVGZuZwBUaXJodXRhLFRpcmgAVGFuZ3NhLFRuc2EAVG90byxUb3RvAFVnYXJpdGljLFVnYXIAVmFpLFZhaWkAVml0aGt1cWksVml0aABXYW5jaG8sV2NobwBXYXJhbmdfQ2l0aSxXYXJhAFllemlkaSxZZXppAFlpLFlpaWkAWmFuYWJhemFyX1NxdWFyZSxaYW5iAAAAAAAAAMAZmUaFGZlGrhmARo4ZgEaEGZZGgBmeRoAZ4WBGphmERoQZgQ2TGeAPOIMsgBmCLAGDLIAZgCwDgCyAGYAsgBmCLACALACTLAC+LI0ajyzgJB2BOOBIHQClBQGxBQGCBQC2NQeaNQOFNQqEBIAZhQSAGY0EgBmCBIAZnwSAGYkEijiZBIA44AsEgBmhBI2JALuJAYKJrwSxkw26ZAGCZK19AY59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U2MsQ3VycmVuY3lfU3ltYm9sAFNrLE1vZGlmaWVyX1N5bWJvbABTbyxPdGhlcl9TeW1ib2wAUGMsQ29ubmVjdG9yX1B1bmN0dWF0aW9uAFBkLERhc2hfUHVuY3R1YXRpb24AUHMsT3Blbl9QdW5jdHVhdGlvbgBQZSxDbG9zZV9QdW5jdHVhdGlvbgBQaSxJbml0aWFsX1B1bmN0dWF0aW9uAFBmLEZpbmFsX1B1bmN0dWF0aW9uAFBvLE90aGVyX1B1bmN0dWF0aW9uAFpzLFNwYWNlX1NlcGFyYXRvcgBabCxMaW5lX1NlcGFyYXRvcgBacCxQYXJhZ3JhcGhfU2VwYXJhdG9yAENjLENvbnRyb2wsY250cmwAQ2YsRm9ybWF0AENzLFN1cnJvZ2F0ZQBDbyxQcml2YXRlX1VzZQBMQyxDYXNlZF9MZXR0ZXIATCxMZXR0ZXIATSxNYXJrLENvbWJpbmluZ19NYXJrAE4sTnVtYmVyAFMsU3ltYm9sAFAsUHVuY3R1YXRpb24scHVuY3QAWixTZXBhcmF0b3IAQyxPdGhlcgBBoLsCC7AIDgAAAD4AAADAAQAAAA4AAADwAAAAAH8AAACAAwEAADxBU0NJSV9IZXhfRGlnaXQsQUhleABCaWRpX0NvbnRyb2wsQmlkaV9DAERhc2gARGVwcmVjYXRlZCxEZXAARGlhY3JpdGljLERpYQBFeHRlbmRlcixFeHQASGV4X0RpZ2l0LEhleABJRFNfQmluYXJ5X09wZXJhdG9yLElEU0IASURTX1RyaW5hcnlfT3BlcmF0b3IsSURTVABJZGVvZ3JhcGhpYyxJZGVvAEpvaW5fQ29udHJvbCxKb2luX0MATG9naWNhbF9PcmRlcl9FeGNlcHRpb24sTE9FAE5vbmNoYXJhY3Rlcl9Db2RlX1BvaW50LE5DaGFyAFBhdHRlcm5fU3ludGF4LFBhdF9TeW4AUGF0dGVybl9XaGl0ZV9TcGFjZSxQYXRfV1MAUXVvdGF0aW9uX01hcmssUU1hcmsAUmFkaWNhbABSZWdpb25hbF9JbmRpY2F0b3IsUkkAU2VudGVuY2VfVGVybWluYWwsU1Rlcm0AU29mdF9Eb3R0ZWQsU0QAVGVybWluYWxfUHVuY3R1YXRpb24sVGVybQBVbmlmaWVkX0lkZW9ncmFwaCxVSWRlbwBWYXJpYXRpb25fU2VsZWN0b3IsVlMAV2hpdGVfU3BhY2Usc3BhY2UAQmlkaV9NaXJyb3JlZCxCaWRpX00ARW1vamkARW1vamlfQ29tcG9uZW50LEVDb21wAEVtb2ppX01vZGlmaWVyLEVNb2QARW1vamlfTW9kaWZpZXJfQmFzZSxFQmFzZQBFbW9qaV9QcmVzZW50YXRpb24sRVByZXMARXh0ZW5kZWRfUGljdG9ncmFwaGljLEV4dFBpY3QARGVmYXVsdF9JZ25vcmFibGVfQ29kZV9Qb2ludCxESQBJRF9TdGFydCxJRFMAQ2FzZV9JZ25vcmFibGUsQ0kAQVNDSUkAQWxwaGFiZXRpYyxBbHBoYQBBbnkAQXNzaWduZWQAQ2FzZWQAQ2hhbmdlc19XaGVuX0Nhc2Vmb2xkZWQsQ1dDRgBDaGFuZ2VzX1doZW5fQ2FzZW1hcHBlZCxDV0NNAENoYW5nZXNfV2hlbl9Mb3dlcmNhc2VkLENXTABDaGFuZ2VzX1doZW5fTkZLQ19DYXNlZm9sZGVkLENXS0NGAENoYW5nZXNfV2hlbl9UaXRsZWNhc2VkLENXVABDaGFuZ2VzX1doZW5fVXBwZXJjYXNlZCxDV1UAR3JhcGhlbWVfQmFzZSxHcl9CYXNlAEdyYXBoZW1lX0V4dGVuZCxHcl9FeHQASURfQ29udGludWUsSURDAExvd2VyY2FzZSxMb3dlcgBNYXRoAFVwcGVyY2FzZSxVcHBlcgBYSURfQ29udGludWUsWElEQwBYSURfU3RhcnQsWElEUwBB4MMCC9QVgQAoAJcAKgCBgCoAl8ArABWBLACXAC0AgUAtAJcALgAVQS4AmQEvABYgMABCCEAAQopEAEIESgCWAEwAF4FMAEICTQBCQ04AL8FPAELDUAC/QFIAQgNTAEIJVQBCCFoAlgBeAEJDXgCBwF8AQgFoAELBawCFAXEAF8NxAERIcwBEg3cAQoN5AL4CewCXQXwAQgF9AEQEfgBCDoAAQoGHAESHiQCDBKwAFwO2AIMCuAAUAtAAlgDRAIAA3QCXgN4AgIDfAJcA4QA+QeEAgMDhAL4E4gCug+oAroLyAK0B9AAuwfQAA0H1AAMD/ACBQP4APgIAAb7AAQG+AQMBvkAGAb5ADgE+AhQBvsAVAb4BFwFEgR0BREEwAUQCNAFEgTUB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QmNhbGtjYWxwAEZuAEa8A0a8A2dtAGdrAGdIAHprSHpNSHpHSHpUSHq8AxMhbQATIWQAEyFrABMhZgBtbgBtvANtbQBtYwBtawBtYwAKCk8ACk9tALIAYwAICk8KClAAClBtALMAawBtALMAbQAVInMAbQAVInMAsgBQYWtQYU1QYUdQYXJhZHJhZNFzcgBhAGQAFSJzALIAcABzbgBzvANzbQBzcABWbgBWvANWbQBWawBWTQBWcABXbgBXvANXbQBXawBXTQBXawCpA00AqQNhLm0uQnFjY2NkQ9FrZ0NvLmRCR3loYUhQaW5LS0tNa3RsbWxubG9nbHhtYm1pbG1vbFBIcC5tLlBQTVBSc3JTdldiVtFtQdFtMQDlZTEAMADlZTIAMADlZTMAMADlZWdhbEoETARDRlEmAVMBJ6c3q2sCUqtIjPRmyo7IjNFuMk7lU5yfnJ9RWdGRh1VIWfZhaXaFfz+Guof4iI+QAmobbdlw3nM9hGqR8ZmCTnVTBGsbci2GHp5QXetvzYVkicli2IEfiMpeF2dqbfxyzpCGT7dR3lLEZNNqEHLndgGABoZchu+NMpdvm/qdjHh/eaB9yYMEk3+e1orfWARfYHx+gGJyynjCjPeW2FhiXBNq2m0Pby99N35LltJSi4DcUcxRHHq+ffGDdZaAi89iAmr+ijlO51sSYIdzcHUXU/t4v0+pXw1OzGx4ZSJ9w1NeWAF3SYSqirprsI+IbP5i5YKgY2V1rk5pUclRgWjnfG+C0orPkfVSQlRzWexexWX+byp5rZVqmpeezp6bUsZmd2tij3RekGEAYppkI29JcYl0ynn0fW+AJo/uhCOQSpMXUqNSvVTIcMKIqorJXvVfe2Ouaz58dXPkTvlW51u6XRxgsnNpdJp/RoA0kvaWSJcYmItPrnm0kbiW4WCGTtpQ7ls/XJllAmrOcUJ2/IR8kI2fiGYulolSe2fzZ0FtnG4JdFl1a3gQfV6YbVEuYniWK1AZXeptKo+LX0RhF2iHc4aWKVIPVGVcE2ZOZ6ho5WwGdOJ1eX/PiOGIzJHilj9Tum4dVNBxmHT6haOWV5yfnpdny23ogct6IHuSfMBymXBYi8BONoM6UgdSpl7TYtZ8hVsebbRmO49MiE2Wi4nTXkBRwFUAAAAAWlgAAHRmAAAAAN5RKnPKdjx5XnlleY95Vpe+fL1/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";if(!T(U)){var ha=U;U=c.locateFile?c.locateFile(ha,t):t+ha;}function ia(){var a=U;try{if(a==U&&v)return new Uint8Array(v);if(T(a))try{var b=ja(a.slice(37)),d=new Uint8Array(b.length);for(a=0;a<b.length;++a)d[a]=b.charCodeAt(a);var e=d;}catch(g){throw Error("Converting base64 string to bytes failed.");}else e=void 0;var f=e;if(f)return f;throw"both async and sync fetching of the wasm failed";}catch(g){w(g);}}function ka(){return v||"function"!=typeof fetch?Promise.resolve().then(function(){return ia();}):fetch(U,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+U+"'";return a.arrayBuffer();}).catch(function(){return ia();});}function V(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(c);else{var d=b.C;"number"==typeof d?void 0===b.A?O.get(d)():O.get(d)(b.A):d(void 0===b.A?null:b.A);}}}function la(a,b,d){function e(q){return(q=q.toTimeString().match(/\(([A-Za-z ]+)\)$/))?q[1]:"GMT";}var f=new Date().getFullYear(),g=new Date(f,0,1),k=new Date(f,6,1);f=g.getTimezoneOffset();var m=k.getTimezoneOffset();N[a>>2]=60*Math.max(f,m);N[b>>2]=Number(f!=m);a=e(g);b=e(k);a=K(a);b=K(b);m<f?(N[d>>2]=a,N[d+4>>2]=b):(N[d>>2]=b,N[d+4>>2]=a);}function W(a,b,d){W.B||(W.B=!0,la(a,b,d));}function X(a){var b=J(a)+1,d=L(b);B(a,C,d,b);return d;}function ma(){}var na=[null,[],[]];ma=(a,b,d)=>{a=G(a);b=null!==b?JSON.parse(G(b)):[];try{const e=c.externalCall(a,b);return e?X(e):null;}catch(e){return c.HEAPU8[d]=1,X(e.message);}};var ja="function"==typeof atob?atob:function(a){var b="",d=0;a=a.replace(/[^A-Za-z0-9\+\/=]/g,"");do{var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(d++));var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(d++));var g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(d++));var k="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(d++));e=e<<2|f>>4;f=(f&15)<<4|g>>2;var m=(g&3)<<6|k;b+=String.fromCharCode(e);64!==g&&(b+=String.fromCharCode(f));64!==k&&(b+=String.fromCharCode(m));}while(d<a.length);return b;},oa={a:function(a,b,d,e){w("Assertion failed: "+G(a)+", at: "+[b?G(b):"unknown filename",d,e?G(e):"unknown function"]);},d:function(){return Date.now();},e:function(a,b){a=new Date(1E3*N[a>>2]);N[b>>2]=a.getSeconds();N[b+4>>2]=a.getMinutes();N[b+8>>2]=a.getHours();N[b+12>>2]=a.getDate();N[b+16>>2]=a.getMonth();N[b+20>>2]=a.getFullYear()-1900;N[b+24>>2]=a.getDay();var d=new Date(a.getFullYear(),0,1);N[b+28>>2]=(a.getTime()-d.getTime())/864E5|0;N[b+36>>2]=-(60*a.getTimezoneOffset());var e=new Date(a.getFullYear(),6,1).getTimezoneOffset();d=d.getTimezoneOffset();N[b+32>>2]=(e!=d&&a.getTimezoneOffset()==Math.min(d,e))|0;},f:W,b:function(){w("");},h:ma,g:function(a,b){a=G(a);let d;try{d=window.JSON.parse(a);}catch(e){d=a;}0!==b?window.alert(a):window.console.log("DUMP",d);},j:function(a){var b=C.length;a>>>=0;if(2147483648<a)return!1;for(var d=1;4>=d;d*=2){var e=b*(1+.2/d);e=Math.min(e,a+100663296);var f=Math;e=Math.max(a,e);f=f.min.call(f,2147483648,e+(65536-e%65536)%65536);a:{try{x.grow(f-M.byteLength+65535>>>16);ba();var g=1;break a;}catch(k){}g=void 0;}if(g)return!0;}return!1;},c:function(a,b,d,e){for(var f=0,g=0;g<d;g++){var k=N[b>>2],m=N[b+4>>2];b+=8;for(var q=0;q<m;q++){var l=C[k+q],p=na[a];0===l||10===l?((1===a?aa:u)(I(p,0)),p.length=0):p.push(l);}f+=m;}N[e>>2]=f;return 0;},k:function(a){a=G(a);window.console.log(a);},i:function(a){a=G(a);return Date.parse(a);},l:function(a,b,d,e){a=G(a);b=G(b);d=G(d);d=`Quickjs -- ${a}: ${b}\n${d}`;0!==e?window.alert(d):window.console.error(d);}};(function(){function a(f){c.asm=f.exports;x=c.asm.m;ba();O=c.asm.v;da.unshift(c.asm.n);P--;c.monitorRunDependencies&&c.monitorRunDependencies(P);0==P&&(null!==Q&&(clearInterval(Q),Q=null),R&&(f=R,R=null,f()));}function b(f){a(f.instance);}function d(f){return ka().then(function(g){return WebAssembly.instantiate(g,e);}).then(function(g){return g;}).then(f,function(g){u("failed to asynchronously prepare wasm: "+g);w(g);});}var e={a:oa};P++;c.monitorRunDependencies&&c.monitorRunDependencies(P);if(c.instantiateWasm)try{return c.instantiateWasm(e,a);}catch(f){return u("Module.instantiateWasm callback failed with error: "+f),!1;}(function(){return v||"function"!=typeof WebAssembly.instantiateStreaming||T(U)||"function"!=typeof fetch?d(b):fetch(U,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,e).then(b,function(g){u("wasm streaming compile failed: "+g);u("falling back to ArrayBuffer instantiation");return d(b);});});})().catch(n);return{};})();c.___wasm_call_ctors=function(){return(c.___wasm_call_ctors=c.asm.n).apply(null,arguments);};c._evalInSandbox=function(){return(c._evalInSandbox=c.asm.o).apply(null,arguments);};c._nukeSandbox=function(){return(c._nukeSandbox=c.asm.p).apply(null,arguments);};c._init=function(){return(c._init=c.asm.q).apply(null,arguments);};c._commFun=function(){return(c._commFun=c.asm.r).apply(null,arguments);};c._dumpMemoryUse=function(){return(c._dumpMemoryUse=c.asm.s).apply(null,arguments);};var L=c._malloc=function(){return(L=c._malloc=c.asm.t).apply(null,arguments);};c._free=function(){return(c._free=c.asm.u).apply(null,arguments);};var E=c.stackSave=function(){return(E=c.stackSave=c.asm.w).apply(null,arguments);},F=c.stackRestore=function(){return(F=c.stackRestore=c.asm.x).apply(null,arguments);},A=c.stackAlloc=function(){return(A=c.stackAlloc=c.asm.y).apply(null,arguments);};c.ccall=z;c.cwrap=function(a,b,d,e){d=d||[];var f=d.every(function(g){return"number"===g;});return"string"!==b&&f&&!e?c["_"+a]:function(){return z(a,b,d,arguments,e);};};c.stringToNewUTF8=X;var Y;R=function pa(){Y||Z();Y||(R=pa);};function Z(){function a(){if(!Y&&(Y=!0,c.calledRun=!0,!y)){V(da);h(c);if(c.onRuntimeInitialized)c.onRuntimeInitialized();if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;){var b=c.postRun.shift();ea.unshift(b);}V(ea);}}if(!(0<P)){if(c.preRun)for("function"==typeof c.preRun&&(c.preRun=[c.preRun]);c.preRun.length;)fa();V(ca);0<P||(c.setStatus?(c.setStatus("Running..."),setTimeout(function(){setTimeout(function(){c.setStatus("");},1);a();},1)):a());}}c.run=Z;if(c.preInit)for("function"==typeof c.preInit&&(c.preInit=[c.preInit]);0<c.preInit.length;)c.preInit.pop()();Z();return Module.ready;};})();/* harmony default export */ const quickjs_eval = (Module); ;// CONCATENATED MODULE: ./src/pdf.sandbox.external.js class SandboxSupportBase { constructor(win) { this.win = win; this.timeoutIds = new Map(); this.commFun = null; } destroy() { this.commFun = null; for (const id of this.timeoutIds.values()) { this.win.clearTimeout(id); } this.timeoutIds = null; } exportValueToSandbox(val) { throw new Error("Not implemented"); } importValueFromSandbox(val) { throw new Error("Not implemented"); } createErrorForSandbox(errorMessage) { throw new Error("Not implemented"); } callSandboxFunction(name, args) { try { args = this.exportValueToSandbox(args); this.commFun(name, args); } catch (e) { this.win.console.error(e); } } createSandboxExternals() { const externals = { setTimeout: (callbackId, nMilliseconds) => { if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") { return; } if (callbackId === 0) { this.win.clearTimeout(this.timeoutIds.get(callbackId)); } const id = this.win.setTimeout(() => { this.timeoutIds.delete(callbackId); this.callSandboxFunction("timeoutCb", { callbackId, interval: false }); }, nMilliseconds); this.timeoutIds.set(callbackId, id); }, clearTimeout: callbackId => { this.win.clearTimeout(this.timeoutIds.get(callbackId)); this.timeoutIds.delete(callbackId); }, setInterval: (callbackId, nMilliseconds) => { if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") { return; } const id = this.win.setInterval(() => { this.callSandboxFunction("timeoutCb", { callbackId, interval: true }); }, nMilliseconds); this.timeoutIds.set(callbackId, id); }, clearInterval: callbackId => { this.win.clearInterval(this.timeoutIds.get(callbackId)); this.timeoutIds.delete(callbackId); }, alert: cMsg => { if (typeof cMsg !== "string") { return; } this.win.alert(cMsg); }, confirm: cMsg => { if (typeof cMsg !== "string") { return false; } return this.win.confirm(cMsg); }, prompt: (cQuestion, cDefault) => { if (typeof cQuestion !== "string" || typeof cDefault !== "string") { return null; } return this.win.prompt(cQuestion, cDefault); }, parseURL: cUrl => { const url = new this.win.URL(cUrl); const props = ["hash", "host", "hostname", "href", "origin", "password", "pathname", "port", "protocol", "search", "searchParams", "username"]; return Object.fromEntries(props.map(name => [name, url[name].toString()])); }, send: data => { if (!data) { return; } const event = new this.win.CustomEvent("updatefromsandbox", { detail: this.importValueFromSandbox(data) }); this.win.dispatchEvent(event); } }; Object.setPrototypeOf(externals, null); return (name, args) => { try { const result = externals[name](...args); return this.exportValueToSandbox(result); } catch (error) { throw this.createErrorForSandbox(error?.toString() ?? ""); } }; } } ;// CONCATENATED MODULE: ./src/pdf.sandbox.js const pdfjsVersion = '4.0.269'; const pdfjsBuild = 'f4b396f6c'; class SandboxSupport extends SandboxSupportBase { exportValueToSandbox(val) { return JSON.stringify(val); } importValueFromSandbox(val) { return val; } createErrorForSandbox(errorMessage) { return new Error(errorMessage); } } class Sandbox { constructor(win, module) { this.support = new SandboxSupport(win, this); module.externalCall = this.support.createSandboxExternals(); this._module = module; this._alertOnError = 0; } create(data) { const code = ['var __webpack_exports__ = globalThis.pdfjsSandbox = {};\n\n;// CONCATENATED MODULE: ./src/scripting_api/constants.js\nconst Border = Object.freeze({\n s: "solid",\n d: "dashed",\n b: "beveled",\n i: "inset",\n u: "underline"\n});\nconst Cursor = Object.freeze({\n visible: 0,\n hidden: 1,\n delay: 2\n});\nconst Display = Object.freeze({\n visible: 0,\n hidden: 1,\n noPrint: 2,\n noView: 3\n});\nconst Font = Object.freeze({\n Times: "Times-Roman",\n TimesB: "Times-Bold",\n TimesI: "Times-Italic",\n TimesBI: "Times-BoldItalic",\n Helv: "Helvetica",\n HelvB: "Helvetica-Bold",\n HelvI: "Helvetica-Oblique",\n HelvBI: "Helvetica-BoldOblique",\n Cour: "Courier",\n CourB: "Courier-Bold",\n CourI: "Courier-Oblique",\n CourBI: "Courier-BoldOblique",\n Symbol: "Symbol",\n ZapfD: "ZapfDingbats",\n KaGo: "HeiseiKakuGo-W5-UniJIS-UCS2-H",\n KaMi: "HeiseiMin-W3-UniJIS-UCS2-H"\n});\nconst Highlight = Object.freeze({\n n: "none",\n i: "invert",\n p: "push",\n o: "outline"\n});\nconst Position = Object.freeze({\n textOnly: 0,\n iconOnly: 1,\n iconTextV: 2,\n textIconV: 3,\n iconTextH: 4,\n textIconH: 5,\n overlay: 6\n});\nconst ScaleHow = Object.freeze({\n proportional: 0,\n anamorphic: 1\n});\nconst ScaleWhen = Object.freeze({\n always: 0,\n never: 1,\n tooBig: 2,\n tooSmall: 3\n});\nconst Style = Object.freeze({\n ch: "check",\n cr: "cross",\n di: "diamond",\n ci: "circle",\n st: "star",\n sq: "square"\n});\nconst Trans = Object.freeze({\n blindsH: "BlindsHorizontal",\n blindsV: "BlindsVertical",\n boxI: "BoxIn",\n boxO: "BoxOut",\n dissolve: "Dissolve",\n glitterD: "GlitterDown",\n glitterR: "GlitterRight",\n glitterRD: "GlitterRightDown",\n random: "Random",\n replace: "Replace",\n splitHI: "SplitHorizontalIn",\n splitHO: "SplitHorizontalOut",\n splitVI: "SplitVerticalIn",\n splitVO: "SplitVerticalOut",\n wipeD: "WipeDown",\n wipeL: "WipeLeft",\n wipeR: "WipeRight",\n wipeU: "WipeUp"\n});\nconst ZoomType = Object.freeze({\n none: "NoVary",\n fitP: "FitPage",\n fitW: "FitWidth",\n fitH: "FitHeight",\n fitV: "FitVisibleWidth",\n pref: "Preferred",\n refW: "ReflowWidth"\n});\nconst GlobalConstants = Object.freeze({\n IDS_GREATER_THAN: "Invalid value: must be greater than or equal to % s.",\n IDS_GT_AND_LT: "Invalid value: must be greater than or equal to % s " + "and less than or equal to % s.",\n IDS_LESS_THAN: "Invalid value: must be less than or equal to % s.",\n IDS_INVALID_MONTH: "** Invalid **",\n IDS_INVALID_DATE: "Invalid date / time: please ensure that the date / time exists.Field",\n IDS_INVALID_DATE2: " should match format ",\n IDS_INVALID_VALUE: "The value entered does not match the format of the field",\n IDS_AM: "am",\n IDS_PM: "pm",\n IDS_MONTH_INFO: "January[1] February[2] March[3] April[4] May[5] " + "June[6] July[7] August[8] September[9] October[10] " + "November[11] December[12] Sept[9] Jan[1] Feb[2] Mar[3] " + "Apr[4] Jun[6] Jul[7] Aug[8] Sep[9] Oct[10] Nov[11] Dec[12]",\n IDS_STARTUP_CONSOLE_MSG: "** ^ _ ^ **",\n RE_NUMBER_ENTRY_DOT_SEP: ["[+-]?\\\\d*\\\\.?\\\\d*"],\n RE_NUMBER_COMMIT_DOT_SEP: ["[+-]?\\\\d+(\\\\.\\\\d+)?", "[+-]?\\\\.\\\\d+", "[+-]?\\\\d+\\\\."],\n RE_NUMBER_ENTRY_COMMA_SEP: ["[+-]?\\\\d*,?\\\\d*"],\n RE_NUMBER_COMMIT_COMMA_SEP: ["[+-]?\\\\d+([.,]\\\\d+)?", "[+-]?[.,]\\\\d+", "[+-]?\\\\d+[.,]"],\n RE_ZIP_ENTRY: ["\\\\d{0,5}"],\n RE_ZIP_COMMIT: ["\\\\d{5}"],\n RE_ZIP4_ENTRY: ["\\\\d{0,5}(\\\\.|[- ])?\\\\d{0,4}"],\n RE_ZIP4_COMMIT: ["\\\\d{5}(\\\\.|[- ])?\\\\d{4}"],\n RE_PHONE_ENTRY: ["\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}", "\\\\(\\\\d{0,3}", "\\\\(\\\\d{0,3}\\\\)(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}", "\\\\(\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}", "\\\\d{0,3}\\\\)(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}", "011(\\\\.|[- \\\\d])*"],\n RE_PHONE_COMMIT: ["\\\\d{3}(\\\\.|[- ])?\\\\d{4}", "\\\\d{3}(\\\\.|[- ])?\\\\d{3}(\\\\.|[- ])?\\\\d{4}", "\\\\(\\\\d{3}\\\\)(\\\\.|[- ])?\\\\d{3}(\\\\.|[- ])?\\\\d{4}", "011(\\\\.|[- \\\\d])*"],\n RE_SSN_ENTRY: ["\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,2}(\\\\.|[- ])?\\\\d{0,4}"],\n RE_SSN_COMMIT: ["\\\\d{3}(\\\\.|[- ])?\\\\d{2}(\\\\.|[- ])?\\\\d{4}"]\n});\n\n;// CONCATENATED MODULE: ./src/scripting_api/common.js\nconst FieldType = {\n none: 0,\n number: 1,\n percent: 2,\n date: 3,\n time: 4\n};\nfunction createActionsMap(actions) {\n const actionsMap = new Map();\n if (actions) {\n for (const [eventType, actionsForEvent] of Object.entries(actions)) {\n actionsMap.set(eventType, actionsForEvent);\n }\n }\n return actionsMap;\n}\nfunction getFieldType(actions) {\n let format = actions.get("Format");\n if (!format) {\n return FieldType.none;\n }\n format = format[0];\n format = format.trim();\n if (format.startsWith("AFNumber_")) {\n return FieldType.number;\n }\n if (format.startsWith("AFPercent_")) {\n return FieldType.percent;\n }\n if (format.startsWith("AFDate_")) {\n return FieldType.date;\n }\n if (format.startsWith("AFTime__")) {\n return FieldType.time;\n }\n return FieldType.none;\n}\n\n;// CONCATENATED MODULE: ./src/shared/scripting_utils.js\nfunction makeColorComp(n) {\n return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0");\n}\nfunction scaleAndClamp(x) {\n return Math.max(0, Math.min(255, 255 * x));\n}\nclass ColorConverters {\n static CMYK_G([c, y, m, k]) {\n return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];\n }\n static G_CMYK([g]) {\n return ["CMYK", 0, 0, 0, 1 - g];\n }\n static G_RGB([g]) {\n return ["RGB", g, g, g];\n }\n static G_rgb([g]) {\n g = scaleAndClamp(g);\n return [g, g, g];\n }\n static G_HTML([g]) {\n const G = makeColorComp(g);\n return `#${G}${G}${G}`;\n }\n static RGB_G([r, g, b]) {\n return ["G", 0.3 * r + 0.59 * g + 0.11 * b];\n }\n static RGB_rgb(color) {\n return color.map(scaleAndClamp);\n }\n static RGB_HTML(color) {\n return `#${color.map(makeColorComp).join("")}`;\n }\n static T_HTML() {\n return "#00000000";\n }\n static T_rgb() {\n return [null];\n }\n static CMYK_RGB([c, y, m, k]) {\n return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];\n }\n static CMYK_rgb([c, y, m, k]) {\n return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];\n }\n static CMYK_HTML(components) {\n const rgb = this.CMYK_RGB(components).slice(1);\n return this.RGB_HTML(rgb);\n }\n static RGB_CMYK([r, g, b]) {\n const c = 1 - r;\n const m = 1 - g;\n const y = 1 - b;\n const k = Math.min(c, m, y);\n return ["CMYK", c, m, y, k];\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/pdf_object.js\nclass PDFObject {\n constructor(data) {\n this._expandos = Object.create(null);\n this._send = data.send || null;\n this._id = data.id || null;\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/color.js\n\n\nclass Color extends PDFObject {\n constructor() {\n super({});\n this.transparent = ["T"];\n this.black = ["G", 0];\n this.white = ["G", 1];\n this.red = ["RGB", 1, 0, 0];\n this.green = ["RGB", 0, 1, 0];\n this.blue = ["RGB", 0, 0, 1];\n this.cyan = ["CMYK", 1, 0, 0, 0];\n this.magenta = ["CMYK", 0, 1, 0, 0];\n this.yellow = ["CMYK", 0, 0, 1, 0];\n this.dkGray = ["G", 0.25];\n this.gray = ["G", 0.5];\n this.ltGray = ["G", 0.75];\n }\n static _isValidSpace(cColorSpace) {\n return typeof cColorSpace === "string" && (cColorSpace === "T" || cColorSpace === "G" || cColorSpace === "RGB" || cColorSpace === "CMYK");\n }\n static _isValidColor(colorArray) {\n if (!Array.isArray(colorArray) || colorArray.length === 0) {\n return false;\n }\n const space = colorArray[0];\n if (!Color._isValidSpace(space)) {\n return false;\n }\n switch (space) {\n case "T":\n if (colorArray.length !== 1) {\n return false;\n }\n break;\n case "G":\n if (colorArray.length !== 2) {\n return false;\n }\n break;\n case "RGB":\n if (colorArray.length !== 4) {\n return false;\n }\n break;\n case "CMYK":\n if (colorArray.length !== 5) {\n return false;\n }\n break;\n default:\n return false;\n }\n return colorArray.slice(1).every(c => typeof c === "number" && c >= 0 && c <= 1);\n }\n static _getCorrectColor(colorArray) {\n return Color._isValidColor(colorArray) ? colorArray : ["G", 0];\n }\n convert(colorArray, cColorSpace) {\n if (!Color._isValidSpace(cColorSpace)) {\n return this.black;\n }\n if (cColorSpace === "T") {\n return ["T"];\n }\n colorArray = Color._getCorrectColor(colorArray);\n if (colorArray[0] === cColorSpace) {\n return colorArray;\n }\n if (colorArray[0] === "T") {\n return this.convert(this.black, cColorSpace);\n }\n return ColorConverters[`${colorArray[0]}_${cColorSpace}`](colorArray.slice(1));\n }\n equal(colorArray1, colorArray2) {\n colorArray1 = Color._getCorrectColor(colorArray1);\n colorArray2 = Color._getCorrectColor(colorArray2);\n if (colorArray1[0] === "T" || colorArray2[0] === "T") {\n return colorArray1[0] === "T" && colorArray2[0] === "T";\n }\n if (colorArray1[0] !== colorArray2[0]) {\n colorArray2 = this.convert(colorArray2, colorArray1[0]);\n }\n return colorArray1.slice(1).every((c, i) => c === colorArray2[i + 1]);\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/field.js\n\n\n\nclass Field extends PDFObject {\n constructor(data) {\n super(data);\n this.alignment = data.alignment || "left";\n this.borderStyle = data.borderStyle || "";\n this.buttonAlignX = data.buttonAlignX || 50;\n this.buttonAlignY = data.buttonAlignY || 50;\n this.buttonFitBounds = data.buttonFitBounds;\n this.buttonPosition = data.buttonPosition;\n this.buttonScaleHow = data.buttonScaleHow;\n this.ButtonScaleWhen = data.buttonScaleWhen;\n this.calcOrderIndex = data.calcOrderIndex;\n this.comb = data.comb;\n this.commitOnSelChange = data.commitOnSelChange;\n this.currentValueIndices = data.currentValueIndices;\n this.defaultStyle = data.defaultStyle;\n this.defaultValue = data.defaultValue;\n this.doNotScroll = data.doNotScroll;\n this.doNotSpellCheck = data.doNotSpellCheck;\n this.delay = data.delay;\n this.display = data.display;\n this.doc = data.doc.wrapped;\n this.editable = data.editable;\n this.exportValues = data.exportValues;\n this.fileSelect = data.fileSelect;\n this.hidden = data.hidden;\n this.highlight = data.highlight;\n this.lineWidth = data.lineWidth;\n this.multiline = data.multiline;\n this.multipleSelection = !!data.multipleSelection;\n this.name = data.name;\n this.password = data.password;\n this.print = data.print;\n this.radiosInUnison = data.radiosInUnison;\n this.readonly = data.readonly;\n this.rect = data.rect;\n this.required = data.required;\n this.richText = data.richText;\n this.richValue = data.richValue;\n this.style = data.style;\n this.submitName = data.submitName;\n this.textFont = data.textFont;\n this.textSize = data.textSize;\n this.type = data.type;\n this.userName = data.userName;\n this._actions = createActionsMap(data.actions);\n this._browseForFileToSubmit = data.browseForFileToSubmit || null;\n this._buttonCaption = null;\n this._buttonIcon = null;\n this._charLimit = data.charLimit;\n this._children = null;\n this._currentValueIndices = data.currentValueIndices || 0;\n this._document = data.doc;\n this._fieldPath = data.fieldPath;\n this._fillColor = data.fillColor || ["T"];\n this._isChoice = Array.isArray(data.items);\n this._items = data.items || [];\n this._hasValue = data.hasOwnProperty("value");\n this._page = data.page || 0;\n this._strokeColor = data.strokeColor || ["G", 0];\n this._textColor = data.textColor || ["G", 0];\n this._value = null;\n this._kidIds = data.kidIds || null;\n this._fieldType = getFieldType(this._actions);\n this._siblings = data.siblings || null;\n this._rotation = data.rotation || 0;\n this._globalEval = data.globalEval;\n this._appObjects = data.appObjects;\n this.value = data.value || "";\n }\n get currentValueIndices() {\n if (!this._isChoice) {\n return 0;\n }\n return this._currentValueIndices;\n }\n set currentValueIndices(indices) {\n if (!this._isChoice) {\n return;\n }\n if (!Array.isArray(indices)) {\n indices = [indices];\n }\n if (!indices.every(i => typeof i === "number" && Number.isInteger(i) && i >= 0 && i < this.numItems)) {\n return;\n }\n indices.sort();\n if (this.multipleSelection) {\n this._currentValueIndices = indices;\n this._value = [];\n indices.forEach(i => {\n this._value.push(this._items[i].displayValue);\n });\n } else if (indices.length > 0) {\n indices = indices.splice(1, indices.length - 1);\n this._currentValueIndices = indices[0];\n this._value = this._items[this._currentValueIndices];\n }\n this._send({\n id: this._id,\n indices\n });\n }\n get fillColor() {\n return this._fillColor;\n }\n set fillColor(color) {\n if (Color._isValidColor(color)) {\n this._fillColor = color;\n }\n }\n get bgColor() {\n return this.fillColor;\n }\n set bgColor(color) {\n this.fillColor = color;\n }\n get charLimit() {\n return this._charLimit;\n }\n set charLimit(limit) {\n if (typeof limit !== "number") {\n throw new Error("Invalid argument value");\n }\n this._charLimit = Math.max(0, Math.floor(limit));\n }\n get numItems() {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n return this._items.length;\n }\n set numItems(_) {\n throw new Error("field.numItems is read-only");\n }\n get strokeColor() {\n return this._strokeColor;\n }\n set strokeColor(color) {\n if (Color._isValidColor(color)) {\n this._strokeColor = color;\n }\n }\n get borderColor() {\n return this.strokeColor;\n }\n set borderColor(color) {\n this.strokeColor = color;\n }\n get page() {\n return this._page;\n }\n set page(_) {\n throw new Error("field.page is read-only");\n }\n get rotation() {\n return this._rotation;\n }\n set rotation(angle) {\n angle = Math.floor(angle);\n if (angle % 90 !== 0) {\n throw new Error("Invalid rotation: must be a multiple of 90");\n }\n angle %= 360;\n if (angle < 0) {\n angle += 360;\n }\n this._rotation = angle;\n }\n get textColor() {\n return this._textColor;\n }\n set textColor(color) {\n if (Color._isValidColor(color)) {\n this._textColor = color;\n }\n }\n get fgColor() {\n return this.textColor;\n }\n set fgColor(color) {\n this.textColor = color;\n }\n get value() {\n return this._value;\n }\n set value(value) {\n if (this._isChoice) {\n this._setChoiceValue(value);\n return;\n }\n if (value === "" || typeof value !== "string") {\n this._originalValue = undefined;\n this._value = value;\n return;\n }\n this._originalValue = value;\n const _value = value.trim().replace(",", ".");\n this._value = !isNaN(_value) ? parseFloat(_value) : value;\n }\n _getValue() {\n return this._originalValue ?? this.value;\n }\n _setChoiceValue(value) {\n if (this.multipleSelection) {\n if (!Array.isArray(value)) {\n value = [value];\n }\n const values = new Set(value);\n if (Array.isArray(this._currentValueIndices)) {\n this._currentValueIndices.length = 0;\n this._value.length = 0;\n } else {\n this._currentValueIndices = [];\n this._value = [];\n }\n this._items.forEach((item, i) => {\n if (values.has(item.exportValue)) {\n this._currentValueIndices.push(i);\n this._value.push(item.exportValue);\n }\n });\n } else {\n if (Array.isArray(value)) {\n value = value[0];\n }\n const index = this._items.findIndex(({\n exportValue\n }) => value === exportValue);\n if (index !== -1) {\n this._currentValueIndices = index;\n this._value = this._items[index].exportValue;\n }\n }\n }\n get valueAsString() {\n return (this._value ?? "").toString();\n }\n set valueAsString(_) {}\n browseForFileToSubmit() {\n if (this._browseForFileToSubmit) {\n this._browseForFileToSubmit();\n }\n }\n buttonGetCaption(nFace = 0) {\n if (this._buttonCaption) {\n return this._buttonCaption[nFace];\n }\n return "";\n }\n buttonGetIcon(nFace = 0) {\n if (this._buttonIcon) {\n return this._buttonIcon[nFace];\n }\n return null;\n }\n buttonImportIcon(cPath = null, nPave = 0) {}\n buttonSetCaption(cCaption, nFace = 0) {\n if (!this._buttonCaption) {\n this._buttonCaption = ["", "", ""];\n }\n this._buttonCaption[nFace] = cCaption;\n }\n buttonSetIcon(oIcon, nFace = 0) {\n if (!this._buttonIcon) {\n this._buttonIcon = [null, null, null];\n }\n this._buttonIcon[nFace] = oIcon;\n }\n checkThisBox(nWidget, bCheckIt = true) {}\n clearItems() {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n this._items = [];\n this._send({\n id: this._id,\n clear: null\n });\n }\n deleteItemAt(nIdx = null) {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n if (!this.numItems) {\n return;\n }\n if (nIdx === null) {\n nIdx = Array.isArray(this._currentValueIndices) ? this._currentValueIndices[0] : this._currentValueIndices;\n nIdx ||= 0;\n }\n if (nIdx < 0 || nIdx >= this.numItems) {\n nIdx = this.numItems - 1;\n }\n this._items.splice(nIdx, 1);\n if (Array.isArray(this._currentValueIndices)) {\n let index = this._currentValueIndices.findIndex(i => i >= nIdx);\n if (index !== -1) {\n if (this._currentValueIndices[index] === nIdx) {\n this._currentValueIndices.splice(index, 1);\n }\n for (const ii = this._currentValueIndices.length; index < ii; index++) {\n --this._currentValueIndices[index];\n }\n }\n } else if (this._currentValueIndices === nIdx) {\n this._currentValueIndices = this.numItems > 0 ? 0 : -1;\n } else if (this._currentValueIndices > nIdx) {\n --this._currentValueIndices;\n }\n this._send({\n id: this._id,\n remove: nIdx\n });\n }\n getItemAt(nIdx = -1, bExportValue = false) {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n if (nIdx < 0 || nIdx >= this.numItems) {\n nIdx = this.numItems - 1;\n }\n const item = this._items[nIdx];\n return bExportValue ? item.exportValue : item.displayValue;\n }\n getArray() {\n if (this._kidIds) {\n const array = [];\n const fillArrayWithKids = kidIds => {\n for (const id of kidIds) {\n const obj = this._appObjects[id];\n if (!obj) {\n continue;\n }\n if (obj.obj._hasValue) {\n array.push(obj.wrapped);\n }\n if (obj.obj._kidIds) {\n fillArrayWithKids(obj.obj._kidIds);\n }\n }\n };\n fillArrayWithKids(this._kidIds);\n return array;\n }\n if (this._children === null) {\n this._children = this._document.obj._getTerminalChildren(this._fieldPath);\n }\n return this._children;\n }\n getLock() {\n return undefined;\n }\n isBoxChecked(nWidget) {\n return false;\n }\n isDefaultChecked(nWidget) {\n return false;\n }\n insertItemAt(cName, cExport = undefined, nIdx = 0) {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n if (!cName) {\n return;\n }\n if (nIdx < 0 || nIdx > this.numItems) {\n nIdx = this.numItems;\n }\n if (this._items.some(({\n displayValue\n }) => displayValue === cName)) {\n return;\n }\n if (cExport === undefined) {\n cExport = cName;\n }\n const data = {\n displayValue: cName,\n exportValue: cExport\n };\n this._items.splice(nIdx, 0, data);\n if (Array.isArray(this._currentValueIndices)) {\n let index = this._currentValueIndices.findIndex(i => i >= nIdx);\n if (index !== -1) {\n for (const ii = this._currentValueIndices.length; index < ii; index++) {\n ++this._currentValueIndices[index];\n }\n }\n } else if (this._currentValueIndices >= nIdx) {\n ++this._currentValueIndices;\n }\n this._send({\n id: this._id,\n insert: {\n index: nIdx,\n ...data\n }\n });\n }\n setAction(cTrigger, cScript) {\n if (typeof cTrigger !== "string" || typeof cScript !== "string") {\n return;\n }\n if (!(cTrigger in this._actions)) {\n this._actions[cTrigger] = [];\n }\n this._actions[cTrigger].push(cScript);\n }\n setFocus() {\n this._send({\n id: this._id,\n focus: true\n });\n }\n setItems(oArray) {\n if (!this._isChoice) {\n throw new Error("Not a choice widget");\n }\n this._items.length = 0;\n for (const element of oArray) {\n let displayValue, exportValue;\n if (Array.isArray(element)) {\n displayValue = element[0]?.toString() || "";\n exportValue = element[1]?.toString() || "";\n } else {\n displayValue = exportValue = element?.toString() || "";\n }\n this._items.push({\n displayValue,\n exportValue\n });\n }\n this._currentValueIndices = 0;\n this._send({\n id: this._id,\n items: this._items\n });\n }\n setLock() {}\n signatureGetModifications() {}\n signatureGetSeedValue() {}\n signatureInfo() {}\n signatureSetSeedValue() {}\n signatureSign() {}\n signatureValidate() {}\n _isButton() {\n return false;\n }\n _reset() {\n this.value = this.defaultValue;\n }\n _runActions(event) {\n const eventName = event.name;\n if (!this._actions.has(eventName)) {\n return false;\n }\n const actions = this._actions.get(eventName);\n try {\n for (const action of actions) {\n this._globalEval(action);\n }\n } catch (error) {\n event.rc = false;\n throw error;\n }\n return true;\n }\n}\nclass RadioButtonField extends Field {\n constructor(otherButtons, data) {\n super(data);\n this.exportValues = [this.exportValues];\n this._radioIds = [this._id];\n this._radioActions = [this._actions];\n for (const radioData of otherButtons) {\n this.exportValues.push(radioData.exportValues);\n this._radioIds.push(radioData.id);\n this._radioActions.push(createActionsMap(radioData.actions));\n if (this._value === radioData.exportValues) {\n this._id = radioData.id;\n }\n }\n this._hasBeenInitialized = true;\n this._value = data.value || "";\n }\n get value() {\n return this._value;\n }\n set value(value) {\n if (!this._hasBeenInitialized) {\n return;\n }\n if (value === null || value === undefined) {\n this._value = "";\n }\n const i = this.exportValues.indexOf(value);\n if (0 <= i && i < this._radioIds.length) {\n this._id = this._radioIds[i];\n this._value = value;\n } else if (value === "Off" && this._radioIds.length === 2) {\n const nextI = (1 + this._radioIds.indexOf(this._id)) % 2;\n this._id = this._radioIds[nextI];\n this._value = this.exportValues[nextI];\n }\n }\n checkThisBox(nWidget, bCheckIt = true) {\n if (nWidget < 0 || nWidget >= this._radioIds.length || !bCheckIt) {\n return;\n }\n this._id = this._radioIds[nWidget];\n this._value = this.exportValues[nWidget];\n this._send({\n id: this._id,\n value: this._value\n });\n }\n isBoxChecked(nWidget) {\n return nWidget >= 0 && nWidget < this._radioIds.length && this._id === this._radioIds[nWidget];\n }\n isDefaultChecked(nWidget) {\n return nWidget >= 0 && nWidget < this.exportValues.length && this.defaultValue === this.exportValues[nWidget];\n }\n _getExportValue(state) {\n const i = this._radioIds.indexOf(this._id);\n return this.exportValues[i];\n }\n _runActions(event) {\n const i = this._radioIds.indexOf(this._id);\n this._actions = this._radioActions[i];\n return super._runActions(event);\n }\n _isButton() {\n return true;\n }\n}\nclass CheckboxField extends RadioButtonField {\n get value() {\n return this._value;\n }\n set value(value) {\n if (!value || value === "Off") {\n this._value = "Off";\n } else {\n super.value = value;\n }\n }\n _getExportValue(state) {\n return state ? super._getExportValue(state) : "Off";\n }\n isBoxChecked(nWidget) {\n if (this._value === "Off") {\n return false;\n }\n return super.isBoxChecked(nWidget);\n }\n isDefaultChecked(nWidget) {\n if (this.defaultValue === "Off") {\n return this._value === "Off";\n }\n return super.isDefaultChecked(nWidget);\n }\n checkThisBox(nWidget, bCheckIt = true) {\n if (nWidget < 0 || nWidget >= this._radioIds.length) {\n return;\n }\n this._id = this._radioIds[nWidget];\n this._value = bCheckIt ? this.exportValues[nWidget] : "Off";\n this._send({\n id: this._id,\n value: this._value\n });\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/aform.js\n\nclass AForm {\n constructor(document, app, util, color) {\n this._document = document;\n this._app = app;\n this._util = util;\n this._color = color;\n this._dateFormats = ["m/d", "m/d/yy", "mm/dd/yy", "mm/yy", "d-mmm", "d-mmm-yy", "dd-mmm-yy", "yy-mm-dd", "mmm-yy", "mmmm-yy", "mmm d, yyyy", "mmmm d, yyyy", "m/d/yy h:MM tt", "m/d/yy HH:MM"];\n this._timeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];\n this._dateActionsCache = new Map();\n this._emailRegex = new RegExp("^[a-zA-Z0-9.!#$%&\'*+\\\\/=?^_`{|}~-]+" + "@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" + "(?:\\\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");\n }\n _mkTargetName(event) {\n return event.target ? `[ ${event.target.name} ]` : "";\n }\n _tryToGuessDate(cFormat, cDate) {\n let actions = this._dateActionsCache.get(cFormat);\n if (!actions) {\n actions = [];\n this._dateActionsCache.set(cFormat, actions);\n cFormat.replaceAll(/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g, function (match, d, m, y, H, M, s) {\n if (d) {\n actions.push((n, date) => {\n if (n >= 1 && n <= 31) {\n date.setDate(n);\n return true;\n }\n return false;\n });\n } else if (m) {\n actions.push((n, date) => {\n if (n >= 1 && n <= 12) {\n date.setMonth(n - 1);\n return true;\n }\n return false;\n });\n } else if (y) {\n actions.push((n, date) => {\n if (n < 50) {\n n += 2000;\n } else if (n < 100) {\n n += 1900;\n }\n date.setYear(n);\n return true;\n });\n } else if (H) {\n actions.push((n, date) => {\n if (n >= 0 && n <= 23) {\n date.setHours(n);\n return true;\n }\n return false;\n });\n } else if (M) {\n actions.push((n, date) => {\n if (n >= 0 && n <= 59) {\n date.setMinutes(n);\n return true;\n }\n return false;\n });\n } else if (s) {\n actions.push((n, date) => {\n if (n >= 0 && n <= 59) {\n date.setSeconds(n);\n return true;\n }\n return false;\n });\n }\n return "";\n });\n }\n const number = /\\d+/g;\n let i = 0;\n let array;\n const date = new Date();\n while ((array = number.exec(cDate)) !== null) {\n if (i < actions.length) {\n if (!actions[i++](parseInt(array[0]), date)) {\n return null;\n }\n } else {\n break;\n }\n }\n if (i === 0) {\n return null;\n }\n return date;\n }\n _parseDate(cFormat, cDate) {\n let date = null;\n try {\n date = this._util.scand(cFormat, cDate);\n } catch {}\n if (!date) {\n date = Date.parse(cDate);\n date = isNaN(date) ? this._tryToGuessDate(cFormat, cDate) : new Date(date);\n }\n return date;\n }\n AFMergeChange(event = globalThis.event) {\n if (event.willCommit) {\n return event.value.toString();\n }\n return this._app._eventDispatcher.mergeChange(event);\n }\n AFParseDateEx(cString, cOrder) {\n return this._parseDate(cOrder, cString);\n }\n AFExtractNums(str) {\n if (typeof str === "number") {\n return [str];\n }\n if (!str || typeof str !== "string") {\n return null;\n }\n const first = str.charAt(0);\n if (first === "." || first === ",") {\n str = `0${str}`;\n }\n const numbers = str.match(/(\\d+)/g);\n if (numbers.length === 0) {\n return null;\n }\n return numbers;\n }\n AFMakeNumber(str) {\n if (typeof str === "number") {\n return str;\n }\n if (typeof str !== "string") {\n return null;\n }\n str = str.trim().replace(",", ".");\n const number = parseFloat(str);\n if (isNaN(number) || !isFinite(number)) {\n return null;\n }\n return number;\n }\n AFMakeArrayFromList(string) {\n if (typeof string === "string") {\n return string.split(/, ?/g);\n }\n return string;\n }\n AFNumber_Format(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {\n const event = globalThis.event;\n if (!event.value) {\n return;\n }\n let value = this.AFMakeNumber(event.value);\n if (value === null) {\n event.value = "";\n return;\n }\n const sign = Math.sign(value);\n const buf = [];\n let hasParen = false;\n if (sign === -1 && bCurrencyPrepend && negStyle === 0) {\n buf.push("-");\n }\n if ((negStyle === 2 || negStyle === 3) && sign === -1) {\n buf.push("(");\n hasParen = true;\n }\n if (bCurrencyPrepend) {\n buf.push(strCurrency);\n }\n sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);\n buf.push("%,", sepStyle, ".", nDec.toString(), "f");\n if (!bCurrencyPrepend) {\n buf.push(strCurrency);\n }\n if (hasParen) {\n buf.push(")");\n }\n if (negStyle === 1 || negStyle === 3) {\n event.target.textColor = sign === 1 ? this._color.black : this._color.red;\n }\n if ((negStyle !== 0 || bCurrencyPrepend) && sign === -1) {\n value = -value;\n }\n const formatStr = buf.join("");\n event.value = this._util.printf(formatStr, value);\n }\n AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {\n const event = globalThis.event;\n let value = this.AFMergeChange(event);\n if (!value) {\n return;\n }\n value = value.trim();\n let pattern;\n if (sepStyle > 1) {\n pattern = event.willCommit ? /^[+-]?(\\d+(,\\d*)?|,\\d+)$/ : /^[+-]?\\d*,?\\d*$/;\n } else {\n pattern = event.willCommit ? /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$/ : /^[+-]?\\d*\\.?\\d*$/;\n }\n if (!pattern.test(value)) {\n if (event.willCommit) {\n const err = `${GlobalConstants.IDS_INVALID_VALUE} ${this._mkTargetName(event)}`;\n this._app.alert(err);\n }\n event.rc = false;\n }\n if (event.willCommit && sepStyle > 1) {\n event.value = parseFloat(value.replace(",", "."));\n }\n }\n AFPercent_Format(nDec, sepStyle, percentPrepend = false) {\n if (typeof nDec !== "number") {\n return;\n }\n if (typeof sepStyle !== "number") {\n return;\n }\n if (nDec < 0) {\n throw new Error("Invalid nDec value in AFPercent_Format");\n }\n const event = globalThis.event;\n if (nDec > 512) {\n event.value = "%";\n return;\n }\n nDec = Math.floor(nDec);\n sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);\n let value = this.AFMakeNumber(event.value);\n if (value === null) {\n event.value = "%";\n return;\n }\n const formatStr = `%,${sepStyle}.${nDec}f`;\n value = this._util.printf(formatStr, value * 100);\n event.value = percentPrepend ? `%${value}` : `${value}%`;\n }\n AFPercent_Keystroke(nDec, sepStyle) {\n this.AFNumber_Keystroke(nDec, sepStyle, 0, 0, "", true);\n }\n AFDate_FormatEx(cFormat) {\n const event = globalThis.event;\n const value = event.value;\n if (!value) {\n return;\n }\n const date = this._parseDate(cFormat, value);\n if (date !== null) {\n event.value = this._util.printd(cFormat, date);\n }\n }\n AFDate_Format(pdf) {\n if (pdf >= 0 && pdf < this._dateFormats.length) {\n this.AFDate_FormatEx(this._dateFormats[pdf]);\n }\n }\n AFDate_KeystrokeEx(cFormat) {\n const event = globalThis.event;\n if (!event.willCommit) {\n return;\n }\n const value = this.AFMergeChange(event);\n if (!value) {\n return;\n }\n if (this._parseDate(cFormat, value) === null) {\n const invalid = GlobalConstants.IDS_INVALID_DATE;\n const invalid2 = GlobalConstants.IDS_INVALID_DATE2;\n const err = `${invalid} ${this._mkTargetName(event)}${invalid2}${cFormat}`;\n this._app.alert(err);\n event.rc = false;\n }\n }\n AFDate_Keystroke(pdf) {\n if (pdf >= 0 && pdf < this._dateFormats.length) {\n this.AFDate_KeystrokeEx(this._dateFormats[pdf]);\n }\n }\n AFRange_Validate(bGreaterThan, nGreaterThan, bLessThan, nLessThan) {\n const event = globalThis.event;\n if (!event.value) {\n return;\n }\n const value = this.AFMakeNumber(event.value);\n if (value === null) {\n return;\n }\n bGreaterThan = !!bGreaterThan;\n bLessThan = !!bLessThan;\n if (bGreaterThan) {\n nGreaterThan = this.AFMakeNumber(nGreaterThan);\n if (nGreaterThan === null) {\n return;\n }\n }\n if (bLessThan) {\n nLessThan = this.AFMakeNumber(nLessThan);\n if (nLessThan === null) {\n return;\n }\n }\n let err = "";\n if (bGreaterThan && bLessThan) {\n if (value < nGreaterThan || value > nLessThan) {\n err = this._util.printf(GlobalConstants.IDS_GT_AND_LT, nGreaterThan, nLessThan);\n }\n } else if (bGreaterThan) {\n if (value < nGreaterThan) {\n err = this._util.printf(GlobalConstants.IDS_GREATER_THAN, nGreaterThan);\n }\n } else if (value > nLessThan) {\n err = this._util.printf(GlobalConstants.IDS_LESS_THAN, nLessThan);\n }\n if (err) {\n this._app.alert(err);\n event.rc = false;\n }\n }\n AFSimple(cFunction, nValue1, nValue2) {\n const value1 = this.AFMakeNumber(nValue1);\n if (value1 === null) {\n throw new Error("Invalid nValue1 in AFSimple");\n }\n const value2 = this.AFMakeNumber(nValue2);\n if (value2 === null) {\n throw new Error("Invalid nValue2 in AFSimple");\n }\n switch (cFunction) {\n case "AVG":\n return (value1 + value2) / 2;\n case "SUM":\n return value1 + value2;\n case "PRD":\n return value1 * value2;\n case "MIN":\n return Math.min(value1, value2);\n case "MAX":\n return Math.max(value1, value2);\n }\n throw new Error("Invalid cFunction in AFSimple");\n }\n AFSimple_Calculate(cFunction, cFields) {\n const actions = {\n AVG: args => args.reduce((acc, value) => acc + value, 0) / args.length,\n SUM: args => args.reduce((acc, value) => acc + value, 0),\n PRD: args => args.reduce((acc, value) => acc * value, 1),\n MIN: args => args.reduce((acc, value) => Math.min(acc, value), Number.MAX_VALUE),\n MAX: args => args.reduce((acc, value) => Math.max(acc, value), Number.MIN_VALUE)\n };\n if (!(cFunction in actions)) {\n throw new TypeError("Invalid function in AFSimple_Calculate");\n }\n const event = globalThis.event;\n const values = [];\n cFields = this.AFMakeArrayFromList(cFields);\n for (const cField of cFields) {\n const field = this._document.getField(cField);\n if (!field) {\n continue;\n }\n for (const child of field.getArray()) {\n const number = this.AFMakeNumber(child.value);\n if (number !== null) {\n values.push(number);\n }\n }\n }\n if (values.length === 0) {\n event.value = cFunction === "PRD" ? 1 : 0;\n return;\n }\n const res = actions[cFunction](values);\n event.value = Math.round(1e6 * res) / 1e6;\n }\n AFSpecial_Format(psf) {\n const event = globalThis.event;\n if (!event.value) {\n return;\n }\n psf = this.AFMakeNumber(psf);\n let formatStr;\n switch (psf) {\n case 0:\n formatStr = "99999";\n break;\n case 1:\n formatStr = "99999-9999";\n break;\n case 2:\n formatStr = this._util.printx("9999999999", event.value).length >= 10 ? "(999) 999-9999" : "999-9999";\n break;\n case 3:\n formatStr = "999-99-9999";\n break;\n default:\n throw new Error("Invalid psf in AFSpecial_Format");\n }\n event.value = this._util.printx(formatStr, event.value);\n }\n AFSpecial_KeystrokeEx(cMask) {\n if (!cMask) {\n return;\n }\n const event = globalThis.event;\n const value = this.AFMergeChange(event);\n if (!value) {\n return;\n }\n const checkers = new Map([["9", char => char >= "0" && char <= "9"], ["A", char => "a" <= char && char <= "z" || "A" <= char && char <= "Z"], ["O", char => "a" <= char && char <= "z" || "A" <= char && char <= "Z" || "0" <= char && char <= "9"], ["X", char => true]]);\n function _checkValidity(_value, _cMask) {\n for (let i = 0, ii = _value.length; i < ii; i++) {\n const mask = _cMask.charAt(i);\n const char = _value.charAt(i);\n const checker = checkers.get(mask);\n if (checker) {\n if (!checker(char)) {\n return false;\n }\n } else if (mask !== char) {\n return false;\n }\n }\n return true;\n }\n const err = `${GlobalConstants.IDS_INVALID_VALUE} = "${cMask}"`;\n if (value.length > cMask.length) {\n this._app.alert(err);\n event.rc = false;\n return;\n }\n if (event.willCommit) {\n if (value.length < cMask.length) {\n this._app.alert(err);\n event.rc = false;\n return;\n }\n if (!_checkValidity(value, cMask)) {\n this._app.alert(err);\n event.rc = false;\n return;\n }\n event.value += cMask.substring(value.length);\n return;\n }\n if (value.length < cMask.length) {\n cMask = cMask.substring(0, value.length);\n }\n if (!_checkValidity(value, cMask)) {\n this._app.alert(err);\n event.rc = false;\n }\n }\n AFSpecial_Keystroke(psf) {\n const event = globalThis.event;\n psf = this.AFMakeNumber(psf);\n let formatStr;\n switch (psf) {\n case 0:\n formatStr = "99999";\n break;\n case 1:\n formatStr = "99999-9999";\n break;\n case 2:\n const value = this.AFMergeChange(event);\n formatStr = value.length > 8 || value.startsWith("(") ? "(999) 999-9999" : "999-9999";\n break;\n case 3:\n formatStr = "999-99-9999";\n break;\n default:\n throw new Error("Invalid psf in AFSpecial_Keystroke");\n }\n this.AFSpecial_KeystrokeEx(formatStr);\n }\n AFTime_FormatEx(cFormat) {\n this.AFDate_FormatEx(cFormat);\n }\n AFTime_Format(pdf) {\n if (pdf >= 0 && pdf < this._timeFormats.length) {\n this.AFDate_FormatEx(this._timeFormats[pdf]);\n }\n }\n AFTime_KeystrokeEx(cFormat) {\n this.AFDate_KeystrokeEx(cFormat);\n }\n AFTime_Keystroke(pdf) {\n if (pdf >= 0 && pdf < this._timeFormats.length) {\n this.AFDate_KeystrokeEx(this._timeFormats[pdf]);\n }\n }\n eMailValidate(str) {\n return this._emailRegex.test(str);\n }\n AFExactMatch(rePatterns, str) {\n if (rePatterns instanceof RegExp) {\n return str.match(rePatterns)?.[0] === str || 0;\n }\n return rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/app_utils.js\nconst VIEWER_TYPE = "PDF.js";\nconst VIEWER_VARIATION = "Full";\nconst VIEWER_VERSION = 21.00720099;\nconst FORMS_VERSION = 21.00720099;\nconst USERACTIVATION_CALLBACKID = 0;\nconst USERACTIVATION_MAXTIME_VALIDITY = 5000;\nfunction serializeError(error) {\n const value = `${error.toString()}\\n${error.stack}`;\n return {\n command: "error",\n value\n };\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/event.js\n\nclass Event {\n constructor(data) {\n this.change = data.change || "";\n this.changeEx = data.changeEx || null;\n this.commitKey = data.commitKey || 0;\n this.fieldFull = data.fieldFull || false;\n this.keyDown = data.keyDown || false;\n this.modifier = data.modifier || false;\n this.name = data.name;\n this.rc = true;\n this.richChange = data.richChange || [];\n this.richChangeEx = data.richChangeEx || [];\n this.richValue = data.richValue || [];\n this.selEnd = data.selEnd ?? -1;\n this.selStart = data.selStart ?? -1;\n this.shift = data.shift || false;\n this.source = data.source || null;\n this.target = data.target || null;\n this.targetName = "";\n this.type = "Field";\n this.value = data.value || "";\n this.willCommit = data.willCommit || false;\n }\n}\nclass EventDispatcher {\n constructor(document, calculationOrder, objects, externalCall) {\n this._document = document;\n this._calculationOrder = calculationOrder;\n this._objects = objects;\n this._externalCall = externalCall;\n this._document.obj._eventDispatcher = this;\n this._isCalculating = false;\n }\n mergeChange(event) {\n let value = event.value;\n if (Array.isArray(value)) {\n return value;\n }\n if (typeof value !== "string") {\n value = value.toString();\n }\n const prefix = event.selStart >= 0 ? value.substring(0, event.selStart) : "";\n const postfix = event.selEnd >= 0 && event.selEnd <= value.length ? value.substring(event.selEnd) : "";\n return `${prefix}${event.change}${postfix}`;\n }\n userActivation() {\n this._document.obj._userActivation = true;\n this._externalCall("setTimeout", [USERACTIVATION_CALLBACKID, USERACTIVATION_MAXTIME_VALIDITY]);\n }\n dispatch(baseEvent) {\n const id = baseEvent.id;\n if (!(id in this._objects)) {\n let event;\n if (id === "doc" || id === "page") {\n event = globalThis.event = new Event(baseEvent);\n event.source = event.target = this._document.wrapped;\n event.name = baseEvent.name;\n }\n if (id === "doc") {\n const eventName = event.name;\n if (eventName === "Open") {\n this.userActivation();\n this._document.obj._initActions();\n this.formatAll();\n }\n if (!["DidPrint", "DidSave", "WillPrint", "WillSave"].includes(eventName)) {\n this.userActivation();\n }\n this._document.obj._dispatchDocEvent(event.name);\n } else if (id === "page") {\n this.userActivation();\n this._document.obj._dispatchPageEvent(event.name, baseEvent.actions, baseEvent.pageNumber);\n } else if (id === "app" && baseEvent.name === "ResetForm") {\n this.userActivation();\n for (const fieldId of baseEvent.ids) {\n const obj = this._objects[fieldId];\n obj?.obj._reset();\n }\n }\n return;\n }\n const name = baseEvent.name;\n const source = this._objects[id];\n const event = globalThis.event = new Event(baseEvent);\n let savedChange;\n this.userActivation();\n if (source.obj._isButton()) {\n source.obj._id = id;\n event.value = source.obj._getExportValue(event.value);\n if (name === "Action") {\n source.obj._value = event.value;\n }\n }\n switch (name) {\n case "Keystroke":\n savedChange = {\n value: event.value,\n changeEx: event.changeEx,\n change: event.change,\n selStart: event.selStart,\n selEnd: event.selEnd\n };\n break;\n case "Blur":\n case "Focus":\n Object.defineProperty(event, "value", {\n configurable: false,\n writable: false,\n enumerable: true,\n value: event.value\n });\n break;\n case "Validate":\n this.runValidation(source, event);\n return;\n case "Action":\n this.runActions(source, source, event, name);\n this.runCalculate(source, event);\n return;\n }\n this.runActions(source, source, event, name);\n if (name !== "Keystroke") {\n return;\n }\n if (event.rc) {\n if (event.willCommit) {\n this.runValidation(source, event);\n } else {\n if (source.obj._isChoice) {\n source.obj.value = savedChange.changeEx;\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: source.obj.value\n });\n return;\n }\n const value = source.obj.value = this.mergeChange(event);\n let selStart, selEnd;\n if (event.selStart !== savedChange.selStart || event.selEnd !== savedChange.selEnd) {\n selStart = event.selStart;\n selEnd = event.selEnd;\n } else {\n selEnd = selStart = savedChange.selStart + event.change.length;\n }\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value,\n selRange: [selStart, selEnd]\n });\n }\n } else if (!event.willCommit) {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: savedChange.value,\n selRange: [savedChange.selStart, savedChange.selEnd]\n });\n } else {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: "",\n formattedValue: null,\n selRange: [0, 0]\n });\n }\n }\n formatAll() {\n const event = globalThis.event = new Event({});\n for (const source of Object.values(this._objects)) {\n event.value = source.obj.value;\n this.runActions(source, source, event, "Format");\n }\n }\n runValidation(source, event) {\n const didValidateRun = this.runActions(source, source, event, "Validate");\n if (event.rc) {\n source.obj.value = event.value;\n this.runCalculate(source, event);\n const savedValue = source.obj._getValue();\n event.value = source.obj.value;\n let formattedValue = null;\n if (this.runActions(source, source, event, "Format")) {\n formattedValue = event.value?.toString?.();\n }\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: savedValue,\n formattedValue\n });\n event.value = savedValue;\n } else if (didValidateRun) {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: "",\n formattedValue: null,\n selRange: [0, 0],\n focus: true\n });\n }\n }\n runActions(source, target, event, eventName) {\n event.source = source.wrapped;\n event.target = target.wrapped;\n event.name = eventName;\n event.targetName = target.obj.name;\n event.rc = true;\n return target.obj._runActions(event);\n }\n calculateNow() {\n if (!this._calculationOrder || this._isCalculating || !this._document.obj.calculate) {\n return;\n }\n this._isCalculating = true;\n const first = this._calculationOrder[0];\n const source = this._objects[first];\n globalThis.event = new Event({});\n try {\n this.runCalculate(source, globalThis.event);\n } catch (error) {\n this._isCalculating = false;\n throw error;\n }\n this._isCalculating = false;\n }\n runCalculate(source, event) {\n if (!this._calculationOrder || !this._document.obj.calculate) {\n return;\n }\n for (const targetId of this._calculationOrder) {\n if (!(targetId in this._objects)) {\n continue;\n }\n if (!this._document.obj.calculate) {\n break;\n }\n event.value = null;\n const target = this._objects[targetId];\n let savedValue = target.obj.value;\n this.runActions(source, target, event, "Calculate");\n if (!event.rc) {\n continue;\n }\n if (event.value !== null) {\n target.obj.value = event.value;\n }\n event.value = target.obj.value;\n this.runActions(target, target, event, "Validate");\n if (!event.rc) {\n if (target.obj.value !== savedValue) {\n target.wrapped.value = savedValue;\n }\n continue;\n }\n savedValue = event.value = target.obj.value;\n let formattedValue = null;\n if (this.runActions(target, target, event, "Format")) {\n formattedValue = event.value?.toString?.();\n }\n target.obj._send({\n id: target.obj._id,\n siblings: target.obj._siblings,\n value: savedValue,\n formattedValue\n });\n }\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/fullscreen.js\n\n\nclass FullScreen extends PDFObject {\n constructor(data) {\n super(data);\n this._backgroundColor = [];\n this._clickAdvances = true;\n this._cursor = Cursor.hidden;\n this._defaultTransition = "";\n this._escapeExits = true;\n this._isFullScreen = true;\n this._loop = false;\n this._timeDelay = 3600;\n this._usePageTiming = false;\n this._useTimer = false;\n }\n get backgroundColor() {\n return this._backgroundColor;\n }\n set backgroundColor(_) {}\n get clickAdvances() {\n return this._clickAdvances;\n }\n set clickAdvances(_) {}\n get cursor() {\n return this._cursor;\n }\n set cursor(_) {}\n get defaultTransition() {\n return this._defaultTransition;\n }\n set defaultTransition(_) {}\n get escapeExits() {\n return this._escapeExits;\n }\n set escapeExits(_) {}\n get isFullScreen() {\n return this._isFullScreen;\n }\n set isFullScreen(_) {}\n get loop() {\n return this._loop;\n }\n set loop(_) {}\n get timeDelay() {\n return this._timeDelay;\n }\n set timeDelay(_) {}\n get transitions() {\n return ["Replace", "WipeRight", "WipeLeft", "WipeDown", "WipeUp", "SplitHorizontalIn", "SplitHorizontalOut", "SplitVerticalIn", "SplitVerticalOut", "BlindsHorizontal", "BlindsVertical", "BoxIn", "BoxOut", "GlitterRight", "GlitterDown", "GlitterRightDown", "Dissolve", "Random"];\n }\n set transitions(_) {\n throw new Error("fullscreen.transitions is read-only");\n }\n get usePageTiming() {\n return this._usePageTiming;\n }\n set usePageTiming(_) {}\n get useTimer() {\n return this._useTimer;\n }\n set useTimer(_) {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/thermometer.js\n\nclass Thermometer extends PDFObject {\n constructor(data) {\n super(data);\n this._cancelled = false;\n this._duration = 100;\n this._text = "";\n this._value = 0;\n }\n get cancelled() {\n return this._cancelled;\n }\n set cancelled(_) {\n throw new Error("thermometer.cancelled is read-only");\n }\n get duration() {\n return this._duration;\n }\n set duration(val) {\n this._duration = val;\n }\n get text() {\n return this._text;\n }\n set text(val) {\n this._text = val;\n }\n get value() {\n return this._value;\n }\n set value(val) {\n this._value = val;\n }\n begin() {}\n end() {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/app.js\n\n\n\n\n\n\nclass App extends PDFObject {\n constructor(data) {\n super(data);\n this._constants = null;\n this._focusRect = true;\n this._fs = null;\n this._language = App._getLanguage(data.language);\n this._openInPlace = false;\n this._platform = App._getPlatform(data.platform);\n this._runtimeHighlight = false;\n this._runtimeHighlightColor = ["T"];\n this._thermometer = null;\n this._toolbar = false;\n this._document = data._document;\n this._proxyHandler = data.proxyHandler;\n this._objects = Object.create(null);\n this._eventDispatcher = new EventDispatcher(this._document, data.calculationOrder, this._objects, data.externalCall);\n this._timeoutIds = new WeakMap();\n if (typeof FinalizationRegistry !== "undefined") {\n this._timeoutIdsRegistry = new FinalizationRegistry(this._cleanTimeout.bind(this));\n } else {\n this._timeoutIdsRegistry = null;\n }\n this._timeoutCallbackIds = new Map();\n this._timeoutCallbackId = USERACTIVATION_CALLBACKID + 1;\n this._globalEval = data.globalEval;\n this._externalCall = data.externalCall;\n }\n _dispatchEvent(pdfEvent) {\n this._eventDispatcher.dispatch(pdfEvent);\n }\n _registerTimeoutCallback(cExpr) {\n const id = this._timeoutCallbackId++;\n this._timeoutCallbackIds.set(id, cExpr);\n return id;\n }\n _unregisterTimeoutCallback(id) {\n this._timeoutCallbackIds.delete(id);\n }\n _evalCallback({\n callbackId,\n interval\n }) {\n if (callbackId === USERACTIVATION_CALLBACKID) {\n this._document.obj._userActivation = false;\n return;\n }\n const expr = this._timeoutCallbackIds.get(callbackId);\n if (!interval) {\n this._unregisterTimeoutCallback(callbackId);\n }\n if (expr) {\n this._globalEval(expr);\n }\n }\n _registerTimeout(callbackId, interval) {\n const timeout = Object.create(null);\n const id = {\n callbackId,\n interval\n };\n this._timeoutIds.set(timeout, id);\n this._timeoutIdsRegistry?.register(timeout, id);\n return timeout;\n }\n _unregisterTimeout(timeout) {\n this._timeoutIdsRegistry?.unregister(timeout);\n const data = this._timeoutIds.get(timeout);\n if (!data) {\n return;\n }\n this._timeoutIds.delete(timeout);\n this._cleanTimeout(data);\n }\n _cleanTimeout({\n callbackId,\n interval\n }) {\n this._unregisterTimeoutCallback(callbackId);\n if (interval) {\n this._externalCall("clearInterval", [callbackId]);\n } else {\n this._externalCall("clearTimeout", [callbackId]);\n }\n }\n static _getPlatform(platform) {\n if (typeof platform === "string") {\n platform = platform.toLowerCase();\n if (platform.includes("win")) {\n return "WIN";\n } else if (platform.includes("mac")) {\n return "MAC";\n }\n }\n return "UNIX";\n }\n static _getLanguage(language) {\n const [main, sub] = language.toLowerCase().split(/[-_]/);\n switch (main) {\n case "zh":\n if (sub === "cn" || sub === "sg") {\n return "CHS";\n }\n return "CHT";\n case "da":\n return "DAN";\n case "de":\n return "DEU";\n case "es":\n return "ESP";\n case "fr":\n return "FRA";\n case "it":\n return "ITA";\n case "ko":\n return "KOR";\n case "ja":\n return "JPN";\n case "nl":\n return "NLD";\n case "no":\n return "NOR";\n case "pt":\n if (sub === "br") {\n return "PTB";\n }\n return "ENU";\n case "fi":\n return "SUO";\n case "SV":\n return "SVE";\n default:\n return "ENU";\n }\n }\n get activeDocs() {\n return [this._document.wrapped];\n }\n set activeDocs(_) {\n throw new Error("app.activeDocs is read-only");\n }\n get calculate() {\n return this._document.obj.calculate;\n }\n set calculate(calculate) {\n this._document.obj.calculate = calculate;\n }\n get constants() {\n if (!this._constants) {\n this._constants = Object.freeze({\n align: Object.freeze({\n left: 0,\n center: 1,\n right: 2,\n top: 3,\n bottom: 4\n })\n });\n }\n return this._constants;\n }\n set constants(_) {\n throw new Error("app.constants is read-only");\n }\n get focusRect() {\n return this._focusRect;\n }\n set focusRect(val) {\n this._focusRect = val;\n }\n get formsVersion() {\n return FORMS_VERSION;\n }\n set formsVersion(_) {\n throw new Error("app.formsVersion is read-only");\n }\n get fromPDFConverters() {\n return [];\n }\n set fromPDFConverters(_) {\n throw new Error("app.fromPDFConverters is read-only");\n }\n get fs() {\n if (this._fs === null) {\n this._fs = new Proxy(new FullScreen({\n send: this._send\n }), this._proxyHandler);\n }\n return this._fs;\n }\n set fs(_) {\n throw new Error("app.fs is read-only");\n }\n get language() {\n return this._language;\n }\n set language(_) {\n throw new Error("app.language is read-only");\n }\n get media() {\n return undefined;\n }\n set media(_) {\n throw new Error("app.media is read-only");\n }\n get monitors() {\n return [];\n }\n set monitors(_) {\n throw new Error("app.monitors is read-only");\n }\n get numPlugins() {\n return 0;\n }\n set numPlugins(_) {\n throw new Error("app.numPlugins is read-only");\n }\n get openInPlace() {\n return this._openInPlace;\n }\n set openInPlace(val) {\n this._openInPlace = val;\n }\n get platform() {\n return this._platform;\n }\n set platform(_) {\n throw new Error("app.platform is read-only");\n }\n get plugins() {\n return [];\n }\n set plugins(_) {\n throw new Error("app.plugins is read-only");\n }\n get printColorProfiles() {\n return [];\n }\n set printColorProfiles(_) {\n throw new Error("app.printColorProfiles is read-only");\n }\n get printerNames() {\n return [];\n }\n set printerNames(_) {\n throw new Error("app.printerNames is read-only");\n }\n get runtimeHighlight() {\n return this._runtimeHighlight;\n }\n set runtimeHighlight(val) {\n this._runtimeHighlight = val;\n }\n get runtimeHighlightColor() {\n return this._runtimeHighlightColor;\n }\n set runtimeHighlightColor(val) {\n if (Color._isValidColor(val)) {\n this._runtimeHighlightColor = val;\n }\n }\n get thermometer() {\n if (this._thermometer === null) {\n this._thermometer = new Proxy(new Thermometer({\n send: this._send\n }), this._proxyHandler);\n }\n return this._thermometer;\n }\n set thermometer(_) {\n throw new Error("app.thermometer is read-only");\n }\n get toolbar() {\n return this._toolbar;\n }\n set toolbar(val) {\n this._toolbar = val;\n }\n get toolbarHorizontal() {\n return this.toolbar;\n }\n set toolbarHorizontal(value) {\n this.toolbar = value;\n }\n get toolbarVertical() {\n return this.toolbar;\n }\n set toolbarVertical(value) {\n this.toolbar = value;\n }\n get viewerType() {\n return VIEWER_TYPE;\n }\n set viewerType(_) {\n throw new Error("app.viewerType is read-only");\n }\n get viewerVariation() {\n return VIEWER_VARIATION;\n }\n set viewerVariation(_) {\n throw new Error("app.viewerVariation is read-only");\n }\n get viewerVersion() {\n return VIEWER_VERSION;\n }\n set viewerVersion(_) {\n throw new Error("app.viewerVersion is read-only");\n }\n addMenuItem() {}\n addSubMenu() {}\n addToolButton() {}\n alert(cMsg, nIcon = 0, nType = 0, cTitle = "PDF.js", oDoc = null, oCheckbox = null) {\n if (!this._document.obj._userActivation) {\n return 0;\n }\n this._document.obj._userActivation = false;\n if (cMsg && typeof cMsg === "object") {\n nType = cMsg.nType;\n cMsg = cMsg.cMsg;\n }\n cMsg = (cMsg || "").toString();\n nType = typeof nType !== "number" || isNaN(nType) || nType < 0 || nType > 3 ? 0 : nType;\n if (nType >= 2) {\n return this._externalCall("confirm", [cMsg]) ? 4 : 3;\n }\n this._externalCall("alert", [cMsg]);\n return 1;\n }\n beep() {}\n beginPriv() {}\n browseForDoc() {}\n clearInterval(oInterval) {\n this._unregisterTimeout(oInterval);\n }\n clearTimeOut(oTime) {\n this._unregisterTimeout(oTime);\n }\n endPriv() {}\n execDialog() {}\n execMenuItem(item) {\n if (!this._document.obj._userActivation) {\n return;\n }\n this._document.obj._userActivation = false;\n switch (item) {\n case "SaveAs":\n if (this._document.obj._disableSaving) {\n return;\n }\n this._send({\n command: item\n });\n break;\n case "FirstPage":\n case "LastPage":\n case "NextPage":\n case "PrevPage":\n case "ZoomViewIn":\n case "ZoomViewOut":\n this._send({\n command: item\n });\n break;\n case "FitPage":\n this._send({\n command: "zoom",\n value: "page-fit"\n });\n break;\n case "Print":\n if (this._document.obj._disablePrinting) {\n return;\n }\n this._send({\n command: "print"\n });\n break;\n }\n }\n getNthPlugInName() {}\n getPath() {}\n goBack() {}\n goForward() {}\n hideMenuItem() {}\n hideToolbarButton() {}\n launchURL() {}\n listMenuItems() {}\n listToolbarButtons() {}\n loadPolicyFile() {}\n mailGetAddrs() {}\n mailMsg() {}\n newDoc() {}\n newCollection() {}\n newFDF() {}\n openDoc() {}\n openFDF() {}\n popUpMenu() {}\n popUpMenuEx() {}\n removeToolButton() {}\n response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {\n if (cQuestion && typeof cQuestion === "object") {\n cDefault = cQuestion.cDefault;\n cQuestion = cQuestion.cQuestion;\n }\n cQuestion = (cQuestion || "").toString();\n cDefault = (cDefault || "").toString();\n return this._externalCall("prompt", [cQuestion, cDefault || ""]);\n }\n setInterval(cExpr, nMilliseconds = 0) {\n if (cExpr && typeof cExpr === "object") {\n nMilliseconds = cExpr.nMilliseconds || 0;\n cExpr = cExpr.cExpr;\n }\n if (typeof cExpr !== "string") {\n throw new TypeError("First argument of app.setInterval must be a string");\n }\n if (typeof nMilliseconds !== "number") {\n throw new TypeError("Second argument of app.setInterval must be a number");\n }\n const callbackId = this._registerTimeoutCallback(cExpr);\n this._externalCall("setInterval", [callbackId, nMilliseconds]);\n return this._registerTimeout(callbackId, true);\n }\n setTimeOut(cExpr, nMilliseconds = 0) {\n if (cExpr && typeof cExpr === "object") {\n nMilliseconds = cExpr.nMilliseconds || 0;\n cExpr = cExpr.cExpr;\n }\n if (typeof cExpr !== "string") {\n throw new TypeError("First argument of app.setTimeOut must be a string");\n }\n if (typeof nMilliseconds !== "number") {\n throw new TypeError("Second argument of app.setTimeOut must be a number");\n }\n const callbackId = this._registerTimeoutCallback(cExpr);\n this._externalCall("setTimeout", [callbackId, nMilliseconds]);\n return this._registerTimeout(callbackId, false);\n }\n trustedFunction() {}\n trustPropagatorFunction() {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/console.js\n\nclass Console extends PDFObject {\n clear() {\n this._send({\n id: "clear"\n });\n }\n hide() {}\n println(msg) {\n if (typeof msg === "string") {\n this._send({\n command: "println",\n value: "PDF.js Console:: " + msg\n });\n }\n }\n show() {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/print_params.js\nclass PrintParams {\n constructor(data) {\n this.binaryOk = true;\n this.bitmapDPI = 150;\n this.booklet = {\n binding: 0,\n duplexMode: 0,\n subsetFrom: 0,\n subsetTo: -1\n };\n this.colorOverride = 0;\n this.colorProfile = "";\n this.constants = Object.freeze({\n bookletBindings: Object.freeze({\n Left: 0,\n Right: 1,\n LeftTall: 2,\n RightTall: 3\n }),\n bookletDuplexMode: Object.freeze({\n BothSides: 0,\n FrontSideOnly: 1,\n BasicSideOnly: 2\n }),\n colorOverrides: Object.freeze({\n auto: 0,\n gray: 1,\n mono: 2\n }),\n fontPolicies: Object.freeze({\n everyPage: 0,\n jobStart: 1,\n pageRange: 2\n }),\n handling: Object.freeze({\n none: 0,\n fit: 1,\n shrink: 2,\n tileAll: 3,\n tileLarge: 4,\n nUp: 5,\n booklet: 6\n }),\n interactionLevel: Object.freeze({\n automatic: 0,\n full: 1,\n silent: 2\n }),\n nUpPageOrders: Object.freeze({\n Horizontal: 0,\n HorizontalReversed: 1,\n Vertical: 2\n }),\n printContents: Object.freeze({\n doc: 0,\n docAndComments: 1,\n formFieldsOnly: 2\n }),\n flagValues: Object.freeze({\n applyOverPrint: 1,\n applySoftProofSettings: 1 << 1,\n applyWorkingColorSpaces: 1 << 2,\n emitHalftones: 1 << 3,\n emitPostScriptXObjects: 1 << 4,\n emitFormsAsPSForms: 1 << 5,\n maxJP2KRes: 1 << 6,\n setPageSize: 1 << 7,\n suppressBG: 1 << 8,\n suppressCenter: 1 << 9,\n suppressCJKFontSubst: 1 << 10,\n suppressCropClip: 1 << 1,\n suppressRotate: 1 << 12,\n suppressTransfer: 1 << 13,\n suppressUCR: 1 << 14,\n useTrapAnnots: 1 << 15,\n usePrintersMarks: 1 << 16\n }),\n rasterFlagValues: Object.freeze({\n textToOutline: 1,\n strokesToOutline: 1 << 1,\n allowComplexClip: 1 << 2,\n preserveOverprint: 1 << 3\n }),\n subsets: Object.freeze({\n all: 0,\n even: 1,\n odd: 2\n }),\n tileMarks: Object.freeze({\n none: 0,\n west: 1,\n east: 2\n }),\n usages: Object.freeze({\n auto: 0,\n use: 1,\n noUse: 2\n })\n });\n this.downloadFarEastFonts = false;\n this.fileName = "";\n this.firstPage = 0;\n this.flags = 0;\n this.fontPolicy = 0;\n this.gradientDPI = 150;\n this.interactive = 1;\n this.lastPage = data.lastPage;\n this.npUpAutoRotate = false;\n this.npUpNumPagesH = 2;\n this.npUpNumPagesV = 2;\n this.npUpPageBorder = false;\n this.npUpPageOrder = 0;\n this.pageHandling = 0;\n this.pageSubset = 0;\n this.printAsImage = false;\n this.printContent = 0;\n this.printerName = "";\n this.psLevel = 0;\n this.rasterFlags = 0;\n this.reversePages = false;\n this.tileLabel = false;\n this.tileMark = 0;\n this.tileOverlap = 0;\n this.tileScale = 1.0;\n this.transparencyLevel = 75;\n this.usePrinterCRD = 0;\n this.useT1Conversion = 0;\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/doc.js\n\n\n\n\n\nconst DOC_EXTERNAL = false;\nclass InfoProxyHandler {\n static get(obj, prop) {\n return obj[prop.toLowerCase()];\n }\n static set(obj, prop, value) {\n throw new Error(`doc.info.${prop} is read-only`);\n }\n}\nclass Doc extends PDFObject {\n constructor(data) {\n super(data);\n this._expandos = globalThis;\n this._baseURL = data.baseURL || "";\n this._calculate = true;\n this._delay = false;\n this._dirty = false;\n this._disclosed = false;\n this._media = undefined;\n this._metadata = data.metadata || "";\n this._noautocomplete = undefined;\n this._nocache = undefined;\n this._spellDictionaryOrder = [];\n this._spellLanguageOrder = [];\n this._printParams = null;\n this._fields = new Map();\n this._fieldNames = [];\n this._event = null;\n this._author = data.Author || "";\n this._creator = data.Creator || "";\n this._creationDate = this._getDate(data.CreationDate) || null;\n this._docID = data.docID || ["", ""];\n this._documentFileName = data.filename || "";\n this._filesize = data.filesize || 0;\n this._keywords = data.Keywords || "";\n this._layout = data.layout || "";\n this._modDate = this._getDate(data.ModDate) || null;\n this._numFields = 0;\n this._numPages = data.numPages || 1;\n this._pageNum = data.pageNum || 0;\n this._producer = data.Producer || "";\n this._securityHandler = data.EncryptFilterName || null;\n this._subject = data.Subject || "";\n this._title = data.Title || "";\n this._URL = data.URL || "";\n this._info = new Proxy({\n title: this._title,\n author: this._author,\n authors: data.authors || [this._author],\n subject: this._subject,\n keywords: this._keywords,\n creator: this._creator,\n producer: this._producer,\n creationdate: this._creationDate,\n moddate: this._modDate,\n trapped: data.Trapped || "Unknown"\n }, InfoProxyHandler);\n this._zoomType = ZoomType.none;\n this._zoom = data.zoom || 100;\n this._actions = createActionsMap(data.actions);\n this._globalEval = data.globalEval;\n this._pageActions = new Map();\n this._userActivation = false;\n this._disablePrinting = false;\n this._disableSaving = false;\n }\n _initActions() {\n const dontRun = new Set(["WillClose", "WillSave", "DidSave", "WillPrint", "DidPrint", "OpenAction"]);\n this._disableSaving = true;\n for (const actionName of this._actions.keys()) {\n if (!dontRun.has(actionName)) {\n this._runActions(actionName);\n }\n }\n this._runActions("OpenAction");\n this._disableSaving = false;\n }\n _dispatchDocEvent(name) {\n switch (name) {\n case "Open":\n this._disableSaving = true;\n this._runActions("OpenAction");\n this._disableSaving = false;\n break;\n case "WillPrint":\n this._disablePrinting = true;\n try {\n this._runActions(name);\n } catch (error) {\n this._send(serializeError(error));\n }\n this._send({\n command: "WillPrintFinished"\n });\n this._disablePrinting = false;\n break;\n case "WillSave":\n this._disableSaving = true;\n this._runActions(name);\n this._disableSaving = false;\n break;\n default:\n this._runActions(name);\n }\n }\n _dispatchPageEvent(name, actions, pageNumber) {\n if (name === "PageOpen") {\n if (!this._pageActions.has(pageNumber)) {\n this._pageActions.set(pageNumber, createActionsMap(actions));\n }\n this._pageNum = pageNumber - 1;\n }\n actions = this._pageActions.get(pageNumber)?.get(name);\n if (actions) {\n for (const action of actions) {\n this._globalEval(action);\n }\n }\n }\n _runActions(name) {\n const actions = this._actions.get(name);\n if (actions) {\n for (const action of actions) {\n this._globalEval(action);\n }\n }\n }\n _addField(name, field) {\n this._fields.set(name, field);\n this._fieldNames.push(name);\n this._numFields++;\n }\n _getDate(date) {\n if (!date || date.length < 15 || !date.startsWith("D:")) {\n return date;\n }\n date = date.substring(2);\n const year = date.substring(0, 4);\n const month = date.substring(4, 6);\n const day = date.substring(6, 8);\n const hour = date.substring(8, 10);\n const minute = date.substring(10, 12);\n const o = date.charAt(12);\n let second, offsetPos;\n if (o === "Z" || o === "+" || o === "-") {\n second = "00";\n offsetPos = 12;\n } else {\n second = date.substring(12, 14);\n offsetPos = 14;\n }\n const offset = date.substring(offsetPos).replaceAll("\'", "");\n return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`);\n }\n get author() {\n return this._author;\n }\n set author(_) {\n throw new Error("doc.author is read-only");\n }\n get baseURL() {\n return this._baseURL;\n }\n set baseURL(baseURL) {\n this._baseURL = baseURL;\n }\n get bookmarkRoot() {\n return undefined;\n }\n set bookmarkRoot(_) {\n throw new Error("doc.bookmarkRoot is read-only");\n }\n get calculate() {\n return this._calculate;\n }\n set calculate(calculate) {\n this._calculate = calculate;\n }\n get creator() {\n return this._creator;\n }\n set creator(_) {\n throw new Error("doc.creator is read-only");\n }\n get dataObjects() {\n return [];\n }\n set dataObjects(_) {\n throw new Error("doc.dataObjects is read-only");\n }\n get delay() {\n return this._delay;\n }\n set delay(delay) {\n this._delay = delay;\n }\n get dirty() {\n return this._dirty;\n }\n set dirty(dirty) {\n this._dirty = dirty;\n }\n get disclosed() {\n return this._disclosed;\n }\n set disclosed(disclosed) {\n this._disclosed = disclosed;\n }\n get docID() {\n return this._docID;\n }\n set docID(_) {\n throw new Error("doc.docID is read-only");\n }\n get documentFileName() {\n return this._documentFileName;\n }\n set documentFileName(_) {\n throw new Error("doc.documentFileName is read-only");\n }\n get dynamicXFAForm() {\n return false;\n }\n set dynamicXFAForm(_) {\n throw new Error("doc.dynamicXFAForm is read-only");\n }\n get external() {\n return DOC_EXTERNAL;\n }\n set external(_) {\n throw new Error("doc.external is read-only");\n }\n get filesize() {\n return this._filesize;\n }\n set filesize(_) {\n throw new Error("doc.filesize is read-only");\n }\n get hidden() {\n return false;\n }\n set hidden(_) {\n throw new Error("doc.hidden is read-only");\n }\n get hostContainer() {\n return undefined;\n }\n set hostContainer(_) {\n throw new Error("doc.hostContainer is read-only");\n }\n get icons() {\n return undefined;\n }\n set icons(_) {\n throw new Error("doc.icons is read-only");\n }\n get info() {\n return this._info;\n }\n set info(_) {\n throw new Error("doc.info is read-only");\n }\n get innerAppWindowRect() {\n return [0, 0, 0, 0];\n }\n set innerAppWindowRect(_) {\n throw new Error("doc.innerAppWindowRect is read-only");\n }\n get innerDocWindowRect() {\n return [0, 0, 0, 0];\n }\n set innerDocWindowRect(_) {\n throw new Error("doc.innerDocWindowRect is read-only");\n }\n get isModal() {\n return false;\n }\n set isModal(_) {\n throw new Error("doc.isModal is read-only");\n }\n get keywords() {\n return this._keywords;\n }\n set keywords(_) {\n throw new Error("doc.keywords is read-only");\n }\n get layout() {\n return this._layout;\n }\n set layout(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== "string") {\n return;\n }\n if (value !== "SinglePage" && value !== "OneColumn" && value !== "TwoColumnLeft" && value !== "TwoPageLeft" && value !== "TwoColumnRight" && value !== "TwoPageRight") {\n value = "SinglePage";\n }\n this._send({\n command: "layout",\n value\n });\n this._layout = value;\n }\n get media() {\n return this._media;\n }\n set media(media) {\n this._media = media;\n }\n get metadata() {\n return this._metadata;\n }\n set metadata(metadata) {\n this._metadata = metadata;\n }\n get modDate() {\n return this._modDate;\n }\n set modDate(_) {\n throw new Error("doc.modDate is read-only");\n }\n get mouseX() {\n return 0;\n }\n set mouseX(_) {\n throw new Error("doc.mouseX is read-only");\n }\n get mouseY() {\n return 0;\n }\n set mouseY(_) {\n throw new Error("doc.mouseY is read-only");\n }\n get noautocomplete() {\n return this._noautocomplete;\n }\n set noautocomplete(noautocomplete) {\n this._noautocomplete = noautocomplete;\n }\n get nocache() {\n return this._nocache;\n }\n set nocache(nocache) {\n this._nocache = nocache;\n }\n get numFields() {\n return this._numFields;\n }\n set numFields(_) {\n throw new Error("doc.numFields is read-only");\n }\n get numPages() {\n return this._numPages;\n }\n set numPages(_) {\n throw new Error("doc.numPages is read-only");\n }\n get numTemplates() {\n return 0;\n }\n set numTemplates(_) {\n throw new Error("doc.numTemplates is read-only");\n }\n get outerAppWindowRect() {\n return [0, 0, 0, 0];\n }\n set outerAppWindowRect(_) {\n throw new Error("doc.outerAppWindowRect is read-only");\n }\n get outerDocWindowRect() {\n return [0, 0, 0, 0];\n }\n set outerDocWindowRect(_) {\n throw new Error("doc.outerDocWindowRect is read-only");\n }\n get pageNum() {\n return this._pageNum;\n }\n set pageNum(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== "number" || value < 0 || value >= this._numPages) {\n return;\n }\n this._send({\n command: "page-num",\n value\n });\n this._pageNum = value;\n }\n get pageWindowRect() {\n return [0, 0, 0, 0];\n }\n set pageWindowRect(_) {\n throw new Error("doc.pageWindowRect is read-only");\n }\n get path() {\n return "";\n }\n set path(_) {\n throw new Error("doc.path is read-only");\n }\n get permStatusReady() {\n return true;\n }\n set permStatusReady(_) {\n throw new Error("doc.permStatusReady is read-only");\n }\n get producer() {\n return this._producer;\n }\n set producer(_) {\n throw new Error("doc.producer is read-only");\n }\n get requiresFullSave() {\n return false;\n }\n set requiresFullSave(_) {\n throw new Error("doc.requiresFullSave is read-only");\n }\n get securityHandler() {\n return this._securityHandler;\n }\n set securityHandler(_) {\n throw new Error("doc.securityHandler is read-only");\n }\n get selectedAnnots() {\n return [];\n }\n set selectedAnnots(_) {\n throw new Error("doc.selectedAnnots is read-only");\n }\n get sounds() {\n return [];\n }\n set sounds(_) {\n throw new Error("doc.sounds is read-only");\n }\n get spellDictionaryOrder() {\n return this._spellDictionaryOrder;\n }\n set spellDictionaryOrder(spellDictionaryOrder) {\n this._spellDictionaryOrder = spellDictionaryOrder;\n }\n get spellLanguageOrder() {\n return this._spellLanguageOrder;\n }\n set spellLanguageOrder(spellLanguageOrder) {\n this._spellLanguageOrder = spellLanguageOrder;\n }\n get subject() {\n return this._subject;\n }\n set subject(_) {\n throw new Error("doc.subject is read-only");\n }\n get templates() {\n return [];\n }\n set templates(_) {\n throw new Error("doc.templates is read-only");\n }\n get title() {\n return this._title;\n }\n set title(_) {\n throw new Error("doc.title is read-only");\n }\n get URL() {\n return this._URL;\n }\n set URL(_) {\n throw new Error("doc.URL is read-only");\n }\n get viewState() {\n return undefined;\n }\n set viewState(_) {\n throw new Error("doc.viewState is read-only");\n }\n get xfa() {\n return this._xfa;\n }\n set xfa(_) {\n throw new Error("doc.xfa is read-only");\n }\n get XFAForeground() {\n return false;\n }\n set XFAForeground(_) {\n throw new Error("doc.XFAForeground is read-only");\n }\n get zoomType() {\n return this._zoomType;\n }\n set zoomType(type) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof type !== "string") {\n return;\n }\n switch (type) {\n case ZoomType.none:\n this._send({\n command: "zoom",\n value: 1\n });\n break;\n case ZoomType.fitP:\n this._send({\n command: "zoom",\n value: "page-fit"\n });\n break;\n case ZoomType.fitW:\n this._send({\n command: "zoom",\n value: "page-width"\n });\n break;\n case ZoomType.fitH:\n this._send({\n command: "zoom",\n value: "page-height"\n });\n break;\n case ZoomType.fitV:\n this._send({\n command: "zoom",\n value: "auto"\n });\n break;\n case ZoomType.pref:\n case ZoomType.refW:\n break;\n default:\n return;\n }\n this._zoomType = type;\n }\n get zoom() {\n return this._zoom;\n }\n set zoom(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== "number" || value < 8.33 || value > 6400) {\n return;\n }\n this._send({\n command: "zoom",\n value: value / 100\n });\n }\n addAnnot() {}\n addField() {}\n addIcon() {}\n addLink() {}\n addRecipientListCryptFilter() {}\n addRequirement() {}\n addScript() {}\n addThumbnails() {}\n addWatermarkFromFile() {}\n addWatermarkFromText() {}\n addWeblinks() {}\n bringToFront() {}\n calculateNow() {\n this._eventDispatcher.calculateNow();\n }\n closeDoc() {}\n colorConvertPage() {}\n createDataObject() {}\n createTemplate() {}\n deletePages() {}\n deleteSound() {}\n embedDocAsDataObject() {}\n embedOutputIntent() {}\n encryptForRecipients() {}\n encryptUsingPolicy() {}\n exportAsFDF() {}\n exportAsFDFStr() {}\n exportAsText() {}\n exportAsXFDF() {}\n exportAsXFDFStr() {}\n exportDataObject() {}\n exportXFAData() {}\n extractPages() {}\n flattenPages() {}\n getAnnot() {}\n getAnnots() {}\n getAnnot3D() {}\n getAnnots3D() {}\n getColorConvertAction() {}\n getDataObject() {}\n getDataObjectContents() {}\n _getField(cName) {\n if (cName && typeof cName === "object") {\n cName = cName.cName;\n }\n if (typeof cName !== "string") {\n throw new TypeError("Invalid field name: must be a string");\n }\n const searchedField = this._fields.get(cName);\n if (searchedField) {\n return searchedField;\n }\n const parts = cName.split("#");\n let childIndex = NaN;\n if (parts.length === 2) {\n childIndex = Math.floor(parseFloat(parts[1]));\n cName = parts[0];\n }\n for (const [name, field] of this._fields.entries()) {\n if (name.endsWith(cName)) {\n if (!isNaN(childIndex)) {\n const children = this._getChildren(name);\n if (childIndex < 0 || childIndex >= children.length) {\n childIndex = 0;\n }\n if (childIndex < children.length) {\n this._fields.set(cName, children[childIndex]);\n return children[childIndex];\n }\n }\n this._fields.set(cName, field);\n return field;\n }\n }\n return null;\n }\n getField(cName) {\n const field = this._getField(cName);\n if (!field) {\n return null;\n }\n return field.wrapped;\n }\n _getChildren(fieldName) {\n const len = fieldName.length;\n const children = [];\n const pattern = /^\\.[^.]+$/;\n for (const [name, field] of this._fields.entries()) {\n if (name.startsWith(fieldName)) {\n const finalPart = name.slice(len);\n if (pattern.test(finalPart)) {\n children.push(field);\n }\n }\n }\n return children;\n }\n _getTerminalChildren(fieldName) {\n const children = [];\n const len = fieldName.length;\n for (const [name, field] of this._fields.entries()) {\n if (name.startsWith(fieldName)) {\n const finalPart = name.slice(len);\n if (field.obj._hasValue && (finalPart === "" || finalPart.startsWith("."))) {\n children.push(field.wrapped);\n }\n }\n }\n return children;\n }\n getIcon() {}\n getLegalWarnings() {}\n getLinks() {}\n getNthFieldName(nIndex) {\n if (nIndex && typeof nIndex === "object") {\n nIndex = nIndex.nIndex;\n }\n if (typeof nIndex !== "number") {\n throw new TypeError("Invalid field index: must be a number");\n }\n if (0 <= nIndex && nIndex < this.numFields) {\n return this._fieldNames[Math.trunc(nIndex)];\n }\n return null;\n }\n getNthTemplate() {\n return null;\n }\n getOCGs() {}\n getOCGOrder() {}\n getPageBox() {}\n getPageLabel() {}\n getPageNthWord() {}\n getPageNthWordQuads() {}\n getPageNumWords() {}\n getPageRotation() {}\n getPageTransition() {}\n getPrintParams() {\n return this._printParams ||= new PrintParams({\n lastPage: this._numPages - 1\n });\n }\n getSound() {}\n getTemplate() {}\n getURL() {}\n gotoNamedDest() {}\n importAnFDF() {}\n importAnXFDF() {}\n importDataObject() {}\n importIcon() {}\n importSound() {}\n importTextData() {}\n importXFAData() {}\n insertPages() {}\n mailDoc() {}\n mailForm() {}\n movePage() {}\n newPage() {}\n openDataObject() {}\n print(bUI = true, nStart = 0, nEnd = -1, bSilent = false, bShrinkToFit = false, bPrintAsImage = false, bReverse = false, bAnnotations = true, printParams = null) {\n if (this._disablePrinting || !this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (bUI && typeof bUI === "object") {\n nStart = bUI.nStart;\n nEnd = bUI.nEnd;\n bSilent = bUI.bSilent;\n bShrinkToFit = bUI.bShrinkToFit;\n bPrintAsImage = bUI.bPrintAsImage;\n bReverse = bUI.bReverse;\n bAnnotations = bUI.bAnnotations;\n printParams = bUI.printParams;\n bUI = bUI.bUI;\n }\n if (printParams) {\n nStart = printParams.firstPage;\n nEnd = printParams.lastPage;\n }\n nStart = typeof nStart === "number" ? Math.max(0, Math.trunc(nStart)) : 0;\n nEnd = typeof nEnd === "number" ? Math.max(0, Math.trunc(nEnd)) : -1;\n this._send({\n command: "print",\n start: nStart,\n end: nEnd\n });\n }\n removeDataObject() {}\n removeField() {}\n removeIcon() {}\n removeLinks() {}\n removeRequirement() {}\n removeScript() {}\n removeTemplate() {}\n removeThumbnails() {}\n removeWeblinks() {}\n replacePages() {}\n resetForm(aFields = null) {\n if (aFields && typeof aFields === "object" && !Array.isArray(aFields)) {\n aFields = aFields.aFields;\n }\n if (aFields && !Array.isArray(aFields)) {\n aFields = [aFields];\n }\n let mustCalculate = false;\n let fieldsToReset;\n if (aFields) {\n fieldsToReset = [];\n for (const fieldName of aFields) {\n if (!fieldName) {\n continue;\n }\n if (typeof fieldName !== "string") {\n fieldsToReset = null;\n break;\n }\n const field = this._getField(fieldName);\n if (!field) {\n continue;\n }\n fieldsToReset.push(field);\n mustCalculate = true;\n }\n }\n if (!fieldsToReset) {\n fieldsToReset = this._fields.values();\n mustCalculate = this._fields.size !== 0;\n }\n for (const field of fieldsToReset) {\n field.obj.value = field.obj.defaultValue;\n this._send({\n id: field.obj._id,\n siblings: field.obj._siblings,\n value: field.obj.defaultValue,\n formattedValue: null,\n selRange: [0, 0]\n });\n }\n if (mustCalculate) {\n this.calculateNow();\n }\n }\n saveAs() {}\n scroll() {}\n selectPageNthWord() {}\n setAction() {}\n setDataObjectContents() {}\n setOCGOrder() {}\n setPageAction() {}\n setPageBoxes() {}\n setPageLabels() {}\n setPageRotations() {}\n setPageTabOrder() {}\n setPageTransitions() {}\n spawnPageFromTemplate() {}\n submitForm() {}\n syncAnnotScan() {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/proxy.js\nclass ProxyHandler {\n constructor() {\n this.nosend = new Set(["delay"]);\n }\n get(obj, prop) {\n if (prop in obj._expandos) {\n const val = obj._expandos[prop];\n if (typeof val === "function") {\n return val.bind(obj);\n }\n return val;\n }\n if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {\n const val = obj[prop];\n if (typeof val === "function") {\n return val.bind(obj);\n }\n return val;\n }\n return undefined;\n }\n set(obj, prop, value) {\n if (obj._kidIds) {\n obj._kidIds.forEach(id => {\n obj._appObjects[id].wrapped[prop] = value;\n });\n }\n if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {\n const old = obj[prop];\n obj[prop] = value;\n if (!this.nosend.has(prop) && obj._send && obj._id !== null && typeof old !== "function") {\n const data = {\n id: obj._id\n };\n data[prop] = prop === "value" ? obj._getValue() : obj[prop];\n if (!obj._siblings) {\n obj._send(data);\n } else {\n data.siblings = obj._siblings;\n obj._send(data);\n }\n }\n } else {\n obj._expandos[prop] = value;\n }\n return true;\n }\n has(obj, prop) {\n return prop in obj._expandos || typeof prop === "string" && !prop.startsWith("_") && prop in obj;\n }\n getPrototypeOf(obj) {\n return null;\n }\n setPrototypeOf(obj, proto) {\n return false;\n }\n isExtensible(obj) {\n return true;\n }\n preventExtensions(obj) {\n return false;\n }\n getOwnPropertyDescriptor(obj, prop) {\n if (prop in obj._expandos) {\n return {\n configurable: true,\n enumerable: true,\n value: obj._expandos[prop]\n };\n }\n if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {\n return {\n configurable: true,\n enumerable: true,\n value: obj[prop]\n };\n }\n return undefined;\n }\n defineProperty(obj, key, descriptor) {\n Object.defineProperty(obj._expandos, key, descriptor);\n return true;\n }\n deleteProperty(obj, prop) {\n if (prop in obj._expandos) {\n delete obj._expandos[prop];\n }\n }\n ownKeys(obj) {\n const fromExpandos = Reflect.ownKeys(obj._expandos);\n const fromObj = Reflect.ownKeys(obj).filter(k => !k.startsWith("_"));\n return fromExpandos.concat(fromObj);\n }\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/util.js\n\nclass Util extends PDFObject {\n constructor(data) {\n super(data);\n this._scandCache = new Map();\n this._months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];\n this._days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];\n this.MILLISECONDS_IN_DAY = 86400000;\n this.MILLISECONDS_IN_WEEK = 604800000;\n this._externalCall = data.externalCall;\n }\n printf(...args) {\n if (args.length === 0) {\n throw new Error("Invalid number of params in printf");\n }\n if (typeof args[0] !== "string") {\n throw new TypeError("First argument of printf must be a string");\n }\n const pattern = /%(,[0-4])?([+ 0#]+)?(\\d+)?(\\.\\d+)?(.)/g;\n const PLUS = 1;\n const SPACE = 2;\n const ZERO = 4;\n const HASH = 8;\n let i = 0;\n return args[0].replaceAll(pattern, function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {\n if (cConvChar !== "d" && cConvChar !== "f" && cConvChar !== "s" && cConvChar !== "x") {\n const buf = ["%"];\n for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {\n if (str) {\n buf.push(str);\n }\n }\n return buf.join("");\n }\n i++;\n if (i === args.length) {\n throw new Error("Not enough arguments in printf");\n }\n const arg = args[i];\n if (cConvChar === "s") {\n return arg.toString();\n }\n let flags = 0;\n if (cFlags) {\n for (const flag of cFlags) {\n switch (flag) {\n case "+":\n flags |= PLUS;\n break;\n case " ":\n flags |= SPACE;\n break;\n case "0":\n flags |= ZERO;\n break;\n case "#":\n flags |= HASH;\n break;\n }\n }\n }\n cFlags = flags;\n if (nWidth) {\n nWidth = parseInt(nWidth);\n }\n let intPart = Math.trunc(arg);\n if (cConvChar === "x") {\n let hex = Math.abs(intPart).toString(16).toUpperCase();\n if (nWidth !== undefined) {\n hex = hex.padStart(nWidth, cFlags & ZERO ? "0" : " ");\n }\n if (cFlags & HASH) {\n hex = `0x${hex}`;\n }\n return hex;\n }\n if (nPrecision) {\n nPrecision = parseInt(nPrecision.substring(1));\n }\n nDecSep = nDecSep ? nDecSep.substring(1) : "0";\n const separators = {\n 0: [",", "."],\n 1: ["", "."],\n 2: [".", ","],\n 3: ["", ","],\n 4: ["\'", "."]\n };\n const [thousandSep, decimalSep] = separators[nDecSep];\n let decPart = "";\n if (cConvChar === "f") {\n decPart = nPrecision !== undefined ? Math.abs(arg - intPart).toFixed(nPrecision) : Math.abs(arg - intPart).toString();\n if (decPart.length > 2) {\n decPart = `${decimalSep}${decPart.substring(2)}`;\n } else {\n if (decPart === "1") {\n intPart += Math.sign(arg);\n }\n decPart = cFlags & HASH ? "." : "";\n }\n }\n let sign = "";\n if (intPart < 0) {\n sign = "-";\n intPart = -intPart;\n } else if (cFlags & PLUS) {\n sign = "+";\n } else if (cFlags & SPACE) {\n sign = " ";\n }\n if (thousandSep && intPart >= 1000) {\n const buf = [];\n while (true) {\n buf.push((intPart % 1000).toString().padStart(3, "0"));\n intPart = Math.trunc(intPart / 1000);\n if (intPart < 1000) {\n buf.push(intPart.toString());\n break;\n }\n }\n intPart = buf.reverse().join(thousandSep);\n } else {\n intPart = intPart.toString();\n }\n let n = `${intPart}${decPart}`;\n if (nWidth !== undefined) {\n n = n.padStart(nWidth - sign.length, cFlags & ZERO ? "0" : " ");\n }\n return `${sign}${n}`;\n });\n }\n iconStreamFromIcon() {}\n printd(cFormat, oDate) {\n switch (cFormat) {\n case 0:\n return this.printd("D:yyyymmddHHMMss", oDate);\n case 1:\n return this.printd("yyyy.mm.dd HH:MM:ss", oDate);\n case 2:\n return this.printd("m/d/yy h:MM:ss tt", oDate);\n }\n const handlers = {\n mmmm: data => {\n return this._months[data.month];\n },\n mmm: data => {\n return this._months[data.month].substring(0, 3);\n },\n mm: data => {\n return (data.month + 1).toString().padStart(2, "0");\n },\n m: data => {\n return (data.month + 1).toString();\n },\n dddd: data => {\n return this._days[data.dayOfWeek];\n },\n ddd: data => {\n return this._days[data.dayOfWeek].substring(0, 3);\n },\n dd: data => {\n return data.day.toString().padStart(2, "0");\n },\n d: data => {\n return data.day.toString();\n },\n yyyy: data => {\n return data.year.toString();\n },\n yy: data => {\n return (data.year % 100).toString().padStart(2, "0");\n },\n HH: data => {\n return data.hours.toString().padStart(2, "0");\n },\n H: data => {\n return data.hours.toString();\n },\n hh: data => {\n return (1 + (data.hours + 11) % 12).toString().padStart(2, "0");\n },\n h: data => {\n return (1 + (data.hours + 11) % 12).toString();\n },\n MM: data => {\n return data.minutes.toString().padStart(2, "0");\n },\n M: data => {\n return data.minutes.toString();\n },\n ss: data => {\n return data.seconds.toString().padStart(2, "0");\n },\n s: data => {\n return data.seconds.toString();\n },\n tt: data => {\n return data.hours < 12 ? "am" : "pm";\n },\n t: data => {\n return data.hours < 12 ? "a" : "p";\n }\n };\n const data = {\n year: oDate.getFullYear(),\n month: oDate.getMonth(),\n day: oDate.getDate(),\n dayOfWeek: oDate.getDay(),\n hours: oDate.getHours(),\n minutes: oDate.getMinutes(),\n seconds: oDate.getSeconds()\n };\n const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\\\.)/g;\n return cFormat.replaceAll(patterns, function (match, pattern) {\n if (pattern in handlers) {\n return handlers[pattern](data);\n }\n return pattern.charCodeAt(1);\n });\n }\n printx(cFormat, cSource) {\n cSource = (cSource ?? "").toString();\n const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];\n const buf = [];\n let i = 0;\n const ii = cSource.length;\n let currCase = handlers[0];\n let escaped = false;\n for (const command of cFormat) {\n if (escaped) {\n buf.push(command);\n escaped = false;\n continue;\n }\n if (i >= ii) {\n break;\n }\n switch (command) {\n case "?":\n buf.push(currCase(cSource.charAt(i++)));\n break;\n case "X":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if ("a" <= char && char <= "z" || "A" <= char && char <= "Z" || "0" <= char && char <= "9") {\n buf.push(currCase(char));\n break;\n }\n }\n break;\n case "A":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if ("a" <= char && char <= "z" || "A" <= char && char <= "Z") {\n buf.push(currCase(char));\n break;\n }\n }\n break;\n case "9":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if ("0" <= char && char <= "9") {\n buf.push(char);\n break;\n }\n }\n break;\n case "*":\n while (i < ii) {\n buf.push(currCase(cSource.charAt(i++)));\n }\n break;\n case "\\\\":\n escaped = true;\n break;\n case ">":\n currCase = handlers[1];\n break;\n case "<":\n currCase = handlers[2];\n break;\n case "=":\n currCase = handlers[0];\n break;\n default:\n buf.push(command);\n }\n }\n return buf.join("");\n }\n scand(cFormat, cDate) {\n if (typeof cDate !== "string") {\n return new Date(cDate);\n }\n if (cDate === "") {\n return new Date();\n }\n switch (cFormat) {\n case 0:\n return this.scand("D:yyyymmddHHMMss", cDate);\n case 1:\n return this.scand("yyyy.mm.dd HH:MM:ss", cDate);\n case 2:\n return this.scand("m/d/yy h:MM:ss tt", cDate);\n }\n if (!this._scandCache.has(cFormat)) {\n const months = this._months;\n const days = this._days;\n const handlers = {\n mmmm: {\n pattern: `(${months.join("|")})`,\n action: (value, data) => {\n data.month = months.indexOf(value);\n }\n },\n mmm: {\n pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,\n action: (value, data) => {\n data.month = months.findIndex(month => month.substring(0, 3) === value);\n }\n },\n mm: {\n pattern: `(\\\\d{2})`,\n action: (value, data) => {\n data.month = parseInt(value) - 1;\n }\n },\n m: {\n pattern: `(\\\\d{1,2})`,\n action: (value, data) => {\n data.month = parseInt(value) - 1;\n }\n },\n dddd: {\n pattern: `(${days.join("|")})`,\n action: (value, data) => {\n data.day = days.indexOf(value);\n }\n },\n ddd: {\n pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,\n action: (value, data) => {\n data.day = days.findIndex(day => day.substring(0, 3) === value);\n }\n },\n dd: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.day = parseInt(value);\n }\n },\n d: {\n pattern: "(\\\\d{1,2})",\n action: (value, data) => {\n data.day = parseInt(value);\n }\n },\n yyyy: {\n pattern: "(\\\\d{4})",\n action: (value, data) => {\n data.year = parseInt(value);\n }\n },\n yy: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.year = 2000 + parseInt(value);\n }\n },\n HH: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n H: {\n pattern: "(\\\\d{1,2})",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n hh: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n h: {\n pattern: "(\\\\d{1,2})",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n MM: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.minutes = parseInt(value);\n }\n },\n M: {\n pattern: "(\\\\d{1,2})",\n action: (value, data) => {\n data.minutes = parseInt(value);\n }\n },\n ss: {\n pattern: "(\\\\d{2})",\n action: (value, data) => {\n data.seconds = parseInt(value);\n }\n },\n s: {\n pattern: "(\\\\d{1,2})",\n action: (value, data) => {\n data.seconds = parseInt(value);\n }\n },\n tt: {\n pattern: "([aApP][mM])",\n action: (value, data) => {\n const char = value.charAt(0);\n data.am = char === "a" || char === "A";\n }\n },\n t: {\n pattern: "([aApP])",\n action: (value, data) => {\n data.am = value === "a" || value === "A";\n }\n }\n };\n const escapedFormat = cFormat.replaceAll(/[.*+\\-?^${}()|[\\]\\\\]/g, "\\\\$&");\n const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;\n const actions = [];\n const re = escapedFormat.replaceAll(patterns, function (match, patternElement) {\n const {\n pattern,\n action\n } = handlers[patternElement];\n actions.push(action);\n return pattern;\n });\n this._scandCache.set(cFormat, [re, actions]);\n }\n const [re, actions] = this._scandCache.get(cFormat);\n const matches = new RegExp(`^${re}$`, "g").exec(cDate);\n if (!matches || matches.length !== actions.length + 1) {\n return null;\n }\n const data = {\n year: 2000,\n month: 0,\n day: 1,\n hours: 0,\n minutes: 0,\n seconds: 0,\n am: null\n };\n actions.forEach((action, i) => action(matches[i + 1], data));\n if (data.am !== null) {\n data.hours = data.hours % 12 + (data.am ? 0 : 12);\n }\n return new Date(data.year, data.month, data.day, data.hours, data.minutes, data.seconds);\n }\n spansToXML() {}\n stringFromStream() {}\n xmlToSpans() {}\n}\n\n;// CONCATENATED MODULE: ./src/scripting_api/initialization.js\n\n\n\n\n\n\n\n\n\n\nfunction initSandbox(params) {\n delete globalThis.pdfjsScripting;\n const externalCall = globalThis.callExternalFunction;\n delete globalThis.callExternalFunction;\n const globalEval = code => globalThis.eval(code);\n const send = data => externalCall("send", [data]);\n const proxyHandler = new ProxyHandler();\n const {\n data\n } = params;\n const doc = new Doc({\n send,\n globalEval,\n ...data.docInfo\n });\n const _document = {\n obj: doc,\n wrapped: new Proxy(doc, proxyHandler)\n };\n const app = new App({\n send,\n globalEval,\n externalCall,\n _document,\n calculationOrder: data.calculationOrder,\n proxyHandler,\n ...data.appInfo\n });\n const util = new Util({\n externalCall\n });\n const appObjects = app._objects;\n if (data.objects) {\n const annotations = [];\n for (const [name, objs] of Object.entries(data.objects)) {\n annotations.length = 0;\n let container = null;\n for (const obj of objs) {\n if (obj.type !== "") {\n annotations.push(obj);\n } else {\n container = obj;\n }\n }\n let obj = container;\n if (annotations.length > 0) {\n obj = annotations[0];\n obj.send = send;\n }\n obj.globalEval = globalEval;\n obj.doc = _document;\n obj.fieldPath = name;\n obj.appObjects = appObjects;\n let field;\n switch (obj.type) {\n case "radiobutton":\n {\n const otherButtons = annotations.slice(1);\n field = new RadioButtonField(otherButtons, obj);\n break;\n }\n case "checkbox":\n {\n const otherButtons = annotations.slice(1);\n field = new CheckboxField(otherButtons, obj);\n break;\n }\n case "text":\n if (annotations.length <= 1) {\n field = new Field(obj);\n break;\n }\n obj.siblings = annotations.map(x => x.id).slice(1);\n field = new Field(obj);\n break;\n default:\n field = new Field(obj);\n }\n const wrapped = new Proxy(field, proxyHandler);\n const _object = {\n obj: field,\n wrapped\n };\n doc._addField(name, _object);\n for (const object of objs) {\n appObjects[object.id] = _object;\n }\n if (container) {\n appObjects[container.id] = _object;\n }\n }\n }\n const color = new Color();\n globalThis.event = null;\n globalThis.global = Object.create(null);\n globalThis.app = new Proxy(app, proxyHandler);\n globalThis.color = new Proxy(color, proxyHandler);\n globalThis.console = new Proxy(new Console({\n send\n }), proxyHandler);\n globalThis.util = new Proxy(util, proxyHandler);\n globalThis.border = Border;\n globalThis.cursor = Cursor;\n globalThis.display = Display;\n globalThis.font = Font;\n globalThis.highlight = Highlight;\n globalThis.position = Position;\n globalThis.scaleHow = ScaleHow;\n globalThis.scaleWhen = ScaleWhen;\n globalThis.style = Style;\n globalThis.trans = Trans;\n globalThis.zoomtype = ZoomType;\n globalThis.ADBE = {\n Reader_Value_Asked: true,\n Viewer_Value_Asked: true\n };\n const aform = new AForm(doc, app, util, color);\n for (const name of Object.getOwnPropertyNames(AForm.prototype)) {\n if (name !== "constructor" && !name.startsWith("_")) {\n globalThis[name] = aform[name].bind(aform);\n }\n }\n for (const [name, value] of Object.entries(GlobalConstants)) {\n Object.defineProperty(globalThis, name, {\n value,\n writable: false\n });\n }\n Object.defineProperties(globalThis, {\n ColorConvert: {\n value: color.convert.bind(color),\n writable: true\n },\n ColorEqual: {\n value: color.equal.bind(color),\n writable: true\n }\n });\n const properties = Object.create(null);\n for (const name of Object.getOwnPropertyNames(Doc.prototype)) {\n if (name === "constructor" || name.startsWith("_")) {\n continue;\n }\n const descriptor = Object.getOwnPropertyDescriptor(Doc.prototype, name);\n if (descriptor.get) {\n properties[name] = {\n get: descriptor.get.bind(doc),\n set: descriptor.set.bind(doc)\n };\n } else {\n properties[name] = {\n value: Doc.prototype[name].bind(doc)\n };\n }\n }\n Object.defineProperties(globalThis, properties);\n const functions = {\n dispatchEvent: app._dispatchEvent.bind(app),\n timeoutCb: app._evalCallback.bind(app)\n };\n return (name, args) => {\n try {\n functions[name](args);\n } catch (error) {\n send(serializeError(error));\n }\n };\n}\n\n;// CONCATENATED MODULE: ./src/pdf.scripting.js\n\nconst pdfjsVersion = \'4.0.269\';\nconst pdfjsBuild = \'f4b396f6c\';\nglobalThis.pdfjsScripting = {\n initSandbox: initSandbox\n};\n']; code.push("delete dump;"); let success = false; let buf = 0; try { const sandboxData = JSON.stringify(data); code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`); buf = this._module.stringToNewUTF8(code.join("\n")); success = !!this._module.ccall("init", "number", ["number", "number"], [buf, this._alertOnError]); } catch (error) { console.error(error); } finally { if (buf) { this._module.ccall("free", "number", ["number"], [buf]); } } if (success) { this.support.commFun = this._module.cwrap("commFun", null, ["string", "string"]); } else { this.nukeSandbox(); throw new Error("Cannot start sandbox"); } } dispatchEvent(event) { this.support?.callSandboxFunction("dispatchEvent", event); } dumpMemoryUse() { this._module?.ccall("dumpMemoryUse", null, []); } nukeSandbox() { if (this._module !== null) { this.support.destroy(); this.support = null; this._module.ccall("nukeSandbox", null, []); this._module = null; } } evalForTesting(code, key) { throw new Error("Not implemented: evalForTesting"); } } function QuickJSSandbox() { return quickjs_eval().then(module => { return new Sandbox(window, module); }); } var __webpack_exports__QuickJSSandbox = __webpack_exports__.QuickJSSandbox; export { __webpack_exports__QuickJSSandbox as QuickJSSandbox }; //# sourceMappingURL=pdf.sandbox.mjs.map ================================================ FILE: public/assets/lib/vendor/pdfjs/pdf.worker.js ================================================ /** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2023 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = globalThis.pdfjsWorker = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { WorkerMessageHandler: () => (/* reexport */ WorkerMessageHandler) }); ;// CONCATENATED MODULE: ./src/shared/util.js const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser"); const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; const MAX_IMAGE_SIZE_TO_CACHE = 10e6; const LINE_FACTOR = 1.35; const LINE_DESCENT_FACTOR = 0.35; const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR; const RenderingIntentFlag = { ANY: 0x01, DISPLAY: 0x02, PRINT: 0x04, SAVE: 0x08, ANNOTATIONS_FORMS: 0x10, ANNOTATIONS_STORAGE: 0x20, ANNOTATIONS_DISABLE: 0x40, OPLIST: 0x100 }; const AnnotationMode = { DISABLE: 0, ENABLE: 1, ENABLE_FORMS: 2, ENABLE_STORAGE: 3 }; const AnnotationEditorPrefix = "pdfjs_internal_editor_"; const AnnotationEditorType = { DISABLE: -1, NONE: 0, FREETEXT: 3, HIGHLIGHT: 9, STAMP: 13, INK: 15 }; const AnnotationEditorParamsType = { RESIZE: 1, CREATE: 2, FREETEXT_SIZE: 11, FREETEXT_COLOR: 12, FREETEXT_OPACITY: 13, INK_COLOR: 21, INK_THICKNESS: 22, INK_OPACITY: 23 }; const PermissionFlag = { PRINT: 0x04, MODIFY_CONTENTS: 0x08, COPY: 0x10, MODIFY_ANNOTATIONS: 0x20, FILL_INTERACTIVE_FORMS: 0x100, COPY_FOR_ACCESSIBILITY: 0x200, ASSEMBLE: 0x400, PRINT_HIGH_QUALITY: 0x800 }; const TextRenderingMode = { FILL: 0, STROKE: 1, FILL_STROKE: 2, INVISIBLE: 3, FILL_ADD_TO_PATH: 4, STROKE_ADD_TO_PATH: 5, FILL_STROKE_ADD_TO_PATH: 6, ADD_TO_PATH: 7, FILL_STROKE_MASK: 3, ADD_TO_PATH_FLAG: 4 }; const ImageKind = { GRAYSCALE_1BPP: 1, RGB_24BPP: 2, RGBA_32BPP: 3 }; const AnnotationType = { TEXT: 1, LINK: 2, FREETEXT: 3, LINE: 4, SQUARE: 5, CIRCLE: 6, POLYGON: 7, POLYLINE: 8, HIGHLIGHT: 9, UNDERLINE: 10, SQUIGGLY: 11, STRIKEOUT: 12, STAMP: 13, CARET: 14, INK: 15, POPUP: 16, FILEATTACHMENT: 17, SOUND: 18, MOVIE: 19, WIDGET: 20, SCREEN: 21, PRINTERMARK: 22, TRAPNET: 23, WATERMARK: 24, THREED: 25, REDACT: 26 }; const AnnotationReplyType = { GROUP: "Group", REPLY: "R" }; const AnnotationFlag = { INVISIBLE: 0x01, HIDDEN: 0x02, PRINT: 0x04, NOZOOM: 0x08, NOROTATE: 0x10, NOVIEW: 0x20, READONLY: 0x40, LOCKED: 0x80, TOGGLENOVIEW: 0x100, LOCKEDCONTENTS: 0x200 }; const AnnotationFieldFlag = { READONLY: 0x0000001, REQUIRED: 0x0000002, NOEXPORT: 0x0000004, MULTILINE: 0x0001000, PASSWORD: 0x0002000, NOTOGGLETOOFF: 0x0004000, RADIO: 0x0008000, PUSHBUTTON: 0x0010000, COMBO: 0x0020000, EDIT: 0x0040000, SORT: 0x0080000, FILESELECT: 0x0100000, MULTISELECT: 0x0200000, DONOTSPELLCHECK: 0x0400000, DONOTSCROLL: 0x0800000, COMB: 0x1000000, RICHTEXT: 0x2000000, RADIOSINUNISON: 0x2000000, COMMITONSELCHANGE: 0x4000000 }; const AnnotationBorderStyleType = { SOLID: 1, DASHED: 2, BEVELED: 3, INSET: 4, UNDERLINE: 5 }; const AnnotationActionEventType = { E: "Mouse Enter", X: "Mouse Exit", D: "Mouse Down", U: "Mouse Up", Fo: "Focus", Bl: "Blur", PO: "PageOpen", PC: "PageClose", PV: "PageVisible", PI: "PageInvisible", K: "Keystroke", F: "Format", V: "Validate", C: "Calculate" }; const DocumentActionEventType = { WC: "WillClose", WS: "WillSave", DS: "DidSave", WP: "WillPrint", DP: "DidPrint" }; const PageActionEventType = { O: "PageOpen", C: "PageClose" }; const VerbosityLevel = { ERRORS: 0, WARNINGS: 1, INFOS: 5 }; const CMapCompressionType = { NONE: 0, BINARY: 1 }; const OPS = { dependency: 1, setLineWidth: 2, setLineCap: 3, setLineJoin: 4, setMiterLimit: 5, setDash: 6, setRenderingIntent: 7, setFlatness: 8, setGState: 9, save: 10, restore: 11, transform: 12, moveTo: 13, lineTo: 14, curveTo: 15, curveTo2: 16, curveTo3: 17, closePath: 18, rectangle: 19, stroke: 20, closeStroke: 21, fill: 22, eoFill: 23, fillStroke: 24, eoFillStroke: 25, closeFillStroke: 26, closeEOFillStroke: 27, endPath: 28, clip: 29, eoClip: 30, beginText: 31, endText: 32, setCharSpacing: 33, setWordSpacing: 34, setHScale: 35, setLeading: 36, setFont: 37, setTextRenderingMode: 38, setTextRise: 39, moveText: 40, setLeadingMoveText: 41, setTextMatrix: 42, nextLine: 43, showText: 44, showSpacedText: 45, nextLineShowText: 46, nextLineSetSpacingShowText: 47, setCharWidth: 48, setCharWidthAndBounds: 49, setStrokeColorSpace: 50, setFillColorSpace: 51, setStrokeColor: 52, setStrokeColorN: 53, setFillColor: 54, setFillColorN: 55, setStrokeGray: 56, setFillGray: 57, setStrokeRGBColor: 58, setFillRGBColor: 59, setStrokeCMYKColor: 60, setFillCMYKColor: 61, shadingFill: 62, beginInlineImage: 63, beginImageData: 64, endInlineImage: 65, paintXObject: 66, markPoint: 67, markPointProps: 68, beginMarkedContent: 69, beginMarkedContentProps: 70, endMarkedContent: 71, beginCompat: 72, endCompat: 73, paintFormXObjectBegin: 74, paintFormXObjectEnd: 75, beginGroup: 76, endGroup: 77, beginAnnotation: 80, endAnnotation: 81, paintImageMaskXObject: 83, paintImageMaskXObjectGroup: 84, paintImageXObject: 85, paintInlineImageXObject: 86, paintInlineImageXObjectGroup: 87, paintImageXObjectRepeat: 88, paintImageMaskXObjectRepeat: 89, paintSolidColorImageMask: 90, constructPath: 91 }; const PasswordResponses = { NEED_PASSWORD: 1, INCORRECT_PASSWORD: 2 }; let verbosity = VerbosityLevel.WARNINGS; function setVerbosityLevel(level) { if (Number.isInteger(level)) { verbosity = level; } } function getVerbosityLevel() { return verbosity; } function info(msg) { if (verbosity >= VerbosityLevel.INFOS) { console.log(`Info: ${msg}`); } } function warn(msg) { if (verbosity >= VerbosityLevel.WARNINGS) { console.log(`Warning: ${msg}`); } } function unreachable(msg) { throw new Error(msg); } function assert(cond, msg) { if (!cond) { unreachable(msg); } } function _isValidProtocol(url) { switch (url?.protocol) { case "http:": case "https:": case "ftp:": case "mailto:": case "tel:": return true; default: return false; } } function createValidAbsoluteUrl(url, baseUrl = null, options = null) { if (!url) { return null; } try { if (options && typeof url === "string") { if (options.addDefaultProtocol && url.startsWith("www.")) { const dots = url.match(/\./g); if (dots?.length >= 2) { url = `http://${url}`; } } if (options.tryConvertEncoding) { try { url = stringToUTF8String(url); } catch {} } } const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url); if (_isValidProtocol(absoluteUrl)) { return absoluteUrl; } } catch {} return null; } function shadow(obj, prop, value, nonSerializable = false) { Object.defineProperty(obj, prop, { value, enumerable: !nonSerializable, configurable: true, writable: false }); return value; } const BaseException = function BaseExceptionClosure() { function BaseException(message, name) { if (this.constructor === BaseException) { unreachable("Cannot initialize BaseException."); } this.message = message; this.name = name; } BaseException.prototype = new Error(); BaseException.constructor = BaseException; return BaseException; }(); class PasswordException extends BaseException { constructor(msg, code) { super(msg, "PasswordException"); this.code = code; } } class UnknownErrorException extends BaseException { constructor(msg, details) { super(msg, "UnknownErrorException"); this.details = details; } } class InvalidPDFException extends BaseException { constructor(msg) { super(msg, "InvalidPDFException"); } } class MissingPDFException extends BaseException { constructor(msg) { super(msg, "MissingPDFException"); } } class UnexpectedResponseException extends BaseException { constructor(msg, status) { super(msg, "UnexpectedResponseException"); this.status = status; } } class FormatError extends BaseException { constructor(msg) { super(msg, "FormatError"); } } class AbortException extends BaseException { constructor(msg) { super(msg, "AbortException"); } } function bytesToString(bytes) { if (typeof bytes !== "object" || bytes?.length === undefined) { unreachable("Invalid argument for bytesToString"); } const length = bytes.length; const MAX_ARGUMENT_COUNT = 8192; if (length < MAX_ARGUMENT_COUNT) { return String.fromCharCode.apply(null, bytes); } const strBuf = []; for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) { const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length); const chunk = bytes.subarray(i, chunkEnd); strBuf.push(String.fromCharCode.apply(null, chunk)); } return strBuf.join(""); } function stringToBytes(str) { if (typeof str !== "string") { unreachable("Invalid argument for stringToBytes"); } const length = str.length; const bytes = new Uint8Array(length); for (let i = 0; i < length; ++i) { bytes[i] = str.charCodeAt(i) & 0xff; } return bytes; } function string32(value) { return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff); } function objectSize(obj) { return Object.keys(obj).length; } function objectFromMap(map) { const obj = Object.create(null); for (const [key, value] of map) { obj[key] = value; } return obj; } function isLittleEndian() { const buffer8 = new Uint8Array(4); buffer8[0] = 1; const view32 = new Uint32Array(buffer8.buffer, 0, 1); return view32[0] === 1; } function isEvalSupported() { try { new Function(""); return true; } catch { return false; } } class FeatureTest { static get isLittleEndian() { return shadow(this, "isLittleEndian", isLittleEndian()); } static get isEvalSupported() { return shadow(this, "isEvalSupported", isEvalSupported()); } static get isOffscreenCanvasSupported() { return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined"); } static get platform() { if (typeof navigator !== "undefined" && typeof navigator?.platform === "string") { return shadow(this, "platform", { isMac: navigator.platform.includes("Mac") }); } return shadow(this, "platform", { isMac: false }); } static get isCSSRoundSupported() { return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)")); } } const hexNumbers = [...Array(256).keys()].map(n => n.toString(16).padStart(2, "0")); class Util { static makeHexColor(r, g, b) { return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`; } static scaleMinMax(transform, minMax) { let temp; if (transform[0]) { if (transform[0] < 0) { temp = minMax[0]; minMax[0] = minMax[1]; minMax[1] = temp; } minMax[0] *= transform[0]; minMax[1] *= transform[0]; if (transform[3] < 0) { temp = minMax[2]; minMax[2] = minMax[3]; minMax[3] = temp; } minMax[2] *= transform[3]; minMax[3] *= transform[3]; } else { temp = minMax[0]; minMax[0] = minMax[2]; minMax[2] = temp; temp = minMax[1]; minMax[1] = minMax[3]; minMax[3] = temp; if (transform[1] < 0) { temp = minMax[2]; minMax[2] = minMax[3]; minMax[3] = temp; } minMax[2] *= transform[1]; minMax[3] *= transform[1]; if (transform[2] < 0) { temp = minMax[0]; minMax[0] = minMax[1]; minMax[1] = temp; } minMax[0] *= transform[2]; minMax[1] *= transform[2]; } minMax[0] += transform[4]; minMax[1] += transform[4]; minMax[2] += transform[5]; minMax[3] += transform[5]; } static transform(m1, m2) { return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]]; } static applyTransform(p, m) { const xt = p[0] * m[0] + p[1] * m[2] + m[4]; const yt = p[0] * m[1] + p[1] * m[3] + m[5]; return [xt, yt]; } static applyInverseTransform(p, m) { const d = m[0] * m[3] - m[1] * m[2]; const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; return [xt, yt]; } static getAxialAlignedBoundingBox(r, m) { const p1 = this.applyTransform(r, m); const p2 = this.applyTransform(r.slice(2, 4), m); const p3 = this.applyTransform([r[0], r[3]], m); const p4 = this.applyTransform([r[2], r[1]], m); return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])]; } static inverseTransform(m) { const d = m[0] * m[3] - m[1] * m[2]; return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; } static singularValueDecompose2dScale(m) { const transpose = [m[0], m[2], m[1], m[3]]; const a = m[0] * transpose[0] + m[1] * transpose[2]; const b = m[0] * transpose[1] + m[1] * transpose[3]; const c = m[2] * transpose[0] + m[3] * transpose[2]; const d = m[2] * transpose[1] + m[3] * transpose[3]; const first = (a + d) / 2; const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2; const sx = first + second || 1; const sy = first - second || 1; return [Math.sqrt(sx), Math.sqrt(sy)]; } static normalizeRect(rect) { const r = rect.slice(0); if (rect[0] > rect[2]) { r[0] = rect[2]; r[2] = rect[0]; } if (rect[1] > rect[3]) { r[1] = rect[3]; r[3] = rect[1]; } return r; } static intersect(rect1, rect2) { const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2])); const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2])); if (xLow > xHigh) { return null; } const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3])); const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3])); if (yLow > yHigh) { return null; } return [xLow, yLow, xHigh, yHigh]; } static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) { const tvalues = [], bounds = [[], []]; let a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (let i = 0; i < 2; ++i) { if (i === 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (Math.abs(a) < 1e-12) { if (Math.abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = Math.sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } let j = tvalues.length, mt; const jlen = j; while (j--) { t = tvalues[j]; mt = 1 - t; bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3; bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3; } bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; bounds[0].length = bounds[1].length = jlen + 2; return [Math.min(...bounds[0]), Math.min(...bounds[1]), Math.max(...bounds[0]), Math.max(...bounds[1])]; } } const PDFStringTranslateTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 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, 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, 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, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac]; function stringToPDFString(str) { if (str[0] >= "\xEF") { let encoding; if (str[0] === "\xFE" && str[1] === "\xFF") { encoding = "utf-16be"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xFF" && str[1] === "\xFE") { encoding = "utf-16le"; if (str.length % 2 === 1) { str = str.slice(0, -1); } } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") { encoding = "utf-8"; } if (encoding) { try { const decoder = new TextDecoder(encoding, { fatal: true }); const buffer = stringToBytes(str); const decoded = decoder.decode(buffer); if (!decoded.includes("\x1b")) { return decoded; } return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, ""); } catch (ex) { warn(`stringToPDFString: "${ex}".`); } } } const strBuf = []; for (let i = 0, ii = str.length; i < ii; i++) { const charCode = str.charCodeAt(i); if (charCode === 0x1b) { while (++i < ii && str.charCodeAt(i) !== 0x1b) {} continue; } const code = PDFStringTranslateTable[charCode]; strBuf.push(code ? String.fromCharCode(code) : str.charAt(i)); } return strBuf.join(""); } function stringToUTF8String(str) { return decodeURIComponent(escape(str)); } function utf8StringToString(str) { return unescape(encodeURIComponent(str)); } function isArrayBuffer(v) { return typeof v === "object" && v?.byteLength !== undefined; } function isArrayEqual(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0, ii = arr1.length; i < ii; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true; } function getModificationDate(date = new Date()) { const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")]; return buffer.join(""); } class PromiseCapability { #settled = false; constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = data => { this.#settled = true; resolve(data); }; this.reject = reason => { this.#settled = true; reject(reason); }; }); } get settled() { return this.#settled; } } let NormalizeRegex = null; let NormalizationMap = null; function normalizeUnicode(str) { if (!NormalizeRegex) { NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu; NormalizationMap = new Map([["ſt", "ſt"]]); } return str.replaceAll(NormalizeRegex, (_, p1, p2) => { return p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2); }); } function getUuid() { if (typeof crypto !== "undefined" && typeof crypto?.randomUUID === "function") { return crypto.randomUUID(); } const buf = new Uint8Array(32); if (typeof crypto !== "undefined" && typeof crypto?.getRandomValues === "function") { crypto.getRandomValues(buf); } else { for (let i = 0; i < 32; i++) { buf[i] = Math.floor(Math.random() * 255); } } return bytesToString(buf); } const AnnotationPrefix = "pdfjs_internal_id_"; ;// CONCATENATED MODULE: ./src/core/primitives.js const CIRCULAR_REF = Symbol("CIRCULAR_REF"); const EOF = Symbol("EOF"); let CmdCache = Object.create(null); let NameCache = Object.create(null); let RefCache = Object.create(null); function clearPrimitiveCaches() { CmdCache = Object.create(null); NameCache = Object.create(null); RefCache = Object.create(null); } class Name { constructor(name) { this.name = name; } static get(name) { return NameCache[name] ||= new Name(name); } } class Cmd { constructor(cmd) { this.cmd = cmd; } static get(cmd) { return CmdCache[cmd] ||= new Cmd(cmd); } } const nonSerializable = function nonSerializableClosure() { return nonSerializable; }; class Dict { constructor(xref = null) { this._map = Object.create(null); this.xref = xref; this.objId = null; this.suppressEncryption = false; this.__nonSerializable__ = nonSerializable; } assignXref(newXref) { this.xref = newXref; } get size() { return Object.keys(this._map).length; } get(key1, key2, key3) { let value = this._map[key1]; if (value === undefined && key2 !== undefined) { value = this._map[key2]; if (value === undefined && key3 !== undefined) { value = this._map[key3]; } } if (value instanceof Ref && this.xref) { return this.xref.fetch(value, this.suppressEncryption); } return value; } async getAsync(key1, key2, key3) { let value = this._map[key1]; if (value === undefined && key2 !== undefined) { value = this._map[key2]; if (value === undefined && key3 !== undefined) { value = this._map[key3]; } } if (value instanceof Ref && this.xref) { return this.xref.fetchAsync(value, this.suppressEncryption); } return value; } getArray(key1, key2, key3) { let value = this._map[key1]; if (value === undefined && key2 !== undefined) { value = this._map[key2]; if (value === undefined && key3 !== undefined) { value = this._map[key3]; } } if (value instanceof Ref && this.xref) { value = this.xref.fetch(value, this.suppressEncryption); } if (Array.isArray(value)) { value = value.slice(); for (let i = 0, ii = value.length; i < ii; i++) { if (value[i] instanceof Ref && this.xref) { value[i] = this.xref.fetch(value[i], this.suppressEncryption); } } } return value; } getRaw(key) { return this._map[key]; } getKeys() { return Object.keys(this._map); } getRawValues() { return Object.values(this._map); } set(key, value) { this._map[key] = value; } has(key) { return this._map[key] !== undefined; } forEach(callback) { for (const key in this._map) { callback(key, this.get(key)); } } static get empty() { const emptyDict = new Dict(null); emptyDict.set = (key, value) => { unreachable("Should not call `set` on the empty dictionary."); }; return shadow(this, "empty", emptyDict); } static merge({ xref, dictArray, mergeSubDicts = false }) { const mergedDict = new Dict(xref), properties = new Map(); for (const dict of dictArray) { if (!(dict instanceof Dict)) { continue; } for (const [key, value] of Object.entries(dict._map)) { let property = properties.get(key); if (property === undefined) { property = []; properties.set(key, property); } else if (!mergeSubDicts || !(value instanceof Dict)) { continue; } property.push(value); } } for (const [name, values] of properties) { if (values.length === 1 || !(values[0] instanceof Dict)) { mergedDict._map[name] = values[0]; continue; } const subDict = new Dict(xref); for (const dict of values) { for (const [key, value] of Object.entries(dict._map)) { if (subDict._map[key] === undefined) { subDict._map[key] = value; } } } if (subDict.size > 0) { mergedDict._map[name] = subDict; } } properties.clear(); return mergedDict.size > 0 ? mergedDict : Dict.empty; } clone() { const dict = new Dict(this.xref); for (const key of this.getKeys()) { dict.set(key, this.getRaw(key)); } return dict; } } class Ref { constructor(num, gen) { this.num = num; this.gen = gen; } toString() { if (this.gen === 0) { return `${this.num}R`; } return `${this.num}R${this.gen}`; } static fromString(str) { const ref = RefCache[str]; if (ref) { return ref; } const m = /^(\d+)R(\d*)$/.exec(str); if (!m || m[1] === "0") { return null; } return RefCache[str] = new Ref(parseInt(m[1]), !m[2] ? 0 : parseInt(m[2])); } static get(num, gen) { const key = gen === 0 ? `${num}R` : `${num}R${gen}`; return RefCache[key] ||= new Ref(num, gen); } } class RefSet { constructor(parent = null) { this._set = new Set(parent?._set); } has(ref) { return this._set.has(ref.toString()); } put(ref) { this._set.add(ref.toString()); } remove(ref) { this._set.delete(ref.toString()); } [Symbol.iterator]() { return this._set.values(); } clear() { this._set.clear(); } } class RefSetCache { constructor() { this._map = new Map(); } get size() { return this._map.size; } get(ref) { return this._map.get(ref.toString()); } has(ref) { return this._map.has(ref.toString()); } put(ref, obj) { this._map.set(ref.toString(), obj); } putAlias(ref, aliasRef) { this._map.set(ref.toString(), this.get(aliasRef)); } [Symbol.iterator]() { return this._map.values(); } clear() { this._map.clear(); } } function isName(v, name) { return v instanceof Name && (name === undefined || v.name === name); } function isCmd(v, cmd) { return v instanceof Cmd && (cmd === undefined || v.cmd === cmd); } function isDict(v, type) { return v instanceof Dict && (type === undefined || isName(v.get("Type"), type)); } function isRefsEqual(v1, v2) { return v1.num === v2.num && v1.gen === v2.gen; } ;// CONCATENATED MODULE: ./src/core/base_stream.js class BaseStream { constructor() { if (this.constructor === BaseStream) { unreachable("Cannot initialize BaseStream."); } } get length() { unreachable("Abstract getter `length` accessed"); } get isEmpty() { unreachable("Abstract getter `isEmpty` accessed"); } get isDataLoaded() { return shadow(this, "isDataLoaded", true); } getByte() { unreachable("Abstract method `getByte` called"); } getBytes(length) { unreachable("Abstract method `getBytes` called"); } peekByte() { const peekedByte = this.getByte(); if (peekedByte !== -1) { this.pos--; } return peekedByte; } peekBytes(length) { const bytes = this.getBytes(length); this.pos -= bytes.length; return bytes; } getUint16() { const b0 = this.getByte(); const b1 = this.getByte(); if (b0 === -1 || b1 === -1) { return -1; } return (b0 << 8) + b1; } getInt32() { const b0 = this.getByte(); const b1 = this.getByte(); const b2 = this.getByte(); const b3 = this.getByte(); return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; } getByteRange(begin, end) { unreachable("Abstract method `getByteRange` called"); } getString(length) { return bytesToString(this.getBytes(length)); } skip(n) { this.pos += n || 1; } reset() { unreachable("Abstract method `reset` called"); } moveStart() { unreachable("Abstract method `moveStart` called"); } makeSubStream(start, length, dict = null) { unreachable("Abstract method `makeSubStream` called"); } getBaseStreams() { return null; } } ;// CONCATENATED MODULE: ./src/core/core_utils.js const PDF_VERSION_REGEXP = /^[1-9]\.\d$/; function getLookupTableFactory(initializer) { let lookup; return function () { if (initializer) { lookup = Object.create(null); initializer(lookup); initializer = null; } return lookup; }; } class MissingDataException extends BaseException { constructor(begin, end) { super(`Missing data [${begin}, ${end})`, "MissingDataException"); this.begin = begin; this.end = end; } } class ParserEOFException extends BaseException { constructor(msg) { super(msg, "ParserEOFException"); } } class XRefEntryException extends BaseException { constructor(msg) { super(msg, "XRefEntryException"); } } class XRefParseException extends BaseException { constructor(msg) { super(msg, "XRefParseException"); } } function arrayBuffersToBytes(arr) { const length = arr.length; if (length === 0) { return new Uint8Array(0); } if (length === 1) { return new Uint8Array(arr[0]); } let dataLength = 0; for (let i = 0; i < length; i++) { dataLength += arr[i].byteLength; } const data = new Uint8Array(dataLength); let pos = 0; for (let i = 0; i < length; i++) { const item = new Uint8Array(arr[i]); data.set(item, pos); pos += item.byteLength; } return data; } function getInheritableProperty({ dict, key, getArray = false, stopWhenFound = true }) { let values; const visited = new RefSet(); while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) { if (dict.objId) { visited.put(dict.objId); } const value = getArray ? dict.getArray(key) : dict.get(key); if (value !== undefined) { if (stopWhenFound) { return value; } (values ||= []).push(value); } dict = dict.get("Parent"); } return values; } const ROMAN_NUMBER_MAP = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]; function toRomanNumerals(number, lowerCase = false) { assert(Number.isInteger(number) && number > 0, "The number should be a positive integer."); const romanBuf = []; let pos; while (number >= 1000) { number -= 1000; romanBuf.push("M"); } pos = number / 100 | 0; number %= 100; romanBuf.push(ROMAN_NUMBER_MAP[pos]); pos = number / 10 | 0; number %= 10; romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]); romanBuf.push(ROMAN_NUMBER_MAP[20 + number]); const romanStr = romanBuf.join(""); return lowerCase ? romanStr.toLowerCase() : romanStr; } function log2(x) { if (x <= 0) { return 0; } return Math.ceil(Math.log2(x)); } function readInt8(data, offset) { return data[offset] << 24 >> 24; } function readUint16(data, offset) { return data[offset] << 8 | data[offset + 1]; } function readUint32(data, offset) { return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0; } function isWhiteSpace(ch) { return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a; } function parseXFAPath(path) { const positionPattern = /(.+)\[(\d+)\]$/; return path.split(".").map(component => { const m = component.match(positionPattern); if (m) { return { name: m[1], pos: parseInt(m[2], 10) }; } return { name: component, pos: 0 }; }); } function escapePDFName(str) { const buffer = []; let start = 0; for (let i = 0, ii = str.length; i < ii; i++) { const char = str.charCodeAt(i); if (char < 0x21 || char > 0x7e || char === 0x23 || char === 0x28 || char === 0x29 || char === 0x3c || char === 0x3e || char === 0x5b || char === 0x5d || char === 0x7b || char === 0x7d || char === 0x2f || char === 0x25) { if (start < i) { buffer.push(str.substring(start, i)); } buffer.push(`#${char.toString(16)}`); start = i + 1; } } if (buffer.length === 0) { return str; } if (start < str.length) { buffer.push(str.substring(start, str.length)); } return buffer.join(""); } function escapeString(str) { return str.replaceAll(/([()\\\n\r])/g, match => { if (match === "\n") { return "\\n"; } else if (match === "\r") { return "\\r"; } return `\\${match}`; }); } function _collectJS(entry, xref, list, parents) { if (!entry) { return; } let parent = null; if (entry instanceof Ref) { if (parents.has(entry)) { return; } parent = entry; parents.put(parent); entry = xref.fetch(entry); } if (Array.isArray(entry)) { for (const element of entry) { _collectJS(element, xref, list, parents); } } else if (entry instanceof Dict) { if (isName(entry.get("S"), "JavaScript")) { const js = entry.get("JS"); let code; if (js instanceof BaseStream) { code = js.getString(); } else if (typeof js === "string") { code = js; } code &&= stringToPDFString(code).replaceAll("\x00", ""); if (code) { list.push(code); } } _collectJS(entry.getRaw("Next"), xref, list, parents); } if (parent) { parents.remove(parent); } } function collectActions(xref, dict, eventType) { const actions = Object.create(null); const additionalActionsDicts = getInheritableProperty({ dict, key: "AA", stopWhenFound: false }); if (additionalActionsDicts) { for (let i = additionalActionsDicts.length - 1; i >= 0; i--) { const additionalActions = additionalActionsDicts[i]; if (!(additionalActions instanceof Dict)) { continue; } for (const key of additionalActions.getKeys()) { const action = eventType[key]; if (!action) { continue; } const actionDict = additionalActions.getRaw(key); const parents = new RefSet(); const list = []; _collectJS(actionDict, xref, list, parents); if (list.length > 0) { actions[action] = list; } } } } if (dict.has("A")) { const actionDict = dict.get("A"); const parents = new RefSet(); const list = []; _collectJS(actionDict, xref, list, parents); if (list.length > 0) { actions.Action = list; } } return objectSize(actions) > 0 ? actions : null; } const XMLEntities = { 0x3c: "<", 0x3e: ">", 0x26: "&", 0x22: """, 0x27: "'" }; function encodeToXmlString(str) { const buffer = []; let start = 0; for (let i = 0, ii = str.length; i < ii; i++) { const char = str.codePointAt(i); if (0x20 <= char && char <= 0x7e) { const entity = XMLEntities[char]; if (entity) { if (start < i) { buffer.push(str.substring(start, i)); } buffer.push(entity); start = i + 1; } } else { if (start < i) { buffer.push(str.substring(start, i)); } buffer.push(`&#x${char.toString(16).toUpperCase()};`); if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) { i++; } start = i + 1; } } if (buffer.length === 0) { return str; } if (start < str.length) { buffer.push(str.substring(start, str.length)); } return buffer.join(""); } function validateFontName(fontFamily, mustWarn = false) { const m = /^("|').*("|')$/.exec(fontFamily); if (m && m[1] === m[2]) { const re = new RegExp(`[^\\\\]${m[1]}`); if (re.test(fontFamily.slice(1, -1))) { if (mustWarn) { warn(`FontFamily contains unescaped ${m[1]}: ${fontFamily}.`); } return false; } } else { for (const ident of fontFamily.split(/[ \t]+/)) { if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) { if (mustWarn) { warn(`FontFamily contains invalid <custom-ident>: ${fontFamily}.`); } return false; } } } return true; } function validateCSSFont(cssFontInfo) { const DEFAULT_CSS_FONT_OBLIQUE = "14"; const DEFAULT_CSS_FONT_WEIGHT = "400"; const CSS_FONT_WEIGHT_VALUES = new Set(["100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "normal", "bold", "bolder", "lighter"]); const { fontFamily, fontWeight, italicAngle } = cssFontInfo; if (!validateFontName(fontFamily, true)) { return false; } const weight = fontWeight ? fontWeight.toString() : ""; cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) ? weight : DEFAULT_CSS_FONT_WEIGHT; const angle = parseFloat(italicAngle); cssFontInfo.italicAngle = isNaN(angle) || angle < -90 || angle > 90 ? DEFAULT_CSS_FONT_OBLIQUE : italicAngle.toString(); return true; } function recoverJsURL(str) { const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"]; const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").replaceAll(".", "\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i"); const jsUrl = regex.exec(str); if (jsUrl?.[2]) { const url = jsUrl[2]; let newWindow = false; if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") { newWindow = true; } return { url, newWindow }; } return null; } function numberToString(value) { if (Number.isInteger(value)) { return value.toString(); } const roundedValue = Math.round(value * 100); if (roundedValue % 100 === 0) { return (roundedValue / 100).toString(); } if (roundedValue % 10 === 0) { return value.toFixed(1); } return value.toFixed(2); } function getNewAnnotationsMap(annotationStorage) { if (!annotationStorage) { return null; } const newAnnotationsByPage = new Map(); for (const [key, value] of annotationStorage) { if (!key.startsWith(AnnotationEditorPrefix)) { continue; } let annotations = newAnnotationsByPage.get(value.pageIndex); if (!annotations) { annotations = []; newAnnotationsByPage.set(value.pageIndex, annotations); } annotations.push(value); } return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null; } function isAscii(str) { return /^[\x00-\x7F]*$/.test(str); } function stringToUTF16HexString(str) { const buf = []; for (let i = 0, ii = str.length; i < ii; i++) { const char = str.charCodeAt(i); buf.push((char >> 8 & 0xff).toString(16).padStart(2, "0"), (char & 0xff).toString(16).padStart(2, "0")); } return buf.join(""); } function stringToUTF16String(str, bigEndian = false) { const buf = []; if (bigEndian) { buf.push("\xFE\xFF"); } for (let i = 0, ii = str.length; i < ii; i++) { const char = str.charCodeAt(i); buf.push(String.fromCharCode(char >> 8 & 0xff), String.fromCharCode(char & 0xff)); } return buf.join(""); } function getRotationMatrix(rotation, width, height) { switch (rotation) { case 90: return [0, 1, -1, 0, width, 0]; case 180: return [-1, 0, 0, -1, width, height]; case 270: return [0, -1, 1, 0, 0, height]; default: throw new Error("Invalid rotation"); } } ;// CONCATENATED MODULE: ./src/core/stream.js class Stream extends BaseStream { constructor(arrayBuffer, start, length, dict) { super(); this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer); this.start = start || 0; this.pos = this.start; this.end = start + length || this.bytes.length; this.dict = dict; } get length() { return this.end - this.start; } get isEmpty() { return this.length === 0; } getByte() { if (this.pos >= this.end) { return -1; } return this.bytes[this.pos++]; } getBytes(length) { const bytes = this.bytes; const pos = this.pos; const strEnd = this.end; if (!length) { return bytes.subarray(pos, strEnd); } let end = pos + length; if (end > strEnd) { end = strEnd; } this.pos = end; return bytes.subarray(pos, end); } getByteRange(begin, end) { if (begin < 0) { begin = 0; } if (end > this.end) { end = this.end; } return this.bytes.subarray(begin, end); } reset() { this.pos = this.start; } moveStart() { this.start = this.pos; } makeSubStream(start, length, dict = null) { return new Stream(this.bytes.buffer, start, length, dict); } } class StringStream extends Stream { constructor(str) { super(stringToBytes(str)); } } class NullStream extends Stream { constructor() { super(new Uint8Array(0)); } } ;// CONCATENATED MODULE: ./src/core/chunked_stream.js class ChunkedStream extends Stream { constructor(length, chunkSize, manager) { super(new Uint8Array(length), 0, length, null); this.chunkSize = chunkSize; this._loadedChunks = new Set(); this.numChunks = Math.ceil(length / chunkSize); this.manager = manager; this.progressiveDataLength = 0; this.lastSuccessfulEnsureByteChunk = -1; } getMissingChunks() { const chunks = []; for (let chunk = 0, n = this.numChunks; chunk < n; ++chunk) { if (!this._loadedChunks.has(chunk)) { chunks.push(chunk); } } return chunks; } get numChunksLoaded() { return this._loadedChunks.size; } get isDataLoaded() { return this.numChunksLoaded === this.numChunks; } onReceiveData(begin, chunk) { const chunkSize = this.chunkSize; if (begin % chunkSize !== 0) { throw new Error(`Bad begin offset: ${begin}`); } const end = begin + chunk.byteLength; if (end % chunkSize !== 0 && end !== this.bytes.length) { throw new Error(`Bad end offset: ${end}`); } this.bytes.set(new Uint8Array(chunk), begin); const beginChunk = Math.floor(begin / chunkSize); const endChunk = Math.floor((end - 1) / chunkSize) + 1; for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { this._loadedChunks.add(curChunk); } } onReceiveProgressiveData(data) { let position = this.progressiveDataLength; const beginChunk = Math.floor(position / this.chunkSize); this.bytes.set(new Uint8Array(data), position); position += data.byteLength; this.progressiveDataLength = position; const endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize); for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { this._loadedChunks.add(curChunk); } } ensureByte(pos) { if (pos < this.progressiveDataLength) { return; } const chunk = Math.floor(pos / this.chunkSize); if (chunk > this.numChunks) { return; } if (chunk === this.lastSuccessfulEnsureByteChunk) { return; } if (!this._loadedChunks.has(chunk)) { throw new MissingDataException(pos, pos + 1); } this.lastSuccessfulEnsureByteChunk = chunk; } ensureRange(begin, end) { if (begin >= end) { return; } if (end <= this.progressiveDataLength) { return; } const beginChunk = Math.floor(begin / this.chunkSize); if (beginChunk > this.numChunks) { return; } const endChunk = Math.min(Math.floor((end - 1) / this.chunkSize) + 1, this.numChunks); for (let chunk = beginChunk; chunk < endChunk; ++chunk) { if (!this._loadedChunks.has(chunk)) { throw new MissingDataException(begin, end); } } } nextEmptyChunk(beginChunk) { const numChunks = this.numChunks; for (let i = 0; i < numChunks; ++i) { const chunk = (beginChunk + i) % numChunks; if (!this._loadedChunks.has(chunk)) { return chunk; } } return null; } hasChunk(chunk) { return this._loadedChunks.has(chunk); } getByte() { const pos = this.pos; if (pos >= this.end) { return -1; } if (pos >= this.progressiveDataLength) { this.ensureByte(pos); } return this.bytes[this.pos++]; } getBytes(length) { const bytes = this.bytes; const pos = this.pos; const strEnd = this.end; if (!length) { if (strEnd > this.progressiveDataLength) { this.ensureRange(pos, strEnd); } return bytes.subarray(pos, strEnd); } let end = pos + length; if (end > strEnd) { end = strEnd; } if (end > this.progressiveDataLength) { this.ensureRange(pos, end); } this.pos = end; return bytes.subarray(pos, end); } getByteRange(begin, end) { if (begin < 0) { begin = 0; } if (end > this.end) { end = this.end; } if (end > this.progressiveDataLength) { this.ensureRange(begin, end); } return this.bytes.subarray(begin, end); } makeSubStream(start, length, dict = null) { if (length) { if (start + length > this.progressiveDataLength) { this.ensureRange(start, start + length); } } else if (start >= this.progressiveDataLength) { this.ensureByte(start); } function ChunkedStreamSubstream() {} ChunkedStreamSubstream.prototype = Object.create(this); ChunkedStreamSubstream.prototype.getMissingChunks = function () { const chunkSize = this.chunkSize; const beginChunk = Math.floor(this.start / chunkSize); const endChunk = Math.floor((this.end - 1) / chunkSize) + 1; const missingChunks = []; for (let chunk = beginChunk; chunk < endChunk; ++chunk) { if (!this._loadedChunks.has(chunk)) { missingChunks.push(chunk); } } return missingChunks; }; Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", { get() { if (this.numChunksLoaded === this.numChunks) { return true; } return this.getMissingChunks().length === 0; }, configurable: true }); const subStream = new ChunkedStreamSubstream(); subStream.pos = subStream.start = start; subStream.end = start + length || this.end; subStream.dict = dict; return subStream; } getBaseStreams() { return [this]; } } class ChunkedStreamManager { constructor(pdfNetworkStream, args) { this.length = args.length; this.chunkSize = args.rangeChunkSize; this.stream = new ChunkedStream(this.length, this.chunkSize, this); this.pdfNetworkStream = pdfNetworkStream; this.disableAutoFetch = args.disableAutoFetch; this.msgHandler = args.msgHandler; this.currRequestId = 0; this._chunksNeededByRequest = new Map(); this._requestsByChunk = new Map(); this._promisesByRequest = new Map(); this.progressiveDataLength = 0; this.aborted = false; this._loadedStreamCapability = new PromiseCapability(); } sendRequest(begin, end) { const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end); if (!rangeReader.isStreamingSupported) { rangeReader.onProgress = this.onProgress.bind(this); } let chunks = [], loaded = 0; return new Promise((resolve, reject) => { const readChunk = ({ value, done }) => { try { if (done) { const chunkData = arrayBuffersToBytes(chunks); chunks = null; resolve(chunkData); return; } loaded += value.byteLength; if (rangeReader.isStreamingSupported) { this.onProgress({ loaded }); } chunks.push(value); rangeReader.read().then(readChunk, reject); } catch (e) { reject(e); } }; rangeReader.read().then(readChunk, reject); }).then(data => { if (this.aborted) { return; } this.onReceiveData({ chunk: data, begin }); }); } requestAllChunks(noFetch = false) { if (!noFetch) { const missingChunks = this.stream.getMissingChunks(); this._requestChunks(missingChunks); } return this._loadedStreamCapability.promise; } _requestChunks(chunks) { const requestId = this.currRequestId++; const chunksNeeded = new Set(); this._chunksNeededByRequest.set(requestId, chunksNeeded); for (const chunk of chunks) { if (!this.stream.hasChunk(chunk)) { chunksNeeded.add(chunk); } } if (chunksNeeded.size === 0) { return Promise.resolve(); } const capability = new PromiseCapability(); this._promisesByRequest.set(requestId, capability); const chunksToRequest = []; for (const chunk of chunksNeeded) { let requestIds = this._requestsByChunk.get(chunk); if (!requestIds) { requestIds = []; this._requestsByChunk.set(chunk, requestIds); chunksToRequest.push(chunk); } requestIds.push(requestId); } if (chunksToRequest.length > 0) { const groupedChunksToRequest = this.groupChunks(chunksToRequest); for (const groupedChunk of groupedChunksToRequest) { const begin = groupedChunk.beginChunk * this.chunkSize; const end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length); this.sendRequest(begin, end).catch(capability.reject); } } return capability.promise.catch(reason => { if (this.aborted) { return; } throw reason; }); } getStream() { return this.stream; } requestRange(begin, end) { end = Math.min(end, this.length); const beginChunk = this.getBeginChunk(begin); const endChunk = this.getEndChunk(end); const chunks = []; for (let chunk = beginChunk; chunk < endChunk; ++chunk) { chunks.push(chunk); } return this._requestChunks(chunks); } requestRanges(ranges = []) { const chunksToRequest = []; for (const range of ranges) { const beginChunk = this.getBeginChunk(range.begin); const endChunk = this.getEndChunk(range.end); for (let chunk = beginChunk; chunk < endChunk; ++chunk) { if (!chunksToRequest.includes(chunk)) { chunksToRequest.push(chunk); } } } chunksToRequest.sort(function (a, b) { return a - b; }); return this._requestChunks(chunksToRequest); } groupChunks(chunks) { const groupedChunks = []; let beginChunk = -1; let prevChunk = -1; for (let i = 0, ii = chunks.length; i < ii; ++i) { const chunk = chunks[i]; if (beginChunk < 0) { beginChunk = chunk; } if (prevChunk >= 0 && prevChunk + 1 !== chunk) { groupedChunks.push({ beginChunk, endChunk: prevChunk + 1 }); beginChunk = chunk; } if (i + 1 === chunks.length) { groupedChunks.push({ beginChunk, endChunk: chunk + 1 }); } prevChunk = chunk; } return groupedChunks; } onProgress(args) { this.msgHandler.send("DocProgress", { loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded, total: this.length }); } onReceiveData(args) { const chunk = args.chunk; const isProgressive = args.begin === undefined; const begin = isProgressive ? this.progressiveDataLength : args.begin; const end = begin + chunk.byteLength; const beginChunk = Math.floor(begin / this.chunkSize); const endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize); if (isProgressive) { this.stream.onReceiveProgressiveData(chunk); this.progressiveDataLength = end; } else { this.stream.onReceiveData(begin, chunk); } if (this.stream.isDataLoaded) { this._loadedStreamCapability.resolve(this.stream); } const loadedRequests = []; for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { const requestIds = this._requestsByChunk.get(curChunk); if (!requestIds) { continue; } this._requestsByChunk.delete(curChunk); for (const requestId of requestIds) { const chunksNeeded = this._chunksNeededByRequest.get(requestId); if (chunksNeeded.has(curChunk)) { chunksNeeded.delete(curChunk); } if (chunksNeeded.size > 0) { continue; } loadedRequests.push(requestId); } } if (!this.disableAutoFetch && this._requestsByChunk.size === 0) { let nextEmptyChunk; if (this.stream.numChunksLoaded === 1) { const lastChunk = this.stream.numChunks - 1; if (!this.stream.hasChunk(lastChunk)) { nextEmptyChunk = lastChunk; } } else { nextEmptyChunk = this.stream.nextEmptyChunk(endChunk); } if (Number.isInteger(nextEmptyChunk)) { this._requestChunks([nextEmptyChunk]); } } for (const requestId of loadedRequests) { const capability = this._promisesByRequest.get(requestId); this._promisesByRequest.delete(requestId); capability.resolve(); } this.msgHandler.send("DocProgress", { loaded: this.stream.numChunksLoaded * this.chunkSize, total: this.length }); } onError(err) { this._loadedStreamCapability.reject(err); } getBeginChunk(begin) { return Math.floor(begin / this.chunkSize); } getEndChunk(end) { return Math.floor((end - 1) / this.chunkSize) + 1; } abort(reason) { this.aborted = true; this.pdfNetworkStream?.cancelAllRequests(reason); for (const capability of this._promisesByRequest.values()) { capability.reject(reason); } } } ;// CONCATENATED MODULE: ./src/core/colorspace.js function resizeRgbImage(src, dest, w1, h1, w2, h2, alpha01) { const COMPONENTS = 3; alpha01 = alpha01 !== 1 ? 0 : alpha01; const xRatio = w1 / w2; const yRatio = h1 / h2; let newIndex = 0, oldIndex; const xScaled = new Uint16Array(w2); const w1Scanline = w1 * COMPONENTS; for (let i = 0; i < w2; i++) { xScaled[i] = Math.floor(i * xRatio) * COMPONENTS; } for (let i = 0; i < h2; i++) { const py = Math.floor(i * yRatio) * w1Scanline; for (let j = 0; j < w2; j++) { oldIndex = py + xScaled[j]; dest[newIndex++] = src[oldIndex++]; dest[newIndex++] = src[oldIndex++]; dest[newIndex++] = src[oldIndex++]; newIndex += alpha01; } } } class ColorSpace { constructor(name, numComps) { if (this.constructor === ColorSpace) { unreachable("Cannot initialize ColorSpace."); } this.name = name; this.numComps = numComps; } getRgb(src, srcOffset) { const rgb = new Uint8ClampedArray(3); this.getRgbItem(src, srcOffset, rgb, 0); return rgb; } getRgbItem(src, srcOffset, dest, destOffset) { unreachable("Should not call ColorSpace.getRgbItem"); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { unreachable("Should not call ColorSpace.getRgbBuffer"); } getOutputLength(inputLength, alpha01) { unreachable("Should not call ColorSpace.getOutputLength"); } isPassthrough(bits) { return false; } isDefaultDecode(decodeMap, bpc) { return ColorSpace.isDefaultDecode(decodeMap, this.numComps); } fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) { const count = originalWidth * originalHeight; let rgbBuf = null; const numComponentColors = 1 << bpc; const needsResizing = originalHeight !== height || originalWidth !== width; if (this.isPassthrough(bpc)) { rgbBuf = comps; } else if (this.numComps === 1 && count > numComponentColors && this.name !== "DeviceGray" && this.name !== "DeviceRGB") { const allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : new Uint16Array(numComponentColors); for (let i = 0; i < numComponentColors; i++) { allColors[i] = i; } const colorMap = new Uint8ClampedArray(numComponentColors * 3); this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, 0); if (!needsResizing) { let destPos = 0; for (let i = 0; i < count; ++i) { const key = comps[i] * 3; dest[destPos++] = colorMap[key]; dest[destPos++] = colorMap[key + 1]; dest[destPos++] = colorMap[key + 2]; destPos += alpha01; } } else { rgbBuf = new Uint8Array(count * 3); let rgbPos = 0; for (let i = 0; i < count; ++i) { const key = comps[i] * 3; rgbBuf[rgbPos++] = colorMap[key]; rgbBuf[rgbPos++] = colorMap[key + 1]; rgbBuf[rgbPos++] = colorMap[key + 2]; } } } else if (!needsResizing) { this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01); } else { rgbBuf = new Uint8ClampedArray(count * 3); this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, 0); } if (rgbBuf) { if (needsResizing) { resizeRgbImage(rgbBuf, dest, originalWidth, originalHeight, width, height, alpha01); } else { let destPos = 0, rgbPos = 0; for (let i = 0, ii = width * actualHeight; i < ii; i++) { dest[destPos++] = rgbBuf[rgbPos++]; dest[destPos++] = rgbBuf[rgbPos++]; dest[destPos++] = rgbBuf[rgbPos++]; destPos += alpha01; } } } } get usesZeroToOneRange() { return shadow(this, "usesZeroToOneRange", true); } static _cache(cacheKey, xref, localColorSpaceCache, parsedColorSpace) { if (!localColorSpaceCache) { throw new Error('ColorSpace._cache - expected "localColorSpaceCache" argument.'); } if (!parsedColorSpace) { throw new Error('ColorSpace._cache - expected "parsedColorSpace" argument.'); } let csName, csRef; if (cacheKey instanceof Ref) { csRef = cacheKey; cacheKey = xref.fetch(cacheKey); } if (cacheKey instanceof Name) { csName = cacheKey.name; } if (csName || csRef) { localColorSpaceCache.set(csName, csRef, parsedColorSpace); } } static getCached(cacheKey, xref, localColorSpaceCache) { if (!localColorSpaceCache) { throw new Error('ColorSpace.getCached - expected "localColorSpaceCache" argument.'); } if (cacheKey instanceof Ref) { const localColorSpace = localColorSpaceCache.getByRef(cacheKey); if (localColorSpace) { return localColorSpace; } try { cacheKey = xref.fetch(cacheKey); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } } } if (cacheKey instanceof Name) { const localColorSpace = localColorSpaceCache.getByName(cacheKey.name); if (localColorSpace) { return localColorSpace; } } return null; } static async parseAsync({ cs, xref, resources = null, pdfFunctionFactory, localColorSpaceCache }) { const parsedColorSpace = this._parse(cs, xref, resources, pdfFunctionFactory); this._cache(cs, xref, localColorSpaceCache, parsedColorSpace); return parsedColorSpace; } static parse({ cs, xref, resources = null, pdfFunctionFactory, localColorSpaceCache }) { const cachedColorSpace = this.getCached(cs, xref, localColorSpaceCache); if (cachedColorSpace) { return cachedColorSpace; } const parsedColorSpace = this._parse(cs, xref, resources, pdfFunctionFactory); this._cache(cs, xref, localColorSpaceCache, parsedColorSpace); return parsedColorSpace; } static _parse(cs, xref, resources = null, pdfFunctionFactory) { cs = xref.fetchIfRef(cs); if (cs instanceof Name) { switch (cs.name) { case "G": case "DeviceGray": return this.singletons.gray; case "RGB": case "DeviceRGB": return this.singletons.rgb; case "CMYK": case "DeviceCMYK": return this.singletons.cmyk; case "Pattern": return new PatternCS(null); default: if (resources instanceof Dict) { const colorSpaces = resources.get("ColorSpace"); if (colorSpaces instanceof Dict) { const resourcesCS = colorSpaces.get(cs.name); if (resourcesCS) { if (resourcesCS instanceof Name) { return this._parse(resourcesCS, xref, resources, pdfFunctionFactory); } cs = resourcesCS; break; } } } throw new FormatError(`Unrecognized ColorSpace: ${cs.name}`); } } if (Array.isArray(cs)) { const mode = xref.fetchIfRef(cs[0]).name; let params, numComps, baseCS, whitePoint, blackPoint, gamma; switch (mode) { case "G": case "DeviceGray": return this.singletons.gray; case "RGB": case "DeviceRGB": return this.singletons.rgb; case "CMYK": case "DeviceCMYK": return this.singletons.cmyk; case "CalGray": params = xref.fetchIfRef(cs[1]); whitePoint = params.getArray("WhitePoint"); blackPoint = params.getArray("BlackPoint"); gamma = params.get("Gamma"); return new CalGrayCS(whitePoint, blackPoint, gamma); case "CalRGB": params = xref.fetchIfRef(cs[1]); whitePoint = params.getArray("WhitePoint"); blackPoint = params.getArray("BlackPoint"); gamma = params.getArray("Gamma"); const matrix = params.getArray("Matrix"); return new CalRGBCS(whitePoint, blackPoint, gamma, matrix); case "ICCBased": const stream = xref.fetchIfRef(cs[1]); const dict = stream.dict; numComps = dict.get("N"); const alt = dict.get("Alternate"); if (alt) { const altCS = this._parse(alt, xref, resources, pdfFunctionFactory); if (altCS.numComps === numComps) { return altCS; } warn("ICCBased color space: Ignoring incorrect /Alternate entry."); } if (numComps === 1) { return this.singletons.gray; } else if (numComps === 3) { return this.singletons.rgb; } else if (numComps === 4) { return this.singletons.cmyk; } break; case "Pattern": baseCS = cs[1] || null; if (baseCS) { baseCS = this._parse(baseCS, xref, resources, pdfFunctionFactory); } return new PatternCS(baseCS); case "I": case "Indexed": baseCS = this._parse(cs[1], xref, resources, pdfFunctionFactory); const hiVal = xref.fetchIfRef(cs[2]) + 1; const lookup = xref.fetchIfRef(cs[3]); return new IndexedCS(baseCS, hiVal, lookup); case "Separation": case "DeviceN": const name = xref.fetchIfRef(cs[1]); numComps = Array.isArray(name) ? name.length : 1; baseCS = this._parse(cs[2], xref, resources, pdfFunctionFactory); const tintFn = pdfFunctionFactory.create(cs[3]); return new AlternateCS(numComps, baseCS, tintFn); case "Lab": params = xref.fetchIfRef(cs[1]); whitePoint = params.getArray("WhitePoint"); blackPoint = params.getArray("BlackPoint"); const range = params.getArray("Range"); return new LabCS(whitePoint, blackPoint, range); default: throw new FormatError(`Unimplemented ColorSpace object: ${mode}`); } } throw new FormatError(`Unrecognized ColorSpace object: ${cs}`); } static isDefaultDecode(decode, numComps) { if (!Array.isArray(decode)) { return true; } if (numComps * 2 !== decode.length) { warn("The decode map is not the correct length"); return true; } for (let i = 0, ii = decode.length; i < ii; i += 2) { if (decode[i] !== 0 || decode[i + 1] !== 1) { return false; } } return true; } static get singletons() { return shadow(this, "singletons", { get gray() { return shadow(this, "gray", new DeviceGrayCS()); }, get rgb() { return shadow(this, "rgb", new DeviceRgbCS()); }, get cmyk() { return shadow(this, "cmyk", new DeviceCmykCS()); } }); } } class AlternateCS extends ColorSpace { constructor(numComps, base, tintFn) { super("Alternate", numComps); this.base = base; this.tintFn = tintFn; this.tmpBuf = new Float32Array(base.numComps); } getRgbItem(src, srcOffset, dest, destOffset) { const tmpBuf = this.tmpBuf; this.tintFn(src, srcOffset, tmpBuf, 0); this.base.getRgbItem(tmpBuf, 0, dest, destOffset); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const tintFn = this.tintFn; const base = this.base; const scale = 1 / ((1 << bits) - 1); const baseNumComps = base.numComps; const usesZeroToOneRange = base.usesZeroToOneRange; const isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0; let pos = isPassthrough ? destOffset : 0; const baseBuf = isPassthrough ? dest : new Uint8ClampedArray(baseNumComps * count); const numComps = this.numComps; const scaled = new Float32Array(numComps); const tinted = new Float32Array(baseNumComps); let i, j; for (i = 0; i < count; i++) { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } tintFn(scaled, 0, tinted, 0); if (usesZeroToOneRange) { for (j = 0; j < baseNumComps; j++) { baseBuf[pos++] = tinted[j] * 255; } } else { base.getRgbItem(tinted, 0, baseBuf, pos); pos += baseNumComps; } } if (!isPassthrough) { base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); } } getOutputLength(inputLength, alpha01) { return this.base.getOutputLength(inputLength * this.base.numComps / this.numComps, alpha01); } } class PatternCS extends ColorSpace { constructor(baseCS) { super("Pattern", null); this.base = baseCS; } isDefaultDecode(decodeMap, bpc) { unreachable("Should not call PatternCS.isDefaultDecode"); } } class IndexedCS extends ColorSpace { constructor(base, highVal, lookup) { super("Indexed", 1); this.base = base; this.highVal = highVal; const length = base.numComps * highVal; this.lookup = new Uint8Array(length); if (lookup instanceof BaseStream) { const bytes = lookup.getBytes(length); this.lookup.set(bytes); } else if (typeof lookup === "string") { for (let i = 0; i < length; ++i) { this.lookup[i] = lookup.charCodeAt(i) & 0xff; } } else { throw new FormatError(`IndexedCS - unrecognized lookup table: ${lookup}`); } } getRgbItem(src, srcOffset, dest, destOffset) { const numComps = this.base.numComps; const start = src[srcOffset] * numComps; this.base.getRgbBuffer(this.lookup, start, 1, dest, destOffset, 8, 0); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const base = this.base; const numComps = base.numComps; const outputDelta = base.getOutputLength(numComps, alpha01); const lookup = this.lookup; for (let i = 0; i < count; ++i) { const lookupPos = src[srcOffset++] * numComps; base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); destOffset += outputDelta; } } getOutputLength(inputLength, alpha01) { return this.base.getOutputLength(inputLength * this.base.numComps, alpha01); } isDefaultDecode(decodeMap, bpc) { if (!Array.isArray(decodeMap)) { return true; } if (decodeMap.length !== 2) { warn("Decode map length is not correct"); return true; } if (!Number.isInteger(bpc) || bpc < 1) { warn("Bits per component is not correct"); return true; } return decodeMap[0] === 0 && decodeMap[1] === (1 << bpc) - 1; } } class DeviceGrayCS extends ColorSpace { constructor() { super("DeviceGray", 1); } getRgbItem(src, srcOffset, dest, destOffset) { const c = src[srcOffset] * 255; dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const scale = 255 / ((1 << bits) - 1); let j = srcOffset, q = destOffset; for (let i = 0; i < count; ++i) { const c = scale * src[j++]; dest[q++] = c; dest[q++] = c; dest[q++] = c; q += alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength * (3 + alpha01); } } class DeviceRgbCS extends ColorSpace { constructor() { super("DeviceRGB", 3); } getRgbItem(src, srcOffset, dest, destOffset) { dest[destOffset] = src[srcOffset] * 255; dest[destOffset + 1] = src[srcOffset + 1] * 255; dest[destOffset + 2] = src[srcOffset + 2] * 255; } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { if (bits === 8 && alpha01 === 0) { dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); return; } const scale = 255 / ((1 << bits) - 1); let j = srcOffset, q = destOffset; for (let i = 0; i < count; ++i) { dest[q++] = scale * src[j++]; dest[q++] = scale * src[j++]; dest[q++] = scale * src[j++]; q += alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength * (3 + alpha01) / 3 | 0; } isPassthrough(bits) { return bits === 8; } } class DeviceCmykCS extends ColorSpace { constructor() { super("DeviceCMYK", 4); } #toRgb(src, srcOffset, srcScale, dest, destOffset) { const c = src[srcOffset] * srcScale; const m = src[srcOffset + 1] * srcScale; const y = src[srcOffset + 2] * srcScale; const k = src[srcOffset + 3] * srcScale; dest[destOffset] = 255 + c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k + -285.2331026137004) + m * (1.7149763477362134 * m - 5.6096736904047315 * y + -17.873870861415444 * k - 5.497006427196366) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + k * (-21.86122147463605 * k - 189.48180835922747); dest[destOffset + 1] = 255 + c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k + -79.2970844816548) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + k * (-20.737325471181034 * k - 187.80453709719578); dest[destOffset + 2] = 255 + c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367); } getRgbItem(src, srcOffset, dest, destOffset) { this.#toRgb(src, srcOffset, 1, dest, destOffset); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const scale = 1 / ((1 << bits) - 1); for (let i = 0; i < count; i++) { this.#toRgb(src, srcOffset, scale, dest, destOffset); srcOffset += 4; destOffset += 3 + alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength / 4 * (3 + alpha01) | 0; } } class CalGrayCS extends ColorSpace { constructor(whitePoint, blackPoint, gamma) { super("CalGray", 1); if (!whitePoint) { throw new FormatError("WhitePoint missing - required for color space CalGray"); } [this.XW, this.YW, this.ZW] = whitePoint; [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0]; this.G = gamma || 1; if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`); } if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { info(`Invalid BlackPoint for ${this.name}, falling back to default.`); this.XB = this.YB = this.ZB = 0; } if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { warn(`${this.name}, BlackPoint: XB: ${this.XB}, YB: ${this.YB}, ` + `ZB: ${this.ZB}, only default values are supported.`); } if (this.G < 1) { info(`Invalid Gamma: ${this.G} for ${this.name}, falling back to default.`); this.G = 1; } } #toRgb(src, srcOffset, dest, destOffset, scale) { const A = src[srcOffset] * scale; const AG = A ** this.G; const L = this.YW * AG; const val = Math.max(295.8 * L ** 0.3333333333333333 - 40.8, 0); dest[destOffset] = val; dest[destOffset + 1] = val; dest[destOffset + 2] = val; } getRgbItem(src, srcOffset, dest, destOffset) { this.#toRgb(src, srcOffset, dest, destOffset, 1); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const scale = 1 / ((1 << bits) - 1); for (let i = 0; i < count; ++i) { this.#toRgb(src, srcOffset, dest, destOffset, scale); srcOffset += 1; destOffset += 3 + alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength * (3 + alpha01); } } class CalRGBCS extends ColorSpace { static #BRADFORD_SCALE_MATRIX = new Float32Array([0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296]); static #BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428, 0.9684867]); static #SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252]); static #FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]); static #tempNormalizeMatrix = new Float32Array(3); static #tempConvertMatrix1 = new Float32Array(3); static #tempConvertMatrix2 = new Float32Array(3); static #DECODE_L_CONSTANT = ((8 + 16) / 116) ** 3 / 8.0; constructor(whitePoint, blackPoint, gamma, matrix) { super("CalRGB", 3); if (!whitePoint) { throw new FormatError("WhitePoint missing - required for color space CalRGB"); } const [XW, YW, ZW] = this.whitePoint = whitePoint; const [XB, YB, ZB] = this.blackPoint = blackPoint || new Float32Array(3); [this.GR, this.GG, this.GB] = gamma || new Float32Array([1, 1, 1]); [this.MXA, this.MYA, this.MZA, this.MXB, this.MYB, this.MZB, this.MXC, this.MYC, this.MZC] = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); if (XW < 0 || ZW < 0 || YW !== 1) { throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`); } if (XB < 0 || YB < 0 || ZB < 0) { info(`Invalid BlackPoint for ${this.name} [${XB}, ${YB}, ${ZB}], ` + "falling back to default."); this.blackPoint = new Float32Array(3); } if (this.GR < 0 || this.GG < 0 || this.GB < 0) { info(`Invalid Gamma [${this.GR}, ${this.GG}, ${this.GB}] for ` + `${this.name}, falling back to default.`); this.GR = this.GG = this.GB = 1; } } #matrixProduct(a, b, result) { result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2]; result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2]; } #toFlat(sourceWhitePoint, LMS, result) { result[0] = LMS[0] * 1 / sourceWhitePoint[0]; result[1] = LMS[1] * 1 / sourceWhitePoint[1]; result[2] = LMS[2] * 1 / sourceWhitePoint[2]; } #toD65(sourceWhitePoint, LMS, result) { const D65X = 0.95047; const D65Y = 1; const D65Z = 1.08883; result[0] = LMS[0] * D65X / sourceWhitePoint[0]; result[1] = LMS[1] * D65Y / sourceWhitePoint[1]; result[2] = LMS[2] * D65Z / sourceWhitePoint[2]; } #sRGBTransferFunction(color) { if (color <= 0.0031308) { return this.#adjustToRange(0, 1, 12.92 * color); } if (color >= 0.99554525) { return 1; } return this.#adjustToRange(0, 1, (1 + 0.055) * color ** (1 / 2.4) - 0.055); } #adjustToRange(min, max, value) { return Math.max(min, Math.min(max, value)); } #decodeL(L) { if (L < 0) { return -this.#decodeL(-L); } if (L > 8.0) { return ((L + 16) / 116) ** 3; } return L * CalRGBCS.#DECODE_L_CONSTANT; } #compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) { if (sourceBlackPoint[0] === 0 && sourceBlackPoint[1] === 0 && sourceBlackPoint[2] === 0) { result[0] = XYZ_Flat[0]; result[1] = XYZ_Flat[1]; result[2] = XYZ_Flat[2]; return; } const zeroDecodeL = this.#decodeL(0); const X_DST = zeroDecodeL; const X_SRC = this.#decodeL(sourceBlackPoint[0]); const Y_DST = zeroDecodeL; const Y_SRC = this.#decodeL(sourceBlackPoint[1]); const Z_DST = zeroDecodeL; const Z_SRC = this.#decodeL(sourceBlackPoint[2]); const X_Scale = (1 - X_DST) / (1 - X_SRC); const X_Offset = 1 - X_Scale; const Y_Scale = (1 - Y_DST) / (1 - Y_SRC); const Y_Offset = 1 - Y_Scale; const Z_Scale = (1 - Z_DST) / (1 - Z_SRC); const Z_Offset = 1 - Z_Scale; result[0] = XYZ_Flat[0] * X_Scale + X_Offset; result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset; result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset; } #normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) { if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) { result[0] = XYZ_In[0]; result[1] = XYZ_In[1]; result[2] = XYZ_In[2]; return; } const LMS = result; this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS); const LMS_Flat = CalRGBCS.#tempNormalizeMatrix; this.#toFlat(sourceWhitePoint, LMS, LMS_Flat); this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result); } #normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) { const LMS = result; this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS); const LMS_D65 = CalRGBCS.#tempNormalizeMatrix; this.#toD65(sourceWhitePoint, LMS, LMS_D65); this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result); } #toRgb(src, srcOffset, dest, destOffset, scale) { const A = this.#adjustToRange(0, 1, src[srcOffset] * scale); const B = this.#adjustToRange(0, 1, src[srcOffset + 1] * scale); const C = this.#adjustToRange(0, 1, src[srcOffset + 2] * scale); const AGR = A === 1 ? 1 : A ** this.GR; const BGG = B === 1 ? 1 : B ** this.GG; const CGB = C === 1 ? 1 : C ** this.GB; const X = this.MXA * AGR + this.MXB * BGG + this.MXC * CGB; const Y = this.MYA * AGR + this.MYB * BGG + this.MYC * CGB; const Z = this.MZA * AGR + this.MZB * BGG + this.MZC * CGB; const XYZ = CalRGBCS.#tempConvertMatrix1; XYZ[0] = X; XYZ[1] = Y; XYZ[2] = Z; const XYZ_Flat = CalRGBCS.#tempConvertMatrix2; this.#normalizeWhitePointToFlat(this.whitePoint, XYZ, XYZ_Flat); const XYZ_Black = CalRGBCS.#tempConvertMatrix1; this.#compensateBlackPoint(this.blackPoint, XYZ_Flat, XYZ_Black); const XYZ_D65 = CalRGBCS.#tempConvertMatrix2; this.#normalizeWhitePointToD65(CalRGBCS.#FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65); const SRGB = CalRGBCS.#tempConvertMatrix1; this.#matrixProduct(CalRGBCS.#SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB); dest[destOffset] = this.#sRGBTransferFunction(SRGB[0]) * 255; dest[destOffset + 1] = this.#sRGBTransferFunction(SRGB[1]) * 255; dest[destOffset + 2] = this.#sRGBTransferFunction(SRGB[2]) * 255; } getRgbItem(src, srcOffset, dest, destOffset) { this.#toRgb(src, srcOffset, dest, destOffset, 1); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const scale = 1 / ((1 << bits) - 1); for (let i = 0; i < count; ++i) { this.#toRgb(src, srcOffset, dest, destOffset, scale); srcOffset += 3; destOffset += 3 + alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength * (3 + alpha01) / 3 | 0; } } class LabCS extends ColorSpace { constructor(whitePoint, blackPoint, range) { super("Lab", 3); if (!whitePoint) { throw new FormatError("WhitePoint missing - required for color space Lab"); } [this.XW, this.YW, this.ZW] = whitePoint; [this.amin, this.amax, this.bmin, this.bmax] = range || [-100, 100, -100, 100]; [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0]; if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { throw new FormatError("Invalid WhitePoint components, no fallback available"); } if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { info("Invalid BlackPoint, falling back to default"); this.XB = this.YB = this.ZB = 0; } if (this.amin > this.amax || this.bmin > this.bmax) { info("Invalid Range, falling back to defaults"); this.amin = -100; this.amax = 100; this.bmin = -100; this.bmax = 100; } } #fn_g(x) { return x >= 6 / 29 ? x ** 3 : 108 / 841 * (x - 4 / 29); } #decode(value, high1, low2, high2) { return low2 + value * (high2 - low2) / high1; } #toRgb(src, srcOffset, maxVal, dest, destOffset) { let Ls = src[srcOffset]; let as = src[srcOffset + 1]; let bs = src[srcOffset + 2]; if (maxVal !== false) { Ls = this.#decode(Ls, maxVal, 0, 100); as = this.#decode(as, maxVal, this.amin, this.amax); bs = this.#decode(bs, maxVal, this.bmin, this.bmax); } if (as > this.amax) { as = this.amax; } else if (as < this.amin) { as = this.amin; } if (bs > this.bmax) { bs = this.bmax; } else if (bs < this.bmin) { bs = this.bmin; } const M = (Ls + 16) / 116; const L = M + as / 500; const N = M - bs / 200; const X = this.XW * this.#fn_g(L); const Y = this.YW * this.#fn_g(M); const Z = this.ZW * this.#fn_g(N); let r, g, b; if (this.ZW < 1) { r = X * 3.1339 + Y * -1.617 + Z * -0.4906; g = X * -0.9785 + Y * 1.916 + Z * 0.0333; b = X * 0.072 + Y * -0.229 + Z * 1.4057; } else { r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; b = X * 0.0557 + Y * -0.204 + Z * 1.057; } dest[destOffset] = Math.sqrt(r) * 255; dest[destOffset + 1] = Math.sqrt(g) * 255; dest[destOffset + 2] = Math.sqrt(b) * 255; } getRgbItem(src, srcOffset, dest, destOffset) { this.#toRgb(src, srcOffset, false, dest, destOffset); } getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { const maxVal = (1 << bits) - 1; for (let i = 0; i < count; i++) { this.#toRgb(src, srcOffset, maxVal, dest, destOffset); srcOffset += 3; destOffset += 3 + alpha01; } } getOutputLength(inputLength, alpha01) { return inputLength * (3 + alpha01) / 3 | 0; } isDefaultDecode(decodeMap, bpc) { return true; } get usesZeroToOneRange() { return shadow(this, "usesZeroToOneRange", false); } } ;// CONCATENATED MODULE: ./src/core/binary_cmap.js function hexToInt(a, size) { let n = 0; for (let i = 0; i <= size; i++) { n = n << 8 | a[i]; } return n >>> 0; } function hexToStr(a, size) { if (size === 1) { return String.fromCharCode(a[0], a[1]); } if (size === 3) { return String.fromCharCode(a[0], a[1], a[2], a[3]); } return String.fromCharCode(...a.subarray(0, size + 1)); } function addHex(a, b, size) { let c = 0; for (let i = size; i >= 0; i--) { c += a[i] + b[i]; a[i] = c & 255; c >>= 8; } } function incHex(a, size) { let c = 1; for (let i = size; i >= 0 && c > 0; i--) { c += a[i]; a[i] = c & 255; c >>= 8; } } const MAX_NUM_SIZE = 16; const MAX_ENCODED_NUM_SIZE = 19; class BinaryCMapStream { constructor(data) { this.buffer = data; this.pos = 0; this.end = data.length; this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE); } readByte() { if (this.pos >= this.end) { return -1; } return this.buffer[this.pos++]; } readNumber() { let n = 0; let last; do { const b = this.readByte(); if (b < 0) { throw new FormatError("unexpected EOF in bcmap"); } last = !(b & 0x80); n = n << 7 | b & 0x7f; } while (!last); return n; } readSigned() { const n = this.readNumber(); return n & 1 ? ~(n >>> 1) : n >>> 1; } readHex(num, size) { num.set(this.buffer.subarray(this.pos, this.pos + size + 1)); this.pos += size + 1; } readHexNumber(num, size) { let last; const stack = this.tmpBuf; let sp = 0; do { const b = this.readByte(); if (b < 0) { throw new FormatError("unexpected EOF in bcmap"); } last = !(b & 0x80); stack[sp++] = b & 0x7f; } while (!last); let i = size, buffer = 0, bufferSize = 0; while (i >= 0) { while (bufferSize < 8 && stack.length > 0) { buffer |= stack[--sp] << bufferSize; bufferSize += 7; } num[i] = buffer & 255; i--; buffer >>= 8; bufferSize -= 8; } } readHexSigned(num, size) { this.readHexNumber(num, size); const sign = num[size] & 1 ? 255 : 0; let c = 0; for (let i = 0; i <= size; i++) { c = (c & 1) << 8 | num[i]; num[i] = c >> 1 ^ sign; } } readString() { const len = this.readNumber(), buf = new Array(len); for (let i = 0; i < len; i++) { buf[i] = this.readNumber(); } return String.fromCharCode(...buf); } } class BinaryCMapReader { async process(data, cMap, extend) { const stream = new BinaryCMapStream(data); const header = stream.readByte(); cMap.vertical = !!(header & 1); let useCMap = null; const start = new Uint8Array(MAX_NUM_SIZE); const end = new Uint8Array(MAX_NUM_SIZE); const char = new Uint8Array(MAX_NUM_SIZE); const charCode = new Uint8Array(MAX_NUM_SIZE); const tmp = new Uint8Array(MAX_NUM_SIZE); let code; let b; while ((b = stream.readByte()) >= 0) { const type = b >> 5; if (type === 7) { switch (b & 0x1f) { case 0: stream.readString(); break; case 1: useCMap = stream.readString(); break; } continue; } const sequence = !!(b & 0x10); const dataSize = b & 15; if (dataSize + 1 > MAX_NUM_SIZE) { throw new Error("BinaryCMapReader.process: Invalid dataSize."); } const ucs2DataSize = 1; const subitemsCount = stream.readNumber(); switch (type) { case 0: stream.readHex(start, dataSize); stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize)); for (let i = 1; i < subitemsCount; i++) { incHex(end, dataSize); stream.readHexNumber(start, dataSize); addHex(start, end, dataSize); stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize)); } break; case 1: stream.readHex(start, dataSize); stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); stream.readNumber(); for (let i = 1; i < subitemsCount; i++) { incHex(end, dataSize); stream.readHexNumber(start, dataSize); addHex(start, end, dataSize); stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); stream.readNumber(); } break; case 2: stream.readHex(char, dataSize); code = stream.readNumber(); cMap.mapOne(hexToInt(char, dataSize), code); for (let i = 1; i < subitemsCount; i++) { incHex(char, dataSize); if (!sequence) { stream.readHexNumber(tmp, dataSize); addHex(char, tmp, dataSize); } code = stream.readSigned() + (code + 1); cMap.mapOne(hexToInt(char, dataSize), code); } break; case 3: stream.readHex(start, dataSize); stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); code = stream.readNumber(); cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code); for (let i = 1; i < subitemsCount; i++) { incHex(end, dataSize); if (!sequence) { stream.readHexNumber(start, dataSize); addHex(start, end, dataSize); } else { start.set(end); } stream.readHexNumber(end, dataSize); addHex(end, start, dataSize); code = stream.readNumber(); cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code); } break; case 4: stream.readHex(char, ucs2DataSize); stream.readHex(charCode, dataSize); cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize)); for (let i = 1; i < subitemsCount; i++) { incHex(char, ucs2DataSize); if (!sequence) { stream.readHexNumber(tmp, ucs2DataSize); addHex(char, tmp, ucs2DataSize); } incHex(charCode, dataSize); stream.readHexSigned(tmp, dataSize); addHex(charCode, tmp, dataSize); cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize)); } break; case 5: stream.readHex(start, ucs2DataSize); stream.readHexNumber(end, ucs2DataSize); addHex(end, start, ucs2DataSize); stream.readHex(charCode, dataSize); cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize)); for (let i = 1; i < subitemsCount; i++) { incHex(end, ucs2DataSize); if (!sequence) { stream.readHexNumber(start, ucs2DataSize); addHex(start, end, ucs2DataSize); } else { start.set(end); } stream.readHexNumber(end, ucs2DataSize); addHex(end, start, ucs2DataSize); stream.readHex(charCode, dataSize); cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize)); } break; default: throw new Error(`BinaryCMapReader.process - unknown type: ${type}`); } } if (useCMap) { return extend(useCMap); } return cMap; } } ;// CONCATENATED MODULE: ./src/core/decode_stream.js const emptyBuffer = new Uint8Array(0); class DecodeStream extends BaseStream { constructor(maybeMinBufferLength) { super(); this._rawMinBufferLength = maybeMinBufferLength || 0; this.pos = 0; this.bufferLength = 0; this.eof = false; this.buffer = emptyBuffer; this.minBufferLength = 512; if (maybeMinBufferLength) { while (this.minBufferLength < maybeMinBufferLength) { this.minBufferLength *= 2; } } } get isEmpty() { while (!this.eof && this.bufferLength === 0) { this.readBlock(); } return this.bufferLength === 0; } ensureBuffer(requested) { const buffer = this.buffer; if (requested <= buffer.byteLength) { return buffer; } let size = this.minBufferLength; while (size < requested) { size *= 2; } const buffer2 = new Uint8Array(size); buffer2.set(buffer); return this.buffer = buffer2; } getByte() { const pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) { return -1; } this.readBlock(); } return this.buffer[this.pos++]; } getBytes(length) { const pos = this.pos; let end; if (length) { this.ensureBuffer(pos + length); end = pos + length; while (!this.eof && this.bufferLength < end) { this.readBlock(); } const bufEnd = this.bufferLength; if (end > bufEnd) { end = bufEnd; } } else { while (!this.eof) { this.readBlock(); } end = this.bufferLength; } this.pos = end; return this.buffer.subarray(pos, end); } reset() { this.pos = 0; } makeSubStream(start, length, dict = null) { if (length === undefined) { while (!this.eof) { this.readBlock(); } } else { const end = start + length; while (this.bufferLength <= end && !this.eof) { this.readBlock(); } } return new Stream(this.buffer, start, length, dict); } getBaseStreams() { return this.str ? this.str.getBaseStreams() : null; } } class StreamsSequenceStream extends DecodeStream { constructor(streams, onError = null) { let maybeLength = 0; for (const stream of streams) { maybeLength += stream instanceof DecodeStream ? stream._rawMinBufferLength : stream.length; } super(maybeLength); this.streams = streams; this._onError = onError; } readBlock() { const streams = this.streams; if (streams.length === 0) { this.eof = true; return; } const stream = streams.shift(); let chunk; try { chunk = stream.getBytes(); } catch (reason) { if (this._onError) { this._onError(reason, stream.dict?.objId); return; } throw reason; } const bufferLength = this.bufferLength; const newLength = bufferLength + chunk.length; const buffer = this.ensureBuffer(newLength); buffer.set(chunk, bufferLength); this.bufferLength = newLength; } getBaseStreams() { const baseStreamsBuf = []; for (const stream of this.streams) { const baseStreams = stream.getBaseStreams(); if (baseStreams) { baseStreamsBuf.push(...baseStreams); } } return baseStreamsBuf.length > 0 ? baseStreamsBuf : null; } } ;// CONCATENATED MODULE: ./src/core/ascii_85_stream.js class Ascii85Stream extends DecodeStream { constructor(str, maybeLength) { if (maybeLength) { maybeLength *= 0.8; } super(maybeLength); this.str = str; this.dict = str.dict; this.input = new Uint8Array(5); } readBlock() { const TILDA_CHAR = 0x7e; const Z_LOWER_CHAR = 0x7a; const EOF = -1; const str = this.str; let c = str.getByte(); while (isWhiteSpace(c)) { c = str.getByte(); } if (c === EOF || c === TILDA_CHAR) { this.eof = true; return; } const bufferLength = this.bufferLength; let buffer, i; if (c === Z_LOWER_CHAR) { buffer = this.ensureBuffer(bufferLength + 4); for (i = 0; i < 4; ++i) { buffer[bufferLength + i] = 0; } this.bufferLength += 4; } else { const input = this.input; input[0] = c; for (i = 1; i < 5; ++i) { c = str.getByte(); while (isWhiteSpace(c)) { c = str.getByte(); } input[i] = c; if (c === EOF || c === TILDA_CHAR) { break; } } buffer = this.ensureBuffer(bufferLength + i - 1); this.bufferLength += i - 1; if (i < 5) { for (; i < 5; ++i) { input[i] = 0x21 + 84; } this.eof = true; } let t = 0; for (i = 0; i < 5; ++i) { t = t * 85 + (input[i] - 0x21); } for (i = 3; i >= 0; --i) { buffer[bufferLength + i] = t & 0xff; t >>= 8; } } } } ;// CONCATENATED MODULE: ./src/core/ascii_hex_stream.js class AsciiHexStream extends DecodeStream { constructor(str, maybeLength) { if (maybeLength) { maybeLength *= 0.5; } super(maybeLength); this.str = str; this.dict = str.dict; this.firstDigit = -1; } readBlock() { const UPSTREAM_BLOCK_SIZE = 8000; const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE); if (!bytes.length) { this.eof = true; return; } const maxDecodeLength = bytes.length + 1 >> 1; const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength); let bufferLength = this.bufferLength; let firstDigit = this.firstDigit; for (const ch of bytes) { let digit; if (ch >= 0x30 && ch <= 0x39) { digit = ch & 0x0f; } else if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) { digit = (ch & 0x0f) + 9; } else if (ch === 0x3e) { this.eof = true; break; } else { continue; } if (firstDigit < 0) { firstDigit = digit; } else { buffer[bufferLength++] = firstDigit << 4 | digit; firstDigit = -1; } } if (firstDigit >= 0 && this.eof) { buffer[bufferLength++] = firstDigit << 4; firstDigit = -1; } this.firstDigit = firstDigit; this.bufferLength = bufferLength; } } ;// CONCATENATED MODULE: ./src/core/ccitt.js const ccittEOL = -2; const ccittEOF = -1; const twoDimPass = 0; const twoDimHoriz = 1; const twoDimVert0 = 2; const twoDimVertR1 = 3; const twoDimVertL1 = 4; const twoDimVertR2 = 5; const twoDimVertL2 = 6; const twoDimVertR3 = 7; const twoDimVertL3 = 8; const twoDimTable = [[-1, -1], [-1, -1], [7, twoDimVertL3], [7, twoDimVertR3], [6, twoDimVertL2], [6, twoDimVertL2], [6, twoDimVertR2], [6, twoDimVertR2], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0]]; const whiteTable1 = [[-1, -1], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [12, 1984], [12, 2048], [12, 2112], [12, 2176], [12, 2240], [12, 2304], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [12, 2368], [12, 2432], [12, 2496], [12, 2560]]; const whiteTable2 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [8, 29], [8, 29], [8, 30], [8, 30], [8, 45], [8, 45], [8, 46], [8, 46], [7, 22], [7, 22], [7, 22], [7, 22], [7, 23], [7, 23], [7, 23], [7, 23], [8, 47], [8, 47], [8, 48], [8, 48], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [7, 20], [7, 20], [7, 20], [7, 20], [8, 33], [8, 33], [8, 34], [8, 34], [8, 35], [8, 35], [8, 36], [8, 36], [8, 37], [8, 37], [8, 38], [8, 38], [7, 19], [7, 19], [7, 19], [7, 19], [8, 31], [8, 31], [8, 32], [8, 32], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [8, 53], [8, 53], [8, 54], [8, 54], [7, 26], [7, 26], [7, 26], [7, 26], [8, 39], [8, 39], [8, 40], [8, 40], [8, 41], [8, 41], [8, 42], [8, 42], [8, 43], [8, 43], [8, 44], [8, 44], [7, 21], [7, 21], [7, 21], [7, 21], [7, 28], [7, 28], [7, 28], [7, 28], [8, 61], [8, 61], [8, 62], [8, 62], [8, 63], [8, 63], [8, 0], [8, 0], [8, 320], [8, 320], [8, 384], [8, 384], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [7, 27], [7, 27], [7, 27], [7, 27], [8, 59], [8, 59], [8, 60], [8, 60], [9, 1472], [9, 1536], [9, 1600], [9, 1728], [7, 18], [7, 18], [7, 18], [7, 18], [7, 24], [7, 24], [7, 24], [7, 24], [8, 49], [8, 49], [8, 50], [8, 50], [8, 51], [8, 51], [8, 52], [8, 52], [7, 25], [7, 25], [7, 25], [7, 25], [8, 55], [8, 55], [8, 56], [8, 56], [8, 57], [8, 57], [8, 58], [8, 58], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [8, 448], [8, 448], [8, 512], [8, 512], [9, 704], [9, 768], [8, 640], [8, 640], [8, 576], [8, 576], [9, 832], [9, 896], [9, 960], [9, 1024], [9, 1088], [9, 1152], [9, 1216], [9, 1280], [9, 1344], [9, 1408], [7, 256], [7, 256], [7, 256], [7, 256], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7]]; const blackTable1 = [[-1, -1], [-1, -1], [12, ccittEOL], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [11, 1792], [11, 1792], [12, 1984], [12, 1984], [12, 2048], [12, 2048], [12, 2112], [12, 2112], [12, 2176], [12, 2176], [12, 2240], [12, 2240], [12, 2304], [12, 2304], [11, 1856], [11, 1856], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [11, 1920], [11, 1920], [12, 2368], [12, 2368], [12, 2432], [12, 2432], [12, 2496], [12, 2496], [12, 2560], [12, 2560], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [12, 52], [12, 52], [13, 640], [13, 704], [13, 768], [13, 832], [12, 55], [12, 55], [12, 56], [12, 56], [13, 1280], [13, 1344], [13, 1408], [13, 1472], [12, 59], [12, 59], [12, 60], [12, 60], [13, 1536], [13, 1600], [11, 24], [11, 24], [11, 24], [11, 24], [11, 25], [11, 25], [11, 25], [11, 25], [13, 1664], [13, 1728], [12, 320], [12, 320], [12, 384], [12, 384], [12, 448], [12, 448], [13, 512], [13, 576], [12, 53], [12, 53], [12, 54], [12, 54], [13, 896], [13, 960], [13, 1024], [13, 1088], [13, 1152], [13, 1216], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64]]; const blackTable2 = [[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [11, 23], [11, 23], [12, 50], [12, 51], [12, 44], [12, 45], [12, 46], [12, 47], [12, 57], [12, 58], [12, 61], [12, 256], [10, 16], [10, 16], [10, 16], [10, 16], [10, 17], [10, 17], [10, 17], [10, 17], [12, 48], [12, 49], [12, 62], [12, 63], [12, 30], [12, 31], [12, 32], [12, 33], [12, 40], [12, 41], [11, 22], [11, 22], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [12, 128], [12, 192], [12, 26], [12, 27], [12, 28], [12, 29], [11, 19], [11, 19], [11, 20], [11, 20], [12, 34], [12, 35], [12, 36], [12, 37], [12, 38], [12, 39], [11, 21], [11, 21], [12, 42], [12, 43], [10, 0], [10, 0], [10, 0], [10, 0], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12]]; const blackTable3 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [6, 9], [6, 8], [5, 7], [5, 7], [4, 6], [4, 6], [4, 6], [4, 6], [4, 5], [4, 5], [4, 5], [4, 5], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]]; class CCITTFaxDecoder { constructor(source, options = {}) { if (!source || typeof source.next !== "function") { throw new Error('CCITTFaxDecoder - invalid "source" parameter.'); } this.source = source; this.eof = false; this.encoding = options.K || 0; this.eoline = options.EndOfLine || false; this.byteAlign = options.EncodedByteAlign || false; this.columns = options.Columns || 1728; this.rows = options.Rows || 0; this.eoblock = options.EndOfBlock ?? true; this.black = options.BlackIs1 || false; this.codingLine = new Uint32Array(this.columns + 1); this.refLine = new Uint32Array(this.columns + 2); this.codingLine[0] = this.columns; this.codingPos = 0; this.row = 0; this.nextLine2D = this.encoding < 0; this.inputBits = 0; this.inputBuf = 0; this.outputBits = 0; this.rowsDone = false; let code1; while ((code1 = this._lookBits(12)) === 0) { this._eatBits(1); } if (code1 === 1) { this._eatBits(12); } if (this.encoding > 0) { this.nextLine2D = !this._lookBits(1); this._eatBits(1); } } readNextChar() { if (this.eof) { return -1; } const refLine = this.refLine; const codingLine = this.codingLine; const columns = this.columns; let refPos, blackPixels, bits, i; if (this.outputBits === 0) { if (this.rowsDone) { this.eof = true; } if (this.eof) { return -1; } this.err = false; let code1, code2, code3; if (this.nextLine2D) { for (i = 0; codingLine[i] < columns; ++i) { refLine[i] = codingLine[i]; } refLine[i++] = columns; refLine[i] = columns; codingLine[0] = 0; this.codingPos = 0; refPos = 0; blackPixels = 0; while (codingLine[this.codingPos] < columns) { code1 = this._getTwoDimCode(); switch (code1) { case twoDimPass: this._addPixels(refLine[refPos + 1], blackPixels); if (refLine[refPos + 1] < columns) { refPos += 2; } break; case twoDimHoriz: code1 = code2 = 0; if (blackPixels) { do { code1 += code3 = this._getBlackCode(); } while (code3 >= 64); do { code2 += code3 = this._getWhiteCode(); } while (code3 >= 64); } else { do { code1 += code3 = this._getWhiteCode(); } while (code3 >= 64); do { code2 += code3 = this._getBlackCode(); } while (code3 >= 64); } this._addPixels(codingLine[this.codingPos] + code1, blackPixels); if (codingLine[this.codingPos] < columns) { this._addPixels(codingLine[this.codingPos] + code2, blackPixels ^ 1); } while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } break; case twoDimVertR3: this._addPixels(refLine[refPos] + 3, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVertR2: this._addPixels(refLine[refPos] + 2, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVertR1: this._addPixels(refLine[refPos] + 1, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVert0: this._addPixels(refLine[refPos], blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { ++refPos; while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVertL3: this._addPixelsNeg(refLine[refPos] - 3, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { if (refPos > 0) { --refPos; } else { ++refPos; } while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVertL2: this._addPixelsNeg(refLine[refPos] - 2, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { if (refPos > 0) { --refPos; } else { ++refPos; } while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case twoDimVertL1: this._addPixelsNeg(refLine[refPos] - 1, blackPixels); blackPixels ^= 1; if (codingLine[this.codingPos] < columns) { if (refPos > 0) { --refPos; } else { ++refPos; } while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) { refPos += 2; } } break; case ccittEOF: this._addPixels(columns, 0); this.eof = true; break; default: info("bad 2d code"); this._addPixels(columns, 0); this.err = true; } } } else { codingLine[0] = 0; this.codingPos = 0; blackPixels = 0; while (codingLine[this.codingPos] < columns) { code1 = 0; if (blackPixels) { do { code1 += code3 = this._getBlackCode(); } while (code3 >= 64); } else { do { code1 += code3 = this._getWhiteCode(); } while (code3 >= 64); } this._addPixels(codingLine[this.codingPos] + code1, blackPixels); blackPixels ^= 1; } } let gotEOL = false; if (this.byteAlign) { this.inputBits &= ~7; } if (!this.eoblock && this.row === this.rows - 1) { this.rowsDone = true; } else { code1 = this._lookBits(12); if (this.eoline) { while (code1 !== ccittEOF && code1 !== 1) { this._eatBits(1); code1 = this._lookBits(12); } } else { while (code1 === 0) { this._eatBits(1); code1 = this._lookBits(12); } } if (code1 === 1) { this._eatBits(12); gotEOL = true; } else if (code1 === ccittEOF) { this.eof = true; } } if (!this.eof && this.encoding > 0 && !this.rowsDone) { this.nextLine2D = !this._lookBits(1); this._eatBits(1); } if (this.eoblock && gotEOL && this.byteAlign) { code1 = this._lookBits(12); if (code1 === 1) { this._eatBits(12); if (this.encoding > 0) { this._lookBits(1); this._eatBits(1); } if (this.encoding >= 0) { for (i = 0; i < 4; ++i) { code1 = this._lookBits(12); if (code1 !== 1) { info("bad rtc code: " + code1); } this._eatBits(12); if (this.encoding > 0) { this._lookBits(1); this._eatBits(1); } } } this.eof = true; } } else if (this.err && this.eoline) { while (true) { code1 = this._lookBits(13); if (code1 === ccittEOF) { this.eof = true; return -1; } if (code1 >> 1 === 1) { break; } this._eatBits(1); } this._eatBits(12); if (this.encoding > 0) { this._eatBits(1); this.nextLine2D = !(code1 & 1); } } this.outputBits = codingLine[0] > 0 ? codingLine[this.codingPos = 0] : codingLine[this.codingPos = 1]; this.row++; } let c; if (this.outputBits >= 8) { c = this.codingPos & 1 ? 0 : 0xff; this.outputBits -= 8; if (this.outputBits === 0 && codingLine[this.codingPos] < columns) { this.codingPos++; this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; } } else { bits = 8; c = 0; do { if (typeof this.outputBits !== "number") { throw new FormatError('Invalid /CCITTFaxDecode data, "outputBits" must be a number.'); } if (this.outputBits > bits) { c <<= bits; if (!(this.codingPos & 1)) { c |= 0xff >> 8 - bits; } this.outputBits -= bits; bits = 0; } else { c <<= this.outputBits; if (!(this.codingPos & 1)) { c |= 0xff >> 8 - this.outputBits; } bits -= this.outputBits; this.outputBits = 0; if (codingLine[this.codingPos] < columns) { this.codingPos++; this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; } else if (bits > 0) { c <<= bits; bits = 0; } } } while (bits); } if (this.black) { c ^= 0xff; } return c; } _addPixels(a1, blackPixels) { const codingLine = this.codingLine; let codingPos = this.codingPos; if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { info("row is wrong length"); this.err = true; a1 = this.columns; } if (codingPos & 1 ^ blackPixels) { ++codingPos; } codingLine[codingPos] = a1; } this.codingPos = codingPos; } _addPixelsNeg(a1, blackPixels) { const codingLine = this.codingLine; let codingPos = this.codingPos; if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { info("row is wrong length"); this.err = true; a1 = this.columns; } if (codingPos & 1 ^ blackPixels) { ++codingPos; } codingLine[codingPos] = a1; } else if (a1 < codingLine[codingPos]) { if (a1 < 0) { info("invalid code"); this.err = true; a1 = 0; } while (codingPos > 0 && a1 < codingLine[codingPos - 1]) { --codingPos; } codingLine[codingPos] = a1; } this.codingPos = codingPos; } _findTableCode(start, end, table, limit) { const limitValue = limit || 0; for (let i = start; i <= end; ++i) { let code = this._lookBits(i); if (code === ccittEOF) { return [true, 1, false]; } if (i < end) { code <<= end - i; } if (!limitValue || code >= limitValue) { const p = table[code - limitValue]; if (p[0] === i) { this._eatBits(i); return [true, p[1], true]; } } } return [false, 0, false]; } _getTwoDimCode() { let code = 0; let p; if (this.eoblock) { code = this._lookBits(7); p = twoDimTable[code]; if (p?.[0] > 0) { this._eatBits(p[0]); return p[1]; } } else { const result = this._findTableCode(1, 7, twoDimTable); if (result[0] && result[2]) { return result[1]; } } info("Bad two dim code"); return ccittEOF; } _getWhiteCode() { let code = 0; let p; if (this.eoblock) { code = this._lookBits(12); if (code === ccittEOF) { return 1; } p = code >> 5 === 0 ? whiteTable1[code] : whiteTable2[code >> 3]; if (p[0] > 0) { this._eatBits(p[0]); return p[1]; } } else { let result = this._findTableCode(1, 9, whiteTable2); if (result[0]) { return result[1]; } result = this._findTableCode(11, 12, whiteTable1); if (result[0]) { return result[1]; } } info("bad white code"); this._eatBits(1); return 1; } _getBlackCode() { let code, p; if (this.eoblock) { code = this._lookBits(13); if (code === ccittEOF) { return 1; } if (code >> 7 === 0) { p = blackTable1[code]; } else if (code >> 9 === 0 && code >> 7 !== 0) { p = blackTable2[(code >> 1) - 64]; } else { p = blackTable3[code >> 7]; } if (p[0] > 0) { this._eatBits(p[0]); return p[1]; } } else { let result = this._findTableCode(2, 6, blackTable3); if (result[0]) { return result[1]; } result = this._findTableCode(7, 12, blackTable2, 64); if (result[0]) { return result[1]; } result = this._findTableCode(10, 13, blackTable1); if (result[0]) { return result[1]; } } info("bad black code"); this._eatBits(1); return 1; } _lookBits(n) { let c; while (this.inputBits < n) { if ((c = this.source.next()) === -1) { if (this.inputBits === 0) { return ccittEOF; } return this.inputBuf << n - this.inputBits & 0xffff >> 16 - n; } this.inputBuf = this.inputBuf << 8 | c; this.inputBits += 8; } return this.inputBuf >> this.inputBits - n & 0xffff >> 16 - n; } _eatBits(n) { if ((this.inputBits -= n) < 0) { this.inputBits = 0; } } } ;// CONCATENATED MODULE: ./src/core/ccitt_stream.js class CCITTFaxStream extends DecodeStream { constructor(str, maybeLength, params) { super(maybeLength); this.str = str; this.dict = str.dict; if (!(params instanceof Dict)) { params = Dict.empty; } const source = { next() { return str.getByte(); } }; this.ccittFaxDecoder = new CCITTFaxDecoder(source, { K: params.get("K"), EndOfLine: params.get("EndOfLine"), EncodedByteAlign: params.get("EncodedByteAlign"), Columns: params.get("Columns"), Rows: params.get("Rows"), EndOfBlock: params.get("EndOfBlock"), BlackIs1: params.get("BlackIs1") }); } readBlock() { while (!this.eof) { const c = this.ccittFaxDecoder.readNextChar(); if (c === -1) { this.eof = true; return; } this.ensureBuffer(this.bufferLength + 1); this.buffer[this.bufferLength++] = c; } } } ;// CONCATENATED MODULE: ./src/core/flate_stream.js const codeLenCodeMap = new Int32Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); const lengthDecode = new Int32Array([0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102]); const distDecode = new Int32Array([0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001]); const fixedLitCodeTab = [new Int32Array([0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff]), 9]; const fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000]), 5]; class FlateStream extends DecodeStream { constructor(str, maybeLength) { super(maybeLength); this.str = str; this.dict = str.dict; const cmf = str.getByte(); const flg = str.getByte(); if (cmf === -1 || flg === -1) { throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`); } if ((cmf & 0x0f) !== 0x08) { throw new FormatError(`Unknown compression method in flate stream: ${cmf}, ${flg}`); } if (((cmf << 8) + flg) % 31 !== 0) { throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`); } if (flg & 0x20) { throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`); } this.codeSize = 0; this.codeBuf = 0; } getBits(bits) { const str = this.str; let codeSize = this.codeSize; let codeBuf = this.codeBuf; let b; while (codeSize < bits) { if ((b = str.getByte()) === -1) { throw new FormatError("Bad encoding in flate stream"); } codeBuf |= b << codeSize; codeSize += 8; } b = codeBuf & (1 << bits) - 1; this.codeBuf = codeBuf >> bits; this.codeSize = codeSize -= bits; return b; } getCode(table) { const str = this.str; const codes = table[0]; const maxLen = table[1]; let codeSize = this.codeSize; let codeBuf = this.codeBuf; let b; while (codeSize < maxLen) { if ((b = str.getByte()) === -1) { break; } codeBuf |= b << codeSize; codeSize += 8; } const code = codes[codeBuf & (1 << maxLen) - 1]; const codeLen = code >> 16; const codeVal = code & 0xffff; if (codeLen < 1 || codeSize < codeLen) { throw new FormatError("Bad encoding in flate stream"); } this.codeBuf = codeBuf >> codeLen; this.codeSize = codeSize - codeLen; return codeVal; } generateHuffmanTable(lengths) { const n = lengths.length; let maxLen = 0; let i; for (i = 0; i < n; ++i) { if (lengths[i] > maxLen) { maxLen = lengths[i]; } } const size = 1 << maxLen; const codes = new Int32Array(size); for (let len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) { for (let val = 0; val < n; ++val) { if (lengths[val] === len) { let code2 = 0; let t = code; for (i = 0; i < len; ++i) { code2 = code2 << 1 | t & 1; t >>= 1; } for (i = code2; i < size; i += skip) { codes[i] = len << 16 | val; } ++code; } } } return [codes, maxLen]; } readBlock() { let buffer, len; const str = this.str; let hdr = this.getBits(3); if (hdr & 1) { this.eof = true; } hdr >>= 1; if (hdr === 0) { let b; if ((b = str.getByte()) === -1) { throw new FormatError("Bad block header in flate stream"); } let blockLen = b; if ((b = str.getByte()) === -1) { throw new FormatError("Bad block header in flate stream"); } blockLen |= b << 8; if ((b = str.getByte()) === -1) { throw new FormatError("Bad block header in flate stream"); } let check = b; if ((b = str.getByte()) === -1) { throw new FormatError("Bad block header in flate stream"); } check |= b << 8; if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) { throw new FormatError("Bad uncompressed block length in flate stream"); } this.codeBuf = 0; this.codeSize = 0; const bufferLength = this.bufferLength, end = bufferLength + blockLen; buffer = this.ensureBuffer(end); this.bufferLength = end; if (blockLen === 0) { if (str.peekByte() === -1) { this.eof = true; } } else { const block = str.getBytes(blockLen); buffer.set(block, bufferLength); if (block.length < blockLen) { this.eof = true; } } return; } let litCodeTable; let distCodeTable; if (hdr === 1) { litCodeTable = fixedLitCodeTab; distCodeTable = fixedDistCodeTab; } else if (hdr === 2) { const numLitCodes = this.getBits(5) + 257; const numDistCodes = this.getBits(5) + 1; const numCodeLenCodes = this.getBits(4) + 4; const codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length); let i; for (i = 0; i < numCodeLenCodes; ++i) { codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3); } const codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); len = 0; i = 0; const codes = numLitCodes + numDistCodes; const codeLengths = new Uint8Array(codes); let bitsLength, bitsOffset, what; while (i < codes) { const code = this.getCode(codeLenCodeTab); if (code === 16) { bitsLength = 2; bitsOffset = 3; what = len; } else if (code === 17) { bitsLength = 3; bitsOffset = 3; what = len = 0; } else if (code === 18) { bitsLength = 7; bitsOffset = 11; what = len = 0; } else { codeLengths[i++] = len = code; continue; } let repeatLength = this.getBits(bitsLength) + bitsOffset; while (repeatLength-- > 0) { codeLengths[i++] = what; } } litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes)); distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes)); } else { throw new FormatError("Unknown block type in flate stream"); } buffer = this.buffer; let limit = buffer ? buffer.length : 0; let pos = this.bufferLength; while (true) { let code1 = this.getCode(litCodeTable); if (code1 < 256) { if (pos + 1 >= limit) { buffer = this.ensureBuffer(pos + 1); limit = buffer.length; } buffer[pos++] = code1; continue; } if (code1 === 256) { this.bufferLength = pos; return; } code1 -= 257; code1 = lengthDecode[code1]; let code2 = code1 >> 16; if (code2 > 0) { code2 = this.getBits(code2); } len = (code1 & 0xffff) + code2; code1 = this.getCode(distCodeTable); code1 = distDecode[code1]; code2 = code1 >> 16; if (code2 > 0) { code2 = this.getBits(code2); } const dist = (code1 & 0xffff) + code2; if (pos + len >= limit) { buffer = this.ensureBuffer(pos + len); limit = buffer.length; } for (let k = 0; k < len; ++k, ++pos) { buffer[pos] = buffer[pos - dist]; } } } } ;// CONCATENATED MODULE: ./src/core/arithmetic_decoder.js const QeTable = [{ qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1 }, { qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0 }, { qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0 }, { qe: 0x0ac1, nmps: 4, nlps: 12, switchFlag: 0 }, { qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0 }, { qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0 }, { qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1 }, { qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0 }, { qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0 }, { qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0 }, { qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0 }, { qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0 }, { qe: 0x1c01, nmps: 13, nlps: 20, switchFlag: 0 }, { qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0 }, { qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1 }, { qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0 }, { qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0 }, { qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0 }, { qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0 }, { qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0 }, { qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0 }, { qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0 }, { qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0 }, { qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0 }, { qe: 0x1c01, nmps: 25, nlps: 22, switchFlag: 0 }, { qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0 }, { qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0 }, { qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0 }, { qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0 }, { qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0 }, { qe: 0x0ac1, nmps: 31, nlps: 28, switchFlag: 0 }, { qe: 0x09c1, nmps: 32, nlps: 29, switchFlag: 0 }, { qe: 0x08a1, nmps: 33, nlps: 30, switchFlag: 0 }, { qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0 }, { qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0 }, { qe: 0x02a1, nmps: 36, nlps: 33, switchFlag: 0 }, { qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0 }, { qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0 }, { qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0 }, { qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0 }, { qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0 }, { qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0 }, { qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0 }, { qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0 }, { qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0 }, { qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0 }, { qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0 }]; class ArithmeticDecoder { constructor(data, start, end) { this.data = data; this.bp = start; this.dataEnd = end; this.chigh = data[start]; this.clow = 0; this.byteIn(); this.chigh = this.chigh << 7 & 0xffff | this.clow >> 9 & 0x7f; this.clow = this.clow << 7 & 0xffff; this.ct -= 7; this.a = 0x8000; } byteIn() { const data = this.data; let bp = this.bp; if (data[bp] === 0xff) { if (data[bp + 1] > 0x8f) { this.clow += 0xff00; this.ct = 8; } else { bp++; this.clow += data[bp] << 9; this.ct = 7; this.bp = bp; } } else { bp++; this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xff00; this.ct = 8; this.bp = bp; } if (this.clow > 0xffff) { this.chigh += this.clow >> 16; this.clow &= 0xffff; } } readBit(contexts, pos) { let cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1; const qeTableIcx = QeTable[cx_index]; const qeIcx = qeTableIcx.qe; let d; let a = this.a - qeIcx; if (this.chigh < qeIcx) { if (a < qeIcx) { a = qeIcx; d = cx_mps; cx_index = qeTableIcx.nmps; } else { a = qeIcx; d = 1 ^ cx_mps; if (qeTableIcx.switchFlag === 1) { cx_mps = d; } cx_index = qeTableIcx.nlps; } } else { this.chigh -= qeIcx; if ((a & 0x8000) !== 0) { this.a = a; return cx_mps; } if (a < qeIcx) { d = 1 ^ cx_mps; if (qeTableIcx.switchFlag === 1) { cx_mps = d; } cx_index = qeTableIcx.nlps; } else { d = cx_mps; cx_index = qeTableIcx.nmps; } } do { if (this.ct === 0) { this.byteIn(); } a <<= 1; this.chigh = this.chigh << 1 & 0xffff | this.clow >> 15 & 1; this.clow = this.clow << 1 & 0xffff; this.ct--; } while ((a & 0x8000) === 0); this.a = a; contexts[pos] = cx_index << 1 | cx_mps; return d; } } ;// CONCATENATED MODULE: ./src/core/jbig2.js class Jbig2Error extends BaseException { constructor(msg) { super(`JBIG2 error: ${msg}`, "Jbig2Error"); } } class ContextCache { getContexts(id) { if (id in this) { return this[id]; } return this[id] = new Int8Array(1 << 16); } } class DecodingContext { constructor(data, start, end) { this.data = data; this.start = start; this.end = end; } get decoder() { const decoder = new ArithmeticDecoder(this.data, this.start, this.end); return shadow(this, "decoder", decoder); } get contextCache() { const cache = new ContextCache(); return shadow(this, "contextCache", cache); } } const MAX_INT_32 = 2 ** 31 - 1; const MIN_INT_32 = -(2 ** 31); function decodeInteger(contextCache, procedure, decoder) { const contexts = contextCache.getContexts(procedure); let prev = 1; function readBits(length) { let v = 0; for (let i = 0; i < length; i++) { const bit = decoder.readBit(contexts, prev); prev = prev < 256 ? prev << 1 | bit : (prev << 1 | bit) & 511 | 256; v = v << 1 | bit; } return v >>> 0; } const sign = readBits(1); const value = readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(32) + 4436 : readBits(12) + 340 : readBits(8) + 84 : readBits(6) + 20 : readBits(4) + 4 : readBits(2); let signedValue; if (sign === 0) { signedValue = value; } else if (value > 0) { signedValue = -value; } if (signedValue >= MIN_INT_32 && signedValue <= MAX_INT_32) { return signedValue; } return null; } function decodeIAID(contextCache, decoder, codeLength) { const contexts = contextCache.getContexts("IAID"); let prev = 1; for (let i = 0; i < codeLength; i++) { const bit = decoder.readBit(contexts, prev); prev = prev << 1 | bit; } if (codeLength < 31) { return prev & (1 << codeLength) - 1; } return prev & 0x7fffffff; } const SegmentTypes = ["SymbolDictionary", null, null, null, "IntermediateTextRegion", null, "ImmediateTextRegion", "ImmediateLosslessTextRegion", null, null, null, null, null, null, null, null, "PatternDictionary", null, null, null, "IntermediateHalftoneRegion", null, "ImmediateHalftoneRegion", "ImmediateLosslessHalftoneRegion", null, null, null, null, null, null, null, null, null, null, null, null, "IntermediateGenericRegion", null, "ImmediateGenericRegion", "ImmediateLosslessGenericRegion", "IntermediateGenericRefinementRegion", null, "ImmediateGenericRefinementRegion", "ImmediateLosslessGenericRefinementRegion", null, null, null, null, "PageInformation", "EndOfPage", "EndOfStripe", "EndOfFile", "Profiles", "Tables", null, null, null, null, null, null, null, null, "Extension"]; const CodingTemplates = [[{ x: -1, y: -2 }, { x: 0, y: -2 }, { x: 1, y: -2 }, { x: -2, y: -1 }, { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, { x: 2, y: -1 }, { x: -4, y: 0 }, { x: -3, y: 0 }, { x: -2, y: 0 }, { x: -1, y: 0 }], [{ x: -1, y: -2 }, { x: 0, y: -2 }, { x: 1, y: -2 }, { x: 2, y: -2 }, { x: -2, y: -1 }, { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, { x: 2, y: -1 }, { x: -3, y: 0 }, { x: -2, y: 0 }, { x: -1, y: 0 }], [{ x: -1, y: -2 }, { x: 0, y: -2 }, { x: 1, y: -2 }, { x: -2, y: -1 }, { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, { x: -2, y: 0 }, { x: -1, y: 0 }], [{ x: -3, y: -1 }, { x: -2, y: -1 }, { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, { x: -4, y: 0 }, { x: -3, y: 0 }, { x: -2, y: 0 }, { x: -1, y: 0 }]]; const RefinementTemplates = [{ coding: [{ x: 0, y: -1 }, { x: 1, y: -1 }, { x: -1, y: 0 }], reference: [{ x: 0, y: -1 }, { x: 1, y: -1 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: -1, y: 1 }, { x: 0, y: 1 }, { x: 1, y: 1 }] }, { coding: [{ x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, { x: -1, y: 0 }], reference: [{ x: 0, y: -1 }, { x: -1, y: 0 }, { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }] }]; const ReusedContexts = [0x9b25, 0x0795, 0x00e5, 0x0195]; const RefinementReusedContexts = [0x0020, 0x0008]; function decodeBitmapTemplate0(width, height, decodingContext) { const decoder = decodingContext.decoder; const contexts = decodingContext.contextCache.getContexts("GB"); const bitmap = []; let contextLabel, i, j, pixel, row, row1, row2; const OLD_PIXEL_MASK = 0x7bf7; for (i = 0; i < height; i++) { row = bitmap[i] = new Uint8Array(width); row1 = i < 1 ? row : bitmap[i - 1]; row2 = i < 2 ? row : bitmap[i - 2]; contextLabel = row2[0] << 13 | row2[1] << 12 | row2[2] << 11 | row1[0] << 7 | row1[1] << 6 | row1[2] << 5 | row1[3] << 4; for (j = 0; j < width; j++) { row[j] = pixel = decoder.readBit(contexts, contextLabel); contextLabel = (contextLabel & OLD_PIXEL_MASK) << 1 | (j + 3 < width ? row2[j + 3] << 11 : 0) | (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel; } } return bitmap; } function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) { if (mmr) { const input = new Reader(decodingContext.data, decodingContext.start, decodingContext.end); return decodeMMRBitmap(input, width, height, false); } if (templateIndex === 0 && !skip && !prediction && at.length === 4 && at[0].x === 3 && at[0].y === -1 && at[1].x === -3 && at[1].y === -1 && at[2].x === 2 && at[2].y === -2 && at[3].x === -2 && at[3].y === -2) { return decodeBitmapTemplate0(width, height, decodingContext); } const useskip = !!skip; const template = CodingTemplates[templateIndex].concat(at); template.sort(function (a, b) { return a.y - b.y || a.x - b.x; }); const templateLength = template.length; const templateX = new Int8Array(templateLength); const templateY = new Int8Array(templateLength); const changingTemplateEntries = []; let reuseMask = 0, minX = 0, maxX = 0, minY = 0; let c, k; for (k = 0; k < templateLength; k++) { templateX[k] = template[k].x; templateY[k] = template[k].y; minX = Math.min(minX, template[k].x); maxX = Math.max(maxX, template[k].x); minY = Math.min(minY, template[k].y); if (k < templateLength - 1 && template[k].y === template[k + 1].y && template[k].x === template[k + 1].x - 1) { reuseMask |= 1 << templateLength - 1 - k; } else { changingTemplateEntries.push(k); } } const changingEntriesLength = changingTemplateEntries.length; const changingTemplateX = new Int8Array(changingEntriesLength); const changingTemplateY = new Int8Array(changingEntriesLength); const changingTemplateBit = new Uint16Array(changingEntriesLength); for (c = 0; c < changingEntriesLength; c++) { k = changingTemplateEntries[c]; changingTemplateX[c] = template[k].x; changingTemplateY[c] = template[k].y; changingTemplateBit[c] = 1 << templateLength - 1 - k; } const sbb_left = -minX; const sbb_top = -minY; const sbb_right = width - maxX; const pseudoPixelContext = ReusedContexts[templateIndex]; let row = new Uint8Array(width); const bitmap = []; const decoder = decodingContext.decoder; const contexts = decodingContext.contextCache.getContexts("GB"); let ltp = 0, j, i0, j0, contextLabel = 0, bit, shift; for (let i = 0; i < height; i++) { if (prediction) { const sltp = decoder.readBit(contexts, pseudoPixelContext); ltp ^= sltp; if (ltp) { bitmap.push(row); continue; } } row = new Uint8Array(row); bitmap.push(row); for (j = 0; j < width; j++) { if (useskip && skip[i][j]) { row[j] = 0; continue; } if (j >= sbb_left && j < sbb_right && i >= sbb_top) { contextLabel = contextLabel << 1 & reuseMask; for (k = 0; k < changingEntriesLength; k++) { i0 = i + changingTemplateY[k]; j0 = j + changingTemplateX[k]; bit = bitmap[i0][j0]; if (bit) { bit = changingTemplateBit[k]; contextLabel |= bit; } } } else { contextLabel = 0; shift = templateLength - 1; for (k = 0; k < templateLength; k++, shift--) { j0 = j + templateX[k]; if (j0 >= 0 && j0 < width) { i0 = i + templateY[k]; if (i0 >= 0) { bit = bitmap[i0][j0]; if (bit) { contextLabel |= bit << shift; } } } } } const pixel = decoder.readBit(contexts, contextLabel); row[j] = pixel; } } return bitmap; } function decodeRefinement(width, height, templateIndex, referenceBitmap, offsetX, offsetY, prediction, at, decodingContext) { let codingTemplate = RefinementTemplates[templateIndex].coding; if (templateIndex === 0) { codingTemplate = codingTemplate.concat([at[0]]); } const codingTemplateLength = codingTemplate.length; const codingTemplateX = new Int32Array(codingTemplateLength); const codingTemplateY = new Int32Array(codingTemplateLength); let k; for (k = 0; k < codingTemplateLength; k++) { codingTemplateX[k] = codingTemplate[k].x; codingTemplateY[k] = codingTemplate[k].y; } let referenceTemplate = RefinementTemplates[templateIndex].reference; if (templateIndex === 0) { referenceTemplate = referenceTemplate.concat([at[1]]); } const referenceTemplateLength = referenceTemplate.length; const referenceTemplateX = new Int32Array(referenceTemplateLength); const referenceTemplateY = new Int32Array(referenceTemplateLength); for (k = 0; k < referenceTemplateLength; k++) { referenceTemplateX[k] = referenceTemplate[k].x; referenceTemplateY[k] = referenceTemplate[k].y; } const referenceWidth = referenceBitmap[0].length; const referenceHeight = referenceBitmap.length; const pseudoPixelContext = RefinementReusedContexts[templateIndex]; const bitmap = []; const decoder = decodingContext.decoder; const contexts = decodingContext.contextCache.getContexts("GR"); let ltp = 0; for (let i = 0; i < height; i++) { if (prediction) { const sltp = decoder.readBit(contexts, pseudoPixelContext); ltp ^= sltp; if (ltp) { throw new Jbig2Error("prediction is not supported"); } } const row = new Uint8Array(width); bitmap.push(row); for (let j = 0; j < width; j++) { let i0, j0; let contextLabel = 0; for (k = 0; k < codingTemplateLength; k++) { i0 = i + codingTemplateY[k]; j0 = j + codingTemplateX[k]; if (i0 < 0 || j0 < 0 || j0 >= width) { contextLabel <<= 1; } else { contextLabel = contextLabel << 1 | bitmap[i0][j0]; } } for (k = 0; k < referenceTemplateLength; k++) { i0 = i + referenceTemplateY[k] - offsetY; j0 = j + referenceTemplateX[k] - offsetX; if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) { contextLabel <<= 1; } else { contextLabel = contextLabel << 1 | referenceBitmap[i0][j0]; } } const pixel = decoder.readBit(contexts, contextLabel); row[j] = pixel; } } return bitmap; } function decodeSymbolDictionary(huffman, refinement, symbols, numberOfNewSymbols, numberOfExportedSymbols, huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext, huffmanInput) { if (huffman && refinement) { throw new Jbig2Error("symbol refinement with Huffman is not supported"); } const newSymbols = []; let currentHeight = 0; let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); const decoder = decodingContext.decoder; const contextCache = decodingContext.contextCache; let tableB1, symbolWidths; if (huffman) { tableB1 = getStandardTable(1); symbolWidths = []; symbolCodeLength = Math.max(symbolCodeLength, 1); } while (newSymbols.length < numberOfNewSymbols) { const deltaHeight = huffman ? huffmanTables.tableDeltaHeight.decode(huffmanInput) : decodeInteger(contextCache, "IADH", decoder); currentHeight += deltaHeight; let currentWidth = 0, totalWidth = 0; const firstSymbol = huffman ? symbolWidths.length : 0; while (true) { const deltaWidth = huffman ? huffmanTables.tableDeltaWidth.decode(huffmanInput) : decodeInteger(contextCache, "IADW", decoder); if (deltaWidth === null) { break; } currentWidth += deltaWidth; totalWidth += currentWidth; let bitmap; if (refinement) { const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder); if (numberOfInstances > 1) { bitmap = decodeTextRegion(huffman, refinement, currentWidth, currentHeight, 0, numberOfInstances, 1, symbols.concat(newSymbols), symbolCodeLength, 0, 0, 1, 0, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, 0, huffmanInput); } else { const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); const rdx = decodeInteger(contextCache, "IARDX", decoder); const rdy = decodeInteger(contextCache, "IARDY", decoder); const symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length]; bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext); } newSymbols.push(bitmap); } else if (huffman) { symbolWidths.push(currentWidth); } else { bitmap = decodeBitmap(false, currentWidth, currentHeight, templateIndex, false, null, at, decodingContext); newSymbols.push(bitmap); } } if (huffman && !refinement) { const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); huffmanInput.byteAlign(); let collectiveBitmap; if (bitmapSize === 0) { collectiveBitmap = readUncompressedBitmap(huffmanInput, totalWidth, currentHeight); } else { const originalEnd = huffmanInput.end; const bitmapEnd = huffmanInput.position + bitmapSize; huffmanInput.end = bitmapEnd; collectiveBitmap = decodeMMRBitmap(huffmanInput, totalWidth, currentHeight, false); huffmanInput.end = originalEnd; huffmanInput.position = bitmapEnd; } const numberOfSymbolsDecoded = symbolWidths.length; if (firstSymbol === numberOfSymbolsDecoded - 1) { newSymbols.push(collectiveBitmap); } else { let i, y, xMin = 0, xMax, bitmapWidth, symbolBitmap; for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { bitmapWidth = symbolWidths[i]; xMax = xMin + bitmapWidth; symbolBitmap = []; for (y = 0; y < currentHeight; y++) { symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); } newSymbols.push(symbolBitmap); xMin = xMax; } } } } const exportedSymbols = [], flags = []; let currentFlag = false, i, ii; const totalSymbolsLength = symbols.length + numberOfNewSymbols; while (flags.length < totalSymbolsLength) { let runLength = huffman ? tableB1.decode(huffmanInput) : decodeInteger(contextCache, "IAEX", decoder); while (runLength--) { flags.push(currentFlag); } currentFlag = !currentFlag; } for (i = 0, ii = symbols.length; i < ii; i++) { if (flags[i]) { exportedSymbols.push(symbols[i]); } } for (let j = 0; j < numberOfNewSymbols; i++, j++) { if (flags[i]) { exportedSymbols.push(newSymbols[j]); } } return exportedSymbols; } function decodeTextRegion(huffman, refinement, width, height, defaultPixelValue, numberOfSymbolInstances, stripSize, inputSymbols, symbolCodeLength, transposed, dsOffset, referenceCorner, combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, logStripSize, huffmanInput) { if (huffman && refinement) { throw new Jbig2Error("refinement with Huffman is not supported"); } const bitmap = []; let i, row; for (i = 0; i < height; i++) { row = new Uint8Array(width); if (defaultPixelValue) { for (let j = 0; j < width; j++) { row[j] = defaultPixelValue; } } bitmap.push(row); } const decoder = decodingContext.decoder; const contextCache = decodingContext.contextCache; let stripT = huffman ? -huffmanTables.tableDeltaT.decode(huffmanInput) : -decodeInteger(contextCache, "IADT", decoder); let firstS = 0; i = 0; while (i < numberOfSymbolInstances) { const deltaT = huffman ? huffmanTables.tableDeltaT.decode(huffmanInput) : decodeInteger(contextCache, "IADT", decoder); stripT += deltaT; const deltaFirstS = huffman ? huffmanTables.tableFirstS.decode(huffmanInput) : decodeInteger(contextCache, "IAFS", decoder); firstS += deltaFirstS; let currentS = firstS; do { let currentT = 0; if (stripSize > 1) { currentT = huffman ? huffmanInput.readBits(logStripSize) : decodeInteger(contextCache, "IAIT", decoder); } const t = stripSize * stripT + currentT; const symbolId = huffman ? huffmanTables.symbolIDTable.decode(huffmanInput) : decodeIAID(contextCache, decoder, symbolCodeLength); const applyRefinement = refinement && (huffman ? huffmanInput.readBit() : decodeInteger(contextCache, "IARI", decoder)); let symbolBitmap = inputSymbols[symbolId]; let symbolWidth = symbolBitmap[0].length; let symbolHeight = symbolBitmap.length; if (applyRefinement) { const rdw = decodeInteger(contextCache, "IARDW", decoder); const rdh = decodeInteger(contextCache, "IARDH", decoder); const rdx = decodeInteger(contextCache, "IARDX", decoder); const rdy = decodeInteger(contextCache, "IARDY", decoder); symbolWidth += rdw; symbolHeight += rdh; symbolBitmap = decodeRefinement(symbolWidth, symbolHeight, refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx, (rdh >> 1) + rdy, false, refinementAt, decodingContext); } const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); let s2, t2, symbolRow; if (transposed) { for (s2 = 0; s2 < symbolHeight; s2++) { row = bitmap[offsetS + s2]; if (!row) { continue; } symbolRow = symbolBitmap[s2]; const maxWidth = Math.min(width - offsetT, symbolWidth); switch (combinationOperator) { case 0: for (t2 = 0; t2 < maxWidth; t2++) { row[offsetT + t2] |= symbolRow[t2]; } break; case 2: for (t2 = 0; t2 < maxWidth; t2++) { row[offsetT + t2] ^= symbolRow[t2]; } break; default: throw new Jbig2Error(`operator ${combinationOperator} is not supported`); } } currentS += symbolHeight - 1; } else { for (t2 = 0; t2 < symbolHeight; t2++) { row = bitmap[offsetT + t2]; if (!row) { continue; } symbolRow = symbolBitmap[t2]; switch (combinationOperator) { case 0: for (s2 = 0; s2 < symbolWidth; s2++) { row[offsetS + s2] |= symbolRow[s2]; } break; case 2: for (s2 = 0; s2 < symbolWidth; s2++) { row[offsetS + s2] ^= symbolRow[s2]; } break; default: throw new Jbig2Error(`operator ${combinationOperator} is not supported`); } } currentS += symbolWidth - 1; } i++; const deltaS = huffman ? huffmanTables.tableDeltaS.decode(huffmanInput) : decodeInteger(contextCache, "IADS", decoder); if (deltaS === null) { break; } currentS += deltaS + dsOffset; } while (true); } return bitmap; } function decodePatternDictionary(mmr, patternWidth, patternHeight, maxPatternIndex, template, decodingContext) { const at = []; if (!mmr) { at.push({ x: -patternWidth, y: 0 }); if (template === 0) { at.push({ x: -3, y: -1 }, { x: 2, y: -2 }, { x: -2, y: -2 }); } } const collectiveWidth = (maxPatternIndex + 1) * patternWidth; const collectiveBitmap = decodeBitmap(mmr, collectiveWidth, patternHeight, template, false, null, at, decodingContext); const patterns = []; for (let i = 0; i <= maxPatternIndex; i++) { const patternBitmap = []; const xMin = patternWidth * i; const xMax = xMin + patternWidth; for (let y = 0; y < patternHeight; y++) { patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); } patterns.push(patternBitmap); } return patterns; } function decodeHalftoneRegion(mmr, patterns, template, regionWidth, regionHeight, defaultPixelValue, enableSkip, combinationOperator, gridWidth, gridHeight, gridOffsetX, gridOffsetY, gridVectorX, gridVectorY, decodingContext) { const skip = null; if (enableSkip) { throw new Jbig2Error("skip is not supported"); } if (combinationOperator !== 0) { throw new Jbig2Error(`operator "${combinationOperator}" is not supported in halftone region`); } const regionBitmap = []; let i, j, row; for (i = 0; i < regionHeight; i++) { row = new Uint8Array(regionWidth); if (defaultPixelValue) { for (j = 0; j < regionWidth; j++) { row[j] = defaultPixelValue; } } regionBitmap.push(row); } const numberOfPatterns = patterns.length; const pattern0 = patterns[0]; const patternWidth = pattern0[0].length, patternHeight = pattern0.length; const bitsPerValue = log2(numberOfPatterns); const at = []; if (!mmr) { at.push({ x: template <= 1 ? 3 : 2, y: -1 }); if (template === 0) { at.push({ x: -3, y: -1 }, { x: 2, y: -2 }, { x: -2, y: -2 }); } } const grayScaleBitPlanes = []; let mmrInput, bitmap; if (mmr) { mmrInput = new Reader(decodingContext.data, decodingContext.start, decodingContext.end); } for (i = bitsPerValue - 1; i >= 0; i--) { if (mmr) { bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); } else { bitmap = decodeBitmap(false, gridWidth, gridHeight, template, false, skip, at, decodingContext); } grayScaleBitPlanes[i] = bitmap; } let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; for (mg = 0; mg < gridHeight; mg++) { for (ng = 0; ng < gridWidth; ng++) { bit = 0; patternIndex = 0; for (j = bitsPerValue - 1; j >= 0; j--) { bit ^= grayScaleBitPlanes[j][mg][ng]; patternIndex |= bit << j; } patternBitmap = patterns[patternIndex]; x = gridOffsetX + mg * gridVectorY + ng * gridVectorX >> 8; y = gridOffsetY + mg * gridVectorX - ng * gridVectorY >> 8; if (x >= 0 && x + patternWidth <= regionWidth && y >= 0 && y + patternHeight <= regionHeight) { for (i = 0; i < patternHeight; i++) { regionRow = regionBitmap[y + i]; patternRow = patternBitmap[i]; for (j = 0; j < patternWidth; j++) { regionRow[x + j] |= patternRow[j]; } } } else { let regionX, regionY; for (i = 0; i < patternHeight; i++) { regionY = y + i; if (regionY < 0 || regionY >= regionHeight) { continue; } regionRow = regionBitmap[regionY]; patternRow = patternBitmap[i]; for (j = 0; j < patternWidth; j++) { regionX = x + j; if (regionX >= 0 && regionX < regionWidth) { regionRow[regionX] |= patternRow[j]; } } } } } } return regionBitmap; } function readSegmentHeader(data, start) { const segmentHeader = {}; segmentHeader.number = readUint32(data, start); const flags = data[start + 4]; const segmentType = flags & 0x3f; if (!SegmentTypes[segmentType]) { throw new Jbig2Error("invalid segment type: " + segmentType); } segmentHeader.type = segmentType; segmentHeader.typeName = SegmentTypes[segmentType]; segmentHeader.deferredNonRetain = !!(flags & 0x80); const pageAssociationFieldSize = !!(flags & 0x40); const referredFlags = data[start + 5]; let referredToCount = referredFlags >> 5 & 7; const retainBits = [referredFlags & 31]; let position = start + 6; if (referredFlags === 7) { referredToCount = readUint32(data, position - 1) & 0x1fffffff; position += 3; let bytes = referredToCount + 7 >> 3; retainBits[0] = data[position++]; while (--bytes > 0) { retainBits.push(data[position++]); } } else if (referredFlags === 5 || referredFlags === 6) { throw new Jbig2Error("invalid referred-to flags"); } segmentHeader.retainBits = retainBits; let referredToSegmentNumberSize = 4; if (segmentHeader.number <= 256) { referredToSegmentNumberSize = 1; } else if (segmentHeader.number <= 65536) { referredToSegmentNumberSize = 2; } const referredTo = []; let i, ii; for (i = 0; i < referredToCount; i++) { let number; if (referredToSegmentNumberSize === 1) { number = data[position]; } else if (referredToSegmentNumberSize === 2) { number = readUint16(data, position); } else { number = readUint32(data, position); } referredTo.push(number); position += referredToSegmentNumberSize; } segmentHeader.referredTo = referredTo; if (!pageAssociationFieldSize) { segmentHeader.pageAssociation = data[position++]; } else { segmentHeader.pageAssociation = readUint32(data, position); position += 4; } segmentHeader.length = readUint32(data, position); position += 4; if (segmentHeader.length === 0xffffffff) { if (segmentType === 38) { const genericRegionInfo = readRegionSegmentInformation(data, position); const genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength]; const genericRegionMmr = !!(genericRegionSegmentFlags & 1); const searchPatternLength = 6; const searchPattern = new Uint8Array(searchPatternLength); if (!genericRegionMmr) { searchPattern[0] = 0xff; searchPattern[1] = 0xac; } searchPattern[2] = genericRegionInfo.height >>> 24 & 0xff; searchPattern[3] = genericRegionInfo.height >> 16 & 0xff; searchPattern[4] = genericRegionInfo.height >> 8 & 0xff; searchPattern[5] = genericRegionInfo.height & 0xff; for (i = position, ii = data.length; i < ii; i++) { let j = 0; while (j < searchPatternLength && searchPattern[j] === data[i + j]) { j++; } if (j === searchPatternLength) { segmentHeader.length = i + searchPatternLength; break; } } if (segmentHeader.length === 0xffffffff) { throw new Jbig2Error("segment end was not found"); } } else { throw new Jbig2Error("invalid unknown segment length"); } } segmentHeader.headerEnd = position; return segmentHeader; } function readSegments(header, data, start, end) { const segments = []; let position = start; while (position < end) { const segmentHeader = readSegmentHeader(data, position); position = segmentHeader.headerEnd; const segment = { header: segmentHeader, data }; if (!header.randomAccess) { segment.start = position; position += segmentHeader.length; segment.end = position; } segments.push(segment); if (segmentHeader.type === 51) { break; } } if (header.randomAccess) { for (let i = 0, ii = segments.length; i < ii; i++) { segments[i].start = position; position += segments[i].header.length; segments[i].end = position; } } return segments; } function readRegionSegmentInformation(data, start) { return { width: readUint32(data, start), height: readUint32(data, start + 4), x: readUint32(data, start + 8), y: readUint32(data, start + 12), combinationOperator: data[start + 16] & 7 }; } const RegionSegmentInformationFieldLength = 17; function processSegment(segment, visitor) { const header = segment.header; const data = segment.data, end = segment.end; let position = segment.start; let args, at, i, atLength; switch (header.type) { case 0: const dictionary = {}; const dictionaryFlags = readUint16(data, position); dictionary.huffman = !!(dictionaryFlags & 1); dictionary.refinement = !!(dictionaryFlags & 2); dictionary.huffmanDHSelector = dictionaryFlags >> 2 & 3; dictionary.huffmanDWSelector = dictionaryFlags >> 4 & 3; dictionary.bitmapSizeSelector = dictionaryFlags >> 6 & 1; dictionary.aggregationInstancesSelector = dictionaryFlags >> 7 & 1; dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); dictionary.template = dictionaryFlags >> 10 & 3; dictionary.refinementTemplate = dictionaryFlags >> 12 & 1; position += 2; if (!dictionary.huffman) { atLength = dictionary.template === 0 ? 4 : 1; at = []; for (i = 0; i < atLength; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) }); position += 2; } dictionary.at = at; } if (dictionary.refinement && !dictionary.refinementTemplate) { at = []; for (i = 0; i < 2; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) }); position += 2; } dictionary.refinementAt = at; } dictionary.numberOfExportedSymbols = readUint32(data, position); position += 4; dictionary.numberOfNewSymbols = readUint32(data, position); position += 4; args = [dictionary, header.number, header.referredTo, data, position, end]; break; case 6: case 7: const textRegion = {}; textRegion.info = readRegionSegmentInformation(data, position); position += RegionSegmentInformationFieldLength; const textRegionSegmentFlags = readUint16(data, position); position += 2; textRegion.huffman = !!(textRegionSegmentFlags & 1); textRegion.refinement = !!(textRegionSegmentFlags & 2); textRegion.logStripSize = textRegionSegmentFlags >> 2 & 3; textRegion.stripSize = 1 << textRegion.logStripSize; textRegion.referenceCorner = textRegionSegmentFlags >> 4 & 3; textRegion.transposed = !!(textRegionSegmentFlags & 64); textRegion.combinationOperator = textRegionSegmentFlags >> 7 & 3; textRegion.defaultPixelValue = textRegionSegmentFlags >> 9 & 1; textRegion.dsOffset = textRegionSegmentFlags << 17 >> 27; textRegion.refinementTemplate = textRegionSegmentFlags >> 15 & 1; if (textRegion.huffman) { const textRegionHuffmanFlags = readUint16(data, position); position += 2; textRegion.huffmanFS = textRegionHuffmanFlags & 3; textRegion.huffmanDS = textRegionHuffmanFlags >> 2 & 3; textRegion.huffmanDT = textRegionHuffmanFlags >> 4 & 3; textRegion.huffmanRefinementDW = textRegionHuffmanFlags >> 6 & 3; textRegion.huffmanRefinementDH = textRegionHuffmanFlags >> 8 & 3; textRegion.huffmanRefinementDX = textRegionHuffmanFlags >> 10 & 3; textRegion.huffmanRefinementDY = textRegionHuffmanFlags >> 12 & 3; textRegion.huffmanRefinementSizeSelector = !!(textRegionHuffmanFlags & 0x4000); } if (textRegion.refinement && !textRegion.refinementTemplate) { at = []; for (i = 0; i < 2; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) }); position += 2; } textRegion.refinementAt = at; } textRegion.numberOfSymbolInstances = readUint32(data, position); position += 4; args = [textRegion, header.referredTo, data, position, end]; break; case 16: const patternDictionary = {}; const patternDictionaryFlags = data[position++]; patternDictionary.mmr = !!(patternDictionaryFlags & 1); patternDictionary.template = patternDictionaryFlags >> 1 & 3; patternDictionary.patternWidth = data[position++]; patternDictionary.patternHeight = data[position++]; patternDictionary.maxPatternIndex = readUint32(data, position); position += 4; args = [patternDictionary, header.number, data, position, end]; break; case 22: case 23: const halftoneRegion = {}; halftoneRegion.info = readRegionSegmentInformation(data, position); position += RegionSegmentInformationFieldLength; const halftoneRegionFlags = data[position++]; halftoneRegion.mmr = !!(halftoneRegionFlags & 1); halftoneRegion.template = halftoneRegionFlags >> 1 & 3; halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); halftoneRegion.combinationOperator = halftoneRegionFlags >> 4 & 7; halftoneRegion.defaultPixelValue = halftoneRegionFlags >> 7 & 1; halftoneRegion.gridWidth = readUint32(data, position); position += 4; halftoneRegion.gridHeight = readUint32(data, position); position += 4; halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; position += 4; halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; position += 4; halftoneRegion.gridVectorX = readUint16(data, position); position += 2; halftoneRegion.gridVectorY = readUint16(data, position); position += 2; args = [halftoneRegion, header.referredTo, data, position, end]; break; case 38: case 39: const genericRegion = {}; genericRegion.info = readRegionSegmentInformation(data, position); position += RegionSegmentInformationFieldLength; const genericRegionSegmentFlags = data[position++]; genericRegion.mmr = !!(genericRegionSegmentFlags & 1); genericRegion.template = genericRegionSegmentFlags >> 1 & 3; genericRegion.prediction = !!(genericRegionSegmentFlags & 8); if (!genericRegion.mmr) { atLength = genericRegion.template === 0 ? 4 : 1; at = []; for (i = 0; i < atLength; i++) { at.push({ x: readInt8(data, position), y: readInt8(data, position + 1) }); position += 2; } genericRegion.at = at; } args = [genericRegion, data, position, end]; break; case 48: const pageInfo = { width: readUint32(data, position), height: readUint32(data, position + 4), resolutionX: readUint32(data, position + 8), resolutionY: readUint32(data, position + 12) }; if (pageInfo.height === 0xffffffff) { delete pageInfo.height; } const pageSegmentFlags = data[position + 16]; readUint16(data, position + 17); pageInfo.lossless = !!(pageSegmentFlags & 1); pageInfo.refinement = !!(pageSegmentFlags & 2); pageInfo.defaultPixelValue = pageSegmentFlags >> 2 & 1; pageInfo.combinationOperator = pageSegmentFlags >> 3 & 3; pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); args = [pageInfo]; break; case 49: break; case 50: break; case 51: break; case 53: args = [header.number, data, position, end]; break; case 62: break; default: throw new Jbig2Error(`segment type ${header.typeName}(${header.type}) is not implemented`); } const callbackName = "on" + header.typeName; if (callbackName in visitor) { visitor[callbackName].apply(visitor, args); } } function processSegments(segments, visitor) { for (let i = 0, ii = segments.length; i < ii; i++) { processSegment(segments[i], visitor); } } function parseJbig2Chunks(chunks) { const visitor = new SimpleSegmentVisitor(); for (let i = 0, ii = chunks.length; i < ii; i++) { const chunk = chunks[i]; const segments = readSegments({}, chunk.data, chunk.start, chunk.end); processSegments(segments, visitor); } return visitor.buffer; } function parseJbig2(data) { throw new Error("Not implemented: parseJbig2"); } class SimpleSegmentVisitor { onPageInformation(info) { this.currentPageInfo = info; const rowSize = info.width + 7 >> 3; const buffer = new Uint8ClampedArray(rowSize * info.height); if (info.defaultPixelValue) { buffer.fill(0xff); } this.buffer = buffer; } drawBitmap(regionInfo, bitmap) { const pageInfo = this.currentPageInfo; const width = regionInfo.width, height = regionInfo.height; const rowSize = pageInfo.width + 7 >> 3; const combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator; const buffer = this.buffer; const mask0 = 128 >> (regionInfo.x & 7); let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); let i, j, mask, offset; switch (combinationOperator) { case 0: for (i = 0; i < height; i++) { mask = mask0; offset = offset0; for (j = 0; j < width; j++) { if (bitmap[i][j]) { buffer[offset] |= mask; } mask >>= 1; if (!mask) { mask = 128; offset++; } } offset0 += rowSize; } break; case 2: for (i = 0; i < height; i++) { mask = mask0; offset = offset0; for (j = 0; j < width; j++) { if (bitmap[i][j]) { buffer[offset] ^= mask; } mask >>= 1; if (!mask) { mask = 128; offset++; } } offset0 += rowSize; } break; default: throw new Jbig2Error(`operator ${combinationOperator} is not supported`); } } onImmediateGenericRegion(region, data, start, end) { const regionInfo = region.info; const decodingContext = new DecodingContext(data, start, end); const bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height, region.template, region.prediction, null, region.at, decodingContext); this.drawBitmap(regionInfo, bitmap); } onImmediateLosslessGenericRegion() { this.onImmediateGenericRegion(...arguments); } onSymbolDictionary(dictionary, currentSegment, referredSegments, data, start, end) { let huffmanTables, huffmanInput; if (dictionary.huffman) { huffmanTables = getSymbolDictionaryHuffmanTables(dictionary, referredSegments, this.customTables); huffmanInput = new Reader(data, start, end); } let symbols = this.symbols; if (!symbols) { this.symbols = symbols = {}; } const inputSymbols = []; for (const referredSegment of referredSegments) { const referredSymbols = symbols[referredSegment]; if (referredSymbols) { inputSymbols.push(...referredSymbols); } } const decodingContext = new DecodingContext(data, start, end); symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols, dictionary.numberOfExportedSymbols, huffmanTables, dictionary.template, dictionary.at, dictionary.refinementTemplate, dictionary.refinementAt, decodingContext, huffmanInput); } onImmediateTextRegion(region, referredSegments, data, start, end) { const regionInfo = region.info; let huffmanTables, huffmanInput; const symbols = this.symbols; const inputSymbols = []; for (const referredSegment of referredSegments) { const referredSymbols = symbols[referredSegment]; if (referredSymbols) { inputSymbols.push(...referredSymbols); } } const symbolCodeLength = log2(inputSymbols.length); if (region.huffman) { huffmanInput = new Reader(data, start, end); huffmanTables = getTextRegionHuffmanTables(region, referredSegments, this.customTables, inputSymbols.length, huffmanInput); } const decodingContext = new DecodingContext(data, start, end); const bitmap = decodeTextRegion(region.huffman, region.refinement, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.numberOfSymbolInstances, region.stripSize, inputSymbols, symbolCodeLength, region.transposed, region.dsOffset, region.referenceCorner, region.combinationOperator, huffmanTables, region.refinementTemplate, region.refinementAt, decodingContext, region.logStripSize, huffmanInput); this.drawBitmap(regionInfo, bitmap); } onImmediateLosslessTextRegion() { this.onImmediateTextRegion(...arguments); } onPatternDictionary(dictionary, currentSegment, data, start, end) { let patterns = this.patterns; if (!patterns) { this.patterns = patterns = {}; } const decodingContext = new DecodingContext(data, start, end); patterns[currentSegment] = decodePatternDictionary(dictionary.mmr, dictionary.patternWidth, dictionary.patternHeight, dictionary.maxPatternIndex, dictionary.template, decodingContext); } onImmediateHalftoneRegion(region, referredSegments, data, start, end) { const patterns = this.patterns[referredSegments[0]]; const regionInfo = region.info; const decodingContext = new DecodingContext(data, start, end); const bitmap = decodeHalftoneRegion(region.mmr, patterns, region.template, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.enableSkip, region.combinationOperator, region.gridWidth, region.gridHeight, region.gridOffsetX, region.gridOffsetY, region.gridVectorX, region.gridVectorY, decodingContext); this.drawBitmap(regionInfo, bitmap); } onImmediateLosslessHalftoneRegion() { this.onImmediateHalftoneRegion(...arguments); } onTables(currentSegment, data, start, end) { let customTables = this.customTables; if (!customTables) { this.customTables = customTables = {}; } customTables[currentSegment] = decodeTablesSegment(data, start, end); } } class HuffmanLine { constructor(lineData) { if (lineData.length === 2) { this.isOOB = true; this.rangeLow = 0; this.prefixLength = lineData[0]; this.rangeLength = 0; this.prefixCode = lineData[1]; this.isLowerRange = false; } else { this.isOOB = false; this.rangeLow = lineData[0]; this.prefixLength = lineData[1]; this.rangeLength = lineData[2]; this.prefixCode = lineData[3]; this.isLowerRange = lineData[4] === "lower"; } } } class HuffmanTreeNode { constructor(line) { this.children = []; if (line) { this.isLeaf = true; this.rangeLength = line.rangeLength; this.rangeLow = line.rangeLow; this.isLowerRange = line.isLowerRange; this.isOOB = line.isOOB; } else { this.isLeaf = false; } } buildTree(line, shift) { const bit = line.prefixCode >> shift & 1; if (shift <= 0) { this.children[bit] = new HuffmanTreeNode(line); } else { let node = this.children[bit]; if (!node) { this.children[bit] = node = new HuffmanTreeNode(null); } node.buildTree(line, shift - 1); } } decodeNode(reader) { if (this.isLeaf) { if (this.isOOB) { return null; } const htOffset = reader.readBits(this.rangeLength); return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); } const node = this.children[reader.readBit()]; if (!node) { throw new Jbig2Error("invalid Huffman data"); } return node.decodeNode(reader); } } class HuffmanTable { constructor(lines, prefixCodesDone) { if (!prefixCodesDone) { this.assignPrefixCodes(lines); } this.rootNode = new HuffmanTreeNode(null); for (let i = 0, ii = lines.length; i < ii; i++) { const line = lines[i]; if (line.prefixLength > 0) { this.rootNode.buildTree(line, line.prefixLength - 1); } } } decode(reader) { return this.rootNode.decodeNode(reader); } assignPrefixCodes(lines) { const linesLength = lines.length; let prefixLengthMax = 0; for (let i = 0; i < linesLength; i++) { prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); } const histogram = new Uint32Array(prefixLengthMax + 1); for (let i = 0; i < linesLength; i++) { histogram[lines[i].prefixLength]++; } let currentLength = 1, firstCode = 0, currentCode, currentTemp, line; histogram[0] = 0; while (currentLength <= prefixLengthMax) { firstCode = firstCode + histogram[currentLength - 1] << 1; currentCode = firstCode; currentTemp = 0; while (currentTemp < linesLength) { line = lines[currentTemp]; if (line.prefixLength === currentLength) { line.prefixCode = currentCode; currentCode++; } currentTemp++; } currentLength++; } } } function decodeTablesSegment(data, start, end) { const flags = data[start]; const lowestValue = readUint32(data, start + 1) & 0xffffffff; const highestValue = readUint32(data, start + 5) & 0xffffffff; const reader = new Reader(data, start + 9, end); const prefixSizeBits = (flags >> 1 & 7) + 1; const rangeSizeBits = (flags >> 4 & 7) + 1; const lines = []; let prefixLength, rangeLength, currentRangeLow = lowestValue; do { prefixLength = reader.readBits(prefixSizeBits); rangeLength = reader.readBits(rangeSizeBits); lines.push(new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0])); currentRangeLow += 1 << rangeLength; } while (currentRangeLow < highestValue); prefixLength = reader.readBits(prefixSizeBits); lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"])); prefixLength = reader.readBits(prefixSizeBits); lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); if (flags & 1) { prefixLength = reader.readBits(prefixSizeBits); lines.push(new HuffmanLine([prefixLength, 0])); } return new HuffmanTable(lines, false); } const standardTablesCache = {}; function getStandardTable(number) { let table = standardTablesCache[number]; if (table) { return table; } let lines; switch (number) { case 1: lines = [[0, 1, 4, 0x0], [16, 2, 8, 0x2], [272, 3, 16, 0x6], [65808, 3, 32, 0x7]]; break; case 2: lines = [[0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [75, 6, 32, 0x3e], [6, 0x3f]]; break; case 3: lines = [[-256, 8, 8, 0xfe], [0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [-257, 8, 32, 0xff, "lower"], [75, 7, 32, 0x7e], [6, 0x3e]]; break; case 4: lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [76, 5, 32, 0x1f]]; break; case 5: lines = [[-255, 7, 8, 0x7e], [1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [-256, 7, 32, 0x7f, "lower"], [76, 6, 32, 0x3e]]; break; case 6: lines = [[-2048, 5, 10, 0x1c], [-1024, 4, 9, 0x8], [-512, 4, 8, 0x9], [-256, 4, 7, 0xa], [-128, 5, 6, 0x1d], [-64, 5, 5, 0x1e], [-32, 4, 5, 0xb], [0, 2, 7, 0x0], [128, 3, 7, 0x2], [256, 3, 8, 0x3], [512, 4, 9, 0xc], [1024, 4, 10, 0xd], [-2049, 6, 32, 0x3e, "lower"], [2048, 6, 32, 0x3f]]; break; case 7: lines = [[-1024, 4, 9, 0x8], [-512, 3, 8, 0x0], [-256, 4, 7, 0x9], [-128, 5, 6, 0x1a], [-64, 5, 5, 0x1b], [-32, 4, 5, 0xa], [0, 4, 5, 0xb], [32, 5, 5, 0x1c], [64, 5, 6, 0x1d], [128, 4, 7, 0xc], [256, 3, 8, 0x1], [512, 3, 9, 0x2], [1024, 3, 10, 0x3], [-1025, 5, 32, 0x1e, "lower"], [2048, 5, 32, 0x1f]]; break; case 8: lines = [[-15, 8, 3, 0xfc], [-7, 9, 1, 0x1fc], [-5, 8, 1, 0xfd], [-3, 9, 0, 0x1fd], [-2, 7, 0, 0x7c], [-1, 4, 0, 0xa], [0, 2, 1, 0x0], [2, 5, 0, 0x1a], [3, 6, 0, 0x3a], [4, 3, 4, 0x4], [20, 6, 1, 0x3b], [22, 4, 4, 0xb], [38, 4, 5, 0xc], [70, 5, 6, 0x1b], [134, 5, 7, 0x1c], [262, 6, 7, 0x3c], [390, 7, 8, 0x7d], [646, 6, 10, 0x3d], [-16, 9, 32, 0x1fe, "lower"], [1670, 9, 32, 0x1ff], [2, 0x1]]; break; case 9: lines = [[-31, 8, 4, 0xfc], [-15, 9, 2, 0x1fc], [-11, 8, 2, 0xfd], [-7, 9, 1, 0x1fd], [-5, 7, 1, 0x7c], [-3, 4, 1, 0xa], [-1, 3, 1, 0x2], [1, 3, 1, 0x3], [3, 5, 1, 0x1a], [5, 6, 1, 0x3a], [7, 3, 5, 0x4], [39, 6, 2, 0x3b], [43, 4, 5, 0xb], [75, 4, 6, 0xc], [139, 5, 7, 0x1b], [267, 5, 8, 0x1c], [523, 6, 8, 0x3c], [779, 7, 9, 0x7d], [1291, 6, 11, 0x3d], [-32, 9, 32, 0x1fe, "lower"], [3339, 9, 32, 0x1ff], [2, 0x0]]; break; case 10: lines = [[-21, 7, 4, 0x7a], [-5, 8, 0, 0xfc], [-4, 7, 0, 0x7b], [-3, 5, 0, 0x18], [-2, 2, 2, 0x0], [2, 5, 0, 0x19], [3, 6, 0, 0x36], [4, 7, 0, 0x7c], [5, 8, 0, 0xfd], [6, 2, 6, 0x1], [70, 5, 5, 0x1a], [102, 6, 5, 0x37], [134, 6, 6, 0x38], [198, 6, 7, 0x39], [326, 6, 8, 0x3a], [582, 6, 9, 0x3b], [1094, 6, 10, 0x3c], [2118, 7, 11, 0x7d], [-22, 8, 32, 0xfe, "lower"], [4166, 8, 32, 0xff], [2, 0x2]]; break; case 11: lines = [[1, 1, 0, 0x0], [2, 2, 1, 0x2], [4, 4, 0, 0xc], [5, 4, 1, 0xd], [7, 5, 1, 0x1c], [9, 5, 2, 0x1d], [13, 6, 2, 0x3c], [17, 7, 2, 0x7a], [21, 7, 3, 0x7b], [29, 7, 4, 0x7c], [45, 7, 5, 0x7d], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]]; break; case 12: lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 1, 0x6], [5, 5, 0, 0x1c], [6, 5, 1, 0x1d], [8, 6, 1, 0x3c], [10, 7, 0, 0x7a], [11, 7, 1, 0x7b], [13, 7, 2, 0x7c], [17, 7, 3, 0x7d], [25, 7, 4, 0x7e], [41, 8, 5, 0xfe], [73, 8, 32, 0xff]]; break; case 13: lines = [[1, 1, 0, 0x0], [2, 3, 0, 0x4], [3, 4, 0, 0xc], [4, 5, 0, 0x1c], [5, 4, 1, 0xd], [7, 3, 3, 0x5], [15, 6, 1, 0x3a], [17, 6, 2, 0x3b], [21, 6, 3, 0x3c], [29, 6, 4, 0x3d], [45, 6, 5, 0x3e], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]]; break; case 14: lines = [[-2, 3, 0, 0x4], [-1, 3, 0, 0x5], [0, 1, 0, 0x0], [1, 3, 0, 0x6], [2, 3, 0, 0x7]]; break; case 15: lines = [[-24, 7, 4, 0x7c], [-8, 6, 2, 0x3c], [-4, 5, 1, 0x1c], [-2, 4, 0, 0xc], [-1, 3, 0, 0x4], [0, 1, 0, 0x0], [1, 3, 0, 0x5], [2, 4, 0, 0xd], [3, 5, 1, 0x1d], [5, 6, 2, 0x3d], [9, 7, 4, 0x7d], [-25, 7, 32, 0x7e, "lower"], [25, 7, 32, 0x7f]]; break; default: throw new Jbig2Error(`standard table B.${number} does not exist`); } for (let i = 0, ii = lines.length; i < ii; i++) { lines[i] = new HuffmanLine(lines[i]); } table = new HuffmanTable(lines, true); standardTablesCache[number] = table; return table; } class Reader { constructor(data, start, end) { this.data = data; this.start = start; this.end = end; this.position = start; this.shift = -1; this.currentByte = 0; } readBit() { if (this.shift < 0) { if (this.position >= this.end) { throw new Jbig2Error("end of data while reading bit"); } this.currentByte = this.data[this.position++]; this.shift = 7; } const bit = this.currentByte >> this.shift & 1; this.shift--; return bit; } readBits(numBits) { let result = 0, i; for (i = numBits - 1; i >= 0; i--) { result |= this.readBit() << i; } return result; } byteAlign() { this.shift = -1; } next() { if (this.position >= this.end) { return -1; } return this.data[this.position++]; } } function getCustomHuffmanTable(index, referredTo, customTables) { let currentIndex = 0; for (let i = 0, ii = referredTo.length; i < ii; i++) { const table = customTables[referredTo[i]]; if (table) { if (index === currentIndex) { return table; } currentIndex++; } } throw new Jbig2Error("can't find custom Huffman table"); } function getTextRegionHuffmanTables(textRegion, referredTo, customTables, numberOfSymbols, reader) { const codes = []; for (let i = 0; i <= 34; i++) { const codeLength = reader.readBits(4); codes.push(new HuffmanLine([i, codeLength, 0, 0])); } const runCodesTable = new HuffmanTable(codes, false); codes.length = 0; for (let i = 0; i < numberOfSymbols;) { const codeLength = runCodesTable.decode(reader); if (codeLength >= 32) { let repeatedLength, numberOfRepeats, j; switch (codeLength) { case 32: if (i === 0) { throw new Jbig2Error("no previous value in symbol ID table"); } numberOfRepeats = reader.readBits(2) + 3; repeatedLength = codes[i - 1].prefixLength; break; case 33: numberOfRepeats = reader.readBits(3) + 3; repeatedLength = 0; break; case 34: numberOfRepeats = reader.readBits(7) + 11; repeatedLength = 0; break; default: throw new Jbig2Error("invalid code length in symbol ID table"); } for (j = 0; j < numberOfRepeats; j++) { codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); i++; } } else { codes.push(new HuffmanLine([i, codeLength, 0, 0])); i++; } } reader.byteAlign(); const symbolIDTable = new HuffmanTable(codes, false); let customIndex = 0, tableFirstS, tableDeltaS, tableDeltaT; switch (textRegion.huffmanFS) { case 0: case 1: tableFirstS = getStandardTable(textRegion.huffmanFS + 6); break; case 3: tableFirstS = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; break; default: throw new Jbig2Error("invalid Huffman FS selector"); } switch (textRegion.huffmanDS) { case 0: case 1: case 2: tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); break; case 3: tableDeltaS = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; break; default: throw new Jbig2Error("invalid Huffman DS selector"); } switch (textRegion.huffmanDT) { case 0: case 1: case 2: tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); break; case 3: tableDeltaT = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; break; default: throw new Jbig2Error("invalid Huffman DT selector"); } if (textRegion.refinement) { throw new Jbig2Error("refinement with Huffman is not supported"); } return { symbolIDTable, tableFirstS, tableDeltaS, tableDeltaT }; } function getSymbolDictionaryHuffmanTables(dictionary, referredTo, customTables) { let customIndex = 0, tableDeltaHeight, tableDeltaWidth; switch (dictionary.huffmanDHSelector) { case 0: case 1: tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); break; case 3: tableDeltaHeight = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; break; default: throw new Jbig2Error("invalid Huffman DH selector"); } switch (dictionary.huffmanDWSelector) { case 0: case 1: tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); break; case 3: tableDeltaWidth = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; break; default: throw new Jbig2Error("invalid Huffman DW selector"); } let tableBitmapSize, tableAggregateInstances; if (dictionary.bitmapSizeSelector) { tableBitmapSize = getCustomHuffmanTable(customIndex, referredTo, customTables); customIndex++; } else { tableBitmapSize = getStandardTable(1); } if (dictionary.aggregationInstancesSelector) { tableAggregateInstances = getCustomHuffmanTable(customIndex, referredTo, customTables); } else { tableAggregateInstances = getStandardTable(1); } return { tableDeltaHeight, tableDeltaWidth, tableBitmapSize, tableAggregateInstances }; } function readUncompressedBitmap(reader, width, height) { const bitmap = []; for (let y = 0; y < height; y++) { const row = new Uint8Array(width); bitmap.push(row); for (let x = 0; x < width; x++) { row[x] = reader.readBit(); } reader.byteAlign(); } return bitmap; } function decodeMMRBitmap(input, width, height, endOfBlock) { const params = { K: -1, Columns: width, Rows: height, BlackIs1: true, EndOfBlock: endOfBlock }; const decoder = new CCITTFaxDecoder(input, params); const bitmap = []; let currentByte, eof = false; for (let y = 0; y < height; y++) { const row = new Uint8Array(width); bitmap.push(row); let shift = -1; for (let x = 0; x < width; x++) { if (shift < 0) { currentByte = decoder.readNextChar(); if (currentByte === -1) { currentByte = 0; eof = true; } shift = 7; } row[x] = currentByte >> shift & 1; shift--; } } if (endOfBlock && !eof) { const lookForEOFLimit = 5; for (let i = 0; i < lookForEOFLimit; i++) { if (decoder.readNextChar() === -1) { break; } } } return bitmap; } class Jbig2Image { parseChunks(chunks) { return parseJbig2Chunks(chunks); } parse(data) { throw new Error("Not implemented: Jbig2Image.parse"); } } ;// CONCATENATED MODULE: ./src/core/jbig2_stream.js class Jbig2Stream extends DecodeStream { constructor(stream, maybeLength, params) { super(maybeLength); this.stream = stream; this.dict = stream.dict; this.maybeLength = maybeLength; this.params = params; } get bytes() { return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); } ensureBuffer(requested) {} readBlock() { if (this.eof) { return; } const jbig2Image = new Jbig2Image(); const chunks = []; if (this.params instanceof Dict) { const globalsStream = this.params.get("JBIG2Globals"); if (globalsStream instanceof BaseStream) { const globals = globalsStream.getBytes(); chunks.push({ data: globals, start: 0, end: globals.length }); } } chunks.push({ data: this.bytes, start: 0, end: this.bytes.length }); const data = jbig2Image.parseChunks(chunks); const dataLength = data.length; for (let i = 0; i < dataLength; i++) { data[i] ^= 0xff; } this.buffer = data; this.bufferLength = dataLength; this.eof = true; } } ;// CONCATENATED MODULE: ./src/shared/image_utils.js function convertToRGBA(params) { switch (params.kind) { case ImageKind.GRAYSCALE_1BPP: return convertBlackAndWhiteToRGBA(params); case ImageKind.RGB_24BPP: return convertRGBToRGBA(params); } return null; } function convertBlackAndWhiteToRGBA({ src, srcPos = 0, dest, width, height, nonBlackColor = 0xffffffff, inverseDecode = false }) { const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff; const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor]; const widthInSource = width >> 3; const widthRemainder = width & 7; const srcLength = src.length; dest = new Uint32Array(dest.buffer); let destPos = 0; for (let i = 0; i < height; i++) { for (const max = srcPos + widthInSource; srcPos < max; srcPos++) { const elem = srcPos < srcLength ? src[srcPos] : 255; dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping; dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping; } if (widthRemainder === 0) { continue; } const elem = srcPos < srcLength ? src[srcPos++] : 255; for (let j = 0; j < widthRemainder; j++) { dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping; } } return { srcPos, destPos }; } function convertRGBToRGBA({ src, srcPos = 0, dest, destPos = 0, width, height }) { let i = 0; const len32 = src.length >> 2; const src32 = new Uint32Array(src.buffer, srcPos, len32); if (FeatureTest.isLittleEndian) { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff000000; dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000; dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000; dest[destPos + 3] = s3 >>> 8 | 0xff000000; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000; } } else { for (; i < len32 - 2; i += 3, destPos += 4) { const s1 = src32[i]; const s2 = src32[i + 1]; const s3 = src32[i + 2]; dest[destPos] = s1 | 0xff; dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff; dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff; dest[destPos + 3] = s3 << 8 | 0xff; } for (let j = i * 4, jj = src.length; j < jj; j += 3) { dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff; } } return { srcPos, destPos }; } function grayToRGBA(src, dest) { if (FeatureTest.isLittleEndian) { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x10101 | 0xff000000; } } else { for (let i = 0, ii = src.length; i < ii; i++) { dest[i] = src[i] * 0x1010100 | 0x000000ff; } } } ;// CONCATENATED MODULE: ./src/core/jpg.js class JpegError extends BaseException { constructor(msg) { super(`JPEG error: ${msg}`, "JpegError"); } } class DNLMarkerError extends BaseException { constructor(message, scanLines) { super(message, "DNLMarkerError"); this.scanLines = scanLines; } } class EOIMarkerError extends BaseException { constructor(msg) { super(msg, "EOIMarkerError"); } } const dctZigZag = new Uint8Array([0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]); const dctCos1 = 4017; const dctSin1 = 799; const dctCos3 = 3406; const dctSin3 = 2276; const dctCos6 = 1567; const dctSin6 = 3784; const dctSqrt2 = 5793; const dctSqrt1d2 = 2896; function buildHuffmanTable(codeLengths, values) { let k = 0, i, j, length = 16; while (length > 0 && !codeLengths[length - 1]) { length--; } const code = [{ children: [], index: 0 }]; let p = code[0], q; for (i = 0; i < length; i++) { for (j = 0; j < codeLengths[i]; j++) { p = code.pop(); p.children[p.index] = values[k]; while (p.index > 0) { p = code.pop(); } p.index++; code.push(p); while (code.length <= i) { code.push(q = { children: [], index: 0 }); p.children[p.index] = q.children; p = q; } k++; } if (i + 1 < length) { code.push(q = { children: [], index: 0 }); p.children[p.index] = q.children; p = q; } } return code[0].children; } function getBlockBufferOffset(component, row, col) { return 64 * ((component.blocksPerLine + 1) * row + col); } function decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successivePrev, successive, parseDNLMarker = false) { const mcusPerLine = frame.mcusPerLine; const progressive = frame.progressive; const startOffset = offset; let bitsData = 0, bitsCount = 0; function readBit() { if (bitsCount > 0) { bitsCount--; return bitsData >> bitsCount & 1; } bitsData = data[offset++]; if (bitsData === 0xff) { const nextByte = data[offset++]; if (nextByte) { if (nextByte === 0xdc && parseDNLMarker) { offset += 2; const scanLines = readUint16(data, offset); offset += 2; if (scanLines > 0 && scanLines !== frame.scanLines) { throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data", scanLines); } } else if (nextByte === 0xd9) { if (parseDNLMarker) { const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0); if (maybeScanLines > 0 && Math.round(frame.scanLines / maybeScanLines) >= 5) { throw new DNLMarkerError("Found EOI marker (0xFFD9) while parsing scan data, " + "possibly caused by incorrect `scanLines` parameter", maybeScanLines); } } throw new EOIMarkerError("Found EOI marker (0xFFD9) while parsing scan data"); } throw new JpegError(`unexpected marker ${(bitsData << 8 | nextByte).toString(16)}`); } } bitsCount = 7; return bitsData >>> 7; } function decodeHuffman(tree) { let node = tree; while (true) { node = node[readBit()]; switch (typeof node) { case "number": return node; case "object": continue; } throw new JpegError("invalid huffman sequence"); } } function receive(length) { let n = 0; while (length > 0) { n = n << 1 | readBit(); length--; } return n; } function receiveAndExtend(length) { if (length === 1) { return readBit() === 1 ? 1 : -1; } const n = receive(length); if (n >= 1 << length - 1) { return n; } return n + (-1 << length) + 1; } function decodeBaseline(component, blockOffset) { const t = decodeHuffman(component.huffmanTableDC); const diff = t === 0 ? 0 : receiveAndExtend(t); component.blockData[blockOffset] = component.pred += diff; let k = 1; while (k < 64) { const rs = decodeHuffman(component.huffmanTableAC); const s = rs & 15, r = rs >> 4; if (s === 0) { if (r < 15) { break; } k += 16; continue; } k += r; const z = dctZigZag[k]; component.blockData[blockOffset + z] = receiveAndExtend(s); k++; } } function decodeDCFirst(component, blockOffset) { const t = decodeHuffman(component.huffmanTableDC); const diff = t === 0 ? 0 : receiveAndExtend(t) << successive; component.blockData[blockOffset] = component.pred += diff; } function decodeDCSuccessive(component, blockOffset) { component.blockData[blockOffset] |= readBit() << successive; } let eobrun = 0; function decodeACFirst(component, blockOffset) { if (eobrun > 0) { eobrun--; return; } let k = spectralStart; const e = spectralEnd; while (k <= e) { const rs = decodeHuffman(component.huffmanTableAC); const s = rs & 15, r = rs >> 4; if (s === 0) { if (r < 15) { eobrun = receive(r) + (1 << r) - 1; break; } k += 16; continue; } k += r; const z = dctZigZag[k]; component.blockData[blockOffset + z] = receiveAndExtend(s) * (1 << successive); k++; } } let successiveACState = 0, successiveACNextValue; function decodeACSuccessive(component, blockOffset) { let k = spectralStart; const e = spectralEnd; let r = 0; let s; let rs; while (k <= e) { const offsetZ = blockOffset + dctZigZag[k]; const sign = component.blockData[offsetZ] < 0 ? -1 : 1; switch (successiveACState) { case 0: rs = decodeHuffman(component.huffmanTableAC); s = rs & 15; r = rs >> 4; if (s === 0) { if (r < 15) { eobrun = receive(r) + (1 << r); successiveACState = 4; } else { r = 16; successiveACState = 1; } } else { if (s !== 1) { throw new JpegError("invalid ACn encoding"); } successiveACNextValue = receiveAndExtend(s); successiveACState = r ? 2 : 3; } continue; case 1: case 2: if (component.blockData[offsetZ]) { component.blockData[offsetZ] += sign * (readBit() << successive); } else { r--; if (r === 0) { successiveACState = successiveACState === 2 ? 3 : 0; } } break; case 3: if (component.blockData[offsetZ]) { component.blockData[offsetZ] += sign * (readBit() << successive); } else { component.blockData[offsetZ] = successiveACNextValue << successive; successiveACState = 0; } break; case 4: if (component.blockData[offsetZ]) { component.blockData[offsetZ] += sign * (readBit() << successive); } break; } k++; } if (successiveACState === 4) { eobrun--; if (eobrun === 0) { successiveACState = 0; } } } let blockRow = 0; function decodeMcu(component, decode, mcu, row, col) { const mcuRow = mcu / mcusPerLine | 0; const mcuCol = mcu % mcusPerLine; blockRow = mcuRow * component.v + row; const blockCol = mcuCol * component.h + col; const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); decode(component, blockOffset); } function decodeBlock(component, decode, mcu) { blockRow = mcu / component.blocksPerLine | 0; const blockCol = mcu % component.blocksPerLine; const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); decode(component, blockOffset); } const componentsLength = components.length; let component, i, j, k, n; let decodeFn; if (progressive) { if (spectralStart === 0) { decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; } else { decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; } } else { decodeFn = decodeBaseline; } let mcu = 0, fileMarker; const mcuExpected = componentsLength === 1 ? components[0].blocksPerLine * components[0].blocksPerColumn : mcusPerLine * frame.mcusPerColumn; let h, v; while (mcu <= mcuExpected) { const mcuToRead = resetInterval ? Math.min(mcuExpected - mcu, resetInterval) : mcuExpected; if (mcuToRead > 0) { for (i = 0; i < componentsLength; i++) { components[i].pred = 0; } eobrun = 0; if (componentsLength === 1) { component = components[0]; for (n = 0; n < mcuToRead; n++) { decodeBlock(component, decodeFn, mcu); mcu++; } } else { for (n = 0; n < mcuToRead; n++) { for (i = 0; i < componentsLength; i++) { component = components[i]; h = component.h; v = component.v; for (j = 0; j < v; j++) { for (k = 0; k < h; k++) { decodeMcu(component, decodeFn, mcu, j, k); } } } mcu++; } } } bitsCount = 0; fileMarker = findNextFileMarker(data, offset); if (!fileMarker) { break; } if (fileMarker.invalid) { const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive"; warn(`decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}`); offset = fileMarker.offset; } if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) { offset += 2; } else { break; } } return offset - startOffset; } function quantizeAndInverse(component, blockBufferOffset, p) { const qt = component.quantizationTable, blockData = component.blockData; let v0, v1, v2, v3, v4, v5, v6, v7; let p0, p1, p2, p3, p4, p5, p6, p7; let t; if (!qt) { throw new JpegError("missing required Quantization Table."); } for (let row = 0; row < 64; row += 8) { p0 = blockData[blockBufferOffset + row]; p1 = blockData[blockBufferOffset + row + 1]; p2 = blockData[blockBufferOffset + row + 2]; p3 = blockData[blockBufferOffset + row + 3]; p4 = blockData[blockBufferOffset + row + 4]; p5 = blockData[blockBufferOffset + row + 5]; p6 = blockData[blockBufferOffset + row + 6]; p7 = blockData[blockBufferOffset + row + 7]; p0 *= qt[row]; if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { t = dctSqrt2 * p0 + 512 >> 10; p[row] = t; p[row + 1] = t; p[row + 2] = t; p[row + 3] = t; p[row + 4] = t; p[row + 5] = t; p[row + 6] = t; p[row + 7] = t; continue; } p1 *= qt[row + 1]; p2 *= qt[row + 2]; p3 *= qt[row + 3]; p4 *= qt[row + 4]; p5 *= qt[row + 5]; p6 *= qt[row + 6]; p7 *= qt[row + 7]; v0 = dctSqrt2 * p0 + 128 >> 8; v1 = dctSqrt2 * p4 + 128 >> 8; v2 = p2; v3 = p6; v4 = dctSqrt1d2 * (p1 - p7) + 128 >> 8; v7 = dctSqrt1d2 * (p1 + p7) + 128 >> 8; v5 = p3 << 4; v6 = p5 << 4; v0 = v0 + v1 + 1 >> 1; v1 = v0 - v1; t = v2 * dctSin6 + v3 * dctCos6 + 128 >> 8; v2 = v2 * dctCos6 - v3 * dctSin6 + 128 >> 8; v3 = t; v4 = v4 + v6 + 1 >> 1; v6 = v4 - v6; v7 = v7 + v5 + 1 >> 1; v5 = v7 - v5; v0 = v0 + v3 + 1 >> 1; v3 = v0 - v3; v1 = v1 + v2 + 1 >> 1; v2 = v1 - v2; t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12; v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12; v7 = t; t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12; v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12; v6 = t; p[row] = v0 + v7; p[row + 7] = v0 - v7; p[row + 1] = v1 + v6; p[row + 6] = v1 - v6; p[row + 2] = v2 + v5; p[row + 5] = v2 - v5; p[row + 3] = v3 + v4; p[row + 4] = v3 - v4; } for (let col = 0; col < 8; ++col) { p0 = p[col]; p1 = p[col + 8]; p2 = p[col + 16]; p3 = p[col + 24]; p4 = p[col + 32]; p5 = p[col + 40]; p6 = p[col + 48]; p7 = p[col + 56]; if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { t = dctSqrt2 * p0 + 8192 >> 14; if (t < -2040) { t = 0; } else if (t >= 2024) { t = 255; } else { t = t + 2056 >> 4; } blockData[blockBufferOffset + col] = t; blockData[blockBufferOffset + col + 8] = t; blockData[blockBufferOffset + col + 16] = t; blockData[blockBufferOffset + col + 24] = t; blockData[blockBufferOffset + col + 32] = t; blockData[blockBufferOffset + col + 40] = t; blockData[blockBufferOffset + col + 48] = t; blockData[blockBufferOffset + col + 56] = t; continue; } v0 = dctSqrt2 * p0 + 2048 >> 12; v1 = dctSqrt2 * p4 + 2048 >> 12; v2 = p2; v3 = p6; v4 = dctSqrt1d2 * (p1 - p7) + 2048 >> 12; v7 = dctSqrt1d2 * (p1 + p7) + 2048 >> 12; v5 = p3; v6 = p5; v0 = (v0 + v1 + 1 >> 1) + 4112; v1 = v0 - v1; t = v2 * dctSin6 + v3 * dctCos6 + 2048 >> 12; v2 = v2 * dctCos6 - v3 * dctSin6 + 2048 >> 12; v3 = t; v4 = v4 + v6 + 1 >> 1; v6 = v4 - v6; v7 = v7 + v5 + 1 >> 1; v5 = v7 - v5; v0 = v0 + v3 + 1 >> 1; v3 = v0 - v3; v1 = v1 + v2 + 1 >> 1; v2 = v1 - v2; t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12; v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12; v7 = t; t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12; v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12; v6 = t; p0 = v0 + v7; p7 = v0 - v7; p1 = v1 + v6; p6 = v1 - v6; p2 = v2 + v5; p5 = v2 - v5; p3 = v3 + v4; p4 = v3 - v4; if (p0 < 16) { p0 = 0; } else if (p0 >= 4080) { p0 = 255; } else { p0 >>= 4; } if (p1 < 16) { p1 = 0; } else if (p1 >= 4080) { p1 = 255; } else { p1 >>= 4; } if (p2 < 16) { p2 = 0; } else if (p2 >= 4080) { p2 = 255; } else { p2 >>= 4; } if (p3 < 16) { p3 = 0; } else if (p3 >= 4080) { p3 = 255; } else { p3 >>= 4; } if (p4 < 16) { p4 = 0; } else if (p4 >= 4080) { p4 = 255; } else { p4 >>= 4; } if (p5 < 16) { p5 = 0; } else if (p5 >= 4080) { p5 = 255; } else { p5 >>= 4; } if (p6 < 16) { p6 = 0; } else if (p6 >= 4080) { p6 = 255; } else { p6 >>= 4; } if (p7 < 16) { p7 = 0; } else if (p7 >= 4080) { p7 = 255; } else { p7 >>= 4; } blockData[blockBufferOffset + col] = p0; blockData[blockBufferOffset + col + 8] = p1; blockData[blockBufferOffset + col + 16] = p2; blockData[blockBufferOffset + col + 24] = p3; blockData[blockBufferOffset + col + 32] = p4; blockData[blockBufferOffset + col + 40] = p5; blockData[blockBufferOffset + col + 48] = p6; blockData[blockBufferOffset + col + 56] = p7; } } function buildComponentData(frame, component) { const blocksPerLine = component.blocksPerLine; const blocksPerColumn = component.blocksPerColumn; const computationBuffer = new Int16Array(64); for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { const offset = getBlockBufferOffset(component, blockRow, blockCol); quantizeAndInverse(component, offset, computationBuffer); } } return component.blockData; } function findNextFileMarker(data, currentPos, startPos = currentPos) { const maxPos = data.length - 1; let newPos = startPos < currentPos ? startPos : currentPos; if (currentPos >= maxPos) { return null; } const currentMarker = readUint16(data, currentPos); if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { return { invalid: null, marker: currentMarker, offset: currentPos }; } let newMarker = readUint16(data, newPos); while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { if (++newPos >= maxPos) { return null; } newMarker = readUint16(data, newPos); } return { invalid: currentMarker.toString(16), marker: newMarker, offset: newPos }; } class JpegImage { constructor({ decodeTransform = null, colorTransform = -1 } = {}) { this._decodeTransform = decodeTransform; this._colorTransform = colorTransform; } parse(data, { dnlScanLines = null } = {}) { function readDataBlock() { const length = readUint16(data, offset); offset += 2; let endOffset = offset + length - 2; const fileMarker = findNextFileMarker(data, endOffset, offset); if (fileMarker?.invalid) { warn("readDataBlock - incorrect length, current marker is: " + fileMarker.invalid); endOffset = fileMarker.offset; } const array = data.subarray(offset, endOffset); offset += array.length; return array; } function prepareComponents(frame) { const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); for (const component of frame.components) { const blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); const blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); const blocksPerLineForMcu = mcusPerLine * component.h; const blocksPerColumnForMcu = mcusPerColumn * component.v; const blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); component.blockData = new Int16Array(blocksBufferSize); component.blocksPerLine = blocksPerLine; component.blocksPerColumn = blocksPerColumn; } frame.mcusPerLine = mcusPerLine; frame.mcusPerColumn = mcusPerColumn; } let offset = 0; let jfif = null; let adobe = null; let frame, resetInterval; let numSOSMarkers = 0; const quantizationTables = []; const huffmanTablesAC = [], huffmanTablesDC = []; let fileMarker = readUint16(data, offset); offset += 2; if (fileMarker !== 0xffd8) { throw new JpegError("SOI not found"); } fileMarker = readUint16(data, offset); offset += 2; markerLoop: while (fileMarker !== 0xffd9) { let i, j, l; switch (fileMarker) { case 0xffe0: case 0xffe1: case 0xffe2: case 0xffe3: case 0xffe4: case 0xffe5: case 0xffe6: case 0xffe7: case 0xffe8: case 0xffe9: case 0xffea: case 0xffeb: case 0xffec: case 0xffed: case 0xffee: case 0xffef: case 0xfffe: const appData = readDataBlock(); if (fileMarker === 0xffe0) { if (appData[0] === 0x4a && appData[1] === 0x46 && appData[2] === 0x49 && appData[3] === 0x46 && appData[4] === 0) { jfif = { version: { major: appData[5], minor: appData[6] }, densityUnits: appData[7], xDensity: appData[8] << 8 | appData[9], yDensity: appData[10] << 8 | appData[11], thumbWidth: appData[12], thumbHeight: appData[13], thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) }; } } if (fileMarker === 0xffee) { if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6f && appData[3] === 0x62 && appData[4] === 0x65) { adobe = { version: appData[5] << 8 | appData[6], flags0: appData[7] << 8 | appData[8], flags1: appData[9] << 8 | appData[10], transformCode: appData[11] }; } } break; case 0xffdb: const quantizationTablesLength = readUint16(data, offset); offset += 2; const quantizationTablesEnd = quantizationTablesLength + offset - 2; let z; while (offset < quantizationTablesEnd) { const quantizationTableSpec = data[offset++]; const tableData = new Uint16Array(64); if (quantizationTableSpec >> 4 === 0) { for (j = 0; j < 64; j++) { z = dctZigZag[j]; tableData[z] = data[offset++]; } } else if (quantizationTableSpec >> 4 === 1) { for (j = 0; j < 64; j++) { z = dctZigZag[j]; tableData[z] = readUint16(data, offset); offset += 2; } } else { throw new JpegError("DQT - invalid table spec"); } quantizationTables[quantizationTableSpec & 15] = tableData; } break; case 0xffc0: case 0xffc1: case 0xffc2: if (frame) { throw new JpegError("Only single frame JPEGs supported"); } offset += 2; frame = {}; frame.extended = fileMarker === 0xffc1; frame.progressive = fileMarker === 0xffc2; frame.precision = data[offset++]; const sofScanLines = readUint16(data, offset); offset += 2; frame.scanLines = dnlScanLines || sofScanLines; frame.samplesPerLine = readUint16(data, offset); offset += 2; frame.components = []; frame.componentIds = {}; const componentsCount = data[offset++]; let maxH = 0, maxV = 0; for (i = 0; i < componentsCount; i++) { const componentId = data[offset]; const h = data[offset + 1] >> 4; const v = data[offset + 1] & 15; if (maxH < h) { maxH = h; } if (maxV < v) { maxV = v; } const qId = data[offset + 2]; l = frame.components.push({ h, v, quantizationId: qId, quantizationTable: null }); frame.componentIds[componentId] = l - 1; offset += 3; } frame.maxH = maxH; frame.maxV = maxV; prepareComponents(frame); break; case 0xffc4: const huffmanLength = readUint16(data, offset); offset += 2; for (i = 2; i < huffmanLength;) { const huffmanTableSpec = data[offset++]; const codeLengths = new Uint8Array(16); let codeLengthSum = 0; for (j = 0; j < 16; j++, offset++) { codeLengthSum += codeLengths[j] = data[offset]; } const huffmanValues = new Uint8Array(codeLengthSum); for (j = 0; j < codeLengthSum; j++, offset++) { huffmanValues[j] = data[offset]; } i += 17 + codeLengthSum; (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues); } break; case 0xffdd: offset += 2; resetInterval = readUint16(data, offset); offset += 2; break; case 0xffda: const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines; offset += 2; const selectorsCount = data[offset++], components = []; for (i = 0; i < selectorsCount; i++) { const index = data[offset++]; const componentIndex = frame.componentIds[index]; const component = frame.components[componentIndex]; component.index = index; const tableSpec = data[offset++]; component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; components.push(component); } const spectralStart = data[offset++], spectralEnd = data[offset++], successiveApproximation = data[offset++]; try { const processed = decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15, parseDNLMarker); offset += processed; } catch (ex) { if (ex instanceof DNLMarkerError) { warn(`${ex.message} -- attempting to re-parse the JPEG image.`); return this.parse(data, { dnlScanLines: ex.scanLines }); } else if (ex instanceof EOIMarkerError) { warn(`${ex.message} -- ignoring the rest of the image data.`); break markerLoop; } throw ex; } break; case 0xffdc: offset += 4; break; case 0xffff: if (data[offset] !== 0xff) { offset--; } break; default: const nextFileMarker = findNextFileMarker(data, offset - 2, offset - 3); if (nextFileMarker?.invalid) { warn("JpegImage.parse - unexpected data, current marker is: " + nextFileMarker.invalid); offset = nextFileMarker.offset; break; } if (!nextFileMarker || offset >= data.length - 1) { warn("JpegImage.parse - reached the end of the image data " + "without finding an EOI marker (0xFFD9)."); break markerLoop; } throw new JpegError("JpegImage.parse - unknown marker: " + fileMarker.toString(16)); } fileMarker = readUint16(data, offset); offset += 2; } if (!frame) { throw new JpegError("JpegImage.parse - no frame data found."); } this.width = frame.samplesPerLine; this.height = frame.scanLines; this.jfif = jfif; this.adobe = adobe; this.components = []; for (const component of frame.components) { const quantizationTable = quantizationTables[component.quantizationId]; if (quantizationTable) { component.quantizationTable = quantizationTable; } this.components.push({ index: component.index, output: buildComponentData(frame, component), scaleX: component.h / frame.maxH, scaleY: component.v / frame.maxV, blocksPerLine: component.blocksPerLine, blocksPerColumn: component.blocksPerColumn }); } this.numComponents = this.components.length; return undefined; } _getLinearizedBlockData(width, height, isSourcePDF = false) { const scaleX = this.width / width, scaleY = this.height / height; let component, componentScaleX, componentScaleY, blocksPerScanline; let x, y, i, j, k; let index; let offset = 0; let output; const numComponents = this.components.length; const dataLength = width * height * numComponents; const data = new Uint8ClampedArray(dataLength); const xScaleBlockOffset = new Uint32Array(width); const mask3LSB = 0xfffffff8; let lastComponentScaleX; for (i = 0; i < numComponents; i++) { component = this.components[i]; componentScaleX = component.scaleX * scaleX; componentScaleY = component.scaleY * scaleY; offset = i; output = component.output; blocksPerScanline = component.blocksPerLine + 1 << 3; if (componentScaleX !== lastComponentScaleX) { for (x = 0; x < width; x++) { j = 0 | x * componentScaleX; xScaleBlockOffset[x] = (j & mask3LSB) << 3 | j & 7; } lastComponentScaleX = componentScaleX; } for (y = 0; y < height; y++) { j = 0 | y * componentScaleY; index = blocksPerScanline * (j & mask3LSB) | (j & 7) << 3; for (x = 0; x < width; x++) { data[offset] = output[index + xScaleBlockOffset[x]]; offset += numComponents; } } } let transform = this._decodeTransform; if (!isSourcePDF && numComponents === 4 && !transform) { transform = new Int32Array([-256, 255, -256, 255, -256, 255, -256, 255]); } if (transform) { for (i = 0; i < dataLength;) { for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { data[i] = (data[i] * transform[k] >> 8) + transform[k + 1]; } } } return data; } get _isColorConversionNeeded() { if (this.adobe) { return !!this.adobe.transformCode; } if (this.numComponents === 3) { if (this._colorTransform === 0) { return false; } else if (this.components[0].index === 0x52 && this.components[1].index === 0x47 && this.components[2].index === 0x42) { return false; } return true; } if (this._colorTransform === 1) { return true; } return false; } _convertYccToRgb(data) { let Y, Cb, Cr; for (let i = 0, length = data.length; i < length; i += 3) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; data[i] = Y - 179.456 + 1.402 * Cr; data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; data[i + 2] = Y - 226.816 + 1.772 * Cb; } return data; } _convertYccToRgba(data, out) { for (let i = 0, j = 0, length = data.length; i < length; i += 3, j += 4) { const Y = data[i]; const Cb = data[i + 1]; const Cr = data[i + 2]; out[j] = Y - 179.456 + 1.402 * Cr; out[j + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; out[j + 2] = Y - 226.816 + 1.772 * Cb; out[j + 3] = 255; } return out; } _convertYcckToRgb(data) { let Y, Cb, Cr, k; let offset = 0; for (let i = 0, length = data.length; i < length; i += 4) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; k = data[i + 3]; data[offset++] = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776); data[offset++] = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665); data[offset++] = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407); } return data.subarray(0, offset); } _convertYcckToRgba(data) { for (let i = 0, length = data.length; i < length; i += 4) { const Y = data[i]; const Cb = data[i + 1]; const Cr = data[i + 2]; const k = data[i + 3]; data[i] = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776); data[i + 1] = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665); data[i + 2] = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407); data[i + 3] = 255; } return data; } _convertYcckToCmyk(data) { let Y, Cb, Cr; for (let i = 0, length = data.length; i < length; i += 4) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; data[i] = 434.456 - Y - 1.402 * Cr; data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr; data[i + 2] = 481.816 - Y - 1.772 * Cb; } return data; } _convertCmykToRgb(data) { let c, m, y, k; let offset = 0; for (let i = 0, length = data.length; i < length; i += 4) { c = data[i]; m = data[i + 1]; y = data[i + 2]; k = data[i + 3]; data[offset++] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254); data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168); data[offset++] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144); } return data.subarray(0, offset); } _convertCmykToRgba(data) { for (let i = 0, length = data.length; i < length; i += 4) { const c = data[i]; const m = data[i + 1]; const y = data[i + 2]; const k = data[i + 3]; data[i] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254); data[i + 1] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168); data[i + 2] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144); data[i + 3] = 255; } return data; } getData({ width, height, forceRGBA = false, forceRGB = false, isSourcePDF = false }) { if (this.numComponents > 4) { throw new JpegError("Unsupported color mode"); } const data = this._getLinearizedBlockData(width, height, isSourcePDF); if (this.numComponents === 1 && (forceRGBA || forceRGB)) { const len = data.length * (forceRGBA ? 4 : 3); const rgbaData = new Uint8ClampedArray(len); let offset = 0; if (forceRGBA) { grayToRGBA(data, new Uint32Array(rgbaData.buffer)); } else { for (const grayColor of data) { rgbaData[offset++] = grayColor; rgbaData[offset++] = grayColor; rgbaData[offset++] = grayColor; } } return rgbaData; } else if (this.numComponents === 3 && this._isColorConversionNeeded) { if (forceRGBA) { const rgbaData = new Uint8ClampedArray(data.length / 3 * 4); return this._convertYccToRgba(data, rgbaData); } return this._convertYccToRgb(data); } else if (this.numComponents === 4) { if (this._isColorConversionNeeded) { if (forceRGBA) { return this._convertYcckToRgba(data); } if (forceRGB) { return this._convertYcckToRgb(data); } return this._convertYcckToCmyk(data); } else if (forceRGBA) { return this._convertCmykToRgba(data); } else if (forceRGB) { return this._convertCmykToRgb(data); } } return data; } } ;// CONCATENATED MODULE: ./src/core/jpeg_stream.js class JpegStream extends DecodeStream { constructor(stream, maybeLength, params) { let ch; while ((ch = stream.getByte()) !== -1) { if (ch === 0xff) { stream.skip(-1); break; } } super(maybeLength); this.stream = stream; this.dict = stream.dict; this.maybeLength = maybeLength; this.params = params; } get bytes() { return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); } ensureBuffer(requested) {} readBlock() { if (this.eof) { return; } const jpegOptions = { decodeTransform: undefined, colorTransform: undefined }; const decodeArr = this.dict.getArray("D", "Decode"); if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) { const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8; const decodeArrLength = decodeArr.length; const transform = new Int32Array(decodeArrLength); let transformNeeded = false; const maxValue = (1 << bitsPerComponent) - 1; for (let i = 0; i < decodeArrLength; i += 2) { transform[i] = (decodeArr[i + 1] - decodeArr[i]) * 256 | 0; transform[i + 1] = decodeArr[i] * maxValue | 0; if (transform[i] !== 256 || transform[i + 1] !== 0) { transformNeeded = true; } } if (transformNeeded) { jpegOptions.decodeTransform = transform; } } if (this.params instanceof Dict) { const colorTransform = this.params.get("ColorTransform"); if (Number.isInteger(colorTransform)) { jpegOptions.colorTransform = colorTransform; } } const jpegImage = new JpegImage(jpegOptions); jpegImage.parse(this.bytes); const data = jpegImage.getData({ width: this.drawWidth, height: this.drawHeight, forceRGBA: this.forceRGBA, forceRGB: this.forceRGB, isSourcePDF: true }); this.buffer = data; this.bufferLength = data.length; this.eof = true; } } ;// CONCATENATED MODULE: ./src/core/jpx.js class JpxError extends BaseException { constructor(msg) { super(`JPX error: ${msg}`, "JpxError"); } } const SubbandsGainLog2 = { LL: 0, LH: 1, HL: 1, HH: 2 }; class JpxImage { constructor() { this.failOnCorruptedImage = false; } parse(data) { const head = readUint16(data, 0); if (head === 0xff4f) { this.parseCodestream(data, 0, data.length); return; } const length = data.length; let position = 0; while (position < length) { let headerSize = 8; let lbox = readUint32(data, position); const tbox = readUint32(data, position + 4); position += headerSize; if (lbox === 1) { lbox = readUint32(data, position) * 4294967296 + readUint32(data, position + 4); position += 8; headerSize += 8; } if (lbox === 0) { lbox = length - position + headerSize; } if (lbox < headerSize) { throw new JpxError("Invalid box field size"); } const dataLength = lbox - headerSize; let jumpDataLength = true; switch (tbox) { case 0x6a703268: jumpDataLength = false; break; case 0x636f6c72: const method = data[position]; if (method === 1) { const colorspace = readUint32(data, position + 3); switch (colorspace) { case 16: case 17: case 18: break; default: warn("Unknown colorspace " + colorspace); break; } } else if (method === 2) { info("ICC profile not supported"); } break; case 0x6a703263: this.parseCodestream(data, position, position + dataLength); break; case 0x6a502020: if (readUint32(data, position) !== 0x0d0a870a) { warn("Invalid JP2 signature"); } break; case 0x6a501a1a: case 0x66747970: case 0x72726571: case 0x72657320: case 0x69686472: break; default: const headerType = String.fromCharCode(tbox >> 24 & 0xff, tbox >> 16 & 0xff, tbox >> 8 & 0xff, tbox & 0xff); warn(`Unsupported header type ${tbox} (${headerType}).`); break; } if (jumpDataLength) { position += dataLength; } } } parseImageProperties(stream) { let newByte = stream.getByte(); while (newByte >= 0) { const oldByte = newByte; newByte = stream.getByte(); const code = oldByte << 8 | newByte; if (code === 0xff51) { stream.skip(4); const Xsiz = stream.getInt32() >>> 0; const Ysiz = stream.getInt32() >>> 0; const XOsiz = stream.getInt32() >>> 0; const YOsiz = stream.getInt32() >>> 0; stream.skip(16); const Csiz = stream.getUint16(); this.width = Xsiz - XOsiz; this.height = Ysiz - YOsiz; this.componentsCount = Csiz; this.bitsPerComponent = 8; return; } } throw new JpxError("No size marker found in JPX stream"); } parseCodestream(data, start, end) { const context = {}; let doNotRecover = false; try { let position = start; while (position + 1 < end) { const code = readUint16(data, position); position += 2; let length = 0, j, sqcd, spqcds, spqcdSize, scalarExpounded, tile; switch (code) { case 0xff4f: context.mainHeader = true; break; case 0xffd9: break; case 0xff51: length = readUint16(data, position); const siz = {}; siz.Xsiz = readUint32(data, position + 4); siz.Ysiz = readUint32(data, position + 8); siz.XOsiz = readUint32(data, position + 12); siz.YOsiz = readUint32(data, position + 16); siz.XTsiz = readUint32(data, position + 20); siz.YTsiz = readUint32(data, position + 24); siz.XTOsiz = readUint32(data, position + 28); siz.YTOsiz = readUint32(data, position + 32); const componentsCount = readUint16(data, position + 36); siz.Csiz = componentsCount; const components = []; j = position + 38; for (let i = 0; i < componentsCount; i++) { const component = { precision: (data[j] & 0x7f) + 1, isSigned: !!(data[j] & 0x80), XRsiz: data[j + 1], YRsiz: data[j + 2] }; j += 3; calculateComponentDimensions(component, siz); components.push(component); } context.SIZ = siz; context.components = components; calculateTileGrids(context, components); context.QCC = []; context.COC = []; break; case 0xff5c: length = readUint16(data, position); const qcd = {}; j = position + 2; sqcd = data[j++]; switch (sqcd & 0x1f) { case 0: spqcdSize = 8; scalarExpounded = true; break; case 1: spqcdSize = 16; scalarExpounded = false; break; case 2: spqcdSize = 16; scalarExpounded = true; break; default: throw new Error("Invalid SQcd value " + sqcd); } qcd.noQuantization = spqcdSize === 8; qcd.scalarExpounded = scalarExpounded; qcd.guardBits = sqcd >> 5; spqcds = []; while (j < length + position) { const spqcd = {}; if (spqcdSize === 8) { spqcd.epsilon = data[j++] >> 3; spqcd.mu = 0; } else { spqcd.epsilon = data[j] >> 3; spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1]; j += 2; } spqcds.push(spqcd); } qcd.SPqcds = spqcds; if (context.mainHeader) { context.QCD = qcd; } else { context.currentTile.QCD = qcd; context.currentTile.QCC = []; } break; case 0xff5d: length = readUint16(data, position); const qcc = {}; j = position + 2; let cqcc; if (context.SIZ.Csiz < 257) { cqcc = data[j++]; } else { cqcc = readUint16(data, j); j += 2; } sqcd = data[j++]; switch (sqcd & 0x1f) { case 0: spqcdSize = 8; scalarExpounded = true; break; case 1: spqcdSize = 16; scalarExpounded = false; break; case 2: spqcdSize = 16; scalarExpounded = true; break; default: throw new Error("Invalid SQcd value " + sqcd); } qcc.noQuantization = spqcdSize === 8; qcc.scalarExpounded = scalarExpounded; qcc.guardBits = sqcd >> 5; spqcds = []; while (j < length + position) { const spqcd = {}; if (spqcdSize === 8) { spqcd.epsilon = data[j++] >> 3; spqcd.mu = 0; } else { spqcd.epsilon = data[j] >> 3; spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1]; j += 2; } spqcds.push(spqcd); } qcc.SPqcds = spqcds; if (context.mainHeader) { context.QCC[cqcc] = qcc; } else { context.currentTile.QCC[cqcc] = qcc; } break; case 0xff52: length = readUint16(data, position); const cod = {}; j = position + 2; const scod = data[j++]; cod.entropyCoderWithCustomPrecincts = !!(scod & 1); cod.sopMarkerUsed = !!(scod & 2); cod.ephMarkerUsed = !!(scod & 4); cod.progressionOrder = data[j++]; cod.layersCount = readUint16(data, j); j += 2; cod.multipleComponentTransform = data[j++]; cod.decompositionLevelsCount = data[j++]; cod.xcb = (data[j++] & 0xf) + 2; cod.ycb = (data[j++] & 0xf) + 2; const blockStyle = data[j++]; cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); cod.resetContextProbabilities = !!(blockStyle & 2); cod.terminationOnEachCodingPass = !!(blockStyle & 4); cod.verticallyStripe = !!(blockStyle & 8); cod.predictableTermination = !!(blockStyle & 16); cod.segmentationSymbolUsed = !!(blockStyle & 32); cod.reversibleTransformation = data[j++]; if (cod.entropyCoderWithCustomPrecincts) { const precinctsSizes = []; while (j < length + position) { const precinctsSize = data[j++]; precinctsSizes.push({ PPx: precinctsSize & 0xf, PPy: precinctsSize >> 4 }); } cod.precinctsSizes = precinctsSizes; } const unsupported = []; if (cod.selectiveArithmeticCodingBypass) { unsupported.push("selectiveArithmeticCodingBypass"); } if (cod.terminationOnEachCodingPass) { unsupported.push("terminationOnEachCodingPass"); } if (cod.verticallyStripe) { unsupported.push("verticallyStripe"); } if (cod.predictableTermination) { unsupported.push("predictableTermination"); } if (unsupported.length > 0) { doNotRecover = true; warn(`JPX: Unsupported COD options (${unsupported.join(", ")}).`); } if (context.mainHeader) { context.COD = cod; } else { context.currentTile.COD = cod; context.currentTile.COC = []; } break; case 0xff90: length = readUint16(data, position); tile = {}; tile.index = readUint16(data, position + 2); tile.length = readUint32(data, position + 4); tile.dataEnd = tile.length + position - 2; tile.partIndex = data[position + 8]; tile.partsCount = data[position + 9]; context.mainHeader = false; if (tile.partIndex === 0) { tile.COD = context.COD; tile.COC = context.COC.slice(0); tile.QCD = context.QCD; tile.QCC = context.QCC.slice(0); } context.currentTile = tile; break; case 0xff93: tile = context.currentTile; if (tile.partIndex === 0) { initializeTile(context, tile.index); buildPackets(context); } length = tile.dataEnd - position; parseTilePackets(context, data, position, length); break; case 0xff53: warn("JPX: Codestream code 0xFF53 (COC) is not implemented."); case 0xff55: case 0xff57: case 0xff58: case 0xff64: length = readUint16(data, position); break; default: throw new Error("Unknown codestream code: " + code.toString(16)); } position += length; } } catch (e) { if (doNotRecover || this.failOnCorruptedImage) { throw new JpxError(e.message); } else { warn(`JPX: Trying to recover from: "${e.message}".`); } } this.tiles = transformComponents(context); this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; this.height = context.SIZ.Ysiz - context.SIZ.YOsiz; this.componentsCount = context.SIZ.Csiz; } } function calculateComponentDimensions(component, siz) { component.x0 = Math.ceil(siz.XOsiz / component.XRsiz); component.x1 = Math.ceil(siz.Xsiz / component.XRsiz); component.y0 = Math.ceil(siz.YOsiz / component.YRsiz); component.y1 = Math.ceil(siz.Ysiz / component.YRsiz); component.width = component.x1 - component.x0; component.height = component.y1 - component.y0; } function calculateTileGrids(context, components) { const siz = context.SIZ; const tiles = []; let tile; const numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); const numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); for (let q = 0; q < numYtiles; q++) { for (let p = 0; p < numXtiles; p++) { tile = {}; tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz); tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz); tile.width = tile.tx1 - tile.tx0; tile.height = tile.ty1 - tile.ty0; tile.components = []; tiles.push(tile); } } context.tiles = tiles; const componentsCount = siz.Csiz; for (let i = 0, ii = componentsCount; i < ii; i++) { const component = components[i]; for (let j = 0, jj = tiles.length; j < jj; j++) { const tileComponent = {}; tile = tiles[j]; tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz); tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz); tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz); tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz); tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0; tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0; tile.components[i] = tileComponent; } } } function getBlocksDimensions(context, component, r) { const codOrCoc = component.codingStyleParameters; const result = {}; if (!codOrCoc.entropyCoderWithCustomPrecincts) { result.PPx = 15; result.PPy = 15; } else { result.PPx = codOrCoc.precinctsSizes[r].PPx; result.PPy = codOrCoc.precinctsSizes[r].PPy; } result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) : Math.min(codOrCoc.xcb, result.PPx); result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) : Math.min(codOrCoc.ycb, result.PPy); return result; } function buildPrecincts(context, resolution, dimensions) { const precinctWidth = 1 << dimensions.PPx; const precinctHeight = 1 << dimensions.PPy; const isZeroRes = resolution.resLevel === 0; const precinctWidthInSubband = 1 << dimensions.PPx + (isZeroRes ? 0 : -1); const precinctHeightInSubband = 1 << dimensions.PPy + (isZeroRes ? 0 : -1); const numprecinctswide = resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - Math.floor(resolution.trx0 / precinctWidth) : 0; const numprecinctshigh = resolution.try1 > resolution.try0 ? Math.ceil(resolution.try1 / precinctHeight) - Math.floor(resolution.try0 / precinctHeight) : 0; const numprecincts = numprecinctswide * numprecinctshigh; resolution.precinctParameters = { precinctWidth, precinctHeight, numprecinctswide, numprecinctshigh, numprecincts, precinctWidthInSubband, precinctHeightInSubband }; } function buildCodeblocks(context, subband, dimensions) { const xcb_ = dimensions.xcb_; const ycb_ = dimensions.ycb_; const codeblockWidth = 1 << xcb_; const codeblockHeight = 1 << ycb_; const cbx0 = subband.tbx0 >> xcb_; const cby0 = subband.tby0 >> ycb_; const cbx1 = subband.tbx1 + codeblockWidth - 1 >> xcb_; const cby1 = subband.tby1 + codeblockHeight - 1 >> ycb_; const precinctParameters = subband.resolution.precinctParameters; const codeblocks = []; const precincts = []; let i, j, codeblock, precinctNumber; for (j = cby0; j < cby1; j++) { for (i = cbx0; i < cbx1; i++) { codeblock = { cbx: i, cby: j, tbx0: codeblockWidth * i, tby0: codeblockHeight * j, tbx1: codeblockWidth * (i + 1), tby1: codeblockHeight * (j + 1) }; codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1); const pi = Math.floor((codeblock.tbx0_ - subband.tbx0) / precinctParameters.precinctWidthInSubband); const pj = Math.floor((codeblock.tby0_ - subband.tby0) / precinctParameters.precinctHeightInSubband); precinctNumber = pi + pj * precinctParameters.numprecinctswide; codeblock.precinctNumber = precinctNumber; codeblock.subbandType = subband.type; codeblock.Lblock = 3; if (codeblock.tbx1_ <= codeblock.tbx0_ || codeblock.tby1_ <= codeblock.tby0_) { continue; } codeblocks.push(codeblock); let precinct = precincts[precinctNumber]; if (precinct !== undefined) { if (i < precinct.cbxMin) { precinct.cbxMin = i; } else if (i > precinct.cbxMax) { precinct.cbxMax = i; } if (j < precinct.cbyMin) { precinct.cbxMin = j; } else if (j > precinct.cbyMax) { precinct.cbyMax = j; } } else { precincts[precinctNumber] = precinct = { cbxMin: i, cbyMin: j, cbxMax: i, cbyMax: j }; } codeblock.precinct = precinct; } } subband.codeblockParameters = { codeblockWidth: xcb_, codeblockHeight: ycb_, numcodeblockwide: cbx1 - cbx0 + 1, numcodeblockhigh: cby1 - cby0 + 1 }; subband.codeblocks = codeblocks; subband.precincts = precincts; } function createPacket(resolution, precinctNumber, layerNumber) { const precinctCodeblocks = []; const subbands = resolution.subbands; for (let i = 0, ii = subbands.length; i < ii; i++) { const subband = subbands[i]; const codeblocks = subband.codeblocks; for (let j = 0, jj = codeblocks.length; j < jj; j++) { const codeblock = codeblocks[j]; if (codeblock.precinctNumber !== precinctNumber) { continue; } precinctCodeblocks.push(codeblock); } } return { layerNumber, codeblocks: precinctCodeblocks }; } function LayerResolutionComponentPositionIterator(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const layersCount = tile.codingStyleDefaultParameters.layersCount; const componentsCount = siz.Csiz; let maxDecompositionLevelsCount = 0; for (let q = 0; q < componentsCount; q++) { maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount); } let l = 0, r = 0, i = 0, k = 0; this.nextPacket = function JpxImage_nextPacket() { for (; l < layersCount; l++) { for (; r <= maxDecompositionLevelsCount; r++) { for (; i < componentsCount; i++) { const component = tile.components[i]; if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[r]; const numprecincts = resolution.precinctParameters.numprecincts; for (; k < numprecincts;) { const packet = createPacket(resolution, k, l); k++; return packet; } k = 0; } i = 0; } r = 0; } throw new JpxError("Out of packets"); }; } function ResolutionLayerComponentPositionIterator(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const layersCount = tile.codingStyleDefaultParameters.layersCount; const componentsCount = siz.Csiz; let maxDecompositionLevelsCount = 0; for (let q = 0; q < componentsCount; q++) { maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount); } let r = 0, l = 0, i = 0, k = 0; this.nextPacket = function JpxImage_nextPacket() { for (; r <= maxDecompositionLevelsCount; r++) { for (; l < layersCount; l++) { for (; i < componentsCount; i++) { const component = tile.components[i]; if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[r]; const numprecincts = resolution.precinctParameters.numprecincts; for (; k < numprecincts;) { const packet = createPacket(resolution, k, l); k++; return packet; } k = 0; } i = 0; } l = 0; } throw new JpxError("Out of packets"); }; } function ResolutionPositionComponentLayerIterator(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const layersCount = tile.codingStyleDefaultParameters.layersCount; const componentsCount = siz.Csiz; let l, r, c, p; let maxDecompositionLevelsCount = 0; for (c = 0; c < componentsCount; c++) { const component = tile.components[c]; maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, component.codingStyleParameters.decompositionLevelsCount); } const maxNumPrecinctsInLevel = new Int32Array(maxDecompositionLevelsCount + 1); for (r = 0; r <= maxDecompositionLevelsCount; ++r) { let maxNumPrecincts = 0; for (c = 0; c < componentsCount; ++c) { const resolutions = tile.components[c].resolutions; if (r < resolutions.length) { maxNumPrecincts = Math.max(maxNumPrecincts, resolutions[r].precinctParameters.numprecincts); } } maxNumPrecinctsInLevel[r] = maxNumPrecincts; } l = 0; r = 0; c = 0; p = 0; this.nextPacket = function JpxImage_nextPacket() { for (; r <= maxDecompositionLevelsCount; r++) { for (; p < maxNumPrecinctsInLevel[r]; p++) { for (; c < componentsCount; c++) { const component = tile.components[c]; if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[r]; const numprecincts = resolution.precinctParameters.numprecincts; if (p >= numprecincts) { continue; } for (; l < layersCount;) { const packet = createPacket(resolution, p, l); l++; return packet; } l = 0; } c = 0; } p = 0; } throw new JpxError("Out of packets"); }; } function PositionComponentResolutionLayerIterator(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const layersCount = tile.codingStyleDefaultParameters.layersCount; const componentsCount = siz.Csiz; const precinctsSizes = getPrecinctSizesInImageScale(tile); const precinctsIterationSizes = precinctsSizes; let l = 0, r = 0, c = 0, px = 0, py = 0; this.nextPacket = function JpxImage_nextPacket() { for (; py < precinctsIterationSizes.maxNumHigh; py++) { for (; px < precinctsIterationSizes.maxNumWide; px++) { for (; c < componentsCount; c++) { const component = tile.components[c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; r <= decompositionLevelsCount; r++) { const resolution = component.resolutions[r]; const sizeInImageScale = precinctsSizes.components[c].resolutions[r]; const k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution); if (k === null) { continue; } for (; l < layersCount;) { const packet = createPacket(resolution, k, l); l++; return packet; } l = 0; } r = 0; } c = 0; } px = 0; } throw new JpxError("Out of packets"); }; } function ComponentPositionResolutionLayerIterator(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const layersCount = tile.codingStyleDefaultParameters.layersCount; const componentsCount = siz.Csiz; const precinctsSizes = getPrecinctSizesInImageScale(tile); let l = 0, r = 0, c = 0, px = 0, py = 0; this.nextPacket = function JpxImage_nextPacket() { for (; c < componentsCount; ++c) { const component = tile.components[c]; const precinctsIterationSizes = precinctsSizes.components[c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; py < precinctsIterationSizes.maxNumHigh; py++) { for (; px < precinctsIterationSizes.maxNumWide; px++) { for (; r <= decompositionLevelsCount; r++) { const resolution = component.resolutions[r]; const sizeInImageScale = precinctsIterationSizes.resolutions[r]; const k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution); if (k === null) { continue; } for (; l < layersCount;) { const packet = createPacket(resolution, k, l); l++; return packet; } l = 0; } r = 0; } px = 0; } py = 0; } throw new JpxError("Out of packets"); }; } function getPrecinctIndexIfExist(pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) { const posX = pxIndex * precinctIterationSizes.minWidth; const posY = pyIndex * precinctIterationSizes.minHeight; if (posX % sizeInImageScale.width !== 0 || posY % sizeInImageScale.height !== 0) { return null; } const startPrecinctRowIndex = posY / sizeInImageScale.width * resolution.precinctParameters.numprecinctswide; return posX / sizeInImageScale.height + startPrecinctRowIndex; } function getPrecinctSizesInImageScale(tile) { const componentsCount = tile.components.length; let minWidth = Number.MAX_VALUE; let minHeight = Number.MAX_VALUE; let maxNumWide = 0; let maxNumHigh = 0; const sizePerComponent = new Array(componentsCount); for (let c = 0; c < componentsCount; c++) { const component = tile.components[c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; const sizePerResolution = new Array(decompositionLevelsCount + 1); let minWidthCurrentComponent = Number.MAX_VALUE; let minHeightCurrentComponent = Number.MAX_VALUE; let maxNumWideCurrentComponent = 0; let maxNumHighCurrentComponent = 0; let scale = 1; for (let r = decompositionLevelsCount; r >= 0; --r) { const resolution = component.resolutions[r]; const widthCurrentResolution = scale * resolution.precinctParameters.precinctWidth; const heightCurrentResolution = scale * resolution.precinctParameters.precinctHeight; minWidthCurrentComponent = Math.min(minWidthCurrentComponent, widthCurrentResolution); minHeightCurrentComponent = Math.min(minHeightCurrentComponent, heightCurrentResolution); maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent, resolution.precinctParameters.numprecinctswide); maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent, resolution.precinctParameters.numprecinctshigh); sizePerResolution[r] = { width: widthCurrentResolution, height: heightCurrentResolution }; scale <<= 1; } minWidth = Math.min(minWidth, minWidthCurrentComponent); minHeight = Math.min(minHeight, minHeightCurrentComponent); maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent); maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent); sizePerComponent[c] = { resolutions: sizePerResolution, minWidth: minWidthCurrentComponent, minHeight: minHeightCurrentComponent, maxNumWide: maxNumWideCurrentComponent, maxNumHigh: maxNumHighCurrentComponent }; } return { components: sizePerComponent, minWidth, minHeight, maxNumWide, maxNumHigh }; } function buildPackets(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const componentsCount = siz.Csiz; for (let c = 0; c < componentsCount; c++) { const component = tile.components[c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; const resolutions = []; const subbands = []; for (let r = 0; r <= decompositionLevelsCount; r++) { const blocksDimensions = getBlocksDimensions(context, component, r); const resolution = {}; const scale = 1 << decompositionLevelsCount - r; resolution.trx0 = Math.ceil(component.tcx0 / scale); resolution.try0 = Math.ceil(component.tcy0 / scale); resolution.trx1 = Math.ceil(component.tcx1 / scale); resolution.try1 = Math.ceil(component.tcy1 / scale); resolution.resLevel = r; buildPrecincts(context, resolution, blocksDimensions); resolutions.push(resolution); let subband; if (r === 0) { subband = {}; subband.type = "LL"; subband.tbx0 = Math.ceil(component.tcx0 / scale); subband.tby0 = Math.ceil(component.tcy0 / scale); subband.tbx1 = Math.ceil(component.tcx1 / scale); subband.tby1 = Math.ceil(component.tcy1 / scale); subband.resolution = resolution; buildCodeblocks(context, subband, blocksDimensions); subbands.push(subband); resolution.subbands = [subband]; } else { const bscale = 1 << decompositionLevelsCount - r + 1; const resolutionSubbands = []; subband = {}; subband.type = "HL"; subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); subband.tby0 = Math.ceil(component.tcy0 / bscale); subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); subband.tby1 = Math.ceil(component.tcy1 / bscale); subband.resolution = resolution; buildCodeblocks(context, subband, blocksDimensions); subbands.push(subband); resolutionSubbands.push(subband); subband = {}; subband.type = "LH"; subband.tbx0 = Math.ceil(component.tcx0 / bscale); subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); subband.tbx1 = Math.ceil(component.tcx1 / bscale); subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); subband.resolution = resolution; buildCodeblocks(context, subband, blocksDimensions); subbands.push(subband); resolutionSubbands.push(subband); subband = {}; subband.type = "HH"; subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); subband.resolution = resolution; buildCodeblocks(context, subband, blocksDimensions); subbands.push(subband); resolutionSubbands.push(subband); resolution.subbands = resolutionSubbands; } } component.resolutions = resolutions; component.subbands = subbands; } const progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; switch (progressionOrder) { case 0: tile.packetsIterator = new LayerResolutionComponentPositionIterator(context); break; case 1: tile.packetsIterator = new ResolutionLayerComponentPositionIterator(context); break; case 2: tile.packetsIterator = new ResolutionPositionComponentLayerIterator(context); break; case 3: tile.packetsIterator = new PositionComponentResolutionLayerIterator(context); break; case 4: tile.packetsIterator = new ComponentPositionResolutionLayerIterator(context); break; default: throw new JpxError(`Unsupported progression order ${progressionOrder}`); } } function parseTilePackets(context, data, offset, dataLength) { let position = 0; let buffer, bufferSize = 0, skipNextBit = false; function readBits(count) { while (bufferSize < count) { const b = data[offset + position]; position++; if (skipNextBit) { buffer = buffer << 7 | b; bufferSize += 7; skipNextBit = false; } else { buffer = buffer << 8 | b; bufferSize += 8; } if (b === 0xff) { skipNextBit = true; } } bufferSize -= count; return buffer >>> bufferSize & (1 << count) - 1; } function skipMarkerIfEqual(value) { if (data[offset + position - 1] === 0xff && data[offset + position] === value) { skipBytes(1); return true; } else if (data[offset + position] === 0xff && data[offset + position + 1] === value) { skipBytes(2); return true; } return false; } function skipBytes(count) { position += count; } function alignToByte() { bufferSize = 0; if (skipNextBit) { position++; skipNextBit = false; } } function readCodingpasses() { if (readBits(1) === 0) { return 1; } if (readBits(1) === 0) { return 2; } let value = readBits(2); if (value < 3) { return value + 3; } value = readBits(5); if (value < 31) { return value + 6; } value = readBits(7); return value + 37; } const tileIndex = context.currentTile.index; const tile = context.tiles[tileIndex]; const sopMarkerUsed = context.COD.sopMarkerUsed; const ephMarkerUsed = context.COD.ephMarkerUsed; const packetsIterator = tile.packetsIterator; while (position < dataLength) { alignToByte(); if (sopMarkerUsed && skipMarkerIfEqual(0x91)) { skipBytes(4); } const packet = packetsIterator.nextPacket(); if (!readBits(1)) { continue; } const layerNumber = packet.layerNumber, queue = []; let codeblock; for (let i = 0, ii = packet.codeblocks.length; i < ii; i++) { codeblock = packet.codeblocks[i]; let precinct = codeblock.precinct; const codeblockColumn = codeblock.cbx - precinct.cbxMin; const codeblockRow = codeblock.cby - precinct.cbyMin; let codeblockIncluded = false; let firstTimeInclusion = false; let valueReady, zeroBitPlanesTree; if (codeblock.included !== undefined) { codeblockIncluded = !!readBits(1); } else { precinct = codeblock.precinct; let inclusionTree; if (precinct.inclusionTree !== undefined) { inclusionTree = precinct.inclusionTree; } else { const width = precinct.cbxMax - precinct.cbxMin + 1; const height = precinct.cbyMax - precinct.cbyMin + 1; inclusionTree = new InclusionTree(width, height, layerNumber); zeroBitPlanesTree = new TagTree(width, height); precinct.inclusionTree = inclusionTree; precinct.zeroBitPlanesTree = zeroBitPlanesTree; for (let l = 0; l < layerNumber; l++) { if (readBits(1) !== 0) { throw new JpxError("Invalid tag tree"); } } } if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) { while (true) { if (readBits(1)) { valueReady = !inclusionTree.nextLevel(); if (valueReady) { codeblock.included = true; codeblockIncluded = firstTimeInclusion = true; break; } } else { inclusionTree.incrementValue(layerNumber); break; } } } } if (!codeblockIncluded) { continue; } if (firstTimeInclusion) { zeroBitPlanesTree = precinct.zeroBitPlanesTree; zeroBitPlanesTree.reset(codeblockColumn, codeblockRow); while (true) { if (readBits(1)) { valueReady = !zeroBitPlanesTree.nextLevel(); if (valueReady) { break; } } else { zeroBitPlanesTree.incrementValue(); } } codeblock.zeroBitPlanes = zeroBitPlanesTree.value; } const codingpasses = readCodingpasses(); while (readBits(1)) { codeblock.Lblock++; } const codingpassesLog2 = log2(codingpasses); const bits = (codingpasses < 1 << codingpassesLog2 ? codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock; const codedDataLength = readBits(bits); queue.push({ codeblock, codingpasses, dataLength: codedDataLength }); } alignToByte(); if (ephMarkerUsed) { skipMarkerIfEqual(0x92); } while (queue.length > 0) { const packetItem = queue.shift(); codeblock = packetItem.codeblock; if (codeblock.data === undefined) { codeblock.data = []; } codeblock.data.push({ data, start: offset + position, end: offset + position + packetItem.dataLength, codingpasses: packetItem.codingpasses }); position += packetItem.dataLength; } } return position; } function copyCoefficients(coefficients, levelWidth, levelHeight, subband, delta, mb, reversible, segmentationSymbolUsed, resetContextProbabilities) { const x0 = subband.tbx0; const y0 = subband.tby0; const width = subband.tbx1 - subband.tbx0; const codeblocks = subband.codeblocks; const right = subband.type.charAt(0) === "H" ? 1 : 0; const bottom = subband.type.charAt(1) === "H" ? levelWidth : 0; for (let i = 0, ii = codeblocks.length; i < ii; ++i) { const codeblock = codeblocks[i]; const blockWidth = codeblock.tbx1_ - codeblock.tbx0_; const blockHeight = codeblock.tby1_ - codeblock.tby0_; if (blockWidth === 0 || blockHeight === 0) { continue; } if (codeblock.data === undefined) { continue; } const bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, codeblock.zeroBitPlanes, mb); let currentCodingpassType = 2; const data = codeblock.data; let totalLength = 0, codingpasses = 0; let j, jj, dataItem; for (j = 0, jj = data.length; j < jj; j++) { dataItem = data[j]; totalLength += dataItem.end - dataItem.start; codingpasses += dataItem.codingpasses; } const encodedData = new Uint8Array(totalLength); let position = 0; for (j = 0, jj = data.length; j < jj; j++) { dataItem = data[j]; const chunk = dataItem.data.subarray(dataItem.start, dataItem.end); encodedData.set(chunk, position); position += chunk.length; } const decoder = new ArithmeticDecoder(encodedData, 0, totalLength); bitModel.setDecoder(decoder); for (j = 0; j < codingpasses; j++) { switch (currentCodingpassType) { case 0: bitModel.runSignificancePropagationPass(); break; case 1: bitModel.runMagnitudeRefinementPass(); break; case 2: bitModel.runCleanupPass(); if (segmentationSymbolUsed) { bitModel.checkSegmentationSymbol(); } break; } if (resetContextProbabilities) { bitModel.reset(); } currentCodingpassType = (currentCodingpassType + 1) % 3; } let offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width; const sign = bitModel.coefficentsSign; const magnitude = bitModel.coefficentsMagnitude; const bitsDecoded = bitModel.bitsDecoded; const magnitudeCorrection = reversible ? 0 : 0.5; let k, n, nb; position = 0; const interleave = subband.type !== "LL"; for (j = 0; j < blockHeight; j++) { const row = offset / width | 0; const levelOffset = 2 * row * (levelWidth - width) + right + bottom; for (k = 0; k < blockWidth; k++) { n = magnitude[position]; if (n !== 0) { n = (n + magnitudeCorrection) * delta; if (sign[position] !== 0) { n = -n; } nb = bitsDecoded[position]; const pos = interleave ? levelOffset + (offset << 1) : offset; coefficients[pos] = reversible && nb >= mb ? n : n * (1 << mb - nb); } offset++; position++; } offset += width - blockWidth; } } } function transformTile(context, tile, c) { const component = tile.components[c]; const codingStyleParameters = component.codingStyleParameters; const quantizationParameters = component.quantizationParameters; const decompositionLevelsCount = codingStyleParameters.decompositionLevelsCount; const spqcds = quantizationParameters.SPqcds; const scalarExpounded = quantizationParameters.scalarExpounded; const guardBits = quantizationParameters.guardBits; const segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; const resetContextProbabilities = codingStyleParameters.resetContextProbabilities; const precision = context.components[c].precision; const reversible = codingStyleParameters.reversibleTransformation; const transform = reversible ? new ReversibleTransform() : new IrreversibleTransform(); const subbandCoefficients = []; let b = 0; for (let i = 0; i <= decompositionLevelsCount; i++) { const resolution = component.resolutions[i]; const width = resolution.trx1 - resolution.trx0; const height = resolution.try1 - resolution.try0; const coefficients = new Float32Array(width * height); for (let j = 0, jj = resolution.subbands.length; j < jj; j++) { let mu, epsilon; if (!scalarExpounded) { mu = spqcds[0].mu; epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0); } else { mu = spqcds[b].mu; epsilon = spqcds[b].epsilon; b++; } const subband = resolution.subbands[j]; const gainLog2 = SubbandsGainLog2[subband.type]; const delta = reversible ? 1 : 2 ** (precision + gainLog2 - epsilon) * (1 + mu / 2048); const mb = guardBits + epsilon - 1; copyCoefficients(coefficients, width, height, subband, delta, mb, reversible, segmentationSymbolUsed, resetContextProbabilities); } subbandCoefficients.push({ width, height, items: coefficients }); } const result = transform.calculate(subbandCoefficients, component.tcx0, component.tcy0); return { left: component.tcx0, top: component.tcy0, width: result.width, height: result.height, items: result.items }; } function transformComponents(context) { const siz = context.SIZ; const components = context.components; const componentsCount = siz.Csiz; const resultImages = []; for (let i = 0, ii = context.tiles.length; i < ii; i++) { const tile = context.tiles[i]; const transformedTiles = []; for (let c = 0; c < componentsCount; c++) { transformedTiles[c] = transformTile(context, tile, c); } const tile0 = transformedTiles[0]; const out = new Uint8ClampedArray(tile0.items.length * componentsCount); const result = { left: tile0.left, top: tile0.top, width: tile0.width, height: tile0.height, items: out }; let shift, offset; let pos = 0, j, jj, y0, y1, y2; if (tile.codingStyleDefaultParameters.multipleComponentTransform) { const fourComponents = componentsCount === 4; const y0items = transformedTiles[0].items; const y1items = transformedTiles[1].items; const y2items = transformedTiles[2].items; const y3items = fourComponents ? transformedTiles[3].items : null; shift = components[0].precision - 8; offset = (128 << shift) + 0.5; const component0 = tile.components[0]; const alpha01 = componentsCount - 3; jj = y0items.length; if (!component0.codingStyleParameters.reversibleTransformation) { for (j = 0; j < jj; j++, pos += alpha01) { y0 = y0items[j] + offset; y1 = y1items[j]; y2 = y2items[j]; out[pos++] = y0 + 1.402 * y2 >> shift; out[pos++] = y0 - 0.34413 * y1 - 0.71414 * y2 >> shift; out[pos++] = y0 + 1.772 * y1 >> shift; } } else { for (j = 0; j < jj; j++, pos += alpha01) { y0 = y0items[j] + offset; y1 = y1items[j]; y2 = y2items[j]; const g = y0 - (y2 + y1 >> 2); out[pos++] = g + y2 >> shift; out[pos++] = g >> shift; out[pos++] = g + y1 >> shift; } } if (fourComponents) { for (j = 0, pos = 3; j < jj; j++, pos += 4) { out[pos] = y3items[j] + offset >> shift; } } } else { for (let c = 0; c < componentsCount; c++) { const items = transformedTiles[c].items; shift = components[c].precision - 8; offset = (128 << shift) + 0.5; for (pos = c, j = 0, jj = items.length; j < jj; j++) { out[pos] = items[j] + offset >> shift; pos += componentsCount; } } } resultImages.push(result); } return resultImages; } function initializeTile(context, tileIndex) { const siz = context.SIZ; const componentsCount = siz.Csiz; const tile = context.tiles[tileIndex]; for (let c = 0; c < componentsCount; c++) { const component = tile.components[c]; const qcdOrQcc = context.currentTile.QCC[c] !== undefined ? context.currentTile.QCC[c] : context.currentTile.QCD; component.quantizationParameters = qcdOrQcc; const codOrCoc = context.currentTile.COC[c] !== undefined ? context.currentTile.COC[c] : context.currentTile.COD; component.codingStyleParameters = codOrCoc; } tile.codingStyleDefaultParameters = context.currentTile.COD; } class TagTree { constructor(width, height) { const levelsLength = log2(Math.max(width, height)) + 1; this.levels = []; for (let i = 0; i < levelsLength; i++) { const level = { width, height, items: [] }; this.levels.push(level); width = Math.ceil(width / 2); height = Math.ceil(height / 2); } } reset(i, j) { let currentLevel = 0, value = 0, level; while (currentLevel < this.levels.length) { level = this.levels[currentLevel]; const index = i + j * level.width; if (level.items[index] !== undefined) { value = level.items[index]; break; } level.index = index; i >>= 1; j >>= 1; currentLevel++; } currentLevel--; level = this.levels[currentLevel]; level.items[level.index] = value; this.currentLevel = currentLevel; delete this.value; } incrementValue() { const level = this.levels[this.currentLevel]; level.items[level.index]++; } nextLevel() { let currentLevel = this.currentLevel; let level = this.levels[currentLevel]; const value = level.items[level.index]; currentLevel--; if (currentLevel < 0) { this.value = value; return false; } this.currentLevel = currentLevel; level = this.levels[currentLevel]; level.items[level.index] = value; return true; } } class InclusionTree { constructor(width, height, defaultValue) { const levelsLength = log2(Math.max(width, height)) + 1; this.levels = []; for (let i = 0; i < levelsLength; i++) { const items = new Uint8Array(width * height); for (let j = 0, jj = items.length; j < jj; j++) { items[j] = defaultValue; } const level = { width, height, items }; this.levels.push(level); width = Math.ceil(width / 2); height = Math.ceil(height / 2); } } reset(i, j, stopValue) { let currentLevel = 0; while (currentLevel < this.levels.length) { const level = this.levels[currentLevel]; const index = i + j * level.width; level.index = index; const value = level.items[index]; if (value === 0xff) { break; } if (value > stopValue) { this.currentLevel = currentLevel; this.propagateValues(); return false; } i >>= 1; j >>= 1; currentLevel++; } this.currentLevel = currentLevel - 1; return true; } incrementValue(stopValue) { const level = this.levels[this.currentLevel]; level.items[level.index] = stopValue + 1; this.propagateValues(); } propagateValues() { let levelIndex = this.currentLevel; let level = this.levels[levelIndex]; const currentValue = level.items[level.index]; while (--levelIndex >= 0) { level = this.levels[levelIndex]; level.items[level.index] = currentValue; } } nextLevel() { let currentLevel = this.currentLevel; let level = this.levels[currentLevel]; const value = level.items[level.index]; level.items[level.index] = 0xff; currentLevel--; if (currentLevel < 0) { return false; } this.currentLevel = currentLevel; level = this.levels[currentLevel]; level.items[level.index] = value; return true; } } class BitModel { static UNIFORM_CONTEXT = 17; static RUNLENGTH_CONTEXT = 18; static LLAndLHContextsLabel = new Uint8Array([0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8]); static HLContextLabel = new Uint8Array([0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8]); static HHContextLabel = new Uint8Array([0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8]); constructor(width, height, subband, zeroBitPlanes, mb) { this.width = width; this.height = height; let contextLabelTable; if (subband === "HH") { contextLabelTable = BitModel.HHContextLabel; } else if (subband === "HL") { contextLabelTable = BitModel.HLContextLabel; } else { contextLabelTable = BitModel.LLAndLHContextsLabel; } this.contextLabelTable = contextLabelTable; const coefficientCount = width * height; this.neighborsSignificance = new Uint8Array(coefficientCount); this.coefficentsSign = new Uint8Array(coefficientCount); let coefficentsMagnitude; if (mb > 14) { coefficentsMagnitude = new Uint32Array(coefficientCount); } else if (mb > 6) { coefficentsMagnitude = new Uint16Array(coefficientCount); } else { coefficentsMagnitude = new Uint8Array(coefficientCount); } this.coefficentsMagnitude = coefficentsMagnitude; this.processingFlags = new Uint8Array(coefficientCount); const bitsDecoded = new Uint8Array(coefficientCount); if (zeroBitPlanes !== 0) { for (let i = 0; i < coefficientCount; i++) { bitsDecoded[i] = zeroBitPlanes; } } this.bitsDecoded = bitsDecoded; this.reset(); } setDecoder(decoder) { this.decoder = decoder; } reset() { this.contexts = new Int8Array(19); this.contexts[0] = 4 << 1 | 0; this.contexts[BitModel.UNIFORM_CONTEXT] = 46 << 1 | 0; this.contexts[BitModel.RUNLENGTH_CONTEXT] = 3 << 1 | 0; } setNeighborsSignificance(row, column, index) { const neighborsSignificance = this.neighborsSignificance; const width = this.width, height = this.height; const left = column > 0; const right = column + 1 < width; let i; if (row > 0) { i = index - width; if (left) { neighborsSignificance[i - 1] += 0x10; } if (right) { neighborsSignificance[i + 1] += 0x10; } neighborsSignificance[i] += 0x04; } if (row + 1 < height) { i = index + width; if (left) { neighborsSignificance[i - 1] += 0x10; } if (right) { neighborsSignificance[i + 1] += 0x10; } neighborsSignificance[i] += 0x04; } if (left) { neighborsSignificance[index - 1] += 0x01; } if (right) { neighborsSignificance[index + 1] += 0x01; } neighborsSignificance[index] |= 0x80; } runSignificancePropagationPass() { const decoder = this.decoder; const width = this.width, height = this.height; const coefficentsMagnitude = this.coefficentsMagnitude; const coefficentsSign = this.coefficentsSign; const neighborsSignificance = this.neighborsSignificance; const processingFlags = this.processingFlags; const contexts = this.contexts; const labels = this.contextLabelTable; const bitsDecoded = this.bitsDecoded; const processedInverseMask = ~1; const processedMask = 1; const firstMagnitudeBitMask = 2; for (let i0 = 0; i0 < height; i0 += 4) { for (let j = 0; j < width; j++) { let index = i0 * width + j; for (let i1 = 0; i1 < 4; i1++, index += width) { const i = i0 + i1; if (i >= height) { break; } processingFlags[index] &= processedInverseMask; if (coefficentsMagnitude[index] || !neighborsSignificance[index]) { continue; } const contextLabel = labels[neighborsSignificance[index]]; const decision = decoder.readBit(contexts, contextLabel); if (decision) { const sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; } bitsDecoded[index]++; processingFlags[index] |= processedMask; } } } } decodeSignBit(row, column, index) { const width = this.width, height = this.height; const coefficentsMagnitude = this.coefficentsMagnitude; const coefficentsSign = this.coefficentsSign; let contribution, sign0, sign1, significance1; let contextLabel, decoded; significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0; if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) { sign1 = coefficentsSign[index + 1]; if (significance1) { sign0 = coefficentsSign[index - 1]; contribution = 1 - sign1 - sign0; } else { contribution = 1 - sign1 - sign1; } } else if (significance1) { sign0 = coefficentsSign[index - 1]; contribution = 1 - sign0 - sign0; } else { contribution = 0; } const horizontalContribution = 3 * contribution; significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0; if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) { sign1 = coefficentsSign[index + width]; if (significance1) { sign0 = coefficentsSign[index - width]; contribution = 1 - sign1 - sign0 + horizontalContribution; } else { contribution = 1 - sign1 - sign1 + horizontalContribution; } } else if (significance1) { sign0 = coefficentsSign[index - width]; contribution = 1 - sign0 - sign0 + horizontalContribution; } else { contribution = horizontalContribution; } if (contribution >= 0) { contextLabel = 9 + contribution; decoded = this.decoder.readBit(this.contexts, contextLabel); } else { contextLabel = 9 - contribution; decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1; } return decoded; } runMagnitudeRefinementPass() { const decoder = this.decoder; const width = this.width, height = this.height; const coefficentsMagnitude = this.coefficentsMagnitude; const neighborsSignificance = this.neighborsSignificance; const contexts = this.contexts; const bitsDecoded = this.bitsDecoded; const processingFlags = this.processingFlags; const processedMask = 1; const firstMagnitudeBitMask = 2; const length = width * height; const width4 = width * 4; for (let index0 = 0, indexNext; index0 < length; index0 = indexNext) { indexNext = Math.min(length, index0 + width4); for (let j = 0; j < width; j++) { for (let index = index0 + j; index < indexNext; index += width) { if (!coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) { continue; } let contextLabel = 16; if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { processingFlags[index] ^= firstMagnitudeBitMask; const significance = neighborsSignificance[index] & 127; contextLabel = significance === 0 ? 15 : 14; } const bit = decoder.readBit(contexts, contextLabel); coefficentsMagnitude[index] = coefficentsMagnitude[index] << 1 | bit; bitsDecoded[index]++; processingFlags[index] |= processedMask; } } } } runCleanupPass() { const decoder = this.decoder; const width = this.width, height = this.height; const neighborsSignificance = this.neighborsSignificance; const coefficentsMagnitude = this.coefficentsMagnitude; const coefficentsSign = this.coefficentsSign; const contexts = this.contexts; const labels = this.contextLabelTable; const bitsDecoded = this.bitsDecoded; const processingFlags = this.processingFlags; const processedMask = 1; const firstMagnitudeBitMask = 2; const oneRowDown = width; const twoRowsDown = width * 2; const threeRowsDown = width * 3; let iNext; for (let i0 = 0; i0 < height; i0 = iNext) { iNext = Math.min(i0 + 4, height); const indexBase = i0 * width; const checkAllEmpty = i0 + 3 < height; for (let j = 0; j < width; j++) { const index0 = indexBase + j; const allEmpty = checkAllEmpty && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && processingFlags[index0 + twoRowsDown] === 0 && processingFlags[index0 + threeRowsDown] === 0 && neighborsSignificance[index0] === 0 && neighborsSignificance[index0 + oneRowDown] === 0 && neighborsSignificance[index0 + twoRowsDown] === 0 && neighborsSignificance[index0 + threeRowsDown] === 0; let i1 = 0, index = index0; let i = i0, sign; if (allEmpty) { const hasSignificantCoefficent = decoder.readBit(contexts, BitModel.RUNLENGTH_CONTEXT); if (!hasSignificantCoefficent) { bitsDecoded[index0]++; bitsDecoded[index0 + oneRowDown]++; bitsDecoded[index0 + twoRowsDown]++; bitsDecoded[index0 + threeRowsDown]++; continue; } i1 = decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT); if (i1 !== 0) { i = i0 + i1; index += i1 * width; } sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; index = index0; for (let i2 = i0; i2 <= i; i2++, index += width) { bitsDecoded[index]++; } i1++; } for (i = i0 + i1; i < iNext; i++, index += width) { if (coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) { continue; } const contextLabel = labels[neighborsSignificance[index]]; const decision = decoder.readBit(contexts, contextLabel); if (decision === 1) { sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; } bitsDecoded[index]++; } } } } checkSegmentationSymbol() { const decoder = this.decoder; const contexts = this.contexts; const symbol = decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT) << 3 | decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT) << 2 | decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, BitModel.UNIFORM_CONTEXT); if (symbol !== 0xa) { throw new JpxError("Invalid segmentation symbol"); } } } class Transform { constructor() { if (this.constructor === Transform) { unreachable("Cannot initialize Transform."); } } calculate(subbands, u0, v0) { let ll = subbands[0]; for (let i = 1, ii = subbands.length; i < ii; i++) { ll = this.iterate(ll, subbands[i], u0, v0); } return ll; } extend(buffer, offset, size) { let i1 = offset - 1, j1 = offset + 1; let i2 = offset + size - 2, j2 = offset + size; buffer[i1--] = buffer[j1++]; buffer[j2++] = buffer[i2--]; buffer[i1--] = buffer[j1++]; buffer[j2++] = buffer[i2--]; buffer[i1--] = buffer[j1++]; buffer[j2++] = buffer[i2--]; buffer[i1] = buffer[j1]; buffer[j2] = buffer[i2]; } filter(x, offset, length) { unreachable("Abstract method `filter` called"); } iterate(ll, hl_lh_hh, u0, v0) { const llWidth = ll.width, llHeight = ll.height; let llItems = ll.items; const width = hl_lh_hh.width; const height = hl_lh_hh.height; const items = hl_lh_hh.items; let i, j, k, l, u, v; for (k = 0, i = 0; i < llHeight; i++) { l = i * 2 * width; for (j = 0; j < llWidth; j++, k++, l += 2) { items[l] = llItems[k]; } } llItems = ll.items = null; const bufferPadding = 4; const rowBuffer = new Float32Array(width + 2 * bufferPadding); if (width === 1) { if ((u0 & 1) !== 0) { for (v = 0, k = 0; v < height; v++, k += width) { items[k] *= 0.5; } } } else { for (v = 0, k = 0; v < height; v++, k += width) { rowBuffer.set(items.subarray(k, k + width), bufferPadding); this.extend(rowBuffer, bufferPadding, width); this.filter(rowBuffer, bufferPadding, width); items.set(rowBuffer.subarray(bufferPadding, bufferPadding + width), k); } } let numBuffers = 16; const colBuffers = []; for (i = 0; i < numBuffers; i++) { colBuffers.push(new Float32Array(height + 2 * bufferPadding)); } let b, currentBuffer = 0; ll = bufferPadding + height; if (height === 1) { if ((v0 & 1) !== 0) { for (u = 0; u < width; u++) { items[u] *= 0.5; } } } else { for (u = 0; u < width; u++) { if (currentBuffer === 0) { numBuffers = Math.min(width - u, numBuffers); for (k = u, l = bufferPadding; l < ll; k += width, l++) { for (b = 0; b < numBuffers; b++) { colBuffers[b][l] = items[k + b]; } } currentBuffer = numBuffers; } currentBuffer--; const buffer = colBuffers[currentBuffer]; this.extend(buffer, bufferPadding, height); this.filter(buffer, bufferPadding, height); if (currentBuffer === 0) { k = u - numBuffers + 1; for (l = bufferPadding; l < ll; k += width, l++) { for (b = 0; b < numBuffers; b++) { items[k + b] = colBuffers[b][l]; } } } } } return { width, height, items }; } } class IrreversibleTransform extends Transform { filter(x, offset, length) { const len = length >> 1; offset |= 0; let j, n, current, next; const alpha = -1.586134342059924; const beta = -0.052980118572961; const gamma = 0.882911075530934; const delta = 0.443506852043971; const K = 1.230174104914001; const K_ = 1 / K; j = offset - 3; for (n = len + 4; n--; j += 2) { x[j] *= K_; } j = offset - 2; current = delta * x[j - 1]; for (n = len + 3; n--; j += 2) { next = delta * x[j + 1]; x[j] = K * x[j] - current - next; if (n--) { j += 2; current = delta * x[j + 1]; x[j] = K * x[j] - current - next; } else { break; } } j = offset - 1; current = gamma * x[j - 1]; for (n = len + 2; n--; j += 2) { next = gamma * x[j + 1]; x[j] -= current + next; if (n--) { j += 2; current = gamma * x[j + 1]; x[j] -= current + next; } else { break; } } j = offset; current = beta * x[j - 1]; for (n = len + 1; n--; j += 2) { next = beta * x[j + 1]; x[j] -= current + next; if (n--) { j += 2; current = beta * x[j + 1]; x[j] -= current + next; } else { break; } } if (len !== 0) { j = offset + 1; current = alpha * x[j - 1]; for (n = len; n--; j += 2) { next = alpha * x[j + 1]; x[j] -= current + next; if (n--) { j += 2; current = alpha * x[j + 1]; x[j] -= current + next; } else { break; } } } } } class ReversibleTransform extends Transform { filter(x, offset, length) { const len = length >> 1; offset |= 0; let j, n; for (j = offset, n = len + 1; n--; j += 2) { x[j] -= x[j - 1] + x[j + 1] + 2 >> 2; } for (j = offset + 1, n = len; n--; j += 2) { x[j] += x[j - 1] + x[j + 1] >> 1; } } } ;// CONCATENATED MODULE: ./src/core/jpx_stream.js class JpxStream extends DecodeStream { constructor(stream, maybeLength, params) { super(maybeLength); this.stream = stream; this.dict = stream.dict; this.maybeLength = maybeLength; this.params = params; } get bytes() { return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); } ensureBuffer(requested) {} readBlock() { if (this.eof) { return; } const jpxImage = new JpxImage(); jpxImage.parse(this.bytes); const width = jpxImage.width; const height = jpxImage.height; const componentsCount = jpxImage.componentsCount; const tileCount = jpxImage.tiles.length; if (tileCount === 1) { this.buffer = jpxImage.tiles[0].items; } else { const data = new Uint8ClampedArray(width * height * componentsCount); for (let k = 0; k < tileCount; k++) { const tileComponents = jpxImage.tiles[k]; const tileWidth = tileComponents.width; const tileHeight = tileComponents.height; const tileLeft = tileComponents.left; const tileTop = tileComponents.top; const src = tileComponents.items; let srcPosition = 0; let dataPosition = (width * tileTop + tileLeft) * componentsCount; const imgRowSize = width * componentsCount; const tileRowSize = tileWidth * componentsCount; for (let j = 0; j < tileHeight; j++) { const rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize); data.set(rowBytes, dataPosition); srcPosition += tileRowSize; dataPosition += imgRowSize; } } this.buffer = data; } this.bufferLength = this.buffer.length; this.eof = true; } } ;// CONCATENATED MODULE: ./src/core/lzw_stream.js class LZWStream extends DecodeStream { constructor(str, maybeLength, earlyChange) { super(maybeLength); this.str = str; this.dict = str.dict; this.cachedData = 0; this.bitsCached = 0; const maxLzwDictionarySize = 4096; const lzwState = { earlyChange, codeLength: 9, nextCode: 258, dictionaryValues: new Uint8Array(maxLzwDictionarySize), dictionaryLengths: new Uint16Array(maxLzwDictionarySize), dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), currentSequence: new Uint8Array(maxLzwDictionarySize), currentSequenceLength: 0 }; for (let i = 0; i < 256; ++i) { lzwState.dictionaryValues[i] = i; lzwState.dictionaryLengths[i] = 1; } this.lzwState = lzwState; } readBits(n) { let bitsCached = this.bitsCached; let cachedData = this.cachedData; while (bitsCached < n) { const c = this.str.getByte(); if (c === -1) { this.eof = true; return null; } cachedData = cachedData << 8 | c; bitsCached += 8; } this.bitsCached = bitsCached -= n; this.cachedData = cachedData; this.lastCode = null; return cachedData >>> bitsCached & (1 << n) - 1; } readBlock() { const blockSize = 512, decodedSizeDelta = blockSize; let estimatedDecodedSize = blockSize * 2; let i, j, q; const lzwState = this.lzwState; if (!lzwState) { return; } const earlyChange = lzwState.earlyChange; let nextCode = lzwState.nextCode; const dictionaryValues = lzwState.dictionaryValues; const dictionaryLengths = lzwState.dictionaryLengths; const dictionaryPrevCodes = lzwState.dictionaryPrevCodes; let codeLength = lzwState.codeLength; let prevCode = lzwState.prevCode; const currentSequence = lzwState.currentSequence; let currentSequenceLength = lzwState.currentSequenceLength; let decodedLength = 0; let currentBufferLength = this.bufferLength; let buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); for (i = 0; i < blockSize; i++) { const code = this.readBits(codeLength); const hasPrev = currentSequenceLength > 0; if (code < 256) { currentSequence[0] = code; currentSequenceLength = 1; } else if (code >= 258) { if (code < nextCode) { currentSequenceLength = dictionaryLengths[code]; for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { currentSequence[j] = dictionaryValues[q]; q = dictionaryPrevCodes[q]; } } else { currentSequence[currentSequenceLength++] = currentSequence[0]; } } else if (code === 256) { codeLength = 9; nextCode = 258; currentSequenceLength = 0; continue; } else { this.eof = true; delete this.lzwState; break; } if (hasPrev) { dictionaryPrevCodes[nextCode] = prevCode; dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; dictionaryValues[nextCode] = currentSequence[0]; nextCode++; codeLength = nextCode + earlyChange & nextCode + earlyChange - 1 ? codeLength : Math.min(Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, 12) | 0; } prevCode = code; decodedLength += currentSequenceLength; if (estimatedDecodedSize < decodedLength) { do { estimatedDecodedSize += decodedSizeDelta; } while (estimatedDecodedSize < decodedLength); buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); } for (j = 0; j < currentSequenceLength; j++) { buffer[currentBufferLength++] = currentSequence[j]; } } lzwState.nextCode = nextCode; lzwState.codeLength = codeLength; lzwState.prevCode = prevCode; lzwState.currentSequenceLength = currentSequenceLength; this.bufferLength = currentBufferLength; } } ;// CONCATENATED MODULE: ./src/core/predictor_stream.js class PredictorStream extends DecodeStream { constructor(str, maybeLength, params) { super(maybeLength); if (!(params instanceof Dict)) { return str; } const predictor = this.predictor = params.get("Predictor") || 1; if (predictor <= 1) { return str; } if (predictor !== 2 && (predictor < 10 || predictor > 15)) { throw new FormatError(`Unsupported predictor: ${predictor}`); } this.readBlock = predictor === 2 ? this.readBlockTiff : this.readBlockPng; this.str = str; this.dict = str.dict; const colors = this.colors = params.get("Colors") || 1; const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8; const columns = this.columns = params.get("Columns") || 1; this.pixBytes = colors * bits + 7 >> 3; this.rowBytes = columns * colors * bits + 7 >> 3; return this; } readBlockTiff() { const rowBytes = this.rowBytes; const bufferLength = this.bufferLength; const buffer = this.ensureBuffer(bufferLength + rowBytes); const bits = this.bits; const colors = this.colors; const rawBytes = this.str.getBytes(rowBytes); this.eof = !rawBytes.length; if (this.eof) { return; } let inbuf = 0, outbuf = 0; let inbits = 0, outbits = 0; let pos = bufferLength; let i; if (bits === 1 && colors === 1) { for (i = 0; i < rowBytes; ++i) { let c = rawBytes[i] ^ inbuf; c ^= c >> 1; c ^= c >> 2; c ^= c >> 4; inbuf = (c & 1) << 7; buffer[pos++] = c; } } else if (bits === 8) { for (i = 0; i < colors; ++i) { buffer[pos++] = rawBytes[i]; } for (; i < rowBytes; ++i) { buffer[pos] = buffer[pos - colors] + rawBytes[i]; pos++; } } else if (bits === 16) { const bytesPerPixel = colors * 2; for (i = 0; i < bytesPerPixel; ++i) { buffer[pos++] = rawBytes[i]; } for (; i < rowBytes; i += 2) { const sum = ((rawBytes[i] & 0xff) << 8) + (rawBytes[i + 1] & 0xff) + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + (buffer[pos - bytesPerPixel + 1] & 0xff); buffer[pos++] = sum >> 8 & 0xff; buffer[pos++] = sum & 0xff; } } else { const compArray = new Uint8Array(colors + 1); const bitMask = (1 << bits) - 1; let j = 0, k = bufferLength; const columns = this.columns; for (i = 0; i < columns; ++i) { for (let kk = 0; kk < colors; ++kk) { if (inbits < bits) { inbuf = inbuf << 8 | rawBytes[j++] & 0xff; inbits += 8; } compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask; inbits -= bits; outbuf = outbuf << bits | compArray[kk]; outbits += bits; if (outbits >= 8) { buffer[k++] = outbuf >> outbits - 8 & 0xff; outbits -= 8; } } } if (outbits > 0) { buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1); } } this.bufferLength += rowBytes; } readBlockPng() { const rowBytes = this.rowBytes; const pixBytes = this.pixBytes; const predictor = this.str.getByte(); const rawBytes = this.str.getBytes(rowBytes); this.eof = !rawBytes.length; if (this.eof) { return; } const bufferLength = this.bufferLength; const buffer = this.ensureBuffer(bufferLength + rowBytes); let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); if (prevRow.length === 0) { prevRow = new Uint8Array(rowBytes); } let i, j = bufferLength, up, c; switch (predictor) { case 0: for (i = 0; i < rowBytes; ++i) { buffer[j++] = rawBytes[i]; } break; case 1: for (i = 0; i < pixBytes; ++i) { buffer[j++] = rawBytes[i]; } for (; i < rowBytes; ++i) { buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xff; j++; } break; case 2: for (i = 0; i < rowBytes; ++i) { buffer[j++] = prevRow[i] + rawBytes[i] & 0xff; } break; case 3: for (i = 0; i < pixBytes; ++i) { buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; } for (; i < rowBytes; ++i) { buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xff; j++; } break; case 4: for (i = 0; i < pixBytes; ++i) { up = prevRow[i]; c = rawBytes[i]; buffer[j++] = up + c; } for (; i < rowBytes; ++i) { up = prevRow[i]; const upLeft = prevRow[i - pixBytes]; const left = buffer[j - pixBytes]; const p = left + up - upLeft; let pa = p - left; if (pa < 0) { pa = -pa; } let pb = p - up; if (pb < 0) { pb = -pb; } let pc = p - upLeft; if (pc < 0) { pc = -pc; } c = rawBytes[i]; if (pa <= pb && pa <= pc) { buffer[j++] = left + c; } else if (pb <= pc) { buffer[j++] = up + c; } else { buffer[j++] = upLeft + c; } } break; default: throw new FormatError(`Unsupported predictor: ${predictor}`); } this.bufferLength += rowBytes; } } ;// CONCATENATED MODULE: ./src/core/run_length_stream.js class RunLengthStream extends DecodeStream { constructor(str, maybeLength) { super(maybeLength); this.str = str; this.dict = str.dict; } readBlock() { const repeatHeader = this.str.getBytes(2); if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { this.eof = true; return; } let buffer; let bufferLength = this.bufferLength; let n = repeatHeader[0]; if (n < 128) { buffer = this.ensureBuffer(bufferLength + n + 1); buffer[bufferLength++] = repeatHeader[1]; if (n > 0) { const source = this.str.getBytes(n); buffer.set(source, bufferLength); bufferLength += n; } } else { n = 257 - n; const b = repeatHeader[1]; buffer = this.ensureBuffer(bufferLength + n + 1); for (let i = 0; i < n; i++) { buffer[bufferLength++] = b; } } this.bufferLength = bufferLength; } } ;// CONCATENATED MODULE: ./src/core/parser.js const MAX_LENGTH_TO_CACHE = 1000; function getInlineImageCacheKey(bytes) { const strBuf = [], ii = bytes.length; let i = 0; while (i < ii - 1) { strBuf.push(bytes[i++] << 8 | bytes[i++]); } if (i < ii) { strBuf.push(bytes[i]); } return ii + "_" + String.fromCharCode.apply(null, strBuf); } class Parser { constructor({ lexer, xref, allowStreams = false, recoveryMode = false }) { this.lexer = lexer; this.xref = xref; this.allowStreams = allowStreams; this.recoveryMode = recoveryMode; this.imageCache = Object.create(null); this._imageId = 0; this.refill(); } refill() { this.buf1 = this.lexer.getObj(); this.buf2 = this.lexer.getObj(); } shift() { if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") { this.buf1 = this.buf2; this.buf2 = null; } else { this.buf1 = this.buf2; this.buf2 = this.lexer.getObj(); } } tryShift() { try { this.shift(); return true; } catch (e) { if (e instanceof MissingDataException) { throw e; } return false; } } getObj(cipherTransform = null) { const buf1 = this.buf1; this.shift(); if (buf1 instanceof Cmd) { switch (buf1.cmd) { case "BI": return this.makeInlineImage(cipherTransform); case "[": const array = []; while (!isCmd(this.buf1, "]") && this.buf1 !== EOF) { array.push(this.getObj(cipherTransform)); } if (this.buf1 === EOF) { if (this.recoveryMode) { return array; } throw new ParserEOFException("End of file inside array."); } this.shift(); return array; case "<<": const dict = new Dict(this.xref); while (!isCmd(this.buf1, ">>") && this.buf1 !== EOF) { if (!(this.buf1 instanceof Name)) { info("Malformed dictionary: key must be a name object"); this.shift(); continue; } const key = this.buf1.name; this.shift(); if (this.buf1 === EOF) { break; } dict.set(key, this.getObj(cipherTransform)); } if (this.buf1 === EOF) { if (this.recoveryMode) { return dict; } throw new ParserEOFException("End of file inside dictionary."); } if (isCmd(this.buf2, "stream")) { return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict; } this.shift(); return dict; default: return buf1; } } if (Number.isInteger(buf1)) { if (Number.isInteger(this.buf1) && isCmd(this.buf2, "R")) { const ref = Ref.get(buf1, this.buf1); this.shift(); this.shift(); return ref; } return buf1; } if (typeof buf1 === "string") { if (cipherTransform) { return cipherTransform.decryptString(buf1); } return buf1; } return buf1; } findDefaultInlineStreamEnd(stream) { const E = 0x45, I = 0x49, SPACE = 0x20, LF = 0xa, CR = 0xd, NUL = 0x0; const { knownCommands } = this.lexer, startPos = stream.pos, n = 15; let state = 0, ch, maybeEIPos; while ((ch = stream.getByte()) !== -1) { if (state === 0) { state = ch === E ? 1 : 0; } else if (state === 1) { state = ch === I ? 2 : 0; } else { if (ch === SPACE || ch === LF || ch === CR) { maybeEIPos = stream.pos; const followingBytes = stream.peekBytes(n); const ii = followingBytes.length; if (ii === 0) { break; } for (let i = 0; i < ii; i++) { ch = followingBytes[i]; if (ch === NUL && followingBytes[i + 1] !== NUL) { continue; } if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7f)) { state = 0; break; } } if (state !== 2) { continue; } if (!knownCommands) { warn("findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined."); continue; } const tmpLexer = new Lexer(new Stream(followingBytes.slice()), knownCommands); tmpLexer._hexStringWarn = () => {}; let numArgs = 0; while (true) { const nextObj = tmpLexer.getObj(); if (nextObj === EOF) { state = 0; break; } if (nextObj instanceof Cmd) { const knownCommand = knownCommands[nextObj.cmd]; if (!knownCommand) { state = 0; break; } else if (knownCommand.variableArgs ? numArgs <= knownCommand.numArgs : numArgs === knownCommand.numArgs) { break; } numArgs = 0; continue; } numArgs++; } if (state === 2) { break; } } else { state = 0; } } } if (ch === -1) { warn("findDefaultInlineStreamEnd: " + "Reached the end of the stream without finding a valid EI marker"); if (maybeEIPos) { warn('... trying to recover by using the last "EI" occurrence.'); stream.skip(-(stream.pos - maybeEIPos)); } } let endOffset = 4; stream.skip(-endOffset); ch = stream.peekByte(); stream.skip(endOffset); if (!isWhiteSpace(ch)) { endOffset--; } return stream.pos - endOffset - startPos; } findDCTDecodeInlineStreamEnd(stream) { const startPos = stream.pos; let foundEOI = false, b, markerLength; while ((b = stream.getByte()) !== -1) { if (b !== 0xff) { continue; } switch (stream.getByte()) { case 0x00: break; case 0xff: stream.skip(-1); break; case 0xd9: foundEOI = true; break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc5: case 0xc6: case 0xc7: case 0xc9: case 0xca: case 0xcb: case 0xcd: case 0xce: case 0xcf: case 0xc4: case 0xcc: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: case 0xfe: markerLength = stream.getUint16(); if (markerLength > 2) { stream.skip(markerLength - 2); } else { stream.skip(-2); } break; } if (foundEOI) { break; } } const length = stream.pos - startPos; if (b === -1) { warn("Inline DCTDecode image stream: " + "EOI marker not found, searching for /EI/ instead."); stream.skip(-length); return this.findDefaultInlineStreamEnd(stream); } this.inlineStreamSkipEI(stream); return length; } findASCII85DecodeInlineStreamEnd(stream) { const TILDE = 0x7e, GT = 0x3e; const startPos = stream.pos; let ch; while ((ch = stream.getByte()) !== -1) { if (ch === TILDE) { const tildePos = stream.pos; ch = stream.peekByte(); while (isWhiteSpace(ch)) { stream.skip(); ch = stream.peekByte(); } if (ch === GT) { stream.skip(); break; } if (stream.pos > tildePos) { const maybeEI = stream.peekBytes(2); if (maybeEI[0] === 0x45 && maybeEI[1] === 0x49) { break; } } } } const length = stream.pos - startPos; if (ch === -1) { warn("Inline ASCII85Decode image stream: " + "EOD marker not found, searching for /EI/ instead."); stream.skip(-length); return this.findDefaultInlineStreamEnd(stream); } this.inlineStreamSkipEI(stream); return length; } findASCIIHexDecodeInlineStreamEnd(stream) { const GT = 0x3e; const startPos = stream.pos; let ch; while ((ch = stream.getByte()) !== -1) { if (ch === GT) { break; } } const length = stream.pos - startPos; if (ch === -1) { warn("Inline ASCIIHexDecode image stream: " + "EOD marker not found, searching for /EI/ instead."); stream.skip(-length); return this.findDefaultInlineStreamEnd(stream); } this.inlineStreamSkipEI(stream); return length; } inlineStreamSkipEI(stream) { const E = 0x45, I = 0x49; let state = 0, ch; while ((ch = stream.getByte()) !== -1) { if (state === 0) { state = ch === E ? 1 : 0; } else if (state === 1) { state = ch === I ? 2 : 0; } else if (state === 2) { break; } } } makeInlineImage(cipherTransform) { const lexer = this.lexer; const stream = lexer.stream; const dictMap = Object.create(null); let dictLength; while (!isCmd(this.buf1, "ID") && this.buf1 !== EOF) { if (!(this.buf1 instanceof Name)) { throw new FormatError("Dictionary key must be a name object"); } const key = this.buf1.name; this.shift(); if (this.buf1 === EOF) { break; } dictMap[key] = this.getObj(cipherTransform); } if (lexer.beginInlineImagePos !== -1) { dictLength = stream.pos - lexer.beginInlineImagePos; } const filter = this.xref.fetchIfRef(dictMap.F || dictMap.Filter); let filterName; if (filter instanceof Name) { filterName = filter.name; } else if (Array.isArray(filter)) { const filterZero = this.xref.fetchIfRef(filter[0]); if (filterZero instanceof Name) { filterName = filterZero.name; } } const startPos = stream.pos; let length; switch (filterName) { case "DCT": case "DCTDecode": length = this.findDCTDecodeInlineStreamEnd(stream); break; case "A85": case "ASCII85Decode": length = this.findASCII85DecodeInlineStreamEnd(stream); break; case "AHx": case "ASCIIHexDecode": length = this.findASCIIHexDecodeInlineStreamEnd(stream); break; default: length = this.findDefaultInlineStreamEnd(stream); } let cacheKey; if (length < MAX_LENGTH_TO_CACHE && dictLength > 0) { const initialStreamPos = stream.pos; stream.pos = lexer.beginInlineImagePos; cacheKey = getInlineImageCacheKey(stream.getBytes(dictLength + length)); stream.pos = initialStreamPos; const cacheEntry = this.imageCache[cacheKey]; if (cacheEntry !== undefined) { this.buf2 = Cmd.get("EI"); this.shift(); cacheEntry.reset(); return cacheEntry; } } const dict = new Dict(this.xref); for (const key in dictMap) { dict.set(key, dictMap[key]); } let imageStream = stream.makeSubStream(startPos, length, dict); if (cipherTransform) { imageStream = cipherTransform.createStream(imageStream, length); } imageStream = this.filter(imageStream, dict, length); imageStream.dict = dict; if (cacheKey !== undefined) { imageStream.cacheKey = `inline_img_${++this._imageId}`; this.imageCache[cacheKey] = imageStream; } this.buf2 = Cmd.get("EI"); this.shift(); return imageStream; } _findStreamLength(startPos, signature) { const { stream } = this.lexer; stream.pos = startPos; const SCAN_BLOCK_LENGTH = 2048; const signatureLength = signature.length; while (stream.pos < stream.end) { const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); const scanLength = scanBytes.length - signatureLength; if (scanLength <= 0) { break; } let pos = 0; while (pos < scanLength) { let j = 0; while (j < signatureLength && scanBytes[pos + j] === signature[j]) { j++; } if (j >= signatureLength) { stream.pos += pos; return stream.pos - startPos; } pos++; } stream.pos += scanLength; } return -1; } makeStream(dict, cipherTransform) { const lexer = this.lexer; let stream = lexer.stream; lexer.skipToNextLine(); const startPos = stream.pos - 1; let length = dict.get("Length"); if (!Number.isInteger(length)) { info(`Bad length "${length && length.toString()}" in stream.`); length = 0; } stream.pos = startPos + length; lexer.nextChar(); if (this.tryShift() && isCmd(this.buf2, "endstream")) { this.shift(); } else { const ENDSTREAM_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d]); let actualLength = this._findStreamLength(startPos, ENDSTREAM_SIGNATURE); if (actualLength < 0) { const MAX_TRUNCATION = 1; for (let i = 1; i <= MAX_TRUNCATION; i++) { const end = ENDSTREAM_SIGNATURE.length - i; const TRUNCATED_SIGNATURE = ENDSTREAM_SIGNATURE.slice(0, end); const maybeLength = this._findStreamLength(startPos, TRUNCATED_SIGNATURE); if (maybeLength >= 0) { const lastByte = stream.peekBytes(end + 1)[end]; if (!isWhiteSpace(lastByte)) { break; } info(`Found "${bytesToString(TRUNCATED_SIGNATURE)}" when ` + "searching for endstream command."); actualLength = maybeLength; break; } } if (actualLength < 0) { throw new FormatError("Missing endstream command."); } } length = actualLength; lexer.nextChar(); this.shift(); this.shift(); } this.shift(); stream = stream.makeSubStream(startPos, length, dict); if (cipherTransform) { stream = cipherTransform.createStream(stream, length); } stream = this.filter(stream, dict, length); stream.dict = dict; return stream; } filter(stream, dict, length) { let filter = dict.get("F", "Filter"); let params = dict.get("DP", "DecodeParms"); if (filter instanceof Name) { if (Array.isArray(params)) { warn("/DecodeParms should not be an Array, when /Filter is a Name."); } return this.makeFilter(stream, filter.name, length, params); } let maybeLength = length; if (Array.isArray(filter)) { const filterArray = filter; const paramsArray = params; for (let i = 0, ii = filterArray.length; i < ii; ++i) { filter = this.xref.fetchIfRef(filterArray[i]); if (!(filter instanceof Name)) { throw new FormatError(`Bad filter name "${filter}"`); } params = null; if (Array.isArray(paramsArray) && i in paramsArray) { params = this.xref.fetchIfRef(paramsArray[i]); } stream = this.makeFilter(stream, filter.name, maybeLength, params); maybeLength = null; } } return stream; } makeFilter(stream, name, maybeLength, params) { if (maybeLength === 0) { warn(`Empty "${name}" stream.`); return new NullStream(); } try { switch (name) { case "Fl": case "FlateDecode": if (params) { return new PredictorStream(new FlateStream(stream, maybeLength), maybeLength, params); } return new FlateStream(stream, maybeLength); case "LZW": case "LZWDecode": let earlyChange = 1; if (params) { if (params.has("EarlyChange")) { earlyChange = params.get("EarlyChange"); } return new PredictorStream(new LZWStream(stream, maybeLength, earlyChange), maybeLength, params); } return new LZWStream(stream, maybeLength, earlyChange); case "DCT": case "DCTDecode": return new JpegStream(stream, maybeLength, params); case "JPX": case "JPXDecode": return new JpxStream(stream, maybeLength, params); case "A85": case "ASCII85Decode": return new Ascii85Stream(stream, maybeLength); case "AHx": case "ASCIIHexDecode": return new AsciiHexStream(stream, maybeLength); case "CCF": case "CCITTFaxDecode": return new CCITTFaxStream(stream, maybeLength, params); case "RL": case "RunLengthDecode": return new RunLengthStream(stream, maybeLength); case "JBIG2Decode": return new Jbig2Stream(stream, maybeLength, params); } warn(`Filter "${name}" is not supported.`); return stream; } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`Invalid stream: "${ex}"`); return new NullStream(); } } } const specialChars = [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 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, 2, 0, 2, 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, 2, 0, 2, 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, 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, 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, 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]; function toHexDigit(ch) { if (ch >= 0x30 && ch <= 0x39) { return ch & 0x0f; } if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) { return (ch & 0x0f) + 9; } return -1; } class Lexer { constructor(stream, knownCommands = null) { this.stream = stream; this.nextChar(); this.strBuf = []; this.knownCommands = knownCommands; this._hexStringNumWarn = 0; this.beginInlineImagePos = -1; } nextChar() { return this.currentChar = this.stream.getByte(); } peekChar() { return this.stream.peekByte(); } getNumber() { let ch = this.currentChar; let eNotation = false; let divideBy = 0; let sign = 1; if (ch === 0x2d) { sign = -1; ch = this.nextChar(); if (ch === 0x2d) { ch = this.nextChar(); } } else if (ch === 0x2b) { ch = this.nextChar(); } if (ch === 0x0a || ch === 0x0d) { do { ch = this.nextChar(); } while (ch === 0x0a || ch === 0x0d); } if (ch === 0x2e) { divideBy = 10; ch = this.nextChar(); } if (ch < 0x30 || ch > 0x39) { const msg = `Invalid number: ${String.fromCharCode(ch)} (charCode ${ch})`; if (isWhiteSpace(ch) || ch === -1) { info(`Lexer.getNumber - "${msg}".`); return 0; } throw new FormatError(msg); } let baseValue = ch - 0x30; let powerValue = 0; let powerValueSign = 1; while ((ch = this.nextChar()) >= 0) { if (ch >= 0x30 && ch <= 0x39) { const currentDigit = ch - 0x30; if (eNotation) { powerValue = powerValue * 10 + currentDigit; } else { if (divideBy !== 0) { divideBy *= 10; } baseValue = baseValue * 10 + currentDigit; } } else if (ch === 0x2e) { if (divideBy === 0) { divideBy = 1; } else { break; } } else if (ch === 0x2d) { warn("Badly formatted number: minus sign in the middle"); } else if (ch === 0x45 || ch === 0x65) { ch = this.peekChar(); if (ch === 0x2b || ch === 0x2d) { powerValueSign = ch === 0x2d ? -1 : 1; this.nextChar(); } else if (ch < 0x30 || ch > 0x39) { break; } eNotation = true; } else { break; } } if (divideBy !== 0) { baseValue /= divideBy; } if (eNotation) { baseValue *= 10 ** (powerValueSign * powerValue); } return sign * baseValue; } getString() { let numParen = 1; let done = false; const strBuf = this.strBuf; strBuf.length = 0; let ch = this.nextChar(); while (true) { let charBuffered = false; switch (ch | 0) { case -1: warn("Unterminated string"); done = true; break; case 0x28: ++numParen; strBuf.push("("); break; case 0x29: if (--numParen === 0) { this.nextChar(); done = true; } else { strBuf.push(")"); } break; case 0x5c: ch = this.nextChar(); switch (ch) { case -1: warn("Unterminated string"); done = true; break; case 0x6e: strBuf.push("\n"); break; case 0x72: strBuf.push("\r"); break; case 0x74: strBuf.push("\t"); break; case 0x62: strBuf.push("\b"); break; case 0x66: strBuf.push("\f"); break; case 0x5c: case 0x28: case 0x29: strBuf.push(String.fromCharCode(ch)); break; case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: let x = ch & 0x0f; ch = this.nextChar(); charBuffered = true; if (ch >= 0x30 && ch <= 0x37) { x = (x << 3) + (ch & 0x0f); ch = this.nextChar(); if (ch >= 0x30 && ch <= 0x37) { charBuffered = false; x = (x << 3) + (ch & 0x0f); } } strBuf.push(String.fromCharCode(x)); break; case 0x0d: if (this.peekChar() === 0x0a) { this.nextChar(); } break; case 0x0a: break; default: strBuf.push(String.fromCharCode(ch)); break; } break; default: strBuf.push(String.fromCharCode(ch)); break; } if (done) { break; } if (!charBuffered) { ch = this.nextChar(); } } return strBuf.join(""); } getName() { let ch, previousCh; const strBuf = this.strBuf; strBuf.length = 0; while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) { if (ch === 0x23) { ch = this.nextChar(); if (specialChars[ch]) { warn("Lexer_getName: " + "NUMBER SIGN (#) should be followed by a hexadecimal number."); strBuf.push("#"); break; } const x = toHexDigit(ch); if (x !== -1) { previousCh = ch; ch = this.nextChar(); const x2 = toHexDigit(ch); if (x2 === -1) { warn(`Lexer_getName: Illegal digit (${String.fromCharCode(ch)}) ` + "in hexadecimal number."); strBuf.push("#", String.fromCharCode(previousCh)); if (specialChars[ch]) { break; } strBuf.push(String.fromCharCode(ch)); continue; } strBuf.push(String.fromCharCode(x << 4 | x2)); } else { strBuf.push("#", String.fromCharCode(ch)); } } else { strBuf.push(String.fromCharCode(ch)); } } if (strBuf.length > 127) { warn(`Name token is longer than allowed by the spec: ${strBuf.length}`); } return Name.get(strBuf.join("")); } _hexStringWarn(ch) { const MAX_HEX_STRING_NUM_WARN = 5; if (this._hexStringNumWarn++ === MAX_HEX_STRING_NUM_WARN) { warn("getHexString - ignoring additional invalid characters."); return; } if (this._hexStringNumWarn > MAX_HEX_STRING_NUM_WARN) { return; } warn(`getHexString - ignoring invalid character: ${ch}`); } getHexString() { const strBuf = this.strBuf; strBuf.length = 0; let ch = this.currentChar; let isFirstHex = true; let firstDigit, secondDigit; this._hexStringNumWarn = 0; while (true) { if (ch < 0) { warn("Unterminated hex string"); break; } else if (ch === 0x3e) { this.nextChar(); break; } else if (specialChars[ch] === 1) { ch = this.nextChar(); continue; } else { if (isFirstHex) { firstDigit = toHexDigit(ch); if (firstDigit === -1) { this._hexStringWarn(ch); ch = this.nextChar(); continue; } } else { secondDigit = toHexDigit(ch); if (secondDigit === -1) { this._hexStringWarn(ch); ch = this.nextChar(); continue; } strBuf.push(String.fromCharCode(firstDigit << 4 | secondDigit)); } isFirstHex = !isFirstHex; ch = this.nextChar(); } } return strBuf.join(""); } getObj() { let comment = false; let ch = this.currentChar; while (true) { if (ch < 0) { return EOF; } if (comment) { if (ch === 0x0a || ch === 0x0d) { comment = false; } } else if (ch === 0x25) { comment = true; } else if (specialChars[ch] !== 1) { break; } ch = this.nextChar(); } switch (ch | 0) { case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x2b: case 0x2d: case 0x2e: return this.getNumber(); case 0x28: return this.getString(); case 0x2f: return this.getName(); case 0x5b: this.nextChar(); return Cmd.get("["); case 0x5d: this.nextChar(); return Cmd.get("]"); case 0x3c: ch = this.nextChar(); if (ch === 0x3c) { this.nextChar(); return Cmd.get("<<"); } return this.getHexString(); case 0x3e: ch = this.nextChar(); if (ch === 0x3e) { this.nextChar(); return Cmd.get(">>"); } return Cmd.get(">"); case 0x7b: this.nextChar(); return Cmd.get("{"); case 0x7d: this.nextChar(); return Cmd.get("}"); case 0x29: this.nextChar(); throw new FormatError(`Illegal character: ${ch}`); } let str = String.fromCharCode(ch); if (ch < 0x20 || ch > 0x7f) { const nextCh = this.peekChar(); if (nextCh >= 0x20 && nextCh <= 0x7f) { this.nextChar(); return Cmd.get(str); } } const knownCommands = this.knownCommands; let knownCommandFound = knownCommands?.[str] !== undefined; while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) { const possibleCommand = str + String.fromCharCode(ch); if (knownCommandFound && knownCommands[possibleCommand] === undefined) { break; } if (str.length === 128) { throw new FormatError(`Command token too long: ${str.length}`); } str = possibleCommand; knownCommandFound = knownCommands?.[str] !== undefined; } if (str === "true") { return true; } if (str === "false") { return false; } if (str === "null") { return null; } if (str === "BI") { this.beginInlineImagePos = this.stream.pos; } return Cmd.get(str); } skipToNextLine() { let ch = this.currentChar; while (ch >= 0) { if (ch === 0x0d) { ch = this.nextChar(); if (ch === 0x0a) { this.nextChar(); } break; } else if (ch === 0x0a) { this.nextChar(); break; } ch = this.nextChar(); } } } class Linearization { static create(stream) { function getInt(linDict, name, allowZeroValue = false) { const obj = linDict.get(name); if (Number.isInteger(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) { return obj; } throw new Error(`The "${name}" parameter in the linearization ` + "dictionary is invalid."); } function getHints(linDict) { const hints = linDict.get("H"); let hintsLength; if (Array.isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) { for (let index = 0; index < hintsLength; index++) { const hint = hints[index]; if (!(Number.isInteger(hint) && hint > 0)) { throw new Error(`Hint (${index}) in the linearization dictionary is invalid.`); } } return hints; } throw new Error("Hint array in the linearization dictionary is invalid."); } const parser = new Parser({ lexer: new Lexer(stream), xref: null }); const obj1 = parser.getObj(); const obj2 = parser.getObj(); const obj3 = parser.getObj(); const linDict = parser.getObj(); let obj, length; if (!(Number.isInteger(obj1) && Number.isInteger(obj2) && isCmd(obj3, "obj") && linDict instanceof Dict && typeof (obj = linDict.get("Linearized")) === "number" && obj > 0)) { return null; } else if ((length = getInt(linDict, "L")) !== stream.length) { throw new Error('The "L" parameter in the linearization dictionary ' + "does not equal the stream length."); } return { length, hints: getHints(linDict), objectNumberFirst: getInt(linDict, "O"), endFirst: getInt(linDict, "E"), numPages: getInt(linDict, "N"), mainXRefEntriesOffset: getInt(linDict, "T"), pageFirst: linDict.has("P") ? getInt(linDict, "P", true) : 0 }; } } ;// CONCATENATED MODULE: ./src/core/cmap.js const BUILT_IN_CMAPS = ["Adobe-GB1-UCS2", "Adobe-CNS1-UCS2", "Adobe-Japan1-UCS2", "Adobe-Korea1-UCS2", "78-EUC-H", "78-EUC-V", "78-H", "78-RKSJ-H", "78-RKSJ-V", "78-V", "78ms-RKSJ-H", "78ms-RKSJ-V", "83pv-RKSJ-H", "90ms-RKSJ-H", "90ms-RKSJ-V", "90msp-RKSJ-H", "90msp-RKSJ-V", "90pv-RKSJ-H", "90pv-RKSJ-V", "Add-H", "Add-RKSJ-H", "Add-RKSJ-V", "Add-V", "Adobe-CNS1-0", "Adobe-CNS1-1", "Adobe-CNS1-2", "Adobe-CNS1-3", "Adobe-CNS1-4", "Adobe-CNS1-5", "Adobe-CNS1-6", "Adobe-GB1-0", "Adobe-GB1-1", "Adobe-GB1-2", "Adobe-GB1-3", "Adobe-GB1-4", "Adobe-GB1-5", "Adobe-Japan1-0", "Adobe-Japan1-1", "Adobe-Japan1-2", "Adobe-Japan1-3", "Adobe-Japan1-4", "Adobe-Japan1-5", "Adobe-Japan1-6", "Adobe-Korea1-0", "Adobe-Korea1-1", "Adobe-Korea1-2", "B5-H", "B5-V", "B5pc-H", "B5pc-V", "CNS-EUC-H", "CNS-EUC-V", "CNS1-H", "CNS1-V", "CNS2-H", "CNS2-V", "ETHK-B5-H", "ETHK-B5-V", "ETen-B5-H", "ETen-B5-V", "ETenms-B5-H", "ETenms-B5-V", "EUC-H", "EUC-V", "Ext-H", "Ext-RKSJ-H", "Ext-RKSJ-V", "Ext-V", "GB-EUC-H", "GB-EUC-V", "GB-H", "GB-V", "GBK-EUC-H", "GBK-EUC-V", "GBK2K-H", "GBK2K-V", "GBKp-EUC-H", "GBKp-EUC-V", "GBT-EUC-H", "GBT-EUC-V", "GBT-H", "GBT-V", "GBTpc-EUC-H", "GBTpc-EUC-V", "GBpc-EUC-H", "GBpc-EUC-V", "H", "HKdla-B5-H", "HKdla-B5-V", "HKdlb-B5-H", "HKdlb-B5-V", "HKgccs-B5-H", "HKgccs-B5-V", "HKm314-B5-H", "HKm314-B5-V", "HKm471-B5-H", "HKm471-B5-V", "HKscs-B5-H", "HKscs-B5-V", "Hankaku", "Hiragana", "KSC-EUC-H", "KSC-EUC-V", "KSC-H", "KSC-Johab-H", "KSC-Johab-V", "KSC-V", "KSCms-UHC-H", "KSCms-UHC-HW-H", "KSCms-UHC-HW-V", "KSCms-UHC-V", "KSCpc-EUC-H", "KSCpc-EUC-V", "Katakana", "NWP-H", "NWP-V", "RKSJ-H", "RKSJ-V", "Roman", "UniCNS-UCS2-H", "UniCNS-UCS2-V", "UniCNS-UTF16-H", "UniCNS-UTF16-V", "UniCNS-UTF32-H", "UniCNS-UTF32-V", "UniCNS-UTF8-H", "UniCNS-UTF8-V", "UniGB-UCS2-H", "UniGB-UCS2-V", "UniGB-UTF16-H", "UniGB-UTF16-V", "UniGB-UTF32-H", "UniGB-UTF32-V", "UniGB-UTF8-H", "UniGB-UTF8-V", "UniJIS-UCS2-H", "UniJIS-UCS2-HW-H", "UniJIS-UCS2-HW-V", "UniJIS-UCS2-V", "UniJIS-UTF16-H", "UniJIS-UTF16-V", "UniJIS-UTF32-H", "UniJIS-UTF32-V", "UniJIS-UTF8-H", "UniJIS-UTF8-V", "UniJIS2004-UTF16-H", "UniJIS2004-UTF16-V", "UniJIS2004-UTF32-H", "UniJIS2004-UTF32-V", "UniJIS2004-UTF8-H", "UniJIS2004-UTF8-V", "UniJISPro-UCS2-HW-V", "UniJISPro-UCS2-V", "UniJISPro-UTF8-V", "UniJISX0213-UTF32-H", "UniJISX0213-UTF32-V", "UniJISX02132004-UTF32-H", "UniJISX02132004-UTF32-V", "UniKS-UCS2-H", "UniKS-UCS2-V", "UniKS-UTF16-H", "UniKS-UTF16-V", "UniKS-UTF32-H", "UniKS-UTF32-V", "UniKS-UTF8-H", "UniKS-UTF8-V", "V", "WP-Symbol"]; const MAX_MAP_RANGE = 2 ** 24 - 1; class CMap { constructor(builtInCMap = false) { this.codespaceRanges = [[], [], [], []]; this.numCodespaceRanges = 0; this._map = []; this.name = ""; this.vertical = false; this.useCMap = null; this.builtInCMap = builtInCMap; } addCodespaceRange(n, low, high) { this.codespaceRanges[n - 1].push(low, high); this.numCodespaceRanges++; } mapCidRange(low, high, dstLow) { if (high - low > MAX_MAP_RANGE) { throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE."); } while (low <= high) { this._map[low++] = dstLow++; } } mapBfRange(low, high, dstLow) { if (high - low > MAX_MAP_RANGE) { throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE."); } const lastByte = dstLow.length - 1; while (low <= high) { this._map[low++] = dstLow; const nextCharCode = dstLow.charCodeAt(lastByte) + 1; if (nextCharCode > 0xff) { dstLow = dstLow.substring(0, lastByte - 1) + String.fromCharCode(dstLow.charCodeAt(lastByte - 1) + 1) + "\x00"; continue; } dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(nextCharCode); } } mapBfRangeToArray(low, high, array) { if (high - low > MAX_MAP_RANGE) { throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE."); } const ii = array.length; let i = 0; while (low <= high && i < ii) { this._map[low] = array[i++]; ++low; } } mapOne(src, dst) { this._map[src] = dst; } lookup(code) { return this._map[code]; } contains(code) { return this._map[code] !== undefined; } forEach(callback) { const map = this._map; const length = map.length; if (length <= 0x10000) { for (let i = 0; i < length; i++) { if (map[i] !== undefined) { callback(i, map[i]); } } } else { for (const i in map) { callback(i, map[i]); } } } charCodeOf(value) { const map = this._map; if (map.length <= 0x10000) { return map.indexOf(value); } for (const charCode in map) { if (map[charCode] === value) { return charCode | 0; } } return -1; } getMap() { return this._map; } readCharCode(str, offset, out) { let c = 0; const codespaceRanges = this.codespaceRanges; for (let n = 0, nn = codespaceRanges.length; n < nn; n++) { c = (c << 8 | str.charCodeAt(offset + n)) >>> 0; const codespaceRange = codespaceRanges[n]; for (let k = 0, kk = codespaceRange.length; k < kk;) { const low = codespaceRange[k++]; const high = codespaceRange[k++]; if (c >= low && c <= high) { out.charcode = c; out.length = n + 1; return; } } } out.charcode = 0; out.length = 1; } getCharCodeLength(charCode) { const codespaceRanges = this.codespaceRanges; for (let n = 0, nn = codespaceRanges.length; n < nn; n++) { const codespaceRange = codespaceRanges[n]; for (let k = 0, kk = codespaceRange.length; k < kk;) { const low = codespaceRange[k++]; const high = codespaceRange[k++]; if (charCode >= low && charCode <= high) { return n + 1; } } } return 1; } get length() { return this._map.length; } get isIdentityCMap() { if (!(this.name === "Identity-H" || this.name === "Identity-V")) { return false; } if (this._map.length !== 0x10000) { return false; } for (let i = 0; i < 0x10000; i++) { if (this._map[i] !== i) { return false; } } return true; } } class IdentityCMap extends CMap { constructor(vertical, n) { super(); this.vertical = vertical; this.addCodespaceRange(n, 0, 0xffff); } mapCidRange(low, high, dstLow) { unreachable("should not call mapCidRange"); } mapBfRange(low, high, dstLow) { unreachable("should not call mapBfRange"); } mapBfRangeToArray(low, high, array) { unreachable("should not call mapBfRangeToArray"); } mapOne(src, dst) { unreachable("should not call mapCidOne"); } lookup(code) { return Number.isInteger(code) && code <= 0xffff ? code : undefined; } contains(code) { return Number.isInteger(code) && code <= 0xffff; } forEach(callback) { for (let i = 0; i <= 0xffff; i++) { callback(i, i); } } charCodeOf(value) { return Number.isInteger(value) && value <= 0xffff ? value : -1; } getMap() { const map = new Array(0x10000); for (let i = 0; i <= 0xffff; i++) { map[i] = i; } return map; } get length() { return 0x10000; } get isIdentityCMap() { unreachable("should not access .isIdentityCMap"); } } function strToInt(str) { let a = 0; for (let i = 0; i < str.length; i++) { a = a << 8 | str.charCodeAt(i); } return a >>> 0; } function expectString(obj) { if (typeof obj !== "string") { throw new FormatError("Malformed CMap: expected string."); } } function expectInt(obj) { if (!Number.isInteger(obj)) { throw new FormatError("Malformed CMap: expected int."); } } function parseBfChar(cMap, lexer) { while (true) { let obj = lexer.getObj(); if (obj === EOF) { break; } if (isCmd(obj, "endbfchar")) { return; } expectString(obj); const src = strToInt(obj); obj = lexer.getObj(); expectString(obj); const dst = obj; cMap.mapOne(src, dst); } } function parseBfRange(cMap, lexer) { while (true) { let obj = lexer.getObj(); if (obj === EOF) { break; } if (isCmd(obj, "endbfrange")) { return; } expectString(obj); const low = strToInt(obj); obj = lexer.getObj(); expectString(obj); const high = strToInt(obj); obj = lexer.getObj(); if (Number.isInteger(obj) || typeof obj === "string") { const dstLow = Number.isInteger(obj) ? String.fromCharCode(obj) : obj; cMap.mapBfRange(low, high, dstLow); } else if (isCmd(obj, "[")) { obj = lexer.getObj(); const array = []; while (!isCmd(obj, "]") && obj !== EOF) { array.push(obj); obj = lexer.getObj(); } cMap.mapBfRangeToArray(low, high, array); } else { break; } } throw new FormatError("Invalid bf range."); } function parseCidChar(cMap, lexer) { while (true) { let obj = lexer.getObj(); if (obj === EOF) { break; } if (isCmd(obj, "endcidchar")) { return; } expectString(obj); const src = strToInt(obj); obj = lexer.getObj(); expectInt(obj); const dst = obj; cMap.mapOne(src, dst); } } function parseCidRange(cMap, lexer) { while (true) { let obj = lexer.getObj(); if (obj === EOF) { break; } if (isCmd(obj, "endcidrange")) { return; } expectString(obj); const low = strToInt(obj); obj = lexer.getObj(); expectString(obj); const high = strToInt(obj); obj = lexer.getObj(); expectInt(obj); const dstLow = obj; cMap.mapCidRange(low, high, dstLow); } } function parseCodespaceRange(cMap, lexer) { while (true) { let obj = lexer.getObj(); if (obj === EOF) { break; } if (isCmd(obj, "endcodespacerange")) { return; } if (typeof obj !== "string") { break; } const low = strToInt(obj); obj = lexer.getObj(); if (typeof obj !== "string") { break; } const high = strToInt(obj); cMap.addCodespaceRange(obj.length, low, high); } throw new FormatError("Invalid codespace range."); } function parseWMode(cMap, lexer) { const obj = lexer.getObj(); if (Number.isInteger(obj)) { cMap.vertical = !!obj; } } function parseCMapName(cMap, lexer) { const obj = lexer.getObj(); if (obj instanceof Name) { cMap.name = obj.name; } } async function parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap) { let previous, embeddedUseCMap; objLoop: while (true) { try { const obj = lexer.getObj(); if (obj === EOF) { break; } else if (obj instanceof Name) { if (obj.name === "WMode") { parseWMode(cMap, lexer); } else if (obj.name === "CMapName") { parseCMapName(cMap, lexer); } previous = obj; } else if (obj instanceof Cmd) { switch (obj.cmd) { case "endcmap": break objLoop; case "usecmap": if (previous instanceof Name) { embeddedUseCMap = previous.name; } break; case "begincodespacerange": parseCodespaceRange(cMap, lexer); break; case "beginbfchar": parseBfChar(cMap, lexer); break; case "begincidchar": parseCidChar(cMap, lexer); break; case "beginbfrange": parseBfRange(cMap, lexer); break; case "begincidrange": parseCidRange(cMap, lexer); break; } } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Invalid cMap data: " + ex); continue; } } if (!useCMap && embeddedUseCMap) { useCMap = embeddedUseCMap; } if (useCMap) { return extendCMap(cMap, fetchBuiltInCMap, useCMap); } return cMap; } async function extendCMap(cMap, fetchBuiltInCMap, useCMap) { cMap.useCMap = await createBuiltInCMap(useCMap, fetchBuiltInCMap); if (cMap.numCodespaceRanges === 0) { const useCodespaceRanges = cMap.useCMap.codespaceRanges; for (let i = 0; i < useCodespaceRanges.length; i++) { cMap.codespaceRanges[i] = useCodespaceRanges[i].slice(); } cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges; } cMap.useCMap.forEach(function (key, value) { if (!cMap.contains(key)) { cMap.mapOne(key, cMap.useCMap.lookup(key)); } }); return cMap; } async function createBuiltInCMap(name, fetchBuiltInCMap) { if (name === "Identity-H") { return new IdentityCMap(false, 2); } else if (name === "Identity-V") { return new IdentityCMap(true, 2); } if (!BUILT_IN_CMAPS.includes(name)) { throw new Error("Unknown CMap name: " + name); } if (!fetchBuiltInCMap) { throw new Error("Built-in CMap parameters are not provided."); } const { cMapData, compressionType } = await fetchBuiltInCMap(name); const cMap = new CMap(true); if (compressionType === CMapCompressionType.BINARY) { return new BinaryCMapReader().process(cMapData, cMap, useCMap => { return extendCMap(cMap, fetchBuiltInCMap, useCMap); }); } if (compressionType === CMapCompressionType.NONE) { const lexer = new Lexer(new Stream(cMapData)); return parseCMap(cMap, lexer, fetchBuiltInCMap, null); } throw new Error(`Invalid CMap "compressionType" value: ${compressionType}`); } class CMapFactory { static async create({ encoding, fetchBuiltInCMap, useCMap }) { if (encoding instanceof Name) { return createBuiltInCMap(encoding.name, fetchBuiltInCMap); } else if (encoding instanceof BaseStream) { const parsedCMap = await parseCMap(new CMap(), new Lexer(encoding), fetchBuiltInCMap, useCMap); if (parsedCMap.isIdentityCMap) { return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap); } return parsedCMap; } throw new Error("Encoding required."); } } ;// CONCATENATED MODULE: ./src/core/charsets.js const ISOAdobeCharset = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron"]; const ExpertCharset = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"]; const ExpertSubsetCharset = [".notdef", "space", "dollaroldstyle", "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior"]; ;// CONCATENATED MODULE: ./src/core/encodings.js const ExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "", "", "", "isuperior", "", "", "lsuperior", "msuperior", "nsuperior", "osuperior", "", "", "rsuperior", "ssuperior", "tsuperior", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdownsmall", "centoldstyle", "Lslashsmall", "", "", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "", "Dotaccentsmall", "", "", "Macronsmall", "", "", "figuredash", "hypheninferior", "", "", "Ogoneksmall", "Ringsmall", "Cedillasmall", "", "", "", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"]; const MacExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "centoldstyle", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "", "threequartersemdash", "", "questionsmall", "", "", "", "", "Ethsmall", "", "", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "", "", "", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hypheninferior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "asuperior", "centsuperior", "", "", "", "", "Aacutesmall", "Agravesmall", "Acircumflexsmall", "Adieresissmall", "Atildesmall", "Aringsmall", "Ccedillasmall", "Eacutesmall", "Egravesmall", "Ecircumflexsmall", "Edieresissmall", "Iacutesmall", "Igravesmall", "Icircumflexsmall", "Idieresissmall", "Ntildesmall", "Oacutesmall", "Ogravesmall", "Ocircumflexsmall", "Odieresissmall", "Otildesmall", "Uacutesmall", "Ugravesmall", "Ucircumflexsmall", "Udieresissmall", "", "eightsuperior", "fourinferior", "threeinferior", "sixinferior", "eightinferior", "seveninferior", "Scaronsmall", "", "centinferior", "twoinferior", "", "Dieresissmall", "", "Caronsmall", "osuperior", "fiveinferior", "", "commainferior", "periodinferior", "Yacutesmall", "", "dollarinferior", "", "", "Thornsmall", "", "nineinferior", "zeroinferior", "Zcaronsmall", "AEsmall", "Oslashsmall", "questiondownsmall", "oneinferior", "Lslashsmall", "", "", "", "", "", "", "Cedillasmall", "", "", "", "", "", "OEsmall", "figuredash", "hyphensuperior", "", "", "", "", "exclamdownsmall", "", "Ydieresissmall", "", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "ninesuperior", "zerosuperior", "", "esuperior", "rsuperior", "tsuperior", "", "", "isuperior", "ssuperior", "dsuperior", "", "", "", "", "", "lsuperior", "Ogoneksmall", "Brevesmall", "Macronsmall", "bsuperior", "nsuperior", "msuperior", "commasuperior", "periodsuperior", "Dotaccentsmall", "Ringsmall", "", "", "", ""]; const MacRomanEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron"]; const StandardEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "", "endash", "dagger", "daggerdbl", "periodcentered", "", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "", "questiondown", "", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "", "ring", "cedilla", "", "hungarumlaut", "ogonek", "caron", "emdash", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "AE", "", "ordfeminine", "", "", "", "", "Lslash", "Oslash", "OE", "ordmasculine", "", "", "", "", "", "ae", "", "", "", "dotlessi", "", "", "lslash", "oslash", "oe", "germandbls", "", "", "", ""]; const WinAnsiEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "bullet", "Euro", "bullet", "quotesinglbase", "florin", "quotedblbase", "ellipsis", "dagger", "daggerdbl", "circumflex", "perthousand", "Scaron", "guilsinglleft", "OE", "bullet", "Zcaron", "bullet", "bullet", "quoteleft", "quoteright", "quotedblleft", "quotedblright", "bullet", "endash", "emdash", "tilde", "trademark", "scaron", "guilsinglright", "oe", "bullet", "zcaron", "Ydieresis", "space", "exclamdown", "cent", "sterling", "currency", "yen", "brokenbar", "section", "dieresis", "copyright", "ordfeminine", "guillemotleft", "logicalnot", "hyphen", "registered", "macron", "degree", "plusminus", "twosuperior", "threesuperior", "acute", "mu", "paragraph", "periodcentered", "cedilla", "onesuperior", "ordmasculine", "guillemotright", "onequarter", "onehalf", "threequarters", "questiondown", "Agrave", "Aacute", "Acircumflex", "Atilde", "Adieresis", "Aring", "AE", "Ccedilla", "Egrave", "Eacute", "Ecircumflex", "Edieresis", "Igrave", "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde", "Ograve", "Oacute", "Ocircumflex", "Otilde", "Odieresis", "multiply", "Oslash", "Ugrave", "Uacute", "Ucircumflex", "Udieresis", "Yacute", "Thorn", "germandbls", "agrave", "aacute", "acircumflex", "atilde", "adieresis", "aring", "ae", "ccedilla", "egrave", "eacute", "ecircumflex", "edieresis", "igrave", "iacute", "icircumflex", "idieresis", "eth", "ntilde", "ograve", "oacute", "ocircumflex", "otilde", "odieresis", "divide", "oslash", "ugrave", "uacute", "ucircumflex", "udieresis", "yacute", "thorn", "ydieresis"]; const SymbolSetEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "universal", "numbersign", "existential", "percent", "ampersand", "suchthat", "parenleft", "parenright", "asteriskmath", "plus", "comma", "minus", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "congruent", "Alpha", "Beta", "Chi", "Delta", "Epsilon", "Phi", "Gamma", "Eta", "Iota", "theta1", "Kappa", "Lambda", "Mu", "Nu", "Omicron", "Pi", "Theta", "Rho", "Sigma", "Tau", "Upsilon", "sigma1", "Omega", "Xi", "Psi", "Zeta", "bracketleft", "therefore", "bracketright", "perpendicular", "underscore", "radicalex", "alpha", "beta", "chi", "delta", "epsilon", "phi", "gamma", "eta", "iota", "phi1", "kappa", "lambda", "mu", "nu", "omicron", "pi", "theta", "rho", "sigma", "tau", "upsilon", "omega1", "omega", "xi", "psi", "zeta", "braceleft", "bar", "braceright", "similar", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Euro", "Upsilon1", "minute", "lessequal", "fraction", "infinity", "florin", "club", "diamond", "heart", "spade", "arrowboth", "arrowleft", "arrowup", "arrowright", "arrowdown", "degree", "plusminus", "second", "greaterequal", "multiply", "proportional", "partialdiff", "bullet", "divide", "notequal", "equivalence", "approxequal", "ellipsis", "arrowvertex", "arrowhorizex", "carriagereturn", "aleph", "Ifraktur", "Rfraktur", "weierstrass", "circlemultiply", "circleplus", "emptyset", "intersection", "union", "propersuperset", "reflexsuperset", "notsubset", "propersubset", "reflexsubset", "element", "notelement", "angle", "gradient", "registerserif", "copyrightserif", "trademarkserif", "product", "radical", "dotmath", "logicalnot", "logicaland", "logicalor", "arrowdblboth", "arrowdblleft", "arrowdblup", "arrowdblright", "arrowdbldown", "lozenge", "angleleft", "registersans", "copyrightsans", "trademarksans", "summation", "parenlefttp", "parenleftex", "parenleftbt", "bracketlefttp", "bracketleftex", "bracketleftbt", "bracelefttp", "braceleftmid", "braceleftbt", "braceex", "", "angleright", "integral", "integraltp", "integralex", "integralbt", "parenrighttp", "parenrightex", "parenrightbt", "bracketrighttp", "bracketrightex", "bracketrightbt", "bracerighttp", "bracerightmid", "bracerightbt", ""]; const ZapfDingbatsEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "a1", "a2", "a202", "a3", "a4", "a5", "a119", "a118", "a117", "a11", "a12", "a13", "a14", "a15", "a16", "a105", "a17", "a18", "a19", "a20", "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a6", "a7", "a8", "a9", "a10", "a29", "a30", "a31", "a32", "a33", "a34", "a35", "a36", "a37", "a38", "a39", "a40", "a41", "a42", "a43", "a44", "a45", "a46", "a47", "a48", "a49", "a50", "a51", "a52", "a53", "a54", "a55", "a56", "a57", "a58", "a59", "a60", "a61", "a62", "a63", "a64", "a65", "a66", "a67", "a68", "a69", "a70", "a71", "a72", "a73", "a74", "a203", "a75", "a204", "a76", "a77", "a78", "a79", "a81", "a82", "a83", "a84", "a97", "a98", "a99", "a100", "", "a89", "a90", "a93", "a94", "a91", "a92", "a205", "a85", "a206", "a86", "a87", "a88", "a95", "a96", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "a101", "a102", "a103", "a104", "a106", "a107", "a108", "a112", "a111", "a110", "a109", "a120", "a121", "a122", "a123", "a124", "a125", "a126", "a127", "a128", "a129", "a130", "a131", "a132", "a133", "a134", "a135", "a136", "a137", "a138", "a139", "a140", "a141", "a142", "a143", "a144", "a145", "a146", "a147", "a148", "a149", "a150", "a151", "a152", "a153", "a154", "a155", "a156", "a157", "a158", "a159", "a160", "a161", "a163", "a164", "a196", "a165", "a192", "a166", "a167", "a168", "a169", "a170", "a171", "a172", "a173", "a162", "a174", "a175", "a176", "a177", "a178", "a179", "a193", "a180", "a199", "a181", "a200", "a182", "", "a201", "a183", "a184", "a197", "a185", "a194", "a198", "a186", "a195", "a187", "a188", "a189", "a190", "a191", ""]; function getEncoding(encodingName) { switch (encodingName) { case "WinAnsiEncoding": return WinAnsiEncoding; case "StandardEncoding": return StandardEncoding; case "MacRomanEncoding": return MacRomanEncoding; case "SymbolSetEncoding": return SymbolSetEncoding; case "ZapfDingbatsEncoding": return ZapfDingbatsEncoding; case "ExpertEncoding": return ExpertEncoding; case "MacExpertEncoding": return MacExpertEncoding; default: return null; } } ;// CONCATENATED MODULE: ./src/core/cff_parser.js const MAX_SUBR_NESTING = 10; const CFFStandardStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold"]; const NUM_STANDARD_CFF_STRINGS = 391; const CharstringValidationData = [null, { id: "hstem", min: 2, stackClearing: true, stem: true }, null, { id: "vstem", min: 2, stackClearing: true, stem: true }, { id: "vmoveto", min: 1, stackClearing: true }, { id: "rlineto", min: 2, resetStack: true }, { id: "hlineto", min: 1, resetStack: true }, { id: "vlineto", min: 1, resetStack: true }, { id: "rrcurveto", min: 6, resetStack: true }, null, { id: "callsubr", min: 1, undefStack: true }, { id: "return", min: 0, undefStack: true }, null, null, { id: "endchar", min: 0, stackClearing: true }, null, null, null, { id: "hstemhm", min: 2, stackClearing: true, stem: true }, { id: "hintmask", min: 0, stackClearing: true }, { id: "cntrmask", min: 0, stackClearing: true }, { id: "rmoveto", min: 2, stackClearing: true }, { id: "hmoveto", min: 1, stackClearing: true }, { id: "vstemhm", min: 2, stackClearing: true, stem: true }, { id: "rcurveline", min: 8, resetStack: true }, { id: "rlinecurve", min: 8, resetStack: true }, { id: "vvcurveto", min: 4, resetStack: true }, { id: "hhcurveto", min: 4, resetStack: true }, null, { id: "callgsubr", min: 1, undefStack: true }, { id: "vhcurveto", min: 4, resetStack: true }, { id: "hvcurveto", min: 4, resetStack: true }]; const CharstringValidationData12 = [null, null, null, { id: "and", min: 2, stackDelta: -1 }, { id: "or", min: 2, stackDelta: -1 }, { id: "not", min: 1, stackDelta: 0 }, null, null, null, { id: "abs", min: 1, stackDelta: 0 }, { id: "add", min: 2, stackDelta: -1, stackFn(stack, index) { stack[index - 2] = stack[index - 2] + stack[index - 1]; } }, { id: "sub", min: 2, stackDelta: -1, stackFn(stack, index) { stack[index - 2] = stack[index - 2] - stack[index - 1]; } }, { id: "div", min: 2, stackDelta: -1, stackFn(stack, index) { stack[index - 2] = stack[index - 2] / stack[index - 1]; } }, null, { id: "neg", min: 1, stackDelta: 0, stackFn(stack, index) { stack[index - 1] = -stack[index - 1]; } }, { id: "eq", min: 2, stackDelta: -1 }, null, null, { id: "drop", min: 1, stackDelta: -1 }, null, { id: "put", min: 2, stackDelta: -2 }, { id: "get", min: 1, stackDelta: 0 }, { id: "ifelse", min: 4, stackDelta: -3 }, { id: "random", min: 0, stackDelta: 1 }, { id: "mul", min: 2, stackDelta: -1, stackFn(stack, index) { stack[index - 2] = stack[index - 2] * stack[index - 1]; } }, null, { id: "sqrt", min: 1, stackDelta: 0 }, { id: "dup", min: 1, stackDelta: 1 }, { id: "exch", min: 2, stackDelta: 0 }, { id: "index", min: 2, stackDelta: 0 }, { id: "roll", min: 3, stackDelta: -2 }, null, null, null, { id: "hflex", min: 7, resetStack: true }, { id: "flex", min: 13, resetStack: true }, { id: "hflex1", min: 9, resetStack: true }, { id: "flex1", min: 11, resetStack: true }]; class CFFParser { constructor(file, properties, seacAnalysisEnabled) { this.bytes = file.getBytes(); this.properties = properties; this.seacAnalysisEnabled = !!seacAnalysisEnabled; } parse() { const properties = this.properties; const cff = new CFF(); this.cff = cff; const header = this.parseHeader(); const nameIndex = this.parseIndex(header.endPos); const topDictIndex = this.parseIndex(nameIndex.endPos); const stringIndex = this.parseIndex(topDictIndex.endPos); const globalSubrIndex = this.parseIndex(stringIndex.endPos); const topDictParsed = this.parseDict(topDictIndex.obj.get(0)); const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings); cff.header = header.obj; cff.names = this.parseNameIndex(nameIndex.obj); cff.strings = this.parseStringIndex(stringIndex.obj); cff.topDict = topDict; cff.globalSubrIndex = globalSubrIndex.obj; this.parsePrivateDict(cff.topDict); cff.isCIDFont = topDict.hasName("ROS"); const charStringOffset = topDict.getByName("CharStrings"); const charStringIndex = this.parseIndex(charStringOffset).obj; const fontMatrix = topDict.getByName("FontMatrix"); if (fontMatrix) { properties.fontMatrix = fontMatrix; } const fontBBox = topDict.getByName("FontBBox"); if (fontBBox) { properties.ascent = Math.max(fontBBox[3], fontBBox[1]); properties.descent = Math.min(fontBBox[1], fontBBox[3]); properties.ascentScaled = true; } let charset, encoding; if (cff.isCIDFont) { const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj; for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) { const dictRaw = fdArrayIndex.get(i); const fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), cff.strings); this.parsePrivateDict(fontDict); cff.fdArray.push(fontDict); } encoding = null; charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, true); cff.fdSelect = this.parseFDSelect(topDict.getByName("FDSelect"), charStringIndex.count); } else { charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, false); encoding = this.parseEncoding(topDict.getByName("Encoding"), properties, cff.strings, charset.charset); } cff.charset = charset; cff.encoding = encoding; const charStringsAndSeacs = this.parseCharStrings({ charStrings: charStringIndex, localSubrIndex: topDict.privateDict.subrsIndex, globalSubrIndex: globalSubrIndex.obj, fdSelect: cff.fdSelect, fdArray: cff.fdArray, privateDict: topDict.privateDict }); cff.charStrings = charStringsAndSeacs.charStrings; cff.seacs = charStringsAndSeacs.seacs; cff.widths = charStringsAndSeacs.widths; return cff; } parseHeader() { let bytes = this.bytes; const bytesLength = bytes.length; let offset = 0; while (offset < bytesLength && bytes[offset] !== 1) { ++offset; } if (offset >= bytesLength) { throw new FormatError("Invalid CFF header"); } if (offset !== 0) { info("cff data is shifted"); bytes = bytes.subarray(offset); this.bytes = bytes; } const major = bytes[0]; const minor = bytes[1]; const hdrSize = bytes[2]; const offSize = bytes[3]; const header = new CFFHeader(major, minor, hdrSize, offSize); return { obj: header, endPos: hdrSize }; } parseDict(dict) { let pos = 0; function parseOperand() { let value = dict[pos++]; if (value === 30) { return parseFloatOperand(); } else if (value === 28) { value = dict[pos++]; value = (value << 24 | dict[pos++] << 16) >> 16; return value; } else if (value === 29) { value = dict[pos++]; value = value << 8 | dict[pos++]; value = value << 8 | dict[pos++]; value = value << 8 | dict[pos++]; return value; } else if (value >= 32 && value <= 246) { return value - 139; } else if (value >= 247 && value <= 250) { return (value - 247) * 256 + dict[pos++] + 108; } else if (value >= 251 && value <= 254) { return -((value - 251) * 256) - dict[pos++] - 108; } warn('CFFParser_parseDict: "' + value + '" is a reserved command.'); return NaN; } function parseFloatOperand() { let str = ""; const eof = 15; const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "E", "E-", null, "-"]; const length = dict.length; while (pos < length) { const b = dict[pos++]; const b1 = b >> 4; const b2 = b & 15; if (b1 === eof) { break; } str += lookup[b1]; if (b2 === eof) { break; } str += lookup[b2]; } return parseFloat(str); } let operands = []; const entries = []; pos = 0; const end = dict.length; while (pos < end) { let b = dict[pos]; if (b <= 21) { if (b === 12) { b = b << 8 | dict[++pos]; } entries.push([b, operands]); operands = []; ++pos; } else { operands.push(parseOperand()); } } return entries; } parseIndex(pos) { const cffIndex = new CFFIndex(); const bytes = this.bytes; const count = bytes[pos++] << 8 | bytes[pos++]; const offsets = []; let end = pos; let i, ii; if (count !== 0) { const offsetSize = bytes[pos++]; const startPos = pos + (count + 1) * offsetSize - 1; for (i = 0, ii = count + 1; i < ii; ++i) { let offset = 0; for (let j = 0; j < offsetSize; ++j) { offset <<= 8; offset += bytes[pos++]; } offsets.push(startPos + offset); } end = offsets[count]; } for (i = 0, ii = offsets.length - 1; i < ii; ++i) { const offsetStart = offsets[i]; const offsetEnd = offsets[i + 1]; cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); } return { obj: cffIndex, endPos: end }; } parseNameIndex(index) { const names = []; for (let i = 0, ii = index.count; i < ii; ++i) { const name = index.get(i); names.push(bytesToString(name)); } return names; } parseStringIndex(index) { const strings = new CFFStrings(); for (let i = 0, ii = index.count; i < ii; ++i) { const data = index.get(i); strings.add(bytesToString(data)); } return strings; } createDict(Type, dict, strings) { const cffDict = new Type(strings); for (const [key, value] of dict) { cffDict.setByKey(key, value); } return cffDict; } parseCharString(state, data, localSubrIndex, globalSubrIndex) { if (!data || state.callDepth > MAX_SUBR_NESTING) { return false; } let stackSize = state.stackSize; const stack = state.stack; let length = data.length; for (let j = 0; j < length;) { const value = data[j++]; let validationCommand = null; if (value === 12) { const q = data[j++]; if (q === 0) { data[j - 2] = 139; data[j - 1] = 22; stackSize = 0; } else { validationCommand = CharstringValidationData12[q]; } } else if (value === 28) { stack[stackSize] = (data[j] << 24 | data[j + 1] << 16) >> 16; j += 2; stackSize++; } else if (value === 14) { if (stackSize >= 4) { stackSize -= 4; if (this.seacAnalysisEnabled) { state.seac = stack.slice(stackSize, stackSize + 4); return false; } } validationCommand = CharstringValidationData[value]; } else if (value >= 32 && value <= 246) { stack[stackSize] = value - 139; stackSize++; } else if (value >= 247 && value <= 254) { stack[stackSize] = value < 251 ? (value - 247 << 8) + data[j] + 108 : -(value - 251 << 8) - data[j] - 108; j++; stackSize++; } else if (value === 255) { stack[stackSize] = (data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) / 65536; j += 4; stackSize++; } else if (value === 19 || value === 20) { state.hints += stackSize >> 1; if (state.hints === 0) { data.copyWithin(j - 1, j, -1); j -= 1; length -= 1; continue; } j += state.hints + 7 >> 3; stackSize %= 2; validationCommand = CharstringValidationData[value]; } else if (value === 10 || value === 29) { const subrsIndex = value === 10 ? localSubrIndex : globalSubrIndex; if (!subrsIndex) { validationCommand = CharstringValidationData[value]; warn("Missing subrsIndex for " + validationCommand.id); return false; } let bias = 32768; if (subrsIndex.count < 1240) { bias = 107; } else if (subrsIndex.count < 33900) { bias = 1131; } const subrNumber = stack[--stackSize] + bias; if (subrNumber < 0 || subrNumber >= subrsIndex.count || isNaN(subrNumber)) { validationCommand = CharstringValidationData[value]; warn("Out of bounds subrIndex for " + validationCommand.id); return false; } state.stackSize = stackSize; state.callDepth++; const valid = this.parseCharString(state, subrsIndex.get(subrNumber), localSubrIndex, globalSubrIndex); if (!valid) { return false; } state.callDepth--; stackSize = state.stackSize; continue; } else if (value === 11) { state.stackSize = stackSize; return true; } else if (value === 0 && j === data.length) { data[j - 1] = 14; validationCommand = CharstringValidationData[14]; } else if (value === 9) { data.copyWithin(j - 1, j, -1); j -= 1; length -= 1; continue; } else { validationCommand = CharstringValidationData[value]; } if (validationCommand) { if (validationCommand.stem) { state.hints += stackSize >> 1; if (value === 3 || value === 23) { state.hasVStems = true; } else if (state.hasVStems && (value === 1 || value === 18)) { warn("CFF stem hints are in wrong order"); data[j - 1] = value === 1 ? 3 : 23; } } if ("min" in validationCommand) { if (!state.undefStack && stackSize < validationCommand.min) { warn("Not enough parameters for " + validationCommand.id + "; actual: " + stackSize + ", expected: " + validationCommand.min); if (stackSize === 0) { data[j - 1] = 14; return true; } return false; } } if (state.firstStackClearing && validationCommand.stackClearing) { state.firstStackClearing = false; stackSize -= validationCommand.min; if (stackSize >= 2 && validationCommand.stem) { stackSize %= 2; } else if (stackSize > 1) { warn("Found too many parameters for stack-clearing command"); } if (stackSize > 0) { state.width = stack[stackSize - 1]; } } if ("stackDelta" in validationCommand) { if ("stackFn" in validationCommand) { validationCommand.stackFn(stack, stackSize); } stackSize += validationCommand.stackDelta; } else if (validationCommand.stackClearing) { stackSize = 0; } else if (validationCommand.resetStack) { stackSize = 0; state.undefStack = false; } else if (validationCommand.undefStack) { stackSize = 0; state.undefStack = true; state.firstStackClearing = false; } } } if (length < data.length) { data.fill(14, length); } state.stackSize = stackSize; return true; } parseCharStrings({ charStrings, localSubrIndex, globalSubrIndex, fdSelect, fdArray, privateDict }) { const seacs = []; const widths = []; const count = charStrings.count; for (let i = 0; i < count; i++) { const charstring = charStrings.get(i); const state = { callDepth: 0, stackSize: 0, stack: [], undefStack: true, hints: 0, firstStackClearing: true, seac: null, width: null, hasVStems: false }; let valid = true; let localSubrToUse = null; let privateDictToUse = privateDict; if (fdSelect && fdArray.length) { const fdIndex = fdSelect.getFDIndex(i); if (fdIndex === -1) { warn("Glyph index is not in fd select."); valid = false; } if (fdIndex >= fdArray.length) { warn("Invalid fd index for glyph index."); valid = false; } if (valid) { privateDictToUse = fdArray[fdIndex].privateDict; localSubrToUse = privateDictToUse.subrsIndex; } } else if (localSubrIndex) { localSubrToUse = localSubrIndex; } if (valid) { valid = this.parseCharString(state, charstring, localSubrToUse, globalSubrIndex); } if (state.width !== null) { const nominalWidth = privateDictToUse.getByName("nominalWidthX"); widths[i] = nominalWidth + state.width; } else { const defaultWidth = privateDictToUse.getByName("defaultWidthX"); widths[i] = defaultWidth; } if (state.seac !== null) { seacs[i] = state.seac; } if (!valid) { charStrings.set(i, new Uint8Array([14])); } } return { charStrings, seacs, widths }; } emptyPrivateDictionary(parentDict) { const privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings); parentDict.setByKey(18, [0, 0]); parentDict.privateDict = privateDict; } parsePrivateDict(parentDict) { if (!parentDict.hasName("Private")) { this.emptyPrivateDictionary(parentDict); return; } const privateOffset = parentDict.getByName("Private"); if (!Array.isArray(privateOffset) || privateOffset.length !== 2) { parentDict.removeByName("Private"); return; } const size = privateOffset[0]; const offset = privateOffset[1]; if (size === 0 || offset >= this.bytes.length) { this.emptyPrivateDictionary(parentDict); return; } const privateDictEnd = offset + size; const dictData = this.bytes.subarray(offset, privateDictEnd); const dict = this.parseDict(dictData); const privateDict = this.createDict(CFFPrivateDict, dict, parentDict.strings); parentDict.privateDict = privateDict; if (privateDict.getByName("ExpansionFactor") === 0) { privateDict.setByName("ExpansionFactor", 0.06); } if (!privateDict.getByName("Subrs")) { return; } const subrsOffset = privateDict.getByName("Subrs"); const relativeOffset = offset + subrsOffset; if (subrsOffset === 0 || relativeOffset >= this.bytes.length) { this.emptyPrivateDictionary(parentDict); return; } const subrsIndex = this.parseIndex(relativeOffset); privateDict.subrsIndex = subrsIndex.obj; } parseCharsets(pos, length, strings, cid) { if (pos === 0) { return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset); } else if (pos === 1) { return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset); } else if (pos === 2) { return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset); } const bytes = this.bytes; const start = pos; const format = bytes[pos++]; const charset = [cid ? 0 : ".notdef"]; let id, count, i; length -= 1; switch (format) { case 0: for (i = 0; i < length; i++) { id = bytes[pos++] << 8 | bytes[pos++]; charset.push(cid ? id : strings.get(id)); } break; case 1: while (charset.length <= length) { id = bytes[pos++] << 8 | bytes[pos++]; count = bytes[pos++]; for (i = 0; i <= count; i++) { charset.push(cid ? id++ : strings.get(id++)); } } break; case 2: while (charset.length <= length) { id = bytes[pos++] << 8 | bytes[pos++]; count = bytes[pos++] << 8 | bytes[pos++]; for (i = 0; i <= count; i++) { charset.push(cid ? id++ : strings.get(id++)); } } break; default: throw new FormatError("Unknown charset format"); } const end = pos; const raw = bytes.subarray(start, end); return new CFFCharset(false, format, charset, raw); } parseEncoding(pos, properties, strings, charset) { const encoding = Object.create(null); const bytes = this.bytes; let predefined = false; let format, i, ii; let raw = null; function readSupplement() { const supplementsCount = bytes[pos++]; for (i = 0; i < supplementsCount; i++) { const code = bytes[pos++]; const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); encoding[code] = charset.indexOf(strings.get(sid)); } } if (pos === 0 || pos === 1) { predefined = true; format = pos; const baseEncoding = pos ? ExpertEncoding : StandardEncoding; for (i = 0, ii = charset.length; i < ii; i++) { const index = baseEncoding.indexOf(charset[i]); if (index !== -1) { encoding[index] = i; } } } else { const dataStart = pos; format = bytes[pos++]; switch (format & 0x7f) { case 0: const glyphsCount = bytes[pos++]; for (i = 1; i <= glyphsCount; i++) { encoding[bytes[pos++]] = i; } break; case 1: const rangesCount = bytes[pos++]; let gid = 1; for (i = 0; i < rangesCount; i++) { const start = bytes[pos++]; const left = bytes[pos++]; for (let j = start; j <= start + left; j++) { encoding[j] = gid++; } } break; default: throw new FormatError(`Unknown encoding format: ${format} in CFF`); } const dataEnd = pos; if (format & 0x80) { bytes[dataStart] &= 0x7f; readSupplement(); } raw = bytes.subarray(dataStart, dataEnd); } format &= 0x7f; return new CFFEncoding(predefined, format, encoding, raw); } parseFDSelect(pos, length) { const bytes = this.bytes; const format = bytes[pos++]; const fdSelect = []; let i; switch (format) { case 0: for (i = 0; i < length; ++i) { const id = bytes[pos++]; fdSelect.push(id); } break; case 3: const rangesCount = bytes[pos++] << 8 | bytes[pos++]; for (i = 0; i < rangesCount; ++i) { let first = bytes[pos++] << 8 | bytes[pos++]; if (i === 0 && first !== 0) { warn("parseFDSelect: The first range must have a first GID of 0" + " -- trying to recover."); first = 0; } const fdIndex = bytes[pos++]; const next = bytes[pos] << 8 | bytes[pos + 1]; for (let j = first; j < next; ++j) { fdSelect.push(fdIndex); } } pos += 2; break; default: throw new FormatError(`parseFDSelect: Unknown format "${format}".`); } if (fdSelect.length !== length) { throw new FormatError("parseFDSelect: Invalid font data."); } return new CFFFDSelect(format, fdSelect); } } class CFF { constructor() { this.header = null; this.names = []; this.topDict = null; this.strings = new CFFStrings(); this.globalSubrIndex = null; this.encoding = null; this.charset = null; this.charStrings = null; this.fdArray = []; this.fdSelect = null; this.isCIDFont = false; } duplicateFirstGlyph() { if (this.charStrings.count >= 65535) { warn("Not enough space in charstrings to duplicate first glyph."); return; } const glyphZero = this.charStrings.get(0); this.charStrings.add(glyphZero); if (this.isCIDFont) { this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]); } } hasGlyphId(id) { if (id < 0 || id >= this.charStrings.count) { return false; } const glyph = this.charStrings.get(id); return glyph.length > 0; } } class CFFHeader { constructor(major, minor, hdrSize, offSize) { this.major = major; this.minor = minor; this.hdrSize = hdrSize; this.offSize = offSize; } } class CFFStrings { constructor() { this.strings = []; } get(index) { if (index >= 0 && index <= NUM_STANDARD_CFF_STRINGS - 1) { return CFFStandardStrings[index]; } if (index - NUM_STANDARD_CFF_STRINGS <= this.strings.length) { return this.strings[index - NUM_STANDARD_CFF_STRINGS]; } return CFFStandardStrings[0]; } getSID(str) { let index = CFFStandardStrings.indexOf(str); if (index !== -1) { return index; } index = this.strings.indexOf(str); if (index !== -1) { return index + NUM_STANDARD_CFF_STRINGS; } return -1; } add(value) { this.strings.push(value); } get count() { return this.strings.length; } } class CFFIndex { constructor() { this.objects = []; this.length = 0; } add(data) { this.length += data.length; this.objects.push(data); } set(index, data) { this.length += data.length - this.objects[index].length; this.objects[index] = data; } get(index) { return this.objects[index]; } get count() { return this.objects.length; } } class CFFDict { constructor(tables, strings) { this.keyToNameMap = tables.keyToNameMap; this.nameToKeyMap = tables.nameToKeyMap; this.defaults = tables.defaults; this.types = tables.types; this.opcodes = tables.opcodes; this.order = tables.order; this.strings = strings; this.values = Object.create(null); } setByKey(key, value) { if (!(key in this.keyToNameMap)) { return false; } if (value.length === 0) { return true; } for (const val of value) { if (isNaN(val)) { warn(`Invalid CFFDict value: "${value}" for key "${key}".`); return true; } } const type = this.types[key]; if (type === "num" || type === "sid" || type === "offset") { value = value[0]; } this.values[key] = value; return true; } setByName(name, value) { if (!(name in this.nameToKeyMap)) { throw new FormatError(`Invalid dictionary name "${name}"`); } this.values[this.nameToKeyMap[name]] = value; } hasName(name) { return this.nameToKeyMap[name] in this.values; } getByName(name) { if (!(name in this.nameToKeyMap)) { throw new FormatError(`Invalid dictionary name ${name}"`); } const key = this.nameToKeyMap[name]; if (!(key in this.values)) { return this.defaults[key]; } return this.values[key]; } removeByName(name) { delete this.values[this.nameToKeyMap[name]]; } static createTables(layout) { const tables = { keyToNameMap: {}, nameToKeyMap: {}, defaults: {}, types: {}, opcodes: {}, order: [] }; for (const entry of layout) { const key = Array.isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0]; tables.keyToNameMap[key] = entry[1]; tables.nameToKeyMap[entry[1]] = key; tables.types[key] = entry[2]; tables.defaults[key] = entry[3]; tables.opcodes[key] = Array.isArray(entry[0]) ? entry[0] : [entry[0]]; tables.order.push(key); } return tables; } } const CFFTopDictLayout = [[[12, 30], "ROS", ["sid", "sid", "num"], null], [[12, 20], "SyntheticBase", "num", null], [0, "version", "sid", null], [1, "Notice", "sid", null], [[12, 0], "Copyright", "sid", null], [2, "FullName", "sid", null], [3, "FamilyName", "sid", null], [4, "Weight", "sid", null], [[12, 1], "isFixedPitch", "num", 0], [[12, 2], "ItalicAngle", "num", 0], [[12, 3], "UnderlinePosition", "num", -100], [[12, 4], "UnderlineThickness", "num", 50], [[12, 5], "PaintType", "num", 0], [[12, 6], "CharstringType", "num", 2], [[12, 7], "FontMatrix", ["num", "num", "num", "num", "num", "num"], [0.001, 0, 0, 0.001, 0, 0]], [13, "UniqueID", "num", null], [5, "FontBBox", ["num", "num", "num", "num"], [0, 0, 0, 0]], [[12, 8], "StrokeWidth", "num", 0], [14, "XUID", "array", null], [15, "charset", "offset", 0], [16, "Encoding", "offset", 0], [17, "CharStrings", "offset", 0], [18, "Private", ["offset", "offset"], null], [[12, 21], "PostScript", "sid", null], [[12, 22], "BaseFontName", "sid", null], [[12, 23], "BaseFontBlend", "delta", null], [[12, 31], "CIDFontVersion", "num", 0], [[12, 32], "CIDFontRevision", "num", 0], [[12, 33], "CIDFontType", "num", 0], [[12, 34], "CIDCount", "num", 8720], [[12, 35], "UIDBase", "num", null], [[12, 37], "FDSelect", "offset", null], [[12, 36], "FDArray", "offset", null], [[12, 38], "FontName", "sid", null]]; class CFFTopDict extends CFFDict { static get tables() { return shadow(this, "tables", this.createTables(CFFTopDictLayout)); } constructor(strings) { super(CFFTopDict.tables, strings); this.privateDict = null; } } const CFFPrivateDictLayout = [[6, "BlueValues", "delta", null], [7, "OtherBlues", "delta", null], [8, "FamilyBlues", "delta", null], [9, "FamilyOtherBlues", "delta", null], [[12, 9], "BlueScale", "num", 0.039625], [[12, 10], "BlueShift", "num", 7], [[12, 11], "BlueFuzz", "num", 1], [10, "StdHW", "num", null], [11, "StdVW", "num", null], [[12, 12], "StemSnapH", "delta", null], [[12, 13], "StemSnapV", "delta", null], [[12, 14], "ForceBold", "num", 0], [[12, 17], "LanguageGroup", "num", 0], [[12, 18], "ExpansionFactor", "num", 0.06], [[12, 19], "initialRandomSeed", "num", 0], [20, "defaultWidthX", "num", 0], [21, "nominalWidthX", "num", 0], [19, "Subrs", "offset", null]]; class CFFPrivateDict extends CFFDict { static get tables() { return shadow(this, "tables", this.createTables(CFFPrivateDictLayout)); } constructor(strings) { super(CFFPrivateDict.tables, strings); this.subrsIndex = null; } } const CFFCharsetPredefinedTypes = { ISO_ADOBE: 0, EXPERT: 1, EXPERT_SUBSET: 2 }; class CFFCharset { constructor(predefined, format, charset, raw) { this.predefined = predefined; this.format = format; this.charset = charset; this.raw = raw; } } class CFFEncoding { constructor(predefined, format, encoding, raw) { this.predefined = predefined; this.format = format; this.encoding = encoding; this.raw = raw; } } class CFFFDSelect { constructor(format, fdSelect) { this.format = format; this.fdSelect = fdSelect; } getFDIndex(glyphIndex) { if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) { return -1; } return this.fdSelect[glyphIndex]; } } class CFFOffsetTracker { constructor() { this.offsets = Object.create(null); } isTracking(key) { return key in this.offsets; } track(key, location) { if (key in this.offsets) { throw new FormatError(`Already tracking location of ${key}`); } this.offsets[key] = location; } offset(value) { for (const key in this.offsets) { this.offsets[key] += value; } } setEntryLocation(key, values, output) { if (!(key in this.offsets)) { throw new FormatError(`Not tracking location of ${key}`); } const data = output.data; const dataOffset = this.offsets[key]; const size = 5; for (let i = 0, ii = values.length; i < ii; ++i) { const offset0 = i * size + dataOffset; const offset1 = offset0 + 1; const offset2 = offset0 + 2; const offset3 = offset0 + 3; const offset4 = offset0 + 4; if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) { throw new FormatError("writing to an offset that is not empty"); } const value = values[i]; data[offset0] = 0x1d; data[offset1] = value >> 24 & 0xff; data[offset2] = value >> 16 & 0xff; data[offset3] = value >> 8 & 0xff; data[offset4] = value & 0xff; } } } class CFFCompiler { constructor(cff) { this.cff = cff; } compile() { const cff = this.cff; const output = { data: [], length: 0, add(data) { try { this.data.push(...data); } catch { this.data = this.data.concat(data); } this.length = this.data.length; } }; const header = this.compileHeader(cff.header); output.add(header); const nameIndex = this.compileNameIndex(cff.names); output.add(nameIndex); if (cff.isCIDFont) { if (cff.topDict.hasName("FontMatrix")) { const base = cff.topDict.getByName("FontMatrix"); cff.topDict.removeByName("FontMatrix"); for (const subDict of cff.fdArray) { let matrix = base.slice(0); if (subDict.hasName("FontMatrix")) { matrix = Util.transform(matrix, subDict.getByName("FontMatrix")); } subDict.setByName("FontMatrix", matrix); } } } const xuid = cff.topDict.getByName("XUID"); if (xuid?.length > 16) { cff.topDict.removeByName("XUID"); } cff.topDict.setByName("charset", 0); let compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont); output.add(compiled.output); const topDictTracker = compiled.trackers[0]; const stringIndex = this.compileStringIndex(cff.strings.strings); output.add(stringIndex); const globalSubrIndex = this.compileIndex(cff.globalSubrIndex); output.add(globalSubrIndex); if (cff.encoding && cff.topDict.hasName("Encoding")) { if (cff.encoding.predefined) { topDictTracker.setEntryLocation("Encoding", [cff.encoding.format], output); } else { const encoding = this.compileEncoding(cff.encoding); topDictTracker.setEntryLocation("Encoding", [output.length], output); output.add(encoding); } } const charset = this.compileCharset(cff.charset, cff.charStrings.count, cff.strings, cff.isCIDFont); topDictTracker.setEntryLocation("charset", [output.length], output); output.add(charset); const charStrings = this.compileCharStrings(cff.charStrings); topDictTracker.setEntryLocation("CharStrings", [output.length], output); output.add(charStrings); if (cff.isCIDFont) { topDictTracker.setEntryLocation("FDSelect", [output.length], output); const fdSelect = this.compileFDSelect(cff.fdSelect); output.add(fdSelect); compiled = this.compileTopDicts(cff.fdArray, output.length, true); topDictTracker.setEntryLocation("FDArray", [output.length], output); output.add(compiled.output); const fontDictTrackers = compiled.trackers; this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output); } this.compilePrivateDicts([cff.topDict], [topDictTracker], output); output.add([0]); return output.data; } encodeNumber(value) { if (Number.isInteger(value)) { return this.encodeInteger(value); } return this.encodeFloat(value); } static get EncodeFloatRegExp() { return shadow(this, "EncodeFloatRegExp", /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/); } encodeFloat(num) { let value = num.toString(); const m = CFFCompiler.EncodeFloatRegExp.exec(value); if (m) { const epsilon = parseFloat("1e" + ((m[2] ? +m[2] : 0) + m[1].length)); value = (Math.round(num * epsilon) / epsilon).toString(); } let nibbles = ""; let i, ii; for (i = 0, ii = value.length; i < ii; ++i) { const a = value[i]; if (a === "e") { nibbles += value[++i] === "-" ? "c" : "b"; } else if (a === ".") { nibbles += "a"; } else if (a === "-") { nibbles += "e"; } else { nibbles += a; } } nibbles += nibbles.length & 1 ? "f" : "ff"; const out = [30]; for (i = 0, ii = nibbles.length; i < ii; i += 2) { out.push(parseInt(nibbles.substring(i, i + 2), 16)); } return out; } encodeInteger(value) { let code; if (value >= -107 && value <= 107) { code = [value + 139]; } else if (value >= 108 && value <= 1131) { value -= 108; code = [(value >> 8) + 247, value & 0xff]; } else if (value >= -1131 && value <= -108) { value = -value - 108; code = [(value >> 8) + 251, value & 0xff]; } else if (value >= -32768 && value <= 32767) { code = [0x1c, value >> 8 & 0xff, value & 0xff]; } else { code = [0x1d, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff]; } return code; } compileHeader(header) { return [header.major, header.minor, 4, header.offSize]; } compileNameIndex(names) { const nameIndex = new CFFIndex(); for (const name of names) { const length = Math.min(name.length, 127); let sanitizedName = new Array(length); for (let j = 0; j < length; j++) { let char = name[j]; if (char < "!" || char > "~" || char === "[" || char === "]" || char === "(" || char === ")" || char === "{" || char === "}" || char === "<" || char === ">" || char === "/" || char === "%") { char = "_"; } sanitizedName[j] = char; } sanitizedName = sanitizedName.join(""); if (sanitizedName === "") { sanitizedName = "Bad_Font_Name"; } nameIndex.add(stringToBytes(sanitizedName)); } return this.compileIndex(nameIndex); } compileTopDicts(dicts, length, removeCidKeys) { const fontDictTrackers = []; let fdArrayIndex = new CFFIndex(); for (const fontDict of dicts) { if (removeCidKeys) { fontDict.removeByName("CIDFontVersion"); fontDict.removeByName("CIDFontRevision"); fontDict.removeByName("CIDFontType"); fontDict.removeByName("CIDCount"); fontDict.removeByName("UIDBase"); } const fontDictTracker = new CFFOffsetTracker(); const fontDictData = this.compileDict(fontDict, fontDictTracker); fontDictTrackers.push(fontDictTracker); fdArrayIndex.add(fontDictData); fontDictTracker.offset(length); } fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers); return { trackers: fontDictTrackers, output: fdArrayIndex }; } compilePrivateDicts(dicts, trackers, output) { for (let i = 0, ii = dicts.length; i < ii; ++i) { const fontDict = dicts[i]; const privateDict = fontDict.privateDict; if (!privateDict || !fontDict.hasName("Private")) { throw new FormatError("There must be a private dictionary."); } const privateDictTracker = new CFFOffsetTracker(); const privateDictData = this.compileDict(privateDict, privateDictTracker); let outputLength = output.length; privateDictTracker.offset(outputLength); if (!privateDictData.length) { outputLength = 0; } trackers[i].setEntryLocation("Private", [privateDictData.length, outputLength], output); output.add(privateDictData); if (privateDict.subrsIndex && privateDict.hasName("Subrs")) { const subrs = this.compileIndex(privateDict.subrsIndex); privateDictTracker.setEntryLocation("Subrs", [privateDictData.length], output); output.add(subrs); } } } compileDict(dict, offsetTracker) { const out = []; for (const key of dict.order) { if (!(key in dict.values)) { continue; } let values = dict.values[key]; let types = dict.types[key]; if (!Array.isArray(types)) { types = [types]; } if (!Array.isArray(values)) { values = [values]; } if (values.length === 0) { continue; } for (let j = 0, jj = types.length; j < jj; ++j) { const type = types[j]; const value = values[j]; switch (type) { case "num": case "sid": out.push(...this.encodeNumber(value)); break; case "offset": const name = dict.keyToNameMap[key]; if (!offsetTracker.isTracking(name)) { offsetTracker.track(name, out.length); } out.push(0x1d, 0, 0, 0, 0); break; case "array": case "delta": out.push(...this.encodeNumber(value)); for (let k = 1, kk = values.length; k < kk; ++k) { out.push(...this.encodeNumber(values[k])); } break; default: throw new FormatError(`Unknown data type of ${type}`); } } out.push(...dict.opcodes[key]); } return out; } compileStringIndex(strings) { const stringIndex = new CFFIndex(); for (const string of strings) { stringIndex.add(stringToBytes(string)); } return this.compileIndex(stringIndex); } compileCharStrings(charStrings) { const charStringsIndex = new CFFIndex(); for (let i = 0; i < charStrings.count; i++) { const glyph = charStrings.get(i); if (glyph.length === 0) { charStringsIndex.add(new Uint8Array([0x8b, 0x0e])); continue; } charStringsIndex.add(glyph); } return this.compileIndex(charStringsIndex); } compileCharset(charset, numGlyphs, strings, isCIDFont) { let out; const numGlyphsLessNotDef = numGlyphs - 1; if (isCIDFont) { out = new Uint8Array([2, 0, 0, numGlyphsLessNotDef >> 8 & 0xff, numGlyphsLessNotDef & 0xff]); } else { const length = 1 + numGlyphsLessNotDef * 2; out = new Uint8Array(length); out[0] = 0; let charsetIndex = 0; const numCharsets = charset.charset.length; let warned = false; for (let i = 1; i < out.length; i += 2) { let sid = 0; if (charsetIndex < numCharsets) { const name = charset.charset[charsetIndex++]; sid = strings.getSID(name); if (sid === -1) { sid = 0; if (!warned) { warned = true; warn(`Couldn't find ${name} in CFF strings`); } } } out[i] = sid >> 8 & 0xff; out[i + 1] = sid & 0xff; } } return this.compileTypedArray(out); } compileEncoding(encoding) { return this.compileTypedArray(encoding.raw); } compileFDSelect(fdSelect) { const format = fdSelect.format; let out, i; switch (format) { case 0: out = new Uint8Array(1 + fdSelect.fdSelect.length); out[0] = format; for (i = 0; i < fdSelect.fdSelect.length; i++) { out[i + 1] = fdSelect.fdSelect[i]; } break; case 3: const start = 0; let lastFD = fdSelect.fdSelect[0]; const ranges = [format, 0, 0, start >> 8 & 0xff, start & 0xff, lastFD]; for (i = 1; i < fdSelect.fdSelect.length; i++) { const currentFD = fdSelect.fdSelect[i]; if (currentFD !== lastFD) { ranges.push(i >> 8 & 0xff, i & 0xff, currentFD); lastFD = currentFD; } } const numRanges = (ranges.length - 3) / 3; ranges[1] = numRanges >> 8 & 0xff; ranges[2] = numRanges & 0xff; ranges.push(i >> 8 & 0xff, i & 0xff); out = new Uint8Array(ranges); break; } return this.compileTypedArray(out); } compileTypedArray(data) { return Array.from(data); } compileIndex(index, trackers = []) { const objects = index.objects; const count = objects.length; if (count === 0) { return [0, 0]; } const data = [count >> 8 & 0xff, count & 0xff]; let lastOffset = 1, i; for (i = 0; i < count; ++i) { lastOffset += objects[i].length; } let offsetSize; if (lastOffset < 0x100) { offsetSize = 1; } else if (lastOffset < 0x10000) { offsetSize = 2; } else if (lastOffset < 0x1000000) { offsetSize = 3; } else { offsetSize = 4; } data.push(offsetSize); let relativeOffset = 1; for (i = 0; i < count + 1; i++) { if (offsetSize === 1) { data.push(relativeOffset & 0xff); } else if (offsetSize === 2) { data.push(relativeOffset >> 8 & 0xff, relativeOffset & 0xff); } else if (offsetSize === 3) { data.push(relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff); } else { data.push(relativeOffset >>> 24 & 0xff, relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff); } if (objects[i]) { relativeOffset += objects[i].length; } } for (i = 0; i < count; i++) { if (trackers[i]) { trackers[i].offset(data.length); } data.push(...objects[i]); } return data; } } ;// CONCATENATED MODULE: ./src/core/glyphlist.js const getGlyphsUnicode = getLookupTableFactory(function (t) { t.A = 0x0041; t.AE = 0x00c6; t.AEacute = 0x01fc; t.AEmacron = 0x01e2; t.AEsmall = 0xf7e6; t.Aacute = 0x00c1; t.Aacutesmall = 0xf7e1; t.Abreve = 0x0102; t.Abreveacute = 0x1eae; t.Abrevecyrillic = 0x04d0; t.Abrevedotbelow = 0x1eb6; t.Abrevegrave = 0x1eb0; t.Abrevehookabove = 0x1eb2; t.Abrevetilde = 0x1eb4; t.Acaron = 0x01cd; t.Acircle = 0x24b6; t.Acircumflex = 0x00c2; t.Acircumflexacute = 0x1ea4; t.Acircumflexdotbelow = 0x1eac; t.Acircumflexgrave = 0x1ea6; t.Acircumflexhookabove = 0x1ea8; t.Acircumflexsmall = 0xf7e2; t.Acircumflextilde = 0x1eaa; t.Acute = 0xf6c9; t.Acutesmall = 0xf7b4; t.Acyrillic = 0x0410; t.Adblgrave = 0x0200; t.Adieresis = 0x00c4; t.Adieresiscyrillic = 0x04d2; t.Adieresismacron = 0x01de; t.Adieresissmall = 0xf7e4; t.Adotbelow = 0x1ea0; t.Adotmacron = 0x01e0; t.Agrave = 0x00c0; t.Agravesmall = 0xf7e0; t.Ahookabove = 0x1ea2; t.Aiecyrillic = 0x04d4; t.Ainvertedbreve = 0x0202; t.Alpha = 0x0391; t.Alphatonos = 0x0386; t.Amacron = 0x0100; t.Amonospace = 0xff21; t.Aogonek = 0x0104; t.Aring = 0x00c5; t.Aringacute = 0x01fa; t.Aringbelow = 0x1e00; t.Aringsmall = 0xf7e5; t.Asmall = 0xf761; t.Atilde = 0x00c3; t.Atildesmall = 0xf7e3; t.Aybarmenian = 0x0531; t.B = 0x0042; t.Bcircle = 0x24b7; t.Bdotaccent = 0x1e02; t.Bdotbelow = 0x1e04; t.Becyrillic = 0x0411; t.Benarmenian = 0x0532; t.Beta = 0x0392; t.Bhook = 0x0181; t.Blinebelow = 0x1e06; t.Bmonospace = 0xff22; t.Brevesmall = 0xf6f4; t.Bsmall = 0xf762; t.Btopbar = 0x0182; t.C = 0x0043; t.Caarmenian = 0x053e; t.Cacute = 0x0106; t.Caron = 0xf6ca; t.Caronsmall = 0xf6f5; t.Ccaron = 0x010c; t.Ccedilla = 0x00c7; t.Ccedillaacute = 0x1e08; t.Ccedillasmall = 0xf7e7; t.Ccircle = 0x24b8; t.Ccircumflex = 0x0108; t.Cdot = 0x010a; t.Cdotaccent = 0x010a; t.Cedillasmall = 0xf7b8; t.Chaarmenian = 0x0549; t.Cheabkhasiancyrillic = 0x04bc; t.Checyrillic = 0x0427; t.Chedescenderabkhasiancyrillic = 0x04be; t.Chedescendercyrillic = 0x04b6; t.Chedieresiscyrillic = 0x04f4; t.Cheharmenian = 0x0543; t.Chekhakassiancyrillic = 0x04cb; t.Cheverticalstrokecyrillic = 0x04b8; t.Chi = 0x03a7; t.Chook = 0x0187; t.Circumflexsmall = 0xf6f6; t.Cmonospace = 0xff23; t.Coarmenian = 0x0551; t.Csmall = 0xf763; t.D = 0x0044; t.DZ = 0x01f1; t.DZcaron = 0x01c4; t.Daarmenian = 0x0534; t.Dafrican = 0x0189; t.Dcaron = 0x010e; t.Dcedilla = 0x1e10; t.Dcircle = 0x24b9; t.Dcircumflexbelow = 0x1e12; t.Dcroat = 0x0110; t.Ddotaccent = 0x1e0a; t.Ddotbelow = 0x1e0c; t.Decyrillic = 0x0414; t.Deicoptic = 0x03ee; t.Delta = 0x2206; t.Deltagreek = 0x0394; t.Dhook = 0x018a; t.Dieresis = 0xf6cb; t.DieresisAcute = 0xf6cc; t.DieresisGrave = 0xf6cd; t.Dieresissmall = 0xf7a8; t.Digammagreek = 0x03dc; t.Djecyrillic = 0x0402; t.Dlinebelow = 0x1e0e; t.Dmonospace = 0xff24; t.Dotaccentsmall = 0xf6f7; t.Dslash = 0x0110; t.Dsmall = 0xf764; t.Dtopbar = 0x018b; t.Dz = 0x01f2; t.Dzcaron = 0x01c5; t.Dzeabkhasiancyrillic = 0x04e0; t.Dzecyrillic = 0x0405; t.Dzhecyrillic = 0x040f; t.E = 0x0045; t.Eacute = 0x00c9; t.Eacutesmall = 0xf7e9; t.Ebreve = 0x0114; t.Ecaron = 0x011a; t.Ecedillabreve = 0x1e1c; t.Echarmenian = 0x0535; t.Ecircle = 0x24ba; t.Ecircumflex = 0x00ca; t.Ecircumflexacute = 0x1ebe; t.Ecircumflexbelow = 0x1e18; t.Ecircumflexdotbelow = 0x1ec6; t.Ecircumflexgrave = 0x1ec0; t.Ecircumflexhookabove = 0x1ec2; t.Ecircumflexsmall = 0xf7ea; t.Ecircumflextilde = 0x1ec4; t.Ecyrillic = 0x0404; t.Edblgrave = 0x0204; t.Edieresis = 0x00cb; t.Edieresissmall = 0xf7eb; t.Edot = 0x0116; t.Edotaccent = 0x0116; t.Edotbelow = 0x1eb8; t.Efcyrillic = 0x0424; t.Egrave = 0x00c8; t.Egravesmall = 0xf7e8; t.Eharmenian = 0x0537; t.Ehookabove = 0x1eba; t.Eightroman = 0x2167; t.Einvertedbreve = 0x0206; t.Eiotifiedcyrillic = 0x0464; t.Elcyrillic = 0x041b; t.Elevenroman = 0x216a; t.Emacron = 0x0112; t.Emacronacute = 0x1e16; t.Emacrongrave = 0x1e14; t.Emcyrillic = 0x041c; t.Emonospace = 0xff25; t.Encyrillic = 0x041d; t.Endescendercyrillic = 0x04a2; t.Eng = 0x014a; t.Enghecyrillic = 0x04a4; t.Enhookcyrillic = 0x04c7; t.Eogonek = 0x0118; t.Eopen = 0x0190; t.Epsilon = 0x0395; t.Epsilontonos = 0x0388; t.Ercyrillic = 0x0420; t.Ereversed = 0x018e; t.Ereversedcyrillic = 0x042d; t.Escyrillic = 0x0421; t.Esdescendercyrillic = 0x04aa; t.Esh = 0x01a9; t.Esmall = 0xf765; t.Eta = 0x0397; t.Etarmenian = 0x0538; t.Etatonos = 0x0389; t.Eth = 0x00d0; t.Ethsmall = 0xf7f0; t.Etilde = 0x1ebc; t.Etildebelow = 0x1e1a; t.Euro = 0x20ac; t.Ezh = 0x01b7; t.Ezhcaron = 0x01ee; t.Ezhreversed = 0x01b8; t.F = 0x0046; t.Fcircle = 0x24bb; t.Fdotaccent = 0x1e1e; t.Feharmenian = 0x0556; t.Feicoptic = 0x03e4; t.Fhook = 0x0191; t.Fitacyrillic = 0x0472; t.Fiveroman = 0x2164; t.Fmonospace = 0xff26; t.Fourroman = 0x2163; t.Fsmall = 0xf766; t.G = 0x0047; t.GBsquare = 0x3387; t.Gacute = 0x01f4; t.Gamma = 0x0393; t.Gammaafrican = 0x0194; t.Gangiacoptic = 0x03ea; t.Gbreve = 0x011e; t.Gcaron = 0x01e6; t.Gcedilla = 0x0122; t.Gcircle = 0x24bc; t.Gcircumflex = 0x011c; t.Gcommaaccent = 0x0122; t.Gdot = 0x0120; t.Gdotaccent = 0x0120; t.Gecyrillic = 0x0413; t.Ghadarmenian = 0x0542; t.Ghemiddlehookcyrillic = 0x0494; t.Ghestrokecyrillic = 0x0492; t.Gheupturncyrillic = 0x0490; t.Ghook = 0x0193; t.Gimarmenian = 0x0533; t.Gjecyrillic = 0x0403; t.Gmacron = 0x1e20; t.Gmonospace = 0xff27; t.Grave = 0xf6ce; t.Gravesmall = 0xf760; t.Gsmall = 0xf767; t.Gsmallhook = 0x029b; t.Gstroke = 0x01e4; t.H = 0x0048; t.H18533 = 0x25cf; t.H18543 = 0x25aa; t.H18551 = 0x25ab; t.H22073 = 0x25a1; t.HPsquare = 0x33cb; t.Haabkhasiancyrillic = 0x04a8; t.Hadescendercyrillic = 0x04b2; t.Hardsigncyrillic = 0x042a; t.Hbar = 0x0126; t.Hbrevebelow = 0x1e2a; t.Hcedilla = 0x1e28; t.Hcircle = 0x24bd; t.Hcircumflex = 0x0124; t.Hdieresis = 0x1e26; t.Hdotaccent = 0x1e22; t.Hdotbelow = 0x1e24; t.Hmonospace = 0xff28; t.Hoarmenian = 0x0540; t.Horicoptic = 0x03e8; t.Hsmall = 0xf768; t.Hungarumlaut = 0xf6cf; t.Hungarumlautsmall = 0xf6f8; t.Hzsquare = 0x3390; t.I = 0x0049; t.IAcyrillic = 0x042f; t.IJ = 0x0132; t.IUcyrillic = 0x042e; t.Iacute = 0x00cd; t.Iacutesmall = 0xf7ed; t.Ibreve = 0x012c; t.Icaron = 0x01cf; t.Icircle = 0x24be; t.Icircumflex = 0x00ce; t.Icircumflexsmall = 0xf7ee; t.Icyrillic = 0x0406; t.Idblgrave = 0x0208; t.Idieresis = 0x00cf; t.Idieresisacute = 0x1e2e; t.Idieresiscyrillic = 0x04e4; t.Idieresissmall = 0xf7ef; t.Idot = 0x0130; t.Idotaccent = 0x0130; t.Idotbelow = 0x1eca; t.Iebrevecyrillic = 0x04d6; t.Iecyrillic = 0x0415; t.Ifraktur = 0x2111; t.Igrave = 0x00cc; t.Igravesmall = 0xf7ec; t.Ihookabove = 0x1ec8; t.Iicyrillic = 0x0418; t.Iinvertedbreve = 0x020a; t.Iishortcyrillic = 0x0419; t.Imacron = 0x012a; t.Imacroncyrillic = 0x04e2; t.Imonospace = 0xff29; t.Iniarmenian = 0x053b; t.Iocyrillic = 0x0401; t.Iogonek = 0x012e; t.Iota = 0x0399; t.Iotaafrican = 0x0196; t.Iotadieresis = 0x03aa; t.Iotatonos = 0x038a; t.Ismall = 0xf769; t.Istroke = 0x0197; t.Itilde = 0x0128; t.Itildebelow = 0x1e2c; t.Izhitsacyrillic = 0x0474; t.Izhitsadblgravecyrillic = 0x0476; t.J = 0x004a; t.Jaarmenian = 0x0541; t.Jcircle = 0x24bf; t.Jcircumflex = 0x0134; t.Jecyrillic = 0x0408; t.Jheharmenian = 0x054b; t.Jmonospace = 0xff2a; t.Jsmall = 0xf76a; t.K = 0x004b; t.KBsquare = 0x3385; t.KKsquare = 0x33cd; t.Kabashkircyrillic = 0x04a0; t.Kacute = 0x1e30; t.Kacyrillic = 0x041a; t.Kadescendercyrillic = 0x049a; t.Kahookcyrillic = 0x04c3; t.Kappa = 0x039a; t.Kastrokecyrillic = 0x049e; t.Kaverticalstrokecyrillic = 0x049c; t.Kcaron = 0x01e8; t.Kcedilla = 0x0136; t.Kcircle = 0x24c0; t.Kcommaaccent = 0x0136; t.Kdotbelow = 0x1e32; t.Keharmenian = 0x0554; t.Kenarmenian = 0x053f; t.Khacyrillic = 0x0425; t.Kheicoptic = 0x03e6; t.Khook = 0x0198; t.Kjecyrillic = 0x040c; t.Klinebelow = 0x1e34; t.Kmonospace = 0xff2b; t.Koppacyrillic = 0x0480; t.Koppagreek = 0x03de; t.Ksicyrillic = 0x046e; t.Ksmall = 0xf76b; t.L = 0x004c; t.LJ = 0x01c7; t.LL = 0xf6bf; t.Lacute = 0x0139; t.Lambda = 0x039b; t.Lcaron = 0x013d; t.Lcedilla = 0x013b; t.Lcircle = 0x24c1; t.Lcircumflexbelow = 0x1e3c; t.Lcommaaccent = 0x013b; t.Ldot = 0x013f; t.Ldotaccent = 0x013f; t.Ldotbelow = 0x1e36; t.Ldotbelowmacron = 0x1e38; t.Liwnarmenian = 0x053c; t.Lj = 0x01c8; t.Ljecyrillic = 0x0409; t.Llinebelow = 0x1e3a; t.Lmonospace = 0xff2c; t.Lslash = 0x0141; t.Lslashsmall = 0xf6f9; t.Lsmall = 0xf76c; t.M = 0x004d; t.MBsquare = 0x3386; t.Macron = 0xf6d0; t.Macronsmall = 0xf7af; t.Macute = 0x1e3e; t.Mcircle = 0x24c2; t.Mdotaccent = 0x1e40; t.Mdotbelow = 0x1e42; t.Menarmenian = 0x0544; t.Mmonospace = 0xff2d; t.Msmall = 0xf76d; t.Mturned = 0x019c; t.Mu = 0x039c; t.N = 0x004e; t.NJ = 0x01ca; t.Nacute = 0x0143; t.Ncaron = 0x0147; t.Ncedilla = 0x0145; t.Ncircle = 0x24c3; t.Ncircumflexbelow = 0x1e4a; t.Ncommaaccent = 0x0145; t.Ndotaccent = 0x1e44; t.Ndotbelow = 0x1e46; t.Nhookleft = 0x019d; t.Nineroman = 0x2168; t.Nj = 0x01cb; t.Njecyrillic = 0x040a; t.Nlinebelow = 0x1e48; t.Nmonospace = 0xff2e; t.Nowarmenian = 0x0546; t.Nsmall = 0xf76e; t.Ntilde = 0x00d1; t.Ntildesmall = 0xf7f1; t.Nu = 0x039d; t.O = 0x004f; t.OE = 0x0152; t.OEsmall = 0xf6fa; t.Oacute = 0x00d3; t.Oacutesmall = 0xf7f3; t.Obarredcyrillic = 0x04e8; t.Obarreddieresiscyrillic = 0x04ea; t.Obreve = 0x014e; t.Ocaron = 0x01d1; t.Ocenteredtilde = 0x019f; t.Ocircle = 0x24c4; t.Ocircumflex = 0x00d4; t.Ocircumflexacute = 0x1ed0; t.Ocircumflexdotbelow = 0x1ed8; t.Ocircumflexgrave = 0x1ed2; t.Ocircumflexhookabove = 0x1ed4; t.Ocircumflexsmall = 0xf7f4; t.Ocircumflextilde = 0x1ed6; t.Ocyrillic = 0x041e; t.Odblacute = 0x0150; t.Odblgrave = 0x020c; t.Odieresis = 0x00d6; t.Odieresiscyrillic = 0x04e6; t.Odieresissmall = 0xf7f6; t.Odotbelow = 0x1ecc; t.Ogoneksmall = 0xf6fb; t.Ograve = 0x00d2; t.Ogravesmall = 0xf7f2; t.Oharmenian = 0x0555; t.Ohm = 0x2126; t.Ohookabove = 0x1ece; t.Ohorn = 0x01a0; t.Ohornacute = 0x1eda; t.Ohorndotbelow = 0x1ee2; t.Ohorngrave = 0x1edc; t.Ohornhookabove = 0x1ede; t.Ohorntilde = 0x1ee0; t.Ohungarumlaut = 0x0150; t.Oi = 0x01a2; t.Oinvertedbreve = 0x020e; t.Omacron = 0x014c; t.Omacronacute = 0x1e52; t.Omacrongrave = 0x1e50; t.Omega = 0x2126; t.Omegacyrillic = 0x0460; t.Omegagreek = 0x03a9; t.Omegaroundcyrillic = 0x047a; t.Omegatitlocyrillic = 0x047c; t.Omegatonos = 0x038f; t.Omicron = 0x039f; t.Omicrontonos = 0x038c; t.Omonospace = 0xff2f; t.Oneroman = 0x2160; t.Oogonek = 0x01ea; t.Oogonekmacron = 0x01ec; t.Oopen = 0x0186; t.Oslash = 0x00d8; t.Oslashacute = 0x01fe; t.Oslashsmall = 0xf7f8; t.Osmall = 0xf76f; t.Ostrokeacute = 0x01fe; t.Otcyrillic = 0x047e; t.Otilde = 0x00d5; t.Otildeacute = 0x1e4c; t.Otildedieresis = 0x1e4e; t.Otildesmall = 0xf7f5; t.P = 0x0050; t.Pacute = 0x1e54; t.Pcircle = 0x24c5; t.Pdotaccent = 0x1e56; t.Pecyrillic = 0x041f; t.Peharmenian = 0x054a; t.Pemiddlehookcyrillic = 0x04a6; t.Phi = 0x03a6; t.Phook = 0x01a4; t.Pi = 0x03a0; t.Piwrarmenian = 0x0553; t.Pmonospace = 0xff30; t.Psi = 0x03a8; t.Psicyrillic = 0x0470; t.Psmall = 0xf770; t.Q = 0x0051; t.Qcircle = 0x24c6; t.Qmonospace = 0xff31; t.Qsmall = 0xf771; t.R = 0x0052; t.Raarmenian = 0x054c; t.Racute = 0x0154; t.Rcaron = 0x0158; t.Rcedilla = 0x0156; t.Rcircle = 0x24c7; t.Rcommaaccent = 0x0156; t.Rdblgrave = 0x0210; t.Rdotaccent = 0x1e58; t.Rdotbelow = 0x1e5a; t.Rdotbelowmacron = 0x1e5c; t.Reharmenian = 0x0550; t.Rfraktur = 0x211c; t.Rho = 0x03a1; t.Ringsmall = 0xf6fc; t.Rinvertedbreve = 0x0212; t.Rlinebelow = 0x1e5e; t.Rmonospace = 0xff32; t.Rsmall = 0xf772; t.Rsmallinverted = 0x0281; t.Rsmallinvertedsuperior = 0x02b6; t.S = 0x0053; t.SF010000 = 0x250c; t.SF020000 = 0x2514; t.SF030000 = 0x2510; t.SF040000 = 0x2518; t.SF050000 = 0x253c; t.SF060000 = 0x252c; t.SF070000 = 0x2534; t.SF080000 = 0x251c; t.SF090000 = 0x2524; t.SF100000 = 0x2500; t.SF110000 = 0x2502; t.SF190000 = 0x2561; t.SF200000 = 0x2562; t.SF210000 = 0x2556; t.SF220000 = 0x2555; t.SF230000 = 0x2563; t.SF240000 = 0x2551; t.SF250000 = 0x2557; t.SF260000 = 0x255d; t.SF270000 = 0x255c; t.SF280000 = 0x255b; t.SF360000 = 0x255e; t.SF370000 = 0x255f; t.SF380000 = 0x255a; t.SF390000 = 0x2554; t.SF400000 = 0x2569; t.SF410000 = 0x2566; t.SF420000 = 0x2560; t.SF430000 = 0x2550; t.SF440000 = 0x256c; t.SF450000 = 0x2567; t.SF460000 = 0x2568; t.SF470000 = 0x2564; t.SF480000 = 0x2565; t.SF490000 = 0x2559; t.SF500000 = 0x2558; t.SF510000 = 0x2552; t.SF520000 = 0x2553; t.SF530000 = 0x256b; t.SF540000 = 0x256a; t.Sacute = 0x015a; t.Sacutedotaccent = 0x1e64; t.Sampigreek = 0x03e0; t.Scaron = 0x0160; t.Scarondotaccent = 0x1e66; t.Scaronsmall = 0xf6fd; t.Scedilla = 0x015e; t.Schwa = 0x018f; t.Schwacyrillic = 0x04d8; t.Schwadieresiscyrillic = 0x04da; t.Scircle = 0x24c8; t.Scircumflex = 0x015c; t.Scommaaccent = 0x0218; t.Sdotaccent = 0x1e60; t.Sdotbelow = 0x1e62; t.Sdotbelowdotaccent = 0x1e68; t.Seharmenian = 0x054d; t.Sevenroman = 0x2166; t.Shaarmenian = 0x0547; t.Shacyrillic = 0x0428; t.Shchacyrillic = 0x0429; t.Sheicoptic = 0x03e2; t.Shhacyrillic = 0x04ba; t.Shimacoptic = 0x03ec; t.Sigma = 0x03a3; t.Sixroman = 0x2165; t.Smonospace = 0xff33; t.Softsigncyrillic = 0x042c; t.Ssmall = 0xf773; t.Stigmagreek = 0x03da; t.T = 0x0054; t.Tau = 0x03a4; t.Tbar = 0x0166; t.Tcaron = 0x0164; t.Tcedilla = 0x0162; t.Tcircle = 0x24c9; t.Tcircumflexbelow = 0x1e70; t.Tcommaaccent = 0x0162; t.Tdotaccent = 0x1e6a; t.Tdotbelow = 0x1e6c; t.Tecyrillic = 0x0422; t.Tedescendercyrillic = 0x04ac; t.Tenroman = 0x2169; t.Tetsecyrillic = 0x04b4; t.Theta = 0x0398; t.Thook = 0x01ac; t.Thorn = 0x00de; t.Thornsmall = 0xf7fe; t.Threeroman = 0x2162; t.Tildesmall = 0xf6fe; t.Tiwnarmenian = 0x054f; t.Tlinebelow = 0x1e6e; t.Tmonospace = 0xff34; t.Toarmenian = 0x0539; t.Tonefive = 0x01bc; t.Tonesix = 0x0184; t.Tonetwo = 0x01a7; t.Tretroflexhook = 0x01ae; t.Tsecyrillic = 0x0426; t.Tshecyrillic = 0x040b; t.Tsmall = 0xf774; t.Twelveroman = 0x216b; t.Tworoman = 0x2161; t.U = 0x0055; t.Uacute = 0x00da; t.Uacutesmall = 0xf7fa; t.Ubreve = 0x016c; t.Ucaron = 0x01d3; t.Ucircle = 0x24ca; t.Ucircumflex = 0x00db; t.Ucircumflexbelow = 0x1e76; t.Ucircumflexsmall = 0xf7fb; t.Ucyrillic = 0x0423; t.Udblacute = 0x0170; t.Udblgrave = 0x0214; t.Udieresis = 0x00dc; t.Udieresisacute = 0x01d7; t.Udieresisbelow = 0x1e72; t.Udieresiscaron = 0x01d9; t.Udieresiscyrillic = 0x04f0; t.Udieresisgrave = 0x01db; t.Udieresismacron = 0x01d5; t.Udieresissmall = 0xf7fc; t.Udotbelow = 0x1ee4; t.Ugrave = 0x00d9; t.Ugravesmall = 0xf7f9; t.Uhookabove = 0x1ee6; t.Uhorn = 0x01af; t.Uhornacute = 0x1ee8; t.Uhorndotbelow = 0x1ef0; t.Uhorngrave = 0x1eea; t.Uhornhookabove = 0x1eec; t.Uhorntilde = 0x1eee; t.Uhungarumlaut = 0x0170; t.Uhungarumlautcyrillic = 0x04f2; t.Uinvertedbreve = 0x0216; t.Ukcyrillic = 0x0478; t.Umacron = 0x016a; t.Umacroncyrillic = 0x04ee; t.Umacrondieresis = 0x1e7a; t.Umonospace = 0xff35; t.Uogonek = 0x0172; t.Upsilon = 0x03a5; t.Upsilon1 = 0x03d2; t.Upsilonacutehooksymbolgreek = 0x03d3; t.Upsilonafrican = 0x01b1; t.Upsilondieresis = 0x03ab; t.Upsilondieresishooksymbolgreek = 0x03d4; t.Upsilonhooksymbol = 0x03d2; t.Upsilontonos = 0x038e; t.Uring = 0x016e; t.Ushortcyrillic = 0x040e; t.Usmall = 0xf775; t.Ustraightcyrillic = 0x04ae; t.Ustraightstrokecyrillic = 0x04b0; t.Utilde = 0x0168; t.Utildeacute = 0x1e78; t.Utildebelow = 0x1e74; t.V = 0x0056; t.Vcircle = 0x24cb; t.Vdotbelow = 0x1e7e; t.Vecyrillic = 0x0412; t.Vewarmenian = 0x054e; t.Vhook = 0x01b2; t.Vmonospace = 0xff36; t.Voarmenian = 0x0548; t.Vsmall = 0xf776; t.Vtilde = 0x1e7c; t.W = 0x0057; t.Wacute = 0x1e82; t.Wcircle = 0x24cc; t.Wcircumflex = 0x0174; t.Wdieresis = 0x1e84; t.Wdotaccent = 0x1e86; t.Wdotbelow = 0x1e88; t.Wgrave = 0x1e80; t.Wmonospace = 0xff37; t.Wsmall = 0xf777; t.X = 0x0058; t.Xcircle = 0x24cd; t.Xdieresis = 0x1e8c; t.Xdotaccent = 0x1e8a; t.Xeharmenian = 0x053d; t.Xi = 0x039e; t.Xmonospace = 0xff38; t.Xsmall = 0xf778; t.Y = 0x0059; t.Yacute = 0x00dd; t.Yacutesmall = 0xf7fd; t.Yatcyrillic = 0x0462; t.Ycircle = 0x24ce; t.Ycircumflex = 0x0176; t.Ydieresis = 0x0178; t.Ydieresissmall = 0xf7ff; t.Ydotaccent = 0x1e8e; t.Ydotbelow = 0x1ef4; t.Yericyrillic = 0x042b; t.Yerudieresiscyrillic = 0x04f8; t.Ygrave = 0x1ef2; t.Yhook = 0x01b3; t.Yhookabove = 0x1ef6; t.Yiarmenian = 0x0545; t.Yicyrillic = 0x0407; t.Yiwnarmenian = 0x0552; t.Ymonospace = 0xff39; t.Ysmall = 0xf779; t.Ytilde = 0x1ef8; t.Yusbigcyrillic = 0x046a; t.Yusbigiotifiedcyrillic = 0x046c; t.Yuslittlecyrillic = 0x0466; t.Yuslittleiotifiedcyrillic = 0x0468; t.Z = 0x005a; t.Zaarmenian = 0x0536; t.Zacute = 0x0179; t.Zcaron = 0x017d; t.Zcaronsmall = 0xf6ff; t.Zcircle = 0x24cf; t.Zcircumflex = 0x1e90; t.Zdot = 0x017b; t.Zdotaccent = 0x017b; t.Zdotbelow = 0x1e92; t.Zecyrillic = 0x0417; t.Zedescendercyrillic = 0x0498; t.Zedieresiscyrillic = 0x04de; t.Zeta = 0x0396; t.Zhearmenian = 0x053a; t.Zhebrevecyrillic = 0x04c1; t.Zhecyrillic = 0x0416; t.Zhedescendercyrillic = 0x0496; t.Zhedieresiscyrillic = 0x04dc; t.Zlinebelow = 0x1e94; t.Zmonospace = 0xff3a; t.Zsmall = 0xf77a; t.Zstroke = 0x01b5; t.a = 0x0061; t.aabengali = 0x0986; t.aacute = 0x00e1; t.aadeva = 0x0906; t.aagujarati = 0x0a86; t.aagurmukhi = 0x0a06; t.aamatragurmukhi = 0x0a3e; t.aarusquare = 0x3303; t.aavowelsignbengali = 0x09be; t.aavowelsigndeva = 0x093e; t.aavowelsigngujarati = 0x0abe; t.abbreviationmarkarmenian = 0x055f; t.abbreviationsigndeva = 0x0970; t.abengali = 0x0985; t.abopomofo = 0x311a; t.abreve = 0x0103; t.abreveacute = 0x1eaf; t.abrevecyrillic = 0x04d1; t.abrevedotbelow = 0x1eb7; t.abrevegrave = 0x1eb1; t.abrevehookabove = 0x1eb3; t.abrevetilde = 0x1eb5; t.acaron = 0x01ce; t.acircle = 0x24d0; t.acircumflex = 0x00e2; t.acircumflexacute = 0x1ea5; t.acircumflexdotbelow = 0x1ead; t.acircumflexgrave = 0x1ea7; t.acircumflexhookabove = 0x1ea9; t.acircumflextilde = 0x1eab; t.acute = 0x00b4; t.acutebelowcmb = 0x0317; t.acutecmb = 0x0301; t.acutecomb = 0x0301; t.acutedeva = 0x0954; t.acutelowmod = 0x02cf; t.acutetonecmb = 0x0341; t.acyrillic = 0x0430; t.adblgrave = 0x0201; t.addakgurmukhi = 0x0a71; t.adeva = 0x0905; t.adieresis = 0x00e4; t.adieresiscyrillic = 0x04d3; t.adieresismacron = 0x01df; t.adotbelow = 0x1ea1; t.adotmacron = 0x01e1; t.ae = 0x00e6; t.aeacute = 0x01fd; t.aekorean = 0x3150; t.aemacron = 0x01e3; t.afii00208 = 0x2015; t.afii08941 = 0x20a4; t.afii10017 = 0x0410; t.afii10018 = 0x0411; t.afii10019 = 0x0412; t.afii10020 = 0x0413; t.afii10021 = 0x0414; t.afii10022 = 0x0415; t.afii10023 = 0x0401; t.afii10024 = 0x0416; t.afii10025 = 0x0417; t.afii10026 = 0x0418; t.afii10027 = 0x0419; t.afii10028 = 0x041a; t.afii10029 = 0x041b; t.afii10030 = 0x041c; t.afii10031 = 0x041d; t.afii10032 = 0x041e; t.afii10033 = 0x041f; t.afii10034 = 0x0420; t.afii10035 = 0x0421; t.afii10036 = 0x0422; t.afii10037 = 0x0423; t.afii10038 = 0x0424; t.afii10039 = 0x0425; t.afii10040 = 0x0426; t.afii10041 = 0x0427; t.afii10042 = 0x0428; t.afii10043 = 0x0429; t.afii10044 = 0x042a; t.afii10045 = 0x042b; t.afii10046 = 0x042c; t.afii10047 = 0x042d; t.afii10048 = 0x042e; t.afii10049 = 0x042f; t.afii10050 = 0x0490; t.afii10051 = 0x0402; t.afii10052 = 0x0403; t.afii10053 = 0x0404; t.afii10054 = 0x0405; t.afii10055 = 0x0406; t.afii10056 = 0x0407; t.afii10057 = 0x0408; t.afii10058 = 0x0409; t.afii10059 = 0x040a; t.afii10060 = 0x040b; t.afii10061 = 0x040c; t.afii10062 = 0x040e; t.afii10063 = 0xf6c4; t.afii10064 = 0xf6c5; t.afii10065 = 0x0430; t.afii10066 = 0x0431; t.afii10067 = 0x0432; t.afii10068 = 0x0433; t.afii10069 = 0x0434; t.afii10070 = 0x0435; t.afii10071 = 0x0451; t.afii10072 = 0x0436; t.afii10073 = 0x0437; t.afii10074 = 0x0438; t.afii10075 = 0x0439; t.afii10076 = 0x043a; t.afii10077 = 0x043b; t.afii10078 = 0x043c; t.afii10079 = 0x043d; t.afii10080 = 0x043e; t.afii10081 = 0x043f; t.afii10082 = 0x0440; t.afii10083 = 0x0441; t.afii10084 = 0x0442; t.afii10085 = 0x0443; t.afii10086 = 0x0444; t.afii10087 = 0x0445; t.afii10088 = 0x0446; t.afii10089 = 0x0447; t.afii10090 = 0x0448; t.afii10091 = 0x0449; t.afii10092 = 0x044a; t.afii10093 = 0x044b; t.afii10094 = 0x044c; t.afii10095 = 0x044d; t.afii10096 = 0x044e; t.afii10097 = 0x044f; t.afii10098 = 0x0491; t.afii10099 = 0x0452; t.afii10100 = 0x0453; t.afii10101 = 0x0454; t.afii10102 = 0x0455; t.afii10103 = 0x0456; t.afii10104 = 0x0457; t.afii10105 = 0x0458; t.afii10106 = 0x0459; t.afii10107 = 0x045a; t.afii10108 = 0x045b; t.afii10109 = 0x045c; t.afii10110 = 0x045e; t.afii10145 = 0x040f; t.afii10146 = 0x0462; t.afii10147 = 0x0472; t.afii10148 = 0x0474; t.afii10192 = 0xf6c6; t.afii10193 = 0x045f; t.afii10194 = 0x0463; t.afii10195 = 0x0473; t.afii10196 = 0x0475; t.afii10831 = 0xf6c7; t.afii10832 = 0xf6c8; t.afii10846 = 0x04d9; t.afii299 = 0x200e; t.afii300 = 0x200f; t.afii301 = 0x200d; t.afii57381 = 0x066a; t.afii57388 = 0x060c; t.afii57392 = 0x0660; t.afii57393 = 0x0661; t.afii57394 = 0x0662; t.afii57395 = 0x0663; t.afii57396 = 0x0664; t.afii57397 = 0x0665; t.afii57398 = 0x0666; t.afii57399 = 0x0667; t.afii57400 = 0x0668; t.afii57401 = 0x0669; t.afii57403 = 0x061b; t.afii57407 = 0x061f; t.afii57409 = 0x0621; t.afii57410 = 0x0622; t.afii57411 = 0x0623; t.afii57412 = 0x0624; t.afii57413 = 0x0625; t.afii57414 = 0x0626; t.afii57415 = 0x0627; t.afii57416 = 0x0628; t.afii57417 = 0x0629; t.afii57418 = 0x062a; t.afii57419 = 0x062b; t.afii57420 = 0x062c; t.afii57421 = 0x062d; t.afii57422 = 0x062e; t.afii57423 = 0x062f; t.afii57424 = 0x0630; t.afii57425 = 0x0631; t.afii57426 = 0x0632; t.afii57427 = 0x0633; t.afii57428 = 0x0634; t.afii57429 = 0x0635; t.afii57430 = 0x0636; t.afii57431 = 0x0637; t.afii57432 = 0x0638; t.afii57433 = 0x0639; t.afii57434 = 0x063a; t.afii57440 = 0x0640; t.afii57441 = 0x0641; t.afii57442 = 0x0642; t.afii57443 = 0x0643; t.afii57444 = 0x0644; t.afii57445 = 0x0645; t.afii57446 = 0x0646; t.afii57448 = 0x0648; t.afii57449 = 0x0649; t.afii57450 = 0x064a; t.afii57451 = 0x064b; t.afii57452 = 0x064c; t.afii57453 = 0x064d; t.afii57454 = 0x064e; t.afii57455 = 0x064f; t.afii57456 = 0x0650; t.afii57457 = 0x0651; t.afii57458 = 0x0652; t.afii57470 = 0x0647; t.afii57505 = 0x06a4; t.afii57506 = 0x067e; t.afii57507 = 0x0686; t.afii57508 = 0x0698; t.afii57509 = 0x06af; t.afii57511 = 0x0679; t.afii57512 = 0x0688; t.afii57513 = 0x0691; t.afii57514 = 0x06ba; t.afii57519 = 0x06d2; t.afii57534 = 0x06d5; t.afii57636 = 0x20aa; t.afii57645 = 0x05be; t.afii57658 = 0x05c3; t.afii57664 = 0x05d0; t.afii57665 = 0x05d1; t.afii57666 = 0x05d2; t.afii57667 = 0x05d3; t.afii57668 = 0x05d4; t.afii57669 = 0x05d5; t.afii57670 = 0x05d6; t.afii57671 = 0x05d7; t.afii57672 = 0x05d8; t.afii57673 = 0x05d9; t.afii57674 = 0x05da; t.afii57675 = 0x05db; t.afii57676 = 0x05dc; t.afii57677 = 0x05dd; t.afii57678 = 0x05de; t.afii57679 = 0x05df; t.afii57680 = 0x05e0; t.afii57681 = 0x05e1; t.afii57682 = 0x05e2; t.afii57683 = 0x05e3; t.afii57684 = 0x05e4; t.afii57685 = 0x05e5; t.afii57686 = 0x05e6; t.afii57687 = 0x05e7; t.afii57688 = 0x05e8; t.afii57689 = 0x05e9; t.afii57690 = 0x05ea; t.afii57694 = 0xfb2a; t.afii57695 = 0xfb2b; t.afii57700 = 0xfb4b; t.afii57705 = 0xfb1f; t.afii57716 = 0x05f0; t.afii57717 = 0x05f1; t.afii57718 = 0x05f2; t.afii57723 = 0xfb35; t.afii57793 = 0x05b4; t.afii57794 = 0x05b5; t.afii57795 = 0x05b6; t.afii57796 = 0x05bb; t.afii57797 = 0x05b8; t.afii57798 = 0x05b7; t.afii57799 = 0x05b0; t.afii57800 = 0x05b2; t.afii57801 = 0x05b1; t.afii57802 = 0x05b3; t.afii57803 = 0x05c2; t.afii57804 = 0x05c1; t.afii57806 = 0x05b9; t.afii57807 = 0x05bc; t.afii57839 = 0x05bd; t.afii57841 = 0x05bf; t.afii57842 = 0x05c0; t.afii57929 = 0x02bc; t.afii61248 = 0x2105; t.afii61289 = 0x2113; t.afii61352 = 0x2116; t.afii61573 = 0x202c; t.afii61574 = 0x202d; t.afii61575 = 0x202e; t.afii61664 = 0x200c; t.afii63167 = 0x066d; t.afii64937 = 0x02bd; t.agrave = 0x00e0; t.agujarati = 0x0a85; t.agurmukhi = 0x0a05; t.ahiragana = 0x3042; t.ahookabove = 0x1ea3; t.aibengali = 0x0990; t.aibopomofo = 0x311e; t.aideva = 0x0910; t.aiecyrillic = 0x04d5; t.aigujarati = 0x0a90; t.aigurmukhi = 0x0a10; t.aimatragurmukhi = 0x0a48; t.ainarabic = 0x0639; t.ainfinalarabic = 0xfeca; t.aininitialarabic = 0xfecb; t.ainmedialarabic = 0xfecc; t.ainvertedbreve = 0x0203; t.aivowelsignbengali = 0x09c8; t.aivowelsigndeva = 0x0948; t.aivowelsigngujarati = 0x0ac8; t.akatakana = 0x30a2; t.akatakanahalfwidth = 0xff71; t.akorean = 0x314f; t.alef = 0x05d0; t.alefarabic = 0x0627; t.alefdageshhebrew = 0xfb30; t.aleffinalarabic = 0xfe8e; t.alefhamzaabovearabic = 0x0623; t.alefhamzaabovefinalarabic = 0xfe84; t.alefhamzabelowarabic = 0x0625; t.alefhamzabelowfinalarabic = 0xfe88; t.alefhebrew = 0x05d0; t.aleflamedhebrew = 0xfb4f; t.alefmaddaabovearabic = 0x0622; t.alefmaddaabovefinalarabic = 0xfe82; t.alefmaksuraarabic = 0x0649; t.alefmaksurafinalarabic = 0xfef0; t.alefmaksurainitialarabic = 0xfef3; t.alefmaksuramedialarabic = 0xfef4; t.alefpatahhebrew = 0xfb2e; t.alefqamatshebrew = 0xfb2f; t.aleph = 0x2135; t.allequal = 0x224c; t.alpha = 0x03b1; t.alphatonos = 0x03ac; t.amacron = 0x0101; t.amonospace = 0xff41; t.ampersand = 0x0026; t.ampersandmonospace = 0xff06; t.ampersandsmall = 0xf726; t.amsquare = 0x33c2; t.anbopomofo = 0x3122; t.angbopomofo = 0x3124; t.angbracketleft = 0x3008; t.angbracketright = 0x3009; t.angkhankhuthai = 0x0e5a; t.angle = 0x2220; t.anglebracketleft = 0x3008; t.anglebracketleftvertical = 0xfe3f; t.anglebracketright = 0x3009; t.anglebracketrightvertical = 0xfe40; t.angleleft = 0x2329; t.angleright = 0x232a; t.angstrom = 0x212b; t.anoteleia = 0x0387; t.anudattadeva = 0x0952; t.anusvarabengali = 0x0982; t.anusvaradeva = 0x0902; t.anusvaragujarati = 0x0a82; t.aogonek = 0x0105; t.apaatosquare = 0x3300; t.aparen = 0x249c; t.apostrophearmenian = 0x055a; t.apostrophemod = 0x02bc; t.apple = 0xf8ff; t.approaches = 0x2250; t.approxequal = 0x2248; t.approxequalorimage = 0x2252; t.approximatelyequal = 0x2245; t.araeaekorean = 0x318e; t.araeakorean = 0x318d; t.arc = 0x2312; t.arighthalfring = 0x1e9a; t.aring = 0x00e5; t.aringacute = 0x01fb; t.aringbelow = 0x1e01; t.arrowboth = 0x2194; t.arrowdashdown = 0x21e3; t.arrowdashleft = 0x21e0; t.arrowdashright = 0x21e2; t.arrowdashup = 0x21e1; t.arrowdblboth = 0x21d4; t.arrowdbldown = 0x21d3; t.arrowdblleft = 0x21d0; t.arrowdblright = 0x21d2; t.arrowdblup = 0x21d1; t.arrowdown = 0x2193; t.arrowdownleft = 0x2199; t.arrowdownright = 0x2198; t.arrowdownwhite = 0x21e9; t.arrowheaddownmod = 0x02c5; t.arrowheadleftmod = 0x02c2; t.arrowheadrightmod = 0x02c3; t.arrowheadupmod = 0x02c4; t.arrowhorizex = 0xf8e7; t.arrowleft = 0x2190; t.arrowleftdbl = 0x21d0; t.arrowleftdblstroke = 0x21cd; t.arrowleftoverright = 0x21c6; t.arrowleftwhite = 0x21e6; t.arrowright = 0x2192; t.arrowrightdblstroke = 0x21cf; t.arrowrightheavy = 0x279e; t.arrowrightoverleft = 0x21c4; t.arrowrightwhite = 0x21e8; t.arrowtableft = 0x21e4; t.arrowtabright = 0x21e5; t.arrowup = 0x2191; t.arrowupdn = 0x2195; t.arrowupdnbse = 0x21a8; t.arrowupdownbase = 0x21a8; t.arrowupleft = 0x2196; t.arrowupleftofdown = 0x21c5; t.arrowupright = 0x2197; t.arrowupwhite = 0x21e7; t.arrowvertex = 0xf8e6; t.asciicircum = 0x005e; t.asciicircummonospace = 0xff3e; t.asciitilde = 0x007e; t.asciitildemonospace = 0xff5e; t.ascript = 0x0251; t.ascriptturned = 0x0252; t.asmallhiragana = 0x3041; t.asmallkatakana = 0x30a1; t.asmallkatakanahalfwidth = 0xff67; t.asterisk = 0x002a; t.asteriskaltonearabic = 0x066d; t.asteriskarabic = 0x066d; t.asteriskmath = 0x2217; t.asteriskmonospace = 0xff0a; t.asterisksmall = 0xfe61; t.asterism = 0x2042; t.asuperior = 0xf6e9; t.asymptoticallyequal = 0x2243; t.at = 0x0040; t.atilde = 0x00e3; t.atmonospace = 0xff20; t.atsmall = 0xfe6b; t.aturned = 0x0250; t.aubengali = 0x0994; t.aubopomofo = 0x3120; t.audeva = 0x0914; t.augujarati = 0x0a94; t.augurmukhi = 0x0a14; t.aulengthmarkbengali = 0x09d7; t.aumatragurmukhi = 0x0a4c; t.auvowelsignbengali = 0x09cc; t.auvowelsigndeva = 0x094c; t.auvowelsigngujarati = 0x0acc; t.avagrahadeva = 0x093d; t.aybarmenian = 0x0561; t.ayin = 0x05e2; t.ayinaltonehebrew = 0xfb20; t.ayinhebrew = 0x05e2; t.b = 0x0062; t.babengali = 0x09ac; t.backslash = 0x005c; t.backslashmonospace = 0xff3c; t.badeva = 0x092c; t.bagujarati = 0x0aac; t.bagurmukhi = 0x0a2c; t.bahiragana = 0x3070; t.bahtthai = 0x0e3f; t.bakatakana = 0x30d0; t.bar = 0x007c; t.barmonospace = 0xff5c; t.bbopomofo = 0x3105; t.bcircle = 0x24d1; t.bdotaccent = 0x1e03; t.bdotbelow = 0x1e05; t.beamedsixteenthnotes = 0x266c; t.because = 0x2235; t.becyrillic = 0x0431; t.beharabic = 0x0628; t.behfinalarabic = 0xfe90; t.behinitialarabic = 0xfe91; t.behiragana = 0x3079; t.behmedialarabic = 0xfe92; t.behmeeminitialarabic = 0xfc9f; t.behmeemisolatedarabic = 0xfc08; t.behnoonfinalarabic = 0xfc6d; t.bekatakana = 0x30d9; t.benarmenian = 0x0562; t.bet = 0x05d1; t.beta = 0x03b2; t.betasymbolgreek = 0x03d0; t.betdagesh = 0xfb31; t.betdageshhebrew = 0xfb31; t.bethebrew = 0x05d1; t.betrafehebrew = 0xfb4c; t.bhabengali = 0x09ad; t.bhadeva = 0x092d; t.bhagujarati = 0x0aad; t.bhagurmukhi = 0x0a2d; t.bhook = 0x0253; t.bihiragana = 0x3073; t.bikatakana = 0x30d3; t.bilabialclick = 0x0298; t.bindigurmukhi = 0x0a02; t.birusquare = 0x3331; t.blackcircle = 0x25cf; t.blackdiamond = 0x25c6; t.blackdownpointingtriangle = 0x25bc; t.blackleftpointingpointer = 0x25c4; t.blackleftpointingtriangle = 0x25c0; t.blacklenticularbracketleft = 0x3010; t.blacklenticularbracketleftvertical = 0xfe3b; t.blacklenticularbracketright = 0x3011; t.blacklenticularbracketrightvertical = 0xfe3c; t.blacklowerlefttriangle = 0x25e3; t.blacklowerrighttriangle = 0x25e2; t.blackrectangle = 0x25ac; t.blackrightpointingpointer = 0x25ba; t.blackrightpointingtriangle = 0x25b6; t.blacksmallsquare = 0x25aa; t.blacksmilingface = 0x263b; t.blacksquare = 0x25a0; t.blackstar = 0x2605; t.blackupperlefttriangle = 0x25e4; t.blackupperrighttriangle = 0x25e5; t.blackuppointingsmalltriangle = 0x25b4; t.blackuppointingtriangle = 0x25b2; t.blank = 0x2423; t.blinebelow = 0x1e07; t.block = 0x2588; t.bmonospace = 0xff42; t.bobaimaithai = 0x0e1a; t.bohiragana = 0x307c; t.bokatakana = 0x30dc; t.bparen = 0x249d; t.bqsquare = 0x33c3; t.braceex = 0xf8f4; t.braceleft = 0x007b; t.braceleftbt = 0xf8f3; t.braceleftmid = 0xf8f2; t.braceleftmonospace = 0xff5b; t.braceleftsmall = 0xfe5b; t.bracelefttp = 0xf8f1; t.braceleftvertical = 0xfe37; t.braceright = 0x007d; t.bracerightbt = 0xf8fe; t.bracerightmid = 0xf8fd; t.bracerightmonospace = 0xff5d; t.bracerightsmall = 0xfe5c; t.bracerighttp = 0xf8fc; t.bracerightvertical = 0xfe38; t.bracketleft = 0x005b; t.bracketleftbt = 0xf8f0; t.bracketleftex = 0xf8ef; t.bracketleftmonospace = 0xff3b; t.bracketlefttp = 0xf8ee; t.bracketright = 0x005d; t.bracketrightbt = 0xf8fb; t.bracketrightex = 0xf8fa; t.bracketrightmonospace = 0xff3d; t.bracketrighttp = 0xf8f9; t.breve = 0x02d8; t.brevebelowcmb = 0x032e; t.brevecmb = 0x0306; t.breveinvertedbelowcmb = 0x032f; t.breveinvertedcmb = 0x0311; t.breveinverteddoublecmb = 0x0361; t.bridgebelowcmb = 0x032a; t.bridgeinvertedbelowcmb = 0x033a; t.brokenbar = 0x00a6; t.bstroke = 0x0180; t.bsuperior = 0xf6ea; t.btopbar = 0x0183; t.buhiragana = 0x3076; t.bukatakana = 0x30d6; t.bullet = 0x2022; t.bulletinverse = 0x25d8; t.bulletoperator = 0x2219; t.bullseye = 0x25ce; t.c = 0x0063; t.caarmenian = 0x056e; t.cabengali = 0x099a; t.cacute = 0x0107; t.cadeva = 0x091a; t.cagujarati = 0x0a9a; t.cagurmukhi = 0x0a1a; t.calsquare = 0x3388; t.candrabindubengali = 0x0981; t.candrabinducmb = 0x0310; t.candrabindudeva = 0x0901; t.candrabindugujarati = 0x0a81; t.capslock = 0x21ea; t.careof = 0x2105; t.caron = 0x02c7; t.caronbelowcmb = 0x032c; t.caroncmb = 0x030c; t.carriagereturn = 0x21b5; t.cbopomofo = 0x3118; t.ccaron = 0x010d; t.ccedilla = 0x00e7; t.ccedillaacute = 0x1e09; t.ccircle = 0x24d2; t.ccircumflex = 0x0109; t.ccurl = 0x0255; t.cdot = 0x010b; t.cdotaccent = 0x010b; t.cdsquare = 0x33c5; t.cedilla = 0x00b8; t.cedillacmb = 0x0327; t.cent = 0x00a2; t.centigrade = 0x2103; t.centinferior = 0xf6df; t.centmonospace = 0xffe0; t.centoldstyle = 0xf7a2; t.centsuperior = 0xf6e0; t.chaarmenian = 0x0579; t.chabengali = 0x099b; t.chadeva = 0x091b; t.chagujarati = 0x0a9b; t.chagurmukhi = 0x0a1b; t.chbopomofo = 0x3114; t.cheabkhasiancyrillic = 0x04bd; t.checkmark = 0x2713; t.checyrillic = 0x0447; t.chedescenderabkhasiancyrillic = 0x04bf; t.chedescendercyrillic = 0x04b7; t.chedieresiscyrillic = 0x04f5; t.cheharmenian = 0x0573; t.chekhakassiancyrillic = 0x04cc; t.cheverticalstrokecyrillic = 0x04b9; t.chi = 0x03c7; t.chieuchacirclekorean = 0x3277; t.chieuchaparenkorean = 0x3217; t.chieuchcirclekorean = 0x3269; t.chieuchkorean = 0x314a; t.chieuchparenkorean = 0x3209; t.chochangthai = 0x0e0a; t.chochanthai = 0x0e08; t.chochingthai = 0x0e09; t.chochoethai = 0x0e0c; t.chook = 0x0188; t.cieucacirclekorean = 0x3276; t.cieucaparenkorean = 0x3216; t.cieuccirclekorean = 0x3268; t.cieuckorean = 0x3148; t.cieucparenkorean = 0x3208; t.cieucuparenkorean = 0x321c; t.circle = 0x25cb; t.circlecopyrt = 0x00a9; t.circlemultiply = 0x2297; t.circleot = 0x2299; t.circleplus = 0x2295; t.circlepostalmark = 0x3036; t.circlewithlefthalfblack = 0x25d0; t.circlewithrighthalfblack = 0x25d1; t.circumflex = 0x02c6; t.circumflexbelowcmb = 0x032d; t.circumflexcmb = 0x0302; t.clear = 0x2327; t.clickalveolar = 0x01c2; t.clickdental = 0x01c0; t.clicklateral = 0x01c1; t.clickretroflex = 0x01c3; t.club = 0x2663; t.clubsuitblack = 0x2663; t.clubsuitwhite = 0x2667; t.cmcubedsquare = 0x33a4; t.cmonospace = 0xff43; t.cmsquaredsquare = 0x33a0; t.coarmenian = 0x0581; t.colon = 0x003a; t.colonmonetary = 0x20a1; t.colonmonospace = 0xff1a; t.colonsign = 0x20a1; t.colonsmall = 0xfe55; t.colontriangularhalfmod = 0x02d1; t.colontriangularmod = 0x02d0; t.comma = 0x002c; t.commaabovecmb = 0x0313; t.commaaboverightcmb = 0x0315; t.commaaccent = 0xf6c3; t.commaarabic = 0x060c; t.commaarmenian = 0x055d; t.commainferior = 0xf6e1; t.commamonospace = 0xff0c; t.commareversedabovecmb = 0x0314; t.commareversedmod = 0x02bd; t.commasmall = 0xfe50; t.commasuperior = 0xf6e2; t.commaturnedabovecmb = 0x0312; t.commaturnedmod = 0x02bb; t.compass = 0x263c; t.congruent = 0x2245; t.contourintegral = 0x222e; t.control = 0x2303; t.controlACK = 0x0006; t.controlBEL = 0x0007; t.controlBS = 0x0008; t.controlCAN = 0x0018; t.controlCR = 0x000d; t.controlDC1 = 0x0011; t.controlDC2 = 0x0012; t.controlDC3 = 0x0013; t.controlDC4 = 0x0014; t.controlDEL = 0x007f; t.controlDLE = 0x0010; t.controlEM = 0x0019; t.controlENQ = 0x0005; t.controlEOT = 0x0004; t.controlESC = 0x001b; t.controlETB = 0x0017; t.controlETX = 0x0003; t.controlFF = 0x000c; t.controlFS = 0x001c; t.controlGS = 0x001d; t.controlHT = 0x0009; t.controlLF = 0x000a; t.controlNAK = 0x0015; t.controlNULL = 0x0000; t.controlRS = 0x001e; t.controlSI = 0x000f; t.controlSO = 0x000e; t.controlSOT = 0x0002; t.controlSTX = 0x0001; t.controlSUB = 0x001a; t.controlSYN = 0x0016; t.controlUS = 0x001f; t.controlVT = 0x000b; t.copyright = 0x00a9; t.copyrightsans = 0xf8e9; t.copyrightserif = 0xf6d9; t.cornerbracketleft = 0x300c; t.cornerbracketlefthalfwidth = 0xff62; t.cornerbracketleftvertical = 0xfe41; t.cornerbracketright = 0x300d; t.cornerbracketrighthalfwidth = 0xff63; t.cornerbracketrightvertical = 0xfe42; t.corporationsquare = 0x337f; t.cosquare = 0x33c7; t.coverkgsquare = 0x33c6; t.cparen = 0x249e; t.cruzeiro = 0x20a2; t.cstretched = 0x0297; t.curlyand = 0x22cf; t.curlyor = 0x22ce; t.currency = 0x00a4; t.cyrBreve = 0xf6d1; t.cyrFlex = 0xf6d2; t.cyrbreve = 0xf6d4; t.cyrflex = 0xf6d5; t.d = 0x0064; t.daarmenian = 0x0564; t.dabengali = 0x09a6; t.dadarabic = 0x0636; t.dadeva = 0x0926; t.dadfinalarabic = 0xfebe; t.dadinitialarabic = 0xfebf; t.dadmedialarabic = 0xfec0; t.dagesh = 0x05bc; t.dageshhebrew = 0x05bc; t.dagger = 0x2020; t.daggerdbl = 0x2021; t.dagujarati = 0x0aa6; t.dagurmukhi = 0x0a26; t.dahiragana = 0x3060; t.dakatakana = 0x30c0; t.dalarabic = 0x062f; t.dalet = 0x05d3; t.daletdagesh = 0xfb33; t.daletdageshhebrew = 0xfb33; t.dalethebrew = 0x05d3; t.dalfinalarabic = 0xfeaa; t.dammaarabic = 0x064f; t.dammalowarabic = 0x064f; t.dammatanaltonearabic = 0x064c; t.dammatanarabic = 0x064c; t.danda = 0x0964; t.dargahebrew = 0x05a7; t.dargalefthebrew = 0x05a7; t.dasiapneumatacyrilliccmb = 0x0485; t.dblGrave = 0xf6d3; t.dblanglebracketleft = 0x300a; t.dblanglebracketleftvertical = 0xfe3d; t.dblanglebracketright = 0x300b; t.dblanglebracketrightvertical = 0xfe3e; t.dblarchinvertedbelowcmb = 0x032b; t.dblarrowleft = 0x21d4; t.dblarrowright = 0x21d2; t.dbldanda = 0x0965; t.dblgrave = 0xf6d6; t.dblgravecmb = 0x030f; t.dblintegral = 0x222c; t.dbllowline = 0x2017; t.dbllowlinecmb = 0x0333; t.dbloverlinecmb = 0x033f; t.dblprimemod = 0x02ba; t.dblverticalbar = 0x2016; t.dblverticallineabovecmb = 0x030e; t.dbopomofo = 0x3109; t.dbsquare = 0x33c8; t.dcaron = 0x010f; t.dcedilla = 0x1e11; t.dcircle = 0x24d3; t.dcircumflexbelow = 0x1e13; t.dcroat = 0x0111; t.ddabengali = 0x09a1; t.ddadeva = 0x0921; t.ddagujarati = 0x0aa1; t.ddagurmukhi = 0x0a21; t.ddalarabic = 0x0688; t.ddalfinalarabic = 0xfb89; t.dddhadeva = 0x095c; t.ddhabengali = 0x09a2; t.ddhadeva = 0x0922; t.ddhagujarati = 0x0aa2; t.ddhagurmukhi = 0x0a22; t.ddotaccent = 0x1e0b; t.ddotbelow = 0x1e0d; t.decimalseparatorarabic = 0x066b; t.decimalseparatorpersian = 0x066b; t.decyrillic = 0x0434; t.degree = 0x00b0; t.dehihebrew = 0x05ad; t.dehiragana = 0x3067; t.deicoptic = 0x03ef; t.dekatakana = 0x30c7; t.deleteleft = 0x232b; t.deleteright = 0x2326; t.delta = 0x03b4; t.deltaturned = 0x018d; t.denominatorminusonenumeratorbengali = 0x09f8; t.dezh = 0x02a4; t.dhabengali = 0x09a7; t.dhadeva = 0x0927; t.dhagujarati = 0x0aa7; t.dhagurmukhi = 0x0a27; t.dhook = 0x0257; t.dialytikatonos = 0x0385; t.dialytikatonoscmb = 0x0344; t.diamond = 0x2666; t.diamondsuitwhite = 0x2662; t.dieresis = 0x00a8; t.dieresisacute = 0xf6d7; t.dieresisbelowcmb = 0x0324; t.dieresiscmb = 0x0308; t.dieresisgrave = 0xf6d8; t.dieresistonos = 0x0385; t.dihiragana = 0x3062; t.dikatakana = 0x30c2; t.dittomark = 0x3003; t.divide = 0x00f7; t.divides = 0x2223; t.divisionslash = 0x2215; t.djecyrillic = 0x0452; t.dkshade = 0x2593; t.dlinebelow = 0x1e0f; t.dlsquare = 0x3397; t.dmacron = 0x0111; t.dmonospace = 0xff44; t.dnblock = 0x2584; t.dochadathai = 0x0e0e; t.dodekthai = 0x0e14; t.dohiragana = 0x3069; t.dokatakana = 0x30c9; t.dollar = 0x0024; t.dollarinferior = 0xf6e3; t.dollarmonospace = 0xff04; t.dollaroldstyle = 0xf724; t.dollarsmall = 0xfe69; t.dollarsuperior = 0xf6e4; t.dong = 0x20ab; t.dorusquare = 0x3326; t.dotaccent = 0x02d9; t.dotaccentcmb = 0x0307; t.dotbelowcmb = 0x0323; t.dotbelowcomb = 0x0323; t.dotkatakana = 0x30fb; t.dotlessi = 0x0131; t.dotlessj = 0xf6be; t.dotlessjstrokehook = 0x0284; t.dotmath = 0x22c5; t.dottedcircle = 0x25cc; t.doubleyodpatah = 0xfb1f; t.doubleyodpatahhebrew = 0xfb1f; t.downtackbelowcmb = 0x031e; t.downtackmod = 0x02d5; t.dparen = 0x249f; t.dsuperior = 0xf6eb; t.dtail = 0x0256; t.dtopbar = 0x018c; t.duhiragana = 0x3065; t.dukatakana = 0x30c5; t.dz = 0x01f3; t.dzaltone = 0x02a3; t.dzcaron = 0x01c6; t.dzcurl = 0x02a5; t.dzeabkhasiancyrillic = 0x04e1; t.dzecyrillic = 0x0455; t.dzhecyrillic = 0x045f; t.e = 0x0065; t.eacute = 0x00e9; t.earth = 0x2641; t.ebengali = 0x098f; t.ebopomofo = 0x311c; t.ebreve = 0x0115; t.ecandradeva = 0x090d; t.ecandragujarati = 0x0a8d; t.ecandravowelsigndeva = 0x0945; t.ecandravowelsigngujarati = 0x0ac5; t.ecaron = 0x011b; t.ecedillabreve = 0x1e1d; t.echarmenian = 0x0565; t.echyiwnarmenian = 0x0587; t.ecircle = 0x24d4; t.ecircumflex = 0x00ea; t.ecircumflexacute = 0x1ebf; t.ecircumflexbelow = 0x1e19; t.ecircumflexdotbelow = 0x1ec7; t.ecircumflexgrave = 0x1ec1; t.ecircumflexhookabove = 0x1ec3; t.ecircumflextilde = 0x1ec5; t.ecyrillic = 0x0454; t.edblgrave = 0x0205; t.edeva = 0x090f; t.edieresis = 0x00eb; t.edot = 0x0117; t.edotaccent = 0x0117; t.edotbelow = 0x1eb9; t.eegurmukhi = 0x0a0f; t.eematragurmukhi = 0x0a47; t.efcyrillic = 0x0444; t.egrave = 0x00e8; t.egujarati = 0x0a8f; t.eharmenian = 0x0567; t.ehbopomofo = 0x311d; t.ehiragana = 0x3048; t.ehookabove = 0x1ebb; t.eibopomofo = 0x311f; t.eight = 0x0038; t.eightarabic = 0x0668; t.eightbengali = 0x09ee; t.eightcircle = 0x2467; t.eightcircleinversesansserif = 0x2791; t.eightdeva = 0x096e; t.eighteencircle = 0x2471; t.eighteenparen = 0x2485; t.eighteenperiod = 0x2499; t.eightgujarati = 0x0aee; t.eightgurmukhi = 0x0a6e; t.eighthackarabic = 0x0668; t.eighthangzhou = 0x3028; t.eighthnotebeamed = 0x266b; t.eightideographicparen = 0x3227; t.eightinferior = 0x2088; t.eightmonospace = 0xff18; t.eightoldstyle = 0xf738; t.eightparen = 0x247b; t.eightperiod = 0x248f; t.eightpersian = 0x06f8; t.eightroman = 0x2177; t.eightsuperior = 0x2078; t.eightthai = 0x0e58; t.einvertedbreve = 0x0207; t.eiotifiedcyrillic = 0x0465; t.ekatakana = 0x30a8; t.ekatakanahalfwidth = 0xff74; t.ekonkargurmukhi = 0x0a74; t.ekorean = 0x3154; t.elcyrillic = 0x043b; t.element = 0x2208; t.elevencircle = 0x246a; t.elevenparen = 0x247e; t.elevenperiod = 0x2492; t.elevenroman = 0x217a; t.ellipsis = 0x2026; t.ellipsisvertical = 0x22ee; t.emacron = 0x0113; t.emacronacute = 0x1e17; t.emacrongrave = 0x1e15; t.emcyrillic = 0x043c; t.emdash = 0x2014; t.emdashvertical = 0xfe31; t.emonospace = 0xff45; t.emphasismarkarmenian = 0x055b; t.emptyset = 0x2205; t.enbopomofo = 0x3123; t.encyrillic = 0x043d; t.endash = 0x2013; t.endashvertical = 0xfe32; t.endescendercyrillic = 0x04a3; t.eng = 0x014b; t.engbopomofo = 0x3125; t.enghecyrillic = 0x04a5; t.enhookcyrillic = 0x04c8; t.enspace = 0x2002; t.eogonek = 0x0119; t.eokorean = 0x3153; t.eopen = 0x025b; t.eopenclosed = 0x029a; t.eopenreversed = 0x025c; t.eopenreversedclosed = 0x025e; t.eopenreversedhook = 0x025d; t.eparen = 0x24a0; t.epsilon = 0x03b5; t.epsilontonos = 0x03ad; t.equal = 0x003d; t.equalmonospace = 0xff1d; t.equalsmall = 0xfe66; t.equalsuperior = 0x207c; t.equivalence = 0x2261; t.erbopomofo = 0x3126; t.ercyrillic = 0x0440; t.ereversed = 0x0258; t.ereversedcyrillic = 0x044d; t.escyrillic = 0x0441; t.esdescendercyrillic = 0x04ab; t.esh = 0x0283; t.eshcurl = 0x0286; t.eshortdeva = 0x090e; t.eshortvowelsigndeva = 0x0946; t.eshreversedloop = 0x01aa; t.eshsquatreversed = 0x0285; t.esmallhiragana = 0x3047; t.esmallkatakana = 0x30a7; t.esmallkatakanahalfwidth = 0xff6a; t.estimated = 0x212e; t.esuperior = 0xf6ec; t.eta = 0x03b7; t.etarmenian = 0x0568; t.etatonos = 0x03ae; t.eth = 0x00f0; t.etilde = 0x1ebd; t.etildebelow = 0x1e1b; t.etnahtafoukhhebrew = 0x0591; t.etnahtafoukhlefthebrew = 0x0591; t.etnahtahebrew = 0x0591; t.etnahtalefthebrew = 0x0591; t.eturned = 0x01dd; t.eukorean = 0x3161; t.euro = 0x20ac; t.evowelsignbengali = 0x09c7; t.evowelsigndeva = 0x0947; t.evowelsigngujarati = 0x0ac7; t.exclam = 0x0021; t.exclamarmenian = 0x055c; t.exclamdbl = 0x203c; t.exclamdown = 0x00a1; t.exclamdownsmall = 0xf7a1; t.exclammonospace = 0xff01; t.exclamsmall = 0xf721; t.existential = 0x2203; t.ezh = 0x0292; t.ezhcaron = 0x01ef; t.ezhcurl = 0x0293; t.ezhreversed = 0x01b9; t.ezhtail = 0x01ba; t.f = 0x0066; t.fadeva = 0x095e; t.fagurmukhi = 0x0a5e; t.fahrenheit = 0x2109; t.fathaarabic = 0x064e; t.fathalowarabic = 0x064e; t.fathatanarabic = 0x064b; t.fbopomofo = 0x3108; t.fcircle = 0x24d5; t.fdotaccent = 0x1e1f; t.feharabic = 0x0641; t.feharmenian = 0x0586; t.fehfinalarabic = 0xfed2; t.fehinitialarabic = 0xfed3; t.fehmedialarabic = 0xfed4; t.feicoptic = 0x03e5; t.female = 0x2640; t.ff = 0xfb00; t.f_f = 0xfb00; t.ffi = 0xfb03; t.f_f_i = 0xfb03; t.ffl = 0xfb04; t.f_f_l = 0xfb04; t.fi = 0xfb01; t.f_i = 0xfb01; t.fifteencircle = 0x246e; t.fifteenparen = 0x2482; t.fifteenperiod = 0x2496; t.figuredash = 0x2012; t.filledbox = 0x25a0; t.filledrect = 0x25ac; t.finalkaf = 0x05da; t.finalkafdagesh = 0xfb3a; t.finalkafdageshhebrew = 0xfb3a; t.finalkafhebrew = 0x05da; t.finalmem = 0x05dd; t.finalmemhebrew = 0x05dd; t.finalnun = 0x05df; t.finalnunhebrew = 0x05df; t.finalpe = 0x05e3; t.finalpehebrew = 0x05e3; t.finaltsadi = 0x05e5; t.finaltsadihebrew = 0x05e5; t.firsttonechinese = 0x02c9; t.fisheye = 0x25c9; t.fitacyrillic = 0x0473; t.five = 0x0035; t.fivearabic = 0x0665; t.fivebengali = 0x09eb; t.fivecircle = 0x2464; t.fivecircleinversesansserif = 0x278e; t.fivedeva = 0x096b; t.fiveeighths = 0x215d; t.fivegujarati = 0x0aeb; t.fivegurmukhi = 0x0a6b; t.fivehackarabic = 0x0665; t.fivehangzhou = 0x3025; t.fiveideographicparen = 0x3224; t.fiveinferior = 0x2085; t.fivemonospace = 0xff15; t.fiveoldstyle = 0xf735; t.fiveparen = 0x2478; t.fiveperiod = 0x248c; t.fivepersian = 0x06f5; t.fiveroman = 0x2174; t.fivesuperior = 0x2075; t.fivethai = 0x0e55; t.fl = 0xfb02; t.f_l = 0xfb02; t.florin = 0x0192; t.fmonospace = 0xff46; t.fmsquare = 0x3399; t.fofanthai = 0x0e1f; t.fofathai = 0x0e1d; t.fongmanthai = 0x0e4f; t.forall = 0x2200; t.four = 0x0034; t.fourarabic = 0x0664; t.fourbengali = 0x09ea; t.fourcircle = 0x2463; t.fourcircleinversesansserif = 0x278d; t.fourdeva = 0x096a; t.fourgujarati = 0x0aea; t.fourgurmukhi = 0x0a6a; t.fourhackarabic = 0x0664; t.fourhangzhou = 0x3024; t.fourideographicparen = 0x3223; t.fourinferior = 0x2084; t.fourmonospace = 0xff14; t.fournumeratorbengali = 0x09f7; t.fouroldstyle = 0xf734; t.fourparen = 0x2477; t.fourperiod = 0x248b; t.fourpersian = 0x06f4; t.fourroman = 0x2173; t.foursuperior = 0x2074; t.fourteencircle = 0x246d; t.fourteenparen = 0x2481; t.fourteenperiod = 0x2495; t.fourthai = 0x0e54; t.fourthtonechinese = 0x02cb; t.fparen = 0x24a1; t.fraction = 0x2044; t.franc = 0x20a3; t.g = 0x0067; t.gabengali = 0x0997; t.gacute = 0x01f5; t.gadeva = 0x0917; t.gafarabic = 0x06af; t.gaffinalarabic = 0xfb93; t.gafinitialarabic = 0xfb94; t.gafmedialarabic = 0xfb95; t.gagujarati = 0x0a97; t.gagurmukhi = 0x0a17; t.gahiragana = 0x304c; t.gakatakana = 0x30ac; t.gamma = 0x03b3; t.gammalatinsmall = 0x0263; t.gammasuperior = 0x02e0; t.gangiacoptic = 0x03eb; t.gbopomofo = 0x310d; t.gbreve = 0x011f; t.gcaron = 0x01e7; t.gcedilla = 0x0123; t.gcircle = 0x24d6; t.gcircumflex = 0x011d; t.gcommaaccent = 0x0123; t.gdot = 0x0121; t.gdotaccent = 0x0121; t.gecyrillic = 0x0433; t.gehiragana = 0x3052; t.gekatakana = 0x30b2; t.geometricallyequal = 0x2251; t.gereshaccenthebrew = 0x059c; t.gereshhebrew = 0x05f3; t.gereshmuqdamhebrew = 0x059d; t.germandbls = 0x00df; t.gershayimaccenthebrew = 0x059e; t.gershayimhebrew = 0x05f4; t.getamark = 0x3013; t.ghabengali = 0x0998; t.ghadarmenian = 0x0572; t.ghadeva = 0x0918; t.ghagujarati = 0x0a98; t.ghagurmukhi = 0x0a18; t.ghainarabic = 0x063a; t.ghainfinalarabic = 0xfece; t.ghaininitialarabic = 0xfecf; t.ghainmedialarabic = 0xfed0; t.ghemiddlehookcyrillic = 0x0495; t.ghestrokecyrillic = 0x0493; t.gheupturncyrillic = 0x0491; t.ghhadeva = 0x095a; t.ghhagurmukhi = 0x0a5a; t.ghook = 0x0260; t.ghzsquare = 0x3393; t.gihiragana = 0x304e; t.gikatakana = 0x30ae; t.gimarmenian = 0x0563; t.gimel = 0x05d2; t.gimeldagesh = 0xfb32; t.gimeldageshhebrew = 0xfb32; t.gimelhebrew = 0x05d2; t.gjecyrillic = 0x0453; t.glottalinvertedstroke = 0x01be; t.glottalstop = 0x0294; t.glottalstopinverted = 0x0296; t.glottalstopmod = 0x02c0; t.glottalstopreversed = 0x0295; t.glottalstopreversedmod = 0x02c1; t.glottalstopreversedsuperior = 0x02e4; t.glottalstopstroke = 0x02a1; t.glottalstopstrokereversed = 0x02a2; t.gmacron = 0x1e21; t.gmonospace = 0xff47; t.gohiragana = 0x3054; t.gokatakana = 0x30b4; t.gparen = 0x24a2; t.gpasquare = 0x33ac; t.gradient = 0x2207; t.grave = 0x0060; t.gravebelowcmb = 0x0316; t.gravecmb = 0x0300; t.gravecomb = 0x0300; t.gravedeva = 0x0953; t.gravelowmod = 0x02ce; t.gravemonospace = 0xff40; t.gravetonecmb = 0x0340; t.greater = 0x003e; t.greaterequal = 0x2265; t.greaterequalorless = 0x22db; t.greatermonospace = 0xff1e; t.greaterorequivalent = 0x2273; t.greaterorless = 0x2277; t.greateroverequal = 0x2267; t.greatersmall = 0xfe65; t.gscript = 0x0261; t.gstroke = 0x01e5; t.guhiragana = 0x3050; t.guillemotleft = 0x00ab; t.guillemotright = 0x00bb; t.guilsinglleft = 0x2039; t.guilsinglright = 0x203a; t.gukatakana = 0x30b0; t.guramusquare = 0x3318; t.gysquare = 0x33c9; t.h = 0x0068; t.haabkhasiancyrillic = 0x04a9; t.haaltonearabic = 0x06c1; t.habengali = 0x09b9; t.hadescendercyrillic = 0x04b3; t.hadeva = 0x0939; t.hagujarati = 0x0ab9; t.hagurmukhi = 0x0a39; t.haharabic = 0x062d; t.hahfinalarabic = 0xfea2; t.hahinitialarabic = 0xfea3; t.hahiragana = 0x306f; t.hahmedialarabic = 0xfea4; t.haitusquare = 0x332a; t.hakatakana = 0x30cf; t.hakatakanahalfwidth = 0xff8a; t.halantgurmukhi = 0x0a4d; t.hamzaarabic = 0x0621; t.hamzalowarabic = 0x0621; t.hangulfiller = 0x3164; t.hardsigncyrillic = 0x044a; t.harpoonleftbarbup = 0x21bc; t.harpoonrightbarbup = 0x21c0; t.hasquare = 0x33ca; t.hatafpatah = 0x05b2; t.hatafpatah16 = 0x05b2; t.hatafpatah23 = 0x05b2; t.hatafpatah2f = 0x05b2; t.hatafpatahhebrew = 0x05b2; t.hatafpatahnarrowhebrew = 0x05b2; t.hatafpatahquarterhebrew = 0x05b2; t.hatafpatahwidehebrew = 0x05b2; t.hatafqamats = 0x05b3; t.hatafqamats1b = 0x05b3; t.hatafqamats28 = 0x05b3; t.hatafqamats34 = 0x05b3; t.hatafqamatshebrew = 0x05b3; t.hatafqamatsnarrowhebrew = 0x05b3; t.hatafqamatsquarterhebrew = 0x05b3; t.hatafqamatswidehebrew = 0x05b3; t.hatafsegol = 0x05b1; t.hatafsegol17 = 0x05b1; t.hatafsegol24 = 0x05b1; t.hatafsegol30 = 0x05b1; t.hatafsegolhebrew = 0x05b1; t.hatafsegolnarrowhebrew = 0x05b1; t.hatafsegolquarterhebrew = 0x05b1; t.hatafsegolwidehebrew = 0x05b1; t.hbar = 0x0127; t.hbopomofo = 0x310f; t.hbrevebelow = 0x1e2b; t.hcedilla = 0x1e29; t.hcircle = 0x24d7; t.hcircumflex = 0x0125; t.hdieresis = 0x1e27; t.hdotaccent = 0x1e23; t.hdotbelow = 0x1e25; t.he = 0x05d4; t.heart = 0x2665; t.heartsuitblack = 0x2665; t.heartsuitwhite = 0x2661; t.hedagesh = 0xfb34; t.hedageshhebrew = 0xfb34; t.hehaltonearabic = 0x06c1; t.heharabic = 0x0647; t.hehebrew = 0x05d4; t.hehfinalaltonearabic = 0xfba7; t.hehfinalalttwoarabic = 0xfeea; t.hehfinalarabic = 0xfeea; t.hehhamzaabovefinalarabic = 0xfba5; t.hehhamzaaboveisolatedarabic = 0xfba4; t.hehinitialaltonearabic = 0xfba8; t.hehinitialarabic = 0xfeeb; t.hehiragana = 0x3078; t.hehmedialaltonearabic = 0xfba9; t.hehmedialarabic = 0xfeec; t.heiseierasquare = 0x337b; t.hekatakana = 0x30d8; t.hekatakanahalfwidth = 0xff8d; t.hekutaarusquare = 0x3336; t.henghook = 0x0267; t.herutusquare = 0x3339; t.het = 0x05d7; t.hethebrew = 0x05d7; t.hhook = 0x0266; t.hhooksuperior = 0x02b1; t.hieuhacirclekorean = 0x327b; t.hieuhaparenkorean = 0x321b; t.hieuhcirclekorean = 0x326d; t.hieuhkorean = 0x314e; t.hieuhparenkorean = 0x320d; t.hihiragana = 0x3072; t.hikatakana = 0x30d2; t.hikatakanahalfwidth = 0xff8b; t.hiriq = 0x05b4; t.hiriq14 = 0x05b4; t.hiriq21 = 0x05b4; t.hiriq2d = 0x05b4; t.hiriqhebrew = 0x05b4; t.hiriqnarrowhebrew = 0x05b4; t.hiriqquarterhebrew = 0x05b4; t.hiriqwidehebrew = 0x05b4; t.hlinebelow = 0x1e96; t.hmonospace = 0xff48; t.hoarmenian = 0x0570; t.hohipthai = 0x0e2b; t.hohiragana = 0x307b; t.hokatakana = 0x30db; t.hokatakanahalfwidth = 0xff8e; t.holam = 0x05b9; t.holam19 = 0x05b9; t.holam26 = 0x05b9; t.holam32 = 0x05b9; t.holamhebrew = 0x05b9; t.holamnarrowhebrew = 0x05b9; t.holamquarterhebrew = 0x05b9; t.holamwidehebrew = 0x05b9; t.honokhukthai = 0x0e2e; t.hookabovecomb = 0x0309; t.hookcmb = 0x0309; t.hookpalatalizedbelowcmb = 0x0321; t.hookretroflexbelowcmb = 0x0322; t.hoonsquare = 0x3342; t.horicoptic = 0x03e9; t.horizontalbar = 0x2015; t.horncmb = 0x031b; t.hotsprings = 0x2668; t.house = 0x2302; t.hparen = 0x24a3; t.hsuperior = 0x02b0; t.hturned = 0x0265; t.huhiragana = 0x3075; t.huiitosquare = 0x3333; t.hukatakana = 0x30d5; t.hukatakanahalfwidth = 0xff8c; t.hungarumlaut = 0x02dd; t.hungarumlautcmb = 0x030b; t.hv = 0x0195; t.hyphen = 0x002d; t.hypheninferior = 0xf6e5; t.hyphenmonospace = 0xff0d; t.hyphensmall = 0xfe63; t.hyphensuperior = 0xf6e6; t.hyphentwo = 0x2010; t.i = 0x0069; t.iacute = 0x00ed; t.iacyrillic = 0x044f; t.ibengali = 0x0987; t.ibopomofo = 0x3127; t.ibreve = 0x012d; t.icaron = 0x01d0; t.icircle = 0x24d8; t.icircumflex = 0x00ee; t.icyrillic = 0x0456; t.idblgrave = 0x0209; t.ideographearthcircle = 0x328f; t.ideographfirecircle = 0x328b; t.ideographicallianceparen = 0x323f; t.ideographiccallparen = 0x323a; t.ideographiccentrecircle = 0x32a5; t.ideographicclose = 0x3006; t.ideographiccomma = 0x3001; t.ideographiccommaleft = 0xff64; t.ideographiccongratulationparen = 0x3237; t.ideographiccorrectcircle = 0x32a3; t.ideographicearthparen = 0x322f; t.ideographicenterpriseparen = 0x323d; t.ideographicexcellentcircle = 0x329d; t.ideographicfestivalparen = 0x3240; t.ideographicfinancialcircle = 0x3296; t.ideographicfinancialparen = 0x3236; t.ideographicfireparen = 0x322b; t.ideographichaveparen = 0x3232; t.ideographichighcircle = 0x32a4; t.ideographiciterationmark = 0x3005; t.ideographiclaborcircle = 0x3298; t.ideographiclaborparen = 0x3238; t.ideographicleftcircle = 0x32a7; t.ideographiclowcircle = 0x32a6; t.ideographicmedicinecircle = 0x32a9; t.ideographicmetalparen = 0x322e; t.ideographicmoonparen = 0x322a; t.ideographicnameparen = 0x3234; t.ideographicperiod = 0x3002; t.ideographicprintcircle = 0x329e; t.ideographicreachparen = 0x3243; t.ideographicrepresentparen = 0x3239; t.ideographicresourceparen = 0x323e; t.ideographicrightcircle = 0x32a8; t.ideographicsecretcircle = 0x3299; t.ideographicselfparen = 0x3242; t.ideographicsocietyparen = 0x3233; t.ideographicspace = 0x3000; t.ideographicspecialparen = 0x3235; t.ideographicstockparen = 0x3231; t.ideographicstudyparen = 0x323b; t.ideographicsunparen = 0x3230; t.ideographicsuperviseparen = 0x323c; t.ideographicwaterparen = 0x322c; t.ideographicwoodparen = 0x322d; t.ideographiczero = 0x3007; t.ideographmetalcircle = 0x328e; t.ideographmooncircle = 0x328a; t.ideographnamecircle = 0x3294; t.ideographsuncircle = 0x3290; t.ideographwatercircle = 0x328c; t.ideographwoodcircle = 0x328d; t.ideva = 0x0907; t.idieresis = 0x00ef; t.idieresisacute = 0x1e2f; t.idieresiscyrillic = 0x04e5; t.idotbelow = 0x1ecb; t.iebrevecyrillic = 0x04d7; t.iecyrillic = 0x0435; t.ieungacirclekorean = 0x3275; t.ieungaparenkorean = 0x3215; t.ieungcirclekorean = 0x3267; t.ieungkorean = 0x3147; t.ieungparenkorean = 0x3207; t.igrave = 0x00ec; t.igujarati = 0x0a87; t.igurmukhi = 0x0a07; t.ihiragana = 0x3044; t.ihookabove = 0x1ec9; t.iibengali = 0x0988; t.iicyrillic = 0x0438; t.iideva = 0x0908; t.iigujarati = 0x0a88; t.iigurmukhi = 0x0a08; t.iimatragurmukhi = 0x0a40; t.iinvertedbreve = 0x020b; t.iishortcyrillic = 0x0439; t.iivowelsignbengali = 0x09c0; t.iivowelsigndeva = 0x0940; t.iivowelsigngujarati = 0x0ac0; t.ij = 0x0133; t.ikatakana = 0x30a4; t.ikatakanahalfwidth = 0xff72; t.ikorean = 0x3163; t.ilde = 0x02dc; t.iluyhebrew = 0x05ac; t.imacron = 0x012b; t.imacroncyrillic = 0x04e3; t.imageorapproximatelyequal = 0x2253; t.imatragurmukhi = 0x0a3f; t.imonospace = 0xff49; t.increment = 0x2206; t.infinity = 0x221e; t.iniarmenian = 0x056b; t.integral = 0x222b; t.integralbottom = 0x2321; t.integralbt = 0x2321; t.integralex = 0xf8f5; t.integraltop = 0x2320; t.integraltp = 0x2320; t.intersection = 0x2229; t.intisquare = 0x3305; t.invbullet = 0x25d8; t.invcircle = 0x25d9; t.invsmileface = 0x263b; t.iocyrillic = 0x0451; t.iogonek = 0x012f; t.iota = 0x03b9; t.iotadieresis = 0x03ca; t.iotadieresistonos = 0x0390; t.iotalatin = 0x0269; t.iotatonos = 0x03af; t.iparen = 0x24a4; t.irigurmukhi = 0x0a72; t.ismallhiragana = 0x3043; t.ismallkatakana = 0x30a3; t.ismallkatakanahalfwidth = 0xff68; t.issharbengali = 0x09fa; t.istroke = 0x0268; t.isuperior = 0xf6ed; t.iterationhiragana = 0x309d; t.iterationkatakana = 0x30fd; t.itilde = 0x0129; t.itildebelow = 0x1e2d; t.iubopomofo = 0x3129; t.iucyrillic = 0x044e; t.ivowelsignbengali = 0x09bf; t.ivowelsigndeva = 0x093f; t.ivowelsigngujarati = 0x0abf; t.izhitsacyrillic = 0x0475; t.izhitsadblgravecyrillic = 0x0477; t.j = 0x006a; t.jaarmenian = 0x0571; t.jabengali = 0x099c; t.jadeva = 0x091c; t.jagujarati = 0x0a9c; t.jagurmukhi = 0x0a1c; t.jbopomofo = 0x3110; t.jcaron = 0x01f0; t.jcircle = 0x24d9; t.jcircumflex = 0x0135; t.jcrossedtail = 0x029d; t.jdotlessstroke = 0x025f; t.jecyrillic = 0x0458; t.jeemarabic = 0x062c; t.jeemfinalarabic = 0xfe9e; t.jeeminitialarabic = 0xfe9f; t.jeemmedialarabic = 0xfea0; t.jeharabic = 0x0698; t.jehfinalarabic = 0xfb8b; t.jhabengali = 0x099d; t.jhadeva = 0x091d; t.jhagujarati = 0x0a9d; t.jhagurmukhi = 0x0a1d; t.jheharmenian = 0x057b; t.jis = 0x3004; t.jmonospace = 0xff4a; t.jparen = 0x24a5; t.jsuperior = 0x02b2; t.k = 0x006b; t.kabashkircyrillic = 0x04a1; t.kabengali = 0x0995; t.kacute = 0x1e31; t.kacyrillic = 0x043a; t.kadescendercyrillic = 0x049b; t.kadeva = 0x0915; t.kaf = 0x05db; t.kafarabic = 0x0643; t.kafdagesh = 0xfb3b; t.kafdageshhebrew = 0xfb3b; t.kaffinalarabic = 0xfeda; t.kafhebrew = 0x05db; t.kafinitialarabic = 0xfedb; t.kafmedialarabic = 0xfedc; t.kafrafehebrew = 0xfb4d; t.kagujarati = 0x0a95; t.kagurmukhi = 0x0a15; t.kahiragana = 0x304b; t.kahookcyrillic = 0x04c4; t.kakatakana = 0x30ab; t.kakatakanahalfwidth = 0xff76; t.kappa = 0x03ba; t.kappasymbolgreek = 0x03f0; t.kapyeounmieumkorean = 0x3171; t.kapyeounphieuphkorean = 0x3184; t.kapyeounpieupkorean = 0x3178; t.kapyeounssangpieupkorean = 0x3179; t.karoriisquare = 0x330d; t.kashidaautoarabic = 0x0640; t.kashidaautonosidebearingarabic = 0x0640; t.kasmallkatakana = 0x30f5; t.kasquare = 0x3384; t.kasraarabic = 0x0650; t.kasratanarabic = 0x064d; t.kastrokecyrillic = 0x049f; t.katahiraprolongmarkhalfwidth = 0xff70; t.kaverticalstrokecyrillic = 0x049d; t.kbopomofo = 0x310e; t.kcalsquare = 0x3389; t.kcaron = 0x01e9; t.kcedilla = 0x0137; t.kcircle = 0x24da; t.kcommaaccent = 0x0137; t.kdotbelow = 0x1e33; t.keharmenian = 0x0584; t.kehiragana = 0x3051; t.kekatakana = 0x30b1; t.kekatakanahalfwidth = 0xff79; t.kenarmenian = 0x056f; t.kesmallkatakana = 0x30f6; t.kgreenlandic = 0x0138; t.khabengali = 0x0996; t.khacyrillic = 0x0445; t.khadeva = 0x0916; t.khagujarati = 0x0a96; t.khagurmukhi = 0x0a16; t.khaharabic = 0x062e; t.khahfinalarabic = 0xfea6; t.khahinitialarabic = 0xfea7; t.khahmedialarabic = 0xfea8; t.kheicoptic = 0x03e7; t.khhadeva = 0x0959; t.khhagurmukhi = 0x0a59; t.khieukhacirclekorean = 0x3278; t.khieukhaparenkorean = 0x3218; t.khieukhcirclekorean = 0x326a; t.khieukhkorean = 0x314b; t.khieukhparenkorean = 0x320a; t.khokhaithai = 0x0e02; t.khokhonthai = 0x0e05; t.khokhuatthai = 0x0e03; t.khokhwaithai = 0x0e04; t.khomutthai = 0x0e5b; t.khook = 0x0199; t.khorakhangthai = 0x0e06; t.khzsquare = 0x3391; t.kihiragana = 0x304d; t.kikatakana = 0x30ad; t.kikatakanahalfwidth = 0xff77; t.kiroguramusquare = 0x3315; t.kiromeetorusquare = 0x3316; t.kirosquare = 0x3314; t.kiyeokacirclekorean = 0x326e; t.kiyeokaparenkorean = 0x320e; t.kiyeokcirclekorean = 0x3260; t.kiyeokkorean = 0x3131; t.kiyeokparenkorean = 0x3200; t.kiyeoksioskorean = 0x3133; t.kjecyrillic = 0x045c; t.klinebelow = 0x1e35; t.klsquare = 0x3398; t.kmcubedsquare = 0x33a6; t.kmonospace = 0xff4b; t.kmsquaredsquare = 0x33a2; t.kohiragana = 0x3053; t.kohmsquare = 0x33c0; t.kokaithai = 0x0e01; t.kokatakana = 0x30b3; t.kokatakanahalfwidth = 0xff7a; t.kooposquare = 0x331e; t.koppacyrillic = 0x0481; t.koreanstandardsymbol = 0x327f; t.koroniscmb = 0x0343; t.kparen = 0x24a6; t.kpasquare = 0x33aa; t.ksicyrillic = 0x046f; t.ktsquare = 0x33cf; t.kturned = 0x029e; t.kuhiragana = 0x304f; t.kukatakana = 0x30af; t.kukatakanahalfwidth = 0xff78; t.kvsquare = 0x33b8; t.kwsquare = 0x33be; t.l = 0x006c; t.labengali = 0x09b2; t.lacute = 0x013a; t.ladeva = 0x0932; t.lagujarati = 0x0ab2; t.lagurmukhi = 0x0a32; t.lakkhangyaothai = 0x0e45; t.lamaleffinalarabic = 0xfefc; t.lamalefhamzaabovefinalarabic = 0xfef8; t.lamalefhamzaaboveisolatedarabic = 0xfef7; t.lamalefhamzabelowfinalarabic = 0xfefa; t.lamalefhamzabelowisolatedarabic = 0xfef9; t.lamalefisolatedarabic = 0xfefb; t.lamalefmaddaabovefinalarabic = 0xfef6; t.lamalefmaddaaboveisolatedarabic = 0xfef5; t.lamarabic = 0x0644; t.lambda = 0x03bb; t.lambdastroke = 0x019b; t.lamed = 0x05dc; t.lameddagesh = 0xfb3c; t.lameddageshhebrew = 0xfb3c; t.lamedhebrew = 0x05dc; t.lamfinalarabic = 0xfede; t.lamhahinitialarabic = 0xfcca; t.laminitialarabic = 0xfedf; t.lamjeeminitialarabic = 0xfcc9; t.lamkhahinitialarabic = 0xfccb; t.lamlamhehisolatedarabic = 0xfdf2; t.lammedialarabic = 0xfee0; t.lammeemhahinitialarabic = 0xfd88; t.lammeeminitialarabic = 0xfccc; t.largecircle = 0x25ef; t.lbar = 0x019a; t.lbelt = 0x026c; t.lbopomofo = 0x310c; t.lcaron = 0x013e; t.lcedilla = 0x013c; t.lcircle = 0x24db; t.lcircumflexbelow = 0x1e3d; t.lcommaaccent = 0x013c; t.ldot = 0x0140; t.ldotaccent = 0x0140; t.ldotbelow = 0x1e37; t.ldotbelowmacron = 0x1e39; t.leftangleabovecmb = 0x031a; t.lefttackbelowcmb = 0x0318; t.less = 0x003c; t.lessequal = 0x2264; t.lessequalorgreater = 0x22da; t.lessmonospace = 0xff1c; t.lessorequivalent = 0x2272; t.lessorgreater = 0x2276; t.lessoverequal = 0x2266; t.lesssmall = 0xfe64; t.lezh = 0x026e; t.lfblock = 0x258c; t.lhookretroflex = 0x026d; t.lira = 0x20a4; t.liwnarmenian = 0x056c; t.lj = 0x01c9; t.ljecyrillic = 0x0459; t.ll = 0xf6c0; t.lladeva = 0x0933; t.llagujarati = 0x0ab3; t.llinebelow = 0x1e3b; t.llladeva = 0x0934; t.llvocalicbengali = 0x09e1; t.llvocalicdeva = 0x0961; t.llvocalicvowelsignbengali = 0x09e3; t.llvocalicvowelsigndeva = 0x0963; t.lmiddletilde = 0x026b; t.lmonospace = 0xff4c; t.lmsquare = 0x33d0; t.lochulathai = 0x0e2c; t.logicaland = 0x2227; t.logicalnot = 0x00ac; t.logicalnotreversed = 0x2310; t.logicalor = 0x2228; t.lolingthai = 0x0e25; t.longs = 0x017f; t.lowlinecenterline = 0xfe4e; t.lowlinecmb = 0x0332; t.lowlinedashed = 0xfe4d; t.lozenge = 0x25ca; t.lparen = 0x24a7; t.lslash = 0x0142; t.lsquare = 0x2113; t.lsuperior = 0xf6ee; t.ltshade = 0x2591; t.luthai = 0x0e26; t.lvocalicbengali = 0x098c; t.lvocalicdeva = 0x090c; t.lvocalicvowelsignbengali = 0x09e2; t.lvocalicvowelsigndeva = 0x0962; t.lxsquare = 0x33d3; t.m = 0x006d; t.mabengali = 0x09ae; t.macron = 0x00af; t.macronbelowcmb = 0x0331; t.macroncmb = 0x0304; t.macronlowmod = 0x02cd; t.macronmonospace = 0xffe3; t.macute = 0x1e3f; t.madeva = 0x092e; t.magujarati = 0x0aae; t.magurmukhi = 0x0a2e; t.mahapakhhebrew = 0x05a4; t.mahapakhlefthebrew = 0x05a4; t.mahiragana = 0x307e; t.maichattawalowleftthai = 0xf895; t.maichattawalowrightthai = 0xf894; t.maichattawathai = 0x0e4b; t.maichattawaupperleftthai = 0xf893; t.maieklowleftthai = 0xf88c; t.maieklowrightthai = 0xf88b; t.maiekthai = 0x0e48; t.maiekupperleftthai = 0xf88a; t.maihanakatleftthai = 0xf884; t.maihanakatthai = 0x0e31; t.maitaikhuleftthai = 0xf889; t.maitaikhuthai = 0x0e47; t.maitholowleftthai = 0xf88f; t.maitholowrightthai = 0xf88e; t.maithothai = 0x0e49; t.maithoupperleftthai = 0xf88d; t.maitrilowleftthai = 0xf892; t.maitrilowrightthai = 0xf891; t.maitrithai = 0x0e4a; t.maitriupperleftthai = 0xf890; t.maiyamokthai = 0x0e46; t.makatakana = 0x30de; t.makatakanahalfwidth = 0xff8f; t.male = 0x2642; t.mansyonsquare = 0x3347; t.maqafhebrew = 0x05be; t.mars = 0x2642; t.masoracirclehebrew = 0x05af; t.masquare = 0x3383; t.mbopomofo = 0x3107; t.mbsquare = 0x33d4; t.mcircle = 0x24dc; t.mcubedsquare = 0x33a5; t.mdotaccent = 0x1e41; t.mdotbelow = 0x1e43; t.meemarabic = 0x0645; t.meemfinalarabic = 0xfee2; t.meeminitialarabic = 0xfee3; t.meemmedialarabic = 0xfee4; t.meemmeeminitialarabic = 0xfcd1; t.meemmeemisolatedarabic = 0xfc48; t.meetorusquare = 0x334d; t.mehiragana = 0x3081; t.meizierasquare = 0x337e; t.mekatakana = 0x30e1; t.mekatakanahalfwidth = 0xff92; t.mem = 0x05de; t.memdagesh = 0xfb3e; t.memdageshhebrew = 0xfb3e; t.memhebrew = 0x05de; t.menarmenian = 0x0574; t.merkhahebrew = 0x05a5; t.merkhakefulahebrew = 0x05a6; t.merkhakefulalefthebrew = 0x05a6; t.merkhalefthebrew = 0x05a5; t.mhook = 0x0271; t.mhzsquare = 0x3392; t.middledotkatakanahalfwidth = 0xff65; t.middot = 0x00b7; t.mieumacirclekorean = 0x3272; t.mieumaparenkorean = 0x3212; t.mieumcirclekorean = 0x3264; t.mieumkorean = 0x3141; t.mieumpansioskorean = 0x3170; t.mieumparenkorean = 0x3204; t.mieumpieupkorean = 0x316e; t.mieumsioskorean = 0x316f; t.mihiragana = 0x307f; t.mikatakana = 0x30df; t.mikatakanahalfwidth = 0xff90; t.minus = 0x2212; t.minusbelowcmb = 0x0320; t.minuscircle = 0x2296; t.minusmod = 0x02d7; t.minusplus = 0x2213; t.minute = 0x2032; t.miribaarusquare = 0x334a; t.mirisquare = 0x3349; t.mlonglegturned = 0x0270; t.mlsquare = 0x3396; t.mmcubedsquare = 0x33a3; t.mmonospace = 0xff4d; t.mmsquaredsquare = 0x339f; t.mohiragana = 0x3082; t.mohmsquare = 0x33c1; t.mokatakana = 0x30e2; t.mokatakanahalfwidth = 0xff93; t.molsquare = 0x33d6; t.momathai = 0x0e21; t.moverssquare = 0x33a7; t.moverssquaredsquare = 0x33a8; t.mparen = 0x24a8; t.mpasquare = 0x33ab; t.mssquare = 0x33b3; t.msuperior = 0xf6ef; t.mturned = 0x026f; t.mu = 0x00b5; t.mu1 = 0x00b5; t.muasquare = 0x3382; t.muchgreater = 0x226b; t.muchless = 0x226a; t.mufsquare = 0x338c; t.mugreek = 0x03bc; t.mugsquare = 0x338d; t.muhiragana = 0x3080; t.mukatakana = 0x30e0; t.mukatakanahalfwidth = 0xff91; t.mulsquare = 0x3395; t.multiply = 0x00d7; t.mumsquare = 0x339b; t.munahhebrew = 0x05a3; t.munahlefthebrew = 0x05a3; t.musicalnote = 0x266a; t.musicalnotedbl = 0x266b; t.musicflatsign = 0x266d; t.musicsharpsign = 0x266f; t.mussquare = 0x33b2; t.muvsquare = 0x33b6; t.muwsquare = 0x33bc; t.mvmegasquare = 0x33b9; t.mvsquare = 0x33b7; t.mwmegasquare = 0x33bf; t.mwsquare = 0x33bd; t.n = 0x006e; t.nabengali = 0x09a8; t.nabla = 0x2207; t.nacute = 0x0144; t.nadeva = 0x0928; t.nagujarati = 0x0aa8; t.nagurmukhi = 0x0a28; t.nahiragana = 0x306a; t.nakatakana = 0x30ca; t.nakatakanahalfwidth = 0xff85; t.napostrophe = 0x0149; t.nasquare = 0x3381; t.nbopomofo = 0x310b; t.nbspace = 0x00a0; t.ncaron = 0x0148; t.ncedilla = 0x0146; t.ncircle = 0x24dd; t.ncircumflexbelow = 0x1e4b; t.ncommaaccent = 0x0146; t.ndotaccent = 0x1e45; t.ndotbelow = 0x1e47; t.nehiragana = 0x306d; t.nekatakana = 0x30cd; t.nekatakanahalfwidth = 0xff88; t.newsheqelsign = 0x20aa; t.nfsquare = 0x338b; t.ngabengali = 0x0999; t.ngadeva = 0x0919; t.ngagujarati = 0x0a99; t.ngagurmukhi = 0x0a19; t.ngonguthai = 0x0e07; t.nhiragana = 0x3093; t.nhookleft = 0x0272; t.nhookretroflex = 0x0273; t.nieunacirclekorean = 0x326f; t.nieunaparenkorean = 0x320f; t.nieuncieuckorean = 0x3135; t.nieuncirclekorean = 0x3261; t.nieunhieuhkorean = 0x3136; t.nieunkorean = 0x3134; t.nieunpansioskorean = 0x3168; t.nieunparenkorean = 0x3201; t.nieunsioskorean = 0x3167; t.nieuntikeutkorean = 0x3166; t.nihiragana = 0x306b; t.nikatakana = 0x30cb; t.nikatakanahalfwidth = 0xff86; t.nikhahitleftthai = 0xf899; t.nikhahitthai = 0x0e4d; t.nine = 0x0039; t.ninearabic = 0x0669; t.ninebengali = 0x09ef; t.ninecircle = 0x2468; t.ninecircleinversesansserif = 0x2792; t.ninedeva = 0x096f; t.ninegujarati = 0x0aef; t.ninegurmukhi = 0x0a6f; t.ninehackarabic = 0x0669; t.ninehangzhou = 0x3029; t.nineideographicparen = 0x3228; t.nineinferior = 0x2089; t.ninemonospace = 0xff19; t.nineoldstyle = 0xf739; t.nineparen = 0x247c; t.nineperiod = 0x2490; t.ninepersian = 0x06f9; t.nineroman = 0x2178; t.ninesuperior = 0x2079; t.nineteencircle = 0x2472; t.nineteenparen = 0x2486; t.nineteenperiod = 0x249a; t.ninethai = 0x0e59; t.nj = 0x01cc; t.njecyrillic = 0x045a; t.nkatakana = 0x30f3; t.nkatakanahalfwidth = 0xff9d; t.nlegrightlong = 0x019e; t.nlinebelow = 0x1e49; t.nmonospace = 0xff4e; t.nmsquare = 0x339a; t.nnabengali = 0x09a3; t.nnadeva = 0x0923; t.nnagujarati = 0x0aa3; t.nnagurmukhi = 0x0a23; t.nnnadeva = 0x0929; t.nohiragana = 0x306e; t.nokatakana = 0x30ce; t.nokatakanahalfwidth = 0xff89; t.nonbreakingspace = 0x00a0; t.nonenthai = 0x0e13; t.nonuthai = 0x0e19; t.noonarabic = 0x0646; t.noonfinalarabic = 0xfee6; t.noonghunnaarabic = 0x06ba; t.noonghunnafinalarabic = 0xfb9f; t.nooninitialarabic = 0xfee7; t.noonjeeminitialarabic = 0xfcd2; t.noonjeemisolatedarabic = 0xfc4b; t.noonmedialarabic = 0xfee8; t.noonmeeminitialarabic = 0xfcd5; t.noonmeemisolatedarabic = 0xfc4e; t.noonnoonfinalarabic = 0xfc8d; t.notcontains = 0x220c; t.notelement = 0x2209; t.notelementof = 0x2209; t.notequal = 0x2260; t.notgreater = 0x226f; t.notgreaternorequal = 0x2271; t.notgreaternorless = 0x2279; t.notidentical = 0x2262; t.notless = 0x226e; t.notlessnorequal = 0x2270; t.notparallel = 0x2226; t.notprecedes = 0x2280; t.notsubset = 0x2284; t.notsucceeds = 0x2281; t.notsuperset = 0x2285; t.nowarmenian = 0x0576; t.nparen = 0x24a9; t.nssquare = 0x33b1; t.nsuperior = 0x207f; t.ntilde = 0x00f1; t.nu = 0x03bd; t.nuhiragana = 0x306c; t.nukatakana = 0x30cc; t.nukatakanahalfwidth = 0xff87; t.nuktabengali = 0x09bc; t.nuktadeva = 0x093c; t.nuktagujarati = 0x0abc; t.nuktagurmukhi = 0x0a3c; t.numbersign = 0x0023; t.numbersignmonospace = 0xff03; t.numbersignsmall = 0xfe5f; t.numeralsigngreek = 0x0374; t.numeralsignlowergreek = 0x0375; t.numero = 0x2116; t.nun = 0x05e0; t.nundagesh = 0xfb40; t.nundageshhebrew = 0xfb40; t.nunhebrew = 0x05e0; t.nvsquare = 0x33b5; t.nwsquare = 0x33bb; t.nyabengali = 0x099e; t.nyadeva = 0x091e; t.nyagujarati = 0x0a9e; t.nyagurmukhi = 0x0a1e; t.o = 0x006f; t.oacute = 0x00f3; t.oangthai = 0x0e2d; t.obarred = 0x0275; t.obarredcyrillic = 0x04e9; t.obarreddieresiscyrillic = 0x04eb; t.obengali = 0x0993; t.obopomofo = 0x311b; t.obreve = 0x014f; t.ocandradeva = 0x0911; t.ocandragujarati = 0x0a91; t.ocandravowelsigndeva = 0x0949; t.ocandravowelsigngujarati = 0x0ac9; t.ocaron = 0x01d2; t.ocircle = 0x24de; t.ocircumflex = 0x00f4; t.ocircumflexacute = 0x1ed1; t.ocircumflexdotbelow = 0x1ed9; t.ocircumflexgrave = 0x1ed3; t.ocircumflexhookabove = 0x1ed5; t.ocircumflextilde = 0x1ed7; t.ocyrillic = 0x043e; t.odblacute = 0x0151; t.odblgrave = 0x020d; t.odeva = 0x0913; t.odieresis = 0x00f6; t.odieresiscyrillic = 0x04e7; t.odotbelow = 0x1ecd; t.oe = 0x0153; t.oekorean = 0x315a; t.ogonek = 0x02db; t.ogonekcmb = 0x0328; t.ograve = 0x00f2; t.ogujarati = 0x0a93; t.oharmenian = 0x0585; t.ohiragana = 0x304a; t.ohookabove = 0x1ecf; t.ohorn = 0x01a1; t.ohornacute = 0x1edb; t.ohorndotbelow = 0x1ee3; t.ohorngrave = 0x1edd; t.ohornhookabove = 0x1edf; t.ohorntilde = 0x1ee1; t.ohungarumlaut = 0x0151; t.oi = 0x01a3; t.oinvertedbreve = 0x020f; t.okatakana = 0x30aa; t.okatakanahalfwidth = 0xff75; t.okorean = 0x3157; t.olehebrew = 0x05ab; t.omacron = 0x014d; t.omacronacute = 0x1e53; t.omacrongrave = 0x1e51; t.omdeva = 0x0950; t.omega = 0x03c9; t.omega1 = 0x03d6; t.omegacyrillic = 0x0461; t.omegalatinclosed = 0x0277; t.omegaroundcyrillic = 0x047b; t.omegatitlocyrillic = 0x047d; t.omegatonos = 0x03ce; t.omgujarati = 0x0ad0; t.omicron = 0x03bf; t.omicrontonos = 0x03cc; t.omonospace = 0xff4f; t.one = 0x0031; t.onearabic = 0x0661; t.onebengali = 0x09e7; t.onecircle = 0x2460; t.onecircleinversesansserif = 0x278a; t.onedeva = 0x0967; t.onedotenleader = 0x2024; t.oneeighth = 0x215b; t.onefitted = 0xf6dc; t.onegujarati = 0x0ae7; t.onegurmukhi = 0x0a67; t.onehackarabic = 0x0661; t.onehalf = 0x00bd; t.onehangzhou = 0x3021; t.oneideographicparen = 0x3220; t.oneinferior = 0x2081; t.onemonospace = 0xff11; t.onenumeratorbengali = 0x09f4; t.oneoldstyle = 0xf731; t.oneparen = 0x2474; t.oneperiod = 0x2488; t.onepersian = 0x06f1; t.onequarter = 0x00bc; t.oneroman = 0x2170; t.onesuperior = 0x00b9; t.onethai = 0x0e51; t.onethird = 0x2153; t.oogonek = 0x01eb; t.oogonekmacron = 0x01ed; t.oogurmukhi = 0x0a13; t.oomatragurmukhi = 0x0a4b; t.oopen = 0x0254; t.oparen = 0x24aa; t.openbullet = 0x25e6; t.option = 0x2325; t.ordfeminine = 0x00aa; t.ordmasculine = 0x00ba; t.orthogonal = 0x221f; t.oshortdeva = 0x0912; t.oshortvowelsigndeva = 0x094a; t.oslash = 0x00f8; t.oslashacute = 0x01ff; t.osmallhiragana = 0x3049; t.osmallkatakana = 0x30a9; t.osmallkatakanahalfwidth = 0xff6b; t.ostrokeacute = 0x01ff; t.osuperior = 0xf6f0; t.otcyrillic = 0x047f; t.otilde = 0x00f5; t.otildeacute = 0x1e4d; t.otildedieresis = 0x1e4f; t.oubopomofo = 0x3121; t.overline = 0x203e; t.overlinecenterline = 0xfe4a; t.overlinecmb = 0x0305; t.overlinedashed = 0xfe49; t.overlinedblwavy = 0xfe4c; t.overlinewavy = 0xfe4b; t.overscore = 0x00af; t.ovowelsignbengali = 0x09cb; t.ovowelsigndeva = 0x094b; t.ovowelsigngujarati = 0x0acb; t.p = 0x0070; t.paampssquare = 0x3380; t.paasentosquare = 0x332b; t.pabengali = 0x09aa; t.pacute = 0x1e55; t.padeva = 0x092a; t.pagedown = 0x21df; t.pageup = 0x21de; t.pagujarati = 0x0aaa; t.pagurmukhi = 0x0a2a; t.pahiragana = 0x3071; t.paiyannoithai = 0x0e2f; t.pakatakana = 0x30d1; t.palatalizationcyrilliccmb = 0x0484; t.palochkacyrillic = 0x04c0; t.pansioskorean = 0x317f; t.paragraph = 0x00b6; t.parallel = 0x2225; t.parenleft = 0x0028; t.parenleftaltonearabic = 0xfd3e; t.parenleftbt = 0xf8ed; t.parenleftex = 0xf8ec; t.parenleftinferior = 0x208d; t.parenleftmonospace = 0xff08; t.parenleftsmall = 0xfe59; t.parenleftsuperior = 0x207d; t.parenlefttp = 0xf8eb; t.parenleftvertical = 0xfe35; t.parenright = 0x0029; t.parenrightaltonearabic = 0xfd3f; t.parenrightbt = 0xf8f8; t.parenrightex = 0xf8f7; t.parenrightinferior = 0x208e; t.parenrightmonospace = 0xff09; t.parenrightsmall = 0xfe5a; t.parenrightsuperior = 0x207e; t.parenrighttp = 0xf8f6; t.parenrightvertical = 0xfe36; t.partialdiff = 0x2202; t.paseqhebrew = 0x05c0; t.pashtahebrew = 0x0599; t.pasquare = 0x33a9; t.patah = 0x05b7; t.patah11 = 0x05b7; t.patah1d = 0x05b7; t.patah2a = 0x05b7; t.patahhebrew = 0x05b7; t.patahnarrowhebrew = 0x05b7; t.patahquarterhebrew = 0x05b7; t.patahwidehebrew = 0x05b7; t.pazerhebrew = 0x05a1; t.pbopomofo = 0x3106; t.pcircle = 0x24df; t.pdotaccent = 0x1e57; t.pe = 0x05e4; t.pecyrillic = 0x043f; t.pedagesh = 0xfb44; t.pedageshhebrew = 0xfb44; t.peezisquare = 0x333b; t.pefinaldageshhebrew = 0xfb43; t.peharabic = 0x067e; t.peharmenian = 0x057a; t.pehebrew = 0x05e4; t.pehfinalarabic = 0xfb57; t.pehinitialarabic = 0xfb58; t.pehiragana = 0x307a; t.pehmedialarabic = 0xfb59; t.pekatakana = 0x30da; t.pemiddlehookcyrillic = 0x04a7; t.perafehebrew = 0xfb4e; t.percent = 0x0025; t.percentarabic = 0x066a; t.percentmonospace = 0xff05; t.percentsmall = 0xfe6a; t.period = 0x002e; t.periodarmenian = 0x0589; t.periodcentered = 0x00b7; t.periodhalfwidth = 0xff61; t.periodinferior = 0xf6e7; t.periodmonospace = 0xff0e; t.periodsmall = 0xfe52; t.periodsuperior = 0xf6e8; t.perispomenigreekcmb = 0x0342; t.perpendicular = 0x22a5; t.perthousand = 0x2030; t.peseta = 0x20a7; t.pfsquare = 0x338a; t.phabengali = 0x09ab; t.phadeva = 0x092b; t.phagujarati = 0x0aab; t.phagurmukhi = 0x0a2b; t.phi = 0x03c6; t.phi1 = 0x03d5; t.phieuphacirclekorean = 0x327a; t.phieuphaparenkorean = 0x321a; t.phieuphcirclekorean = 0x326c; t.phieuphkorean = 0x314d; t.phieuphparenkorean = 0x320c; t.philatin = 0x0278; t.phinthuthai = 0x0e3a; t.phisymbolgreek = 0x03d5; t.phook = 0x01a5; t.phophanthai = 0x0e1e; t.phophungthai = 0x0e1c; t.phosamphaothai = 0x0e20; t.pi = 0x03c0; t.pieupacirclekorean = 0x3273; t.pieupaparenkorean = 0x3213; t.pieupcieuckorean = 0x3176; t.pieupcirclekorean = 0x3265; t.pieupkiyeokkorean = 0x3172; t.pieupkorean = 0x3142; t.pieupparenkorean = 0x3205; t.pieupsioskiyeokkorean = 0x3174; t.pieupsioskorean = 0x3144; t.pieupsiostikeutkorean = 0x3175; t.pieupthieuthkorean = 0x3177; t.pieuptikeutkorean = 0x3173; t.pihiragana = 0x3074; t.pikatakana = 0x30d4; t.pisymbolgreek = 0x03d6; t.piwrarmenian = 0x0583; t.planckover2pi = 0x210f; t.planckover2pi1 = 0x210f; t.plus = 0x002b; t.plusbelowcmb = 0x031f; t.pluscircle = 0x2295; t.plusminus = 0x00b1; t.plusmod = 0x02d6; t.plusmonospace = 0xff0b; t.plussmall = 0xfe62; t.plussuperior = 0x207a; t.pmonospace = 0xff50; t.pmsquare = 0x33d8; t.pohiragana = 0x307d; t.pointingindexdownwhite = 0x261f; t.pointingindexleftwhite = 0x261c; t.pointingindexrightwhite = 0x261e; t.pointingindexupwhite = 0x261d; t.pokatakana = 0x30dd; t.poplathai = 0x0e1b; t.postalmark = 0x3012; t.postalmarkface = 0x3020; t.pparen = 0x24ab; t.precedes = 0x227a; t.prescription = 0x211e; t.primemod = 0x02b9; t.primereversed = 0x2035; t.product = 0x220f; t.projective = 0x2305; t.prolongedkana = 0x30fc; t.propellor = 0x2318; t.propersubset = 0x2282; t.propersuperset = 0x2283; t.proportion = 0x2237; t.proportional = 0x221d; t.psi = 0x03c8; t.psicyrillic = 0x0471; t.psilipneumatacyrilliccmb = 0x0486; t.pssquare = 0x33b0; t.puhiragana = 0x3077; t.pukatakana = 0x30d7; t.pvsquare = 0x33b4; t.pwsquare = 0x33ba; t.q = 0x0071; t.qadeva = 0x0958; t.qadmahebrew = 0x05a8; t.qafarabic = 0x0642; t.qaffinalarabic = 0xfed6; t.qafinitialarabic = 0xfed7; t.qafmedialarabic = 0xfed8; t.qamats = 0x05b8; t.qamats10 = 0x05b8; t.qamats1a = 0x05b8; t.qamats1c = 0x05b8; t.qamats27 = 0x05b8; t.qamats29 = 0x05b8; t.qamats33 = 0x05b8; t.qamatsde = 0x05b8; t.qamatshebrew = 0x05b8; t.qamatsnarrowhebrew = 0x05b8; t.qamatsqatanhebrew = 0x05b8; t.qamatsqatannarrowhebrew = 0x05b8; t.qamatsqatanquarterhebrew = 0x05b8; t.qamatsqatanwidehebrew = 0x05b8; t.qamatsquarterhebrew = 0x05b8; t.qamatswidehebrew = 0x05b8; t.qarneyparahebrew = 0x059f; t.qbopomofo = 0x3111; t.qcircle = 0x24e0; t.qhook = 0x02a0; t.qmonospace = 0xff51; t.qof = 0x05e7; t.qofdagesh = 0xfb47; t.qofdageshhebrew = 0xfb47; t.qofhebrew = 0x05e7; t.qparen = 0x24ac; t.quarternote = 0x2669; t.qubuts = 0x05bb; t.qubuts18 = 0x05bb; t.qubuts25 = 0x05bb; t.qubuts31 = 0x05bb; t.qubutshebrew = 0x05bb; t.qubutsnarrowhebrew = 0x05bb; t.qubutsquarterhebrew = 0x05bb; t.qubutswidehebrew = 0x05bb; t.question = 0x003f; t.questionarabic = 0x061f; t.questionarmenian = 0x055e; t.questiondown = 0x00bf; t.questiondownsmall = 0xf7bf; t.questiongreek = 0x037e; t.questionmonospace = 0xff1f; t.questionsmall = 0xf73f; t.quotedbl = 0x0022; t.quotedblbase = 0x201e; t.quotedblleft = 0x201c; t.quotedblmonospace = 0xff02; t.quotedblprime = 0x301e; t.quotedblprimereversed = 0x301d; t.quotedblright = 0x201d; t.quoteleft = 0x2018; t.quoteleftreversed = 0x201b; t.quotereversed = 0x201b; t.quoteright = 0x2019; t.quoterightn = 0x0149; t.quotesinglbase = 0x201a; t.quotesingle = 0x0027; t.quotesinglemonospace = 0xff07; t.r = 0x0072; t.raarmenian = 0x057c; t.rabengali = 0x09b0; t.racute = 0x0155; t.radeva = 0x0930; t.radical = 0x221a; t.radicalex = 0xf8e5; t.radoverssquare = 0x33ae; t.radoverssquaredsquare = 0x33af; t.radsquare = 0x33ad; t.rafe = 0x05bf; t.rafehebrew = 0x05bf; t.ragujarati = 0x0ab0; t.ragurmukhi = 0x0a30; t.rahiragana = 0x3089; t.rakatakana = 0x30e9; t.rakatakanahalfwidth = 0xff97; t.ralowerdiagonalbengali = 0x09f1; t.ramiddlediagonalbengali = 0x09f0; t.ramshorn = 0x0264; t.ratio = 0x2236; t.rbopomofo = 0x3116; t.rcaron = 0x0159; t.rcedilla = 0x0157; t.rcircle = 0x24e1; t.rcommaaccent = 0x0157; t.rdblgrave = 0x0211; t.rdotaccent = 0x1e59; t.rdotbelow = 0x1e5b; t.rdotbelowmacron = 0x1e5d; t.referencemark = 0x203b; t.reflexsubset = 0x2286; t.reflexsuperset = 0x2287; t.registered = 0x00ae; t.registersans = 0xf8e8; t.registerserif = 0xf6da; t.reharabic = 0x0631; t.reharmenian = 0x0580; t.rehfinalarabic = 0xfeae; t.rehiragana = 0x308c; t.rekatakana = 0x30ec; t.rekatakanahalfwidth = 0xff9a; t.resh = 0x05e8; t.reshdageshhebrew = 0xfb48; t.reshhebrew = 0x05e8; t.reversedtilde = 0x223d; t.reviahebrew = 0x0597; t.reviamugrashhebrew = 0x0597; t.revlogicalnot = 0x2310; t.rfishhook = 0x027e; t.rfishhookreversed = 0x027f; t.rhabengali = 0x09dd; t.rhadeva = 0x095d; t.rho = 0x03c1; t.rhook = 0x027d; t.rhookturned = 0x027b; t.rhookturnedsuperior = 0x02b5; t.rhosymbolgreek = 0x03f1; t.rhotichookmod = 0x02de; t.rieulacirclekorean = 0x3271; t.rieulaparenkorean = 0x3211; t.rieulcirclekorean = 0x3263; t.rieulhieuhkorean = 0x3140; t.rieulkiyeokkorean = 0x313a; t.rieulkiyeoksioskorean = 0x3169; t.rieulkorean = 0x3139; t.rieulmieumkorean = 0x313b; t.rieulpansioskorean = 0x316c; t.rieulparenkorean = 0x3203; t.rieulphieuphkorean = 0x313f; t.rieulpieupkorean = 0x313c; t.rieulpieupsioskorean = 0x316b; t.rieulsioskorean = 0x313d; t.rieulthieuthkorean = 0x313e; t.rieultikeutkorean = 0x316a; t.rieulyeorinhieuhkorean = 0x316d; t.rightangle = 0x221f; t.righttackbelowcmb = 0x0319; t.righttriangle = 0x22bf; t.rihiragana = 0x308a; t.rikatakana = 0x30ea; t.rikatakanahalfwidth = 0xff98; t.ring = 0x02da; t.ringbelowcmb = 0x0325; t.ringcmb = 0x030a; t.ringhalfleft = 0x02bf; t.ringhalfleftarmenian = 0x0559; t.ringhalfleftbelowcmb = 0x031c; t.ringhalfleftcentered = 0x02d3; t.ringhalfright = 0x02be; t.ringhalfrightbelowcmb = 0x0339; t.ringhalfrightcentered = 0x02d2; t.rinvertedbreve = 0x0213; t.rittorusquare = 0x3351; t.rlinebelow = 0x1e5f; t.rlongleg = 0x027c; t.rlonglegturned = 0x027a; t.rmonospace = 0xff52; t.rohiragana = 0x308d; t.rokatakana = 0x30ed; t.rokatakanahalfwidth = 0xff9b; t.roruathai = 0x0e23; t.rparen = 0x24ad; t.rrabengali = 0x09dc; t.rradeva = 0x0931; t.rragurmukhi = 0x0a5c; t.rreharabic = 0x0691; t.rrehfinalarabic = 0xfb8d; t.rrvocalicbengali = 0x09e0; t.rrvocalicdeva = 0x0960; t.rrvocalicgujarati = 0x0ae0; t.rrvocalicvowelsignbengali = 0x09c4; t.rrvocalicvowelsigndeva = 0x0944; t.rrvocalicvowelsigngujarati = 0x0ac4; t.rsuperior = 0xf6f1; t.rtblock = 0x2590; t.rturned = 0x0279; t.rturnedsuperior = 0x02b4; t.ruhiragana = 0x308b; t.rukatakana = 0x30eb; t.rukatakanahalfwidth = 0xff99; t.rupeemarkbengali = 0x09f2; t.rupeesignbengali = 0x09f3; t.rupiah = 0xf6dd; t.ruthai = 0x0e24; t.rvocalicbengali = 0x098b; t.rvocalicdeva = 0x090b; t.rvocalicgujarati = 0x0a8b; t.rvocalicvowelsignbengali = 0x09c3; t.rvocalicvowelsigndeva = 0x0943; t.rvocalicvowelsigngujarati = 0x0ac3; t.s = 0x0073; t.sabengali = 0x09b8; t.sacute = 0x015b; t.sacutedotaccent = 0x1e65; t.sadarabic = 0x0635; t.sadeva = 0x0938; t.sadfinalarabic = 0xfeba; t.sadinitialarabic = 0xfebb; t.sadmedialarabic = 0xfebc; t.sagujarati = 0x0ab8; t.sagurmukhi = 0x0a38; t.sahiragana = 0x3055; t.sakatakana = 0x30b5; t.sakatakanahalfwidth = 0xff7b; t.sallallahoualayhewasallamarabic = 0xfdfa; t.samekh = 0x05e1; t.samekhdagesh = 0xfb41; t.samekhdageshhebrew = 0xfb41; t.samekhhebrew = 0x05e1; t.saraaathai = 0x0e32; t.saraaethai = 0x0e41; t.saraaimaimalaithai = 0x0e44; t.saraaimaimuanthai = 0x0e43; t.saraamthai = 0x0e33; t.saraathai = 0x0e30; t.saraethai = 0x0e40; t.saraiileftthai = 0xf886; t.saraiithai = 0x0e35; t.saraileftthai = 0xf885; t.saraithai = 0x0e34; t.saraothai = 0x0e42; t.saraueeleftthai = 0xf888; t.saraueethai = 0x0e37; t.saraueleftthai = 0xf887; t.sarauethai = 0x0e36; t.sarauthai = 0x0e38; t.sarauuthai = 0x0e39; t.sbopomofo = 0x3119; t.scaron = 0x0161; t.scarondotaccent = 0x1e67; t.scedilla = 0x015f; t.schwa = 0x0259; t.schwacyrillic = 0x04d9; t.schwadieresiscyrillic = 0x04db; t.schwahook = 0x025a; t.scircle = 0x24e2; t.scircumflex = 0x015d; t.scommaaccent = 0x0219; t.sdotaccent = 0x1e61; t.sdotbelow = 0x1e63; t.sdotbelowdotaccent = 0x1e69; t.seagullbelowcmb = 0x033c; t.second = 0x2033; t.secondtonechinese = 0x02ca; t.section = 0x00a7; t.seenarabic = 0x0633; t.seenfinalarabic = 0xfeb2; t.seeninitialarabic = 0xfeb3; t.seenmedialarabic = 0xfeb4; t.segol = 0x05b6; t.segol13 = 0x05b6; t.segol1f = 0x05b6; t.segol2c = 0x05b6; t.segolhebrew = 0x05b6; t.segolnarrowhebrew = 0x05b6; t.segolquarterhebrew = 0x05b6; t.segoltahebrew = 0x0592; t.segolwidehebrew = 0x05b6; t.seharmenian = 0x057d; t.sehiragana = 0x305b; t.sekatakana = 0x30bb; t.sekatakanahalfwidth = 0xff7e; t.semicolon = 0x003b; t.semicolonarabic = 0x061b; t.semicolonmonospace = 0xff1b; t.semicolonsmall = 0xfe54; t.semivoicedmarkkana = 0x309c; t.semivoicedmarkkanahalfwidth = 0xff9f; t.sentisquare = 0x3322; t.sentosquare = 0x3323; t.seven = 0x0037; t.sevenarabic = 0x0667; t.sevenbengali = 0x09ed; t.sevencircle = 0x2466; t.sevencircleinversesansserif = 0x2790; t.sevendeva = 0x096d; t.seveneighths = 0x215e; t.sevengujarati = 0x0aed; t.sevengurmukhi = 0x0a6d; t.sevenhackarabic = 0x0667; t.sevenhangzhou = 0x3027; t.sevenideographicparen = 0x3226; t.seveninferior = 0x2087; t.sevenmonospace = 0xff17; t.sevenoldstyle = 0xf737; t.sevenparen = 0x247a; t.sevenperiod = 0x248e; t.sevenpersian = 0x06f7; t.sevenroman = 0x2176; t.sevensuperior = 0x2077; t.seventeencircle = 0x2470; t.seventeenparen = 0x2484; t.seventeenperiod = 0x2498; t.seventhai = 0x0e57; t.sfthyphen = 0x00ad; t.shaarmenian = 0x0577; t.shabengali = 0x09b6; t.shacyrillic = 0x0448; t.shaddaarabic = 0x0651; t.shaddadammaarabic = 0xfc61; t.shaddadammatanarabic = 0xfc5e; t.shaddafathaarabic = 0xfc60; t.shaddakasraarabic = 0xfc62; t.shaddakasratanarabic = 0xfc5f; t.shade = 0x2592; t.shadedark = 0x2593; t.shadelight = 0x2591; t.shademedium = 0x2592; t.shadeva = 0x0936; t.shagujarati = 0x0ab6; t.shagurmukhi = 0x0a36; t.shalshelethebrew = 0x0593; t.shbopomofo = 0x3115; t.shchacyrillic = 0x0449; t.sheenarabic = 0x0634; t.sheenfinalarabic = 0xfeb6; t.sheeninitialarabic = 0xfeb7; t.sheenmedialarabic = 0xfeb8; t.sheicoptic = 0x03e3; t.sheqel = 0x20aa; t.sheqelhebrew = 0x20aa; t.sheva = 0x05b0; t.sheva115 = 0x05b0; t.sheva15 = 0x05b0; t.sheva22 = 0x05b0; t.sheva2e = 0x05b0; t.shevahebrew = 0x05b0; t.shevanarrowhebrew = 0x05b0; t.shevaquarterhebrew = 0x05b0; t.shevawidehebrew = 0x05b0; t.shhacyrillic = 0x04bb; t.shimacoptic = 0x03ed; t.shin = 0x05e9; t.shindagesh = 0xfb49; t.shindageshhebrew = 0xfb49; t.shindageshshindot = 0xfb2c; t.shindageshshindothebrew = 0xfb2c; t.shindageshsindot = 0xfb2d; t.shindageshsindothebrew = 0xfb2d; t.shindothebrew = 0x05c1; t.shinhebrew = 0x05e9; t.shinshindot = 0xfb2a; t.shinshindothebrew = 0xfb2a; t.shinsindot = 0xfb2b; t.shinsindothebrew = 0xfb2b; t.shook = 0x0282; t.sigma = 0x03c3; t.sigma1 = 0x03c2; t.sigmafinal = 0x03c2; t.sigmalunatesymbolgreek = 0x03f2; t.sihiragana = 0x3057; t.sikatakana = 0x30b7; t.sikatakanahalfwidth = 0xff7c; t.siluqhebrew = 0x05bd; t.siluqlefthebrew = 0x05bd; t.similar = 0x223c; t.sindothebrew = 0x05c2; t.siosacirclekorean = 0x3274; t.siosaparenkorean = 0x3214; t.sioscieuckorean = 0x317e; t.sioscirclekorean = 0x3266; t.sioskiyeokkorean = 0x317a; t.sioskorean = 0x3145; t.siosnieunkorean = 0x317b; t.siosparenkorean = 0x3206; t.siospieupkorean = 0x317d; t.siostikeutkorean = 0x317c; t.six = 0x0036; t.sixarabic = 0x0666; t.sixbengali = 0x09ec; t.sixcircle = 0x2465; t.sixcircleinversesansserif = 0x278f; t.sixdeva = 0x096c; t.sixgujarati = 0x0aec; t.sixgurmukhi = 0x0a6c; t.sixhackarabic = 0x0666; t.sixhangzhou = 0x3026; t.sixideographicparen = 0x3225; t.sixinferior = 0x2086; t.sixmonospace = 0xff16; t.sixoldstyle = 0xf736; t.sixparen = 0x2479; t.sixperiod = 0x248d; t.sixpersian = 0x06f6; t.sixroman = 0x2175; t.sixsuperior = 0x2076; t.sixteencircle = 0x246f; t.sixteencurrencydenominatorbengali = 0x09f9; t.sixteenparen = 0x2483; t.sixteenperiod = 0x2497; t.sixthai = 0x0e56; t.slash = 0x002f; t.slashmonospace = 0xff0f; t.slong = 0x017f; t.slongdotaccent = 0x1e9b; t.smileface = 0x263a; t.smonospace = 0xff53; t.sofpasuqhebrew = 0x05c3; t.softhyphen = 0x00ad; t.softsigncyrillic = 0x044c; t.sohiragana = 0x305d; t.sokatakana = 0x30bd; t.sokatakanahalfwidth = 0xff7f; t.soliduslongoverlaycmb = 0x0338; t.solidusshortoverlaycmb = 0x0337; t.sorusithai = 0x0e29; t.sosalathai = 0x0e28; t.sosothai = 0x0e0b; t.sosuathai = 0x0e2a; t.space = 0x0020; t.spacehackarabic = 0x0020; t.spade = 0x2660; t.spadesuitblack = 0x2660; t.spadesuitwhite = 0x2664; t.sparen = 0x24ae; t.squarebelowcmb = 0x033b; t.squarecc = 0x33c4; t.squarecm = 0x339d; t.squarediagonalcrosshatchfill = 0x25a9; t.squarehorizontalfill = 0x25a4; t.squarekg = 0x338f; t.squarekm = 0x339e; t.squarekmcapital = 0x33ce; t.squareln = 0x33d1; t.squarelog = 0x33d2; t.squaremg = 0x338e; t.squaremil = 0x33d5; t.squaremm = 0x339c; t.squaremsquared = 0x33a1; t.squareorthogonalcrosshatchfill = 0x25a6; t.squareupperlefttolowerrightfill = 0x25a7; t.squareupperrighttolowerleftfill = 0x25a8; t.squareverticalfill = 0x25a5; t.squarewhitewithsmallblack = 0x25a3; t.srsquare = 0x33db; t.ssabengali = 0x09b7; t.ssadeva = 0x0937; t.ssagujarati = 0x0ab7; t.ssangcieuckorean = 0x3149; t.ssanghieuhkorean = 0x3185; t.ssangieungkorean = 0x3180; t.ssangkiyeokkorean = 0x3132; t.ssangnieunkorean = 0x3165; t.ssangpieupkorean = 0x3143; t.ssangsioskorean = 0x3146; t.ssangtikeutkorean = 0x3138; t.ssuperior = 0xf6f2; t.sterling = 0x00a3; t.sterlingmonospace = 0xffe1; t.strokelongoverlaycmb = 0x0336; t.strokeshortoverlaycmb = 0x0335; t.subset = 0x2282; t.subsetnotequal = 0x228a; t.subsetorequal = 0x2286; t.succeeds = 0x227b; t.suchthat = 0x220b; t.suhiragana = 0x3059; t.sukatakana = 0x30b9; t.sukatakanahalfwidth = 0xff7d; t.sukunarabic = 0x0652; t.summation = 0x2211; t.sun = 0x263c; t.superset = 0x2283; t.supersetnotequal = 0x228b; t.supersetorequal = 0x2287; t.svsquare = 0x33dc; t.syouwaerasquare = 0x337c; t.t = 0x0074; t.tabengali = 0x09a4; t.tackdown = 0x22a4; t.tackleft = 0x22a3; t.tadeva = 0x0924; t.tagujarati = 0x0aa4; t.tagurmukhi = 0x0a24; t.taharabic = 0x0637; t.tahfinalarabic = 0xfec2; t.tahinitialarabic = 0xfec3; t.tahiragana = 0x305f; t.tahmedialarabic = 0xfec4; t.taisyouerasquare = 0x337d; t.takatakana = 0x30bf; t.takatakanahalfwidth = 0xff80; t.tatweelarabic = 0x0640; t.tau = 0x03c4; t.tav = 0x05ea; t.tavdages = 0xfb4a; t.tavdagesh = 0xfb4a; t.tavdageshhebrew = 0xfb4a; t.tavhebrew = 0x05ea; t.tbar = 0x0167; t.tbopomofo = 0x310a; t.tcaron = 0x0165; t.tccurl = 0x02a8; t.tcedilla = 0x0163; t.tcheharabic = 0x0686; t.tchehfinalarabic = 0xfb7b; t.tchehinitialarabic = 0xfb7c; t.tchehmedialarabic = 0xfb7d; t.tcircle = 0x24e3; t.tcircumflexbelow = 0x1e71; t.tcommaaccent = 0x0163; t.tdieresis = 0x1e97; t.tdotaccent = 0x1e6b; t.tdotbelow = 0x1e6d; t.tecyrillic = 0x0442; t.tedescendercyrillic = 0x04ad; t.teharabic = 0x062a; t.tehfinalarabic = 0xfe96; t.tehhahinitialarabic = 0xfca2; t.tehhahisolatedarabic = 0xfc0c; t.tehinitialarabic = 0xfe97; t.tehiragana = 0x3066; t.tehjeeminitialarabic = 0xfca1; t.tehjeemisolatedarabic = 0xfc0b; t.tehmarbutaarabic = 0x0629; t.tehmarbutafinalarabic = 0xfe94; t.tehmedialarabic = 0xfe98; t.tehmeeminitialarabic = 0xfca4; t.tehmeemisolatedarabic = 0xfc0e; t.tehnoonfinalarabic = 0xfc73; t.tekatakana = 0x30c6; t.tekatakanahalfwidth = 0xff83; t.telephone = 0x2121; t.telephoneblack = 0x260e; t.telishagedolahebrew = 0x05a0; t.telishaqetanahebrew = 0x05a9; t.tencircle = 0x2469; t.tenideographicparen = 0x3229; t.tenparen = 0x247d; t.tenperiod = 0x2491; t.tenroman = 0x2179; t.tesh = 0x02a7; t.tet = 0x05d8; t.tetdagesh = 0xfb38; t.tetdageshhebrew = 0xfb38; t.tethebrew = 0x05d8; t.tetsecyrillic = 0x04b5; t.tevirhebrew = 0x059b; t.tevirlefthebrew = 0x059b; t.thabengali = 0x09a5; t.thadeva = 0x0925; t.thagujarati = 0x0aa5; t.thagurmukhi = 0x0a25; t.thalarabic = 0x0630; t.thalfinalarabic = 0xfeac; t.thanthakhatlowleftthai = 0xf898; t.thanthakhatlowrightthai = 0xf897; t.thanthakhatthai = 0x0e4c; t.thanthakhatupperleftthai = 0xf896; t.theharabic = 0x062b; t.thehfinalarabic = 0xfe9a; t.thehinitialarabic = 0xfe9b; t.thehmedialarabic = 0xfe9c; t.thereexists = 0x2203; t.therefore = 0x2234; t.theta = 0x03b8; t.theta1 = 0x03d1; t.thetasymbolgreek = 0x03d1; t.thieuthacirclekorean = 0x3279; t.thieuthaparenkorean = 0x3219; t.thieuthcirclekorean = 0x326b; t.thieuthkorean = 0x314c; t.thieuthparenkorean = 0x320b; t.thirteencircle = 0x246c; t.thirteenparen = 0x2480; t.thirteenperiod = 0x2494; t.thonangmonthothai = 0x0e11; t.thook = 0x01ad; t.thophuthaothai = 0x0e12; t.thorn = 0x00fe; t.thothahanthai = 0x0e17; t.thothanthai = 0x0e10; t.thothongthai = 0x0e18; t.thothungthai = 0x0e16; t.thousandcyrillic = 0x0482; t.thousandsseparatorarabic = 0x066c; t.thousandsseparatorpersian = 0x066c; t.three = 0x0033; t.threearabic = 0x0663; t.threebengali = 0x09e9; t.threecircle = 0x2462; t.threecircleinversesansserif = 0x278c; t.threedeva = 0x0969; t.threeeighths = 0x215c; t.threegujarati = 0x0ae9; t.threegurmukhi = 0x0a69; t.threehackarabic = 0x0663; t.threehangzhou = 0x3023; t.threeideographicparen = 0x3222; t.threeinferior = 0x2083; t.threemonospace = 0xff13; t.threenumeratorbengali = 0x09f6; t.threeoldstyle = 0xf733; t.threeparen = 0x2476; t.threeperiod = 0x248a; t.threepersian = 0x06f3; t.threequarters = 0x00be; t.threequartersemdash = 0xf6de; t.threeroman = 0x2172; t.threesuperior = 0x00b3; t.threethai = 0x0e53; t.thzsquare = 0x3394; t.tihiragana = 0x3061; t.tikatakana = 0x30c1; t.tikatakanahalfwidth = 0xff81; t.tikeutacirclekorean = 0x3270; t.tikeutaparenkorean = 0x3210; t.tikeutcirclekorean = 0x3262; t.tikeutkorean = 0x3137; t.tikeutparenkorean = 0x3202; t.tilde = 0x02dc; t.tildebelowcmb = 0x0330; t.tildecmb = 0x0303; t.tildecomb = 0x0303; t.tildedoublecmb = 0x0360; t.tildeoperator = 0x223c; t.tildeoverlaycmb = 0x0334; t.tildeverticalcmb = 0x033e; t.timescircle = 0x2297; t.tipehahebrew = 0x0596; t.tipehalefthebrew = 0x0596; t.tippigurmukhi = 0x0a70; t.titlocyrilliccmb = 0x0483; t.tiwnarmenian = 0x057f; t.tlinebelow = 0x1e6f; t.tmonospace = 0xff54; t.toarmenian = 0x0569; t.tohiragana = 0x3068; t.tokatakana = 0x30c8; t.tokatakanahalfwidth = 0xff84; t.tonebarextrahighmod = 0x02e5; t.tonebarextralowmod = 0x02e9; t.tonebarhighmod = 0x02e6; t.tonebarlowmod = 0x02e8; t.tonebarmidmod = 0x02e7; t.tonefive = 0x01bd; t.tonesix = 0x0185; t.tonetwo = 0x01a8; t.tonos = 0x0384; t.tonsquare = 0x3327; t.topatakthai = 0x0e0f; t.tortoiseshellbracketleft = 0x3014; t.tortoiseshellbracketleftsmall = 0xfe5d; t.tortoiseshellbracketleftvertical = 0xfe39; t.tortoiseshellbracketright = 0x3015; t.tortoiseshellbracketrightsmall = 0xfe5e; t.tortoiseshellbracketrightvertical = 0xfe3a; t.totaothai = 0x0e15; t.tpalatalhook = 0x01ab; t.tparen = 0x24af; t.trademark = 0x2122; t.trademarksans = 0xf8ea; t.trademarkserif = 0xf6db; t.tretroflexhook = 0x0288; t.triagdn = 0x25bc; t.triaglf = 0x25c4; t.triagrt = 0x25ba; t.triagup = 0x25b2; t.ts = 0x02a6; t.tsadi = 0x05e6; t.tsadidagesh = 0xfb46; t.tsadidageshhebrew = 0xfb46; t.tsadihebrew = 0x05e6; t.tsecyrillic = 0x0446; t.tsere = 0x05b5; t.tsere12 = 0x05b5; t.tsere1e = 0x05b5; t.tsere2b = 0x05b5; t.tserehebrew = 0x05b5; t.tserenarrowhebrew = 0x05b5; t.tserequarterhebrew = 0x05b5; t.tserewidehebrew = 0x05b5; t.tshecyrillic = 0x045b; t.tsuperior = 0xf6f3; t.ttabengali = 0x099f; t.ttadeva = 0x091f; t.ttagujarati = 0x0a9f; t.ttagurmukhi = 0x0a1f; t.tteharabic = 0x0679; t.ttehfinalarabic = 0xfb67; t.ttehinitialarabic = 0xfb68; t.ttehmedialarabic = 0xfb69; t.tthabengali = 0x09a0; t.tthadeva = 0x0920; t.tthagujarati = 0x0aa0; t.tthagurmukhi = 0x0a20; t.tturned = 0x0287; t.tuhiragana = 0x3064; t.tukatakana = 0x30c4; t.tukatakanahalfwidth = 0xff82; t.tusmallhiragana = 0x3063; t.tusmallkatakana = 0x30c3; t.tusmallkatakanahalfwidth = 0xff6f; t.twelvecircle = 0x246b; t.twelveparen = 0x247f; t.twelveperiod = 0x2493; t.twelveroman = 0x217b; t.twentycircle = 0x2473; t.twentyhangzhou = 0x5344; t.twentyparen = 0x2487; t.twentyperiod = 0x249b; t.two = 0x0032; t.twoarabic = 0x0662; t.twobengali = 0x09e8; t.twocircle = 0x2461; t.twocircleinversesansserif = 0x278b; t.twodeva = 0x0968; t.twodotenleader = 0x2025; t.twodotleader = 0x2025; t.twodotleadervertical = 0xfe30; t.twogujarati = 0x0ae8; t.twogurmukhi = 0x0a68; t.twohackarabic = 0x0662; t.twohangzhou = 0x3022; t.twoideographicparen = 0x3221; t.twoinferior = 0x2082; t.twomonospace = 0xff12; t.twonumeratorbengali = 0x09f5; t.twooldstyle = 0xf732; t.twoparen = 0x2475; t.twoperiod = 0x2489; t.twopersian = 0x06f2; t.tworoman = 0x2171; t.twostroke = 0x01bb; t.twosuperior = 0x00b2; t.twothai = 0x0e52; t.twothirds = 0x2154; t.u = 0x0075; t.uacute = 0x00fa; t.ubar = 0x0289; t.ubengali = 0x0989; t.ubopomofo = 0x3128; t.ubreve = 0x016d; t.ucaron = 0x01d4; t.ucircle = 0x24e4; t.ucircumflex = 0x00fb; t.ucircumflexbelow = 0x1e77; t.ucyrillic = 0x0443; t.udattadeva = 0x0951; t.udblacute = 0x0171; t.udblgrave = 0x0215; t.udeva = 0x0909; t.udieresis = 0x00fc; t.udieresisacute = 0x01d8; t.udieresisbelow = 0x1e73; t.udieresiscaron = 0x01da; t.udieresiscyrillic = 0x04f1; t.udieresisgrave = 0x01dc; t.udieresismacron = 0x01d6; t.udotbelow = 0x1ee5; t.ugrave = 0x00f9; t.ugujarati = 0x0a89; t.ugurmukhi = 0x0a09; t.uhiragana = 0x3046; t.uhookabove = 0x1ee7; t.uhorn = 0x01b0; t.uhornacute = 0x1ee9; t.uhorndotbelow = 0x1ef1; t.uhorngrave = 0x1eeb; t.uhornhookabove = 0x1eed; t.uhorntilde = 0x1eef; t.uhungarumlaut = 0x0171; t.uhungarumlautcyrillic = 0x04f3; t.uinvertedbreve = 0x0217; t.ukatakana = 0x30a6; t.ukatakanahalfwidth = 0xff73; t.ukcyrillic = 0x0479; t.ukorean = 0x315c; t.umacron = 0x016b; t.umacroncyrillic = 0x04ef; t.umacrondieresis = 0x1e7b; t.umatragurmukhi = 0x0a41; t.umonospace = 0xff55; t.underscore = 0x005f; t.underscoredbl = 0x2017; t.underscoremonospace = 0xff3f; t.underscorevertical = 0xfe33; t.underscorewavy = 0xfe4f; t.union = 0x222a; t.universal = 0x2200; t.uogonek = 0x0173; t.uparen = 0x24b0; t.upblock = 0x2580; t.upperdothebrew = 0x05c4; t.upsilon = 0x03c5; t.upsilondieresis = 0x03cb; t.upsilondieresistonos = 0x03b0; t.upsilonlatin = 0x028a; t.upsilontonos = 0x03cd; t.uptackbelowcmb = 0x031d; t.uptackmod = 0x02d4; t.uragurmukhi = 0x0a73; t.uring = 0x016f; t.ushortcyrillic = 0x045e; t.usmallhiragana = 0x3045; t.usmallkatakana = 0x30a5; t.usmallkatakanahalfwidth = 0xff69; t.ustraightcyrillic = 0x04af; t.ustraightstrokecyrillic = 0x04b1; t.utilde = 0x0169; t.utildeacute = 0x1e79; t.utildebelow = 0x1e75; t.uubengali = 0x098a; t.uudeva = 0x090a; t.uugujarati = 0x0a8a; t.uugurmukhi = 0x0a0a; t.uumatragurmukhi = 0x0a42; t.uuvowelsignbengali = 0x09c2; t.uuvowelsigndeva = 0x0942; t.uuvowelsigngujarati = 0x0ac2; t.uvowelsignbengali = 0x09c1; t.uvowelsigndeva = 0x0941; t.uvowelsigngujarati = 0x0ac1; t.v = 0x0076; t.vadeva = 0x0935; t.vagujarati = 0x0ab5; t.vagurmukhi = 0x0a35; t.vakatakana = 0x30f7; t.vav = 0x05d5; t.vavdagesh = 0xfb35; t.vavdagesh65 = 0xfb35; t.vavdageshhebrew = 0xfb35; t.vavhebrew = 0x05d5; t.vavholam = 0xfb4b; t.vavholamhebrew = 0xfb4b; t.vavvavhebrew = 0x05f0; t.vavyodhebrew = 0x05f1; t.vcircle = 0x24e5; t.vdotbelow = 0x1e7f; t.vecyrillic = 0x0432; t.veharabic = 0x06a4; t.vehfinalarabic = 0xfb6b; t.vehinitialarabic = 0xfb6c; t.vehmedialarabic = 0xfb6d; t.vekatakana = 0x30f9; t.venus = 0x2640; t.verticalbar = 0x007c; t.verticallineabovecmb = 0x030d; t.verticallinebelowcmb = 0x0329; t.verticallinelowmod = 0x02cc; t.verticallinemod = 0x02c8; t.vewarmenian = 0x057e; t.vhook = 0x028b; t.vikatakana = 0x30f8; t.viramabengali = 0x09cd; t.viramadeva = 0x094d; t.viramagujarati = 0x0acd; t.visargabengali = 0x0983; t.visargadeva = 0x0903; t.visargagujarati = 0x0a83; t.vmonospace = 0xff56; t.voarmenian = 0x0578; t.voicediterationhiragana = 0x309e; t.voicediterationkatakana = 0x30fe; t.voicedmarkkana = 0x309b; t.voicedmarkkanahalfwidth = 0xff9e; t.vokatakana = 0x30fa; t.vparen = 0x24b1; t.vtilde = 0x1e7d; t.vturned = 0x028c; t.vuhiragana = 0x3094; t.vukatakana = 0x30f4; t.w = 0x0077; t.wacute = 0x1e83; t.waekorean = 0x3159; t.wahiragana = 0x308f; t.wakatakana = 0x30ef; t.wakatakanahalfwidth = 0xff9c; t.wakorean = 0x3158; t.wasmallhiragana = 0x308e; t.wasmallkatakana = 0x30ee; t.wattosquare = 0x3357; t.wavedash = 0x301c; t.wavyunderscorevertical = 0xfe34; t.wawarabic = 0x0648; t.wawfinalarabic = 0xfeee; t.wawhamzaabovearabic = 0x0624; t.wawhamzaabovefinalarabic = 0xfe86; t.wbsquare = 0x33dd; t.wcircle = 0x24e6; t.wcircumflex = 0x0175; t.wdieresis = 0x1e85; t.wdotaccent = 0x1e87; t.wdotbelow = 0x1e89; t.wehiragana = 0x3091; t.weierstrass = 0x2118; t.wekatakana = 0x30f1; t.wekorean = 0x315e; t.weokorean = 0x315d; t.wgrave = 0x1e81; t.whitebullet = 0x25e6; t.whitecircle = 0x25cb; t.whitecircleinverse = 0x25d9; t.whitecornerbracketleft = 0x300e; t.whitecornerbracketleftvertical = 0xfe43; t.whitecornerbracketright = 0x300f; t.whitecornerbracketrightvertical = 0xfe44; t.whitediamond = 0x25c7; t.whitediamondcontainingblacksmalldiamond = 0x25c8; t.whitedownpointingsmalltriangle = 0x25bf; t.whitedownpointingtriangle = 0x25bd; t.whiteleftpointingsmalltriangle = 0x25c3; t.whiteleftpointingtriangle = 0x25c1; t.whitelenticularbracketleft = 0x3016; t.whitelenticularbracketright = 0x3017; t.whiterightpointingsmalltriangle = 0x25b9; t.whiterightpointingtriangle = 0x25b7; t.whitesmallsquare = 0x25ab; t.whitesmilingface = 0x263a; t.whitesquare = 0x25a1; t.whitestar = 0x2606; t.whitetelephone = 0x260f; t.whitetortoiseshellbracketleft = 0x3018; t.whitetortoiseshellbracketright = 0x3019; t.whiteuppointingsmalltriangle = 0x25b5; t.whiteuppointingtriangle = 0x25b3; t.wihiragana = 0x3090; t.wikatakana = 0x30f0; t.wikorean = 0x315f; t.wmonospace = 0xff57; t.wohiragana = 0x3092; t.wokatakana = 0x30f2; t.wokatakanahalfwidth = 0xff66; t.won = 0x20a9; t.wonmonospace = 0xffe6; t.wowaenthai = 0x0e27; t.wparen = 0x24b2; t.wring = 0x1e98; t.wsuperior = 0x02b7; t.wturned = 0x028d; t.wynn = 0x01bf; t.x = 0x0078; t.xabovecmb = 0x033d; t.xbopomofo = 0x3112; t.xcircle = 0x24e7; t.xdieresis = 0x1e8d; t.xdotaccent = 0x1e8b; t.xeharmenian = 0x056d; t.xi = 0x03be; t.xmonospace = 0xff58; t.xparen = 0x24b3; t.xsuperior = 0x02e3; t.y = 0x0079; t.yaadosquare = 0x334e; t.yabengali = 0x09af; t.yacute = 0x00fd; t.yadeva = 0x092f; t.yaekorean = 0x3152; t.yagujarati = 0x0aaf; t.yagurmukhi = 0x0a2f; t.yahiragana = 0x3084; t.yakatakana = 0x30e4; t.yakatakanahalfwidth = 0xff94; t.yakorean = 0x3151; t.yamakkanthai = 0x0e4e; t.yasmallhiragana = 0x3083; t.yasmallkatakana = 0x30e3; t.yasmallkatakanahalfwidth = 0xff6c; t.yatcyrillic = 0x0463; t.ycircle = 0x24e8; t.ycircumflex = 0x0177; t.ydieresis = 0x00ff; t.ydotaccent = 0x1e8f; t.ydotbelow = 0x1ef5; t.yeharabic = 0x064a; t.yehbarreearabic = 0x06d2; t.yehbarreefinalarabic = 0xfbaf; t.yehfinalarabic = 0xfef2; t.yehhamzaabovearabic = 0x0626; t.yehhamzaabovefinalarabic = 0xfe8a; t.yehhamzaaboveinitialarabic = 0xfe8b; t.yehhamzaabovemedialarabic = 0xfe8c; t.yehinitialarabic = 0xfef3; t.yehmedialarabic = 0xfef4; t.yehmeeminitialarabic = 0xfcdd; t.yehmeemisolatedarabic = 0xfc58; t.yehnoonfinalarabic = 0xfc94; t.yehthreedotsbelowarabic = 0x06d1; t.yekorean = 0x3156; t.yen = 0x00a5; t.yenmonospace = 0xffe5; t.yeokorean = 0x3155; t.yeorinhieuhkorean = 0x3186; t.yerahbenyomohebrew = 0x05aa; t.yerahbenyomolefthebrew = 0x05aa; t.yericyrillic = 0x044b; t.yerudieresiscyrillic = 0x04f9; t.yesieungkorean = 0x3181; t.yesieungpansioskorean = 0x3183; t.yesieungsioskorean = 0x3182; t.yetivhebrew = 0x059a; t.ygrave = 0x1ef3; t.yhook = 0x01b4; t.yhookabove = 0x1ef7; t.yiarmenian = 0x0575; t.yicyrillic = 0x0457; t.yikorean = 0x3162; t.yinyang = 0x262f; t.yiwnarmenian = 0x0582; t.ymonospace = 0xff59; t.yod = 0x05d9; t.yoddagesh = 0xfb39; t.yoddageshhebrew = 0xfb39; t.yodhebrew = 0x05d9; t.yodyodhebrew = 0x05f2; t.yodyodpatahhebrew = 0xfb1f; t.yohiragana = 0x3088; t.yoikorean = 0x3189; t.yokatakana = 0x30e8; t.yokatakanahalfwidth = 0xff96; t.yokorean = 0x315b; t.yosmallhiragana = 0x3087; t.yosmallkatakana = 0x30e7; t.yosmallkatakanahalfwidth = 0xff6e; t.yotgreek = 0x03f3; t.yoyaekorean = 0x3188; t.yoyakorean = 0x3187; t.yoyakthai = 0x0e22; t.yoyingthai = 0x0e0d; t.yparen = 0x24b4; t.ypogegrammeni = 0x037a; t.ypogegrammenigreekcmb = 0x0345; t.yr = 0x01a6; t.yring = 0x1e99; t.ysuperior = 0x02b8; t.ytilde = 0x1ef9; t.yturned = 0x028e; t.yuhiragana = 0x3086; t.yuikorean = 0x318c; t.yukatakana = 0x30e6; t.yukatakanahalfwidth = 0xff95; t.yukorean = 0x3160; t.yusbigcyrillic = 0x046b; t.yusbigiotifiedcyrillic = 0x046d; t.yuslittlecyrillic = 0x0467; t.yuslittleiotifiedcyrillic = 0x0469; t.yusmallhiragana = 0x3085; t.yusmallkatakana = 0x30e5; t.yusmallkatakanahalfwidth = 0xff6d; t.yuyekorean = 0x318b; t.yuyeokorean = 0x318a; t.yyabengali = 0x09df; t.yyadeva = 0x095f; t.z = 0x007a; t.zaarmenian = 0x0566; t.zacute = 0x017a; t.zadeva = 0x095b; t.zagurmukhi = 0x0a5b; t.zaharabic = 0x0638; t.zahfinalarabic = 0xfec6; t.zahinitialarabic = 0xfec7; t.zahiragana = 0x3056; t.zahmedialarabic = 0xfec8; t.zainarabic = 0x0632; t.zainfinalarabic = 0xfeb0; t.zakatakana = 0x30b6; t.zaqefgadolhebrew = 0x0595; t.zaqefqatanhebrew = 0x0594; t.zarqahebrew = 0x0598; t.zayin = 0x05d6; t.zayindagesh = 0xfb36; t.zayindageshhebrew = 0xfb36; t.zayinhebrew = 0x05d6; t.zbopomofo = 0x3117; t.zcaron = 0x017e; t.zcircle = 0x24e9; t.zcircumflex = 0x1e91; t.zcurl = 0x0291; t.zdot = 0x017c; t.zdotaccent = 0x017c; t.zdotbelow = 0x1e93; t.zecyrillic = 0x0437; t.zedescendercyrillic = 0x0499; t.zedieresiscyrillic = 0x04df; t.zehiragana = 0x305c; t.zekatakana = 0x30bc; t.zero = 0x0030; t.zeroarabic = 0x0660; t.zerobengali = 0x09e6; t.zerodeva = 0x0966; t.zerogujarati = 0x0ae6; t.zerogurmukhi = 0x0a66; t.zerohackarabic = 0x0660; t.zeroinferior = 0x2080; t.zeromonospace = 0xff10; t.zerooldstyle = 0xf730; t.zeropersian = 0x06f0; t.zerosuperior = 0x2070; t.zerothai = 0x0e50; t.zerowidthjoiner = 0xfeff; t.zerowidthnonjoiner = 0x200c; t.zerowidthspace = 0x200b; t.zeta = 0x03b6; t.zhbopomofo = 0x3113; t.zhearmenian = 0x056a; t.zhebrevecyrillic = 0x04c2; t.zhecyrillic = 0x0436; t.zhedescendercyrillic = 0x0497; t.zhedieresiscyrillic = 0x04dd; t.zihiragana = 0x3058; t.zikatakana = 0x30b8; t.zinorhebrew = 0x05ae; t.zlinebelow = 0x1e95; t.zmonospace = 0xff5a; t.zohiragana = 0x305e; t.zokatakana = 0x30be; t.zparen = 0x24b5; t.zretroflexhook = 0x0290; t.zstroke = 0x01b6; t.zuhiragana = 0x305a; t.zukatakana = 0x30ba; t[".notdef"] = 0x0000; t.angbracketleftbig = 0x2329; t.angbracketleftBig = 0x2329; t.angbracketleftbigg = 0x2329; t.angbracketleftBigg = 0x2329; t.angbracketrightBig = 0x232a; t.angbracketrightbig = 0x232a; t.angbracketrightBigg = 0x232a; t.angbracketrightbigg = 0x232a; t.arrowhookleft = 0x21aa; t.arrowhookright = 0x21a9; t.arrowlefttophalf = 0x21bc; t.arrowleftbothalf = 0x21bd; t.arrownortheast = 0x2197; t.arrownorthwest = 0x2196; t.arrowrighttophalf = 0x21c0; t.arrowrightbothalf = 0x21c1; t.arrowsoutheast = 0x2198; t.arrowsouthwest = 0x2199; t.backslashbig = 0x2216; t.backslashBig = 0x2216; t.backslashBigg = 0x2216; t.backslashbigg = 0x2216; t.bardbl = 0x2016; t.bracehtipdownleft = 0xfe37; t.bracehtipdownright = 0xfe37; t.bracehtipupleft = 0xfe38; t.bracehtipupright = 0xfe38; t.braceleftBig = 0x007b; t.braceleftbig = 0x007b; t.braceleftbigg = 0x007b; t.braceleftBigg = 0x007b; t.bracerightBig = 0x007d; t.bracerightbig = 0x007d; t.bracerightbigg = 0x007d; t.bracerightBigg = 0x007d; t.bracketleftbig = 0x005b; t.bracketleftBig = 0x005b; t.bracketleftbigg = 0x005b; t.bracketleftBigg = 0x005b; t.bracketrightBig = 0x005d; t.bracketrightbig = 0x005d; t.bracketrightbigg = 0x005d; t.bracketrightBigg = 0x005d; t.ceilingleftbig = 0x2308; t.ceilingleftBig = 0x2308; t.ceilingleftBigg = 0x2308; t.ceilingleftbigg = 0x2308; t.ceilingrightbig = 0x2309; t.ceilingrightBig = 0x2309; t.ceilingrightbigg = 0x2309; t.ceilingrightBigg = 0x2309; t.circledotdisplay = 0x2299; t.circledottext = 0x2299; t.circlemultiplydisplay = 0x2297; t.circlemultiplytext = 0x2297; t.circleplusdisplay = 0x2295; t.circleplustext = 0x2295; t.contintegraldisplay = 0x222e; t.contintegraltext = 0x222e; t.coproductdisplay = 0x2210; t.coproducttext = 0x2210; t.floorleftBig = 0x230a; t.floorleftbig = 0x230a; t.floorleftbigg = 0x230a; t.floorleftBigg = 0x230a; t.floorrightbig = 0x230b; t.floorrightBig = 0x230b; t.floorrightBigg = 0x230b; t.floorrightbigg = 0x230b; t.hatwide = 0x0302; t.hatwider = 0x0302; t.hatwidest = 0x0302; t.intercal = 0x1d40; t.integraldisplay = 0x222b; t.integraltext = 0x222b; t.intersectiondisplay = 0x22c2; t.intersectiontext = 0x22c2; t.logicalanddisplay = 0x2227; t.logicalandtext = 0x2227; t.logicalordisplay = 0x2228; t.logicalortext = 0x2228; t.parenleftBig = 0x0028; t.parenleftbig = 0x0028; t.parenleftBigg = 0x0028; t.parenleftbigg = 0x0028; t.parenrightBig = 0x0029; t.parenrightbig = 0x0029; t.parenrightBigg = 0x0029; t.parenrightbigg = 0x0029; t.prime = 0x2032; t.productdisplay = 0x220f; t.producttext = 0x220f; t.radicalbig = 0x221a; t.radicalBig = 0x221a; t.radicalBigg = 0x221a; t.radicalbigg = 0x221a; t.radicalbt = 0x221a; t.radicaltp = 0x221a; t.radicalvertex = 0x221a; t.slashbig = 0x002f; t.slashBig = 0x002f; t.slashBigg = 0x002f; t.slashbigg = 0x002f; t.summationdisplay = 0x2211; t.summationtext = 0x2211; t.tildewide = 0x02dc; t.tildewider = 0x02dc; t.tildewidest = 0x02dc; t.uniondisplay = 0x22c3; t.unionmultidisplay = 0x228e; t.unionmultitext = 0x228e; t.unionsqdisplay = 0x2294; t.unionsqtext = 0x2294; t.uniontext = 0x22c3; t.vextenddouble = 0x2225; t.vextendsingle = 0x2223; }); const getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) { t.space = 0x0020; t.a1 = 0x2701; t.a2 = 0x2702; t.a202 = 0x2703; t.a3 = 0x2704; t.a4 = 0x260e; t.a5 = 0x2706; t.a119 = 0x2707; t.a118 = 0x2708; t.a117 = 0x2709; t.a11 = 0x261b; t.a12 = 0x261e; t.a13 = 0x270c; t.a14 = 0x270d; t.a15 = 0x270e; t.a16 = 0x270f; t.a105 = 0x2710; t.a17 = 0x2711; t.a18 = 0x2712; t.a19 = 0x2713; t.a20 = 0x2714; t.a21 = 0x2715; t.a22 = 0x2716; t.a23 = 0x2717; t.a24 = 0x2718; t.a25 = 0x2719; t.a26 = 0x271a; t.a27 = 0x271b; t.a28 = 0x271c; t.a6 = 0x271d; t.a7 = 0x271e; t.a8 = 0x271f; t.a9 = 0x2720; t.a10 = 0x2721; t.a29 = 0x2722; t.a30 = 0x2723; t.a31 = 0x2724; t.a32 = 0x2725; t.a33 = 0x2726; t.a34 = 0x2727; t.a35 = 0x2605; t.a36 = 0x2729; t.a37 = 0x272a; t.a38 = 0x272b; t.a39 = 0x272c; t.a40 = 0x272d; t.a41 = 0x272e; t.a42 = 0x272f; t.a43 = 0x2730; t.a44 = 0x2731; t.a45 = 0x2732; t.a46 = 0x2733; t.a47 = 0x2734; t.a48 = 0x2735; t.a49 = 0x2736; t.a50 = 0x2737; t.a51 = 0x2738; t.a52 = 0x2739; t.a53 = 0x273a; t.a54 = 0x273b; t.a55 = 0x273c; t.a56 = 0x273d; t.a57 = 0x273e; t.a58 = 0x273f; t.a59 = 0x2740; t.a60 = 0x2741; t.a61 = 0x2742; t.a62 = 0x2743; t.a63 = 0x2744; t.a64 = 0x2745; t.a65 = 0x2746; t.a66 = 0x2747; t.a67 = 0x2748; t.a68 = 0x2749; t.a69 = 0x274a; t.a70 = 0x274b; t.a71 = 0x25cf; t.a72 = 0x274d; t.a73 = 0x25a0; t.a74 = 0x274f; t.a203 = 0x2750; t.a75 = 0x2751; t.a204 = 0x2752; t.a76 = 0x25b2; t.a77 = 0x25bc; t.a78 = 0x25c6; t.a79 = 0x2756; t.a81 = 0x25d7; t.a82 = 0x2758; t.a83 = 0x2759; t.a84 = 0x275a; t.a97 = 0x275b; t.a98 = 0x275c; t.a99 = 0x275d; t.a100 = 0x275e; t.a101 = 0x2761; t.a102 = 0x2762; t.a103 = 0x2763; t.a104 = 0x2764; t.a106 = 0x2765; t.a107 = 0x2766; t.a108 = 0x2767; t.a112 = 0x2663; t.a111 = 0x2666; t.a110 = 0x2665; t.a109 = 0x2660; t.a120 = 0x2460; t.a121 = 0x2461; t.a122 = 0x2462; t.a123 = 0x2463; t.a124 = 0x2464; t.a125 = 0x2465; t.a126 = 0x2466; t.a127 = 0x2467; t.a128 = 0x2468; t.a129 = 0x2469; t.a130 = 0x2776; t.a131 = 0x2777; t.a132 = 0x2778; t.a133 = 0x2779; t.a134 = 0x277a; t.a135 = 0x277b; t.a136 = 0x277c; t.a137 = 0x277d; t.a138 = 0x277e; t.a139 = 0x277f; t.a140 = 0x2780; t.a141 = 0x2781; t.a142 = 0x2782; t.a143 = 0x2783; t.a144 = 0x2784; t.a145 = 0x2785; t.a146 = 0x2786; t.a147 = 0x2787; t.a148 = 0x2788; t.a149 = 0x2789; t.a150 = 0x278a; t.a151 = 0x278b; t.a152 = 0x278c; t.a153 = 0x278d; t.a154 = 0x278e; t.a155 = 0x278f; t.a156 = 0x2790; t.a157 = 0x2791; t.a158 = 0x2792; t.a159 = 0x2793; t.a160 = 0x2794; t.a161 = 0x2192; t.a163 = 0x2194; t.a164 = 0x2195; t.a196 = 0x2798; t.a165 = 0x2799; t.a192 = 0x279a; t.a166 = 0x279b; t.a167 = 0x279c; t.a168 = 0x279d; t.a169 = 0x279e; t.a170 = 0x279f; t.a171 = 0x27a0; t.a172 = 0x27a1; t.a173 = 0x27a2; t.a162 = 0x27a3; t.a174 = 0x27a4; t.a175 = 0x27a5; t.a176 = 0x27a6; t.a177 = 0x27a7; t.a178 = 0x27a8; t.a179 = 0x27a9; t.a193 = 0x27aa; t.a180 = 0x27ab; t.a199 = 0x27ac; t.a181 = 0x27ad; t.a200 = 0x27ae; t.a182 = 0x27af; t.a201 = 0x27b1; t.a183 = 0x27b2; t.a184 = 0x27b3; t.a197 = 0x27b4; t.a185 = 0x27b5; t.a194 = 0x27b6; t.a198 = 0x27b7; t.a186 = 0x27b8; t.a195 = 0x27b9; t.a187 = 0x27ba; t.a188 = 0x27bb; t.a189 = 0x27bc; t.a190 = 0x27bd; t.a191 = 0x27be; t.a89 = 0x2768; t.a90 = 0x2769; t.a93 = 0x276a; t.a94 = 0x276b; t.a91 = 0x276c; t.a92 = 0x276d; t.a205 = 0x276e; t.a85 = 0x276f; t.a206 = 0x2770; t.a86 = 0x2771; t.a87 = 0x2772; t.a88 = 0x2773; t.a95 = 0x2774; t.a96 = 0x2775; t[".notdef"] = 0x0000; }); ;// CONCATENATED MODULE: ./src/core/unicode.js const getSpecialPUASymbols = getLookupTableFactory(function (t) { t[63721] = 0x00a9; t[63193] = 0x00a9; t[63720] = 0x00ae; t[63194] = 0x00ae; t[63722] = 0x2122; t[63195] = 0x2122; t[63729] = 0x23a7; t[63730] = 0x23a8; t[63731] = 0x23a9; t[63740] = 0x23ab; t[63741] = 0x23ac; t[63742] = 0x23ad; t[63726] = 0x23a1; t[63727] = 0x23a2; t[63728] = 0x23a3; t[63737] = 0x23a4; t[63738] = 0x23a5; t[63739] = 0x23a6; t[63723] = 0x239b; t[63724] = 0x239c; t[63725] = 0x239d; t[63734] = 0x239e; t[63735] = 0x239f; t[63736] = 0x23a0; }); function mapSpecialUnicodeValues(code) { if (code >= 0xfff0 && code <= 0xffff) { return 0; } else if (code >= 0xf600 && code <= 0xf8ff) { return getSpecialPUASymbols()[code] || code; } else if (code === 0x00ad) { return 0x002d; } return code; } function getUnicodeForGlyph(name, glyphsUnicodeMap) { let unicode = glyphsUnicodeMap[name]; if (unicode !== undefined) { return unicode; } if (!name) { return -1; } if (name[0] === "u") { const nameLen = name.length; let hexStr; if (nameLen === 7 && name[1] === "n" && name[2] === "i") { hexStr = name.substring(3); } else if (nameLen >= 5 && nameLen <= 7) { hexStr = name.substring(1); } else { return -1; } if (hexStr === hexStr.toUpperCase()) { unicode = parseInt(hexStr, 16); if (unicode >= 0) { return unicode; } } } return -1; } const UnicodeRanges = [[0x0000, 0x007f], [0x0080, 0x00ff], [0x0100, 0x017f], [0x0180, 0x024f], [0x0250, 0x02af, 0x1d00, 0x1d7f, 0x1d80, 0x1dbf], [0x02b0, 0x02ff, 0xa700, 0xa71f], [0x0300, 0x036f, 0x1dc0, 0x1dff], [0x0370, 0x03ff], [0x2c80, 0x2cff], [0x0400, 0x04ff, 0x0500, 0x052f, 0x2de0, 0x2dff, 0xa640, 0xa69f], [0x0530, 0x058f], [0x0590, 0x05ff], [0xa500, 0xa63f], [0x0600, 0x06ff, 0x0750, 0x077f], [0x07c0, 0x07ff], [0x0900, 0x097f], [0x0980, 0x09ff], [0x0a00, 0x0a7f], [0x0a80, 0x0aff], [0x0b00, 0x0b7f], [0x0b80, 0x0bff], [0x0c00, 0x0c7f], [0x0c80, 0x0cff], [0x0d00, 0x0d7f], [0x0e00, 0x0e7f], [0x0e80, 0x0eff], [0x10a0, 0x10ff, 0x2d00, 0x2d2f], [0x1b00, 0x1b7f], [0x1100, 0x11ff], [0x1e00, 0x1eff, 0x2c60, 0x2c7f, 0xa720, 0xa7ff], [0x1f00, 0x1fff], [0x2000, 0x206f, 0x2e00, 0x2e7f], [0x2070, 0x209f], [0x20a0, 0x20cf], [0x20d0, 0x20ff], [0x2100, 0x214f], [0x2150, 0x218f], [0x2190, 0x21ff, 0x27f0, 0x27ff, 0x2900, 0x297f, 0x2b00, 0x2bff], [0x2200, 0x22ff, 0x2a00, 0x2aff, 0x27c0, 0x27ef, 0x2980, 0x29ff], [0x2300, 0x23ff], [0x2400, 0x243f], [0x2440, 0x245f], [0x2460, 0x24ff], [0x2500, 0x257f], [0x2580, 0x259f], [0x25a0, 0x25ff], [0x2600, 0x26ff], [0x2700, 0x27bf], [0x3000, 0x303f], [0x3040, 0x309f], [0x30a0, 0x30ff, 0x31f0, 0x31ff], [0x3100, 0x312f, 0x31a0, 0x31bf], [0x3130, 0x318f], [0xa840, 0xa87f], [0x3200, 0x32ff], [0x3300, 0x33ff], [0xac00, 0xd7af], [0xd800, 0xdfff], [0x10900, 0x1091f], [0x4e00, 0x9fff, 0x2e80, 0x2eff, 0x2f00, 0x2fdf, 0x2ff0, 0x2fff, 0x3400, 0x4dbf, 0x20000, 0x2a6df, 0x3190, 0x319f], [0xe000, 0xf8ff], [0x31c0, 0x31ef, 0xf900, 0xfaff, 0x2f800, 0x2fa1f], [0xfb00, 0xfb4f], [0xfb50, 0xfdff], [0xfe20, 0xfe2f], [0xfe10, 0xfe1f], [0xfe50, 0xfe6f], [0xfe70, 0xfeff], [0xff00, 0xffef], [0xfff0, 0xffff], [0x0f00, 0x0fff], [0x0700, 0x074f], [0x0780, 0x07bf], [0x0d80, 0x0dff], [0x1000, 0x109f], [0x1200, 0x137f, 0x1380, 0x139f, 0x2d80, 0x2ddf], [0x13a0, 0x13ff], [0x1400, 0x167f], [0x1680, 0x169f], [0x16a0, 0x16ff], [0x1780, 0x17ff], [0x1800, 0x18af], [0x2800, 0x28ff], [0xa000, 0xa48f], [0x1700, 0x171f, 0x1720, 0x173f, 0x1740, 0x175f, 0x1760, 0x177f], [0x10300, 0x1032f], [0x10330, 0x1034f], [0x10400, 0x1044f], [0x1d000, 0x1d0ff, 0x1d100, 0x1d1ff, 0x1d200, 0x1d24f], [0x1d400, 0x1d7ff], [0xff000, 0xffffd], [0xfe00, 0xfe0f, 0xe0100, 0xe01ef], [0xe0000, 0xe007f], [0x1900, 0x194f], [0x1950, 0x197f], [0x1980, 0x19df], [0x1a00, 0x1a1f], [0x2c00, 0x2c5f], [0x2d30, 0x2d7f], [0x4dc0, 0x4dff], [0xa800, 0xa82f], [0x10000, 0x1007f, 0x10080, 0x100ff, 0x10100, 0x1013f], [0x10140, 0x1018f], [0x10380, 0x1039f], [0x103a0, 0x103df], [0x10450, 0x1047f], [0x10480, 0x104af], [0x10800, 0x1083f], [0x10a00, 0x10a5f], [0x1d300, 0x1d35f], [0x12000, 0x123ff, 0x12400, 0x1247f], [0x1d360, 0x1d37f], [0x1b80, 0x1bbf], [0x1c00, 0x1c4f], [0x1c50, 0x1c7f], [0xa880, 0xa8df], [0xa900, 0xa92f], [0xa930, 0xa95f], [0xaa00, 0xaa5f], [0x10190, 0x101cf], [0x101d0, 0x101ff], [0x102a0, 0x102df, 0x10280, 0x1029f, 0x10920, 0x1093f], [0x1f030, 0x1f09f, 0x1f000, 0x1f02f]]; function getUnicodeRangeFor(value, lastPosition = -1) { if (lastPosition !== -1) { const range = UnicodeRanges[lastPosition]; for (let i = 0, ii = range.length; i < ii; i += 2) { if (value >= range[i] && value <= range[i + 1]) { return lastPosition; } } } for (let i = 0, ii = UnicodeRanges.length; i < ii; i++) { const range = UnicodeRanges[i]; for (let j = 0, jj = range.length; j < jj; j += 2) { if (value >= range[j] && value <= range[j + 1]) { return i; } } } return -1; } const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u"); const CategoryCache = new Map(); function getCharUnicodeCategory(char) { const cachedCategory = CategoryCache.get(char); if (cachedCategory) { return cachedCategory; } const groups = char.match(SpecialCharRegExp); const category = { isWhitespace: !!groups?.[1], isZeroWidthDiacritic: !!groups?.[2], isInvisibleFormatMark: !!groups?.[3] }; CategoryCache.set(char, category); return category; } function clearUnicodeCaches() { CategoryCache.clear(); } ;// CONCATENATED MODULE: ./src/core/fonts_utils.js const SEAC_ANALYSIS_ENABLED = true; const FontFlags = { FixedPitch: 1, Serif: 2, Symbolic: 4, Script: 8, Nonsymbolic: 32, Italic: 64, AllCap: 65536, SmallCap: 131072, ForceBold: 262144 }; const MacStandardGlyphOrdering = [".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat"]; function recoverGlyphName(name, glyphsUnicodeMap) { if (glyphsUnicodeMap[name] !== undefined) { return name; } const unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); if (unicode !== -1) { for (const key in glyphsUnicodeMap) { if (glyphsUnicodeMap[key] === unicode) { return key; } } } info("Unable to recover a standard glyph name for: " + name); return name; } function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { const charCodeToGlyphId = Object.create(null); let glyphId, charCode, baseEncoding; const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); if (properties.isInternalFont) { baseEncoding = builtInEncoding; for (charCode = 0; charCode < baseEncoding.length; charCode++) { glyphId = glyphNames.indexOf(baseEncoding[charCode]); charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0; } } else if (properties.baseEncodingName) { baseEncoding = getEncoding(properties.baseEncodingName); for (charCode = 0; charCode < baseEncoding.length; charCode++) { glyphId = glyphNames.indexOf(baseEncoding[charCode]); charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0; } } else if (isSymbolicFont) { for (charCode in builtInEncoding) { charCodeToGlyphId[charCode] = builtInEncoding[charCode]; } } else { baseEncoding = StandardEncoding; for (charCode = 0; charCode < baseEncoding.length; charCode++) { glyphId = glyphNames.indexOf(baseEncoding[charCode]); charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0; } } const differences = properties.differences; let glyphsUnicodeMap; if (differences) { for (charCode in differences) { const glyphName = differences[charCode]; glyphId = glyphNames.indexOf(glyphName); if (glyphId === -1) { if (!glyphsUnicodeMap) { glyphsUnicodeMap = getGlyphsUnicode(); } const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); if (standardGlyphName !== glyphName) { glyphId = glyphNames.indexOf(standardGlyphName); } } charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0; } } return charCodeToGlyphId; } function normalizeFontName(name) { return name.replaceAll(/[,_]/g, "-").replaceAll(/\s/g, ""); } ;// CONCATENATED MODULE: ./src/core/standard_fonts.js const getStdFontMap = getLookupTableFactory(function (t) { t["Times-Roman"] = "Times-Roman"; t.Helvetica = "Helvetica"; t.Courier = "Courier"; t.Symbol = "Symbol"; t["Times-Bold"] = "Times-Bold"; t["Helvetica-Bold"] = "Helvetica-Bold"; t["Courier-Bold"] = "Courier-Bold"; t.ZapfDingbats = "ZapfDingbats"; t["Times-Italic"] = "Times-Italic"; t["Helvetica-Oblique"] = "Helvetica-Oblique"; t["Courier-Oblique"] = "Courier-Oblique"; t["Times-BoldItalic"] = "Times-BoldItalic"; t["Helvetica-BoldOblique"] = "Helvetica-BoldOblique"; t["Courier-BoldOblique"] = "Courier-BoldOblique"; t.ArialNarrow = "Helvetica"; t["ArialNarrow-Bold"] = "Helvetica-Bold"; t["ArialNarrow-BoldItalic"] = "Helvetica-BoldOblique"; t["ArialNarrow-Italic"] = "Helvetica-Oblique"; t.ArialBlack = "Helvetica"; t["ArialBlack-Bold"] = "Helvetica-Bold"; t["ArialBlack-BoldItalic"] = "Helvetica-BoldOblique"; t["ArialBlack-Italic"] = "Helvetica-Oblique"; t["Arial-Black"] = "Helvetica"; t["Arial-Black-Bold"] = "Helvetica-Bold"; t["Arial-Black-BoldItalic"] = "Helvetica-BoldOblique"; t["Arial-Black-Italic"] = "Helvetica-Oblique"; t.Arial = "Helvetica"; t["Arial-Bold"] = "Helvetica-Bold"; t["Arial-BoldItalic"] = "Helvetica-BoldOblique"; t["Arial-Italic"] = "Helvetica-Oblique"; t.ArialMT = "Helvetica"; t["Arial-BoldItalicMT"] = "Helvetica-BoldOblique"; t["Arial-BoldMT"] = "Helvetica-Bold"; t["Arial-ItalicMT"] = "Helvetica-Oblique"; t["Arial-BoldItalicMT-BoldItalic"] = "Helvetica-BoldOblique"; t["Arial-BoldMT-Bold"] = "Helvetica-Bold"; t["Arial-ItalicMT-Italic"] = "Helvetica-Oblique"; t.ArialUnicodeMS = "Helvetica"; t["ArialUnicodeMS-Bold"] = "Helvetica-Bold"; t["ArialUnicodeMS-BoldItalic"] = "Helvetica-BoldOblique"; t["ArialUnicodeMS-Italic"] = "Helvetica-Oblique"; t["Courier-BoldItalic"] = "Courier-BoldOblique"; t["Courier-Italic"] = "Courier-Oblique"; t.CourierNew = "Courier"; t["CourierNew-Bold"] = "Courier-Bold"; t["CourierNew-BoldItalic"] = "Courier-BoldOblique"; t["CourierNew-Italic"] = "Courier-Oblique"; t["CourierNewPS-BoldItalicMT"] = "Courier-BoldOblique"; t["CourierNewPS-BoldMT"] = "Courier-Bold"; t["CourierNewPS-ItalicMT"] = "Courier-Oblique"; t.CourierNewPSMT = "Courier"; t["Helvetica-BoldItalic"] = "Helvetica-BoldOblique"; t["Helvetica-Italic"] = "Helvetica-Oblique"; t["Symbol-Bold"] = "Symbol"; t["Symbol-BoldItalic"] = "Symbol"; t["Symbol-Italic"] = "Symbol"; t.TimesNewRoman = "Times-Roman"; t["TimesNewRoman-Bold"] = "Times-Bold"; t["TimesNewRoman-BoldItalic"] = "Times-BoldItalic"; t["TimesNewRoman-Italic"] = "Times-Italic"; t.TimesNewRomanPS = "Times-Roman"; t["TimesNewRomanPS-Bold"] = "Times-Bold"; t["TimesNewRomanPS-BoldItalic"] = "Times-BoldItalic"; t["TimesNewRomanPS-BoldItalicMT"] = "Times-BoldItalic"; t["TimesNewRomanPS-BoldMT"] = "Times-Bold"; t["TimesNewRomanPS-Italic"] = "Times-Italic"; t["TimesNewRomanPS-ItalicMT"] = "Times-Italic"; t.TimesNewRomanPSMT = "Times-Roman"; t["TimesNewRomanPSMT-Bold"] = "Times-Bold"; t["TimesNewRomanPSMT-BoldItalic"] = "Times-BoldItalic"; t["TimesNewRomanPSMT-Italic"] = "Times-Italic"; }); const getFontNameToFileMap = getLookupTableFactory(function (t) { t.Courier = "FoxitFixed.pfb"; t["Courier-Bold"] = "FoxitFixedBold.pfb"; t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb"; t["Courier-Oblique"] = "FoxitFixedItalic.pfb"; t.Helvetica = "LiberationSans-Regular.ttf"; t["Helvetica-Bold"] = "LiberationSans-Bold.ttf"; t["Helvetica-BoldOblique"] = "LiberationSans-BoldItalic.ttf"; t["Helvetica-Oblique"] = "LiberationSans-Italic.ttf"; t["Times-Roman"] = "FoxitSerif.pfb"; t["Times-Bold"] = "FoxitSerifBold.pfb"; t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb"; t["Times-Italic"] = "FoxitSerifItalic.pfb"; t.Symbol = "FoxitSymbol.pfb"; t.ZapfDingbats = "FoxitDingbats.pfb"; t["LiberationSans-Regular"] = "LiberationSans-Regular.ttf"; t["LiberationSans-Bold"] = "LiberationSans-Bold.ttf"; t["LiberationSans-Italic"] = "LiberationSans-Italic.ttf"; t["LiberationSans-BoldItalic"] = "LiberationSans-BoldItalic.ttf"; }); const getNonStdFontMap = getLookupTableFactory(function (t) { t.Calibri = "Helvetica"; t["Calibri-Bold"] = "Helvetica-Bold"; t["Calibri-BoldItalic"] = "Helvetica-BoldOblique"; t["Calibri-Italic"] = "Helvetica-Oblique"; t.CenturyGothic = "Helvetica"; t["CenturyGothic-Bold"] = "Helvetica-Bold"; t["CenturyGothic-BoldItalic"] = "Helvetica-BoldOblique"; t["CenturyGothic-Italic"] = "Helvetica-Oblique"; t.ComicSansMS = "Comic Sans MS"; t["ComicSansMS-Bold"] = "Comic Sans MS-Bold"; t["ComicSansMS-BoldItalic"] = "Comic Sans MS-BoldItalic"; t["ComicSansMS-Italic"] = "Comic Sans MS-Italic"; t.Impact = "Helvetica"; t["ItcSymbol-Bold"] = "Helvetica-Bold"; t["ItcSymbol-BoldItalic"] = "Helvetica-BoldOblique"; t["ItcSymbol-Book"] = "Helvetica"; t["ItcSymbol-BookItalic"] = "Helvetica-Oblique"; t["ItcSymbol-Medium"] = "Helvetica"; t["ItcSymbol-MediumItalic"] = "Helvetica-Oblique"; t.LucidaConsole = "Courier"; t["LucidaConsole-Bold"] = "Courier-Bold"; t["LucidaConsole-BoldItalic"] = "Courier-BoldOblique"; t["LucidaConsole-Italic"] = "Courier-Oblique"; t["LucidaSans-Demi"] = "Helvetica-Bold"; t["MS-Gothic"] = "MS Gothic"; t["MS-Gothic-Bold"] = "MS Gothic-Bold"; t["MS-Gothic-BoldItalic"] = "MS Gothic-BoldItalic"; t["MS-Gothic-Italic"] = "MS Gothic-Italic"; t["MS-Mincho"] = "MS Mincho"; t["MS-Mincho-Bold"] = "MS Mincho-Bold"; t["MS-Mincho-BoldItalic"] = "MS Mincho-BoldItalic"; t["MS-Mincho-Italic"] = "MS Mincho-Italic"; t["MS-PGothic"] = "MS PGothic"; t["MS-PGothic-Bold"] = "MS PGothic-Bold"; t["MS-PGothic-BoldItalic"] = "MS PGothic-BoldItalic"; t["MS-PGothic-Italic"] = "MS PGothic-Italic"; t["MS-PMincho"] = "MS PMincho"; t["MS-PMincho-Bold"] = "MS PMincho-Bold"; t["MS-PMincho-BoldItalic"] = "MS PMincho-BoldItalic"; t["MS-PMincho-Italic"] = "MS PMincho-Italic"; t.NuptialScript = "Times-Italic"; t.SegoeUISymbol = "Helvetica"; }); const getSerifFonts = getLookupTableFactory(function (t) { t["Adobe Jenson"] = true; t["Adobe Text"] = true; t.Albertus = true; t.Aldus = true; t.Alexandria = true; t.Algerian = true; t["American Typewriter"] = true; t.Antiqua = true; t.Apex = true; t.Arno = true; t.Aster = true; t.Aurora = true; t.Baskerville = true; t.Bell = true; t.Bembo = true; t["Bembo Schoolbook"] = true; t.Benguiat = true; t["Berkeley Old Style"] = true; t["Bernhard Modern"] = true; t["Berthold City"] = true; t.Bodoni = true; t["Bauer Bodoni"] = true; t["Book Antiqua"] = true; t.Bookman = true; t["Bordeaux Roman"] = true; t["Californian FB"] = true; t.Calisto = true; t.Calvert = true; t.Capitals = true; t.Cambria = true; t.Cartier = true; t.Caslon = true; t.Catull = true; t.Centaur = true; t["Century Old Style"] = true; t["Century Schoolbook"] = true; t.Chaparral = true; t["Charis SIL"] = true; t.Cheltenham = true; t["Cholla Slab"] = true; t.Clarendon = true; t.Clearface = true; t.Cochin = true; t.Colonna = true; t["Computer Modern"] = true; t["Concrete Roman"] = true; t.Constantia = true; t["Cooper Black"] = true; t.Corona = true; t.Ecotype = true; t.Egyptienne = true; t.Elephant = true; t.Excelsior = true; t.Fairfield = true; t["FF Scala"] = true; t.Folkard = true; t.Footlight = true; t.FreeSerif = true; t["Friz Quadrata"] = true; t.Garamond = true; t.Gentium = true; t.Georgia = true; t.Gloucester = true; t["Goudy Old Style"] = true; t["Goudy Schoolbook"] = true; t["Goudy Pro Font"] = true; t.Granjon = true; t["Guardian Egyptian"] = true; t.Heather = true; t.Hercules = true; t["High Tower Text"] = true; t.Hiroshige = true; t["Hoefler Text"] = true; t["Humana Serif"] = true; t.Imprint = true; t["Ionic No. 5"] = true; t.Janson = true; t.Joanna = true; t.Korinna = true; t.Lexicon = true; t.LiberationSerif = true; t["Liberation Serif"] = true; t["Linux Libertine"] = true; t.Literaturnaya = true; t.Lucida = true; t["Lucida Bright"] = true; t.Melior = true; t.Memphis = true; t.Miller = true; t.Minion = true; t.Modern = true; t["Mona Lisa"] = true; t["Mrs Eaves"] = true; t["MS Serif"] = true; t["Museo Slab"] = true; t["New York"] = true; t["Nimbus Roman"] = true; t["NPS Rawlinson Roadway"] = true; t.NuptialScript = true; t.Palatino = true; t.Perpetua = true; t.Plantin = true; t["Plantin Schoolbook"] = true; t.Playbill = true; t["Poor Richard"] = true; t["Rawlinson Roadway"] = true; t.Renault = true; t.Requiem = true; t.Rockwell = true; t.Roman = true; t["Rotis Serif"] = true; t.Sabon = true; t.Scala = true; t.Seagull = true; t.Sistina = true; t.Souvenir = true; t.STIX = true; t["Stone Informal"] = true; t["Stone Serif"] = true; t.Sylfaen = true; t.Times = true; t.Trajan = true; t["Trinité"] = true; t["Trump Mediaeval"] = true; t.Utopia = true; t["Vale Type"] = true; t["Bitstream Vera"] = true; t["Vera Serif"] = true; t.Versailles = true; t.Wanted = true; t.Weiss = true; t["Wide Latin"] = true; t.Windsor = true; t.XITS = true; }); const getSymbolsFonts = getLookupTableFactory(function (t) { t.Dingbats = true; t.Symbol = true; t.ZapfDingbats = true; t.Wingdings = true; t["Wingdings-Bold"] = true; t["Wingdings-Regular"] = true; }); const getGlyphMapForStandardFonts = getLookupTableFactory(function (t) { t[2] = 10; t[3] = 32; t[4] = 33; t[5] = 34; t[6] = 35; t[7] = 36; t[8] = 37; t[9] = 38; t[10] = 39; t[11] = 40; t[12] = 41; t[13] = 42; t[14] = 43; t[15] = 44; t[16] = 45; t[17] = 46; t[18] = 47; t[19] = 48; t[20] = 49; t[21] = 50; t[22] = 51; t[23] = 52; t[24] = 53; t[25] = 54; t[26] = 55; t[27] = 56; t[28] = 57; t[29] = 58; t[30] = 894; t[31] = 60; t[32] = 61; t[33] = 62; t[34] = 63; t[35] = 64; t[36] = 65; t[37] = 66; t[38] = 67; t[39] = 68; t[40] = 69; t[41] = 70; t[42] = 71; t[43] = 72; t[44] = 73; t[45] = 74; t[46] = 75; t[47] = 76; t[48] = 77; t[49] = 78; t[50] = 79; t[51] = 80; t[52] = 81; t[53] = 82; t[54] = 83; t[55] = 84; t[56] = 85; t[57] = 86; t[58] = 87; t[59] = 88; t[60] = 89; t[61] = 90; t[62] = 91; t[63] = 92; t[64] = 93; t[65] = 94; t[66] = 95; t[67] = 96; t[68] = 97; t[69] = 98; t[70] = 99; t[71] = 100; t[72] = 101; t[73] = 102; t[74] = 103; t[75] = 104; t[76] = 105; t[77] = 106; t[78] = 107; t[79] = 108; t[80] = 109; t[81] = 110; t[82] = 111; t[83] = 112; t[84] = 113; t[85] = 114; t[86] = 115; t[87] = 116; t[88] = 117; t[89] = 118; t[90] = 119; t[91] = 120; t[92] = 121; t[93] = 122; t[94] = 123; t[95] = 124; t[96] = 125; t[97] = 126; t[98] = 196; t[99] = 197; t[100] = 199; t[101] = 201; t[102] = 209; t[103] = 214; t[104] = 220; t[105] = 225; t[106] = 224; t[107] = 226; t[108] = 228; t[109] = 227; t[110] = 229; t[111] = 231; t[112] = 233; t[113] = 232; t[114] = 234; t[115] = 235; t[116] = 237; t[117] = 236; t[118] = 238; t[119] = 239; t[120] = 241; t[121] = 243; t[122] = 242; t[123] = 244; t[124] = 246; t[125] = 245; t[126] = 250; t[127] = 249; t[128] = 251; t[129] = 252; t[130] = 8224; t[131] = 176; t[132] = 162; t[133] = 163; t[134] = 167; t[135] = 8226; t[136] = 182; t[137] = 223; t[138] = 174; t[139] = 169; t[140] = 8482; t[141] = 180; t[142] = 168; t[143] = 8800; t[144] = 198; t[145] = 216; t[146] = 8734; t[147] = 177; t[148] = 8804; t[149] = 8805; t[150] = 165; t[151] = 181; t[152] = 8706; t[153] = 8721; t[154] = 8719; t[156] = 8747; t[157] = 170; t[158] = 186; t[159] = 8486; t[160] = 230; t[161] = 248; t[162] = 191; t[163] = 161; t[164] = 172; t[165] = 8730; t[166] = 402; t[167] = 8776; t[168] = 8710; t[169] = 171; t[170] = 187; t[171] = 8230; t[179] = 8220; t[180] = 8221; t[181] = 8216; t[182] = 8217; t[200] = 193; t[203] = 205; t[207] = 211; t[210] = 218; t[223] = 711; t[224] = 321; t[225] = 322; t[226] = 352; t[227] = 353; t[228] = 381; t[229] = 382; t[233] = 221; t[234] = 253; t[252] = 263; t[253] = 268; t[254] = 269; t[258] = 258; t[260] = 260; t[261] = 261; t[265] = 280; t[266] = 281; t[267] = 282; t[268] = 283; t[269] = 313; t[275] = 323; t[276] = 324; t[278] = 328; t[283] = 344; t[284] = 345; t[285] = 346; t[286] = 347; t[292] = 367; t[295] = 377; t[296] = 378; t[298] = 380; t[305] = 963; t[306] = 964; t[307] = 966; t[308] = 8215; t[309] = 8252; t[310] = 8319; t[311] = 8359; t[312] = 8592; t[313] = 8593; t[337] = 9552; t[493] = 1039; t[494] = 1040; t[672] = 1488; t[673] = 1489; t[674] = 1490; t[675] = 1491; t[676] = 1492; t[677] = 1493; t[678] = 1494; t[679] = 1495; t[680] = 1496; t[681] = 1497; t[682] = 1498; t[683] = 1499; t[684] = 1500; t[685] = 1501; t[686] = 1502; t[687] = 1503; t[688] = 1504; t[689] = 1505; t[690] = 1506; t[691] = 1507; t[692] = 1508; t[693] = 1509; t[694] = 1510; t[695] = 1511; t[696] = 1512; t[697] = 1513; t[698] = 1514; t[705] = 1524; t[706] = 8362; t[710] = 64288; t[711] = 64298; t[759] = 1617; t[761] = 1776; t[763] = 1778; t[775] = 1652; t[777] = 1764; t[778] = 1780; t[779] = 1781; t[780] = 1782; t[782] = 771; t[783] = 64726; t[786] = 8363; t[788] = 8532; t[790] = 768; t[791] = 769; t[792] = 768; t[795] = 803; t[797] = 64336; t[798] = 64337; t[799] = 64342; t[800] = 64343; t[801] = 64344; t[802] = 64345; t[803] = 64362; t[804] = 64363; t[805] = 64364; t[2424] = 7821; t[2425] = 7822; t[2426] = 7823; t[2427] = 7824; t[2428] = 7825; t[2429] = 7826; t[2430] = 7827; t[2433] = 7682; t[2678] = 8045; t[2679] = 8046; t[2830] = 1552; t[2838] = 686; t[2840] = 751; t[2842] = 753; t[2843] = 754; t[2844] = 755; t[2846] = 757; t[2856] = 767; t[2857] = 848; t[2858] = 849; t[2862] = 853; t[2863] = 854; t[2864] = 855; t[2865] = 861; t[2866] = 862; t[2906] = 7460; t[2908] = 7462; t[2909] = 7463; t[2910] = 7464; t[2912] = 7466; t[2913] = 7467; t[2914] = 7468; t[2916] = 7470; t[2917] = 7471; t[2918] = 7472; t[2920] = 7474; t[2921] = 7475; t[2922] = 7476; t[2924] = 7478; t[2925] = 7479; t[2926] = 7480; t[2928] = 7482; t[2929] = 7483; t[2930] = 7484; t[2932] = 7486; t[2933] = 7487; t[2934] = 7488; t[2936] = 7490; t[2937] = 7491; t[2938] = 7492; t[2940] = 7494; t[2941] = 7495; t[2942] = 7496; t[2944] = 7498; t[2946] = 7500; t[2948] = 7502; t[2950] = 7504; t[2951] = 7505; t[2952] = 7506; t[2954] = 7508; t[2955] = 7509; t[2956] = 7510; t[2958] = 7512; t[2959] = 7513; t[2960] = 7514; t[2962] = 7516; t[2963] = 7517; t[2964] = 7518; t[2966] = 7520; t[2967] = 7521; t[2968] = 7522; t[2970] = 7524; t[2971] = 7525; t[2972] = 7526; t[2974] = 7528; t[2975] = 7529; t[2976] = 7530; t[2978] = 1537; t[2979] = 1538; t[2980] = 1539; t[2982] = 1549; t[2983] = 1551; t[2984] = 1552; t[2986] = 1554; t[2987] = 1555; t[2988] = 1556; t[2990] = 1623; t[2991] = 1624; t[2995] = 1775; t[2999] = 1791; t[3002] = 64290; t[3003] = 64291; t[3004] = 64292; t[3006] = 64294; t[3007] = 64295; t[3008] = 64296; t[3011] = 1900; t[3014] = 8223; t[3015] = 8244; t[3017] = 7532; t[3018] = 7533; t[3019] = 7534; t[3075] = 7590; t[3076] = 7591; t[3079] = 7594; t[3080] = 7595; t[3083] = 7598; t[3084] = 7599; t[3087] = 7602; t[3088] = 7603; t[3091] = 7606; t[3092] = 7607; t[3095] = 7610; t[3096] = 7611; t[3099] = 7614; t[3100] = 7615; t[3103] = 7618; t[3104] = 7619; t[3107] = 8337; t[3108] = 8338; t[3116] = 1884; t[3119] = 1885; t[3120] = 1885; t[3123] = 1886; t[3124] = 1886; t[3127] = 1887; t[3128] = 1887; t[3131] = 1888; t[3132] = 1888; t[3135] = 1889; t[3136] = 1889; t[3139] = 1890; t[3140] = 1890; t[3143] = 1891; t[3144] = 1891; t[3147] = 1892; t[3148] = 1892; t[3153] = 580; t[3154] = 581; t[3157] = 584; t[3158] = 585; t[3161] = 588; t[3162] = 589; t[3165] = 891; t[3166] = 892; t[3169] = 1274; t[3170] = 1275; t[3173] = 1278; t[3174] = 1279; t[3181] = 7622; t[3182] = 7623; t[3282] = 11799; t[3316] = 578; t[3379] = 42785; t[3393] = 1159; t[3416] = 8377; }); const getSupplementalGlyphMapForArialBlack = getLookupTableFactory(function (t) { t[227] = 322; t[264] = 261; t[291] = 346; }); const getSupplementalGlyphMapForCalibri = getLookupTableFactory(function (t) { t[1] = 32; t[4] = 65; t[5] = 192; t[6] = 193; t[9] = 196; t[17] = 66; t[18] = 67; t[21] = 268; t[24] = 68; t[28] = 69; t[29] = 200; t[30] = 201; t[32] = 282; t[38] = 70; t[39] = 71; t[44] = 72; t[47] = 73; t[48] = 204; t[49] = 205; t[58] = 74; t[60] = 75; t[62] = 76; t[68] = 77; t[69] = 78; t[75] = 79; t[76] = 210; t[80] = 214; t[87] = 80; t[89] = 81; t[90] = 82; t[92] = 344; t[94] = 83; t[97] = 352; t[100] = 84; t[104] = 85; t[109] = 220; t[115] = 86; t[116] = 87; t[121] = 88; t[122] = 89; t[124] = 221; t[127] = 90; t[129] = 381; t[258] = 97; t[259] = 224; t[260] = 225; t[263] = 228; t[268] = 261; t[271] = 98; t[272] = 99; t[273] = 263; t[275] = 269; t[282] = 100; t[286] = 101; t[287] = 232; t[288] = 233; t[290] = 283; t[295] = 281; t[296] = 102; t[336] = 103; t[346] = 104; t[349] = 105; t[350] = 236; t[351] = 237; t[361] = 106; t[364] = 107; t[367] = 108; t[371] = 322; t[373] = 109; t[374] = 110; t[381] = 111; t[382] = 242; t[383] = 243; t[386] = 246; t[393] = 112; t[395] = 113; t[396] = 114; t[398] = 345; t[400] = 115; t[401] = 347; t[403] = 353; t[410] = 116; t[437] = 117; t[442] = 252; t[448] = 118; t[449] = 119; t[454] = 120; t[455] = 121; t[457] = 253; t[460] = 122; t[462] = 382; t[463] = 380; t[853] = 44; t[855] = 58; t[856] = 46; t[876] = 47; t[878] = 45; t[882] = 45; t[894] = 40; t[895] = 41; t[896] = 91; t[897] = 93; t[923] = 64; t[1004] = 48; t[1005] = 49; t[1006] = 50; t[1007] = 51; t[1008] = 52; t[1009] = 53; t[1010] = 54; t[1011] = 55; t[1012] = 56; t[1013] = 57; t[1081] = 37; t[1085] = 43; t[1086] = 45; }); function getStandardFontName(name) { const fontName = normalizeFontName(name); const stdFontMap = getStdFontMap(); return stdFontMap[fontName]; } function isKnownFontName(name) { const fontName = normalizeFontName(name); return !!(getStdFontMap()[fontName] || getNonStdFontMap()[fontName] || getSerifFonts()[fontName] || getSymbolsFonts()[fontName]); } ;// CONCATENATED MODULE: ./src/core/to_unicode_map.js class ToUnicodeMap { constructor(cmap = []) { this._map = cmap; } get length() { return this._map.length; } forEach(callback) { for (const charCode in this._map) { callback(charCode, this._map[charCode].charCodeAt(0)); } } has(i) { return this._map[i] !== undefined; } get(i) { return this._map[i]; } charCodeOf(value) { const map = this._map; if (map.length <= 0x10000) { return map.indexOf(value); } for (const charCode in map) { if (map[charCode] === value) { return charCode | 0; } } return -1; } amend(map) { for (const charCode in map) { this._map[charCode] = map[charCode]; } } } class IdentityToUnicodeMap { constructor(firstChar, lastChar) { this.firstChar = firstChar; this.lastChar = lastChar; } get length() { return this.lastChar + 1 - this.firstChar; } forEach(callback) { for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) { callback(i, i); } } has(i) { return this.firstChar <= i && i <= this.lastChar; } get(i) { if (this.firstChar <= i && i <= this.lastChar) { return String.fromCharCode(i); } return undefined; } charCodeOf(v) { return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar ? v : -1; } amend(map) { unreachable("Should not call amend()"); } } ;// CONCATENATED MODULE: ./src/core/cff_font.js class CFFFont { constructor(file, properties) { this.properties = properties; const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); this.cff = parser.parse(); this.cff.duplicateFirstGlyph(); const compiler = new CFFCompiler(this.cff); this.seacs = this.cff.seacs; try { this.data = compiler.compile(); } catch { warn("Failed to compile font " + properties.loadedName); this.data = file; } this._createBuiltInEncoding(); } get numGlyphs() { return this.cff.charStrings.count; } getCharset() { return this.cff.charset.charset; } getGlyphMapping() { const cff = this.cff; const properties = this.properties; const { cidToGidMap, cMap } = properties; const charsets = cff.charset.charset; let charCodeToGlyphId; let glyphId; if (properties.composite) { let invCidToGidMap; if (cidToGidMap?.length > 0) { invCidToGidMap = Object.create(null); for (let i = 0, ii = cidToGidMap.length; i < ii; i++) { const gid = cidToGidMap[i]; if (gid !== undefined) { invCidToGidMap[gid] = i; } } } charCodeToGlyphId = Object.create(null); let charCode; if (cff.isCIDFont) { for (glyphId = 0; glyphId < charsets.length; glyphId++) { const cid = charsets[glyphId]; charCode = cMap.charCodeOf(cid); if (invCidToGidMap?.[charCode] !== undefined) { charCode = invCidToGidMap[charCode]; } charCodeToGlyphId[charCode] = glyphId; } } else { for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { charCode = cMap.charCodeOf(glyphId); charCodeToGlyphId[charCode] = glyphId; } } return charCodeToGlyphId; } let encoding = cff.encoding ? cff.encoding.encoding : null; if (properties.isInternalFont) { encoding = properties.defaultEncoding; } charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); return charCodeToGlyphId; } hasGlyphId(id) { return this.cff.hasGlyphId(id); } _createBuiltInEncoding() { const { charset, encoding } = this.cff; if (!charset || !encoding) { return; } const charsets = charset.charset, encodings = encoding.encoding; const map = []; for (const charCode in encodings) { const glyphId = encodings[charCode]; if (glyphId >= 0) { const glyphName = charsets[glyphId]; if (glyphName) { map[charCode] = glyphName; } } } if (map.length > 0) { this.properties.builtInEncoding = map; } } } ;// CONCATENATED MODULE: ./src/core/font_renderer.js function getUint32(data, offset) { return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0; } function getUint16(data, offset) { return data[offset] << 8 | data[offset + 1]; } function getInt16(data, offset) { return (data[offset] << 24 | data[offset + 1] << 16) >> 16; } function getInt8(data, offset) { return data[offset] << 24 >> 24; } function getFloat214(data, offset) { return getInt16(data, offset) / 16384; } function getSubroutineBias(subrs) { const numSubrs = subrs.length; let bias = 32768; if (numSubrs < 1240) { bias = 107; } else if (numSubrs < 33900) { bias = 1131; } return bias; } function parseCmap(data, start, end) { const offset = getUint16(data, start + 2) === 1 ? getUint32(data, start + 8) : getUint32(data, start + 16); const format = getUint16(data, start + offset); let ranges, p, i; if (format === 4) { getUint16(data, start + offset + 2); const segCount = getUint16(data, start + offset + 6) >> 1; p = start + offset + 14; ranges = []; for (i = 0; i < segCount; i++, p += 2) { ranges[i] = { end: getUint16(data, p) }; } p += 2; for (i = 0; i < segCount; i++, p += 2) { ranges[i].start = getUint16(data, p); } for (i = 0; i < segCount; i++, p += 2) { ranges[i].idDelta = getUint16(data, p); } for (i = 0; i < segCount; i++, p += 2) { let idOffset = getUint16(data, p); if (idOffset === 0) { continue; } ranges[i].ids = []; for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) { ranges[i].ids[j] = getUint16(data, p + idOffset); idOffset += 2; } } return ranges; } else if (format === 12) { const groups = getUint32(data, start + offset + 12); p = start + offset + 16; ranges = []; for (i = 0; i < groups; i++) { start = getUint32(data, p); ranges.push({ start, end: getUint32(data, p + 4), idDelta: getUint32(data, p + 8) - start }); p += 12; } return ranges; } throw new FormatError(`unsupported cmap: ${format}`); } function parseCff(data, start, end, seacAnalysisEnabled) { const properties = {}; const parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled); const cff = parser.parse(); return { glyphs: cff.charStrings.objects, subrs: cff.topDict.privateDict?.subrsIndex?.objects, gsubrs: cff.globalSubrIndex?.objects, isCFFCIDFont: cff.isCIDFont, fdSelect: cff.fdSelect, fdArray: cff.fdArray }; } function parseGlyfTable(glyf, loca, isGlyphLocationsLong) { let itemSize, itemDecode; if (isGlyphLocationsLong) { itemSize = 4; itemDecode = getUint32; } else { itemSize = 2; itemDecode = (data, offset) => 2 * getUint16(data, offset); } const glyphs = []; let startOffset = itemDecode(loca, 0); for (let j = itemSize; j < loca.length; j += itemSize) { const endOffset = itemDecode(loca, j); glyphs.push(glyf.subarray(startOffset, endOffset)); startOffset = endOffset; } return glyphs; } function lookupCmap(ranges, unicode) { const code = unicode.codePointAt(0); let gid = 0, l = 0, r = ranges.length - 1; while (l < r) { const c = l + r + 1 >> 1; if (code < ranges[c].start) { r = c - 1; } else { l = c; } } if (ranges[l].start <= code && code <= ranges[l].end) { gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xffff; } return { charCode: code, glyphId: gid }; } function compileGlyf(code, cmds, font) { function moveTo(x, y) { cmds.push({ cmd: "moveTo", args: [x, y] }); } function lineTo(x, y) { cmds.push({ cmd: "lineTo", args: [x, y] }); } function quadraticCurveTo(xa, ya, x, y) { cmds.push({ cmd: "quadraticCurveTo", args: [xa, ya, x, y] }); } let i = 0; const numberOfContours = getInt16(code, i); let flags; let x = 0, y = 0; i += 10; if (numberOfContours < 0) { do { flags = getUint16(code, i); const glyphIndex = getUint16(code, i + 2); i += 4; let arg1, arg2; if (flags & 0x01) { if (flags & 0x02) { arg1 = getInt16(code, i); arg2 = getInt16(code, i + 2); } else { arg1 = getUint16(code, i); arg2 = getUint16(code, i + 2); } i += 4; } else if (flags & 0x02) { arg1 = getInt8(code, i++); arg2 = getInt8(code, i++); } else { arg1 = code[i++]; arg2 = code[i++]; } if (flags & 0x02) { x = arg1; y = arg2; } else { x = 0; y = 0; } let scaleX = 1, scaleY = 1, scale01 = 0, scale10 = 0; if (flags & 0x08) { scaleX = scaleY = getFloat214(code, i); i += 2; } else if (flags & 0x40) { scaleX = getFloat214(code, i); scaleY = getFloat214(code, i + 2); i += 4; } else if (flags & 0x80) { scaleX = getFloat214(code, i); scale01 = getFloat214(code, i + 2); scale10 = getFloat214(code, i + 4); scaleY = getFloat214(code, i + 6); i += 8; } const subglyph = font.glyphs[glyphIndex]; if (subglyph) { cmds.push({ cmd: "save" }, { cmd: "transform", args: [scaleX, scale01, scale10, scaleY, x, y] }); if (!(flags & 0x02)) {} compileGlyf(subglyph, cmds, font); cmds.push({ cmd: "restore" }); } } while (flags & 0x20); } else { const endPtsOfContours = []; let j, jj; for (j = 0; j < numberOfContours; j++) { endPtsOfContours.push(getUint16(code, i)); i += 2; } const instructionLength = getUint16(code, i); i += 2 + instructionLength; const numberOfPoints = endPtsOfContours.at(-1) + 1; const points = []; while (points.length < numberOfPoints) { flags = code[i++]; let repeat = 1; if (flags & 0x08) { repeat += code[i++]; } while (repeat-- > 0) { points.push({ flags }); } } for (j = 0; j < numberOfPoints; j++) { switch (points[j].flags & 0x12) { case 0x00: x += getInt16(code, i); i += 2; break; case 0x02: x -= code[i++]; break; case 0x12: x += code[i++]; break; } points[j].x = x; } for (j = 0; j < numberOfPoints; j++) { switch (points[j].flags & 0x24) { case 0x00: y += getInt16(code, i); i += 2; break; case 0x04: y -= code[i++]; break; case 0x24: y += code[i++]; break; } points[j].y = y; } let startPoint = 0; for (i = 0; i < numberOfContours; i++) { const endPoint = endPtsOfContours[i]; const contour = points.slice(startPoint, endPoint + 1); if (contour[0].flags & 1) { contour.push(contour[0]); } else if (contour.at(-1).flags & 1) { contour.unshift(contour.at(-1)); } else { const p = { flags: 1, x: (contour[0].x + contour.at(-1).x) / 2, y: (contour[0].y + contour.at(-1).y) / 2 }; contour.unshift(p); contour.push(p); } moveTo(contour[0].x, contour[0].y); for (j = 1, jj = contour.length; j < jj; j++) { if (contour[j].flags & 1) { lineTo(contour[j].x, contour[j].y); } else if (contour[j + 1].flags & 1) { quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y); j++; } else { quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2); } } startPoint = endPoint + 1; } } } function compileCharString(charStringCode, cmds, font, glyphId) { function moveTo(x, y) { cmds.push({ cmd: "moveTo", args: [x, y] }); } function lineTo(x, y) { cmds.push({ cmd: "lineTo", args: [x, y] }); } function bezierCurveTo(x1, y1, x2, y2, x, y) { cmds.push({ cmd: "bezierCurveTo", args: [x1, y1, x2, y2, x, y] }); } const stack = []; let x = 0, y = 0; let stems = 0; function parse(code) { let i = 0; while (i < code.length) { let stackClean = false; let v = code[i++]; let xa, xb, ya, yb, y1, y2, y3, n, subrCode; switch (v) { case 1: stems += stack.length >> 1; stackClean = true; break; case 3: stems += stack.length >> 1; stackClean = true; break; case 4: y += stack.pop(); moveTo(x, y); stackClean = true; break; case 5: while (stack.length > 0) { x += stack.shift(); y += stack.shift(); lineTo(x, y); } break; case 6: while (stack.length > 0) { x += stack.shift(); lineTo(x, y); if (stack.length === 0) { break; } y += stack.shift(); lineTo(x, y); } break; case 7: while (stack.length > 0) { y += stack.shift(); lineTo(x, y); if (stack.length === 0) { break; } x += stack.shift(); lineTo(x, y); } break; case 8: while (stack.length > 0) { xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } break; case 10: n = stack.pop(); subrCode = null; if (font.isCFFCIDFont) { const fdIndex = font.fdSelect.getFDIndex(glyphId); if (fdIndex >= 0 && fdIndex < font.fdArray.length) { const fontDict = font.fdArray[fdIndex]; let subrs; if (fontDict.privateDict?.subrsIndex) { subrs = fontDict.privateDict.subrsIndex.objects; } if (subrs) { n += getSubroutineBias(subrs); subrCode = subrs[n]; } } else { warn("Invalid fd index for glyph index."); } } else { subrCode = font.subrs[n + font.subrsBias]; } if (subrCode) { parse(subrCode); } break; case 11: return; case 12: v = code[i++]; switch (v) { case 34: xa = x + stack.shift(); xb = xa + stack.shift(); y1 = y + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y, xb, y1, x, y1); xa = x + stack.shift(); xb = xa + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y1, xb, y, x, y); break; case 35: xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); stack.pop(); break; case 36: xa = x + stack.shift(); y1 = y + stack.shift(); xb = xa + stack.shift(); y2 = y1 + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y1, xb, y2, x, y2); xa = x + stack.shift(); xb = xa + stack.shift(); y3 = y2 + stack.shift(); x = xb + stack.shift(); bezierCurveTo(xa, y2, xb, y3, x, y); break; case 37: const x0 = x, y0 = y; xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb; y = yb; if (Math.abs(x - x0) > Math.abs(y - y0)) { x += stack.shift(); } else { y += stack.shift(); } bezierCurveTo(xa, ya, xb, yb, x, y); break; default: throw new FormatError(`unknown operator: 12 ${v}`); } break; case 14: if (stack.length >= 4) { const achar = stack.pop(); const bchar = stack.pop(); y = stack.pop(); x = stack.pop(); cmds.push({ cmd: "save" }, { cmd: "translate", args: [x, y] }); let cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]])); compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId); cmds.push({ cmd: "restore" }); cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]])); compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId); } return; case 18: stems += stack.length >> 1; stackClean = true; break; case 19: stems += stack.length >> 1; i += stems + 7 >> 3; stackClean = true; break; case 20: stems += stack.length >> 1; i += stems + 7 >> 3; stackClean = true; break; case 21: y += stack.pop(); x += stack.pop(); moveTo(x, y); stackClean = true; break; case 22: x += stack.pop(); moveTo(x, y); stackClean = true; break; case 23: stems += stack.length >> 1; stackClean = true; break; case 24: while (stack.length > 2) { xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } x += stack.shift(); y += stack.shift(); lineTo(x, y); break; case 25: while (stack.length > 6) { x += stack.shift(); y += stack.shift(); lineTo(x, y); } xa = x + stack.shift(); ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); break; case 26: if (stack.length % 2) { x += stack.shift(); } while (stack.length > 0) { xa = x; ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb; y = yb + stack.shift(); bezierCurveTo(xa, ya, xb, yb, x, y); } break; case 27: if (stack.length % 2) { y += stack.shift(); } while (stack.length > 0) { xa = x + stack.shift(); ya = y; xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb; bezierCurveTo(xa, ya, xb, yb, x, y); } break; case 28: stack.push((code[i] << 24 | code[i + 1] << 16) >> 16); i += 2; break; case 29: n = stack.pop() + font.gsubrsBias; subrCode = font.gsubrs[n]; if (subrCode) { parse(subrCode); } break; case 30: while (stack.length > 0) { xa = x; ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); if (stack.length === 0) { break; } xa = x + stack.shift(); ya = y; xb = xa + stack.shift(); yb = ya + stack.shift(); y = yb + stack.shift(); x = xb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); } break; case 31: while (stack.length > 0) { xa = x + stack.shift(); ya = y; xb = xa + stack.shift(); yb = ya + stack.shift(); y = yb + stack.shift(); x = xb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); if (stack.length === 0) { break; } xa = x; ya = y + stack.shift(); xb = xa + stack.shift(); yb = ya + stack.shift(); x = xb + stack.shift(); y = yb + (stack.length === 1 ? stack.shift() : 0); bezierCurveTo(xa, ya, xb, yb, x, y); } break; default: if (v < 32) { throw new FormatError(`unknown operator: ${v}`); } if (v < 247) { stack.push(v - 139); } else if (v < 251) { stack.push((v - 247) * 256 + code[i++] + 108); } else if (v < 255) { stack.push(-(v - 251) * 256 - code[i++] - 108); } else { stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536); i += 4; } break; } if (stackClean) { stack.length = 0; } } } parse(charStringCode); } const NOOP = []; class CompiledFont { constructor(fontMatrix) { if (this.constructor === CompiledFont) { unreachable("Cannot initialize CompiledFont."); } this.fontMatrix = fontMatrix; this.compiledGlyphs = Object.create(null); this.compiledCharCodeToGlyphId = Object.create(null); } getPathJs(unicode) { const { charCode, glyphId } = lookupCmap(this.cmap, unicode); let fn = this.compiledGlyphs[glyphId]; if (!fn) { try { fn = this.compileGlyph(this.glyphs[glyphId], glyphId); this.compiledGlyphs[glyphId] = fn; } catch (ex) { this.compiledGlyphs[glyphId] = NOOP; if (this.compiledCharCodeToGlyphId[charCode] === undefined) { this.compiledCharCodeToGlyphId[charCode] = glyphId; } throw ex; } } if (this.compiledCharCodeToGlyphId[charCode] === undefined) { this.compiledCharCodeToGlyphId[charCode] = glyphId; } return fn; } compileGlyph(code, glyphId) { if (!code || code.length === 0 || code[0] === 14) { return NOOP; } let fontMatrix = this.fontMatrix; if (this.isCFFCIDFont) { const fdIndex = this.fdSelect.getFDIndex(glyphId); if (fdIndex >= 0 && fdIndex < this.fdArray.length) { const fontDict = this.fdArray[fdIndex]; fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX; } else { warn("Invalid fd index for glyph index."); } } const cmds = [{ cmd: "save" }, { cmd: "transform", args: fontMatrix.slice() }, { cmd: "scale", args: ["size", "-size"] }]; this.compileGlyphImpl(code, cmds, glyphId); cmds.push({ cmd: "restore" }); return cmds; } compileGlyphImpl() { unreachable("Children classes should implement this."); } hasBuiltPath(unicode) { const { charCode, glyphId } = lookupCmap(this.cmap, unicode); return this.compiledGlyphs[glyphId] !== undefined && this.compiledCharCodeToGlyphId[charCode] !== undefined; } } class TrueTypeCompiled extends CompiledFont { constructor(glyphs, cmap, fontMatrix) { super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]); this.glyphs = glyphs; this.cmap = cmap; } compileGlyphImpl(code, cmds) { compileGlyf(code, cmds, this); } } class Type2Compiled extends CompiledFont { constructor(cffInfo, cmap, fontMatrix, glyphNameMap) { super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]); this.glyphs = cffInfo.glyphs; this.gsubrs = cffInfo.gsubrs || []; this.subrs = cffInfo.subrs || []; this.cmap = cmap; this.glyphNameMap = glyphNameMap || getGlyphsUnicode(); this.gsubrsBias = getSubroutineBias(this.gsubrs); this.subrsBias = getSubroutineBias(this.subrs); this.isCFFCIDFont = cffInfo.isCFFCIDFont; this.fdSelect = cffInfo.fdSelect; this.fdArray = cffInfo.fdArray; } compileGlyphImpl(code, cmds, glyphId) { compileCharString(code, cmds, this, glyphId); } } class FontRendererFactory { static create(font, seacAnalysisEnabled) { const data = new Uint8Array(font.data); let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm; const numTables = getUint16(data, 4); for (let i = 0, p = 12; i < numTables; i++, p += 16) { const tag = bytesToString(data.subarray(p, p + 4)); const offset = getUint32(data, p + 8); const length = getUint32(data, p + 12); switch (tag) { case "cmap": cmap = parseCmap(data, offset, offset + length); break; case "glyf": glyf = data.subarray(offset, offset + length); break; case "loca": loca = data.subarray(offset, offset + length); break; case "head": unitsPerEm = getUint16(data, offset + 18); indexToLocFormat = getUint16(data, offset + 50); break; case "CFF ": cff = parseCff(data, offset, offset + length, seacAnalysisEnabled); break; } } if (glyf) { const fontMatrix = !unitsPerEm ? font.fontMatrix : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0]; return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix); } return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap); } } ;// CONCATENATED MODULE: ./src/core/metrics.js const getMetrics = getLookupTableFactory(function (t) { t.Courier = 600; t["Courier-Bold"] = 600; t["Courier-BoldOblique"] = 600; t["Courier-Oblique"] = 600; t.Helvetica = getLookupTableFactory(function (t) { t.space = 278; t.exclam = 278; t.quotedbl = 355; t.numbersign = 556; t.dollar = 556; t.percent = 889; t.ampersand = 667; t.quoteright = 222; t.parenleft = 333; t.parenright = 333; t.asterisk = 389; t.plus = 584; t.comma = 278; t.hyphen = 333; t.period = 278; t.slash = 278; t.zero = 556; t.one = 556; t.two = 556; t.three = 556; t.four = 556; t.five = 556; t.six = 556; t.seven = 556; t.eight = 556; t.nine = 556; t.colon = 278; t.semicolon = 278; t.less = 584; t.equal = 584; t.greater = 584; t.question = 556; t.at = 1015; t.A = 667; t.B = 667; t.C = 722; t.D = 722; t.E = 667; t.F = 611; t.G = 778; t.H = 722; t.I = 278; t.J = 500; t.K = 667; t.L = 556; t.M = 833; t.N = 722; t.O = 778; t.P = 667; t.Q = 778; t.R = 722; t.S = 667; t.T = 611; t.U = 722; t.V = 667; t.W = 944; t.X = 667; t.Y = 667; t.Z = 611; t.bracketleft = 278; t.backslash = 278; t.bracketright = 278; t.asciicircum = 469; t.underscore = 556; t.quoteleft = 222; t.a = 556; t.b = 556; t.c = 500; t.d = 556; t.e = 556; t.f = 278; t.g = 556; t.h = 556; t.i = 222; t.j = 222; t.k = 500; t.l = 222; t.m = 833; t.n = 556; t.o = 556; t.p = 556; t.q = 556; t.r = 333; t.s = 500; t.t = 278; t.u = 556; t.v = 500; t.w = 722; t.x = 500; t.y = 500; t.z = 500; t.braceleft = 334; t.bar = 260; t.braceright = 334; t.asciitilde = 584; t.exclamdown = 333; t.cent = 556; t.sterling = 556; t.fraction = 167; t.yen = 556; t.florin = 556; t.section = 556; t.currency = 556; t.quotesingle = 191; t.quotedblleft = 333; t.guillemotleft = 556; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 500; t.fl = 500; t.endash = 556; t.dagger = 556; t.daggerdbl = 556; t.periodcentered = 278; t.paragraph = 537; t.bullet = 350; t.quotesinglbase = 222; t.quotedblbase = 333; t.quotedblright = 333; t.guillemotright = 556; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 611; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 1000; t.ordfeminine = 370; t.Lslash = 556; t.Oslash = 778; t.OE = 1000; t.ordmasculine = 365; t.ae = 889; t.dotlessi = 278; t.lslash = 222; t.oslash = 611; t.oe = 944; t.germandbls = 611; t.Idieresis = 278; t.eacute = 556; t.abreve = 556; t.uhungarumlaut = 556; t.ecaron = 556; t.Ydieresis = 667; t.divide = 584; t.Yacute = 667; t.Acircumflex = 667; t.aacute = 556; t.Ucircumflex = 722; t.yacute = 500; t.scommaaccent = 500; t.ecircumflex = 556; t.Uring = 722; t.Udieresis = 722; t.aogonek = 556; t.Uacute = 722; t.uogonek = 556; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 737; t.Emacron = 667; t.ccaron = 500; t.aring = 556; t.Ncommaaccent = 722; t.lacute = 222; t.agrave = 556; t.Tcommaaccent = 611; t.Cacute = 722; t.atilde = 556; t.Edotaccent = 667; t.scaron = 500; t.scedilla = 500; t.iacute = 278; t.lozenge = 471; t.Rcaron = 722; t.Gcommaaccent = 778; t.ucircumflex = 556; t.acircumflex = 556; t.Amacron = 667; t.rcaron = 333; t.ccedilla = 500; t.Zdotaccent = 611; t.Thorn = 667; t.Omacron = 778; t.Racute = 722; t.Sacute = 667; t.dcaron = 643; t.Umacron = 722; t.uring = 556; t.threesuperior = 333; t.Ograve = 778; t.Agrave = 667; t.Abreve = 667; t.multiply = 584; t.uacute = 556; t.Tcaron = 611; t.partialdiff = 476; t.ydieresis = 500; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 556; t.edieresis = 556; t.cacute = 500; t.nacute = 556; t.umacron = 556; t.Ncaron = 722; t.Iacute = 278; t.plusminus = 584; t.brokenbar = 260; t.registered = 737; t.Gbreve = 778; t.Idotaccent = 278; t.summation = 600; t.Egrave = 667; t.racute = 333; t.omacron = 556; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 722; t.lcommaaccent = 222; t.tcaron = 317; t.eogonek = 556; t.Uogonek = 722; t.Aacute = 667; t.Adieresis = 667; t.egrave = 556; t.zacute = 500; t.iogonek = 222; t.Oacute = 778; t.oacute = 556; t.amacron = 556; t.sacute = 500; t.idieresis = 278; t.Ocircumflex = 778; t.Ugrave = 722; t.Delta = 612; t.thorn = 556; t.twosuperior = 333; t.Odieresis = 778; t.mu = 556; t.igrave = 278; t.ohungarumlaut = 556; t.Eogonek = 667; t.dcroat = 556; t.threequarters = 834; t.Scedilla = 667; t.lcaron = 299; t.Kcommaaccent = 667; t.Lacute = 556; t.trademark = 1000; t.edotaccent = 556; t.Igrave = 278; t.Imacron = 278; t.Lcaron = 556; t.onehalf = 834; t.lessequal = 549; t.ocircumflex = 556; t.ntilde = 556; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 556; t.gbreve = 556; t.onequarter = 834; t.Scaron = 667; t.Scommaaccent = 667; t.Ohungarumlaut = 778; t.degree = 400; t.ograve = 556; t.Ccaron = 722; t.ugrave = 556; t.radical = 453; t.Dcaron = 722; t.rcommaaccent = 333; t.Ntilde = 722; t.otilde = 556; t.Rcommaaccent = 722; t.Lcommaaccent = 556; t.Atilde = 667; t.Aogonek = 667; t.Aring = 667; t.Otilde = 778; t.zdotaccent = 500; t.Ecaron = 667; t.Iogonek = 278; t.kcommaaccent = 500; t.minus = 584; t.Icircumflex = 278; t.ncaron = 556; t.tcommaaccent = 278; t.logicalnot = 584; t.odieresis = 556; t.udieresis = 556; t.notequal = 549; t.gcommaaccent = 556; t.eth = 556; t.zcaron = 500; t.ncommaaccent = 556; t.onesuperior = 333; t.imacron = 278; t.Euro = 556; }); t["Helvetica-Bold"] = getLookupTableFactory(function (t) { t.space = 278; t.exclam = 333; t.quotedbl = 474; t.numbersign = 556; t.dollar = 556; t.percent = 889; t.ampersand = 722; t.quoteright = 278; t.parenleft = 333; t.parenright = 333; t.asterisk = 389; t.plus = 584; t.comma = 278; t.hyphen = 333; t.period = 278; t.slash = 278; t.zero = 556; t.one = 556; t.two = 556; t.three = 556; t.four = 556; t.five = 556; t.six = 556; t.seven = 556; t.eight = 556; t.nine = 556; t.colon = 333; t.semicolon = 333; t.less = 584; t.equal = 584; t.greater = 584; t.question = 611; t.at = 975; t.A = 722; t.B = 722; t.C = 722; t.D = 722; t.E = 667; t.F = 611; t.G = 778; t.H = 722; t.I = 278; t.J = 556; t.K = 722; t.L = 611; t.M = 833; t.N = 722; t.O = 778; t.P = 667; t.Q = 778; t.R = 722; t.S = 667; t.T = 611; t.U = 722; t.V = 667; t.W = 944; t.X = 667; t.Y = 667; t.Z = 611; t.bracketleft = 333; t.backslash = 278; t.bracketright = 333; t.asciicircum = 584; t.underscore = 556; t.quoteleft = 278; t.a = 556; t.b = 611; t.c = 556; t.d = 611; t.e = 556; t.f = 333; t.g = 611; t.h = 611; t.i = 278; t.j = 278; t.k = 556; t.l = 278; t.m = 889; t.n = 611; t.o = 611; t.p = 611; t.q = 611; t.r = 389; t.s = 556; t.t = 333; t.u = 611; t.v = 556; t.w = 778; t.x = 556; t.y = 556; t.z = 500; t.braceleft = 389; t.bar = 280; t.braceright = 389; t.asciitilde = 584; t.exclamdown = 333; t.cent = 556; t.sterling = 556; t.fraction = 167; t.yen = 556; t.florin = 556; t.section = 556; t.currency = 556; t.quotesingle = 238; t.quotedblleft = 500; t.guillemotleft = 556; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 611; t.fl = 611; t.endash = 556; t.dagger = 556; t.daggerdbl = 556; t.periodcentered = 278; t.paragraph = 556; t.bullet = 350; t.quotesinglbase = 278; t.quotedblbase = 500; t.quotedblright = 500; t.guillemotright = 556; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 611; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 1000; t.ordfeminine = 370; t.Lslash = 611; t.Oslash = 778; t.OE = 1000; t.ordmasculine = 365; t.ae = 889; t.dotlessi = 278; t.lslash = 278; t.oslash = 611; t.oe = 944; t.germandbls = 611; t.Idieresis = 278; t.eacute = 556; t.abreve = 556; t.uhungarumlaut = 611; t.ecaron = 556; t.Ydieresis = 667; t.divide = 584; t.Yacute = 667; t.Acircumflex = 722; t.aacute = 556; t.Ucircumflex = 722; t.yacute = 556; t.scommaaccent = 556; t.ecircumflex = 556; t.Uring = 722; t.Udieresis = 722; t.aogonek = 556; t.Uacute = 722; t.uogonek = 611; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 737; t.Emacron = 667; t.ccaron = 556; t.aring = 556; t.Ncommaaccent = 722; t.lacute = 278; t.agrave = 556; t.Tcommaaccent = 611; t.Cacute = 722; t.atilde = 556; t.Edotaccent = 667; t.scaron = 556; t.scedilla = 556; t.iacute = 278; t.lozenge = 494; t.Rcaron = 722; t.Gcommaaccent = 778; t.ucircumflex = 611; t.acircumflex = 556; t.Amacron = 722; t.rcaron = 389; t.ccedilla = 556; t.Zdotaccent = 611; t.Thorn = 667; t.Omacron = 778; t.Racute = 722; t.Sacute = 667; t.dcaron = 743; t.Umacron = 722; t.uring = 611; t.threesuperior = 333; t.Ograve = 778; t.Agrave = 722; t.Abreve = 722; t.multiply = 584; t.uacute = 611; t.Tcaron = 611; t.partialdiff = 494; t.ydieresis = 556; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 556; t.edieresis = 556; t.cacute = 556; t.nacute = 611; t.umacron = 611; t.Ncaron = 722; t.Iacute = 278; t.plusminus = 584; t.brokenbar = 280; t.registered = 737; t.Gbreve = 778; t.Idotaccent = 278; t.summation = 600; t.Egrave = 667; t.racute = 389; t.omacron = 611; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 722; t.lcommaaccent = 278; t.tcaron = 389; t.eogonek = 556; t.Uogonek = 722; t.Aacute = 722; t.Adieresis = 722; t.egrave = 556; t.zacute = 500; t.iogonek = 278; t.Oacute = 778; t.oacute = 611; t.amacron = 556; t.sacute = 556; t.idieresis = 278; t.Ocircumflex = 778; t.Ugrave = 722; t.Delta = 612; t.thorn = 611; t.twosuperior = 333; t.Odieresis = 778; t.mu = 611; t.igrave = 278; t.ohungarumlaut = 611; t.Eogonek = 667; t.dcroat = 611; t.threequarters = 834; t.Scedilla = 667; t.lcaron = 400; t.Kcommaaccent = 722; t.Lacute = 611; t.trademark = 1000; t.edotaccent = 556; t.Igrave = 278; t.Imacron = 278; t.Lcaron = 611; t.onehalf = 834; t.lessequal = 549; t.ocircumflex = 611; t.ntilde = 611; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 556; t.gbreve = 611; t.onequarter = 834; t.Scaron = 667; t.Scommaaccent = 667; t.Ohungarumlaut = 778; t.degree = 400; t.ograve = 611; t.Ccaron = 722; t.ugrave = 611; t.radical = 549; t.Dcaron = 722; t.rcommaaccent = 389; t.Ntilde = 722; t.otilde = 611; t.Rcommaaccent = 722; t.Lcommaaccent = 611; t.Atilde = 722; t.Aogonek = 722; t.Aring = 722; t.Otilde = 778; t.zdotaccent = 500; t.Ecaron = 667; t.Iogonek = 278; t.kcommaaccent = 556; t.minus = 584; t.Icircumflex = 278; t.ncaron = 611; t.tcommaaccent = 333; t.logicalnot = 584; t.odieresis = 611; t.udieresis = 611; t.notequal = 549; t.gcommaaccent = 611; t.eth = 611; t.zcaron = 500; t.ncommaaccent = 611; t.onesuperior = 333; t.imacron = 278; t.Euro = 556; }); t["Helvetica-BoldOblique"] = getLookupTableFactory(function (t) { t.space = 278; t.exclam = 333; t.quotedbl = 474; t.numbersign = 556; t.dollar = 556; t.percent = 889; t.ampersand = 722; t.quoteright = 278; t.parenleft = 333; t.parenright = 333; t.asterisk = 389; t.plus = 584; t.comma = 278; t.hyphen = 333; t.period = 278; t.slash = 278; t.zero = 556; t.one = 556; t.two = 556; t.three = 556; t.four = 556; t.five = 556; t.six = 556; t.seven = 556; t.eight = 556; t.nine = 556; t.colon = 333; t.semicolon = 333; t.less = 584; t.equal = 584; t.greater = 584; t.question = 611; t.at = 975; t.A = 722; t.B = 722; t.C = 722; t.D = 722; t.E = 667; t.F = 611; t.G = 778; t.H = 722; t.I = 278; t.J = 556; t.K = 722; t.L = 611; t.M = 833; t.N = 722; t.O = 778; t.P = 667; t.Q = 778; t.R = 722; t.S = 667; t.T = 611; t.U = 722; t.V = 667; t.W = 944; t.X = 667; t.Y = 667; t.Z = 611; t.bracketleft = 333; t.backslash = 278; t.bracketright = 333; t.asciicircum = 584; t.underscore = 556; t.quoteleft = 278; t.a = 556; t.b = 611; t.c = 556; t.d = 611; t.e = 556; t.f = 333; t.g = 611; t.h = 611; t.i = 278; t.j = 278; t.k = 556; t.l = 278; t.m = 889; t.n = 611; t.o = 611; t.p = 611; t.q = 611; t.r = 389; t.s = 556; t.t = 333; t.u = 611; t.v = 556; t.w = 778; t.x = 556; t.y = 556; t.z = 500; t.braceleft = 389; t.bar = 280; t.braceright = 389; t.asciitilde = 584; t.exclamdown = 333; t.cent = 556; t.sterling = 556; t.fraction = 167; t.yen = 556; t.florin = 556; t.section = 556; t.currency = 556; t.quotesingle = 238; t.quotedblleft = 500; t.guillemotleft = 556; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 611; t.fl = 611; t.endash = 556; t.dagger = 556; t.daggerdbl = 556; t.periodcentered = 278; t.paragraph = 556; t.bullet = 350; t.quotesinglbase = 278; t.quotedblbase = 500; t.quotedblright = 500; t.guillemotright = 556; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 611; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 1000; t.ordfeminine = 370; t.Lslash = 611; t.Oslash = 778; t.OE = 1000; t.ordmasculine = 365; t.ae = 889; t.dotlessi = 278; t.lslash = 278; t.oslash = 611; t.oe = 944; t.germandbls = 611; t.Idieresis = 278; t.eacute = 556; t.abreve = 556; t.uhungarumlaut = 611; t.ecaron = 556; t.Ydieresis = 667; t.divide = 584; t.Yacute = 667; t.Acircumflex = 722; t.aacute = 556; t.Ucircumflex = 722; t.yacute = 556; t.scommaaccent = 556; t.ecircumflex = 556; t.Uring = 722; t.Udieresis = 722; t.aogonek = 556; t.Uacute = 722; t.uogonek = 611; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 737; t.Emacron = 667; t.ccaron = 556; t.aring = 556; t.Ncommaaccent = 722; t.lacute = 278; t.agrave = 556; t.Tcommaaccent = 611; t.Cacute = 722; t.atilde = 556; t.Edotaccent = 667; t.scaron = 556; t.scedilla = 556; t.iacute = 278; t.lozenge = 494; t.Rcaron = 722; t.Gcommaaccent = 778; t.ucircumflex = 611; t.acircumflex = 556; t.Amacron = 722; t.rcaron = 389; t.ccedilla = 556; t.Zdotaccent = 611; t.Thorn = 667; t.Omacron = 778; t.Racute = 722; t.Sacute = 667; t.dcaron = 743; t.Umacron = 722; t.uring = 611; t.threesuperior = 333; t.Ograve = 778; t.Agrave = 722; t.Abreve = 722; t.multiply = 584; t.uacute = 611; t.Tcaron = 611; t.partialdiff = 494; t.ydieresis = 556; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 556; t.edieresis = 556; t.cacute = 556; t.nacute = 611; t.umacron = 611; t.Ncaron = 722; t.Iacute = 278; t.plusminus = 584; t.brokenbar = 280; t.registered = 737; t.Gbreve = 778; t.Idotaccent = 278; t.summation = 600; t.Egrave = 667; t.racute = 389; t.omacron = 611; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 722; t.lcommaaccent = 278; t.tcaron = 389; t.eogonek = 556; t.Uogonek = 722; t.Aacute = 722; t.Adieresis = 722; t.egrave = 556; t.zacute = 500; t.iogonek = 278; t.Oacute = 778; t.oacute = 611; t.amacron = 556; t.sacute = 556; t.idieresis = 278; t.Ocircumflex = 778; t.Ugrave = 722; t.Delta = 612; t.thorn = 611; t.twosuperior = 333; t.Odieresis = 778; t.mu = 611; t.igrave = 278; t.ohungarumlaut = 611; t.Eogonek = 667; t.dcroat = 611; t.threequarters = 834; t.Scedilla = 667; t.lcaron = 400; t.Kcommaaccent = 722; t.Lacute = 611; t.trademark = 1000; t.edotaccent = 556; t.Igrave = 278; t.Imacron = 278; t.Lcaron = 611; t.onehalf = 834; t.lessequal = 549; t.ocircumflex = 611; t.ntilde = 611; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 556; t.gbreve = 611; t.onequarter = 834; t.Scaron = 667; t.Scommaaccent = 667; t.Ohungarumlaut = 778; t.degree = 400; t.ograve = 611; t.Ccaron = 722; t.ugrave = 611; t.radical = 549; t.Dcaron = 722; t.rcommaaccent = 389; t.Ntilde = 722; t.otilde = 611; t.Rcommaaccent = 722; t.Lcommaaccent = 611; t.Atilde = 722; t.Aogonek = 722; t.Aring = 722; t.Otilde = 778; t.zdotaccent = 500; t.Ecaron = 667; t.Iogonek = 278; t.kcommaaccent = 556; t.minus = 584; t.Icircumflex = 278; t.ncaron = 611; t.tcommaaccent = 333; t.logicalnot = 584; t.odieresis = 611; t.udieresis = 611; t.notequal = 549; t.gcommaaccent = 611; t.eth = 611; t.zcaron = 500; t.ncommaaccent = 611; t.onesuperior = 333; t.imacron = 278; t.Euro = 556; }); t["Helvetica-Oblique"] = getLookupTableFactory(function (t) { t.space = 278; t.exclam = 278; t.quotedbl = 355; t.numbersign = 556; t.dollar = 556; t.percent = 889; t.ampersand = 667; t.quoteright = 222; t.parenleft = 333; t.parenright = 333; t.asterisk = 389; t.plus = 584; t.comma = 278; t.hyphen = 333; t.period = 278; t.slash = 278; t.zero = 556; t.one = 556; t.two = 556; t.three = 556; t.four = 556; t.five = 556; t.six = 556; t.seven = 556; t.eight = 556; t.nine = 556; t.colon = 278; t.semicolon = 278; t.less = 584; t.equal = 584; t.greater = 584; t.question = 556; t.at = 1015; t.A = 667; t.B = 667; t.C = 722; t.D = 722; t.E = 667; t.F = 611; t.G = 778; t.H = 722; t.I = 278; t.J = 500; t.K = 667; t.L = 556; t.M = 833; t.N = 722; t.O = 778; t.P = 667; t.Q = 778; t.R = 722; t.S = 667; t.T = 611; t.U = 722; t.V = 667; t.W = 944; t.X = 667; t.Y = 667; t.Z = 611; t.bracketleft = 278; t.backslash = 278; t.bracketright = 278; t.asciicircum = 469; t.underscore = 556; t.quoteleft = 222; t.a = 556; t.b = 556; t.c = 500; t.d = 556; t.e = 556; t.f = 278; t.g = 556; t.h = 556; t.i = 222; t.j = 222; t.k = 500; t.l = 222; t.m = 833; t.n = 556; t.o = 556; t.p = 556; t.q = 556; t.r = 333; t.s = 500; t.t = 278; t.u = 556; t.v = 500; t.w = 722; t.x = 500; t.y = 500; t.z = 500; t.braceleft = 334; t.bar = 260; t.braceright = 334; t.asciitilde = 584; t.exclamdown = 333; t.cent = 556; t.sterling = 556; t.fraction = 167; t.yen = 556; t.florin = 556; t.section = 556; t.currency = 556; t.quotesingle = 191; t.quotedblleft = 333; t.guillemotleft = 556; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 500; t.fl = 500; t.endash = 556; t.dagger = 556; t.daggerdbl = 556; t.periodcentered = 278; t.paragraph = 537; t.bullet = 350; t.quotesinglbase = 222; t.quotedblbase = 333; t.quotedblright = 333; t.guillemotright = 556; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 611; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 1000; t.ordfeminine = 370; t.Lslash = 556; t.Oslash = 778; t.OE = 1000; t.ordmasculine = 365; t.ae = 889; t.dotlessi = 278; t.lslash = 222; t.oslash = 611; t.oe = 944; t.germandbls = 611; t.Idieresis = 278; t.eacute = 556; t.abreve = 556; t.uhungarumlaut = 556; t.ecaron = 556; t.Ydieresis = 667; t.divide = 584; t.Yacute = 667; t.Acircumflex = 667; t.aacute = 556; t.Ucircumflex = 722; t.yacute = 500; t.scommaaccent = 500; t.ecircumflex = 556; t.Uring = 722; t.Udieresis = 722; t.aogonek = 556; t.Uacute = 722; t.uogonek = 556; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 737; t.Emacron = 667; t.ccaron = 500; t.aring = 556; t.Ncommaaccent = 722; t.lacute = 222; t.agrave = 556; t.Tcommaaccent = 611; t.Cacute = 722; t.atilde = 556; t.Edotaccent = 667; t.scaron = 500; t.scedilla = 500; t.iacute = 278; t.lozenge = 471; t.Rcaron = 722; t.Gcommaaccent = 778; t.ucircumflex = 556; t.acircumflex = 556; t.Amacron = 667; t.rcaron = 333; t.ccedilla = 500; t.Zdotaccent = 611; t.Thorn = 667; t.Omacron = 778; t.Racute = 722; t.Sacute = 667; t.dcaron = 643; t.Umacron = 722; t.uring = 556; t.threesuperior = 333; t.Ograve = 778; t.Agrave = 667; t.Abreve = 667; t.multiply = 584; t.uacute = 556; t.Tcaron = 611; t.partialdiff = 476; t.ydieresis = 500; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 556; t.edieresis = 556; t.cacute = 500; t.nacute = 556; t.umacron = 556; t.Ncaron = 722; t.Iacute = 278; t.plusminus = 584; t.brokenbar = 260; t.registered = 737; t.Gbreve = 778; t.Idotaccent = 278; t.summation = 600; t.Egrave = 667; t.racute = 333; t.omacron = 556; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 722; t.lcommaaccent = 222; t.tcaron = 317; t.eogonek = 556; t.Uogonek = 722; t.Aacute = 667; t.Adieresis = 667; t.egrave = 556; t.zacute = 500; t.iogonek = 222; t.Oacute = 778; t.oacute = 556; t.amacron = 556; t.sacute = 500; t.idieresis = 278; t.Ocircumflex = 778; t.Ugrave = 722; t.Delta = 612; t.thorn = 556; t.twosuperior = 333; t.Odieresis = 778; t.mu = 556; t.igrave = 278; t.ohungarumlaut = 556; t.Eogonek = 667; t.dcroat = 556; t.threequarters = 834; t.Scedilla = 667; t.lcaron = 299; t.Kcommaaccent = 667; t.Lacute = 556; t.trademark = 1000; t.edotaccent = 556; t.Igrave = 278; t.Imacron = 278; t.Lcaron = 556; t.onehalf = 834; t.lessequal = 549; t.ocircumflex = 556; t.ntilde = 556; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 556; t.gbreve = 556; t.onequarter = 834; t.Scaron = 667; t.Scommaaccent = 667; t.Ohungarumlaut = 778; t.degree = 400; t.ograve = 556; t.Ccaron = 722; t.ugrave = 556; t.radical = 453; t.Dcaron = 722; t.rcommaaccent = 333; t.Ntilde = 722; t.otilde = 556; t.Rcommaaccent = 722; t.Lcommaaccent = 556; t.Atilde = 667; t.Aogonek = 667; t.Aring = 667; t.Otilde = 778; t.zdotaccent = 500; t.Ecaron = 667; t.Iogonek = 278; t.kcommaaccent = 500; t.minus = 584; t.Icircumflex = 278; t.ncaron = 556; t.tcommaaccent = 278; t.logicalnot = 584; t.odieresis = 556; t.udieresis = 556; t.notequal = 549; t.gcommaaccent = 556; t.eth = 556; t.zcaron = 500; t.ncommaaccent = 556; t.onesuperior = 333; t.imacron = 278; t.Euro = 556; }); t.Symbol = getLookupTableFactory(function (t) { t.space = 250; t.exclam = 333; t.universal = 713; t.numbersign = 500; t.existential = 549; t.percent = 833; t.ampersand = 778; t.suchthat = 439; t.parenleft = 333; t.parenright = 333; t.asteriskmath = 500; t.plus = 549; t.comma = 250; t.minus = 549; t.period = 250; t.slash = 278; t.zero = 500; t.one = 500; t.two = 500; t.three = 500; t.four = 500; t.five = 500; t.six = 500; t.seven = 500; t.eight = 500; t.nine = 500; t.colon = 278; t.semicolon = 278; t.less = 549; t.equal = 549; t.greater = 549; t.question = 444; t.congruent = 549; t.Alpha = 722; t.Beta = 667; t.Chi = 722; t.Delta = 612; t.Epsilon = 611; t.Phi = 763; t.Gamma = 603; t.Eta = 722; t.Iota = 333; t.theta1 = 631; t.Kappa = 722; t.Lambda = 686; t.Mu = 889; t.Nu = 722; t.Omicron = 722; t.Pi = 768; t.Theta = 741; t.Rho = 556; t.Sigma = 592; t.Tau = 611; t.Upsilon = 690; t.sigma1 = 439; t.Omega = 768; t.Xi = 645; t.Psi = 795; t.Zeta = 611; t.bracketleft = 333; t.therefore = 863; t.bracketright = 333; t.perpendicular = 658; t.underscore = 500; t.radicalex = 500; t.alpha = 631; t.beta = 549; t.chi = 549; t.delta = 494; t.epsilon = 439; t.phi = 521; t.gamma = 411; t.eta = 603; t.iota = 329; t.phi1 = 603; t.kappa = 549; t.lambda = 549; t.mu = 576; t.nu = 521; t.omicron = 549; t.pi = 549; t.theta = 521; t.rho = 549; t.sigma = 603; t.tau = 439; t.upsilon = 576; t.omega1 = 713; t.omega = 686; t.xi = 493; t.psi = 686; t.zeta = 494; t.braceleft = 480; t.bar = 200; t.braceright = 480; t.similar = 549; t.Euro = 750; t.Upsilon1 = 620; t.minute = 247; t.lessequal = 549; t.fraction = 167; t.infinity = 713; t.florin = 500; t.club = 753; t.diamond = 753; t.heart = 753; t.spade = 753; t.arrowboth = 1042; t.arrowleft = 987; t.arrowup = 603; t.arrowright = 987; t.arrowdown = 603; t.degree = 400; t.plusminus = 549; t.second = 411; t.greaterequal = 549; t.multiply = 549; t.proportional = 713; t.partialdiff = 494; t.bullet = 460; t.divide = 549; t.notequal = 549; t.equivalence = 549; t.approxequal = 549; t.ellipsis = 1000; t.arrowvertex = 603; t.arrowhorizex = 1000; t.carriagereturn = 658; t.aleph = 823; t.Ifraktur = 686; t.Rfraktur = 795; t.weierstrass = 987; t.circlemultiply = 768; t.circleplus = 768; t.emptyset = 823; t.intersection = 768; t.union = 768; t.propersuperset = 713; t.reflexsuperset = 713; t.notsubset = 713; t.propersubset = 713; t.reflexsubset = 713; t.element = 713; t.notelement = 713; t.angle = 768; t.gradient = 713; t.registerserif = 790; t.copyrightserif = 790; t.trademarkserif = 890; t.product = 823; t.radical = 549; t.dotmath = 250; t.logicalnot = 713; t.logicaland = 603; t.logicalor = 603; t.arrowdblboth = 1042; t.arrowdblleft = 987; t.arrowdblup = 603; t.arrowdblright = 987; t.arrowdbldown = 603; t.lozenge = 494; t.angleleft = 329; t.registersans = 790; t.copyrightsans = 790; t.trademarksans = 786; t.summation = 713; t.parenlefttp = 384; t.parenleftex = 384; t.parenleftbt = 384; t.bracketlefttp = 384; t.bracketleftex = 384; t.bracketleftbt = 384; t.bracelefttp = 494; t.braceleftmid = 494; t.braceleftbt = 494; t.braceex = 494; t.angleright = 329; t.integral = 274; t.integraltp = 686; t.integralex = 686; t.integralbt = 686; t.parenrighttp = 384; t.parenrightex = 384; t.parenrightbt = 384; t.bracketrighttp = 384; t.bracketrightex = 384; t.bracketrightbt = 384; t.bracerighttp = 494; t.bracerightmid = 494; t.bracerightbt = 494; t.apple = 790; }); t["Times-Roman"] = getLookupTableFactory(function (t) { t.space = 250; t.exclam = 333; t.quotedbl = 408; t.numbersign = 500; t.dollar = 500; t.percent = 833; t.ampersand = 778; t.quoteright = 333; t.parenleft = 333; t.parenright = 333; t.asterisk = 500; t.plus = 564; t.comma = 250; t.hyphen = 333; t.period = 250; t.slash = 278; t.zero = 500; t.one = 500; t.two = 500; t.three = 500; t.four = 500; t.five = 500; t.six = 500; t.seven = 500; t.eight = 500; t.nine = 500; t.colon = 278; t.semicolon = 278; t.less = 564; t.equal = 564; t.greater = 564; t.question = 444; t.at = 921; t.A = 722; t.B = 667; t.C = 667; t.D = 722; t.E = 611; t.F = 556; t.G = 722; t.H = 722; t.I = 333; t.J = 389; t.K = 722; t.L = 611; t.M = 889; t.N = 722; t.O = 722; t.P = 556; t.Q = 722; t.R = 667; t.S = 556; t.T = 611; t.U = 722; t.V = 722; t.W = 944; t.X = 722; t.Y = 722; t.Z = 611; t.bracketleft = 333; t.backslash = 278; t.bracketright = 333; t.asciicircum = 469; t.underscore = 500; t.quoteleft = 333; t.a = 444; t.b = 500; t.c = 444; t.d = 500; t.e = 444; t.f = 333; t.g = 500; t.h = 500; t.i = 278; t.j = 278; t.k = 500; t.l = 278; t.m = 778; t.n = 500; t.o = 500; t.p = 500; t.q = 500; t.r = 333; t.s = 389; t.t = 278; t.u = 500; t.v = 500; t.w = 722; t.x = 500; t.y = 500; t.z = 444; t.braceleft = 480; t.bar = 200; t.braceright = 480; t.asciitilde = 541; t.exclamdown = 333; t.cent = 500; t.sterling = 500; t.fraction = 167; t.yen = 500; t.florin = 500; t.section = 500; t.currency = 500; t.quotesingle = 180; t.quotedblleft = 444; t.guillemotleft = 500; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 556; t.fl = 556; t.endash = 500; t.dagger = 500; t.daggerdbl = 500; t.periodcentered = 250; t.paragraph = 453; t.bullet = 350; t.quotesinglbase = 333; t.quotedblbase = 444; t.quotedblright = 444; t.guillemotright = 500; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 444; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 889; t.ordfeminine = 276; t.Lslash = 611; t.Oslash = 722; t.OE = 889; t.ordmasculine = 310; t.ae = 667; t.dotlessi = 278; t.lslash = 278; t.oslash = 500; t.oe = 722; t.germandbls = 500; t.Idieresis = 333; t.eacute = 444; t.abreve = 444; t.uhungarumlaut = 500; t.ecaron = 444; t.Ydieresis = 722; t.divide = 564; t.Yacute = 722; t.Acircumflex = 722; t.aacute = 444; t.Ucircumflex = 722; t.yacute = 500; t.scommaaccent = 389; t.ecircumflex = 444; t.Uring = 722; t.Udieresis = 722; t.aogonek = 444; t.Uacute = 722; t.uogonek = 500; t.Edieresis = 611; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 760; t.Emacron = 611; t.ccaron = 444; t.aring = 444; t.Ncommaaccent = 722; t.lacute = 278; t.agrave = 444; t.Tcommaaccent = 611; t.Cacute = 667; t.atilde = 444; t.Edotaccent = 611; t.scaron = 389; t.scedilla = 389; t.iacute = 278; t.lozenge = 471; t.Rcaron = 667; t.Gcommaaccent = 722; t.ucircumflex = 500; t.acircumflex = 444; t.Amacron = 722; t.rcaron = 333; t.ccedilla = 444; t.Zdotaccent = 611; t.Thorn = 556; t.Omacron = 722; t.Racute = 667; t.Sacute = 556; t.dcaron = 588; t.Umacron = 722; t.uring = 500; t.threesuperior = 300; t.Ograve = 722; t.Agrave = 722; t.Abreve = 722; t.multiply = 564; t.uacute = 500; t.Tcaron = 611; t.partialdiff = 476; t.ydieresis = 500; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 611; t.adieresis = 444; t.edieresis = 444; t.cacute = 444; t.nacute = 500; t.umacron = 500; t.Ncaron = 722; t.Iacute = 333; t.plusminus = 564; t.brokenbar = 200; t.registered = 760; t.Gbreve = 722; t.Idotaccent = 333; t.summation = 600; t.Egrave = 611; t.racute = 333; t.omacron = 500; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 667; t.lcommaaccent = 278; t.tcaron = 326; t.eogonek = 444; t.Uogonek = 722; t.Aacute = 722; t.Adieresis = 722; t.egrave = 444; t.zacute = 444; t.iogonek = 278; t.Oacute = 722; t.oacute = 500; t.amacron = 444; t.sacute = 389; t.idieresis = 278; t.Ocircumflex = 722; t.Ugrave = 722; t.Delta = 612; t.thorn = 500; t.twosuperior = 300; t.Odieresis = 722; t.mu = 500; t.igrave = 278; t.ohungarumlaut = 500; t.Eogonek = 611; t.dcroat = 500; t.threequarters = 750; t.Scedilla = 556; t.lcaron = 344; t.Kcommaaccent = 722; t.Lacute = 611; t.trademark = 980; t.edotaccent = 444; t.Igrave = 333; t.Imacron = 333; t.Lcaron = 611; t.onehalf = 750; t.lessequal = 549; t.ocircumflex = 500; t.ntilde = 500; t.Uhungarumlaut = 722; t.Eacute = 611; t.emacron = 444; t.gbreve = 500; t.onequarter = 750; t.Scaron = 556; t.Scommaaccent = 556; t.Ohungarumlaut = 722; t.degree = 400; t.ograve = 500; t.Ccaron = 667; t.ugrave = 500; t.radical = 453; t.Dcaron = 722; t.rcommaaccent = 333; t.Ntilde = 722; t.otilde = 500; t.Rcommaaccent = 667; t.Lcommaaccent = 611; t.Atilde = 722; t.Aogonek = 722; t.Aring = 722; t.Otilde = 722; t.zdotaccent = 444; t.Ecaron = 611; t.Iogonek = 333; t.kcommaaccent = 500; t.minus = 564; t.Icircumflex = 333; t.ncaron = 500; t.tcommaaccent = 278; t.logicalnot = 564; t.odieresis = 500; t.udieresis = 500; t.notequal = 549; t.gcommaaccent = 500; t.eth = 500; t.zcaron = 444; t.ncommaaccent = 500; t.onesuperior = 300; t.imacron = 278; t.Euro = 500; }); t["Times-Bold"] = getLookupTableFactory(function (t) { t.space = 250; t.exclam = 333; t.quotedbl = 555; t.numbersign = 500; t.dollar = 500; t.percent = 1000; t.ampersand = 833; t.quoteright = 333; t.parenleft = 333; t.parenright = 333; t.asterisk = 500; t.plus = 570; t.comma = 250; t.hyphen = 333; t.period = 250; t.slash = 278; t.zero = 500; t.one = 500; t.two = 500; t.three = 500; t.four = 500; t.five = 500; t.six = 500; t.seven = 500; t.eight = 500; t.nine = 500; t.colon = 333; t.semicolon = 333; t.less = 570; t.equal = 570; t.greater = 570; t.question = 500; t.at = 930; t.A = 722; t.B = 667; t.C = 722; t.D = 722; t.E = 667; t.F = 611; t.G = 778; t.H = 778; t.I = 389; t.J = 500; t.K = 778; t.L = 667; t.M = 944; t.N = 722; t.O = 778; t.P = 611; t.Q = 778; t.R = 722; t.S = 556; t.T = 667; t.U = 722; t.V = 722; t.W = 1000; t.X = 722; t.Y = 722; t.Z = 667; t.bracketleft = 333; t.backslash = 278; t.bracketright = 333; t.asciicircum = 581; t.underscore = 500; t.quoteleft = 333; t.a = 500; t.b = 556; t.c = 444; t.d = 556; t.e = 444; t.f = 333; t.g = 500; t.h = 556; t.i = 278; t.j = 333; t.k = 556; t.l = 278; t.m = 833; t.n = 556; t.o = 500; t.p = 556; t.q = 556; t.r = 444; t.s = 389; t.t = 333; t.u = 556; t.v = 500; t.w = 722; t.x = 500; t.y = 500; t.z = 444; t.braceleft = 394; t.bar = 220; t.braceright = 394; t.asciitilde = 520; t.exclamdown = 333; t.cent = 500; t.sterling = 500; t.fraction = 167; t.yen = 500; t.florin = 500; t.section = 500; t.currency = 500; t.quotesingle = 278; t.quotedblleft = 500; t.guillemotleft = 500; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 556; t.fl = 556; t.endash = 500; t.dagger = 500; t.daggerdbl = 500; t.periodcentered = 250; t.paragraph = 540; t.bullet = 350; t.quotesinglbase = 333; t.quotedblbase = 500; t.quotedblright = 500; t.guillemotright = 500; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 500; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 1000; t.ordfeminine = 300; t.Lslash = 667; t.Oslash = 778; t.OE = 1000; t.ordmasculine = 330; t.ae = 722; t.dotlessi = 278; t.lslash = 278; t.oslash = 500; t.oe = 722; t.germandbls = 556; t.Idieresis = 389; t.eacute = 444; t.abreve = 500; t.uhungarumlaut = 556; t.ecaron = 444; t.Ydieresis = 722; t.divide = 570; t.Yacute = 722; t.Acircumflex = 722; t.aacute = 500; t.Ucircumflex = 722; t.yacute = 500; t.scommaaccent = 389; t.ecircumflex = 444; t.Uring = 722; t.Udieresis = 722; t.aogonek = 500; t.Uacute = 722; t.uogonek = 556; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 747; t.Emacron = 667; t.ccaron = 444; t.aring = 500; t.Ncommaaccent = 722; t.lacute = 278; t.agrave = 500; t.Tcommaaccent = 667; t.Cacute = 722; t.atilde = 500; t.Edotaccent = 667; t.scaron = 389; t.scedilla = 389; t.iacute = 278; t.lozenge = 494; t.Rcaron = 722; t.Gcommaaccent = 778; t.ucircumflex = 556; t.acircumflex = 500; t.Amacron = 722; t.rcaron = 444; t.ccedilla = 444; t.Zdotaccent = 667; t.Thorn = 611; t.Omacron = 778; t.Racute = 722; t.Sacute = 556; t.dcaron = 672; t.Umacron = 722; t.uring = 556; t.threesuperior = 300; t.Ograve = 778; t.Agrave = 722; t.Abreve = 722; t.multiply = 570; t.uacute = 556; t.Tcaron = 667; t.partialdiff = 494; t.ydieresis = 500; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 500; t.edieresis = 444; t.cacute = 444; t.nacute = 556; t.umacron = 556; t.Ncaron = 722; t.Iacute = 389; t.plusminus = 570; t.brokenbar = 220; t.registered = 747; t.Gbreve = 778; t.Idotaccent = 389; t.summation = 600; t.Egrave = 667; t.racute = 444; t.omacron = 500; t.Zacute = 667; t.Zcaron = 667; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 722; t.lcommaaccent = 278; t.tcaron = 416; t.eogonek = 444; t.Uogonek = 722; t.Aacute = 722; t.Adieresis = 722; t.egrave = 444; t.zacute = 444; t.iogonek = 278; t.Oacute = 778; t.oacute = 500; t.amacron = 500; t.sacute = 389; t.idieresis = 278; t.Ocircumflex = 778; t.Ugrave = 722; t.Delta = 612; t.thorn = 556; t.twosuperior = 300; t.Odieresis = 778; t.mu = 556; t.igrave = 278; t.ohungarumlaut = 500; t.Eogonek = 667; t.dcroat = 556; t.threequarters = 750; t.Scedilla = 556; t.lcaron = 394; t.Kcommaaccent = 778; t.Lacute = 667; t.trademark = 1000; t.edotaccent = 444; t.Igrave = 389; t.Imacron = 389; t.Lcaron = 667; t.onehalf = 750; t.lessequal = 549; t.ocircumflex = 500; t.ntilde = 556; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 444; t.gbreve = 500; t.onequarter = 750; t.Scaron = 556; t.Scommaaccent = 556; t.Ohungarumlaut = 778; t.degree = 400; t.ograve = 500; t.Ccaron = 722; t.ugrave = 556; t.radical = 549; t.Dcaron = 722; t.rcommaaccent = 444; t.Ntilde = 722; t.otilde = 500; t.Rcommaaccent = 722; t.Lcommaaccent = 667; t.Atilde = 722; t.Aogonek = 722; t.Aring = 722; t.Otilde = 778; t.zdotaccent = 444; t.Ecaron = 667; t.Iogonek = 389; t.kcommaaccent = 556; t.minus = 570; t.Icircumflex = 389; t.ncaron = 556; t.tcommaaccent = 333; t.logicalnot = 570; t.odieresis = 500; t.udieresis = 556; t.notequal = 549; t.gcommaaccent = 500; t.eth = 500; t.zcaron = 444; t.ncommaaccent = 556; t.onesuperior = 300; t.imacron = 278; t.Euro = 500; }); t["Times-BoldItalic"] = getLookupTableFactory(function (t) { t.space = 250; t.exclam = 389; t.quotedbl = 555; t.numbersign = 500; t.dollar = 500; t.percent = 833; t.ampersand = 778; t.quoteright = 333; t.parenleft = 333; t.parenright = 333; t.asterisk = 500; t.plus = 570; t.comma = 250; t.hyphen = 333; t.period = 250; t.slash = 278; t.zero = 500; t.one = 500; t.two = 500; t.three = 500; t.four = 500; t.five = 500; t.six = 500; t.seven = 500; t.eight = 500; t.nine = 500; t.colon = 333; t.semicolon = 333; t.less = 570; t.equal = 570; t.greater = 570; t.question = 500; t.at = 832; t.A = 667; t.B = 667; t.C = 667; t.D = 722; t.E = 667; t.F = 667; t.G = 722; t.H = 778; t.I = 389; t.J = 500; t.K = 667; t.L = 611; t.M = 889; t.N = 722; t.O = 722; t.P = 611; t.Q = 722; t.R = 667; t.S = 556; t.T = 611; t.U = 722; t.V = 667; t.W = 889; t.X = 667; t.Y = 611; t.Z = 611; t.bracketleft = 333; t.backslash = 278; t.bracketright = 333; t.asciicircum = 570; t.underscore = 500; t.quoteleft = 333; t.a = 500; t.b = 500; t.c = 444; t.d = 500; t.e = 444; t.f = 333; t.g = 500; t.h = 556; t.i = 278; t.j = 278; t.k = 500; t.l = 278; t.m = 778; t.n = 556; t.o = 500; t.p = 500; t.q = 500; t.r = 389; t.s = 389; t.t = 278; t.u = 556; t.v = 444; t.w = 667; t.x = 500; t.y = 444; t.z = 389; t.braceleft = 348; t.bar = 220; t.braceright = 348; t.asciitilde = 570; t.exclamdown = 389; t.cent = 500; t.sterling = 500; t.fraction = 167; t.yen = 500; t.florin = 500; t.section = 500; t.currency = 500; t.quotesingle = 278; t.quotedblleft = 500; t.guillemotleft = 500; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 556; t.fl = 556; t.endash = 500; t.dagger = 500; t.daggerdbl = 500; t.periodcentered = 250; t.paragraph = 500; t.bullet = 350; t.quotesinglbase = 333; t.quotedblbase = 500; t.quotedblright = 500; t.guillemotright = 500; t.ellipsis = 1000; t.perthousand = 1000; t.questiondown = 500; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 1000; t.AE = 944; t.ordfeminine = 266; t.Lslash = 611; t.Oslash = 722; t.OE = 944; t.ordmasculine = 300; t.ae = 722; t.dotlessi = 278; t.lslash = 278; t.oslash = 500; t.oe = 722; t.germandbls = 500; t.Idieresis = 389; t.eacute = 444; t.abreve = 500; t.uhungarumlaut = 556; t.ecaron = 444; t.Ydieresis = 611; t.divide = 570; t.Yacute = 611; t.Acircumflex = 667; t.aacute = 500; t.Ucircumflex = 722; t.yacute = 444; t.scommaaccent = 389; t.ecircumflex = 444; t.Uring = 722; t.Udieresis = 722; t.aogonek = 500; t.Uacute = 722; t.uogonek = 556; t.Edieresis = 667; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 747; t.Emacron = 667; t.ccaron = 444; t.aring = 500; t.Ncommaaccent = 722; t.lacute = 278; t.agrave = 500; t.Tcommaaccent = 611; t.Cacute = 667; t.atilde = 500; t.Edotaccent = 667; t.scaron = 389; t.scedilla = 389; t.iacute = 278; t.lozenge = 494; t.Rcaron = 667; t.Gcommaaccent = 722; t.ucircumflex = 556; t.acircumflex = 500; t.Amacron = 667; t.rcaron = 389; t.ccedilla = 444; t.Zdotaccent = 611; t.Thorn = 611; t.Omacron = 722; t.Racute = 667; t.Sacute = 556; t.dcaron = 608; t.Umacron = 722; t.uring = 556; t.threesuperior = 300; t.Ograve = 722; t.Agrave = 667; t.Abreve = 667; t.multiply = 570; t.uacute = 556; t.Tcaron = 611; t.partialdiff = 494; t.ydieresis = 444; t.Nacute = 722; t.icircumflex = 278; t.Ecircumflex = 667; t.adieresis = 500; t.edieresis = 444; t.cacute = 444; t.nacute = 556; t.umacron = 556; t.Ncaron = 722; t.Iacute = 389; t.plusminus = 570; t.brokenbar = 220; t.registered = 747; t.Gbreve = 722; t.Idotaccent = 389; t.summation = 600; t.Egrave = 667; t.racute = 389; t.omacron = 500; t.Zacute = 611; t.Zcaron = 611; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 667; t.lcommaaccent = 278; t.tcaron = 366; t.eogonek = 444; t.Uogonek = 722; t.Aacute = 667; t.Adieresis = 667; t.egrave = 444; t.zacute = 389; t.iogonek = 278; t.Oacute = 722; t.oacute = 500; t.amacron = 500; t.sacute = 389; t.idieresis = 278; t.Ocircumflex = 722; t.Ugrave = 722; t.Delta = 612; t.thorn = 500; t.twosuperior = 300; t.Odieresis = 722; t.mu = 576; t.igrave = 278; t.ohungarumlaut = 500; t.Eogonek = 667; t.dcroat = 500; t.threequarters = 750; t.Scedilla = 556; t.lcaron = 382; t.Kcommaaccent = 667; t.Lacute = 611; t.trademark = 1000; t.edotaccent = 444; t.Igrave = 389; t.Imacron = 389; t.Lcaron = 611; t.onehalf = 750; t.lessequal = 549; t.ocircumflex = 500; t.ntilde = 556; t.Uhungarumlaut = 722; t.Eacute = 667; t.emacron = 444; t.gbreve = 500; t.onequarter = 750; t.Scaron = 556; t.Scommaaccent = 556; t.Ohungarumlaut = 722; t.degree = 400; t.ograve = 500; t.Ccaron = 667; t.ugrave = 556; t.radical = 549; t.Dcaron = 722; t.rcommaaccent = 389; t.Ntilde = 722; t.otilde = 500; t.Rcommaaccent = 667; t.Lcommaaccent = 611; t.Atilde = 667; t.Aogonek = 667; t.Aring = 667; t.Otilde = 722; t.zdotaccent = 389; t.Ecaron = 667; t.Iogonek = 389; t.kcommaaccent = 500; t.minus = 606; t.Icircumflex = 389; t.ncaron = 556; t.tcommaaccent = 278; t.logicalnot = 606; t.odieresis = 500; t.udieresis = 556; t.notequal = 549; t.gcommaaccent = 500; t.eth = 500; t.zcaron = 389; t.ncommaaccent = 556; t.onesuperior = 300; t.imacron = 278; t.Euro = 500; }); t["Times-Italic"] = getLookupTableFactory(function (t) { t.space = 250; t.exclam = 333; t.quotedbl = 420; t.numbersign = 500; t.dollar = 500; t.percent = 833; t.ampersand = 778; t.quoteright = 333; t.parenleft = 333; t.parenright = 333; t.asterisk = 500; t.plus = 675; t.comma = 250; t.hyphen = 333; t.period = 250; t.slash = 278; t.zero = 500; t.one = 500; t.two = 500; t.three = 500; t.four = 500; t.five = 500; t.six = 500; t.seven = 500; t.eight = 500; t.nine = 500; t.colon = 333; t.semicolon = 333; t.less = 675; t.equal = 675; t.greater = 675; t.question = 500; t.at = 920; t.A = 611; t.B = 611; t.C = 667; t.D = 722; t.E = 611; t.F = 611; t.G = 722; t.H = 722; t.I = 333; t.J = 444; t.K = 667; t.L = 556; t.M = 833; t.N = 667; t.O = 722; t.P = 611; t.Q = 722; t.R = 611; t.S = 500; t.T = 556; t.U = 722; t.V = 611; t.W = 833; t.X = 611; t.Y = 556; t.Z = 556; t.bracketleft = 389; t.backslash = 278; t.bracketright = 389; t.asciicircum = 422; t.underscore = 500; t.quoteleft = 333; t.a = 500; t.b = 500; t.c = 444; t.d = 500; t.e = 444; t.f = 278; t.g = 500; t.h = 500; t.i = 278; t.j = 278; t.k = 444; t.l = 278; t.m = 722; t.n = 500; t.o = 500; t.p = 500; t.q = 500; t.r = 389; t.s = 389; t.t = 278; t.u = 500; t.v = 444; t.w = 667; t.x = 444; t.y = 444; t.z = 389; t.braceleft = 400; t.bar = 275; t.braceright = 400; t.asciitilde = 541; t.exclamdown = 389; t.cent = 500; t.sterling = 500; t.fraction = 167; t.yen = 500; t.florin = 500; t.section = 500; t.currency = 500; t.quotesingle = 214; t.quotedblleft = 556; t.guillemotleft = 500; t.guilsinglleft = 333; t.guilsinglright = 333; t.fi = 500; t.fl = 500; t.endash = 500; t.dagger = 500; t.daggerdbl = 500; t.periodcentered = 250; t.paragraph = 523; t.bullet = 350; t.quotesinglbase = 333; t.quotedblbase = 556; t.quotedblright = 556; t.guillemotright = 500; t.ellipsis = 889; t.perthousand = 1000; t.questiondown = 500; t.grave = 333; t.acute = 333; t.circumflex = 333; t.tilde = 333; t.macron = 333; t.breve = 333; t.dotaccent = 333; t.dieresis = 333; t.ring = 333; t.cedilla = 333; t.hungarumlaut = 333; t.ogonek = 333; t.caron = 333; t.emdash = 889; t.AE = 889; t.ordfeminine = 276; t.Lslash = 556; t.Oslash = 722; t.OE = 944; t.ordmasculine = 310; t.ae = 667; t.dotlessi = 278; t.lslash = 278; t.oslash = 500; t.oe = 667; t.germandbls = 500; t.Idieresis = 333; t.eacute = 444; t.abreve = 500; t.uhungarumlaut = 500; t.ecaron = 444; t.Ydieresis = 556; t.divide = 675; t.Yacute = 556; t.Acircumflex = 611; t.aacute = 500; t.Ucircumflex = 722; t.yacute = 444; t.scommaaccent = 389; t.ecircumflex = 444; t.Uring = 722; t.Udieresis = 722; t.aogonek = 500; t.Uacute = 722; t.uogonek = 500; t.Edieresis = 611; t.Dcroat = 722; t.commaaccent = 250; t.copyright = 760; t.Emacron = 611; t.ccaron = 444; t.aring = 500; t.Ncommaaccent = 667; t.lacute = 278; t.agrave = 500; t.Tcommaaccent = 556; t.Cacute = 667; t.atilde = 500; t.Edotaccent = 611; t.scaron = 389; t.scedilla = 389; t.iacute = 278; t.lozenge = 471; t.Rcaron = 611; t.Gcommaaccent = 722; t.ucircumflex = 500; t.acircumflex = 500; t.Amacron = 611; t.rcaron = 389; t.ccedilla = 444; t.Zdotaccent = 556; t.Thorn = 611; t.Omacron = 722; t.Racute = 611; t.Sacute = 500; t.dcaron = 544; t.Umacron = 722; t.uring = 500; t.threesuperior = 300; t.Ograve = 722; t.Agrave = 611; t.Abreve = 611; t.multiply = 675; t.uacute = 500; t.Tcaron = 556; t.partialdiff = 476; t.ydieresis = 444; t.Nacute = 667; t.icircumflex = 278; t.Ecircumflex = 611; t.adieresis = 500; t.edieresis = 444; t.cacute = 444; t.nacute = 500; t.umacron = 500; t.Ncaron = 667; t.Iacute = 333; t.plusminus = 675; t.brokenbar = 275; t.registered = 760; t.Gbreve = 722; t.Idotaccent = 333; t.summation = 600; t.Egrave = 611; t.racute = 389; t.omacron = 500; t.Zacute = 556; t.Zcaron = 556; t.greaterequal = 549; t.Eth = 722; t.Ccedilla = 667; t.lcommaaccent = 278; t.tcaron = 300; t.eogonek = 444; t.Uogonek = 722; t.Aacute = 611; t.Adieresis = 611; t.egrave = 444; t.zacute = 389; t.iogonek = 278; t.Oacute = 722; t.oacute = 500; t.amacron = 500; t.sacute = 389; t.idieresis = 278; t.Ocircumflex = 722; t.Ugrave = 722; t.Delta = 612; t.thorn = 500; t.twosuperior = 300; t.Odieresis = 722; t.mu = 500; t.igrave = 278; t.ohungarumlaut = 500; t.Eogonek = 611; t.dcroat = 500; t.threequarters = 750; t.Scedilla = 500; t.lcaron = 300; t.Kcommaaccent = 667; t.Lacute = 556; t.trademark = 980; t.edotaccent = 444; t.Igrave = 333; t.Imacron = 333; t.Lcaron = 611; t.onehalf = 750; t.lessequal = 549; t.ocircumflex = 500; t.ntilde = 500; t.Uhungarumlaut = 722; t.Eacute = 611; t.emacron = 444; t.gbreve = 500; t.onequarter = 750; t.Scaron = 500; t.Scommaaccent = 500; t.Ohungarumlaut = 722; t.degree = 400; t.ograve = 500; t.Ccaron = 667; t.ugrave = 500; t.radical = 453; t.Dcaron = 722; t.rcommaaccent = 389; t.Ntilde = 667; t.otilde = 500; t.Rcommaaccent = 611; t.Lcommaaccent = 556; t.Atilde = 611; t.Aogonek = 611; t.Aring = 611; t.Otilde = 722; t.zdotaccent = 389; t.Ecaron = 611; t.Iogonek = 333; t.kcommaaccent = 444; t.minus = 675; t.Icircumflex = 333; t.ncaron = 500; t.tcommaaccent = 278; t.logicalnot = 675; t.odieresis = 500; t.udieresis = 500; t.notequal = 549; t.gcommaaccent = 500; t.eth = 500; t.zcaron = 389; t.ncommaaccent = 500; t.onesuperior = 300; t.imacron = 278; t.Euro = 500; }); t.ZapfDingbats = getLookupTableFactory(function (t) { t.space = 278; t.a1 = 974; t.a2 = 961; t.a202 = 974; t.a3 = 980; t.a4 = 719; t.a5 = 789; t.a119 = 790; t.a118 = 791; t.a117 = 690; t.a11 = 960; t.a12 = 939; t.a13 = 549; t.a14 = 855; t.a15 = 911; t.a16 = 933; t.a105 = 911; t.a17 = 945; t.a18 = 974; t.a19 = 755; t.a20 = 846; t.a21 = 762; t.a22 = 761; t.a23 = 571; t.a24 = 677; t.a25 = 763; t.a26 = 760; t.a27 = 759; t.a28 = 754; t.a6 = 494; t.a7 = 552; t.a8 = 537; t.a9 = 577; t.a10 = 692; t.a29 = 786; t.a30 = 788; t.a31 = 788; t.a32 = 790; t.a33 = 793; t.a34 = 794; t.a35 = 816; t.a36 = 823; t.a37 = 789; t.a38 = 841; t.a39 = 823; t.a40 = 833; t.a41 = 816; t.a42 = 831; t.a43 = 923; t.a44 = 744; t.a45 = 723; t.a46 = 749; t.a47 = 790; t.a48 = 792; t.a49 = 695; t.a50 = 776; t.a51 = 768; t.a52 = 792; t.a53 = 759; t.a54 = 707; t.a55 = 708; t.a56 = 682; t.a57 = 701; t.a58 = 826; t.a59 = 815; t.a60 = 789; t.a61 = 789; t.a62 = 707; t.a63 = 687; t.a64 = 696; t.a65 = 689; t.a66 = 786; t.a67 = 787; t.a68 = 713; t.a69 = 791; t.a70 = 785; t.a71 = 791; t.a72 = 873; t.a73 = 761; t.a74 = 762; t.a203 = 762; t.a75 = 759; t.a204 = 759; t.a76 = 892; t.a77 = 892; t.a78 = 788; t.a79 = 784; t.a81 = 438; t.a82 = 138; t.a83 = 277; t.a84 = 415; t.a97 = 392; t.a98 = 392; t.a99 = 668; t.a100 = 668; t.a89 = 390; t.a90 = 390; t.a93 = 317; t.a94 = 317; t.a91 = 276; t.a92 = 276; t.a205 = 509; t.a85 = 509; t.a206 = 410; t.a86 = 410; t.a87 = 234; t.a88 = 234; t.a95 = 334; t.a96 = 334; t.a101 = 732; t.a102 = 544; t.a103 = 544; t.a104 = 910; t.a106 = 667; t.a107 = 760; t.a108 = 760; t.a112 = 776; t.a111 = 595; t.a110 = 694; t.a109 = 626; t.a120 = 788; t.a121 = 788; t.a122 = 788; t.a123 = 788; t.a124 = 788; t.a125 = 788; t.a126 = 788; t.a127 = 788; t.a128 = 788; t.a129 = 788; t.a130 = 788; t.a131 = 788; t.a132 = 788; t.a133 = 788; t.a134 = 788; t.a135 = 788; t.a136 = 788; t.a137 = 788; t.a138 = 788; t.a139 = 788; t.a140 = 788; t.a141 = 788; t.a142 = 788; t.a143 = 788; t.a144 = 788; t.a145 = 788; t.a146 = 788; t.a147 = 788; t.a148 = 788; t.a149 = 788; t.a150 = 788; t.a151 = 788; t.a152 = 788; t.a153 = 788; t.a154 = 788; t.a155 = 788; t.a156 = 788; t.a157 = 788; t.a158 = 788; t.a159 = 788; t.a160 = 894; t.a161 = 838; t.a163 = 1016; t.a164 = 458; t.a196 = 748; t.a165 = 924; t.a192 = 748; t.a166 = 918; t.a167 = 927; t.a168 = 928; t.a169 = 928; t.a170 = 834; t.a171 = 873; t.a172 = 828; t.a173 = 924; t.a162 = 924; t.a174 = 917; t.a175 = 930; t.a176 = 931; t.a177 = 463; t.a178 = 883; t.a179 = 836; t.a193 = 836; t.a180 = 867; t.a199 = 867; t.a181 = 696; t.a200 = 696; t.a182 = 874; t.a201 = 874; t.a183 = 760; t.a184 = 946; t.a197 = 771; t.a185 = 865; t.a194 = 771; t.a198 = 888; t.a186 = 967; t.a195 = 888; t.a187 = 831; t.a188 = 873; t.a189 = 927; t.a190 = 970; t.a191 = 918; }); }); const getFontBasicMetrics = getLookupTableFactory(function (t) { t.Courier = { ascent: 629, descent: -157, capHeight: 562, xHeight: -426 }; t["Courier-Bold"] = { ascent: 629, descent: -157, capHeight: 562, xHeight: 439 }; t["Courier-Oblique"] = { ascent: 629, descent: -157, capHeight: 562, xHeight: 426 }; t["Courier-BoldOblique"] = { ascent: 629, descent: -157, capHeight: 562, xHeight: 426 }; t.Helvetica = { ascent: 718, descent: -207, capHeight: 718, xHeight: 523 }; t["Helvetica-Bold"] = { ascent: 718, descent: -207, capHeight: 718, xHeight: 532 }; t["Helvetica-Oblique"] = { ascent: 718, descent: -207, capHeight: 718, xHeight: 523 }; t["Helvetica-BoldOblique"] = { ascent: 718, descent: -207, capHeight: 718, xHeight: 532 }; t["Times-Roman"] = { ascent: 683, descent: -217, capHeight: 662, xHeight: 450 }; t["Times-Bold"] = { ascent: 683, descent: -217, capHeight: 676, xHeight: 461 }; t["Times-Italic"] = { ascent: 683, descent: -217, capHeight: 653, xHeight: 441 }; t["Times-BoldItalic"] = { ascent: 683, descent: -217, capHeight: 669, xHeight: 462 }; t.Symbol = { ascent: Math.NaN, descent: Math.NaN, capHeight: Math.NaN, xHeight: Math.NaN }; t.ZapfDingbats = { ascent: Math.NaN, descent: Math.NaN, capHeight: Math.NaN, xHeight: Math.NaN }; }); ;// CONCATENATED MODULE: ./src/core/glyf.js const ON_CURVE_POINT = 1 << 0; const X_SHORT_VECTOR = 1 << 1; const Y_SHORT_VECTOR = 1 << 2; const REPEAT_FLAG = 1 << 3; const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4; const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5; const OVERLAP_SIMPLE = 1 << 6; const ARG_1_AND_2_ARE_WORDS = 1 << 0; const ARGS_ARE_XY_VALUES = 1 << 1; const WE_HAVE_A_SCALE = 1 << 3; const MORE_COMPONENTS = 1 << 5; const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; const WE_HAVE_A_TWO_BY_TWO = 1 << 7; const WE_HAVE_INSTRUCTIONS = 1 << 8; class GlyfTable { constructor({ glyfTable, isGlyphLocationsLong, locaTable, numGlyphs }) { this.glyphs = []; const loca = new DataView(locaTable.buffer, locaTable.byteOffset, locaTable.byteLength); const glyf = new DataView(glyfTable.buffer, glyfTable.byteOffset, glyfTable.byteLength); const offsetSize = isGlyphLocationsLong ? 4 : 2; let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0); let pos = 0; for (let i = 0; i < numGlyphs; i++) { pos += offsetSize; const next = isGlyphLocationsLong ? loca.getUint32(pos) : 2 * loca.getUint16(pos); if (next === prev) { this.glyphs.push(new Glyph({})); continue; } const glyph = Glyph.parse(prev, glyf); this.glyphs.push(glyph); prev = next; } } getSize() { return this.glyphs.reduce((a, g) => { const size = g.getSize(); return a + (size + 3 & ~3); }, 0); } write() { const totalSize = this.getSize(); const glyfTable = new DataView(new ArrayBuffer(totalSize)); const isLocationLong = totalSize > 0x1fffe; const offsetSize = isLocationLong ? 4 : 2; const locaTable = new DataView(new ArrayBuffer((this.glyphs.length + 1) * offsetSize)); if (isLocationLong) { locaTable.setUint32(0, 0); } else { locaTable.setUint16(0, 0); } let pos = 0; let locaIndex = 0; for (const glyph of this.glyphs) { pos += glyph.write(pos, glyfTable); pos = pos + 3 & ~3; locaIndex += offsetSize; if (isLocationLong) { locaTable.setUint32(locaIndex, pos); } else { locaTable.setUint16(locaIndex, pos >> 1); } } return { isLocationLong, loca: new Uint8Array(locaTable.buffer), glyf: new Uint8Array(glyfTable.buffer) }; } scale(factors) { for (let i = 0, ii = this.glyphs.length; i < ii; i++) { this.glyphs[i].scale(factors[i]); } } } class Glyph { constructor({ header = null, simple = null, composites = null }) { this.header = header; this.simple = simple; this.composites = composites; } static parse(pos, glyf) { const [read, header] = GlyphHeader.parse(pos, glyf); pos += read; if (header.numberOfContours < 0) { const composites = []; while (true) { const [n, composite] = CompositeGlyph.parse(pos, glyf); pos += n; composites.push(composite); if (!(composite.flags & MORE_COMPONENTS)) { break; } } return new Glyph({ header, composites }); } const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours); return new Glyph({ header, simple }); } getSize() { if (!this.header) { return 0; } const size = this.simple ? this.simple.getSize() : this.composites.reduce((a, c) => a + c.getSize(), 0); return this.header.getSize() + size; } write(pos, buf) { if (!this.header) { return 0; } const spos = pos; pos += this.header.write(pos, buf); if (this.simple) { pos += this.simple.write(pos, buf); } else { for (const composite of this.composites) { pos += composite.write(pos, buf); } } return pos - spos; } scale(factor) { if (!this.header) { return; } const xMiddle = (this.header.xMin + this.header.xMax) / 2; this.header.scale(xMiddle, factor); if (this.simple) { this.simple.scale(xMiddle, factor); } else { for (const composite of this.composites) { composite.scale(xMiddle, factor); } } } } class GlyphHeader { constructor({ numberOfContours, xMin, yMin, xMax, yMax }) { this.numberOfContours = numberOfContours; this.xMin = xMin; this.yMin = yMin; this.xMax = xMax; this.yMax = yMax; } static parse(pos, glyf) { return [10, new GlyphHeader({ numberOfContours: glyf.getInt16(pos), xMin: glyf.getInt16(pos + 2), yMin: glyf.getInt16(pos + 4), xMax: glyf.getInt16(pos + 6), yMax: glyf.getInt16(pos + 8) })]; } getSize() { return 10; } write(pos, buf) { buf.setInt16(pos, this.numberOfContours); buf.setInt16(pos + 2, this.xMin); buf.setInt16(pos + 4, this.yMin); buf.setInt16(pos + 6, this.xMax); buf.setInt16(pos + 8, this.yMax); return 10; } scale(x, factor) { this.xMin = Math.round(x + (this.xMin - x) * factor); this.xMax = Math.round(x + (this.xMax - x) * factor); } } class Contour { constructor({ flags, xCoordinates, yCoordinates }) { this.xCoordinates = xCoordinates; this.yCoordinates = yCoordinates; this.flags = flags; } } class SimpleGlyph { constructor({ contours, instructions }) { this.contours = contours; this.instructions = instructions; } static parse(pos, glyf, numberOfContours) { const endPtsOfContours = []; for (let i = 0; i < numberOfContours; i++) { const endPt = glyf.getUint16(pos); pos += 2; endPtsOfContours.push(endPt); } const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1; const instructionLength = glyf.getUint16(pos); pos += 2; const instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength); pos += instructionLength; const flags = []; for (let i = 0; i < numberOfPt; pos++, i++) { let flag = glyf.getUint8(pos); flags.push(flag); if (flag & REPEAT_FLAG) { const count = glyf.getUint8(++pos); flag ^= REPEAT_FLAG; for (let m = 0; m < count; m++) { flags.push(flag); } i += count; } } const allXCoordinates = []; let xCoordinates = []; let yCoordinates = []; let pointFlags = []; const contours = []; let endPtsOfContoursIndex = 0; let lastCoordinate = 0; for (let i = 0; i < numberOfPt; i++) { const flag = flags[i]; if (flag & X_SHORT_VECTOR) { const x = glyf.getUint8(pos++); lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x; xCoordinates.push(lastCoordinate); } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { xCoordinates.push(lastCoordinate); } else { lastCoordinate += glyf.getInt16(pos); pos += 2; xCoordinates.push(lastCoordinate); } if (endPtsOfContours[endPtsOfContoursIndex] === i) { endPtsOfContoursIndex++; allXCoordinates.push(xCoordinates); xCoordinates = []; } } lastCoordinate = 0; endPtsOfContoursIndex = 0; for (let i = 0; i < numberOfPt; i++) { const flag = flags[i]; if (flag & Y_SHORT_VECTOR) { const y = glyf.getUint8(pos++); lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y; yCoordinates.push(lastCoordinate); } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) { yCoordinates.push(lastCoordinate); } else { lastCoordinate += glyf.getInt16(pos); pos += 2; yCoordinates.push(lastCoordinate); } pointFlags.push(flag & ON_CURVE_POINT | flag & OVERLAP_SIMPLE); if (endPtsOfContours[endPtsOfContoursIndex] === i) { xCoordinates = allXCoordinates[endPtsOfContoursIndex]; endPtsOfContoursIndex++; contours.push(new Contour({ flags: pointFlags, xCoordinates, yCoordinates })); yCoordinates = []; pointFlags = []; } } return new SimpleGlyph({ contours, instructions }); } getSize() { let size = this.contours.length * 2 + 2 + this.instructions.length; let lastX = 0; let lastY = 0; for (const contour of this.contours) { size += contour.flags.length; for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { const x = contour.xCoordinates[i]; const y = contour.yCoordinates[i]; let abs = Math.abs(x - lastX); if (abs > 255) { size += 2; } else if (abs > 0) { size += 1; } lastX = x; abs = Math.abs(y - lastY); if (abs > 255) { size += 2; } else if (abs > 0) { size += 1; } lastY = y; } } return size; } write(pos, buf) { const spos = pos; const xCoordinates = []; const yCoordinates = []; const flags = []; let lastX = 0; let lastY = 0; for (const contour of this.contours) { for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { let flag = contour.flags[i]; const x = contour.xCoordinates[i]; let delta = x - lastX; if (delta === 0) { flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR; xCoordinates.push(0); } else { const abs = Math.abs(delta); if (abs <= 255) { flag |= delta >= 0 ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR : X_SHORT_VECTOR; xCoordinates.push(abs); } else { xCoordinates.push(delta); } } lastX = x; const y = contour.yCoordinates[i]; delta = y - lastY; if (delta === 0) { flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR; yCoordinates.push(0); } else { const abs = Math.abs(delta); if (abs <= 255) { flag |= delta >= 0 ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR : Y_SHORT_VECTOR; yCoordinates.push(abs); } else { yCoordinates.push(delta); } } lastY = y; flags.push(flag); } buf.setUint16(pos, xCoordinates.length - 1); pos += 2; } buf.setUint16(pos, this.instructions.length); pos += 2; if (this.instructions.length) { new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos); pos += this.instructions.length; } for (const flag of flags) { buf.setUint8(pos++, flag); } for (let i = 0, ii = xCoordinates.length; i < ii; i++) { const x = xCoordinates[i]; const flag = flags[i]; if (flag & X_SHORT_VECTOR) { buf.setUint8(pos++, x); } else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) { buf.setInt16(pos, x); pos += 2; } } for (let i = 0, ii = yCoordinates.length; i < ii; i++) { const y = yCoordinates[i]; const flag = flags[i]; if (flag & Y_SHORT_VECTOR) { buf.setUint8(pos++, y); } else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) { buf.setInt16(pos, y); pos += 2; } } return pos - spos; } scale(x, factor) { for (const contour of this.contours) { if (contour.xCoordinates.length === 0) { continue; } for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { contour.xCoordinates[i] = Math.round(x + (contour.xCoordinates[i] - x) * factor); } } } } class CompositeGlyph { constructor({ flags, glyphIndex, argument1, argument2, transf, instructions }) { this.flags = flags; this.glyphIndex = glyphIndex; this.argument1 = argument1; this.argument2 = argument2; this.transf = transf; this.instructions = instructions; } static parse(pos, glyf) { const spos = pos; const transf = []; let flags = glyf.getUint16(pos); const glyphIndex = glyf.getUint16(pos + 2); pos += 4; let argument1, argument2; if (flags & ARG_1_AND_2_ARE_WORDS) { if (flags & ARGS_ARE_XY_VALUES) { argument1 = glyf.getInt16(pos); argument2 = glyf.getInt16(pos + 2); } else { argument1 = glyf.getUint16(pos); argument2 = glyf.getUint16(pos + 2); } pos += 4; flags ^= ARG_1_AND_2_ARE_WORDS; } else { if (flags & ARGS_ARE_XY_VALUES) { argument1 = glyf.getInt8(pos); argument2 = glyf.getInt8(pos + 1); } else { argument1 = glyf.getUint8(pos); argument2 = glyf.getUint8(pos + 1); } pos += 2; } if (flags & WE_HAVE_A_SCALE) { transf.push(glyf.getUint16(pos)); pos += 2; } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2)); pos += 4; } else if (flags & WE_HAVE_A_TWO_BY_TWO) { transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2), glyf.getUint16(pos + 4), glyf.getUint16(pos + 6)); pos += 8; } let instructions = null; if (flags & WE_HAVE_INSTRUCTIONS) { const instructionLength = glyf.getUint16(pos); pos += 2; instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength); pos += instructionLength; } return [pos - spos, new CompositeGlyph({ flags, glyphIndex, argument1, argument2, transf, instructions })]; } getSize() { let size = 2 + 2 + this.transf.length * 2; if (this.flags & WE_HAVE_INSTRUCTIONS) { size += 2 + this.instructions.length; } size += 2; if (this.flags & 2) { if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) { size += 2; } } else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) { size += 2; } return size; } write(pos, buf) { const spos = pos; if (this.flags & ARGS_ARE_XY_VALUES) { if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) { this.flags |= ARG_1_AND_2_ARE_WORDS; } } else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) { this.flags |= ARG_1_AND_2_ARE_WORDS; } buf.setUint16(pos, this.flags); buf.setUint16(pos + 2, this.glyphIndex); pos += 4; if (this.flags & ARG_1_AND_2_ARE_WORDS) { if (this.flags & ARGS_ARE_XY_VALUES) { buf.setInt16(pos, this.argument1); buf.setInt16(pos + 2, this.argument2); } else { buf.setUint16(pos, this.argument1); buf.setUint16(pos + 2, this.argument2); } pos += 4; } else { buf.setUint8(pos, this.argument1); buf.setUint8(pos + 1, this.argument2); pos += 2; } if (this.flags & WE_HAVE_INSTRUCTIONS) { buf.setUint16(pos, this.instructions.length); pos += 2; if (this.instructions.length) { new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos); pos += this.instructions.length; } } return pos - spos; } scale(x, factor) {} } ;// CONCATENATED MODULE: ./src/core/opentype_file_builder.js function writeInt16(dest, offset, num) { dest[offset] = num >> 8 & 0xff; dest[offset + 1] = num & 0xff; } function writeInt32(dest, offset, num) { dest[offset] = num >> 24 & 0xff; dest[offset + 1] = num >> 16 & 0xff; dest[offset + 2] = num >> 8 & 0xff; dest[offset + 3] = num & 0xff; } function writeData(dest, offset, data) { if (data instanceof Uint8Array) { dest.set(data, offset); } else if (typeof data === "string") { for (let i = 0, ii = data.length; i < ii; i++) { dest[offset++] = data.charCodeAt(i) & 0xff; } } else { for (const num of data) { dest[offset++] = num & 0xff; } } } const OTF_HEADER_SIZE = 12; const OTF_TABLE_ENTRY_SIZE = 16; class OpenTypeFileBuilder { constructor(sfnt) { this.sfnt = sfnt; this.tables = Object.create(null); } static getSearchParams(entriesCount, entrySize) { let maxPower2 = 1, log2 = 0; while ((maxPower2 ^ entriesCount) > maxPower2) { maxPower2 <<= 1; log2++; } const searchRange = maxPower2 * entrySize; return { range: searchRange, entry: log2, rangeShift: entrySize * entriesCount - searchRange }; } toArray() { let sfnt = this.sfnt; const tables = this.tables; const tablesNames = Object.keys(tables); tablesNames.sort(); const numTables = tablesNames.length; let i, j, jj, table, tableName; let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; const tableOffsets = [offset]; for (i = 0; i < numTables; i++) { table = tables[tablesNames[i]]; const paddedLength = (table.length + 3 & ~3) >>> 0; offset += paddedLength; tableOffsets.push(offset); } const file = new Uint8Array(offset); for (i = 0; i < numTables; i++) { table = tables[tablesNames[i]]; writeData(file, tableOffsets[i], table); } if (sfnt === "true") { sfnt = string32(0x00010000); } file[0] = sfnt.charCodeAt(0) & 0xff; file[1] = sfnt.charCodeAt(1) & 0xff; file[2] = sfnt.charCodeAt(2) & 0xff; file[3] = sfnt.charCodeAt(3) & 0xff; writeInt16(file, 4, numTables); const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); writeInt16(file, 6, searchParams.range); writeInt16(file, 8, searchParams.entry); writeInt16(file, 10, searchParams.rangeShift); offset = OTF_HEADER_SIZE; for (i = 0; i < numTables; i++) { tableName = tablesNames[i]; file[offset] = tableName.charCodeAt(0) & 0xff; file[offset + 1] = tableName.charCodeAt(1) & 0xff; file[offset + 2] = tableName.charCodeAt(2) & 0xff; file[offset + 3] = tableName.charCodeAt(3) & 0xff; let checksum = 0; for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { const quad = readUint32(file, j); checksum = checksum + quad >>> 0; } writeInt32(file, offset + 4, checksum); writeInt32(file, offset + 8, tableOffsets[i]); writeInt32(file, offset + 12, tables[tableName].length); offset += OTF_TABLE_ENTRY_SIZE; } return file; } addTable(tag, data) { if (tag in this.tables) { throw new Error("Table " + tag + " already exists"); } this.tables[tag] = data; } } ;// CONCATENATED MODULE: ./src/core/type1_parser.js const HINTING_ENABLED = false; const COMMAND_MAP = { hstem: [1], vstem: [3], vmoveto: [4], rlineto: [5], hlineto: [6], vlineto: [7], rrcurveto: [8], callsubr: [10], flex: [12, 35], drop: [12, 18], endchar: [14], rmoveto: [21], hmoveto: [22], vhcurveto: [30], hvcurveto: [31] }; class Type1CharString { constructor() { this.width = 0; this.lsb = 0; this.flexing = false; this.output = []; this.stack = []; } convert(encoded, subrs, seacAnalysisEnabled) { const count = encoded.length; let error = false; let wx, sbx, subrNumber; for (let i = 0; i < count; i++) { let value = encoded[i]; if (value < 32) { if (value === 12) { value = (value << 8) + encoded[++i]; } switch (value) { case 1: if (!HINTING_ENABLED) { this.stack = []; break; } error = this.executeCommand(2, COMMAND_MAP.hstem); break; case 3: if (!HINTING_ENABLED) { this.stack = []; break; } error = this.executeCommand(2, COMMAND_MAP.vstem); break; case 4: if (this.flexing) { if (this.stack.length < 1) { error = true; break; } const dy = this.stack.pop(); this.stack.push(0, dy); break; } error = this.executeCommand(1, COMMAND_MAP.vmoveto); break; case 5: error = this.executeCommand(2, COMMAND_MAP.rlineto); break; case 6: error = this.executeCommand(1, COMMAND_MAP.hlineto); break; case 7: error = this.executeCommand(1, COMMAND_MAP.vlineto); break; case 8: error = this.executeCommand(6, COMMAND_MAP.rrcurveto); break; case 9: this.stack = []; break; case 10: if (this.stack.length < 1) { error = true; break; } subrNumber = this.stack.pop(); if (!subrs[subrNumber]) { error = true; break; } error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled); break; case 11: return error; case 13: if (this.stack.length < 2) { error = true; break; } wx = this.stack.pop(); sbx = this.stack.pop(); this.lsb = sbx; this.width = wx; this.stack.push(wx, sbx); error = this.executeCommand(2, COMMAND_MAP.hmoveto); break; case 14: this.output.push(COMMAND_MAP.endchar[0]); break; case 21: if (this.flexing) { break; } error = this.executeCommand(2, COMMAND_MAP.rmoveto); break; case 22: if (this.flexing) { this.stack.push(0); break; } error = this.executeCommand(1, COMMAND_MAP.hmoveto); break; case 30: error = this.executeCommand(4, COMMAND_MAP.vhcurveto); break; case 31: error = this.executeCommand(4, COMMAND_MAP.hvcurveto); break; case (12 << 8) + 0: this.stack = []; break; case (12 << 8) + 1: if (!HINTING_ENABLED) { this.stack = []; break; } error = this.executeCommand(2, COMMAND_MAP.vstem); break; case (12 << 8) + 2: if (!HINTING_ENABLED) { this.stack = []; break; } error = this.executeCommand(2, COMMAND_MAP.hstem); break; case (12 << 8) + 6: if (seacAnalysisEnabled) { const asb = this.stack.at(-5); this.seac = this.stack.splice(-4, 4); this.seac[0] += this.lsb - asb; error = this.executeCommand(0, COMMAND_MAP.endchar); } else { error = this.executeCommand(4, COMMAND_MAP.endchar); } break; case (12 << 8) + 7: if (this.stack.length < 4) { error = true; break; } this.stack.pop(); wx = this.stack.pop(); const sby = this.stack.pop(); sbx = this.stack.pop(); this.lsb = sbx; this.width = wx; this.stack.push(wx, sbx, sby); error = this.executeCommand(3, COMMAND_MAP.rmoveto); break; case (12 << 8) + 12: if (this.stack.length < 2) { error = true; break; } const num2 = this.stack.pop(); const num1 = this.stack.pop(); this.stack.push(num1 / num2); break; case (12 << 8) + 16: if (this.stack.length < 2) { error = true; break; } subrNumber = this.stack.pop(); const numArgs = this.stack.pop(); if (subrNumber === 0 && numArgs === 3) { const flexArgs = this.stack.splice(-17, 17); this.stack.push(flexArgs[2] + flexArgs[0], flexArgs[3] + flexArgs[1], flexArgs[4], flexArgs[5], flexArgs[6], flexArgs[7], flexArgs[8], flexArgs[9], flexArgs[10], flexArgs[11], flexArgs[12], flexArgs[13], flexArgs[14]); error = this.executeCommand(13, COMMAND_MAP.flex, true); this.flexing = false; this.stack.push(flexArgs[15], flexArgs[16]); } else if (subrNumber === 1 && numArgs === 0) { this.flexing = true; } break; case (12 << 8) + 17: break; case (12 << 8) + 33: this.stack = []; break; default: warn('Unknown type 1 charstring command of "' + value + '"'); break; } if (error) { break; } continue; } else if (value <= 246) { value -= 139; } else if (value <= 250) { value = (value - 247) * 256 + encoded[++i] + 108; } else if (value <= 254) { value = -((value - 251) * 256) - encoded[++i] - 108; } else { value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0; } this.stack.push(value); } return error; } executeCommand(howManyArgs, command, keepStack) { const stackLength = this.stack.length; if (howManyArgs > stackLength) { return true; } const start = stackLength - howManyArgs; for (let i = start; i < stackLength; i++) { let value = this.stack[i]; if (Number.isInteger(value)) { this.output.push(28, value >> 8 & 0xff, value & 0xff); } else { value = 65536 * value | 0; this.output.push(255, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff); } } this.output.push(...command); if (keepStack) { this.stack.splice(start, howManyArgs); } else { this.stack.length = 0; } return false; } } const EEXEC_ENCRYPT_KEY = 55665; const CHAR_STRS_ENCRYPT_KEY = 4330; function isHexDigit(code) { return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102; } function decrypt(data, key, discardNumber) { if (discardNumber >= data.length) { return new Uint8Array(0); } const c1 = 52845, c2 = 22719; let r = key | 0, i, j; for (i = 0; i < discardNumber; i++) { r = (data[i] + r) * c1 + c2 & (1 << 16) - 1; } const count = data.length - discardNumber; const decrypted = new Uint8Array(count); for (i = discardNumber, j = 0; j < count; i++, j++) { const value = data[i]; decrypted[j] = value ^ r >> 8; r = (value + r) * c1 + c2 & (1 << 16) - 1; } return decrypted; } function decryptAscii(data, key, discardNumber) { const c1 = 52845, c2 = 22719; let r = key | 0; const count = data.length, maybeLength = count >>> 1; const decrypted = new Uint8Array(maybeLength); let i, j; for (i = 0, j = 0; i < count; i++) { const digit1 = data[i]; if (!isHexDigit(digit1)) { continue; } i++; let digit2; while (i < count && !isHexDigit(digit2 = data[i])) { i++; } if (i < count) { const value = parseInt(String.fromCharCode(digit1, digit2), 16); decrypted[j++] = value ^ r >> 8; r = (value + r) * c1 + c2 & (1 << 16) - 1; } } return decrypted.slice(discardNumber, j); } function isSpecial(c) { return c === 0x2f || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d || c === 0x28 || c === 0x29; } class Type1Parser { constructor(stream, encrypted, seacAnalysisEnabled) { if (encrypted) { const data = stream.getBytes(); const isBinary = !((isHexDigit(data[0]) || isWhiteSpace(data[0])) && isHexDigit(data[1]) && isHexDigit(data[2]) && isHexDigit(data[3]) && isHexDigit(data[4]) && isHexDigit(data[5]) && isHexDigit(data[6]) && isHexDigit(data[7])); stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4)); } this.seacAnalysisEnabled = !!seacAnalysisEnabled; this.stream = stream; this.nextChar(); } readNumberArray() { this.getToken(); const array = []; while (true) { const token = this.getToken(); if (token === null || token === "]" || token === "}") { break; } array.push(parseFloat(token || 0)); } return array; } readNumber() { const token = this.getToken(); return parseFloat(token || 0); } readInt() { const token = this.getToken(); return parseInt(token || 0, 10) | 0; } readBoolean() { const token = this.getToken(); return token === "true" ? 1 : 0; } nextChar() { return this.currentChar = this.stream.getByte(); } prevChar() { this.stream.skip(-2); return this.currentChar = this.stream.getByte(); } getToken() { let comment = false; let ch = this.currentChar; while (true) { if (ch === -1) { return null; } if (comment) { if (ch === 0x0a || ch === 0x0d) { comment = false; } } else if (ch === 0x25) { comment = true; } else if (!isWhiteSpace(ch)) { break; } ch = this.nextChar(); } if (isSpecial(ch)) { this.nextChar(); return String.fromCharCode(ch); } let token = ""; do { token += String.fromCharCode(ch); ch = this.nextChar(); } while (ch >= 0 && !isWhiteSpace(ch) && !isSpecial(ch)); return token; } readCharStrings(bytes, lenIV) { if (lenIV === -1) { return bytes; } return decrypt(bytes, CHAR_STRS_ENCRYPT_KEY, lenIV); } extractFontProgram(properties) { const stream = this.stream; const subrs = [], charstrings = []; const privateData = Object.create(null); privateData.lenIV = 4; const program = { subrs: [], charstrings: [], properties: { privateData } }; let token, length, data, lenIV; while ((token = this.getToken()) !== null) { if (token !== "/") { continue; } token = this.getToken(); switch (token) { case "CharStrings": this.getToken(); this.getToken(); this.getToken(); this.getToken(); while (true) { token = this.getToken(); if (token === null || token === "end") { break; } if (token !== "/") { continue; } const glyph = this.getToken(); length = this.readInt(); this.getToken(); data = length > 0 ? stream.getBytes(length) : new Uint8Array(0); lenIV = program.properties.privateData.lenIV; const encoded = this.readCharStrings(data, lenIV); this.nextChar(); token = this.getToken(); if (token === "noaccess") { this.getToken(); } else if (token === "/") { this.prevChar(); } charstrings.push({ glyph, encoded }); } break; case "Subrs": this.readInt(); this.getToken(); while (this.getToken() === "dup") { const index = this.readInt(); length = this.readInt(); this.getToken(); data = length > 0 ? stream.getBytes(length) : new Uint8Array(0); lenIV = program.properties.privateData.lenIV; const encoded = this.readCharStrings(data, lenIV); this.nextChar(); token = this.getToken(); if (token === "noaccess") { this.getToken(); } subrs[index] = encoded; } break; case "BlueValues": case "OtherBlues": case "FamilyBlues": case "FamilyOtherBlues": const blueArray = this.readNumberArray(); if (blueArray.length > 0 && blueArray.length % 2 === 0 && HINTING_ENABLED) { program.properties.privateData[token] = blueArray; } break; case "StemSnapH": case "StemSnapV": program.properties.privateData[token] = this.readNumberArray(); break; case "StdHW": case "StdVW": program.properties.privateData[token] = this.readNumberArray()[0]; break; case "BlueShift": case "lenIV": case "BlueFuzz": case "BlueScale": case "LanguageGroup": program.properties.privateData[token] = this.readNumber(); break; case "ExpansionFactor": program.properties.privateData[token] = this.readNumber() || 0.06; break; case "ForceBold": program.properties.privateData[token] = this.readBoolean(); break; } } for (const { encoded, glyph } of charstrings) { const charString = new Type1CharString(); const error = charString.convert(encoded, subrs, this.seacAnalysisEnabled); let output = charString.output; if (error) { output = [14]; } const charStringObject = { glyphName: glyph, charstring: output, width: charString.width, lsb: charString.lsb, seac: charString.seac }; if (glyph === ".notdef") { program.charstrings.unshift(charStringObject); } else { program.charstrings.push(charStringObject); } if (properties.builtInEncoding) { const index = properties.builtInEncoding.indexOf(glyph); if (index > -1 && properties.widths[index] === undefined && index >= properties.firstChar && index <= properties.lastChar) { properties.widths[index] = charString.width; } } } return program; } extractFontHeader(properties) { let token; while ((token = this.getToken()) !== null) { if (token !== "/") { continue; } token = this.getToken(); switch (token) { case "FontMatrix": const matrix = this.readNumberArray(); properties.fontMatrix = matrix; break; case "Encoding": const encodingArg = this.getToken(); let encoding; if (!/^\d+$/.test(encodingArg)) { encoding = getEncoding(encodingArg); } else { encoding = []; const size = parseInt(encodingArg, 10) | 0; this.getToken(); for (let j = 0; j < size; j++) { token = this.getToken(); while (token !== "dup" && token !== "def") { token = this.getToken(); if (token === null) { return; } } if (token === "def") { break; } const index = this.readInt(); this.getToken(); const glyph = this.getToken(); encoding[index] = glyph; this.getToken(); } } properties.builtInEncoding = encoding; break; case "FontBBox": const fontBBox = this.readNumberArray(); properties.ascent = Math.max(fontBBox[3], fontBBox[1]); properties.descent = Math.min(fontBBox[1], fontBBox[3]); properties.ascentScaled = true; break; } } } } ;// CONCATENATED MODULE: ./src/core/type1_font.js function findBlock(streamBytes, signature, startIndex) { const streamBytesLength = streamBytes.length; const signatureLength = signature.length; const scanLength = streamBytesLength - signatureLength; let i = startIndex, found = false; while (i < scanLength) { let j = 0; while (j < signatureLength && streamBytes[i + j] === signature[j]) { j++; } if (j >= signatureLength) { i += j; while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { i++; } found = true; break; } i++; } return { found, length: i }; } function getHeaderBlock(stream, suggestedLength) { const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; const streamStartPos = stream.pos; let headerBytes, headerBytesLength, block; try { headerBytes = stream.getBytes(suggestedLength); headerBytesLength = headerBytes.length; } catch {} if (headerBytesLength === suggestedLength) { block = findBlock(headerBytes, EEXEC_SIGNATURE, suggestedLength - 2 * EEXEC_SIGNATURE.length); if (block.found && block.length === suggestedLength) { return { stream: new Stream(headerBytes), length: suggestedLength }; } } warn('Invalid "Length1" property in Type1 font -- trying to recover.'); stream.pos = streamStartPos; const SCAN_BLOCK_LENGTH = 2048; let actualLength; while (true) { const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); if (block.length === 0) { break; } stream.pos += block.length; if (block.found) { actualLength = stream.pos - streamStartPos; break; } } stream.pos = streamStartPos; if (actualLength) { return { stream: new Stream(stream.getBytes(actualLength)), length: actualLength }; } warn('Unable to recover "Length1" property in Type1 font -- using as is.'); return { stream: new Stream(stream.getBytes(suggestedLength)), length: suggestedLength }; } function getEexecBlock(stream, suggestedLength) { const eexecBytes = stream.getBytes(); if (eexecBytes.length === 0) { throw new FormatError("getEexecBlock - no font program found."); } return { stream: new Stream(eexecBytes), length: eexecBytes.length }; } class Type1Font { constructor(name, file, properties) { const PFB_HEADER_SIZE = 6; let headerBlockLength = properties.length1; let eexecBlockLength = properties.length2; let pfbHeader = file.peekBytes(PFB_HEADER_SIZE); const pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; if (pfbHeaderPresent) { file.skip(PFB_HEADER_SIZE); headerBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2]; } const headerBlock = getHeaderBlock(file, headerBlockLength); const headerBlockParser = new Type1Parser(headerBlock.stream, false, SEAC_ANALYSIS_ENABLED); headerBlockParser.extractFontHeader(properties); if (pfbHeaderPresent) { pfbHeader = file.getBytes(PFB_HEADER_SIZE); eexecBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2]; } const eexecBlock = getEexecBlock(file, eexecBlockLength); const eexecBlockParser = new Type1Parser(eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED); const data = eexecBlockParser.extractFontProgram(properties); for (const key in data.properties) { properties[key] = data.properties[key]; } const charstrings = data.charstrings; const type2Charstrings = this.getType2Charstrings(charstrings); const subrs = this.getType2Subrs(data.subrs); this.charstrings = charstrings; this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties); this.seacs = this.getSeacs(data.charstrings); } get numGlyphs() { return this.charstrings.length + 1; } getCharset() { const charset = [".notdef"]; for (const { glyphName } of this.charstrings) { charset.push(glyphName); } return charset; } getGlyphMapping(properties) { const charstrings = this.charstrings; if (properties.composite) { const charCodeToGlyphId = Object.create(null); for (let glyphId = 0, charstringsLen = charstrings.length; glyphId < charstringsLen; glyphId++) { const charCode = properties.cMap.charCodeOf(glyphId); charCodeToGlyphId[charCode] = glyphId + 1; } return charCodeToGlyphId; } const glyphNames = [".notdef"]; let builtInEncoding, glyphId; for (glyphId = 0; glyphId < charstrings.length; glyphId++) { glyphNames.push(charstrings[glyphId].glyphName); } const encoding = properties.builtInEncoding; if (encoding) { builtInEncoding = Object.create(null); for (const charCode in encoding) { glyphId = glyphNames.indexOf(encoding[charCode]); if (glyphId >= 0) { builtInEncoding[charCode] = glyphId; } } } return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); } hasGlyphId(id) { if (id < 0 || id >= this.numGlyphs) { return false; } if (id === 0) { return true; } const glyph = this.charstrings[id - 1]; return glyph.charstring.length > 0; } getSeacs(charstrings) { const seacMap = []; for (let i = 0, ii = charstrings.length; i < ii; i++) { const charstring = charstrings[i]; if (charstring.seac) { seacMap[i + 1] = charstring.seac; } } return seacMap; } getType2Charstrings(type1Charstrings) { const type2Charstrings = []; for (const type1Charstring of type1Charstrings) { type2Charstrings.push(type1Charstring.charstring); } return type2Charstrings; } getType2Subrs(type1Subrs) { let bias = 0; const count = type1Subrs.length; if (count < 1133) { bias = 107; } else if (count < 33769) { bias = 1131; } else { bias = 32768; } const type2Subrs = []; let i; for (i = 0; i < bias; i++) { type2Subrs.push([0x0b]); } for (i = 0; i < count; i++) { type2Subrs.push(type1Subrs[i]); } return type2Subrs; } wrap(name, glyphs, charstrings, subrs, properties) { const cff = new CFF(); cff.header = new CFFHeader(1, 0, 4, 4); cff.names = [name]; const topDict = new CFFTopDict(); topDict.setByName("version", 391); topDict.setByName("Notice", 392); topDict.setByName("FullName", 393); topDict.setByName("FamilyName", 394); topDict.setByName("Weight", 395); topDict.setByName("Encoding", null); topDict.setByName("FontMatrix", properties.fontMatrix); topDict.setByName("FontBBox", properties.bbox); topDict.setByName("charset", null); topDict.setByName("CharStrings", null); topDict.setByName("Private", null); cff.topDict = topDict; const strings = new CFFStrings(); strings.add("Version 0.11"); strings.add("See original notice"); strings.add(name); strings.add(name); strings.add("Medium"); cff.strings = strings; cff.globalSubrIndex = new CFFIndex(); const count = glyphs.length; const charsetArray = [".notdef"]; let i, ii; for (i = 0; i < count; i++) { const glyphName = charstrings[i].glyphName; const index = CFFStandardStrings.indexOf(glyphName); if (index === -1) { strings.add(glyphName); } charsetArray.push(glyphName); } cff.charset = new CFFCharset(false, 0, charsetArray); const charStringsIndex = new CFFIndex(); charStringsIndex.add([0x8b, 0x0e]); for (i = 0; i < count; i++) { charStringsIndex.add(glyphs[i]); } cff.charStrings = charStringsIndex; const privateDict = new CFFPrivateDict(); privateDict.setByName("Subrs", null); const fields = ["BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StemSnapH", "StemSnapV", "BlueShift", "BlueFuzz", "BlueScale", "LanguageGroup", "ExpansionFactor", "ForceBold", "StdHW", "StdVW"]; for (i = 0, ii = fields.length; i < ii; i++) { const field = fields[i]; if (!(field in properties.privateData)) { continue; } const value = properties.privateData[field]; if (Array.isArray(value)) { for (let j = value.length - 1; j > 0; j--) { value[j] -= value[j - 1]; } } privateDict.setByName(field, value); } cff.topDict.privateDict = privateDict; const subrIndex = new CFFIndex(); for (i = 0, ii = subrs.length; i < ii; i++) { subrIndex.add(subrs[i]); } privateDict.subrsIndex = subrIndex; const compiler = new CFFCompiler(cff); return compiler.compile(); } } ;// CONCATENATED MODULE: ./src/core/fonts.js const PRIVATE_USE_AREAS = [[0xe000, 0xf8ff], [0x100000, 0x10fffd]]; const PDF_GLYPH_SPACE_UNITS = 1000; const EXPORT_DATA_PROPERTIES = ["ascent", "bbox", "black", "bold", "charProcOperatorList", "composite", "cssFontInfo", "data", "defaultVMetrics", "defaultWidth", "descent", "fallbackName", "fontMatrix", "isInvalidPDFjsFont", "isType3Font", "italic", "loadedName", "mimetype", "missingFile", "name", "remeasure", "subtype", "systemFontInfo", "type", "vertical"]; const EXPORT_DATA_EXTRA_PROPERTIES = ["cMap", "defaultEncoding", "differences", "isMonospace", "isSerifFont", "isSymbolicFont", "seacMap", "toFontChar", "toUnicode", "vmetrics", "widths"]; function adjustWidths(properties) { if (!properties.fontMatrix) { return; } if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) { return; } const scale = 0.001 / properties.fontMatrix[0]; const glyphsWidths = properties.widths; for (const glyph in glyphsWidths) { glyphsWidths[glyph] *= scale; } properties.defaultWidth *= scale; } function adjustTrueTypeToUnicode(properties, isSymbolicFont, nameRecords) { if (properties.isInternalFont) { return; } if (properties.hasIncludedToUnicodeMap) { return; } if (properties.hasEncoding) { return; } if (properties.toUnicode instanceof IdentityToUnicodeMap) { return; } if (!isSymbolicFont) { return; } if (nameRecords.length === 0) { return; } if (properties.defaultEncoding === WinAnsiEncoding) { return; } for (const r of nameRecords) { if (!isWinNameRecord(r)) { return; } } const encoding = WinAnsiEncoding; const toUnicode = [], glyphsUnicodeMap = getGlyphsUnicode(); for (const charCode in encoding) { const glyphName = encoding[charCode]; if (glyphName === "") { continue; } const unicode = glyphsUnicodeMap[glyphName]; if (unicode === undefined) { continue; } toUnicode[charCode] = String.fromCharCode(unicode); } if (toUnicode.length > 0) { properties.toUnicode.amend(toUnicode); } } function adjustType1ToUnicode(properties, builtInEncoding) { if (properties.isInternalFont) { return; } if (properties.hasIncludedToUnicodeMap) { return; } if (builtInEncoding === properties.defaultEncoding) { return; } if (properties.toUnicode instanceof IdentityToUnicodeMap) { return; } const toUnicode = [], glyphsUnicodeMap = getGlyphsUnicode(); for (const charCode in builtInEncoding) { if (properties.hasEncoding) { if (properties.baseEncodingName || properties.differences[charCode] !== undefined) { continue; } } const glyphName = builtInEncoding[charCode]; const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); if (unicode !== -1) { toUnicode[charCode] = String.fromCharCode(unicode); } } if (toUnicode.length > 0) { properties.toUnicode.amend(toUnicode); } } function amendFallbackToUnicode(properties) { if (!properties.fallbackToUnicode) { return; } if (properties.toUnicode instanceof IdentityToUnicodeMap) { return; } const toUnicode = []; for (const charCode in properties.fallbackToUnicode) { if (properties.toUnicode.has(charCode)) { continue; } toUnicode[charCode] = properties.fallbackToUnicode[charCode]; } if (toUnicode.length > 0) { properties.toUnicode.amend(toUnicode); } } class fonts_Glyph { constructor(originalCharCode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) { this.originalCharCode = originalCharCode; this.fontChar = fontChar; this.unicode = unicode; this.accent = accent; this.width = width; this.vmetric = vmetric; this.operatorListId = operatorListId; this.isSpace = isSpace; this.isInFont = isInFont; } get category() { return shadow(this, "category", getCharUnicodeCategory(this.unicode), true); } } function int16(b0, b1) { return (b0 << 8) + b1; } function writeSignedInt16(bytes, index, value) { bytes[index + 1] = value; bytes[index] = value >>> 8; } function signedInt16(b0, b1) { const value = (b0 << 8) + b1; return value & 1 << 15 ? value - 0x10000 : value; } function writeUint32(bytes, index, value) { bytes[index + 3] = value & 0xff; bytes[index + 2] = value >>> 8; bytes[index + 1] = value >>> 16; bytes[index] = value >>> 24; } function int32(b0, b1, b2, b3) { return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; } function string16(value) { return String.fromCharCode(value >> 8 & 0xff, value & 0xff); } function safeString16(value) { if (value > 0x7fff) { value = 0x7fff; } else if (value < -0x8000) { value = -0x8000; } return String.fromCharCode(value >> 8 & 0xff, value & 0xff); } function isTrueTypeFile(file) { const header = file.peekBytes(4); return readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true"; } function isTrueTypeCollectionFile(file) { const header = file.peekBytes(4); return bytesToString(header) === "ttcf"; } function isOpenTypeFile(file) { const header = file.peekBytes(4); return bytesToString(header) === "OTTO"; } function isType1File(file) { const header = file.peekBytes(2); if (header[0] === 0x25 && header[1] === 0x21) { return true; } if (header[0] === 0x80 && header[1] === 0x01) { return true; } return false; } function isCFFFile(file) { const header = file.peekBytes(4); if (header[0] >= 1 && header[3] >= 1 && header[3] <= 4) { return true; } return false; } function getFontFileType(file, { type, subtype, composite }) { let fileType, fileSubtype; if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) { fileType = composite ? "CIDFontType2" : "TrueType"; } else if (isOpenTypeFile(file)) { fileType = composite ? "CIDFontType2" : "OpenType"; } else if (isType1File(file)) { if (composite) { fileType = "CIDFontType0"; } else { fileType = type === "MMType1" ? "MMType1" : "Type1"; } } else if (isCFFFile(file)) { if (composite) { fileType = "CIDFontType0"; fileSubtype = "CIDFontType0C"; } else { fileType = type === "MMType1" ? "MMType1" : "Type1"; fileSubtype = "Type1C"; } } else { warn("getFontFileType: Unable to detect correct font file Type/Subtype."); fileType = type; fileSubtype = subtype; } return [fileType, fileSubtype]; } function applyStandardFontGlyphMap(map, glyphMap) { for (const charCode in glyphMap) { map[+charCode] = glyphMap[charCode]; } } function buildToFontChar(encoding, glyphsUnicodeMap, differences) { const toFontChar = []; let unicode; for (let i = 0, ii = encoding.length; i < ii; i++) { unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); if (unicode !== -1) { toFontChar[i] = unicode; } } for (const charCode in differences) { unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap); if (unicode !== -1) { toFontChar[+charCode] = unicode; } } return toFontChar; } function isMacNameRecord(r) { return r.platform === 1 && r.encoding === 0 && r.language === 0; } function isWinNameRecord(r) { return r.platform === 3 && r.encoding === 1 && r.language === 0x409; } function convertCidString(charCode, cid, shouldThrow = false) { switch (cid.length) { case 1: return cid.charCodeAt(0); case 2: return cid.charCodeAt(0) << 8 | cid.charCodeAt(1); } const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`; if (shouldThrow) { throw new FormatError(msg); } warn(msg); return cid; } function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId, toUnicode) { const newMap = Object.create(null); const toUnicodeExtraMap = new Map(); const toFontChar = []; const usedGlyphIds = new Set(); let privateUseAreaIndex = 0; const privateUseOffetStart = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; let nextAvailableFontCharCode = privateUseOffetStart; let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; const isInPrivateArea = code => PRIVATE_USE_AREAS[0][0] <= code && code <= PRIVATE_USE_AREAS[0][1] || PRIVATE_USE_AREAS[1][0] <= code && code <= PRIVATE_USE_AREAS[1][1]; for (let originalCharCode in charCodeToGlyphId) { originalCharCode |= 0; let glyphId = charCodeToGlyphId[originalCharCode]; if (!hasGlyph(glyphId)) { continue; } if (nextAvailableFontCharCode > privateUseOffetEnd) { privateUseAreaIndex++; if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) { warn("Ran out of space in font private use area."); break; } nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; } const fontCharCode = nextAvailableFontCharCode++; if (glyphId === 0) { glyphId = newGlyphZeroId; } let unicode = toUnicode.get(originalCharCode); if (typeof unicode === "string") { unicode = unicode.codePointAt(0); } if (unicode && !isInPrivateArea(unicode) && !usedGlyphIds.has(glyphId)) { toUnicodeExtraMap.set(unicode, glyphId); usedGlyphIds.add(glyphId); } newMap[fontCharCode] = glyphId; toFontChar[originalCharCode] = fontCharCode; } return { toFontChar, charCodeToGlyphId: newMap, toUnicodeExtraMap, nextAvailableFontCharCode }; } function getRanges(glyphs, toUnicodeExtraMap, numGlyphs) { const codes = []; for (const charCode in glyphs) { if (glyphs[charCode] >= numGlyphs) { continue; } codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] }); } if (toUnicodeExtraMap) { for (const [unicode, glyphId] of toUnicodeExtraMap) { if (glyphId >= numGlyphs) { continue; } codes.push({ fontCharCode: unicode, glyphId }); } } if (codes.length === 0) { codes.push({ fontCharCode: 0, glyphId: 0 }); } codes.sort(function fontGetRangesSort(a, b) { return a.fontCharCode - b.fontCharCode; }); const ranges = []; const length = codes.length; for (let n = 0; n < length;) { const start = codes[n].fontCharCode; const codeIndices = [codes[n].glyphId]; ++n; let end = start; while (n < length && end + 1 === codes[n].fontCharCode) { codeIndices.push(codes[n].glyphId); ++end; ++n; if (end === 0xffff) { break; } } ranges.push([start, end, codeIndices]); } return ranges; } function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) { const ranges = getRanges(glyphs, toUnicodeExtraMap, numGlyphs); const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1; let cmap = "\x00\x00" + string16(numTables) + "\x00\x03" + "\x00\x01" + string32(4 + numTables * 8); let i, ii, j, jj; for (i = ranges.length - 1; i >= 0; --i) { if (ranges[i][0] <= 0xffff) { break; } } const bmpLength = i + 1; if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) { ranges[i][1] = 0xfffe; } const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; const segCount = bmpLength + trailingRangesCount; const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); let startCount = ""; let endCount = ""; let idDeltas = ""; let idRangeOffsets = ""; let glyphsIds = ""; let bias = 0; let range, start, end, codes; for (i = 0, ii = bmpLength; i < ii; i++) { range = ranges[i]; start = range[0]; end = range[1]; startCount += string16(start); endCount += string16(end); codes = range[2]; let contiguous = true; for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { contiguous = false; break; } } if (!contiguous) { const offset = (segCount - i) * 2 + bias * 2; bias += end - start + 1; idDeltas += string16(0); idRangeOffsets += string16(offset); for (j = 0, jj = codes.length; j < jj; ++j) { glyphsIds += string16(codes[j]); } } else { const startCode = codes[0]; idDeltas += string16(startCode - start & 0xffff); idRangeOffsets += string16(0); } } if (trailingRangesCount > 0) { endCount += "\xFF\xFF"; startCount += "\xFF\xFF"; idDeltas += "\x00\x01"; idRangeOffsets += "\x00\x00"; } const format314 = "\x00\x00" + string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds; let format31012 = ""; let header31012 = ""; if (numTables > 1) { cmap += "\x00\x03" + "\x00\x0A" + string32(4 + numTables * 8 + 4 + format314.length); format31012 = ""; for (i = 0, ii = ranges.length; i < ii; i++) { range = ranges[i]; start = range[0]; codes = range[2]; let code = codes[0]; for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { end = range[0] + j - 1; format31012 += string32(start) + string32(end) + string32(code); start = end + 1; code = codes[j]; } } format31012 += string32(start) + string32(range[1]) + string32(code); } header31012 = "\x00\x0C" + "\x00\x00" + string32(format31012.length + 16) + "\x00\x00\x00\x00" + string32(format31012.length / 12); } return cmap + "\x00\x04" + string16(format314.length + 4) + format314 + header31012 + format31012; } function validateOS2Table(os2, file) { file.pos = (file.start || 0) + os2.offset; const version = file.getUint16(); file.skip(60); const selection = file.getUint16(); if (version < 4 && selection & 0x0300) { return false; } const firstChar = file.getUint16(); const lastChar = file.getUint16(); if (firstChar > lastChar) { return false; } file.skip(6); const usWinAscent = file.getUint16(); if (usWinAscent === 0) { return false; } os2.data[8] = os2.data[9] = 0; return true; } function createOS2Table(properties, charstrings, override) { override ||= { unitsPerEm: 0, yMax: 0, yMin: 0, ascent: 0, descent: 0 }; let ulUnicodeRange1 = 0; let ulUnicodeRange2 = 0; let ulUnicodeRange3 = 0; let ulUnicodeRange4 = 0; let firstCharIndex = null; let lastCharIndex = 0; let position = -1; if (charstrings) { for (let code in charstrings) { code |= 0; if (firstCharIndex > code || !firstCharIndex) { firstCharIndex = code; } if (lastCharIndex < code) { lastCharIndex = code; } position = getUnicodeRangeFor(code, position); if (position < 32) { ulUnicodeRange1 |= 1 << position; } else if (position < 64) { ulUnicodeRange2 |= 1 << position - 32; } else if (position < 96) { ulUnicodeRange3 |= 1 << position - 64; } else if (position < 123) { ulUnicodeRange4 |= 1 << position - 96; } else { throw new FormatError("Unicode ranges Bits > 123 are reserved for internal usage"); } } if (lastCharIndex > 0xffff) { lastCharIndex = 0xffff; } } else { firstCharIndex = 0; lastCharIndex = 255; } const bbox = properties.bbox || [0, 0, 0, 0]; const unitsPerEm = override.unitsPerEm || 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; const scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS; const typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3])); let typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1])); if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { typoDescent = -typoDescent; } const winAscent = override.yMax || typoAscent; const winDescent = -override.yMin || -typoDescent; return "\x00\x03" + "\x02\x24" + "\x01\xF4" + "\x00\x05" + "\x00\x00" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x00\x8C" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x01\xDF" + "\x00\x31" + "\x01\x02" + "\x00\x00" + "\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + "\x00\x00\x00\x00\x00\x00" + string32(ulUnicodeRange1) + string32(ulUnicodeRange2) + string32(ulUnicodeRange3) + string32(ulUnicodeRange4) + "\x2A\x32\x31\x2A" + string16(properties.italicAngle ? 1 : 0) + string16(firstCharIndex || properties.firstChar) + string16(lastCharIndex || properties.lastChar) + string16(typoAscent) + string16(typoDescent) + "\x00\x64" + string16(winAscent) + string16(winDescent) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + string16(properties.xHeight) + string16(properties.capHeight) + string16(0) + string16(firstCharIndex || properties.firstChar) + "\x00\x03"; } function createPostTable(properties) { const angle = Math.floor(properties.italicAngle * 2 ** 16); return "\x00\x03\x00\x00" + string32(angle) + "\x00\x00" + "\x00\x00" + string32(properties.fixedPitch ? 1 : 0) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00"; } function createPostscriptName(name) { return name.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63); } function createNameTable(name, proto) { if (!proto) { proto = [[], []]; } const strings = [proto[0][0] || "Original licence", proto[0][1] || name, proto[0][2] || "Unknown", proto[0][3] || "uniqueID", proto[0][4] || name, proto[0][5] || "Version 0.11", proto[0][6] || createPostscriptName(name), proto[0][7] || "Unknown", proto[0][8] || "Unknown", proto[0][9] || "Unknown"]; const stringsUnicode = []; let i, ii, j, jj, str; for (i = 0, ii = strings.length; i < ii; i++) { str = proto[1][i] || strings[i]; const strBufUnicode = []; for (j = 0, jj = str.length; j < jj; j++) { strBufUnicode.push(string16(str.charCodeAt(j))); } stringsUnicode.push(strBufUnicode.join("")); } const names = [strings, stringsUnicode]; const platforms = ["\x00\x01", "\x00\x03"]; const encodings = ["\x00\x00", "\x00\x01"]; const languages = ["\x00\x00", "\x04\x09"]; const namesRecordCount = strings.length * platforms.length; let nameTable = "\x00\x00" + string16(namesRecordCount) + string16(namesRecordCount * 12 + 6); let strOffset = 0; for (i = 0, ii = platforms.length; i < ii; i++) { const strs = names[i]; for (j = 0, jj = strs.length; j < jj; j++) { str = strs[j]; const nameRecord = platforms[i] + encodings[i] + languages[i] + string16(j) + string16(str.length) + string16(strOffset); nameTable += nameRecord; strOffset += str.length; } } nameTable += strings.join("") + stringsUnicode.join(""); return nameTable; } class Font { constructor(name, file, properties) { this.name = name; this.psName = null; this.mimetype = null; this.disableFontFace = false; this.loadedName = properties.loadedName; this.isType3Font = properties.isType3Font; this.missingFile = false; this.cssFontInfo = properties.cssFontInfo; this._charsCache = Object.create(null); this._glyphCache = Object.create(null); let isSerifFont = !!(properties.flags & FontFlags.Serif); if (!isSerifFont && !properties.isSimulatedFlags) { const baseName = name.replaceAll(/[,_]/g, "-").split("-")[0], serifFonts = getSerifFonts(); for (const namePart of baseName.split("+")) { if (serifFonts[namePart]) { isSerifFont = true; break; } } } this.isSerifFont = isSerifFont; this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); this.isMonospace = !!(properties.flags & FontFlags.FixedPitch); let { type, subtype } = properties; this.type = type; this.subtype = subtype; this.systemFontInfo = properties.systemFontInfo; const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/); this.isInvalidPDFjsFont = !!matches; if (this.isInvalidPDFjsFont) { this.fallbackName = matches[1]; } else if (this.isMonospace) { this.fallbackName = "monospace"; } else if (this.isSerifFont) { this.fallbackName = "serif"; } else { this.fallbackName = "sans-serif"; } if (this.systemFontInfo?.guessFallback) { this.systemFontInfo.guessFallback = false; this.systemFontInfo.css += `,${this.fallbackName}`; } this.differences = properties.differences; this.widths = properties.widths; this.defaultWidth = properties.defaultWidth; this.composite = properties.composite; this.cMap = properties.cMap; this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS; this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS; this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS; this.lineHeight = this.ascent - this.descent; this.fontMatrix = properties.fontMatrix; this.bbox = properties.bbox; this.defaultEncoding = properties.defaultEncoding; this.toUnicode = properties.toUnicode; this.toFontChar = []; if (properties.type === "Type3") { for (let charCode = 0; charCode < 256; charCode++) { this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode]; } return; } this.cidEncoding = properties.cidEncoding || ""; this.vertical = !!properties.vertical; if (this.vertical) { this.vmetrics = properties.vmetrics; this.defaultVMetrics = properties.defaultVMetrics; } if (!file || file.isEmpty) { if (file) { warn('Font file is empty in "' + name + '" (' + this.loadedName + ")"); } this.fallbackToSystemFont(properties); return; } [type, subtype] = getFontFileType(file, properties); if (type !== this.type || subtype !== this.subtype) { info("Inconsistent font file Type/SubType, expected: " + `${this.type}/${this.subtype} but found: ${type}/${subtype}.`); } let data; try { switch (type) { case "MMType1": info("MMType1 font (" + name + "), falling back to Type1."); case "Type1": case "CIDFontType0": this.mimetype = "font/opentype"; const cff = subtype === "Type1C" || subtype === "CIDFontType0C" ? new CFFFont(file, properties) : new Type1Font(name, file, properties); adjustWidths(properties); data = this.convert(name, cff, properties); break; case "OpenType": case "TrueType": case "CIDFontType2": this.mimetype = "font/opentype"; data = this.checkAndRepair(name, file, properties); if (this.isOpenType) { adjustWidths(properties); type = "OpenType"; } break; default: throw new FormatError(`Font ${type} is not supported`); } } catch (e) { warn(e); this.fallbackToSystemFont(properties); return; } amendFallbackToUnicode(properties); this.data = data; this.type = type; this.subtype = subtype; this.fontMatrix = properties.fontMatrix; this.widths = properties.widths; this.defaultWidth = properties.defaultWidth; this.toUnicode = properties.toUnicode; this.seacMap = properties.seacMap; } get renderer() { const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); return shadow(this, "renderer", renderer); } exportData(extraProperties = false) { const exportDataProperties = extraProperties ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] : EXPORT_DATA_PROPERTIES; const data = Object.create(null); let property, value; for (property of exportDataProperties) { value = this[property]; if (value !== undefined) { data[property] = value; } } return data; } fallbackToSystemFont(properties) { this.missingFile = true; const { name, type } = this; let fontName = normalizeFontName(name); const stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap(); const isStandardFont = !!stdFontMap[fontName]; const isMappedToStandardFont = !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]); fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; const fontBasicMetricsMap = getFontBasicMetrics(); const metrics = fontBasicMetricsMap[fontName]; if (metrics) { if (isNaN(this.ascent)) { this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS; } if (isNaN(this.descent)) { this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS; } if (isNaN(this.capHeight)) { this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS; } } this.bold = /bold/gi.test(fontName); this.italic = /oblique|italic/gi.test(fontName); this.black = /Black/g.test(name); const isNarrow = /Narrow/g.test(name); this.remeasure = (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0; if ((isStandardFont || isMappedToStandardFont) && type === "CIDFontType2" && this.cidEncoding.startsWith("Identity-")) { const cidToGidMap = properties.cidToGidMap; const map = []; applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts()); if (/Arial-?Black/i.test(name)) { applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack()); } else if (/Calibri/i.test(name)) { applyStandardFontGlyphMap(map, getSupplementalGlyphMapForCalibri()); } if (cidToGidMap) { for (const charCode in map) { const cid = map[charCode]; if (cidToGidMap[cid] !== undefined) { map[+charCode] = cidToGidMap[cid]; } } if (cidToGidMap.length !== this.toUnicode.length && properties.hasIncludedToUnicodeMap && this.toUnicode instanceof IdentityToUnicodeMap) { this.toUnicode.forEach(function (charCode, unicodeCharCode) { const cid = map[charCode]; if (cidToGidMap[cid] === undefined) { map[+charCode] = unicodeCharCode; } }); } } if (!(this.toUnicode instanceof IdentityToUnicodeMap)) { this.toUnicode.forEach(function (charCode, unicodeCharCode) { map[+charCode] = unicodeCharCode; }); } this.toFontChar = map; this.toUnicode = new ToUnicodeMap(map); } else if (/Symbol/i.test(fontName)) { this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(), this.differences); } else if (/Dingbats/i.test(fontName)) { this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), this.differences); } else if (isStandardFont) { const map = buildToFontChar(this.defaultEncoding, getGlyphsUnicode(), this.differences); if (type === "CIDFontType2" && !this.cidEncoding.startsWith("Identity-") && !(this.toUnicode instanceof IdentityToUnicodeMap)) { this.toUnicode.forEach(function (charCode, unicodeCharCode) { map[+charCode] = unicodeCharCode; }); } this.toFontChar = map; } else { const glyphsUnicodeMap = getGlyphsUnicode(); const map = []; this.toUnicode.forEach((charCode, unicodeCharCode) => { if (!this.composite) { const glyphName = this.differences[charCode] || this.defaultEncoding[charCode]; const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); if (unicode !== -1) { unicodeCharCode = unicode; } } map[+charCode] = unicodeCharCode; }); if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) { if (/Tahoma|Verdana/i.test(name)) { applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts()); } } this.toFontChar = map; } amendFallbackToUnicode(properties); this.loadedName = fontName.split("-")[0]; } checkAndRepair(name, font, properties) { const VALID_TABLES = ["OS/2", "cmap", "head", "hhea", "hmtx", "maxp", "name", "post", "loca", "glyf", "fpgm", "prep", "cvt ", "CFF "]; function readTables(file, numTables) { const tables = Object.create(null); tables["OS/2"] = null; tables.cmap = null; tables.head = null; tables.hhea = null; tables.hmtx = null; tables.maxp = null; tables.name = null; tables.post = null; for (let i = 0; i < numTables; i++) { const table = readTableEntry(file); if (!VALID_TABLES.includes(table.tag)) { continue; } if (table.length === 0) { continue; } tables[table.tag] = table; } return tables; } function readTableEntry(file) { const tag = file.getString(4); const checksum = file.getInt32() >>> 0; const offset = file.getInt32() >>> 0; const length = file.getInt32() >>> 0; const previousPosition = file.pos; file.pos = file.start || 0; file.skip(offset); const data = file.getBytes(length); file.pos = previousPosition; if (tag === "head") { data[8] = data[9] = data[10] = data[11] = 0; data[17] |= 0x20; } return { tag, checksum, length, offset, data }; } function readOpenTypeHeader(ttf) { return { version: ttf.getString(4), numTables: ttf.getUint16(), searchRange: ttf.getUint16(), entrySelector: ttf.getUint16(), rangeShift: ttf.getUint16() }; } function readTrueTypeCollectionHeader(ttc) { const ttcTag = ttc.getString(4); assert(ttcTag === "ttcf", "Must be a TrueType Collection font."); const majorVersion = ttc.getUint16(); const minorVersion = ttc.getUint16(); const numFonts = ttc.getInt32() >>> 0; const offsetTable = []; for (let i = 0; i < numFonts; i++) { offsetTable.push(ttc.getInt32() >>> 0); } const header = { ttcTag, majorVersion, minorVersion, numFonts, offsetTable }; switch (majorVersion) { case 1: return header; case 2: header.dsigTag = ttc.getInt32() >>> 0; header.dsigLength = ttc.getInt32() >>> 0; header.dsigOffset = ttc.getInt32() >>> 0; return header; } throw new FormatError(`Invalid TrueType Collection majorVersion: ${majorVersion}.`); } function readTrueTypeCollectionData(ttc, fontName) { const { numFonts, offsetTable } = readTrueTypeCollectionHeader(ttc); const fontNameParts = fontName.split("+"); let fallbackData; for (let i = 0; i < numFonts; i++) { ttc.pos = (ttc.start || 0) + offsetTable[i]; const potentialHeader = readOpenTypeHeader(ttc); const potentialTables = readTables(ttc, potentialHeader.numTables); if (!potentialTables.name) { throw new FormatError('TrueType Collection font must contain a "name" table.'); } const [nameTable] = readNameTable(potentialTables.name); for (let j = 0, jj = nameTable.length; j < jj; j++) { for (let k = 0, kk = nameTable[j].length; k < kk; k++) { const nameEntry = nameTable[j][k]?.replaceAll(/\s/g, ""); if (!nameEntry) { continue; } if (nameEntry === fontName) { return { header: potentialHeader, tables: potentialTables }; } if (fontNameParts.length < 2) { continue; } for (const part of fontNameParts) { if (nameEntry === part) { fallbackData = { name: part, header: potentialHeader, tables: potentialTables }; } } } } } if (fallbackData) { warn(`TrueType Collection does not contain "${fontName}" font, ` + `falling back to "${fallbackData.name}" font instead.`); return { header: fallbackData.header, tables: fallbackData.tables }; } throw new FormatError(`TrueType Collection does not contain "${fontName}" font.`); } function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) { if (!cmap) { warn("No cmap table available."); return { platformId: -1, encodingId: -1, mappings: [], hasShortCmap: false }; } let segment; let start = (file.start || 0) + cmap.offset; file.pos = start; file.skip(2); const numTables = file.getUint16(); let potentialTable; let canBreak = false; for (let i = 0; i < numTables; i++) { const platformId = file.getUint16(); const encodingId = file.getUint16(); const offset = file.getInt32() >>> 0; let useTable = false; if (potentialTable?.platformId === platformId && potentialTable?.encodingId === encodingId) { continue; } if (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 3)) { useTable = true; } else if (platformId === 1 && encodingId === 0) { useTable = true; } else if (platformId === 3 && encodingId === 1 && (hasEncoding || !potentialTable)) { useTable = true; if (!isSymbolicFont) { canBreak = true; } } else if (isSymbolicFont && platformId === 3 && encodingId === 0) { useTable = true; let correctlySorted = true; if (i < numTables - 1) { const nextBytes = file.peekBytes(2), nextPlatformId = int16(nextBytes[0], nextBytes[1]); if (nextPlatformId < platformId) { correctlySorted = false; } } if (correctlySorted) { canBreak = true; } } if (useTable) { potentialTable = { platformId, encodingId, offset }; } if (canBreak) { break; } } if (potentialTable) { file.pos = start + potentialTable.offset; } if (!potentialTable || file.peekByte() === -1) { warn("Could not find a preferred cmap table."); return { platformId: -1, encodingId: -1, mappings: [], hasShortCmap: false }; } const format = file.getUint16(); let hasShortCmap = false; const mappings = []; let j, glyphId; if (format === 0) { file.skip(2 + 2); for (j = 0; j < 256; j++) { const index = file.getByte(); if (!index) { continue; } mappings.push({ charCode: j, glyphId: index }); } hasShortCmap = true; } else if (format === 2) { file.skip(2 + 2); const subHeaderKeys = []; let maxSubHeaderKey = 0; for (let i = 0; i < 256; i++) { const subHeaderKey = file.getUint16() >> 3; subHeaderKeys.push(subHeaderKey); maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey); } const subHeaders = []; for (let i = 0; i <= maxSubHeaderKey; i++) { subHeaders.push({ firstCode: file.getUint16(), entryCount: file.getUint16(), idDelta: signedInt16(file.getByte(), file.getByte()), idRangePos: file.pos + file.getUint16() }); } for (let i = 0; i < 256; i++) { if (subHeaderKeys[i] === 0) { file.pos = subHeaders[0].idRangePos + 2 * i; glyphId = file.getUint16(); mappings.push({ charCode: i, glyphId }); } else { const s = subHeaders[subHeaderKeys[i]]; for (j = 0; j < s.entryCount; j++) { const charCode = (i << 8) + j + s.firstCode; file.pos = s.idRangePos + 2 * j; glyphId = file.getUint16(); if (glyphId !== 0) { glyphId = (glyphId + s.idDelta) % 65536; } mappings.push({ charCode, glyphId }); } } } } else if (format === 4) { file.skip(2 + 2); const segCount = file.getUint16() >> 1; file.skip(6); const segments = []; let segIndex; for (segIndex = 0; segIndex < segCount; segIndex++) { segments.push({ end: file.getUint16() }); } file.skip(2); for (segIndex = 0; segIndex < segCount; segIndex++) { segments[segIndex].start = file.getUint16(); } for (segIndex = 0; segIndex < segCount; segIndex++) { segments[segIndex].delta = file.getUint16(); } let offsetsCount = 0, offsetIndex; for (segIndex = 0; segIndex < segCount; segIndex++) { segment = segments[segIndex]; const rangeOffset = file.getUint16(); if (!rangeOffset) { segment.offsetIndex = -1; continue; } offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); segment.offsetIndex = offsetIndex; offsetsCount = Math.max(offsetsCount, offsetIndex + segment.end - segment.start + 1); } const offsets = []; for (j = 0; j < offsetsCount; j++) { offsets.push(file.getUint16()); } for (segIndex = 0; segIndex < segCount; segIndex++) { segment = segments[segIndex]; start = segment.start; const end = segment.end; const delta = segment.delta; offsetIndex = segment.offsetIndex; for (j = start; j <= end; j++) { if (j === 0xffff) { continue; } glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start]; glyphId = glyphId + delta & 0xffff; mappings.push({ charCode: j, glyphId }); } } } else if (format === 6) { file.skip(2 + 2); const firstCode = file.getUint16(); const entryCount = file.getUint16(); for (j = 0; j < entryCount; j++) { glyphId = file.getUint16(); const charCode = firstCode + j; mappings.push({ charCode, glyphId }); } } else if (format === 12) { file.skip(2 + 4 + 4); const nGroups = file.getInt32() >>> 0; for (j = 0; j < nGroups; j++) { const startCharCode = file.getInt32() >>> 0; const endCharCode = file.getInt32() >>> 0; let glyphCode = file.getInt32() >>> 0; for (let charCode = startCharCode; charCode <= endCharCode; charCode++) { mappings.push({ charCode, glyphId: glyphCode++ }); } } } else { warn("cmap table has unsupported format: " + format); return { platformId: -1, encodingId: -1, mappings: [], hasShortCmap: false }; } mappings.sort(function (a, b) { return a.charCode - b.charCode; }); for (let i = 1; i < mappings.length; i++) { if (mappings[i - 1].charCode === mappings[i].charCode) { mappings.splice(i, 1); i--; } } return { platformId: potentialTable.platformId, encodingId: potentialTable.encodingId, mappings, hasShortCmap }; } function sanitizeMetrics(file, header, metrics, headTable, numGlyphs, dupFirstEntry) { if (!header) { if (metrics) { metrics.data = null; } return; } file.pos = (file.start || 0) + header.offset; file.pos += 4; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; file.pos += 2; const caretOffset = file.getUint16(); file.pos += 8; file.pos += 2; let numOfMetrics = file.getUint16(); if (caretOffset !== 0) { const macStyle = int16(headTable.data[44], headTable.data[45]); if (!(macStyle & 2)) { header.data[22] = 0; header.data[23] = 0; } } if (numOfMetrics > numGlyphs) { info(`The numOfMetrics (${numOfMetrics}) should not be ` + `greater than the numGlyphs (${numGlyphs}).`); numOfMetrics = numGlyphs; header.data[34] = (numOfMetrics & 0xff00) >> 8; header.data[35] = numOfMetrics & 0x00ff; } const numOfSidebearings = numGlyphs - numOfMetrics; const numMissing = numOfSidebearings - (metrics.length - numOfMetrics * 4 >> 1); if (numMissing > 0) { const entries = new Uint8Array(metrics.length + numMissing * 2); entries.set(metrics.data); if (dupFirstEntry) { entries[metrics.length] = metrics.data[2]; entries[metrics.length + 1] = metrics.data[3]; } metrics.data = entries; } } function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, hintsValid) { const glyphProfile = { length: 0, sizeOfInstructions: 0 }; if (sourceStart < 0 || sourceStart >= source.length || sourceEnd > source.length || sourceEnd - sourceStart <= 12) { return glyphProfile; } const glyf = source.subarray(sourceStart, sourceEnd); const xMin = signedInt16(glyf[2], glyf[3]); const yMin = signedInt16(glyf[4], glyf[5]); const xMax = signedInt16(glyf[6], glyf[7]); const yMax = signedInt16(glyf[8], glyf[9]); if (xMin > xMax) { writeSignedInt16(glyf, 2, xMax); writeSignedInt16(glyf, 6, xMin); } if (yMin > yMax) { writeSignedInt16(glyf, 4, yMax); writeSignedInt16(glyf, 8, yMin); } const contoursCount = signedInt16(glyf[0], glyf[1]); if (contoursCount < 0) { if (contoursCount < -1) { return glyphProfile; } dest.set(glyf, destStart); glyphProfile.length = glyf.length; return glyphProfile; } let i, j = 10, flagsCount = 0; for (i = 0; i < contoursCount; i++) { const endPoint = glyf[j] << 8 | glyf[j + 1]; flagsCount = endPoint + 1; j += 2; } const instructionsStart = j; const instructionsLength = glyf[j] << 8 | glyf[j + 1]; glyphProfile.sizeOfInstructions = instructionsLength; j += 2 + instructionsLength; const instructionsEnd = j; let coordinatesLength = 0; for (i = 0; i < flagsCount; i++) { const flag = glyf[j++]; if (flag & 0xc0) { glyf[j - 1] = flag & 0x3f; } let xLength = 2; if (flag & 2) { xLength = 1; } else if (flag & 16) { xLength = 0; } let yLength = 2; if (flag & 4) { yLength = 1; } else if (flag & 32) { yLength = 0; } const xyLength = xLength + yLength; coordinatesLength += xyLength; if (flag & 8) { const repeat = glyf[j++]; if (repeat === 0) { glyf[j - 1] ^= 8; } i += repeat; coordinatesLength += repeat * xyLength; } } if (coordinatesLength === 0) { return glyphProfile; } let glyphDataLength = j + coordinatesLength; if (glyphDataLength > glyf.length) { return glyphProfile; } if (!hintsValid && instructionsLength > 0) { dest.set(glyf.subarray(0, instructionsStart), destStart); dest.set([0, 0], destStart + instructionsStart); dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2); glyphDataLength -= instructionsLength; if (glyf.length - glyphDataLength > 3) { glyphDataLength = glyphDataLength + 3 & ~3; } glyphProfile.length = glyphDataLength; return glyphProfile; } if (glyf.length - glyphDataLength > 3) { glyphDataLength = glyphDataLength + 3 & ~3; dest.set(glyf.subarray(0, glyphDataLength), destStart); glyphProfile.length = glyphDataLength; return glyphProfile; } dest.set(glyf, destStart); glyphProfile.length = glyf.length; return glyphProfile; } function sanitizeHead(head, numGlyphs, locaLength) { const data = head.data; const version = int32(data[0], data[1], data[2], data[3]); if (version >> 16 !== 1) { info("Attempting to fix invalid version in head table: " + version); data[0] = 0; data[1] = 1; data[2] = 0; data[3] = 0; } const indexToLocFormat = int16(data[50], data[51]); if (indexToLocFormat < 0 || indexToLocFormat > 1) { info("Attempting to fix invalid indexToLocFormat in head table: " + indexToLocFormat); const numGlyphsPlusOne = numGlyphs + 1; if (locaLength === numGlyphsPlusOne << 1) { data[50] = 0; data[51] = 0; } else if (locaLength === numGlyphsPlusOne << 2) { data[50] = 0; data[51] = 1; } else { throw new FormatError("Could not fix indexToLocFormat: " + indexToLocFormat); } } } function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions) { let itemSize, itemDecode, itemEncode; if (isGlyphLocationsLong) { itemSize = 4; itemDecode = function fontItemDecodeLong(data, offset) { return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]; }; itemEncode = function fontItemEncodeLong(data, offset, value) { data[offset] = value >>> 24 & 0xff; data[offset + 1] = value >> 16 & 0xff; data[offset + 2] = value >> 8 & 0xff; data[offset + 3] = value & 0xff; }; } else { itemSize = 2; itemDecode = function fontItemDecode(data, offset) { return data[offset] << 9 | data[offset + 1] << 1; }; itemEncode = function fontItemEncode(data, offset, value) { data[offset] = value >> 9 & 0xff; data[offset + 1] = value >> 1 & 0xff; }; } const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; const locaDataSize = itemSize * (1 + numGlyphsOut); const locaData = new Uint8Array(locaDataSize); locaData.set(loca.data.subarray(0, locaDataSize)); loca.data = locaData; const oldGlyfData = glyf.data; const oldGlyfDataLength = oldGlyfData.length; const newGlyfData = new Uint8Array(oldGlyfDataLength); let i, j; const locaEntries = []; for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) { let offset = itemDecode(locaData, j); if (offset > oldGlyfDataLength) { offset = oldGlyfDataLength; } locaEntries.push({ index: i, offset, endOffset: 0 }); } locaEntries.sort((a, b) => { return a.offset - b.offset; }); for (i = 0; i < numGlyphs; i++) { locaEntries[i].endOffset = locaEntries[i + 1].offset; } locaEntries.sort((a, b) => { return a.index - b.index; }); for (i = 0; i < numGlyphs; i++) { const { offset, endOffset } = locaEntries[i]; if (offset !== 0 || endOffset !== 0) { break; } const nextOffset = locaEntries[i + 1].offset; if (nextOffset === 0) { continue; } locaEntries[i].endOffset = nextOffset; break; } const missingGlyphs = Object.create(null); let writeOffset = 0; itemEncode(locaData, 0, writeOffset); for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { const glyphProfile = sanitizeGlyph(oldGlyfData, locaEntries[i].offset, locaEntries[i].endOffset, newGlyfData, writeOffset, hintsValid); const newLength = glyphProfile.length; if (newLength === 0) { missingGlyphs[i] = true; } if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) { maxSizeOfInstructions = glyphProfile.sizeOfInstructions; } writeOffset += newLength; itemEncode(locaData, j, writeOffset); } if (writeOffset === 0) { const simpleGlyph = new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]); for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) { itemEncode(locaData, j, simpleGlyph.length); } glyf.data = simpleGlyph; } else if (dupFirstEntry) { const firstEntryLength = itemDecode(locaData, itemSize); if (newGlyfData.length > firstEntryLength + writeOffset) { glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset); } else { glyf.data = new Uint8Array(firstEntryLength + writeOffset); glyf.data.set(newGlyfData.subarray(0, writeOffset)); } glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset); itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength); } else { glyf.data = newGlyfData.subarray(0, writeOffset); } return { missingGlyphs, maxSizeOfInstructions }; } function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) { const start = (font.start || 0) + post.offset; font.pos = start; const length = post.length, end = start + length; const version = font.getInt32(); font.skip(28); let glyphNames; let valid = true; let i; switch (version) { case 0x00010000: glyphNames = MacStandardGlyphOrdering; break; case 0x00020000: const numGlyphs = font.getUint16(); if (numGlyphs !== maxpNumGlyphs) { valid = false; break; } const glyphNameIndexes = []; for (i = 0; i < numGlyphs; ++i) { const index = font.getUint16(); if (index >= 32768) { valid = false; break; } glyphNameIndexes.push(index); } if (!valid) { break; } const customNames = [], strBuf = []; while (font.pos < end) { const stringLength = font.getByte(); strBuf.length = stringLength; for (i = 0; i < stringLength; ++i) { strBuf[i] = String.fromCharCode(font.getByte()); } customNames.push(strBuf.join("")); } glyphNames = []; for (i = 0; i < numGlyphs; ++i) { const j = glyphNameIndexes[i]; if (j < 258) { glyphNames.push(MacStandardGlyphOrdering[j]); continue; } glyphNames.push(customNames[j - 258]); } break; case 0x00030000: break; default: warn("Unknown/unsupported post table version " + version); valid = false; if (propertiesObj.defaultEncoding) { glyphNames = propertiesObj.defaultEncoding; } break; } propertiesObj.glyphNames = glyphNames; return valid; } function readNameTable(nameTable) { const start = (font.start || 0) + nameTable.offset; font.pos = start; const names = [[], []], records = []; const length = nameTable.length, end = start + length; const format = font.getUint16(); const FORMAT_0_HEADER_LENGTH = 6; if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { return [names, records]; } const numRecords = font.getUint16(); const stringsStart = font.getUint16(); const NAME_RECORD_LENGTH = 12; let i, ii; for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) { const r = { platform: font.getUint16(), encoding: font.getUint16(), language: font.getUint16(), name: font.getUint16(), length: font.getUint16(), offset: font.getUint16() }; if (isMacNameRecord(r) || isWinNameRecord(r)) { records.push(r); } } for (i = 0, ii = records.length; i < ii; i++) { const record = records[i]; if (record.length <= 0) { continue; } const pos = start + stringsStart + record.offset; if (pos + record.length > end) { continue; } font.pos = pos; const nameIndex = record.name; if (record.encoding) { let str = ""; for (let j = 0, jj = record.length; j < jj; j += 2) { str += String.fromCharCode(font.getUint16()); } names[1][nameIndex] = str; } else { names[0][nameIndex] = font.getString(record.length); } } return [names, records]; } const TTOpsStackDeltas = [0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2, 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2]; function sanitizeTTProgram(table, ttContext) { let data = table.data; let i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0; const stack = []; const callstack = []; const functionsCalled = []; let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; let inFDEF = false, ifLevel = 0, inELSE = 0; for (let ii = data.length; i < ii;) { const op = data[i++]; if (op === 0x40) { n = data[i++]; if (inFDEF || inELSE) { i += n; } else { for (j = 0; j < n; j++) { stack.push(data[i++]); } } } else if (op === 0x41) { n = data[i++]; if (inFDEF || inELSE) { i += n * 2; } else { for (j = 0; j < n; j++) { b = data[i++]; stack.push(b << 8 | data[i++]); } } } else if ((op & 0xf8) === 0xb0) { n = op - 0xb0 + 1; if (inFDEF || inELSE) { i += n; } else { for (j = 0; j < n; j++) { stack.push(data[i++]); } } } else if ((op & 0xf8) === 0xb8) { n = op - 0xb8 + 1; if (inFDEF || inELSE) { i += n * 2; } else { for (j = 0; j < n; j++) { b = data[i++]; stack.push(b << 8 | data[i++]); } } } else if (op === 0x2b && !tooComplexToFollowFunctions) { if (!inFDEF && !inELSE) { funcId = stack.at(-1); if (isNaN(funcId)) { info("TT: CALL empty stack (or invalid entry)."); } else { ttContext.functionsUsed[funcId] = true; if (funcId in ttContext.functionsStackDeltas) { const newStackLength = stack.length + ttContext.functionsStackDeltas[funcId]; if (newStackLength < 0) { warn("TT: CALL invalid functions stack delta."); ttContext.hintsValid = false; return; } stack.length = newStackLength; } else if (funcId in ttContext.functionsDefined && !functionsCalled.includes(funcId)) { callstack.push({ data, i, stackTop: stack.length - 1 }); functionsCalled.push(funcId); pc = ttContext.functionsDefined[funcId]; if (!pc) { warn("TT: CALL non-existent function"); ttContext.hintsValid = false; return; } data = pc.data; i = pc.i; } } } } else if (op === 0x2c && !tooComplexToFollowFunctions) { if (inFDEF || inELSE) { warn("TT: nested FDEFs not allowed"); tooComplexToFollowFunctions = true; } inFDEF = true; lastDeff = i; funcId = stack.pop(); ttContext.functionsDefined[funcId] = { data, i }; } else if (op === 0x2d) { if (inFDEF) { inFDEF = false; lastEndf = i; } else { pc = callstack.pop(); if (!pc) { warn("TT: ENDF bad stack"); ttContext.hintsValid = false; return; } funcId = functionsCalled.pop(); data = pc.data; i = pc.i; ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop; } } else if (op === 0x89) { if (inFDEF || inELSE) { warn("TT: nested IDEFs not allowed"); tooComplexToFollowFunctions = true; } inFDEF = true; lastDeff = i; } else if (op === 0x58) { ++ifLevel; } else if (op === 0x1b) { inELSE = ifLevel; } else if (op === 0x59) { if (inELSE === ifLevel) { inELSE = 0; } --ifLevel; } else if (op === 0x1c) { if (!inFDEF && !inELSE) { const offset = stack.at(-1); if (offset > 0) { i += offset - 1; } } } if (!inFDEF && !inELSE) { let stackDelta = 0; if (op <= 0x8e) { stackDelta = TTOpsStackDeltas[op]; } else if (op >= 0xc0 && op <= 0xdf) { stackDelta = -1; } else if (op >= 0xe0) { stackDelta = -2; } if (op >= 0x71 && op <= 0x75) { n = stack.pop(); if (!isNaN(n)) { stackDelta = -n * 2; } } while (stackDelta < 0 && stack.length > 0) { stack.pop(); stackDelta++; } while (stackDelta > 0) { stack.push(NaN); stackDelta--; } } } ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; const content = [data]; if (i > data.length) { content.push(new Uint8Array(i - data.length)); } if (lastDeff > lastEndf) { warn("TT: complementing a missing function tail"); content.push(new Uint8Array([0x22, 0x2d])); } foldTTTable(table, content); } function checkInvalidFunctions(ttContext, maxFunctionDefs) { if (ttContext.tooComplexToFollowFunctions) { return; } if (ttContext.functionsDefined.length > maxFunctionDefs) { warn("TT: more functions defined than expected"); ttContext.hintsValid = false; return; } for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { if (j > maxFunctionDefs) { warn("TT: invalid function id: " + j); ttContext.hintsValid = false; return; } if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) { warn("TT: undefined function: " + j); ttContext.hintsValid = false; return; } } } function foldTTTable(table, content) { if (content.length > 1) { let newLength = 0; let j, jj; for (j = 0, jj = content.length; j < jj; j++) { newLength += content[j].length; } newLength = newLength + 3 & ~3; const result = new Uint8Array(newLength); let pos = 0; for (j = 0, jj = content.length; j < jj; j++) { result.set(content[j], pos); pos += content[j].length; } table.data = result; table.length = newLength; } } function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) { const ttContext = { functionsDefined: [], functionsUsed: [], functionsStackDeltas: [], tooComplexToFollowFunctions: false, hintsValid: true }; if (fpgm) { sanitizeTTProgram(fpgm, ttContext); } if (prep) { sanitizeTTProgram(prep, ttContext); } if (fpgm) { checkInvalidFunctions(ttContext, maxFunctionDefs); } if (cvt && cvt.length & 1) { const cvtData = new Uint8Array(cvt.length + 1); cvtData.set(cvt.data); cvt.data = cvtData; } return ttContext.hintsValid; } font = new Stream(new Uint8Array(font.getBytes())); let header, tables; if (isTrueTypeCollectionFile(font)) { const ttcData = readTrueTypeCollectionData(font, this.name); header = ttcData.header; tables = ttcData.tables; } else { header = readOpenTypeHeader(font); tables = readTables(font, header.numTables); } let cff, cffFile; const isTrueType = !tables["CFF "]; if (!isTrueType) { const isComposite = properties.composite && (properties.cidToGidMap?.length > 0 || !(properties.cMap instanceof IdentityCMap)); if (header.version === "OTTO" && !isComposite || !tables.head || !tables.hhea || !tables.maxp || !tables.post) { cffFile = new Stream(tables["CFF "].data); cff = new CFFFont(cffFile, properties); adjustWidths(properties); return this.convert(name, cff, properties); } delete tables.glyf; delete tables.loca; delete tables.fpgm; delete tables.prep; delete tables["cvt "]; this.isOpenType = true; } else { if (!tables.loca) { throw new FormatError('Required "loca" table is not found'); } if (!tables.glyf) { warn('Required "glyf" table is not found -- trying to recover.'); tables.glyf = { tag: "glyf", data: new Uint8Array(0) }; } this.isOpenType = false; } if (!tables.maxp) { throw new FormatError('Required "maxp" table is not found'); } font.pos = (font.start || 0) + tables.maxp.offset; let version = font.getInt32(); const numGlyphs = font.getUint16(); if (version !== 0x00010000 && version !== 0x00005000) { if (tables.maxp.length === 6) { version = 0x0005000; } else if (tables.maxp.length >= 32) { version = 0x00010000; } else { throw new FormatError(`"maxp" table has a wrong version number`); } writeUint32(tables.maxp.data, 0, version); } if (properties.scaleFactors?.length === numGlyphs && isTrueType) { const { scaleFactors } = properties; const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]); const glyphs = new GlyfTable({ glyfTable: tables.glyf.data, isGlyphLocationsLong, locaTable: tables.loca.data, numGlyphs }); glyphs.scale(scaleFactors); const { glyf, loca, isLocationLong } = glyphs.write(); tables.glyf.data = glyf; tables.loca.data = loca; if (isLocationLong !== !!isGlyphLocationsLong) { tables.head.data[50] = 0; tables.head.data[51] = isLocationLong ? 1 : 0; } const metrics = tables.hmtx.data; for (let i = 0; i < numGlyphs; i++) { const j = 4 * i; const advanceWidth = Math.round(scaleFactors[i] * int16(metrics[j], metrics[j + 1])); metrics[j] = advanceWidth >> 8 & 0xff; metrics[j + 1] = advanceWidth & 0xff; const lsb = Math.round(scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3])); writeSignedInt16(metrics, j + 2, lsb); } } let numGlyphsOut = numGlyphs + 1; let dupFirstEntry = true; if (numGlyphsOut > 0xffff) { dupFirstEntry = false; numGlyphsOut = numGlyphs; warn("Not enough space in glyfs to duplicate first glyph."); } let maxFunctionDefs = 0; let maxSizeOfInstructions = 0; if (version >= 0x00010000 && tables.maxp.length >= 32) { font.pos += 8; const maxZones = font.getUint16(); if (maxZones > 2) { tables.maxp.data[14] = 0; tables.maxp.data[15] = 2; } font.pos += 4; maxFunctionDefs = font.getUint16(); font.pos += 4; maxSizeOfInstructions = font.getUint16(); } tables.maxp.data[4] = numGlyphsOut >> 8; tables.maxp.data[5] = numGlyphsOut & 255; const hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, tables["cvt "], maxFunctionDefs); if (!hintsValid) { delete tables.fpgm; delete tables.prep; delete tables["cvt "]; } sanitizeMetrics(font, tables.hhea, tables.hmtx, tables.head, numGlyphsOut, dupFirstEntry); if (!tables.head) { throw new FormatError('Required "head" table is not found'); } sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); let missingGlyphs = Object.create(null); if (isTrueType) { const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]); const glyphsInfo = sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions); missingGlyphs = glyphsInfo.missingGlyphs; if (version >= 0x00010000 && tables.maxp.length >= 32) { tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8; tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255; } } if (!tables.hhea) { throw new FormatError('Required "hhea" table is not found'); } if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) { tables.hhea.data[10] = 0xff; tables.hhea.data[11] = 0xff; } const metricsOverride = { unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), yMax: signedInt16(tables.head.data[42], tables.head.data[43]), yMin: signedInt16(tables.head.data[38], tables.head.data[39]), ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]), descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]), lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9]) }; this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm; this.descent = metricsOverride.descent / metricsOverride.unitsPerEm; this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm; if (this.cssFontInfo?.lineHeight) { this.lineHeight = this.cssFontInfo.metrics.lineHeight; this.lineGap = this.cssFontInfo.metrics.lineGap; } else { this.lineHeight = this.ascent - this.descent + this.lineGap; } if (tables.post) { readPostScriptTable(tables.post, properties, numGlyphs); } tables.post = { tag: "post", data: createPostTable(properties) }; const charCodeToGlyphId = []; function hasGlyph(glyphId) { return !missingGlyphs[glyphId]; } if (properties.composite) { const cidToGidMap = properties.cidToGidMap || []; const isCidToGidMapEmpty = cidToGidMap.length === 0; properties.cMap.forEach(function (charCode, cid) { if (typeof cid === "string") { cid = convertCidString(charCode, cid, true); } if (cid > 0xffff) { throw new FormatError("Max size of CID is 65,535"); } let glyphId = -1; if (isCidToGidMapEmpty) { glyphId = cid; } else if (cidToGidMap[cid] !== undefined) { glyphId = cidToGidMap[cid]; } if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) { charCodeToGlyphId[charCode] = glyphId; } }); } else { const cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont, properties.hasEncoding); const cmapPlatformId = cmapTable.platformId; const cmapEncodingId = cmapTable.encodingId; const cmapMappings = cmapTable.mappings; let baseEncoding = [], forcePostTable = false; if (properties.hasEncoding && (properties.baseEncodingName === "MacRomanEncoding" || properties.baseEncodingName === "WinAnsiEncoding")) { baseEncoding = getEncoding(properties.baseEncodingName); } if (properties.hasEncoding && !this.isSymbolicFont && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0)) { const glyphsUnicodeMap = getGlyphsUnicode(); for (let charCode = 0; charCode < 256; charCode++) { let glyphName; if (this.differences[charCode] !== undefined) { glyphName = this.differences[charCode]; } else if (baseEncoding.length && baseEncoding[charCode] !== "") { glyphName = baseEncoding[charCode]; } else { glyphName = StandardEncoding[charCode]; } if (!glyphName) { continue; } const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); let unicodeOrCharCode; if (cmapPlatformId === 3 && cmapEncodingId === 1) { unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName]; } else if (cmapPlatformId === 1 && cmapEncodingId === 0) { unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName); } if (unicodeOrCharCode === undefined) { if (!properties.glyphNames && properties.hasIncludedToUnicodeMap && !(this.toUnicode instanceof IdentityToUnicodeMap)) { const unicode = this.toUnicode.get(charCode); if (unicode) { unicodeOrCharCode = unicode.codePointAt(0); } } if (unicodeOrCharCode === undefined) { continue; } } for (const mapping of cmapMappings) { if (mapping.charCode !== unicodeOrCharCode) { continue; } charCodeToGlyphId[charCode] = mapping.glyphId; break; } } } else if (cmapPlatformId === 0) { for (const mapping of cmapMappings) { charCodeToGlyphId[mapping.charCode] = mapping.glyphId; } forcePostTable = true; } else { for (const mapping of cmapMappings) { let charCode = mapping.charCode; if (cmapPlatformId === 3 && charCode >= 0xf000 && charCode <= 0xf0ff) { charCode &= 0xff; } charCodeToGlyphId[charCode] = mapping.glyphId; } } if (properties.glyphNames && (baseEncoding.length || this.differences.length)) { for (let i = 0; i < 256; ++i) { if (!forcePostTable && charCodeToGlyphId[i] !== undefined) { continue; } const glyphName = this.differences[i] || baseEncoding[i]; if (!glyphName) { continue; } const glyphId = properties.glyphNames.indexOf(glyphName); if (glyphId > 0 && hasGlyph(glyphId)) { charCodeToGlyphId[i] = glyphId; } } } } if (charCodeToGlyphId.length === 0) { charCodeToGlyphId[0] = 0; } let glyphZeroId = numGlyphsOut - 1; if (!dupFirstEntry) { glyphZeroId = 0; } if (!properties.cssFontInfo) { const newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId, this.toUnicode); this.toFontChar = newMapping.toFontChar; tables.cmap = { tag: "cmap", data: createCmapTable(newMapping.charCodeToGlyphId, newMapping.toUnicodeExtraMap, numGlyphsOut) }; if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { tables["OS/2"] = { tag: "OS/2", data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride) }; } } if (!isTrueType) { try { cffFile = new Stream(tables["CFF "].data); const parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED); cff = parser.parse(); cff.duplicateFirstGlyph(); const compiler = new CFFCompiler(cff); tables["CFF "].data = compiler.compile(); } catch { warn("Failed to compile font " + properties.loadedName); } } if (!tables.name) { tables.name = { tag: "name", data: createNameTable(this.name) }; } else { const [namePrototype, nameRecords] = readNameTable(tables.name); tables.name.data = createNameTable(name, namePrototype); this.psName = namePrototype[0][6] || null; if (!properties.composite) { adjustTrueTypeToUnicode(properties, this.isSymbolicFont, nameRecords); } } const builder = new OpenTypeFileBuilder(header.version); for (const tableTag in tables) { builder.addTable(tableTag, tables[tableTag].data); } return builder.toArray(); } convert(fontName, font, properties) { properties.fixedPitch = false; if (properties.builtInEncoding) { adjustType1ToUnicode(properties, properties.builtInEncoding); } let glyphZeroId = 1; if (font instanceof CFFFont) { glyphZeroId = font.numGlyphs - 1; } const mapping = font.getGlyphMapping(properties); let newMapping = null; let newCharCodeToGlyphId = mapping; let toUnicodeExtraMap = null; if (!properties.cssFontInfo) { newMapping = adjustMapping(mapping, font.hasGlyphId.bind(font), glyphZeroId, this.toUnicode); this.toFontChar = newMapping.toFontChar; newCharCodeToGlyphId = newMapping.charCodeToGlyphId; toUnicodeExtraMap = newMapping.toUnicodeExtraMap; } const numGlyphs = font.numGlyphs; function getCharCodes(charCodeToGlyphId, glyphId) { let charCodes = null; for (const charCode in charCodeToGlyphId) { if (glyphId === charCodeToGlyphId[charCode]) { (charCodes ||= []).push(charCode | 0); } } return charCodes; } function createCharCode(charCodeToGlyphId, glyphId) { for (const charCode in charCodeToGlyphId) { if (glyphId === charCodeToGlyphId[charCode]) { return charCode | 0; } } newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = glyphId; return newMapping.nextAvailableFontCharCode++; } const seacs = font.seacs; if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) { const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; const charset = font.getCharset(); const seacMap = Object.create(null); for (let glyphId in seacs) { glyphId |= 0; const seac = seacs[glyphId]; const baseGlyphName = StandardEncoding[seac[2]]; const accentGlyphName = StandardEncoding[seac[3]]; const baseGlyphId = charset.indexOf(baseGlyphName); const accentGlyphId = charset.indexOf(accentGlyphName); if (baseGlyphId < 0 || accentGlyphId < 0) { continue; } const accentOffset = { x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5] }; const charCodes = getCharCodes(mapping, glyphId); if (!charCodes) { continue; } for (const charCode of charCodes) { const charCodeToGlyphId = newMapping.charCodeToGlyphId; const baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId); const accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId); seacMap[charCode] = { baseFontCharCode, accentFontCharCode, accentOffset }; } } properties.seacMap = seacMap; } const unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); builder.addTable("CFF ", font.data); builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId)); builder.addTable("cmap", createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs)); builder.addTable("head", "\x00\x01\x00\x00" + "\x00\x00\x10\x00" + "\x00\x00\x00\x00" + "\x5F\x0F\x3C\xF5" + "\x00\x00" + safeString16(unitsPerEm) + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00" + safeString16(properties.descent) + "\x0F\xFF" + safeString16(properties.ascent) + string16(properties.italicAngle ? 2 : 0) + "\x00\x11" + "\x00\x00" + "\x00\x00" + "\x00\x00"); builder.addTable("hhea", "\x00\x01\x00\x00" + safeString16(properties.ascent) + safeString16(properties.descent) + "\x00\x00" + "\xFF\xFF" + "\x00\x00" + "\x00\x00" + "\x00\x00" + safeString16(properties.capHeight) + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + string16(numGlyphs)); builder.addTable("hmtx", function fontFieldsHmtx() { const charstrings = font.charstrings; const cffWidths = font.cff ? font.cff.widths : null; let hmtx = "\x00\x00\x00\x00"; for (let i = 1, ii = numGlyphs; i < ii; i++) { let width = 0; if (charstrings) { const charstring = charstrings[i - 1]; width = "width" in charstring ? charstring.width : 0; } else if (cffWidths) { width = Math.ceil(cffWidths[i] || 0); } hmtx += string16(width) + string16(0); } return hmtx; }()); builder.addTable("maxp", "\x00\x00\x50\x00" + string16(numGlyphs)); builder.addTable("name", createNameTable(fontName)); builder.addTable("post", createPostTable(properties)); return builder.toArray(); } get spaceWidth() { const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; let width; for (const glyphName of possibleSpaceReplacements) { if (glyphName in this.widths) { width = this.widths[glyphName]; break; } const glyphsUnicodeMap = getGlyphsUnicode(); const glyphUnicode = glyphsUnicodeMap[glyphName]; let charcode = 0; if (this.composite && this.cMap.contains(glyphUnicode)) { charcode = this.cMap.lookup(glyphUnicode); if (typeof charcode === "string") { charcode = convertCidString(glyphUnicode, charcode); } } if (!charcode && this.toUnicode) { charcode = this.toUnicode.charCodeOf(glyphUnicode); } if (charcode <= 0) { charcode = glyphUnicode; } width = this.widths[charcode]; if (width) { break; } } return shadow(this, "spaceWidth", width || this.defaultWidth); } _charToGlyph(charcode, isSpace = false) { let glyph = this._glyphCache[charcode]; if (glyph?.isSpace === isSpace) { return glyph; } let fontCharCode, width, operatorListId; let widthCode = charcode; if (this.cMap?.contains(charcode)) { widthCode = this.cMap.lookup(charcode); if (typeof widthCode === "string") { widthCode = convertCidString(charcode, widthCode); } } width = this.widths[widthCode]; if (typeof width !== "number") { width = this.defaultWidth; } const vmetric = this.vmetrics?.[widthCode]; let unicode = this.toUnicode.get(charcode) || charcode; if (typeof unicode === "number") { unicode = String.fromCharCode(unicode); } let isInFont = this.toFontChar[charcode] !== undefined; fontCharCode = this.toFontChar[charcode] || charcode; if (this.missingFile) { const glyphName = this.differences[charcode] || this.defaultEncoding[charcode]; if ((glyphName === ".notdef" || glyphName === "") && this.type === "Type1") { fontCharCode = 0x20; } fontCharCode = mapSpecialUnicodeValues(fontCharCode); } if (this.isType3Font) { operatorListId = fontCharCode; } let accent = null; if (this.seacMap?.[charcode]) { isInFont = true; const seac = this.seacMap[charcode]; fontCharCode = seac.baseFontCharCode; accent = { fontChar: String.fromCodePoint(seac.accentFontCharCode), offset: seac.accentOffset }; } let fontChar = ""; if (typeof fontCharCode === "number") { if (fontCharCode <= 0x10ffff) { fontChar = String.fromCodePoint(fontCharCode); } else { warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`); } } glyph = new fonts_Glyph(charcode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont); return this._glyphCache[charcode] = glyph; } charsToGlyphs(chars) { let glyphs = this._charsCache[chars]; if (glyphs) { return glyphs; } glyphs = []; if (this.cMap) { const c = Object.create(null), ii = chars.length; let i = 0; while (i < ii) { this.cMap.readCharCode(chars, i, c); const { charcode, length } = c; i += length; const glyph = this._charToGlyph(charcode, length === 1 && chars.charCodeAt(i - 1) === 0x20); glyphs.push(glyph); } } else { for (let i = 0, ii = chars.length; i < ii; ++i) { const charcode = chars.charCodeAt(i); const glyph = this._charToGlyph(charcode, charcode === 0x20); glyphs.push(glyph); } } return this._charsCache[chars] = glyphs; } getCharPositions(chars) { const positions = []; if (this.cMap) { const c = Object.create(null); let i = 0; while (i < chars.length) { this.cMap.readCharCode(chars, i, c); const length = c.length; positions.push([i, i + length]); i += length; } } else { for (let i = 0, ii = chars.length; i < ii; ++i) { positions.push([i, i + 1]); } } return positions; } get glyphCacheValues() { return Object.values(this._glyphCache); } encodeString(str) { const buffers = []; const currentBuf = []; const hasCurrentBufErrors = () => buffers.length % 2 === 1; const getCharCode = this.toUnicode instanceof IdentityToUnicodeMap ? unicode => this.toUnicode.charCodeOf(unicode) : unicode => this.toUnicode.charCodeOf(String.fromCodePoint(unicode)); for (let i = 0, ii = str.length; i < ii; i++) { const unicode = str.codePointAt(i); if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) { i++; } if (this.toUnicode) { const charCode = getCharCode(unicode); if (charCode !== -1) { if (hasCurrentBufErrors()) { buffers.push(currentBuf.join("")); currentBuf.length = 0; } const charCodeLength = this.cMap ? this.cMap.getCharCodeLength(charCode) : 1; for (let j = charCodeLength - 1; j >= 0; j--) { currentBuf.push(String.fromCharCode(charCode >> 8 * j & 0xff)); } continue; } } if (!hasCurrentBufErrors()) { buffers.push(currentBuf.join("")); currentBuf.length = 0; } currentBuf.push(String.fromCodePoint(unicode)); } buffers.push(currentBuf.join("")); return buffers; } } class ErrorFont { constructor(error) { this.error = error; this.loadedName = "g_font_error"; this.missingFile = true; } charsToGlyphs() { return []; } encodeString(chars) { return [chars]; } exportData(extraProperties = false) { return { error: this.error }; } } ;// CONCATENATED MODULE: ./src/core/pattern.js const ShadingType = { FUNCTION_BASED: 1, AXIAL: 2, RADIAL: 3, FREE_FORM_MESH: 4, LATTICE_FORM_MESH: 5, COONS_PATCH_MESH: 6, TENSOR_PATCH_MESH: 7 }; class Pattern { constructor() { unreachable("Cannot initialize Pattern."); } static parseShading(shading, xref, res, pdfFunctionFactory, localColorSpaceCache) { const dict = shading instanceof BaseStream ? shading.dict : shading; const type = dict.get("ShadingType"); try { switch (type) { case ShadingType.AXIAL: case ShadingType.RADIAL: return new RadialAxialShading(dict, xref, res, pdfFunctionFactory, localColorSpaceCache); case ShadingType.FREE_FORM_MESH: case ShadingType.LATTICE_FORM_MESH: case ShadingType.COONS_PATCH_MESH: case ShadingType.TENSOR_PATCH_MESH: return new MeshShading(shading, xref, res, pdfFunctionFactory, localColorSpaceCache); default: throw new FormatError("Unsupported ShadingType: " + type); } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(ex); return new DummyShading(); } } } class BaseShading { static SMALL_NUMBER = 1e-6; constructor() { if (this.constructor === BaseShading) { unreachable("Cannot initialize BaseShading."); } } getIR() { unreachable("Abstract method `getIR` called."); } } class RadialAxialShading extends BaseShading { constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) { super(); this.coordsArr = dict.getArray("Coords"); this.shadingType = dict.get("ShadingType"); const cs = ColorSpace.parse({ cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), xref, resources, pdfFunctionFactory, localColorSpaceCache }); const bbox = dict.getArray("BBox"); this.bbox = Array.isArray(bbox) && bbox.length === 4 ? Util.normalizeRect(bbox) : null; let t0 = 0.0, t1 = 1.0; if (dict.has("Domain")) { const domainArr = dict.getArray("Domain"); t0 = domainArr[0]; t1 = domainArr[1]; } let extendStart = false, extendEnd = false; if (dict.has("Extend")) { const extendArr = dict.getArray("Extend"); extendStart = extendArr[0]; extendEnd = extendArr[1]; } if (this.shadingType === ShadingType.RADIAL && (!extendStart || !extendEnd)) { const [x1, y1, r1, x2, y2, r2] = this.coordsArr; const distance = Math.hypot(x1 - x2, y1 - y2); if (r1 <= r2 + distance && r2 <= r1 + distance) { warn("Unsupported radial gradient."); } } this.extendStart = extendStart; this.extendEnd = extendEnd; const fnObj = dict.getRaw("Function"); const fn = pdfFunctionFactory.createFromArray(fnObj); const NUMBER_OF_SAMPLES = 840; const step = (t1 - t0) / NUMBER_OF_SAMPLES; const colorStops = this.colorStops = []; if (t0 >= t1 || step <= 0) { info("Bad shading domain."); return; } const color = new Float32Array(cs.numComps), ratio = new Float32Array(1); let rgbColor; let iBase = 0; ratio[0] = t0; fn(ratio, 0, color, 0); let rgbBase = cs.getRgb(color, 0); const cssColorBase = Util.makeHexColor(rgbBase[0], rgbBase[1], rgbBase[2]); colorStops.push([0, cssColorBase]); let iPrev = 1; ratio[0] = t0 + step; fn(ratio, 0, color, 0); let rgbPrev = cs.getRgb(color, 0); let maxSlopeR = rgbPrev[0] - rgbBase[0] + 1; let maxSlopeG = rgbPrev[1] - rgbBase[1] + 1; let maxSlopeB = rgbPrev[2] - rgbBase[2] + 1; let minSlopeR = rgbPrev[0] - rgbBase[0] - 1; let minSlopeG = rgbPrev[1] - rgbBase[1] - 1; let minSlopeB = rgbPrev[2] - rgbBase[2] - 1; for (let i = 2; i < NUMBER_OF_SAMPLES; i++) { ratio[0] = t0 + i * step; fn(ratio, 0, color, 0); rgbColor = cs.getRgb(color, 0); const run = i - iBase; maxSlopeR = Math.min(maxSlopeR, (rgbColor[0] - rgbBase[0] + 1) / run); maxSlopeG = Math.min(maxSlopeG, (rgbColor[1] - rgbBase[1] + 1) / run); maxSlopeB = Math.min(maxSlopeB, (rgbColor[2] - rgbBase[2] + 1) / run); minSlopeR = Math.max(minSlopeR, (rgbColor[0] - rgbBase[0] - 1) / run); minSlopeG = Math.max(minSlopeG, (rgbColor[1] - rgbBase[1] - 1) / run); minSlopeB = Math.max(minSlopeB, (rgbColor[2] - rgbBase[2] - 1) / run); const slopesExist = minSlopeR <= maxSlopeR && minSlopeG <= maxSlopeG && minSlopeB <= maxSlopeB; if (!slopesExist) { const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); colorStops.push([iPrev / NUMBER_OF_SAMPLES, cssColor]); maxSlopeR = rgbColor[0] - rgbPrev[0] + 1; maxSlopeG = rgbColor[1] - rgbPrev[1] + 1; maxSlopeB = rgbColor[2] - rgbPrev[2] + 1; minSlopeR = rgbColor[0] - rgbPrev[0] - 1; minSlopeG = rgbColor[1] - rgbPrev[1] - 1; minSlopeB = rgbColor[2] - rgbPrev[2] - 1; iBase = iPrev; rgbBase = rgbPrev; } iPrev = i; rgbPrev = rgbColor; } const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); colorStops.push([1, cssColor]); let background = "transparent"; if (dict.has("Background")) { rgbColor = cs.getRgb(dict.get("Background"), 0); background = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]); } if (!extendStart) { colorStops.unshift([0, background]); colorStops[1][0] += BaseShading.SMALL_NUMBER; } if (!extendEnd) { colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER; colorStops.push([1, background]); } this.colorStops = colorStops; } getIR() { const coordsArr = this.coordsArr; const shadingType = this.shadingType; let type, p0, p1, r0, r1; if (shadingType === ShadingType.AXIAL) { p0 = [coordsArr[0], coordsArr[1]]; p1 = [coordsArr[2], coordsArr[3]]; r0 = null; r1 = null; type = "axial"; } else if (shadingType === ShadingType.RADIAL) { p0 = [coordsArr[0], coordsArr[1]]; p1 = [coordsArr[3], coordsArr[4]]; r0 = coordsArr[2]; r1 = coordsArr[5]; type = "radial"; } else { unreachable(`getPattern type unknown: ${shadingType}`); } return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1]; } } class MeshStreamReader { constructor(stream, context) { this.stream = stream; this.context = context; this.buffer = 0; this.bufferLength = 0; const numComps = context.numComps; this.tmpCompsBuf = new Float32Array(numComps); const csNumComps = context.colorSpace.numComps; this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : this.tmpCompsBuf; } get hasData() { if (this.stream.end) { return this.stream.pos < this.stream.end; } if (this.bufferLength > 0) { return true; } const nextByte = this.stream.getByte(); if (nextByte < 0) { return false; } this.buffer = nextByte; this.bufferLength = 8; return true; } readBits(n) { let buffer = this.buffer; let bufferLength = this.bufferLength; if (n === 32) { if (bufferLength === 0) { return (this.stream.getByte() << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte()) >>> 0; } buffer = buffer << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte(); const nextByte = this.stream.getByte(); this.buffer = nextByte & (1 << bufferLength) - 1; return (buffer << 8 - bufferLength | (nextByte & 0xff) >> bufferLength) >>> 0; } if (n === 8 && bufferLength === 0) { return this.stream.getByte(); } while (bufferLength < n) { buffer = buffer << 8 | this.stream.getByte(); bufferLength += 8; } bufferLength -= n; this.bufferLength = bufferLength; this.buffer = buffer & (1 << bufferLength) - 1; return buffer >> bufferLength; } align() { this.buffer = 0; this.bufferLength = 0; } readFlag() { return this.readBits(this.context.bitsPerFlag); } readCoordinate() { const bitsPerCoordinate = this.context.bitsPerCoordinate; const xi = this.readBits(bitsPerCoordinate); const yi = this.readBits(bitsPerCoordinate); const decode = this.context.decode; const scale = bitsPerCoordinate < 32 ? 1 / ((1 << bitsPerCoordinate) - 1) : 2.3283064365386963e-10; return [xi * scale * (decode[1] - decode[0]) + decode[0], yi * scale * (decode[3] - decode[2]) + decode[2]]; } readComponents() { const numComps = this.context.numComps; const bitsPerComponent = this.context.bitsPerComponent; const scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10; const decode = this.context.decode; const components = this.tmpCompsBuf; for (let i = 0, j = 4; i < numComps; i++, j += 2) { const ci = this.readBits(bitsPerComponent); components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j]; } const color = this.tmpCsCompsBuf; if (this.context.colorFn) { this.context.colorFn(components, 0, color, 0); } return this.context.colorSpace.getRgb(color, 0); } } let bCache = Object.create(null); function buildB(count) { const lut = []; for (let i = 0; i <= count; i++) { const t = i / count, t_ = 1 - t; lut.push(new Float32Array([t_ ** 3, 3 * t * t_ ** 2, 3 * t ** 2 * t_, t ** 3])); } return lut; } function getB(count) { return bCache[count] ||= buildB(count); } function clearPatternCaches() { bCache = Object.create(null); } class MeshShading extends BaseShading { static MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3; static MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20; static TRIANGLE_DENSITY = 20; constructor(stream, xref, resources, pdfFunctionFactory, localColorSpaceCache) { super(); if (!(stream instanceof BaseStream)) { throw new FormatError("Mesh data is not a stream"); } const dict = stream.dict; this.shadingType = dict.get("ShadingType"); const bbox = dict.getArray("BBox"); this.bbox = Array.isArray(bbox) && bbox.length === 4 ? Util.normalizeRect(bbox) : null; const cs = ColorSpace.parse({ cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), xref, resources, pdfFunctionFactory, localColorSpaceCache }); this.background = dict.has("Background") ? cs.getRgb(dict.get("Background"), 0) : null; const fnObj = dict.getRaw("Function"); const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null; this.coords = []; this.colors = []; this.figures = []; const decodeContext = { bitsPerCoordinate: dict.get("BitsPerCoordinate"), bitsPerComponent: dict.get("BitsPerComponent"), bitsPerFlag: dict.get("BitsPerFlag"), decode: dict.getArray("Decode"), colorFn: fn, colorSpace: cs, numComps: fn ? 1 : cs.numComps }; const reader = new MeshStreamReader(stream, decodeContext); let patchMesh = false; switch (this.shadingType) { case ShadingType.FREE_FORM_MESH: this._decodeType4Shading(reader); break; case ShadingType.LATTICE_FORM_MESH: const verticesPerRow = dict.get("VerticesPerRow") | 0; if (verticesPerRow < 2) { throw new FormatError("Invalid VerticesPerRow"); } this._decodeType5Shading(reader, verticesPerRow); break; case ShadingType.COONS_PATCH_MESH: this._decodeType6Shading(reader); patchMesh = true; break; case ShadingType.TENSOR_PATCH_MESH: this._decodeType7Shading(reader); patchMesh = true; break; default: unreachable("Unsupported mesh type."); break; } if (patchMesh) { this._updateBounds(); for (let i = 0, ii = this.figures.length; i < ii; i++) { this._buildFigureFromPatch(i); } } this._updateBounds(); this._packData(); } _decodeType4Shading(reader) { const coords = this.coords; const colors = this.colors; const operators = []; const ps = []; let verticesLeft = 0; while (reader.hasData) { const f = reader.readFlag(); const coord = reader.readCoordinate(); const color = reader.readComponents(); if (verticesLeft === 0) { if (!(0 <= f && f <= 2)) { throw new FormatError("Unknown type4 flag"); } switch (f) { case 0: verticesLeft = 3; break; case 1: ps.push(ps.at(-2), ps.at(-1)); verticesLeft = 1; break; case 2: ps.push(ps.at(-3), ps.at(-1)); verticesLeft = 1; break; } operators.push(f); } ps.push(coords.length); coords.push(coord); colors.push(color); verticesLeft--; reader.align(); } this.figures.push({ type: "triangles", coords: new Int32Array(ps), colors: new Int32Array(ps) }); } _decodeType5Shading(reader, verticesPerRow) { const coords = this.coords; const colors = this.colors; const ps = []; while (reader.hasData) { const coord = reader.readCoordinate(); const color = reader.readComponents(); ps.push(coords.length); coords.push(coord); colors.push(color); } this.figures.push({ type: "lattice", coords: new Int32Array(ps), colors: new Int32Array(ps), verticesPerRow }); } _decodeType6Shading(reader) { const coords = this.coords; const colors = this.colors; const ps = new Int32Array(16); const cs = new Int32Array(4); while (reader.hasData) { const f = reader.readFlag(); if (!(0 <= f && f <= 3)) { throw new FormatError("Unknown type6 flag"); } const pi = coords.length; for (let i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) { coords.push(reader.readCoordinate()); } const ci = colors.length; for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) { colors.push(reader.readComponents()); } let tmp1, tmp2, tmp3, tmp4; switch (f) { case 0: ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6; ps[8] = pi + 2; ps[11] = pi + 7; ps[4] = pi + 1; ps[7] = pi + 8; ps[0] = pi; ps[1] = pi + 11; ps[2] = pi + 10; ps[3] = pi + 9; cs[2] = ci + 1; cs[3] = ci + 2; cs[0] = ci; cs[1] = ci + 3; break; case 1: tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15]; ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = tmp3; ps[11] = pi + 3; ps[4] = tmp2; ps[7] = pi + 4; ps[0] = tmp1; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; tmp1 = cs[2]; tmp2 = cs[3]; cs[2] = tmp2; cs[3] = ci; cs[0] = tmp1; cs[1] = ci + 1; break; case 2: tmp1 = ps[15]; tmp2 = ps[11]; ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = ps[7]; ps[11] = pi + 3; ps[4] = tmp2; ps[7] = pi + 4; ps[0] = tmp1; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; tmp1 = cs[3]; cs[2] = cs[1]; cs[3] = ci; cs[0] = tmp1; cs[1] = ci + 1; break; case 3: ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = ps[1]; ps[11] = pi + 3; ps[4] = ps[2]; ps[7] = pi + 4; ps[0] = ps[3]; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; cs[2] = cs[0]; cs[3] = ci; cs[0] = cs[1]; cs[1] = ci + 1; break; } ps[5] = coords.length; coords.push([(-4 * coords[ps[0]][0] - coords[ps[15]][0] + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / 9, (-4 * coords[ps[0]][1] - coords[ps[15]][1] + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / 9]); ps[6] = coords.length; coords.push([(-4 * coords[ps[3]][0] - coords[ps[12]][0] + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / 9, (-4 * coords[ps[3]][1] - coords[ps[12]][1] + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / 9]); ps[9] = coords.length; coords.push([(-4 * coords[ps[12]][0] - coords[ps[3]][0] + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / 9, (-4 * coords[ps[12]][1] - coords[ps[3]][1] + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / 9]); ps[10] = coords.length; coords.push([(-4 * coords[ps[15]][0] - coords[ps[0]][0] + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / 9, (-4 * coords[ps[15]][1] - coords[ps[0]][1] + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / 9]); this.figures.push({ type: "patch", coords: new Int32Array(ps), colors: new Int32Array(cs) }); } } _decodeType7Shading(reader) { const coords = this.coords; const colors = this.colors; const ps = new Int32Array(16); const cs = new Int32Array(4); while (reader.hasData) { const f = reader.readFlag(); if (!(0 <= f && f <= 3)) { throw new FormatError("Unknown type7 flag"); } const pi = coords.length; for (let i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) { coords.push(reader.readCoordinate()); } const ci = colors.length; for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) { colors.push(reader.readComponents()); } let tmp1, tmp2, tmp3, tmp4; switch (f) { case 0: ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6; ps[8] = pi + 2; ps[9] = pi + 13; ps[10] = pi + 14; ps[11] = pi + 7; ps[4] = pi + 1; ps[5] = pi + 12; ps[6] = pi + 15; ps[7] = pi + 8; ps[0] = pi; ps[1] = pi + 11; ps[2] = pi + 10; ps[3] = pi + 9; cs[2] = ci + 1; cs[3] = ci + 2; cs[0] = ci; cs[1] = ci + 3; break; case 1: tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15]; ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = tmp3; ps[9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; ps[4] = tmp2; ps[5] = pi + 8; ps[6] = pi + 11; ps[7] = pi + 4; ps[0] = tmp1; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; tmp1 = cs[2]; tmp2 = cs[3]; cs[2] = tmp2; cs[3] = ci; cs[0] = tmp1; cs[1] = ci + 1; break; case 2: tmp1 = ps[15]; tmp2 = ps[11]; ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = ps[7]; ps[9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; ps[4] = tmp2; ps[5] = pi + 8; ps[6] = pi + 11; ps[7] = pi + 4; ps[0] = tmp1; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; tmp1 = cs[3]; cs[2] = cs[1]; cs[3] = ci; cs[0] = tmp1; cs[1] = ci + 1; break; case 3: ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; ps[8] = ps[1]; ps[9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; ps[4] = ps[2]; ps[5] = pi + 8; ps[6] = pi + 11; ps[7] = pi + 4; ps[0] = ps[3]; ps[1] = pi + 7; ps[2] = pi + 6; ps[3] = pi + 5; cs[2] = cs[0]; cs[3] = ci; cs[0] = cs[1]; cs[1] = ci + 1; break; } this.figures.push({ type: "patch", coords: new Int32Array(ps), colors: new Int32Array(cs) }); } } _buildFigureFromPatch(index) { const figure = this.figures[index]; assert(figure.type === "patch", "Unexpected patch mesh figure"); const coords = this.coords, colors = this.colors; const pi = figure.coords; const ci = figure.colors; const figureMinX = Math.min(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]); const figureMinY = Math.min(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]); const figureMaxX = Math.max(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]); const figureMaxY = Math.max(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]); let splitXBy = Math.ceil((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY / (this.bounds[2] - this.bounds[0])); splitXBy = Math.max(MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy)); let splitYBy = Math.ceil((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY / (this.bounds[3] - this.bounds[1])); splitYBy = Math.max(MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy)); const verticesPerRow = splitXBy + 1; const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow); const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow); let k = 0; const cl = new Uint8Array(3), cr = new Uint8Array(3); const c0 = colors[ci[0]], c1 = colors[ci[1]], c2 = colors[ci[2]], c3 = colors[ci[3]]; const bRow = getB(splitYBy), bCol = getB(splitXBy); for (let row = 0; row <= splitYBy; row++) { cl[0] = (c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy | 0; cl[1] = (c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy | 0; cl[2] = (c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy | 0; cr[0] = (c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy | 0; cr[1] = (c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy | 0; cr[2] = (c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy | 0; for (let col = 0; col <= splitXBy; col++, k++) { if ((row === 0 || row === splitYBy) && (col === 0 || col === splitXBy)) { continue; } let x = 0, y = 0; let q = 0; for (let i = 0; i <= 3; i++) { for (let j = 0; j <= 3; j++, q++) { const m = bRow[row][i] * bCol[col][j]; x += coords[pi[q]][0] * m; y += coords[pi[q]][1] * m; } } figureCoords[k] = coords.length; coords.push([x, y]); figureColors[k] = colors.length; const newColor = new Uint8Array(3); newColor[0] = (cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy | 0; newColor[1] = (cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy | 0; newColor[2] = (cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy | 0; colors.push(newColor); } } figureCoords[0] = pi[0]; figureColors[0] = ci[0]; figureCoords[splitXBy] = pi[3]; figureColors[splitXBy] = ci[1]; figureCoords[verticesPerRow * splitYBy] = pi[12]; figureColors[verticesPerRow * splitYBy] = ci[2]; figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15]; figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3]; this.figures[index] = { type: "lattice", coords: figureCoords, colors: figureColors, verticesPerRow }; } _updateBounds() { let minX = this.coords[0][0], minY = this.coords[0][1], maxX = minX, maxY = minY; for (let i = 1, ii = this.coords.length; i < ii; i++) { const x = this.coords[i][0], y = this.coords[i][1]; minX = minX > x ? x : minX; minY = minY > y ? y : minY; maxX = maxX < x ? x : maxX; maxY = maxY < y ? y : maxY; } this.bounds = [minX, minY, maxX, maxY]; } _packData() { let i, ii, j, jj; const coords = this.coords; const coordsPacked = new Float32Array(coords.length * 2); for (i = 0, j = 0, ii = coords.length; i < ii; i++) { const xy = coords[i]; coordsPacked[j++] = xy[0]; coordsPacked[j++] = xy[1]; } this.coords = coordsPacked; const colors = this.colors; const colorsPacked = new Uint8Array(colors.length * 3); for (i = 0, j = 0, ii = colors.length; i < ii; i++) { const c = colors[i]; colorsPacked[j++] = c[0]; colorsPacked[j++] = c[1]; colorsPacked[j++] = c[2]; } this.colors = colorsPacked; const figures = this.figures; for (i = 0, ii = figures.length; i < ii; i++) { const figure = figures[i], ps = figure.coords, cs = figure.colors; for (j = 0, jj = ps.length; j < jj; j++) { ps[j] *= 2; cs[j] *= 3; } } } getIR() { return ["Mesh", this.shadingType, this.coords, this.colors, this.figures, this.bounds, this.bbox, this.background]; } } class DummyShading extends BaseShading { getIR() { return ["Dummy"]; } } function getTilingPatternIR(operatorList, dict, color) { const matrix = dict.getArray("Matrix"); const bbox = Util.normalizeRect(dict.getArray("BBox")); const xstep = dict.get("XStep"); const ystep = dict.get("YStep"); const paintType = dict.get("PaintType"); const tilingType = dict.get("TilingType"); if (bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) { throw new FormatError(`Invalid getTilingPatternIR /BBox array: [${bbox}].`); } return ["TilingPattern", color, operatorList, matrix, bbox, xstep, ystep, paintType, tilingType]; } ;// CONCATENATED MODULE: ./src/core/calibri_factors.js const CalibriBoldFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.54657, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.73293, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.9121, 0.86943, 0.79795, 0.88198, 0.77958, 0.70864, 0.81055, 0.90399, 0.88653, 0.96017, 0.82577, 0.77892, 0.78257, 0.97507, 1.54657, 0.97507, 0.85284, 0.89552, 0.90176, 0.88762, 0.8785, 0.75241, 0.8785, 0.90518, 0.95015, 0.77618, 0.8785, 0.88401, 0.91916, 0.86304, 0.88401, 0.91488, 0.8785, 0.8801, 0.8785, 0.8785, 0.91343, 0.7173, 1.04106, 0.8785, 0.85075, 0.95794, 0.82616, 0.85162, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.12401, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.73293, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.9121, 0.86943, 0.86943, 0.86943, 0.86943, 0.86943, 0.85284, 0.87508, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.8715, 0.75241, 0.90518, 0.90518, 0.90518, 0.90518, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.8785, 0.8801, 0.8801, 0.8801, 0.8801, 0.8801, 0.90747, 0.89049, 0.8785, 0.8785, 0.8785, 0.8785, 0.85162, 0.8785, 0.85162, 0.83908, 0.88762, 0.83908, 0.88762, 0.83908, 0.88762, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.87289, 0.83016, 0.88506, 0.93125, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.81921, 0.77618, 0.81921, 0.77618, 0.81921, 0.77618, 1, 1, 0.87356, 0.8785, 0.91075, 0.89608, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76229, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.79468, 0.91926, 0.88175, 0.70823, 0.94903, 0.9121, 0.8785, 1, 1, 0.9121, 0.8785, 0.87802, 0.88656, 0.8785, 0.86943, 0.8801, 0.86943, 0.8801, 0.86943, 0.8801, 0.87402, 0.89291, 0.77958, 0.91343, 1, 1, 0.77958, 0.91343, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.96017, 0.95794, 0.77892, 0.85162, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.88762, 0.77539, 0.8715, 0.87508, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70674, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.06303, 0.83908, 0.80352, 0.57184, 0.6965, 0.56289, 0.82001, 0.56029, 0.81235, 1.02988, 0.83908, 0.7762, 0.68156, 0.80367, 0.73133, 0.78257, 0.87356, 0.86943, 0.95958, 0.75727, 0.89019, 1.04924, 0.9121, 0.7648, 0.86943, 0.87356, 0.79795, 0.78275, 0.81055, 0.77892, 0.9762, 0.82577, 0.99819, 0.84896, 0.95958, 0.77892, 0.96108, 1.01407, 0.89049, 1.02988, 0.94211, 0.96108, 0.8936, 0.84021, 0.87842, 0.96399, 0.79109, 0.89049, 1.00813, 1.02988, 0.86077, 0.87445, 0.92099, 0.84723, 0.86513, 0.8801, 0.75638, 0.85714, 0.78216, 0.79586, 0.87965, 0.94211, 0.97747, 0.78287, 0.97926, 0.84971, 1.02988, 0.94211, 0.8801, 0.94211, 0.84971, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90264, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90518, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90548, 1, 1, 1, 1, 1, 1, 0.96017, 0.95794, 0.96017, 0.95794, 0.96017, 0.95794, 0.77892, 0.85162, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.92794, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71143, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.93835, 0.83406, 0.91133, 0.84107, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90527, 1.81055, 0.90527, 1.81055, 1.31006, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const CalibriBoldMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; const CalibriBoldItalicFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.56239, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.71805, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.90872, 0.85938, 0.79795, 0.87068, 0.77958, 0.69766, 0.81055, 0.90399, 0.88653, 0.96068, 0.82577, 0.77892, 0.78257, 0.97507, 1.529, 0.97507, 0.85284, 0.89552, 0.90176, 0.94908, 0.86411, 0.74012, 0.86411, 0.88323, 0.95015, 0.86411, 0.86331, 0.88401, 0.91916, 0.86304, 0.88401, 0.9039, 0.86331, 0.86331, 0.86411, 0.86411, 0.90464, 0.70852, 1.04106, 0.86331, 0.84372, 0.95794, 0.82616, 0.84548, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.19129, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.71805, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.90872, 0.85938, 0.85938, 0.85938, 0.85938, 0.85938, 0.85284, 0.87068, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.85887, 0.74012, 0.88323, 0.88323, 0.88323, 0.88323, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.90747, 0.89049, 0.86331, 0.86331, 0.86331, 0.86331, 0.84548, 0.86411, 0.84548, 0.83908, 0.94908, 0.83908, 0.94908, 0.83908, 0.94908, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.87289, 0.79538, 0.88506, 0.92726, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.81921, 0.86411, 0.81921, 0.86411, 0.81921, 0.86411, 1, 1, 0.87356, 0.86331, 0.91075, 0.8777, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76467, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.77312, 0.91926, 0.88175, 0.70823, 0.94903, 0.90872, 0.86331, 1, 1, 0.90872, 0.86331, 0.86906, 0.88116, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.87402, 0.86549, 0.77958, 0.90464, 1, 1, 0.77958, 0.90464, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.96068, 0.95794, 0.77892, 0.84548, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.94908, 0.77539, 0.85887, 0.87068, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70088, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.48387, 0.83908, 0.80352, 0.57118, 0.6965, 0.56347, 0.79179, 0.55853, 0.80346, 1.02988, 0.83908, 0.7762, 0.67174, 0.86036, 0.73133, 0.78257, 0.87356, 0.86441, 0.95958, 0.75727, 0.89019, 1.04924, 0.90872, 0.74889, 0.85938, 0.87891, 0.79795, 0.7957, 0.81055, 0.77892, 0.97447, 0.82577, 0.97466, 0.87179, 0.95958, 0.77892, 0.94252, 0.95612, 0.8753, 1.02988, 0.92733, 0.94252, 0.87411, 0.84021, 0.8728, 0.95612, 0.74081, 0.8753, 1.02189, 1.02988, 0.84814, 0.87445, 0.91822, 0.84723, 0.85668, 0.86331, 0.81344, 0.87581, 0.76422, 0.82046, 0.96057, 0.92733, 0.99375, 0.78022, 0.95452, 0.86015, 1.02988, 0.92733, 0.86331, 0.92733, 0.86015, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90631, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88323, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85174, 1, 1, 1, 1, 1, 1, 0.96068, 0.95794, 0.96068, 0.95794, 0.96068, 0.95794, 0.77892, 0.84548, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.89807, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71094, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.92972, 0.83406, 0.91133, 0.83326, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90616, 1.81055, 0.90527, 1.81055, 1.3107, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const CalibriBoldItalicMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; const CalibriItalicFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39543, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.72346, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89249, 0.84118, 0.77452, 0.85374, 0.75186, 0.67789, 0.79776, 0.88844, 0.85066, 0.94309, 0.77818, 0.7306, 0.76659, 1.10369, 1.38313, 1.10369, 1.06139, 0.89552, 0.8739, 0.9245, 0.9245, 0.83203, 0.9245, 0.85865, 1.09842, 0.9245, 0.9245, 1.03297, 1.07692, 0.90918, 1.03297, 0.94959, 0.9245, 0.92274, 0.9245, 0.9245, 1.02933, 0.77832, 1.20562, 0.9245, 0.8916, 0.98986, 0.86621, 0.89453, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.16359, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.72346, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89249, 0.84118, 0.84118, 0.84118, 0.84118, 0.84118, 0.85284, 0.84557, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.84843, 0.83203, 0.85865, 0.85865, 0.85865, 0.85865, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.9245, 0.92274, 0.92274, 0.92274, 0.92274, 0.92274, 0.90747, 0.86651, 0.9245, 0.9245, 0.9245, 0.9245, 0.89453, 0.9245, 0.89453, 0.8675, 0.9245, 0.8675, 0.9245, 0.8675, 0.9245, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.85193, 0.8875, 0.86477, 0.99034, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.81105, 0.9245, 0.81105, 0.9245, 0.81105, 0.9245, 1, 1, 0.86275, 0.9245, 0.90872, 0.93591, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77896, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.9375, 0.98156, 0.93407, 0.77261, 1.11429, 0.89249, 0.9245, 1, 1, 0.89249, 0.9245, 0.92534, 0.86698, 0.9245, 0.84118, 0.92274, 0.84118, 0.92274, 0.84118, 0.92274, 0.8667, 0.86291, 0.75186, 1.02933, 1, 1, 0.75186, 1.02933, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 1, 1, 0.79776, 0.97655, 0.79776, 1.23023, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.94309, 0.98986, 0.7306, 0.89453, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.9245, 0.76318, 0.84843, 0.84557, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67009, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.8675, 0.90861, 0.6192, 0.7363, 0.64824, 0.82411, 0.56321, 0.85696, 1.23516, 0.8675, 0.81552, 0.7286, 0.84134, 0.73206, 0.76659, 0.86275, 0.84369, 0.90685, 0.77892, 0.85871, 1.02638, 0.89249, 0.75828, 0.84118, 0.85984, 0.77452, 0.76466, 0.79776, 0.7306, 0.90782, 0.77818, 0.903, 0.87291, 0.90685, 0.7306, 0.99058, 1.03667, 0.94635, 1.23516, 0.9849, 0.99058, 0.92393, 0.8916, 0.942, 1.03667, 0.75026, 0.94635, 1.0297, 1.23516, 0.90918, 0.94048, 0.98217, 0.89746, 0.84153, 0.92274, 0.82507, 0.88832, 0.84438, 0.88178, 1.03525, 0.9849, 1.00225, 0.78086, 0.97248, 0.89404, 1.23516, 0.9849, 0.92274, 0.9849, 0.89404, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89693, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90933, 1, 1, 1, 1, 1, 1, 0.94309, 0.98986, 0.94309, 0.98986, 0.94309, 0.98986, 0.7306, 0.89453, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.68994, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.97858, 0.82616, 0.91133, 0.83437, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90572, 1.81055, 0.90749, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85284, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const CalibriItalicMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; const CalibriRegularFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39016, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.73834, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89385, 0.85122, 0.77452, 0.86503, 0.75186, 0.68887, 0.79776, 0.88844, 0.85066, 0.94258, 0.77818, 0.7306, 0.76659, 1.10369, 1.39016, 1.10369, 1.06139, 0.89552, 0.8739, 0.86128, 0.94469, 0.8457, 0.94469, 0.89464, 1.09842, 0.84636, 0.94469, 1.03297, 1.07692, 0.90918, 1.03297, 0.95897, 0.94469, 0.9482, 0.94469, 0.94469, 1.04692, 0.78223, 1.20562, 0.94469, 0.90332, 0.98986, 0.86621, 0.90527, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.08707, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.73834, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89385, 0.85122, 0.85122, 0.85122, 0.85122, 0.85122, 0.85284, 0.85311, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.8693, 0.8457, 0.89464, 0.89464, 0.89464, 0.89464, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.94469, 0.9482, 0.9482, 0.9482, 0.9482, 0.9482, 0.90747, 0.86651, 0.94469, 0.94469, 0.94469, 0.94469, 0.90527, 0.94469, 0.90527, 0.8675, 0.86128, 0.8675, 0.86128, 0.8675, 0.86128, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.85193, 0.92454, 0.86477, 0.9921, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.81105, 0.84636, 0.81105, 0.84636, 0.81105, 0.84636, 1, 1, 0.86275, 0.94469, 0.90872, 0.95786, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77741, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.90452, 0.98156, 1.11842, 0.77261, 1.11429, 0.89385, 0.94469, 1, 1, 0.89385, 0.94469, 0.95877, 0.86901, 0.94469, 0.85122, 0.9482, 0.85122, 0.9482, 0.85122, 0.9482, 0.8667, 0.90016, 0.75186, 1.04692, 1, 1, 0.75186, 1.04692, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 1, 1, 0.79776, 0.92188, 0.79776, 1.23023, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.94258, 0.98986, 0.7306, 0.90527, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.86128, 0.76318, 0.8693, 0.85311, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67742, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.86686, 0.90861, 0.62267, 0.74359, 0.65649, 0.85498, 0.56963, 0.88254, 1.23516, 0.8675, 0.81552, 0.75443, 0.84503, 0.73206, 0.76659, 0.86275, 0.85122, 0.90685, 0.77892, 0.85746, 1.02638, 0.89385, 0.75657, 0.85122, 0.86275, 0.77452, 0.74171, 0.79776, 0.7306, 0.95165, 0.77818, 0.89772, 0.88831, 0.90685, 0.7306, 0.98142, 1.02191, 0.96576, 1.23516, 0.99018, 0.98142, 0.9236, 0.89258, 0.94035, 1.02191, 0.78848, 0.96576, 0.9561, 1.23516, 0.90918, 0.92578, 0.95424, 0.89746, 0.83969, 0.9482, 0.80113, 0.89442, 0.85208, 0.86155, 0.98022, 0.99018, 1.00452, 0.81209, 0.99247, 0.89181, 1.23516, 0.99018, 0.9482, 0.99018, 0.89181, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88844, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89464, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96766, 1, 1, 1, 1, 1, 1, 0.94258, 0.98986, 0.94258, 0.98986, 0.94258, 0.98986, 0.7306, 0.90527, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.69043, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.99331, 0.82616, 0.91133, 0.84286, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90527, 1.81055, 0.90527, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1.07185, 0.99413, 0.96334, 1.08065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const CalibriRegularMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; ;// CONCATENATED MODULE: ./src/core/helvetica_factors.js const HelveticaBoldFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.03374, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.00042, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.03828, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00034, 0.99977, 1, 0.99997, 1.00026, 1.00078, 1.00036, 0.99973, 1.00013, 1.0006, 0.99977, 0.99977, 0.99988, 0.85148, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 1.00069, 1.00022, 0.99977, 1.00001, 0.99984, 1.00026, 1.00001, 1.00024, 1.00001, 0.9999, 1, 1.0006, 1.00001, 1.00041, 0.99962, 1.00026, 1.0006, 0.99995, 1.00041, 0.99942, 0.99973, 0.99927, 1.00082, 0.99902, 1.00026, 1.00087, 1.0006, 1.00069, 0.99973, 0.99867, 0.99973, 0.9993, 1.00026, 1.00049, 1.00056, 1, 0.99988, 0.99935, 0.99995, 0.99954, 1.00055, 0.99945, 1.00032, 1.0006, 0.99995, 1.00026, 0.99995, 1.00032, 1.00001, 1.00008, 0.99971, 1.00019, 0.9994, 1.00001, 1.0006, 1.00044, 0.99973, 1.00023, 1.00047, 1, 0.99942, 0.99561, 0.99989, 1.00035, 0.99977, 1.00035, 0.99977, 1.00019, 0.99944, 1.00001, 1.00021, 0.99926, 1.00035, 1.00035, 0.99942, 1.00048, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.99989, 1.00057, 1.00001, 0.99936, 1.00052, 1.00012, 0.99996, 1.00043, 1, 1.00035, 0.9994, 0.99976, 1.00035, 0.99973, 1.00052, 1.00041, 1.00119, 1.00037, 0.99973, 1.00002, 0.99986, 1.00041, 1.00041, 0.99902, 0.9996, 1.00034, 0.99999, 1.00026, 0.99999, 1.00026, 0.99973, 1.00052, 0.99973, 1, 0.99973, 1.00041, 1.00075, 0.9994, 1.0003, 0.99999, 1, 1.00041, 0.99955, 1, 0.99915, 0.99973, 0.99973, 1.00026, 1.00119, 0.99955, 0.99973, 1.0006, 0.99911, 1.0006, 1.00026, 0.99972, 1.00026, 0.99902, 1.00041, 0.99973, 0.99999, 1, 1, 1.00038, 1.0005, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 1.00047, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const HelveticaBoldMetrics = { lineHeight: 1.2, lineGap: 0.2 }; const HelveticaBoldItalicFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.0044, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99971, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.01011, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99977, 1, 1, 1.00026, 0.99969, 0.99972, 0.99981, 0.9998, 1.0006, 0.99977, 0.99977, 1.00022, 0.91155, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 0.99966, 1.00022, 1.00032, 1.00001, 0.99944, 1.00026, 1.00001, 0.99968, 1.00001, 1.00047, 1, 1.0006, 1.00001, 0.99981, 1.00101, 1.00026, 1.0006, 0.99948, 0.99981, 1.00064, 0.99973, 0.99942, 1.00101, 1.00061, 1.00026, 1.00069, 1.0006, 1.00014, 0.99973, 1.01322, 0.99973, 1.00065, 1.00026, 1.00012, 0.99923, 1, 1.00064, 1.00076, 0.99948, 1.00055, 1.00063, 1.00007, 0.99943, 1.0006, 0.99948, 1.00026, 0.99948, 0.99943, 1.00001, 1.00001, 1.00029, 1.00038, 1.00035, 1.00001, 1.0006, 1.0006, 0.99973, 0.99978, 1.00001, 1.00057, 0.99989, 0.99967, 0.99964, 0.99967, 0.99977, 0.99999, 0.99977, 1.00038, 0.99977, 1.00001, 0.99973, 1.00066, 0.99967, 0.99967, 1.00041, 0.99998, 0.99999, 0.99977, 1.00022, 0.99967, 1.00001, 0.99977, 1.00026, 0.99964, 1.00031, 1.00001, 0.99999, 0.99999, 1, 1.00023, 1, 1, 0.99999, 1.00035, 1.00001, 0.99999, 0.99973, 0.99977, 0.99999, 1.00058, 0.99973, 0.99973, 0.99955, 0.9995, 1.00026, 1.00026, 1.00032, 0.99989, 1.00034, 0.99999, 1.00026, 1.00026, 1.00026, 0.99973, 0.45998, 0.99973, 1.00026, 0.99973, 1.00001, 0.99999, 0.99982, 0.99994, 0.99996, 1, 1.00042, 1.00044, 1.00029, 1.00023, 0.99973, 0.99973, 1.00026, 0.99949, 1.00002, 0.99973, 1.0006, 1.0006, 1.0006, 0.99975, 1.00026, 1.00026, 1.00032, 0.98685, 0.99973, 1.00026, 1, 1, 0.99966, 1.00044, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1, 0.99973, 0.99971, 0.99978, 1, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00098, 1, 1, 1, 1.00049, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const HelveticaBoldItalicMetrics = { lineHeight: 1.35, lineGap: 0.2 }; const HelveticaItalicFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.0288, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 0.99946, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.06311, 0.99973, 1.00024, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00041, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.89547, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00001, 1, 1.00054, 0.99977, 1.00084, 1.00007, 0.99973, 1.00013, 0.99924, 1.00001, 1.00001, 0.99945, 0.91221, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00001, 0.99999, 0.99977, 0.99933, 1.00022, 1.00054, 1.00001, 1.00065, 1.00026, 1.00001, 1.0001, 1.00001, 1.00052, 1, 1.0006, 1.00001, 0.99945, 0.99897, 0.99968, 0.99924, 1.00036, 0.99945, 0.99949, 1, 1.0006, 0.99897, 0.99918, 0.99968, 0.99911, 0.99924, 1, 0.99962, 1.01487, 1, 1.0005, 0.99973, 1.00012, 1.00043, 1, 0.99995, 0.99994, 1.00036, 0.99947, 1.00019, 1.00063, 1.00025, 0.99924, 1.00036, 0.99973, 1.00036, 1.00025, 1.00001, 1.00001, 1.00027, 1.0001, 1.00068, 1.00001, 1.0006, 1.0006, 1, 1.00008, 0.99957, 0.99972, 0.9994, 0.99954, 0.99975, 1.00051, 1.00001, 1.00019, 1.00001, 1.0001, 0.99986, 1.00001, 1.00001, 1.00038, 0.99954, 0.99954, 0.9994, 1.00066, 0.99999, 0.99977, 1.00022, 1.00054, 1.00001, 0.99977, 1.00026, 0.99975, 1.0001, 1.00001, 0.99993, 0.9995, 0.99955, 1.00016, 0.99978, 0.99974, 1.00019, 1.00022, 0.99955, 1.00053, 0.99973, 1.00089, 1.00005, 0.99967, 1.00048, 0.99973, 1.00002, 1.00034, 0.99973, 0.99973, 0.99964, 1.00006, 1.00066, 0.99947, 0.99973, 0.98894, 0.99973, 1, 0.44898, 1, 0.99946, 1, 1.00039, 1.00082, 0.99991, 0.99991, 0.99985, 1.00022, 1.00023, 1.00061, 1.00006, 0.99966, 0.99973, 0.99973, 0.99973, 1.00019, 1.0008, 1, 0.99924, 0.99924, 0.99924, 0.99983, 1.00044, 0.99973, 0.99964, 0.98332, 1, 0.99973, 1, 1, 0.99962, 0.99895, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 1.00423, 0.99925, 0.99999, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00049, 1, 1.00245, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 1.00003, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const HelveticaItalicMetrics = { lineHeight: 1.35, lineGap: 0.2 }; const HelveticaRegularFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.04596, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 1.00019, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.02572, 0.99973, 1.00005, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99999, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.84533, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99928, 1, 0.99977, 1.00013, 1.00055, 0.99947, 0.99945, 0.99941, 0.99924, 1.00001, 1.00001, 1.0004, 0.91621, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00005, 0.99999, 0.99977, 1.00015, 1.00022, 0.99977, 1.00001, 0.99973, 1.00026, 1.00001, 1.00019, 1.00001, 0.99946, 1, 1.0006, 1.00001, 0.99978, 1.00045, 0.99973, 0.99924, 1.00023, 0.99978, 0.99966, 1, 1.00065, 1.00045, 1.00019, 0.99973, 0.99973, 0.99924, 1, 1, 0.96499, 1, 1.00055, 0.99973, 1.00008, 1.00027, 1, 0.9997, 0.99995, 1.00023, 0.99933, 1.00019, 1.00015, 1.00031, 0.99924, 1.00023, 0.99973, 1.00023, 1.00031, 1.00001, 0.99928, 1.00029, 1.00092, 1.00035, 1.00001, 1.0006, 1.0006, 1, 0.99988, 0.99975, 1, 1.00082, 0.99561, 0.9996, 1.00035, 1.00001, 0.99962, 1.00001, 1.00092, 0.99964, 1.00001, 0.99963, 0.99999, 1.00035, 1.00035, 1.00082, 0.99962, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.9996, 0.99967, 1.00001, 1.00034, 1.00074, 1.00054, 1.00053, 1.00063, 0.99971, 0.99962, 1.00035, 0.99975, 0.99977, 0.99973, 1.00043, 0.99953, 1.0007, 0.99915, 0.99973, 1.00008, 0.99892, 1.00073, 1.00073, 1.00114, 0.99915, 1.00073, 0.99955, 0.99973, 1.00092, 0.99973, 1, 0.99998, 1, 1.0003, 1, 1.00043, 1.00001, 0.99969, 1.0003, 1, 1.00035, 1.00001, 0.9995, 1, 1.00092, 0.99973, 0.99973, 0.99973, 1.0007, 0.9995, 1, 0.99924, 1.0006, 0.99924, 0.99972, 1.00062, 0.99973, 1.00114, 1.00073, 1, 0.99955, 1, 1, 1.00047, 0.99968, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 0.99925, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const HelveticaRegularMetrics = { lineHeight: 1.2, lineGap: 0.2 }; ;// CONCATENATED MODULE: ./src/core/liberationsans_widths.js const LiberationSansBoldWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 719, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 785, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 385, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 465, 722, 333, 853, 906, 474, 825, 927, 838, 278, 722, 722, 601, 719, 667, 611, 722, 778, 278, 722, 667, 833, 722, 644, 778, 722, 667, 600, 611, 667, 821, 667, 809, 802, 278, 667, 615, 451, 611, 278, 582, 615, 610, 556, 606, 475, 460, 611, 541, 278, 558, 556, 612, 556, 445, 611, 766, 619, 520, 684, 446, 582, 715, 576, 753, 845, 278, 582, 611, 582, 845, 667, 669, 885, 567, 711, 667, 278, 276, 556, 1094, 1062, 875, 610, 722, 622, 719, 722, 719, 722, 567, 712, 667, 904, 626, 719, 719, 610, 702, 833, 722, 778, 719, 667, 722, 611, 622, 854, 667, 730, 703, 1005, 1019, 870, 979, 719, 711, 1031, 719, 556, 618, 615, 417, 635, 556, 709, 497, 615, 615, 500, 635, 740, 604, 611, 604, 611, 556, 490, 556, 875, 556, 615, 581, 833, 844, 729, 854, 615, 552, 854, 583, 556, 556, 611, 417, 552, 556, 278, 281, 278, 969, 906, 611, 500, 615, 556, 604, 778, 611, 487, 447, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1094, 556, 885, 489, 1115, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333]; const LiberationSansBoldMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; const LiberationSansBoldItalicWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 740, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 782, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 396, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 722, 333, 854, 906, 473, 844, 930, 847, 278, 722, 722, 610, 671, 667, 611, 722, 778, 278, 722, 667, 833, 722, 657, 778, 718, 667, 590, 611, 667, 822, 667, 829, 781, 278, 667, 620, 479, 611, 278, 591, 620, 621, 556, 610, 479, 492, 611, 558, 278, 566, 556, 603, 556, 450, 611, 712, 605, 532, 664, 409, 591, 704, 578, 773, 834, 278, 591, 611, 591, 834, 667, 667, 886, 614, 719, 667, 278, 278, 556, 1094, 1042, 854, 622, 719, 677, 719, 722, 708, 722, 614, 722, 667, 927, 643, 719, 719, 615, 687, 833, 722, 778, 719, 667, 722, 611, 677, 781, 667, 729, 708, 979, 989, 854, 1000, 708, 719, 1042, 729, 556, 619, 604, 534, 618, 556, 736, 510, 611, 611, 507, 622, 740, 604, 611, 611, 611, 556, 889, 556, 885, 556, 646, 583, 889, 935, 707, 854, 594, 552, 865, 589, 556, 556, 611, 469, 563, 556, 278, 278, 278, 969, 906, 611, 507, 619, 556, 611, 778, 611, 575, 467, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1104, 556, 885, 516, 1146, 1000, 768, 600, 834, 834, 834, 834, 999, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333]; const LiberationSansBoldItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; const LiberationSansItalicWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 625, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 733, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 281, 556, 400, 556, 222, 722, 556, 722, 556, 722, 556, 615, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 354, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 789, 846, 389, 794, 865, 775, 222, 667, 667, 570, 671, 667, 611, 722, 778, 278, 667, 667, 833, 722, 648, 778, 725, 667, 600, 611, 667, 837, 667, 831, 761, 278, 667, 570, 439, 555, 222, 550, 570, 571, 500, 556, 439, 463, 555, 542, 222, 500, 492, 548, 500, 447, 556, 670, 573, 486, 603, 374, 550, 652, 546, 728, 779, 222, 550, 556, 550, 779, 667, 667, 843, 544, 708, 667, 278, 278, 500, 1066, 982, 844, 589, 715, 639, 724, 667, 651, 667, 544, 704, 667, 917, 614, 715, 715, 589, 686, 833, 722, 778, 725, 667, 722, 611, 639, 795, 667, 727, 673, 920, 923, 805, 886, 651, 694, 1022, 682, 556, 562, 522, 493, 553, 556, 688, 465, 556, 556, 472, 564, 686, 550, 556, 556, 556, 500, 833, 500, 835, 500, 572, 518, 830, 851, 621, 736, 526, 492, 752, 534, 556, 556, 556, 378, 496, 500, 222, 222, 222, 910, 828, 556, 472, 565, 500, 556, 778, 556, 492, 339, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1083, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 998, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 584, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285]; const LiberationSansItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; const LiberationSansRegularWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 615, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 735, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 292, 556, 334, 556, 222, 722, 556, 722, 556, 722, 556, 604, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 375, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 784, 838, 384, 774, 855, 752, 222, 667, 667, 551, 668, 667, 611, 722, 778, 278, 667, 668, 833, 722, 650, 778, 722, 667, 618, 611, 667, 798, 667, 835, 748, 278, 667, 578, 446, 556, 222, 547, 578, 575, 500, 557, 446, 441, 556, 556, 222, 500, 500, 576, 500, 448, 556, 690, 569, 482, 617, 395, 547, 648, 525, 713, 781, 222, 547, 556, 547, 781, 667, 667, 865, 542, 719, 667, 278, 278, 500, 1057, 1010, 854, 583, 722, 635, 719, 667, 656, 667, 542, 677, 667, 923, 604, 719, 719, 583, 656, 833, 722, 778, 719, 667, 722, 611, 635, 760, 667, 740, 667, 917, 938, 792, 885, 656, 719, 1010, 722, 556, 573, 531, 365, 583, 556, 669, 458, 559, 559, 438, 583, 688, 552, 556, 542, 556, 500, 458, 500, 823, 500, 573, 521, 802, 823, 625, 719, 521, 510, 750, 542, 556, 556, 556, 365, 510, 500, 222, 278, 222, 906, 812, 556, 438, 559, 500, 552, 778, 556, 489, 411, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1073, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285]; const LiberationSansRegularMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; ;// CONCATENATED MODULE: ./src/core/myriadpro_factors.js const MyriadProBoldFactors = [1.36898, 1, 1, 0.72706, 0.80479, 0.83734, 0.98894, 0.99793, 0.9897, 0.93884, 0.86209, 0.94292, 0.94292, 1.16661, 1.02058, 0.93582, 0.96694, 0.93582, 1.19137, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.72851, 0.78966, 0.90838, 0.83637, 0.82391, 0.96376, 0.80061, 0.86275, 0.8768, 0.95407, 1.0258, 0.73901, 0.85022, 0.83655, 1.0156, 0.95546, 0.92179, 0.87107, 0.92179, 0.82114, 0.8096, 0.89713, 0.94438, 0.95353, 0.94083, 0.91905, 0.90406, 0.9446, 0.94292, 1.18777, 0.94292, 1.02058, 0.89903, 0.90088, 0.94938, 0.97898, 0.81093, 0.97571, 0.94938, 1.024, 0.9577, 0.95933, 0.98621, 1.0474, 0.97455, 0.98981, 0.9672, 0.95933, 0.9446, 0.97898, 0.97407, 0.97646, 0.78036, 1.10208, 0.95442, 0.95298, 0.97579, 0.9332, 0.94039, 0.938, 0.80687, 1.01149, 0.80687, 1.02058, 0.80479, 0.99793, 0.99793, 0.99793, 0.99793, 1.01149, 1.00872, 0.90088, 0.91882, 1.0213, 0.8361, 1.02058, 0.62295, 0.54324, 0.89022, 1.08595, 1, 1, 0.90088, 1, 0.97455, 0.93582, 0.90088, 1, 1.05686, 0.8361, 0.99642, 0.99642, 0.99642, 0.72851, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.868, 0.82391, 0.80061, 0.80061, 0.80061, 0.80061, 1.0258, 1.0258, 1.0258, 1.0258, 0.97484, 0.95546, 0.92179, 0.92179, 0.92179, 0.92179, 0.92179, 1.02058, 0.92179, 0.94438, 0.94438, 0.94438, 0.94438, 0.90406, 0.86958, 0.98225, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.9031, 0.81093, 0.94938, 0.94938, 0.94938, 0.94938, 0.98621, 0.98621, 0.98621, 0.98621, 0.93969, 0.95933, 0.9446, 0.9446, 0.9446, 0.9446, 0.9446, 1.08595, 0.9446, 0.95442, 0.95442, 0.95442, 0.95442, 0.94039, 0.97898, 0.94039, 0.90838, 0.94938, 0.90838, 0.94938, 0.90838, 0.94938, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.96376, 0.84313, 0.97484, 0.97571, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.8768, 0.9577, 0.8768, 0.9577, 0.8768, 0.9577, 1, 1, 0.95407, 0.95933, 0.97069, 0.95933, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 0.887, 1.01591, 0.73901, 1.0474, 1, 1, 0.97455, 0.83655, 0.98981, 1, 1, 0.83655, 0.73977, 0.83655, 0.73903, 0.84638, 1.033, 0.95546, 0.95933, 1, 1, 0.95546, 0.95933, 0.8271, 0.95417, 0.95933, 0.92179, 0.9446, 0.92179, 0.9446, 0.92179, 0.9446, 0.936, 0.91964, 0.82114, 0.97646, 1, 1, 0.82114, 0.97646, 0.8096, 0.78036, 0.8096, 0.78036, 1, 1, 0.8096, 0.78036, 1, 1, 0.89713, 0.77452, 0.89713, 1.10208, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94083, 0.97579, 0.90406, 0.94039, 0.90406, 0.9446, 0.938, 0.9446, 0.938, 0.9446, 0.938, 1, 0.99793, 0.90838, 0.94938, 0.868, 0.9031, 0.92179, 0.9446, 1, 1, 0.89713, 1.10208, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90989, 0.9358, 0.91945, 0.83181, 0.75261, 0.87992, 0.82976, 0.96034, 0.83689, 0.97268, 1.0078, 0.90838, 0.83637, 0.8019, 0.90157, 0.80061, 0.9446, 0.95407, 0.92436, 1.0258, 0.85022, 0.97153, 1.0156, 0.95546, 0.89192, 0.92179, 0.92361, 0.87107, 0.96318, 0.89713, 0.93704, 0.95638, 0.91905, 0.91709, 0.92796, 1.0258, 0.93704, 0.94836, 1.0373, 0.95933, 1.0078, 0.95871, 0.94836, 0.96174, 0.92601, 0.9498, 0.98607, 0.95776, 0.95933, 1.05453, 1.0078, 0.98275, 0.9314, 0.95617, 0.91701, 1.05993, 0.9446, 0.78367, 0.9553, 1, 0.86832, 1.0128, 0.95871, 0.99394, 0.87548, 0.96361, 0.86774, 1.0078, 0.95871, 0.9446, 0.95871, 0.86774, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.94083, 0.97579, 0.94083, 0.97579, 0.94083, 0.97579, 0.90406, 0.94039, 0.96694, 1, 0.89903, 1, 1, 1, 0.93582, 0.93582, 0.93582, 1, 0.908, 0.908, 0.918, 0.94219, 0.94219, 0.96544, 1, 1.285, 1, 1, 0.81079, 0.81079, 1, 1, 0.74854, 1, 1, 1, 1, 0.99793, 1, 1, 1, 0.65, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.17173, 1, 0.80535, 0.76169, 1.02058, 1.0732, 1.05486, 1, 1, 1.30692, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.16161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const MyriadProBoldMetrics = { lineHeight: 1.2, lineGap: 0.2 }; const MyriadProBoldItalicFactors = [1.36898, 1, 1, 0.66227, 0.80779, 0.81625, 0.97276, 0.97276, 0.97733, 0.92222, 0.83266, 0.94292, 0.94292, 1.16148, 1.02058, 0.93582, 0.96694, 0.93582, 1.17337, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.71541, 0.76813, 0.85576, 0.80591, 0.80729, 0.94299, 0.77512, 0.83655, 0.86523, 0.92222, 0.98621, 0.71743, 0.81698, 0.79726, 0.98558, 0.92222, 0.90637, 0.83809, 0.90637, 0.80729, 0.76463, 0.86275, 0.90699, 0.91605, 0.9154, 0.85308, 0.85458, 0.90531, 0.94292, 1.21296, 0.94292, 1.02058, 0.89903, 1.18616, 0.99613, 0.91677, 0.78216, 0.91677, 0.90083, 0.98796, 0.9135, 0.92168, 0.95381, 0.98981, 0.95298, 0.95381, 0.93459, 0.92168, 0.91513, 0.92004, 0.91677, 0.95077, 0.748, 1.04502, 0.91677, 0.92061, 0.94236, 0.89544, 0.89364, 0.9, 0.80687, 0.8578, 0.80687, 1.02058, 0.80779, 0.97276, 0.97276, 0.97276, 0.97276, 0.8578, 0.99973, 1.18616, 0.91339, 1.08074, 0.82891, 1.02058, 0.55509, 0.71526, 0.89022, 1.08595, 1, 1, 1.18616, 1, 0.96736, 0.93582, 1.18616, 1, 1.04864, 0.82711, 0.99043, 0.99043, 0.99043, 0.71541, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.845, 0.80729, 0.77512, 0.77512, 0.77512, 0.77512, 0.98621, 0.98621, 0.98621, 0.98621, 0.95961, 0.92222, 0.90637, 0.90637, 0.90637, 0.90637, 0.90637, 1.02058, 0.90251, 0.90699, 0.90699, 0.90699, 0.90699, 0.85458, 0.83659, 0.94951, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.85811, 0.78216, 0.90083, 0.90083, 0.90083, 0.90083, 0.95381, 0.95381, 0.95381, 0.95381, 0.9135, 0.92168, 0.91513, 0.91513, 0.91513, 0.91513, 0.91513, 1.08595, 0.91677, 0.91677, 0.91677, 0.91677, 0.91677, 0.89364, 0.92332, 0.89364, 0.85576, 0.99613, 0.85576, 0.99613, 0.85576, 0.99613, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.94299, 0.76783, 0.95961, 0.91677, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.86523, 0.9135, 0.86523, 0.9135, 0.86523, 0.9135, 1, 1, 0.92222, 0.92168, 0.92222, 0.92168, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.86036, 0.97096, 0.71743, 0.98981, 1, 1, 0.95298, 0.79726, 0.95381, 1, 1, 0.79726, 0.6894, 0.79726, 0.74321, 0.81691, 1.0006, 0.92222, 0.92168, 1, 1, 0.92222, 0.92168, 0.79464, 0.92098, 0.92168, 0.90637, 0.91513, 0.90637, 0.91513, 0.90637, 0.91513, 0.909, 0.87514, 0.80729, 0.95077, 1, 1, 0.80729, 0.95077, 0.76463, 0.748, 0.76463, 0.748, 1, 1, 0.76463, 0.748, 1, 1, 0.86275, 0.72651, 0.86275, 1.04502, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.9154, 0.94236, 0.85458, 0.89364, 0.85458, 0.90531, 0.9, 0.90531, 0.9, 0.90531, 0.9, 1, 0.97276, 0.85576, 0.99613, 0.845, 0.85811, 0.90251, 0.91677, 1, 1, 0.86275, 1.04502, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.00899, 1.30628, 0.85576, 0.80178, 0.66862, 0.7927, 0.69323, 0.88127, 0.72459, 0.89711, 0.95381, 0.85576, 0.80591, 0.7805, 0.94729, 0.77512, 0.90531, 0.92222, 0.90637, 0.98621, 0.81698, 0.92655, 0.98558, 0.92222, 0.85359, 0.90637, 0.90976, 0.83809, 0.94523, 0.86275, 0.83509, 0.93157, 0.85308, 0.83392, 0.92346, 0.98621, 0.83509, 0.92886, 0.91324, 0.92168, 0.95381, 0.90646, 0.92886, 0.90557, 0.86847, 0.90276, 0.91324, 0.86842, 0.92168, 0.99531, 0.95381, 0.9224, 0.85408, 0.92699, 0.86847, 1.0051, 0.91513, 0.80487, 0.93481, 1, 0.88159, 1.05214, 0.90646, 0.97355, 0.81539, 0.89398, 0.85923, 0.95381, 0.90646, 0.91513, 0.90646, 0.85923, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9154, 0.94236, 0.9154, 0.94236, 0.9154, 0.94236, 0.85458, 0.89364, 0.96694, 1, 0.89903, 1, 1, 1, 0.91782, 0.91782, 0.91782, 1, 0.896, 0.896, 0.896, 0.9332, 0.9332, 0.95973, 1, 1.26, 1, 1, 0.80479, 0.80178, 1, 1, 0.85633, 1, 1, 1, 1, 0.97276, 1, 1, 1, 0.698, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.14542, 1, 0.79199, 0.78694, 1.02058, 1.03493, 1.05486, 1, 1, 1.23026, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.20006, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const MyriadProBoldItalicMetrics = { lineHeight: 1.2, lineGap: 0.2 }; const MyriadProItalicFactors = [1.36898, 1, 1, 0.65507, 0.84943, 0.85639, 0.88465, 0.88465, 0.86936, 0.88307, 0.86948, 0.85283, 0.85283, 1.06383, 1.02058, 0.75945, 0.9219, 0.75945, 1.17337, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.75945, 0.75945, 1.02058, 1.02058, 1.02058, 0.69046, 0.70926, 0.85158, 0.77812, 0.76852, 0.89591, 0.70466, 0.76125, 0.80094, 0.86822, 0.83864, 0.728, 0.77212, 0.79475, 0.93637, 0.87514, 0.8588, 0.76013, 0.8588, 0.72421, 0.69866, 0.77598, 0.85991, 0.80811, 0.87832, 0.78112, 0.77512, 0.8562, 1.0222, 1.18417, 1.0222, 1.27014, 0.89903, 1.15012, 0.93859, 0.94399, 0.846, 0.94399, 0.81453, 1.0186, 0.94219, 0.96017, 1.03075, 1.02175, 0.912, 1.03075, 0.96998, 0.96017, 0.93859, 0.94399, 0.94399, 0.95493, 0.746, 1.12658, 0.94578, 0.91, 0.979, 0.882, 0.882, 0.83, 0.85034, 0.83537, 0.85034, 1.02058, 0.70869, 0.88465, 0.88465, 0.88465, 0.88465, 0.83537, 0.90083, 1.15012, 0.9161, 0.94565, 0.73541, 1.02058, 0.53609, 0.69353, 0.79519, 1.08595, 1, 1, 1.15012, 1, 0.91974, 0.75945, 1.15012, 1, 0.9446, 0.73361, 0.9005, 0.9005, 0.9005, 0.62864, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.773, 0.76852, 0.70466, 0.70466, 0.70466, 0.70466, 0.83864, 0.83864, 0.83864, 0.83864, 0.90561, 0.87514, 0.8588, 0.8588, 0.8588, 0.8588, 0.8588, 1.02058, 0.85751, 0.85991, 0.85991, 0.85991, 0.85991, 0.77512, 0.76013, 0.88075, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.8075, 0.846, 0.81453, 0.81453, 0.81453, 0.81453, 0.82424, 0.82424, 0.82424, 0.82424, 0.9278, 0.96017, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 1.08595, 0.8562, 0.94578, 0.94578, 0.94578, 0.94578, 0.882, 0.94578, 0.882, 0.85158, 0.93859, 0.85158, 0.93859, 0.85158, 0.93859, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.89591, 0.8544, 0.90561, 0.94399, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.80094, 0.94219, 0.80094, 0.94219, 0.80094, 0.94219, 1, 1, 0.86822, 0.96017, 0.86822, 0.96017, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 1.03075, 0.83864, 0.82424, 0.81402, 1.02738, 0.728, 1.02175, 1, 1, 0.912, 0.79475, 1.03075, 1, 1, 0.79475, 0.83911, 0.79475, 0.66266, 0.80553, 1.06676, 0.87514, 0.96017, 1, 1, 0.87514, 0.96017, 0.86865, 0.87396, 0.96017, 0.8588, 0.93859, 0.8588, 0.93859, 0.8588, 0.93859, 0.867, 0.84759, 0.72421, 0.95493, 1, 1, 0.72421, 0.95493, 0.69866, 0.746, 0.69866, 0.746, 1, 1, 0.69866, 0.746, 1, 1, 0.77598, 0.88417, 0.77598, 1.12658, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.87832, 0.979, 0.77512, 0.882, 0.77512, 0.8562, 0.83, 0.8562, 0.83, 0.8562, 0.83, 1, 0.88465, 0.85158, 0.93859, 0.773, 0.8075, 0.85751, 0.8562, 1, 1, 0.77598, 1.12658, 1.15012, 1.15012, 1.15012, 1.15012, 1.15012, 1.15313, 1.15012, 1.15012, 1.15012, 1.08106, 1.03901, 0.85158, 0.77025, 0.62264, 0.7646, 0.65351, 0.86026, 0.69461, 0.89947, 1.03075, 0.85158, 0.77812, 0.76449, 0.88836, 0.70466, 0.8562, 0.86822, 0.8588, 0.83864, 0.77212, 0.85308, 0.93637, 0.87514, 0.82352, 0.8588, 0.85701, 0.76013, 0.89058, 0.77598, 0.8156, 0.82565, 0.78112, 0.77899, 0.89386, 0.83864, 0.8156, 0.9486, 0.92388, 0.96186, 1.03075, 0.91123, 0.9486, 0.93298, 0.878, 0.93942, 0.92388, 0.84596, 0.96186, 0.95119, 1.03075, 0.922, 0.88787, 0.95829, 0.88, 0.93559, 0.93859, 0.78815, 0.93758, 1, 0.89217, 1.03737, 0.91123, 0.93969, 0.77487, 0.85769, 0.86799, 1.03075, 0.91123, 0.93859, 0.91123, 0.86799, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87832, 0.979, 0.87832, 0.979, 0.87832, 0.979, 0.77512, 0.882, 0.9219, 1, 0.89903, 1, 1, 1, 0.87321, 0.87321, 0.87321, 1, 1.027, 1.027, 1.027, 0.86847, 0.86847, 0.79121, 1, 1.124, 1, 1, 0.73572, 0.73572, 1, 1, 0.85034, 1, 1, 1, 1, 0.88465, 1, 1, 1, 0.669, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04828, 1, 0.74948, 0.75187, 1.02058, 0.98391, 1.02119, 1, 1, 1.06233, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05233, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const MyriadProItalicMetrics = { lineHeight: 1.2, lineGap: 0.2 }; const MyriadProRegularFactors = [1.36898, 1, 1, 0.76305, 0.82784, 0.94935, 0.89364, 0.92241, 0.89073, 0.90706, 0.98472, 0.85283, 0.85283, 1.0664, 1.02058, 0.74505, 0.9219, 0.74505, 1.23456, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.74505, 0.74505, 1.02058, 1.02058, 1.02058, 0.73002, 0.72601, 0.91755, 0.8126, 0.80314, 0.92222, 0.73764, 0.79726, 0.83051, 0.90284, 0.86023, 0.74, 0.8126, 0.84869, 0.96518, 0.91115, 0.8858, 0.79761, 0.8858, 0.74498, 0.73914, 0.81363, 0.89591, 0.83659, 0.89633, 0.85608, 0.8111, 0.90531, 1.0222, 1.22736, 1.0222, 1.27014, 0.89903, 0.90088, 0.86667, 1.0231, 0.896, 1.01411, 0.90083, 1.05099, 1.00512, 0.99793, 1.05326, 1.09377, 0.938, 1.06226, 1.00119, 0.99793, 0.98714, 1.0231, 1.01231, 0.98196, 0.792, 1.19137, 0.99074, 0.962, 1.01915, 0.926, 0.942, 0.856, 0.85034, 0.92006, 0.85034, 1.02058, 0.69067, 0.92241, 0.92241, 0.92241, 0.92241, 0.92006, 0.9332, 0.90088, 0.91882, 0.93484, 0.75339, 1.02058, 0.56866, 0.54324, 0.79519, 1.08595, 1, 1, 0.90088, 1, 0.95325, 0.74505, 0.90088, 1, 0.97198, 0.75339, 0.91009, 0.91009, 0.91009, 0.66466, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.788, 0.80314, 0.73764, 0.73764, 0.73764, 0.73764, 0.86023, 0.86023, 0.86023, 0.86023, 0.92915, 0.91115, 0.8858, 0.8858, 0.8858, 0.8858, 0.8858, 1.02058, 0.8858, 0.89591, 0.89591, 0.89591, 0.89591, 0.8111, 0.79611, 0.89713, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86936, 0.896, 0.90083, 0.90083, 0.90083, 0.90083, 0.84224, 0.84224, 0.84224, 0.84224, 0.97276, 0.99793, 0.98714, 0.98714, 0.98714, 0.98714, 0.98714, 1.08595, 0.89876, 0.99074, 0.99074, 0.99074, 0.99074, 0.942, 1.0231, 0.942, 0.91755, 0.86667, 0.91755, 0.86667, 0.91755, 0.86667, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.92222, 0.93372, 0.92915, 1.01411, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.83051, 1.00512, 0.83051, 1.00512, 0.83051, 1.00512, 1, 1, 0.90284, 0.99793, 0.90976, 0.99793, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 1.05326, 0.86023, 0.84224, 0.82873, 1.07469, 0.74, 1.09377, 1, 1, 0.938, 0.84869, 1.06226, 1, 1, 0.84869, 0.83704, 0.84869, 0.81441, 0.85588, 1.08927, 0.91115, 0.99793, 1, 1, 0.91115, 0.99793, 0.91887, 0.90991, 0.99793, 0.8858, 0.98714, 0.8858, 0.98714, 0.8858, 0.98714, 0.894, 0.91434, 0.74498, 0.98196, 1, 1, 0.74498, 0.98196, 0.73914, 0.792, 0.73914, 0.792, 1, 1, 0.73914, 0.792, 1, 1, 0.81363, 0.904, 0.81363, 1.19137, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89633, 1.01915, 0.8111, 0.942, 0.8111, 0.90531, 0.856, 0.90531, 0.856, 0.90531, 0.856, 1, 0.92241, 0.91755, 0.86667, 0.788, 0.86936, 0.8858, 0.89876, 1, 1, 0.81363, 1.19137, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90388, 1.03901, 0.92138, 0.78105, 0.7154, 0.86169, 0.80513, 0.94007, 0.82528, 0.98612, 1.06226, 0.91755, 0.8126, 0.81884, 0.92819, 0.73764, 0.90531, 0.90284, 0.8858, 0.86023, 0.8126, 0.91172, 0.96518, 0.91115, 0.83089, 0.8858, 0.87791, 0.79761, 0.89297, 0.81363, 0.88157, 0.89992, 0.85608, 0.81992, 0.94307, 0.86023, 0.88157, 0.95308, 0.98699, 0.99793, 1.06226, 0.95817, 0.95308, 0.97358, 0.928, 0.98088, 0.98699, 0.92761, 0.99793, 0.96017, 1.06226, 0.986, 0.944, 0.95978, 0.938, 0.96705, 0.98714, 0.80442, 0.98972, 1, 0.89762, 1.04552, 0.95817, 0.99007, 0.87064, 0.91879, 0.88888, 1.06226, 0.95817, 0.98714, 0.95817, 0.88888, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89633, 1.01915, 0.89633, 1.01915, 0.89633, 1.01915, 0.8111, 0.942, 0.9219, 1, 0.89903, 1, 1, 1, 0.93173, 0.93173, 0.93173, 1, 1.06304, 1.06304, 1.06904, 0.89903, 0.89903, 0.80549, 1, 1.156, 1, 1, 0.76575, 0.76575, 1, 1, 0.72458, 1, 1, 1, 1, 0.92241, 1, 1, 1, 0.619, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.07257, 1, 0.74705, 0.71119, 1.02058, 1.024, 1.02119, 1, 1, 1.1536, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05638, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const MyriadProRegularMetrics = { lineHeight: 1.2, lineGap: 0.2 }; ;// CONCATENATED MODULE: ./src/core/segoeui_factors.js const SegoeuiBoldFactors = [1.76738, 1, 1, 0.99297, 0.9824, 1.04016, 1.06497, 1.03424, 0.97529, 1.17647, 1.23203, 1.1085, 1.1085, 1.16939, 1.2107, 0.9754, 1.21408, 0.9754, 1.59578, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 0.81378, 0.81378, 1.2107, 1.2107, 1.2107, 0.71703, 0.97847, 0.97363, 0.88776, 0.8641, 1.02096, 0.79795, 0.85132, 0.914, 1.06085, 1.1406, 0.8007, 0.89858, 0.83693, 1.14889, 1.09398, 0.97489, 0.92094, 0.97489, 0.90399, 0.84041, 0.95923, 1.00135, 1, 1.06467, 0.98243, 0.90996, 0.99361, 1.1085, 1.56942, 1.1085, 1.2107, 0.74627, 0.94282, 0.96752, 1.01519, 0.86304, 1.01359, 0.97278, 1.15103, 1.01359, 0.98561, 1.02285, 1.02285, 1.00527, 1.02285, 1.0302, 0.99041, 1.0008, 1.01519, 1.01359, 1.02258, 0.79104, 1.16862, 0.99041, 0.97454, 1.02511, 0.99298, 0.96752, 0.95801, 0.94856, 1.16579, 0.94856, 1.2107, 0.9824, 1.03424, 1.03424, 1, 1.03424, 1.16579, 0.8727, 1.3871, 1.18622, 1.10818, 1.04478, 1.2107, 1.18622, 0.75155, 0.94994, 1.28826, 1.21408, 1.21408, 0.91056, 1, 0.91572, 0.9754, 0.64663, 1.18328, 1.24866, 1.04478, 1.14169, 1.15749, 1.17389, 0.71703, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.93506, 0.8641, 0.79795, 0.79795, 0.79795, 0.79795, 1.1406, 1.1406, 1.1406, 1.1406, 1.02096, 1.09398, 0.97426, 0.97426, 0.97426, 0.97426, 0.97426, 1.2107, 0.97489, 1.00135, 1.00135, 1.00135, 1.00135, 0.90996, 0.92094, 1.02798, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.93136, 0.86304, 0.97278, 0.97278, 0.97278, 0.97278, 1.02285, 1.02285, 1.02285, 1.02285, 0.97122, 0.99041, 1, 1, 1, 1, 1, 1.28826, 1.0008, 0.99041, 0.99041, 0.99041, 0.99041, 0.96752, 1.01519, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 1.02096, 1.03057, 1.02096, 1.03517, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.914, 1.01359, 0.914, 1.01359, 0.914, 1.01359, 1, 1, 1.06085, 0.98561, 1.06085, 1.00879, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 0.97138, 1.08692, 0.8007, 1.02285, 1, 1, 1.00527, 0.83693, 1.02285, 1, 1, 0.83693, 0.9455, 0.83693, 0.90418, 0.83693, 1.13005, 1.09398, 0.99041, 1, 1, 1.09398, 0.99041, 0.96692, 1.09251, 0.99041, 0.97489, 1.0008, 0.97489, 1.0008, 0.97489, 1.0008, 0.93994, 0.97931, 0.90399, 1.02258, 1, 1, 0.90399, 1.02258, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 1, 1, 0.95923, 1.07034, 0.95923, 1.16862, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.06467, 1.02511, 0.90996, 0.96752, 0.90996, 0.99361, 0.95801, 0.99361, 0.95801, 0.99361, 0.95801, 1.07733, 1.03424, 0.97363, 0.96752, 0.93506, 0.93136, 0.97489, 1.0008, 1, 1, 0.95923, 1.16862, 1.15103, 1.15103, 1.01173, 1.03959, 0.75953, 0.81378, 0.79912, 1.15103, 1.21994, 0.95161, 0.87815, 1.01149, 0.81525, 0.7676, 0.98167, 1.01134, 1.02546, 0.84097, 1.03089, 1.18102, 0.97363, 0.88776, 0.85134, 0.97826, 0.79795, 0.99361, 1.06085, 0.97489, 1.1406, 0.89858, 1.0388, 1.14889, 1.09398, 0.86039, 0.97489, 1.0595, 0.92094, 0.94793, 0.95923, 0.90996, 0.99346, 0.98243, 1.02112, 0.95493, 1.1406, 0.90996, 1.03574, 1.02597, 1.0008, 1.18102, 1.06628, 1.03574, 1.0192, 1.01932, 1.00886, 0.97531, 1.0106, 1.0008, 1.13189, 1.18102, 1.02277, 0.98683, 1.0016, 0.99561, 1.07237, 1.0008, 0.90434, 0.99921, 0.93803, 0.8965, 1.23085, 1.06628, 1.04983, 0.96268, 1.0499, 0.98439, 1.18102, 1.06628, 1.0008, 1.06628, 0.98439, 0.79795, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09466, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.97278, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.02065, 1, 1, 1, 1, 1, 1, 1.06467, 1.02511, 1.06467, 1.02511, 1.06467, 1.02511, 0.90996, 0.96752, 1, 1.21408, 0.89903, 1, 1, 0.75155, 1.04394, 1.04394, 1.04394, 1.04394, 0.98633, 0.98633, 0.98633, 0.73047, 0.73047, 1.20642, 0.91211, 1.25635, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.12454, 0.93503, 1.03424, 1.19687, 1.03424, 1, 1, 1, 0.771, 1, 1, 1.15749, 1.15749, 1.15749, 1.10948, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.16897, 1, 0.96085, 0.90137, 1.2107, 1.18416, 1.13973, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21172, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18874, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.09193, 1.09193, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const SegoeuiBoldMetrics = { lineHeight: 1.33008, lineGap: 0 }; const SegoeuiBoldItalicFactors = [1.76738, 1, 1, 0.98946, 1.03959, 1.04016, 1.02809, 1.036, 0.97639, 1.10953, 1.23203, 1.11144, 1.11144, 1.16939, 1.21237, 0.9754, 1.21261, 0.9754, 1.59754, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 0.81378, 0.81378, 1.21237, 1.21237, 1.21237, 0.73541, 0.97847, 0.97363, 0.89723, 0.87897, 1.0426, 0.79429, 0.85292, 0.91149, 1.05815, 1.1406, 0.79631, 0.90128, 0.83853, 1.04396, 1.10615, 0.97552, 0.94436, 0.97552, 0.88641, 0.80527, 0.96083, 1.00135, 1, 1.06777, 0.9817, 0.91142, 0.99361, 1.11144, 1.57293, 1.11144, 1.21237, 0.74627, 1.31818, 1.06585, 0.97042, 0.83055, 0.97042, 0.93503, 1.1261, 0.97042, 0.97922, 1.14236, 0.94552, 1.01054, 1.14236, 1.02471, 0.97922, 0.94165, 0.97042, 0.97042, 1.0276, 0.78929, 1.1261, 0.97922, 0.95874, 1.02197, 0.98507, 0.96752, 0.97168, 0.95107, 1.16579, 0.95107, 1.21237, 1.03959, 1.036, 1.036, 1, 1.036, 1.16579, 0.87357, 1.31818, 1.18754, 1.26781, 1.05356, 1.21237, 1.18622, 0.79487, 0.94994, 1.29004, 1.24047, 1.24047, 1.31818, 1, 0.91484, 0.9754, 1.31818, 1.1349, 1.24866, 1.05356, 1.13934, 1.15574, 1.17389, 0.73541, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.94385, 0.87897, 0.79429, 0.79429, 0.79429, 0.79429, 1.1406, 1.1406, 1.1406, 1.1406, 1.0426, 1.10615, 0.97552, 0.97552, 0.97552, 0.97552, 0.97552, 1.21237, 0.97552, 1.00135, 1.00135, 1.00135, 1.00135, 0.91142, 0.94436, 0.98721, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 0.96705, 0.83055, 0.93503, 0.93503, 0.93503, 0.93503, 1.14236, 1.14236, 1.14236, 1.14236, 0.93125, 0.97922, 0.94165, 0.94165, 0.94165, 0.94165, 0.94165, 1.29004, 0.94165, 0.97922, 0.97922, 0.97922, 0.97922, 0.96752, 0.97042, 0.96752, 0.97363, 1.06585, 0.97363, 1.06585, 0.97363, 1.06585, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 1.0426, 1.0033, 1.0426, 0.97042, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.91149, 0.97042, 0.91149, 0.97042, 0.91149, 0.97042, 1, 1, 1.05815, 0.97922, 1.05815, 0.97922, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 0.97441, 1.04302, 0.79631, 1.01582, 1, 1, 1.01054, 0.83853, 1.14236, 1, 1, 0.83853, 1.09125, 0.83853, 0.90418, 0.83853, 1.19508, 1.10615, 0.97922, 1, 1, 1.10615, 0.97922, 1.01034, 1.10466, 0.97922, 0.97552, 0.94165, 0.97552, 0.94165, 0.97552, 0.94165, 0.91602, 0.91981, 0.88641, 1.0276, 1, 1, 0.88641, 1.0276, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 1, 1, 0.96083, 1.05403, 0.95923, 1.16862, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.06777, 1.02197, 0.91142, 0.96752, 0.91142, 0.99361, 0.97168, 0.99361, 0.97168, 0.99361, 0.97168, 1.23199, 1.036, 0.97363, 1.06585, 0.94385, 0.96705, 0.97552, 0.94165, 1, 1, 0.96083, 1.1261, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 0.95161, 1.27126, 1.00811, 0.83284, 0.77702, 0.99137, 0.95253, 1.0347, 0.86142, 1.07205, 1.14236, 0.97363, 0.89723, 0.86869, 1.09818, 0.79429, 0.99361, 1.05815, 0.97552, 1.1406, 0.90128, 1.06662, 1.04396, 1.10615, 0.84918, 0.97552, 1.04694, 0.94436, 0.98015, 0.96083, 0.91142, 1.00356, 0.9817, 1.01945, 0.98999, 1.1406, 0.91142, 1.04961, 0.9898, 1.00639, 1.14236, 1.07514, 1.04961, 0.99607, 1.02897, 1.008, 0.9898, 0.95134, 1.00639, 1.11121, 1.14236, 1.00518, 0.97981, 1.02186, 1, 1.08578, 0.94165, 0.99314, 0.98387, 0.93028, 0.93377, 1.35125, 1.07514, 1.10687, 0.93491, 1.04232, 1.00351, 1.14236, 1.07514, 0.94165, 1.07514, 1.00351, 0.79429, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09097, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.93503, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96609, 1, 1, 1, 1, 1, 1, 1.06777, 1.02197, 1.06777, 1.02197, 1.06777, 1.02197, 0.91142, 0.96752, 1, 1.21261, 0.89903, 1, 1, 0.75155, 1.04745, 1.04745, 1.04745, 1.04394, 0.98633, 0.98633, 0.98633, 0.72959, 0.72959, 1.20502, 0.91406, 1.26514, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.09125, 0.93327, 1.03336, 1.16541, 1.036, 1, 1, 1, 0.771, 1, 1, 1.15574, 1.15574, 1.15574, 1.15574, 0.86364, 0.94434, 0.86279, 0.94434, 0.86224, 1, 1, 1.16798, 1, 0.96085, 0.90068, 1.21237, 1.18416, 1.13904, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21339, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18775, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.13269, 1.13269, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const SegoeuiBoldItalicMetrics = { lineHeight: 1.33008, lineGap: 0 }; const SegoeuiItalicFactors = [1.76738, 1, 1, 0.98946, 1.14763, 1.05365, 1.06234, 0.96927, 0.92586, 1.15373, 1.18414, 0.91349, 0.91349, 1.07403, 1.17308, 0.78383, 1.20088, 0.78383, 1.42531, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78383, 0.78383, 1.17308, 1.17308, 1.17308, 0.77349, 0.94565, 0.94729, 0.85944, 0.88506, 0.9858, 0.74817, 0.80016, 0.88449, 0.98039, 0.95782, 0.69238, 0.89898, 0.83231, 0.98183, 1.03989, 0.96924, 0.86237, 0.96924, 0.80595, 0.74524, 0.86091, 0.95402, 0.94143, 0.98448, 0.8858, 0.83089, 0.93285, 1.0949, 1.39016, 1.0949, 1.45994, 0.74627, 1.04839, 0.97454, 0.97454, 0.87207, 0.97454, 0.87533, 1.06151, 0.97454, 1.00176, 1.16484, 1.08132, 0.98047, 1.16484, 1.02989, 1.01054, 0.96225, 0.97454, 0.97454, 1.06598, 0.79004, 1.16344, 1.00351, 0.94629, 0.9973, 0.91016, 0.96777, 0.9043, 0.91082, 0.92481, 0.91082, 1.17308, 0.95748, 0.96927, 0.96927, 1, 0.96927, 0.92481, 0.80597, 1.04839, 1.23393, 1.1781, 0.9245, 1.17308, 1.20808, 0.63218, 0.94261, 1.24822, 1.09971, 1.09971, 1.04839, 1, 0.85273, 0.78032, 1.04839, 1.09971, 1.22326, 0.9245, 1.09836, 1.13525, 1.15222, 0.70424, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.85498, 0.88506, 0.74817, 0.74817, 0.74817, 0.74817, 0.95782, 0.95782, 0.95782, 0.95782, 0.9858, 1.03989, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.17308, 0.96924, 0.95402, 0.95402, 0.95402, 0.95402, 0.83089, 0.86237, 0.88409, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.92916, 0.87207, 0.87533, 0.87533, 0.87533, 0.87533, 0.93146, 0.93146, 0.93146, 0.93146, 0.93854, 1.01054, 0.96225, 0.96225, 0.96225, 0.96225, 0.96225, 1.24822, 0.8761, 1.00351, 1.00351, 1.00351, 1.00351, 0.96777, 0.97454, 0.96777, 0.94729, 0.97454, 0.94729, 0.97454, 0.94729, 0.97454, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.9858, 0.95391, 0.9858, 0.97454, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.88449, 0.97454, 0.88449, 0.97454, 0.88449, 0.97454, 1, 1, 0.98039, 1.00176, 0.98039, 1.00176, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 1.16484, 0.95782, 0.93146, 0.84421, 1.12761, 0.69238, 1.08132, 1, 1, 0.98047, 0.83231, 1.16484, 1, 1, 0.84723, 1.04861, 0.84723, 0.78755, 0.83231, 1.23736, 1.03989, 1.01054, 1, 1, 1.03989, 1.01054, 0.9857, 1.03849, 1.01054, 0.96924, 0.96225, 0.96924, 0.96225, 0.96924, 0.96225, 0.92383, 0.90171, 0.80595, 1.06598, 1, 1, 0.80595, 1.06598, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 1, 1, 0.86091, 1.02759, 0.85771, 1.16344, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.98448, 0.9973, 0.83089, 0.96777, 0.83089, 0.93285, 0.9043, 0.93285, 0.9043, 0.93285, 0.9043, 1.31868, 0.96927, 0.94729, 0.97454, 0.85498, 0.92916, 0.96924, 0.8761, 1, 1, 0.86091, 1.16344, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 0.81965, 0.81965, 0.94729, 0.78032, 0.71022, 0.90883, 0.84171, 0.99877, 0.77596, 1.05734, 1.2, 0.94729, 0.85944, 0.82791, 0.9607, 0.74817, 0.93285, 0.98039, 0.96924, 0.95782, 0.89898, 0.98316, 0.98183, 1.03989, 0.78614, 0.96924, 0.97642, 0.86237, 0.86075, 0.86091, 0.83089, 0.90082, 0.8858, 0.97296, 1.01284, 0.95782, 0.83089, 1.0976, 1.04, 1.03342, 1.2, 1.0675, 1.0976, 0.98205, 1.03809, 1.05097, 1.04, 0.95364, 1.03342, 1.05401, 1.2, 1.02148, 1.0119, 1.04724, 1.0127, 1.02732, 0.96225, 0.8965, 0.97783, 0.93574, 0.94818, 1.30679, 1.0675, 1.11826, 0.99821, 1.0557, 1.0326, 1.2, 1.0675, 0.96225, 1.0675, 1.0326, 0.74817, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03754, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.98705, 1, 1, 1, 1, 1, 1, 0.98448, 0.9973, 0.98448, 0.9973, 0.98448, 0.9973, 0.83089, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 0.94945, 0.94945, 0.94945, 0.94945, 1.12317, 1.12317, 1.12317, 0.67603, 0.67603, 1.15621, 0.73584, 1.21191, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87709, 0.96927, 1.01473, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.09836, 1.09836, 1.09836, 1.01522, 0.86321, 0.94434, 0.8649, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86438, 1.17308, 1.18416, 1.14589, 0.69825, 0.97622, 1.96791, 1.24822, 1.24822, 1.17308, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.17984, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10742, 1.10742, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const SegoeuiItalicMetrics = { lineHeight: 1.33008, lineGap: 0 }; const SegoeuiRegularFactors = [1.76738, 1, 1, 0.98594, 1.02285, 1.10454, 1.06234, 0.96927, 0.92037, 1.19985, 1.2046, 0.90616, 0.90616, 1.07152, 1.1714, 0.78032, 1.20088, 0.78032, 1.40246, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78032, 0.78032, 1.1714, 1.1714, 1.1714, 0.80597, 0.94084, 0.96706, 0.85944, 0.85734, 0.97093, 0.75842, 0.79936, 0.88198, 0.9831, 0.95782, 0.71387, 0.86969, 0.84636, 1.07796, 1.03584, 0.96924, 0.83968, 0.96924, 0.82826, 0.79649, 0.85771, 0.95132, 0.93119, 0.98965, 0.88433, 0.8287, 0.93365, 1.08612, 1.3638, 1.08612, 1.45786, 0.74627, 0.80499, 0.91484, 1.05707, 0.92383, 1.05882, 0.9403, 1.12654, 1.05882, 1.01756, 1.09011, 1.09011, 0.99414, 1.09011, 1.034, 1.01756, 1.05356, 1.05707, 1.05882, 1.04399, 0.84863, 1.21968, 1.01756, 0.95801, 1.00068, 0.91797, 0.96777, 0.9043, 0.90351, 0.92105, 0.90351, 1.1714, 0.85337, 0.96927, 0.96927, 0.99912, 0.96927, 0.92105, 0.80597, 1.2434, 1.20808, 1.05937, 0.90957, 1.1714, 1.20808, 0.75155, 0.94261, 1.24644, 1.09971, 1.09971, 0.84751, 1, 0.85273, 0.78032, 0.61584, 1.05425, 1.17914, 0.90957, 1.08665, 1.11593, 1.14169, 0.73381, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.86035, 0.85734, 0.75842, 0.75842, 0.75842, 0.75842, 0.95782, 0.95782, 0.95782, 0.95782, 0.97093, 1.03584, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.1714, 0.96924, 0.95132, 0.95132, 0.95132, 0.95132, 0.8287, 0.83968, 0.89049, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.93575, 0.92383, 0.9403, 0.9403, 0.9403, 0.9403, 0.8717, 0.8717, 0.8717, 0.8717, 1.00527, 1.01756, 1.05356, 1.05356, 1.05356, 1.05356, 1.05356, 1.24644, 0.95923, 1.01756, 1.01756, 1.01756, 1.01756, 0.96777, 1.05707, 0.96777, 0.96706, 0.91484, 0.96706, 0.91484, 0.96706, 0.91484, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.97093, 1.0969, 0.97093, 1.05882, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.88198, 1.05882, 0.88198, 1.05882, 0.88198, 1.05882, 1, 1, 0.9831, 1.01756, 0.9831, 1.01756, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 1.09011, 0.95782, 0.8717, 0.84784, 1.11551, 0.71387, 1.09011, 1, 1, 0.99414, 0.84636, 1.09011, 1, 1, 0.84636, 1.0536, 0.84636, 0.94298, 0.84636, 1.23297, 1.03584, 1.01756, 1, 1, 1.03584, 1.01756, 1.00323, 1.03444, 1.01756, 0.96924, 1.05356, 0.96924, 1.05356, 0.96924, 1.05356, 0.93066, 0.98293, 0.82826, 1.04399, 1, 1, 0.82826, 1.04399, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 1, 1, 0.85771, 1.17318, 0.85771, 1.21968, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.98965, 1.00068, 0.8287, 0.96777, 0.8287, 0.93365, 0.9043, 0.93365, 0.9043, 0.93365, 0.9043, 1.08571, 0.96927, 0.96706, 0.91484, 0.86035, 0.93575, 0.96924, 0.95923, 1, 1, 0.85771, 1.21968, 1.11437, 1.11437, 0.93109, 0.91202, 0.60411, 0.84164, 0.55572, 1.01173, 0.97361, 0.81818, 0.81818, 0.96635, 0.78032, 0.72727, 0.92366, 0.98601, 1.03405, 0.77968, 1.09799, 1.2, 0.96706, 0.85944, 0.85638, 0.96491, 0.75842, 0.93365, 0.9831, 0.96924, 0.95782, 0.86969, 0.94152, 1.07796, 1.03584, 0.78437, 0.96924, 0.98715, 0.83968, 0.83491, 0.85771, 0.8287, 0.94492, 0.88433, 0.9287, 1.0098, 0.95782, 0.8287, 1.0625, 0.98248, 1.03424, 1.2, 1.01071, 1.0625, 0.95246, 1.03809, 1.04912, 0.98248, 1.00221, 1.03424, 1.05443, 1.2, 1.04785, 0.99609, 1.00169, 1.05176, 0.99346, 1.05356, 0.9087, 1.03004, 0.95542, 0.93117, 1.23362, 1.01071, 1.07831, 1.02512, 1.05205, 1.03502, 1.2, 1.01071, 1.05356, 1.01071, 1.03502, 0.75842, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03719, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9403, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04021, 1, 1, 1, 1, 1, 1, 0.98965, 1.00068, 0.98965, 1.00068, 0.98965, 1.00068, 0.8287, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 1.03077, 1.03077, 1.03077, 1.03077, 1.13196, 1.13196, 1.13196, 0.67428, 0.67428, 1.16039, 0.73291, 1.20996, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87796, 0.96927, 1.01518, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.10539, 1.10539, 1.11358, 1.06967, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86507, 1.1714, 1.18416, 1.14589, 0.69825, 0.97622, 1.9697, 1.24822, 1.24822, 1.17238, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18083, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10938, 1.10938, 1, 1, 1, 1.05425, 1.09971, 1.09971, 1.09971, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; const SegoeuiRegularMetrics = { lineHeight: 1.33008, lineGap: 0 }; ;// CONCATENATED MODULE: ./src/core/xfa_fonts.js const getXFAFontMap = getLookupTableFactory(function (t) { t["MyriadPro-Regular"] = t["PdfJS-Fallback-Regular"] = { name: "LiberationSans-Regular", factors: MyriadProRegularFactors, baseWidths: LiberationSansRegularWidths, baseMapping: LiberationSansRegularMapping, metrics: MyriadProRegularMetrics }; t["MyriadPro-Bold"] = t["PdfJS-Fallback-Bold"] = { name: "LiberationSans-Bold", factors: MyriadProBoldFactors, baseWidths: LiberationSansBoldWidths, baseMapping: LiberationSansBoldMapping, metrics: MyriadProBoldMetrics }; t["MyriadPro-It"] = t["MyriadPro-Italic"] = t["PdfJS-Fallback-Italic"] = { name: "LiberationSans-Italic", factors: MyriadProItalicFactors, baseWidths: LiberationSansItalicWidths, baseMapping: LiberationSansItalicMapping, metrics: MyriadProItalicMetrics }; t["MyriadPro-BoldIt"] = t["MyriadPro-BoldItalic"] = t["PdfJS-Fallback-BoldItalic"] = { name: "LiberationSans-BoldItalic", factors: MyriadProBoldItalicFactors, baseWidths: LiberationSansBoldItalicWidths, baseMapping: LiberationSansBoldItalicMapping, metrics: MyriadProBoldItalicMetrics }; t.ArialMT = t.Arial = t["Arial-Regular"] = { name: "LiberationSans-Regular", baseWidths: LiberationSansRegularWidths, baseMapping: LiberationSansRegularMapping }; t["Arial-BoldMT"] = t["Arial-Bold"] = { name: "LiberationSans-Bold", baseWidths: LiberationSansBoldWidths, baseMapping: LiberationSansBoldMapping }; t["Arial-ItalicMT"] = t["Arial-Italic"] = { name: "LiberationSans-Italic", baseWidths: LiberationSansItalicWidths, baseMapping: LiberationSansItalicMapping }; t["Arial-BoldItalicMT"] = t["Arial-BoldItalic"] = { name: "LiberationSans-BoldItalic", baseWidths: LiberationSansBoldItalicWidths, baseMapping: LiberationSansBoldItalicMapping }; t["Calibri-Regular"] = { name: "LiberationSans-Regular", factors: CalibriRegularFactors, baseWidths: LiberationSansRegularWidths, baseMapping: LiberationSansRegularMapping, metrics: CalibriRegularMetrics }; t["Calibri-Bold"] = { name: "LiberationSans-Bold", factors: CalibriBoldFactors, baseWidths: LiberationSansBoldWidths, baseMapping: LiberationSansBoldMapping, metrics: CalibriBoldMetrics }; t["Calibri-Italic"] = { name: "LiberationSans-Italic", factors: CalibriItalicFactors, baseWidths: LiberationSansItalicWidths, baseMapping: LiberationSansItalicMapping, metrics: CalibriItalicMetrics }; t["Calibri-BoldItalic"] = { name: "LiberationSans-BoldItalic", factors: CalibriBoldItalicFactors, baseWidths: LiberationSansBoldItalicWidths, baseMapping: LiberationSansBoldItalicMapping, metrics: CalibriBoldItalicMetrics }; t["Segoeui-Regular"] = { name: "LiberationSans-Regular", factors: SegoeuiRegularFactors, baseWidths: LiberationSansRegularWidths, baseMapping: LiberationSansRegularMapping, metrics: SegoeuiRegularMetrics }; t["Segoeui-Bold"] = { name: "LiberationSans-Bold", factors: SegoeuiBoldFactors, baseWidths: LiberationSansBoldWidths, baseMapping: LiberationSansBoldMapping, metrics: SegoeuiBoldMetrics }; t["Segoeui-Italic"] = { name: "LiberationSans-Italic", factors: SegoeuiItalicFactors, baseWidths: LiberationSansItalicWidths, baseMapping: LiberationSansItalicMapping, metrics: SegoeuiItalicMetrics }; t["Segoeui-BoldItalic"] = { name: "LiberationSans-BoldItalic", factors: SegoeuiBoldItalicFactors, baseWidths: LiberationSansBoldItalicWidths, baseMapping: LiberationSansBoldItalicMapping, metrics: SegoeuiBoldItalicMetrics }; t["Helvetica-Regular"] = t.Helvetica = { name: "LiberationSans-Regular", factors: HelveticaRegularFactors, baseWidths: LiberationSansRegularWidths, baseMapping: LiberationSansRegularMapping, metrics: HelveticaRegularMetrics }; t["Helvetica-Bold"] = { name: "LiberationSans-Bold", factors: HelveticaBoldFactors, baseWidths: LiberationSansBoldWidths, baseMapping: LiberationSansBoldMapping, metrics: HelveticaBoldMetrics }; t["Helvetica-Italic"] = { name: "LiberationSans-Italic", factors: HelveticaItalicFactors, baseWidths: LiberationSansItalicWidths, baseMapping: LiberationSansItalicMapping, metrics: HelveticaItalicMetrics }; t["Helvetica-BoldItalic"] = { name: "LiberationSans-BoldItalic", factors: HelveticaBoldItalicFactors, baseWidths: LiberationSansBoldItalicWidths, baseMapping: LiberationSansBoldItalicMapping, metrics: HelveticaBoldItalicMetrics }; }); function getXfaFontName(name) { const fontName = normalizeFontName(name); const fontMap = getXFAFontMap(); return fontMap[fontName]; } function getXfaFontWidths(name) { const info = getXfaFontName(name); if (!info) { return null; } const { baseWidths, baseMapping, factors } = info; const rescaledBaseWidths = !factors ? baseWidths : baseWidths.map((w, i) => w * factors[i]); let currentCode = -2; let currentArray; const newWidths = []; for (const [unicode, glyphIndex] of baseMapping.map((charUnicode, index) => [charUnicode, index]).sort(([unicode1], [unicode2]) => unicode1 - unicode2)) { if (unicode === -1) { continue; } if (unicode === currentCode + 1) { currentArray.push(rescaledBaseWidths[glyphIndex]); currentCode += 1; } else { currentCode = unicode; currentArray = [rescaledBaseWidths[glyphIndex]]; newWidths.push(unicode, currentArray); } } return newWidths; } function getXfaFontDict(name) { const widths = getXfaFontWidths(name); const dict = new Dict(null); dict.set("BaseFont", Name.get(name)); dict.set("Type", Name.get("Font")); dict.set("Subtype", Name.get("CIDFontType2")); dict.set("Encoding", Name.get("Identity-H")); dict.set("CIDToGIDMap", Name.get("Identity")); dict.set("W", widths); dict.set("FirstChar", widths[0]); dict.set("LastChar", widths.at(-2) + widths.at(-1).length - 1); const descriptor = new Dict(null); dict.set("FontDescriptor", descriptor); const systemInfo = new Dict(null); systemInfo.set("Ordering", "Identity"); systemInfo.set("Registry", "Adobe"); systemInfo.set("Supplement", 0); dict.set("CIDSystemInfo", systemInfo); return dict; } ;// CONCATENATED MODULE: ./src/core/ps_parser.js class PostScriptParser { constructor(lexer) { this.lexer = lexer; this.operators = []; this.token = null; this.prev = null; } nextToken() { this.prev = this.token; this.token = this.lexer.getToken(); } accept(type) { if (this.token.type === type) { this.nextToken(); return true; } return false; } expect(type) { if (this.accept(type)) { return true; } throw new FormatError(`Unexpected symbol: found ${this.token.type} expected ${type}.`); } parse() { this.nextToken(); this.expect(PostScriptTokenTypes.LBRACE); this.parseBlock(); this.expect(PostScriptTokenTypes.RBRACE); return this.operators; } parseBlock() { while (true) { if (this.accept(PostScriptTokenTypes.NUMBER)) { this.operators.push(this.prev.value); } else if (this.accept(PostScriptTokenTypes.OPERATOR)) { this.operators.push(this.prev.value); } else if (this.accept(PostScriptTokenTypes.LBRACE)) { this.parseCondition(); } else { return; } } } parseCondition() { const conditionLocation = this.operators.length; this.operators.push(null, null); this.parseBlock(); this.expect(PostScriptTokenTypes.RBRACE); if (this.accept(PostScriptTokenTypes.IF)) { this.operators[conditionLocation] = this.operators.length; this.operators[conditionLocation + 1] = "jz"; } else if (this.accept(PostScriptTokenTypes.LBRACE)) { const jumpLocation = this.operators.length; this.operators.push(null, null); const endOfTrue = this.operators.length; this.parseBlock(); this.expect(PostScriptTokenTypes.RBRACE); this.expect(PostScriptTokenTypes.IFELSE); this.operators[jumpLocation] = this.operators.length; this.operators[jumpLocation + 1] = "j"; this.operators[conditionLocation] = endOfTrue; this.operators[conditionLocation + 1] = "jz"; } else { throw new FormatError("PS Function: error parsing conditional."); } } } const PostScriptTokenTypes = { LBRACE: 0, RBRACE: 1, NUMBER: 2, OPERATOR: 3, IF: 4, IFELSE: 5 }; class PostScriptToken { static get opCache() { return shadow(this, "opCache", Object.create(null)); } constructor(type, value) { this.type = type; this.value = value; } static getOperator(op) { return PostScriptToken.opCache[op] ||= new PostScriptToken(PostScriptTokenTypes.OPERATOR, op); } static get LBRACE() { return shadow(this, "LBRACE", new PostScriptToken(PostScriptTokenTypes.LBRACE, "{")); } static get RBRACE() { return shadow(this, "RBRACE", new PostScriptToken(PostScriptTokenTypes.RBRACE, "}")); } static get IF() { return shadow(this, "IF", new PostScriptToken(PostScriptTokenTypes.IF, "IF")); } static get IFELSE() { return shadow(this, "IFELSE", new PostScriptToken(PostScriptTokenTypes.IFELSE, "IFELSE")); } } class PostScriptLexer { constructor(stream) { this.stream = stream; this.nextChar(); this.strBuf = []; } nextChar() { return this.currentChar = this.stream.getByte(); } getToken() { let comment = false; let ch = this.currentChar; while (true) { if (ch < 0) { return EOF; } if (comment) { if (ch === 0x0a || ch === 0x0d) { comment = false; } } else if (ch === 0x25) { comment = true; } else if (!isWhiteSpace(ch)) { break; } ch = this.nextChar(); } switch (ch | 0) { case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x2b: case 0x2d: case 0x2e: return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber()); case 0x7b: this.nextChar(); return PostScriptToken.LBRACE; case 0x7d: this.nextChar(); return PostScriptToken.RBRACE; } const strBuf = this.strBuf; strBuf.length = 0; strBuf[0] = String.fromCharCode(ch); while ((ch = this.nextChar()) >= 0 && (ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a)) { strBuf.push(String.fromCharCode(ch)); } const str = strBuf.join(""); switch (str.toLowerCase()) { case "if": return PostScriptToken.IF; case "ifelse": return PostScriptToken.IFELSE; default: return PostScriptToken.getOperator(str); } } getNumber() { let ch = this.currentChar; const strBuf = this.strBuf; strBuf.length = 0; strBuf[0] = String.fromCharCode(ch); while ((ch = this.nextChar()) >= 0) { if (ch >= 0x30 && ch <= 0x39 || ch === 0x2d || ch === 0x2e) { strBuf.push(String.fromCharCode(ch)); } else { break; } } const value = parseFloat(strBuf.join("")); if (isNaN(value)) { throw new FormatError(`Invalid floating point number: ${value}`); } return value; } } ;// CONCATENATED MODULE: ./src/core/image_utils.js class BaseLocalCache { constructor(options) { if (this.constructor === BaseLocalCache) { unreachable("Cannot initialize BaseLocalCache."); } this._onlyRefs = options?.onlyRefs === true; if (!this._onlyRefs) { this._nameRefMap = new Map(); this._imageMap = new Map(); } this._imageCache = new RefSetCache(); } getByName(name) { if (this._onlyRefs) { unreachable("Should not call `getByName` method."); } const ref = this._nameRefMap.get(name); if (ref) { return this.getByRef(ref); } return this._imageMap.get(name) || null; } getByRef(ref) { return this._imageCache.get(ref) || null; } set(name, ref, data) { unreachable("Abstract method `set` called."); } } class LocalImageCache extends BaseLocalCache { set(name, ref = null, data) { if (typeof name !== "string") { throw new Error('LocalImageCache.set - expected "name" argument.'); } if (ref) { if (this._imageCache.has(ref)) { return; } this._nameRefMap.set(name, ref); this._imageCache.put(ref, data); return; } if (this._imageMap.has(name)) { return; } this._imageMap.set(name, data); } } class LocalColorSpaceCache extends BaseLocalCache { set(name = null, ref = null, data) { if (typeof name !== "string" && !ref) { throw new Error('LocalColorSpaceCache.set - expected "name" and/or "ref" argument.'); } if (ref) { if (this._imageCache.has(ref)) { return; } if (name !== null) { this._nameRefMap.set(name, ref); } this._imageCache.put(ref, data); return; } if (this._imageMap.has(name)) { return; } this._imageMap.set(name, data); } } class LocalFunctionCache extends BaseLocalCache { constructor(options) { super({ onlyRefs: true }); } set(name = null, ref, data) { if (!ref) { throw new Error('LocalFunctionCache.set - expected "ref" argument.'); } if (this._imageCache.has(ref)) { return; } this._imageCache.put(ref, data); } } class LocalGStateCache extends BaseLocalCache { set(name, ref = null, data) { if (typeof name !== "string") { throw new Error('LocalGStateCache.set - expected "name" argument.'); } if (ref) { if (this._imageCache.has(ref)) { return; } this._nameRefMap.set(name, ref); this._imageCache.put(ref, data); return; } if (this._imageMap.has(name)) { return; } this._imageMap.set(name, data); } } class LocalTilingPatternCache extends BaseLocalCache { constructor(options) { super({ onlyRefs: true }); } set(name = null, ref, data) { if (!ref) { throw new Error('LocalTilingPatternCache.set - expected "ref" argument.'); } if (this._imageCache.has(ref)) { return; } this._imageCache.put(ref, data); } } class RegionalImageCache extends BaseLocalCache { constructor(options) { super({ onlyRefs: true }); } set(name = null, ref, data) { if (!ref) { throw new Error('RegionalImageCache.set - expected "ref" argument.'); } if (this._imageCache.has(ref)) { return; } this._imageCache.put(ref, data); } } class GlobalImageCache { static NUM_PAGES_THRESHOLD = 2; static MIN_IMAGES_TO_CACHE = 10; static MAX_BYTE_SIZE = 5 * MAX_IMAGE_SIZE_TO_CACHE; constructor() { this._refCache = new RefSetCache(); this._imageCache = new RefSetCache(); } get _byteSize() { let byteSize = 0; for (const imageData of this._imageCache) { byteSize += imageData.byteSize; } return byteSize; } get _cacheLimitReached() { if (this._imageCache.size < GlobalImageCache.MIN_IMAGES_TO_CACHE) { return false; } if (this._byteSize < GlobalImageCache.MAX_BYTE_SIZE) { return false; } return true; } shouldCache(ref, pageIndex) { let pageIndexSet = this._refCache.get(ref); if (!pageIndexSet) { pageIndexSet = new Set(); this._refCache.put(ref, pageIndexSet); } pageIndexSet.add(pageIndex); if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) { return false; } if (!this._imageCache.has(ref) && this._cacheLimitReached) { return false; } return true; } addByteSize(ref, byteSize) { const imageData = this._imageCache.get(ref); if (!imageData) { return; } if (imageData.byteSize) { return; } imageData.byteSize = byteSize; } getData(ref, pageIndex) { const pageIndexSet = this._refCache.get(ref); if (!pageIndexSet) { return null; } if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) { return null; } const imageData = this._imageCache.get(ref); if (!imageData) { return null; } pageIndexSet.add(pageIndex); return imageData; } setData(ref, data) { if (!this._refCache.has(ref)) { throw new Error('GlobalImageCache.setData - expected "shouldCache" to have been called.'); } if (this._imageCache.has(ref)) { return; } if (this._cacheLimitReached) { warn("GlobalImageCache.setData - cache limit reached."); return; } this._imageCache.put(ref, data); } clear(onlyData = false) { if (!onlyData) { this._refCache.clear(); } this._imageCache.clear(); } } ;// CONCATENATED MODULE: ./src/core/function.js class PDFFunctionFactory { constructor({ xref, isEvalSupported = true }) { this.xref = xref; this.isEvalSupported = isEvalSupported !== false; } create(fn) { const cachedFunction = this.getCached(fn); if (cachedFunction) { return cachedFunction; } const parsedFunction = PDFFunction.parse({ xref: this.xref, isEvalSupported: this.isEvalSupported, fn: fn instanceof Ref ? this.xref.fetch(fn) : fn }); this._cache(fn, parsedFunction); return parsedFunction; } createFromArray(fnObj) { const cachedFunction = this.getCached(fnObj); if (cachedFunction) { return cachedFunction; } const parsedFunction = PDFFunction.parseArray({ xref: this.xref, isEvalSupported: this.isEvalSupported, fnObj: fnObj instanceof Ref ? this.xref.fetch(fnObj) : fnObj }); this._cache(fnObj, parsedFunction); return parsedFunction; } getCached(cacheKey) { let fnRef; if (cacheKey instanceof Ref) { fnRef = cacheKey; } else if (cacheKey instanceof Dict) { fnRef = cacheKey.objId; } else if (cacheKey instanceof BaseStream) { fnRef = cacheKey.dict?.objId; } if (fnRef) { const localFunction = this._localFunctionCache.getByRef(fnRef); if (localFunction) { return localFunction; } } return null; } _cache(cacheKey, parsedFunction) { if (!parsedFunction) { throw new Error('PDFFunctionFactory._cache - expected "parsedFunction" argument.'); } let fnRef; if (cacheKey instanceof Ref) { fnRef = cacheKey; } else if (cacheKey instanceof Dict) { fnRef = cacheKey.objId; } else if (cacheKey instanceof BaseStream) { fnRef = cacheKey.dict?.objId; } if (fnRef) { this._localFunctionCache.set(null, fnRef, parsedFunction); } } get _localFunctionCache() { return shadow(this, "_localFunctionCache", new LocalFunctionCache()); } } function toNumberArray(arr) { if (!Array.isArray(arr)) { return null; } const length = arr.length; for (let i = 0; i < length; i++) { if (typeof arr[i] !== "number") { const result = new Array(length); for (let j = 0; j < length; j++) { result[j] = +arr[j]; } return result; } } return arr; } class PDFFunction { static getSampleArray(size, outputSize, bps, stream) { let i, ii; let length = 1; for (i = 0, ii = size.length; i < ii; i++) { length *= size[i]; } length *= outputSize; const array = new Array(length); let codeSize = 0; let codeBuf = 0; const sampleMul = 1.0 / (2.0 ** bps - 1); const strBytes = stream.getBytes((length * bps + 7) / 8); let strIdx = 0; for (i = 0; i < length; i++) { while (codeSize < bps) { codeBuf <<= 8; codeBuf |= strBytes[strIdx++]; codeSize += 8; } codeSize -= bps; array[i] = (codeBuf >> codeSize) * sampleMul; codeBuf &= (1 << codeSize) - 1; } return array; } static parse({ xref, isEvalSupported, fn }) { const dict = fn.dict || fn; const typeNum = dict.get("FunctionType"); switch (typeNum) { case 0: return this.constructSampled({ xref, isEvalSupported, fn, dict }); case 1: break; case 2: return this.constructInterpolated({ xref, isEvalSupported, dict }); case 3: return this.constructStiched({ xref, isEvalSupported, dict }); case 4: return this.constructPostScript({ xref, isEvalSupported, fn, dict }); } throw new FormatError("Unknown type of function"); } static parseArray({ xref, isEvalSupported, fnObj }) { if (!Array.isArray(fnObj)) { return this.parse({ xref, isEvalSupported, fn: fnObj }); } const fnArray = []; for (const fn of fnObj) { fnArray.push(this.parse({ xref, isEvalSupported, fn: xref.fetchIfRef(fn) })); } return function (src, srcOffset, dest, destOffset) { for (let i = 0, ii = fnArray.length; i < ii; i++) { fnArray[i](src, srcOffset, dest, destOffset + i); } }; } static constructSampled({ xref, isEvalSupported, fn, dict }) { function toMultiArray(arr) { const inputLength = arr.length; const out = []; let index = 0; for (let i = 0; i < inputLength; i += 2) { out[index++] = [arr[i], arr[i + 1]]; } return out; } function interpolate(x, xmin, xmax, ymin, ymax) { return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin)); } let domain = toNumberArray(dict.getArray("Domain")); let range = toNumberArray(dict.getArray("Range")); if (!domain || !range) { throw new FormatError("No domain or range"); } const inputSize = domain.length / 2; const outputSize = range.length / 2; domain = toMultiArray(domain); range = toMultiArray(range); const size = toNumberArray(dict.getArray("Size")); const bps = dict.get("BitsPerSample"); const order = dict.get("Order") || 1; if (order !== 1) { info("No support for cubic spline interpolation: " + order); } let encode = toNumberArray(dict.getArray("Encode")); if (!encode) { encode = []; for (let i = 0; i < inputSize; ++i) { encode.push([0, size[i] - 1]); } } else { encode = toMultiArray(encode); } let decode = toNumberArray(dict.getArray("Decode")); decode = !decode ? range : toMultiArray(decode); const samples = this.getSampleArray(size, outputSize, bps, fn); return function constructSampledFn(src, srcOffset, dest, destOffset) { const cubeVertices = 1 << inputSize; const cubeN = new Float64Array(cubeVertices); const cubeVertex = new Uint32Array(cubeVertices); let i, j; for (j = 0; j < cubeVertices; j++) { cubeN[j] = 1; } let k = outputSize, pos = 1; for (i = 0; i < inputSize; ++i) { const domain_2i = domain[i][0]; const domain_2i_1 = domain[i][1]; const xi = Math.min(Math.max(src[srcOffset + i], domain_2i), domain_2i_1); let e = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]); const size_i = size[i]; e = Math.min(Math.max(e, 0), size_i - 1); const e0 = e < size_i - 1 ? Math.floor(e) : e - 1; const n0 = e0 + 1 - e; const n1 = e - e0; const offset0 = e0 * k; const offset1 = offset0 + k; for (j = 0; j < cubeVertices; j++) { if (j & pos) { cubeN[j] *= n1; cubeVertex[j] += offset1; } else { cubeN[j] *= n0; cubeVertex[j] += offset0; } } k *= size_i; pos <<= 1; } for (j = 0; j < outputSize; ++j) { let rj = 0; for (i = 0; i < cubeVertices; i++) { rj += samples[cubeVertex[i] + j] * cubeN[i]; } rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); } }; } static constructInterpolated({ xref, isEvalSupported, dict }) { const c0 = toNumberArray(dict.getArray("C0")) || [0]; const c1 = toNumberArray(dict.getArray("C1")) || [1]; const n = dict.get("N"); const diff = []; for (let i = 0, ii = c0.length; i < ii; ++i) { diff.push(c1[i] - c0[i]); } const length = diff.length; return function constructInterpolatedFn(src, srcOffset, dest, destOffset) { const x = n === 1 ? src[srcOffset] : src[srcOffset] ** n; for (let j = 0; j < length; ++j) { dest[destOffset + j] = c0[j] + x * diff[j]; } }; } static constructStiched({ xref, isEvalSupported, dict }) { const domain = toNumberArray(dict.getArray("Domain")); if (!domain) { throw new FormatError("No domain"); } const inputSize = domain.length / 2; if (inputSize !== 1) { throw new FormatError("Bad domain for stiched function"); } const fns = []; for (const fn of dict.get("Functions")) { fns.push(this.parse({ xref, isEvalSupported, fn: xref.fetchIfRef(fn) })); } const bounds = toNumberArray(dict.getArray("Bounds")); const encode = toNumberArray(dict.getArray("Encode")); const tmpBuf = new Float32Array(1); return function constructStichedFn(src, srcOffset, dest, destOffset) { const clip = function constructStichedFromIRClip(v, min, max) { if (v > max) { v = max; } else if (v < min) { v = min; } return v; }; const v = clip(src[srcOffset], domain[0], domain[1]); const length = bounds.length; let i; for (i = 0; i < length; ++i) { if (v < bounds[i]) { break; } } let dmin = domain[0]; if (i > 0) { dmin = bounds[i - 1]; } let dmax = domain[1]; if (i < bounds.length) { dmax = bounds[i]; } const rmin = encode[2 * i]; const rmax = encode[2 * i + 1]; tmpBuf[0] = dmin === dmax ? rmin : rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); fns[i](tmpBuf, 0, dest, destOffset); }; } static constructPostScript({ xref, isEvalSupported, fn, dict }) { const domain = toNumberArray(dict.getArray("Domain")); const range = toNumberArray(dict.getArray("Range")); if (!domain) { throw new FormatError("No domain."); } if (!range) { throw new FormatError("No range."); } const lexer = new PostScriptLexer(fn); const parser = new PostScriptParser(lexer); const code = parser.parse(); if (isEvalSupported && FeatureTest.isEvalSupported) { const compiled = new PostScriptCompiler().compile(code, domain, range); if (compiled) { return new Function("src", "srcOffset", "dest", "destOffset", compiled); } } info("Unable to compile PS function"); const numOutputs = range.length >> 1; const numInputs = domain.length >> 1; const evaluator = new PostScriptEvaluator(code); const cache = Object.create(null); const MAX_CACHE_SIZE = 2048 * 4; let cache_available = MAX_CACHE_SIZE; const tmpBuf = new Float32Array(numInputs); return function constructPostScriptFn(src, srcOffset, dest, destOffset) { let i, value; let key = ""; const input = tmpBuf; for (i = 0; i < numInputs; i++) { value = src[srcOffset + i]; input[i] = value; key += value + "_"; } const cachedValue = cache[key]; if (cachedValue !== undefined) { dest.set(cachedValue, destOffset); return; } const output = new Float32Array(numOutputs); const stack = evaluator.execute(input); const stackIndex = stack.length - numOutputs; for (i = 0; i < numOutputs; i++) { value = stack[stackIndex + i]; let bound = range[i * 2]; if (value < bound) { value = bound; } else { bound = range[i * 2 + 1]; if (value > bound) { value = bound; } } output[i] = value; } if (cache_available > 0) { cache_available--; cache[key] = output; } dest.set(output, destOffset); }; } } function isPDFFunction(v) { let fnDict; if (v instanceof Dict) { fnDict = v; } else if (v instanceof BaseStream) { fnDict = v.dict; } else { return false; } return fnDict.has("FunctionType"); } class PostScriptStack { static MAX_STACK_SIZE = 100; constructor(initialStack) { this.stack = initialStack ? Array.from(initialStack) : []; } push(value) { if (this.stack.length >= PostScriptStack.MAX_STACK_SIZE) { throw new Error("PostScript function stack overflow."); } this.stack.push(value); } pop() { if (this.stack.length <= 0) { throw new Error("PostScript function stack underflow."); } return this.stack.pop(); } copy(n) { if (this.stack.length + n >= PostScriptStack.MAX_STACK_SIZE) { throw new Error("PostScript function stack overflow."); } const stack = this.stack; for (let i = stack.length - n, j = n - 1; j >= 0; j--, i++) { stack.push(stack[i]); } } index(n) { this.push(this.stack[this.stack.length - n - 1]); } roll(n, p) { const stack = this.stack; const l = stack.length - n; const r = stack.length - 1; const c = l + (p - Math.floor(p / n) * n); for (let i = l, j = r; i < j; i++, j--) { const t = stack[i]; stack[i] = stack[j]; stack[j] = t; } for (let i = l, j = c - 1; i < j; i++, j--) { const t = stack[i]; stack[i] = stack[j]; stack[j] = t; } for (let i = c, j = r; i < j; i++, j--) { const t = stack[i]; stack[i] = stack[j]; stack[j] = t; } } } class PostScriptEvaluator { constructor(operators) { this.operators = operators; } execute(initialStack) { const stack = new PostScriptStack(initialStack); let counter = 0; const operators = this.operators; const length = operators.length; let operator, a, b; while (counter < length) { operator = operators[counter++]; if (typeof operator === "number") { stack.push(operator); continue; } switch (operator) { case "jz": b = stack.pop(); a = stack.pop(); if (!a) { counter = b; } break; case "j": a = stack.pop(); counter = a; break; case "abs": a = stack.pop(); stack.push(Math.abs(a)); break; case "add": b = stack.pop(); a = stack.pop(); stack.push(a + b); break; case "and": b = stack.pop(); a = stack.pop(); if (typeof a === "boolean" && typeof b === "boolean") { stack.push(a && b); } else { stack.push(a & b); } break; case "atan": b = stack.pop(); a = stack.pop(); a = Math.atan2(a, b) / Math.PI * 180; if (a < 0) { a += 360; } stack.push(a); break; case "bitshift": b = stack.pop(); a = stack.pop(); if (a > 0) { stack.push(a << b); } else { stack.push(a >> b); } break; case "ceiling": a = stack.pop(); stack.push(Math.ceil(a)); break; case "copy": a = stack.pop(); stack.copy(a); break; case "cos": a = stack.pop(); stack.push(Math.cos(a % 360 / 180 * Math.PI)); break; case "cvi": a = stack.pop() | 0; stack.push(a); break; case "cvr": break; case "div": b = stack.pop(); a = stack.pop(); stack.push(a / b); break; case "dup": stack.copy(1); break; case "eq": b = stack.pop(); a = stack.pop(); stack.push(a === b); break; case "exch": stack.roll(2, 1); break; case "exp": b = stack.pop(); a = stack.pop(); stack.push(a ** b); break; case "false": stack.push(false); break; case "floor": a = stack.pop(); stack.push(Math.floor(a)); break; case "ge": b = stack.pop(); a = stack.pop(); stack.push(a >= b); break; case "gt": b = stack.pop(); a = stack.pop(); stack.push(a > b); break; case "idiv": b = stack.pop(); a = stack.pop(); stack.push(a / b | 0); break; case "index": a = stack.pop(); stack.index(a); break; case "le": b = stack.pop(); a = stack.pop(); stack.push(a <= b); break; case "ln": a = stack.pop(); stack.push(Math.log(a)); break; case "log": a = stack.pop(); stack.push(Math.log10(a)); break; case "lt": b = stack.pop(); a = stack.pop(); stack.push(a < b); break; case "mod": b = stack.pop(); a = stack.pop(); stack.push(a % b); break; case "mul": b = stack.pop(); a = stack.pop(); stack.push(a * b); break; case "ne": b = stack.pop(); a = stack.pop(); stack.push(a !== b); break; case "neg": a = stack.pop(); stack.push(-a); break; case "not": a = stack.pop(); if (typeof a === "boolean") { stack.push(!a); } else { stack.push(~a); } break; case "or": b = stack.pop(); a = stack.pop(); if (typeof a === "boolean" && typeof b === "boolean") { stack.push(a || b); } else { stack.push(a | b); } break; case "pop": stack.pop(); break; case "roll": b = stack.pop(); a = stack.pop(); stack.roll(a, b); break; case "round": a = stack.pop(); stack.push(Math.round(a)); break; case "sin": a = stack.pop(); stack.push(Math.sin(a % 360 / 180 * Math.PI)); break; case "sqrt": a = stack.pop(); stack.push(Math.sqrt(a)); break; case "sub": b = stack.pop(); a = stack.pop(); stack.push(a - b); break; case "true": stack.push(true); break; case "truncate": a = stack.pop(); a = a < 0 ? Math.ceil(a) : Math.floor(a); stack.push(a); break; case "xor": b = stack.pop(); a = stack.pop(); if (typeof a === "boolean" && typeof b === "boolean") { stack.push(a !== b); } else { stack.push(a ^ b); } break; default: throw new FormatError(`Unknown operator ${operator}`); } } return stack.stack; } } class AstNode { constructor(type) { this.type = type; } visit(visitor) { unreachable("abstract method"); } } class AstArgument extends AstNode { constructor(index, min, max) { super("args"); this.index = index; this.min = min; this.max = max; } visit(visitor) { visitor.visitArgument(this); } } class AstLiteral extends AstNode { constructor(number) { super("literal"); this.number = number; this.min = number; this.max = number; } visit(visitor) { visitor.visitLiteral(this); } } class AstBinaryOperation extends AstNode { constructor(op, arg1, arg2, min, max) { super("binary"); this.op = op; this.arg1 = arg1; this.arg2 = arg2; this.min = min; this.max = max; } visit(visitor) { visitor.visitBinaryOperation(this); } } class AstMin extends AstNode { constructor(arg, max) { super("max"); this.arg = arg; this.min = arg.min; this.max = max; } visit(visitor) { visitor.visitMin(this); } } class AstVariable extends AstNode { constructor(index, min, max) { super("var"); this.index = index; this.min = min; this.max = max; } visit(visitor) { visitor.visitVariable(this); } } class AstVariableDefinition extends AstNode { constructor(variable, arg) { super("definition"); this.variable = variable; this.arg = arg; } visit(visitor) { visitor.visitVariableDefinition(this); } } class ExpressionBuilderVisitor { constructor() { this.parts = []; } visitArgument(arg) { this.parts.push("Math.max(", arg.min, ", Math.min(", arg.max, ", src[srcOffset + ", arg.index, "]))"); } visitVariable(variable) { this.parts.push("v", variable.index); } visitLiteral(literal) { this.parts.push(literal.number); } visitBinaryOperation(operation) { this.parts.push("("); operation.arg1.visit(this); this.parts.push(" ", operation.op, " "); operation.arg2.visit(this); this.parts.push(")"); } visitVariableDefinition(definition) { this.parts.push("var "); definition.variable.visit(this); this.parts.push(" = "); definition.arg.visit(this); this.parts.push(";"); } visitMin(max) { this.parts.push("Math.min("); max.arg.visit(this); this.parts.push(", ", max.max, ")"); } toString() { return this.parts.join(""); } } function buildAddOperation(num1, num2) { if (num2.type === "literal" && num2.number === 0) { return num1; } if (num1.type === "literal" && num1.number === 0) { return num2; } if (num2.type === "literal" && num1.type === "literal") { return new AstLiteral(num1.number + num2.number); } return new AstBinaryOperation("+", num1, num2, num1.min + num2.min, num1.max + num2.max); } function buildMulOperation(num1, num2) { if (num2.type === "literal") { if (num2.number === 0) { return new AstLiteral(0); } else if (num2.number === 1) { return num1; } else if (num1.type === "literal") { return new AstLiteral(num1.number * num2.number); } } if (num1.type === "literal") { if (num1.number === 0) { return new AstLiteral(0); } else if (num1.number === 1) { return num2; } } const min = Math.min(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max); const max = Math.max(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max); return new AstBinaryOperation("*", num1, num2, min, max); } function buildSubOperation(num1, num2) { if (num2.type === "literal") { if (num2.number === 0) { return num1; } else if (num1.type === "literal") { return new AstLiteral(num1.number - num2.number); } } if (num2.type === "binary" && num2.op === "-" && num1.type === "literal" && num1.number === 1 && num2.arg1.type === "literal" && num2.arg1.number === 1) { return num2.arg2; } return new AstBinaryOperation("-", num1, num2, num1.min - num2.max, num1.max - num2.min); } function buildMinOperation(num1, max) { if (num1.min >= max) { return new AstLiteral(max); } else if (num1.max <= max) { return num1; } return new AstMin(num1, max); } class PostScriptCompiler { compile(code, domain, range) { const stack = []; const instructions = []; const inputSize = domain.length >> 1, outputSize = range.length >> 1; let lastRegister = 0; let n, j; let num1, num2, ast1, ast2, tmpVar, item; for (let i = 0; i < inputSize; i++) { stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1])); } for (let i = 0, ii = code.length; i < ii; i++) { item = code[i]; if (typeof item === "number") { stack.push(new AstLiteral(item)); continue; } switch (item) { case "add": if (stack.length < 2) { return null; } num2 = stack.pop(); num1 = stack.pop(); stack.push(buildAddOperation(num1, num2)); break; case "cvr": if (stack.length < 1) { return null; } break; case "mul": if (stack.length < 2) { return null; } num2 = stack.pop(); num1 = stack.pop(); stack.push(buildMulOperation(num1, num2)); break; case "sub": if (stack.length < 2) { return null; } num2 = stack.pop(); num1 = stack.pop(); stack.push(buildSubOperation(num1, num2)); break; case "exch": if (stack.length < 2) { return null; } ast1 = stack.pop(); ast2 = stack.pop(); stack.push(ast1, ast2); break; case "pop": if (stack.length < 1) { return null; } stack.pop(); break; case "index": if (stack.length < 1) { return null; } num1 = stack.pop(); if (num1.type !== "literal") { return null; } n = num1.number; if (n < 0 || !Number.isInteger(n) || stack.length < n) { return null; } ast1 = stack[stack.length - n - 1]; if (ast1.type === "literal" || ast1.type === "var") { stack.push(ast1); break; } tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); stack[stack.length - n - 1] = tmpVar; stack.push(tmpVar); instructions.push(new AstVariableDefinition(tmpVar, ast1)); break; case "dup": if (stack.length < 1) { return null; } if (typeof code[i + 1] === "number" && code[i + 2] === "gt" && code[i + 3] === i + 7 && code[i + 4] === "jz" && code[i + 5] === "pop" && code[i + 6] === code[i + 1]) { num1 = stack.pop(); stack.push(buildMinOperation(num1, code[i + 1])); i += 6; break; } ast1 = stack.at(-1); if (ast1.type === "literal" || ast1.type === "var") { stack.push(ast1); break; } tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); stack[stack.length - 1] = tmpVar; stack.push(tmpVar); instructions.push(new AstVariableDefinition(tmpVar, ast1)); break; case "roll": if (stack.length < 2) { return null; } num2 = stack.pop(); num1 = stack.pop(); if (num2.type !== "literal" || num1.type !== "literal") { return null; } j = num2.number; n = num1.number; if (n <= 0 || !Number.isInteger(n) || !Number.isInteger(j) || stack.length < n) { return null; } j = (j % n + n) % n; if (j === 0) { break; } stack.push(...stack.splice(stack.length - n, n - j)); break; default: return null; } } if (stack.length !== outputSize) { return null; } const result = []; for (const instruction of instructions) { const statementBuilder = new ExpressionBuilderVisitor(); instruction.visit(statementBuilder); result.push(statementBuilder.toString()); } for (let i = 0, ii = stack.length; i < ii; i++) { const expr = stack[i], statementBuilder = new ExpressionBuilderVisitor(); expr.visit(statementBuilder); const min = range[i * 2], max = range[i * 2 + 1]; const out = [statementBuilder.toString()]; if (min > expr.min) { out.unshift("Math.max(", min, ", "); out.push(")"); } if (max < expr.max) { out.unshift("Math.min(", max, ", "); out.push(")"); } out.unshift("dest[destOffset + ", i, "] = "); out.push(";"); result.push(out.join("")); } return result.join("\n"); } } ;// CONCATENATED MODULE: ./src/core/bidi.js const baseTypes = ["BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "S", "B", "S", "WS", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "B", "B", "B", "S", "WS", "ON", "ON", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "ON", "ES", "CS", "ES", "CS", "CS", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "CS", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "BN", "BN", "BN", "BN", "BN", "BN", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "CS", "ON", "ET", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "L", "ON", "ON", "BN", "ON", "ON", "ET", "ET", "EN", "EN", "ON", "L", "ON", "ON", "ON", "EN", "L", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L"]; const arabicTypes = ["AN", "AN", "AN", "AN", "AN", "AN", "ON", "ON", "AL", "ET", "ET", "AL", "CS", "AL", "ON", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "ET", "AN", "AN", "AL", "AL", "AL", "NSM", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "NSM", "NSM", "ON", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "AL", "AL", "AL", "AL", "AL", "AL"]; function isOdd(i) { return (i & 1) !== 0; } function isEven(i) { return (i & 1) === 0; } function findUnequal(arr, start, value) { let j, jj; for (j = start, jj = arr.length; j < jj; ++j) { if (arr[j] !== value) { return j; } } return j; } function setValues(arr, start, end, value) { for (let j = start; j < end; ++j) { arr[j] = value; } } function reverseValues(arr, start, end) { for (let i = start, j = end - 1; i < j; ++i, --j) { const temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } function createBidiText(str, isLTR, vertical = false) { let dir = "ltr"; if (vertical) { dir = "ttb"; } else if (!isLTR) { dir = "rtl"; } return { str, dir }; } const chars = []; const types = []; function bidi(str, startLevel = -1, vertical = false) { let isLTR = true; const strLength = str.length; if (strLength === 0 || vertical) { return createBidiText(str, isLTR, vertical); } chars.length = strLength; types.length = strLength; let numBidi = 0; let i, ii; for (i = 0; i < strLength; ++i) { chars[i] = str.charAt(i); const charCode = str.charCodeAt(i); let charType = "L"; if (charCode <= 0x00ff) { charType = baseTypes[charCode]; } else if (0x0590 <= charCode && charCode <= 0x05f4) { charType = "R"; } else if (0x0600 <= charCode && charCode <= 0x06ff) { charType = arabicTypes[charCode & 0xff]; if (!charType) { warn("Bidi: invalid Unicode character " + charCode.toString(16)); } } else if (0x0700 <= charCode && charCode <= 0x08ac || 0xfb50 <= charCode && charCode <= 0xfdff || 0xfe70 <= charCode && charCode <= 0xfeff) { charType = "AL"; } if (charType === "R" || charType === "AL" || charType === "AN") { numBidi++; } types[i] = charType; } if (numBidi === 0) { isLTR = true; return createBidiText(str, isLTR); } if (startLevel === -1) { if (numBidi / strLength < 0.3 && strLength > 4) { isLTR = true; startLevel = 0; } else { isLTR = false; startLevel = 1; } } const levels = []; for (i = 0; i < strLength; ++i) { levels[i] = startLevel; } const e = isOdd(startLevel) ? "R" : "L"; const sor = e; const eor = sor; let lastType = sor; for (i = 0; i < strLength; ++i) { if (types[i] === "NSM") { types[i] = lastType; } else { lastType = types[i]; } } lastType = sor; let t; for (i = 0; i < strLength; ++i) { t = types[i]; if (t === "EN") { types[i] = lastType === "AL" ? "AN" : "EN"; } else if (t === "R" || t === "L" || t === "AL") { lastType = t; } } for (i = 0; i < strLength; ++i) { t = types[i]; if (t === "AL") { types[i] = "R"; } } for (i = 1; i < strLength - 1; ++i) { if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") { types[i] = "EN"; } if (types[i] === "CS" && (types[i - 1] === "EN" || types[i - 1] === "AN") && types[i + 1] === types[i - 1]) { types[i] = types[i - 1]; } } for (i = 0; i < strLength; ++i) { if (types[i] === "EN") { for (let j = i - 1; j >= 0; --j) { if (types[j] !== "ET") { break; } types[j] = "EN"; } for (let j = i + 1; j < strLength; ++j) { if (types[j] !== "ET") { break; } types[j] = "EN"; } } } for (i = 0; i < strLength; ++i) { t = types[i]; if (t === "WS" || t === "ES" || t === "ET" || t === "CS") { types[i] = "ON"; } } lastType = sor; for (i = 0; i < strLength; ++i) { t = types[i]; if (t === "EN") { types[i] = lastType === "L" ? "L" : "EN"; } else if (t === "R" || t === "L") { lastType = t; } } for (i = 0; i < strLength; ++i) { if (types[i] === "ON") { const end = findUnequal(types, i + 1, "ON"); let before = sor; if (i > 0) { before = types[i - 1]; } let after = eor; if (end + 1 < strLength) { after = types[end + 1]; } if (before !== "L") { before = "R"; } if (after !== "L") { after = "R"; } if (before === after) { setValues(types, i, end, before); } i = end - 1; } } for (i = 0; i < strLength; ++i) { if (types[i] === "ON") { types[i] = e; } } for (i = 0; i < strLength; ++i) { t = types[i]; if (isEven(levels[i])) { if (t === "R") { levels[i] += 1; } else if (t === "AN" || t === "EN") { levels[i] += 2; } } else if (t === "L" || t === "AN" || t === "EN") { levels[i] += 1; } } let highestLevel = -1; let lowestOddLevel = 99; let level; for (i = 0, ii = levels.length; i < ii; ++i) { level = levels[i]; if (highestLevel < level) { highestLevel = level; } if (lowestOddLevel > level && isOdd(level)) { lowestOddLevel = level; } } for (level = highestLevel; level >= lowestOddLevel; --level) { let start = -1; for (i = 0, ii = levels.length; i < ii; ++i) { if (levels[i] < level) { if (start >= 0) { reverseValues(chars, start, i); start = -1; } } else if (start < 0) { start = i; } } if (start >= 0) { reverseValues(chars, start, levels.length); } } for (i = 0, ii = chars.length; i < ii; ++i) { const ch = chars[i]; if (ch === "<" || ch === ">") { chars[i] = ""; } } return createBidiText(chars.join(""), isLTR); } ;// CONCATENATED MODULE: ./src/core/font_substitutions.js const NORMAL = { style: "normal", weight: "normal" }; const BOLD = { style: "normal", weight: "bold" }; const ITALIC = { style: "italic", weight: "normal" }; const BOLDITALIC = { style: "italic", weight: "bold" }; const substitutionMap = new Map([["Times-Roman", { local: ["Times New Roman", "Times-Roman", "Times", "Liberation Serif", "Nimbus Roman", "Nimbus Roman L", "Tinos", "Thorndale", "TeX Gyre Termes", "FreeSerif", "DejaVu Serif", "Bitstream Vera Serif", "Ubuntu"], style: NORMAL, ultimate: "serif" }], ["Times-Bold", { alias: "Times-Roman", style: BOLD, ultimate: "serif" }], ["Times-Italic", { alias: "Times-Roman", style: ITALIC, ultimate: "serif" }], ["Times-BoldItalic", { alias: "Times-Roman", style: BOLDITALIC, ultimate: "serif" }], ["Helvetica", { local: ["Helvetica", "Helvetica Neue", "Arial", "Arial Nova", "Liberation Sans", "Arimo", "Nimbus Sans", "Nimbus Sans L", "A030", "TeX Gyre Heros", "FreeSans", "DejaVu Sans", "Albany", "Bitstream Vera Sans", "Arial Unicode MS", "Microsoft Sans Serif", "Apple Symbols", "Cantarell"], path: "LiberationSans-Regular.ttf", style: NORMAL, ultimate: "sans-serif" }], ["Helvetica-Bold", { alias: "Helvetica", path: "LiberationSans-Bold.ttf", style: BOLD, ultimate: "sans-serif" }], ["Helvetica-Oblique", { alias: "Helvetica", path: "LiberationSans-Italic.ttf", style: ITALIC, ultimate: "sans-serif" }], ["Helvetica-BoldOblique", { alias: "Helvetica", path: "LiberationSans-BoldItalic.ttf", style: BOLDITALIC, ultimate: "sans-serif" }], ["Courier", { local: ["Courier", "Courier New", "Liberation Mono", "Nimbus Mono", "Nimbus Mono L", "Cousine", "Cumberland", "TeX Gyre Cursor", "FreeMono"], style: NORMAL, ultimate: "monospace" }], ["Courier-Bold", { alias: "Courier", style: BOLD, ultimate: "monospace" }], ["Courier-Oblique", { alias: "Courier", style: ITALIC, ultimate: "monospace" }], ["Courier-BoldOblique", { alias: "Courier", style: BOLDITALIC, ultimate: "monospace" }], ["ArialBlack", { local: ["Arial Black"], style: { style: "normal", weight: "900" }, fallback: "Helvetica-Bold" }], ["ArialBlack-Bold", { alias: "ArialBlack" }], ["ArialBlack-Italic", { alias: "ArialBlack", style: { style: "italic", weight: "900" }, fallback: "Helvetica-BoldOblique" }], ["ArialBlack-BoldItalic", { alias: "ArialBlack-Italic" }], ["ArialNarrow", { local: ["Arial Narrow", "Liberation Sans Narrow", "Helvetica Condensed", "Nimbus Sans Narrow", "TeX Gyre Heros Cn"], style: NORMAL, fallback: "Helvetica" }], ["ArialNarrow-Bold", { alias: "ArialNarrow", style: BOLD, fallback: "Helvetica-Bold" }], ["ArialNarrow-Italic", { alias: "ArialNarrow", style: ITALIC, fallback: "Helvetica-Oblique" }], ["ArialNarrow-BoldItalic", { alias: "ArialNarrow", style: BOLDITALIC, fallback: "Helvetica-BoldOblique" }], ["Calibri", { local: ["Calibri", "Carlito"], style: NORMAL, fallback: "Helvetica" }], ["Calibri-Bold", { alias: "Calibri", style: BOLD, fallback: "Helvetica-Bold" }], ["Calibri-Italic", { alias: "Calibri", style: ITALIC, fallback: "Helvetica-Oblique" }], ["Calibri-BoldItalic", { alias: "Calibri", style: BOLDITALIC, fallback: "Helvetica-BoldOblique" }], ["Wingdings", { local: ["Wingdings", "URW Dingbats"], style: NORMAL }], ["Wingdings-Regular", { alias: "Wingdings" }], ["Wingdings-Bold", { alias: "Wingdings" }]]); const fontAliases = new Map([["Arial-Black", "ArialBlack"]]); function getStyleToAppend(style) { switch (style) { case BOLD: return "Bold"; case ITALIC: return "Italic"; case BOLDITALIC: return "Bold Italic"; default: if (style?.weight === "bold") { return "Bold"; } if (style?.style === "italic") { return "Italic"; } } return ""; } function generateFont({ alias, local, path, fallback, style, ultimate }, src, localFontPath, useFallback = true, usePath = true, append = "") { const result = { style: null, ultimate: null }; if (local) { const extra = append ? ` ${append}` : ""; for (const name of local) { src.push(`local(${name}${extra})`); } } if (alias) { const substitution = substitutionMap.get(alias); const aliasAppend = append || getStyleToAppend(style); Object.assign(result, generateFont(substitution, src, localFontPath, useFallback && !fallback, usePath && !path, aliasAppend)); } if (style) { result.style = style; } if (ultimate) { result.ultimate = ultimate; } if (useFallback && fallback) { const fallbackInfo = substitutionMap.get(fallback); const { ultimate: fallbackUltimate } = generateFont(fallbackInfo, src, localFontPath, useFallback, usePath && !path, append); result.ultimate ||= fallbackUltimate; } if (usePath && path && localFontPath) { src.push(`url(${localFontPath}${path})`); } return result; } function getFontSubstitution(systemFontCache, idFactory, localFontPath, baseFontName, standardFontName) { baseFontName = normalizeFontName(baseFontName); const key = baseFontName; let substitutionInfo = systemFontCache.get(key); if (substitutionInfo) { return substitutionInfo; } let substitution = substitutionMap.get(baseFontName); if (!substitution) { for (const [alias, subst] of fontAliases) { if (baseFontName.startsWith(alias)) { baseFontName = `${subst}${baseFontName.substring(alias.length)}`; substitution = substitutionMap.get(baseFontName); break; } } } let mustAddBaseFont = false; if (!substitution) { substitution = substitutionMap.get(standardFontName); mustAddBaseFont = true; } const loadedName = `${idFactory.getDocId()}_s${idFactory.createFontId()}`; if (!substitution) { if (!validateFontName(baseFontName)) { systemFontCache.set(key, null); return null; } const bold = /bold/gi.test(baseFontName); const italic = /oblique|italic/gi.test(baseFontName); const style = bold && italic && BOLDITALIC || bold && BOLD || italic && ITALIC || NORMAL; substitutionInfo = { css: loadedName, guessFallback: true, loadedName, baseFontName, src: `local(${baseFontName})`, style }; systemFontCache.set(key, substitutionInfo); return substitutionInfo; } const src = []; if (mustAddBaseFont && validateFontName(baseFontName)) { src.push(`local(${baseFontName})`); } const { style, ultimate } = generateFont(substitution, src, localFontPath); const guessFallback = ultimate === null; const fallback = guessFallback ? "" : `,${ultimate}`; substitutionInfo = { css: `${loadedName}${fallback}`, guessFallback, loadedName, baseFontName, src: src.join(","), style }; systemFontCache.set(key, substitutionInfo); return substitutionInfo; } ;// CONCATENATED MODULE: ./src/core/image_resizer.js const MIN_IMAGE_DIM = 2048; const MAX_IMAGE_DIM = 65537; const MAX_ERROR = 128; class ImageResizer { constructor(imgData, isMask) { this._imgData = imgData; this._isMask = isMask; } static needsToBeResized(width, height) { if (width <= this._goodSquareLength && height <= this._goodSquareLength) { return false; } const { MAX_DIM } = this; if (width > MAX_DIM || height > MAX_DIM) { return true; } const area = width * height; if (this._hasMaxArea) { return area > this.MAX_AREA; } if (area < this._goodSquareLength ** 2) { return false; } if (this._areGoodDims(width, height)) { this._goodSquareLength = Math.max(this._goodSquareLength, Math.floor(Math.sqrt(width * height))); return false; } this._goodSquareLength = this._guessMax(this._goodSquareLength, MAX_DIM, MAX_ERROR, 0); const maxArea = this.MAX_AREA = this._goodSquareLength ** 2; return area > maxArea; } static get MAX_DIM() { return shadow(this, "MAX_DIM", this._guessMax(MIN_IMAGE_DIM, MAX_IMAGE_DIM, 0, 1)); } static get MAX_AREA() { this._hasMaxArea = true; return shadow(this, "MAX_AREA", this._guessMax(ImageResizer._goodSquareLength, this.MAX_DIM, MAX_ERROR, 0) ** 2); } static set MAX_AREA(area) { if (area >= 0) { this._hasMaxArea = true; shadow(this, "MAX_AREA", area); } } static setMaxArea(area) { if (!this._hasMaxArea) { this.MAX_AREA = area >> 2; } } static _areGoodDims(width, height) { try { const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("2d"); ctx.fillRect(0, 0, 1, 1); const opacity = ctx.getImageData(0, 0, 1, 1).data[3]; canvas.width = canvas.height = 1; return opacity !== 0; } catch { return false; } } static _guessMax(start, end, tolerance, defaultHeight) { while (start + tolerance + 1 < end) { const middle = Math.floor((start + end) / 2); const height = defaultHeight || middle; if (this._areGoodDims(middle, height)) { start = middle; } else { end = middle; } } return start; } static async createImage(imgData, isMask = false) { return new ImageResizer(imgData, isMask)._createImage(); } async _createImage() { const data = this._encodeBMP(); const blob = new Blob([data.buffer], { type: "image/bmp" }); const bitmapPromise = createImageBitmap(blob); const { MAX_AREA, MAX_DIM } = ImageResizer; const { _imgData: imgData } = this; const { width, height } = imgData; const minFactor = Math.max(width / MAX_DIM, height / MAX_DIM, Math.sqrt(width * height / MAX_AREA)); const firstFactor = Math.max(minFactor, 2); const factor = Math.round(10 * (minFactor + 1.25)) / 10 / firstFactor; const N = Math.floor(Math.log2(factor)); const steps = new Array(N + 2).fill(2); steps[0] = firstFactor; steps.splice(-1, 1, factor / (1 << N)); let newWidth = width; let newHeight = height; let bitmap = await bitmapPromise; for (const step of steps) { const prevWidth = newWidth; const prevHeight = newHeight; newWidth = Math.floor(newWidth / step) - 1; newHeight = Math.floor(newHeight / step) - 1; const canvas = new OffscreenCanvas(newWidth, newHeight); const ctx = canvas.getContext("2d"); ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight); bitmap = canvas.transferToImageBitmap(); } imgData.data = null; imgData.bitmap = bitmap; imgData.width = newWidth; imgData.height = newHeight; return imgData; } _encodeBMP() { const { width, height, kind } = this._imgData; let data = this._imgData.data; let bitPerPixel; let colorTable = new Uint8Array(0); let maskTable = colorTable; let compression = 0; switch (kind) { case ImageKind.GRAYSCALE_1BPP: { bitPerPixel = 1; colorTable = new Uint8Array(this._isMask ? [255, 255, 255, 255, 0, 0, 0, 0] : [0, 0, 0, 0, 255, 255, 255, 255]); const rowLen = width + 7 >> 3; const rowSize = rowLen + 3 & -4; if (rowLen !== rowSize) { const newData = new Uint8Array(rowSize * height); let k = 0; for (let i = 0, ii = height * rowLen; i < ii; i += rowLen, k += rowSize) { newData.set(data.subarray(i, i + rowLen), k); } data = newData; } break; } case ImageKind.RGB_24BPP: { bitPerPixel = 24; if (width & 3) { const rowLen = 3 * width; const rowSize = rowLen + 3 & -4; const extraLen = rowSize - rowLen; const newData = new Uint8Array(rowSize * height); let k = 0; for (let i = 0, ii = height * rowLen; i < ii; i += rowLen) { const row = data.subarray(i, i + rowLen); for (let j = 0; j < rowLen; j += 3) { newData[k++] = row[j + 2]; newData[k++] = row[j + 1]; newData[k++] = row[j]; } k += extraLen; } data = newData; } else { for (let i = 0, ii = data.length; i < ii; i += 3) { const tmp = data[i]; data[i] = data[i + 2]; data[i + 2] = tmp; } } break; } case ImageKind.RGBA_32BPP: bitPerPixel = 32; compression = 3; maskTable = new Uint8Array(4 + 4 + 4 + 4 + 52); const view = new DataView(maskTable.buffer); if (FeatureTest.isLittleEndian) { view.setUint32(0, 0x000000ff, true); view.setUint32(4, 0x0000ff00, true); view.setUint32(8, 0x00ff0000, true); view.setUint32(12, 0xff000000, true); } else { view.setUint32(0, 0xff000000, true); view.setUint32(4, 0x00ff0000, true); view.setUint32(8, 0x0000ff00, true); view.setUint32(12, 0x000000ff, true); } break; default: throw new Error("invalid format"); } let i = 0; const headerLength = 40 + maskTable.length; const fileLength = 14 + headerLength + colorTable.length + data.length; const bmpData = new Uint8Array(fileLength); const view = new DataView(bmpData.buffer); view.setUint16(i, 0x4d42, true); i += 2; view.setUint32(i, fileLength, true); i += 4; view.setUint32(i, 0, true); i += 4; view.setUint32(i, 14 + headerLength + colorTable.length, true); i += 4; view.setUint32(i, headerLength, true); i += 4; view.setInt32(i, width, true); i += 4; view.setInt32(i, -height, true); i += 4; view.setUint16(i, 1, true); i += 2; view.setUint16(i, bitPerPixel, true); i += 2; view.setUint32(i, compression, true); i += 4; view.setUint32(i, 0, true); i += 4; view.setInt32(i, 0, true); i += 4; view.setInt32(i, 0, true); i += 4; view.setUint32(i, colorTable.length / 4, true); i += 4; view.setUint32(i, 0, true); i += 4; bmpData.set(maskTable, i); i += maskTable.length; bmpData.set(colorTable, i); i += colorTable.length; bmpData.set(data, i); return bmpData; } } ImageResizer._goodSquareLength = MIN_IMAGE_DIM; ;// CONCATENATED MODULE: ./src/shared/murmurhash3.js const SEED = 0xc3d2e1f0; const MASK_HIGH = 0xffff0000; const MASK_LOW = 0xffff; class MurmurHash3_64 { constructor(seed) { this.h1 = seed ? seed & 0xffffffff : SEED; this.h2 = seed ? seed & 0xffffffff : SEED; } update(input) { let data, length; if (typeof input === "string") { data = new Uint8Array(input.length * 2); length = 0; for (let i = 0, ii = input.length; i < ii; i++) { const code = input.charCodeAt(i); if (code <= 0xff) { data[length++] = code; } else { data[length++] = code >>> 8; data[length++] = code & 0xff; } } } else if (isArrayBuffer(input)) { data = input.slice(); length = data.byteLength; } else { throw new Error("Wrong data format in MurmurHash3_64_update. " + "Input must be a string or array."); } const blockCounts = length >> 2; const tailLength = length - blockCounts * 4; const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts); let k1 = 0, k2 = 0; let h1 = this.h1, h2 = this.h2; const C1 = 0xcc9e2d51, C2 = 0x1b873593; const C1_LOW = C1 & MASK_LOW, C2_LOW = C2 & MASK_LOW; for (let i = 0; i < blockCounts; i++) { if (i & 1) { k1 = dataUint32[i]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; h1 ^= k1; h1 = h1 << 13 | h1 >>> 19; h1 = h1 * 5 + 0xe6546b64; } else { k2 = dataUint32[i]; k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW; k2 = k2 << 15 | k2 >>> 17; k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW; h2 ^= k2; h2 = h2 << 13 | h2 >>> 19; h2 = h2 * 5 + 0xe6546b64; } } k1 = 0; switch (tailLength) { case 3: k1 ^= data[blockCounts * 4 + 2] << 16; case 2: k1 ^= data[blockCounts * 4 + 1] << 8; case 1: k1 ^= data[blockCounts * 4]; k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW; k1 = k1 << 15 | k1 >>> 17; k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW; if (blockCounts & 1) { h1 ^= k1; } else { h2 ^= k1; } } this.h1 = h1; this.h2 = h2; } hexdigest() { let h1 = this.h1, h2 = this.h2; h1 ^= h2 >>> 1; h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW; h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW; h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16; h1 ^= h2 >>> 1; return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0"); } } ;// CONCATENATED MODULE: ./src/core/operator_list.js function addState(parentState, pattern, checkFn, iterateFn, processFn) { let state = parentState; for (let i = 0, ii = pattern.length - 1; i < ii; i++) { const item = pattern[i]; state = state[item] ||= []; } state[pattern.at(-1)] = { checkFn, iterateFn, processFn }; } const InitialState = []; addState(InitialState, [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], null, function iterateInlineImageGroup(context, i) { const fnArray = context.fnArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === OPS.save; case 1: return fnArray[i] === OPS.transform; case 2: return fnArray[i] === OPS.paintInlineImageXObject; case 3: return fnArray[i] === OPS.restore; } throw new Error(`iterateInlineImageGroup - invalid pos: ${pos}`); }, function foundInlineImageGroup(context, i) { const MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10; const MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200; const MAX_WIDTH = 1000; const IMAGE_PADDING = 1; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIIXO = curr - 1; const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_INLINE_IMAGES_BLOCK); if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { return i - (i - iFirstSave) % 4; } let maxX = 0; const map = []; let maxLineHeight = 0; let currentX = IMAGE_PADDING, currentY = IMAGE_PADDING; for (let q = 0; q < count; q++) { const transform = argsArray[iFirstTransform + (q << 2)]; const img = argsArray[iFirstPIIXO + (q << 2)][0]; if (currentX + img.width > MAX_WIDTH) { maxX = Math.max(maxX, currentX); currentY += maxLineHeight + 2 * IMAGE_PADDING; currentX = 0; maxLineHeight = 0; } map.push({ transform, x: currentX, y: currentY, w: img.width, h: img.height }); currentX += img.width + 2 * IMAGE_PADDING; maxLineHeight = Math.max(maxLineHeight, img.height); } const imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING; const imgHeight = currentY + maxLineHeight + IMAGE_PADDING; const imgData = new Uint8Array(imgWidth * imgHeight * 4); const imgRowSize = imgWidth << 2; for (let q = 0; q < count; q++) { const data = argsArray[iFirstPIIXO + (q << 2)][0].data; const rowSize = map[q].w << 2; let dataOffset = 0; let offset = map[q].x + map[q].y * imgWidth << 2; imgData.set(data.subarray(0, rowSize), offset - imgRowSize); for (let k = 0, kk = map[q].h; k < kk; k++) { imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset); dataOffset += rowSize; offset += imgRowSize; } imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset); while (offset >= 0) { data[offset - 4] = data[offset]; data[offset - 3] = data[offset + 1]; data[offset - 2] = data[offset + 2]; data[offset - 1] = data[offset + 3]; data[offset + rowSize] = data[offset + rowSize - 4]; data[offset + rowSize + 1] = data[offset + rowSize - 3]; data[offset + rowSize + 2] = data[offset + rowSize - 2]; data[offset + rowSize + 3] = data[offset + rowSize - 1]; offset -= imgRowSize; } } const img = { width: imgWidth, height: imgHeight }; if (context.isOffscreenCanvasSupported) { const canvas = new OffscreenCanvas(imgWidth, imgHeight); const ctx = canvas.getContext("2d"); ctx.putImageData(new ImageData(new Uint8ClampedArray(imgData.buffer), imgWidth, imgHeight), 0, 0); img.bitmap = canvas.transferToImageBitmap(); img.data = null; } else { img.kind = ImageKind.RGBA_32BPP; img.data = imgData; } fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup); argsArray.splice(iFirstSave, count * 4, [img, map]); return iFirstSave + 1; }); addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], null, function iterateImageMaskGroup(context, i) { const fnArray = context.fnArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === OPS.save; case 1: return fnArray[i] === OPS.transform; case 2: return fnArray[i] === OPS.paintImageMaskXObject; case 3: return fnArray[i] === OPS.restore; } throw new Error(`iterateImageMaskGroup - invalid pos: ${pos}`); }, function foundImageMaskGroup(context, i) { const MIN_IMAGES_IN_MASKS_BLOCK = 10; const MAX_IMAGES_IN_MASKS_BLOCK = 100; const MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIMXO = curr - 1; let count = Math.floor((i - iFirstSave) / 4); if (count < MIN_IMAGES_IN_MASKS_BLOCK) { return i - (i - iFirstSave) % 4; } let isSameImage = false; let iTransform, transformArgs; const firstPIMXOArg0 = argsArray[iFirstPIMXO][0]; const firstTransformArg0 = argsArray[iFirstTransform][0], firstTransformArg1 = argsArray[iFirstTransform][1], firstTransformArg2 = argsArray[iFirstTransform][2], firstTransformArg3 = argsArray[iFirstTransform][3]; if (firstTransformArg1 === firstTransformArg2) { isSameImage = true; iTransform = iFirstTransform + 4; let iPIMXO = iFirstPIMXO + 4; for (let q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) { transformArgs = argsArray[iTransform]; if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== firstTransformArg1 || transformArgs[2] !== firstTransformArg2 || transformArgs[3] !== firstTransformArg3) { if (q < MIN_IMAGES_IN_MASKS_BLOCK) { isSameImage = false; } else { count = q; } break; } } } if (isSameImage) { count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK); const positions = new Float32Array(count * 2); iTransform = iFirstTransform; for (let q = 0; q < count; q++, iTransform += 4) { transformArgs = argsArray[iTransform]; positions[q << 1] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; } fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat); argsArray.splice(iFirstSave, count * 4, [firstPIMXOArg0, firstTransformArg0, firstTransformArg1, firstTransformArg2, firstTransformArg3, positions]); } else { count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK); const images = []; for (let q = 0; q < count; q++) { transformArgs = argsArray[iFirstTransform + (q << 2)]; const maskParams = argsArray[iFirstPIMXO + (q << 2)][0]; images.push({ data: maskParams.data, width: maskParams.width, height: maskParams.height, interpolate: maskParams.interpolate, count: maskParams.count, transform: transformArgs }); } fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup); argsArray.splice(iFirstSave, count * 4, [images]); } return iFirstSave + 1; }); addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], function (context) { const argsArray = context.argsArray; const iFirstTransform = context.iCurr - 2; return argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0; }, function iterateImageGroup(context, i) { const fnArray = context.fnArray, argsArray = context.argsArray; const iFirstSave = context.iCurr - 3; const pos = (i - iFirstSave) % 4; switch (pos) { case 0: return fnArray[i] === OPS.save; case 1: if (fnArray[i] !== OPS.transform) { return false; } const iFirstTransform = context.iCurr - 2; const firstTransformArg0 = argsArray[iFirstTransform][0]; const firstTransformArg3 = argsArray[iFirstTransform][3]; if (argsArray[i][0] !== firstTransformArg0 || argsArray[i][1] !== 0 || argsArray[i][2] !== 0 || argsArray[i][3] !== firstTransformArg3) { return false; } return true; case 2: if (fnArray[i] !== OPS.paintImageXObject) { return false; } const iFirstPIXO = context.iCurr - 1; const firstPIXOArg0 = argsArray[iFirstPIXO][0]; if (argsArray[i][0] !== firstPIXOArg0) { return false; } return true; case 3: return fnArray[i] === OPS.restore; } throw new Error(`iterateImageGroup - invalid pos: ${pos}`); }, function (context, i) { const MIN_IMAGES_IN_BLOCK = 3; const MAX_IMAGES_IN_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstSave = curr - 3; const iFirstTransform = curr - 2; const iFirstPIXO = curr - 1; const firstPIXOArg0 = argsArray[iFirstPIXO][0]; const firstTransformArg0 = argsArray[iFirstTransform][0]; const firstTransformArg3 = argsArray[iFirstTransform][3]; const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_BLOCK); if (count < MIN_IMAGES_IN_BLOCK) { return i - (i - iFirstSave) % 4; } const positions = new Float32Array(count * 2); let iTransform = iFirstTransform; for (let q = 0; q < count; q++, iTransform += 4) { const transformArgs = argsArray[iTransform]; positions[q << 1] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; } const args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, positions]; fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat); argsArray.splice(iFirstSave, count * 4, args); return iFirstSave + 1; }); addState(InitialState, [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], null, function iterateShowTextGroup(context, i) { const fnArray = context.fnArray, argsArray = context.argsArray; const iFirstSave = context.iCurr - 4; const pos = (i - iFirstSave) % 5; switch (pos) { case 0: return fnArray[i] === OPS.beginText; case 1: return fnArray[i] === OPS.setFont; case 2: return fnArray[i] === OPS.setTextMatrix; case 3: if (fnArray[i] !== OPS.showText) { return false; } const iFirstSetFont = context.iCurr - 3; const firstSetFontArg0 = argsArray[iFirstSetFont][0]; const firstSetFontArg1 = argsArray[iFirstSetFont][1]; if (argsArray[i][0] !== firstSetFontArg0 || argsArray[i][1] !== firstSetFontArg1) { return false; } return true; case 4: return fnArray[i] === OPS.endText; } throw new Error(`iterateShowTextGroup - invalid pos: ${pos}`); }, function (context, i) { const MIN_CHARS_IN_BLOCK = 3; const MAX_CHARS_IN_BLOCK = 1000; const fnArray = context.fnArray, argsArray = context.argsArray; const curr = context.iCurr; const iFirstBeginText = curr - 4; const iFirstSetFont = curr - 3; const iFirstSetTextMatrix = curr - 2; const iFirstShowText = curr - 1; const iFirstEndText = curr; const firstSetFontArg0 = argsArray[iFirstSetFont][0]; const firstSetFontArg1 = argsArray[iFirstSetFont][1]; let count = Math.min(Math.floor((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK); if (count < MIN_CHARS_IN_BLOCK) { return i - (i - iFirstBeginText) % 5; } let iFirst = iFirstBeginText; if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) { count++; iFirst -= 5; } let iEndText = iFirst + 4; for (let q = 1; q < count; q++) { fnArray.splice(iEndText, 3); argsArray.splice(iEndText, 3); iEndText += 2; } return iEndText + 1; }); class NullOptimizer { constructor(queue) { this.queue = queue; } _optimize() {} push(fn, args) { this.queue.fnArray.push(fn); this.queue.argsArray.push(args); this._optimize(); } flush() {} reset() {} } class QueueOptimizer extends NullOptimizer { constructor(queue) { super(queue); this.state = null; this.context = { iCurr: 0, fnArray: queue.fnArray, argsArray: queue.argsArray, isOffscreenCanvasSupported: false }; this.match = null; this.lastProcessed = 0; } set isOffscreenCanvasSupported(value) { this.context.isOffscreenCanvasSupported = value; } _optimize() { const fnArray = this.queue.fnArray; let i = this.lastProcessed, ii = fnArray.length; let state = this.state; let match = this.match; if (!state && !match && i + 1 === ii && !InitialState[fnArray[i]]) { this.lastProcessed = ii; return; } const context = this.context; while (i < ii) { if (match) { const iterate = (0, match.iterateFn)(context, i); if (iterate) { i++; continue; } i = (0, match.processFn)(context, i + 1); ii = fnArray.length; match = null; state = null; if (i >= ii) { break; } } state = (state || InitialState)[fnArray[i]]; if (!state || Array.isArray(state)) { i++; continue; } context.iCurr = i; i++; if (state.checkFn && !(0, state.checkFn)(context)) { state = null; continue; } match = state; state = null; } this.state = state; this.match = match; this.lastProcessed = i; } flush() { while (this.match) { const length = this.queue.fnArray.length; this.lastProcessed = (0, this.match.processFn)(this.context, length); this.match = null; this.state = null; this._optimize(); } } reset() { this.state = null; this.match = null; this.lastProcessed = 0; } } class OperatorList { static CHUNK_SIZE = 1000; static CHUNK_SIZE_ABOUT = this.CHUNK_SIZE - 5; constructor(intent = 0, streamSink) { this._streamSink = streamSink; this.fnArray = []; this.argsArray = []; this.optimizer = streamSink && !(intent & RenderingIntentFlag.OPLIST) ? new QueueOptimizer(this) : new NullOptimizer(this); this.dependencies = new Set(); this._totalLength = 0; this.weight = 0; this._resolved = streamSink ? null : Promise.resolve(); } set isOffscreenCanvasSupported(value) { this.optimizer.isOffscreenCanvasSupported = value; } get length() { return this.argsArray.length; } get ready() { return this._resolved || this._streamSink.ready; } get totalLength() { return this._totalLength + this.length; } addOp(fn, args) { this.optimizer.push(fn, args); this.weight++; if (this._streamSink) { if (this.weight >= OperatorList.CHUNK_SIZE) { this.flush(); } else if (this.weight >= OperatorList.CHUNK_SIZE_ABOUT && (fn === OPS.restore || fn === OPS.endText)) { this.flush(); } } } addImageOps(fn, args, optionalContent) { if (optionalContent !== undefined) { this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); } this.addOp(fn, args); if (optionalContent !== undefined) { this.addOp(OPS.endMarkedContent, []); } } addDependency(dependency) { if (this.dependencies.has(dependency)) { return; } this.dependencies.add(dependency); this.addOp(OPS.dependency, [dependency]); } addDependencies(dependencies) { for (const dependency of dependencies) { this.addDependency(dependency); } } addOpList(opList) { if (!(opList instanceof OperatorList)) { warn('addOpList - ignoring invalid "opList" parameter.'); return; } for (const dependency of opList.dependencies) { this.dependencies.add(dependency); } for (let i = 0, ii = opList.length; i < ii; i++) { this.addOp(opList.fnArray[i], opList.argsArray[i]); } } getIR() { return { fnArray: this.fnArray, argsArray: this.argsArray, length: this.length }; } get _transfers() { const transfers = []; const { fnArray, argsArray, length } = this; for (let i = 0; i < length; i++) { switch (fnArray[i]) { case OPS.paintInlineImageXObject: case OPS.paintInlineImageXObjectGroup: case OPS.paintImageMaskXObject: const arg = argsArray[i][0]; if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) { transfers.push(arg.data.buffer); } break; } } return transfers; } flush(lastChunk = false, separateAnnots = null) { this.optimizer.flush(); const length = this.length; this._totalLength += length; this._streamSink.enqueue({ fnArray: this.fnArray, argsArray: this.argsArray, lastChunk, separateAnnots, length }, 1, this._transfers); this.dependencies.clear(); this.fnArray.length = 0; this.argsArray.length = 0; this.weight = 0; this.optimizer.reset(); } } ;// CONCATENATED MODULE: ./src/core/image.js function decodeAndClamp(value, addend, coefficient, max) { value = addend + value * coefficient; if (value < 0) { value = 0; } else if (value > max) { value = max; } return value; } function resizeImageMask(src, bpc, w1, h1, w2, h2) { const length = w2 * h2; let dest; if (bpc <= 8) { dest = new Uint8Array(length); } else if (bpc <= 16) { dest = new Uint16Array(length); } else { dest = new Uint32Array(length); } const xRatio = w1 / w2; const yRatio = h1 / h2; let i, j, py, newIndex = 0, oldIndex; const xScaled = new Uint16Array(w2); const w1Scanline = w1; for (i = 0; i < w2; i++) { xScaled[i] = Math.floor(i * xRatio); } for (i = 0; i < h2; i++) { py = Math.floor(i * yRatio) * w1Scanline; for (j = 0; j < w2; j++) { oldIndex = py + xScaled[j]; dest[newIndex++] = src[oldIndex]; } } return dest; } class PDFImage { constructor({ xref, res, image, isInline = false, smask = null, mask = null, isMask = false, pdfFunctionFactory, localColorSpaceCache }) { this.image = image; const dict = image.dict; const filter = dict.get("F", "Filter"); let filterName; if (filter instanceof Name) { filterName = filter.name; } else if (Array.isArray(filter)) { const filterZero = xref.fetchIfRef(filter[0]); if (filterZero instanceof Name) { filterName = filterZero.name; } } switch (filterName) { case "JPXDecode": const jpxImage = new JpxImage(); jpxImage.parseImageProperties(image.stream); image.stream.reset(); image.width = jpxImage.width; image.height = jpxImage.height; image.bitsPerComponent = jpxImage.bitsPerComponent; image.numComps = jpxImage.componentsCount; break; case "JBIG2Decode": image.bitsPerComponent = 1; image.numComps = 1; break; } let width = dict.get("W", "Width"); let height = dict.get("H", "Height"); if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) { warn("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary."); width = image.width; height = image.height; } if (width < 1 || height < 1) { throw new FormatError(`Invalid image width: ${width} or height: ${height}`); } this.width = width; this.height = height; this.interpolate = dict.get("I", "Interpolate"); this.imageMask = dict.get("IM", "ImageMask") || false; this.matte = dict.get("Matte") || false; let bitsPerComponent = image.bitsPerComponent; if (!bitsPerComponent) { bitsPerComponent = dict.get("BPC", "BitsPerComponent"); if (!bitsPerComponent) { if (this.imageMask) { bitsPerComponent = 1; } else { throw new FormatError(`Bits per component missing in image: ${this.imageMask}`); } } } this.bpc = bitsPerComponent; if (!this.imageMask) { let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace"); if (!colorSpace) { info("JPX images (which do not require color spaces)"); switch (image.numComps) { case 1: colorSpace = Name.get("DeviceGray"); break; case 3: colorSpace = Name.get("DeviceRGB"); break; case 4: colorSpace = Name.get("DeviceCMYK"); break; default: throw new Error(`JPX images with ${image.numComps} color components not supported.`); } } this.colorSpace = ColorSpace.parse({ cs: colorSpace, xref, resources: isInline ? res : null, pdfFunctionFactory, localColorSpaceCache }); this.numComps = this.colorSpace.numComps; } this.decode = dict.getArray("D", "Decode"); this.needsDecode = false; if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !ColorSpace.isDefaultDecode(this.decode, 1))) { this.needsDecode = true; const max = (1 << bitsPerComponent) - 1; this.decodeCoefficients = []; this.decodeAddends = []; const isIndexed = this.colorSpace?.name === "Indexed"; for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) { const dmin = this.decode[i]; const dmax = this.decode[i + 1]; this.decodeCoefficients[j] = isIndexed ? (dmax - dmin) / max : dmax - dmin; this.decodeAddends[j] = isIndexed ? dmin : max * dmin; } } if (smask) { this.smask = new PDFImage({ xref, res, image: smask, isInline, pdfFunctionFactory, localColorSpaceCache }); } else if (mask) { if (mask instanceof BaseStream) { const maskDict = mask.dict, imageMask = maskDict.get("IM", "ImageMask"); if (!imageMask) { warn("Ignoring /Mask in image without /ImageMask."); } else { this.mask = new PDFImage({ xref, res, image: mask, isInline, isMask: true, pdfFunctionFactory, localColorSpaceCache }); } } else { this.mask = mask; } } } static async buildImage({ xref, res, image, isInline = false, pdfFunctionFactory, localColorSpaceCache }) { const imageData = image; let smaskData = null; let maskData = null; const smask = image.dict.get("SMask"); const mask = image.dict.get("Mask"); if (smask) { if (smask instanceof BaseStream) { smaskData = smask; } else { warn("Unsupported /SMask format."); } } else if (mask) { if (mask instanceof BaseStream || Array.isArray(mask)) { maskData = mask; } else { warn("Unsupported /Mask format."); } } return new PDFImage({ xref, res, image: imageData, isInline, smask: smaskData, mask: maskData, pdfFunctionFactory, localColorSpaceCache }); } static createRawMask({ imgArray, width, height, imageIsFromDecodeStream, inverseDecode, interpolate }) { const computedLength = (width + 7 >> 3) * height; const actualLength = imgArray.byteLength; const haveFullData = computedLength === actualLength; let data, i; if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) { data = imgArray; } else if (!inverseDecode) { data = new Uint8Array(imgArray); } else { data = new Uint8Array(computedLength); data.set(imgArray); data.fill(0xff, actualLength); } if (inverseDecode) { for (i = 0; i < actualLength; i++) { data[i] ^= 0xff; } } return { data, width, height, interpolate }; } static async createMask({ imgArray, width, height, imageIsFromDecodeStream, inverseDecode, interpolate, isOffscreenCanvasSupported = false }) { const isSingleOpaquePixel = width === 1 && height === 1 && inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128)); if (isSingleOpaquePixel) { return { isSingleOpaquePixel }; } if (isOffscreenCanvasSupported) { if (ImageResizer.needsToBeResized(width, height)) { const data = new Uint8ClampedArray(width * height * 4); convertBlackAndWhiteToRGBA({ src: imgArray, dest: data, width, height, nonBlackColor: 0, inverseDecode }); return ImageResizer.createImage({ kind: ImageKind.RGBA_32BPP, data, width, height, interpolate }); } const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("2d"); const imgData = ctx.createImageData(width, height); convertBlackAndWhiteToRGBA({ src: imgArray, dest: imgData.data, width, height, nonBlackColor: 0, inverseDecode }); ctx.putImageData(imgData, 0, 0); const bitmap = canvas.transferToImageBitmap(); return { data: null, width, height, interpolate, bitmap }; } return this.createRawMask({ imgArray, width, height, inverseDecode, imageIsFromDecodeStream, interpolate }); } get drawWidth() { return Math.max(this.width, this.smask?.width || 0, this.mask?.width || 0); } get drawHeight() { return Math.max(this.height, this.smask?.height || 0, this.mask?.height || 0); } decodeBuffer(buffer) { const bpc = this.bpc; const numComps = this.numComps; const decodeAddends = this.decodeAddends; const decodeCoefficients = this.decodeCoefficients; const max = (1 << bpc) - 1; let i, ii; if (bpc === 1) { for (i = 0, ii = buffer.length; i < ii; i++) { buffer[i] = +!buffer[i]; } return; } let index = 0; for (i = 0, ii = this.width * this.height; i < ii; i++) { for (let j = 0; j < numComps; j++) { buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max); index++; } } } getComponents(buffer) { const bpc = this.bpc; if (bpc === 8) { return buffer; } const width = this.width; const height = this.height; const numComps = this.numComps; const length = width * height * numComps; let bufferPos = 0; let output; if (bpc <= 8) { output = new Uint8Array(length); } else if (bpc <= 16) { output = new Uint16Array(length); } else { output = new Uint32Array(length); } const rowComps = width * numComps; const max = (1 << bpc) - 1; let i = 0, ii, buf; if (bpc === 1) { let mask, loop1End, loop2End; for (let j = 0; j < height; j++) { loop1End = i + (rowComps & ~7); loop2End = i + rowComps; while (i < loop1End) { buf = buffer[bufferPos++]; output[i] = buf >> 7 & 1; output[i + 1] = buf >> 6 & 1; output[i + 2] = buf >> 5 & 1; output[i + 3] = buf >> 4 & 1; output[i + 4] = buf >> 3 & 1; output[i + 5] = buf >> 2 & 1; output[i + 6] = buf >> 1 & 1; output[i + 7] = buf & 1; i += 8; } if (i < loop2End) { buf = buffer[bufferPos++]; mask = 128; while (i < loop2End) { output[i++] = +!!(buf & mask); mask >>= 1; } } } } else { let bits = 0; buf = 0; for (i = 0, ii = length; i < ii; ++i) { if (i % rowComps === 0) { buf = 0; bits = 0; } while (bits < bpc) { buf = buf << 8 | buffer[bufferPos++]; bits += 8; } const remainingBits = bits - bpc; let value = buf >> remainingBits; if (value < 0) { value = 0; } else if (value > max) { value = max; } output[i] = value; buf &= (1 << remainingBits) - 1; bits = remainingBits; } } return output; } fillOpacity(rgbaBuf, width, height, actualHeight, image) { const smask = this.smask; const mask = this.mask; let alphaBuf, sw, sh, i, ii, j; if (smask) { sw = smask.width; sh = smask.height; alphaBuf = new Uint8ClampedArray(sw * sh); smask.fillGrayBuffer(alphaBuf); if (sw !== width || sh !== height) { alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height); } } else if (mask) { if (mask instanceof PDFImage) { sw = mask.width; sh = mask.height; alphaBuf = new Uint8ClampedArray(sw * sh); mask.numComps = 1; mask.fillGrayBuffer(alphaBuf); for (i = 0, ii = sw * sh; i < ii; ++i) { alphaBuf[i] = 255 - alphaBuf[i]; } if (sw !== width || sh !== height) { alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height); } } else if (Array.isArray(mask)) { alphaBuf = new Uint8ClampedArray(width * height); const numComps = this.numComps; for (i = 0, ii = width * height; i < ii; ++i) { let opacity = 0; const imageOffset = i * numComps; for (j = 0; j < numComps; ++j) { const color = image[imageOffset + j]; const maskOffset = j * 2; if (color < mask[maskOffset] || color > mask[maskOffset + 1]) { opacity = 255; break; } } alphaBuf[i] = opacity; } } else { throw new FormatError("Unknown mask format."); } } if (alphaBuf) { for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { rgbaBuf[j] = alphaBuf[i]; } } else { for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { rgbaBuf[j] = 255; } } } undoPreblend(buffer, width, height) { const matte = this.smask?.matte; if (!matte) { return; } const matteRgb = this.colorSpace.getRgb(matte, 0); const matteR = matteRgb[0]; const matteG = matteRgb[1]; const matteB = matteRgb[2]; const length = width * height * 4; for (let i = 0; i < length; i += 4) { const alpha = buffer[i + 3]; if (alpha === 0) { buffer[i] = 255; buffer[i + 1] = 255; buffer[i + 2] = 255; continue; } const k = 255 / alpha; buffer[i] = (buffer[i] - matteR) * k + matteR; buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG; buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB; } } async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) { const drawWidth = this.drawWidth; const drawHeight = this.drawHeight; const imgData = { width: drawWidth, height: drawHeight, interpolate: this.interpolate, kind: 0, data: null }; const numComps = this.numComps; const originalWidth = this.width; const originalHeight = this.height; const bpc = this.bpc; const rowBytes = originalWidth * numComps * bpc + 7 >> 3; const mustBeResized = isOffscreenCanvasSupported && ImageResizer.needsToBeResized(drawWidth, drawHeight); if (!forceRGBA) { let kind; if (this.colorSpace.name === "DeviceGray" && bpc === 1) { kind = ImageKind.GRAYSCALE_1BPP; } else if (this.colorSpace.name === "DeviceRGB" && bpc === 8 && !this.needsDecode) { kind = ImageKind.RGB_24BPP; } if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) { const data = this.getImageBytes(originalHeight * rowBytes, {}); if (isOffscreenCanvasSupported) { if (mustBeResized) { return ImageResizer.createImage({ data, kind, width: drawWidth, height: drawHeight, interpolate: this.interpolate }, this.needsDecode); } return this.createBitmap(kind, originalWidth, originalHeight, data); } imgData.kind = kind; imgData.data = data; if (this.needsDecode) { assert(kind === ImageKind.GRAYSCALE_1BPP, "PDFImage.createImageData: The image must be grayscale."); const buffer = imgData.data; for (let i = 0, ii = buffer.length; i < ii; i++) { buffer[i] ^= 0xff; } } return imgData; } if (this.image instanceof JpegStream && !this.smask && !this.mask && !this.needsDecode) { let imageLength = originalHeight * rowBytes; if (isOffscreenCanvasSupported && !mustBeResized) { let isHandled = false; switch (this.colorSpace.name) { case "DeviceGray": imageLength *= 4; isHandled = true; break; case "DeviceRGB": imageLength = imageLength / 3 * 4; isHandled = true; break; case "DeviceCMYK": isHandled = true; break; } if (isHandled) { const rgba = this.getImageBytes(imageLength, { drawWidth, drawHeight, forceRGBA: true }); return this.createBitmap(ImageKind.RGBA_32BPP, drawWidth, drawHeight, rgba); } } else { switch (this.colorSpace.name) { case "DeviceGray": imageLength *= 3; case "DeviceRGB": case "DeviceCMYK": imgData.kind = ImageKind.RGB_24BPP; imgData.data = this.getImageBytes(imageLength, { drawWidth, drawHeight, forceRGB: true }); if (mustBeResized) { return ImageResizer.createImage(imgData); } return imgData; } } } } const imgArray = this.getImageBytes(originalHeight * rowBytes, { internal: true }); const actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight; const comps = this.getComponents(imgArray); let alpha01, maybeUndoPreblend; let canvas, ctx, canvasImgData, data; if (isOffscreenCanvasSupported && !mustBeResized) { canvas = new OffscreenCanvas(drawWidth, drawHeight); ctx = canvas.getContext("2d"); canvasImgData = ctx.createImageData(drawWidth, drawHeight); data = canvasImgData.data; } imgData.kind = ImageKind.RGBA_32BPP; if (!forceRGBA && !this.smask && !this.mask) { if (!isOffscreenCanvasSupported || mustBeResized) { imgData.kind = ImageKind.RGB_24BPP; data = new Uint8ClampedArray(drawWidth * drawHeight * 3); alpha01 = 0; } else { const arr = new Uint32Array(data.buffer); arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff); alpha01 = 1; } maybeUndoPreblend = false; } else { if (!isOffscreenCanvasSupported || mustBeResized) { data = new Uint8ClampedArray(drawWidth * drawHeight * 4); } alpha01 = 1; maybeUndoPreblend = true; this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps); } if (this.needsDecode) { this.decodeBuffer(comps); } this.colorSpace.fillRgb(data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01); if (maybeUndoPreblend) { this.undoPreblend(data, drawWidth, actualHeight); } if (isOffscreenCanvasSupported && !mustBeResized) { ctx.putImageData(canvasImgData, 0, 0); const bitmap = canvas.transferToImageBitmap(); return { data: null, width: drawWidth, height: drawHeight, bitmap, interpolate: this.interpolate }; } imgData.data = data; if (mustBeResized) { return ImageResizer.createImage(imgData); } return imgData; } fillGrayBuffer(buffer) { const numComps = this.numComps; if (numComps !== 1) { throw new FormatError(`Reading gray scale from a color image: ${numComps}`); } const width = this.width; const height = this.height; const bpc = this.bpc; const rowBytes = width * numComps * bpc + 7 >> 3; const imgArray = this.getImageBytes(height * rowBytes, { internal: true }); const comps = this.getComponents(imgArray); let i, length; if (bpc === 1) { length = width * height; if (this.needsDecode) { for (i = 0; i < length; ++i) { buffer[i] = comps[i] - 1 & 255; } } else { for (i = 0; i < length; ++i) { buffer[i] = -comps[i] & 255; } } return; } if (this.needsDecode) { this.decodeBuffer(comps); } length = width * height; const scale = 255 / ((1 << bpc) - 1); for (i = 0; i < length; ++i) { buffer[i] = scale * comps[i]; } } createBitmap(kind, width, height, src) { const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("2d"); let imgData; if (kind === ImageKind.RGBA_32BPP) { imgData = new ImageData(src, width, height); } else { imgData = ctx.createImageData(width, height); convertToRGBA({ kind, src, dest: new Uint32Array(imgData.data.buffer), width, height, inverseDecode: this.needsDecode }); } ctx.putImageData(imgData, 0, 0); const bitmap = canvas.transferToImageBitmap(); return { data: null, width, height, bitmap, interpolate: this.interpolate }; } getImageBytes(length, { drawWidth, drawHeight, forceRGBA = false, forceRGB = false, internal = false }) { this.image.reset(); this.image.drawWidth = drawWidth || this.width; this.image.drawHeight = drawHeight || this.height; this.image.forceRGBA = !!forceRGBA; this.image.forceRGB = !!forceRGB; const imageBytes = this.image.getBytes(length); if (internal || this.image instanceof DecodeStream) { return imageBytes; } assert(imageBytes instanceof Uint8Array, 'PDFImage.getImageBytes: Unsupported "imageBytes" type.'); return new Uint8Array(imageBytes); } } ;// CONCATENATED MODULE: ./src/core/evaluator.js const DefaultPartialEvaluatorOptions = Object.freeze({ maxImageSize: -1, disableFontFace: false, ignoreErrors: false, isEvalSupported: true, isOffscreenCanvasSupported: false, canvasMaxAreaInBytes: -1, fontExtraProperties: false, useSystemFonts: true, cMapUrl: null, standardFontDataUrl: null }); const PatternType = { TILING: 1, SHADING: 2 }; const TEXT_CHUNK_BATCH_SIZE = 10; const deferred = Promise.resolve(); function normalizeBlendMode(value, parsingArray = false) { if (Array.isArray(value)) { for (const val of value) { const maybeBM = normalizeBlendMode(val, true); if (maybeBM) { return maybeBM; } } warn(`Unsupported blend mode Array: ${value}`); return "source-over"; } if (!(value instanceof Name)) { if (parsingArray) { return null; } return "source-over"; } switch (value.name) { case "Normal": case "Compatible": return "source-over"; case "Multiply": return "multiply"; case "Screen": return "screen"; case "Overlay": return "overlay"; case "Darken": return "darken"; case "Lighten": return "lighten"; case "ColorDodge": return "color-dodge"; case "ColorBurn": return "color-burn"; case "HardLight": return "hard-light"; case "SoftLight": return "soft-light"; case "Difference": return "difference"; case "Exclusion": return "exclusion"; case "Hue": return "hue"; case "Saturation": return "saturation"; case "Color": return "color"; case "Luminosity": return "luminosity"; } if (parsingArray) { return null; } warn(`Unsupported blend mode: ${value.name}`); return "source-over"; } function incrementCachedImageMaskCount(data) { if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) { data.args[0].count++; } } class TimeSlotManager { static TIME_SLOT_DURATION_MS = 20; static CHECK_TIME_EVERY = 100; constructor() { this.reset(); } check() { if (++this.checked < TimeSlotManager.CHECK_TIME_EVERY) { return false; } this.checked = 0; return this.endTime <= Date.now(); } reset() { this.endTime = Date.now() + TimeSlotManager.TIME_SLOT_DURATION_MS; this.checked = 0; } } class PartialEvaluator { constructor({ xref, handler, pageIndex, idFactory, fontCache, builtInCMapCache, standardFontDataCache, globalImageCache, systemFontCache, options = null }) { this.xref = xref; this.handler = handler; this.pageIndex = pageIndex; this.idFactory = idFactory; this.fontCache = fontCache; this.builtInCMapCache = builtInCMapCache; this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; this.systemFontCache = systemFontCache; this.options = options || DefaultPartialEvaluatorOptions; this.parsingType3Font = false; this._regionalImageCache = new RegionalImageCache(); this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this); ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes); } get _pdfFunctionFactory() { const pdfFunctionFactory = new PDFFunctionFactory({ xref: this.xref, isEvalSupported: this.options.isEvalSupported }); return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory); } clone(newOptions = null) { const newEvaluator = Object.create(this); newEvaluator.options = Object.assign(Object.create(null), this.options, newOptions); return newEvaluator; } hasBlendModes(resources, nonBlendModesSet) { if (!(resources instanceof Dict)) { return false; } if (resources.objId && nonBlendModesSet.has(resources.objId)) { return false; } const processed = new RefSet(nonBlendModesSet); if (resources.objId) { processed.put(resources.objId); } const nodes = [resources], xref = this.xref; while (nodes.length) { const node = nodes.shift(); const graphicStates = node.get("ExtGState"); if (graphicStates instanceof Dict) { for (let graphicState of graphicStates.getRawValues()) { if (graphicState instanceof Ref) { if (processed.has(graphicState)) { continue; } try { graphicState = xref.fetch(graphicState); } catch (ex) { processed.put(graphicState); info(`hasBlendModes - ignoring ExtGState: "${ex}".`); continue; } } if (!(graphicState instanceof Dict)) { continue; } if (graphicState.objId) { processed.put(graphicState.objId); } const bm = graphicState.get("BM"); if (bm instanceof Name) { if (bm.name !== "Normal") { return true; } continue; } if (bm !== undefined && Array.isArray(bm)) { for (const element of bm) { if (element instanceof Name && element.name !== "Normal") { return true; } } } } } const xObjects = node.get("XObject"); if (!(xObjects instanceof Dict)) { continue; } for (let xObject of xObjects.getRawValues()) { if (xObject instanceof Ref) { if (processed.has(xObject)) { continue; } try { xObject = xref.fetch(xObject); } catch (ex) { processed.put(xObject); info(`hasBlendModes - ignoring XObject: "${ex}".`); continue; } } if (!(xObject instanceof BaseStream)) { continue; } if (xObject.dict.objId) { processed.put(xObject.dict.objId); } const xResources = xObject.dict.get("Resources"); if (!(xResources instanceof Dict)) { continue; } if (xResources.objId && processed.has(xResources.objId)) { continue; } nodes.push(xResources); if (xResources.objId) { processed.put(xResources.objId); } } } for (const ref of processed) { nonBlendModesSet.put(ref); } return false; } async fetchBuiltInCMap(name) { const cachedData = this.builtInCMapCache.get(name); if (cachedData) { return cachedData; } let data; if (this.options.cMapUrl !== null) { const url = `${this.options.cMapUrl}${name}.bcmap`; const response = await fetch(url); if (!response.ok) { throw new Error(`fetchBuiltInCMap: failed to fetch file "${url}" with "${response.statusText}".`); } data = { cMapData: new Uint8Array(await response.arrayBuffer()), compressionType: CMapCompressionType.BINARY }; } else { data = await this.handler.sendWithPromise("FetchBuiltInCMap", { name }); } if (data.compressionType !== CMapCompressionType.NONE) { this.builtInCMapCache.set(name, data); } return data; } async fetchStandardFontData(name) { const cachedData = this.standardFontDataCache.get(name); if (cachedData) { return new Stream(cachedData); } if (this.options.useSystemFonts && name !== "Symbol" && name !== "ZapfDingbats") { return null; } const standardFontNameToFileName = getFontNameToFileMap(), filename = standardFontNameToFileName[name]; let data; if (this.options.standardFontDataUrl !== null) { const url = `${this.options.standardFontDataUrl}${filename}`; const response = await fetch(url); if (!response.ok) { warn(`fetchStandardFontData: failed to fetch file "${url}" with "${response.statusText}".`); } else { data = new Uint8Array(await response.arrayBuffer()); } } else { try { data = await this.handler.sendWithPromise("FetchStandardFontData", { filename }); } catch (e) { warn(`fetchStandardFontData: failed to fetch file "${filename}" with "${e}".`); } } if (!data) { return null; } this.standardFontDataCache.set(name, data); return new Stream(data); } async buildFormXObject(resources, xobj, smask, operatorList, task, initialState, localColorSpaceCache) { const dict = xobj.dict; const matrix = dict.getArray("Matrix"); let bbox = dict.getArray("BBox"); bbox = Array.isArray(bbox) && bbox.length === 4 ? Util.normalizeRect(bbox) : null; let optionalContent, groupOptions; if (dict.has("OC")) { optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources); } if (optionalContent !== undefined) { operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); } const group = dict.get("Group"); if (group) { groupOptions = { matrix, bbox, smask, isolated: false, knockout: false }; const groupSubtype = group.get("S"); let colorSpace = null; if (isName(groupSubtype, "Transparency")) { groupOptions.isolated = group.get("I") || false; groupOptions.knockout = group.get("K") || false; if (group.has("CS")) { const cs = group.getRaw("CS"); const cachedColorSpace = ColorSpace.getCached(cs, this.xref, localColorSpaceCache); if (cachedColorSpace) { colorSpace = cachedColorSpace; } else { colorSpace = await this.parseColorSpace({ cs, resources, localColorSpaceCache }); } } } if (smask?.backdrop) { colorSpace ||= ColorSpace.singletons.rgb; smask.backdrop = colorSpace.getRgb(smask.backdrop, 0); } operatorList.addOp(OPS.beginGroup, [groupOptions]); } const args = group ? [matrix, null] : [matrix, bbox]; operatorList.addOp(OPS.paintFormXObjectBegin, args); return this.getOperatorList({ stream: xobj, task, resources: dict.get("Resources") || resources, operatorList, initialState }).then(function () { operatorList.addOp(OPS.paintFormXObjectEnd, []); if (group) { operatorList.addOp(OPS.endGroup, [groupOptions]); } if (optionalContent !== undefined) { operatorList.addOp(OPS.endMarkedContent, []); } }); } _sendImgData(objId, imgData, cacheGlobally = false) { const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null; if (this.parsingType3Font || cacheGlobally) { return this.handler.send("commonobj", [objId, "Image", imgData], transfers); } return this.handler.send("obj", [objId, this.pageIndex, "Image", imgData], transfers); } async buildPaintImageXObject({ resources, image, isInline = false, operatorList, cacheKey, localImageCache, localColorSpaceCache }) { const dict = image.dict; const imageRef = dict.objId; const w = dict.get("W", "Width"); const h = dict.get("H", "Height"); if (!(w && typeof w === "number") || !(h && typeof h === "number")) { warn("Image dimensions are missing, or not numbers."); return; } const maxImageSize = this.options.maxImageSize; if (maxImageSize !== -1 && w * h > maxImageSize) { const msg = "Image exceeded maximum allowed size and was removed."; if (this.options.ignoreErrors) { warn(msg); return; } throw new Error(msg); } let optionalContent; if (dict.has("OC")) { optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources); } const imageMask = dict.get("IM", "ImageMask") || false; let imgData, args; if (imageMask) { const interpolate = dict.get("I", "Interpolate"); const bitStrideLength = w + 7 >> 3; const imgArray = image.getBytes(bitStrideLength * h); const decode = dict.getArray("D", "Decode"); if (this.parsingType3Font) { imgData = PDFImage.createRawMask({ imgArray, width: w, height: h, imageIsFromDecodeStream: image instanceof DecodeStream, inverseDecode: decode?.[0] > 0, interpolate }); imgData.cached = !!cacheKey; args = [imgData]; operatorList.addImageOps(OPS.paintImageMaskXObject, args, optionalContent); if (cacheKey) { const cacheData = { fn: OPS.paintImageMaskXObject, args, optionalContent }; localImageCache.set(cacheKey, imageRef, cacheData); if (imageRef) { this._regionalImageCache.set(null, imageRef, cacheData); } } return; } imgData = await PDFImage.createMask({ imgArray, width: w, height: h, imageIsFromDecodeStream: image instanceof DecodeStream, inverseDecode: decode?.[0] > 0, interpolate, isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported }); if (imgData.isSingleOpaquePixel) { operatorList.addImageOps(OPS.paintSolidColorImageMask, [], optionalContent); if (cacheKey) { const cacheData = { fn: OPS.paintSolidColorImageMask, args: [], optionalContent }; localImageCache.set(cacheKey, imageRef, cacheData); if (imageRef) { this._regionalImageCache.set(null, imageRef, cacheData); } } return; } const objId = `mask_${this.idFactory.createObjId()}`; operatorList.addDependency(objId); this._sendImgData(objId, imgData); args = [{ data: objId, width: imgData.width, height: imgData.height, interpolate: imgData.interpolate, count: 1 }]; operatorList.addImageOps(OPS.paintImageMaskXObject, args, optionalContent); if (cacheKey) { const cacheData = { fn: OPS.paintImageMaskXObject, args, optionalContent }; localImageCache.set(cacheKey, imageRef, cacheData); if (imageRef) { this._regionalImageCache.set(null, imageRef, cacheData); } } return; } const SMALL_IMAGE_DIMENSIONS = 200; if (isInline && !dict.has("SMask") && !dict.has("Mask") && w + h < SMALL_IMAGE_DIMENSIONS) { const imageObj = new PDFImage({ xref: this.xref, res: resources, image, isInline, pdfFunctionFactory: this._pdfFunctionFactory, localColorSpaceCache }); imgData = await imageObj.createImageData(true, false); operatorList.isOffscreenCanvasSupported = this.options.isOffscreenCanvasSupported; operatorList.addImageOps(OPS.paintInlineImageXObject, [imgData], optionalContent); return; } let objId = `img_${this.idFactory.createObjId()}`, cacheGlobally = false; if (this.parsingType3Font) { objId = `${this.idFactory.getDocId()}_type3_${objId}`; } else if (imageRef) { cacheGlobally = this.globalImageCache.shouldCache(imageRef, this.pageIndex); if (cacheGlobally) { objId = `${this.idFactory.getDocId()}_${objId}`; } } operatorList.addDependency(objId); args = [objId, w, h]; PDFImage.buildImage({ xref: this.xref, res: resources, image, isInline, pdfFunctionFactory: this._pdfFunctionFactory, localColorSpaceCache }).then(async imageObj => { imgData = await imageObj.createImageData(false, this.options.isOffscreenCanvasSupported); if (cacheKey && imageRef && cacheGlobally) { const length = imgData.bitmap ? imgData.width * imgData.height * 4 : imgData.data.length; this.globalImageCache.addByteSize(imageRef, length); } return this._sendImgData(objId, imgData, cacheGlobally); }).catch(reason => { warn(`Unable to decode image "${objId}": "${reason}".`); return this._sendImgData(objId, null, cacheGlobally); }); operatorList.addImageOps(OPS.paintImageXObject, args, optionalContent); if (cacheKey) { const cacheData = { fn: OPS.paintImageXObject, args, optionalContent }; localImageCache.set(cacheKey, imageRef, cacheData); if (imageRef) { this._regionalImageCache.set(null, imageRef, cacheData); if (cacheGlobally) { assert(!isInline, "Cannot cache an inline image globally."); this.globalImageCache.setData(imageRef, { objId, fn: OPS.paintImageXObject, args, optionalContent, byteSize: 0 }); } } } } handleSMask(smask, resources, operatorList, task, stateManager, localColorSpaceCache) { const smaskContent = smask.get("G"); const smaskOptions = { subtype: smask.get("S").name, backdrop: smask.get("BC") }; const transferObj = smask.get("TR"); if (isPDFFunction(transferObj)) { const transferFn = this._pdfFunctionFactory.create(transferObj); const transferMap = new Uint8Array(256); const tmp = new Float32Array(1); for (let i = 0; i < 256; i++) { tmp[0] = i / 255; transferFn(tmp, 0, tmp, 0); transferMap[i] = tmp[0] * 255 | 0; } smaskOptions.transferMap = transferMap; } return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone(), localColorSpaceCache); } handleTransferFunction(tr) { let transferArray; if (Array.isArray(tr)) { transferArray = tr; } else if (isPDFFunction(tr)) { transferArray = [tr]; } else { return null; } const transferMaps = []; let numFns = 0, numEffectfulFns = 0; for (const entry of transferArray) { const transferObj = this.xref.fetchIfRef(entry); numFns++; if (isName(transferObj, "Identity")) { transferMaps.push(null); continue; } else if (!isPDFFunction(transferObj)) { return null; } const transferFn = this._pdfFunctionFactory.create(transferObj); const transferMap = new Uint8Array(256), tmp = new Float32Array(1); for (let j = 0; j < 256; j++) { tmp[0] = j / 255; transferFn(tmp, 0, tmp, 0); transferMap[j] = tmp[0] * 255 | 0; } transferMaps.push(transferMap); numEffectfulFns++; } if (!(numFns === 1 || numFns === 4)) { return null; } if (numEffectfulFns === 0) { return null; } return transferMaps; } handleTilingType(fn, color, resources, pattern, patternDict, operatorList, task, localTilingPatternCache) { const tilingOpList = new OperatorList(); const patternResources = Dict.merge({ xref: this.xref, dictArray: [patternDict.get("Resources"), resources] }); return this.getOperatorList({ stream: pattern, task, resources: patternResources, operatorList: tilingOpList }).then(function () { const operatorListIR = tilingOpList.getIR(); const tilingPatternIR = getTilingPatternIR(operatorListIR, patternDict, color); operatorList.addDependencies(tilingOpList.dependencies); operatorList.addOp(fn, tilingPatternIR); if (patternDict.objId) { localTilingPatternCache.set(null, patternDict.objId, { operatorListIR, dict: patternDict }); } }).catch(reason => { if (reason instanceof AbortException) { return; } if (this.options.ignoreErrors) { warn(`handleTilingType - ignoring pattern: "${reason}".`); return; } throw reason; }); } handleSetFont(resources, fontArgs, fontRef, operatorList, task, state, fallbackFontDict = null, cssFontInfo = null) { const fontName = fontArgs?.[0] instanceof Name ? fontArgs[0].name : null; return this.loadFont(fontName, fontRef, resources, fallbackFontDict, cssFontInfo).then(translated => { if (!translated.font.isType3Font) { return translated; } return translated.loadType3Data(this, resources, task).then(function () { operatorList.addDependencies(translated.type3Dependencies); return translated; }).catch(reason => { return new TranslatedFont({ loadedName: "g_font_error", font: new ErrorFont(`Type3 font load error: ${reason}`), dict: translated.font, evaluatorOptions: this.options }); }); }).then(translated => { state.font = translated.font; translated.send(this.handler); return translated.loadedName; }); } handleText(chars, state) { const font = state.font; const glyphs = font.charsToGlyphs(chars); if (font.data) { const isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG); if (isAddToPathSet || state.fillColorSpace.name === "Pattern" || font.disableFontFace || this.options.disableFontFace) { PartialEvaluator.buildFontPaths(font, glyphs, this.handler, this.options); } } return glyphs; } ensureStateFont(state) { if (state.font) { return; } const reason = new FormatError("Missing setFont (Tf) operator before text rendering operator."); if (this.options.ignoreErrors) { warn(`ensureStateFont: "${reason}".`); return; } throw reason; } async setGState({ resources, gState, operatorList, cacheKey, task, stateManager, localGStateCache, localColorSpaceCache }) { const gStateRef = gState.objId; let isSimpleGState = true; const gStateObj = []; let promise = Promise.resolve(); for (const key of gState.getKeys()) { const value = gState.get(key); switch (key) { case "Type": break; case "LW": case "LC": case "LJ": case "ML": case "D": case "RI": case "FL": case "CA": case "ca": gStateObj.push([key, value]); break; case "Font": isSimpleGState = false; promise = promise.then(() => { return this.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) { operatorList.addDependency(loadedName); gStateObj.push([key, [loadedName, value[1]]]); }); }); break; case "BM": gStateObj.push([key, normalizeBlendMode(value)]); break; case "SMask": if (isName(value, "None")) { gStateObj.push([key, false]); break; } if (value instanceof Dict) { isSimpleGState = false; promise = promise.then(() => { return this.handleSMask(value, resources, operatorList, task, stateManager, localColorSpaceCache); }); gStateObj.push([key, true]); } else { warn("Unsupported SMask type"); } break; case "TR": const transferMaps = this.handleTransferFunction(value); gStateObj.push([key, transferMaps]); break; case "OP": case "op": case "OPM": case "BG": case "BG2": case "UCR": case "UCR2": case "TR2": case "HT": case "SM": case "SA": case "AIS": case "TK": info("graphic state operator " + key); break; default: info("Unknown graphic state operator " + key); break; } } return promise.then(function () { if (gStateObj.length > 0) { operatorList.addOp(OPS.setGState, [gStateObj]); } if (isSimpleGState) { localGStateCache.set(cacheKey, gStateRef, gStateObj); } }); } loadFont(fontName, font, resources, fallbackFontDict = null, cssFontInfo = null) { const errorFont = async () => { return new TranslatedFont({ loadedName: "g_font_error", font: new ErrorFont(`Font "${fontName}" is not available.`), dict: font, evaluatorOptions: this.options }); }; let fontRef; if (font) { if (font instanceof Ref) { fontRef = font; } } else { const fontRes = resources.get("Font"); if (fontRes) { fontRef = fontRes.getRaw(fontName); } } if (fontRef) { if (this.parsingType3Font && this.type3FontRefs.has(fontRef)) { return errorFont(); } if (this.fontCache.has(fontRef)) { return this.fontCache.get(fontRef); } font = this.xref.fetchIfRef(fontRef); } if (!(font instanceof Dict)) { if (!this.options.ignoreErrors && !this.parsingType3Font) { warn(`Font "${fontName}" is not available.`); return errorFont(); } warn(`Font "${fontName}" is not available -- attempting to fallback to a default font.`); font = fallbackFontDict || PartialEvaluator.fallbackFontDict; } if (font.cacheKey && this.fontCache.has(font.cacheKey)) { return this.fontCache.get(font.cacheKey); } const fontCapability = new PromiseCapability(); let preEvaluatedFont; try { preEvaluatedFont = this.preEvaluateFont(font); preEvaluatedFont.cssFontInfo = cssFontInfo; } catch (reason) { warn(`loadFont - preEvaluateFont failed: "${reason}".`); return errorFont(); } const { descriptor, hash } = preEvaluatedFont; const fontRefIsRef = fontRef instanceof Ref; let fontID; if (hash && descriptor instanceof Dict) { const fontAliases = descriptor.fontAliases ||= Object.create(null); if (fontAliases[hash]) { const aliasFontRef = fontAliases[hash].aliasRef; if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) { this.fontCache.putAlias(fontRef, aliasFontRef); return this.fontCache.get(fontRef); } } else { fontAliases[hash] = { fontID: this.idFactory.createFontId() }; } if (fontRefIsRef) { fontAliases[hash].aliasRef = fontRef; } fontID = fontAliases[hash].fontID; } else { fontID = this.idFactory.createFontId(); } assert(fontID?.startsWith("f"), 'The "fontID" must be (correctly) defined.'); if (fontRefIsRef) { this.fontCache.put(fontRef, fontCapability.promise); } else { font.cacheKey = `cacheKey_${fontID}`; this.fontCache.put(font.cacheKey, fontCapability.promise); } font.loadedName = `${this.idFactory.getDocId()}_${fontID}`; this.translateFont(preEvaluatedFont).then(translatedFont => { fontCapability.resolve(new TranslatedFont({ loadedName: font.loadedName, font: translatedFont, dict: font, evaluatorOptions: this.options })); }).catch(reason => { warn(`loadFont - translateFont failed: "${reason}".`); fontCapability.resolve(new TranslatedFont({ loadedName: font.loadedName, font: new ErrorFont(reason instanceof Error ? reason.message : reason), dict: font, evaluatorOptions: this.options })); }); return fontCapability.promise; } buildPath(operatorList, fn, args, parsingText = false) { const lastIndex = operatorList.length - 1; if (!args) { args = []; } if (lastIndex < 0 || operatorList.fnArray[lastIndex] !== OPS.constructPath) { if (parsingText) { warn(`Encountered path operator "${fn}" inside of a text object.`); operatorList.addOp(OPS.save, null); } let minMax; switch (fn) { case OPS.rectangle: const x = args[0] + args[2]; const y = args[1] + args[3]; minMax = [Math.min(args[0], x), Math.max(args[0], x), Math.min(args[1], y), Math.max(args[1], y)]; break; case OPS.moveTo: case OPS.lineTo: minMax = [args[0], args[0], args[1], args[1]]; break; default: minMax = [Infinity, -Infinity, Infinity, -Infinity]; break; } operatorList.addOp(OPS.constructPath, [[fn], args, minMax]); if (parsingText) { operatorList.addOp(OPS.restore, null); } } else { const opArgs = operatorList.argsArray[lastIndex]; opArgs[0].push(fn); opArgs[1].push(...args); const minMax = opArgs[2]; switch (fn) { case OPS.rectangle: const x = args[0] + args[2]; const y = args[1] + args[3]; minMax[0] = Math.min(minMax[0], args[0], x); minMax[1] = Math.max(minMax[1], args[0], x); minMax[2] = Math.min(minMax[2], args[1], y); minMax[3] = Math.max(minMax[3], args[1], y); break; case OPS.moveTo: case OPS.lineTo: minMax[0] = Math.min(minMax[0], args[0]); minMax[1] = Math.max(minMax[1], args[0]); minMax[2] = Math.min(minMax[2], args[1]); minMax[3] = Math.max(minMax[3], args[1]); break; } } } parseColorSpace({ cs, resources, localColorSpaceCache }) { return ColorSpace.parseAsync({ cs, xref: this.xref, resources, pdfFunctionFactory: this._pdfFunctionFactory, localColorSpaceCache }).catch(reason => { if (reason instanceof AbortException) { return null; } if (this.options.ignoreErrors) { warn(`parseColorSpace - ignoring ColorSpace: "${reason}".`); return null; } throw reason; }); } parseShading({ shading, resources, localColorSpaceCache, localShadingPatternCache }) { let id = localShadingPatternCache.get(shading); if (!id) { var shadingFill = Pattern.parseShading(shading, this.xref, resources, this._pdfFunctionFactory, localColorSpaceCache); const patternIR = shadingFill.getIR(); id = `pattern_${this.idFactory.createObjId()}`; if (this.parsingType3Font) { id = `${this.idFactory.getDocId()}_type3_${id}`; } localShadingPatternCache.set(shading, id); if (this.parsingType3Font) { this.handler.send("commonobj", [id, "Pattern", patternIR]); } else { this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]); } } return id; } handleColorN(operatorList, fn, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache) { const patternName = args.pop(); if (patternName instanceof Name) { const rawPattern = patterns.getRaw(patternName.name); const localTilingPattern = rawPattern instanceof Ref && localTilingPatternCache.getByRef(rawPattern); if (localTilingPattern) { try { const color = cs.base ? cs.base.getRgb(args, 0) : null; const tilingPatternIR = getTilingPatternIR(localTilingPattern.operatorListIR, localTilingPattern.dict, color); operatorList.addOp(fn, tilingPatternIR); return undefined; } catch {} } const pattern = this.xref.fetchIfRef(rawPattern); if (pattern) { const dict = pattern instanceof BaseStream ? pattern.dict : pattern; const typeNum = dict.get("PatternType"); if (typeNum === PatternType.TILING) { const color = cs.base ? cs.base.getRgb(args, 0) : null; return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task, localTilingPatternCache); } else if (typeNum === PatternType.SHADING) { const shading = dict.get("Shading"); const matrix = dict.getArray("Matrix"); const objId = this.parseShading({ shading, resources, localColorSpaceCache, localShadingPatternCache }); operatorList.addOp(fn, ["Shading", objId, matrix]); return undefined; } throw new FormatError(`Unknown PatternType: ${typeNum}`); } } throw new FormatError(`Unknown PatternName: ${patternName}`); } _parseVisibilityExpression(array, nestingCounter, currentResult) { const MAX_NESTING = 10; if (++nestingCounter > MAX_NESTING) { warn("Visibility expression is too deeply nested"); return; } const length = array.length; const operator = this.xref.fetchIfRef(array[0]); if (length < 2 || !(operator instanceof Name)) { warn("Invalid visibility expression"); return; } switch (operator.name) { case "And": case "Or": case "Not": currentResult.push(operator.name); break; default: warn(`Invalid operator ${operator.name} in visibility expression`); return; } for (let i = 1; i < length; i++) { const raw = array[i]; const object = this.xref.fetchIfRef(raw); if (Array.isArray(object)) { const nestedResult = []; currentResult.push(nestedResult); this._parseVisibilityExpression(object, nestingCounter, nestedResult); } else if (raw instanceof Ref) { currentResult.push(raw.toString()); } } } async parseMarkedContentProps(contentProperties, resources) { let optionalContent; if (contentProperties instanceof Name) { const properties = resources.get("Properties"); optionalContent = properties.get(contentProperties.name); } else if (contentProperties instanceof Dict) { optionalContent = contentProperties; } else { throw new FormatError("Optional content properties malformed."); } const optionalContentType = optionalContent.get("Type")?.name; if (optionalContentType === "OCG") { return { type: optionalContentType, id: optionalContent.objId }; } else if (optionalContentType === "OCMD") { const expression = optionalContent.get("VE"); if (Array.isArray(expression)) { const result = []; this._parseVisibilityExpression(expression, 0, result); if (result.length > 0) { return { type: "OCMD", expression: result }; } } const optionalContentGroups = optionalContent.get("OCGs"); if (Array.isArray(optionalContentGroups) || optionalContentGroups instanceof Dict) { const groupIds = []; if (Array.isArray(optionalContentGroups)) { for (const ocg of optionalContentGroups) { groupIds.push(ocg.toString()); } } else { groupIds.push(optionalContentGroups.objId); } return { type: optionalContentType, ids: groupIds, policy: optionalContent.get("P") instanceof Name ? optionalContent.get("P").name : null, expression: null }; } else if (optionalContentGroups instanceof Ref) { return { type: optionalContentType, id: optionalContentGroups.toString() }; } } return null; } getOperatorList({ stream, task, resources, operatorList, initialState = null, fallbackFontDict = null }) { resources ||= Dict.empty; initialState ||= new EvalState(); if (!operatorList) { throw new Error('getOperatorList: missing "operatorList" parameter'); } const self = this; const xref = this.xref; let parsingText = false; const localImageCache = new LocalImageCache(); const localColorSpaceCache = new LocalColorSpaceCache(); const localGStateCache = new LocalGStateCache(); const localTilingPatternCache = new LocalTilingPatternCache(); const localShadingPatternCache = new Map(); const xobjs = resources.get("XObject") || Dict.empty; const patterns = resources.get("Pattern") || Dict.empty; const stateManager = new StateManager(initialState); const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); const timeSlotManager = new TimeSlotManager(); function closePendingRestoreOPS(argument) { for (let i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { operatorList.addOp(OPS.restore, []); } } return new Promise(function promiseBody(resolve, reject) { const next = function (promise) { Promise.all([promise, operatorList.ready]).then(function () { try { promiseBody(resolve, reject); } catch (ex) { reject(ex); } }, reject); }; task.ensureNotTerminated(); timeSlotManager.reset(); const operation = {}; let stop, i, ii, cs, name, isValidName; while (!(stop = timeSlotManager.check())) { operation.args = null; if (!preprocessor.read(operation)) { break; } let args = operation.args; let fn = operation.fn; switch (fn | 0) { case OPS.paintXObject: isValidName = args[0] instanceof Name; name = args[0].name; if (isValidName) { const localImage = localImageCache.getByName(name); if (localImage) { operatorList.addImageOps(localImage.fn, localImage.args, localImage.optionalContent); incrementCachedImageMaskCount(localImage); args = null; continue; } } next(new Promise(function (resolveXObject, rejectXObject) { if (!isValidName) { throw new FormatError("XObject must be referred to by name."); } let xobj = xobjs.getRaw(name); if (xobj instanceof Ref) { const localImage = localImageCache.getByRef(xobj) || self._regionalImageCache.getByRef(xobj); if (localImage) { operatorList.addImageOps(localImage.fn, localImage.args, localImage.optionalContent); incrementCachedImageMaskCount(localImage); resolveXObject(); return; } const globalImage = self.globalImageCache.getData(xobj, self.pageIndex); if (globalImage) { operatorList.addDependency(globalImage.objId); operatorList.addImageOps(globalImage.fn, globalImage.args, globalImage.optionalContent); resolveXObject(); return; } xobj = xref.fetch(xobj); } if (!(xobj instanceof BaseStream)) { throw new FormatError("XObject should be a stream"); } const type = xobj.dict.get("Subtype"); if (!(type instanceof Name)) { throw new FormatError("XObject should have a Name subtype"); } if (type.name === "Form") { stateManager.save(); self.buildFormXObject(resources, xobj, null, operatorList, task, stateManager.state.clone(), localColorSpaceCache).then(function () { stateManager.restore(); resolveXObject(); }, rejectXObject); return; } else if (type.name === "Image") { self.buildPaintImageXObject({ resources, image: xobj, operatorList, cacheKey: name, localImageCache, localColorSpaceCache }).then(resolveXObject, rejectXObject); return; } else if (type.name === "PS") { info("Ignored XObject subtype PS"); } else { throw new FormatError(`Unhandled XObject subtype ${type.name}`); } resolveXObject(); }).catch(function (reason) { if (reason instanceof AbortException) { return; } if (self.options.ignoreErrors) { warn(`getOperatorList - ignoring XObject: "${reason}".`); return; } throw reason; })); return; case OPS.setFont: var fontSize = args[1]; next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state, fallbackFontDict).then(function (loadedName) { operatorList.addDependency(loadedName); operatorList.addOp(OPS.setFont, [loadedName, fontSize]); })); return; case OPS.beginText: parsingText = true; break; case OPS.endText: parsingText = false; break; case OPS.endInlineImage: var cacheKey = args[0].cacheKey; if (cacheKey) { const localImage = localImageCache.getByName(cacheKey); if (localImage) { operatorList.addImageOps(localImage.fn, localImage.args, localImage.optionalContent); incrementCachedImageMaskCount(localImage); args = null; continue; } } next(self.buildPaintImageXObject({ resources, image: args[0], isInline: true, operatorList, cacheKey, localImageCache, localColorSpaceCache })); return; case OPS.showText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } args[0] = self.handleText(args[0], stateManager.state); break; case OPS.showSpacedText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } var combinedGlyphs = []; var state = stateManager.state; for (const arrItem of args[0]) { if (typeof arrItem === "string") { combinedGlyphs.push(...self.handleText(arrItem, state)); } else if (typeof arrItem === "number") { combinedGlyphs.push(arrItem); } } args[0] = combinedGlyphs; fn = OPS.showText; break; case OPS.nextLineShowText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } operatorList.addOp(OPS.nextLine); args[0] = self.handleText(args[0], stateManager.state); fn = OPS.showText; break; case OPS.nextLineSetSpacingShowText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } operatorList.addOp(OPS.nextLine); operatorList.addOp(OPS.setWordSpacing, [args.shift()]); operatorList.addOp(OPS.setCharSpacing, [args.shift()]); args[0] = self.handleText(args[0], stateManager.state); fn = OPS.showText; break; case OPS.setTextRenderingMode: stateManager.state.textRenderingMode = args[0]; break; case OPS.setFillColorSpace: { const cachedColorSpace = ColorSpace.getCached(args[0], xref, localColorSpaceCache); if (cachedColorSpace) { stateManager.state.fillColorSpace = cachedColorSpace; continue; } next(self.parseColorSpace({ cs: args[0], resources, localColorSpaceCache }).then(function (colorSpace) { if (colorSpace) { stateManager.state.fillColorSpace = colorSpace; } })); return; } case OPS.setStrokeColorSpace: { const cachedColorSpace = ColorSpace.getCached(args[0], xref, localColorSpaceCache); if (cachedColorSpace) { stateManager.state.strokeColorSpace = cachedColorSpace; continue; } next(self.parseColorSpace({ cs: args[0], resources, localColorSpaceCache }).then(function (colorSpace) { if (colorSpace) { stateManager.state.strokeColorSpace = colorSpace; } })); return; } case OPS.setFillColor: cs = stateManager.state.fillColorSpace; args = cs.getRgb(args, 0); fn = OPS.setFillRGBColor; break; case OPS.setStrokeColor: cs = stateManager.state.strokeColorSpace; args = cs.getRgb(args, 0); fn = OPS.setStrokeRGBColor; break; case OPS.setFillGray: stateManager.state.fillColorSpace = ColorSpace.singletons.gray; args = ColorSpace.singletons.gray.getRgb(args, 0); fn = OPS.setFillRGBColor; break; case OPS.setStrokeGray: stateManager.state.strokeColorSpace = ColorSpace.singletons.gray; args = ColorSpace.singletons.gray.getRgb(args, 0); fn = OPS.setStrokeRGBColor; break; case OPS.setFillCMYKColor: stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk; args = ColorSpace.singletons.cmyk.getRgb(args, 0); fn = OPS.setFillRGBColor; break; case OPS.setStrokeCMYKColor: stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk; args = ColorSpace.singletons.cmyk.getRgb(args, 0); fn = OPS.setStrokeRGBColor; break; case OPS.setFillRGBColor: stateManager.state.fillColorSpace = ColorSpace.singletons.rgb; args = ColorSpace.singletons.rgb.getRgb(args, 0); break; case OPS.setStrokeRGBColor: stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb; args = ColorSpace.singletons.rgb.getRgb(args, 0); break; case OPS.setFillColorN: cs = stateManager.state.fillColorSpace; if (cs.name === "Pattern") { next(self.handleColorN(operatorList, OPS.setFillColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache)); return; } args = cs.getRgb(args, 0); fn = OPS.setFillRGBColor; break; case OPS.setStrokeColorN: cs = stateManager.state.strokeColorSpace; if (cs.name === "Pattern") { next(self.handleColorN(operatorList, OPS.setStrokeColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache)); return; } args = cs.getRgb(args, 0); fn = OPS.setStrokeRGBColor; break; case OPS.shadingFill: var shadingRes = resources.get("Shading"); if (!shadingRes) { throw new FormatError("No shading resource found"); } var shading = shadingRes.get(args[0].name); if (!shading) { throw new FormatError("No shading object found"); } const patternId = self.parseShading({ shading, resources, localColorSpaceCache, localShadingPatternCache }); args = [patternId]; fn = OPS.shadingFill; break; case OPS.setGState: isValidName = args[0] instanceof Name; name = args[0].name; if (isValidName) { const localGStateObj = localGStateCache.getByName(name); if (localGStateObj) { if (localGStateObj.length > 0) { operatorList.addOp(OPS.setGState, [localGStateObj]); } args = null; continue; } } next(new Promise(function (resolveGState, rejectGState) { if (!isValidName) { throw new FormatError("GState must be referred to by name."); } const extGState = resources.get("ExtGState"); if (!(extGState instanceof Dict)) { throw new FormatError("ExtGState should be a dictionary."); } const gState = extGState.get(name); if (!(gState instanceof Dict)) { throw new FormatError("GState should be a dictionary."); } self.setGState({ resources, gState, operatorList, cacheKey: name, task, stateManager, localGStateCache, localColorSpaceCache }).then(resolveGState, rejectGState); }).catch(function (reason) { if (reason instanceof AbortException) { return; } if (self.options.ignoreErrors) { warn(`getOperatorList - ignoring ExtGState: "${reason}".`); return; } throw reason; })); return; case OPS.moveTo: case OPS.lineTo: case OPS.curveTo: case OPS.curveTo2: case OPS.curveTo3: case OPS.closePath: case OPS.rectangle: self.buildPath(operatorList, fn, args, parsingText); continue; case OPS.markPoint: case OPS.markPointProps: case OPS.beginCompat: case OPS.endCompat: continue; case OPS.beginMarkedContentProps: if (!(args[0] instanceof Name)) { warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`); continue; } if (args[0].name === "OC") { next(self.parseMarkedContentProps(args[1], resources).then(data => { operatorList.addOp(OPS.beginMarkedContentProps, ["OC", data]); }).catch(reason => { if (reason instanceof AbortException) { return; } if (self.options.ignoreErrors) { warn(`getOperatorList - ignoring beginMarkedContentProps: "${reason}".`); return; } throw reason; })); return; } args = [args[0].name, args[1] instanceof Dict ? args[1].get("MCID") : null]; break; case OPS.beginMarkedContent: case OPS.endMarkedContent: default: if (args !== null) { for (i = 0, ii = args.length; i < ii; i++) { if (args[i] instanceof Dict) { break; } } if (i < ii) { warn("getOperatorList - ignoring operator: " + fn); continue; } } } operatorList.addOp(fn, args); } if (stop) { next(deferred); return; } closePendingRestoreOPS(); resolve(); }).catch(reason => { if (reason instanceof AbortException) { return; } if (this.options.ignoreErrors) { warn(`getOperatorList - ignoring errors during "${task.name}" ` + `task: "${reason}".`); closePendingRestoreOPS(); return; } throw reason; }); } getTextContent({ stream, task, resources, stateManager = null, includeMarkedContent = false, sink, seenStyles = new Set(), viewBox, markedContentData = null, disableNormalization = false }) { resources ||= Dict.empty; stateManager ||= new StateManager(new TextState()); if (includeMarkedContent) { markedContentData ||= { level: 0 }; } const textContent = { items: [], styles: Object.create(null) }; const textContentItem = { initialized: false, str: [], totalWidth: 0, totalHeight: 0, width: 0, height: 0, vertical: false, prevTransform: null, textAdvanceScale: 0, spaceInFlowMin: 0, spaceInFlowMax: 0, trackingSpaceMin: Infinity, negativeSpaceMax: -Infinity, notASpace: -Infinity, transform: null, fontName: null, hasEOL: false }; const twoLastChars = [" ", " "]; let twoLastCharsPos = 0; function saveLastChar(char) { const nextPos = (twoLastCharsPos + 1) % 2; const ret = twoLastChars[twoLastCharsPos] !== " " && twoLastChars[nextPos] === " "; twoLastChars[twoLastCharsPos] = char; twoLastCharsPos = nextPos; return ret; } function shouldAddWhitepsace() { return twoLastChars[twoLastCharsPos] !== " " && twoLastChars[(twoLastCharsPos + 1) % 2] === " "; } function resetLastChars() { twoLastChars[0] = twoLastChars[1] = " "; twoLastCharsPos = 0; } const TRACKING_SPACE_FACTOR = 0.102; const NOT_A_SPACE_FACTOR = 0.03; const NEGATIVE_SPACE_FACTOR = -0.2; const SPACE_IN_FLOW_MIN_FACTOR = 0.102; const SPACE_IN_FLOW_MAX_FACTOR = 0.6; const VERTICAL_SHIFT_RATIO = 0.25; const self = this; const xref = this.xref; const showSpacedTextBuffer = []; let xobjs = null; const emptyXObjectCache = new LocalImageCache(); const emptyGStateCache = new LocalGStateCache(); const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); let textState; function pushWhitespace({ width = 0, height = 0, transform = textContentItem.prevTransform, fontName = textContentItem.fontName }) { textContent.items.push({ str: " ", dir: "ltr", width, height, transform, fontName, hasEOL: false }); } function getCurrentTextTransform() { const font = textState.font; const tsm = [textState.fontSize * textState.textHScale, 0, 0, textState.fontSize, 0, textState.textRise]; if (font.isType3Font && (textState.fontSize <= 1 || font.isCharBBox) && !isArrayEqual(textState.fontMatrix, FONT_IDENTITY_MATRIX)) { const glyphHeight = font.bbox[3] - font.bbox[1]; if (glyphHeight > 0) { tsm[3] *= glyphHeight * textState.fontMatrix[3]; } } return Util.transform(textState.ctm, Util.transform(textState.textMatrix, tsm)); } function ensureTextContentItem() { if (textContentItem.initialized) { return textContentItem; } const { font, loadedName } = textState; if (!seenStyles.has(loadedName)) { seenStyles.add(loadedName); textContent.styles[loadedName] = { fontFamily: font.fallbackName, ascent: font.ascent, descent: font.descent, vertical: font.vertical }; if (self.options.fontExtraProperties && font.systemFontInfo) { const style = textContent.styles[loadedName]; style.fontSubstitution = font.systemFontInfo.css; style.fontSubstitutionLoadedName = font.systemFontInfo.loadedName; } } textContentItem.fontName = loadedName; const trm = textContentItem.transform = getCurrentTextTransform(); if (!font.vertical) { textContentItem.width = textContentItem.totalWidth = 0; textContentItem.height = textContentItem.totalHeight = Math.hypot(trm[2], trm[3]); textContentItem.vertical = false; } else { textContentItem.width = textContentItem.totalWidth = Math.hypot(trm[0], trm[1]); textContentItem.height = textContentItem.totalHeight = 0; textContentItem.vertical = true; } const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]); const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]); textContentItem.textAdvanceScale = scaleCtmX * scaleLineX; const { fontSize } = textState; textContentItem.trackingSpaceMin = fontSize * TRACKING_SPACE_FACTOR; textContentItem.notASpace = fontSize * NOT_A_SPACE_FACTOR; textContentItem.negativeSpaceMax = fontSize * NEGATIVE_SPACE_FACTOR; textContentItem.spaceInFlowMin = fontSize * SPACE_IN_FLOW_MIN_FACTOR; textContentItem.spaceInFlowMax = fontSize * SPACE_IN_FLOW_MAX_FACTOR; textContentItem.hasEOL = false; textContentItem.initialized = true; return textContentItem; } function updateAdvanceScale() { if (!textContentItem.initialized) { return; } const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]); const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]); const scaleFactor = scaleCtmX * scaleLineX; if (scaleFactor === textContentItem.textAdvanceScale) { return; } if (!textContentItem.vertical) { textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale; textContentItem.width = 0; } else { textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale; textContentItem.height = 0; } textContentItem.textAdvanceScale = scaleFactor; } function runBidiTransform(textChunk) { let text = textChunk.str.join(""); if (!disableNormalization) { text = normalizeUnicode(text); } const bidiResult = bidi(text, -1, textChunk.vertical); return { str: bidiResult.str, dir: bidiResult.dir, width: Math.abs(textChunk.totalWidth), height: Math.abs(textChunk.totalHeight), transform: textChunk.transform, fontName: textChunk.fontName, hasEOL: textChunk.hasEOL }; } function handleSetFont(fontName, fontRef) { return self.loadFont(fontName, fontRef, resources).then(function (translated) { if (!translated.font.isType3Font) { return translated; } return translated.loadType3Data(self, resources, task).catch(function () {}).then(function () { return translated; }); }).then(function (translated) { textState.loadedName = translated.loadedName; textState.font = translated.font; textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX; }); } function applyInverseRotation(x, y, matrix) { const scale = Math.hypot(matrix[0], matrix[1]); return [(matrix[0] * x + matrix[1] * y) / scale, (matrix[2] * x + matrix[3] * y) / scale]; } function compareWithLastPosition(glyphWidth) { const currentTransform = getCurrentTextTransform(); let posX = currentTransform[4]; let posY = currentTransform[5]; if (textState.font?.vertical) { if (posX < viewBox[0] || posX > viewBox[2] || posY + glyphWidth < viewBox[1] || posY > viewBox[3]) { return false; } } else if (posX + glyphWidth < viewBox[0] || posX > viewBox[2] || posY < viewBox[1] || posY > viewBox[3]) { return false; } if (!textState.font || !textContentItem.prevTransform) { return true; } let lastPosX = textContentItem.prevTransform[4]; let lastPosY = textContentItem.prevTransform[5]; if (lastPosX === posX && lastPosY === posY) { return true; } let rotate = -1; if (currentTransform[0] && currentTransform[1] === 0 && currentTransform[2] === 0) { rotate = currentTransform[0] > 0 ? 0 : 180; } else if (currentTransform[1] && currentTransform[0] === 0 && currentTransform[3] === 0) { rotate = currentTransform[1] > 0 ? 90 : 270; } switch (rotate) { case 0: break; case 90: [posX, posY] = [posY, posX]; [lastPosX, lastPosY] = [lastPosY, lastPosX]; break; case 180: [posX, posY, lastPosX, lastPosY] = [-posX, -posY, -lastPosX, -lastPosY]; break; case 270: [posX, posY] = [-posY, -posX]; [lastPosX, lastPosY] = [-lastPosY, -lastPosX]; break; default: [posX, posY] = applyInverseRotation(posX, posY, currentTransform); [lastPosX, lastPosY] = applyInverseRotation(lastPosX, lastPosY, textContentItem.prevTransform); } if (textState.font.vertical) { const advanceY = (lastPosY - posY) / textContentItem.textAdvanceScale; const advanceX = posX - lastPosX; const textOrientation = Math.sign(textContentItem.height); if (advanceY < textOrientation * textContentItem.negativeSpaceMax) { if (Math.abs(advanceX) > 0.5 * textContentItem.width) { appendEOL(); return true; } resetLastChars(); flushTextContentItem(); return true; } if (Math.abs(advanceX) > textContentItem.width) { appendEOL(); return true; } if (advanceY <= textOrientation * textContentItem.notASpace) { resetLastChars(); } if (advanceY <= textOrientation * textContentItem.trackingSpaceMin) { if (shouldAddWhitepsace()) { resetLastChars(); flushTextContentItem(); pushWhitespace({ height: Math.abs(advanceY) }); } else { textContentItem.height += advanceY; } } else if (!addFakeSpaces(advanceY, textContentItem.prevTransform, textOrientation)) { if (textContentItem.str.length === 0) { resetLastChars(); pushWhitespace({ height: Math.abs(advanceY) }); } else { textContentItem.height += advanceY; } } if (Math.abs(advanceX) > textContentItem.width * VERTICAL_SHIFT_RATIO) { flushTextContentItem(); } return true; } const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale; const advanceY = posY - lastPosY; const textOrientation = Math.sign(textContentItem.width); if (advanceX < textOrientation * textContentItem.negativeSpaceMax) { if (Math.abs(advanceY) > 0.5 * textContentItem.height) { appendEOL(); return true; } resetLastChars(); flushTextContentItem(); return true; } if (Math.abs(advanceY) > textContentItem.height) { appendEOL(); return true; } if (advanceX <= textOrientation * textContentItem.notASpace) { resetLastChars(); } if (advanceX <= textOrientation * textContentItem.trackingSpaceMin) { if (shouldAddWhitepsace()) { resetLastChars(); flushTextContentItem(); pushWhitespace({ width: Math.abs(advanceX) }); } else { textContentItem.width += advanceX; } } else if (!addFakeSpaces(advanceX, textContentItem.prevTransform, textOrientation)) { if (textContentItem.str.length === 0) { resetLastChars(); pushWhitespace({ width: Math.abs(advanceX) }); } else { textContentItem.width += advanceX; } } if (Math.abs(advanceY) > textContentItem.height * VERTICAL_SHIFT_RATIO) { flushTextContentItem(); } return true; } function buildTextContentItem({ chars, extraSpacing }) { const font = textState.font; if (!chars) { const charSpacing = textState.charSpacing + extraSpacing; if (charSpacing) { if (!font.vertical) { textState.translateTextMatrix(charSpacing * textState.textHScale, 0); } else { textState.translateTextMatrix(0, -charSpacing); } } return; } const glyphs = font.charsToGlyphs(chars); const scale = textState.fontMatrix[0] * textState.fontSize; for (let i = 0, ii = glyphs.length; i < ii; i++) { const glyph = glyphs[i]; const { category } = glyph; if (category.isInvisibleFormatMark) { continue; } let charSpacing = textState.charSpacing + (i + 1 === ii ? extraSpacing : 0); let glyphWidth = glyph.width; if (font.vertical) { glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth; } let scaledDim = glyphWidth * scale; if (category.isWhitespace) { if (!font.vertical) { charSpacing += scaledDim + textState.wordSpacing; textState.translateTextMatrix(charSpacing * textState.textHScale, 0); } else { charSpacing += -scaledDim + textState.wordSpacing; textState.translateTextMatrix(0, -charSpacing); } saveLastChar(" "); continue; } if (!category.isZeroWidthDiacritic && !compareWithLastPosition(scaledDim)) { if (!font.vertical) { textState.translateTextMatrix(scaledDim * textState.textHScale, 0); } else { textState.translateTextMatrix(0, scaledDim); } continue; } const textChunk = ensureTextContentItem(); if (category.isZeroWidthDiacritic) { scaledDim = 0; } if (!font.vertical) { scaledDim *= textState.textHScale; textState.translateTextMatrix(scaledDim, 0); textChunk.width += scaledDim; } else { textState.translateTextMatrix(0, scaledDim); scaledDim = Math.abs(scaledDim); textChunk.height += scaledDim; } if (scaledDim) { textChunk.prevTransform = getCurrentTextTransform(); } const glyphUnicode = glyph.unicode; if (saveLastChar(glyphUnicode)) { textChunk.str.push(" "); } textChunk.str.push(glyphUnicode); if (charSpacing) { if (!font.vertical) { textState.translateTextMatrix(charSpacing * textState.textHScale, 0); } else { textState.translateTextMatrix(0, -charSpacing); } } } } function appendEOL() { resetLastChars(); if (textContentItem.initialized) { textContentItem.hasEOL = true; flushTextContentItem(); } else { textContent.items.push({ str: "", dir: "ltr", width: 0, height: 0, transform: getCurrentTextTransform(), fontName: textState.loadedName, hasEOL: true }); } } function addFakeSpaces(width, transf, textOrientation) { if (textOrientation * textContentItem.spaceInFlowMin <= width && width <= textOrientation * textContentItem.spaceInFlowMax) { if (textContentItem.initialized) { resetLastChars(); textContentItem.str.push(" "); } return false; } const fontName = textContentItem.fontName; let height = 0; if (textContentItem.vertical) { height = width; width = 0; } flushTextContentItem(); resetLastChars(); pushWhitespace({ width: Math.abs(width), height: Math.abs(height), transform: transf || getCurrentTextTransform(), fontName }); return true; } function flushTextContentItem() { if (!textContentItem.initialized || !textContentItem.str) { return; } if (!textContentItem.vertical) { textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale; } else { textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale; } textContent.items.push(runBidiTransform(textContentItem)); textContentItem.initialized = false; textContentItem.str.length = 0; } function enqueueChunk(batch = false) { const length = textContent.items.length; if (length === 0) { return; } if (batch && length < TEXT_CHUNK_BATCH_SIZE) { return; } sink.enqueue(textContent, length); textContent.items = []; textContent.styles = Object.create(null); } const timeSlotManager = new TimeSlotManager(); return new Promise(function promiseBody(resolve, reject) { const next = function (promise) { enqueueChunk(true); Promise.all([promise, sink.ready]).then(function () { try { promiseBody(resolve, reject); } catch (ex) { reject(ex); } }, reject); }; task.ensureNotTerminated(); timeSlotManager.reset(); const operation = {}; let stop, args = []; while (!(stop = timeSlotManager.check())) { args.length = 0; operation.args = args; if (!preprocessor.read(operation)) { break; } const previousState = textState; textState = stateManager.state; const fn = operation.fn; args = operation.args; switch (fn | 0) { case OPS.setFont: var fontNameArg = args[0].name, fontSizeArg = args[1]; if (textState.font && fontNameArg === textState.fontName && fontSizeArg === textState.fontSize) { break; } flushTextContentItem(); textState.fontName = fontNameArg; textState.fontSize = fontSizeArg; next(handleSetFont(fontNameArg, null)); return; case OPS.setTextRise: textState.textRise = args[0]; break; case OPS.setHScale: textState.textHScale = args[0] / 100; break; case OPS.setLeading: textState.leading = args[0]; break; case OPS.moveText: textState.translateTextLineMatrix(args[0], args[1]); textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.setLeadingMoveText: textState.leading = -args[1]; textState.translateTextLineMatrix(args[0], args[1]); textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.nextLine: textState.carriageReturn(); break; case OPS.setTextMatrix: textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]); textState.setTextLineMatrix(args[0], args[1], args[2], args[3], args[4], args[5]); updateAdvanceScale(); break; case OPS.setCharSpacing: textState.charSpacing = args[0]; break; case OPS.setWordSpacing: textState.wordSpacing = args[0]; break; case OPS.beginText: textState.textMatrix = IDENTITY_MATRIX.slice(); textState.textLineMatrix = IDENTITY_MATRIX.slice(); break; case OPS.showSpacedText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } const spaceFactor = (textState.font.vertical ? 1 : -1) * textState.fontSize / 1000; const elements = args[0]; for (let i = 0, ii = elements.length; i < ii; i++) { const item = elements[i]; if (typeof item === "string") { showSpacedTextBuffer.push(item); } else if (typeof item === "number" && item !== 0) { const str = showSpacedTextBuffer.join(""); showSpacedTextBuffer.length = 0; buildTextContentItem({ chars: str, extraSpacing: item * spaceFactor }); } } if (showSpacedTextBuffer.length > 0) { const str = showSpacedTextBuffer.join(""); showSpacedTextBuffer.length = 0; buildTextContentItem({ chars: str, extraSpacing: 0 }); } break; case OPS.showText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } buildTextContentItem({ chars: args[0], extraSpacing: 0 }); break; case OPS.nextLineShowText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } textState.carriageReturn(); buildTextContentItem({ chars: args[0], extraSpacing: 0 }); break; case OPS.nextLineSetSpacingShowText: if (!stateManager.state.font) { self.ensureStateFont(stateManager.state); continue; } textState.wordSpacing = args[0]; textState.charSpacing = args[1]; textState.carriageReturn(); buildTextContentItem({ chars: args[2], extraSpacing: 0 }); break; case OPS.paintXObject: flushTextContentItem(); if (!xobjs) { xobjs = resources.get("XObject") || Dict.empty; } var isValidName = args[0] instanceof Name; var name = args[0].name; if (isValidName && emptyXObjectCache.getByName(name)) { break; } next(new Promise(function (resolveXObject, rejectXObject) { if (!isValidName) { throw new FormatError("XObject must be referred to by name."); } let xobj = xobjs.getRaw(name); if (xobj instanceof Ref) { if (emptyXObjectCache.getByRef(xobj)) { resolveXObject(); return; } const globalImage = self.globalImageCache.getData(xobj, self.pageIndex); if (globalImage) { resolveXObject(); return; } xobj = xref.fetch(xobj); } if (!(xobj instanceof BaseStream)) { throw new FormatError("XObject should be a stream"); } const type = xobj.dict.get("Subtype"); if (!(type instanceof Name)) { throw new FormatError("XObject should have a Name subtype"); } if (type.name !== "Form") { emptyXObjectCache.set(name, xobj.dict.objId, true); resolveXObject(); return; } const currentState = stateManager.state.clone(); const xObjStateManager = new StateManager(currentState); const matrix = xobj.dict.getArray("Matrix"); if (Array.isArray(matrix) && matrix.length === 6) { xObjStateManager.transform(matrix); } enqueueChunk(); const sinkWrapper = { enqueueInvoked: false, enqueue(chunk, size) { this.enqueueInvoked = true; sink.enqueue(chunk, size); }, get desiredSize() { return sink.desiredSize; }, get ready() { return sink.ready; } }; self.getTextContent({ stream: xobj, task, resources: xobj.dict.get("Resources") || resources, stateManager: xObjStateManager, includeMarkedContent, sink: sinkWrapper, seenStyles, viewBox, markedContentData, disableNormalization }).then(function () { if (!sinkWrapper.enqueueInvoked) { emptyXObjectCache.set(name, xobj.dict.objId, true); } resolveXObject(); }, rejectXObject); }).catch(function (reason) { if (reason instanceof AbortException) { return; } if (self.options.ignoreErrors) { warn(`getTextContent - ignoring XObject: "${reason}".`); return; } throw reason; })); return; case OPS.setGState: isValidName = args[0] instanceof Name; name = args[0].name; if (isValidName && emptyGStateCache.getByName(name)) { break; } next(new Promise(function (resolveGState, rejectGState) { if (!isValidName) { throw new FormatError("GState must be referred to by name."); } const extGState = resources.get("ExtGState"); if (!(extGState instanceof Dict)) { throw new FormatError("ExtGState should be a dictionary."); } const gState = extGState.get(name); if (!(gState instanceof Dict)) { throw new FormatError("GState should be a dictionary."); } const gStateFont = gState.get("Font"); if (!gStateFont) { emptyGStateCache.set(name, gState.objId, true); resolveGState(); return; } flushTextContentItem(); textState.fontName = null; textState.fontSize = gStateFont[1]; handleSetFont(null, gStateFont[0]).then(resolveGState, rejectGState); }).catch(function (reason) { if (reason instanceof AbortException) { return; } if (self.options.ignoreErrors) { warn(`getTextContent - ignoring ExtGState: "${reason}".`); return; } throw reason; })); return; case OPS.beginMarkedContent: flushTextContentItem(); if (includeMarkedContent) { markedContentData.level++; textContent.items.push({ type: "beginMarkedContent", tag: args[0] instanceof Name ? args[0].name : null }); } break; case OPS.beginMarkedContentProps: flushTextContentItem(); if (includeMarkedContent) { markedContentData.level++; let mcid = null; if (args[1] instanceof Dict) { mcid = args[1].get("MCID"); } textContent.items.push({ type: "beginMarkedContentProps", id: Number.isInteger(mcid) ? `${self.idFactory.getPageObjId()}_mc${mcid}` : null, tag: args[0] instanceof Name ? args[0].name : null }); } break; case OPS.endMarkedContent: flushTextContentItem(); if (includeMarkedContent) { if (markedContentData.level === 0) { break; } markedContentData.level--; textContent.items.push({ type: "endMarkedContent" }); } break; case OPS.restore: if (previousState && (previousState.font !== textState.font || previousState.fontSize !== textState.fontSize || previousState.fontName !== textState.fontName)) { flushTextContentItem(); } break; } if (textContent.items.length >= sink.desiredSize) { stop = true; break; } } if (stop) { next(deferred); return; } flushTextContentItem(); enqueueChunk(); resolve(); }).catch(reason => { if (reason instanceof AbortException) { return; } if (this.options.ignoreErrors) { warn(`getTextContent - ignoring errors during "${task.name}" ` + `task: "${reason}".`); flushTextContentItem(); enqueueChunk(); return; } throw reason; }); } extractDataStructures(dict, baseDict, properties) { const xref = this.xref; let cidToGidBytes; const toUnicodePromise = this.readToUnicode(properties.toUnicode || dict.get("ToUnicode") || baseDict.get("ToUnicode")); if (properties.composite) { const cidSystemInfo = dict.get("CIDSystemInfo"); if (cidSystemInfo instanceof Dict) { properties.cidSystemInfo = { registry: stringToPDFString(cidSystemInfo.get("Registry")), ordering: stringToPDFString(cidSystemInfo.get("Ordering")), supplement: cidSystemInfo.get("Supplement") }; } try { const cidToGidMap = dict.get("CIDToGIDMap"); if (cidToGidMap instanceof BaseStream) { cidToGidBytes = cidToGidMap.getBytes(); } } catch (ex) { if (!this.options.ignoreErrors) { throw ex; } warn(`extractDataStructures - ignoring CIDToGIDMap data: "${ex}".`); } } const differences = []; let baseEncodingName = null; let encoding; if (dict.has("Encoding")) { encoding = dict.get("Encoding"); if (encoding instanceof Dict) { baseEncodingName = encoding.get("BaseEncoding"); baseEncodingName = baseEncodingName instanceof Name ? baseEncodingName.name : null; if (encoding.has("Differences")) { const diffEncoding = encoding.get("Differences"); let index = 0; for (const entry of diffEncoding) { const data = xref.fetchIfRef(entry); if (typeof data === "number") { index = data; } else if (data instanceof Name) { differences[index++] = data.name; } else { throw new FormatError(`Invalid entry in 'Differences' array: ${data}`); } } } } else if (encoding instanceof Name) { baseEncodingName = encoding.name; } else { const msg = "Encoding is not a Name nor a Dict"; if (!this.options.ignoreErrors) { throw new FormatError(msg); } warn(msg); } if (baseEncodingName !== "MacRomanEncoding" && baseEncodingName !== "MacExpertEncoding" && baseEncodingName !== "WinAnsiEncoding") { baseEncodingName = null; } } const nonEmbeddedFont = !properties.file || properties.isInternalFont, isSymbolsFontName = getSymbolsFonts()[properties.name]; if (baseEncodingName && nonEmbeddedFont && isSymbolsFontName) { baseEncodingName = null; } if (baseEncodingName) { properties.defaultEncoding = getEncoding(baseEncodingName); } else { const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); const isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic); encoding = StandardEncoding; if (properties.type === "TrueType" && !isNonsymbolicFont) { encoding = WinAnsiEncoding; } if (isSymbolicFont || isSymbolsFontName) { encoding = MacRomanEncoding; if (nonEmbeddedFont) { if (/Symbol/i.test(properties.name)) { encoding = SymbolSetEncoding; } else if (/Dingbats/i.test(properties.name)) { encoding = ZapfDingbatsEncoding; } else if (/Wingdings/i.test(properties.name)) { encoding = WinAnsiEncoding; } } } properties.defaultEncoding = encoding; } properties.differences = differences; properties.baseEncodingName = baseEncodingName; properties.hasEncoding = !!baseEncodingName || differences.length > 0; properties.dict = dict; return toUnicodePromise.then(readToUnicode => { properties.toUnicode = readToUnicode; return this.buildToUnicode(properties); }).then(builtToUnicode => { properties.toUnicode = builtToUnicode; if (cidToGidBytes) { properties.cidToGidMap = this.readCidToGidMap(cidToGidBytes, builtToUnicode); } return properties; }); } _simpleFontToUnicode(properties, forceGlyphs = false) { assert(!properties.composite, "Must be a simple font."); const toUnicode = []; const encoding = properties.defaultEncoding.slice(); const baseEncodingName = properties.baseEncodingName; const differences = properties.differences; for (const charcode in differences) { const glyphName = differences[charcode]; if (glyphName === ".notdef") { continue; } encoding[charcode] = glyphName; } const glyphsUnicodeMap = getGlyphsUnicode(); for (const charcode in encoding) { let glyphName = encoding[charcode]; if (glyphName === "") { continue; } let unicode = glyphsUnicodeMap[glyphName]; if (unicode !== undefined) { toUnicode[charcode] = String.fromCharCode(unicode); continue; } let code = 0; switch (glyphName[0]) { case "G": if (glyphName.length === 3) { code = parseInt(glyphName.substring(1), 16); } break; case "g": if (glyphName.length === 5) { code = parseInt(glyphName.substring(1), 16); } break; case "C": case "c": if (glyphName.length >= 3 && glyphName.length <= 4) { const codeStr = glyphName.substring(1); if (forceGlyphs) { code = parseInt(codeStr, 16); break; } code = +codeStr; if (Number.isNaN(code) && Number.isInteger(parseInt(codeStr, 16))) { return this._simpleFontToUnicode(properties, true); } } break; case "u": unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); if (unicode !== -1) { code = unicode; } break; default: switch (glyphName) { case "f_h": case "f_t": case "T_h": toUnicode[charcode] = glyphName.replaceAll("_", ""); continue; } break; } if (code > 0 && code <= 0x10ffff && Number.isInteger(code)) { if (baseEncodingName && code === +charcode) { const baseEncoding = getEncoding(baseEncodingName); if (baseEncoding && (glyphName = baseEncoding[charcode])) { toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]); continue; } } toUnicode[charcode] = String.fromCodePoint(code); } } return toUnicode; } async buildToUnicode(properties) { properties.hasIncludedToUnicodeMap = properties.toUnicode?.length > 0; if (properties.hasIncludedToUnicodeMap) { if (!properties.composite && properties.hasEncoding) { properties.fallbackToUnicode = this._simpleFontToUnicode(properties); } return properties.toUnicode; } if (!properties.composite) { return new ToUnicodeMap(this._simpleFontToUnicode(properties)); } if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo.registry === "Adobe" && (properties.cidSystemInfo.ordering === "GB1" || properties.cidSystemInfo.ordering === "CNS1" || properties.cidSystemInfo.ordering === "Japan1" || properties.cidSystemInfo.ordering === "Korea1"))) { const { registry, ordering } = properties.cidSystemInfo; const ucs2CMapName = Name.get(`${registry}-${ordering}-UCS2`); const ucs2CMap = await CMapFactory.create({ encoding: ucs2CMapName, fetchBuiltInCMap: this._fetchBuiltInCMapBound, useCMap: null }); const toUnicode = [], buf = []; properties.cMap.forEach(function (charcode, cid) { if (cid > 0xffff) { throw new FormatError("Max size of CID is 65,535"); } const ucs2 = ucs2CMap.lookup(cid); if (ucs2) { buf.length = 0; for (let i = 0, ii = ucs2.length; i < ii; i += 2) { buf.push((ucs2.charCodeAt(i) << 8) + ucs2.charCodeAt(i + 1)); } toUnicode[charcode] = String.fromCharCode(...buf); } }); return new ToUnicodeMap(toUnicode); } return new IdentityToUnicodeMap(properties.firstChar, properties.lastChar); } readToUnicode(cmapObj) { if (!cmapObj) { return Promise.resolve(null); } if (cmapObj instanceof Name) { return CMapFactory.create({ encoding: cmapObj, fetchBuiltInCMap: this._fetchBuiltInCMapBound, useCMap: null }).then(function (cmap) { if (cmap instanceof IdentityCMap) { return new IdentityToUnicodeMap(0, 0xffff); } return new ToUnicodeMap(cmap.getMap()); }); } else if (cmapObj instanceof BaseStream) { return CMapFactory.create({ encoding: cmapObj, fetchBuiltInCMap: this._fetchBuiltInCMapBound, useCMap: null }).then(function (cmap) { if (cmap instanceof IdentityCMap) { return new IdentityToUnicodeMap(0, 0xffff); } const map = new Array(cmap.length); cmap.forEach(function (charCode, token) { if (typeof token === "number") { map[charCode] = String.fromCodePoint(token); return; } const str = []; for (let k = 0; k < token.length; k += 2) { const w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1); if ((w1 & 0xf800) !== 0xd800) { str.push(w1); continue; } k += 2; const w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1); str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000); } map[charCode] = String.fromCodePoint(...str); }); return new ToUnicodeMap(map); }, reason => { if (reason instanceof AbortException) { return null; } if (this.options.ignoreErrors) { warn(`readToUnicode - ignoring ToUnicode data: "${reason}".`); return null; } throw reason; }); } return Promise.resolve(null); } readCidToGidMap(glyphsData, toUnicode) { const result = []; for (let j = 0, jj = glyphsData.length; j < jj; j++) { const glyphID = glyphsData[j++] << 8 | glyphsData[j]; const code = j >> 1; if (glyphID === 0 && !toUnicode.has(code)) { continue; } result[code] = glyphID; } return result; } extractWidths(dict, descriptor, properties) { const xref = this.xref; let glyphsWidths = []; let defaultWidth = 0; const glyphsVMetrics = []; let defaultVMetrics; let i, ii, j, jj, start, code, widths; if (properties.composite) { defaultWidth = dict.has("DW") ? dict.get("DW") : 1000; widths = dict.get("W"); if (widths) { for (i = 0, ii = widths.length; i < ii; i++) { start = xref.fetchIfRef(widths[i++]); code = xref.fetchIfRef(widths[i]); if (Array.isArray(code)) { for (j = 0, jj = code.length; j < jj; j++) { glyphsWidths[start++] = xref.fetchIfRef(code[j]); } } else { const width = xref.fetchIfRef(widths[++i]); for (j = start; j <= code; j++) { glyphsWidths[j] = width; } } } } if (properties.vertical) { let vmetrics = dict.getArray("DW2") || [880, -1000]; defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]]; vmetrics = dict.get("W2"); if (vmetrics) { for (i = 0, ii = vmetrics.length; i < ii; i++) { start = xref.fetchIfRef(vmetrics[i++]); code = xref.fetchIfRef(vmetrics[i]); if (Array.isArray(code)) { for (j = 0, jj = code.length; j < jj; j++) { glyphsVMetrics[start++] = [xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j])]; } } else { const vmetric = [xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i])]; for (j = start; j <= code; j++) { glyphsVMetrics[j] = vmetric; } } } } } } else { const firstChar = properties.firstChar; widths = dict.get("Widths"); if (widths) { j = firstChar; for (i = 0, ii = widths.length; i < ii; i++) { glyphsWidths[j++] = xref.fetchIfRef(widths[i]); } defaultWidth = parseFloat(descriptor.get("MissingWidth")) || 0; } else { const baseFontName = dict.get("BaseFont"); if (baseFontName instanceof Name) { const metrics = this.getBaseFontMetrics(baseFontName.name); glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties); defaultWidth = metrics.defaultWidth; } } } let isMonospace = true; let firstWidth = defaultWidth; for (const glyph in glyphsWidths) { const glyphWidth = glyphsWidths[glyph]; if (!glyphWidth) { continue; } if (!firstWidth) { firstWidth = glyphWidth; continue; } if (firstWidth !== glyphWidth) { isMonospace = false; break; } } if (isMonospace) { properties.flags |= FontFlags.FixedPitch; } else { properties.flags &= ~FontFlags.FixedPitch; } properties.defaultWidth = defaultWidth; properties.widths = glyphsWidths; properties.defaultVMetrics = defaultVMetrics; properties.vmetrics = glyphsVMetrics; } isSerifFont(baseFontName) { const fontNameWoStyle = baseFontName.split("-")[0]; return fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle); } getBaseFontMetrics(name) { let defaultWidth = 0; let widths = Object.create(null); let monospace = false; const stdFontMap = getStdFontMap(); let lookupName = stdFontMap[name] || name; const Metrics = getMetrics(); if (!(lookupName in Metrics)) { lookupName = this.isSerifFont(name) ? "Times-Roman" : "Helvetica"; } const glyphWidths = Metrics[lookupName]; if (typeof glyphWidths === "number") { defaultWidth = glyphWidths; monospace = true; } else { widths = glyphWidths(); } return { defaultWidth, monospace, widths }; } buildCharCodeToWidth(widthsByGlyphName, properties) { const widths = Object.create(null); const differences = properties.differences; const encoding = properties.defaultEncoding; for (let charCode = 0; charCode < 256; charCode++) { if (charCode in differences && widthsByGlyphName[differences[charCode]]) { widths[charCode] = widthsByGlyphName[differences[charCode]]; continue; } if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) { widths[charCode] = widthsByGlyphName[encoding[charCode]]; continue; } } return widths; } preEvaluateFont(dict) { const baseDict = dict; let type = dict.get("Subtype"); if (!(type instanceof Name)) { throw new FormatError("invalid font Subtype"); } let composite = false; let hash, toUnicode; if (type.name === "Type0") { const df = dict.get("DescendantFonts"); if (!df) { throw new FormatError("Descendant fonts are not specified"); } dict = Array.isArray(df) ? this.xref.fetchIfRef(df[0]) : df; if (!(dict instanceof Dict)) { throw new FormatError("Descendant font is not a dictionary."); } type = dict.get("Subtype"); if (!(type instanceof Name)) { throw new FormatError("invalid font Subtype"); } composite = true; } const firstChar = dict.get("FirstChar") || 0, lastChar = dict.get("LastChar") || (composite ? 0xffff : 0xff); const descriptor = dict.get("FontDescriptor"); if (descriptor) { hash = new MurmurHash3_64(); const encoding = baseDict.getRaw("Encoding"); if (encoding instanceof Name) { hash.update(encoding.name); } else if (encoding instanceof Ref) { hash.update(encoding.toString()); } else if (encoding instanceof Dict) { for (const entry of encoding.getRawValues()) { if (entry instanceof Name) { hash.update(entry.name); } else if (entry instanceof Ref) { hash.update(entry.toString()); } else if (Array.isArray(entry)) { const diffLength = entry.length, diffBuf = new Array(diffLength); for (let j = 0; j < diffLength; j++) { const diffEntry = entry[j]; if (diffEntry instanceof Name) { diffBuf[j] = diffEntry.name; } else if (typeof diffEntry === "number" || diffEntry instanceof Ref) { diffBuf[j] = diffEntry.toString(); } } hash.update(diffBuf.join()); } } } hash.update(`${firstChar}-${lastChar}`); toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode"); if (toUnicode instanceof BaseStream) { const stream = toUnicode.str || toUnicode; const uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start); hash.update(uint8array); } else if (toUnicode instanceof Name) { hash.update(toUnicode.name); } const widths = dict.get("Widths") || baseDict.get("Widths"); if (Array.isArray(widths)) { const widthsBuf = []; for (const entry of widths) { if (typeof entry === "number" || entry instanceof Ref) { widthsBuf.push(entry.toString()); } } hash.update(widthsBuf.join()); } if (composite) { hash.update("compositeFont"); const compositeWidths = dict.get("W") || baseDict.get("W"); if (Array.isArray(compositeWidths)) { const widthsBuf = []; for (const entry of compositeWidths) { if (typeof entry === "number" || entry instanceof Ref) { widthsBuf.push(entry.toString()); } else if (Array.isArray(entry)) { const subWidthsBuf = []; for (const element of entry) { if (typeof element === "number" || element instanceof Ref) { subWidthsBuf.push(element.toString()); } } widthsBuf.push(`[${subWidthsBuf.join()}]`); } } hash.update(widthsBuf.join()); } const cidToGidMap = dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap"); if (cidToGidMap instanceof Name) { hash.update(cidToGidMap.name); } else if (cidToGidMap instanceof Ref) { hash.update(cidToGidMap.toString()); } else if (cidToGidMap instanceof BaseStream) { hash.update(cidToGidMap.peekBytes()); } } } return { descriptor, dict, baseDict, composite, type: type.name, firstChar, lastChar, toUnicode, hash: hash ? hash.hexdigest() : "" }; } async translateFont({ descriptor, dict, baseDict, composite, type, firstChar, lastChar, toUnicode, cssFontInfo }) { const isType3Font = type === "Type3"; let properties; if (!descriptor) { if (isType3Font) { descriptor = new Dict(null); descriptor.set("FontName", Name.get(type)); descriptor.set("FontBBox", dict.getArray("FontBBox") || [0, 0, 0, 0]); } else { let baseFontName = dict.get("BaseFont"); if (!(baseFontName instanceof Name)) { throw new FormatError("Base font is not specified"); } baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-"); const metrics = this.getBaseFontMetrics(baseFontName); const fontNameWoStyle = baseFontName.split("-")[0]; const flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic); properties = { type, name: baseFontName, loadedName: baseDict.loadedName, systemFontInfo: null, widths: metrics.widths, defaultWidth: metrics.defaultWidth, isSimulatedFlags: true, flags, firstChar, lastChar, toUnicode, xHeight: 0, capHeight: 0, italicAngle: 0, isType3Font }; const widths = dict.get("Widths"); const standardFontName = getStandardFontName(baseFontName); let file = null; if (standardFontName) { file = await this.fetchStandardFontData(standardFontName); properties.isInternalFont = !!file; } if (!properties.isInternalFont && this.options.useSystemFonts) { properties.systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, baseFontName, standardFontName); } return this.extractDataStructures(dict, dict, properties).then(newProperties => { if (widths) { const glyphWidths = []; let j = firstChar; for (const width of widths) { glyphWidths[j++] = this.xref.fetchIfRef(width); } newProperties.widths = glyphWidths; } else { newProperties.widths = this.buildCharCodeToWidth(metrics.widths, newProperties); } return new Font(baseFontName, file, newProperties); }); } } let fontName = descriptor.get("FontName"); let baseFont = dict.get("BaseFont"); if (typeof fontName === "string") { fontName = Name.get(fontName); } if (typeof baseFont === "string") { baseFont = Name.get(baseFont); } const fontNameStr = fontName?.name; const baseFontStr = baseFont?.name; if (!isType3Font && fontNameStr !== baseFontStr) { info(`The FontDescriptor's FontName is "${fontNameStr}" but ` + `should be the same as the Font's BaseFont "${baseFontStr}".`); if (fontNameStr && baseFontStr && (baseFontStr.startsWith(fontNameStr) || !isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr))) { fontName = null; } } fontName ||= baseFont; if (!(fontName instanceof Name)) { throw new FormatError("invalid font name"); } let fontFile, subtype, length1, length2, length3; try { fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3"); } catch (ex) { if (!this.options.ignoreErrors) { throw ex; } warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`); fontFile = new NullStream(); } let isInternalFont = false; let glyphScaleFactors = null; let systemFontInfo = null; if (fontFile) { if (fontFile.dict) { const subtypeEntry = fontFile.dict.get("Subtype"); if (subtypeEntry instanceof Name) { subtype = subtypeEntry.name; } length1 = fontFile.dict.get("Length1"); length2 = fontFile.dict.get("Length2"); length3 = fontFile.dict.get("Length3"); } } else if (cssFontInfo) { const standardFontName = getXfaFontName(fontName.name); if (standardFontName) { cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`; cssFontInfo.metrics = standardFontName.metrics || null; glyphScaleFactors = standardFontName.factors || null; fontFile = await this.fetchStandardFontData(standardFontName.name); isInternalFont = !!fontFile; baseDict = dict = getXfaFontDict(fontName.name); composite = true; } } else if (!isType3Font) { const standardFontName = getStandardFontName(fontName.name); if (standardFontName) { fontFile = await this.fetchStandardFontData(standardFontName); isInternalFont = !!fontFile; } if (!isInternalFont && this.options.useSystemFonts) { systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, fontName.name, standardFontName); } } properties = { type, name: fontName.name, subtype, file: fontFile, length1, length2, length3, isInternalFont, loadedName: baseDict.loadedName, composite, fixedPitch: false, fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX, firstChar, lastChar, toUnicode, bbox: descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), ascent: descriptor.get("Ascent"), descent: descriptor.get("Descent"), xHeight: descriptor.get("XHeight") || 0, capHeight: descriptor.get("CapHeight") || 0, flags: descriptor.get("Flags"), italicAngle: descriptor.get("ItalicAngle") || 0, isType3Font, cssFontInfo, scaleFactors: glyphScaleFactors, systemFontInfo }; if (composite) { const cidEncoding = baseDict.get("Encoding"); if (cidEncoding instanceof Name) { properties.cidEncoding = cidEncoding.name; } const cMap = await CMapFactory.create({ encoding: cidEncoding, fetchBuiltInCMap: this._fetchBuiltInCMapBound, useCMap: null }); properties.cMap = cMap; properties.vertical = properties.cMap.vertical; } return this.extractDataStructures(dict, baseDict, properties).then(newProperties => { this.extractWidths(dict, descriptor, newProperties); return new Font(fontName.name, fontFile, newProperties); }); } static buildFontPaths(font, glyphs, handler, evaluatorOptions) { function buildPath(fontChar) { const glyphName = `${font.loadedName}_path_${fontChar}`; try { if (font.renderer.hasBuiltPath(fontChar)) { return; } handler.send("commonobj", [glyphName, "FontPath", font.renderer.getPathJs(fontChar)]); } catch (reason) { if (evaluatorOptions.ignoreErrors) { warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`); return; } throw reason; } } for (const glyph of glyphs) { buildPath(glyph.fontChar); const accent = glyph.accent; if (accent?.fontChar) { buildPath(accent.fontChar); } } } static get fallbackFontDict() { const dict = new Dict(); dict.set("BaseFont", Name.get("Helvetica")); dict.set("Type", Name.get("FallbackType")); dict.set("Subtype", Name.get("FallbackType")); dict.set("Encoding", Name.get("WinAnsiEncoding")); return shadow(this, "fallbackFontDict", dict); } } class TranslatedFont { constructor({ loadedName, font, dict, evaluatorOptions }) { this.loadedName = loadedName; this.font = font; this.dict = dict; this._evaluatorOptions = evaluatorOptions || DefaultPartialEvaluatorOptions; this.type3Loaded = null; this.type3Dependencies = font.isType3Font ? new Set() : null; this.sent = false; } send(handler) { if (this.sent) { return; } this.sent = true; handler.send("commonobj", [this.loadedName, "Font", this.font.exportData(this._evaluatorOptions.fontExtraProperties)]); } fallback(handler) { if (!this.font.data) { return; } this.font.disableFontFace = true; PartialEvaluator.buildFontPaths(this.font, this.font.glyphCacheValues, handler, this._evaluatorOptions); } loadType3Data(evaluator, resources, task) { if (this.type3Loaded) { return this.type3Loaded; } if (!this.font.isType3Font) { throw new Error("Must be a Type3 font."); } const type3Evaluator = evaluator.clone({ ignoreErrors: false }); type3Evaluator.parsingType3Font = true; const type3FontRefs = new RefSet(evaluator.type3FontRefs); if (this.dict.objId && !type3FontRefs.has(this.dict.objId)) { type3FontRefs.put(this.dict.objId); } type3Evaluator.type3FontRefs = type3FontRefs; const translatedFont = this.font, type3Dependencies = this.type3Dependencies; let loadCharProcsPromise = Promise.resolve(); const charProcs = this.dict.get("CharProcs"); const fontResources = this.dict.get("Resources") || resources; const charProcOperatorList = Object.create(null); const fontBBox = Util.normalizeRect(translatedFont.bbox || [0, 0, 0, 0]), width = fontBBox[2] - fontBBox[0], height = fontBBox[3] - fontBBox[1]; const fontBBoxSize = Math.hypot(width, height); for (const key of charProcs.getKeys()) { loadCharProcsPromise = loadCharProcsPromise.then(() => { const glyphStream = charProcs.get(key); const operatorList = new OperatorList(); return type3Evaluator.getOperatorList({ stream: glyphStream, task, resources: fontResources, operatorList }).then(() => { if (operatorList.fnArray[0] === OPS.setCharWidthAndBounds) { this._removeType3ColorOperators(operatorList, fontBBoxSize); } charProcOperatorList[key] = operatorList.getIR(); for (const dependency of operatorList.dependencies) { type3Dependencies.add(dependency); } }).catch(function (reason) { warn(`Type3 font resource "${key}" is not available.`); const dummyOperatorList = new OperatorList(); charProcOperatorList[key] = dummyOperatorList.getIR(); }); }); } this.type3Loaded = loadCharProcsPromise.then(() => { translatedFont.charProcOperatorList = charProcOperatorList; if (this._bbox) { translatedFont.isCharBBox = true; translatedFont.bbox = this._bbox; } }); return this.type3Loaded; } _removeType3ColorOperators(operatorList, fontBBoxSize = NaN) { const charBBox = Util.normalizeRect(operatorList.argsArray[0].slice(2)), width = charBBox[2] - charBBox[0], height = charBBox[3] - charBBox[1]; const charBBoxSize = Math.hypot(width, height); if (width === 0 || height === 0) { operatorList.fnArray.splice(0, 1); operatorList.argsArray.splice(0, 1); } else if (fontBBoxSize === 0 || Math.round(charBBoxSize / fontBBoxSize) >= 10) { if (!this._bbox) { this._bbox = [Infinity, Infinity, -Infinity, -Infinity]; } this._bbox[0] = Math.min(this._bbox[0], charBBox[0]); this._bbox[1] = Math.min(this._bbox[1], charBBox[1]); this._bbox[2] = Math.max(this._bbox[2], charBBox[2]); this._bbox[3] = Math.max(this._bbox[3], charBBox[3]); } let i = 0, ii = operatorList.length; while (i < ii) { switch (operatorList.fnArray[i]) { case OPS.setCharWidthAndBounds: break; case OPS.setStrokeColorSpace: case OPS.setFillColorSpace: case OPS.setStrokeColor: case OPS.setStrokeColorN: case OPS.setFillColor: case OPS.setFillColorN: case OPS.setStrokeGray: case OPS.setFillGray: case OPS.setStrokeRGBColor: case OPS.setFillRGBColor: case OPS.setStrokeCMYKColor: case OPS.setFillCMYKColor: case OPS.shadingFill: case OPS.setRenderingIntent: operatorList.fnArray.splice(i, 1); operatorList.argsArray.splice(i, 1); ii--; continue; case OPS.setGState: const [gStateObj] = operatorList.argsArray[i]; let j = 0, jj = gStateObj.length; while (j < jj) { const [gStateKey] = gStateObj[j]; switch (gStateKey) { case "TR": case "TR2": case "HT": case "BG": case "BG2": case "UCR": case "UCR2": gStateObj.splice(j, 1); jj--; continue; } j++; } break; } i++; } } } class StateManager { constructor(initialState = new EvalState()) { this.state = initialState; this.stateStack = []; } save() { const old = this.state; this.stateStack.push(this.state); this.state = old.clone(); } restore() { const prev = this.stateStack.pop(); if (prev) { this.state = prev; } } transform(args) { this.state.ctm = Util.transform(this.state.ctm, args); } } class TextState { constructor() { this.ctm = new Float32Array(IDENTITY_MATRIX); this.fontName = null; this.fontSize = 0; this.loadedName = null; this.font = null; this.fontMatrix = FONT_IDENTITY_MATRIX; this.textMatrix = IDENTITY_MATRIX.slice(); this.textLineMatrix = IDENTITY_MATRIX.slice(); this.charSpacing = 0; this.wordSpacing = 0; this.leading = 0; this.textHScale = 1; this.textRise = 0; } setTextMatrix(a, b, c, d, e, f) { const m = this.textMatrix; m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f; } setTextLineMatrix(a, b, c, d, e, f) { const m = this.textLineMatrix; m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f; } translateTextMatrix(x, y) { const m = this.textMatrix; m[4] = m[0] * x + m[2] * y + m[4]; m[5] = m[1] * x + m[3] * y + m[5]; } translateTextLineMatrix(x, y) { const m = this.textLineMatrix; m[4] = m[0] * x + m[2] * y + m[4]; m[5] = m[1] * x + m[3] * y + m[5]; } carriageReturn() { this.translateTextLineMatrix(0, -this.leading); this.textMatrix = this.textLineMatrix.slice(); } clone() { const clone = Object.create(this); clone.textMatrix = this.textMatrix.slice(); clone.textLineMatrix = this.textLineMatrix.slice(); clone.fontMatrix = this.fontMatrix.slice(); return clone; } } class EvalState { constructor() { this.ctm = new Float32Array(IDENTITY_MATRIX); this.font = null; this.textRenderingMode = TextRenderingMode.FILL; this.fillColorSpace = ColorSpace.singletons.gray; this.strokeColorSpace = ColorSpace.singletons.gray; } clone() { return Object.create(this); } } class EvaluatorPreprocessor { static get opMap() { return shadow(this, "opMap", { w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false }, J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false }, j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false }, M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false }, d: { id: OPS.setDash, numArgs: 2, variableArgs: false }, ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false }, i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false }, gs: { id: OPS.setGState, numArgs: 1, variableArgs: false }, q: { id: OPS.save, numArgs: 0, variableArgs: false }, Q: { id: OPS.restore, numArgs: 0, variableArgs: false }, cm: { id: OPS.transform, numArgs: 6, variableArgs: false }, m: { id: OPS.moveTo, numArgs: 2, variableArgs: false }, l: { id: OPS.lineTo, numArgs: 2, variableArgs: false }, c: { id: OPS.curveTo, numArgs: 6, variableArgs: false }, v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false }, y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false }, h: { id: OPS.closePath, numArgs: 0, variableArgs: false }, re: { id: OPS.rectangle, numArgs: 4, variableArgs: false }, S: { id: OPS.stroke, numArgs: 0, variableArgs: false }, s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false }, f: { id: OPS.fill, numArgs: 0, variableArgs: false }, F: { id: OPS.fill, numArgs: 0, variableArgs: false }, "f*": { id: OPS.eoFill, numArgs: 0, variableArgs: false }, B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false }, "B*": { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false }, b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false }, "b*": { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false }, n: { id: OPS.endPath, numArgs: 0, variableArgs: false }, W: { id: OPS.clip, numArgs: 0, variableArgs: false }, "W*": { id: OPS.eoClip, numArgs: 0, variableArgs: false }, BT: { id: OPS.beginText, numArgs: 0, variableArgs: false }, ET: { id: OPS.endText, numArgs: 0, variableArgs: false }, Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false }, Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false }, Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false }, TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false }, Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false }, Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false }, Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false }, Td: { id: OPS.moveText, numArgs: 2, variableArgs: false }, TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false }, Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false }, "T*": { id: OPS.nextLine, numArgs: 0, variableArgs: false }, Tj: { id: OPS.showText, numArgs: 1, variableArgs: false }, TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false }, "'": { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false }, '"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3, variableArgs: false }, d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false }, d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false }, CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false }, cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false }, SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true }, SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true }, sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true }, scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true }, G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false }, g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false }, RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false }, rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false }, K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false }, k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false }, sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false }, BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false }, ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false }, EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false }, Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false }, MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false }, DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false }, BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false }, BDC: { id: OPS.beginMarkedContentProps, numArgs: 2, variableArgs: false }, EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false }, BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false }, EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false }, BM: null, BD: null, true: null, fa: null, fal: null, fals: null, false: null, nu: null, nul: null, null: null }); } static MAX_INVALID_PATH_OPS = 10; constructor(stream, xref, stateManager = new StateManager()) { this.parser = new Parser({ lexer: new Lexer(stream, EvaluatorPreprocessor.opMap), xref }); this.stateManager = stateManager; this.nonProcessedArgs = []; this._isPathOp = false; this._numInvalidPathOPS = 0; } get savedStatesDepth() { return this.stateManager.stateStack.length; } read(operation) { let args = operation.args; while (true) { const obj = this.parser.getObj(); if (obj instanceof Cmd) { const cmd = obj.cmd; const opSpec = EvaluatorPreprocessor.opMap[cmd]; if (!opSpec) { warn(`Unknown command "${cmd}".`); continue; } const fn = opSpec.id; const numArgs = opSpec.numArgs; let argsLength = args !== null ? args.length : 0; if (!this._isPathOp) { this._numInvalidPathOPS = 0; } this._isPathOp = fn >= OPS.moveTo && fn <= OPS.endPath; if (!opSpec.variableArgs) { if (argsLength !== numArgs) { const nonProcessedArgs = this.nonProcessedArgs; while (argsLength > numArgs) { nonProcessedArgs.push(args.shift()); argsLength--; } while (argsLength < numArgs && nonProcessedArgs.length !== 0) { if (args === null) { args = []; } args.unshift(nonProcessedArgs.pop()); argsLength++; } } if (argsLength < numArgs) { const partialMsg = `command ${cmd}: expected ${numArgs} args, ` + `but received ${argsLength} args.`; if (this._isPathOp && ++this._numInvalidPathOPS > EvaluatorPreprocessor.MAX_INVALID_PATH_OPS) { throw new FormatError(`Invalid ${partialMsg}`); } warn(`Skipping ${partialMsg}`); if (args !== null) { args.length = 0; } continue; } } else if (argsLength > numArgs) { info(`Command ${cmd}: expected [0, ${numArgs}] args, ` + `but received ${argsLength} args.`); } this.preprocessCommand(fn, args); operation.fn = fn; operation.args = args; return true; } if (obj === EOF) { return false; } if (obj !== null) { if (args === null) { args = []; } args.push(obj); if (args.length > 33) { throw new FormatError("Too many arguments"); } } } } preprocessCommand(fn, args) { switch (fn | 0) { case OPS.save: this.stateManager.save(); break; case OPS.restore: this.stateManager.restore(); break; case OPS.transform: this.stateManager.transform(args); break; } } } ;// CONCATENATED MODULE: ./src/core/default_appearance.js class DefaultAppearanceEvaluator extends EvaluatorPreprocessor { constructor(str) { super(new StringStream(str)); } parse() { const operation = { fn: 0, args: [] }; const result = { fontSize: 0, fontName: "", fontColor: new Uint8ClampedArray(3) }; try { while (true) { operation.args.length = 0; if (!this.read(operation)) { break; } if (this.savedStatesDepth !== 0) { continue; } const { fn, args } = operation; switch (fn | 0) { case OPS.setFont: const [fontName, fontSize] = args; if (fontName instanceof Name) { result.fontName = fontName.name; } if (typeof fontSize === "number" && fontSize > 0) { result.fontSize = fontSize; } break; case OPS.setFillRGBColor: ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.setFillGray: ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.setFillCMYKColor: ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0); break; } } } catch (reason) { warn(`parseDefaultAppearance - ignoring errors: "${reason}".`); } return result; } } function parseDefaultAppearance(str) { return new DefaultAppearanceEvaluator(str).parse(); } class AppearanceStreamEvaluator extends EvaluatorPreprocessor { constructor(stream, evaluatorOptions, xref) { super(stream); this.stream = stream; this.evaluatorOptions = evaluatorOptions; this.xref = xref; this.resources = stream.dict?.get("Resources"); } parse() { const operation = { fn: 0, args: [] }; let result = { scaleFactor: 1, fontSize: 0, fontName: "", fontColor: new Uint8ClampedArray(3), fillColorSpace: ColorSpace.singletons.gray }; let breakLoop = false; const stack = []; try { while (true) { operation.args.length = 0; if (breakLoop || !this.read(operation)) { break; } const { fn, args } = operation; switch (fn | 0) { case OPS.save: stack.push({ scaleFactor: result.scaleFactor, fontSize: result.fontSize, fontName: result.fontName, fontColor: result.fontColor.slice(), fillColorSpace: result.fillColorSpace }); break; case OPS.restore: result = stack.pop() || result; break; case OPS.setTextMatrix: result.scaleFactor *= Math.hypot(args[0], args[1]); break; case OPS.setFont: const [fontName, fontSize] = args; if (fontName instanceof Name) { result.fontName = fontName.name; } if (typeof fontSize === "number" && fontSize > 0) { result.fontSize = fontSize * result.scaleFactor; } break; case OPS.setFillColorSpace: result.fillColorSpace = ColorSpace.parse({ cs: args[0], xref: this.xref, resources: this.resources, pdfFunctionFactory: this._pdfFunctionFactory, localColorSpaceCache: this._localColorSpaceCache }); break; case OPS.setFillColor: const cs = result.fillColorSpace; cs.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.setFillRGBColor: ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.setFillGray: ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.setFillCMYKColor: ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0); break; case OPS.showText: case OPS.showSpacedText: case OPS.nextLineShowText: case OPS.nextLineSetSpacingShowText: breakLoop = true; break; } } } catch (reason) { warn(`parseAppearanceStream - ignoring errors: "${reason}".`); } this.stream.reset(); delete result.scaleFactor; delete result.fillColorSpace; return result; } get _localColorSpaceCache() { return shadow(this, "_localColorSpaceCache", new LocalColorSpaceCache()); } get _pdfFunctionFactory() { const pdfFunctionFactory = new PDFFunctionFactory({ xref: this.xref, isEvalSupported: this.evaluatorOptions.isEvalSupported }); return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory); } } function parseAppearanceStream(stream, evaluatorOptions, xref) { return new AppearanceStreamEvaluator(stream, evaluatorOptions, xref).parse(); } function getPdfColor(color, isFill) { if (color[0] === color[1] && color[1] === color[2]) { const gray = color[0] / 255; return `${numberToString(gray)} ${isFill ? "g" : "G"}`; } return Array.from(color, c => numberToString(c / 255)).join(" ") + ` ${isFill ? "rg" : "RG"}`; } function createDefaultAppearance({ fontSize, fontName, fontColor }) { return `/${escapePDFName(fontName)} ${fontSize} Tf ${getPdfColor(fontColor, true)}`; } class FakeUnicodeFont { constructor(xref, fontFamily) { this.xref = xref; this.widths = null; this.firstChar = Infinity; this.lastChar = -Infinity; this.fontFamily = fontFamily; const canvas = new OffscreenCanvas(1, 1); this.ctxMeasure = canvas.getContext("2d"); if (!FakeUnicodeFont._fontNameId) { FakeUnicodeFont._fontNameId = 1; } this.fontName = Name.get(`InvalidPDFjsFont_${fontFamily}_${FakeUnicodeFont._fontNameId++}`); } get toUnicodeRef() { if (!FakeUnicodeFont._toUnicodeRef) { const toUnicode = `/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> <FFFF> endcodespacerange 1 beginbfrange <0000> <FFFF> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end`; const toUnicodeStream = FakeUnicodeFont.toUnicodeStream = new StringStream(toUnicode); const toUnicodeDict = new Dict(this.xref); toUnicodeStream.dict = toUnicodeDict; toUnicodeDict.set("Length", toUnicode.length); FakeUnicodeFont._toUnicodeRef = this.xref.getNewPersistentRef(toUnicodeStream); } return FakeUnicodeFont._toUnicodeRef; } get fontDescriptorRef() { if (!FakeUnicodeFont._fontDescriptorRef) { const fontDescriptor = new Dict(this.xref); fontDescriptor.set("Type", Name.get("FontDescriptor")); fontDescriptor.set("FontName", this.fontName); fontDescriptor.set("FontFamily", "MyriadPro Regular"); fontDescriptor.set("FontBBox", [0, 0, 0, 0]); fontDescriptor.set("FontStretch", Name.get("Normal")); fontDescriptor.set("FontWeight", 400); fontDescriptor.set("ItalicAngle", 0); FakeUnicodeFont._fontDescriptorRef = this.xref.getNewPersistentRef(fontDescriptor); } return FakeUnicodeFont._fontDescriptorRef; } get descendantFontRef() { const descendantFont = new Dict(this.xref); descendantFont.set("BaseFont", this.fontName); descendantFont.set("Type", Name.get("Font")); descendantFont.set("Subtype", Name.get("CIDFontType0")); descendantFont.set("CIDToGIDMap", Name.get("Identity")); descendantFont.set("FirstChar", this.firstChar); descendantFont.set("LastChar", this.lastChar); descendantFont.set("FontDescriptor", this.fontDescriptorRef); descendantFont.set("DW", 1000); const widths = []; const chars = [...this.widths.entries()].sort(); let currentChar = null; let currentWidths = null; for (const [char, width] of chars) { if (!currentChar) { currentChar = char; currentWidths = [width]; continue; } if (char === currentChar + currentWidths.length) { currentWidths.push(width); } else { widths.push(currentChar, currentWidths); currentChar = char; currentWidths = [width]; } } if (currentChar) { widths.push(currentChar, currentWidths); } descendantFont.set("W", widths); const cidSystemInfo = new Dict(this.xref); cidSystemInfo.set("Ordering", "Identity"); cidSystemInfo.set("Registry", "Adobe"); cidSystemInfo.set("Supplement", 0); descendantFont.set("CIDSystemInfo", cidSystemInfo); return this.xref.getNewPersistentRef(descendantFont); } get baseFontRef() { const baseFont = new Dict(this.xref); baseFont.set("BaseFont", this.fontName); baseFont.set("Type", Name.get("Font")); baseFont.set("Subtype", Name.get("Type0")); baseFont.set("Encoding", Name.get("Identity-H")); baseFont.set("DescendantFonts", [this.descendantFontRef]); baseFont.set("ToUnicode", this.toUnicodeRef); return this.xref.getNewPersistentRef(baseFont); } get resources() { const resources = new Dict(this.xref); const font = new Dict(this.xref); font.set(this.fontName.name, this.baseFontRef); resources.set("Font", font); return resources; } _createContext() { this.widths = new Map(); this.ctxMeasure.font = `1000px ${this.fontFamily}`; return this.ctxMeasure; } createFontResources(text) { const ctx = this._createContext(); for (const line of text.split(/\r\n?|\n/)) { for (const char of line.split("")) { const code = char.charCodeAt(0); if (this.widths.has(code)) { continue; } const metrics = ctx.measureText(char); const width = Math.ceil(metrics.width); this.widths.set(code, width); this.firstChar = Math.min(code, this.firstChar); this.lastChar = Math.max(code, this.lastChar); } } return this.resources; } createAppearance(text, rect, rotation, fontSize, bgColor, strokeAlpha) { const ctx = this._createContext(); const lines = []; let maxWidth = -Infinity; for (const line of text.split(/\r\n?|\n/)) { lines.push(line); const lineWidth = ctx.measureText(line).width; maxWidth = Math.max(maxWidth, lineWidth); for (const char of line.split("")) { const code = char.charCodeAt(0); let width = this.widths.get(code); if (width === undefined) { const metrics = ctx.measureText(char); width = Math.ceil(metrics.width); this.widths.set(code, width); this.firstChar = Math.min(code, this.firstChar); this.lastChar = Math.max(code, this.lastChar); } } } maxWidth *= fontSize / 1000; const [x1, y1, x2, y2] = rect; let w = x2 - x1; let h = y2 - y1; if (rotation % 180 !== 0) { [w, h] = [h, w]; } let hscale = 1; if (maxWidth > w) { hscale = w / maxWidth; } let vscale = 1; const lineHeight = LINE_FACTOR * fontSize; const lineDescent = LINE_DESCENT_FACTOR * fontSize; const maxHeight = lineHeight * lines.length; if (maxHeight > h) { vscale = h / maxHeight; } const fscale = Math.min(hscale, vscale); const newFontSize = fontSize * fscale; const buffer = ["q", `0 0 ${numberToString(w)} ${numberToString(h)} re W n`, `BT`, `1 0 0 1 0 ${numberToString(h + lineDescent)} Tm 0 Tc ${getPdfColor(bgColor, true)}`, `/${this.fontName.name} ${numberToString(newFontSize)} Tf`]; const { resources } = this; strokeAlpha = typeof strokeAlpha === "number" && strokeAlpha >= 0 && strokeAlpha <= 1 ? strokeAlpha : 1; if (strokeAlpha !== 1) { buffer.push("/R0 gs"); const extGState = new Dict(this.xref); const r0 = new Dict(this.xref); r0.set("ca", strokeAlpha); r0.set("CA", strokeAlpha); r0.set("Type", Name.get("ExtGState")); extGState.set("R0", r0); resources.set("ExtGState", extGState); } const vShift = numberToString(lineHeight); for (const line of lines) { buffer.push(`0 -${vShift} Td <${stringToUTF16HexString(line)}> Tj`); } buffer.push("ET", "Q"); const appearance = buffer.join("\n"); const appearanceStreamDict = new Dict(this.xref); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", [0, 0, w, h]); appearanceStreamDict.set("Length", appearance.length); appearanceStreamDict.set("Resources", resources); if (rotation) { const matrix = getRotationMatrix(rotation, w, h); appearanceStreamDict.set("Matrix", matrix); } const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; return ap; } } ;// CONCATENATED MODULE: ./src/core/name_number_tree.js class NameOrNumberTree { constructor(root, xref, type) { if (this.constructor === NameOrNumberTree) { unreachable("Cannot initialize NameOrNumberTree."); } this.root = root; this.xref = xref; this._type = type; } getAll() { const map = new Map(); if (!this.root) { return map; } const xref = this.xref; const processed = new RefSet(); processed.put(this.root); const queue = [this.root]; while (queue.length > 0) { const obj = xref.fetchIfRef(queue.shift()); if (!(obj instanceof Dict)) { continue; } if (obj.has("Kids")) { const kids = obj.get("Kids"); if (!Array.isArray(kids)) { continue; } for (const kid of kids) { if (processed.has(kid)) { throw new FormatError(`Duplicate entry in "${this._type}" tree.`); } queue.push(kid); processed.put(kid); } continue; } const entries = obj.get(this._type); if (!Array.isArray(entries)) { continue; } for (let i = 0, ii = entries.length; i < ii; i += 2) { map.set(xref.fetchIfRef(entries[i]), xref.fetchIfRef(entries[i + 1])); } } return map; } get(key) { if (!this.root) { return null; } const xref = this.xref; let kidsOrEntries = xref.fetchIfRef(this.root); let loopCount = 0; const MAX_LEVELS = 10; while (kidsOrEntries.has("Kids")) { if (++loopCount > MAX_LEVELS) { warn(`Search depth limit reached for "${this._type}" tree.`); return null; } const kids = kidsOrEntries.get("Kids"); if (!Array.isArray(kids)) { return null; } let l = 0, r = kids.length - 1; while (l <= r) { const m = l + r >> 1; const kid = xref.fetchIfRef(kids[m]); const limits = kid.get("Limits"); if (key < xref.fetchIfRef(limits[0])) { r = m - 1; } else if (key > xref.fetchIfRef(limits[1])) { l = m + 1; } else { kidsOrEntries = kid; break; } } if (l > r) { return null; } } const entries = kidsOrEntries.get(this._type); if (Array.isArray(entries)) { let l = 0, r = entries.length - 2; while (l <= r) { const tmp = l + r >> 1, m = tmp + (tmp & 1); const currentKey = xref.fetchIfRef(entries[m]); if (key < currentKey) { r = m - 2; } else if (key > currentKey) { l = m + 2; } else { return xref.fetchIfRef(entries[m + 1]); } } } return null; } } class NameTree extends NameOrNumberTree { constructor(root, xref) { super(root, xref, "Names"); } } class NumberTree extends NameOrNumberTree { constructor(root, xref) { super(root, xref, "Nums"); } } ;// CONCATENATED MODULE: ./src/core/cleanup_helper.js function clearGlobalCaches() { clearPatternCaches(); clearPrimitiveCaches(); clearUnicodeCaches(); } ;// CONCATENATED MODULE: ./src/core/file_spec.js function pickPlatformItem(dict) { if (dict.has("UF")) { return dict.get("UF"); } else if (dict.has("F")) { return dict.get("F"); } else if (dict.has("Unix")) { return dict.get("Unix"); } else if (dict.has("Mac")) { return dict.get("Mac"); } else if (dict.has("DOS")) { return dict.get("DOS"); } return null; } class FileSpec { constructor(root, xref) { if (!(root instanceof Dict)) { return; } this.xref = xref; this.root = root; if (root.has("FS")) { this.fs = root.get("FS"); } this.description = root.has("Desc") ? stringToPDFString(root.get("Desc")) : ""; if (root.has("RF")) { warn("Related file specifications are not supported"); } this.contentAvailable = true; if (!root.has("EF")) { this.contentAvailable = false; warn("Non-embedded file specifications are not supported"); } } get filename() { if (!this._filename && this.root) { const filename = pickPlatformItem(this.root) || "unnamed"; this._filename = stringToPDFString(filename).replaceAll("\\\\", "\\").replaceAll("\\/", "/").replaceAll("\\", "/"); } return this._filename; } get content() { if (!this.contentAvailable) { return null; } if (!this.contentRef && this.root) { this.contentRef = pickPlatformItem(this.root.get("EF")); } let content = null; if (this.contentRef) { const fileObj = this.xref.fetchIfRef(this.contentRef); if (fileObj instanceof BaseStream) { content = fileObj.getBytes(); } else { warn("Embedded file specification points to non-existing/invalid content"); } } else { warn("Embedded file specification does not have a content"); } return content; } get serializable() { return { filename: this.filename, content: this.content }; } } ;// CONCATENATED MODULE: ./src/core/xml_parser.js const XMLParserErrorCode = { NoError: 0, EndOfDocument: -1, UnterminatedCdat: -2, UnterminatedXmlDeclaration: -3, UnterminatedDoctypeDeclaration: -4, UnterminatedComment: -5, MalformedElement: -6, OutOfMemory: -7, UnterminatedAttributeValue: -8, UnterminatedElement: -9, ElementNeverBegun: -10 }; function isWhitespace(s, index) { const ch = s[index]; return ch === " " || ch === "\n" || ch === "\r" || ch === "\t"; } function isWhitespaceString(s) { for (let i = 0, ii = s.length; i < ii; i++) { if (!isWhitespace(s, i)) { return false; } } return true; } class XMLParserBase { _resolveEntities(s) { return s.replaceAll(/&([^;]+);/g, (all, entity) => { if (entity.substring(0, 2) === "#x") { return String.fromCodePoint(parseInt(entity.substring(2), 16)); } else if (entity.substring(0, 1) === "#") { return String.fromCodePoint(parseInt(entity.substring(1), 10)); } switch (entity) { case "lt": return "<"; case "gt": return ">"; case "amp": return "&"; case "quot": return '"'; case "apos": return "'"; } return this.onResolveEntity(entity); }); } _parseContent(s, start) { const attributes = []; let pos = start; function skipWs() { while (pos < s.length && isWhitespace(s, pos)) { ++pos; } } while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "/") { ++pos; } const name = s.substring(start, pos); skipWs(); while (pos < s.length && s[pos] !== ">" && s[pos] !== "/" && s[pos] !== "?") { skipWs(); let attrName = "", attrValue = ""; while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== "=") { attrName += s[pos]; ++pos; } skipWs(); if (s[pos] !== "=") { return null; } ++pos; skipWs(); const attrEndChar = s[pos]; if (attrEndChar !== '"' && attrEndChar !== "'") { return null; } const attrEndIndex = s.indexOf(attrEndChar, ++pos); if (attrEndIndex < 0) { return null; } attrValue = s.substring(pos, attrEndIndex); attributes.push({ name: attrName, value: this._resolveEntities(attrValue) }); pos = attrEndIndex + 1; skipWs(); } return { name, attributes, parsed: pos - start }; } _parseProcessingInstruction(s, start) { let pos = start; function skipWs() { while (pos < s.length && isWhitespace(s, pos)) { ++pos; } } while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "?" && s[pos] !== "/") { ++pos; } const name = s.substring(start, pos); skipWs(); const attrStart = pos; while (pos < s.length && (s[pos] !== "?" || s[pos + 1] !== ">")) { ++pos; } const value = s.substring(attrStart, pos); return { name, value, parsed: pos - start }; } parseXml(s) { let i = 0; while (i < s.length) { const ch = s[i]; let j = i; if (ch === "<") { ++j; const ch2 = s[j]; let q; switch (ch2) { case "/": ++j; q = s.indexOf(">", j); if (q < 0) { this.onError(XMLParserErrorCode.UnterminatedElement); return; } this.onEndElement(s.substring(j, q)); j = q + 1; break; case "?": ++j; const pi = this._parseProcessingInstruction(s, j); if (s.substring(j + pi.parsed, j + pi.parsed + 2) !== "?>") { this.onError(XMLParserErrorCode.UnterminatedXmlDeclaration); return; } this.onPi(pi.name, pi.value); j += pi.parsed + 2; break; case "!": if (s.substring(j + 1, j + 3) === "--") { q = s.indexOf("-->", j + 3); if (q < 0) { this.onError(XMLParserErrorCode.UnterminatedComment); return; } this.onComment(s.substring(j + 3, q)); j = q + 3; } else if (s.substring(j + 1, j + 8) === "[CDATA[") { q = s.indexOf("]]>", j + 8); if (q < 0) { this.onError(XMLParserErrorCode.UnterminatedCdat); return; } this.onCdata(s.substring(j + 8, q)); j = q + 3; } else if (s.substring(j + 1, j + 8) === "DOCTYPE") { const q2 = s.indexOf("[", j + 8); let complexDoctype = false; q = s.indexOf(">", j + 8); if (q < 0) { this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration); return; } if (q2 > 0 && q > q2) { q = s.indexOf("]>", j + 8); if (q < 0) { this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration); return; } complexDoctype = true; } const doctypeContent = s.substring(j + 8, q + (complexDoctype ? 1 : 0)); this.onDoctype(doctypeContent); j = q + (complexDoctype ? 2 : 1); } else { this.onError(XMLParserErrorCode.MalformedElement); return; } break; default: const content = this._parseContent(s, j); if (content === null) { this.onError(XMLParserErrorCode.MalformedElement); return; } let isClosed = false; if (s.substring(j + content.parsed, j + content.parsed + 2) === "/>") { isClosed = true; } else if (s.substring(j + content.parsed, j + content.parsed + 1) !== ">") { this.onError(XMLParserErrorCode.UnterminatedElement); return; } this.onBeginElement(content.name, content.attributes, isClosed); j += content.parsed + (isClosed ? 2 : 1); break; } } else { while (j < s.length && s[j] !== "<") { j++; } const text = s.substring(i, j); this.onText(this._resolveEntities(text)); } i = j; } } onResolveEntity(name) { return `&${name};`; } onPi(name, value) {} onComment(text) {} onCdata(text) {} onDoctype(doctypeContent) {} onText(text) {} onBeginElement(name, attributes, isEmpty) {} onEndElement(name) {} onError(code) {} } class SimpleDOMNode { constructor(nodeName, nodeValue) { this.nodeName = nodeName; this.nodeValue = nodeValue; Object.defineProperty(this, "parentNode", { value: null, writable: true }); } get firstChild() { return this.childNodes?.[0]; } get nextSibling() { const childNodes = this.parentNode.childNodes; if (!childNodes) { return undefined; } const index = childNodes.indexOf(this); if (index === -1) { return undefined; } return childNodes[index + 1]; } get textContent() { if (!this.childNodes) { return this.nodeValue || ""; } return this.childNodes.map(function (child) { return child.textContent; }).join(""); } get children() { return this.childNodes || []; } hasChildNodes() { return this.childNodes?.length > 0; } searchNode(paths, pos) { if (pos >= paths.length) { return this; } const component = paths[pos]; if (component.name.startsWith("#") && pos < paths.length - 1) { return this.searchNode(paths, pos + 1); } const stack = []; let node = this; while (true) { if (component.name === node.nodeName) { if (component.pos === 0) { const res = node.searchNode(paths, pos + 1); if (res !== null) { return res; } } else if (stack.length === 0) { return null; } else { const [parent] = stack.pop(); let siblingPos = 0; for (const child of parent.childNodes) { if (component.name === child.nodeName) { if (siblingPos === component.pos) { return child.searchNode(paths, pos + 1); } siblingPos++; } } return node.searchNode(paths, pos + 1); } } if (node.childNodes?.length > 0) { stack.push([node, 0]); node = node.childNodes[0]; } else if (stack.length === 0) { return null; } else { while (stack.length !== 0) { const [parent, currentPos] = stack.pop(); const newPos = currentPos + 1; if (newPos < parent.childNodes.length) { stack.push([parent, newPos]); node = parent.childNodes[newPos]; break; } } if (stack.length === 0) { return null; } } } } dump(buffer) { if (this.nodeName === "#text") { buffer.push(encodeToXmlString(this.nodeValue)); return; } buffer.push(`<${this.nodeName}`); if (this.attributes) { for (const attribute of this.attributes) { buffer.push(` ${attribute.name}="${encodeToXmlString(attribute.value)}"`); } } if (this.hasChildNodes()) { buffer.push(">"); for (const child of this.childNodes) { child.dump(buffer); } buffer.push(`</${this.nodeName}>`); } else if (this.nodeValue) { buffer.push(`>${encodeToXmlString(this.nodeValue)}</${this.nodeName}>`); } else { buffer.push("/>"); } } } class SimpleXMLParser extends XMLParserBase { constructor({ hasAttributes = false, lowerCaseName = false }) { super(); this._currentFragment = null; this._stack = null; this._errorCode = XMLParserErrorCode.NoError; this._hasAttributes = hasAttributes; this._lowerCaseName = lowerCaseName; } parseFromString(data) { this._currentFragment = []; this._stack = []; this._errorCode = XMLParserErrorCode.NoError; this.parseXml(data); if (this._errorCode !== XMLParserErrorCode.NoError) { return undefined; } const [documentElement] = this._currentFragment; if (!documentElement) { return undefined; } return { documentElement }; } onText(text) { if (isWhitespaceString(text)) { return; } const node = new SimpleDOMNode("#text", text); this._currentFragment.push(node); } onCdata(text) { const node = new SimpleDOMNode("#text", text); this._currentFragment.push(node); } onBeginElement(name, attributes, isEmpty) { if (this._lowerCaseName) { name = name.toLowerCase(); } const node = new SimpleDOMNode(name); node.childNodes = []; if (this._hasAttributes) { node.attributes = attributes; } this._currentFragment.push(node); if (isEmpty) { return; } this._stack.push(this._currentFragment); this._currentFragment = node.childNodes; } onEndElement(name) { this._currentFragment = this._stack.pop() || []; const lastElement = this._currentFragment.at(-1); if (!lastElement) { return null; } for (const childNode of lastElement.childNodes) { childNode.parentNode = lastElement; } return lastElement; } onError(code) { this._errorCode = code; } } ;// CONCATENATED MODULE: ./src/core/metadata_parser.js class MetadataParser { constructor(data) { data = this._repair(data); const parser = new SimpleXMLParser({ lowerCaseName: true }); const xmlDocument = parser.parseFromString(data); this._metadataMap = new Map(); this._data = data; if (xmlDocument) { this._parse(xmlDocument); } } _repair(data) { return data.replace(/^[^<]+/, "").replaceAll(/>\\376\\377([^<]+)/g, function (all, codes) { const bytes = codes.replaceAll(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) { return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1); }).replaceAll(/&(amp|apos|gt|lt|quot);/g, function (str, name) { switch (name) { case "amp": return "&"; case "apos": return "'"; case "gt": return ">"; case "lt": return "<"; case "quot": return '"'; } throw new Error(`_repair: ${name} isn't defined.`); }); const charBuf = [">"]; for (let i = 0, ii = bytes.length; i < ii; i += 2) { const code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1); if (code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38) { charBuf.push(String.fromCharCode(code)); } else { charBuf.push("&#x" + (0x10000 + code).toString(16).substring(1) + ";"); } } return charBuf.join(""); }); } _getSequence(entry) { const name = entry.nodeName; if (name !== "rdf:bag" && name !== "rdf:seq" && name !== "rdf:alt") { return null; } return entry.childNodes.filter(node => node.nodeName === "rdf:li"); } _parseArray(entry) { if (!entry.hasChildNodes()) { return; } const [seqNode] = entry.childNodes; const sequence = this._getSequence(seqNode) || []; this._metadataMap.set(entry.nodeName, sequence.map(node => node.textContent.trim())); } _parse(xmlDocument) { let rdf = xmlDocument.documentElement; if (rdf.nodeName !== "rdf:rdf") { rdf = rdf.firstChild; while (rdf && rdf.nodeName !== "rdf:rdf") { rdf = rdf.nextSibling; } } if (!rdf || rdf.nodeName !== "rdf:rdf" || !rdf.hasChildNodes()) { return; } for (const desc of rdf.childNodes) { if (desc.nodeName !== "rdf:description") { continue; } for (const entry of desc.childNodes) { const name = entry.nodeName; switch (name) { case "#text": continue; case "dc:creator": case "dc:subject": this._parseArray(entry); continue; } this._metadataMap.set(name, entry.textContent.trim()); } } } get serializable() { return { parsedData: this._metadataMap, rawData: this._data }; } } ;// CONCATENATED MODULE: ./src/core/decrypt_stream.js const chunkSize = 512; class DecryptStream extends DecodeStream { constructor(str, maybeLength, decrypt) { super(maybeLength); this.str = str; this.dict = str.dict; this.decrypt = decrypt; this.nextChunk = null; this.initialized = false; } readBlock() { let chunk; if (this.initialized) { chunk = this.nextChunk; } else { chunk = this.str.getBytes(chunkSize); this.initialized = true; } if (!chunk || chunk.length === 0) { this.eof = true; return; } this.nextChunk = this.str.getBytes(chunkSize); const hasMoreData = this.nextChunk?.length > 0; const decrypt = this.decrypt; chunk = decrypt(chunk, !hasMoreData); const bufferLength = this.bufferLength, newLength = bufferLength + chunk.length, buffer = this.ensureBuffer(newLength); buffer.set(chunk, bufferLength); this.bufferLength = newLength; } } ;// CONCATENATED MODULE: ./src/core/crypto.js class ARCFourCipher { constructor(key) { this.a = 0; this.b = 0; const s = new Uint8Array(256); const keyLength = key.length; for (let i = 0; i < 256; ++i) { s[i] = i; } for (let i = 0, j = 0; i < 256; ++i) { const tmp = s[i]; j = j + tmp + key[i % keyLength] & 0xff; s[i] = s[j]; s[j] = tmp; } this.s = s; } encryptBlock(data) { let a = this.a, b = this.b; const s = this.s; const n = data.length; const output = new Uint8Array(n); for (let i = 0; i < n; ++i) { a = a + 1 & 0xff; const tmp = s[a]; b = b + tmp & 0xff; const tmp2 = s[b]; s[a] = tmp2; s[b] = tmp; output[i] = data[i] ^ s[tmp + tmp2 & 0xff]; } this.a = a; this.b = b; return output; } decryptBlock(data) { return this.encryptBlock(data); } encrypt(data) { return this.encryptBlock(data); } } const calculateMD5 = function calculateMD5Closure() { const r = new Uint8Array([7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]); const k = new Int32Array([-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, 718787259, -343485551]); function hash(data, offset, length) { let h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878; const paddedLength = length + 72 & ~63; const padded = new Uint8Array(paddedLength); let i, j; for (i = 0; i < length; ++i) { padded[i] = data[offset++]; } padded[i++] = 0x80; const n = paddedLength - 8; while (i < n) { padded[i++] = 0; } padded[i++] = length << 3 & 0xff; padded[i++] = length >> 5 & 0xff; padded[i++] = length >> 13 & 0xff; padded[i++] = length >> 21 & 0xff; padded[i++] = length >>> 29 & 0xff; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; const w = new Int32Array(16); for (i = 0; i < paddedLength;) { for (j = 0; j < 16; ++j, i += 4) { w[j] = padded[i] | padded[i + 1] << 8 | padded[i + 2] << 16 | padded[i + 3] << 24; } let a = h0, b = h1, c = h2, d = h3, f, g; for (j = 0; j < 64; ++j) { if (j < 16) { f = b & c | ~b & d; g = j; } else if (j < 32) { f = d & b | ~d & c; g = 5 * j + 1 & 15; } else if (j < 48) { f = b ^ c ^ d; g = 3 * j + 5 & 15; } else { f = c ^ (b | ~d); g = 7 * j & 15; } const tmp = d, rotateArg = a + f + k[j] + w[g] | 0, rotate = r[j]; d = c; c = b; b = b + (rotateArg << rotate | rotateArg >>> 32 - rotate) | 0; a = tmp; } h0 = h0 + a | 0; h1 = h1 + b | 0; h2 = h2 + c | 0; h3 = h3 + d | 0; } return new Uint8Array([h0 & 0xFF, h0 >> 8 & 0xFF, h0 >> 16 & 0xFF, h0 >>> 24 & 0xFF, h1 & 0xFF, h1 >> 8 & 0xFF, h1 >> 16 & 0xFF, h1 >>> 24 & 0xFF, h2 & 0xFF, h2 >> 8 & 0xFF, h2 >> 16 & 0xFF, h2 >>> 24 & 0xFF, h3 & 0xFF, h3 >> 8 & 0xFF, h3 >> 16 & 0xFF, h3 >>> 24 & 0xFF]); } return hash; }(); class Word64 { constructor(highInteger, lowInteger) { this.high = highInteger | 0; this.low = lowInteger | 0; } and(word) { this.high &= word.high; this.low &= word.low; } xor(word) { this.high ^= word.high; this.low ^= word.low; } or(word) { this.high |= word.high; this.low |= word.low; } shiftRight(places) { if (places >= 32) { this.low = this.high >>> places - 32 | 0; this.high = 0; } else { this.low = this.low >>> places | this.high << 32 - places; this.high = this.high >>> places | 0; } } shiftLeft(places) { if (places >= 32) { this.high = this.low << places - 32; this.low = 0; } else { this.high = this.high << places | this.low >>> 32 - places; this.low <<= places; } } rotateRight(places) { let low, high; if (places & 32) { high = this.low; low = this.high; } else { low = this.low; high = this.high; } places &= 31; this.low = low >>> places | high << 32 - places; this.high = high >>> places | low << 32 - places; } not() { this.high = ~this.high; this.low = ~this.low; } add(word) { const lowAdd = (this.low >>> 0) + (word.low >>> 0); let highAdd = (this.high >>> 0) + (word.high >>> 0); if (lowAdd > 0xffffffff) { highAdd += 1; } this.low = lowAdd | 0; this.high = highAdd | 0; } copyTo(bytes, offset) { bytes[offset] = this.high >>> 24 & 0xff; bytes[offset + 1] = this.high >> 16 & 0xff; bytes[offset + 2] = this.high >> 8 & 0xff; bytes[offset + 3] = this.high & 0xff; bytes[offset + 4] = this.low >>> 24 & 0xff; bytes[offset + 5] = this.low >> 16 & 0xff; bytes[offset + 6] = this.low >> 8 & 0xff; bytes[offset + 7] = this.low & 0xff; } assign(word) { this.high = word.high; this.low = word.low; } } const calculateSHA256 = function calculateSHA256Closure() { function rotr(x, n) { return x >>> n | x << 32 - n; } function ch(x, y, z) { return x & y ^ ~x & z; } function maj(x, y, z) { return x & y ^ x & z ^ y & z; } function sigma(x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); } function sigmaPrime(x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); } function littleSigma(x) { return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3; } function littleSigmaPrime(x) { return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10; } const 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]; function hash(data, offset, length) { let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a, h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19; const paddedLength = Math.ceil((length + 9) / 64) * 64; const padded = new Uint8Array(paddedLength); let i, j; for (i = 0; i < length; ++i) { padded[i] = data[offset++]; } padded[i++] = 0x80; const n = paddedLength - 8; while (i < n) { padded[i++] = 0; } padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = length >>> 29 & 0xff; padded[i++] = length >> 21 & 0xff; padded[i++] = length >> 13 & 0xff; padded[i++] = length >> 5 & 0xff; padded[i++] = length << 3 & 0xff; const w = new Uint32Array(64); for (i = 0; i < paddedLength;) { for (j = 0; j < 16; ++j) { w[j] = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3]; i += 4; } for (j = 16; j < 64; ++j) { w[j] = littleSigmaPrime(w[j - 2]) + w[j - 7] + littleSigma(w[j - 15]) + w[j - 16] | 0; } let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7, t1, t2; for (j = 0; j < 64; ++j) { t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j]; t2 = sigma(a) + maj(a, b, c); h = g; g = f; f = e; e = d + t1 | 0; d = c; c = b; b = a; a = t1 + t2 | 0; } h0 = h0 + a | 0; h1 = h1 + b | 0; h2 = h2 + c | 0; h3 = h3 + d | 0; h4 = h4 + e | 0; h5 = h5 + f | 0; h6 = h6 + g | 0; h7 = h7 + h | 0; } return new Uint8Array([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, h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF]); } return hash; }(); const calculateSHA512 = function calculateSHA512Closure() { function ch(result, x, y, z, tmp) { result.assign(x); result.and(y); tmp.assign(x); tmp.not(); tmp.and(z); result.xor(tmp); } function maj(result, x, y, z, tmp) { result.assign(x); result.and(y); tmp.assign(x); tmp.and(z); result.xor(tmp); tmp.assign(y); tmp.and(z); result.xor(tmp); } function sigma(result, x, tmp) { result.assign(x); result.rotateRight(28); tmp.assign(x); tmp.rotateRight(34); result.xor(tmp); tmp.assign(x); tmp.rotateRight(39); result.xor(tmp); } function sigmaPrime(result, x, tmp) { result.assign(x); result.rotateRight(14); tmp.assign(x); tmp.rotateRight(18); result.xor(tmp); tmp.assign(x); tmp.rotateRight(41); result.xor(tmp); } function littleSigma(result, x, tmp) { result.assign(x); result.rotateRight(1); tmp.assign(x); tmp.rotateRight(8); result.xor(tmp); tmp.assign(x); tmp.shiftRight(7); result.xor(tmp); } function littleSigmaPrime(result, x, tmp) { result.assign(x); result.rotateRight(19); tmp.assign(x); tmp.rotateRight(61); result.xor(tmp); tmp.assign(x); tmp.shiftRight(6); result.xor(tmp); } const k = [new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd), new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc), new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019), new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118), new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe), new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2), new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1), new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694), new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3), new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65), new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483), new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5), new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210), new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4), new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725), new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70), new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926), new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df), new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8), new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b), new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001), new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30), new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910), new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8), new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53), new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8), new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb), new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3), new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60), new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec), new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9), new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b), new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207), new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178), new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6), new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b), new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493), new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c), new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)]; function hash(data, offset, length, mode384 = false) { let h0, h1, h2, h3, h4, h5, h6, h7; if (!mode384) { h0 = new Word64(0x6a09e667, 0xf3bcc908); h1 = new Word64(0xbb67ae85, 0x84caa73b); h2 = new Word64(0x3c6ef372, 0xfe94f82b); h3 = new Word64(0xa54ff53a, 0x5f1d36f1); h4 = new Word64(0x510e527f, 0xade682d1); h5 = new Word64(0x9b05688c, 0x2b3e6c1f); h6 = new Word64(0x1f83d9ab, 0xfb41bd6b); h7 = new Word64(0x5be0cd19, 0x137e2179); } else { h0 = new Word64(0xcbbb9d5d, 0xc1059ed8); h1 = new Word64(0x629a292a, 0x367cd507); h2 = new Word64(0x9159015a, 0x3070dd17); h3 = new Word64(0x152fecd8, 0xf70e5939); h4 = new Word64(0x67332667, 0xffc00b31); h5 = new Word64(0x8eb44a87, 0x68581511); h6 = new Word64(0xdb0c2e0d, 0x64f98fa7); h7 = new Word64(0x47b5481d, 0xbefa4fa4); } const paddedLength = Math.ceil((length + 17) / 128) * 128; const padded = new Uint8Array(paddedLength); let i, j; for (i = 0; i < length; ++i) { padded[i] = data[offset++]; } padded[i++] = 0x80; const n = paddedLength - 16; while (i < n) { padded[i++] = 0; } padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = length >>> 29 & 0xff; padded[i++] = length >> 21 & 0xff; padded[i++] = length >> 13 & 0xff; padded[i++] = length >> 5 & 0xff; padded[i++] = length << 3 & 0xff; const w = new Array(80); for (i = 0; i < 80; i++) { w[i] = new Word64(0, 0); } let a = new Word64(0, 0), b = new Word64(0, 0), c = new Word64(0, 0); let d = new Word64(0, 0), e = new Word64(0, 0), f = new Word64(0, 0); let g = new Word64(0, 0), h = new Word64(0, 0); const t1 = new Word64(0, 0), t2 = new Word64(0, 0); const tmp1 = new Word64(0, 0), tmp2 = new Word64(0, 0); let tmp3; for (i = 0; i < paddedLength;) { for (j = 0; j < 16; ++j) { w[j].high = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3]; w[j].low = padded[i + 4] << 24 | padded[i + 5] << 16 | padded[i + 6] << 8 | padded[i + 7]; i += 8; } for (j = 16; j < 80; ++j) { tmp3 = w[j]; littleSigmaPrime(tmp3, w[j - 2], tmp2); tmp3.add(w[j - 7]); littleSigma(tmp1, w[j - 15], tmp2); tmp3.add(tmp1); tmp3.add(w[j - 16]); } a.assign(h0); b.assign(h1); c.assign(h2); d.assign(h3); e.assign(h4); f.assign(h5); g.assign(h6); h.assign(h7); for (j = 0; j < 80; ++j) { t1.assign(h); sigmaPrime(tmp1, e, tmp2); t1.add(tmp1); ch(tmp1, e, f, g, tmp2); t1.add(tmp1); t1.add(k[j]); t1.add(w[j]); sigma(t2, a, tmp2); maj(tmp1, a, b, c, tmp2); t2.add(tmp1); tmp3 = h; h = g; g = f; f = e; d.add(t1); e = d; d = c; c = b; b = a; tmp3.assign(t1); tmp3.add(t2); a = tmp3; } h0.add(a); h1.add(b); h2.add(c); h3.add(d); h4.add(e); h5.add(f); h6.add(g); h7.add(h); } let result; if (!mode384) { result = new Uint8Array(64); h0.copyTo(result, 0); h1.copyTo(result, 8); h2.copyTo(result, 16); h3.copyTo(result, 24); h4.copyTo(result, 32); h5.copyTo(result, 40); h6.copyTo(result, 48); h7.copyTo(result, 56); } else { result = new Uint8Array(48); h0.copyTo(result, 0); h1.copyTo(result, 8); h2.copyTo(result, 16); h3.copyTo(result, 24); h4.copyTo(result, 32); h5.copyTo(result, 40); } return result; } return hash; }(); function calculateSHA384(data, offset, length) { return calculateSHA512(data, offset, length, true); } class NullCipher { decryptBlock(data) { return data; } encrypt(data) { return data; } } class AESBaseCipher { constructor() { if (this.constructor === AESBaseCipher) { unreachable("Cannot initialize AESBaseCipher."); } this._s = new Uint8Array([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]); this._inv_s = new Uint8Array([0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]); this._mix = new Uint32Array([0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]); this._mixCol = new Uint8Array(256); for (let i = 0; i < 256; i++) { this._mixCol[i] = i < 128 ? i << 1 : i << 1 ^ 0x1b; } this.buffer = new Uint8Array(16); this.bufferPosition = 0; } _expandKey(cipherKey) { unreachable("Cannot call `_expandKey` on the base class"); } _decrypt(input, key) { let t, u, v; const state = new Uint8Array(16); state.set(input); for (let j = 0, k = this._keySize; j < 16; ++j, ++k) { state[j] ^= key[k]; } for (let i = this._cyclesOfRepetition - 1; i >= 1; --i) { t = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t; t = state[14]; u = state[10]; state[14] = state[6]; state[10] = state[2]; state[6] = t; state[2] = u; t = state[15]; u = state[11]; v = state[7]; state[15] = state[3]; state[11] = t; state[7] = u; state[3] = v; for (let j = 0; j < 16; ++j) { state[j] = this._inv_s[state[j]]; } for (let j = 0, k = i * 16; j < 16; ++j, ++k) { state[j] ^= key[k]; } for (let j = 0; j < 16; j += 4) { const s0 = this._mix[state[j]]; const s1 = this._mix[state[j + 1]]; const s2 = this._mix[state[j + 2]]; const s3 = this._mix[state[j + 3]]; t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8; state[j] = t >>> 24 & 0xff; state[j + 1] = t >> 16 & 0xff; state[j + 2] = t >> 8 & 0xff; state[j + 3] = t & 0xff; } } t = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = t; t = state[14]; u = state[10]; state[14] = state[6]; state[10] = state[2]; state[6] = t; state[2] = u; t = state[15]; u = state[11]; v = state[7]; state[15] = state[3]; state[11] = t; state[7] = u; state[3] = v; for (let j = 0; j < 16; ++j) { state[j] = this._inv_s[state[j]]; state[j] ^= key[j]; } return state; } _encrypt(input, key) { const s = this._s; let t, u, v; const state = new Uint8Array(16); state.set(input); for (let j = 0; j < 16; ++j) { state[j] ^= key[j]; } for (let i = 1; i < this._cyclesOfRepetition; i++) { for (let j = 0; j < 16; ++j) { state[j] = s[state[j]]; } v = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = v; v = state[2]; u = state[6]; state[2] = state[10]; state[6] = state[14]; state[10] = v; state[14] = u; v = state[3]; u = state[7]; t = state[11]; state[3] = state[15]; state[7] = v; state[11] = u; state[15] = t; for (let j = 0; j < 16; j += 4) { const s0 = state[j + 0]; const s1 = state[j + 1]; const s2 = state[j + 2]; const s3 = state[j + 3]; t = s0 ^ s1 ^ s2 ^ s3; state[j + 0] ^= t ^ this._mixCol[s0 ^ s1]; state[j + 1] ^= t ^ this._mixCol[s1 ^ s2]; state[j + 2] ^= t ^ this._mixCol[s2 ^ s3]; state[j + 3] ^= t ^ this._mixCol[s3 ^ s0]; } for (let j = 0, k = i * 16; j < 16; ++j, ++k) { state[j] ^= key[k]; } } for (let j = 0; j < 16; ++j) { state[j] = s[state[j]]; } v = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = v; v = state[2]; u = state[6]; state[2] = state[10]; state[6] = state[14]; state[10] = v; state[14] = u; v = state[3]; u = state[7]; t = state[11]; state[3] = state[15]; state[7] = v; state[11] = u; state[15] = t; for (let j = 0, k = this._keySize; j < 16; ++j, ++k) { state[j] ^= key[k]; } return state; } _decryptBlock2(data, finalize) { const sourceLength = data.length; let buffer = this.buffer, bufferLength = this.bufferPosition; const result = []; let iv = this.iv; for (let i = 0; i < sourceLength; ++i) { buffer[bufferLength] = data[i]; ++bufferLength; if (bufferLength < 16) { continue; } const plain = this._decrypt(buffer, this._key); for (let j = 0; j < 16; ++j) { plain[j] ^= iv[j]; } iv = buffer; result.push(plain); buffer = new Uint8Array(16); bufferLength = 0; } this.buffer = buffer; this.bufferLength = bufferLength; this.iv = iv; if (result.length === 0) { return new Uint8Array(0); } let outputLength = 16 * result.length; if (finalize) { const lastBlock = result.at(-1); let psLen = lastBlock[15]; if (psLen <= 16) { for (let i = 15, ii = 16 - psLen; i >= ii; --i) { if (lastBlock[i] !== psLen) { psLen = 0; break; } } outputLength -= psLen; result[result.length - 1] = lastBlock.subarray(0, 16 - psLen); } } const output = new Uint8Array(outputLength); for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) { output.set(result[i], j); } return output; } decryptBlock(data, finalize, iv = null) { const sourceLength = data.length; const buffer = this.buffer; let bufferLength = this.bufferPosition; if (iv) { this.iv = iv; } else { for (let i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) { buffer[bufferLength] = data[i]; } if (bufferLength < 16) { this.bufferLength = bufferLength; return new Uint8Array(0); } this.iv = buffer; data = data.subarray(16); } this.buffer = new Uint8Array(16); this.bufferLength = 0; this.decryptBlock = this._decryptBlock2; return this.decryptBlock(data, finalize); } encrypt(data, iv) { const sourceLength = data.length; let buffer = this.buffer, bufferLength = this.bufferPosition; const result = []; if (!iv) { iv = new Uint8Array(16); } for (let i = 0; i < sourceLength; ++i) { buffer[bufferLength] = data[i]; ++bufferLength; if (bufferLength < 16) { continue; } for (let j = 0; j < 16; ++j) { buffer[j] ^= iv[j]; } const cipher = this._encrypt(buffer, this._key); iv = cipher; result.push(cipher); buffer = new Uint8Array(16); bufferLength = 0; } this.buffer = buffer; this.bufferLength = bufferLength; this.iv = iv; if (result.length === 0) { return new Uint8Array(0); } const outputLength = 16 * result.length; const output = new Uint8Array(outputLength); for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) { output.set(result[i], j); } return output; } } class AES128Cipher extends AESBaseCipher { constructor(key) { super(); this._cyclesOfRepetition = 10; this._keySize = 160; this._rcon = new Uint8Array([0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]); this._key = this._expandKey(key); } _expandKey(cipherKey) { const b = 176; const s = this._s; const rcon = this._rcon; const result = new Uint8Array(b); result.set(cipherKey); for (let j = 16, i = 1; j < b; ++i) { let t1 = result[j - 3]; let t2 = result[j - 2]; let t3 = result[j - 1]; let t4 = result[j - 4]; t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4]; t1 ^= rcon[i]; for (let n = 0; n < 4; ++n) { result[j] = t1 ^= result[j - 16]; j++; result[j] = t2 ^= result[j - 16]; j++; result[j] = t3 ^= result[j - 16]; j++; result[j] = t4 ^= result[j - 16]; j++; } } return result; } } class AES256Cipher extends AESBaseCipher { constructor(key) { super(); this._cyclesOfRepetition = 14; this._keySize = 224; this._key = this._expandKey(key); } _expandKey(cipherKey) { const b = 240; const s = this._s; const result = new Uint8Array(b); result.set(cipherKey); let r = 1; let t1, t2, t3, t4; for (let j = 32, i = 1; j < b; ++i) { if (j % 32 === 16) { t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4]; } else if (j % 32 === 0) { t1 = result[j - 3]; t2 = result[j - 2]; t3 = result[j - 1]; t4 = result[j - 4]; t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4]; t1 ^= r; if ((r <<= 1) >= 256) { r = (r ^ 0x1b) & 0xff; } } for (let n = 0; n < 4; ++n) { result[j] = t1 ^= result[j - 32]; j++; result[j] = t2 ^= result[j - 32]; j++; result[j] = t3 ^= result[j - 32]; j++; result[j] = t4 ^= result[j - 32]; j++; } } return result; } } class PDF17 { checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) { const hashData = new Uint8Array(password.length + 56); hashData.set(password, 0); hashData.set(ownerValidationSalt, password.length); hashData.set(userBytes, password.length + ownerValidationSalt.length); const result = calculateSHA256(hashData, 0, hashData.length); return isArrayEqual(result, ownerPassword); } checkUserPassword(password, userValidationSalt, userPassword) { const hashData = new Uint8Array(password.length + 8); hashData.set(password, 0); hashData.set(userValidationSalt, password.length); const result = calculateSHA256(hashData, 0, hashData.length); return isArrayEqual(result, userPassword); } getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) { const hashData = new Uint8Array(password.length + 56); hashData.set(password, 0); hashData.set(ownerKeySalt, password.length); hashData.set(userBytes, password.length + ownerKeySalt.length); const key = calculateSHA256(hashData, 0, hashData.length); const cipher = new AES256Cipher(key); return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16)); } getUserKey(password, userKeySalt, userEncryption) { const hashData = new Uint8Array(password.length + 8); hashData.set(password, 0); hashData.set(userKeySalt, password.length); const key = calculateSHA256(hashData, 0, hashData.length); const cipher = new AES256Cipher(key); return cipher.decryptBlock(userEncryption, false, new Uint8Array(16)); } } class PDF20 { _hash(password, input, userBytes) { let k = calculateSHA256(input, 0, input.length).subarray(0, 32); let e = [0]; let i = 0; while (i < 64 || e.at(-1) > i - 32) { const combinedLength = password.length + k.length + userBytes.length, combinedArray = new Uint8Array(combinedLength); let writeOffset = 0; combinedArray.set(password, writeOffset); writeOffset += password.length; combinedArray.set(k, writeOffset); writeOffset += k.length; combinedArray.set(userBytes, writeOffset); const k1 = new Uint8Array(combinedLength * 64); for (let j = 0, pos = 0; j < 64; j++, pos += combinedLength) { k1.set(combinedArray, pos); } const cipher = new AES128Cipher(k.subarray(0, 16)); e = cipher.encrypt(k1, k.subarray(16, 32)); const remainder = e.slice(0, 16).reduce((a, b) => a + b, 0) % 3; if (remainder === 0) { k = calculateSHA256(e, 0, e.length); } else if (remainder === 1) { k = calculateSHA384(e, 0, e.length); } else if (remainder === 2) { k = calculateSHA512(e, 0, e.length); } i++; } return k.subarray(0, 32); } checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) { const hashData = new Uint8Array(password.length + 56); hashData.set(password, 0); hashData.set(ownerValidationSalt, password.length); hashData.set(userBytes, password.length + ownerValidationSalt.length); const result = this._hash(password, hashData, userBytes); return isArrayEqual(result, ownerPassword); } checkUserPassword(password, userValidationSalt, userPassword) { const hashData = new Uint8Array(password.length + 8); hashData.set(password, 0); hashData.set(userValidationSalt, password.length); const result = this._hash(password, hashData, []); return isArrayEqual(result, userPassword); } getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) { const hashData = new Uint8Array(password.length + 56); hashData.set(password, 0); hashData.set(ownerKeySalt, password.length); hashData.set(userBytes, password.length + ownerKeySalt.length); const key = this._hash(password, hashData, userBytes); const cipher = new AES256Cipher(key); return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16)); } getUserKey(password, userKeySalt, userEncryption) { const hashData = new Uint8Array(password.length + 8); hashData.set(password, 0); hashData.set(userKeySalt, password.length); const key = this._hash(password, hashData, []); const cipher = new AES256Cipher(key); return cipher.decryptBlock(userEncryption, false, new Uint8Array(16)); } } class CipherTransform { constructor(stringCipherConstructor, streamCipherConstructor) { this.StringCipherConstructor = stringCipherConstructor; this.StreamCipherConstructor = streamCipherConstructor; } createStream(stream, length) { const cipher = new this.StreamCipherConstructor(); return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) { return cipher.decryptBlock(data, finalize); }); } decryptString(s) { const cipher = new this.StringCipherConstructor(); let data = stringToBytes(s); data = cipher.decryptBlock(data, true); return bytesToString(data); } encryptString(s) { const cipher = new this.StringCipherConstructor(); if (cipher instanceof AESBaseCipher) { const strLen = s.length; const pad = 16 - strLen % 16; s += String.fromCharCode(pad).repeat(pad); const iv = new Uint8Array(16); if (typeof crypto !== "undefined") { crypto.getRandomValues(iv); } else { for (let i = 0; i < 16; i++) { iv[i] = Math.floor(256 * Math.random()); } } let data = stringToBytes(s); data = cipher.encrypt(data, iv); const buf = new Uint8Array(16 + data.length); buf.set(iv); buf.set(data, 16); return bytesToString(buf); } let data = stringToBytes(s); data = cipher.encrypt(data); return bytesToString(data); } } class CipherTransformFactory { static #defaultPasswordBytes = new Uint8Array([0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]); #createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) { if (password) { const passwordLength = Math.min(127, password.length); password = password.subarray(0, passwordLength); } else { password = []; } const pdfAlgorithm = revision === 6 ? new PDF20() : new PDF17(); if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) { return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption); } else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) { return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption); } return null; } #prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) { const hashDataSize = 40 + ownerPassword.length + fileId.length; const hashData = new Uint8Array(hashDataSize); let i = 0, j, n; if (password) { n = Math.min(32, password.length); for (; i < n; ++i) { hashData[i] = password[i]; } } j = 0; while (i < 32) { hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++]; } for (j = 0, n = ownerPassword.length; j < n; ++j) { hashData[i++] = ownerPassword[j]; } hashData[i++] = flags & 0xff; hashData[i++] = flags >> 8 & 0xff; hashData[i++] = flags >> 16 & 0xff; hashData[i++] = flags >>> 24 & 0xff; for (j = 0, n = fileId.length; j < n; ++j) { hashData[i++] = fileId[j]; } if (revision >= 4 && !encryptMetadata) { hashData[i++] = 0xff; hashData[i++] = 0xff; hashData[i++] = 0xff; hashData[i++] = 0xff; } let hash = calculateMD5(hashData, 0, i); const keyLengthInBytes = keyLength >> 3; if (revision >= 3) { for (j = 0; j < 50; ++j) { hash = calculateMD5(hash, 0, keyLengthInBytes); } } const encryptionKey = hash.subarray(0, keyLengthInBytes); let cipher, checkData; if (revision >= 3) { for (i = 0; i < 32; ++i) { hashData[i] = CipherTransformFactory.#defaultPasswordBytes[i]; } for (j = 0, n = fileId.length; j < n; ++j) { hashData[i++] = fileId[j]; } cipher = new ARCFourCipher(encryptionKey); checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i)); n = encryptionKey.length; const derivedKey = new Uint8Array(n); for (j = 1; j <= 19; ++j) { for (let k = 0; k < n; ++k) { derivedKey[k] = encryptionKey[k] ^ j; } cipher = new ARCFourCipher(derivedKey); checkData = cipher.encryptBlock(checkData); } for (j = 0, n = checkData.length; j < n; ++j) { if (userPassword[j] !== checkData[j]) { return null; } } } else { cipher = new ARCFourCipher(encryptionKey); checkData = cipher.encryptBlock(CipherTransformFactory.#defaultPasswordBytes); for (j = 0, n = checkData.length; j < n; ++j) { if (userPassword[j] !== checkData[j]) { return null; } } } return encryptionKey; } #decodeUserPassword(password, ownerPassword, revision, keyLength) { const hashData = new Uint8Array(32); let i = 0; const n = Math.min(32, password.length); for (; i < n; ++i) { hashData[i] = password[i]; } let j = 0; while (i < 32) { hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++]; } let hash = calculateMD5(hashData, 0, i); const keyLengthInBytes = keyLength >> 3; if (revision >= 3) { for (j = 0; j < 50; ++j) { hash = calculateMD5(hash, 0, hash.length); } } let cipher, userPassword; if (revision >= 3) { userPassword = ownerPassword; const derivedKey = new Uint8Array(keyLengthInBytes); for (j = 19; j >= 0; j--) { for (let k = 0; k < keyLengthInBytes; ++k) { derivedKey[k] = hash[k] ^ j; } cipher = new ARCFourCipher(derivedKey); userPassword = cipher.encryptBlock(userPassword); } } else { cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes)); userPassword = cipher.encryptBlock(ownerPassword); } return userPassword; } #buildObjectKey(num, gen, encryptionKey, isAes = false) { const key = new Uint8Array(encryptionKey.length + 9); const n = encryptionKey.length; let i; for (i = 0; i < n; ++i) { key[i] = encryptionKey[i]; } key[i++] = num & 0xff; key[i++] = num >> 8 & 0xff; key[i++] = num >> 16 & 0xff; key[i++] = gen & 0xff; key[i++] = gen >> 8 & 0xff; if (isAes) { key[i++] = 0x73; key[i++] = 0x41; key[i++] = 0x6c; key[i++] = 0x54; } const hash = calculateMD5(key, 0, i); return hash.subarray(0, Math.min(encryptionKey.length + 5, 16)); } #buildCipherConstructor(cf, name, num, gen, key) { if (!(name instanceof Name)) { throw new FormatError("Invalid crypt filter name."); } const self = this; const cryptFilter = cf.get(name.name); const cfm = cryptFilter?.get("CFM"); if (!cfm || cfm.name === "None") { return function () { return new NullCipher(); }; } if (cfm.name === "V2") { return function () { return new ARCFourCipher(self.#buildObjectKey(num, gen, key, false)); }; } if (cfm.name === "AESV2") { return function () { return new AES128Cipher(self.#buildObjectKey(num, gen, key, true)); }; } if (cfm.name === "AESV3") { return function () { return new AES256Cipher(key); }; } throw new FormatError("Unknown crypto method"); } constructor(dict, fileId, password) { const filter = dict.get("Filter"); if (!isName(filter, "Standard")) { throw new FormatError("unknown encryption method"); } this.filterName = filter.name; this.dict = dict; const algorithm = dict.get("V"); if (!Number.isInteger(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) { throw new FormatError("unsupported encryption algorithm"); } this.algorithm = algorithm; let keyLength = dict.get("Length"); if (!keyLength) { if (algorithm <= 3) { keyLength = 40; } else { const cfDict = dict.get("CF"); const streamCryptoName = dict.get("StmF"); if (cfDict instanceof Dict && streamCryptoName instanceof Name) { cfDict.suppressEncryption = true; const handlerDict = cfDict.get(streamCryptoName.name); keyLength = handlerDict?.get("Length") || 128; if (keyLength < 40) { keyLength <<= 3; } } } } if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) { throw new FormatError("invalid key length"); } const ownerBytes = stringToBytes(dict.get("O")), userBytes = stringToBytes(dict.get("U")); const ownerPassword = ownerBytes.subarray(0, 32); const userPassword = userBytes.subarray(0, 32); const flags = dict.get("P"); const revision = dict.get("R"); const encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get("EncryptMetadata") !== false; this.encryptMetadata = encryptMetadata; const fileIdBytes = stringToBytes(fileId); let passwordBytes; if (password) { if (revision === 6) { try { password = utf8StringToString(password); } catch { warn("CipherTransformFactory: Unable to convert UTF8 encoded password."); } } passwordBytes = stringToBytes(password); } let encryptionKey; if (algorithm !== 5) { encryptionKey = this.#prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata); } else { const ownerValidationSalt = ownerBytes.subarray(32, 40); const ownerKeySalt = ownerBytes.subarray(40, 48); const uBytes = userBytes.subarray(0, 48); const userValidationSalt = userBytes.subarray(32, 40); const userKeySalt = userBytes.subarray(40, 48); const ownerEncryption = stringToBytes(dict.get("OE")); const userEncryption = stringToBytes(dict.get("UE")); const perms = stringToBytes(dict.get("Perms")); encryptionKey = this.#createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms); } if (!encryptionKey && !password) { throw new PasswordException("No password given", PasswordResponses.NEED_PASSWORD); } else if (!encryptionKey && password) { const decodedPassword = this.#decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength); encryptionKey = this.#prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata); } if (!encryptionKey) { throw new PasswordException("Incorrect Password", PasswordResponses.INCORRECT_PASSWORD); } this.encryptionKey = encryptionKey; if (algorithm >= 4) { const cf = dict.get("CF"); if (cf instanceof Dict) { cf.suppressEncryption = true; } this.cf = cf; this.stmf = dict.get("StmF") || Name.get("Identity"); this.strf = dict.get("StrF") || Name.get("Identity"); this.eff = dict.get("EFF") || this.stmf; } } createCipherTransform(num, gen) { if (this.algorithm === 4 || this.algorithm === 5) { return new CipherTransform(this.#buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey), this.#buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey)); } const key = this.#buildObjectKey(num, gen, this.encryptionKey, false); const cipherConstructor = function () { return new ARCFourCipher(key); }; return new CipherTransform(cipherConstructor, cipherConstructor); } } ;// CONCATENATED MODULE: ./src/core/writer.js async function writeObject(ref, obj, buffer, { encrypt = null }) { const transform = encrypt?.createCipherTransform(ref.num, ref.gen); buffer.push(`${ref.num} ${ref.gen} obj\n`); if (obj instanceof Dict) { await writeDict(obj, buffer, transform); } else if (obj instanceof BaseStream) { await writeStream(obj, buffer, transform); } else if (Array.isArray(obj)) { await writeArray(obj, buffer, transform); } buffer.push("\nendobj\n"); } async function writeDict(dict, buffer, transform) { buffer.push("<<"); for (const key of dict.getKeys()) { buffer.push(` /${escapePDFName(key)} `); await writeValue(dict.getRaw(key), buffer, transform); } buffer.push(">>"); } async function writeStream(stream, buffer, transform) { let bytes = stream.getBytes(); const { dict } = stream; const [filter, params] = await Promise.all([dict.getAsync("Filter"), dict.getAsync("DecodeParms")]); const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter; const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode"); const MIN_LENGTH_FOR_COMPRESSING = 256; if (typeof CompressionStream !== "undefined" && (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode)) { try { const cs = new CompressionStream("deflate"); const writer = cs.writable.getWriter(); writer.write(bytes); writer.close(); const buf = await new Response(cs.readable).arrayBuffer(); bytes = new Uint8Array(buf); let newFilter, newParams; if (!filter) { newFilter = Name.get("FlateDecode"); } else if (!isFilterZeroFlateDecode) { newFilter = Array.isArray(filter) ? [Name.get("FlateDecode"), ...filter] : [Name.get("FlateDecode"), filter]; if (params) { newParams = Array.isArray(params) ? [null, ...params] : [null, params]; } } if (newFilter) { dict.set("Filter", newFilter); } if (newParams) { dict.set("DecodeParms", newParams); } } catch (ex) { info(`writeStream - cannot compress data: "${ex}".`); } } let string = bytesToString(bytes); if (transform) { string = transform.encryptString(string); } dict.set("Length", string.length); await writeDict(dict, buffer, transform); buffer.push(" stream\n", string, "\nendstream"); } async function writeArray(array, buffer, transform) { buffer.push("["); let first = true; for (const val of array) { if (!first) { buffer.push(" "); } else { first = false; } await writeValue(val, buffer, transform); } buffer.push("]"); } async function writeValue(value, buffer, transform) { if (value instanceof Name) { buffer.push(`/${escapePDFName(value.name)}`); } else if (value instanceof Ref) { buffer.push(`${value.num} ${value.gen} R`); } else if (Array.isArray(value)) { await writeArray(value, buffer, transform); } else if (typeof value === "string") { if (transform) { value = transform.encryptString(value); } buffer.push(`(${escapeString(value)})`); } else if (typeof value === "number") { buffer.push(numberToString(value)); } else if (typeof value === "boolean") { buffer.push(value.toString()); } else if (value instanceof Dict) { await writeDict(value, buffer, transform); } else if (value instanceof BaseStream) { await writeStream(value, buffer, transform); } else if (value === null) { buffer.push("null"); } else { warn(`Unhandled value in writer: ${typeof value}, please file a bug.`); } } function writeInt(number, size, offset, buffer) { for (let i = size + offset - 1; i > offset - 1; i--) { buffer[i] = number & 0xff; number >>= 8; } return offset + size; } function writeString(string, offset, buffer) { for (let i = 0, len = string.length; i < len; i++) { buffer[offset + i] = string.charCodeAt(i) & 0xff; } } function computeMD5(filesize, xrefInfo) { const time = Math.floor(Date.now() / 1000); const filename = xrefInfo.filename || ""; const md5Buffer = [time.toString(), filename, filesize.toString()]; let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0); for (const value of Object.values(xrefInfo.info)) { md5Buffer.push(value); md5BufferLen += value.length; } const array = new Uint8Array(md5BufferLen); let offset = 0; for (const str of md5Buffer) { writeString(str, offset, array); offset += str.length; } return bytesToString(calculateMD5(array)); } function writeXFADataForAcroform(str, newRefs) { const xml = new SimpleXMLParser({ hasAttributes: true }).parseFromString(str); for (const { xfa } of newRefs) { if (!xfa) { continue; } const { path, value } = xfa; if (!path) { continue; } const nodePath = parseXFAPath(path); let node = xml.documentElement.searchNode(nodePath, 0); if (!node && nodePath.length > 1) { node = xml.documentElement.searchNode([nodePath.at(-1)], 0); } if (node) { node.childNodes = Array.isArray(value) ? value.map(val => new SimpleDOMNode("value", val)) : [new SimpleDOMNode("#text", value)]; } else { warn(`Node not found for path: ${path}`); } } const buffer = []; xml.documentElement.dump(buffer); return buffer.join(""); } async function updateAcroform({ xref, acroForm, acroFormRef, hasXfa, hasXfaDatasetsEntry, xfaDatasetsRef, needAppearances, newRefs }) { if (hasXfa && !hasXfaDatasetsEntry && !xfaDatasetsRef) { warn("XFA - Cannot save it"); } if (!needAppearances && (!hasXfa || !xfaDatasetsRef || hasXfaDatasetsEntry)) { return; } const dict = acroForm.clone(); if (hasXfa && !hasXfaDatasetsEntry) { const newXfa = acroForm.get("XFA").slice(); newXfa.splice(2, 0, "datasets"); newXfa.splice(3, 0, xfaDatasetsRef); dict.set("XFA", newXfa); } if (needAppearances) { dict.set("NeedAppearances", true); } const buffer = []; await writeObject(acroFormRef, dict, buffer, xref); newRefs.push({ ref: acroFormRef, data: buffer.join("") }); } function updateXFA({ xfaData, xfaDatasetsRef, newRefs, xref }) { if (xfaData === null) { const datasets = xref.fetchIfRef(xfaDatasetsRef); xfaData = writeXFADataForAcroform(datasets.getString(), newRefs); } const encrypt = xref.encrypt; if (encrypt) { const transform = encrypt.createCipherTransform(xfaDatasetsRef.num, xfaDatasetsRef.gen); xfaData = transform.encryptString(xfaData); } const data = `${xfaDatasetsRef.num} ${xfaDatasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` + xfaData + "\nendstream\nendobj\n"; newRefs.push({ ref: xfaDatasetsRef, data }); } async function incrementalUpdate({ originalData, xrefInfo, newRefs, xref = null, hasXfa = false, xfaDatasetsRef = null, hasXfaDatasetsEntry = false, needAppearances, acroFormRef = null, acroForm = null, xfaData = null }) { await updateAcroform({ xref, acroForm, acroFormRef, hasXfa, hasXfaDatasetsEntry, xfaDatasetsRef, needAppearances, newRefs }); if (hasXfa) { updateXFA({ xfaData, xfaDatasetsRef, newRefs, xref }); } const newXref = new Dict(null); const refForXrefTable = xrefInfo.newRef; let buffer, baseOffset; const lastByte = originalData.at(-1); if (lastByte === 0x0a || lastByte === 0x0d) { buffer = []; baseOffset = originalData.length; } else { buffer = ["\n"]; baseOffset = originalData.length + 1; } newXref.set("Size", refForXrefTable.num + 1); newXref.set("Prev", xrefInfo.startXRef); newXref.set("Type", Name.get("XRef")); if (xrefInfo.rootRef !== null) { newXref.set("Root", xrefInfo.rootRef); } if (xrefInfo.infoRef !== null) { newXref.set("Info", xrefInfo.infoRef); } if (xrefInfo.encryptRef !== null) { newXref.set("Encrypt", xrefInfo.encryptRef); } newRefs.push({ ref: refForXrefTable, data: "" }); newRefs = newRefs.sort((a, b) => { return a.ref.num - b.ref.num; }); const xrefTableData = [[0, 1, 0xffff]]; const indexes = [0, 1]; let maxOffset = 0; for (const { ref, data } of newRefs) { maxOffset = Math.max(maxOffset, baseOffset); xrefTableData.push([1, baseOffset, Math.min(ref.gen, 0xffff)]); baseOffset += data.length; indexes.push(ref.num, 1); buffer.push(data); } newXref.set("Index", indexes); if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) { const md5 = computeMD5(baseOffset, xrefInfo); newXref.set("ID", [xrefInfo.fileIds[0], md5]); } const offsetSize = Math.ceil(Math.log2(maxOffset) / 8); const sizes = [1, offsetSize, 2]; const structSize = sizes[0] + sizes[1] + sizes[2]; const tableLength = structSize * xrefTableData.length; newXref.set("W", sizes); newXref.set("Length", tableLength); buffer.push(`${refForXrefTable.num} ${refForXrefTable.gen} obj\n`); await writeDict(newXref, buffer, null); buffer.push(" stream\n"); const bufferLen = buffer.reduce((a, str) => a + str.length, 0); const footer = `\nendstream\nendobj\nstartxref\n${baseOffset}\n%%EOF\n`; const array = new Uint8Array(originalData.length + bufferLen + tableLength + footer.length); array.set(originalData); let offset = originalData.length; for (const str of buffer) { writeString(str, offset, array); offset += str.length; } for (const [type, objOffset, gen] of xrefTableData) { offset = writeInt(type, sizes[0], offset, array); offset = writeInt(objOffset, sizes[1], offset, array); offset = writeInt(gen, sizes[2], offset, array); } writeString(footer, offset, array); return array; } ;// CONCATENATED MODULE: ./src/core/struct_tree.js const MAX_DEPTH = 40; const StructElementType = { PAGE_CONTENT: 1, STREAM_CONTENT: 2, OBJECT: 3, ANNOTATION: 4, ELEMENT: 5 }; class StructTreeRoot { constructor(rootDict, rootRef) { this.dict = rootDict; this.ref = rootRef instanceof Ref ? rootRef : null; this.roleMap = new Map(); this.structParentIds = null; } init() { this.readRoleMap(); } #addIdToPage(pageRef, id, type) { if (!(pageRef instanceof Ref) || id < 0) { return; } this.structParentIds ||= new RefSetCache(); let ids = this.structParentIds.get(pageRef); if (!ids) { ids = []; this.structParentIds.put(pageRef, ids); } ids.push([id, type]); } addAnnotationIdToPage(pageRef, id) { this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION); } readRoleMap() { const roleMapDict = this.dict.get("RoleMap"); if (!(roleMapDict instanceof Dict)) { return; } roleMapDict.forEach((key, value) => { if (!(value instanceof Name)) { return; } this.roleMap.set(key, value.name); }); } static async canCreateStructureTree({ catalogRef, pdfManager, newAnnotationsByPage }) { if (!(catalogRef instanceof Ref)) { warn("Cannot save the struct tree: no catalog reference."); return false; } let nextKey = 0; let hasNothingToUpdate = true; for (const [pageIndex, elements] of newAnnotationsByPage) { const { ref: pageRef } = await pdfManager.getPage(pageIndex); if (!(pageRef instanceof Ref)) { warn(`Cannot save the struct tree: page ${pageIndex} has no ref.`); hasNothingToUpdate = true; break; } for (const element of elements) { if (element.accessibilityData?.type) { element.parentTreeId = nextKey++; hasNothingToUpdate = false; } } } if (hasNothingToUpdate) { for (const elements of newAnnotationsByPage.values()) { for (const element of elements) { delete element.parentTreeId; } } return false; } return true; } static async createStructureTree({ newAnnotationsByPage, xref, catalogRef, pdfManager, newRefs }) { const root = pdfManager.catalog.cloneDict(); const structTreeRootRef = xref.getNewTemporaryRef(); root.set("StructTreeRoot", structTreeRootRef); const buffer = []; await writeObject(catalogRef, root, buffer, xref); newRefs.push({ ref: catalogRef, data: buffer.join("") }); const structTreeRoot = new Dict(xref); structTreeRoot.set("Type", Name.get("StructTreeRoot")); const parentTreeRef = xref.getNewTemporaryRef(); structTreeRoot.set("ParentTree", parentTreeRef); const kids = []; structTreeRoot.set("K", kids); const parentTree = new Dict(xref); const nums = []; parentTree.set("Nums", nums); const nextKey = await this.#writeKids({ newAnnotationsByPage, structTreeRootRef, kids, nums, xref, pdfManager, newRefs, buffer }); structTreeRoot.set("ParentTreeNextKey", nextKey); buffer.length = 0; await writeObject(parentTreeRef, parentTree, buffer, xref); newRefs.push({ ref: parentTreeRef, data: buffer.join("") }); buffer.length = 0; await writeObject(structTreeRootRef, structTreeRoot, buffer, xref); newRefs.push({ ref: structTreeRootRef, data: buffer.join("") }); } async canUpdateStructTree({ pdfManager, xref, newAnnotationsByPage }) { if (!this.ref) { warn("Cannot update the struct tree: no root reference."); return false; } let nextKey = this.dict.get("ParentTreeNextKey"); if (!Number.isInteger(nextKey) || nextKey < 0) { warn("Cannot update the struct tree: invalid next key."); return false; } const parentTree = this.dict.get("ParentTree"); if (!(parentTree instanceof Dict)) { warn("Cannot update the struct tree: ParentTree isn't a dict."); return false; } const nums = parentTree.get("Nums"); if (!Array.isArray(nums)) { warn("Cannot update the struct tree: nums isn't an array."); return false; } const numberTree = new NumberTree(parentTree, xref); for (const pageIndex of newAnnotationsByPage.keys()) { const { pageDict } = await pdfManager.getPage(pageIndex); if (!pageDict.has("StructParents")) { continue; } const id = pageDict.get("StructParents"); if (!Number.isInteger(id) || !Array.isArray(numberTree.get(id))) { warn(`Cannot save the struct tree: page ${pageIndex} has a wrong id.`); return false; } } let hasNothingToUpdate = true; for (const [pageIndex, elements] of newAnnotationsByPage) { const { pageDict } = await pdfManager.getPage(pageIndex); StructTreeRoot.#collectParents({ elements, xref: this.dict.xref, pageDict, numberTree }); for (const element of elements) { if (element.accessibilityData?.type) { element.parentTreeId = nextKey++; hasNothingToUpdate = false; } } } if (hasNothingToUpdate) { for (const elements of newAnnotationsByPage.values()) { for (const element of elements) { delete element.parentTreeId; delete element.structTreeParent; } } return false; } return true; } async updateStructureTree({ newAnnotationsByPage, pdfManager, newRefs }) { const xref = this.dict.xref; const structTreeRoot = this.dict.clone(); const structTreeRootRef = this.ref; let parentTreeRef = structTreeRoot.getRaw("ParentTree"); let parentTree; if (parentTreeRef instanceof Ref) { parentTree = xref.fetch(parentTreeRef); } else { parentTree = parentTreeRef; parentTreeRef = xref.getNewTemporaryRef(); structTreeRoot.set("ParentTree", parentTreeRef); } parentTree = parentTree.clone(); let nums = parentTree.getRaw("Nums"); let numsRef = null; if (nums instanceof Ref) { numsRef = nums; nums = xref.fetch(numsRef); } nums = nums.slice(); if (!numsRef) { parentTree.set("Nums", nums); } let kids = structTreeRoot.getRaw("K"); let kidsRef = null; if (kids instanceof Ref) { kidsRef = kids; kids = xref.fetch(kidsRef); } else { kidsRef = xref.getNewTemporaryRef(); structTreeRoot.set("K", kidsRef); } kids = Array.isArray(kids) ? kids.slice() : [kids]; const buffer = []; const newNextkey = await StructTreeRoot.#writeKids({ newAnnotationsByPage, structTreeRootRef, kids, nums, xref, pdfManager, newRefs, buffer }); structTreeRoot.set("ParentTreeNextKey", newNextkey); buffer.length = 0; await writeObject(kidsRef, kids, buffer, xref); newRefs.push({ ref: kidsRef, data: buffer.join("") }); if (numsRef) { buffer.length = 0; await writeObject(numsRef, nums, buffer, xref); newRefs.push({ ref: numsRef, data: buffer.join("") }); } buffer.length = 0; await writeObject(parentTreeRef, parentTree, buffer, xref); newRefs.push({ ref: parentTreeRef, data: buffer.join("") }); buffer.length = 0; await writeObject(structTreeRootRef, structTreeRoot, buffer, xref); newRefs.push({ ref: structTreeRootRef, data: buffer.join("") }); } static async #writeKids({ newAnnotationsByPage, structTreeRootRef, kids, nums, xref, pdfManager, newRefs, buffer }) { const objr = Name.get("OBJR"); let nextKey = -Infinity; for (const [pageIndex, elements] of newAnnotationsByPage) { const { ref: pageRef } = await pdfManager.getPage(pageIndex); const isPageRef = pageRef instanceof Ref; for (const { accessibilityData, ref, parentTreeId, structTreeParent } of elements) { if (!accessibilityData?.type) { continue; } const { type, title, lang, alt, expanded, actualText } = accessibilityData; nextKey = Math.max(nextKey, parentTreeId); const tagRef = xref.getNewTemporaryRef(); const tagDict = new Dict(xref); tagDict.set("S", Name.get(type)); if (title) { tagDict.set("T", title); } if (lang) { tagDict.set("Lang", lang); } if (alt) { tagDict.set("Alt", alt); } if (expanded) { tagDict.set("E", expanded); } if (actualText) { tagDict.set("ActualText", actualText); } if (structTreeParent) { await this.#updateParentTag({ structTreeParent, tagDict, newTagRef: tagRef, fallbackRef: structTreeRootRef, xref, newRefs, buffer }); } else { tagDict.set("P", structTreeRootRef); } const objDict = new Dict(xref); tagDict.set("K", objDict); objDict.set("Type", objr); if (isPageRef) { objDict.set("Pg", pageRef); } objDict.set("Obj", ref); buffer.length = 0; await writeObject(tagRef, tagDict, buffer, xref); newRefs.push({ ref: tagRef, data: buffer.join("") }); nums.push(parentTreeId, tagRef); kids.push(tagRef); } } return nextKey + 1; } static #collectParents({ elements, xref, pageDict, numberTree }) { const idToElement = new Map(); for (const element of elements) { if (element.structTreeParentId) { const id = parseInt(element.structTreeParentId.split("_mc")[1], 10); idToElement.set(id, element); } } const id = pageDict.get("StructParents"); if (!Number.isInteger(id)) { return; } const parentArray = numberTree.get(id); const updateElement = (kid, pageKid, kidRef) => { const element = idToElement.get(kid); if (element) { const parentRef = pageKid.getRaw("P"); const parentDict = xref.fetchIfRef(parentRef); if (parentRef instanceof Ref && parentDict instanceof Dict) { element.structTreeParent = { ref: kidRef, dict: pageKid }; } return true; } return false; }; for (const kidRef of parentArray) { if (!(kidRef instanceof Ref)) { continue; } const pageKid = xref.fetch(kidRef); const k = pageKid.get("K"); if (Number.isInteger(k)) { updateElement(k, pageKid, kidRef); continue; } if (!Array.isArray(k)) { continue; } for (let kid of k) { kid = xref.fetchIfRef(kid); if (Number.isInteger(kid) && updateElement(kid, pageKid, kidRef)) { break; } } } } static async #updateParentTag({ structTreeParent: { ref, dict }, tagDict, newTagRef, fallbackRef, xref, newRefs, buffer }) { const parentRef = dict.getRaw("P"); let parentDict = xref.fetchIfRef(parentRef); tagDict.set("P", parentRef); let saveParentDict = false; let parentKids; let parentKidsRef = parentDict.getRaw("K"); if (!(parentKidsRef instanceof Ref)) { parentKids = parentKidsRef; parentKidsRef = xref.getNewTemporaryRef(); parentDict = parentDict.clone(); parentDict.set("K", parentKidsRef); saveParentDict = true; } else { parentKids = xref.fetch(parentKidsRef); } if (Array.isArray(parentKids)) { const index = parentKids.indexOf(ref); if (index >= 0) { parentKids = parentKids.slice(); parentKids.splice(index + 1, 0, newTagRef); } else { warn("Cannot update the struct tree: parent kid not found."); tagDict.set("P", fallbackRef); return; } } else if (parentKids instanceof Dict) { parentKids = [parentKidsRef, newTagRef]; parentKidsRef = xref.getNewTemporaryRef(); parentDict.set("K", parentKidsRef); saveParentDict = true; } buffer.length = 0; await writeObject(parentKidsRef, parentKids, buffer, xref); newRefs.push({ ref: parentKidsRef, data: buffer.join("") }); if (!saveParentDict) { return; } buffer.length = 0; await writeObject(parentRef, parentDict, buffer, xref); newRefs.push({ ref: parentRef, data: buffer.join("") }); } } class StructElementNode { constructor(tree, dict) { this.tree = tree; this.dict = dict; this.kids = []; this.parseKids(); } get role() { const nameObj = this.dict.get("S"); const name = nameObj instanceof Name ? nameObj.name : ""; const { root } = this.tree; if (root.roleMap.has(name)) { return root.roleMap.get(name); } return name; } parseKids() { let pageObjId = null; const objRef = this.dict.getRaw("Pg"); if (objRef instanceof Ref) { pageObjId = objRef.toString(); } const kids = this.dict.get("K"); if (Array.isArray(kids)) { for (const kid of kids) { const element = this.parseKid(pageObjId, kid); if (element) { this.kids.push(element); } } } else { const element = this.parseKid(pageObjId, kids); if (element) { this.kids.push(element); } } } parseKid(pageObjId, kid) { if (Number.isInteger(kid)) { if (this.tree.pageDict.objId !== pageObjId) { return null; } return new StructElement({ type: StructElementType.PAGE_CONTENT, mcid: kid, pageObjId }); } let kidDict = null; if (kid instanceof Ref) { kidDict = this.dict.xref.fetch(kid); } else if (kid instanceof Dict) { kidDict = kid; } if (!kidDict) { return null; } const pageRef = kidDict.getRaw("Pg"); if (pageRef instanceof Ref) { pageObjId = pageRef.toString(); } const type = kidDict.get("Type") instanceof Name ? kidDict.get("Type").name : null; if (type === "MCR") { if (this.tree.pageDict.objId !== pageObjId) { return null; } const kidRef = kidDict.getRaw("Stm"); return new StructElement({ type: StructElementType.STREAM_CONTENT, refObjId: kidRef instanceof Ref ? kidRef.toString() : null, pageObjId, mcid: kidDict.get("MCID") }); } if (type === "OBJR") { if (this.tree.pageDict.objId !== pageObjId) { return null; } const kidRef = kidDict.getRaw("Obj"); return new StructElement({ type: StructElementType.OBJECT, refObjId: kidRef instanceof Ref ? kidRef.toString() : null, pageObjId }); } return new StructElement({ type: StructElementType.ELEMENT, dict: kidDict }); } } class StructElement { constructor({ type, dict = null, mcid = null, pageObjId = null, refObjId = null }) { this.type = type; this.dict = dict; this.mcid = mcid; this.pageObjId = pageObjId; this.refObjId = refObjId; this.parentNode = null; } } class StructTreePage { constructor(structTreeRoot, pageDict) { this.root = structTreeRoot; this.rootDict = structTreeRoot ? structTreeRoot.dict : null; this.pageDict = pageDict; this.nodes = []; } parse(pageRef) { if (!this.root || !this.rootDict) { return; } const parentTree = this.rootDict.get("ParentTree"); if (!parentTree) { return; } const id = this.pageDict.get("StructParents"); const ids = pageRef instanceof Ref && this.root.structParentIds?.get(pageRef); if (!Number.isInteger(id) && !ids) { return; } const map = new Map(); const numberTree = new NumberTree(parentTree, this.rootDict.xref); if (Number.isInteger(id)) { const parentArray = numberTree.get(id); if (Array.isArray(parentArray)) { for (const ref of parentArray) { if (ref instanceof Ref) { this.addNode(this.rootDict.xref.fetch(ref), map); } } } } if (!ids) { return; } for (const [elemId, type] of ids) { const obj = numberTree.get(elemId); if (obj) { const elem = this.addNode(this.rootDict.xref.fetchIfRef(obj), map); if (elem?.kids?.length === 1 && elem.kids[0].type === StructElementType.OBJECT) { elem.kids[0].type = type; } } } } addNode(dict, map, level = 0) { if (level > MAX_DEPTH) { warn("StructTree MAX_DEPTH reached."); return null; } if (map.has(dict)) { return map.get(dict); } const element = new StructElementNode(this, dict); map.set(dict, element); const parent = dict.get("P"); if (!parent || isName(parent.get("Type"), "StructTreeRoot")) { if (!this.addTopLevelNode(dict, element)) { map.delete(dict); } return element; } const parentNode = this.addNode(parent, map, level + 1); if (!parentNode) { return element; } let save = false; for (const kid of parentNode.kids) { if (kid.type === StructElementType.ELEMENT && kid.dict === dict) { kid.parentNode = element; save = true; } } if (!save) { map.delete(dict); } return element; } addTopLevelNode(dict, element) { const obj = this.rootDict.get("K"); if (!obj) { return false; } if (obj instanceof Dict) { if (obj.objId !== dict.objId) { return false; } this.nodes[0] = element; return true; } if (!Array.isArray(obj)) { return true; } let save = false; for (let i = 0; i < obj.length; i++) { const kidRef = obj[i]; if (kidRef?.toString() === dict.objId) { this.nodes[i] = element; save = true; } } return save; } get serializable() { function nodeToSerializable(node, parent, level = 0) { if (level > MAX_DEPTH) { warn("StructTree too deep to be fully serialized."); return; } const obj = Object.create(null); obj.role = node.role; obj.children = []; parent.children.push(obj); const alt = node.dict.get("Alt"); if (typeof alt === "string") { obj.alt = stringToPDFString(alt); } const lang = node.dict.get("Lang"); if (typeof lang === "string") { obj.lang = stringToPDFString(lang); } for (const kid of node.kids) { const kidElement = kid.type === StructElementType.ELEMENT ? kid.parentNode : null; if (kidElement) { nodeToSerializable(kidElement, obj, level + 1); continue; } else if (kid.type === StructElementType.PAGE_CONTENT || kid.type === StructElementType.STREAM_CONTENT) { obj.children.push({ type: "content", id: `p${kid.pageObjId}_mc${kid.mcid}` }); } else if (kid.type === StructElementType.OBJECT) { obj.children.push({ type: "object", id: kid.refObjId }); } else if (kid.type === StructElementType.ANNOTATION) { obj.children.push({ type: "annotation", id: `${AnnotationPrefix}${kid.refObjId}` }); } } } const root = Object.create(null); root.children = []; root.role = "Root"; for (const child of this.nodes) { if (!child) { continue; } nodeToSerializable(child, root); } return root; } } ;// CONCATENATED MODULE: ./src/core/catalog.js function fetchDestination(dest) { if (dest instanceof Dict) { dest = dest.get("D"); } return Array.isArray(dest) ? dest : null; } function fetchRemoteDest(action) { let dest = action.get("D"); if (dest) { if (dest instanceof Name) { dest = dest.name; } if (typeof dest === "string") { return stringToPDFString(dest); } else if (Array.isArray(dest)) { return JSON.stringify(dest); } } return null; } class Catalog { constructor(pdfManager, xref) { this.pdfManager = pdfManager; this.xref = xref; this._catDict = xref.getCatalogObj(); if (!(this._catDict instanceof Dict)) { throw new FormatError("Catalog object is not a dictionary."); } this.toplevelPagesDict; this._actualNumPages = null; this.fontCache = new RefSetCache(); this.builtInCMapCache = new Map(); this.standardFontDataCache = new Map(); this.globalImageCache = new GlobalImageCache(); this.pageKidsCountCache = new RefSetCache(); this.pageIndexCache = new RefSetCache(); this.nonBlendModesSet = new RefSet(); this.systemFontCache = new Map(); } cloneDict() { return this._catDict.clone(); } get version() { const version = this._catDict.get("Version"); if (version instanceof Name) { if (PDF_VERSION_REGEXP.test(version.name)) { return shadow(this, "version", version.name); } warn(`Invalid PDF catalog version: ${version.name}`); } return shadow(this, "version", null); } get lang() { const lang = this._catDict.get("Lang"); return shadow(this, "lang", typeof lang === "string" ? stringToPDFString(lang) : null); } get needsRendering() { const needsRendering = this._catDict.get("NeedsRendering"); return shadow(this, "needsRendering", typeof needsRendering === "boolean" ? needsRendering : false); } get collection() { let collection = null; try { const obj = this._catDict.get("Collection"); if (obj instanceof Dict && obj.size > 0) { collection = obj; } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } info("Cannot fetch Collection entry; assuming no collection is present."); } return shadow(this, "collection", collection); } get acroForm() { let acroForm = null; try { const obj = this._catDict.get("AcroForm"); if (obj instanceof Dict && obj.size > 0) { acroForm = obj; } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } info("Cannot fetch AcroForm entry; assuming no forms are present."); } return shadow(this, "acroForm", acroForm); } get acroFormRef() { const value = this._catDict.getRaw("AcroForm"); return shadow(this, "acroFormRef", value instanceof Ref ? value : null); } get metadata() { const streamRef = this._catDict.getRaw("Metadata"); if (!(streamRef instanceof Ref)) { return shadow(this, "metadata", null); } let metadata = null; try { const stream = this.xref.fetch(streamRef, !this.xref.encrypt?.encryptMetadata); if (stream instanceof BaseStream && stream.dict instanceof Dict) { const type = stream.dict.get("Type"); const subtype = stream.dict.get("Subtype"); if (isName(type, "Metadata") && isName(subtype, "XML")) { const data = stringToUTF8String(stream.getString()); if (data) { metadata = new MetadataParser(data).serializable; } } } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } info(`Skipping invalid Metadata: "${ex}".`); } return shadow(this, "metadata", metadata); } get markInfo() { let markInfo = null; try { markInfo = this._readMarkInfo(); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Unable to read mark info."); } return shadow(this, "markInfo", markInfo); } _readMarkInfo() { const obj = this._catDict.get("MarkInfo"); if (!(obj instanceof Dict)) { return null; } const markInfo = { Marked: false, UserProperties: false, Suspects: false }; for (const key in markInfo) { const value = obj.get(key); if (typeof value === "boolean") { markInfo[key] = value; } } return markInfo; } get structTreeRoot() { let structTree = null; try { structTree = this._readStructTreeRoot(); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Unable read to structTreeRoot info."); } return shadow(this, "structTreeRoot", structTree); } _readStructTreeRoot() { const rawObj = this._catDict.getRaw("StructTreeRoot"); const obj = this.xref.fetchIfRef(rawObj); if (!(obj instanceof Dict)) { return null; } const root = new StructTreeRoot(obj, rawObj); root.init(); return root; } get toplevelPagesDict() { const pagesObj = this._catDict.get("Pages"); if (!(pagesObj instanceof Dict)) { throw new FormatError("Invalid top-level pages dictionary."); } return shadow(this, "toplevelPagesDict", pagesObj); } get documentOutline() { let obj = null; try { obj = this._readDocumentOutline(); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Unable to read document outline."); } return shadow(this, "documentOutline", obj); } _readDocumentOutline() { let obj = this._catDict.get("Outlines"); if (!(obj instanceof Dict)) { return null; } obj = obj.getRaw("First"); if (!(obj instanceof Ref)) { return null; } const root = { items: [] }; const queue = [{ obj, parent: root }]; const processed = new RefSet(); processed.put(obj); const xref = this.xref, blackColor = new Uint8ClampedArray(3); while (queue.length > 0) { const i = queue.shift(); const outlineDict = xref.fetchIfRef(i.obj); if (outlineDict === null) { continue; } if (!outlineDict.has("Title")) { throw new FormatError("Invalid outline item encountered."); } const data = { url: null, dest: null, action: null }; Catalog.parseDestDictionary({ destDict: outlineDict, resultObj: data, docBaseUrl: this.baseUrl, docAttachments: this.attachments }); const title = outlineDict.get("Title"); const flags = outlineDict.get("F") || 0; const color = outlineDict.getArray("C"); const count = outlineDict.get("Count"); let rgbColor = blackColor; if (Array.isArray(color) && color.length === 3 && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) { rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); } const outlineItem = { action: data.action, attachment: data.attachment, dest: data.dest, url: data.url, unsafeUrl: data.unsafeUrl, newWindow: data.newWindow, setOCGState: data.setOCGState, title: stringToPDFString(title), color: rgbColor, count: Number.isInteger(count) ? count : undefined, bold: !!(flags & 2), italic: !!(flags & 1), items: [] }; i.parent.items.push(outlineItem); obj = outlineDict.getRaw("First"); if (obj instanceof Ref && !processed.has(obj)) { queue.push({ obj, parent: outlineItem }); processed.put(obj); } obj = outlineDict.getRaw("Next"); if (obj instanceof Ref && !processed.has(obj)) { queue.push({ obj, parent: i.parent }); processed.put(obj); } } return root.items.length > 0 ? root.items : null; } get permissions() { let permissions = null; try { permissions = this._readPermissions(); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Unable to read permissions."); } return shadow(this, "permissions", permissions); } _readPermissions() { const encrypt = this.xref.trailer.get("Encrypt"); if (!(encrypt instanceof Dict)) { return null; } let flags = encrypt.get("P"); if (typeof flags !== "number") { return null; } flags += 2 ** 32; const permissions = []; for (const key in PermissionFlag) { const value = PermissionFlag[key]; if (flags & value) { permissions.push(value); } } return permissions; } get optionalContentConfig() { let config = null; try { const properties = this._catDict.get("OCProperties"); if (!properties) { return shadow(this, "optionalContentConfig", null); } const defaultConfig = properties.get("D"); if (!defaultConfig) { return shadow(this, "optionalContentConfig", null); } const groupsData = properties.get("OCGs"); if (!Array.isArray(groupsData)) { return shadow(this, "optionalContentConfig", null); } const groups = []; const groupRefs = new RefSet(); for (const groupRef of groupsData) { if (!(groupRef instanceof Ref) || groupRefs.has(groupRef)) { continue; } groupRefs.put(groupRef); const group = this.xref.fetch(groupRef); groups.push({ id: groupRef.toString(), name: typeof group.get("Name") === "string" ? stringToPDFString(group.get("Name")) : null, intent: typeof group.get("Intent") === "string" ? stringToPDFString(group.get("Intent")) : null }); } config = this._readOptionalContentConfig(defaultConfig, groupRefs); config.groups = groups; } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`Unable to read optional content config: ${ex}`); } return shadow(this, "optionalContentConfig", config); } _readOptionalContentConfig(config, contentGroupRefs) { function parseOnOff(refs) { const onParsed = []; if (Array.isArray(refs)) { for (const value of refs) { if (!(value instanceof Ref)) { continue; } if (contentGroupRefs.has(value)) { onParsed.push(value.toString()); } } } return onParsed; } function parseOrder(refs, nestedLevels = 0) { if (!Array.isArray(refs)) { return null; } const order = []; for (const value of refs) { if (value instanceof Ref && contentGroupRefs.has(value)) { parsedOrderRefs.put(value); order.push(value.toString()); continue; } const nestedOrder = parseNestedOrder(value, nestedLevels); if (nestedOrder) { order.push(nestedOrder); } } if (nestedLevels > 0) { return order; } const hiddenGroups = []; for (const groupRef of contentGroupRefs) { if (parsedOrderRefs.has(groupRef)) { continue; } hiddenGroups.push(groupRef.toString()); } if (hiddenGroups.length) { order.push({ name: null, order: hiddenGroups }); } return order; } function parseNestedOrder(ref, nestedLevels) { if (++nestedLevels > MAX_NESTED_LEVELS) { warn("parseNestedOrder - reached MAX_NESTED_LEVELS."); return null; } const value = xref.fetchIfRef(ref); if (!Array.isArray(value)) { return null; } const nestedName = xref.fetchIfRef(value[0]); if (typeof nestedName !== "string") { return null; } const nestedOrder = parseOrder(value.slice(1), nestedLevels); if (!nestedOrder || !nestedOrder.length) { return null; } return { name: stringToPDFString(nestedName), order: nestedOrder }; } const xref = this.xref, parsedOrderRefs = new RefSet(), MAX_NESTED_LEVELS = 10; return { name: typeof config.get("Name") === "string" ? stringToPDFString(config.get("Name")) : null, creator: typeof config.get("Creator") === "string" ? stringToPDFString(config.get("Creator")) : null, baseState: config.get("BaseState") instanceof Name ? config.get("BaseState").name : null, on: parseOnOff(config.get("ON")), off: parseOnOff(config.get("OFF")), order: parseOrder(config.get("Order")), groups: null }; } setActualNumPages(num = null) { this._actualNumPages = num; } get hasActualNumPages() { return this._actualNumPages !== null; } get _pagesCount() { const obj = this.toplevelPagesDict.get("Count"); if (!Number.isInteger(obj)) { throw new FormatError("Page count in top-level pages dictionary is not an integer."); } return shadow(this, "_pagesCount", obj); } get numPages() { return this.hasActualNumPages ? this._actualNumPages : this._pagesCount; } get destinations() { const obj = this._readDests(), dests = Object.create(null); if (obj instanceof NameTree) { for (const [key, value] of obj.getAll()) { const dest = fetchDestination(value); if (dest) { dests[stringToPDFString(key)] = dest; } } } else if (obj instanceof Dict) { obj.forEach(function (key, value) { const dest = fetchDestination(value); if (dest) { dests[key] = dest; } }); } return shadow(this, "destinations", dests); } getDestination(id) { const obj = this._readDests(); if (obj instanceof NameTree) { const dest = fetchDestination(obj.get(id)); if (dest) { return dest; } const allDest = this.destinations[id]; if (allDest) { warn(`Found "${id}" at an incorrect position in the NameTree.`); return allDest; } } else if (obj instanceof Dict) { const dest = fetchDestination(obj.get(id)); if (dest) { return dest; } } return null; } _readDests() { const obj = this._catDict.get("Names"); if (obj?.has("Dests")) { return new NameTree(obj.getRaw("Dests"), this.xref); } else if (this._catDict.has("Dests")) { return this._catDict.get("Dests"); } return undefined; } get pageLabels() { let obj = null; try { obj = this._readPageLabels(); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn("Unable to read page labels."); } return shadow(this, "pageLabels", obj); } _readPageLabels() { const obj = this._catDict.getRaw("PageLabels"); if (!obj) { return null; } const pageLabels = new Array(this.numPages); let style = null, prefix = ""; const numberTree = new NumberTree(obj, this.xref); const nums = numberTree.getAll(); let currentLabel = "", currentIndex = 1; for (let i = 0, ii = this.numPages; i < ii; i++) { const labelDict = nums.get(i); if (labelDict !== undefined) { if (!(labelDict instanceof Dict)) { throw new FormatError("PageLabel is not a dictionary."); } if (labelDict.has("Type") && !isName(labelDict.get("Type"), "PageLabel")) { throw new FormatError("Invalid type in PageLabel dictionary."); } if (labelDict.has("S")) { const s = labelDict.get("S"); if (!(s instanceof Name)) { throw new FormatError("Invalid style in PageLabel dictionary."); } style = s.name; } else { style = null; } if (labelDict.has("P")) { const p = labelDict.get("P"); if (typeof p !== "string") { throw new FormatError("Invalid prefix in PageLabel dictionary."); } prefix = stringToPDFString(p); } else { prefix = ""; } if (labelDict.has("St")) { const st = labelDict.get("St"); if (!(Number.isInteger(st) && st >= 1)) { throw new FormatError("Invalid start in PageLabel dictionary."); } currentIndex = st; } else { currentIndex = 1; } } switch (style) { case "D": currentLabel = currentIndex; break; case "R": case "r": currentLabel = toRomanNumerals(currentIndex, style === "r"); break; case "A": case "a": const LIMIT = 26; const A_UPPER_CASE = 0x41, A_LOWER_CASE = 0x61; const baseCharCode = style === "a" ? A_LOWER_CASE : A_UPPER_CASE; const letterIndex = currentIndex - 1; const character = String.fromCharCode(baseCharCode + letterIndex % LIMIT); currentLabel = character.repeat(Math.floor(letterIndex / LIMIT) + 1); break; default: if (style) { throw new FormatError(`Invalid style "${style}" in PageLabel dictionary.`); } currentLabel = ""; } pageLabels[i] = prefix + currentLabel; currentIndex++; } return pageLabels; } get pageLayout() { const obj = this._catDict.get("PageLayout"); let pageLayout = ""; if (obj instanceof Name) { switch (obj.name) { case "SinglePage": case "OneColumn": case "TwoColumnLeft": case "TwoColumnRight": case "TwoPageLeft": case "TwoPageRight": pageLayout = obj.name; } } return shadow(this, "pageLayout", pageLayout); } get pageMode() { const obj = this._catDict.get("PageMode"); let pageMode = "UseNone"; if (obj instanceof Name) { switch (obj.name) { case "UseNone": case "UseOutlines": case "UseThumbs": case "FullScreen": case "UseOC": case "UseAttachments": pageMode = obj.name; } } return shadow(this, "pageMode", pageMode); } get viewerPreferences() { const obj = this._catDict.get("ViewerPreferences"); if (!(obj instanceof Dict)) { return shadow(this, "viewerPreferences", null); } let prefs = null; for (const key of obj.getKeys()) { const value = obj.get(key); let prefValue; switch (key) { case "HideToolbar": case "HideMenubar": case "HideWindowUI": case "FitWindow": case "CenterWindow": case "DisplayDocTitle": case "PickTrayByPDFSize": if (typeof value === "boolean") { prefValue = value; } break; case "NonFullScreenPageMode": if (value instanceof Name) { switch (value.name) { case "UseNone": case "UseOutlines": case "UseThumbs": case "UseOC": prefValue = value.name; break; default: prefValue = "UseNone"; } } break; case "Direction": if (value instanceof Name) { switch (value.name) { case "L2R": case "R2L": prefValue = value.name; break; default: prefValue = "L2R"; } } break; case "ViewArea": case "ViewClip": case "PrintArea": case "PrintClip": if (value instanceof Name) { switch (value.name) { case "MediaBox": case "CropBox": case "BleedBox": case "TrimBox": case "ArtBox": prefValue = value.name; break; default: prefValue = "CropBox"; } } break; case "PrintScaling": if (value instanceof Name) { switch (value.name) { case "None": case "AppDefault": prefValue = value.name; break; default: prefValue = "AppDefault"; } } break; case "Duplex": if (value instanceof Name) { switch (value.name) { case "Simplex": case "DuplexFlipShortEdge": case "DuplexFlipLongEdge": prefValue = value.name; break; default: prefValue = "None"; } } break; case "PrintPageRange": if (Array.isArray(value) && value.length % 2 === 0) { const isValid = value.every((page, i, arr) => { return Number.isInteger(page) && page > 0 && (i === 0 || page >= arr[i - 1]) && page <= this.numPages; }); if (isValid) { prefValue = value; } } break; case "NumCopies": if (Number.isInteger(value) && value > 0) { prefValue = value; } break; default: warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`); continue; } if (prefValue === undefined) { warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`); continue; } if (!prefs) { prefs = Object.create(null); } prefs[key] = prefValue; } return shadow(this, "viewerPreferences", prefs); } get openAction() { const obj = this._catDict.get("OpenAction"); const openAction = Object.create(null); if (obj instanceof Dict) { const destDict = new Dict(this.xref); destDict.set("A", obj); const resultObj = { url: null, dest: null, action: null }; Catalog.parseDestDictionary({ destDict, resultObj }); if (Array.isArray(resultObj.dest)) { openAction.dest = resultObj.dest; } else if (resultObj.action) { openAction.action = resultObj.action; } } else if (Array.isArray(obj)) { openAction.dest = obj; } return shadow(this, "openAction", objectSize(openAction) > 0 ? openAction : null); } get attachments() { const obj = this._catDict.get("Names"); let attachments = null; if (obj instanceof Dict && obj.has("EmbeddedFiles")) { const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref); for (const [key, value] of nameTree.getAll()) { const fs = new FileSpec(value, this.xref); if (!attachments) { attachments = Object.create(null); } attachments[stringToPDFString(key)] = fs.serializable; } } return shadow(this, "attachments", attachments); } get xfaImages() { const obj = this._catDict.get("Names"); let xfaImages = null; if (obj instanceof Dict && obj.has("XFAImages")) { const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref); for (const [key, value] of nameTree.getAll()) { if (!xfaImages) { xfaImages = new Dict(this.xref); } xfaImages.set(stringToPDFString(key), value); } } return shadow(this, "xfaImages", xfaImages); } _collectJavaScript() { const obj = this._catDict.get("Names"); let javaScript = null; function appendIfJavaScriptDict(name, jsDict) { if (!(jsDict instanceof Dict)) { return; } if (!isName(jsDict.get("S"), "JavaScript")) { return; } let js = jsDict.get("JS"); if (js instanceof BaseStream) { js = js.getString(); } else if (typeof js !== "string") { return; } js = stringToPDFString(js).replaceAll("\x00", ""); if (js) { (javaScript ||= new Map()).set(name, js); } } if (obj instanceof Dict && obj.has("JavaScript")) { const nameTree = new NameTree(obj.getRaw("JavaScript"), this.xref); for (const [key, value] of nameTree.getAll()) { appendIfJavaScriptDict(stringToPDFString(key), value); } } const openAction = this._catDict.get("OpenAction"); if (openAction) { appendIfJavaScriptDict("OpenAction", openAction); } return javaScript; } get jsActions() { const javaScript = this._collectJavaScript(); let actions = collectActions(this.xref, this._catDict, DocumentActionEventType); if (javaScript) { actions ||= Object.create(null); for (const [key, val] of javaScript) { if (key in actions) { actions[key].push(val); } else { actions[key] = [val]; } } } return shadow(this, "jsActions", actions); } async fontFallback(id, handler) { const translatedFonts = await Promise.all(this.fontCache); for (const translatedFont of translatedFonts) { if (translatedFont.loadedName === id) { translatedFont.fallback(handler); return; } } } async cleanup(manuallyTriggered = false) { clearGlobalCaches(); this.globalImageCache.clear(manuallyTriggered); this.pageKidsCountCache.clear(); this.pageIndexCache.clear(); this.nonBlendModesSet.clear(); const translatedFonts = await Promise.all(this.fontCache); for (const { dict } of translatedFonts) { delete dict.cacheKey; } this.fontCache.clear(); this.builtInCMapCache.clear(); this.standardFontDataCache.clear(); this.systemFontCache.clear(); } async getPageDict(pageIndex) { const nodesToVisit = [this.toplevelPagesDict]; const visitedNodes = new RefSet(); const pagesRef = this._catDict.getRaw("Pages"); if (pagesRef instanceof Ref) { visitedNodes.put(pagesRef); } const xref = this.xref, pageKidsCountCache = this.pageKidsCountCache, pageIndexCache = this.pageIndexCache; let currentPageIndex = 0; while (nodesToVisit.length) { const currentNode = nodesToVisit.pop(); if (currentNode instanceof Ref) { const count = pageKidsCountCache.get(currentNode); if (count >= 0 && currentPageIndex + count <= pageIndex) { currentPageIndex += count; continue; } if (visitedNodes.has(currentNode)) { throw new FormatError("Pages tree contains circular reference."); } visitedNodes.put(currentNode); const obj = await xref.fetchAsync(currentNode); if (obj instanceof Dict) { let type = obj.getRaw("Type"); if (type instanceof Ref) { type = await xref.fetchAsync(type); } if (isName(type, "Page") || !obj.has("Kids")) { if (!pageKidsCountCache.has(currentNode)) { pageKidsCountCache.put(currentNode, 1); } if (!pageIndexCache.has(currentNode)) { pageIndexCache.put(currentNode, currentPageIndex); } if (currentPageIndex === pageIndex) { return [obj, currentNode]; } currentPageIndex++; continue; } } nodesToVisit.push(obj); continue; } if (!(currentNode instanceof Dict)) { throw new FormatError("Page dictionary kid reference points to wrong type of object."); } const { objId } = currentNode; let count = currentNode.getRaw("Count"); if (count instanceof Ref) { count = await xref.fetchAsync(count); } if (Number.isInteger(count) && count >= 0) { if (objId && !pageKidsCountCache.has(objId)) { pageKidsCountCache.put(objId, count); } if (currentPageIndex + count <= pageIndex) { currentPageIndex += count; continue; } } let kids = currentNode.getRaw("Kids"); if (kids instanceof Ref) { kids = await xref.fetchAsync(kids); } if (!Array.isArray(kids)) { let type = currentNode.getRaw("Type"); if (type instanceof Ref) { type = await xref.fetchAsync(type); } if (isName(type, "Page") || !currentNode.has("Kids")) { if (currentPageIndex === pageIndex) { return [currentNode, null]; } currentPageIndex++; continue; } throw new FormatError("Page dictionary kids object is not an array."); } for (let last = kids.length - 1; last >= 0; last--) { nodesToVisit.push(kids[last]); } } throw new Error(`Page index ${pageIndex} not found.`); } async getAllPageDicts(recoveryMode = false) { const { ignoreErrors } = this.pdfManager.evaluatorOptions; const queue = [{ currentNode: this.toplevelPagesDict, posInKids: 0 }]; const visitedNodes = new RefSet(); const pagesRef = this._catDict.getRaw("Pages"); if (pagesRef instanceof Ref) { visitedNodes.put(pagesRef); } const map = new Map(), xref = this.xref, pageIndexCache = this.pageIndexCache; let pageIndex = 0; function addPageDict(pageDict, pageRef) { if (pageRef && !pageIndexCache.has(pageRef)) { pageIndexCache.put(pageRef, pageIndex); } map.set(pageIndex++, [pageDict, pageRef]); } function addPageError(error) { if (error instanceof XRefEntryException && !recoveryMode) { throw error; } if (recoveryMode && ignoreErrors && pageIndex === 0) { warn(`getAllPageDicts - Skipping invalid first page: "${error}".`); error = Dict.empty; } map.set(pageIndex++, [error, null]); } while (queue.length > 0) { const queueItem = queue.at(-1); const { currentNode, posInKids } = queueItem; let kids = currentNode.getRaw("Kids"); if (kids instanceof Ref) { try { kids = await xref.fetchAsync(kids); } catch (ex) { addPageError(ex); break; } } if (!Array.isArray(kids)) { addPageError(new FormatError("Page dictionary kids object is not an array.")); break; } if (posInKids >= kids.length) { queue.pop(); continue; } const kidObj = kids[posInKids]; let obj; if (kidObj instanceof Ref) { if (visitedNodes.has(kidObj)) { addPageError(new FormatError("Pages tree contains circular reference.")); break; } visitedNodes.put(kidObj); try { obj = await xref.fetchAsync(kidObj); } catch (ex) { addPageError(ex); break; } } else { obj = kidObj; } if (!(obj instanceof Dict)) { addPageError(new FormatError("Page dictionary kid reference points to wrong type of object.")); break; } let type = obj.getRaw("Type"); if (type instanceof Ref) { try { type = await xref.fetchAsync(type); } catch (ex) { addPageError(ex); break; } } if (isName(type, "Page") || !obj.has("Kids")) { addPageDict(obj, kidObj instanceof Ref ? kidObj : null); } else { queue.push({ currentNode: obj, posInKids: 0 }); } queueItem.posInKids++; } return map; } getPageIndex(pageRef) { const cachedPageIndex = this.pageIndexCache.get(pageRef); if (cachedPageIndex !== undefined) { return Promise.resolve(cachedPageIndex); } const xref = this.xref; function pagesBeforeRef(kidRef) { let total = 0, parentRef; return xref.fetchAsync(kidRef).then(function (node) { if (isRefsEqual(kidRef, pageRef) && !isDict(node, "Page") && !(node instanceof Dict && !node.has("Type") && node.has("Contents"))) { throw new FormatError("The reference does not point to a /Page dictionary."); } if (!node) { return null; } if (!(node instanceof Dict)) { throw new FormatError("Node must be a dictionary."); } parentRef = node.getRaw("Parent"); return node.getAsync("Parent"); }).then(function (parent) { if (!parent) { return null; } if (!(parent instanceof Dict)) { throw new FormatError("Parent must be a dictionary."); } return parent.getAsync("Kids"); }).then(function (kids) { if (!kids) { return null; } const kidPromises = []; let found = false; for (const kid of kids) { if (!(kid instanceof Ref)) { throw new FormatError("Kid must be a reference."); } if (isRefsEqual(kid, kidRef)) { found = true; break; } kidPromises.push(xref.fetchAsync(kid).then(function (obj) { if (!(obj instanceof Dict)) { throw new FormatError("Kid node must be a dictionary."); } if (obj.has("Count")) { total += obj.get("Count"); } else { total++; } })); } if (!found) { throw new FormatError("Kid reference not found in parent's kids."); } return Promise.all(kidPromises).then(function () { return [total, parentRef]; }); }); } let total = 0; const next = ref => pagesBeforeRef(ref).then(args => { if (!args) { this.pageIndexCache.put(pageRef, total); return total; } const [count, parentRef] = args; total += count; return next(parentRef); }); return next(pageRef); } get baseUrl() { const uri = this._catDict.get("URI"); if (uri instanceof Dict) { const base = uri.get("Base"); if (typeof base === "string") { const absoluteUrl = createValidAbsoluteUrl(base, null, { tryConvertEncoding: true }); if (absoluteUrl) { return shadow(this, "baseUrl", absoluteUrl.href); } } } return shadow(this, "baseUrl", this.pdfManager.docBaseUrl); } static parseDestDictionary({ destDict, resultObj, docBaseUrl = null, docAttachments = null }) { if (!(destDict instanceof Dict)) { warn("parseDestDictionary: `destDict` must be a dictionary."); return; } let action = destDict.get("A"), url, dest; if (!(action instanceof Dict)) { if (destDict.has("Dest")) { action = destDict.get("Dest"); } else { action = destDict.get("AA"); if (action instanceof Dict) { if (action.has("D")) { action = action.get("D"); } else if (action.has("U")) { action = action.get("U"); } } } } if (action instanceof Dict) { const actionType = action.get("S"); if (!(actionType instanceof Name)) { warn("parseDestDictionary: Invalid type in Action dictionary."); return; } const actionName = actionType.name; switch (actionName) { case "ResetForm": const flags = action.get("Flags"); const include = ((typeof flags === "number" ? flags : 0) & 1) === 0; const fields = []; const refs = []; for (const obj of action.get("Fields") || []) { if (obj instanceof Ref) { refs.push(obj.toString()); } else if (typeof obj === "string") { fields.push(stringToPDFString(obj)); } } resultObj.resetForm = { fields, refs, include }; break; case "URI": url = action.get("URI"); if (url instanceof Name) { url = "/" + url.name; } break; case "GoTo": dest = action.get("D"); break; case "Launch": case "GoToR": const urlDict = action.get("F"); if (urlDict instanceof Dict) { url = urlDict.get("F") || null; } else if (typeof urlDict === "string") { url = urlDict; } const remoteDest = fetchRemoteDest(action); if (remoteDest && typeof url === "string") { url = url.split("#", 1)[0] + "#" + remoteDest; } const newWindow = action.get("NewWindow"); if (typeof newWindow === "boolean") { resultObj.newWindow = newWindow; } break; case "GoToE": const target = action.get("T"); let attachment; if (docAttachments && target instanceof Dict) { const relationship = target.get("R"); const name = target.get("N"); if (isName(relationship, "C") && typeof name === "string") { attachment = docAttachments[stringToPDFString(name)]; } } if (attachment) { resultObj.attachment = attachment; const attachmentDest = fetchRemoteDest(action); if (attachmentDest) { resultObj.attachmentDest = attachmentDest; } } else { warn(`parseDestDictionary - unimplemented "GoToE" action.`); } break; case "Named": const namedAction = action.get("N"); if (namedAction instanceof Name) { resultObj.action = namedAction.name; } break; case "SetOCGState": const state = action.get("State"); const preserveRB = action.get("PreserveRB"); if (!Array.isArray(state) || state.length === 0) { break; } const stateArr = []; for (const elem of state) { if (elem instanceof Name) { switch (elem.name) { case "ON": case "OFF": case "Toggle": stateArr.push(elem.name); break; } } else if (elem instanceof Ref) { stateArr.push(elem.toString()); } } if (stateArr.length !== state.length) { break; } resultObj.setOCGState = { state: stateArr, preserveRB: typeof preserveRB === "boolean" ? preserveRB : true }; break; case "JavaScript": const jsAction = action.get("JS"); let js; if (jsAction instanceof BaseStream) { js = jsAction.getString(); } else if (typeof jsAction === "string") { js = jsAction; } const jsURL = js && recoverJsURL(stringToPDFString(js)); if (jsURL) { url = jsURL.url; resultObj.newWindow = jsURL.newWindow; break; } default: if (actionName === "JavaScript" || actionName === "SubmitForm") { break; } warn(`parseDestDictionary - unsupported action: "${actionName}".`); break; } } else if (destDict.has("Dest")) { dest = destDict.get("Dest"); } if (typeof url === "string") { const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, { addDefaultProtocol: true, tryConvertEncoding: true }); if (absoluteUrl) { resultObj.url = absoluteUrl.href; } resultObj.unsafeUrl = url; } if (dest) { if (dest instanceof Name) { dest = dest.name; } if (typeof dest === "string") { resultObj.dest = stringToPDFString(dest); } else if (Array.isArray(dest)) { resultObj.dest = dest; } } } } ;// CONCATENATED MODULE: ./src/core/object_loader.js function mayHaveChildren(value) { return value instanceof Ref || value instanceof Dict || value instanceof BaseStream || Array.isArray(value); } function addChildren(node, nodesToVisit) { if (node instanceof Dict) { node = node.getRawValues(); } else if (node instanceof BaseStream) { node = node.dict.getRawValues(); } else if (!Array.isArray(node)) { return; } for (const rawValue of node) { if (mayHaveChildren(rawValue)) { nodesToVisit.push(rawValue); } } } class ObjectLoader { constructor(dict, keys, xref) { this.dict = dict; this.keys = keys; this.xref = xref; this.refSet = null; } async load() { if (this.xref.stream.isDataLoaded) { return undefined; } const { keys, dict } = this; this.refSet = new RefSet(); const nodesToVisit = []; for (const key of keys) { const rawValue = dict.getRaw(key); if (rawValue !== undefined) { nodesToVisit.push(rawValue); } } return this._walk(nodesToVisit); } async _walk(nodesToVisit) { const nodesToRevisit = []; const pendingRequests = []; while (nodesToVisit.length) { let currentNode = nodesToVisit.pop(); if (currentNode instanceof Ref) { if (this.refSet.has(currentNode)) { continue; } try { this.refSet.put(currentNode); currentNode = this.xref.fetch(currentNode); } catch (ex) { if (!(ex instanceof MissingDataException)) { warn(`ObjectLoader._walk - requesting all data: "${ex}".`); this.refSet = null; const { manager } = this.xref.stream; return manager.requestAllChunks(); } nodesToRevisit.push(currentNode); pendingRequests.push({ begin: ex.begin, end: ex.end }); } } if (currentNode instanceof BaseStream) { const baseStreams = currentNode.getBaseStreams(); if (baseStreams) { let foundMissingData = false; for (const stream of baseStreams) { if (stream.isDataLoaded) { continue; } foundMissingData = true; pendingRequests.push({ begin: stream.start, end: stream.end }); } if (foundMissingData) { nodesToRevisit.push(currentNode); } } } addChildren(currentNode, nodesToVisit); } if (pendingRequests.length) { await this.xref.stream.manager.requestRanges(pendingRequests); for (const node of nodesToRevisit) { if (node instanceof Ref) { this.refSet.remove(node); } } return this._walk(nodesToRevisit); } this.refSet = null; return undefined; } } ;// CONCATENATED MODULE: ./src/core/xfa/symbol_utils.js const $acceptWhitespace = Symbol(); const $addHTML = Symbol(); const $appendChild = Symbol(); const $childrenToHTML = Symbol(); const $clean = Symbol(); const $cleanPage = Symbol(); const $cleanup = Symbol(); const $clone = Symbol(); const $consumed = Symbol(); const $content = Symbol("content"); const $data = Symbol("data"); const $dump = Symbol(); const $extra = Symbol("extra"); const $finalize = Symbol(); const $flushHTML = Symbol(); const $getAttributeIt = Symbol(); const $getAttributes = Symbol(); const $getAvailableSpace = Symbol(); const $getChildrenByClass = Symbol(); const $getChildrenByName = Symbol(); const $getChildrenByNameIt = Symbol(); const $getDataValue = Symbol(); const $getExtra = Symbol(); const $getRealChildrenByNameIt = Symbol(); const $getChildren = Symbol(); const $getContainedChildren = Symbol(); const $getNextPage = Symbol(); const $getSubformParent = Symbol(); const $getParent = Symbol(); const $getTemplateRoot = Symbol(); const $globalData = Symbol(); const $hasSettableValue = Symbol(); const $ids = Symbol(); const $indexOf = Symbol(); const $insertAt = Symbol(); const $isCDATAXml = Symbol(); const $isBindable = Symbol(); const $isDataValue = Symbol(); const $isDescendent = Symbol(); const $isNsAgnostic = Symbol(); const $isSplittable = Symbol(); const $isThereMoreWidth = Symbol(); const $isTransparent = Symbol(); const $isUsable = Symbol(); const $lastAttribute = Symbol(); const $namespaceId = Symbol("namespaceId"); const $nodeName = Symbol("nodeName"); const $nsAttributes = Symbol(); const $onChild = Symbol(); const $onChildCheck = Symbol(); const $onText = Symbol(); const $pushGlyphs = Symbol(); const $popPara = Symbol(); const $pushPara = Symbol(); const $removeChild = Symbol(); const $root = Symbol("root"); const $resolvePrototypes = Symbol(); const $searchNode = Symbol(); const $setId = Symbol(); const $setSetAttributes = Symbol(); const $setValue = Symbol(); const $tabIndex = Symbol(); const $text = Symbol(); const $toPages = Symbol(); const $toHTML = Symbol(); const $toString = Symbol(); const $toStyle = Symbol(); const $uid = Symbol("uid"); ;// CONCATENATED MODULE: ./src/core/xfa/namespaces.js const $buildXFAObject = Symbol(); const NamespaceIds = { config: { id: 0, check: ns => ns.startsWith("http://www.xfa.org/schema/xci/") }, connectionSet: { id: 1, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-connection-set/") }, datasets: { id: 2, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-data/") }, form: { id: 3, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-form/") }, localeSet: { id: 4, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-locale-set/") }, pdf: { id: 5, check: ns => ns === "http://ns.adobe.com/xdp/pdf/" }, signature: { id: 6, check: ns => ns === "http://www.w3.org/2000/09/xmldsig#" }, sourceSet: { id: 7, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-source-set/") }, stylesheet: { id: 8, check: ns => ns === "http://www.w3.org/1999/XSL/Transform" }, template: { id: 9, check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-template/") }, xdc: { id: 10, check: ns => ns.startsWith("http://www.xfa.org/schema/xdc/") }, xdp: { id: 11, check: ns => ns === "http://ns.adobe.com/xdp/" }, xfdf: { id: 12, check: ns => ns === "http://ns.adobe.com/xfdf/" }, xhtml: { id: 13, check: ns => ns === "http://www.w3.org/1999/xhtml" }, xmpmeta: { id: 14, check: ns => ns === "http://ns.adobe.com/xmpmeta/" } }; ;// CONCATENATED MODULE: ./src/core/xfa/utils.js const dimConverters = { pt: x => x, cm: x => x / 2.54 * 72, mm: x => x / (10 * 2.54) * 72, in: x => x * 72, px: x => x }; const measurementPattern = /([+-]?\d+\.?\d*)(.*)/; function stripQuotes(str) { if (str.startsWith("'") || str.startsWith('"')) { return str.slice(1, -1); } return str; } function getInteger({ data, defaultValue, validate }) { if (!data) { return defaultValue; } data = data.trim(); const n = parseInt(data, 10); if (!isNaN(n) && validate(n)) { return n; } return defaultValue; } function getFloat({ data, defaultValue, validate }) { if (!data) { return defaultValue; } data = data.trim(); const n = parseFloat(data); if (!isNaN(n) && validate(n)) { return n; } return defaultValue; } function getKeyword({ data, defaultValue, validate }) { if (!data) { return defaultValue; } data = data.trim(); if (validate(data)) { return data; } return defaultValue; } function getStringOption(data, options) { return getKeyword({ data, defaultValue: options[0], validate: k => options.includes(k) }); } function getMeasurement(str, def = "0") { def ||= "0"; if (!str) { return getMeasurement(def); } const match = str.trim().match(measurementPattern); if (!match) { return getMeasurement(def); } const [, valueStr, unit] = match; const value = parseFloat(valueStr); if (isNaN(value)) { return getMeasurement(def); } if (value === 0) { return 0; } const conv = dimConverters[unit]; if (conv) { return conv(value); } return value; } function getRatio(data) { if (!data) { return { num: 1, den: 1 }; } const ratio = data.trim().split(/\s*:\s*/).map(x => parseFloat(x)).filter(x => !isNaN(x)); if (ratio.length === 1) { ratio.push(1); } if (ratio.length === 0) { return { num: 1, den: 1 }; } const [num, den] = ratio; return { num, den }; } function getRelevant(data) { if (!data) { return []; } return data.trim().split(/\s+/).map(e => { return { excluded: e[0] === "-", viewname: e.substring(1) }; }); } function getColor(data, def = [0, 0, 0]) { let [r, g, b] = def; if (!data) { return { r, g, b }; } const color = data.trim().split(/\s*,\s*/).map(c => Math.min(Math.max(0, parseInt(c.trim(), 10)), 255)).map(c => isNaN(c) ? 0 : c); if (color.length < 3) { return { r, g, b }; } [r, g, b] = color; return { r, g, b }; } function getBBox(data) { const def = -1; if (!data) { return { x: def, y: def, width: def, height: def }; } const bbox = data.trim().split(/\s*,\s*/).map(m => getMeasurement(m, "-1")); if (bbox.length < 4 || bbox[2] < 0 || bbox[3] < 0) { return { x: def, y: def, width: def, height: def }; } const [x, y, width, height] = bbox; return { x, y, width, height }; } class HTMLResult { static get FAILURE() { return shadow(this, "FAILURE", new HTMLResult(false, null, null, null)); } static get EMPTY() { return shadow(this, "EMPTY", new HTMLResult(true, null, null, null)); } constructor(success, html, bbox, breakNode) { this.success = success; this.html = html; this.bbox = bbox; this.breakNode = breakNode; } isBreak() { return !!this.breakNode; } static breakNode(node) { return new HTMLResult(false, null, null, node); } static success(html, bbox = null) { return new HTMLResult(true, html, bbox, null); } } ;// CONCATENATED MODULE: ./src/core/xfa/fonts.js class FontFinder { constructor(pdfFonts) { this.fonts = new Map(); this.cache = new Map(); this.warned = new Set(); this.defaultFont = null; this.add(pdfFonts); } add(pdfFonts, reallyMissingFonts = null) { for (const pdfFont of pdfFonts) { this.addPdfFont(pdfFont); } for (const pdfFont of this.fonts.values()) { if (!pdfFont.regular) { pdfFont.regular = pdfFont.italic || pdfFont.bold || pdfFont.bolditalic; } } if (!reallyMissingFonts || reallyMissingFonts.size === 0) { return; } const myriad = this.fonts.get("PdfJS-Fallback-PdfJS-XFA"); for (const missing of reallyMissingFonts) { this.fonts.set(missing, myriad); } } addPdfFont(pdfFont) { const cssFontInfo = pdfFont.cssFontInfo; const name = cssFontInfo.fontFamily; let font = this.fonts.get(name); if (!font) { font = Object.create(null); this.fonts.set(name, font); if (!this.defaultFont) { this.defaultFont = font; } } let property = ""; const fontWeight = parseFloat(cssFontInfo.fontWeight); if (parseFloat(cssFontInfo.italicAngle) !== 0) { property = fontWeight >= 700 ? "bolditalic" : "italic"; } else if (fontWeight >= 700) { property = "bold"; } if (!property) { if (pdfFont.name.includes("Bold") || pdfFont.psName?.includes("Bold")) { property = "bold"; } if (pdfFont.name.includes("Italic") || pdfFont.name.endsWith("It") || pdfFont.psName?.includes("Italic") || pdfFont.psName?.endsWith("It")) { property += "italic"; } } if (!property) { property = "regular"; } font[property] = pdfFont; } getDefault() { return this.defaultFont; } find(fontName, mustWarn = true) { let font = this.fonts.get(fontName) || this.cache.get(fontName); if (font) { return font; } const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi; let name = fontName.replaceAll(pattern, ""); font = this.fonts.get(name); if (font) { this.cache.set(fontName, font); return font; } name = name.toLowerCase(); const maybe = []; for (const [family, pdfFont] of this.fonts.entries()) { if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) { maybe.push(pdfFont); } } if (maybe.length === 0) { for (const [, pdfFont] of this.fonts.entries()) { if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) { maybe.push(pdfFont); } } } if (maybe.length === 0) { name = name.replaceAll(/psmt|mt/gi, ""); for (const [family, pdfFont] of this.fonts.entries()) { if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) { maybe.push(pdfFont); } } } if (maybe.length === 0) { for (const pdfFont of this.fonts.values()) { if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) { maybe.push(pdfFont); } } } if (maybe.length >= 1) { if (maybe.length !== 1 && mustWarn) { warn(`XFA - Too many choices to guess the correct font: ${fontName}`); } this.cache.set(fontName, maybe[0]); return maybe[0]; } if (mustWarn && !this.warned.has(fontName)) { this.warned.add(fontName); warn(`XFA - Cannot find the font: ${fontName}`); } return null; } } function selectFont(xfaFont, typeface) { if (xfaFont.posture === "italic") { if (xfaFont.weight === "bold") { return typeface.bolditalic; } return typeface.italic; } else if (xfaFont.weight === "bold") { return typeface.bold; } return typeface.regular; } function fonts_getMetrics(xfaFont, real = false) { let pdfFont = null; if (xfaFont) { const name = stripQuotes(xfaFont.typeface); const typeface = xfaFont[$globalData].fontFinder.find(name); pdfFont = selectFont(xfaFont, typeface); } if (!pdfFont) { return { lineHeight: 12, lineGap: 2, lineNoGap: 10 }; } const size = xfaFont.size || 10; const lineHeight = pdfFont.lineHeight ? Math.max(real ? 0 : 1.2, pdfFont.lineHeight) : 1.2; const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap; return { lineHeight: lineHeight * size, lineGap: lineGap * size, lineNoGap: Math.max(1, lineHeight - lineGap) * size }; } ;// CONCATENATED MODULE: ./src/core/xfa/text.js const WIDTH_FACTOR = 1.02; class FontInfo { constructor(xfaFont, margin, lineHeight, fontFinder) { this.lineHeight = lineHeight; this.paraMargin = margin || { top: 0, bottom: 0, left: 0, right: 0 }; if (!xfaFont) { [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); return; } this.xfaFont = { typeface: xfaFont.typeface, posture: xfaFont.posture, weight: xfaFont.weight, size: xfaFont.size, letterSpacing: xfaFont.letterSpacing }; const typeface = fontFinder.find(xfaFont.typeface); if (!typeface) { [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); return; } this.pdfFont = selectFont(xfaFont, typeface); if (!this.pdfFont) { [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); } } defaultFont(fontFinder) { const font = fontFinder.find("Helvetica", false) || fontFinder.find("Myriad Pro", false) || fontFinder.find("Arial", false) || fontFinder.getDefault(); if (font?.regular) { const pdfFont = font.regular; const info = pdfFont.cssFontInfo; const xfaFont = { typeface: info.fontFamily, posture: "normal", weight: "normal", size: 10, letterSpacing: 0 }; return [pdfFont, xfaFont]; } const xfaFont = { typeface: "Courier", posture: "normal", weight: "normal", size: 10, letterSpacing: 0 }; return [null, xfaFont]; } } class FontSelector { constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder) { this.fontFinder = fontFinder; this.stack = [new FontInfo(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder)]; } pushData(xfaFont, margin, lineHeight) { const lastFont = this.stack.at(-1); for (const name of ["typeface", "posture", "weight", "size", "letterSpacing"]) { if (!xfaFont[name]) { xfaFont[name] = lastFont.xfaFont[name]; } } for (const name of ["top", "bottom", "left", "right"]) { if (isNaN(margin[name])) { margin[name] = lastFont.paraMargin[name]; } } const fontInfo = new FontInfo(xfaFont, margin, lineHeight || lastFont.lineHeight, this.fontFinder); if (!fontInfo.pdfFont) { fontInfo.pdfFont = lastFont.pdfFont; } this.stack.push(fontInfo); } popFont() { this.stack.pop(); } topFont() { return this.stack.at(-1); } } class TextMeasure { constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) { this.glyphs = []; this.fontSelector = new FontSelector(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts); this.extraHeight = 0; } pushData(xfaFont, margin, lineHeight) { this.fontSelector.pushData(xfaFont, margin, lineHeight); } popFont(xfaFont) { return this.fontSelector.popFont(); } addPara() { const lastFont = this.fontSelector.topFont(); this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom; } addString(str) { if (!str) { return; } const lastFont = this.fontSelector.topFont(); const fontSize = lastFont.xfaFont.size; if (lastFont.pdfFont) { const letterSpacing = lastFont.xfaFont.letterSpacing; const pdfFont = lastFont.pdfFont; const fontLineHeight = pdfFont.lineHeight || 1.2; const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize; const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap; const noGap = fontLineHeight - lineGap; const firstLineHeight = Math.max(1, noGap) * fontSize; const scale = fontSize / 1000; const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width; for (const line of str.split(/[\u2029\n]/)) { const encodedLine = pdfFont.encodeString(line).join(""); const glyphs = pdfFont.charsToGlyphs(encodedLine); for (const glyph of glyphs) { const width = glyph.width || fallbackWidth; this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]); } this.glyphs.push([0, 0, 0, "\n", true]); } this.glyphs.pop(); return; } for (const line of str.split(/[\u2029\n]/)) { for (const char of line.split("")) { this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]); } this.glyphs.push([0, 0, 0, "\n", true]); } this.glyphs.pop(); } compute(maxWidth) { let lastSpacePos = -1, lastSpaceWidth = 0, width = 0, height = 0, currentLineWidth = 0, currentLineHeight = 0; let isBroken = false; let isFirstLine = true; for (let i = 0, ii = this.glyphs.length; i < ii; i++) { const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i]; const isSpace = char === " "; const glyphHeight = isFirstLine ? firstLineHeight : lineHeight; if (isEOL) { width = Math.max(width, currentLineWidth); currentLineWidth = 0; height += currentLineHeight; currentLineHeight = glyphHeight; lastSpacePos = -1; lastSpaceWidth = 0; isFirstLine = false; continue; } if (isSpace) { if (currentLineWidth + glyphWidth > maxWidth) { width = Math.max(width, currentLineWidth); currentLineWidth = 0; height += currentLineHeight; currentLineHeight = glyphHeight; lastSpacePos = -1; lastSpaceWidth = 0; isBroken = true; isFirstLine = false; } else { currentLineHeight = Math.max(glyphHeight, currentLineHeight); lastSpaceWidth = currentLineWidth; currentLineWidth += glyphWidth; lastSpacePos = i; } continue; } if (currentLineWidth + glyphWidth > maxWidth) { height += currentLineHeight; currentLineHeight = glyphHeight; if (lastSpacePos !== -1) { i = lastSpacePos; width = Math.max(width, lastSpaceWidth); currentLineWidth = 0; lastSpacePos = -1; lastSpaceWidth = 0; } else { width = Math.max(width, currentLineWidth); currentLineWidth = glyphWidth; } isBroken = true; isFirstLine = false; continue; } currentLineWidth += glyphWidth; currentLineHeight = Math.max(glyphHeight, currentLineHeight); } width = Math.max(width, currentLineWidth); height += currentLineHeight + this.extraHeight; return { width: WIDTH_FACTOR * width, height, isBroken }; } } ;// CONCATENATED MODULE: ./src/core/xfa/som.js const namePattern = /^[^.[]+/; const indexPattern = /^[^\]]+/; const operators = { dot: 0, dotDot: 1, dotHash: 2, dotBracket: 3, dotParen: 4 }; const shortcuts = new Map([["$data", (root, current) => root.datasets ? root.datasets.data : root], ["$record", (root, current) => (root.datasets ? root.datasets.data : root)[$getChildren]()[0]], ["$template", (root, current) => root.template], ["$connectionSet", (root, current) => root.connectionSet], ["$form", (root, current) => root.form], ["$layout", (root, current) => root.layout], ["$host", (root, current) => root.host], ["$dataWindow", (root, current) => root.dataWindow], ["$event", (root, current) => root.event], ["!", (root, current) => root.datasets], ["$xfa", (root, current) => root], ["xfa", (root, current) => root], ["$", (root, current) => current]]); const somCache = new WeakMap(); function parseIndex(index) { index = index.trim(); if (index === "*") { return Infinity; } return parseInt(index, 10) || 0; } function parseExpression(expr, dotDotAllowed, noExpr = true) { let match = expr.match(namePattern); if (!match) { return null; } let [name] = match; const parsed = [{ name, cacheName: "." + name, index: 0, js: null, formCalc: null, operator: operators.dot }]; let pos = name.length; while (pos < expr.length) { const spos = pos; const char = expr.charAt(pos++); if (char === "[") { match = expr.slice(pos).match(indexPattern); if (!match) { warn("XFA - Invalid index in SOM expression"); return null; } parsed.at(-1).index = parseIndex(match[0]); pos += match[0].length + 1; continue; } let operator; switch (expr.charAt(pos)) { case ".": if (!dotDotAllowed) { return null; } pos++; operator = operators.dotDot; break; case "#": pos++; operator = operators.dotHash; break; case "[": if (noExpr) { warn("XFA - SOM expression contains a FormCalc subexpression which is not supported for now."); return null; } operator = operators.dotBracket; break; case "(": if (noExpr) { warn("XFA - SOM expression contains a JavaScript subexpression which is not supported for now."); return null; } operator = operators.dotParen; break; default: operator = operators.dot; break; } match = expr.slice(pos).match(namePattern); if (!match) { break; } [name] = match; pos += name.length; parsed.push({ name, cacheName: expr.slice(spos, pos), operator, index: 0, js: null, formCalc: null }); } return parsed; } function searchNode(root, container, expr, dotDotAllowed = true, useCache = true) { const parsed = parseExpression(expr, dotDotAllowed); if (!parsed) { return null; } const fn = shortcuts.get(parsed[0].name); let i = 0; let isQualified; if (fn) { isQualified = true; root = [fn(root, container)]; i = 1; } else { isQualified = container === null; root = [container || root]; } for (let ii = parsed.length; i < ii; i++) { const { name, cacheName, operator, index } = parsed[i]; const nodes = []; for (const node of root) { if (!node.isXFAObject) { continue; } let children, cached; if (useCache) { cached = somCache.get(node); if (!cached) { cached = new Map(); somCache.set(node, cached); } children = cached.get(cacheName); } if (!children) { switch (operator) { case operators.dot: children = node[$getChildrenByName](name, false); break; case operators.dotDot: children = node[$getChildrenByName](name, true); break; case operators.dotHash: children = node[$getChildrenByClass](name); children = children.isXFAObjectArray ? children.children : [children]; break; default: break; } if (useCache) { cached.set(cacheName, children); } } if (children.length > 0) { nodes.push(children); } } if (nodes.length === 0 && !isQualified && i === 0) { const parent = container[$getParent](); container = parent; if (!container) { return null; } i = -1; root = [container]; continue; } root = isFinite(index) ? nodes.filter(node => index < node.length).map(node => node[index]) : nodes.flat(); } if (root.length === 0) { return null; } return root; } function createDataNode(root, container, expr) { const parsed = parseExpression(expr); if (!parsed) { return null; } if (parsed.some(x => x.operator === operators.dotDot)) { return null; } const fn = shortcuts.get(parsed[0].name); let i = 0; if (fn) { root = fn(root, container); i = 1; } else { root = container || root; } for (let ii = parsed.length; i < ii; i++) { const { name, operator, index } = parsed[i]; if (!isFinite(index)) { parsed[i].index = 0; return root.createNodes(parsed.slice(i)); } let children; switch (operator) { case operators.dot: children = root[$getChildrenByName](name, false); break; case operators.dotDot: children = root[$getChildrenByName](name, true); break; case operators.dotHash: children = root[$getChildrenByClass](name); children = children.isXFAObjectArray ? children.children : [children]; break; default: break; } if (children.length === 0) { return root.createNodes(parsed.slice(i)); } if (index < children.length) { const child = children[index]; if (!child.isXFAObject) { warn(`XFA - Cannot create a node.`); return null; } root = child; } else { parsed[i].index = index - children.length; return root.createNodes(parsed.slice(i)); } } return null; } ;// CONCATENATED MODULE: ./src/core/xfa/xfa_object.js const _applyPrototype = Symbol(); const _attributes = Symbol(); const _attributeNames = Symbol(); const _children = Symbol("_children"); const _cloneAttribute = Symbol(); const _dataValue = Symbol(); const _defaultValue = Symbol(); const _filteredChildrenGenerator = Symbol(); const _getPrototype = Symbol(); const _getUnsetAttributes = Symbol(); const _hasChildren = Symbol(); const _max = Symbol(); const _options = Symbol(); const _parent = Symbol("parent"); const _resolvePrototypesHelper = Symbol(); const _setAttributes = Symbol(); const _validator = Symbol(); let uid = 0; const NS_DATASETS = NamespaceIds.datasets.id; class XFAObject { constructor(nsId, name, hasChildren = false) { this[$namespaceId] = nsId; this[$nodeName] = name; this[_hasChildren] = hasChildren; this[_parent] = null; this[_children] = []; this[$uid] = `${name}${uid++}`; this[$globalData] = null; } get isXFAObject() { return true; } get isXFAObjectArray() { return false; } createNodes(path) { let root = this, node = null; for (const { name, index } of path) { for (let i = 0, ii = isFinite(index) ? index : 0; i <= ii; i++) { const nsId = root[$namespaceId] === NS_DATASETS ? -1 : root[$namespaceId]; node = new XmlObject(nsId, name); root[$appendChild](node); } root = node; } return node; } [$onChild](child) { if (!this[_hasChildren] || !this[$onChildCheck](child)) { return false; } const name = child[$nodeName]; const node = this[name]; if (node instanceof XFAObjectArray) { if (node.push(child)) { this[$appendChild](child); return true; } } else { if (node !== null) { this[$removeChild](node); } this[name] = child; this[$appendChild](child); return true; } let id = ""; if (this.id) { id = ` (id: ${this.id})`; } else if (this.name) { id = ` (name: ${this.name} ${this.h.value})`; } warn(`XFA - node "${this[$nodeName]}"${id} has already enough "${name}"!`); return false; } [$onChildCheck](child) { return this.hasOwnProperty(child[$nodeName]) && child[$namespaceId] === this[$namespaceId]; } [$isNsAgnostic]() { return false; } [$acceptWhitespace]() { return false; } [$isCDATAXml]() { return false; } [$isBindable]() { return false; } [$popPara]() { if (this.para) { this[$getTemplateRoot]()[$extra].paraStack.pop(); } } [$pushPara]() { this[$getTemplateRoot]()[$extra].paraStack.push(this.para); } [$setId](ids) { if (this.id && this[$namespaceId] === NamespaceIds.template.id) { ids.set(this.id, this); } } [$getTemplateRoot]() { return this[$globalData].template; } [$isSplittable]() { return false; } [$isThereMoreWidth]() { return false; } [$appendChild](child) { child[_parent] = this; this[_children].push(child); if (!child[$globalData] && this[$globalData]) { child[$globalData] = this[$globalData]; } } [$removeChild](child) { const i = this[_children].indexOf(child); this[_children].splice(i, 1); } [$hasSettableValue]() { return this.hasOwnProperty("value"); } [$setValue](_) {} [$onText](_) {} [$finalize]() {} [$clean](builder) { delete this[_hasChildren]; if (this[$cleanup]) { builder.clean(this[$cleanup]); delete this[$cleanup]; } } [$indexOf](child) { return this[_children].indexOf(child); } [$insertAt](i, child) { child[_parent] = this; this[_children].splice(i, 0, child); if (!child[$globalData] && this[$globalData]) { child[$globalData] = this[$globalData]; } } [$isTransparent]() { return !this.name; } [$lastAttribute]() { return ""; } [$text]() { if (this[_children].length === 0) { return this[$content]; } return this[_children].map(c => c[$text]()).join(""); } get [_attributeNames]() { const proto = Object.getPrototypeOf(this); if (!proto._attributes) { const attributes = proto._attributes = new Set(); for (const name of Object.getOwnPropertyNames(this)) { if (this[name] === null || this[name] instanceof XFAObject || this[name] instanceof XFAObjectArray) { break; } attributes.add(name); } } return shadow(this, _attributeNames, proto._attributes); } [$isDescendent](parent) { let node = this; while (node) { if (node === parent) { return true; } node = node[$getParent](); } return false; } [$getParent]() { return this[_parent]; } [$getSubformParent]() { return this[$getParent](); } [$getChildren](name = null) { if (!name) { return this[_children]; } return this[name]; } [$dump]() { const dumped = Object.create(null); if (this[$content]) { dumped.$content = this[$content]; } for (const name of Object.getOwnPropertyNames(this)) { const value = this[name]; if (value === null) { continue; } if (value instanceof XFAObject) { dumped[name] = value[$dump](); } else if (value instanceof XFAObjectArray) { if (!value.isEmpty()) { dumped[name] = value.dump(); } } else { dumped[name] = value; } } return dumped; } [$toStyle]() { return null; } [$toHTML]() { return HTMLResult.EMPTY; } *[$getContainedChildren]() { for (const node of this[$getChildren]()) { yield node; } } *[_filteredChildrenGenerator](filter, include) { for (const node of this[$getContainedChildren]()) { if (!filter || include === filter.has(node[$nodeName])) { const availableSpace = this[$getAvailableSpace](); const res = node[$toHTML](availableSpace); if (!res.success) { this[$extra].failingNode = node; } yield res; } } } [$flushHTML]() { return null; } [$addHTML](html, bbox) { this[$extra].children.push(html); } [$getAvailableSpace]() {} [$childrenToHTML]({ filter = null, include = true }) { if (!this[$extra].generator) { this[$extra].generator = this[_filteredChildrenGenerator](filter, include); } else { const availableSpace = this[$getAvailableSpace](); const res = this[$extra].failingNode[$toHTML](availableSpace); if (!res.success) { return res; } if (res.html) { this[$addHTML](res.html, res.bbox); } delete this[$extra].failingNode; } while (true) { const gen = this[$extra].generator.next(); if (gen.done) { break; } const res = gen.value; if (!res.success) { return res; } if (res.html) { this[$addHTML](res.html, res.bbox); } } this[$extra].generator = null; return HTMLResult.EMPTY; } [$setSetAttributes](attributes) { this[_setAttributes] = new Set(Object.keys(attributes)); } [_getUnsetAttributes](protoAttributes) { const allAttr = this[_attributeNames]; const setAttr = this[_setAttributes]; return [...protoAttributes].filter(x => allAttr.has(x) && !setAttr.has(x)); } [$resolvePrototypes](ids, ancestors = new Set()) { for (const child of this[_children]) { child[_resolvePrototypesHelper](ids, ancestors); } } [_resolvePrototypesHelper](ids, ancestors) { const proto = this[_getPrototype](ids, ancestors); if (proto) { this[_applyPrototype](proto, ids, ancestors); } else { this[$resolvePrototypes](ids, ancestors); } } [_getPrototype](ids, ancestors) { const { use, usehref } = this; if (!use && !usehref) { return null; } let proto = null; let somExpression = null; let id = null; let ref = use; if (usehref) { ref = usehref; if (usehref.startsWith("#som(") && usehref.endsWith(")")) { somExpression = usehref.slice("#som(".length, -1); } else if (usehref.startsWith(".#som(") && usehref.endsWith(")")) { somExpression = usehref.slice(".#som(".length, -1); } else if (usehref.startsWith("#")) { id = usehref.slice(1); } else if (usehref.startsWith(".#")) { id = usehref.slice(2); } } else if (use.startsWith("#")) { id = use.slice(1); } else { somExpression = use; } this.use = this.usehref = ""; if (id) { proto = ids.get(id); } else { proto = searchNode(ids.get($root), this, somExpression, true, false); if (proto) { proto = proto[0]; } } if (!proto) { warn(`XFA - Invalid prototype reference: ${ref}.`); return null; } if (proto[$nodeName] !== this[$nodeName]) { warn(`XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.`); return null; } if (ancestors.has(proto)) { warn(`XFA - Cycle detected in prototypes use.`); return null; } ancestors.add(proto); const protoProto = proto[_getPrototype](ids, ancestors); if (protoProto) { proto[_applyPrototype](protoProto, ids, ancestors); } proto[$resolvePrototypes](ids, ancestors); ancestors.delete(proto); return proto; } [_applyPrototype](proto, ids, ancestors) { if (ancestors.has(proto)) { warn(`XFA - Cycle detected in prototypes use.`); return; } if (!this[$content] && proto[$content]) { this[$content] = proto[$content]; } const newAncestors = new Set(ancestors); newAncestors.add(proto); for (const unsetAttrName of this[_getUnsetAttributes](proto[_setAttributes])) { this[unsetAttrName] = proto[unsetAttrName]; if (this[_setAttributes]) { this[_setAttributes].add(unsetAttrName); } } for (const name of Object.getOwnPropertyNames(this)) { if (this[_attributeNames].has(name)) { continue; } const value = this[name]; const protoValue = proto[name]; if (value instanceof XFAObjectArray) { for (const child of value[_children]) { child[_resolvePrototypesHelper](ids, ancestors); } for (let i = value[_children].length, ii = protoValue[_children].length; i < ii; i++) { const child = proto[_children][i][$clone](); if (value.push(child)) { child[_parent] = this; this[_children].push(child); child[_resolvePrototypesHelper](ids, ancestors); } else { break; } } continue; } if (value !== null) { value[$resolvePrototypes](ids, ancestors); if (protoValue) { value[_applyPrototype](protoValue, ids, ancestors); } continue; } if (protoValue !== null) { const child = protoValue[$clone](); child[_parent] = this; this[name] = child; this[_children].push(child); child[_resolvePrototypesHelper](ids, ancestors); } } } static [_cloneAttribute](obj) { if (Array.isArray(obj)) { return obj.map(x => XFAObject[_cloneAttribute](x)); } if (typeof obj === "object" && obj !== null) { return Object.assign({}, obj); } return obj; } [$clone]() { const clone = Object.create(Object.getPrototypeOf(this)); for (const $symbol of Object.getOwnPropertySymbols(this)) { try { clone[$symbol] = this[$symbol]; } catch { shadow(clone, $symbol, this[$symbol]); } } clone[$uid] = `${clone[$nodeName]}${uid++}`; clone[_children] = []; for (const name of Object.getOwnPropertyNames(this)) { if (this[_attributeNames].has(name)) { clone[name] = XFAObject[_cloneAttribute](this[name]); continue; } const value = this[name]; clone[name] = value instanceof XFAObjectArray ? new XFAObjectArray(value[_max]) : null; } for (const child of this[_children]) { const name = child[$nodeName]; const clonedChild = child[$clone](); clone[_children].push(clonedChild); clonedChild[_parent] = clone; if (clone[name] === null) { clone[name] = clonedChild; } else { clone[name][_children].push(clonedChild); } } return clone; } [$getChildren](name = null) { if (!name) { return this[_children]; } return this[_children].filter(c => c[$nodeName] === name); } [$getChildrenByClass](name) { return this[name]; } [$getChildrenByName](name, allTransparent, first = true) { return Array.from(this[$getChildrenByNameIt](name, allTransparent, first)); } *[$getChildrenByNameIt](name, allTransparent, first = true) { if (name === "parent") { yield this[_parent]; return; } for (const child of this[_children]) { if (child[$nodeName] === name) { yield child; } if (child.name === name) { yield child; } if (allTransparent || child[$isTransparent]()) { yield* child[$getChildrenByNameIt](name, allTransparent, false); } } if (first && this[_attributeNames].has(name)) { yield new XFAAttribute(this, name, this[name]); } } } class XFAObjectArray { constructor(max = Infinity) { this[_max] = max; this[_children] = []; } get isXFAObject() { return false; } get isXFAObjectArray() { return true; } push(child) { const len = this[_children].length; if (len <= this[_max]) { this[_children].push(child); return true; } warn(`XFA - node "${child[$nodeName]}" accepts no more than ${this[_max]} children`); return false; } isEmpty() { return this[_children].length === 0; } dump() { return this[_children].length === 1 ? this[_children][0][$dump]() : this[_children].map(x => x[$dump]()); } [$clone]() { const clone = new XFAObjectArray(this[_max]); clone[_children] = this[_children].map(c => c[$clone]()); return clone; } get children() { return this[_children]; } clear() { this[_children].length = 0; } } class XFAAttribute { constructor(node, name, value) { this[_parent] = node; this[$nodeName] = name; this[$content] = value; this[$consumed] = false; this[$uid] = `attribute${uid++}`; } [$getParent]() { return this[_parent]; } [$isDataValue]() { return true; } [$getDataValue]() { return this[$content].trim(); } [$setValue](value) { value = value.value || ""; this[$content] = value.toString(); } [$text]() { return this[$content]; } [$isDescendent](parent) { return this[_parent] === parent || this[_parent][$isDescendent](parent); } } class XmlObject extends XFAObject { constructor(nsId, name, attributes = {}) { super(nsId, name); this[$content] = ""; this[_dataValue] = null; if (name !== "#text") { const map = new Map(); this[_attributes] = map; for (const [attrName, value] of Object.entries(attributes)) { map.set(attrName, new XFAAttribute(this, attrName, value)); } if (attributes.hasOwnProperty($nsAttributes)) { const dataNode = attributes[$nsAttributes].xfa.dataNode; if (dataNode !== undefined) { if (dataNode === "dataGroup") { this[_dataValue] = false; } else if (dataNode === "dataValue") { this[_dataValue] = true; } } } } this[$consumed] = false; } [$toString](buf) { const tagName = this[$nodeName]; if (tagName === "#text") { buf.push(encodeToXmlString(this[$content])); return; } const utf8TagName = utf8StringToString(tagName); const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : ""; buf.push(`<${prefix}${utf8TagName}`); for (const [name, value] of this[_attributes].entries()) { const utf8Name = utf8StringToString(name); buf.push(` ${utf8Name}="${encodeToXmlString(value[$content])}"`); } if (this[_dataValue] !== null) { if (this[_dataValue]) { buf.push(` xfa:dataNode="dataValue"`); } else { buf.push(` xfa:dataNode="dataGroup"`); } } if (!this[$content] && this[_children].length === 0) { buf.push("/>"); return; } buf.push(">"); if (this[$content]) { if (typeof this[$content] === "string") { buf.push(encodeToXmlString(this[$content])); } else { this[$content][$toString](buf); } } else { for (const child of this[_children]) { child[$toString](buf); } } buf.push(`</${prefix}${utf8TagName}>`); } [$onChild](child) { if (this[$content]) { const node = new XmlObject(this[$namespaceId], "#text"); this[$appendChild](node); node[$content] = this[$content]; this[$content] = ""; } this[$appendChild](child); return true; } [$onText](str) { this[$content] += str; } [$finalize]() { if (this[$content] && this[_children].length > 0) { const node = new XmlObject(this[$namespaceId], "#text"); this[$appendChild](node); node[$content] = this[$content]; delete this[$content]; } } [$toHTML]() { if (this[$nodeName] === "#text") { return HTMLResult.success({ name: "#text", value: this[$content] }); } return HTMLResult.EMPTY; } [$getChildren](name = null) { if (!name) { return this[_children]; } return this[_children].filter(c => c[$nodeName] === name); } [$getAttributes]() { return this[_attributes]; } [$getChildrenByClass](name) { const value = this[_attributes].get(name); if (value !== undefined) { return value; } return this[$getChildren](name); } *[$getChildrenByNameIt](name, allTransparent) { const value = this[_attributes].get(name); if (value) { yield value; } for (const child of this[_children]) { if (child[$nodeName] === name) { yield child; } if (allTransparent) { yield* child[$getChildrenByNameIt](name, allTransparent); } } } *[$getAttributeIt](name, skipConsumed) { const value = this[_attributes].get(name); if (value && (!skipConsumed || !value[$consumed])) { yield value; } for (const child of this[_children]) { yield* child[$getAttributeIt](name, skipConsumed); } } *[$getRealChildrenByNameIt](name, allTransparent, skipConsumed) { for (const child of this[_children]) { if (child[$nodeName] === name && (!skipConsumed || !child[$consumed])) { yield child; } if (allTransparent) { yield* child[$getRealChildrenByNameIt](name, allTransparent, skipConsumed); } } } [$isDataValue]() { if (this[_dataValue] === null) { return this[_children].length === 0 || this[_children][0][$namespaceId] === NamespaceIds.xhtml.id; } return this[_dataValue]; } [$getDataValue]() { if (this[_dataValue] === null) { if (this[_children].length === 0) { return this[$content].trim(); } if (this[_children][0][$namespaceId] === NamespaceIds.xhtml.id) { return this[_children][0][$text]().trim(); } return null; } return this[$content].trim(); } [$setValue](value) { value = value.value || ""; this[$content] = value.toString(); } [$dump](hasNS = false) { const dumped = Object.create(null); if (hasNS) { dumped.$ns = this[$namespaceId]; } if (this[$content]) { dumped.$content = this[$content]; } dumped.$name = this[$nodeName]; dumped.children = []; for (const child of this[_children]) { dumped.children.push(child[$dump](hasNS)); } dumped.attributes = Object.create(null); for (const [name, value] of this[_attributes]) { dumped.attributes[name] = value[$content]; } return dumped; } } class ContentObject extends XFAObject { constructor(nsId, name) { super(nsId, name); this[$content] = ""; } [$onText](text) { this[$content] += text; } [$finalize]() {} } class OptionObject extends ContentObject { constructor(nsId, name, options) { super(nsId, name); this[_options] = options; } [$finalize]() { this[$content] = getKeyword({ data: this[$content], defaultValue: this[_options][0], validate: k => this[_options].includes(k) }); } [$clean](builder) { super[$clean](builder); delete this[_options]; } } class StringObject extends ContentObject { [$finalize]() { this[$content] = this[$content].trim(); } } class IntegerObject extends ContentObject { constructor(nsId, name, defaultValue, validator) { super(nsId, name); this[_defaultValue] = defaultValue; this[_validator] = validator; } [$finalize]() { this[$content] = getInteger({ data: this[$content], defaultValue: this[_defaultValue], validate: this[_validator] }); } [$clean](builder) { super[$clean](builder); delete this[_defaultValue]; delete this[_validator]; } } class Option01 extends IntegerObject { constructor(nsId, name) { super(nsId, name, 0, n => n === 1); } } class Option10 extends IntegerObject { constructor(nsId, name) { super(nsId, name, 1, n => n === 0); } } ;// CONCATENATED MODULE: ./src/core/xfa/html_utils.js function measureToString(m) { if (typeof m === "string") { return "0px"; } return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`; } const converters = { anchorType(node, style) { const parent = node[$getSubformParent](); if (!parent || parent.layout && parent.layout !== "position") { return; } if (!("transform" in style)) { style.transform = ""; } switch (node.anchorType) { case "bottomCenter": style.transform += "translate(-50%, -100%)"; break; case "bottomLeft": style.transform += "translate(0,-100%)"; break; case "bottomRight": style.transform += "translate(-100%,-100%)"; break; case "middleCenter": style.transform += "translate(-50%,-50%)"; break; case "middleLeft": style.transform += "translate(0,-50%)"; break; case "middleRight": style.transform += "translate(-100%,-50%)"; break; case "topCenter": style.transform += "translate(-50%,0)"; break; case "topRight": style.transform += "translate(-100%,0)"; break; } }, dimensions(node, style) { const parent = node[$getSubformParent](); let width = node.w; const height = node.h; if (parent.layout?.includes("row")) { const extra = parent[$extra]; const colSpan = node.colSpan; let w; if (colSpan === -1) { w = extra.columnWidths.slice(extra.currentColumn).reduce((a, x) => a + x, 0); extra.currentColumn = 0; } else { w = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, x) => a + x, 0); extra.currentColumn = (extra.currentColumn + node.colSpan) % extra.columnWidths.length; } if (!isNaN(w)) { width = node.w = w; } } style.width = width !== "" ? measureToString(width) : "auto"; style.height = height !== "" ? measureToString(height) : "auto"; }, position(node, style) { const parent = node[$getSubformParent](); if (parent?.layout && parent.layout !== "position") { return; } style.position = "absolute"; style.left = measureToString(node.x); style.top = measureToString(node.y); }, rotate(node, style) { if (node.rotate) { if (!("transform" in style)) { style.transform = ""; } style.transform += `rotate(-${node.rotate}deg)`; style.transformOrigin = "top left"; } }, presence(node, style) { switch (node.presence) { case "invisible": style.visibility = "hidden"; break; case "hidden": case "inactive": style.display = "none"; break; } }, hAlign(node, style) { if (node[$nodeName] === "para") { switch (node.hAlign) { case "justifyAll": style.textAlign = "justify-all"; break; case "radix": style.textAlign = "left"; break; default: style.textAlign = node.hAlign; } } else { switch (node.hAlign) { case "left": style.alignSelf = "start"; break; case "center": style.alignSelf = "center"; break; case "right": style.alignSelf = "end"; break; } } }, margin(node, style) { if (node.margin) { style.margin = node.margin[$toStyle]().margin; } } }; function setMinMaxDimensions(node, style) { const parent = node[$getSubformParent](); if (parent.layout === "position") { if (node.minW > 0) { style.minWidth = measureToString(node.minW); } if (node.maxW > 0) { style.maxWidth = measureToString(node.maxW); } if (node.minH > 0) { style.minHeight = measureToString(node.minH); } if (node.maxH > 0) { style.maxHeight = measureToString(node.maxH); } } } function layoutText(text, xfaFont, margin, lineHeight, fontFinder, width) { const measure = new TextMeasure(xfaFont, margin, lineHeight, fontFinder); if (typeof text === "string") { measure.addString(text); } else { text[$pushGlyphs](measure); } return measure.compute(width); } function layoutNode(node, availableSpace) { let height = null; let width = null; let isBroken = false; if ((!node.w || !node.h) && node.value) { let marginH = 0; let marginV = 0; if (node.margin) { marginH = node.margin.leftInset + node.margin.rightInset; marginV = node.margin.topInset + node.margin.bottomInset; } let lineHeight = null; let margin = null; if (node.para) { margin = Object.create(null); lineHeight = node.para.lineHeight === "" ? null : node.para.lineHeight; margin.top = node.para.spaceAbove === "" ? 0 : node.para.spaceAbove; margin.bottom = node.para.spaceBelow === "" ? 0 : node.para.spaceBelow; margin.left = node.para.marginLeft === "" ? 0 : node.para.marginLeft; margin.right = node.para.marginRight === "" ? 0 : node.para.marginRight; } let font = node.font; if (!font) { const root = node[$getTemplateRoot](); let parent = node[$getParent](); while (parent && parent !== root) { if (parent.font) { font = parent.font; break; } parent = parent[$getParent](); } } const maxWidth = (node.w || availableSpace.width) - marginH; const fontFinder = node[$globalData].fontFinder; if (node.value.exData && node.value.exData[$content] && node.value.exData.contentType === "text/html") { const res = layoutText(node.value.exData[$content], font, margin, lineHeight, fontFinder, maxWidth); width = res.width; height = res.height; isBroken = res.isBroken; } else { const text = node.value[$text](); if (text) { const res = layoutText(text, font, margin, lineHeight, fontFinder, maxWidth); width = res.width; height = res.height; isBroken = res.isBroken; } } if (width !== null && !node.w) { width += marginH; } if (height !== null && !node.h) { height += marginV; } } return { w: width, h: height, isBroken }; } function computeBbox(node, html, availableSpace) { let bbox; if (node.w !== "" && node.h !== "") { bbox = [node.x, node.y, node.w, node.h]; } else { if (!availableSpace) { return null; } let width = node.w; if (width === "") { if (node.maxW === 0) { const parent = node[$getSubformParent](); width = parent.layout === "position" && parent.w !== "" ? 0 : node.minW; } else { width = Math.min(node.maxW, availableSpace.width); } html.attributes.style.width = measureToString(width); } let height = node.h; if (height === "") { if (node.maxH === 0) { const parent = node[$getSubformParent](); height = parent.layout === "position" && parent.h !== "" ? 0 : node.minH; } else { height = Math.min(node.maxH, availableSpace.height); } html.attributes.style.height = measureToString(height); } bbox = [node.x, node.y, width, height]; } return bbox; } function fixDimensions(node) { const parent = node[$getSubformParent](); if (parent.layout?.includes("row")) { const extra = parent[$extra]; const colSpan = node.colSpan; let width; if (colSpan === -1) { width = extra.columnWidths.slice(extra.currentColumn).reduce((a, w) => a + w, 0); } else { width = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, w) => a + w, 0); } if (!isNaN(width)) { node.w = width; } } if (parent.layout && parent.layout !== "position") { node.x = node.y = 0; } if (node.layout === "table") { if (node.w === "" && Array.isArray(node.columnWidths)) { node.w = node.columnWidths.reduce((a, x) => a + x, 0); } } } function layoutClass(node) { switch (node.layout) { case "position": return "xfaPosition"; case "lr-tb": return "xfaLrTb"; case "rl-row": return "xfaRlRow"; case "rl-tb": return "xfaRlTb"; case "row": return "xfaRow"; case "table": return "xfaTable"; case "tb": return "xfaTb"; default: return "xfaPosition"; } } function toStyle(node, ...names) { const style = Object.create(null); for (const name of names) { const value = node[name]; if (value === null) { continue; } if (converters.hasOwnProperty(name)) { converters[name](node, style); continue; } if (value instanceof XFAObject) { const newStyle = value[$toStyle](); if (newStyle) { Object.assign(style, newStyle); } else { warn(`(DEBUG) - XFA - style for ${name} not implemented yet`); } } } return style; } function createWrapper(node, html) { const { attributes } = html; const { style } = attributes; const wrapper = { name: "div", attributes: { class: ["xfaWrapper"], style: Object.create(null) }, children: [] }; attributes.class.push("xfaWrapped"); if (node.border) { const { widths, insets } = node.border[$extra]; let width, height; let top = insets[0]; let left = insets[3]; const insetsH = insets[0] + insets[2]; const insetsW = insets[1] + insets[3]; switch (node.border.hand) { case "even": top -= widths[0] / 2; left -= widths[3] / 2; width = `calc(100% + ${(widths[1] + widths[3]) / 2 - insetsW}px)`; height = `calc(100% + ${(widths[0] + widths[2]) / 2 - insetsH}px)`; break; case "left": top -= widths[0]; left -= widths[3]; width = `calc(100% + ${widths[1] + widths[3] - insetsW}px)`; height = `calc(100% + ${widths[0] + widths[2] - insetsH}px)`; break; case "right": width = insetsW ? `calc(100% - ${insetsW}px)` : "100%"; height = insetsH ? `calc(100% - ${insetsH}px)` : "100%"; break; } const classNames = ["xfaBorder"]; if (isPrintOnly(node.border)) { classNames.push("xfaPrintOnly"); } const border = { name: "div", attributes: { class: classNames, style: { top: `${top}px`, left: `${left}px`, width, height } }, children: [] }; for (const key of ["border", "borderWidth", "borderColor", "borderRadius", "borderStyle"]) { if (style[key] !== undefined) { border.attributes.style[key] = style[key]; delete style[key]; } } wrapper.children.push(border, html); } else { wrapper.children.push(html); } for (const key of ["background", "backgroundClip", "top", "left", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "transform", "transformOrigin", "visibility"]) { if (style[key] !== undefined) { wrapper.attributes.style[key] = style[key]; delete style[key]; } } wrapper.attributes.style.position = style.position === "absolute" ? "absolute" : "relative"; delete style.position; if (style.alignSelf) { wrapper.attributes.style.alignSelf = style.alignSelf; delete style.alignSelf; } return wrapper; } function fixTextIndent(styles) { const indent = getMeasurement(styles.textIndent, "0px"); if (indent >= 0) { return; } const align = styles.textAlign === "right" ? "right" : "left"; const name = "padding" + (align === "left" ? "Left" : "Right"); const padding = getMeasurement(styles[name], "0px"); styles[name] = `${padding - indent}px`; } function setAccess(node, classNames) { switch (node.access) { case "nonInteractive": classNames.push("xfaNonInteractive"); break; case "readOnly": classNames.push("xfaReadOnly"); break; case "protected": classNames.push("xfaDisabled"); break; } } function isPrintOnly(node) { return node.relevant.length > 0 && !node.relevant[0].excluded && node.relevant[0].viewname === "print"; } function getCurrentPara(node) { const stack = node[$getTemplateRoot]()[$extra].paraStack; return stack.length ? stack.at(-1) : null; } function setPara(node, nodeStyle, value) { if (value.attributes.class?.includes("xfaRich")) { if (nodeStyle) { if (node.h === "") { nodeStyle.height = "auto"; } if (node.w === "") { nodeStyle.width = "auto"; } } const para = getCurrentPara(node); if (para) { const valueStyle = value.attributes.style; valueStyle.display = "flex"; valueStyle.flexDirection = "column"; switch (para.vAlign) { case "top": valueStyle.justifyContent = "start"; break; case "bottom": valueStyle.justifyContent = "end"; break; case "middle": valueStyle.justifyContent = "center"; break; } const paraStyle = para[$toStyle](); for (const [key, val] of Object.entries(paraStyle)) { if (!(key in valueStyle)) { valueStyle[key] = val; } } } } } function setFontFamily(xfaFont, node, fontFinder, style) { if (!fontFinder) { delete style.fontFamily; return; } const name = stripQuotes(xfaFont.typeface); style.fontFamily = `"${name}"`; const typeface = fontFinder.find(name); if (typeface) { const { fontFamily } = typeface.regular.cssFontInfo; if (fontFamily !== name) { style.fontFamily = `"${fontFamily}"`; } const para = getCurrentPara(node); if (para && para.lineHeight !== "") { return; } if (style.lineHeight) { return; } const pdfFont = selectFont(xfaFont, typeface); if (pdfFont) { style.lineHeight = Math.max(1.2, pdfFont.lineHeight); } } } function fixURL(str) { const absoluteUrl = createValidAbsoluteUrl(str, null, { addDefaultProtocol: true, tryConvertEncoding: true }); return absoluteUrl ? absoluteUrl.href : null; } ;// CONCATENATED MODULE: ./src/core/xfa/layout.js function createLine(node, children) { return { name: "div", attributes: { class: [node.layout === "lr-tb" ? "xfaLr" : "xfaRl"] }, children }; } function flushHTML(node) { if (!node[$extra]) { return null; } const attributes = node[$extra].attributes; const html = { name: "div", attributes, children: node[$extra].children }; if (node[$extra].failingNode) { const htmlFromFailing = node[$extra].failingNode[$flushHTML](); if (htmlFromFailing) { if (node.layout.endsWith("-tb")) { html.children.push(createLine(node, [htmlFromFailing])); } else { html.children.push(htmlFromFailing); } } } if (html.children.length === 0) { return null; } return html; } function addHTML(node, html, bbox) { const extra = node[$extra]; const availableSpace = extra.availableSpace; const [x, y, w, h] = bbox; switch (node.layout) { case "position": { extra.width = Math.max(extra.width, x + w); extra.height = Math.max(extra.height, y + h); extra.children.push(html); break; } case "lr-tb": case "rl-tb": if (!extra.line || extra.attempt === 1) { extra.line = createLine(node, []); extra.children.push(extra.line); extra.numberInLine = 0; } extra.numberInLine += 1; extra.line.children.push(html); if (extra.attempt === 0) { extra.currentWidth += w; extra.height = Math.max(extra.height, extra.prevHeight + h); } else { extra.currentWidth = w; extra.prevHeight = extra.height; extra.height += h; extra.attempt = 0; } extra.width = Math.max(extra.width, extra.currentWidth); break; case "rl-row": case "row": { extra.children.push(html); extra.width += w; extra.height = Math.max(extra.height, h); const height = measureToString(extra.height); for (const child of extra.children) { child.attributes.style.height = height; } break; } case "table": { extra.width = Math.min(availableSpace.width, Math.max(extra.width, w)); extra.height += h; extra.children.push(html); break; } case "tb": { extra.width = Math.min(availableSpace.width, Math.max(extra.width, w)); extra.height += h; extra.children.push(html); break; } } } function getAvailableSpace(node) { const availableSpace = node[$extra].availableSpace; const marginV = node.margin ? node.margin.topInset + node.margin.bottomInset : 0; const marginH = node.margin ? node.margin.leftInset + node.margin.rightInset : 0; switch (node.layout) { case "lr-tb": case "rl-tb": if (node[$extra].attempt === 0) { return { width: availableSpace.width - marginH - node[$extra].currentWidth, height: availableSpace.height - marginV - node[$extra].prevHeight }; } return { width: availableSpace.width - marginH, height: availableSpace.height - marginV - node[$extra].height }; case "rl-row": case "row": const width = node[$extra].columnWidths.slice(node[$extra].currentColumn).reduce((a, x) => a + x); return { width, height: availableSpace.height - marginH }; case "table": case "tb": return { width: availableSpace.width - marginH, height: availableSpace.height - marginV - node[$extra].height }; case "position": default: return availableSpace; } } function getTransformedBBox(node) { let w = node.w === "" ? NaN : node.w; let h = node.h === "" ? NaN : node.h; let [centerX, centerY] = [0, 0]; switch (node.anchorType || "") { case "bottomCenter": [centerX, centerY] = [w / 2, h]; break; case "bottomLeft": [centerX, centerY] = [0, h]; break; case "bottomRight": [centerX, centerY] = [w, h]; break; case "middleCenter": [centerX, centerY] = [w / 2, h / 2]; break; case "middleLeft": [centerX, centerY] = [0, h / 2]; break; case "middleRight": [centerX, centerY] = [w, h / 2]; break; case "topCenter": [centerX, centerY] = [w / 2, 0]; break; case "topRight": [centerX, centerY] = [w, 0]; break; } let x, y; switch (node.rotate || 0) { case 0: [x, y] = [-centerX, -centerY]; break; case 90: [x, y] = [-centerY, centerX]; [w, h] = [h, -w]; break; case 180: [x, y] = [centerX, centerY]; [w, h] = [-w, -h]; break; case 270: [x, y] = [centerY, -centerX]; [w, h] = [-h, w]; break; } return [node.x + x + Math.min(0, w), node.y + y + Math.min(0, h), Math.abs(w), Math.abs(h)]; } function checkDimensions(node, space) { if (node[$getTemplateRoot]()[$extra].firstUnsplittable === null) { return true; } if (node.w === 0 || node.h === 0) { return true; } const ERROR = 2; const parent = node[$getSubformParent](); const attempt = parent[$extra]?.attempt || 0; const [, y, w, h] = getTransformedBBox(node); switch (parent.layout) { case "lr-tb": case "rl-tb": if (attempt === 0) { if (!node[$getTemplateRoot]()[$extra].noLayoutFailure) { if (node.h !== "" && Math.round(h - space.height) > ERROR) { return false; } if (node.w !== "") { if (Math.round(w - space.width) <= ERROR) { return true; } if (parent[$extra].numberInLine === 0) { return space.height > ERROR; } return false; } return space.width > ERROR; } if (node.w !== "") { return Math.round(w - space.width) <= ERROR; } return space.width > ERROR; } if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { return true; } if (node.h !== "" && Math.round(h - space.height) > ERROR) { return false; } if (node.w === "" || Math.round(w - space.width) <= ERROR) { return space.height > ERROR; } if (parent[$isThereMoreWidth]()) { return false; } return space.height > ERROR; case "table": case "tb": if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { return true; } if (node.h !== "" && !node[$isSplittable]()) { return Math.round(h - space.height) <= ERROR; } if (node.w === "" || Math.round(w - space.width) <= ERROR) { return space.height > ERROR; } if (parent[$isThereMoreWidth]()) { return false; } return space.height > ERROR; case "position": if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { return true; } if (node.h === "" || Math.round(h + y - space.height) <= ERROR) { return true; } const area = node[$getTemplateRoot]()[$extra].currentContentArea; return h + y > area.h; case "rl-row": case "row": if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { return true; } if (node.h !== "") { return Math.round(h - space.height) <= ERROR; } return true; default: return true; } } ;// CONCATENATED MODULE: ./src/core/xfa/template.js const TEMPLATE_NS_ID = NamespaceIds.template.id; const SVG_NS = "http://www.w3.org/2000/svg"; const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2; const MAX_EMPTY_PAGES = 3; const DEFAULT_TAB_INDEX = 5000; const HEADING_PATTERN = /^H(\d+)$/; const MIMES = new Set(["image/gif", "image/jpeg", "image/jpg", "image/pjpeg", "image/png", "image/apng", "image/x-png", "image/bmp", "image/x-ms-bmp", "image/tiff", "image/tif", "application/octet-stream"]); const IMAGES_HEADERS = [[[0x42, 0x4d], "image/bmp"], [[0xff, 0xd8, 0xff], "image/jpeg"], [[0x49, 0x49, 0x2a, 0x00], "image/tiff"], [[0x4d, 0x4d, 0x00, 0x2a], "image/tiff"], [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], "image/gif"], [[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], "image/png"]]; function getBorderDims(node) { if (!node || !node.border) { return { w: 0, h: 0 }; } const borderExtra = node.border[$getExtra](); if (!borderExtra) { return { w: 0, h: 0 }; } return { w: borderExtra.widths[0] + borderExtra.widths[2] + borderExtra.insets[0] + borderExtra.insets[2], h: borderExtra.widths[1] + borderExtra.widths[3] + borderExtra.insets[1] + borderExtra.insets[3] }; } function hasMargin(node) { return node.margin && (node.margin.topInset || node.margin.rightInset || node.margin.bottomInset || node.margin.leftInset); } function _setValue(templateNode, value) { if (!templateNode.value) { const nodeValue = new Value({}); templateNode[$appendChild](nodeValue); templateNode.value = nodeValue; } templateNode.value[$setValue](value); } function* getContainedChildren(node) { for (const child of node[$getChildren]()) { if (child instanceof SubformSet) { yield* child[$getContainedChildren](); continue; } yield child; } } function isRequired(node) { return node.validate?.nullTest === "error"; } function setTabIndex(node) { while (node) { if (!node.traversal) { node[$tabIndex] = node[$getParent]()[$tabIndex]; return; } if (node[$tabIndex]) { return; } let next = null; for (const child of node.traversal[$getChildren]()) { if (child.operation === "next") { next = child; break; } } if (!next || !next.ref) { node[$tabIndex] = node[$getParent]()[$tabIndex]; return; } const root = node[$getTemplateRoot](); node[$tabIndex] = ++root[$tabIndex]; const ref = root[$searchNode](next.ref, node); if (!ref) { return; } node = ref[0]; } } function applyAssist(obj, attributes) { const assist = obj.assist; if (assist) { const assistTitle = assist[$toHTML](); if (assistTitle) { attributes.title = assistTitle; } const role = assist.role; const match = role.match(HEADING_PATTERN); if (match) { const ariaRole = "heading"; const ariaLevel = match[1]; attributes.role = ariaRole; attributes["aria-level"] = ariaLevel; } } if (obj.layout === "table") { attributes.role = "table"; } else if (obj.layout === "row") { attributes.role = "row"; } else { const parent = obj[$getParent](); if (parent.layout === "row") { attributes.role = parent.assist?.role === "TH" ? "columnheader" : "cell"; } } } function ariaLabel(obj) { if (!obj.assist) { return null; } const assist = obj.assist; if (assist.speak && assist.speak[$content] !== "") { return assist.speak[$content]; } if (assist.toolTip) { return assist.toolTip[$content]; } return null; } function valueToHtml(value) { return HTMLResult.success({ name: "div", attributes: { class: ["xfaRich"], style: Object.create(null) }, children: [{ name: "span", attributes: { style: Object.create(null) }, value }] }); } function setFirstUnsplittable(node) { const root = node[$getTemplateRoot](); if (root[$extra].firstUnsplittable === null) { root[$extra].firstUnsplittable = node; root[$extra].noLayoutFailure = true; } } function unsetFirstUnsplittable(node) { const root = node[$getTemplateRoot](); if (root[$extra].firstUnsplittable === node) { root[$extra].noLayoutFailure = false; } } function handleBreak(node) { if (node[$extra]) { return false; } node[$extra] = Object.create(null); if (node.targetType === "auto") { return false; } const root = node[$getTemplateRoot](); let target = null; if (node.target) { target = root[$searchNode](node.target, node[$getParent]()); if (!target) { return false; } target = target[0]; } const { currentPageArea, currentContentArea } = root[$extra]; if (node.targetType === "pageArea") { if (!(target instanceof PageArea)) { target = null; } if (node.startNew) { node[$extra].target = target || currentPageArea; return true; } else if (target && target !== currentPageArea) { node[$extra].target = target; return true; } return false; } if (!(target instanceof ContentArea)) { target = null; } const pageArea = target && target[$getParent](); let index; let nextPageArea = pageArea; if (node.startNew) { if (target) { const contentAreas = pageArea.contentArea.children; const indexForCurrent = contentAreas.indexOf(currentContentArea); const indexForTarget = contentAreas.indexOf(target); if (indexForCurrent !== -1 && indexForCurrent < indexForTarget) { nextPageArea = null; } index = indexForTarget - 1; } else { index = currentPageArea.contentArea.children.indexOf(currentContentArea); } } else if (target && target !== currentContentArea) { const contentAreas = pageArea.contentArea.children; index = contentAreas.indexOf(target) - 1; nextPageArea = pageArea === currentPageArea ? null : pageArea; } else { return false; } node[$extra].target = nextPageArea; node[$extra].index = index; return true; } function handleOverflow(node, extraNode, space) { const root = node[$getTemplateRoot](); const saved = root[$extra].noLayoutFailure; const savedMethod = extraNode[$getSubformParent]; extraNode[$getSubformParent] = () => node; root[$extra].noLayoutFailure = true; const res = extraNode[$toHTML](space); node[$addHTML](res.html, res.bbox); root[$extra].noLayoutFailure = saved; extraNode[$getSubformParent] = savedMethod; } class AppearanceFilter extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "appearanceFilter"); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Arc extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "arc", true); this.circular = getInteger({ data: attributes.circular, defaultValue: 0, validate: x => x === 1 }); this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); this.id = attributes.id || ""; this.startAngle = getFloat({ data: attributes.startAngle, defaultValue: 0, validate: x => true }); this.sweepAngle = getFloat({ data: attributes.sweepAngle, defaultValue: 360, validate: x => true }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.edge = null; this.fill = null; } [$toHTML]() { const edge = this.edge || new Edge({}); const edgeStyle = edge[$toStyle](); const style = Object.create(null); if (this.fill?.presence === "visible") { Object.assign(style, this.fill[$toStyle]()); } else { style.fill = "transparent"; } style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0); style.stroke = edgeStyle.color; let arc; const attributes = { xmlns: SVG_NS, style: { width: "100%", height: "100%", overflow: "visible" } }; if (this.sweepAngle === 360) { arc = { name: "ellipse", attributes: { xmlns: SVG_NS, cx: "50%", cy: "50%", rx: "50%", ry: "50%", style } }; } else { const startAngle = this.startAngle * Math.PI / 180; const sweepAngle = this.sweepAngle * Math.PI / 180; const largeArc = this.sweepAngle > 180 ? 1 : 0; const [x1, y1, x2, y2] = [50 * (1 + Math.cos(startAngle)), 50 * (1 - Math.sin(startAngle)), 50 * (1 + Math.cos(startAngle + sweepAngle)), 50 * (1 - Math.sin(startAngle + sweepAngle))]; arc = { name: "path", attributes: { xmlns: SVG_NS, d: `M ${x1} ${y1} A 50 50 0 ${largeArc} 0 ${x2} ${y2}`, vectorEffect: "non-scaling-stroke", style } }; Object.assign(attributes, { viewBox: "0 0 100 100", preserveAspectRatio: "none" }); } const svg = { name: "svg", children: [arc], attributes }; const parent = this[$getParent]()[$getParent](); if (hasMargin(parent)) { return HTMLResult.success({ name: "div", attributes: { style: { display: "inline", width: "100%", height: "100%" } }, children: [svg] }); } svg.attributes.style.position = "absolute"; return HTMLResult.success(svg); } } class Area extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "area", true); this.colSpan = getInteger({ data: attributes.colSpan, defaultValue: 1, validate: n => n >= 1 || n === -1 }); this.id = attributes.id || ""; this.name = attributes.name || ""; this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.desc = null; this.extras = null; this.area = new XFAObjectArray(); this.draw = new XFAObjectArray(); this.exObject = new XFAObjectArray(); this.exclGroup = new XFAObjectArray(); this.field = new XFAObjectArray(); this.subform = new XFAObjectArray(); this.subformSet = new XFAObjectArray(); } *[$getContainedChildren]() { yield* getContainedChildren(this); } [$isTransparent]() { return true; } [$isBindable]() { return true; } [$addHTML](html, bbox) { const [x, y, w, h] = bbox; this[$extra].width = Math.max(this[$extra].width, x + w); this[$extra].height = Math.max(this[$extra].height, y + h); this[$extra].children.push(html); } [$getAvailableSpace]() { return this[$extra].availableSpace; } [$toHTML](availableSpace) { const style = toStyle(this, "position"); const attributes = { style, id: this[$uid], class: ["xfaArea"] }; if (isPrintOnly(this)) { attributes.class.push("xfaPrintOnly"); } if (this.name) { attributes.xfaName = this.name; } const children = []; this[$extra] = { children, width: 0, height: 0, availableSpace }; const result = this[$childrenToHTML]({ filter: new Set(["area", "draw", "field", "exclGroup", "subform", "subformSet"]), include: true }); if (!result.success) { if (result.isBreak()) { return result; } delete this[$extra]; return HTMLResult.FAILURE; } style.width = measureToString(this[$extra].width); style.height = measureToString(this[$extra].height); const html = { name: "div", attributes, children }; const bbox = [this.x, this.y, this[$extra].width, this[$extra].height]; delete this[$extra]; return HTMLResult.success(html, bbox); } } class Assist extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "assist", true); this.id = attributes.id || ""; this.role = attributes.role || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.speak = null; this.toolTip = null; } [$toHTML]() { return this.toolTip?.[$content] || null; } } class Barcode extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "barcode", true); this.charEncoding = getKeyword({ data: attributes.charEncoding ? attributes.charEncoding.toLowerCase() : "", defaultValue: "", validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/) }); this.checksum = getStringOption(attributes.checksum, ["none", "1mod10", "1mod10_1mod11", "2mod10", "auto"]); this.dataColumnCount = getInteger({ data: attributes.dataColumnCount, defaultValue: -1, validate: x => x >= 0 }); this.dataLength = getInteger({ data: attributes.dataLength, defaultValue: -1, validate: x => x >= 0 }); this.dataPrep = getStringOption(attributes.dataPrep, ["none", "flateCompress"]); this.dataRowCount = getInteger({ data: attributes.dataRowCount, defaultValue: -1, validate: x => x >= 0 }); this.endChar = attributes.endChar || ""; this.errorCorrectionLevel = getInteger({ data: attributes.errorCorrectionLevel, defaultValue: -1, validate: x => x >= 0 && x <= 8 }); this.id = attributes.id || ""; this.moduleHeight = getMeasurement(attributes.moduleHeight, "5mm"); this.moduleWidth = getMeasurement(attributes.moduleWidth, "0.25mm"); this.printCheckDigit = getInteger({ data: attributes.printCheckDigit, defaultValue: 0, validate: x => x === 1 }); this.rowColumnRatio = getRatio(attributes.rowColumnRatio); this.startChar = attributes.startChar || ""; this.textLocation = getStringOption(attributes.textLocation, ["below", "above", "aboveEmbedded", "belowEmbedded", "none"]); this.truncate = getInteger({ data: attributes.truncate, defaultValue: 0, validate: x => x === 1 }); this.type = getStringOption(attributes.type ? attributes.type.toLowerCase() : "", ["aztec", "codabar", "code2of5industrial", "code2of5interleaved", "code2of5matrix", "code2of5standard", "code3of9", "code3of9extended", "code11", "code49", "code93", "code128", "code128a", "code128b", "code128c", "code128sscc", "datamatrix", "ean8", "ean8add2", "ean8add5", "ean13", "ean13add2", "ean13add5", "ean13pwcd", "fim", "logmars", "maxicode", "msi", "pdf417", "pdf417macro", "plessey", "postauscust2", "postauscust3", "postausreplypaid", "postausstandard", "postukrm4scc", "postusdpbc", "postusimb", "postusstandard", "postus5zip", "qrcode", "rfid", "rss14", "rss14expanded", "rss14limited", "rss14stacked", "rss14stackedomni", "rss14truncated", "telepen", "ucc128", "ucc128random", "ucc128sscc", "upca", "upcaadd2", "upcaadd5", "upcapwcd", "upce", "upceadd2", "upceadd5", "upcean2", "upcean5", "upsmaxicode"]); this.upsMode = getStringOption(attributes.upsMode, ["usCarrier", "internationalCarrier", "secureSymbol", "standardSymbol"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.wideNarrowRatio = getRatio(attributes.wideNarrowRatio); this.encrypt = null; this.extras = null; } } class Bind extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "bind", true); this.match = getStringOption(attributes.match, ["once", "dataRef", "global", "none"]); this.ref = attributes.ref || ""; this.picture = null; } } class BindItems extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "bindItems"); this.connection = attributes.connection || ""; this.labelRef = attributes.labelRef || ""; this.ref = attributes.ref || ""; this.valueRef = attributes.valueRef || ""; } } class Bookend extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "bookend"); this.id = attributes.id || ""; this.leader = attributes.leader || ""; this.trailer = attributes.trailer || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class BooleanElement extends Option01 { constructor(attributes) { super(TEMPLATE_NS_ID, "boolean"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$toHTML](availableSpace) { return valueToHtml(this[$content] === 1 ? "1" : "0"); } } class Border extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "border", true); this.break = getStringOption(attributes.break, ["close", "open"]); this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); this.id = attributes.id || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.corner = new XFAObjectArray(4); this.edge = new XFAObjectArray(4); this.extras = null; this.fill = null; this.margin = null; } [$getExtra]() { if (!this[$extra]) { const edges = this.edge.children.slice(); if (edges.length < 4) { const defaultEdge = edges.at(-1) || new Edge({}); for (let i = edges.length; i < 4; i++) { edges.push(defaultEdge); } } const widths = edges.map(edge => edge.thickness); const insets = [0, 0, 0, 0]; if (this.margin) { insets[0] = this.margin.topInset; insets[1] = this.margin.rightInset; insets[2] = this.margin.bottomInset; insets[3] = this.margin.leftInset; } this[$extra] = { widths, insets, edges }; } return this[$extra]; } [$toStyle]() { const { edges } = this[$getExtra](); const edgeStyles = edges.map(node => { const style = node[$toStyle](); style.color ||= "#000000"; return style; }); const style = Object.create(null); if (this.margin) { Object.assign(style, this.margin[$toStyle]()); } if (this.fill?.presence === "visible") { Object.assign(style, this.fill[$toStyle]()); } if (this.corner.children.some(node => node.radius !== 0)) { const cornerStyles = this.corner.children.map(node => node[$toStyle]()); if (cornerStyles.length === 2 || cornerStyles.length === 3) { const last = cornerStyles.at(-1); for (let i = cornerStyles.length; i < 4; i++) { cornerStyles.push(last); } } style.borderRadius = cornerStyles.map(s => s.radius).join(" "); } switch (this.presence) { case "invisible": case "hidden": style.borderStyle = ""; break; case "inactive": style.borderStyle = "none"; break; default: style.borderStyle = edgeStyles.map(s => s.style).join(" "); break; } style.borderWidth = edgeStyles.map(s => s.width).join(" "); style.borderColor = edgeStyles.map(s => s.color).join(" "); return style; } } class Break extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "break", true); this.after = getStringOption(attributes.after, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]); this.afterTarget = attributes.afterTarget || ""; this.before = getStringOption(attributes.before, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]); this.beforeTarget = attributes.beforeTarget || ""; this.bookendLeader = attributes.bookendLeader || ""; this.bookendTrailer = attributes.bookendTrailer || ""; this.id = attributes.id || ""; this.overflowLeader = attributes.overflowLeader || ""; this.overflowTarget = attributes.overflowTarget || ""; this.overflowTrailer = attributes.overflowTrailer || ""; this.startNew = getInteger({ data: attributes.startNew, defaultValue: 0, validate: x => x === 1 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } } class BreakAfter extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "breakAfter", true); this.id = attributes.id || ""; this.leader = attributes.leader || ""; this.startNew = getInteger({ data: attributes.startNew, defaultValue: 0, validate: x => x === 1 }); this.target = attributes.target || ""; this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]); this.trailer = attributes.trailer || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.script = null; } } class BreakBefore extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "breakBefore", true); this.id = attributes.id || ""; this.leader = attributes.leader || ""; this.startNew = getInteger({ data: attributes.startNew, defaultValue: 0, validate: x => x === 1 }); this.target = attributes.target || ""; this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]); this.trailer = attributes.trailer || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.script = null; } [$toHTML](availableSpace) { this[$extra] = {}; return HTMLResult.FAILURE; } } class Button extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "button", true); this.highlight = getStringOption(attributes.highlight, ["inverted", "none", "outline", "push"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } [$toHTML](availableSpace) { const parent = this[$getParent](); const grandpa = parent[$getParent](); const htmlButton = { name: "button", attributes: { id: this[$uid], class: ["xfaButton"], style: {} }, children: [] }; for (const event of grandpa.event.children) { if (event.activity !== "click" || !event.script) { continue; } const jsURL = recoverJsURL(event.script[$content]); if (!jsURL) { continue; } const href = fixURL(jsURL.url); if (!href) { continue; } htmlButton.children.push({ name: "a", attributes: { id: "link" + this[$uid], href, newWindow: jsURL.newWindow, class: ["xfaLink"], style: {} }, children: [] }); } return HTMLResult.success(htmlButton); } } class Calculate extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "calculate", true); this.id = attributes.id || ""; this.override = getStringOption(attributes.override, ["disabled", "error", "ignore", "warning"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.message = null; this.script = null; } } class Caption extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "caption", true); this.id = attributes.id || ""; this.placement = getStringOption(attributes.placement, ["left", "bottom", "inline", "right", "top"]); this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.reserve = Math.ceil(getMeasurement(attributes.reserve)); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.font = null; this.margin = null; this.para = null; this.value = null; } [$setValue](value) { _setValue(this, value); } [$getExtra](availableSpace) { if (!this[$extra]) { let { width, height } = availableSpace; switch (this.placement) { case "left": case "right": case "inline": width = this.reserve <= 0 ? width : this.reserve; break; case "top": case "bottom": height = this.reserve <= 0 ? height : this.reserve; break; } this[$extra] = layoutNode(this, { width, height }); } return this[$extra]; } [$toHTML](availableSpace) { if (!this.value) { return HTMLResult.EMPTY; } this[$pushPara](); const value = this.value[$toHTML](availableSpace).html; if (!value) { this[$popPara](); return HTMLResult.EMPTY; } const savedReserve = this.reserve; if (this.reserve <= 0) { const { w, h } = this[$getExtra](availableSpace); switch (this.placement) { case "left": case "right": case "inline": this.reserve = w; break; case "top": case "bottom": this.reserve = h; break; } } const children = []; if (typeof value === "string") { children.push({ name: "#text", value }); } else { children.push(value); } const style = toStyle(this, "font", "margin", "visibility"); switch (this.placement) { case "left": case "right": if (this.reserve > 0) { style.width = measureToString(this.reserve); } break; case "top": case "bottom": if (this.reserve > 0) { style.height = measureToString(this.reserve); } break; } setPara(this, null, value); this[$popPara](); this.reserve = savedReserve; return HTMLResult.success({ name: "div", attributes: { style, class: ["xfaCaption"] }, children }); } } class Certificate extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "certificate"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Certificates extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "certificates", true); this.credentialServerPolicy = getStringOption(attributes.credentialServerPolicy, ["optional", "required"]); this.id = attributes.id || ""; this.url = attributes.url || ""; this.urlPolicy = attributes.urlPolicy || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.encryption = null; this.issuers = null; this.keyUsage = null; this.oids = null; this.signing = null; this.subjectDNs = null; } } class CheckButton extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "checkButton", true); this.id = attributes.id || ""; this.mark = getStringOption(attributes.mark, ["default", "check", "circle", "cross", "diamond", "square", "star"]); this.shape = getStringOption(attributes.shape, ["square", "round"]); this.size = getMeasurement(attributes.size, "10pt"); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { const style = toStyle("margin"); const size = measureToString(this.size); style.width = style.height = size; let type; let className; let groupId; const field = this[$getParent]()[$getParent](); const items = field.items.children.length && field.items.children[0][$toHTML]().html || []; const exportedValue = { on: (items[0] !== undefined ? items[0] : "on").toString(), off: (items[1] !== undefined ? items[1] : "off").toString() }; const value = field.value?.[$text]() || "off"; const checked = value === exportedValue.on || undefined; const container = field[$getSubformParent](); const fieldId = field[$uid]; let dataId; if (container instanceof ExclGroup) { groupId = container[$uid]; type = "radio"; className = "xfaRadio"; dataId = container[$data]?.[$uid] || container[$uid]; } else { type = "checkbox"; className = "xfaCheckbox"; dataId = field[$data]?.[$uid] || field[$uid]; } const input = { name: "input", attributes: { class: [className], style, fieldId, dataId, type, checked, xfaOn: exportedValue.on, xfaOff: exportedValue.off, "aria-label": ariaLabel(field), "aria-required": false } }; if (groupId) { input.attributes.name = groupId; } if (isRequired(field)) { input.attributes["aria-required"] = true; input.attributes.required = true; } return HTMLResult.success({ name: "label", attributes: { class: ["xfaLabel"] }, children: [input] }); } } class ChoiceList extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "choiceList", true); this.commitOn = getStringOption(attributes.commitOn, ["select", "exit"]); this.id = attributes.id || ""; this.open = getStringOption(attributes.open, ["userControl", "always", "multiSelect", "onEntry"]); this.textEntry = getInteger({ data: attributes.textEntry, defaultValue: 0, validate: x => x === 1 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { const style = toStyle(this, "border", "margin"); const ui = this[$getParent](); const field = ui[$getParent](); const fontSize = field.font?.size || 10; const optionStyle = { fontSize: `calc(${fontSize}px * var(--scale-factor))` }; const children = []; if (field.items.children.length > 0) { const items = field.items; let displayedIndex = 0; let saveIndex = 0; if (items.children.length === 2) { displayedIndex = items.children[0].save; saveIndex = 1 - displayedIndex; } const displayed = items.children[displayedIndex][$toHTML]().html; const values = items.children[saveIndex][$toHTML]().html; let selected = false; const value = field.value?.[$text]() || ""; for (let i = 0, ii = displayed.length; i < ii; i++) { const option = { name: "option", attributes: { value: values[i] || displayed[i], style: optionStyle }, value: displayed[i] }; if (values[i] === value) { option.attributes.selected = selected = true; } children.push(option); } if (!selected) { children.splice(0, 0, { name: "option", attributes: { hidden: true, selected: true }, value: " " }); } } const selectAttributes = { class: ["xfaSelect"], fieldId: field[$uid], dataId: field[$data]?.[$uid] || field[$uid], style, "aria-label": ariaLabel(field), "aria-required": false }; if (isRequired(field)) { selectAttributes["aria-required"] = true; selectAttributes.required = true; } if (this.open === "multiSelect") { selectAttributes.multiple = true; } return HTMLResult.success({ name: "label", attributes: { class: ["xfaLabel"] }, children: [{ name: "select", children, attributes: selectAttributes }] }); } } class Color extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "color", true); this.cSpace = getStringOption(attributes.cSpace, ["SRGB"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.value = attributes.value ? getColor(attributes.value) : ""; this.extras = null; } [$hasSettableValue]() { return false; } [$toStyle]() { return this.value ? Util.makeHexColor(this.value.r, this.value.g, this.value.b) : null; } } class Comb extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "comb"); this.id = attributes.id || ""; this.numberOfCells = getInteger({ data: attributes.numberOfCells, defaultValue: 0, validate: x => x >= 0 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Connect extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "connect", true); this.connection = attributes.connection || ""; this.id = attributes.id || ""; this.ref = attributes.ref || ""; this.usage = getStringOption(attributes.usage, ["exportAndImport", "exportOnly", "importOnly"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.picture = null; } } class ContentArea extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "contentArea", true); this.h = getMeasurement(attributes.h); this.id = attributes.id || ""; this.name = attributes.name || ""; this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.w = getMeasurement(attributes.w); this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.desc = null; this.extras = null; } [$toHTML](availableSpace) { const left = measureToString(this.x); const top = measureToString(this.y); const style = { left, top, width: measureToString(this.w), height: measureToString(this.h) }; const classNames = ["xfaContentarea"]; if (isPrintOnly(this)) { classNames.push("xfaPrintOnly"); } return HTMLResult.success({ name: "div", children: [], attributes: { style, class: classNames, id: this[$uid] } }); } } class Corner extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "corner", true); this.id = attributes.id || ""; this.inverted = getInteger({ data: attributes.inverted, defaultValue: 0, validate: x => x === 1 }); this.join = getStringOption(attributes.join, ["square", "round"]); this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.radius = getMeasurement(attributes.radius); this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]); this.thickness = getMeasurement(attributes.thickness, "0.5pt"); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle]() { const style = toStyle(this, "visibility"); style.radius = measureToString(this.join === "square" ? 0 : this.radius); return style; } } class DateElement extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "date"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const date = this[$content].trim(); this[$content] = date ? new Date(date) : null; } [$toHTML](availableSpace) { return valueToHtml(this[$content] ? this[$content].toString() : ""); } } class DateTime extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "dateTime"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const date = this[$content].trim(); this[$content] = date ? new Date(date) : null; } [$toHTML](availableSpace) { return valueToHtml(this[$content] ? this[$content].toString() : ""); } } class DateTimeEdit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "dateTimeEdit", true); this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]); this.id = attributes.id || ""; this.picker = getStringOption(attributes.picker, ["host", "none"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.comb = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { const style = toStyle(this, "border", "font", "margin"); const field = this[$getParent]()[$getParent](); const html = { name: "input", attributes: { type: "text", fieldId: field[$uid], dataId: field[$data]?.[$uid] || field[$uid], class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), "aria-required": false } }; if (isRequired(field)) { html.attributes["aria-required"] = true; html.attributes.required = true; } return HTMLResult.success({ name: "label", attributes: { class: ["xfaLabel"] }, children: [html] }); } } class Decimal extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "decimal"); this.fracDigits = getInteger({ data: attributes.fracDigits, defaultValue: 2, validate: x => true }); this.id = attributes.id || ""; this.leadDigits = getInteger({ data: attributes.leadDigits, defaultValue: -1, validate: x => true }); this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const number = parseFloat(this[$content].trim()); this[$content] = isNaN(number) ? null : number; } [$toHTML](availableSpace) { return valueToHtml(this[$content] !== null ? this[$content].toString() : ""); } } class DefaultUi extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "defaultUi", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } } class Desc extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "desc", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.boolean = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.float = new XFAObjectArray(); this.image = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.text = new XFAObjectArray(); this.time = new XFAObjectArray(); } } class DigestMethod extends OptionObject { constructor(attributes) { super(TEMPLATE_NS_ID, "digestMethod", ["", "SHA1", "SHA256", "SHA512", "RIPEMD160"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class DigestMethods extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "digestMethods", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.digestMethod = new XFAObjectArray(); } } class Draw extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "draw", true); this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]); this.colSpan = getInteger({ data: attributes.colSpan, defaultValue: 1, validate: n => n >= 1 || n === -1 }); this.h = attributes.h ? getMeasurement(attributes.h) : ""; this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]); this.id = attributes.id || ""; this.locale = attributes.locale || ""; this.maxH = getMeasurement(attributes.maxH, "0pt"); this.maxW = getMeasurement(attributes.maxW, "0pt"); this.minH = getMeasurement(attributes.minH, "0pt"); this.minW = getMeasurement(attributes.minW, "0pt"); this.name = attributes.name || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.relevant = getRelevant(attributes.relevant); this.rotate = getInteger({ data: attributes.rotate, defaultValue: 0, validate: x => x % 90 === 0 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.w = attributes.w ? getMeasurement(attributes.w) : ""; this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.assist = null; this.border = null; this.caption = null; this.desc = null; this.extras = null; this.font = null; this.keep = null; this.margin = null; this.para = null; this.traversal = null; this.ui = null; this.value = null; this.setProperty = new XFAObjectArray(); } [$setValue](value) { _setValue(this, value); } [$toHTML](availableSpace) { setTabIndex(this); if (this.presence === "hidden" || this.presence === "inactive") { return HTMLResult.EMPTY; } fixDimensions(this); this[$pushPara](); const savedW = this.w; const savedH = this.h; const { w, h, isBroken } = layoutNode(this, availableSpace); if (w && this.w === "") { if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) { this[$popPara](); return HTMLResult.FAILURE; } this.w = w; } if (h && this.h === "") { this.h = h; } setFirstUnsplittable(this); if (!checkDimensions(this, availableSpace)) { this.w = savedW; this.h = savedH; this[$popPara](); return HTMLResult.FAILURE; } unsetFirstUnsplittable(this); const style = toStyle(this, "font", "hAlign", "dimensions", "position", "presence", "rotate", "anchorType", "border", "margin"); setMinMaxDimensions(this, style); if (style.margin) { style.padding = style.margin; delete style.margin; } const classNames = ["xfaDraw"]; if (this.font) { classNames.push("xfaFont"); } if (isPrintOnly(this)) { classNames.push("xfaPrintOnly"); } const attributes = { style, id: this[$uid], class: classNames }; if (this.name) { attributes.xfaName = this.name; } const html = { name: "div", attributes, children: [] }; applyAssist(this, attributes); const bbox = computeBbox(this, html, availableSpace); const value = this.value ? this.value[$toHTML](availableSpace).html : null; if (value === null) { this.w = savedW; this.h = savedH; this[$popPara](); return HTMLResult.success(createWrapper(this, html), bbox); } html.children.push(value); setPara(this, style, value); this.w = savedW; this.h = savedH; this[$popPara](); return HTMLResult.success(createWrapper(this, html), bbox); } } class Edge extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "edge", true); this.cap = getStringOption(attributes.cap, ["square", "butt", "round"]); this.id = attributes.id || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]); this.thickness = getMeasurement(attributes.thickness, "0.5pt"); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle]() { const style = toStyle(this, "visibility"); Object.assign(style, { linecap: this.cap, width: measureToString(this.thickness), color: this.color ? this.color[$toStyle]() : "#000000", style: "" }); if (this.presence !== "visible") { style.style = "none"; } else { switch (this.stroke) { case "solid": style.style = "solid"; break; case "dashDot": style.style = "dashed"; break; case "dashDotDot": style.style = "dashed"; break; case "dashed": style.style = "dashed"; break; case "dotted": style.style = "dotted"; break; case "embossed": style.style = "ridge"; break; case "etched": style.style = "groove"; break; case "lowered": style.style = "inset"; break; case "raised": style.style = "outset"; break; } } return style; } } class Encoding extends OptionObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encoding", ["adbe.x509.rsa_sha1", "adbe.pkcs7.detached", "adbe.pkcs7.sha1"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Encodings extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encodings", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.encoding = new XFAObjectArray(); } } class Encrypt extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encrypt", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.certificate = null; } } class EncryptData extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encryptData", true); this.id = attributes.id || ""; this.operation = getStringOption(attributes.operation, ["encrypt", "decrypt"]); this.target = attributes.target || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.filter = null; this.manifest = null; } } class Encryption extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encryption", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.certificate = new XFAObjectArray(); } } class EncryptionMethod extends OptionObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encryptionMethod", ["", "AES256-CBC", "TRIPLEDES-CBC", "AES128-CBC", "AES192-CBC"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class EncryptionMethods extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "encryptionMethods", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.encryptionMethod = new XFAObjectArray(); } } class Event extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "event", true); this.activity = getStringOption(attributes.activity, ["click", "change", "docClose", "docReady", "enter", "exit", "full", "indexChange", "initialize", "mouseDown", "mouseEnter", "mouseExit", "mouseUp", "postExecute", "postOpen", "postPrint", "postSave", "postSign", "postSubmit", "preExecute", "preOpen", "prePrint", "preSave", "preSign", "preSubmit", "ready", "validationState"]); this.id = attributes.id || ""; this.listen = getStringOption(attributes.listen, ["refOnly", "refAndDescendents"]); this.name = attributes.name || ""; this.ref = attributes.ref || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.encryptData = null; this.execute = null; this.script = null; this.signData = null; this.submit = null; } } class ExData extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "exData"); this.contentType = attributes.contentType || ""; this.href = attributes.href || ""; this.id = attributes.id || ""; this.maxLength = getInteger({ data: attributes.maxLength, defaultValue: -1, validate: x => x >= -1 }); this.name = attributes.name || ""; this.rid = attributes.rid || ""; this.transferEncoding = getStringOption(attributes.transferEncoding, ["none", "base64", "package"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$isCDATAXml]() { return this.contentType === "text/html"; } [$onChild](child) { if (this.contentType === "text/html" && child[$namespaceId] === NamespaceIds.xhtml.id) { this[$content] = child; return true; } if (this.contentType === "text/xml") { this[$content] = child; return true; } return false; } [$toHTML](availableSpace) { if (this.contentType !== "text/html" || !this[$content]) { return HTMLResult.EMPTY; } return this[$content][$toHTML](availableSpace); } } class ExObject extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "exObject", true); this.archive = attributes.archive || ""; this.classId = attributes.classId || ""; this.codeBase = attributes.codeBase || ""; this.codeType = attributes.codeType || ""; this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.boolean = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.exObject = new XFAObjectArray(); this.float = new XFAObjectArray(); this.image = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.text = new XFAObjectArray(); this.time = new XFAObjectArray(); } } class ExclGroup extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "exclGroup", true); this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]); this.accessKey = attributes.accessKey || ""; this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]); this.colSpan = getInteger({ data: attributes.colSpan, defaultValue: 1, validate: n => n >= 1 || n === -1 }); this.h = attributes.h ? getMeasurement(attributes.h) : ""; this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]); this.id = attributes.id || ""; this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]); this.maxH = getMeasurement(attributes.maxH, "0pt"); this.maxW = getMeasurement(attributes.maxW, "0pt"); this.minH = getMeasurement(attributes.minH, "0pt"); this.minW = getMeasurement(attributes.minW, "0pt"); this.name = attributes.name || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.w = attributes.w ? getMeasurement(attributes.w) : ""; this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.assist = null; this.bind = null; this.border = null; this.calculate = null; this.caption = null; this.desc = null; this.extras = null; this.margin = null; this.para = null; this.traversal = null; this.validate = null; this.connect = new XFAObjectArray(); this.event = new XFAObjectArray(); this.field = new XFAObjectArray(); this.setProperty = new XFAObjectArray(); } [$isBindable]() { return true; } [$hasSettableValue]() { return true; } [$setValue](value) { for (const field of this.field.children) { if (!field.value) { const nodeValue = new Value({}); field[$appendChild](nodeValue); field.value = nodeValue; } field.value[$setValue](value); } } [$isThereMoreWidth]() { return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth](); } [$isSplittable]() { const parent = this[$getSubformParent](); if (!parent[$isSplittable]()) { return false; } if (this[$extra]._isSplittable !== undefined) { return this[$extra]._isSplittable; } if (this.layout === "position" || this.layout.includes("row")) { this[$extra]._isSplittable = false; return false; } if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) { return false; } this[$extra]._isSplittable = true; return true; } [$flushHTML]() { return flushHTML(this); } [$addHTML](html, bbox) { addHTML(this, html, bbox); } [$getAvailableSpace]() { return getAvailableSpace(this); } [$toHTML](availableSpace) { setTabIndex(this); if (this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) { return HTMLResult.EMPTY; } fixDimensions(this); const children = []; const attributes = { id: this[$uid], class: [] }; setAccess(this, attributes.class); if (!this[$extra]) { this[$extra] = Object.create(null); } Object.assign(this[$extra], { children, attributes, attempt: 0, line: null, numberInLine: 0, availableSpace: { width: Math.min(this.w || Infinity, availableSpace.width), height: Math.min(this.h || Infinity, availableSpace.height) }, width: 0, height: 0, prevHeight: 0, currentWidth: 0 }); const isSplittable = this[$isSplittable](); if (!isSplittable) { setFirstUnsplittable(this); } if (!checkDimensions(this, availableSpace)) { return HTMLResult.FAILURE; } const filter = new Set(["field"]); if (this.layout.includes("row")) { const columnWidths = this[$getSubformParent]().columnWidths; if (Array.isArray(columnWidths) && columnWidths.length > 0) { this[$extra].columnWidths = columnWidths; this[$extra].currentColumn = 0; } } const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign"); const classNames = ["xfaExclgroup"]; const cl = layoutClass(this); if (cl) { classNames.push(cl); } if (isPrintOnly(this)) { classNames.push("xfaPrintOnly"); } attributes.style = style; attributes.class = classNames; if (this.name) { attributes.xfaName = this.name; } this[$pushPara](); const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb"; const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1; for (; this[$extra].attempt < maxRun; this[$extra].attempt++) { if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) { this[$extra].numberInLine = 0; } const result = this[$childrenToHTML]({ filter, include: true }); if (result.success) { break; } if (result.isBreak()) { this[$popPara](); return result; } if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !this[$getTemplateRoot]()[$extra].noLayoutFailure) { this[$extra].attempt = maxRun; break; } } this[$popPara](); if (!isSplittable) { unsetFirstUnsplittable(this); } if (this[$extra].attempt === maxRun) { if (!isSplittable) { delete this[$extra]; } return HTMLResult.FAILURE; } let marginH = 0; let marginV = 0; if (this.margin) { marginH = this.margin.leftInset + this.margin.rightInset; marginV = this.margin.topInset + this.margin.bottomInset; } const width = Math.max(this[$extra].width + marginH, this.w || 0); const height = Math.max(this[$extra].height + marginV, this.h || 0); const bbox = [this.x, this.y, width, height]; if (this.w === "") { style.width = measureToString(width); } if (this.h === "") { style.height = measureToString(height); } const html = { name: "div", attributes, children }; applyAssist(this, attributes); delete this[$extra]; return HTMLResult.success(createWrapper(this, html), bbox); } } class Execute extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "execute"); this.connection = attributes.connection || ""; this.executeType = getStringOption(attributes.executeType, ["import", "remerge"]); this.id = attributes.id || ""; this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Extras extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "extras", true); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.boolean = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.extras = new XFAObjectArray(); this.float = new XFAObjectArray(); this.image = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.text = new XFAObjectArray(); this.time = new XFAObjectArray(); } } class Field extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "field", true); this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]); this.accessKey = attributes.accessKey || ""; this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]); this.colSpan = getInteger({ data: attributes.colSpan, defaultValue: 1, validate: n => n >= 1 || n === -1 }); this.h = attributes.h ? getMeasurement(attributes.h) : ""; this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]); this.id = attributes.id || ""; this.locale = attributes.locale || ""; this.maxH = getMeasurement(attributes.maxH, "0pt"); this.maxW = getMeasurement(attributes.maxW, "0pt"); this.minH = getMeasurement(attributes.minH, "0pt"); this.minW = getMeasurement(attributes.minW, "0pt"); this.name = attributes.name || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.relevant = getRelevant(attributes.relevant); this.rotate = getInteger({ data: attributes.rotate, defaultValue: 0, validate: x => x % 90 === 0 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.w = attributes.w ? getMeasurement(attributes.w) : ""; this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.assist = null; this.bind = null; this.border = null; this.calculate = null; this.caption = null; this.desc = null; this.extras = null; this.font = null; this.format = null; this.items = new XFAObjectArray(2); this.keep = null; this.margin = null; this.para = null; this.traversal = null; this.ui = null; this.validate = null; this.value = null; this.bindItems = new XFAObjectArray(); this.connect = new XFAObjectArray(); this.event = new XFAObjectArray(); this.setProperty = new XFAObjectArray(); } [$isBindable]() { return true; } [$setValue](value) { _setValue(this, value); } [$toHTML](availableSpace) { setTabIndex(this); if (!this.ui) { this.ui = new Ui({}); this.ui[$globalData] = this[$globalData]; this[$appendChild](this.ui); let node; switch (this.items.children.length) { case 0: node = new TextEdit({}); this.ui.textEdit = node; break; case 1: node = new CheckButton({}); this.ui.checkButton = node; break; case 2: node = new ChoiceList({}); this.ui.choiceList = node; break; } this.ui[$appendChild](node); } if (!this.ui || this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) { return HTMLResult.EMPTY; } if (this.caption) { delete this.caption[$extra]; } this[$pushPara](); const caption = this.caption ? this.caption[$toHTML](availableSpace).html : null; const savedW = this.w; const savedH = this.h; let marginH = 0; let marginV = 0; if (this.margin) { marginH = this.margin.leftInset + this.margin.rightInset; marginV = this.margin.topInset + this.margin.bottomInset; } let borderDims = null; if (this.w === "" || this.h === "") { let width = null; let height = null; let uiW = 0; let uiH = 0; if (this.ui.checkButton) { uiW = uiH = this.ui.checkButton.size; } else { const { w, h } = layoutNode(this, availableSpace); if (w !== null) { uiW = w; uiH = h; } else { uiH = fonts_getMetrics(this.font, true).lineNoGap; } } borderDims = getBorderDims(this.ui[$getExtra]()); uiW += borderDims.w; uiH += borderDims.h; if (this.caption) { const { w, h, isBroken } = this.caption[$getExtra](availableSpace); if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) { this[$popPara](); return HTMLResult.FAILURE; } width = w; height = h; switch (this.caption.placement) { case "left": case "right": case "inline": width += uiW; break; case "top": case "bottom": height += uiH; break; } } else { width = uiW; height = uiH; } if (width && this.w === "") { width += marginH; this.w = Math.min(this.maxW <= 0 ? Infinity : this.maxW, this.minW + 1 < width ? width : this.minW); } if (height && this.h === "") { height += marginV; this.h = Math.min(this.maxH <= 0 ? Infinity : this.maxH, this.minH + 1 < height ? height : this.minH); } } this[$popPara](); fixDimensions(this); setFirstUnsplittable(this); if (!checkDimensions(this, availableSpace)) { this.w = savedW; this.h = savedH; this[$popPara](); return HTMLResult.FAILURE; } unsetFirstUnsplittable(this); const style = toStyle(this, "font", "dimensions", "position", "rotate", "anchorType", "presence", "margin", "hAlign"); setMinMaxDimensions(this, style); const classNames = ["xfaField"]; if (this.font) { classNames.push("xfaFont"); } if (isPrintOnly(this)) { classNames.push("xfaPrintOnly"); } const attributes = { style, id: this[$uid], class: classNames }; if (style.margin) { style.padding = style.margin; delete style.margin; } setAccess(this, classNames); if (this.name) { attributes.xfaName = this.name; } const children = []; const html = { name: "div", attributes, children }; applyAssist(this, attributes); const borderStyle = this.border ? this.border[$toStyle]() : null; const bbox = computeBbox(this, html, availableSpace); const ui = this.ui[$toHTML]().html; if (!ui) { Object.assign(style, borderStyle); return HTMLResult.success(createWrapper(this, html), bbox); } if (this[$tabIndex]) { if (ui.children?.[0]) { ui.children[0].attributes.tabindex = this[$tabIndex]; } else { ui.attributes.tabindex = this[$tabIndex]; } } if (!ui.attributes.style) { ui.attributes.style = Object.create(null); } let aElement = null; if (this.ui.button) { if (ui.children.length === 1) { [aElement] = ui.children.splice(0, 1); } Object.assign(ui.attributes.style, borderStyle); } else { Object.assign(style, borderStyle); } children.push(ui); if (this.value) { if (this.ui.imageEdit) { ui.children.push(this.value[$toHTML]().html); } else if (!this.ui.button) { let value = ""; if (this.value.exData) { value = this.value.exData[$text](); } else if (this.value.text) { value = this.value.text[$getExtra](); } else { const htmlValue = this.value[$toHTML]().html; if (htmlValue !== null) { value = htmlValue.children[0].value; } } if (this.ui.textEdit && this.value.text?.maxChars) { ui.children[0].attributes.maxLength = this.value.text.maxChars; } if (value) { if (this.ui.numericEdit) { value = parseFloat(value); value = isNaN(value) ? "" : value.toString(); } if (ui.children[0].name === "textarea") { ui.children[0].attributes.textContent = value; } else { ui.children[0].attributes.value = value; } } } } if (!this.ui.imageEdit && ui.children?.[0] && this.h) { borderDims = borderDims || getBorderDims(this.ui[$getExtra]()); let captionHeight = 0; if (this.caption && ["top", "bottom"].includes(this.caption.placement)) { captionHeight = this.caption.reserve; if (captionHeight <= 0) { captionHeight = this.caption[$getExtra](availableSpace).h; } const inputHeight = this.h - captionHeight - marginV - borderDims.h; ui.children[0].attributes.style.height = measureToString(inputHeight); } else { ui.children[0].attributes.style.height = "100%"; } } if (aElement) { ui.children.push(aElement); } if (!caption) { if (ui.attributes.class) { ui.attributes.class.push("xfaLeft"); } this.w = savedW; this.h = savedH; return HTMLResult.success(createWrapper(this, html), bbox); } if (this.ui.button) { if (style.padding) { delete style.padding; } if (caption.name === "div") { caption.name = "span"; } ui.children.push(caption); return HTMLResult.success(html, bbox); } else if (this.ui.checkButton) { caption.attributes.class[0] = "xfaCaptionForCheckButton"; } if (!ui.attributes.class) { ui.attributes.class = []; } ui.children.splice(0, 0, caption); switch (this.caption.placement) { case "left": ui.attributes.class.push("xfaLeft"); break; case "right": ui.attributes.class.push("xfaRight"); break; case "top": ui.attributes.class.push("xfaTop"); break; case "bottom": ui.attributes.class.push("xfaBottom"); break; case "inline": ui.attributes.class.push("xfaLeft"); break; } this.w = savedW; this.h = savedH; return HTMLResult.success(createWrapper(this, html), bbox); } } class Fill extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "fill", true); this.id = attributes.id || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; this.linear = null; this.pattern = null; this.radial = null; this.solid = null; this.stipple = null; } [$toStyle]() { const parent = this[$getParent](); const grandpa = parent[$getParent](); const ggrandpa = grandpa[$getParent](); const style = Object.create(null); let propName = "color"; let altPropName = propName; if (parent instanceof Border) { propName = "background-color"; altPropName = "background"; if (ggrandpa instanceof Ui) { style.backgroundColor = "white"; } } if (parent instanceof Rectangle || parent instanceof Arc) { propName = altPropName = "fill"; style.fill = "white"; } for (const name of Object.getOwnPropertyNames(this)) { if (name === "extras" || name === "color") { continue; } const obj = this[name]; if (!(obj instanceof XFAObject)) { continue; } const color = obj[$toStyle](this.color); if (color) { style[color.startsWith("#") ? propName : altPropName] = color; } return style; } if (this.color?.value) { const color = this.color[$toStyle](); style[color.startsWith("#") ? propName : altPropName] = color; } return style; } } class Filter extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "filter", true); this.addRevocationInfo = getStringOption(attributes.addRevocationInfo, ["", "required", "optional", "none"]); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.version = getInteger({ data: this.version, defaultValue: 5, validate: x => x >= 1 && x <= 5 }); this.appearanceFilter = null; this.certificates = null; this.digestMethods = null; this.encodings = null; this.encryptionMethods = null; this.handler = null; this.lockDocument = null; this.mdp = null; this.reasons = null; this.timeStamp = null; } } class Float extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "float"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const number = parseFloat(this[$content].trim()); this[$content] = isNaN(number) ? null : number; } [$toHTML](availableSpace) { return valueToHtml(this[$content] !== null ? this[$content].toString() : ""); } } class template_Font extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "font", true); this.baselineShift = getMeasurement(attributes.baselineShift); this.fontHorizontalScale = getFloat({ data: attributes.fontHorizontalScale, defaultValue: 100, validate: x => x >= 0 }); this.fontVerticalScale = getFloat({ data: attributes.fontVerticalScale, defaultValue: 100, validate: x => x >= 0 }); this.id = attributes.id || ""; this.kerningMode = getStringOption(attributes.kerningMode, ["none", "pair"]); this.letterSpacing = getMeasurement(attributes.letterSpacing, "0"); this.lineThrough = getInteger({ data: attributes.lineThrough, defaultValue: 0, validate: x => x === 1 || x === 2 }); this.lineThroughPeriod = getStringOption(attributes.lineThroughPeriod, ["all", "word"]); this.overline = getInteger({ data: attributes.overline, defaultValue: 0, validate: x => x === 1 || x === 2 }); this.overlinePeriod = getStringOption(attributes.overlinePeriod, ["all", "word"]); this.posture = getStringOption(attributes.posture, ["normal", "italic"]); this.size = getMeasurement(attributes.size, "10pt"); this.typeface = attributes.typeface || "Courier"; this.underline = getInteger({ data: attributes.underline, defaultValue: 0, validate: x => x === 1 || x === 2 }); this.underlinePeriod = getStringOption(attributes.underlinePeriod, ["all", "word"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.weight = getStringOption(attributes.weight, ["normal", "bold"]); this.extras = null; this.fill = null; } [$clean](builder) { super[$clean](builder); this[$globalData].usedTypefaces.add(this.typeface); } [$toStyle]() { const style = toStyle(this, "fill"); const color = style.color; if (color) { if (color === "#000000") { delete style.color; } else if (!color.startsWith("#")) { style.background = color; style.backgroundClip = "text"; style.color = "transparent"; } } if (this.baselineShift) { style.verticalAlign = measureToString(this.baselineShift); } style.fontKerning = this.kerningMode === "none" ? "none" : "normal"; style.letterSpacing = measureToString(this.letterSpacing); if (this.lineThrough !== 0) { style.textDecoration = "line-through"; if (this.lineThrough === 2) { style.textDecorationStyle = "double"; } } if (this.overline !== 0) { style.textDecoration = "overline"; if (this.overline === 2) { style.textDecorationStyle = "double"; } } style.fontStyle = this.posture; style.fontSize = measureToString(0.99 * this.size); setFontFamily(this, this, this[$globalData].fontFinder, style); if (this.underline !== 0) { style.textDecoration = "underline"; if (this.underline === 2) { style.textDecorationStyle = "double"; } } style.fontWeight = this.weight; return style; } } class Format extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "format", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.picture = null; } } class Handler extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "handler"); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Hyphenation extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "hyphenation"); this.excludeAllCaps = getInteger({ data: attributes.excludeAllCaps, defaultValue: 0, validate: x => x === 1 }); this.excludeInitialCap = getInteger({ data: attributes.excludeInitialCap, defaultValue: 0, validate: x => x === 1 }); this.hyphenate = getInteger({ data: attributes.hyphenate, defaultValue: 0, validate: x => x === 1 }); this.id = attributes.id || ""; this.pushCharacterCount = getInteger({ data: attributes.pushCharacterCount, defaultValue: 3, validate: x => x >= 0 }); this.remainCharacterCount = getInteger({ data: attributes.remainCharacterCount, defaultValue: 3, validate: x => x >= 0 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.wordCharacterCount = getInteger({ data: attributes.wordCharacterCount, defaultValue: 7, validate: x => x >= 0 }); } } class Image extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "image"); this.aspect = getStringOption(attributes.aspect, ["fit", "actual", "height", "none", "width"]); this.contentType = attributes.contentType || ""; this.href = attributes.href || ""; this.id = attributes.id || ""; this.name = attributes.name || ""; this.transferEncoding = getStringOption(attributes.transferEncoding, ["base64", "none", "package"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$toHTML]() { if (this.contentType && !MIMES.has(this.contentType.toLowerCase())) { return HTMLResult.EMPTY; } let buffer = this[$globalData].images && this[$globalData].images.get(this.href); if (!buffer && (this.href || !this[$content])) { return HTMLResult.EMPTY; } if (!buffer && this.transferEncoding === "base64") { buffer = stringToBytes(atob(this[$content])); } if (!buffer) { return HTMLResult.EMPTY; } if (!this.contentType) { for (const [header, type] of IMAGES_HEADERS) { if (buffer.length > header.length && header.every((x, i) => x === buffer[i])) { this.contentType = type; break; } } if (!this.contentType) { return HTMLResult.EMPTY; } } const blob = new Blob([buffer], { type: this.contentType }); let style; switch (this.aspect) { case "fit": case "actual": break; case "height": style = { height: "100%", objectFit: "fill" }; break; case "none": style = { width: "100%", height: "100%", objectFit: "fill" }; break; case "width": style = { width: "100%", objectFit: "fill" }; break; } const parent = this[$getParent](); return HTMLResult.success({ name: "img", attributes: { class: ["xfaImage"], style, src: URL.createObjectURL(blob), alt: parent ? ariaLabel(parent[$getParent]()) : null } }); } } class ImageEdit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "imageEdit", true); this.data = getStringOption(attributes.data, ["link", "embed"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { if (this.data === "embed") { return HTMLResult.success({ name: "div", children: [], attributes: {} }); } return HTMLResult.EMPTY; } } class Integer extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "integer"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const number = parseInt(this[$content].trim(), 10); this[$content] = isNaN(number) ? null : number; } [$toHTML](availableSpace) { return valueToHtml(this[$content] !== null ? this[$content].toString() : ""); } } class Issuers extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "issuers", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.certificate = new XFAObjectArray(); } } class Items extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "items", true); this.id = attributes.id || ""; this.name = attributes.name || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.ref = attributes.ref || ""; this.save = getInteger({ data: attributes.save, defaultValue: 0, validate: x => x === 1 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.boolean = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.float = new XFAObjectArray(); this.image = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.text = new XFAObjectArray(); this.time = new XFAObjectArray(); } [$toHTML]() { const output = []; for (const child of this[$getChildren]()) { output.push(child[$text]()); } return HTMLResult.success(output); } } class Keep extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "keep", true); this.id = attributes.id || ""; const options = ["none", "contentArea", "pageArea"]; this.intact = getStringOption(attributes.intact, options); this.next = getStringOption(attributes.next, options); this.previous = getStringOption(attributes.previous, options); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } } class KeyUsage extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "keyUsage"); const options = ["", "yes", "no"]; this.crlSign = getStringOption(attributes.crlSign, options); this.dataEncipherment = getStringOption(attributes.dataEncipherment, options); this.decipherOnly = getStringOption(attributes.decipherOnly, options); this.digitalSignature = getStringOption(attributes.digitalSignature, options); this.encipherOnly = getStringOption(attributes.encipherOnly, options); this.id = attributes.id || ""; this.keyAgreement = getStringOption(attributes.keyAgreement, options); this.keyCertSign = getStringOption(attributes.keyCertSign, options); this.keyEncipherment = getStringOption(attributes.keyEncipherment, options); this.nonRepudiation = getStringOption(attributes.nonRepudiation, options); this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Line extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "line", true); this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); this.id = attributes.id || ""; this.slope = getStringOption(attributes.slope, ["\\", "/"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.edge = null; } [$toHTML]() { const parent = this[$getParent]()[$getParent](); const edge = this.edge || new Edge({}); const edgeStyle = edge[$toStyle](); const style = Object.create(null); const thickness = edge.presence === "visible" ? edge.thickness : 0; style.strokeWidth = measureToString(thickness); style.stroke = edgeStyle.color; let x1, y1, x2, y2; let width = "100%"; let height = "100%"; if (parent.w <= thickness) { [x1, y1, x2, y2] = ["50%", 0, "50%", "100%"]; width = style.strokeWidth; } else if (parent.h <= thickness) { [x1, y1, x2, y2] = [0, "50%", "100%", "50%"]; height = style.strokeWidth; } else if (this.slope === "\\") { [x1, y1, x2, y2] = [0, 0, "100%", "100%"]; } else { [x1, y1, x2, y2] = [0, "100%", "100%", 0]; } const line = { name: "line", attributes: { xmlns: SVG_NS, x1, y1, x2, y2, style } }; const svg = { name: "svg", children: [line], attributes: { xmlns: SVG_NS, width, height, style: { overflow: "visible" } } }; if (hasMargin(parent)) { return HTMLResult.success({ name: "div", attributes: { style: { display: "inline", width: "100%", height: "100%" } }, children: [svg] }); } svg.attributes.style.position = "absolute"; return HTMLResult.success(svg); } } class Linear extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "linear", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["toRight", "toBottom", "toLeft", "toTop"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle](startColor) { startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; const transf = this.type.replace(/([RBLT])/, " $1").toLowerCase(); const endColor = this.color ? this.color[$toStyle]() : "#000000"; return `linear-gradient(${transf}, ${startColor}, ${endColor})`; } } class LockDocument extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "lockDocument"); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { this[$content] = getStringOption(this[$content], ["auto", "0", "1"]); } } class Manifest extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "manifest", true); this.action = getStringOption(attributes.action, ["include", "all", "exclude"]); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.ref = new XFAObjectArray(); } } class Margin extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "margin", true); this.bottomInset = getMeasurement(attributes.bottomInset, "0"); this.id = attributes.id || ""; this.leftInset = getMeasurement(attributes.leftInset, "0"); this.rightInset = getMeasurement(attributes.rightInset, "0"); this.topInset = getMeasurement(attributes.topInset, "0"); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } [$toStyle]() { return { margin: measureToString(this.topInset) + " " + measureToString(this.rightInset) + " " + measureToString(this.bottomInset) + " " + measureToString(this.leftInset) }; } } class Mdp extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "mdp"); this.id = attributes.id || ""; this.permissions = getInteger({ data: attributes.permissions, defaultValue: 2, validate: x => x === 1 || x === 3 }); this.signatureType = getStringOption(attributes.signatureType, ["filler", "author"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Medium extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "medium"); this.id = attributes.id || ""; this.imagingBBox = getBBox(attributes.imagingBBox); this.long = getMeasurement(attributes.long); this.orientation = getStringOption(attributes.orientation, ["portrait", "landscape"]); this.short = getMeasurement(attributes.short); this.stock = attributes.stock || ""; this.trayIn = getStringOption(attributes.trayIn, ["auto", "delegate", "pageFront"]); this.trayOut = getStringOption(attributes.trayOut, ["auto", "delegate"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Message extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "message", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.text = new XFAObjectArray(); } } class NumericEdit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "numericEdit", true); this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.comb = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { const style = toStyle(this, "border", "font", "margin"); const field = this[$getParent]()[$getParent](); const html = { name: "input", attributes: { type: "text", fieldId: field[$uid], dataId: field[$data]?.[$uid] || field[$uid], class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), "aria-required": false } }; if (isRequired(field)) { html.attributes["aria-required"] = true; html.attributes.required = true; } return HTMLResult.success({ name: "label", attributes: { class: ["xfaLabel"] }, children: [html] }); } } class Occur extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "occur", true); this.id = attributes.id || ""; this.initial = attributes.initial !== "" ? getInteger({ data: attributes.initial, defaultValue: "", validate: x => true }) : ""; this.max = attributes.max !== "" ? getInteger({ data: attributes.max, defaultValue: 1, validate: x => true }) : ""; this.min = attributes.min !== "" ? getInteger({ data: attributes.min, defaultValue: 1, validate: x => true }) : ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } [$clean]() { const parent = this[$getParent](); const originalMin = this.min; if (this.min === "") { this.min = parent instanceof PageArea || parent instanceof PageSet ? 0 : 1; } if (this.max === "") { if (originalMin === "") { this.max = parent instanceof PageArea || parent instanceof PageSet ? -1 : 1; } else { this.max = this.min; } } if (this.max !== -1 && this.max < this.min) { this.max = this.min; } if (this.initial === "") { this.initial = parent instanceof Template ? 1 : this.min; } } } class Oid extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "oid"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Oids extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "oids", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.oid = new XFAObjectArray(); } } class Overflow extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "overflow"); this.id = attributes.id || ""; this.leader = attributes.leader || ""; this.target = attributes.target || ""; this.trailer = attributes.trailer || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$getExtra]() { if (!this[$extra]) { const parent = this[$getParent](); const root = this[$getTemplateRoot](); const target = root[$searchNode](this.target, parent); const leader = root[$searchNode](this.leader, parent); const trailer = root[$searchNode](this.trailer, parent); this[$extra] = { target: target?.[0] || null, leader: leader?.[0] || null, trailer: trailer?.[0] || null, addLeader: false, addTrailer: false }; } return this[$extra]; } } class PageArea extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "pageArea", true); this.blankOrNotBlank = getStringOption(attributes.blankOrNotBlank, ["any", "blank", "notBlank"]); this.id = attributes.id || ""; this.initialNumber = getInteger({ data: attributes.initialNumber, defaultValue: 1, validate: x => true }); this.name = attributes.name || ""; this.numbered = getInteger({ data: attributes.numbered, defaultValue: 1, validate: x => true }); this.oddOrEven = getStringOption(attributes.oddOrEven, ["any", "even", "odd"]); this.pagePosition = getStringOption(attributes.pagePosition, ["any", "first", "last", "only", "rest"]); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.desc = null; this.extras = null; this.medium = null; this.occur = null; this.area = new XFAObjectArray(); this.contentArea = new XFAObjectArray(); this.draw = new XFAObjectArray(); this.exclGroup = new XFAObjectArray(); this.field = new XFAObjectArray(); this.subform = new XFAObjectArray(); } [$isUsable]() { if (!this[$extra]) { this[$extra] = { numberOfUse: 0 }; return true; } return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max; } [$cleanPage]() { delete this[$extra]; } [$getNextPage]() { if (!this[$extra]) { this[$extra] = { numberOfUse: 0 }; } const parent = this[$getParent](); if (parent.relation === "orderedOccurrence") { if (this[$isUsable]()) { this[$extra].numberOfUse += 1; return this; } } return parent[$getNextPage](); } [$getAvailableSpace]() { return this[$extra].space || { width: 0, height: 0 }; } [$toHTML]() { if (!this[$extra]) { this[$extra] = { numberOfUse: 1 }; } const children = []; this[$extra].children = children; const style = Object.create(null); if (this.medium && this.medium.short && this.medium.long) { style.width = measureToString(this.medium.short); style.height = measureToString(this.medium.long); this[$extra].space = { width: this.medium.short, height: this.medium.long }; if (this.medium.orientation === "landscape") { const x = style.width; style.width = style.height; style.height = x; this[$extra].space = { width: this.medium.long, height: this.medium.short }; } } else { warn("XFA - No medium specified in pageArea: please file a bug."); } this[$childrenToHTML]({ filter: new Set(["area", "draw", "field", "subform"]), include: true }); this[$childrenToHTML]({ filter: new Set(["contentArea"]), include: true }); return HTMLResult.success({ name: "div", children, attributes: { class: ["xfaPage"], id: this[$uid], style, xfaName: this.name } }); } } class PageSet extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "pageSet", true); this.duplexImposition = getStringOption(attributes.duplexImposition, ["longEdge", "shortEdge"]); this.id = attributes.id || ""; this.name = attributes.name || ""; this.relation = getStringOption(attributes.relation, ["orderedOccurrence", "duplexPaginated", "simplexPaginated"]); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.occur = null; this.pageArea = new XFAObjectArray(); this.pageSet = new XFAObjectArray(); } [$cleanPage]() { for (const page of this.pageArea.children) { page[$cleanPage](); } for (const page of this.pageSet.children) { page[$cleanPage](); } } [$isUsable]() { return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max; } [$getNextPage]() { if (!this[$extra]) { this[$extra] = { numberOfUse: 1, pageIndex: -1, pageSetIndex: -1 }; } if (this.relation === "orderedOccurrence") { if (this[$extra].pageIndex + 1 < this.pageArea.children.length) { this[$extra].pageIndex += 1; const pageArea = this.pageArea.children[this[$extra].pageIndex]; return pageArea[$getNextPage](); } if (this[$extra].pageSetIndex + 1 < this.pageSet.children.length) { this[$extra].pageSetIndex += 1; return this.pageSet.children[this[$extra].pageSetIndex][$getNextPage](); } if (this[$isUsable]()) { this[$extra].numberOfUse += 1; this[$extra].pageIndex = -1; this[$extra].pageSetIndex = -1; return this[$getNextPage](); } const parent = this[$getParent](); if (parent instanceof PageSet) { return parent[$getNextPage](); } this[$cleanPage](); return this[$getNextPage](); } const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber; const parity = pageNumber % 2 === 0 ? "even" : "odd"; const position = pageNumber === 0 ? "first" : "rest"; let page = this.pageArea.children.find(p => p.oddOrEven === parity && p.pagePosition === position); if (page) { return page; } page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === position); if (page) { return page; } page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === "any"); if (page) { return page; } return this.pageArea.children[0]; } } class Para extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "para", true); this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]); this.id = attributes.id || ""; this.lineHeight = attributes.lineHeight ? getMeasurement(attributes.lineHeight, "0pt") : ""; this.marginLeft = attributes.marginLeft ? getMeasurement(attributes.marginLeft, "0pt") : ""; this.marginRight = attributes.marginRight ? getMeasurement(attributes.marginRight, "0pt") : ""; this.orphans = getInteger({ data: attributes.orphans, defaultValue: 0, validate: x => x >= 0 }); this.preserve = attributes.preserve || ""; this.radixOffset = attributes.radixOffset ? getMeasurement(attributes.radixOffset, "0pt") : ""; this.spaceAbove = attributes.spaceAbove ? getMeasurement(attributes.spaceAbove, "0pt") : ""; this.spaceBelow = attributes.spaceBelow ? getMeasurement(attributes.spaceBelow, "0pt") : ""; this.tabDefault = attributes.tabDefault ? getMeasurement(this.tabDefault) : ""; this.tabStops = (attributes.tabStops || "").trim().split(/\s+/).map((x, i) => i % 2 === 1 ? getMeasurement(x) : x); this.textIndent = attributes.textIndent ? getMeasurement(attributes.textIndent, "0pt") : ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.vAlign = getStringOption(attributes.vAlign, ["top", "bottom", "middle"]); this.widows = getInteger({ data: attributes.widows, defaultValue: 0, validate: x => x >= 0 }); this.hyphenation = null; } [$toStyle]() { const style = toStyle(this, "hAlign"); if (this.marginLeft !== "") { style.paddingLeft = measureToString(this.marginLeft); } if (this.marginRight !== "") { style.paddingight = measureToString(this.marginRight); } if (this.spaceAbove !== "") { style.paddingTop = measureToString(this.spaceAbove); } if (this.spaceBelow !== "") { style.paddingBottom = measureToString(this.spaceBelow); } if (this.textIndent !== "") { style.textIndent = measureToString(this.textIndent); fixTextIndent(style); } if (this.lineHeight > 0) { style.lineHeight = measureToString(this.lineHeight); } if (this.tabDefault !== "") { style.tabSize = measureToString(this.tabDefault); } if (this.tabStops.length > 0) {} if (this.hyphenatation) { Object.assign(style, this.hyphenatation[$toStyle]()); } return style; } } class PasswordEdit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "passwordEdit", true); this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]); this.id = attributes.id || ""; this.passwordChar = attributes.passwordChar || "*"; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.extras = null; this.margin = null; } } class template_Pattern extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "pattern", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["crossHatch", "crossDiagonal", "diagonalLeft", "diagonalRight", "horizontal", "vertical"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle](startColor) { startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; const endColor = this.color ? this.color[$toStyle]() : "#000000"; const width = 5; const cmd = "repeating-linear-gradient"; const colors = `${startColor},${startColor} ${width}px,${endColor} ${width}px,${endColor} ${2 * width}px`; switch (this.type) { case "crossHatch": return `${cmd}(to top,${colors}) ${cmd}(to right,${colors})`; case "crossDiagonal": return `${cmd}(45deg,${colors}) ${cmd}(-45deg,${colors})`; case "diagonalLeft": return `${cmd}(45deg,${colors})`; case "diagonalRight": return `${cmd}(-45deg,${colors})`; case "horizontal": return `${cmd}(to top,${colors})`; case "vertical": return `${cmd}(to right,${colors})`; } return ""; } } class Picture extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "picture"); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Proto extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "proto", true); this.appearanceFilter = new XFAObjectArray(); this.arc = new XFAObjectArray(); this.area = new XFAObjectArray(); this.assist = new XFAObjectArray(); this.barcode = new XFAObjectArray(); this.bindItems = new XFAObjectArray(); this.bookend = new XFAObjectArray(); this.boolean = new XFAObjectArray(); this.border = new XFAObjectArray(); this.break = new XFAObjectArray(); this.breakAfter = new XFAObjectArray(); this.breakBefore = new XFAObjectArray(); this.button = new XFAObjectArray(); this.calculate = new XFAObjectArray(); this.caption = new XFAObjectArray(); this.certificate = new XFAObjectArray(); this.certificates = new XFAObjectArray(); this.checkButton = new XFAObjectArray(); this.choiceList = new XFAObjectArray(); this.color = new XFAObjectArray(); this.comb = new XFAObjectArray(); this.connect = new XFAObjectArray(); this.contentArea = new XFAObjectArray(); this.corner = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.dateTimeEdit = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.defaultUi = new XFAObjectArray(); this.desc = new XFAObjectArray(); this.digestMethod = new XFAObjectArray(); this.digestMethods = new XFAObjectArray(); this.draw = new XFAObjectArray(); this.edge = new XFAObjectArray(); this.encoding = new XFAObjectArray(); this.encodings = new XFAObjectArray(); this.encrypt = new XFAObjectArray(); this.encryptData = new XFAObjectArray(); this.encryption = new XFAObjectArray(); this.encryptionMethod = new XFAObjectArray(); this.encryptionMethods = new XFAObjectArray(); this.event = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.exObject = new XFAObjectArray(); this.exclGroup = new XFAObjectArray(); this.execute = new XFAObjectArray(); this.extras = new XFAObjectArray(); this.field = new XFAObjectArray(); this.fill = new XFAObjectArray(); this.filter = new XFAObjectArray(); this.float = new XFAObjectArray(); this.font = new XFAObjectArray(); this.format = new XFAObjectArray(); this.handler = new XFAObjectArray(); this.hyphenation = new XFAObjectArray(); this.image = new XFAObjectArray(); this.imageEdit = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.issuers = new XFAObjectArray(); this.items = new XFAObjectArray(); this.keep = new XFAObjectArray(); this.keyUsage = new XFAObjectArray(); this.line = new XFAObjectArray(); this.linear = new XFAObjectArray(); this.lockDocument = new XFAObjectArray(); this.manifest = new XFAObjectArray(); this.margin = new XFAObjectArray(); this.mdp = new XFAObjectArray(); this.medium = new XFAObjectArray(); this.message = new XFAObjectArray(); this.numericEdit = new XFAObjectArray(); this.occur = new XFAObjectArray(); this.oid = new XFAObjectArray(); this.oids = new XFAObjectArray(); this.overflow = new XFAObjectArray(); this.pageArea = new XFAObjectArray(); this.pageSet = new XFAObjectArray(); this.para = new XFAObjectArray(); this.passwordEdit = new XFAObjectArray(); this.pattern = new XFAObjectArray(); this.picture = new XFAObjectArray(); this.radial = new XFAObjectArray(); this.reason = new XFAObjectArray(); this.reasons = new XFAObjectArray(); this.rectangle = new XFAObjectArray(); this.ref = new XFAObjectArray(); this.script = new XFAObjectArray(); this.setProperty = new XFAObjectArray(); this.signData = new XFAObjectArray(); this.signature = new XFAObjectArray(); this.signing = new XFAObjectArray(); this.solid = new XFAObjectArray(); this.speak = new XFAObjectArray(); this.stipple = new XFAObjectArray(); this.subform = new XFAObjectArray(); this.subformSet = new XFAObjectArray(); this.subjectDN = new XFAObjectArray(); this.subjectDNs = new XFAObjectArray(); this.submit = new XFAObjectArray(); this.text = new XFAObjectArray(); this.textEdit = new XFAObjectArray(); this.time = new XFAObjectArray(); this.timeStamp = new XFAObjectArray(); this.toolTip = new XFAObjectArray(); this.traversal = new XFAObjectArray(); this.traverse = new XFAObjectArray(); this.ui = new XFAObjectArray(); this.validate = new XFAObjectArray(); this.value = new XFAObjectArray(); this.variables = new XFAObjectArray(); } } class Radial extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "radial", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["toEdge", "toCenter"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle](startColor) { startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; const endColor = this.color ? this.color[$toStyle]() : "#000000"; const colors = this.type === "toEdge" ? `${startColor},${endColor}` : `${endColor},${startColor}`; return `radial-gradient(circle at center, ${colors})`; } } class Reason extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "reason"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Reasons extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "reasons", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.reason = new XFAObjectArray(); } } class Rectangle extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "rectangle", true); this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.corner = new XFAObjectArray(4); this.edge = new XFAObjectArray(4); this.fill = null; } [$toHTML]() { const edge = this.edge.children.length ? this.edge.children[0] : new Edge({}); const edgeStyle = edge[$toStyle](); const style = Object.create(null); if (this.fill?.presence === "visible") { Object.assign(style, this.fill[$toStyle]()); } else { style.fill = "transparent"; } style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0); style.stroke = edgeStyle.color; const corner = this.corner.children.length ? this.corner.children[0] : new Corner({}); const cornerStyle = corner[$toStyle](); const rect = { name: "rect", attributes: { xmlns: SVG_NS, width: "100%", height: "100%", x: 0, y: 0, rx: cornerStyle.radius, ry: cornerStyle.radius, style } }; const svg = { name: "svg", children: [rect], attributes: { xmlns: SVG_NS, style: { overflow: "visible" }, width: "100%", height: "100%" } }; const parent = this[$getParent]()[$getParent](); if (hasMargin(parent)) { return HTMLResult.success({ name: "div", attributes: { style: { display: "inline", width: "100%", height: "100%" } }, children: [svg] }); } svg.attributes.style.position = "absolute"; return HTMLResult.success(svg); } } class RefElement extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "ref"); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Script extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "script"); this.binding = attributes.binding || ""; this.contentType = attributes.contentType || ""; this.id = attributes.id || ""; this.name = attributes.name || ""; this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class SetProperty extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "setProperty"); this.connection = attributes.connection || ""; this.ref = attributes.ref || ""; this.target = attributes.target || ""; } } class SignData extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "signData", true); this.id = attributes.id || ""; this.operation = getStringOption(attributes.operation, ["sign", "clear", "verify"]); this.ref = attributes.ref || ""; this.target = attributes.target || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.filter = null; this.manifest = null; } } class Signature extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "signature", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["PDF1.3", "PDF1.6"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.border = null; this.extras = null; this.filter = null; this.manifest = null; this.margin = null; } } class Signing extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "signing", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.certificate = new XFAObjectArray(); } } class Solid extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "solid", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; } [$toStyle](startColor) { return startColor ? startColor[$toStyle]() : "#FFFFFF"; } } class Speak extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "speak"); this.disable = getInteger({ data: attributes.disable, defaultValue: 0, validate: x => x === 1 }); this.id = attributes.id || ""; this.priority = getStringOption(attributes.priority, ["custom", "caption", "name", "toolTip"]); this.rid = attributes.rid || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Stipple extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "stipple", true); this.id = attributes.id || ""; this.rate = getInteger({ data: attributes.rate, defaultValue: 50, validate: x => x >= 0 && x <= 100 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.color = null; this.extras = null; } [$toStyle](bgColor) { const alpha = this.rate / 100; return Util.makeHexColor(Math.round(bgColor.value.r * (1 - alpha) + this.value.r * alpha), Math.round(bgColor.value.g * (1 - alpha) + this.value.g * alpha), Math.round(bgColor.value.b * (1 - alpha) + this.value.b * alpha)); } } class Subform extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "subform", true); this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]); this.allowMacro = getInteger({ data: attributes.allowMacro, defaultValue: 0, validate: x => x === 1 }); this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]); this.colSpan = getInteger({ data: attributes.colSpan, defaultValue: 1, validate: n => n >= 1 || n === -1 }); this.columnWidths = (attributes.columnWidths || "").trim().split(/\s+/).map(x => x === "-1" ? -1 : getMeasurement(x)); this.h = attributes.h ? getMeasurement(attributes.h) : ""; this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]); this.id = attributes.id || ""; this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]); this.locale = attributes.locale || ""; this.maxH = getMeasurement(attributes.maxH, "0pt"); this.maxW = getMeasurement(attributes.maxW, "0pt"); this.mergeMode = getStringOption(attributes.mergeMode, ["consumeData", "matchTemplate"]); this.minH = getMeasurement(attributes.minH, "0pt"); this.minW = getMeasurement(attributes.minW, "0pt"); this.name = attributes.name || ""; this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]); this.relevant = getRelevant(attributes.relevant); this.restoreState = getStringOption(attributes.restoreState, ["manual", "auto"]); this.scope = getStringOption(attributes.scope, ["name", "none"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.w = attributes.w ? getMeasurement(attributes.w) : ""; this.x = getMeasurement(attributes.x, "0pt"); this.y = getMeasurement(attributes.y, "0pt"); this.assist = null; this.bind = null; this.bookend = null; this.border = null; this.break = null; this.calculate = null; this.desc = null; this.extras = null; this.keep = null; this.margin = null; this.occur = null; this.overflow = null; this.pageSet = null; this.para = null; this.traversal = null; this.validate = null; this.variables = null; this.area = new XFAObjectArray(); this.breakAfter = new XFAObjectArray(); this.breakBefore = new XFAObjectArray(); this.connect = new XFAObjectArray(); this.draw = new XFAObjectArray(); this.event = new XFAObjectArray(); this.exObject = new XFAObjectArray(); this.exclGroup = new XFAObjectArray(); this.field = new XFAObjectArray(); this.proto = new XFAObjectArray(); this.setProperty = new XFAObjectArray(); this.subform = new XFAObjectArray(); this.subformSet = new XFAObjectArray(); } [$getSubformParent]() { const parent = this[$getParent](); if (parent instanceof SubformSet) { return parent[$getSubformParent](); } return parent; } [$isBindable]() { return true; } [$isThereMoreWidth]() { return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth](); } *[$getContainedChildren]() { yield* getContainedChildren(this); } [$flushHTML]() { return flushHTML(this); } [$addHTML](html, bbox) { addHTML(this, html, bbox); } [$getAvailableSpace]() { return getAvailableSpace(this); } [$isSplittable]() { const parent = this[$getSubformParent](); if (!parent[$isSplittable]()) { return false; } if (this[$extra]._isSplittable !== undefined) { return this[$extra]._isSplittable; } if (this.layout === "position" || this.layout.includes("row")) { this[$extra]._isSplittable = false; return false; } if (this.keep && this.keep.intact !== "none") { this[$extra]._isSplittable = false; return false; } if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) { return false; } this[$extra]._isSplittable = true; return true; } [$toHTML](availableSpace) { setTabIndex(this); if (this.break) { if (this.break.after !== "auto" || this.break.afterTarget !== "") { const node = new BreakAfter({ targetType: this.break.after, target: this.break.afterTarget, startNew: this.break.startNew.toString() }); node[$globalData] = this[$globalData]; this[$appendChild](node); this.breakAfter.push(node); } if (this.break.before !== "auto" || this.break.beforeTarget !== "") { const node = new BreakBefore({ targetType: this.break.before, target: this.break.beforeTarget, startNew: this.break.startNew.toString() }); node[$globalData] = this[$globalData]; this[$appendChild](node); this.breakBefore.push(node); } if (this.break.overflowTarget !== "") { const node = new Overflow({ target: this.break.overflowTarget, leader: this.break.overflowLeader, trailer: this.break.overflowTrailer }); node[$globalData] = this[$globalData]; this[$appendChild](node); this.overflow.push(node); } this[$removeChild](this.break); this.break = null; } if (this.presence === "hidden" || this.presence === "inactive") { return HTMLResult.EMPTY; } if (this.breakBefore.children.length > 1 || this.breakAfter.children.length > 1) { warn("XFA - Several breakBefore or breakAfter in subforms: please file a bug."); } if (this.breakBefore.children.length >= 1) { const breakBefore = this.breakBefore.children[0]; if (handleBreak(breakBefore)) { return HTMLResult.breakNode(breakBefore); } } if (this[$extra]?.afterBreakAfter) { return HTMLResult.EMPTY; } fixDimensions(this); const children = []; const attributes = { id: this[$uid], class: [] }; setAccess(this, attributes.class); if (!this[$extra]) { this[$extra] = Object.create(null); } Object.assign(this[$extra], { children, line: null, attributes, attempt: 0, numberInLine: 0, availableSpace: { width: Math.min(this.w || Infinity, availableSpace.width), height: Math.min(this.h || Infinity, availableSpace.height) }, width: 0, height: 0, prevHeight: 0, currentWidth: 0 }); const root = this[$getTemplateRoot](); const savedNoLayoutFailure = root[$extra].noLayoutFailure; const isSplittable = this[$isSplittable](); if (!isSplittable) { setFirstUnsplittable(this); } if (!checkDimensions(this, availableSpace)) { return HTMLResult.FAILURE; } const filter = new Set(["area", "draw", "exclGroup", "field", "subform", "subformSet"]); if (this.layout.includes("row")) { const columnWidths = this[$getSubformParent]().columnWidths; if (Array.isArray(columnWidths) && columnWidths.length > 0) { this[$extra].columnWidths = columnWidths; this[$extra].currentColumn = 0; } } const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign"); const classNames = ["xfaSubform"]; const cl = layoutClass(this); if (cl) { classNames.push(cl); } attributes.style = style; attributes.class = classNames; if (this.name) { attributes.xfaName = this.name; } if (this.overflow) { const overflowExtra = this.overflow[$getExtra](); if (overflowExtra.addLeader) { overflowExtra.addLeader = false; handleOverflow(this, overflowExtra.leader, availableSpace); } } this[$pushPara](); const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb"; const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1; for (; this[$extra].attempt < maxRun; this[$extra].attempt++) { if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) { this[$extra].numberInLine = 0; } const result = this[$childrenToHTML]({ filter, include: true }); if (result.success) { break; } if (result.isBreak()) { this[$popPara](); return result; } if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !root[$extra].noLayoutFailure) { this[$extra].attempt = maxRun; break; } } this[$popPara](); if (!isSplittable) { unsetFirstUnsplittable(this); } root[$extra].noLayoutFailure = savedNoLayoutFailure; if (this[$extra].attempt === maxRun) { if (this.overflow) { this[$getTemplateRoot]()[$extra].overflowNode = this.overflow; } if (!isSplittable) { delete this[$extra]; } return HTMLResult.FAILURE; } if (this.overflow) { const overflowExtra = this.overflow[$getExtra](); if (overflowExtra.addTrailer) { overflowExtra.addTrailer = false; handleOverflow(this, overflowExtra.trailer, availableSpace); } } let marginH = 0; let marginV = 0; if (this.margin) { marginH = this.margin.leftInset + this.margin.rightInset; marginV = this.margin.topInset + this.margin.bottomInset; } const width = Math.max(this[$extra].width + marginH, this.w || 0); const height = Math.max(this[$extra].height + marginV, this.h || 0); const bbox = [this.x, this.y, width, height]; if (this.w === "") { style.width = measureToString(width); } if (this.h === "") { style.height = measureToString(height); } if ((style.width === "0px" || style.height === "0px") && children.length === 0) { return HTMLResult.EMPTY; } const html = { name: "div", attributes, children }; applyAssist(this, attributes); const result = HTMLResult.success(createWrapper(this, html), bbox); if (this.breakAfter.children.length >= 1) { const breakAfter = this.breakAfter.children[0]; if (handleBreak(breakAfter)) { this[$extra].afterBreakAfter = result; return HTMLResult.breakNode(breakAfter); } } delete this[$extra]; return result; } } class SubformSet extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "subformSet", true); this.id = attributes.id || ""; this.name = attributes.name || ""; this.relation = getStringOption(attributes.relation, ["ordered", "choice", "unordered"]); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.bookend = null; this.break = null; this.desc = null; this.extras = null; this.occur = null; this.overflow = null; this.breakAfter = new XFAObjectArray(); this.breakBefore = new XFAObjectArray(); this.subform = new XFAObjectArray(); this.subformSet = new XFAObjectArray(); } *[$getContainedChildren]() { yield* getContainedChildren(this); } [$getSubformParent]() { let parent = this[$getParent](); while (!(parent instanceof Subform)) { parent = parent[$getParent](); } return parent; } [$isBindable]() { return true; } } class SubjectDN extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "subjectDN"); this.delimiter = attributes.delimiter || ","; this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { this[$content] = new Map(this[$content].split(this.delimiter).map(kv => { kv = kv.split("=", 2); kv[0] = kv[0].trim(); return kv; })); } } class SubjectDNs extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "subjectDNs", true); this.id = attributes.id || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.subjectDN = new XFAObjectArray(); } } class Submit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "submit", true); this.embedPDF = getInteger({ data: attributes.embedPDF, defaultValue: 0, validate: x => x === 1 }); this.format = getStringOption(attributes.format, ["xdp", "formdata", "pdf", "urlencoded", "xfd", "xml"]); this.id = attributes.id || ""; this.target = attributes.target || ""; this.textEncoding = getKeyword({ data: attributes.textEncoding ? attributes.textEncoding.toLowerCase() : "", defaultValue: "", validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/) }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.xdpContent = attributes.xdpContent || ""; this.encrypt = null; this.encryptData = new XFAObjectArray(); this.signData = new XFAObjectArray(); } } class Template extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "template", true); this.baseProfile = getStringOption(attributes.baseProfile, ["full", "interactiveForms"]); this.extras = null; this.subform = new XFAObjectArray(); } [$finalize]() { if (this.subform.children.length === 0) { warn("XFA - No subforms in template node."); } if (this.subform.children.length >= 2) { warn("XFA - Several subforms in template node: please file a bug."); } this[$tabIndex] = DEFAULT_TAB_INDEX; } [$isSplittable]() { return true; } [$searchNode](expr, container) { if (expr.startsWith("#")) { return [this[$ids].get(expr.slice(1))]; } return searchNode(this, container, expr, true, true); } *[$toPages]() { if (!this.subform.children.length) { return HTMLResult.success({ name: "div", children: [] }); } this[$extra] = { overflowNode: null, firstUnsplittable: null, currentContentArea: null, currentPageArea: null, noLayoutFailure: false, pageNumber: 1, pagePosition: "first", oddOrEven: "odd", blankOrNotBlank: "nonBlank", paraStack: [] }; const root = this.subform.children[0]; root.pageSet[$cleanPage](); const pageAreas = root.pageSet.pageArea.children; const mainHtml = { name: "div", children: [] }; let pageArea = null; let breakBefore = null; let breakBeforeTarget = null; if (root.breakBefore.children.length >= 1) { breakBefore = root.breakBefore.children[0]; breakBeforeTarget = breakBefore.target; } else if (root.subform.children.length >= 1 && root.subform.children[0].breakBefore.children.length >= 1) { breakBefore = root.subform.children[0].breakBefore.children[0]; breakBeforeTarget = breakBefore.target; } else if (root.break?.beforeTarget) { breakBefore = root.break; breakBeforeTarget = breakBefore.beforeTarget; } else if (root.subform.children.length >= 1 && root.subform.children[0].break?.beforeTarget) { breakBefore = root.subform.children[0].break; breakBeforeTarget = breakBefore.beforeTarget; } if (breakBefore) { const target = this[$searchNode](breakBeforeTarget, breakBefore[$getParent]()); if (target instanceof PageArea) { pageArea = target; breakBefore[$extra] = {}; } } if (!pageArea) { pageArea = pageAreas[0]; } pageArea[$extra] = { numberOfUse: 1 }; const pageAreaParent = pageArea[$getParent](); pageAreaParent[$extra] = { numberOfUse: 1, pageIndex: pageAreaParent.pageArea.children.indexOf(pageArea), pageSetIndex: 0 }; let targetPageArea; let leader = null; let trailer = null; let hasSomething = true; let hasSomethingCounter = 0; let startIndex = 0; while (true) { if (!hasSomething) { mainHtml.children.pop(); if (++hasSomethingCounter === MAX_EMPTY_PAGES) { warn("XFA - Something goes wrong: please file a bug."); return mainHtml; } } else { hasSomethingCounter = 0; } targetPageArea = null; this[$extra].currentPageArea = pageArea; const page = pageArea[$toHTML]().html; mainHtml.children.push(page); if (leader) { this[$extra].noLayoutFailure = true; page.children.push(leader[$toHTML](pageArea[$extra].space).html); leader = null; } if (trailer) { this[$extra].noLayoutFailure = true; page.children.push(trailer[$toHTML](pageArea[$extra].space).html); trailer = null; } const contentAreas = pageArea.contentArea.children; const htmlContentAreas = page.children.filter(node => node.attributes.class.includes("xfaContentarea")); hasSomething = false; this[$extra].firstUnsplittable = null; this[$extra].noLayoutFailure = false; const flush = index => { const html = root[$flushHTML](); if (html) { hasSomething ||= html.children?.length > 0; htmlContentAreas[index].children.push(html); } }; for (let i = startIndex, ii = contentAreas.length; i < ii; i++) { const contentArea = this[$extra].currentContentArea = contentAreas[i]; const space = { width: contentArea.w, height: contentArea.h }; startIndex = 0; if (leader) { htmlContentAreas[i].children.push(leader[$toHTML](space).html); leader = null; } if (trailer) { htmlContentAreas[i].children.push(trailer[$toHTML](space).html); trailer = null; } const html = root[$toHTML](space); if (html.success) { if (html.html) { hasSomething ||= html.html.children?.length > 0; htmlContentAreas[i].children.push(html.html); } else if (!hasSomething && mainHtml.children.length > 1) { mainHtml.children.pop(); } return mainHtml; } if (html.isBreak()) { const node = html.breakNode; flush(i); if (node.targetType === "auto") { continue; } if (node.leader) { leader = this[$searchNode](node.leader, node[$getParent]()); leader = leader ? leader[0] : null; } if (node.trailer) { trailer = this[$searchNode](node.trailer, node[$getParent]()); trailer = trailer ? trailer[0] : null; } if (node.targetType === "pageArea") { targetPageArea = node[$extra].target; i = Infinity; } else if (!node[$extra].target) { i = node[$extra].index; } else { targetPageArea = node[$extra].target; startIndex = node[$extra].index + 1; i = Infinity; } continue; } if (this[$extra].overflowNode) { const node = this[$extra].overflowNode; this[$extra].overflowNode = null; const overflowExtra = node[$getExtra](); const target = overflowExtra.target; overflowExtra.addLeader = overflowExtra.leader !== null; overflowExtra.addTrailer = overflowExtra.trailer !== null; flush(i); const currentIndex = i; i = Infinity; if (target instanceof PageArea) { targetPageArea = target; } else if (target instanceof ContentArea) { const index = contentAreas.indexOf(target); if (index !== -1) { if (index > currentIndex) { i = index - 1; } else { startIndex = index; } } else { targetPageArea = target[$getParent](); startIndex = targetPageArea.contentArea.children.indexOf(target); } } continue; } flush(i); } this[$extra].pageNumber += 1; if (targetPageArea) { if (targetPageArea[$isUsable]()) { targetPageArea[$extra].numberOfUse += 1; } else { targetPageArea = null; } } pageArea = targetPageArea || pageArea[$getNextPage](); yield null; } } } class Text extends ContentObject { constructor(attributes) { super(TEMPLATE_NS_ID, "text"); this.id = attributes.id || ""; this.maxChars = getInteger({ data: attributes.maxChars, defaultValue: 0, validate: x => x >= 0 }); this.name = attributes.name || ""; this.rid = attributes.rid || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$acceptWhitespace]() { return true; } [$onChild](child) { if (child[$namespaceId] === NamespaceIds.xhtml.id) { this[$content] = child; return true; } warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`); return false; } [$onText](str) { if (this[$content] instanceof XFAObject) { return; } super[$onText](str); } [$finalize]() { if (typeof this[$content] === "string") { this[$content] = this[$content].replaceAll("\r\n", "\n"); } } [$getExtra]() { if (typeof this[$content] === "string") { return this[$content].split(/[\u2029\u2028\n]/).reduce((acc, line) => { if (line) { acc.push(line); } return acc; }, []).join("\n"); } return this[$content][$text](); } [$toHTML](availableSpace) { if (typeof this[$content] === "string") { const html = valueToHtml(this[$content]).html; if (this[$content].includes("\u2029")) { html.name = "div"; html.children = []; this[$content].split("\u2029").map(para => para.split(/[\u2028\n]/).reduce((acc, line) => { acc.push({ name: "span", value: line }, { name: "br" }); return acc; }, [])).forEach(lines => { html.children.push({ name: "p", children: lines }); }); } else if (/[\u2028\n]/.test(this[$content])) { html.name = "div"; html.children = []; this[$content].split(/[\u2028\n]/).forEach(line => { html.children.push({ name: "span", value: line }, { name: "br" }); }); } return HTMLResult.success(html); } return this[$content][$toHTML](availableSpace); } } class TextEdit extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "textEdit", true); this.allowRichText = getInteger({ data: attributes.allowRichText, defaultValue: 0, validate: x => x === 1 }); this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]); this.id = attributes.id || ""; this.multiLine = getInteger({ data: attributes.multiLine, defaultValue: "", validate: x => x === 0 || x === 1 }); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.vScrollPolicy = getStringOption(attributes.vScrollPolicy, ["auto", "off", "on"]); this.border = null; this.comb = null; this.extras = null; this.margin = null; } [$toHTML](availableSpace) { const style = toStyle(this, "border", "font", "margin"); let html; const field = this[$getParent]()[$getParent](); if (this.multiLine === "") { this.multiLine = field instanceof Draw ? 1 : 0; } if (this.multiLine === 1) { html = { name: "textarea", attributes: { dataId: field[$data]?.[$uid] || field[$uid], fieldId: field[$uid], class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), "aria-required": false } }; } else { html = { name: "input", attributes: { type: "text", dataId: field[$data]?.[$uid] || field[$uid], fieldId: field[$uid], class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), "aria-required": false } }; } if (isRequired(field)) { html.attributes["aria-required"] = true; html.attributes.required = true; } return HTMLResult.success({ name: "label", attributes: { class: ["xfaLabel"] }, children: [html] }); } } class Time extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "time"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } [$finalize]() { const date = this[$content].trim(); this[$content] = date ? new Date(date) : null; } [$toHTML](availableSpace) { return valueToHtml(this[$content] ? this[$content].toString() : ""); } } class TimeStamp extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "timeStamp"); this.id = attributes.id || ""; this.server = attributes.server || ""; this.type = getStringOption(attributes.type, ["optional", "required"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class ToolTip extends StringObject { constructor(attributes) { super(TEMPLATE_NS_ID, "toolTip"); this.id = attributes.id || ""; this.rid = attributes.rid || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Traversal extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "traversal", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.traverse = new XFAObjectArray(); } } class Traverse extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "traverse", true); this.id = attributes.id || ""; this.operation = getStringOption(attributes.operation, ["next", "back", "down", "first", "left", "right", "up"]); this.ref = attributes.ref || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.script = null; } get name() { return this.operation; } [$isTransparent]() { return false; } } class Ui extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "ui", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.picture = null; this.barcode = null; this.button = null; this.checkButton = null; this.choiceList = null; this.dateTimeEdit = null; this.defaultUi = null; this.imageEdit = null; this.numericEdit = null; this.passwordEdit = null; this.signature = null; this.textEdit = null; } [$getExtra]() { if (this[$extra] === undefined) { for (const name of Object.getOwnPropertyNames(this)) { if (name === "extras" || name === "picture") { continue; } const obj = this[name]; if (!(obj instanceof XFAObject)) { continue; } this[$extra] = obj; return obj; } this[$extra] = null; } return this[$extra]; } [$toHTML](availableSpace) { const obj = this[$getExtra](); if (obj) { return obj[$toHTML](availableSpace); } return HTMLResult.EMPTY; } } class Validate extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "validate", true); this.formatTest = getStringOption(attributes.formatTest, ["warning", "disabled", "error"]); this.id = attributes.id || ""; this.nullTest = getStringOption(attributes.nullTest, ["disabled", "error", "warning"]); this.scriptTest = getStringOption(attributes.scriptTest, ["error", "disabled", "warning"]); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.extras = null; this.message = null; this.picture = null; this.script = null; } } class Value extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "value", true); this.id = attributes.id || ""; this.override = getInteger({ data: attributes.override, defaultValue: 0, validate: x => x === 1 }); this.relevant = getRelevant(attributes.relevant); this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.arc = null; this.boolean = null; this.date = null; this.dateTime = null; this.decimal = null; this.exData = null; this.float = null; this.image = null; this.integer = null; this.line = null; this.rectangle = null; this.text = null; this.time = null; } [$setValue](value) { const parent = this[$getParent](); if (parent instanceof Field) { if (parent.ui?.imageEdit) { if (!this.image) { this.image = new Image({}); this[$appendChild](this.image); } this.image[$content] = value[$content]; return; } } const valueName = value[$nodeName]; if (this[valueName] !== null) { this[valueName][$content] = value[$content]; return; } for (const name of Object.getOwnPropertyNames(this)) { const obj = this[name]; if (obj instanceof XFAObject) { this[name] = null; this[$removeChild](obj); } } this[value[$nodeName]] = value; this[$appendChild](value); } [$text]() { if (this.exData) { if (typeof this.exData[$content] === "string") { return this.exData[$content].trim(); } return this.exData[$content][$text]().trim(); } for (const name of Object.getOwnPropertyNames(this)) { if (name === "image") { continue; } const obj = this[name]; if (obj instanceof XFAObject) { return (obj[$content] || "").toString().trim(); } } return null; } [$toHTML](availableSpace) { for (const name of Object.getOwnPropertyNames(this)) { const obj = this[name]; if (!(obj instanceof XFAObject)) { continue; } return obj[$toHTML](availableSpace); } return HTMLResult.EMPTY; } } class Variables extends XFAObject { constructor(attributes) { super(TEMPLATE_NS_ID, "variables", true); this.id = attributes.id || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; this.boolean = new XFAObjectArray(); this.date = new XFAObjectArray(); this.dateTime = new XFAObjectArray(); this.decimal = new XFAObjectArray(); this.exData = new XFAObjectArray(); this.float = new XFAObjectArray(); this.image = new XFAObjectArray(); this.integer = new XFAObjectArray(); this.manifest = new XFAObjectArray(); this.script = new XFAObjectArray(); this.text = new XFAObjectArray(); this.time = new XFAObjectArray(); } [$isTransparent]() { return true; } } class TemplateNamespace { static [$buildXFAObject](name, attributes) { if (TemplateNamespace.hasOwnProperty(name)) { const node = TemplateNamespace[name](attributes); node[$setSetAttributes](attributes); return node; } return undefined; } static appearanceFilter(attrs) { return new AppearanceFilter(attrs); } static arc(attrs) { return new Arc(attrs); } static area(attrs) { return new Area(attrs); } static assist(attrs) { return new Assist(attrs); } static barcode(attrs) { return new Barcode(attrs); } static bind(attrs) { return new Bind(attrs); } static bindItems(attrs) { return new BindItems(attrs); } static bookend(attrs) { return new Bookend(attrs); } static boolean(attrs) { return new BooleanElement(attrs); } static border(attrs) { return new Border(attrs); } static break(attrs) { return new Break(attrs); } static breakAfter(attrs) { return new BreakAfter(attrs); } static breakBefore(attrs) { return new BreakBefore(attrs); } static button(attrs) { return new Button(attrs); } static calculate(attrs) { return new Calculate(attrs); } static caption(attrs) { return new Caption(attrs); } static certificate(attrs) { return new Certificate(attrs); } static certificates(attrs) { return new Certificates(attrs); } static checkButton(attrs) { return new CheckButton(attrs); } static choiceList(attrs) { return new ChoiceList(attrs); } static color(attrs) { return new Color(attrs); } static comb(attrs) { return new Comb(attrs); } static connect(attrs) { return new Connect(attrs); } static contentArea(attrs) { return new ContentArea(attrs); } static corner(attrs) { return new Corner(attrs); } static date(attrs) { return new DateElement(attrs); } static dateTime(attrs) { return new DateTime(attrs); } static dateTimeEdit(attrs) { return new DateTimeEdit(attrs); } static decimal(attrs) { return new Decimal(attrs); } static defaultUi(attrs) { return new DefaultUi(attrs); } static desc(attrs) { return new Desc(attrs); } static digestMethod(attrs) { return new DigestMethod(attrs); } static digestMethods(attrs) { return new DigestMethods(attrs); } static draw(attrs) { return new Draw(attrs); } static edge(attrs) { return new Edge(attrs); } static encoding(attrs) { return new Encoding(attrs); } static encodings(attrs) { return new Encodings(attrs); } static encrypt(attrs) { return new Encrypt(attrs); } static encryptData(attrs) { return new EncryptData(attrs); } static encryption(attrs) { return new Encryption(attrs); } static encryptionMethod(attrs) { return new EncryptionMethod(attrs); } static encryptionMethods(attrs) { return new EncryptionMethods(attrs); } static event(attrs) { return new Event(attrs); } static exData(attrs) { return new ExData(attrs); } static exObject(attrs) { return new ExObject(attrs); } static exclGroup(attrs) { return new ExclGroup(attrs); } static execute(attrs) { return new Execute(attrs); } static extras(attrs) { return new Extras(attrs); } static field(attrs) { return new Field(attrs); } static fill(attrs) { return new Fill(attrs); } static filter(attrs) { return new Filter(attrs); } static float(attrs) { return new Float(attrs); } static font(attrs) { return new template_Font(attrs); } static format(attrs) { return new Format(attrs); } static handler(attrs) { return new Handler(attrs); } static hyphenation(attrs) { return new Hyphenation(attrs); } static image(attrs) { return new Image(attrs); } static imageEdit(attrs) { return new ImageEdit(attrs); } static integer(attrs) { return new Integer(attrs); } static issuers(attrs) { return new Issuers(attrs); } static items(attrs) { return new Items(attrs); } static keep(attrs) { return new Keep(attrs); } static keyUsage(attrs) { return new KeyUsage(attrs); } static line(attrs) { return new Line(attrs); } static linear(attrs) { return new Linear(attrs); } static lockDocument(attrs) { return new LockDocument(attrs); } static manifest(attrs) { return new Manifest(attrs); } static margin(attrs) { return new Margin(attrs); } static mdp(attrs) { return new Mdp(attrs); } static medium(attrs) { return new Medium(attrs); } static message(attrs) { return new Message(attrs); } static numericEdit(attrs) { return new NumericEdit(attrs); } static occur(attrs) { return new Occur(attrs); } static oid(attrs) { return new Oid(attrs); } static oids(attrs) { return new Oids(attrs); } static overflow(attrs) { return new Overflow(attrs); } static pageArea(attrs) { return new PageArea(attrs); } static pageSet(attrs) { return new PageSet(attrs); } static para(attrs) { return new Para(attrs); } static passwordEdit(attrs) { return new PasswordEdit(attrs); } static pattern(attrs) { return new template_Pattern(attrs); } static picture(attrs) { return new Picture(attrs); } static proto(attrs) { return new Proto(attrs); } static radial(attrs) { return new Radial(attrs); } static reason(attrs) { return new Reason(attrs); } static reasons(attrs) { return new Reasons(attrs); } static rectangle(attrs) { return new Rectangle(attrs); } static ref(attrs) { return new RefElement(attrs); } static script(attrs) { return new Script(attrs); } static setProperty(attrs) { return new SetProperty(attrs); } static signData(attrs) { return new SignData(attrs); } static signature(attrs) { return new Signature(attrs); } static signing(attrs) { return new Signing(attrs); } static solid(attrs) { return new Solid(attrs); } static speak(attrs) { return new Speak(attrs); } static stipple(attrs) { return new Stipple(attrs); } static subform(attrs) { return new Subform(attrs); } static subformSet(attrs) { return new SubformSet(attrs); } static subjectDN(attrs) { return new SubjectDN(attrs); } static subjectDNs(attrs) { return new SubjectDNs(attrs); } static submit(attrs) { return new Submit(attrs); } static template(attrs) { return new Template(attrs); } static text(attrs) { return new Text(attrs); } static textEdit(attrs) { return new TextEdit(attrs); } static time(attrs) { return new Time(attrs); } static timeStamp(attrs) { return new TimeStamp(attrs); } static toolTip(attrs) { return new ToolTip(attrs); } static traversal(attrs) { return new Traversal(attrs); } static traverse(attrs) { return new Traverse(attrs); } static ui(attrs) { return new Ui(attrs); } static validate(attrs) { return new Validate(attrs); } static value(attrs) { return new Value(attrs); } static variables(attrs) { return new Variables(attrs); } } ;// CONCATENATED MODULE: ./src/core/xfa/bind.js const bind_NS_DATASETS = NamespaceIds.datasets.id; function createText(content) { const node = new Text({}); node[$content] = content; return node; } class Binder { constructor(root) { this.root = root; this.datasets = root.datasets; this.data = root.datasets?.data || new XmlObject(NamespaceIds.datasets.id, "data"); this.emptyMerge = this.data[$getChildren]().length === 0; this.root.form = this.form = root.template[$clone](); } _isConsumeData() { return !this.emptyMerge && this._mergeMode; } _isMatchTemplate() { return !this._isConsumeData(); } bind() { this._bindElement(this.form, this.data); return this.form; } getData() { return this.data; } _bindValue(formNode, data, picture) { formNode[$data] = data; if (formNode[$hasSettableValue]()) { if (data[$isDataValue]()) { const value = data[$getDataValue](); formNode[$setValue](createText(value)); } else if (formNode instanceof Field && formNode.ui?.choiceList?.open === "multiSelect") { const value = data[$getChildren]().map(child => child[$content].trim()).join("\n"); formNode[$setValue](createText(value)); } else if (this._isConsumeData()) { warn(`XFA - Nodes haven't the same type.`); } } else if (!data[$isDataValue]() || this._isMatchTemplate()) { this._bindElement(formNode, data); } else { warn(`XFA - Nodes haven't the same type.`); } } _findDataByNameToConsume(name, isValue, dataNode, global) { if (!name) { return null; } let generator, match; for (let i = 0; i < 3; i++) { generator = dataNode[$getRealChildrenByNameIt](name, false, true); while (true) { match = generator.next().value; if (!match) { break; } if (isValue === match[$isDataValue]()) { return match; } } if (dataNode[$namespaceId] === NamespaceIds.datasets.id && dataNode[$nodeName] === "data") { break; } dataNode = dataNode[$getParent](); } if (!global) { return null; } generator = this.data[$getRealChildrenByNameIt](name, true, false); match = generator.next().value; if (match) { return match; } generator = this.data[$getAttributeIt](name, true); match = generator.next().value; if (match?.[$isDataValue]()) { return match; } return null; } _setProperties(formNode, dataNode) { if (!formNode.hasOwnProperty("setProperty")) { return; } for (const { ref, target, connection } of formNode.setProperty.children) { if (connection) { continue; } if (!ref) { continue; } const nodes = searchNode(this.root, dataNode, ref, false, false); if (!nodes) { warn(`XFA - Invalid reference: ${ref}.`); continue; } const [node] = nodes; if (!node[$isDescendent](this.data)) { warn(`XFA - Invalid node: must be a data node.`); continue; } const targetNodes = searchNode(this.root, formNode, target, false, false); if (!targetNodes) { warn(`XFA - Invalid target: ${target}.`); continue; } const [targetNode] = targetNodes; if (!targetNode[$isDescendent](formNode)) { warn(`XFA - Invalid target: must be a property or subproperty.`); continue; } const targetParent = targetNode[$getParent](); if (targetNode instanceof SetProperty || targetParent instanceof SetProperty) { warn(`XFA - Invalid target: cannot be a setProperty or one of its properties.`); continue; } if (targetNode instanceof BindItems || targetParent instanceof BindItems) { warn(`XFA - Invalid target: cannot be a bindItems or one of its properties.`); continue; } const content = node[$text](); const name = targetNode[$nodeName]; if (targetNode instanceof XFAAttribute) { const attrs = Object.create(null); attrs[name] = content; const obj = Reflect.construct(Object.getPrototypeOf(targetParent).constructor, [attrs]); targetParent[name] = obj[name]; continue; } if (!targetNode.hasOwnProperty($content)) { warn(`XFA - Invalid node to use in setProperty`); continue; } targetNode[$data] = node; targetNode[$content] = content; targetNode[$finalize](); } } _bindItems(formNode, dataNode) { if (!formNode.hasOwnProperty("items") || !formNode.hasOwnProperty("bindItems") || formNode.bindItems.isEmpty()) { return; } for (const item of formNode.items.children) { formNode[$removeChild](item); } formNode.items.clear(); const labels = new Items({}); const values = new Items({}); formNode[$appendChild](labels); formNode.items.push(labels); formNode[$appendChild](values); formNode.items.push(values); for (const { ref, labelRef, valueRef, connection } of formNode.bindItems.children) { if (connection) { continue; } if (!ref) { continue; } const nodes = searchNode(this.root, dataNode, ref, false, false); if (!nodes) { warn(`XFA - Invalid reference: ${ref}.`); continue; } for (const node of nodes) { if (!node[$isDescendent](this.datasets)) { warn(`XFA - Invalid ref (${ref}): must be a datasets child.`); continue; } const labelNodes = searchNode(this.root, node, labelRef, true, false); if (!labelNodes) { warn(`XFA - Invalid label: ${labelRef}.`); continue; } const [labelNode] = labelNodes; if (!labelNode[$isDescendent](this.datasets)) { warn(`XFA - Invalid label: must be a datasets child.`); continue; } const valueNodes = searchNode(this.root, node, valueRef, true, false); if (!valueNodes) { warn(`XFA - Invalid value: ${valueRef}.`); continue; } const [valueNode] = valueNodes; if (!valueNode[$isDescendent](this.datasets)) { warn(`XFA - Invalid value: must be a datasets child.`); continue; } const label = createText(labelNode[$text]()); const value = createText(valueNode[$text]()); labels[$appendChild](label); labels.text.push(label); values[$appendChild](value); values.text.push(value); } } } _bindOccurrences(formNode, matches, picture) { let baseClone; if (matches.length > 1) { baseClone = formNode[$clone](); baseClone[$removeChild](baseClone.occur); baseClone.occur = null; } this._bindValue(formNode, matches[0], picture); this._setProperties(formNode, matches[0]); this._bindItems(formNode, matches[0]); if (matches.length === 1) { return; } const parent = formNode[$getParent](); const name = formNode[$nodeName]; const pos = parent[$indexOf](formNode); for (let i = 1, ii = matches.length; i < ii; i++) { const match = matches[i]; const clone = baseClone[$clone](); parent[name].push(clone); parent[$insertAt](pos + i, clone); this._bindValue(clone, match, picture); this._setProperties(clone, match); this._bindItems(clone, match); } } _createOccurrences(formNode) { if (!this.emptyMerge) { return; } const { occur } = formNode; if (!occur || occur.initial <= 1) { return; } const parent = formNode[$getParent](); const name = formNode[$nodeName]; if (!(parent[name] instanceof XFAObjectArray)) { return; } let currentNumber; if (formNode.name) { currentNumber = parent[name].children.filter(e => e.name === formNode.name).length; } else { currentNumber = parent[name].children.length; } const pos = parent[$indexOf](formNode) + 1; const ii = occur.initial - currentNumber; if (ii) { const nodeClone = formNode[$clone](); nodeClone[$removeChild](nodeClone.occur); nodeClone.occur = null; parent[name].push(nodeClone); parent[$insertAt](pos, nodeClone); for (let i = 1; i < ii; i++) { const clone = nodeClone[$clone](); parent[name].push(clone); parent[$insertAt](pos + i, clone); } } } _getOccurInfo(formNode) { const { name, occur } = formNode; if (!occur || !name) { return [1, 1]; } const max = occur.max === -1 ? Infinity : occur.max; return [occur.min, max]; } _setAndBind(formNode, dataNode) { this._setProperties(formNode, dataNode); this._bindItems(formNode, dataNode); this._bindElement(formNode, dataNode); } _bindElement(formNode, dataNode) { const uselessNodes = []; this._createOccurrences(formNode); for (const child of formNode[$getChildren]()) { if (child[$data]) { continue; } if (this._mergeMode === undefined && child[$nodeName] === "subform") { this._mergeMode = child.mergeMode === "consumeData"; const dataChildren = dataNode[$getChildren](); if (dataChildren.length > 0) { this._bindOccurrences(child, [dataChildren[0]], null); } else if (this.emptyMerge) { const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId]; const dataChild = child[$data] = new XmlObject(nsId, child.name || "root"); dataNode[$appendChild](dataChild); this._bindElement(child, dataChild); } continue; } if (!child[$isBindable]()) { continue; } let global = false; let picture = null; let ref = null; let match = null; if (child.bind) { switch (child.bind.match) { case "none": this._setAndBind(child, dataNode); continue; case "global": global = true; break; case "dataRef": if (!child.bind.ref) { warn(`XFA - ref is empty in node ${child[$nodeName]}.`); this._setAndBind(child, dataNode); continue; } ref = child.bind.ref; break; default: break; } if (child.bind.picture) { picture = child.bind.picture[$content]; } } const [min, max] = this._getOccurInfo(child); if (ref) { match = searchNode(this.root, dataNode, ref, true, false); if (match === null) { match = createDataNode(this.data, dataNode, ref); if (!match) { continue; } if (this._isConsumeData()) { match[$consumed] = true; } this._setAndBind(child, match); continue; } else { if (this._isConsumeData()) { match = match.filter(node => !node[$consumed]); } if (match.length > max) { match = match.slice(0, max); } else if (match.length === 0) { match = null; } if (match && this._isConsumeData()) { match.forEach(node => { node[$consumed] = true; }); } } } else { if (!child.name) { this._setAndBind(child, dataNode); continue; } if (this._isConsumeData()) { const matches = []; while (matches.length < max) { const found = this._findDataByNameToConsume(child.name, child[$hasSettableValue](), dataNode, global); if (!found) { break; } found[$consumed] = true; matches.push(found); } match = matches.length > 0 ? matches : null; } else { match = dataNode[$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value; if (!match) { if (min === 0) { uselessNodes.push(child); continue; } const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId]; match = child[$data] = new XmlObject(nsId, child.name); if (this.emptyMerge) { match[$consumed] = true; } dataNode[$appendChild](match); this._setAndBind(child, match); continue; } if (this.emptyMerge) { match[$consumed] = true; } match = [match]; } } if (match) { this._bindOccurrences(child, match, picture); } else if (min > 0) { this._setAndBind(child, dataNode); } else { uselessNodes.push(child); } } uselessNodes.forEach(node => node[$getParent]()[$removeChild](node)); } } ;// CONCATENATED MODULE: ./src/core/xfa/data.js class DataHandler { constructor(root, data) { this.data = data; this.dataset = root.datasets || null; } serialize(storage) { const stack = [[-1, this.data[$getChildren]()]]; while (stack.length > 0) { const last = stack.at(-1); const [i, children] = last; if (i + 1 === children.length) { stack.pop(); continue; } const child = children[++last[0]]; const storageEntry = storage.get(child[$uid]); if (storageEntry) { child[$setValue](storageEntry); } else { const attributes = child[$getAttributes](); for (const value of attributes.values()) { const entry = storage.get(value[$uid]); if (entry) { value[$setValue](entry); break; } } } const nodes = child[$getChildren](); if (nodes.length > 0) { stack.push([-1, nodes]); } } const buf = [`<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">`]; if (this.dataset) { for (const child of this.dataset[$getChildren]()) { if (child[$nodeName] !== "data") { child[$toString](buf); } } } this.data[$toString](buf); buf.push("</xfa:datasets>"); return buf.join(""); } } ;// CONCATENATED MODULE: ./src/core/xfa/config.js const CONFIG_NS_ID = NamespaceIds.config.id; class Acrobat extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "acrobat", true); this.acrobat7 = null; this.autoSave = null; this.common = null; this.validate = null; this.validateApprovalSignatures = null; this.submitUrl = new XFAObjectArray(); } } class Acrobat7 extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "acrobat7", true); this.dynamicRender = null; } } class ADBE_JSConsole extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "ADBE_JSConsole", ["delegate", "Enable", "Disable"]); } } class ADBE_JSDebugger extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "ADBE_JSDebugger", ["delegate", "Enable", "Disable"]); } } class AddSilentPrint extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "addSilentPrint"); } } class AddViewerPreferences extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "addViewerPreferences"); } } class AdjustData extends Option10 { constructor(attributes) { super(CONFIG_NS_ID, "adjustData"); } } class AdobeExtensionLevel extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "adobeExtensionLevel", 0, n => n >= 1 && n <= 8); } } class Agent extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "agent", true); this.name = attributes.name ? attributes.name.trim() : ""; this.common = new XFAObjectArray(); } } class AlwaysEmbed extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "alwaysEmbed"); } } class Amd extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "amd"); } } class config_Area extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "area"); this.level = getInteger({ data: attributes.level, defaultValue: 0, validate: n => n >= 1 && n <= 3 }); this.name = getStringOption(attributes.name, ["", "barcode", "coreinit", "deviceDriver", "font", "general", "layout", "merge", "script", "signature", "sourceSet", "templateCache"]); } } class Attributes extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "attributes", ["preserve", "delegate", "ignore"]); } } class AutoSave extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "autoSave", ["disabled", "enabled"]); } } class Base extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "base"); } } class BatchOutput extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "batchOutput"); this.format = getStringOption(attributes.format, ["none", "concat", "zip", "zipCompress"]); } } class BehaviorOverride extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "behaviorOverride"); } [$finalize]() { this[$content] = new Map(this[$content].trim().split(/\s+/).filter(x => x.includes(":")).map(x => x.split(":", 2))); } } class Cache extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "cache", true); this.templateCache = null; } } class Change extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "change"); } } class Common extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "common", true); this.data = null; this.locale = null; this.localeSet = null; this.messaging = null; this.suppressBanner = null; this.template = null; this.validationMessaging = null; this.versionControl = null; this.log = new XFAObjectArray(); } } class Compress extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "compress"); this.scope = getStringOption(attributes.scope, ["imageOnly", "document"]); } } class CompressLogicalStructure extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "compressLogicalStructure"); } } class CompressObjectStream extends Option10 { constructor(attributes) { super(CONFIG_NS_ID, "compressObjectStream"); } } class Compression extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "compression", true); this.compressLogicalStructure = null; this.compressObjectStream = null; this.level = null; this.type = null; } } class Config extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "config", true); this.acrobat = null; this.present = null; this.trace = null; this.agent = new XFAObjectArray(); } } class Conformance extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "conformance", ["A", "B"]); } } class ContentCopy extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "contentCopy"); } } class Copies extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "copies", 1, n => n >= 1); } } class Creator extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "creator"); } } class CurrentPage extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "currentPage", 0, n => n >= 0); } } class Data extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "data", true); this.adjustData = null; this.attributes = null; this.incrementalLoad = null; this.outputXSL = null; this.range = null; this.record = null; this.startNode = null; this.uri = null; this.window = null; this.xsl = null; this.excludeNS = new XFAObjectArray(); this.transform = new XFAObjectArray(); } } class Debug extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "debug", true); this.uri = null; } } class DefaultTypeface extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "defaultTypeface"); this.writingScript = getStringOption(attributes.writingScript, ["*", "Arabic", "Cyrillic", "EastEuropeanRoman", "Greek", "Hebrew", "Japanese", "Korean", "Roman", "SimplifiedChinese", "Thai", "TraditionalChinese", "Vietnamese"]); } } class Destination extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "destination", ["pdf", "pcl", "ps", "webClient", "zpl"]); } } class DocumentAssembly extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "documentAssembly"); } } class Driver extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "driver", true); this.name = attributes.name ? attributes.name.trim() : ""; this.fontInfo = null; this.xdc = null; } } class DuplexOption extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "duplexOption", ["simplex", "duplexFlipLongEdge", "duplexFlipShortEdge"]); } } class DynamicRender extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "dynamicRender", ["forbidden", "required"]); } } class Embed extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "embed"); } } class config_Encrypt extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "encrypt"); } } class config_Encryption extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "encryption", true); this.encrypt = null; this.encryptionLevel = null; this.permissions = null; } } class EncryptionLevel extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "encryptionLevel", ["40bit", "128bit"]); } } class Enforce extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "enforce"); } } class Equate extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "equate"); this.force = getInteger({ data: attributes.force, defaultValue: 1, validate: n => n === 0 }); this.from = attributes.from || ""; this.to = attributes.to || ""; } } class EquateRange extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "equateRange"); this.from = attributes.from || ""; this.to = attributes.to || ""; this._unicodeRange = attributes.unicodeRange || ""; } get unicodeRange() { const ranges = []; const unicodeRegex = /U\+([0-9a-fA-F]+)/; const unicodeRange = this._unicodeRange; for (let range of unicodeRange.split(",").map(x => x.trim()).filter(x => !!x)) { range = range.split("-", 2).map(x => { const found = x.match(unicodeRegex); if (!found) { return 0; } return parseInt(found[1], 16); }); if (range.length === 1) { range.push(range[0]); } ranges.push(range); } return shadow(this, "unicodeRange", ranges); } } class Exclude extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "exclude"); } [$finalize]() { this[$content] = this[$content].trim().split(/\s+/).filter(x => x && ["calculate", "close", "enter", "exit", "initialize", "ready", "validate"].includes(x)); } } class ExcludeNS extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "excludeNS"); } } class FlipLabel extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "flipLabel", ["usePrinterSetting", "on", "off"]); } } class config_FontInfo extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "fontInfo", true); this.embed = null; this.map = null; this.subsetBelow = null; this.alwaysEmbed = new XFAObjectArray(); this.defaultTypeface = new XFAObjectArray(); this.neverEmbed = new XFAObjectArray(); } } class FormFieldFilling extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "formFieldFilling"); } } class GroupParent extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "groupParent"); } } class IfEmpty extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "ifEmpty", ["dataValue", "dataGroup", "ignore", "remove"]); } } class IncludeXDPContent extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "includeXDPContent"); } } class IncrementalLoad extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "incrementalLoad", ["none", "forwardOnly"]); } } class IncrementalMerge extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "incrementalMerge"); } } class Interactive extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "interactive"); } } class Jog extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "jog", ["usePrinterSetting", "none", "pageSet"]); } } class LabelPrinter extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "labelPrinter", true); this.name = getStringOption(attributes.name, ["zpl", "dpl", "ipl", "tcpl"]); this.batchOutput = null; this.flipLabel = null; this.fontInfo = null; this.xdc = null; } } class Layout extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "layout", ["paginate", "panel"]); } } class Level extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "level", 0, n => n > 0); } } class Linearized extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "linearized"); } } class Locale extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "locale"); } } class LocaleSet extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "localeSet"); } } class Log extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "log", true); this.mode = null; this.threshold = null; this.to = null; this.uri = null; } } class MapElement extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "map", true); this.equate = new XFAObjectArray(); this.equateRange = new XFAObjectArray(); } } class MediumInfo extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "mediumInfo", true); this.map = null; } } class config_Message extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "message", true); this.msgId = null; this.severity = null; } } class Messaging extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "messaging", true); this.message = new XFAObjectArray(); } } class Mode extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "mode", ["append", "overwrite"]); } } class ModifyAnnots extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "modifyAnnots"); } } class MsgId extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "msgId", 1, n => n >= 1); } } class NameAttr extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "nameAttr"); } } class NeverEmbed extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "neverEmbed"); } } class NumberOfCopies extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "numberOfCopies", null, n => n >= 2 && n <= 5); } } class OpenAction extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "openAction", true); this.destination = null; } } class Output extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "output", true); this.to = null; this.type = null; this.uri = null; } } class OutputBin extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "outputBin"); } } class OutputXSL extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "outputXSL", true); this.uri = null; } } class Overprint extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "overprint", ["none", "both", "draw", "field"]); } } class Packets extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "packets"); } [$finalize]() { if (this[$content] === "*") { return; } this[$content] = this[$content].trim().split(/\s+/).filter(x => ["config", "datasets", "template", "xfdf", "xslt"].includes(x)); } } class PageOffset extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "pageOffset"); this.x = getInteger({ data: attributes.x, defaultValue: "useXDCSetting", validate: n => true }); this.y = getInteger({ data: attributes.y, defaultValue: "useXDCSetting", validate: n => true }); } } class PageRange extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "pageRange"); } [$finalize]() { const numbers = this[$content].trim().split(/\s+/).map(x => parseInt(x, 10)); const ranges = []; for (let i = 0, ii = numbers.length; i < ii; i += 2) { ranges.push(numbers.slice(i, i + 2)); } this[$content] = ranges; } } class Pagination extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "pagination", ["simplex", "duplexShortEdge", "duplexLongEdge"]); } } class PaginationOverride extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "paginationOverride", ["none", "forceDuplex", "forceDuplexLongEdge", "forceDuplexShortEdge", "forceSimplex"]); } } class Part extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "part", 1, n => false); } } class Pcl extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "pcl", true); this.name = attributes.name || ""; this.batchOutput = null; this.fontInfo = null; this.jog = null; this.mediumInfo = null; this.outputBin = null; this.pageOffset = null; this.staple = null; this.xdc = null; } } class Pdf extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "pdf", true); this.name = attributes.name || ""; this.adobeExtensionLevel = null; this.batchOutput = null; this.compression = null; this.creator = null; this.encryption = null; this.fontInfo = null; this.interactive = null; this.linearized = null; this.openAction = null; this.pdfa = null; this.producer = null; this.renderPolicy = null; this.scriptModel = null; this.silentPrint = null; this.submitFormat = null; this.tagged = null; this.version = null; this.viewerPreferences = null; this.xdc = null; } } class Pdfa extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "pdfa", true); this.amd = null; this.conformance = null; this.includeXDPContent = null; this.part = null; } } class Permissions extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "permissions", true); this.accessibleContent = null; this.change = null; this.contentCopy = null; this.documentAssembly = null; this.formFieldFilling = null; this.modifyAnnots = null; this.plaintextMetadata = null; this.print = null; this.printHighQuality = null; } } class PickTrayByPDFSize extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "pickTrayByPDFSize"); } } class config_Picture extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "picture"); } } class PlaintextMetadata extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "plaintextMetadata"); } } class Presence extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "presence", ["preserve", "dissolve", "dissolveStructure", "ignore", "remove"]); } } class Present extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "present", true); this.behaviorOverride = null; this.cache = null; this.common = null; this.copies = null; this.destination = null; this.incrementalMerge = null; this.layout = null; this.output = null; this.overprint = null; this.pagination = null; this.paginationOverride = null; this.script = null; this.validate = null; this.xdp = null; this.driver = new XFAObjectArray(); this.labelPrinter = new XFAObjectArray(); this.pcl = new XFAObjectArray(); this.pdf = new XFAObjectArray(); this.ps = new XFAObjectArray(); this.submitUrl = new XFAObjectArray(); this.webClient = new XFAObjectArray(); this.zpl = new XFAObjectArray(); } } class Print extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "print"); } } class PrintHighQuality extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "printHighQuality"); } } class PrintScaling extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "printScaling", ["appdefault", "noScaling"]); } } class PrinterName extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "printerName"); } } class Producer extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "producer"); } } class Ps extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "ps", true); this.name = attributes.name || ""; this.batchOutput = null; this.fontInfo = null; this.jog = null; this.mediumInfo = null; this.outputBin = null; this.staple = null; this.xdc = null; } } class Range extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "range"); } [$finalize]() { this[$content] = this[$content].trim().split(/\s*,\s*/, 2).map(range => range.split("-").map(x => parseInt(x.trim(), 10))).filter(range => range.every(x => !isNaN(x))).map(range => { if (range.length === 1) { range.push(range[0]); } return range; }); } } class Record extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "record"); } [$finalize]() { this[$content] = this[$content].trim(); const n = parseInt(this[$content], 10); if (!isNaN(n) && n >= 0) { this[$content] = n; } } } class Relevant extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "relevant"); } [$finalize]() { this[$content] = this[$content].trim().split(/\s+/); } } class Rename extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "rename"); } [$finalize]() { this[$content] = this[$content].trim(); if (this[$content].toLowerCase().startsWith("xml") || new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content])) { warn("XFA - Rename: invalid XFA name"); } } } class RenderPolicy extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "renderPolicy", ["server", "client"]); } } class RunScripts extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "runScripts", ["both", "client", "none", "server"]); } } class config_Script extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "script", true); this.currentPage = null; this.exclude = null; this.runScripts = null; } } class ScriptModel extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "scriptModel", ["XFA", "none"]); } } class Severity extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "severity", ["ignore", "error", "information", "trace", "warning"]); } } class SilentPrint extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "silentPrint", true); this.addSilentPrint = null; this.printerName = null; } } class Staple extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "staple"); this.mode = getStringOption(attributes.mode, ["usePrinterSetting", "on", "off"]); } } class StartNode extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "startNode"); } } class StartPage extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "startPage", 0, n => true); } } class SubmitFormat extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "submitFormat", ["html", "delegate", "fdf", "xml", "pdf"]); } } class SubmitUrl extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "submitUrl"); } } class SubsetBelow extends IntegerObject { constructor(attributes) { super(CONFIG_NS_ID, "subsetBelow", 100, n => n >= 0 && n <= 100); } } class SuppressBanner extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "suppressBanner"); } } class Tagged extends Option01 { constructor(attributes) { super(CONFIG_NS_ID, "tagged"); } } class config_Template extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "template", true); this.base = null; this.relevant = null; this.startPage = null; this.uri = null; this.xsl = null; } } class Threshold extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "threshold", ["trace", "error", "information", "warning"]); } } class To extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "to", ["null", "memory", "stderr", "stdout", "system", "uri"]); } } class TemplateCache extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "templateCache"); this.maxEntries = getInteger({ data: attributes.maxEntries, defaultValue: 5, validate: n => n >= 0 }); } } class Trace extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "trace", true); this.area = new XFAObjectArray(); } } class config_Transform extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "transform", true); this.groupParent = null; this.ifEmpty = null; this.nameAttr = null; this.picture = null; this.presence = null; this.rename = null; this.whitespace = null; } } class Type extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "type", ["none", "ascii85", "asciiHex", "ccittfax", "flate", "lzw", "runLength", "native", "xdp", "mergedXDP"]); } } class Uri extends StringObject { constructor(attributes) { super(CONFIG_NS_ID, "uri"); } } class config_Validate extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "validate", ["preSubmit", "prePrint", "preExecute", "preSave"]); } } class ValidateApprovalSignatures extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "validateApprovalSignatures"); } [$finalize]() { this[$content] = this[$content].trim().split(/\s+/).filter(x => ["docReady", "postSign"].includes(x)); } } class ValidationMessaging extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "validationMessaging", ["allMessagesIndividually", "allMessagesTogether", "firstMessageOnly", "noMessages"]); } } class Version extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "version", ["1.7", "1.6", "1.5", "1.4", "1.3", "1.2"]); } } class VersionControl extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "VersionControl"); this.outputBelow = getStringOption(attributes.outputBelow, ["warn", "error", "update"]); this.sourceAbove = getStringOption(attributes.sourceAbove, ["warn", "error"]); this.sourceBelow = getStringOption(attributes.sourceBelow, ["update", "maintain"]); } } class ViewerPreferences extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "viewerPreferences", true); this.ADBE_JSConsole = null; this.ADBE_JSDebugger = null; this.addViewerPreferences = null; this.duplexOption = null; this.enforce = null; this.numberOfCopies = null; this.pageRange = null; this.pickTrayByPDFSize = null; this.printScaling = null; } } class WebClient extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "webClient", true); this.name = attributes.name ? attributes.name.trim() : ""; this.fontInfo = null; this.xdc = null; } } class Whitespace extends OptionObject { constructor(attributes) { super(CONFIG_NS_ID, "whitespace", ["preserve", "ltrim", "normalize", "rtrim", "trim"]); } } class Window extends ContentObject { constructor(attributes) { super(CONFIG_NS_ID, "window"); } [$finalize]() { const pair = this[$content].trim().split(/\s*,\s*/, 2).map(x => parseInt(x, 10)); if (pair.some(x => isNaN(x))) { this[$content] = [0, 0]; return; } if (pair.length === 1) { pair.push(pair[0]); } this[$content] = pair; } } class Xdc extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "xdc", true); this.uri = new XFAObjectArray(); this.xsl = new XFAObjectArray(); } } class Xdp extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "xdp", true); this.packets = null; } } class Xsl extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "xsl", true); this.debug = null; this.uri = null; } } class Zpl extends XFAObject { constructor(attributes) { super(CONFIG_NS_ID, "zpl", true); this.name = attributes.name ? attributes.name.trim() : ""; this.batchOutput = null; this.flipLabel = null; this.fontInfo = null; this.xdc = null; } } class ConfigNamespace { static [$buildXFAObject](name, attributes) { if (ConfigNamespace.hasOwnProperty(name)) { return ConfigNamespace[name](attributes); } return undefined; } static acrobat(attrs) { return new Acrobat(attrs); } static acrobat7(attrs) { return new Acrobat7(attrs); } static ADBE_JSConsole(attrs) { return new ADBE_JSConsole(attrs); } static ADBE_JSDebugger(attrs) { return new ADBE_JSDebugger(attrs); } static addSilentPrint(attrs) { return new AddSilentPrint(attrs); } static addViewerPreferences(attrs) { return new AddViewerPreferences(attrs); } static adjustData(attrs) { return new AdjustData(attrs); } static adobeExtensionLevel(attrs) { return new AdobeExtensionLevel(attrs); } static agent(attrs) { return new Agent(attrs); } static alwaysEmbed(attrs) { return new AlwaysEmbed(attrs); } static amd(attrs) { return new Amd(attrs); } static area(attrs) { return new config_Area(attrs); } static attributes(attrs) { return new Attributes(attrs); } static autoSave(attrs) { return new AutoSave(attrs); } static base(attrs) { return new Base(attrs); } static batchOutput(attrs) { return new BatchOutput(attrs); } static behaviorOverride(attrs) { return new BehaviorOverride(attrs); } static cache(attrs) { return new Cache(attrs); } static change(attrs) { return new Change(attrs); } static common(attrs) { return new Common(attrs); } static compress(attrs) { return new Compress(attrs); } static compressLogicalStructure(attrs) { return new CompressLogicalStructure(attrs); } static compressObjectStream(attrs) { return new CompressObjectStream(attrs); } static compression(attrs) { return new Compression(attrs); } static config(attrs) { return new Config(attrs); } static conformance(attrs) { return new Conformance(attrs); } static contentCopy(attrs) { return new ContentCopy(attrs); } static copies(attrs) { return new Copies(attrs); } static creator(attrs) { return new Creator(attrs); } static currentPage(attrs) { return new CurrentPage(attrs); } static data(attrs) { return new Data(attrs); } static debug(attrs) { return new Debug(attrs); } static defaultTypeface(attrs) { return new DefaultTypeface(attrs); } static destination(attrs) { return new Destination(attrs); } static documentAssembly(attrs) { return new DocumentAssembly(attrs); } static driver(attrs) { return new Driver(attrs); } static duplexOption(attrs) { return new DuplexOption(attrs); } static dynamicRender(attrs) { return new DynamicRender(attrs); } static embed(attrs) { return new Embed(attrs); } static encrypt(attrs) { return new config_Encrypt(attrs); } static encryption(attrs) { return new config_Encryption(attrs); } static encryptionLevel(attrs) { return new EncryptionLevel(attrs); } static enforce(attrs) { return new Enforce(attrs); } static equate(attrs) { return new Equate(attrs); } static equateRange(attrs) { return new EquateRange(attrs); } static exclude(attrs) { return new Exclude(attrs); } static excludeNS(attrs) { return new ExcludeNS(attrs); } static flipLabel(attrs) { return new FlipLabel(attrs); } static fontInfo(attrs) { return new config_FontInfo(attrs); } static formFieldFilling(attrs) { return new FormFieldFilling(attrs); } static groupParent(attrs) { return new GroupParent(attrs); } static ifEmpty(attrs) { return new IfEmpty(attrs); } static includeXDPContent(attrs) { return new IncludeXDPContent(attrs); } static incrementalLoad(attrs) { return new IncrementalLoad(attrs); } static incrementalMerge(attrs) { return new IncrementalMerge(attrs); } static interactive(attrs) { return new Interactive(attrs); } static jog(attrs) { return new Jog(attrs); } static labelPrinter(attrs) { return new LabelPrinter(attrs); } static layout(attrs) { return new Layout(attrs); } static level(attrs) { return new Level(attrs); } static linearized(attrs) { return new Linearized(attrs); } static locale(attrs) { return new Locale(attrs); } static localeSet(attrs) { return new LocaleSet(attrs); } static log(attrs) { return new Log(attrs); } static map(attrs) { return new MapElement(attrs); } static mediumInfo(attrs) { return new MediumInfo(attrs); } static message(attrs) { return new config_Message(attrs); } static messaging(attrs) { return new Messaging(attrs); } static mode(attrs) { return new Mode(attrs); } static modifyAnnots(attrs) { return new ModifyAnnots(attrs); } static msgId(attrs) { return new MsgId(attrs); } static nameAttr(attrs) { return new NameAttr(attrs); } static neverEmbed(attrs) { return new NeverEmbed(attrs); } static numberOfCopies(attrs) { return new NumberOfCopies(attrs); } static openAction(attrs) { return new OpenAction(attrs); } static output(attrs) { return new Output(attrs); } static outputBin(attrs) { return new OutputBin(attrs); } static outputXSL(attrs) { return new OutputXSL(attrs); } static overprint(attrs) { return new Overprint(attrs); } static packets(attrs) { return new Packets(attrs); } static pageOffset(attrs) { return new PageOffset(attrs); } static pageRange(attrs) { return new PageRange(attrs); } static pagination(attrs) { return new Pagination(attrs); } static paginationOverride(attrs) { return new PaginationOverride(attrs); } static part(attrs) { return new Part(attrs); } static pcl(attrs) { return new Pcl(attrs); } static pdf(attrs) { return new Pdf(attrs); } static pdfa(attrs) { return new Pdfa(attrs); } static permissions(attrs) { return new Permissions(attrs); } static pickTrayByPDFSize(attrs) { return new PickTrayByPDFSize(attrs); } static picture(attrs) { return new config_Picture(attrs); } static plaintextMetadata(attrs) { return new PlaintextMetadata(attrs); } static presence(attrs) { return new Presence(attrs); } static present(attrs) { return new Present(attrs); } static print(attrs) { return new Print(attrs); } static printHighQuality(attrs) { return new PrintHighQuality(attrs); } static printScaling(attrs) { return new PrintScaling(attrs); } static printerName(attrs) { return new PrinterName(attrs); } static producer(attrs) { return new Producer(attrs); } static ps(attrs) { return new Ps(attrs); } static range(attrs) { return new Range(attrs); } static record(attrs) { return new Record(attrs); } static relevant(attrs) { return new Relevant(attrs); } static rename(attrs) { return new Rename(attrs); } static renderPolicy(attrs) { return new RenderPolicy(attrs); } static runScripts(attrs) { return new RunScripts(attrs); } static script(attrs) { return new config_Script(attrs); } static scriptModel(attrs) { return new ScriptModel(attrs); } static severity(attrs) { return new Severity(attrs); } static silentPrint(attrs) { return new SilentPrint(attrs); } static staple(attrs) { return new Staple(attrs); } static startNode(attrs) { return new StartNode(attrs); } static startPage(attrs) { return new StartPage(attrs); } static submitFormat(attrs) { return new SubmitFormat(attrs); } static submitUrl(attrs) { return new SubmitUrl(attrs); } static subsetBelow(attrs) { return new SubsetBelow(attrs); } static suppressBanner(attrs) { return new SuppressBanner(attrs); } static tagged(attrs) { return new Tagged(attrs); } static template(attrs) { return new config_Template(attrs); } static templateCache(attrs) { return new TemplateCache(attrs); } static threshold(attrs) { return new Threshold(attrs); } static to(attrs) { return new To(attrs); } static trace(attrs) { return new Trace(attrs); } static transform(attrs) { return new config_Transform(attrs); } static type(attrs) { return new Type(attrs); } static uri(attrs) { return new Uri(attrs); } static validate(attrs) { return new config_Validate(attrs); } static validateApprovalSignatures(attrs) { return new ValidateApprovalSignatures(attrs); } static validationMessaging(attrs) { return new ValidationMessaging(attrs); } static version(attrs) { return new Version(attrs); } static versionControl(attrs) { return new VersionControl(attrs); } static viewerPreferences(attrs) { return new ViewerPreferences(attrs); } static webClient(attrs) { return new WebClient(attrs); } static whitespace(attrs) { return new Whitespace(attrs); } static window(attrs) { return new Window(attrs); } static xdc(attrs) { return new Xdc(attrs); } static xdp(attrs) { return new Xdp(attrs); } static xsl(attrs) { return new Xsl(attrs); } static zpl(attrs) { return new Zpl(attrs); } } ;// CONCATENATED MODULE: ./src/core/xfa/connection_set.js const CONNECTION_SET_NS_ID = NamespaceIds.connectionSet.id; class ConnectionSet extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "connectionSet", true); this.wsdlConnection = new XFAObjectArray(); this.xmlConnection = new XFAObjectArray(); this.xsdConnection = new XFAObjectArray(); } } class EffectiveInputPolicy extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "effectiveInputPolicy"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class EffectiveOutputPolicy extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "effectiveOutputPolicy"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class Operation extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "operation"); this.id = attributes.id || ""; this.input = attributes.input || ""; this.name = attributes.name || ""; this.output = attributes.output || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class RootElement extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "rootElement"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class SoapAction extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "soapAction"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class SoapAddress extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "soapAddress"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class connection_set_Uri extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "uri"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class WsdlAddress extends StringObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "wsdlAddress"); this.id = attributes.id || ""; this.name = attributes.name || ""; this.use = attributes.use || ""; this.usehref = attributes.usehref || ""; } } class WsdlConnection extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "wsdlConnection", true); this.dataDescription = attributes.dataDescription || ""; this.name = attributes.name || ""; this.effectiveInputPolicy = null; this.effectiveOutputPolicy = null; this.operation = null; this.soapAction = null; this.soapAddress = null; this.wsdlAddress = null; } } class XmlConnection extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "xmlConnection", true); this.dataDescription = attributes.dataDescription || ""; this.name = attributes.name || ""; this.uri = null; } } class XsdConnection extends XFAObject { constructor(attributes) { super(CONNECTION_SET_NS_ID, "xsdConnection", true); this.dataDescription = attributes.dataDescription || ""; this.name = attributes.name || ""; this.rootElement = null; this.uri = null; } } class ConnectionSetNamespace { static [$buildXFAObject](name, attributes) { if (ConnectionSetNamespace.hasOwnProperty(name)) { return ConnectionSetNamespace[name](attributes); } return undefined; } static connectionSet(attrs) { return new ConnectionSet(attrs); } static effectiveInputPolicy(attrs) { return new EffectiveInputPolicy(attrs); } static effectiveOutputPolicy(attrs) { return new EffectiveOutputPolicy(attrs); } static operation(attrs) { return new Operation(attrs); } static rootElement(attrs) { return new RootElement(attrs); } static soapAction(attrs) { return new SoapAction(attrs); } static soapAddress(attrs) { return new SoapAddress(attrs); } static uri(attrs) { return new connection_set_Uri(attrs); } static wsdlAddress(attrs) { return new WsdlAddress(attrs); } static wsdlConnection(attrs) { return new WsdlConnection(attrs); } static xmlConnection(attrs) { return new XmlConnection(attrs); } static xsdConnection(attrs) { return new XsdConnection(attrs); } } ;// CONCATENATED MODULE: ./src/core/xfa/datasets.js const DATASETS_NS_ID = NamespaceIds.datasets.id; class datasets_Data extends XmlObject { constructor(attributes) { super(DATASETS_NS_ID, "data", attributes); } [$isNsAgnostic]() { return true; } } class Datasets extends XFAObject { constructor(attributes) { super(DATASETS_NS_ID, "datasets", true); this.data = null; this.Signature = null; } [$onChild](child) { const name = child[$nodeName]; if (name === "data" && child[$namespaceId] === DATASETS_NS_ID || name === "Signature" && child[$namespaceId] === NamespaceIds.signature.id) { this[name] = child; } this[$appendChild](child); } } class DatasetsNamespace { static [$buildXFAObject](name, attributes) { if (DatasetsNamespace.hasOwnProperty(name)) { return DatasetsNamespace[name](attributes); } return undefined; } static datasets(attributes) { return new Datasets(attributes); } static data(attributes) { return new datasets_Data(attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/locale_set.js const LOCALE_SET_NS_ID = NamespaceIds.localeSet.id; class CalendarSymbols extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "calendarSymbols", true); this.name = "gregorian"; this.dayNames = new XFAObjectArray(2); this.eraNames = null; this.meridiemNames = null; this.monthNames = new XFAObjectArray(2); } } class CurrencySymbol extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "currencySymbol"); this.name = getStringOption(attributes.name, ["symbol", "isoname", "decimal"]); } } class CurrencySymbols extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "currencySymbols", true); this.currencySymbol = new XFAObjectArray(3); } } class DatePattern extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "datePattern"); this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]); } } class DatePatterns extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "datePatterns", true); this.datePattern = new XFAObjectArray(4); } } class DateTimeSymbols extends ContentObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "dateTimeSymbols"); } } class Day extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "day"); } } class DayNames extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "dayNames", true); this.abbr = getInteger({ data: attributes.abbr, defaultValue: 0, validate: x => x === 1 }); this.day = new XFAObjectArray(7); } } class Era extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "era"); } } class EraNames extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "eraNames", true); this.era = new XFAObjectArray(2); } } class locale_set_Locale extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "locale", true); this.desc = attributes.desc || ""; this.name = "isoname"; this.calendarSymbols = null; this.currencySymbols = null; this.datePatterns = null; this.dateTimeSymbols = null; this.numberPatterns = null; this.numberSymbols = null; this.timePatterns = null; this.typeFaces = null; } } class locale_set_LocaleSet extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "localeSet", true); this.locale = new XFAObjectArray(); } } class Meridiem extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "meridiem"); } } class MeridiemNames extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "meridiemNames", true); this.meridiem = new XFAObjectArray(2); } } class Month extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "month"); } } class MonthNames extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "monthNames", true); this.abbr = getInteger({ data: attributes.abbr, defaultValue: 0, validate: x => x === 1 }); this.month = new XFAObjectArray(12); } } class NumberPattern extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "numberPattern"); this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]); } } class NumberPatterns extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "numberPatterns", true); this.numberPattern = new XFAObjectArray(4); } } class NumberSymbol extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "numberSymbol"); this.name = getStringOption(attributes.name, ["decimal", "grouping", "percent", "minus", "zero"]); } } class NumberSymbols extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "numberSymbols", true); this.numberSymbol = new XFAObjectArray(5); } } class TimePattern extends StringObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "timePattern"); this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]); } } class TimePatterns extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "timePatterns", true); this.timePattern = new XFAObjectArray(4); } } class TypeFace extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "typeFace", true); this.name = attributes.name | ""; } } class TypeFaces extends XFAObject { constructor(attributes) { super(LOCALE_SET_NS_ID, "typeFaces", true); this.typeFace = new XFAObjectArray(); } } class LocaleSetNamespace { static [$buildXFAObject](name, attributes) { if (LocaleSetNamespace.hasOwnProperty(name)) { return LocaleSetNamespace[name](attributes); } return undefined; } static calendarSymbols(attrs) { return new CalendarSymbols(attrs); } static currencySymbol(attrs) { return new CurrencySymbol(attrs); } static currencySymbols(attrs) { return new CurrencySymbols(attrs); } static datePattern(attrs) { return new DatePattern(attrs); } static datePatterns(attrs) { return new DatePatterns(attrs); } static dateTimeSymbols(attrs) { return new DateTimeSymbols(attrs); } static day(attrs) { return new Day(attrs); } static dayNames(attrs) { return new DayNames(attrs); } static era(attrs) { return new Era(attrs); } static eraNames(attrs) { return new EraNames(attrs); } static locale(attrs) { return new locale_set_Locale(attrs); } static localeSet(attrs) { return new locale_set_LocaleSet(attrs); } static meridiem(attrs) { return new Meridiem(attrs); } static meridiemNames(attrs) { return new MeridiemNames(attrs); } static month(attrs) { return new Month(attrs); } static monthNames(attrs) { return new MonthNames(attrs); } static numberPattern(attrs) { return new NumberPattern(attrs); } static numberPatterns(attrs) { return new NumberPatterns(attrs); } static numberSymbol(attrs) { return new NumberSymbol(attrs); } static numberSymbols(attrs) { return new NumberSymbols(attrs); } static timePattern(attrs) { return new TimePattern(attrs); } static timePatterns(attrs) { return new TimePatterns(attrs); } static typeFace(attrs) { return new TypeFace(attrs); } static typeFaces(attrs) { return new TypeFaces(attrs); } } ;// CONCATENATED MODULE: ./src/core/xfa/signature.js const SIGNATURE_NS_ID = NamespaceIds.signature.id; class signature_Signature extends XFAObject { constructor(attributes) { super(SIGNATURE_NS_ID, "signature", true); } } class SignatureNamespace { static [$buildXFAObject](name, attributes) { if (SignatureNamespace.hasOwnProperty(name)) { return SignatureNamespace[name](attributes); } return undefined; } static signature(attributes) { return new signature_Signature(attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/stylesheet.js const STYLESHEET_NS_ID = NamespaceIds.stylesheet.id; class Stylesheet extends XFAObject { constructor(attributes) { super(STYLESHEET_NS_ID, "stylesheet", true); } } class StylesheetNamespace { static [$buildXFAObject](name, attributes) { if (StylesheetNamespace.hasOwnProperty(name)) { return StylesheetNamespace[name](attributes); } return undefined; } static stylesheet(attributes) { return new Stylesheet(attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/xdp.js const XDP_NS_ID = NamespaceIds.xdp.id; class xdp_Xdp extends XFAObject { constructor(attributes) { super(XDP_NS_ID, "xdp", true); this.uuid = attributes.uuid || ""; this.timeStamp = attributes.timeStamp || ""; this.config = null; this.connectionSet = null; this.datasets = null; this.localeSet = null; this.stylesheet = new XFAObjectArray(); this.template = null; } [$onChildCheck](child) { const ns = NamespaceIds[child[$nodeName]]; return ns && child[$namespaceId] === ns.id; } } class XdpNamespace { static [$buildXFAObject](name, attributes) { if (XdpNamespace.hasOwnProperty(name)) { return XdpNamespace[name](attributes); } return undefined; } static xdp(attributes) { return new xdp_Xdp(attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/xhtml.js const XHTML_NS_ID = NamespaceIds.xhtml.id; const $richText = Symbol(); const VALID_STYLES = new Set(["color", "font", "font-family", "font-size", "font-stretch", "font-style", "font-weight", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "letter-spacing", "line-height", "orphans", "page-break-after", "page-break-before", "page-break-inside", "tab-interval", "tab-stop", "text-align", "text-decoration", "text-indent", "vertical-align", "widows", "kerning-mode", "xfa-font-horizontal-scale", "xfa-font-vertical-scale", "xfa-spacerun", "xfa-tab-stops"]); const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-before", "breakBefore"], ["page-break-inside", "breakInside"], ["kerning-mode", value => value === "none" ? "none" : "normal"], ["xfa-font-horizontal-scale", value => `scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-font-vertical-scale", value => `scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-spacerun", ""], ["xfa-tab-stops", ""], ["font-size", (value, original) => { value = original.fontSize = getMeasurement(value); return measureToString(0.99 * value); }], ["letter-spacing", value => measureToString(getMeasurement(value))], ["line-height", value => measureToString(getMeasurement(value))], ["margin", value => measureToString(getMeasurement(value))], ["margin-bottom", value => measureToString(getMeasurement(value))], ["margin-left", value => measureToString(getMeasurement(value))], ["margin-right", value => measureToString(getMeasurement(value))], ["margin-top", value => measureToString(getMeasurement(value))], ["text-indent", value => measureToString(getMeasurement(value))], ["font-family", value => value], ["vertical-align", value => measureToString(getMeasurement(value))]]); const spacesRegExp = /\s+/g; const crlfRegExp = /[\r\n]+/g; const crlfForRichTextRegExp = /\r\n?/g; function mapStyle(styleStr, node, richText) { const style = Object.create(null); if (!styleStr) { return style; } const original = Object.create(null); for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) { const mapping = StyleMapping.get(key); if (mapping === "") { continue; } let newValue = value; if (mapping) { newValue = typeof mapping === "string" ? mapping : mapping(value, original); } if (key.endsWith("scale")) { style.transform = style.transform ? `${style[key]} ${newValue}` : newValue; } else { style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] = newValue; } } if (style.fontFamily) { setFontFamily({ typeface: style.fontFamily, weight: style.fontWeight || "normal", posture: style.fontStyle || "normal", size: original.fontSize || 0 }, node, node[$globalData].fontFinder, style); } if (richText && style.verticalAlign && style.verticalAlign !== "0px" && style.fontSize) { const SUB_SUPER_SCRIPT_FACTOR = 0.583; const VERTICAL_FACTOR = 0.333; const fontSize = getMeasurement(style.fontSize); style.fontSize = measureToString(fontSize * SUB_SUPER_SCRIPT_FACTOR); style.verticalAlign = measureToString(Math.sign(getMeasurement(style.verticalAlign)) * fontSize * VERTICAL_FACTOR); } if (richText && style.fontSize) { style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`; } fixTextIndent(style); return style; } function checkStyle(node) { if (!node.style) { return ""; } return node.style.trim().split(/\s*;\s*/).filter(s => !!s).map(s => s.split(/\s*:\s*/, 2)).filter(([key, value]) => { if (key === "font-family") { node[$globalData].usedTypefaces.add(value); } return VALID_STYLES.has(key); }).map(kv => kv.join(":")).join(";"); } const NoWhites = new Set(["body", "html"]); class XhtmlObject extends XmlObject { constructor(attributes, name) { super(XHTML_NS_ID, name); this[$richText] = false; this.style = attributes.style || ""; } [$clean](builder) { super[$clean](builder); this.style = checkStyle(this); } [$acceptWhitespace]() { return !NoWhites.has(this[$nodeName]); } [$onText](str, richText = false) { if (!richText) { str = str.replaceAll(crlfRegExp, ""); if (!this.style.includes("xfa-spacerun:yes")) { str = str.replaceAll(spacesRegExp, " "); } } else { this[$richText] = true; } if (str) { this[$content] += str; } } [$pushGlyphs](measure, mustPop = true) { const xfaFont = Object.create(null); const margin = { top: NaN, bottom: NaN, left: NaN, right: NaN }; let lineHeight = null; for (const [key, value] of this.style.split(";").map(s => s.split(":", 2))) { switch (key) { case "font-family": xfaFont.typeface = stripQuotes(value); break; case "font-size": xfaFont.size = getMeasurement(value); break; case "font-weight": xfaFont.weight = value; break; case "font-style": xfaFont.posture = value; break; case "letter-spacing": xfaFont.letterSpacing = getMeasurement(value); break; case "margin": const values = value.split(/ \t/).map(x => getMeasurement(x)); switch (values.length) { case 1: margin.top = margin.bottom = margin.left = margin.right = values[0]; break; case 2: margin.top = margin.bottom = values[0]; margin.left = margin.right = values[1]; break; case 3: margin.top = values[0]; margin.bottom = values[2]; margin.left = margin.right = values[1]; break; case 4: margin.top = values[0]; margin.left = values[1]; margin.bottom = values[2]; margin.right = values[3]; break; } break; case "margin-top": margin.top = getMeasurement(value); break; case "margin-bottom": margin.bottom = getMeasurement(value); break; case "margin-left": margin.left = getMeasurement(value); break; case "margin-right": margin.right = getMeasurement(value); break; case "line-height": lineHeight = getMeasurement(value); break; } } measure.pushData(xfaFont, margin, lineHeight); if (this[$content]) { measure.addString(this[$content]); } else { for (const child of this[$getChildren]()) { if (child[$nodeName] === "#text") { measure.addString(child[$content]); continue; } child[$pushGlyphs](measure); } } if (mustPop) { measure.popFont(); } } [$toHTML](availableSpace) { const children = []; this[$extra] = { children }; this[$childrenToHTML]({}); if (children.length === 0 && !this[$content]) { return HTMLResult.EMPTY; } let value; if (this[$richText]) { value = this[$content] ? this[$content].replaceAll(crlfForRichTextRegExp, "\n") : undefined; } else { value = this[$content] || undefined; } return HTMLResult.success({ name: this[$nodeName], attributes: { href: this.href, style: mapStyle(this.style, this, this[$richText]) }, children, value }); } } class A extends XhtmlObject { constructor(attributes) { super(attributes, "a"); this.href = fixURL(attributes.href) || ""; } } class B extends XhtmlObject { constructor(attributes) { super(attributes, "b"); } [$pushGlyphs](measure) { measure.pushFont({ weight: "bold" }); super[$pushGlyphs](measure); measure.popFont(); } } class Body extends XhtmlObject { constructor(attributes) { super(attributes, "body"); } [$toHTML](availableSpace) { const res = super[$toHTML](availableSpace); const { html } = res; if (!html) { return HTMLResult.EMPTY; } html.name = "div"; html.attributes.class = ["xfaRich"]; return res; } } class Br extends XhtmlObject { constructor(attributes) { super(attributes, "br"); } [$text]() { return "\n"; } [$pushGlyphs](measure) { measure.addString("\n"); } [$toHTML](availableSpace) { return HTMLResult.success({ name: "br" }); } } class Html extends XhtmlObject { constructor(attributes) { super(attributes, "html"); } [$toHTML](availableSpace) { const children = []; this[$extra] = { children }; this[$childrenToHTML]({}); if (children.length === 0) { return HTMLResult.success({ name: "div", attributes: { class: ["xfaRich"], style: {} }, value: this[$content] || "" }); } if (children.length === 1) { const child = children[0]; if (child.attributes?.class.includes("xfaRich")) { return HTMLResult.success(child); } } return HTMLResult.success({ name: "div", attributes: { class: ["xfaRich"], style: {} }, children }); } } class I extends XhtmlObject { constructor(attributes) { super(attributes, "i"); } [$pushGlyphs](measure) { measure.pushFont({ posture: "italic" }); super[$pushGlyphs](measure); measure.popFont(); } } class Li extends XhtmlObject { constructor(attributes) { super(attributes, "li"); } } class Ol extends XhtmlObject { constructor(attributes) { super(attributes, "ol"); } } class P extends XhtmlObject { constructor(attributes) { super(attributes, "p"); } [$pushGlyphs](measure) { super[$pushGlyphs](measure, false); measure.addString("\n"); measure.addPara(); measure.popFont(); } [$text]() { const siblings = this[$getParent]()[$getChildren](); if (siblings.at(-1) === this) { return super[$text](); } return super[$text]() + "\n"; } } class Span extends XhtmlObject { constructor(attributes) { super(attributes, "span"); } } class Sub extends XhtmlObject { constructor(attributes) { super(attributes, "sub"); } } class Sup extends XhtmlObject { constructor(attributes) { super(attributes, "sup"); } } class Ul extends XhtmlObject { constructor(attributes) { super(attributes, "ul"); } } class XhtmlNamespace { static [$buildXFAObject](name, attributes) { if (XhtmlNamespace.hasOwnProperty(name)) { return XhtmlNamespace[name](attributes); } return undefined; } static a(attributes) { return new A(attributes); } static b(attributes) { return new B(attributes); } static body(attributes) { return new Body(attributes); } static br(attributes) { return new Br(attributes); } static html(attributes) { return new Html(attributes); } static i(attributes) { return new I(attributes); } static li(attributes) { return new Li(attributes); } static ol(attributes) { return new Ol(attributes); } static p(attributes) { return new P(attributes); } static span(attributes) { return new Span(attributes); } static sub(attributes) { return new Sub(attributes); } static sup(attributes) { return new Sup(attributes); } static ul(attributes) { return new Ul(attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/setup.js const NamespaceSetUp = { config: ConfigNamespace, connection: ConnectionSetNamespace, datasets: DatasetsNamespace, localeSet: LocaleSetNamespace, signature: SignatureNamespace, stylesheet: StylesheetNamespace, template: TemplateNamespace, xdp: XdpNamespace, xhtml: XhtmlNamespace }; ;// CONCATENATED MODULE: ./src/core/xfa/unknown.js class UnknownNamespace { constructor(nsId) { this.namespaceId = nsId; } [$buildXFAObject](name, attributes) { return new XmlObject(this.namespaceId, name, attributes); } } ;// CONCATENATED MODULE: ./src/core/xfa/builder.js class Root extends XFAObject { constructor(ids) { super(-1, "root", Object.create(null)); this.element = null; this[$ids] = ids; } [$onChild](child) { this.element = child; return true; } [$finalize]() { super[$finalize](); if (this.element.template instanceof Template) { this[$ids].set($root, this.element); this.element.template[$resolvePrototypes](this[$ids]); this.element.template[$ids] = this[$ids]; } } } class Empty extends XFAObject { constructor() { super(-1, "", Object.create(null)); } [$onChild](_) { return false; } } class Builder { constructor(rootNameSpace = null) { this._namespaceStack = []; this._nsAgnosticLevel = 0; this._namespacePrefixes = new Map(); this._namespaces = new Map(); this._nextNsId = Math.max(...Object.values(NamespaceIds).map(({ id }) => id)); this._currentNamespace = rootNameSpace || new UnknownNamespace(++this._nextNsId); } buildRoot(ids) { return new Root(ids); } build({ nsPrefix, name, attributes, namespace, prefixes }) { const hasNamespaceDef = namespace !== null; if (hasNamespaceDef) { this._namespaceStack.push(this._currentNamespace); this._currentNamespace = this._searchNamespace(namespace); } if (prefixes) { this._addNamespacePrefix(prefixes); } if (attributes.hasOwnProperty($nsAttributes)) { const dataTemplate = NamespaceSetUp.datasets; const nsAttrs = attributes[$nsAttributes]; let xfaAttrs = null; for (const [ns, attrs] of Object.entries(nsAttrs)) { const nsToUse = this._getNamespaceToUse(ns); if (nsToUse === dataTemplate) { xfaAttrs = { xfa: attrs }; break; } } if (xfaAttrs) { attributes[$nsAttributes] = xfaAttrs; } else { delete attributes[$nsAttributes]; } } const namespaceToUse = this._getNamespaceToUse(nsPrefix); const node = namespaceToUse?.[$buildXFAObject](name, attributes) || new Empty(); if (node[$isNsAgnostic]()) { this._nsAgnosticLevel++; } if (hasNamespaceDef || prefixes || node[$isNsAgnostic]()) { node[$cleanup] = { hasNamespace: hasNamespaceDef, prefixes, nsAgnostic: node[$isNsAgnostic]() }; } return node; } isNsAgnostic() { return this._nsAgnosticLevel > 0; } _searchNamespace(nsName) { let ns = this._namespaces.get(nsName); if (ns) { return ns; } for (const [name, { check }] of Object.entries(NamespaceIds)) { if (check(nsName)) { ns = NamespaceSetUp[name]; if (ns) { this._namespaces.set(nsName, ns); return ns; } break; } } ns = new UnknownNamespace(++this._nextNsId); this._namespaces.set(nsName, ns); return ns; } _addNamespacePrefix(prefixes) { for (const { prefix, value } of prefixes) { const namespace = this._searchNamespace(value); let prefixStack = this._namespacePrefixes.get(prefix); if (!prefixStack) { prefixStack = []; this._namespacePrefixes.set(prefix, prefixStack); } prefixStack.push(namespace); } } _getNamespaceToUse(prefix) { if (!prefix) { return this._currentNamespace; } const prefixStack = this._namespacePrefixes.get(prefix); if (prefixStack?.length > 0) { return prefixStack.at(-1); } warn(`Unknown namespace prefix: ${prefix}.`); return null; } clean(data) { const { hasNamespace, prefixes, nsAgnostic } = data; if (hasNamespace) { this._currentNamespace = this._namespaceStack.pop(); } if (prefixes) { prefixes.forEach(({ prefix }) => { this._namespacePrefixes.get(prefix).pop(); }); } if (nsAgnostic) { this._nsAgnosticLevel--; } } } ;// CONCATENATED MODULE: ./src/core/xfa/parser.js class XFAParser extends XMLParserBase { constructor(rootNameSpace = null, richText = false) { super(); this._builder = new Builder(rootNameSpace); this._stack = []; this._globalData = { usedTypefaces: new Set() }; this._ids = new Map(); this._current = this._builder.buildRoot(this._ids); this._errorCode = XMLParserErrorCode.NoError; this._whiteRegex = /^\s+$/; this._nbsps = /\xa0+/g; this._richText = richText; } parse(data) { this.parseXml(data); if (this._errorCode !== XMLParserErrorCode.NoError) { return undefined; } this._current[$finalize](); return this._current.element; } onText(text) { text = text.replace(this._nbsps, match => match.slice(1) + " "); if (this._richText || this._current[$acceptWhitespace]()) { this._current[$onText](text, this._richText); return; } if (this._whiteRegex.test(text)) { return; } this._current[$onText](text.trim()); } onCdata(text) { this._current[$onText](text); } _mkAttributes(attributes, tagName) { let namespace = null; let prefixes = null; const attributeObj = Object.create({}); for (const { name, value } of attributes) { if (name === "xmlns") { if (!namespace) { namespace = value; } else { warn(`XFA - multiple namespace definition in <${tagName}>`); } } else if (name.startsWith("xmlns:")) { const prefix = name.substring("xmlns:".length); if (!prefixes) { prefixes = []; } prefixes.push({ prefix, value }); } else { const i = name.indexOf(":"); if (i === -1) { attributeObj[name] = value; } else { let nsAttrs = attributeObj[$nsAttributes]; if (!nsAttrs) { nsAttrs = attributeObj[$nsAttributes] = Object.create(null); } const [ns, attrName] = [name.slice(0, i), name.slice(i + 1)]; const attrs = nsAttrs[ns] ||= Object.create(null); attrs[attrName] = value; } } } return [namespace, prefixes, attributeObj]; } _getNameAndPrefix(name, nsAgnostic) { const i = name.indexOf(":"); if (i === -1) { return [name, null]; } return [name.substring(i + 1), nsAgnostic ? "" : name.substring(0, i)]; } onBeginElement(tagName, attributes, isEmpty) { const [namespace, prefixes, attributesObj] = this._mkAttributes(attributes, tagName); const [name, nsPrefix] = this._getNameAndPrefix(tagName, this._builder.isNsAgnostic()); const node = this._builder.build({ nsPrefix, name, attributes: attributesObj, namespace, prefixes }); node[$globalData] = this._globalData; if (isEmpty) { node[$finalize](); if (this._current[$onChild](node)) { node[$setId](this._ids); } node[$clean](this._builder); return; } this._stack.push(this._current); this._current = node; } onEndElement(name) { const node = this._current; if (node[$isCDATAXml]() && typeof node[$content] === "string") { const parser = new XFAParser(); parser._globalData = this._globalData; const root = parser.parse(node[$content]); node[$content] = null; node[$onChild](root); } node[$finalize](); this._current = this._stack.pop(); if (this._current[$onChild](node)) { node[$setId](this._ids); } node[$clean](this._builder); } onError(code) { this._errorCode = code; } } ;// CONCATENATED MODULE: ./src/core/xfa/factory.js class XFAFactory { constructor(data) { try { this.root = new XFAParser().parse(XFAFactory._createDocument(data)); const binder = new Binder(this.root); this.form = binder.bind(); this.dataHandler = new DataHandler(this.root, binder.getData()); this.form[$globalData].template = this.form; } catch (e) { warn(`XFA - an error occurred during parsing and binding: ${e}`); } } isValid() { return this.root && this.form; } _createPagesHelper() { const iterator = this.form[$toPages](); return new Promise((resolve, reject) => { const nextIteration = () => { try { const value = iterator.next(); if (value.done) { resolve(value.value); } else { setTimeout(nextIteration, 0); } } catch (e) { reject(e); } }; setTimeout(nextIteration, 0); }); } async _createPages() { try { this.pages = await this._createPagesHelper(); this.dims = this.pages.children.map(c => { const { width, height } = c.attributes.style; return [0, 0, parseInt(width), parseInt(height)]; }); } catch (e) { warn(`XFA - an error occurred during layout: ${e}`); } } getBoundingBox(pageIndex) { return this.dims[pageIndex]; } async getNumPages() { if (!this.pages) { await this._createPages(); } return this.dims.length; } setImages(images) { this.form[$globalData].images = images; } setFonts(fonts) { this.form[$globalData].fontFinder = new FontFinder(fonts); const missingFonts = []; for (let typeface of this.form[$globalData].usedTypefaces) { typeface = stripQuotes(typeface); const font = this.form[$globalData].fontFinder.find(typeface); if (!font) { missingFonts.push(typeface); } } if (missingFonts.length > 0) { return missingFonts; } return null; } appendFonts(fonts, reallyMissingFonts) { this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts); } async getPages() { if (!this.pages) { await this._createPages(); } const pages = this.pages; this.pages = null; return pages; } serializeData(storage) { return this.dataHandler.serialize(storage); } static _createDocument(data) { if (!data["/xdp:xdp"]) { return data["xdp:xdp"]; } return Object.values(data).join(""); } static getRichTextAsHtml(rc) { if (!rc || typeof rc !== "string") { return null; } try { let root = new XFAParser(XhtmlNamespace, true).parse(rc); if (!["body", "xhtml"].includes(root[$nodeName])) { const newRoot = XhtmlNamespace.body({}); newRoot[$appendChild](root); root = newRoot; } const result = root[$toHTML](); if (!result.success) { return null; } const { html } = result; const { attributes } = html; if (attributes) { if (attributes.class) { attributes.class = attributes.class.filter(attr => !attr.startsWith("xfa")); } attributes.dir = "auto"; } return { html, str: root[$text]() }; } catch (e) { warn(`XFA - an error occurred during parsing of rich text: ${e}`); } return null; } } ;// CONCATENATED MODULE: ./src/core/annotation.js class AnnotationFactory { static createGlobals(pdfManager) { return Promise.all([pdfManager.ensureCatalog("acroForm"), pdfManager.ensureDoc("xfaDatasets"), pdfManager.ensureCatalog("structTreeRoot"), pdfManager.ensureCatalog("baseUrl"), pdfManager.ensureCatalog("attachments")]).then(([acroForm, xfaDatasets, structTreeRoot, baseUrl, attachments]) => { return { pdfManager, acroForm: acroForm instanceof Dict ? acroForm : Dict.empty, xfaDatasets, structTreeRoot, baseUrl, attachments }; }, reason => { warn(`createGlobals: "${reason}".`); return null; }); } static async create(xref, ref, annotationGlobals, idFactory, collectFields, pageRef) { const pageIndex = collectFields ? await this._getPageIndex(xref, ref, annotationGlobals.pdfManager) : null; return annotationGlobals.pdfManager.ensure(this, "_create", [xref, ref, annotationGlobals, idFactory, collectFields, pageIndex, pageRef]); } static _create(xref, ref, annotationGlobals, idFactory, collectFields = false, pageIndex = null, pageRef = null) { const dict = xref.fetchIfRef(ref); if (!(dict instanceof Dict)) { return undefined; } const { acroForm, pdfManager } = annotationGlobals; const id = ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`; let subtype = dict.get("Subtype"); subtype = subtype instanceof Name ? subtype.name : null; const parameters = { xref, ref, dict, subtype, id, annotationGlobals, collectFields, needAppearances: !collectFields && acroForm.get("NeedAppearances") === true, pageIndex, evaluatorOptions: pdfManager.evaluatorOptions, pageRef }; switch (subtype) { case "Link": return new LinkAnnotation(parameters); case "Text": return new TextAnnotation(parameters); case "Widget": let fieldType = getInheritableProperty({ dict, key: "FT" }); fieldType = fieldType instanceof Name ? fieldType.name : null; switch (fieldType) { case "Tx": return new TextWidgetAnnotation(parameters); case "Btn": return new ButtonWidgetAnnotation(parameters); case "Ch": return new ChoiceWidgetAnnotation(parameters); case "Sig": return new SignatureWidgetAnnotation(parameters); } warn(`Unimplemented widget field type "${fieldType}", ` + "falling back to base field type."); return new WidgetAnnotation(parameters); case "Popup": return new PopupAnnotation(parameters); case "FreeText": return new FreeTextAnnotation(parameters); case "Line": return new LineAnnotation(parameters); case "Square": return new SquareAnnotation(parameters); case "Circle": return new CircleAnnotation(parameters); case "PolyLine": return new PolylineAnnotation(parameters); case "Polygon": return new PolygonAnnotation(parameters); case "Caret": return new CaretAnnotation(parameters); case "Ink": return new InkAnnotation(parameters); case "Highlight": return new HighlightAnnotation(parameters); case "Underline": return new UnderlineAnnotation(parameters); case "Squiggly": return new SquigglyAnnotation(parameters); case "StrikeOut": return new StrikeOutAnnotation(parameters); case "Stamp": return new StampAnnotation(parameters); case "FileAttachment": return new FileAttachmentAnnotation(parameters); default: if (!collectFields) { if (!subtype) { warn("Annotation is missing the required /Subtype."); } else { warn(`Unimplemented annotation type "${subtype}", ` + "falling back to base annotation."); } } return new Annotation(parameters); } } static async _getPageIndex(xref, ref, pdfManager) { try { const annotDict = await xref.fetchIfRefAsync(ref); if (!(annotDict instanceof Dict)) { return -1; } const pageRef = annotDict.getRaw("P"); if (pageRef instanceof Ref) { try { const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [pageRef]); return pageIndex; } catch (ex) { info(`_getPageIndex -- not a valid page reference: "${ex}".`); } } if (annotDict.has("Kids")) { return -1; } const numPages = await pdfManager.ensureDoc("numPages"); for (let pageIndex = 0; pageIndex < numPages; pageIndex++) { const page = await pdfManager.getPage(pageIndex); const annotations = await pdfManager.ensure(page, "annotations"); for (const annotRef of annotations) { if (annotRef instanceof Ref && isRefsEqual(annotRef, ref)) { return pageIndex; } } } } catch (ex) { warn(`_getPageIndex: "${ex}".`); } return -1; } static generateImages(annotations, xref, isOffscreenCanvasSupported) { if (!isOffscreenCanvasSupported) { warn("generateImages: OffscreenCanvas is not supported, cannot save or print some annotations with images."); return null; } let imagePromises; for (const { bitmapId, bitmap } of annotations) { if (!bitmap) { continue; } imagePromises ||= new Map(); imagePromises.set(bitmapId, StampAnnotation.createImage(bitmap, xref)); } return imagePromises; } static async saveNewAnnotations(evaluator, task, annotations, imagePromises) { const xref = evaluator.xref; let baseFontRef; const dependencies = []; const promises = []; const { isOffscreenCanvasSupported } = evaluator.options; for (const annotation of annotations) { if (annotation.deleted) { continue; } switch (annotation.annotationType) { case AnnotationEditorType.FREETEXT: if (!baseFontRef) { const baseFont = new Dict(xref); baseFont.set("BaseFont", Name.get("Helvetica")); baseFont.set("Type", Name.get("Font")); baseFont.set("Subtype", Name.get("Type1")); baseFont.set("Encoding", Name.get("WinAnsiEncoding")); const buffer = []; baseFontRef = xref.getNewTemporaryRef(); await writeObject(baseFontRef, baseFont, buffer, xref); dependencies.push({ ref: baseFontRef, data: buffer.join("") }); } promises.push(FreeTextAnnotation.createNewAnnotation(xref, annotation, dependencies, { evaluator, task, baseFontRef })); break; case AnnotationEditorType.HIGHLIGHT: promises.push(HighlightAnnotation.createNewAnnotation(xref, annotation, dependencies)); break; case AnnotationEditorType.INK: promises.push(InkAnnotation.createNewAnnotation(xref, annotation, dependencies)); break; case AnnotationEditorType.STAMP: if (!isOffscreenCanvasSupported) { break; } const image = await imagePromises.get(annotation.bitmapId); if (image.imageStream) { const { imageStream, smaskStream } = image; const buffer = []; if (smaskStream) { const smaskRef = xref.getNewTemporaryRef(); await writeObject(smaskRef, smaskStream, buffer, xref); dependencies.push({ ref: smaskRef, data: buffer.join("") }); imageStream.dict.set("SMask", smaskRef); buffer.length = 0; } const imageRef = image.imageRef = xref.getNewTemporaryRef(); await writeObject(imageRef, imageStream, buffer, xref); dependencies.push({ ref: imageRef, data: buffer.join("") }); image.imageStream = image.smaskStream = null; } promises.push(StampAnnotation.createNewAnnotation(xref, annotation, dependencies, { image })); break; } } return { annotations: await Promise.all(promises), dependencies }; } static async printNewAnnotations(annotationGlobals, evaluator, task, annotations, imagePromises) { if (!annotations) { return null; } const { options, xref } = evaluator; const promises = []; for (const annotation of annotations) { if (annotation.deleted) { continue; } switch (annotation.annotationType) { case AnnotationEditorType.FREETEXT: promises.push(FreeTextAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, { evaluator, task, evaluatorOptions: options })); break; case AnnotationEditorType.HIGHLIGHT: promises.push(HighlightAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, { evaluatorOptions: options })); break; case AnnotationEditorType.INK: promises.push(InkAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, { evaluatorOptions: options })); break; case AnnotationEditorType.STAMP: if (!options.isOffscreenCanvasSupported) { break; } const image = await imagePromises.get(annotation.bitmapId); if (image.imageStream) { const { imageStream, smaskStream } = image; if (smaskStream) { imageStream.dict.set("SMask", smaskStream); } image.imageRef = new JpegStream(imageStream, imageStream.length); image.imageStream = image.smaskStream = null; } promises.push(StampAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, { image, evaluatorOptions: options })); break; } } return Promise.all(promises); } } function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) { if (!Array.isArray(color)) { return defaultColor; } const rgbColor = defaultColor || new Uint8ClampedArray(3); switch (color.length) { case 0: return null; case 1: ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0); return rgbColor; case 3: ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0); return rgbColor; case 4: ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0); return rgbColor; default: return defaultColor; } } function getPdfColorArray(color) { return Array.from(color, c => c / 255); } function getQuadPoints(dict, rect) { const quadPoints = dict.getArray("QuadPoints"); if (!Array.isArray(quadPoints) || quadPoints.length === 0 || quadPoints.length % 8 > 0) { return null; } const quadPointsLists = []; for (let i = 0, ii = quadPoints.length / 8; i < ii; i++) { let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; for (let j = i * 8, jj = i * 8 + 8; j < jj; j += 2) { const x = quadPoints[j]; const y = quadPoints[j + 1]; minX = Math.min(x, minX); maxX = Math.max(x, maxX); minY = Math.min(y, minY); maxY = Math.max(y, maxY); } if (rect !== null && (minX < rect[0] || maxX > rect[2] || minY < rect[1] || maxY > rect[3])) { return null; } quadPointsLists.push([{ x: minX, y: maxY }, { x: maxX, y: maxY }, { x: minX, y: minY }, { x: maxX, y: minY }]); } return quadPointsLists; } function getTransformMatrix(rect, bbox, matrix) { const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox(bbox, matrix); if (minX === maxX || minY === maxY) { return [1, 0, 0, 1, rect[0], rect[1]]; } const xRatio = (rect[2] - rect[0]) / (maxX - minX); const yRatio = (rect[3] - rect[1]) / (maxY - minY); return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio]; } class Annotation { constructor(params) { const { dict, xref, annotationGlobals } = params; this.setTitle(dict.get("T")); this.setContents(dict.get("Contents")); this.setModificationDate(dict.get("M")); this.setFlags(dict.get("F")); this.setRectangle(dict.getArray("Rect")); this.setColor(dict.getArray("C")); this.setBorderStyle(dict); this.setAppearance(dict); this.setOptionalContent(dict); const MK = dict.get("MK"); this.setBorderAndBackgroundColors(MK); this.setRotation(MK, dict); this.ref = params.ref instanceof Ref ? params.ref : null; this._streams = []; if (this.appearance) { this._streams.push(this.appearance); } const isLocked = !!(this.flags & AnnotationFlag.LOCKED); const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS); if (annotationGlobals.structTreeRoot) { let structParent = dict.get("StructParent"); structParent = Number.isInteger(structParent) && structParent >= 0 ? structParent : -1; annotationGlobals.structTreeRoot.addAnnotationIdToPage(params.pageRef, structParent); } this.data = { annotationFlags: this.flags, borderStyle: this.borderStyle, color: this.color, backgroundColor: this.backgroundColor, borderColor: this.borderColor, rotation: this.rotation, contentsObj: this._contents, hasAppearance: !!this.appearance, id: params.id, modificationDate: this.modificationDate, rect: this.rectangle, subtype: params.subtype, hasOwnCanvas: false, noRotate: !!(this.flags & AnnotationFlag.NOROTATE), noHTML: isLocked && isContentLocked }; if (params.collectFields) { const kids = dict.get("Kids"); if (Array.isArray(kids)) { const kidIds = []; for (const kid of kids) { if (kid instanceof Ref) { kidIds.push(kid.toString()); } } if (kidIds.length !== 0) { this.data.kidIds = kidIds; } } this.data.actions = collectActions(xref, dict, AnnotationActionEventType); this.data.fieldName = this._constructFieldName(dict); this.data.pageIndex = params.pageIndex; } this._isOffscreenCanvasSupported = params.evaluatorOptions.isOffscreenCanvasSupported; this._fallbackFontDict = null; this._needAppearances = false; } _hasFlag(flags, flag) { return !!(flags & flag); } _isViewable(flags) { return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.NOVIEW); } _isPrintable(flags) { return this._hasFlag(flags, AnnotationFlag.PRINT) && !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.INVISIBLE); } mustBeViewed(annotationStorage, _renderForms) { const noView = annotationStorage?.get(this.data.id)?.noView; if (noView !== undefined) { return !noView; } return this.viewable && !this._hasFlag(this.flags, AnnotationFlag.HIDDEN); } mustBePrinted(annotationStorage) { const noPrint = annotationStorage?.get(this.data.id)?.noPrint; if (noPrint !== undefined) { return !noPrint; } return this.printable; } get viewable() { if (this.data.quadPoints === null) { return false; } if (this.flags === 0) { return true; } return this._isViewable(this.flags); } get printable() { if (this.data.quadPoints === null) { return false; } if (this.flags === 0) { return false; } return this._isPrintable(this.flags); } _parseStringHelper(data) { const str = typeof data === "string" ? stringToPDFString(data) : ""; const dir = str && bidi(str).dir === "rtl" ? "rtl" : "ltr"; return { str, dir }; } setDefaultAppearance(params) { const { dict, annotationGlobals } = params; const defaultAppearance = getInheritableProperty({ dict, key: "DA" }) || annotationGlobals.acroForm.get("DA"); this._defaultAppearance = typeof defaultAppearance === "string" ? defaultAppearance : ""; this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance); } setTitle(title) { this._title = this._parseStringHelper(title); } setContents(contents) { this._contents = this._parseStringHelper(contents); } setModificationDate(modificationDate) { this.modificationDate = typeof modificationDate === "string" ? modificationDate : null; } setFlags(flags) { this.flags = Number.isInteger(flags) && flags > 0 ? flags : 0; if (this.flags & AnnotationFlag.INVISIBLE && this.constructor.name !== "Annotation") { this.flags ^= AnnotationFlag.INVISIBLE; } } hasFlag(flag) { return this._hasFlag(this.flags, flag); } setRectangle(rectangle) { this.rectangle = Array.isArray(rectangle) && rectangle.length === 4 ? Util.normalizeRect(rectangle) : [0, 0, 0, 0]; } setColor(color) { this.color = getRgbColor(color); } setLineEndings(lineEndings) { this.lineEndings = ["None", "None"]; if (Array.isArray(lineEndings) && lineEndings.length === 2) { for (let i = 0; i < 2; i++) { const obj = lineEndings[i]; if (obj instanceof Name) { switch (obj.name) { case "None": continue; case "Square": case "Circle": case "Diamond": case "OpenArrow": case "ClosedArrow": case "Butt": case "ROpenArrow": case "RClosedArrow": case "Slash": this.lineEndings[i] = obj.name; continue; } } warn(`Ignoring invalid lineEnding: ${obj}`); } } } setRotation(mk, dict) { this.rotation = 0; let angle = mk instanceof Dict ? mk.get("R") || 0 : dict.get("Rotate") || 0; if (Number.isInteger(angle) && angle !== 0) { angle %= 360; if (angle < 0) { angle += 360; } if (angle % 90 === 0) { this.rotation = angle; } } } setBorderAndBackgroundColors(mk) { if (mk instanceof Dict) { this.borderColor = getRgbColor(mk.getArray("BC"), null); this.backgroundColor = getRgbColor(mk.getArray("BG"), null); } else { this.borderColor = this.backgroundColor = null; } } setBorderStyle(borderStyle) { this.borderStyle = new AnnotationBorderStyle(); if (!(borderStyle instanceof Dict)) { return; } if (borderStyle.has("BS")) { const dict = borderStyle.get("BS"); const dictType = dict.get("Type"); if (!dictType || isName(dictType, "Border")) { this.borderStyle.setWidth(dict.get("W"), this.rectangle); this.borderStyle.setStyle(dict.get("S")); this.borderStyle.setDashArray(dict.getArray("D")); } } else if (borderStyle.has("Border")) { const array = borderStyle.getArray("Border"); if (Array.isArray(array) && array.length >= 3) { this.borderStyle.setHorizontalCornerRadius(array[0]); this.borderStyle.setVerticalCornerRadius(array[1]); this.borderStyle.setWidth(array[2], this.rectangle); if (array.length === 4) { this.borderStyle.setDashArray(array[3], true); } } } else { this.borderStyle.setWidth(0); } } setAppearance(dict) { this.appearance = null; const appearanceStates = dict.get("AP"); if (!(appearanceStates instanceof Dict)) { return; } const normalAppearanceState = appearanceStates.get("N"); if (normalAppearanceState instanceof BaseStream) { this.appearance = normalAppearanceState; return; } if (!(normalAppearanceState instanceof Dict)) { return; } const as = dict.get("AS"); if (!(as instanceof Name) || !normalAppearanceState.has(as.name)) { return; } const appearance = normalAppearanceState.get(as.name); if (appearance instanceof BaseStream) { this.appearance = appearance; } } setOptionalContent(dict) { this.oc = null; const oc = dict.get("OC"); if (oc instanceof Name) { warn("setOptionalContent: Support for /Name-entry is not implemented."); } else if (oc instanceof Dict) { this.oc = oc; } } loadResources(keys, appearance) { return appearance.dict.getAsync("Resources").then(resources => { if (!resources) { return undefined; } const objectLoader = new ObjectLoader(resources, keys, resources.xref); return objectLoader.load().then(function () { return resources; }); }); } async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) { const data = this.data; let appearance = this.appearance; const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY); if (!appearance) { if (!isUsingOwnCanvas) { return { opList: new OperatorList(), separateForm: false, separateCanvas: false }; } appearance = new StringStream(""); appearance.dict = new Dict(); } const appearanceDict = appearance.dict; const resources = await this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], appearance); const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1]; const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0]; const transform = getTransformMatrix(data.rect, bbox, matrix); const opList = new OperatorList(); let optionalContent; if (this.oc) { optionalContent = await evaluator.parseMarkedContentProps(this.oc, null); } if (optionalContent !== undefined) { opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); } opList.addOp(OPS.beginAnnotation, [data.id, data.rect, transform, matrix, isUsingOwnCanvas]); await evaluator.getOperatorList({ stream: appearance, task, resources, operatorList: opList, fallbackFontDict: this._fallbackFontDict }); opList.addOp(OPS.endAnnotation, []); if (optionalContent !== undefined) { opList.addOp(OPS.endMarkedContent, []); } this.reset(); return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas }; } async save(evaluator, task, annotationStorage) { return null; } get hasTextContent() { return false; } async extractTextContent(evaluator, task, viewBox) { if (!this.appearance) { return; } const resources = await this.loadResources(["ExtGState", "Font", "Properties", "XObject"], this.appearance); const text = []; const buffer = []; let firstPosition = null; const sink = { desiredSize: Math.Infinity, ready: true, enqueue(chunk, size) { for (const item of chunk.items) { if (item.str === undefined) { continue; } firstPosition ||= item.transform.slice(-2); buffer.push(item.str); if (item.hasEOL) { text.push(buffer.join("")); buffer.length = 0; } } } }; await evaluator.getTextContent({ stream: this.appearance, task, resources, includeMarkedContent: true, sink, viewBox }); this.reset(); if (buffer.length) { text.push(buffer.join("")); } if (text.length > 1 || text[0]) { const appearanceDict = this.appearance.dict; const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1]; const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0]; const rect = this.data.rect; const transform = getTransformMatrix(rect, bbox, matrix); transform[4] -= rect[0]; transform[5] -= rect[1]; firstPosition = Util.applyTransform(firstPosition, transform); firstPosition = Util.applyTransform(firstPosition, matrix); this.data.textPosition = firstPosition; this.data.textContent = text; } } getFieldObject() { if (this.data.kidIds) { return { id: this.data.id, actions: this.data.actions, name: this.data.fieldName, strokeColor: this.data.borderColor, fillColor: this.data.backgroundColor, type: "", kidIds: this.data.kidIds, page: this.data.pageIndex, rotation: this.rotation }; } return null; } reset() { for (const stream of this._streams) { stream.reset(); } } _constructFieldName(dict) { if (!dict.has("T") && !dict.has("Parent")) { warn("Unknown field name, falling back to empty field name."); return ""; } if (!dict.has("Parent")) { return stringToPDFString(dict.get("T")); } const fieldName = []; if (dict.has("T")) { fieldName.unshift(stringToPDFString(dict.get("T"))); } let loopDict = dict; const visited = new RefSet(); if (dict.objId) { visited.put(dict.objId); } while (loopDict.has("Parent")) { loopDict = loopDict.get("Parent"); if (!(loopDict instanceof Dict) || loopDict.objId && visited.has(loopDict.objId)) { break; } if (loopDict.objId) { visited.put(loopDict.objId); } if (loopDict.has("T")) { fieldName.unshift(stringToPDFString(loopDict.get("T"))); } } return fieldName.join("."); } } class AnnotationBorderStyle { constructor() { this.width = 1; this.style = AnnotationBorderStyleType.SOLID; this.dashArray = [3]; this.horizontalCornerRadius = 0; this.verticalCornerRadius = 0; } setWidth(width, rect = [0, 0, 0, 0]) { if (width instanceof Name) { this.width = 0; return; } if (typeof width === "number") { if (width > 0) { const maxWidth = (rect[2] - rect[0]) / 2; const maxHeight = (rect[3] - rect[1]) / 2; if (maxWidth > 0 && maxHeight > 0 && (width > maxWidth || width > maxHeight)) { warn(`AnnotationBorderStyle.setWidth - ignoring width: ${width}`); width = 1; } } this.width = width; } } setStyle(style) { if (!(style instanceof Name)) { return; } switch (style.name) { case "S": this.style = AnnotationBorderStyleType.SOLID; break; case "D": this.style = AnnotationBorderStyleType.DASHED; break; case "B": this.style = AnnotationBorderStyleType.BEVELED; break; case "I": this.style = AnnotationBorderStyleType.INSET; break; case "U": this.style = AnnotationBorderStyleType.UNDERLINE; break; default: break; } } setDashArray(dashArray, forceStyle = false) { if (Array.isArray(dashArray) && dashArray.length > 0) { let isValid = true; let allZeros = true; for (const element of dashArray) { const validNumber = +element >= 0; if (!validNumber) { isValid = false; break; } else if (element > 0) { allZeros = false; } } if (isValid && !allZeros) { this.dashArray = dashArray; if (forceStyle) { this.setStyle(Name.get("D")); } } else { this.width = 0; } } else if (dashArray) { this.width = 0; } } setHorizontalCornerRadius(radius) { if (Number.isInteger(radius)) { this.horizontalCornerRadius = radius; } } setVerticalCornerRadius(radius) { if (Number.isInteger(radius)) { this.verticalCornerRadius = radius; } } } class MarkupAnnotation extends Annotation { constructor(params) { super(params); const { dict } = params; if (dict.has("IRT")) { const rawIRT = dict.getRaw("IRT"); this.data.inReplyTo = rawIRT instanceof Ref ? rawIRT.toString() : null; const rt = dict.get("RT"); this.data.replyType = rt instanceof Name ? rt.name : AnnotationReplyType.REPLY; } let popupRef = null; if (this.data.replyType === AnnotationReplyType.GROUP) { const parent = dict.get("IRT"); this.setTitle(parent.get("T")); this.data.titleObj = this._title; this.setContents(parent.get("Contents")); this.data.contentsObj = this._contents; if (!parent.has("CreationDate")) { this.data.creationDate = null; } else { this.setCreationDate(parent.get("CreationDate")); this.data.creationDate = this.creationDate; } if (!parent.has("M")) { this.data.modificationDate = null; } else { this.setModificationDate(parent.get("M")); this.data.modificationDate = this.modificationDate; } popupRef = parent.getRaw("Popup"); if (!parent.has("C")) { this.data.color = null; } else { this.setColor(parent.getArray("C")); this.data.color = this.color; } } else { this.data.titleObj = this._title; this.setCreationDate(dict.get("CreationDate")); this.data.creationDate = this.creationDate; popupRef = dict.getRaw("Popup"); if (!dict.has("C")) { this.data.color = null; } } this.data.popupRef = popupRef instanceof Ref ? popupRef.toString() : null; if (dict.has("RC")) { this.data.richText = XFAFactory.getRichTextAsHtml(dict.get("RC")); } } setCreationDate(creationDate) { this.creationDate = typeof creationDate === "string" ? creationDate : null; } _setDefaultAppearance({ xref, extra, strokeColor, fillColor, blendMode, strokeAlpha, fillAlpha, pointsCallback }) { let minX = Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxX = Number.MIN_VALUE; let maxY = Number.MIN_VALUE; const buffer = ["q"]; if (extra) { buffer.push(extra); } if (strokeColor) { buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`); } if (fillColor) { buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`); } let pointsArray = this.data.quadPoints; if (!pointsArray) { pointsArray = [[{ x: this.rectangle[0], y: this.rectangle[3] }, { x: this.rectangle[2], y: this.rectangle[3] }, { x: this.rectangle[0], y: this.rectangle[1] }, { x: this.rectangle[2], y: this.rectangle[1] }]]; } for (const points of pointsArray) { const [mX, MX, mY, MY] = pointsCallback(buffer, points); minX = Math.min(minX, mX); maxX = Math.max(maxX, MX); minY = Math.min(minY, mY); maxY = Math.max(maxY, MY); } buffer.push("Q"); const formDict = new Dict(xref); const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("Subtype", Name.get("Form")); const appearanceStream = new StringStream(buffer.join(" ")); appearanceStream.dict = appearanceStreamDict; formDict.set("Fm0", appearanceStream); const gsDict = new Dict(xref); if (blendMode) { gsDict.set("BM", Name.get(blendMode)); } if (typeof strokeAlpha === "number") { gsDict.set("CA", strokeAlpha); } if (typeof fillAlpha === "number") { gsDict.set("ca", fillAlpha); } const stateDict = new Dict(xref); stateDict.set("GS0", gsDict); const resources = new Dict(xref); resources.set("ExtGState", stateDict); resources.set("XObject", formDict); const appearanceDict = new Dict(xref); appearanceDict.set("Resources", resources); const bbox = this.data.rect = [minX, minY, maxX, maxY]; appearanceDict.set("BBox", bbox); this.appearance = new StringStream("/GS0 gs /Fm0 Do"); this.appearance.dict = appearanceDict; this._streams.push(this.appearance, appearanceStream); } static async createNewAnnotation(xref, annotation, dependencies, params) { const annotationRef = annotation.ref ||= xref.getNewTemporaryRef(); const ap = await this.createNewAppearanceStream(annotation, xref, params); const buffer = []; let annotationDict; if (ap) { const apRef = xref.getNewTemporaryRef(); annotationDict = this.createNewDict(annotation, xref, { apRef }); await writeObject(apRef, ap, buffer, xref); dependencies.push({ ref: apRef, data: buffer.join("") }); } else { annotationDict = this.createNewDict(annotation, xref, {}); } if (Number.isInteger(annotation.parentTreeId)) { annotationDict.set("StructParent", annotation.parentTreeId); } buffer.length = 0; await writeObject(annotationRef, annotationDict, buffer, xref); return { ref: annotationRef, data: buffer.join("") }; } static async createNewPrintAnnotation(annotationGlobals, xref, annotation, params) { const ap = await this.createNewAppearanceStream(annotation, xref, params); const annotationDict = this.createNewDict(annotation, xref, { ap }); const newAnnotation = new this.prototype.constructor({ dict: annotationDict, xref, annotationGlobals, evaluatorOptions: params.evaluatorOptions }); if (annotation.ref) { newAnnotation.ref = newAnnotation.refToReplace = annotation.ref; } return newAnnotation; } } class WidgetAnnotation extends Annotation { constructor(params) { super(params); const { dict, xref, annotationGlobals } = params; const data = this.data; this._needAppearances = params.needAppearances; data.annotationType = AnnotationType.WIDGET; if (data.fieldName === undefined) { data.fieldName = this._constructFieldName(dict); } if (data.actions === undefined) { data.actions = collectActions(xref, dict, AnnotationActionEventType); } let fieldValue = getInheritableProperty({ dict, key: "V", getArray: true }); data.fieldValue = this._decodeFormValue(fieldValue); const defaultFieldValue = getInheritableProperty({ dict, key: "DV", getArray: true }); data.defaultFieldValue = this._decodeFormValue(defaultFieldValue); if (fieldValue === undefined && annotationGlobals.xfaDatasets) { const path = this._title.str; if (path) { this._hasValueFromXFA = true; data.fieldValue = fieldValue = annotationGlobals.xfaDatasets.getValue(path); } } if (fieldValue === undefined && data.defaultFieldValue !== null) { data.fieldValue = data.defaultFieldValue; } data.alternativeText = stringToPDFString(dict.get("TU") || ""); this.setDefaultAppearance(params); data.hasAppearance ||= this._needAppearances && data.fieldValue !== undefined && data.fieldValue !== null; const fieldType = getInheritableProperty({ dict, key: "FT" }); data.fieldType = fieldType instanceof Name ? fieldType.name : null; const localResources = getInheritableProperty({ dict, key: "DR" }); const acroFormResources = annotationGlobals.acroForm.get("DR"); const appearanceResources = this.appearance?.dict.get("Resources"); this._fieldResources = { localResources, acroFormResources, appearanceResources, mergedResources: Dict.merge({ xref, dictArray: [localResources, appearanceResources, acroFormResources], mergeSubDicts: true }) }; data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { data.fieldFlags = 0; } data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED); data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN) || this._hasFlag(data.annotationFlags, AnnotationFlag.NOVIEW); } _decodeFormValue(formValue) { if (Array.isArray(formValue)) { return formValue.filter(item => typeof item === "string").map(item => stringToPDFString(item)); } else if (formValue instanceof Name) { return stringToPDFString(formValue.name); } else if (typeof formValue === "string") { return stringToPDFString(formValue); } return null; } hasFieldFlag(flag) { return !!(this.data.fieldFlags & flag); } _isViewable(flags) { return true; } mustBeViewed(annotationStorage, renderForms) { if (renderForms) { return this.viewable; } return super.mustBeViewed(annotationStorage, renderForms) && !this._hasFlag(this.flags, AnnotationFlag.NOVIEW); } getRotationMatrix(annotationStorage) { let rotation = annotationStorage?.get(this.data.id)?.rotation; if (rotation === undefined) { rotation = this.rotation; } if (rotation === 0) { return IDENTITY_MATRIX; } const width = this.data.rect[2] - this.data.rect[0]; const height = this.data.rect[3] - this.data.rect[1]; return getRotationMatrix(rotation, width, height); } getBorderAndBackgroundAppearances(annotationStorage) { let rotation = annotationStorage?.get(this.data.id)?.rotation; if (rotation === undefined) { rotation = this.rotation; } if (!this.backgroundColor && !this.borderColor) { return ""; } const width = this.data.rect[2] - this.data.rect[0]; const height = this.data.rect[3] - this.data.rect[1]; const rect = rotation === 0 || rotation === 180 ? `0 0 ${width} ${height} re` : `0 0 ${height} ${width} re`; let str = ""; if (this.backgroundColor) { str = `${getPdfColor(this.backgroundColor, true)} ${rect} f `; } if (this.borderColor) { const borderWidth = this.borderStyle.width || 1; str += `${borderWidth} w ${getPdfColor(this.borderColor, false)} ${rect} S `; } return str; } async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) { if (renderForms && !(this instanceof SignatureWidgetAnnotation) && !this.data.noHTML && !this.data.hasOwnCanvas) { return { opList: new OperatorList(), separateForm: true, separateCanvas: false }; } if (!this._hasText) { return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage); } const content = await this._getAppearance(evaluator, task, intent, annotationStorage); if (this.appearance && content === null) { return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage); } const opList = new OperatorList(); if (!this._defaultAppearance || content === null) { return { opList, separateForm: false, separateCanvas: false }; } const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY); const matrix = [1, 0, 0, 1, 0, 0]; const bbox = [0, 0, this.data.rect[2] - this.data.rect[0], this.data.rect[3] - this.data.rect[1]]; const transform = getTransformMatrix(this.data.rect, bbox, matrix); let optionalContent; if (this.oc) { optionalContent = await evaluator.parseMarkedContentProps(this.oc, null); } if (optionalContent !== undefined) { opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); } opList.addOp(OPS.beginAnnotation, [this.data.id, this.data.rect, transform, this.getRotationMatrix(annotationStorage), isUsingOwnCanvas]); const stream = new StringStream(content); await evaluator.getOperatorList({ stream, task, resources: this._fieldResources.mergedResources, operatorList: opList }); opList.addOp(OPS.endAnnotation, []); if (optionalContent !== undefined) { opList.addOp(OPS.endMarkedContent, []); } return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas }; } _getMKDict(rotation) { const mk = new Dict(null); if (rotation) { mk.set("R", rotation); } if (this.borderColor) { mk.set("BC", getPdfColorArray(this.borderColor)); } if (this.backgroundColor) { mk.set("BG", getPdfColorArray(this.backgroundColor)); } return mk.size > 0 ? mk : null; } amendSavedDict(annotationStorage, dict) {} async save(evaluator, task, annotationStorage) { const storageEntry = annotationStorage?.get(this.data.id); let value = storageEntry?.value, rotation = storageEntry?.rotation; if (value === this.data.fieldValue || value === undefined) { if (!this._hasValueFromXFA && rotation === undefined) { return null; } value ||= this.data.fieldValue; } if (rotation === undefined && !this._hasValueFromXFA && Array.isArray(value) && Array.isArray(this.data.fieldValue) && value.length === this.data.fieldValue.length && value.every((x, i) => x === this.data.fieldValue[i])) { return null; } if (rotation === undefined) { rotation = this.rotation; } let appearance = null; if (!this._needAppearances) { appearance = await this._getAppearance(evaluator, task, RenderingIntentFlag.SAVE, annotationStorage); if (appearance === null) { return null; } } else {} let needAppearances = false; if (appearance?.needAppearances) { needAppearances = true; appearance = null; } const { xref } = evaluator; const originalDict = xref.fetchIfRef(this.ref); if (!(originalDict instanceof Dict)) { return null; } const dict = new Dict(xref); for (const key of originalDict.getKeys()) { if (key !== "AP") { dict.set(key, originalDict.getRaw(key)); } } const xfa = { path: this.data.fieldName, value }; const encoder = val => { return isAscii(val) ? val : stringToUTF16String(val, true); }; dict.set("V", Array.isArray(value) ? value.map(encoder) : encoder(value)); this.amendSavedDict(annotationStorage, dict); const maybeMK = this._getMKDict(rotation); if (maybeMK) { dict.set("MK", maybeMK); } const buffer = []; const changes = [{ ref: this.ref, data: "", xfa, needAppearances }]; if (appearance !== null) { const newRef = xref.getNewTemporaryRef(); const AP = new Dict(xref); dict.set("AP", AP); AP.set("N", newRef); const resources = this._getSaveFieldResources(xref); const appearanceStream = new StringStream(appearance); const appearanceDict = appearanceStream.dict = new Dict(xref); appearanceDict.set("Subtype", Name.get("Form")); appearanceDict.set("Resources", resources); appearanceDict.set("BBox", [0, 0, this.data.rect[2] - this.data.rect[0], this.data.rect[3] - this.data.rect[1]]); const rotationMatrix = this.getRotationMatrix(annotationStorage); if (rotationMatrix !== IDENTITY_MATRIX) { appearanceDict.set("Matrix", rotationMatrix); } await writeObject(newRef, appearanceStream, buffer, xref); changes.push({ ref: newRef, data: buffer.join(""), xfa: null, needAppearances: false }); buffer.length = 0; } dict.set("M", `D:${getModificationDate()}`); await writeObject(this.ref, dict, buffer, xref); changes[0].data = buffer.join(""); return changes; } async _getAppearance(evaluator, task, intent, annotationStorage) { const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD); if (isPassword) { return null; } const storageEntry = annotationStorage?.get(this.data.id); let value, rotation; if (storageEntry) { value = storageEntry.formattedValue || storageEntry.value; rotation = storageEntry.rotation; } if (rotation === undefined && value === undefined && !this._needAppearances) { if (!this._hasValueFromXFA || this.appearance) { return null; } } const colors = this.getBorderAndBackgroundAppearances(annotationStorage); if (value === undefined) { value = this.data.fieldValue; if (!value) { return `/Tx BMC q ${colors}Q EMC`; } } if (Array.isArray(value) && value.length === 1) { value = value[0]; } assert(typeof value === "string", "Expected `value` to be a string."); value = value.trim(); if (this.data.combo) { const option = this.data.options.find(({ exportValue }) => value === exportValue); value = option?.displayValue || value; } if (value === "") { return `/Tx BMC q ${colors}Q EMC`; } if (rotation === undefined) { rotation = this.rotation; } let lineCount = -1; let lines; if (this.data.multiLine) { lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC")); lineCount = lines.length; } else { lines = [value.replace(/\r\n?|\n/, "").normalize("NFC")]; } const defaultPadding = 1; const defaultHPadding = 2; let totalHeight = this.data.rect[3] - this.data.rect[1]; let totalWidth = this.data.rect[2] - this.data.rect[0]; if (rotation === 90 || rotation === 270) { [totalWidth, totalHeight] = [totalHeight, totalWidth]; } if (!this._defaultAppearance) { this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g"); } let font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources); let defaultAppearance, fontSize, lineHeight; const encodedLines = []; let encodingError = false; for (const line of lines) { const encodedString = font.encodeString(line); if (encodedString.length > 1) { encodingError = true; } encodedLines.push(encodedString.join("")); } if (encodingError && intent & RenderingIntentFlag.SAVE) { return { needAppearances: true }; } if (encodingError && this._isOffscreenCanvasSupported) { const fontFamily = this.data.comb ? "monospace" : "sans-serif"; const fakeUnicodeFont = new FakeUnicodeFont(evaluator.xref, fontFamily); const resources = fakeUnicodeFont.createFontResources(lines.join("")); const newFont = resources.getRaw("Font"); if (this._fieldResources.mergedResources.has("Font")) { const oldFont = this._fieldResources.mergedResources.get("Font"); for (const key of newFont.getKeys()) { oldFont.set(key, newFont.getRaw(key)); } } else { this._fieldResources.mergedResources.set("Font", newFont); } const fontName = fakeUnicodeFont.fontName.name; font = await WidgetAnnotation._getFontData(evaluator, task, { fontName, fontSize: 0 }, resources); for (let i = 0, ii = encodedLines.length; i < ii; i++) { encodedLines[i] = stringToUTF16String(lines[i]); } const savedDefaultAppearance = Object.assign(Object.create(null), this.data.defaultAppearanceData); this.data.defaultAppearanceData.fontSize = 0; this.data.defaultAppearanceData.fontName = fontName; [defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount); this.data.defaultAppearanceData = savedDefaultAppearance; } else { if (!this._isOffscreenCanvasSupported) { warn("_getAppearance: OffscreenCanvas is not supported, annotation may not render correctly."); } [defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount); } let descent = font.descent; if (isNaN(descent)) { descent = BASELINE_FACTOR * lineHeight; } else { descent = Math.max(BASELINE_FACTOR * lineHeight, Math.abs(descent) * fontSize); } const defaultVPadding = Math.min(Math.floor((totalHeight - fontSize) / 2), defaultPadding); const alignment = this.data.textAlignment; if (this.data.multiLine) { return this._getMultilineAppearance(defaultAppearance, encodedLines, font, fontSize, totalWidth, totalHeight, alignment, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage); } if (this.data.comb) { return this._getCombAppearance(defaultAppearance, font, encodedLines[0], fontSize, totalWidth, totalHeight, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage); } const bottomPadding = defaultVPadding + descent; if (alignment === 0 || alignment > 2) { return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(defaultHPadding)} ${numberToString(bottomPadding)} Tm (${escapeString(encodedLines[0])}) Tj` + " ET Q EMC"; } const prevInfo = { shift: 0 }; const renderedText = this._renderText(encodedLines[0], font, fontSize, totalWidth, alignment, prevInfo, defaultHPadding, bottomPadding); return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 0 Tm ${renderedText}` + " ET Q EMC"; } static async _getFontData(evaluator, task, appearanceData, resources) { const operatorList = new OperatorList(); const initialState = { font: null, clone() { return this; } }; const { fontName, fontSize } = appearanceData; await evaluator.handleSetFont(resources, [fontName && Name.get(fontName), fontSize], null, operatorList, task, initialState, null); return initialState.font; } _getTextWidth(text, font) { return font.charsToGlyphs(text).reduce((width, glyph) => width + glyph.width, 0) / 1000; } _computeFontSize(height, width, text, font, lineCount) { let { fontSize } = this.data.defaultAppearanceData; let lineHeight = (fontSize || 12) * LINE_FACTOR, numberOfLines = Math.round(height / lineHeight); if (!fontSize) { const roundWithTwoDigits = x => Math.floor(x * 100) / 100; if (lineCount === -1) { const textWidth = this._getTextWidth(text, font); fontSize = roundWithTwoDigits(Math.min(height / LINE_FACTOR, textWidth > width ? width / textWidth : Infinity)); numberOfLines = 1; } else { const lines = text.split(/\r\n?|\n/); const cachedLines = []; for (const line of lines) { const encoded = font.encodeString(line).join(""); const glyphs = font.charsToGlyphs(encoded); const positions = font.getCharPositions(encoded); cachedLines.push({ line: encoded, glyphs, positions }); } const isTooBig = fsize => { let totalHeight = 0; for (const cache of cachedLines) { const chunks = this._splitLine(null, font, fsize, width, cache); totalHeight += chunks.length * fsize; if (totalHeight > height) { return true; } } return false; }; numberOfLines = Math.max(numberOfLines, lineCount); while (true) { lineHeight = height / numberOfLines; fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR); if (isTooBig(fontSize)) { numberOfLines++; continue; } break; } } const { fontName, fontColor } = this.data.defaultAppearanceData; this._defaultAppearance = createDefaultAppearance({ fontSize, fontName, fontColor }); } return [this._defaultAppearance, fontSize, height / numberOfLines]; } _renderText(text, font, fontSize, totalWidth, alignment, prevInfo, hPadding, vPadding) { let shift; if (alignment === 1) { const width = this._getTextWidth(text, font) * fontSize; shift = (totalWidth - width) / 2; } else if (alignment === 2) { const width = this._getTextWidth(text, font) * fontSize; shift = totalWidth - width - hPadding; } else { shift = hPadding; } const shiftStr = numberToString(shift - prevInfo.shift); prevInfo.shift = shift; vPadding = numberToString(vPadding); return `${shiftStr} ${vPadding} Td (${escapeString(text)}) Tj`; } _getSaveFieldResources(xref) { const { localResources, appearanceResources, acroFormResources } = this._fieldResources; const fontName = this.data.defaultAppearanceData?.fontName; if (!fontName) { return localResources || Dict.empty; } for (const resources of [localResources, appearanceResources]) { if (resources instanceof Dict) { const localFont = resources.get("Font"); if (localFont instanceof Dict && localFont.has(fontName)) { return resources; } } } if (acroFormResources instanceof Dict) { const acroFormFont = acroFormResources.get("Font"); if (acroFormFont instanceof Dict && acroFormFont.has(fontName)) { const subFontDict = new Dict(xref); subFontDict.set(fontName, acroFormFont.getRaw(fontName)); const subResourcesDict = new Dict(xref); subResourcesDict.set("Font", subFontDict); return Dict.merge({ xref, dictArray: [subResourcesDict, localResources], mergeSubDicts: true }); } } return localResources || Dict.empty; } getFieldObject() { return null; } } class TextWidgetAnnotation extends WidgetAnnotation { constructor(params) { super(params); this.data.hasOwnCanvas = this.data.readOnly && !this.data.noHTML; this._hasText = true; const dict = params.dict; if (typeof this.data.fieldValue !== "string") { this.data.fieldValue = ""; } let alignment = getInheritableProperty({ dict, key: "Q" }); if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) { alignment = null; } this.data.textAlignment = alignment; let maximumLength = getInheritableProperty({ dict, key: "MaxLen" }); if (!Number.isInteger(maximumLength) || maximumLength < 0) { maximumLength = 0; } this.data.maxLen = maximumLength; this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE); this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== 0; this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL); } get hasTextContent() { return !!this.appearance && !this._needAppearances; } _getCombAppearance(defaultAppearance, font, text, fontSize, width, height, hPadding, vPadding, descent, lineHeight, annotationStorage) { const combWidth = width / this.data.maxLen; const colors = this.getBorderAndBackgroundAppearances(annotationStorage); const buf = []; const positions = font.getCharPositions(text); for (const [start, end] of positions) { buf.push(`(${escapeString(text.substring(start, end))}) Tj`); } const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `); return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(hPadding)} ${numberToString(vPadding + descent)} Tm ${renderedComb}` + " ET Q EMC"; } _getMultilineAppearance(defaultAppearance, lines, font, fontSize, width, height, alignment, hPadding, vPadding, descent, lineHeight, annotationStorage) { const buf = []; const totalWidth = width - 2 * hPadding; const prevInfo = { shift: 0 }; for (let i = 0, ii = lines.length; i < ii; i++) { const line = lines[i]; const chunks = this._splitLine(line, font, fontSize, totalWidth); for (let j = 0, jj = chunks.length; j < jj; j++) { const chunk = chunks[j]; const vShift = i === 0 && j === 0 ? -vPadding - (lineHeight - descent) : -lineHeight; buf.push(this._renderText(chunk, font, fontSize, width, alignment, prevInfo, hPadding, vShift)); } } const colors = this.getBorderAndBackgroundAppearances(annotationStorage); const renderedText = buf.join("\n"); return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 ${numberToString(height)} Tm ${renderedText}` + " ET Q EMC"; } _splitLine(line, font, fontSize, width, cache = {}) { line = cache.line || line; const glyphs = cache.glyphs || font.charsToGlyphs(line); if (glyphs.length <= 1) { return [line]; } const positions = cache.positions || font.getCharPositions(line); const scale = fontSize / 1000; const chunks = []; let lastSpacePosInStringStart = -1, lastSpacePosInStringEnd = -1, lastSpacePos = -1, startChunk = 0, currentWidth = 0; for (let i = 0, ii = glyphs.length; i < ii; i++) { const [start, end] = positions[i]; const glyph = glyphs[i]; const glyphWidth = glyph.width * scale; if (glyph.unicode === " ") { if (currentWidth + glyphWidth > width) { chunks.push(line.substring(startChunk, start)); startChunk = start; currentWidth = glyphWidth; lastSpacePosInStringStart = -1; lastSpacePos = -1; } else { currentWidth += glyphWidth; lastSpacePosInStringStart = start; lastSpacePosInStringEnd = end; lastSpacePos = i; } } else if (currentWidth + glyphWidth > width) { if (lastSpacePosInStringStart !== -1) { chunks.push(line.substring(startChunk, lastSpacePosInStringEnd)); startChunk = lastSpacePosInStringEnd; i = lastSpacePos + 1; lastSpacePosInStringStart = -1; currentWidth = 0; } else { chunks.push(line.substring(startChunk, start)); startChunk = start; currentWidth = glyphWidth; } } else { currentWidth += glyphWidth; } } if (startChunk < line.length) { chunks.push(line.substring(startChunk, line.length)); } return chunks; } getFieldObject() { return { id: this.data.id, value: this.data.fieldValue, defaultValue: this.data.defaultFieldValue || "", multiline: this.data.multiLine, password: this.hasFieldFlag(AnnotationFieldFlag.PASSWORD), charLimit: this.data.maxLen, comb: this.data.comb, editable: !this.data.readOnly, hidden: this.data.hidden, name: this.data.fieldName, rect: this.data.rect, actions: this.data.actions, page: this.data.pageIndex, strokeColor: this.data.borderColor, fillColor: this.data.backgroundColor, rotation: this.rotation, type: "text" }; } } class ButtonWidgetAnnotation extends WidgetAnnotation { constructor(params) { super(params); this.checkedAppearance = null; this.uncheckedAppearance = null; this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); this.data.pushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); this.data.isTooltipOnly = false; if (this.data.checkBox) { this._processCheckBox(params); } else if (this.data.radioButton) { this._processRadioButton(params); } else if (this.data.pushButton) { this.data.hasOwnCanvas = true; this.data.noHTML = false; this._processPushButton(params); } else { warn("Invalid field flags for button widget annotation"); } } async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) { if (this.data.pushButton) { return super.getOperatorList(evaluator, task, intent, false, annotationStorage); } let value = null; let rotation = null; if (annotationStorage) { const storageEntry = annotationStorage.get(this.data.id); value = storageEntry ? storageEntry.value : null; rotation = storageEntry ? storageEntry.rotation : null; } if (value === null && this.appearance) { return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage); } if (value === null || value === undefined) { value = this.data.checkBox ? this.data.fieldValue === this.data.exportValue : this.data.fieldValue === this.data.buttonValue; } const appearance = value ? this.checkedAppearance : this.uncheckedAppearance; if (appearance) { const savedAppearance = this.appearance; const savedMatrix = appearance.dict.getArray("Matrix") || IDENTITY_MATRIX; if (rotation) { appearance.dict.set("Matrix", this.getRotationMatrix(annotationStorage)); } this.appearance = appearance; const operatorList = super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage); this.appearance = savedAppearance; appearance.dict.set("Matrix", savedMatrix); return operatorList; } return { opList: new OperatorList(), separateForm: false, separateCanvas: false }; } async save(evaluator, task, annotationStorage) { if (this.data.checkBox) { return this._saveCheckbox(evaluator, task, annotationStorage); } if (this.data.radioButton) { return this._saveRadioButton(evaluator, task, annotationStorage); } return null; } async _saveCheckbox(evaluator, task, annotationStorage) { if (!annotationStorage) { return null; } const storageEntry = annotationStorage.get(this.data.id); let rotation = storageEntry?.rotation, value = storageEntry?.value; if (rotation === undefined) { if (value === undefined) { return null; } const defaultValue = this.data.fieldValue === this.data.exportValue; if (defaultValue === value) { return null; } } const dict = evaluator.xref.fetchIfRef(this.ref); if (!(dict instanceof Dict)) { return null; } if (rotation === undefined) { rotation = this.rotation; } if (value === undefined) { value = this.data.fieldValue === this.data.exportValue; } const xfa = { path: this.data.fieldName, value: value ? this.data.exportValue : "" }; const name = Name.get(value ? this.data.exportValue : "Off"); dict.set("V", name); dict.set("AS", name); dict.set("M", `D:${getModificationDate()}`); const maybeMK = this._getMKDict(rotation); if (maybeMK) { dict.set("MK", maybeMK); } const buffer = []; await writeObject(this.ref, dict, buffer, evaluator.xref); return [{ ref: this.ref, data: buffer.join(""), xfa }]; } async _saveRadioButton(evaluator, task, annotationStorage) { if (!annotationStorage) { return null; } const storageEntry = annotationStorage.get(this.data.id); let rotation = storageEntry?.rotation, value = storageEntry?.value; if (rotation === undefined) { if (value === undefined) { return null; } const defaultValue = this.data.fieldValue === this.data.buttonValue; if (defaultValue === value) { return null; } } const dict = evaluator.xref.fetchIfRef(this.ref); if (!(dict instanceof Dict)) { return null; } if (value === undefined) { value = this.data.fieldValue === this.data.buttonValue; } if (rotation === undefined) { rotation = this.rotation; } const xfa = { path: this.data.fieldName, value: value ? this.data.buttonValue : "" }; const name = Name.get(value ? this.data.buttonValue : "Off"); const buffer = []; let parentData = null; if (value) { if (this.parent instanceof Ref) { const parent = evaluator.xref.fetch(this.parent); parent.set("V", name); await writeObject(this.parent, parent, buffer, evaluator.xref); parentData = buffer.join(""); buffer.length = 0; } else if (this.parent instanceof Dict) { this.parent.set("V", name); } } dict.set("AS", name); dict.set("M", `D:${getModificationDate()}`); const maybeMK = this._getMKDict(rotation); if (maybeMK) { dict.set("MK", maybeMK); } await writeObject(this.ref, dict, buffer, evaluator.xref); const newRefs = [{ ref: this.ref, data: buffer.join(""), xfa }]; if (parentData) { newRefs.push({ ref: this.parent, data: parentData, xfa: null }); } return newRefs; } _getDefaultCheckedAppearance(params, type) { const width = this.data.rect[2] - this.data.rect[0]; const height = this.data.rect[3] - this.data.rect[1]; const bbox = [0, 0, width, height]; const FONT_RATIO = 0.8; const fontSize = Math.min(width, height) * FONT_RATIO; let metrics, char; if (type === "check") { metrics = { width: 0.755 * fontSize, height: 0.705 * fontSize }; char = "\x33"; } else if (type === "disc") { metrics = { width: 0.791 * fontSize, height: 0.705 * fontSize }; char = "\x6C"; } else { unreachable(`_getDefaultCheckedAppearance - unsupported type: ${type}`); } const xShift = numberToString((width - metrics.width) / 2); const yShift = numberToString((height - metrics.height) / 2); const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`; const appearanceStreamDict = new Dict(params.xref); appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", bbox); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]); appearanceStreamDict.set("Length", appearance.length); const resources = new Dict(params.xref); const font = new Dict(params.xref); font.set("PdfJsZaDb", this.fallbackFontDict); resources.set("Font", font); appearanceStreamDict.set("Resources", resources); this.checkedAppearance = new StringStream(appearance); this.checkedAppearance.dict = appearanceStreamDict; this._streams.push(this.checkedAppearance); } _processCheckBox(params) { const customAppearance = params.dict.get("AP"); if (!(customAppearance instanceof Dict)) { return; } const normalAppearance = customAppearance.get("N"); if (!(normalAppearance instanceof Dict)) { return; } const asValue = this._decodeFormValue(params.dict.get("AS")); if (typeof asValue === "string") { this.data.fieldValue = asValue; } const yes = this.data.fieldValue !== null && this.data.fieldValue !== "Off" ? this.data.fieldValue : "Yes"; const exportValues = normalAppearance.getKeys(); if (exportValues.length === 0) { exportValues.push("Off", yes); } else if (exportValues.length === 1) { if (exportValues[0] === "Off") { exportValues.push(yes); } else { exportValues.unshift("Off"); } } else if (exportValues.includes(yes)) { exportValues.length = 0; exportValues.push("Off", yes); } else { const otherYes = exportValues.find(v => v !== "Off"); exportValues.length = 0; exportValues.push("Off", otherYes); } if (!exportValues.includes(this.data.fieldValue)) { this.data.fieldValue = "Off"; } this.data.exportValue = exportValues[1]; const checkedAppearance = normalAppearance.get(this.data.exportValue); this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null; const uncheckedAppearance = normalAppearance.get("Off"); this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null; if (this.checkedAppearance) { this._streams.push(this.checkedAppearance); } else { this._getDefaultCheckedAppearance(params, "check"); } if (this.uncheckedAppearance) { this._streams.push(this.uncheckedAppearance); } this._fallbackFontDict = this.fallbackFontDict; if (this.data.defaultFieldValue === null) { this.data.defaultFieldValue = "Off"; } } _processRadioButton(params) { this.data.buttonValue = null; const fieldParent = params.dict.get("Parent"); if (fieldParent instanceof Dict) { this.parent = params.dict.getRaw("Parent"); const fieldParentValue = fieldParent.get("V"); if (fieldParentValue instanceof Name) { this.data.fieldValue = this._decodeFormValue(fieldParentValue); } } const appearanceStates = params.dict.get("AP"); if (!(appearanceStates instanceof Dict)) { return; } const normalAppearance = appearanceStates.get("N"); if (!(normalAppearance instanceof Dict)) { return; } for (const key of normalAppearance.getKeys()) { if (key !== "Off") { this.data.buttonValue = this._decodeFormValue(key); break; } } const checkedAppearance = normalAppearance.get(this.data.buttonValue); this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null; const uncheckedAppearance = normalAppearance.get("Off"); this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null; if (this.checkedAppearance) { this._streams.push(this.checkedAppearance); } else { this._getDefaultCheckedAppearance(params, "disc"); } if (this.uncheckedAppearance) { this._streams.push(this.uncheckedAppearance); } this._fallbackFontDict = this.fallbackFontDict; if (this.data.defaultFieldValue === null) { this.data.defaultFieldValue = "Off"; } } _processPushButton(params) { const { dict, annotationGlobals } = params; if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) { warn("Push buttons without action dictionaries are not supported"); return; } this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA"); Catalog.parseDestDictionary({ destDict: dict, resultObj: this.data, docBaseUrl: annotationGlobals.baseUrl, docAttachments: annotationGlobals.attachments }); } getFieldObject() { let type = "button"; let exportValues; if (this.data.checkBox) { type = "checkbox"; exportValues = this.data.exportValue; } else if (this.data.radioButton) { type = "radiobutton"; exportValues = this.data.buttonValue; } return { id: this.data.id, value: this.data.fieldValue || "Off", defaultValue: this.data.defaultFieldValue, exportValues, editable: !this.data.readOnly, name: this.data.fieldName, rect: this.data.rect, hidden: this.data.hidden, actions: this.data.actions, page: this.data.pageIndex, strokeColor: this.data.borderColor, fillColor: this.data.backgroundColor, rotation: this.rotation, type }; } get fallbackFontDict() { const dict = new Dict(); dict.set("BaseFont", Name.get("ZapfDingbats")); dict.set("Type", Name.get("FallbackType")); dict.set("Subtype", Name.get("FallbackType")); dict.set("Encoding", Name.get("ZapfDingbatsEncoding")); return shadow(this, "fallbackFontDict", dict); } } class ChoiceWidgetAnnotation extends WidgetAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.indices = dict.getArray("I"); this.hasIndices = Array.isArray(this.indices) && this.indices.length > 0; this.data.options = []; const options = getInheritableProperty({ dict, key: "Opt" }); if (Array.isArray(options)) { for (let i = 0, ii = options.length; i < ii; i++) { const option = xref.fetchIfRef(options[i]); const isOptionArray = Array.isArray(option); this.data.options[i] = { exportValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[0]) : option), displayValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[1]) : option) }; } } if (!this.hasIndices) { if (typeof this.data.fieldValue === "string") { this.data.fieldValue = [this.data.fieldValue]; } else if (!this.data.fieldValue) { this.data.fieldValue = []; } } else { this.data.fieldValue = []; const ii = this.data.options.length; for (const i of this.indices) { if (Number.isInteger(i) && i >= 0 && i < ii) { this.data.fieldValue.push(this.data.options[i].exportValue); } } } this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO); this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT); this._hasText = true; } getFieldObject() { const type = this.data.combo ? "combobox" : "listbox"; const value = this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null; return { id: this.data.id, value, defaultValue: this.data.defaultFieldValue, editable: !this.data.readOnly, name: this.data.fieldName, rect: this.data.rect, numItems: this.data.fieldValue.length, multipleSelection: this.data.multiSelect, hidden: this.data.hidden, actions: this.data.actions, items: this.data.options, page: this.data.pageIndex, strokeColor: this.data.borderColor, fillColor: this.data.backgroundColor, rotation: this.rotation, type }; } amendSavedDict(annotationStorage, dict) { if (!this.hasIndices) { return; } let values = annotationStorage?.get(this.data.id)?.value; if (!Array.isArray(values)) { values = [values]; } const indices = []; const { options } = this.data; for (let i = 0, j = 0, ii = options.length; i < ii; i++) { if (options[i].exportValue === values[j]) { indices.push(i); j += 1; } } dict.set("I", indices); } async _getAppearance(evaluator, task, intent, annotationStorage) { if (this.data.combo) { return super._getAppearance(evaluator, task, intent, annotationStorage); } let exportedValue, rotation; const storageEntry = annotationStorage?.get(this.data.id); if (storageEntry) { rotation = storageEntry.rotation; exportedValue = storageEntry.value; } if (rotation === undefined && exportedValue === undefined && !this._needAppearances) { return null; } if (exportedValue === undefined) { exportedValue = this.data.fieldValue; } else if (!Array.isArray(exportedValue)) { exportedValue = [exportedValue]; } const defaultPadding = 1; const defaultHPadding = 2; let totalHeight = this.data.rect[3] - this.data.rect[1]; let totalWidth = this.data.rect[2] - this.data.rect[0]; if (rotation === 90 || rotation === 270) { [totalWidth, totalHeight] = [totalHeight, totalWidth]; } const lineCount = this.data.options.length; const valueIndices = []; for (let i = 0; i < lineCount; i++) { const { exportValue } = this.data.options[i]; if (exportedValue.includes(exportValue)) { valueIndices.push(i); } } if (!this._defaultAppearance) { this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g"); } const font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources); let defaultAppearance; let { fontSize } = this.data.defaultAppearanceData; if (!fontSize) { const lineHeight = (totalHeight - defaultPadding) / lineCount; let lineWidth = -1; let value; for (const { displayValue } of this.data.options) { const width = this._getTextWidth(displayValue, font); if (width > lineWidth) { lineWidth = width; value = displayValue; } } [defaultAppearance, fontSize] = this._computeFontSize(lineHeight, totalWidth - 2 * defaultHPadding, value, font, -1); } else { defaultAppearance = this._defaultAppearance; } const lineHeight = fontSize * LINE_FACTOR; const vPadding = (lineHeight - fontSize) / 2; const numberOfVisibleLines = Math.floor(totalHeight / lineHeight); let firstIndex = 0; if (valueIndices.length > 0) { const minIndex = Math.min(...valueIndices); const maxIndex = Math.max(...valueIndices); firstIndex = Math.max(0, maxIndex - numberOfVisibleLines + 1); if (firstIndex > minIndex) { firstIndex = minIndex; } } const end = Math.min(firstIndex + numberOfVisibleLines + 1, lineCount); const buf = ["/Tx BMC q", `1 1 ${totalWidth} ${totalHeight} re W n`]; if (valueIndices.length) { buf.push("0.600006 0.756866 0.854904 rg"); for (const index of valueIndices) { if (firstIndex <= index && index < end) { buf.push(`1 ${totalHeight - (index - firstIndex + 1) * lineHeight} ${totalWidth} ${lineHeight} re f`); } } } buf.push("BT", defaultAppearance, `1 0 0 1 0 ${totalHeight} Tm`); const prevInfo = { shift: 0 }; for (let i = firstIndex; i < end; i++) { const { displayValue } = this.data.options[i]; const vpadding = i === firstIndex ? vPadding : 0; buf.push(this._renderText(displayValue, font, fontSize, totalWidth, 0, prevInfo, defaultHPadding, -lineHeight + vpadding)); } buf.push("ET Q EMC"); return buf.join("\n"); } } class SignatureWidgetAnnotation extends WidgetAnnotation { constructor(params) { super(params); this.data.fieldValue = null; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = !this.data.hasOwnCanvas; } getFieldObject() { return { id: this.data.id, value: null, page: this.data.pageIndex, type: "signature" }; } } class TextAnnotation extends MarkupAnnotation { constructor(params) { const DEFAULT_ICON_SIZE = 22; super(params); this.data.noRotate = true; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; const { dict } = params; this.data.annotationType = AnnotationType.TEXT; if (this.data.hasAppearance) { this.data.name = "NoIcon"; } else { this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE; this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE; this.data.name = dict.has("Name") ? dict.get("Name").name : "Note"; } if (dict.has("State")) { this.data.state = dict.get("State") || null; this.data.stateModel = dict.get("StateModel") || null; } else { this.data.state = null; this.data.stateModel = null; } } } class LinkAnnotation extends Annotation { constructor(params) { super(params); const { dict, annotationGlobals } = params; this.data.annotationType = AnnotationType.LINK; const quadPoints = getQuadPoints(dict, this.rectangle); if (quadPoints) { this.data.quadPoints = quadPoints; } this.data.borderColor ||= this.data.color; Catalog.parseDestDictionary({ destDict: dict, resultObj: this.data, docBaseUrl: annotationGlobals.baseUrl, docAttachments: annotationGlobals.attachments }); } } class PopupAnnotation extends Annotation { constructor(params) { super(params); const { dict } = params; this.data.annotationType = AnnotationType.POPUP; this.data.noHTML = false; if (this.data.rect[0] === this.data.rect[2] || this.data.rect[1] === this.data.rect[3]) { this.data.rect = null; } let parentItem = dict.get("Parent"); if (!parentItem) { warn("Popup annotation has a missing or invalid parent annotation."); return; } const parentRect = parentItem.getArray("Rect"); this.data.parentRect = Array.isArray(parentRect) && parentRect.length === 4 ? Util.normalizeRect(parentRect) : null; const rt = parentItem.get("RT"); if (isName(rt, AnnotationReplyType.GROUP)) { parentItem = parentItem.get("IRT"); } if (!parentItem.has("M")) { this.data.modificationDate = null; } else { this.setModificationDate(parentItem.get("M")); this.data.modificationDate = this.modificationDate; } if (!parentItem.has("C")) { this.data.color = null; } else { this.setColor(parentItem.getArray("C")); this.data.color = this.color; } if (!this.viewable) { const parentFlags = parentItem.get("F"); if (this._isViewable(parentFlags)) { this.setFlags(parentFlags); } } this.setTitle(parentItem.get("T")); this.data.titleObj = this._title; this.setContents(parentItem.get("Contents")); this.data.contentsObj = this._contents; if (parentItem.has("RC")) { this.data.richText = XFAFactory.getRichTextAsHtml(parentItem.get("RC")); } this.data.open = !!dict.get("Open"); } } class FreeTextAnnotation extends MarkupAnnotation { constructor(params) { super(params); this.data.hasOwnCanvas = !this.data.noHTML; this.data.noHTML = false; const { evaluatorOptions, xref } = params; this.data.annotationType = AnnotationType.FREETEXT; this.setDefaultAppearance(params); if (this.appearance) { const { fontColor, fontSize } = parseAppearanceStream(this.appearance, evaluatorOptions, xref); this.data.defaultAppearanceData.fontColor = fontColor; this.data.defaultAppearanceData.fontSize = fontSize || 10; } else if (this._isOffscreenCanvasSupported) { const strokeAlpha = params.dict.get("CA"); const fakeUnicodeFont = new FakeUnicodeFont(xref, "sans-serif"); this.data.defaultAppearanceData.fontSize ||= 10; const { fontColor, fontSize } = this.data.defaultAppearanceData; this.appearance = fakeUnicodeFont.createAppearance(this._contents.str, this.rectangle, this.rotation, fontSize, fontColor, strokeAlpha); this._streams.push(this.appearance, FakeUnicodeFont.toUnicodeStream); } else { warn("FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly."); } } get hasTextContent() { return !!this.appearance; } static createNewDict(annotation, xref, { apRef, ap }) { const { color, fontSize, rect, rotation, user, value } = annotation; const freetext = new Dict(xref); freetext.set("Type", Name.get("Annot")); freetext.set("Subtype", Name.get("FreeText")); freetext.set("CreationDate", `D:${getModificationDate()}`); freetext.set("Rect", rect); const da = `/Helv ${fontSize} Tf ${getPdfColor(color, true)}`; freetext.set("DA", da); freetext.set("Contents", isAscii(value) ? value : stringToUTF16String(value, true)); freetext.set("F", 4); freetext.set("Border", [0, 0, 0]); freetext.set("Rotate", rotation); if (user) { freetext.set("T", isAscii(user) ? user : stringToUTF16String(user, true)); } if (apRef || ap) { const n = new Dict(xref); freetext.set("AP", n); if (apRef) { n.set("N", apRef); } else { n.set("N", ap); } } return freetext; } static async createNewAppearanceStream(annotation, xref, params) { const { baseFontRef, evaluator, task } = params; const { color, fontSize, rect, rotation, value } = annotation; const resources = new Dict(xref); const font = new Dict(xref); if (baseFontRef) { font.set("Helv", baseFontRef); } else { const baseFont = new Dict(xref); baseFont.set("BaseFont", Name.get("Helvetica")); baseFont.set("Type", Name.get("Font")); baseFont.set("Subtype", Name.get("Type1")); baseFont.set("Encoding", Name.get("WinAnsiEncoding")); font.set("Helv", baseFont); } resources.set("Font", font); const helv = await WidgetAnnotation._getFontData(evaluator, task, { fontName: "Helv", fontSize }, resources); const [x1, y1, x2, y2] = rect; let w = x2 - x1; let h = y2 - y1; if (rotation % 180 !== 0) { [w, h] = [h, w]; } const lines = value.split("\n"); const scale = fontSize / 1000; let totalWidth = -Infinity; const encodedLines = []; for (let line of lines) { const encoded = helv.encodeString(line); if (encoded.length > 1) { return null; } line = encoded.join(""); encodedLines.push(line); let lineWidth = 0; const glyphs = helv.charsToGlyphs(line); for (const glyph of glyphs) { lineWidth += glyph.width * scale; } totalWidth = Math.max(totalWidth, lineWidth); } let hscale = 1; if (totalWidth > w) { hscale = w / totalWidth; } let vscale = 1; const lineHeight = LINE_FACTOR * fontSize; const lineAscent = (LINE_FACTOR - LINE_DESCENT_FACTOR) * fontSize; const totalHeight = lineHeight * lines.length; if (totalHeight > h) { vscale = h / totalHeight; } const fscale = Math.min(hscale, vscale); const newFontSize = fontSize * fscale; let firstPoint, clipBox, matrix; switch (rotation) { case 0: matrix = [1, 0, 0, 1]; clipBox = [rect[0], rect[1], w, h]; firstPoint = [rect[0], rect[3] - lineAscent]; break; case 90: matrix = [0, 1, -1, 0]; clipBox = [rect[1], -rect[2], w, h]; firstPoint = [rect[1], -rect[0] - lineAscent]; break; case 180: matrix = [-1, 0, 0, -1]; clipBox = [-rect[2], -rect[3], w, h]; firstPoint = [-rect[2], -rect[1] - lineAscent]; break; case 270: matrix = [0, -1, 1, 0]; clipBox = [-rect[3], rect[0], w, h]; firstPoint = [-rect[3], rect[2] - lineAscent]; break; } const buffer = ["q", `${matrix.join(" ")} 0 0 cm`, `${clipBox.join(" ")} re W n`, `BT`, `${getPdfColor(color, true)}`, `0 Tc /Helv ${numberToString(newFontSize)} Tf`]; buffer.push(`${firstPoint.join(" ")} Td (${escapeString(encodedLines[0])}) Tj`); const vShift = numberToString(lineHeight); for (let i = 1, ii = encodedLines.length; i < ii; i++) { const line = encodedLines[i]; buffer.push(`0 -${vShift} Td (${escapeString(line)}) Tj`); } buffer.push("ET", "Q"); const appearance = buffer.join("\n"); const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Resources", resources); appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]); const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; return ap; } } class LineAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.LINE; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; const lineCoordinates = dict.getArray("L"); this.data.lineCoordinates = Util.normalizeRect(lineCoordinates); this.setLineEndings(dict.getArray("LE")); this.data.lineEndings = this.lineEndings; if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); const interiorColor = getRgbColor(dict.getArray("IC"), null); const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillAlpha = fillColor ? strokeAlpha : null; const borderWidth = this.borderStyle.width || 1, borderAdjust = 2 * borderWidth; const bbox = [this.data.lineCoordinates[0] - borderAdjust, this.data.lineCoordinates[1] - borderAdjust, this.data.lineCoordinates[2] + borderAdjust, this.data.lineCoordinates[3] + borderAdjust]; if (!Util.intersect(this.rectangle, bbox)) { this.rectangle = bbox; } this._setDefaultAppearance({ xref, extra: `${borderWidth} w`, strokeColor, fillColor, strokeAlpha, fillAlpha, pointsCallback: (buffer, points) => { buffer.push(`${lineCoordinates[0]} ${lineCoordinates[1]} m`, `${lineCoordinates[2]} ${lineCoordinates[3]} l`, "S"); return [points[0].x - borderWidth, points[1].x + borderWidth, points[3].y - borderWidth, points[1].y + borderWidth]; } }); } } } class SquareAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.SQUARE; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); const interiorColor = getRgbColor(dict.getArray("IC"), null); const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillAlpha = fillColor ? strokeAlpha : null; if (this.borderStyle.width === 0 && !fillColor) { return; } this._setDefaultAppearance({ xref, extra: `${this.borderStyle.width} w`, strokeColor, fillColor, strokeAlpha, fillAlpha, pointsCallback: (buffer, points) => { const x = points[2].x + this.borderStyle.width / 2; const y = points[2].y + this.borderStyle.width / 2; const width = points[3].x - points[2].x - this.borderStyle.width; const height = points[1].y - points[3].y - this.borderStyle.width; buffer.push(`${x} ${y} ${width} ${height} re`); if (fillColor) { buffer.push("B"); } else { buffer.push("S"); } return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } } class CircleAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.CIRCLE; if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); const interiorColor = getRgbColor(dict.getArray("IC"), null); const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; const fillAlpha = fillColor ? strokeAlpha : null; if (this.borderStyle.width === 0 && !fillColor) { return; } const controlPointsDistance = 4 / 3 * Math.tan(Math.PI / (2 * 4)); this._setDefaultAppearance({ xref, extra: `${this.borderStyle.width} w`, strokeColor, fillColor, strokeAlpha, fillAlpha, pointsCallback: (buffer, points) => { const x0 = points[0].x + this.borderStyle.width / 2; const y0 = points[0].y - this.borderStyle.width / 2; const x1 = points[3].x - this.borderStyle.width / 2; const y1 = points[3].y + this.borderStyle.width / 2; const xMid = x0 + (x1 - x0) / 2; const yMid = y0 + (y1 - y0) / 2; const xOffset = (x1 - x0) / 2 * controlPointsDistance; const yOffset = (y1 - y0) / 2 * controlPointsDistance; buffer.push(`${xMid} ${y1} m`, `${xMid + xOffset} ${y1} ${x1} ${yMid + yOffset} ${x1} ${yMid} c`, `${x1} ${yMid - yOffset} ${xMid + xOffset} ${y0} ${xMid} ${y0} c`, `${xMid - xOffset} ${y0} ${x0} ${yMid - yOffset} ${x0} ${yMid} c`, `${x0} ${yMid + yOffset} ${xMid - xOffset} ${y1} ${xMid} ${y1} c`, "h"); if (fillColor) { buffer.push("B"); } else { buffer.push("S"); } return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } } class PolylineAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.POLYLINE; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; this.data.vertices = []; if (!(this instanceof PolygonAnnotation)) { this.setLineEndings(dict.getArray("LE")); this.data.lineEndings = this.lineEndings; } const rawVertices = dict.getArray("Vertices"); if (!Array.isArray(rawVertices)) { return; } for (let i = 0, ii = rawVertices.length; i < ii; i += 2) { this.data.vertices.push({ x: rawVertices[i], y: rawVertices[i + 1] }); } if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); const borderWidth = this.borderStyle.width || 1, borderAdjust = 2 * borderWidth; const bbox = [Infinity, Infinity, -Infinity, -Infinity]; for (const vertex of this.data.vertices) { bbox[0] = Math.min(bbox[0], vertex.x - borderAdjust); bbox[1] = Math.min(bbox[1], vertex.y - borderAdjust); bbox[2] = Math.max(bbox[2], vertex.x + borderAdjust); bbox[3] = Math.max(bbox[3], vertex.y + borderAdjust); } if (!Util.intersect(this.rectangle, bbox)) { this.rectangle = bbox; } this._setDefaultAppearance({ xref, extra: `${borderWidth} w`, strokeColor, strokeAlpha, pointsCallback: (buffer, points) => { const vertices = this.data.vertices; for (let i = 0, ii = vertices.length; i < ii; i++) { buffer.push(`${vertices[i].x} ${vertices[i].y} ${i === 0 ? "m" : "l"}`); } buffer.push("S"); return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } } class PolygonAnnotation extends PolylineAnnotation { constructor(params) { super(params); this.data.annotationType = AnnotationType.POLYGON; } } class CaretAnnotation extends MarkupAnnotation { constructor(params) { super(params); this.data.annotationType = AnnotationType.CARET; } } class InkAnnotation extends MarkupAnnotation { constructor(params) { super(params); this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; const { dict, xref } = params; this.data.annotationType = AnnotationType.INK; this.data.inkLists = []; const rawInkLists = dict.getArray("InkList"); if (!Array.isArray(rawInkLists)) { return; } for (let i = 0, ii = rawInkLists.length; i < ii; ++i) { this.data.inkLists.push([]); for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) { this.data.inkLists[i].push({ x: xref.fetchIfRef(rawInkLists[i][j]), y: xref.fetchIfRef(rawInkLists[i][j + 1]) }); } } if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); const borderWidth = this.borderStyle.width || 1, borderAdjust = 2 * borderWidth; const bbox = [Infinity, Infinity, -Infinity, -Infinity]; for (const inkLists of this.data.inkLists) { for (const vertex of inkLists) { bbox[0] = Math.min(bbox[0], vertex.x - borderAdjust); bbox[1] = Math.min(bbox[1], vertex.y - borderAdjust); bbox[2] = Math.max(bbox[2], vertex.x + borderAdjust); bbox[3] = Math.max(bbox[3], vertex.y + borderAdjust); } } if (!Util.intersect(this.rectangle, bbox)) { this.rectangle = bbox; } this._setDefaultAppearance({ xref, extra: `${borderWidth} w`, strokeColor, strokeAlpha, pointsCallback: (buffer, points) => { for (const inkList of this.data.inkLists) { for (let i = 0, ii = inkList.length; i < ii; i++) { buffer.push(`${inkList[i].x} ${inkList[i].y} ${i === 0 ? "m" : "l"}`); } buffer.push("S"); } return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } static createNewDict(annotation, xref, { apRef, ap }) { const { color, opacity, paths, rect, rotation, thickness } = annotation; const ink = new Dict(xref); ink.set("Type", Name.get("Annot")); ink.set("Subtype", Name.get("Ink")); ink.set("CreationDate", `D:${getModificationDate()}`); ink.set("Rect", rect); ink.set("InkList", paths.map(p => p.points)); ink.set("F", 4); ink.set("Rotate", rotation); const bs = new Dict(xref); ink.set("BS", bs); bs.set("W", thickness); ink.set("C", Array.from(color, c => c / 255)); ink.set("CA", opacity); const n = new Dict(xref); ink.set("AP", n); if (apRef) { n.set("N", apRef); } else { n.set("N", ap); } return ink; } static async createNewAppearanceStream(annotation, xref, params) { const { color, rect, paths, thickness, opacity } = annotation; const appearanceBuffer = [`${thickness} w 1 J 1 j`, `${getPdfColor(color, false)}`]; if (opacity !== 1) { appearanceBuffer.push("/R0 gs"); } const buffer = []; for (const { bezier } of paths) { buffer.length = 0; buffer.push(`${numberToString(bezier[0])} ${numberToString(bezier[1])} m`); for (let i = 2, ii = bezier.length; i < ii; i += 6) { const curve = bezier.slice(i, i + 6).map(numberToString).join(" "); buffer.push(`${curve} c`); } buffer.push("S"); appearanceBuffer.push(buffer.join("\n")); } const appearance = appearanceBuffer.join("\n"); const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); if (opacity !== 1) { const resources = new Dict(xref); const extGState = new Dict(xref); const r0 = new Dict(xref); r0.set("CA", opacity); r0.set("Type", Name.get("ExtGState")); extGState.set("R0", r0); resources.set("ExtGState", extGState); appearanceStreamDict.set("Resources", resources); } const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; return ap; } } class HighlightAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.HIGHLIGHT; const quadPoints = this.data.quadPoints = getQuadPoints(dict, null); if (quadPoints) { const resources = this.appearance?.dict.get("Resources"); if (!this.appearance || !resources?.has("ExtGState")) { if (this.appearance) { warn("HighlightAnnotation - ignoring built-in appearance stream."); } const fillColor = this.color ? getPdfColorArray(this.color) : [1, 1, 0]; const fillAlpha = dict.get("CA"); this._setDefaultAppearance({ xref, fillColor, blendMode: "Multiply", fillAlpha, pointsCallback: (buffer, points) => { buffer.push(`${points[0].x} ${points[0].y} m`, `${points[1].x} ${points[1].y} l`, `${points[3].x} ${points[3].y} l`, `${points[2].x} ${points[2].y} l`, "f"); return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } else { this.data.popupRef = null; } } static createNewDict(annotation, xref, { apRef, ap }) { const { color, opacity, rect, rotation, user, quadPoints } = annotation; const highlight = new Dict(xref); highlight.set("Type", Name.get("Annot")); highlight.set("Subtype", Name.get("Highlight")); highlight.set("CreationDate", `D:${getModificationDate()}`); highlight.set("Rect", rect); highlight.set("F", 4); highlight.set("Border", [0, 0, 0]); highlight.set("Rotate", rotation); highlight.set("QuadPoints", quadPoints); highlight.set("C", Array.from(color, c => c / 255)); highlight.set("CA", opacity); if (user) { highlight.set("T", isAscii(user) ? user : stringToUTF16String(user, true)); } if (apRef || ap) { const n = new Dict(xref); highlight.set("AP", n); n.set("N", apRef || ap); } return highlight; } static async createNewAppearanceStream(annotation, xref, params) { const { color, rect, outlines, opacity } = annotation; const appearanceBuffer = [`${getPdfColor(color, true)}`, "/R0 gs"]; const buffer = []; for (const outline of outlines) { buffer.length = 0; buffer.push(`${numberToString(outline[0])} ${numberToString(outline[1])} m`); for (let i = 2, ii = outline.length; i < ii; i += 2) { buffer.push(`${numberToString(outline[i])} ${numberToString(outline[i + 1])} l`); } buffer.push("h"); appearanceBuffer.push(buffer.join("\n")); } appearanceBuffer.push("f*"); const appearance = appearanceBuffer.join("\n"); const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", rect); appearanceStreamDict.set("Length", appearance.length); const resources = new Dict(xref); const extGState = new Dict(xref); resources.set("ExtGState", extGState); appearanceStreamDict.set("Resources", resources); const r0 = new Dict(xref); extGState.set("R0", r0); r0.set("BM", Name.get("Multiply")); if (opacity !== 1) { r0.set("ca", opacity); r0.set("Type", Name.get("ExtGState")); } const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; return ap; } } class UnderlineAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.UNDERLINE; const quadPoints = this.data.quadPoints = getQuadPoints(dict, null); if (quadPoints) { if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); this._setDefaultAppearance({ xref, extra: "[] 0 d 0.571 w", strokeColor, strokeAlpha, pointsCallback: (buffer, points) => { buffer.push(`${points[2].x} ${points[2].y + 1.3} m`, `${points[3].x} ${points[3].y + 1.3} l`, "S"); return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } else { this.data.popupRef = null; } } } class SquigglyAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.SQUIGGLY; const quadPoints = this.data.quadPoints = getQuadPoints(dict, null); if (quadPoints) { if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); this._setDefaultAppearance({ xref, extra: "[] 0 d 1 w", strokeColor, strokeAlpha, pointsCallback: (buffer, points) => { const dy = (points[0].y - points[2].y) / 6; let shift = dy; let x = points[2].x; const y = points[2].y; const xEnd = points[3].x; buffer.push(`${x} ${y + shift} m`); do { x += 2; shift = shift === 0 ? dy : 0; buffer.push(`${x} ${y + shift} l`); } while (x < xEnd); buffer.push("S"); return [points[2].x, xEnd, y - 2 * dy, y + 2 * dy]; } }); } } else { this.data.popupRef = null; } } } class StrikeOutAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; this.data.annotationType = AnnotationType.STRIKEOUT; const quadPoints = this.data.quadPoints = getQuadPoints(dict, null); if (quadPoints) { if (!this.appearance) { const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; const strokeAlpha = dict.get("CA"); this._setDefaultAppearance({ xref, extra: "[] 0 d 1 w", strokeColor, strokeAlpha, pointsCallback: (buffer, points) => { buffer.push(`${(points[0].x + points[2].x) / 2} ` + `${(points[0].y + points[2].y) / 2} m`, `${(points[1].x + points[3].x) / 2} ` + `${(points[1].y + points[3].y) / 2} l`, "S"); return [points[0].x, points[1].x, points[3].y, points[1].y]; } }); } } else { this.data.popupRef = null; } } } class StampAnnotation extends MarkupAnnotation { constructor(params) { super(params); this.data.annotationType = AnnotationType.STAMP; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; } static async createImage(bitmap, xref) { const { width, height } = bitmap; const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("2d", { alpha: true }); ctx.drawImage(bitmap, 0, 0); const data = ctx.getImageData(0, 0, width, height).data; const buf32 = new Uint32Array(data.buffer); const hasAlpha = buf32.some(FeatureTest.isLittleEndian ? x => x >>> 24 !== 0xff : x => (x & 0xff) !== 0xff); if (hasAlpha) { ctx.fillStyle = "white"; ctx.fillRect(0, 0, width, height); ctx.drawImage(bitmap, 0, 0); } const jpegBufferPromise = canvas.convertToBlob({ type: "image/jpeg", quality: 1 }).then(blob => { return blob.arrayBuffer(); }); const xobjectName = Name.get("XObject"); const imageName = Name.get("Image"); const image = new Dict(xref); image.set("Type", xobjectName); image.set("Subtype", imageName); image.set("BitsPerComponent", 8); image.set("ColorSpace", Name.get("DeviceRGB")); image.set("Filter", Name.get("DCTDecode")); image.set("BBox", [0, 0, width, height]); image.set("Width", width); image.set("Height", height); let smaskStream = null; if (hasAlpha) { const alphaBuffer = new Uint8Array(buf32.length); if (FeatureTest.isLittleEndian) { for (let i = 0, ii = buf32.length; i < ii; i++) { alphaBuffer[i] = buf32[i] >>> 24; } } else { for (let i = 0, ii = buf32.length; i < ii; i++) { alphaBuffer[i] = buf32[i] & 0xff; } } const smask = new Dict(xref); smask.set("Type", xobjectName); smask.set("Subtype", imageName); smask.set("BitsPerComponent", 8); smask.set("ColorSpace", Name.get("DeviceGray")); smask.set("Width", width); smask.set("Height", height); smaskStream = new Stream(alphaBuffer, 0, 0, smask); } const imageStream = new Stream(await jpegBufferPromise, 0, 0, image); return { imageStream, smaskStream, width, height }; } static createNewDict(annotation, xref, { apRef, ap }) { const { rect, rotation, user } = annotation; const stamp = new Dict(xref); stamp.set("Type", Name.get("Annot")); stamp.set("Subtype", Name.get("Stamp")); stamp.set("CreationDate", `D:${getModificationDate()}`); stamp.set("Rect", rect); stamp.set("F", 4); stamp.set("Border", [0, 0, 0]); stamp.set("Rotate", rotation); if (user) { stamp.set("T", isAscii(user) ? user : stringToUTF16String(user, true)); } if (apRef || ap) { const n = new Dict(xref); stamp.set("AP", n); if (apRef) { n.set("N", apRef); } else { n.set("N", ap); } } return stamp; } static async createNewAppearanceStream(annotation, xref, params) { const { rotation } = annotation; const { imageRef, width, height } = params.image; const resources = new Dict(xref); const xobject = new Dict(xref); resources.set("XObject", xobject); xobject.set("Im0", imageRef); const appearance = `q ${width} 0 0 ${height} 0 0 cm /Im0 Do Q`; const appearanceStreamDict = new Dict(xref); appearanceStreamDict.set("FormType", 1); appearanceStreamDict.set("Subtype", Name.get("Form")); appearanceStreamDict.set("Type", Name.get("XObject")); appearanceStreamDict.set("BBox", [0, 0, width, height]); appearanceStreamDict.set("Resources", resources); if (rotation) { const matrix = getRotationMatrix(rotation, width, height); appearanceStreamDict.set("Matrix", matrix); } const ap = new StringStream(appearance); ap.dict = appearanceStreamDict; return ap; } } class FileAttachmentAnnotation extends MarkupAnnotation { constructor(params) { super(params); const { dict, xref } = params; const file = new FileSpec(dict.get("FS"), xref); this.data.annotationType = AnnotationType.FILEATTACHMENT; this.data.hasOwnCanvas = this.data.noRotate; this.data.noHTML = false; this.data.file = file.serializable; const name = dict.get("Name"); this.data.name = name instanceof Name ? stringToPDFString(name.name) : "PushPin"; const fillAlpha = dict.get("ca"); this.data.fillAlpha = typeof fillAlpha === "number" && fillAlpha >= 0 && fillAlpha <= 1 ? fillAlpha : null; } } ;// CONCATENATED MODULE: ./src/core/dataset_reader.js function decodeString(str) { try { return stringToUTF8String(str); } catch (ex) { warn(`UTF-8 decoding failed: "${ex}".`); return str; } } class DatasetXMLParser extends SimpleXMLParser { constructor(options) { super(options); this.node = null; } onEndElement(name) { const node = super.onEndElement(name); if (node && name === "xfa:datasets") { this.node = node; throw new Error("Aborting DatasetXMLParser."); } } } class DatasetReader { constructor(data) { if (data.datasets) { this.node = new SimpleXMLParser({ hasAttributes: true }).parseFromString(data.datasets).documentElement; } else { const parser = new DatasetXMLParser({ hasAttributes: true }); try { parser.parseFromString(data["xdp:xdp"]); } catch {} this.node = parser.node; } } getValue(path) { if (!this.node || !path) { return ""; } const node = this.node.searchNode(parseXFAPath(path), 0); if (!node) { return ""; } const first = node.firstChild; if (first?.nodeName === "value") { return node.children.map(child => decodeString(child.textContent)); } return decodeString(node.textContent); } } ;// CONCATENATED MODULE: ./src/core/xref.js class XRef { #firstXRefStmPos = null; constructor(stream, pdfManager) { this.stream = stream; this.pdfManager = pdfManager; this.entries = []; this._xrefStms = new Set(); this._cacheMap = new Map(); this._pendingRefs = new RefSet(); this._newPersistentRefNum = null; this._newTemporaryRefNum = null; } getNewPersistentRef(obj) { if (this._newPersistentRefNum === null) { this._newPersistentRefNum = this.entries.length || 1; } const num = this._newPersistentRefNum++; this._cacheMap.set(num, obj); return Ref.get(num, 0); } getNewTemporaryRef() { if (this._newTemporaryRefNum === null) { this._newTemporaryRefNum = this.entries.length || 1; } return Ref.get(this._newTemporaryRefNum++, 0); } resetNewTemporaryRef() { this._newTemporaryRefNum = null; } setStartXRef(startXRef) { this.startXRefQueue = [startXRef]; } parse(recoveryMode = false) { let trailerDict; if (!recoveryMode) { trailerDict = this.readXRef(); } else { warn("Indexing all PDF objects"); trailerDict = this.indexObjects(); } trailerDict.assignXref(this); this.trailer = trailerDict; let encrypt; try { encrypt = trailerDict.get("Encrypt"); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`); } if (encrypt instanceof Dict) { const ids = trailerDict.get("ID"); const fileId = ids?.length ? ids[0] : ""; encrypt.suppressEncryption = true; this.encrypt = new CipherTransformFactory(encrypt, fileId, this.pdfManager.password); } let root; try { root = trailerDict.get("Root"); } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`XRef.parse - Invalid "Root" reference: "${ex}".`); } if (root instanceof Dict) { try { const pages = root.get("Pages"); if (pages instanceof Dict) { this.root = root; return; } } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`XRef.parse - Invalid "Pages" reference: "${ex}".`); } } if (!recoveryMode) { throw new XRefParseException(); } throw new InvalidPDFException("Invalid Root reference."); } processXRefTable(parser) { if (!("tableState" in this)) { this.tableState = { entryNum: 0, streamPos: parser.lexer.stream.pos, parserBuf1: parser.buf1, parserBuf2: parser.buf2 }; } const obj = this.readXRefTable(parser); if (!isCmd(obj, "trailer")) { throw new FormatError("Invalid XRef table: could not find trailer dictionary"); } let dict = parser.getObj(); if (!(dict instanceof Dict) && dict.dict) { dict = dict.dict; } if (!(dict instanceof Dict)) { throw new FormatError("Invalid XRef table: could not parse trailer dictionary"); } delete this.tableState; return dict; } readXRefTable(parser) { const stream = parser.lexer.stream; const tableState = this.tableState; stream.pos = tableState.streamPos; parser.buf1 = tableState.parserBuf1; parser.buf2 = tableState.parserBuf2; let obj; while (true) { if (!("firstEntryNum" in tableState) || !("entryCount" in tableState)) { if (isCmd(obj = parser.getObj(), "trailer")) { break; } tableState.firstEntryNum = obj; tableState.entryCount = parser.getObj(); } let first = tableState.firstEntryNum; const count = tableState.entryCount; if (!Number.isInteger(first) || !Number.isInteger(count)) { throw new FormatError("Invalid XRef table: wrong types in subsection header"); } for (let i = tableState.entryNum; i < count; i++) { tableState.streamPos = stream.pos; tableState.entryNum = i; tableState.parserBuf1 = parser.buf1; tableState.parserBuf2 = parser.buf2; const entry = {}; entry.offset = parser.getObj(); entry.gen = parser.getObj(); const type = parser.getObj(); if (type instanceof Cmd) { switch (type.cmd) { case "f": entry.free = true; break; case "n": entry.uncompressed = true; break; } } if (!Number.isInteger(entry.offset) || !Number.isInteger(entry.gen) || !(entry.free || entry.uncompressed)) { throw new FormatError(`Invalid entry in XRef subsection: ${first}, ${count}`); } if (i === 0 && entry.free && first === 1) { first = 0; } if (!this.entries[i + first]) { this.entries[i + first] = entry; } } tableState.entryNum = 0; tableState.streamPos = stream.pos; tableState.parserBuf1 = parser.buf1; tableState.parserBuf2 = parser.buf2; delete tableState.firstEntryNum; delete tableState.entryCount; } if (this.entries[0] && !this.entries[0].free) { throw new FormatError("Invalid XRef table: unexpected first object"); } return obj; } processXRefStream(stream) { if (!("streamState" in this)) { const streamParameters = stream.dict; const byteWidths = streamParameters.get("W"); let range = streamParameters.get("Index"); if (!range) { range = [0, streamParameters.get("Size")]; } this.streamState = { entryRanges: range, byteWidths, entryNum: 0, streamPos: stream.pos }; } this.readXRefStream(stream); delete this.streamState; return stream.dict; } readXRefStream(stream) { const streamState = this.streamState; stream.pos = streamState.streamPos; const [typeFieldWidth, offsetFieldWidth, generationFieldWidth] = streamState.byteWidths; const entryRanges = streamState.entryRanges; while (entryRanges.length > 0) { const [first, n] = entryRanges; if (!Number.isInteger(first) || !Number.isInteger(n)) { throw new FormatError(`Invalid XRef range fields: ${first}, ${n}`); } if (!Number.isInteger(typeFieldWidth) || !Number.isInteger(offsetFieldWidth) || !Number.isInteger(generationFieldWidth)) { throw new FormatError(`Invalid XRef entry fields length: ${first}, ${n}`); } for (let i = streamState.entryNum; i < n; ++i) { streamState.entryNum = i; streamState.streamPos = stream.pos; let type = 0, offset = 0, generation = 0; for (let j = 0; j < typeFieldWidth; ++j) { const typeByte = stream.getByte(); if (typeByte === -1) { throw new FormatError("Invalid XRef byteWidths 'type'."); } type = type << 8 | typeByte; } if (typeFieldWidth === 0) { type = 1; } for (let j = 0; j < offsetFieldWidth; ++j) { const offsetByte = stream.getByte(); if (offsetByte === -1) { throw new FormatError("Invalid XRef byteWidths 'offset'."); } offset = offset << 8 | offsetByte; } for (let j = 0; j < generationFieldWidth; ++j) { const generationByte = stream.getByte(); if (generationByte === -1) { throw new FormatError("Invalid XRef byteWidths 'generation'."); } generation = generation << 8 | generationByte; } const entry = {}; entry.offset = offset; entry.gen = generation; switch (type) { case 0: entry.free = true; break; case 1: entry.uncompressed = true; break; case 2: break; default: throw new FormatError(`Invalid XRef entry type: ${type}`); } if (!this.entries[first + i]) { this.entries[first + i] = entry; } } streamState.entryNum = 0; streamState.streamPos = stream.pos; entryRanges.splice(0, 2); } } indexObjects() { const TAB = 0x9, LF = 0xa, CR = 0xd, SPACE = 0x20; const PERCENT = 0x25, LT = 0x3c; function readToken(data, offset) { let token = "", ch = data[offset]; while (ch !== LF && ch !== CR && ch !== LT) { if (++offset >= data.length) { break; } token += String.fromCharCode(ch); ch = data[offset]; } return token; } function skipUntil(data, offset, what) { const length = what.length, dataLength = data.length; let skipped = 0; while (offset < dataLength) { let i = 0; while (i < length && data[offset + i] === what[i]) { ++i; } if (i >= length) { break; } offset++; skipped++; } return skipped; } const gEndobjRegExp = /\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g; const gStartxrefRegExp = /\b(startxref|\d+\s+\d+\s+obj)\b/g; const objRegExp = /^(\d+)\s+(\d+)\s+obj\b/; const trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]); const startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]); const xrefBytes = new Uint8Array([47, 88, 82, 101, 102]); this.entries.length = 0; this._cacheMap.clear(); const stream = this.stream; stream.pos = 0; const buffer = stream.getBytes(), bufferStr = bytesToString(buffer), length = buffer.length; let position = stream.start; const trailers = [], xrefStms = []; while (position < length) { let ch = buffer[position]; if (ch === TAB || ch === LF || ch === CR || ch === SPACE) { ++position; continue; } if (ch === PERCENT) { do { ++position; if (position >= length) { break; } ch = buffer[position]; } while (ch !== LF && ch !== CR); continue; } const token = readToken(buffer, position); let m; if (token.startsWith("xref") && (token.length === 4 || /\s/.test(token[4]))) { position += skipUntil(buffer, position, trailerBytes); trailers.push(position); position += skipUntil(buffer, position, startxrefBytes); } else if (m = objRegExp.exec(token)) { const num = m[1] | 0, gen = m[2] | 0; const startPos = position + token.length; let contentLength, updateEntries = false; if (!this.entries[num]) { updateEntries = true; } else if (this.entries[num].gen === gen) { try { const parser = new Parser({ lexer: new Lexer(stream.makeSubStream(startPos)) }); parser.getObj(); updateEntries = true; } catch (ex) { if (ex instanceof ParserEOFException) { warn(`indexObjects -- checking object (${token}): "${ex}".`); } else { updateEntries = true; } } } if (updateEntries) { this.entries[num] = { offset: position - stream.start, gen, uncompressed: true }; } gEndobjRegExp.lastIndex = startPos; const match = gEndobjRegExp.exec(bufferStr); if (match) { const endPos = gEndobjRegExp.lastIndex + 1; contentLength = endPos - position; if (match[1] !== "endobj") { warn(`indexObjects: Found "${match[1]}" inside of another "obj", ` + 'caused by missing "endobj" -- trying to recover.'); contentLength -= match[1].length + 1; } } else { contentLength = length - position; } const content = buffer.subarray(position, position + contentLength); const xrefTagOffset = skipUntil(content, 0, xrefBytes); if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) { xrefStms.push(position - stream.start); this._xrefStms.add(position - stream.start); } position += contentLength; } else if (token.startsWith("trailer") && (token.length === 7 || /\s/.test(token[7]))) { trailers.push(position); const startPos = position + token.length; let contentLength; gStartxrefRegExp.lastIndex = startPos; const match = gStartxrefRegExp.exec(bufferStr); if (match) { const endPos = gStartxrefRegExp.lastIndex + 1; contentLength = endPos - position; if (match[1] !== "startxref") { warn(`indexObjects: Found "${match[1]}" after "trailer", ` + 'caused by missing "startxref" -- trying to recover.'); contentLength -= match[1].length + 1; } } else { contentLength = length - position; } position += contentLength; } else { position += token.length + 1; } } for (const xrefStm of xrefStms) { this.startXRefQueue.push(xrefStm); this.readXRef(true); } const trailerDicts = []; let isEncrypted = false; for (const trailer of trailers) { stream.pos = trailer; const parser = new Parser({ lexer: new Lexer(stream), xref: this, allowStreams: true, recoveryMode: true }); const obj = parser.getObj(); if (!isCmd(obj, "trailer")) { continue; } const dict = parser.getObj(); if (!(dict instanceof Dict)) { continue; } trailerDicts.push(dict); if (dict.has("Encrypt")) { isEncrypted = true; } } let trailerDict, trailerError; for (const dict of [...trailerDicts, "genFallback", ...trailerDicts]) { if (dict === "genFallback") { if (!trailerError) { break; } this._generationFallback = true; continue; } let validPagesDict = false; try { const rootDict = dict.get("Root"); if (!(rootDict instanceof Dict)) { continue; } const pagesDict = rootDict.get("Pages"); if (!(pagesDict instanceof Dict)) { continue; } const pagesCount = pagesDict.get("Count"); if (Number.isInteger(pagesCount)) { validPagesDict = true; } } catch (ex) { trailerError = ex; continue; } if (validPagesDict && (!isEncrypted || dict.has("Encrypt")) && dict.has("ID")) { return dict; } trailerDict = dict; } if (trailerDict) { return trailerDict; } if (this.topDict) { return this.topDict; } throw new InvalidPDFException("Invalid PDF structure."); } readXRef(recoveryMode = false) { const stream = this.stream; const startXRefParsedCache = new Set(); while (this.startXRefQueue.length) { try { const startXRef = this.startXRefQueue[0]; if (startXRefParsedCache.has(startXRef)) { warn("readXRef - skipping XRef table since it was already parsed."); this.startXRefQueue.shift(); continue; } startXRefParsedCache.add(startXRef); stream.pos = startXRef + stream.start; const parser = new Parser({ lexer: new Lexer(stream), xref: this, allowStreams: true }); let obj = parser.getObj(); let dict; if (isCmd(obj, "xref")) { dict = this.processXRefTable(parser); if (!this.topDict) { this.topDict = dict; } obj = dict.get("XRefStm"); if (Number.isInteger(obj) && !this._xrefStms.has(obj)) { this._xrefStms.add(obj); this.startXRefQueue.push(obj); this.#firstXRefStmPos ??= obj; } } else if (Number.isInteger(obj)) { if (!Number.isInteger(parser.getObj()) || !isCmd(parser.getObj(), "obj") || !((obj = parser.getObj()) instanceof BaseStream)) { throw new FormatError("Invalid XRef stream"); } dict = this.processXRefStream(obj); if (!this.topDict) { this.topDict = dict; } if (!dict) { throw new FormatError("Failed to read XRef stream"); } } else { throw new FormatError("Invalid XRef stream header"); } obj = dict.get("Prev"); if (Number.isInteger(obj)) { this.startXRefQueue.push(obj); } else if (obj instanceof Ref) { this.startXRefQueue.push(obj.num); } } catch (e) { if (e instanceof MissingDataException) { throw e; } info("(while reading XRef): " + e); } this.startXRefQueue.shift(); } if (this.topDict) { return this.topDict; } if (recoveryMode) { return undefined; } throw new XRefParseException(); } get lastXRefStreamPos() { return this.#firstXRefStmPos ?? (this._xrefStms.size > 0 ? Math.max(...this._xrefStms) : null); } getEntry(i) { const xrefEntry = this.entries[i]; if (xrefEntry && !xrefEntry.free && xrefEntry.offset) { return xrefEntry; } return null; } fetchIfRef(obj, suppressEncryption = false) { if (obj instanceof Ref) { return this.fetch(obj, suppressEncryption); } return obj; } fetch(ref, suppressEncryption = false) { if (!(ref instanceof Ref)) { throw new Error("ref object is not a reference"); } const num = ref.num; const cacheEntry = this._cacheMap.get(num); if (cacheEntry !== undefined) { if (cacheEntry instanceof Dict && !cacheEntry.objId) { cacheEntry.objId = ref.toString(); } return cacheEntry; } let xrefEntry = this.getEntry(num); if (xrefEntry === null) { this._cacheMap.set(num, xrefEntry); return xrefEntry; } if (this._pendingRefs.has(ref)) { this._pendingRefs.remove(ref); warn(`Ignoring circular reference: ${ref}.`); return CIRCULAR_REF; } this._pendingRefs.put(ref); try { xrefEntry = xrefEntry.uncompressed ? this.fetchUncompressed(ref, xrefEntry, suppressEncryption) : this.fetchCompressed(ref, xrefEntry, suppressEncryption); this._pendingRefs.remove(ref); } catch (ex) { this._pendingRefs.remove(ref); throw ex; } if (xrefEntry instanceof Dict) { xrefEntry.objId = ref.toString(); } else if (xrefEntry instanceof BaseStream) { xrefEntry.dict.objId = ref.toString(); } return xrefEntry; } fetchUncompressed(ref, xrefEntry, suppressEncryption = false) { const gen = ref.gen; let num = ref.num; if (xrefEntry.gen !== gen) { const msg = `Inconsistent generation in XRef: ${ref}`; if (this._generationFallback && xrefEntry.gen < gen) { warn(msg); return this.fetchUncompressed(Ref.get(num, xrefEntry.gen), xrefEntry, suppressEncryption); } throw new XRefEntryException(msg); } const stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start); const parser = new Parser({ lexer: new Lexer(stream), xref: this, allowStreams: true }); const obj1 = parser.getObj(); const obj2 = parser.getObj(); const obj3 = parser.getObj(); if (obj1 !== num || obj2 !== gen || !(obj3 instanceof Cmd)) { throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`); } if (obj3.cmd !== "obj") { if (obj3.cmd.startsWith("obj")) { num = parseInt(obj3.cmd.substring(3), 10); if (!Number.isNaN(num)) { return num; } } throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`); } xrefEntry = this.encrypt && !suppressEncryption ? parser.getObj(this.encrypt.createCipherTransform(num, gen)) : parser.getObj(); if (!(xrefEntry instanceof BaseStream)) { this._cacheMap.set(num, xrefEntry); } return xrefEntry; } fetchCompressed(ref, xrefEntry, suppressEncryption = false) { const tableOffset = xrefEntry.offset; const stream = this.fetch(Ref.get(tableOffset, 0)); if (!(stream instanceof BaseStream)) { throw new FormatError("bad ObjStm stream"); } const first = stream.dict.get("First"); const n = stream.dict.get("N"); if (!Number.isInteger(first) || !Number.isInteger(n)) { throw new FormatError("invalid first and n parameters for ObjStm stream"); } let parser = new Parser({ lexer: new Lexer(stream), xref: this, allowStreams: true }); const nums = new Array(n); const offsets = new Array(n); for (let i = 0; i < n; ++i) { const num = parser.getObj(); if (!Number.isInteger(num)) { throw new FormatError(`invalid object number in the ObjStm stream: ${num}`); } const offset = parser.getObj(); if (!Number.isInteger(offset)) { throw new FormatError(`invalid object offset in the ObjStm stream: ${offset}`); } nums[i] = num; offsets[i] = offset; } const start = (stream.start || 0) + first; const entries = new Array(n); for (let i = 0; i < n; ++i) { const length = i < n - 1 ? offsets[i + 1] - offsets[i] : undefined; if (length < 0) { throw new FormatError("Invalid offset in the ObjStm stream."); } parser = new Parser({ lexer: new Lexer(stream.makeSubStream(start + offsets[i], length, stream.dict)), xref: this, allowStreams: true }); const obj = parser.getObj(); entries[i] = obj; if (obj instanceof BaseStream) { continue; } const num = nums[i], entry = this.entries[num]; if (entry && entry.offset === tableOffset && entry.gen === i) { this._cacheMap.set(num, obj); } } xrefEntry = entries[xrefEntry.gen]; if (xrefEntry === undefined) { throw new XRefEntryException(`Bad (compressed) XRef entry: ${ref}`); } return xrefEntry; } async fetchIfRefAsync(obj, suppressEncryption) { if (obj instanceof Ref) { return this.fetchAsync(obj, suppressEncryption); } return obj; } async fetchAsync(ref, suppressEncryption) { try { return this.fetch(ref, suppressEncryption); } catch (ex) { if (!(ex instanceof MissingDataException)) { throw ex; } await this.pdfManager.requestRange(ex.begin, ex.end); return this.fetchAsync(ref, suppressEncryption); } } getCatalogObj() { return this.root; } } ;// CONCATENATED MODULE: ./src/core/document.js const DEFAULT_USER_UNIT = 1.0; const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792]; class Page { constructor({ pdfManager, xref, pageIndex, pageDict, ref, globalIdFactory, fontCache, builtInCMapCache, standardFontDataCache, globalImageCache, systemFontCache, nonBlendModesSet, xfaFactory }) { this.pdfManager = pdfManager; this.pageIndex = pageIndex; this.pageDict = pageDict; this.xref = xref; this.ref = ref; this.fontCache = fontCache; this.builtInCMapCache = builtInCMapCache; this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; this.systemFontCache = systemFontCache; this.nonBlendModesSet = nonBlendModesSet; this.evaluatorOptions = pdfManager.evaluatorOptions; this.resourcesPromise = null; this.xfaFactory = xfaFactory; const idCounters = { obj: 0 }; this._localIdFactory = class extends globalIdFactory { static createObjId() { return `p${pageIndex}_${++idCounters.obj}`; } static getPageObjId() { return `p${ref.toString()}`; } }; } _getInheritableProperty(key, getArray = false) { const value = getInheritableProperty({ dict: this.pageDict, key, getArray, stopWhenFound: false }); if (!Array.isArray(value)) { return value; } if (value.length === 1 || !(value[0] instanceof Dict)) { return value[0]; } return Dict.merge({ xref: this.xref, dictArray: value }); } get content() { return this.pageDict.getArray("Contents"); } get resources() { const resources = this._getInheritableProperty("Resources"); return shadow(this, "resources", resources instanceof Dict ? resources : Dict.empty); } _getBoundingBox(name) { if (this.xfaData) { return this.xfaData.bbox; } let box = this._getInheritableProperty(name, true); if (Array.isArray(box) && box.length === 4) { box = Util.normalizeRect(box); if (box[2] - box[0] > 0 && box[3] - box[1] > 0) { return box; } warn(`Empty, or invalid, /${name} entry.`); } return null; } get mediaBox() { return shadow(this, "mediaBox", this._getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX); } get cropBox() { return shadow(this, "cropBox", this._getBoundingBox("CropBox") || this.mediaBox); } get userUnit() { let obj = this.pageDict.get("UserUnit"); if (typeof obj !== "number" || obj <= 0) { obj = DEFAULT_USER_UNIT; } return shadow(this, "userUnit", obj); } get view() { const { cropBox, mediaBox } = this; if (cropBox !== mediaBox && !isArrayEqual(cropBox, mediaBox)) { const box = Util.intersect(cropBox, mediaBox); if (box && box[2] - box[0] > 0 && box[3] - box[1] > 0) { return shadow(this, "view", box); } warn("Empty /CropBox and /MediaBox intersection."); } return shadow(this, "view", mediaBox); } get rotate() { let rotate = this._getInheritableProperty("Rotate") || 0; if (rotate % 90 !== 0) { rotate = 0; } else if (rotate >= 360) { rotate %= 360; } else if (rotate < 0) { rotate = (rotate % 360 + 360) % 360; } return shadow(this, "rotate", rotate); } _onSubStreamError(reason, objId) { if (this.evaluatorOptions.ignoreErrors) { warn(`getContentStream - ignoring sub-stream (${objId}): "${reason}".`); return; } throw reason; } getContentStream() { return this.pdfManager.ensure(this, "content").then(content => { if (content instanceof BaseStream) { return content; } if (Array.isArray(content)) { return new StreamsSequenceStream(content, this._onSubStreamError.bind(this)); } return new NullStream(); }); } get xfaData() { return shadow(this, "xfaData", this.xfaFactory ? { bbox: this.xfaFactory.getBoundingBox(this.pageIndex) } : null); } #replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) { for (const annotation of annotations) { if (annotation.id) { const ref = Ref.fromString(annotation.id); if (!ref) { warn(`A non-linked annotation cannot be modified: ${annotation.id}`); continue; } if (annotation.deleted) { deletedAnnotations.put(ref); continue; } existingAnnotations?.put(ref); annotation.ref = ref; delete annotation.id; } } } async saveNewAnnotations(handler, task, annotations, imagePromises) { if (this.xfaFactory) { throw new Error("XFA: Cannot save new annotations."); } const partialEvaluator = new PartialEvaluator({ xref: this.xref, handler, pageIndex: this.pageIndex, idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, systemFontCache: this.systemFontCache, options: this.evaluatorOptions }); const deletedAnnotations = new RefSet(); const existingAnnotations = new RefSet(); this.#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations); const pageDict = this.pageDict; const annotationsArray = this.annotations.filter(a => !(a instanceof Ref && deletedAnnotations.has(a))); const newData = await AnnotationFactory.saveNewAnnotations(partialEvaluator, task, annotations, imagePromises); for (const { ref } of newData.annotations) { if (ref instanceof Ref && !existingAnnotations.has(ref)) { annotationsArray.push(ref); } } const savedDict = pageDict.get("Annots"); pageDict.set("Annots", annotationsArray); const buffer = []; await writeObject(this.ref, pageDict, buffer, this.xref); if (savedDict) { pageDict.set("Annots", savedDict); } const objects = newData.dependencies; objects.push({ ref: this.ref, data: buffer.join("") }, ...newData.annotations); return objects; } save(handler, task, annotationStorage) { const partialEvaluator = new PartialEvaluator({ xref: this.xref, handler, pageIndex: this.pageIndex, idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, systemFontCache: this.systemFontCache, options: this.evaluatorOptions }); return this._parsedAnnotations.then(function (annotations) { const newRefsPromises = []; for (const annotation of annotations) { if (!annotation.mustBePrinted(annotationStorage)) { continue; } newRefsPromises.push(annotation.save(partialEvaluator, task, annotationStorage).catch(function (reason) { warn("save - ignoring annotation data during " + `"${task.name}" task: "${reason}".`); return null; })); } return Promise.all(newRefsPromises).then(function (newRefs) { return newRefs.filter(newRef => !!newRef); }); }); } loadResources(keys) { if (!this.resourcesPromise) { this.resourcesPromise = this.pdfManager.ensure(this, "resources"); } return this.resourcesPromise.then(() => { const objectLoader = new ObjectLoader(this.resources, keys, this.xref); return objectLoader.load(); }); } getOperatorList({ handler, sink, task, intent, cacheKey, annotationStorage = null }) { const contentStreamPromise = this.getContentStream(); const resourcesPromise = this.loadResources(["ColorSpace", "ExtGState", "Font", "Pattern", "Properties", "Shading", "XObject"]); const partialEvaluator = new PartialEvaluator({ xref: this.xref, handler, pageIndex: this.pageIndex, idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, systemFontCache: this.systemFontCache, options: this.evaluatorOptions }); const newAnnotationsByPage = !this.xfaFactory ? getNewAnnotationsMap(annotationStorage) : null; let deletedAnnotations = null; let newAnnotationsPromise = Promise.resolve(null); if (newAnnotationsByPage) { const newAnnotations = newAnnotationsByPage.get(this.pageIndex); if (newAnnotations) { const annotationGlobalsPromise = this.pdfManager.ensureDoc("annotationGlobals"); let imagePromises; const missingBitmaps = new Set(); for (const { bitmapId, bitmap } of newAnnotations) { if (bitmapId && !bitmap && !missingBitmaps.has(bitmapId)) { missingBitmaps.add(bitmapId); } } const { isOffscreenCanvasSupported } = this.evaluatorOptions; if (missingBitmaps.size > 0) { const annotationWithBitmaps = newAnnotations.slice(); for (const [key, annotation] of annotationStorage) { if (!key.startsWith(AnnotationEditorPrefix)) { continue; } if (annotation.bitmap && missingBitmaps.has(annotation.bitmapId)) { annotationWithBitmaps.push(annotation); } } imagePromises = AnnotationFactory.generateImages(annotationWithBitmaps, this.xref, isOffscreenCanvasSupported); } else { imagePromises = AnnotationFactory.generateImages(newAnnotations, this.xref, isOffscreenCanvasSupported); } deletedAnnotations = new RefSet(); this.#replaceIdByRef(newAnnotations, deletedAnnotations, null); newAnnotationsPromise = annotationGlobalsPromise.then(annotationGlobals => { if (!annotationGlobals) { return null; } return AnnotationFactory.printNewAnnotations(annotationGlobals, partialEvaluator, task, newAnnotations, imagePromises); }); } } const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); const pageListPromise = dataPromises.then(([contentStream]) => { const opList = new OperatorList(intent, sink); handler.send("StartRenderPage", { transparency: partialEvaluator.hasBlendModes(this.resources, this.nonBlendModesSet), pageIndex: this.pageIndex, cacheKey }); return partialEvaluator.getOperatorList({ stream: contentStream, task, resources: this.resources, operatorList: opList }).then(function () { return opList; }); }); return Promise.all([pageListPromise, this._parsedAnnotations, newAnnotationsPromise]).then(function ([pageOpList, annotations, newAnnotations]) { if (newAnnotations) { annotations = annotations.filter(a => !(a.ref && deletedAnnotations.has(a.ref))); for (let i = 0, ii = newAnnotations.length; i < ii; i++) { const newAnnotation = newAnnotations[i]; if (newAnnotation.refToReplace) { const j = annotations.findIndex(a => a.ref && isRefsEqual(a.ref, newAnnotation.refToReplace)); if (j >= 0) { annotations.splice(j, 1, newAnnotation); newAnnotations.splice(i--, 1); ii--; } } } annotations = annotations.concat(newAnnotations); } if (annotations.length === 0 || intent & RenderingIntentFlag.ANNOTATIONS_DISABLE) { pageOpList.flush(true); return { length: pageOpList.totalLength }; } const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS), intentAny = !!(intent & RenderingIntentFlag.ANY), intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY), intentPrint = !!(intent & RenderingIntentFlag.PRINT); const opListPromises = []; for (const annotation of annotations) { if (intentAny || intentDisplay && annotation.mustBeViewed(annotationStorage, renderForms) || intentPrint && annotation.mustBePrinted(annotationStorage)) { opListPromises.push(annotation.getOperatorList(partialEvaluator, task, intent, renderForms, annotationStorage).catch(function (reason) { warn("getOperatorList - ignoring annotation data during " + `"${task.name}" task: "${reason}".`); return { opList: null, separateForm: false, separateCanvas: false }; })); } } return Promise.all(opListPromises).then(function (opLists) { let form = false, canvas = false; for (const { opList, separateForm, separateCanvas } of opLists) { pageOpList.addOpList(opList); form ||= separateForm; canvas ||= separateCanvas; } pageOpList.flush(true, { form, canvas }); return { length: pageOpList.totalLength }; }); }); } extractTextContent({ handler, task, includeMarkedContent, disableNormalization, sink }) { const contentStreamPromise = this.getContentStream(); const resourcesPromise = this.loadResources(["ExtGState", "Font", "Properties", "XObject"]); const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]); return dataPromises.then(([contentStream]) => { const partialEvaluator = new PartialEvaluator({ xref: this.xref, handler, pageIndex: this.pageIndex, idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, systemFontCache: this.systemFontCache, options: this.evaluatorOptions }); return partialEvaluator.getTextContent({ stream: contentStream, task, resources: this.resources, includeMarkedContent, disableNormalization, sink, viewBox: this.view }); }); } async getStructTree() { const structTreeRoot = await this.pdfManager.ensureCatalog("structTreeRoot"); if (!structTreeRoot) { return null; } await this._parsedAnnotations; const structTree = await this.pdfManager.ensure(this, "_parseStructTree", [structTreeRoot]); return structTree.serializable; } _parseStructTree(structTreeRoot) { const tree = new StructTreePage(structTreeRoot, this.pageDict); tree.parse(this.ref); return tree; } async getAnnotationsData(handler, task, intent) { const annotations = await this._parsedAnnotations; if (annotations.length === 0) { return annotations; } const annotationsData = [], textContentPromises = []; let partialEvaluator; const intentAny = !!(intent & RenderingIntentFlag.ANY), intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY), intentPrint = !!(intent & RenderingIntentFlag.PRINT); for (const annotation of annotations) { const isVisible = intentAny || intentDisplay && annotation.viewable; if (isVisible || intentPrint && annotation.printable) { annotationsData.push(annotation.data); } if (annotation.hasTextContent && isVisible) { partialEvaluator ||= new PartialEvaluator({ xref: this.xref, handler, pageIndex: this.pageIndex, idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, systemFontCache: this.systemFontCache, options: this.evaluatorOptions }); textContentPromises.push(annotation.extractTextContent(partialEvaluator, task, [-Infinity, -Infinity, Infinity, Infinity]).catch(function (reason) { warn(`getAnnotationsData - ignoring textContent during "${task.name}" task: "${reason}".`); })); } } await Promise.all(textContentPromises); return annotationsData; } get annotations() { const annots = this._getInheritableProperty("Annots"); return shadow(this, "annotations", Array.isArray(annots) ? annots : []); } get _parsedAnnotations() { const promise = this.pdfManager.ensure(this, "annotations").then(async annots => { if (annots.length === 0) { return annots; } const annotationGlobals = await this.pdfManager.ensureDoc("annotationGlobals"); if (!annotationGlobals) { return []; } const annotationPromises = []; for (const annotationRef of annots) { annotationPromises.push(AnnotationFactory.create(this.xref, annotationRef, annotationGlobals, this._localIdFactory, false, this.ref).catch(function (reason) { warn(`_parsedAnnotations: "${reason}".`); return null; })); } const sortedAnnotations = []; let popupAnnotations; for (const annotation of await Promise.all(annotationPromises)) { if (!annotation) { continue; } if (annotation instanceof PopupAnnotation) { (popupAnnotations ||= []).push(annotation); continue; } sortedAnnotations.push(annotation); } if (popupAnnotations) { sortedAnnotations.push(...popupAnnotations); } return sortedAnnotations; }); return shadow(this, "_parsedAnnotations", promise); } get jsActions() { const actions = collectActions(this.xref, this.pageDict, PageActionEventType); return shadow(this, "jsActions", actions); } } const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]); const STARTXREF_SIGNATURE = new Uint8Array([0x73, 0x74, 0x61, 0x72, 0x74, 0x78, 0x72, 0x65, 0x66]); const ENDOBJ_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]); const FINGERPRINT_FIRST_BYTES = 1024; const EMPTY_FINGERPRINT = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; function find(stream, signature, limit = 1024, backwards = false) { const signatureLength = signature.length; const scanBytes = stream.peekBytes(limit); const scanLength = scanBytes.length - signatureLength; if (scanLength <= 0) { return false; } if (backwards) { const signatureEnd = signatureLength - 1; let pos = scanBytes.length - 1; while (pos >= signatureEnd) { let j = 0; while (j < signatureLength && scanBytes[pos - j] === signature[signatureEnd - j]) { j++; } if (j >= signatureLength) { stream.pos += pos - signatureEnd; return true; } pos--; } } else { let pos = 0; while (pos <= scanLength) { let j = 0; while (j < signatureLength && scanBytes[pos + j] === signature[j]) { j++; } if (j >= signatureLength) { stream.pos += pos; return true; } pos++; } } return false; } class PDFDocument { constructor(pdfManager, stream) { if (stream.length <= 0) { throw new InvalidPDFException("The PDF file is empty, i.e. its size is zero bytes."); } this.pdfManager = pdfManager; this.stream = stream; this.xref = new XRef(stream, pdfManager); this._pagePromises = new Map(); this._version = null; const idCounters = { font: 0 }; this._globalIdFactory = class { static getDocId() { return `g_${pdfManager.docId}`; } static createFontId() { return `f${++idCounters.font}`; } static createObjId() { unreachable("Abstract method `createObjId` called."); } static getPageObjId() { unreachable("Abstract method `getPageObjId` called."); } }; } parse(recoveryMode) { this.xref.parse(recoveryMode); this.catalog = new Catalog(this.pdfManager, this.xref); } get linearization() { let linearization = null; try { linearization = Linearization.create(this.stream); } catch (err) { if (err instanceof MissingDataException) { throw err; } info(err); } return shadow(this, "linearization", linearization); } get startXRef() { const stream = this.stream; let startXRef = 0; if (this.linearization) { stream.reset(); if (find(stream, ENDOBJ_SIGNATURE)) { startXRef = stream.pos + 6 - stream.start; } } else { const step = 1024; const startXRefLength = STARTXREF_SIGNATURE.length; let found = false, pos = stream.end; while (!found && pos > 0) { pos -= step - startXRefLength; if (pos < 0) { pos = 0; } stream.pos = pos; found = find(stream, STARTXREF_SIGNATURE, step, true); } if (found) { stream.skip(9); let ch; do { ch = stream.getByte(); } while (isWhiteSpace(ch)); let str = ""; while (ch >= 0x20 && ch <= 0x39) { str += String.fromCharCode(ch); ch = stream.getByte(); } startXRef = parseInt(str, 10); if (isNaN(startXRef)) { startXRef = 0; } } } return shadow(this, "startXRef", startXRef); } checkHeader() { const stream = this.stream; stream.reset(); if (!find(stream, PDF_HEADER_SIGNATURE)) { return; } stream.moveStart(); stream.skip(PDF_HEADER_SIGNATURE.length); let version = "", ch; while ((ch = stream.getByte()) > 0x20 && version.length < 7) { version += String.fromCharCode(ch); } if (PDF_VERSION_REGEXP.test(version)) { this._version = version; } else { warn(`Invalid PDF header version: ${version}`); } } parseStartXRef() { this.xref.setStartXRef(this.startXRef); } get numPages() { let num = 0; if (this.catalog.hasActualNumPages) { num = this.catalog.numPages; } else if (this.xfaFactory) { num = this.xfaFactory.getNumPages(); } else if (this.linearization) { num = this.linearization.numPages; } else { num = this.catalog.numPages; } return shadow(this, "numPages", num); } _hasOnlyDocumentSignatures(fields, recursionDepth = 0) { const RECURSION_LIMIT = 10; if (!Array.isArray(fields)) { return false; } return fields.every(field => { field = this.xref.fetchIfRef(field); if (!(field instanceof Dict)) { return false; } if (field.has("Kids")) { if (++recursionDepth > RECURSION_LIMIT) { warn("_hasOnlyDocumentSignatures: maximum recursion depth reached"); return false; } return this._hasOnlyDocumentSignatures(field.get("Kids"), recursionDepth); } const isSignature = isName(field.get("FT"), "Sig"); const rectangle = field.get("Rect"); const isInvisible = Array.isArray(rectangle) && rectangle.every(value => value === 0); return isSignature && isInvisible; }); } get _xfaStreams() { const acroForm = this.catalog.acroForm; if (!acroForm) { return null; } const xfa = acroForm.get("XFA"); const entries = { "xdp:xdp": "", template: "", datasets: "", config: "", connectionSet: "", localeSet: "", stylesheet: "", "/xdp:xdp": "" }; if (xfa instanceof BaseStream && !xfa.isEmpty) { entries["xdp:xdp"] = xfa; return entries; } if (!Array.isArray(xfa) || xfa.length === 0) { return null; } for (let i = 0, ii = xfa.length; i < ii; i += 2) { let name; if (i === 0) { name = "xdp:xdp"; } else if (i === ii - 2) { name = "/xdp:xdp"; } else { name = xfa[i]; } if (!entries.hasOwnProperty(name)) { continue; } const data = this.xref.fetchIfRef(xfa[i + 1]); if (!(data instanceof BaseStream) || data.isEmpty) { continue; } entries[name] = data; } return entries; } get xfaDatasets() { const streams = this._xfaStreams; if (!streams) { return shadow(this, "xfaDatasets", null); } for (const key of ["datasets", "xdp:xdp"]) { const stream = streams[key]; if (!stream) { continue; } try { const str = stringToUTF8String(stream.getString()); const data = { [key]: str }; return shadow(this, "xfaDatasets", new DatasetReader(data)); } catch { warn("XFA - Invalid utf-8 string."); break; } } return shadow(this, "xfaDatasets", null); } get xfaData() { const streams = this._xfaStreams; if (!streams) { return null; } const data = Object.create(null); for (const [key, stream] of Object.entries(streams)) { if (!stream) { continue; } try { data[key] = stringToUTF8String(stream.getString()); } catch { warn("XFA - Invalid utf-8 string."); return null; } } return data; } get xfaFactory() { let data; if (this.pdfManager.enableXfa && this.catalog.needsRendering && this.formInfo.hasXfa && !this.formInfo.hasAcroForm) { data = this.xfaData; } return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null); } get isPureXfa() { return this.xfaFactory ? this.xfaFactory.isValid() : false; } get htmlForXfa() { return this.xfaFactory ? this.xfaFactory.getPages() : null; } async loadXfaImages() { const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages"); if (!xfaImagesDict) { return; } const keys = xfaImagesDict.getKeys(); const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref); await objectLoader.load(); const xfaImages = new Map(); for (const key of keys) { const stream = xfaImagesDict.get(key); if (stream instanceof BaseStream) { xfaImages.set(key, stream.getBytes()); } } this.xfaFactory.setImages(xfaImages); } async loadXfaFonts(handler, task) { const acroForm = await this.pdfManager.ensureCatalog("acroForm"); if (!acroForm) { return; } const resources = await acroForm.getAsync("DR"); if (!(resources instanceof Dict)) { return; } const objectLoader = new ObjectLoader(resources, ["Font"], this.xref); await objectLoader.load(); const fontRes = resources.get("Font"); if (!(fontRes instanceof Dict)) { return; } const options = Object.assign(Object.create(null), this.pdfManager.evaluatorOptions); options.useSystemFonts = false; const partialEvaluator = new PartialEvaluator({ xref: this.xref, handler, pageIndex: -1, idFactory: this._globalIdFactory, fontCache: this.catalog.fontCache, builtInCMapCache: this.catalog.builtInCMapCache, standardFontDataCache: this.catalog.standardFontDataCache, options }); const operatorList = new OperatorList(); const pdfFonts = []; const initialState = { get font() { return pdfFonts.at(-1); }, set font(font) { pdfFonts.push(font); }, clone() { return this; } }; const fonts = new Map(); fontRes.forEach((fontName, font) => { fonts.set(fontName, font); }); const promises = []; for (const [fontName, font] of fonts) { const descriptor = font.get("FontDescriptor"); if (!(descriptor instanceof Dict)) { continue; } let fontFamily = descriptor.get("FontFamily"); fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1"); const fontWeight = descriptor.get("FontWeight"); const italicAngle = -descriptor.get("ItalicAngle"); const cssFontInfo = { fontFamily, fontWeight, italicAngle }; if (!validateCSSFont(cssFontInfo)) { continue; } promises.push(partialEvaluator.handleSetFont(resources, [Name.get(fontName), 1], null, operatorList, task, initialState, null, cssFontInfo).catch(function (reason) { warn(`loadXfaFonts: "${reason}".`); return null; })); } await Promise.all(promises); const missingFonts = this.xfaFactory.setFonts(pdfFonts); if (!missingFonts) { return; } options.ignoreErrors = true; promises.length = 0; pdfFonts.length = 0; const reallyMissingFonts = new Set(); for (const missing of missingFonts) { if (!getXfaFontName(`${missing}-Regular`)) { reallyMissingFonts.add(missing); } } if (reallyMissingFonts.size) { missingFonts.push("PdfJS-Fallback"); } for (const missing of missingFonts) { if (reallyMissingFonts.has(missing)) { continue; } for (const fontInfo of [{ name: "Regular", fontWeight: 400, italicAngle: 0 }, { name: "Bold", fontWeight: 700, italicAngle: 0 }, { name: "Italic", fontWeight: 400, italicAngle: 12 }, { name: "BoldItalic", fontWeight: 700, italicAngle: 12 }]) { const name = `${missing}-${fontInfo.name}`; const dict = getXfaFontDict(name); promises.push(partialEvaluator.handleSetFont(resources, [Name.get(name), 1], null, operatorList, task, initialState, dict, { fontFamily: missing, fontWeight: fontInfo.fontWeight, italicAngle: fontInfo.italicAngle }).catch(function (reason) { warn(`loadXfaFonts: "${reason}".`); return null; })); } } await Promise.all(promises); this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts); } async serializeXfaData(annotationStorage) { return this.xfaFactory ? this.xfaFactory.serializeData(annotationStorage) : null; } get version() { return this.catalog.version || this._version; } get formInfo() { const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false, hasSignatures: false }; const acroForm = this.catalog.acroForm; if (!acroForm) { return shadow(this, "formInfo", formInfo); } try { const fields = acroForm.get("Fields"); const hasFields = Array.isArray(fields) && fields.length > 0; formInfo.hasFields = hasFields; const xfa = acroForm.get("XFA"); formInfo.hasXfa = Array.isArray(xfa) && xfa.length > 0 || xfa instanceof BaseStream && !xfa.isEmpty; const sigFlags = acroForm.get("SigFlags"); const hasSignatures = !!(sigFlags & 0x1); const hasOnlyDocumentSignatures = hasSignatures && this._hasOnlyDocumentSignatures(fields); formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures; formInfo.hasSignatures = hasSignatures; } catch (ex) { if (ex instanceof MissingDataException) { throw ex; } warn(`Cannot fetch form information: "${ex}".`); } return shadow(this, "formInfo", formInfo); } get documentInfo() { const docInfo = { PDFFormatVersion: this.version, Language: this.catalog.lang, EncryptFilterName: this.xref.encrypt ? this.xref.encrypt.filterName : null, IsLinearized: !!this.linearization, IsAcroFormPresent: this.formInfo.hasAcroForm, IsXFAPresent: this.formInfo.hasXfa, IsCollectionPresent: !!this.catalog.collection, IsSignaturesPresent: this.formInfo.hasSignatures }; let infoDict; try { infoDict = this.xref.trailer.get("Info"); } catch (err) { if (err instanceof MissingDataException) { throw err; } info("The document information dictionary is invalid."); } if (!(infoDict instanceof Dict)) { return shadow(this, "documentInfo", docInfo); } for (const key of infoDict.getKeys()) { const value = infoDict.get(key); switch (key) { case "Title": case "Author": case "Subject": case "Keywords": case "Creator": case "Producer": case "CreationDate": case "ModDate": if (typeof value === "string") { docInfo[key] = stringToPDFString(value); continue; } break; case "Trapped": if (value instanceof Name) { docInfo[key] = value; continue; } break; default: let customValue; switch (typeof value) { case "string": customValue = stringToPDFString(value); break; case "number": case "boolean": customValue = value; break; default: if (value instanceof Name) { customValue = value; } break; } if (customValue === undefined) { warn(`Bad value, for custom key "${key}", in Info: ${value}.`); continue; } if (!docInfo.Custom) { docInfo.Custom = Object.create(null); } docInfo.Custom[key] = customValue; continue; } warn(`Bad value, for key "${key}", in Info: ${value}.`); } return shadow(this, "documentInfo", docInfo); } get fingerprints() { function validate(data) { return typeof data === "string" && data.length > 0 && data !== EMPTY_FINGERPRINT; } function hexString(hash) { const buf = []; for (const num of hash) { const hex = num.toString(16); buf.push(hex.padStart(2, "0")); } return buf.join(""); } const idArray = this.xref.trailer.get("ID"); let hashOriginal, hashModified; if (Array.isArray(idArray) && validate(idArray[0])) { hashOriginal = stringToBytes(idArray[0]); if (idArray[1] !== idArray[0] && validate(idArray[1])) { hashModified = stringToBytes(idArray[1]); } } else { hashOriginal = calculateMD5(this.stream.getByteRange(0, FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES); } return shadow(this, "fingerprints", [hexString(hashOriginal), hashModified ? hexString(hashModified) : null]); } async _getLinearizationPage(pageIndex) { const { catalog, linearization, xref } = this; const ref = Ref.get(linearization.objectNumberFirst, 0); try { const obj = await xref.fetchAsync(ref); if (obj instanceof Dict) { let type = obj.getRaw("Type"); if (type instanceof Ref) { type = await xref.fetchAsync(type); } if (isName(type, "Page") || !obj.has("Type") && !obj.has("Kids")) { if (!catalog.pageKidsCountCache.has(ref)) { catalog.pageKidsCountCache.put(ref, 1); } if (!catalog.pageIndexCache.has(ref)) { catalog.pageIndexCache.put(ref, 0); } return [obj, ref]; } } throw new FormatError("The Linearization dictionary doesn't point to a valid Page dictionary."); } catch (reason) { warn(`_getLinearizationPage: "${reason.message}".`); return catalog.getPageDict(pageIndex); } } getPage(pageIndex) { const cachedPromise = this._pagePromises.get(pageIndex); if (cachedPromise) { return cachedPromise; } const { catalog, linearization, xfaFactory } = this; let promise; if (xfaFactory) { promise = Promise.resolve([Dict.empty, null]); } else if (linearization?.pageFirst === pageIndex) { promise = this._getLinearizationPage(pageIndex); } else { promise = catalog.getPageDict(pageIndex); } promise = promise.then(([pageDict, ref]) => { return new Page({ pdfManager: this.pdfManager, xref: this.xref, pageIndex, pageDict, ref, globalIdFactory: this._globalIdFactory, fontCache: catalog.fontCache, builtInCMapCache: catalog.builtInCMapCache, standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, systemFontCache: catalog.systemFontCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory }); }); this._pagePromises.set(pageIndex, promise); return promise; } async checkFirstPage(recoveryMode = false) { if (recoveryMode) { return; } try { await this.getPage(0); } catch (reason) { if (reason instanceof XRefEntryException) { this._pagePromises.delete(0); await this.cleanup(); throw new XRefParseException(); } } } async checkLastPage(recoveryMode = false) { const { catalog, pdfManager } = this; catalog.setActualNumPages(); let numPages; try { await Promise.all([pdfManager.ensureDoc("xfaFactory"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("numPages")]); if (this.xfaFactory) { return; } else if (this.linearization) { numPages = this.linearization.numPages; } else { numPages = catalog.numPages; } if (!Number.isInteger(numPages)) { throw new FormatError("Page count is not an integer."); } else if (numPages <= 1) { return; } await this.getPage(numPages - 1); } catch (reason) { this._pagePromises.delete(numPages - 1); await this.cleanup(); if (reason instanceof XRefEntryException && !recoveryMode) { throw new XRefParseException(); } warn(`checkLastPage - invalid /Pages tree /Count: ${numPages}.`); let pagesTree; try { pagesTree = await catalog.getAllPageDicts(recoveryMode); } catch (reasonAll) { if (reasonAll instanceof XRefEntryException && !recoveryMode) { throw new XRefParseException(); } catalog.setActualNumPages(1); return; } for (const [pageIndex, [pageDict, ref]] of pagesTree) { let promise; if (pageDict instanceof Error) { promise = Promise.reject(pageDict); promise.catch(() => {}); } else { promise = Promise.resolve(new Page({ pdfManager, xref: this.xref, pageIndex, pageDict, ref, globalIdFactory: this._globalIdFactory, fontCache: catalog.fontCache, builtInCMapCache: catalog.builtInCMapCache, standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, systemFontCache: catalog.systemFontCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory: null })); } this._pagePromises.set(pageIndex, promise); } catalog.setActualNumPages(pagesTree.size); } } fontFallback(id, handler) { return this.catalog.fontFallback(id, handler); } async cleanup(manuallyTriggered = false) { return this.catalog ? this.catalog.cleanup(manuallyTriggered) : clearGlobalCaches(); } async #collectFieldObjects(name, fieldRef, promises, annotationGlobals, visitedRefs) { const { xref } = this; if (!(fieldRef instanceof Ref) || visitedRefs.has(fieldRef)) { return; } visitedRefs.put(fieldRef); const field = await xref.fetchAsync(fieldRef); if (!(field instanceof Dict)) { return; } if (field.has("T")) { const partName = stringToPDFString(await field.getAsync("T")); name = name === "" ? partName : `${name}.${partName}`; } else { let obj = field; while (true) { obj = obj.getRaw("Parent"); if (obj instanceof Ref) { if (visitedRefs.has(obj)) { break; } obj = await xref.fetchAsync(obj); } if (!(obj instanceof Dict)) { break; } if (obj.has("T")) { const partName = stringToPDFString(await obj.getAsync("T")); name = name === "" ? partName : `${name}.${partName}`; break; } } } if (!promises.has(name)) { promises.set(name, []); } promises.get(name).push(AnnotationFactory.create(xref, fieldRef, annotationGlobals, null, true, null).then(annotation => annotation?.getFieldObject()).catch(function (reason) { warn(`#collectFieldObjects: "${reason}".`); return null; })); if (!field.has("Kids")) { return; } const kids = await field.getAsync("Kids"); if (Array.isArray(kids)) { for (const kid of kids) { await this.#collectFieldObjects(name, kid, promises, annotationGlobals, visitedRefs); } } } get fieldObjects() { if (!this.formInfo.hasFields) { return shadow(this, "fieldObjects", Promise.resolve(null)); } const promise = Promise.all([this.pdfManager.ensureDoc("annotationGlobals"), this.pdfManager.ensureCatalog("acroForm")]).then(async ([annotationGlobals, acroForm]) => { if (!annotationGlobals) { return null; } const visitedRefs = new RefSet(); const allFields = Object.create(null); const fieldPromises = new Map(); for (const fieldRef of await acroForm.getAsync("Fields")) { await this.#collectFieldObjects("", fieldRef, fieldPromises, annotationGlobals, visitedRefs); } const allPromises = []; for (const [name, promises] of fieldPromises) { allPromises.push(Promise.all(promises).then(fields => { fields = fields.filter(field => !!field); if (fields.length > 0) { allFields[name] = fields; } })); } await Promise.all(allPromises); return allFields; }); return shadow(this, "fieldObjects", promise); } get hasJSActions() { const promise = this.pdfManager.ensureDoc("_parseHasJSActions"); return shadow(this, "hasJSActions", promise); } async _parseHasJSActions() { const [catalogJsActions, fieldObjects] = await Promise.all([this.pdfManager.ensureCatalog("jsActions"), this.pdfManager.ensureDoc("fieldObjects")]); if (catalogJsActions) { return true; } if (fieldObjects) { return Object.values(fieldObjects).some(fieldObject => fieldObject.some(object => object.actions !== null)); } return false; } get calculationOrderIds() { const acroForm = this.catalog.acroForm; if (!acroForm?.has("CO")) { return shadow(this, "calculationOrderIds", null); } const calculationOrder = acroForm.get("CO"); if (!Array.isArray(calculationOrder) || calculationOrder.length === 0) { return shadow(this, "calculationOrderIds", null); } const ids = []; for (const id of calculationOrder) { if (id instanceof Ref) { ids.push(id.toString()); } } if (ids.length === 0) { return shadow(this, "calculationOrderIds", null); } return shadow(this, "calculationOrderIds", ids); } get annotationGlobals() { return shadow(this, "annotationGlobals", AnnotationFactory.createGlobals(this.pdfManager)); } } ;// CONCATENATED MODULE: ./src/core/pdf_manager.js function parseDocBaseUrl(url) { if (url) { const absoluteUrl = createValidAbsoluteUrl(url); if (absoluteUrl) { return absoluteUrl.href; } warn(`Invalid absolute docBaseUrl: "${url}".`); } return null; } class BasePdfManager { constructor(args) { if (this.constructor === BasePdfManager) { unreachable("Cannot initialize BasePdfManager."); } this._docBaseUrl = parseDocBaseUrl(args.docBaseUrl); this._docId = args.docId; this._password = args.password; this.enableXfa = args.enableXfa; args.evaluatorOptions.isOffscreenCanvasSupported &&= FeatureTest.isOffscreenCanvasSupported; this.evaluatorOptions = args.evaluatorOptions; } get docId() { return this._docId; } get password() { return this._password; } get docBaseUrl() { return this._docBaseUrl; } get catalog() { return this.pdfDocument.catalog; } ensureDoc(prop, args) { return this.ensure(this.pdfDocument, prop, args); } ensureXRef(prop, args) { return this.ensure(this.pdfDocument.xref, prop, args); } ensureCatalog(prop, args) { return this.ensure(this.pdfDocument.catalog, prop, args); } getPage(pageIndex) { return this.pdfDocument.getPage(pageIndex); } fontFallback(id, handler) { return this.pdfDocument.fontFallback(id, handler); } loadXfaFonts(handler, task) { return this.pdfDocument.loadXfaFonts(handler, task); } loadXfaImages() { return this.pdfDocument.loadXfaImages(); } serializeXfaData(annotationStorage) { return this.pdfDocument.serializeXfaData(annotationStorage); } cleanup(manuallyTriggered = false) { return this.pdfDocument.cleanup(manuallyTriggered); } async ensure(obj, prop, args) { unreachable("Abstract method `ensure` called"); } requestRange(begin, end) { unreachable("Abstract method `requestRange` called"); } requestLoadedStream(noFetch = false) { unreachable("Abstract method `requestLoadedStream` called"); } sendProgressiveData(chunk) { unreachable("Abstract method `sendProgressiveData` called"); } updatePassword(password) { this._password = password; } terminate(reason) { unreachable("Abstract method `terminate` called"); } } class LocalPdfManager extends BasePdfManager { constructor(args) { super(args); const stream = new Stream(args.source); this.pdfDocument = new PDFDocument(this, stream); this._loadedStreamPromise = Promise.resolve(stream); } async ensure(obj, prop, args) { const value = obj[prop]; if (typeof value === "function") { return value.apply(obj, args); } return value; } requestRange(begin, end) { return Promise.resolve(); } requestLoadedStream(noFetch = false) { return this._loadedStreamPromise; } terminate(reason) {} } class NetworkPdfManager extends BasePdfManager { constructor(args) { super(args); this.streamManager = new ChunkedStreamManager(args.source, { msgHandler: args.handler, length: args.length, disableAutoFetch: args.disableAutoFetch, rangeChunkSize: args.rangeChunkSize }); this.pdfDocument = new PDFDocument(this, this.streamManager.getStream()); } async ensure(obj, prop, args) { try { const value = obj[prop]; if (typeof value === "function") { return value.apply(obj, args); } return value; } catch (ex) { if (!(ex instanceof MissingDataException)) { throw ex; } await this.requestRange(ex.begin, ex.end); return this.ensure(obj, prop, args); } } requestRange(begin, end) { return this.streamManager.requestRange(begin, end); } requestLoadedStream(noFetch = false) { return this.streamManager.requestAllChunks(noFetch); } sendProgressiveData(chunk) { this.streamManager.onReceiveData({ chunk }); } terminate(reason) { this.streamManager.abort(reason); } } ;// CONCATENATED MODULE: ./src/shared/message_handler.js const CallbackKind = { UNKNOWN: 0, DATA: 1, ERROR: 2 }; const StreamKind = { UNKNOWN: 0, CANCEL: 1, CANCEL_COMPLETE: 2, CLOSE: 3, ENQUEUE: 4, ERROR: 5, PULL: 6, PULL_COMPLETE: 7, START_COMPLETE: 8 }; function wrapReason(reason) { if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) { unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.'); } switch (reason.name) { case "AbortException": return new AbortException(reason.message); case "MissingPDFException": return new MissingPDFException(reason.message); case "PasswordException": return new PasswordException(reason.message, reason.code); case "UnexpectedResponseException": return new UnexpectedResponseException(reason.message, reason.status); case "UnknownErrorException": return new UnknownErrorException(reason.message, reason.details); default: return new UnknownErrorException(reason.message, reason.toString()); } } class MessageHandler { constructor(sourceName, targetName, comObj) { this.sourceName = sourceName; this.targetName = targetName; this.comObj = comObj; this.callbackId = 1; this.streamId = 1; this.streamSinks = Object.create(null); this.streamControllers = Object.create(null); this.callbackCapabilities = Object.create(null); this.actionHandler = Object.create(null); this._onComObjOnMessage = event => { const data = event.data; if (data.targetName !== this.sourceName) { return; } if (data.stream) { this.#processStreamMessage(data); return; } if (data.callback) { const callbackId = data.callbackId; const capability = this.callbackCapabilities[callbackId]; if (!capability) { throw new Error(`Cannot resolve callback ${callbackId}`); } delete this.callbackCapabilities[callbackId]; if (data.callback === CallbackKind.DATA) { capability.resolve(data.data); } else if (data.callback === CallbackKind.ERROR) { capability.reject(wrapReason(data.reason)); } else { throw new Error("Unexpected callback case"); } return; } const action = this.actionHandler[data.action]; if (!action) { throw new Error(`Unknown action from worker: ${data.action}`); } if (data.callbackId) { const cbSourceName = this.sourceName; const cbTargetName = data.sourceName; new Promise(function (resolve) { resolve(action(data.data)); }).then(function (result) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.DATA, callbackId: data.callbackId, data: result }); }, function (reason) { comObj.postMessage({ sourceName: cbSourceName, targetName: cbTargetName, callback: CallbackKind.ERROR, callbackId: data.callbackId, reason: wrapReason(reason) }); }); return; } if (data.streamId) { this.#createStreamSink(data); return; } action(data.data); }; comObj.addEventListener("message", this._onComObjOnMessage); } on(actionName, handler) { const ah = this.actionHandler; if (ah[actionName]) { throw new Error(`There is already an actionName called "${actionName}"`); } ah[actionName] = handler; } send(actionName, data, transfers) { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, data }, transfers); } sendWithPromise(actionName, data, transfers) { const callbackId = this.callbackId++; const capability = new PromiseCapability(); this.callbackCapabilities[callbackId] = capability; try { this.comObj.postMessage({ sourceName: this.sourceName, targetName: this.targetName, action: actionName, callbackId, data }, transfers); } catch (ex) { capability.reject(ex); } return capability.promise; } sendWithStream(actionName, data, queueingStrategy, transfers) { const streamId = this.streamId++, sourceName = this.sourceName, targetName = this.targetName, comObj = this.comObj; return new ReadableStream({ start: controller => { const startCapability = new PromiseCapability(); this.streamControllers[streamId] = { controller, startCall: startCapability, pullCall: null, cancelCall: null, isClosed: false }; comObj.postMessage({ sourceName, targetName, action: actionName, streamId, data, desiredSize: controller.desiredSize }, transfers); return startCapability.promise; }, pull: controller => { const pullCapability = new PromiseCapability(); this.streamControllers[streamId].pullCall = pullCapability; comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL, streamId, desiredSize: controller.desiredSize }); return pullCapability.promise; }, cancel: reason => { assert(reason instanceof Error, "cancel must have a valid reason"); const cancelCapability = new PromiseCapability(); this.streamControllers[streamId].cancelCall = cancelCapability; this.streamControllers[streamId].isClosed = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL, streamId, reason: wrapReason(reason) }); return cancelCapability.promise; } }, queueingStrategy); } #createStreamSink(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const self = this, action = this.actionHandler[data.action]; const streamSink = { enqueue(chunk, size = 1, transfers) { if (this.isCancelled) { return; } const lastDesiredSize = this.desiredSize; this.desiredSize -= size; if (lastDesiredSize > 0 && this.desiredSize <= 0) { this.sinkCapability = new PromiseCapability(); this.ready = this.sinkCapability.promise; } comObj.postMessage({ sourceName, targetName, stream: StreamKind.ENQUEUE, streamId, chunk }, transfers); }, close() { if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.CLOSE, streamId }); delete self.streamSinks[streamId]; }, error(reason) { assert(reason instanceof Error, "error must have a valid reason"); if (this.isCancelled) { return; } this.isCancelled = true; comObj.postMessage({ sourceName, targetName, stream: StreamKind.ERROR, streamId, reason: wrapReason(reason) }); }, sinkCapability: new PromiseCapability(), onPull: null, onCancel: null, isCancelled: false, desiredSize: data.desiredSize, ready: null }; streamSink.sinkCapability.resolve(); streamSink.ready = streamSink.sinkCapability.promise; this.streamSinks[streamId] = streamSink; new Promise(function (resolve) { resolve(action(data.data, streamSink)); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.START_COMPLETE, streamId, reason: wrapReason(reason) }); }); } #processStreamMessage(data) { const streamId = data.streamId, sourceName = this.sourceName, targetName = data.sourceName, comObj = this.comObj; const streamController = this.streamControllers[streamId], streamSink = this.streamSinks[streamId]; switch (data.stream) { case StreamKind.START_COMPLETE: if (data.success) { streamController.startCall.resolve(); } else { streamController.startCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL_COMPLETE: if (data.success) { streamController.pullCall.resolve(); } else { streamController.pullCall.reject(wrapReason(data.reason)); } break; case StreamKind.PULL: if (!streamSink) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); break; } if (streamSink.desiredSize <= 0 && data.desiredSize > 0) { streamSink.sinkCapability.resolve(); } streamSink.desiredSize = data.desiredSize; new Promise(function (resolve) { resolve(streamSink.onPull?.()); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.PULL_COMPLETE, streamId, reason: wrapReason(reason) }); }); break; case StreamKind.ENQUEUE: assert(streamController, "enqueue should have stream controller"); if (streamController.isClosed) { break; } streamController.controller.enqueue(data.chunk); break; case StreamKind.CLOSE: assert(streamController, "close should have stream controller"); if (streamController.isClosed) { break; } streamController.isClosed = true; streamController.controller.close(); this.#deleteStreamController(streamController, streamId); break; case StreamKind.ERROR: assert(streamController, "error should have stream controller"); streamController.controller.error(wrapReason(data.reason)); this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL_COMPLETE: if (data.success) { streamController.cancelCall.resolve(); } else { streamController.cancelCall.reject(wrapReason(data.reason)); } this.#deleteStreamController(streamController, streamId); break; case StreamKind.CANCEL: if (!streamSink) { break; } new Promise(function (resolve) { resolve(streamSink.onCancel?.(wrapReason(data.reason))); }).then(function () { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, success: true }); }, function (reason) { comObj.postMessage({ sourceName, targetName, stream: StreamKind.CANCEL_COMPLETE, streamId, reason: wrapReason(reason) }); }); streamSink.sinkCapability.reject(wrapReason(data.reason)); streamSink.isCancelled = true; delete this.streamSinks[streamId]; break; default: throw new Error("Unexpected stream case"); } } async #deleteStreamController(streamController, streamId) { await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]); delete this.streamControllers[streamId]; } destroy() { this.comObj.removeEventListener("message", this._onComObjOnMessage); } } ;// CONCATENATED MODULE: ./src/core/worker_stream.js class PDFWorkerStream { constructor(msgHandler) { this._msgHandler = msgHandler; this._contentLength = null; this._fullRequestReader = null; this._rangeRequestReaders = []; } getFullReader() { assert(!this._fullRequestReader, "PDFWorkerStream.getFullReader can only be called once."); this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler); return this._fullRequestReader; } getRangeReader(begin, end) { const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler); this._rangeRequestReaders.push(reader); return reader; } cancelAllRequests(reason) { this._fullRequestReader?.cancel(reason); for (const reader of this._rangeRequestReaders.slice(0)) { reader.cancel(reason); } } } class PDFWorkerStreamReader { constructor(msgHandler) { this._msgHandler = msgHandler; this.onProgress = null; this._contentLength = null; this._isRangeSupported = false; this._isStreamingSupported = false; const readableStream = this._msgHandler.sendWithStream("GetReader"); this._reader = readableStream.getReader(); this._headersReady = this._msgHandler.sendWithPromise("ReaderHeadersReady").then(data => { this._isStreamingSupported = data.isStreamingSupported; this._isRangeSupported = data.isRangeSupported; this._contentLength = data.contentLength; }); } get headersReady() { return this._headersReady; } get contentLength() { return this._contentLength; } get isStreamingSupported() { return this._isStreamingSupported; } get isRangeSupported() { return this._isRangeSupported; } async read() { const { value, done } = await this._reader.read(); if (done) { return { value: undefined, done: true }; } return { value: value.buffer, done: false }; } cancel(reason) { this._reader.cancel(reason); } } class PDFWorkerStreamRangeReader { constructor(begin, end, msgHandler) { this._msgHandler = msgHandler; this.onProgress = null; const readableStream = this._msgHandler.sendWithStream("GetRangeReader", { begin, end }); this._reader = readableStream.getReader(); } get isStreamingSupported() { return false; } async read() { const { value, done } = await this._reader.read(); if (done) { return { value: undefined, done: true }; } return { value: value.buffer, done: false }; } cancel(reason) { this._reader.cancel(reason); } } ;// CONCATENATED MODULE: ./src/core/worker.js class WorkerTask { constructor(name) { this.name = name; this.terminated = false; this._capability = new PromiseCapability(); } get finished() { return this._capability.promise; } finish() { this._capability.resolve(); } terminate() { this.terminated = true; } ensureNotTerminated() { if (this.terminated) { throw new Error("Worker task was terminated"); } } } class WorkerMessageHandler { static setup(handler, port) { let testMessageProcessed = false; handler.on("test", function (data) { if (testMessageProcessed) { return; } testMessageProcessed = true; handler.send("test", data instanceof Uint8Array); }); handler.on("configure", function (data) { setVerbosityLevel(data.verbosity); }); handler.on("GetDocRequest", function (data) { return WorkerMessageHandler.createDocumentHandler(data, port); }); } static createDocumentHandler(docParams, port) { let pdfManager; let terminated = false; let cancelXHRs = null; const WorkerTasks = new Set(); const verbosity = getVerbosityLevel(); const { docId, apiVersion } = docParams; const workerVersion = '4.0.269'; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } const enumerableProperties = []; for (const property in []) { enumerableProperties.push(property); } if (enumerableProperties.length) { throw new Error("The `Array.prototype` contains unexpected enumerable properties: " + enumerableProperties.join(", ") + "; thus breaking e.g. `for...in` iteration of `Array`s."); } const workerHandlerName = docId + "_worker"; let handler = new MessageHandler(workerHandlerName, docId, port); function ensureNotTerminated() { if (terminated) { throw new Error("Worker was terminated"); } } function startWorkerTask(task) { WorkerTasks.add(task); } function finishWorkerTask(task) { task.finish(); WorkerTasks.delete(task); } async function loadDocument(recoveryMode) { await pdfManager.ensureDoc("checkHeader"); await pdfManager.ensureDoc("parseStartXRef"); await pdfManager.ensureDoc("parse", [recoveryMode]); await pdfManager.ensureDoc("checkFirstPage", [recoveryMode]); await pdfManager.ensureDoc("checkLastPage", [recoveryMode]); const isPureXfa = await pdfManager.ensureDoc("isPureXfa"); if (isPureXfa) { const task = new WorkerTask("loadXfaFonts"); startWorkerTask(task); await Promise.all([pdfManager.loadXfaFonts(handler, task).catch(reason => {}).then(() => finishWorkerTask(task)), pdfManager.loadXfaImages()]); } const [numPages, fingerprints] = await Promise.all([pdfManager.ensureDoc("numPages"), pdfManager.ensureDoc("fingerprints")]); const htmlForXfa = isPureXfa ? await pdfManager.ensureDoc("htmlForXfa") : null; return { numPages, fingerprints, htmlForXfa }; } function getPdfManager({ data, password, disableAutoFetch, rangeChunkSize, length, docBaseUrl, enableXfa, evaluatorOptions }) { const pdfManagerArgs = { source: null, disableAutoFetch, docBaseUrl, docId, enableXfa, evaluatorOptions, handler, length, password, rangeChunkSize }; const pdfManagerCapability = new PromiseCapability(); let newPdfManager; if (data) { try { pdfManagerArgs.source = data; newPdfManager = new LocalPdfManager(pdfManagerArgs); pdfManagerCapability.resolve(newPdfManager); } catch (ex) { pdfManagerCapability.reject(ex); } return pdfManagerCapability.promise; } let pdfStream, cachedChunks = []; try { pdfStream = new PDFWorkerStream(handler); } catch (ex) { pdfManagerCapability.reject(ex); return pdfManagerCapability.promise; } const fullRequest = pdfStream.getFullReader(); fullRequest.headersReady.then(function () { if (!fullRequest.isRangeSupported) { return; } pdfManagerArgs.source = pdfStream; pdfManagerArgs.length = fullRequest.contentLength; pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported; newPdfManager = new NetworkPdfManager(pdfManagerArgs); for (const chunk of cachedChunks) { newPdfManager.sendProgressiveData(chunk); } cachedChunks = []; pdfManagerCapability.resolve(newPdfManager); cancelXHRs = null; }).catch(function (reason) { pdfManagerCapability.reject(reason); cancelXHRs = null; }); let loaded = 0; const flushChunks = function () { const pdfFile = arrayBuffersToBytes(cachedChunks); if (length && pdfFile.length !== length) { warn("reported HTTP length is different from actual"); } try { pdfManagerArgs.source = pdfFile; newPdfManager = new LocalPdfManager(pdfManagerArgs); pdfManagerCapability.resolve(newPdfManager); } catch (ex) { pdfManagerCapability.reject(ex); } cachedChunks = []; }; new Promise(function (resolve, reject) { const readChunk = function ({ value, done }) { try { ensureNotTerminated(); if (done) { if (!newPdfManager) { flushChunks(); } cancelXHRs = null; return; } loaded += value.byteLength; if (!fullRequest.isStreamingSupported) { handler.send("DocProgress", { loaded, total: Math.max(loaded, fullRequest.contentLength || 0) }); } if (newPdfManager) { newPdfManager.sendProgressiveData(value); } else { cachedChunks.push(value); } fullRequest.read().then(readChunk, reject); } catch (e) { reject(e); } }; fullRequest.read().then(readChunk, reject); }).catch(function (e) { pdfManagerCapability.reject(e); cancelXHRs = null; }); cancelXHRs = function (reason) { pdfStream.cancelAllRequests(reason); }; return pdfManagerCapability.promise; } function setupDoc(data) { function onSuccess(doc) { ensureNotTerminated(); handler.send("GetDoc", { pdfInfo: doc }); } function onFailure(ex) { ensureNotTerminated(); if (ex instanceof PasswordException) { const task = new WorkerTask(`PasswordException: response ${ex.code}`); startWorkerTask(task); handler.sendWithPromise("PasswordRequest", ex).then(function ({ password }) { finishWorkerTask(task); pdfManager.updatePassword(password); pdfManagerReady(); }).catch(function () { finishWorkerTask(task); handler.send("DocException", ex); }); } else if (ex instanceof InvalidPDFException || ex instanceof MissingPDFException || ex instanceof UnexpectedResponseException || ex instanceof UnknownErrorException) { handler.send("DocException", ex); } else { handler.send("DocException", new UnknownErrorException(ex.message, ex.toString())); } } function pdfManagerReady() { ensureNotTerminated(); loadDocument(false).then(onSuccess, function (reason) { ensureNotTerminated(); if (!(reason instanceof XRefParseException)) { onFailure(reason); return; } pdfManager.requestLoadedStream().then(function () { ensureNotTerminated(); loadDocument(true).then(onSuccess, onFailure); }); }); } ensureNotTerminated(); getPdfManager(data).then(function (newPdfManager) { if (terminated) { newPdfManager.terminate(new AbortException("Worker was terminated.")); throw new Error("Worker was terminated"); } pdfManager = newPdfManager; pdfManager.requestLoadedStream(true).then(stream => { handler.send("DataLoaded", { length: stream.bytes.byteLength }); }); }).then(pdfManagerReady, onFailure); } handler.on("GetPage", function (data) { return pdfManager.getPage(data.pageIndex).then(function (page) { return Promise.all([pdfManager.ensure(page, "rotate"), pdfManager.ensure(page, "ref"), pdfManager.ensure(page, "userUnit"), pdfManager.ensure(page, "view")]).then(function ([rotate, ref, userUnit, view]) { return { rotate, ref, userUnit, view }; }); }); }); handler.on("GetPageIndex", function (data) { const pageRef = Ref.get(data.num, data.gen); return pdfManager.ensureCatalog("getPageIndex", [pageRef]); }); handler.on("GetDestinations", function (data) { return pdfManager.ensureCatalog("destinations"); }); handler.on("GetDestination", function (data) { return pdfManager.ensureCatalog("getDestination", [data.id]); }); handler.on("GetPageLabels", function (data) { return pdfManager.ensureCatalog("pageLabels"); }); handler.on("GetPageLayout", function (data) { return pdfManager.ensureCatalog("pageLayout"); }); handler.on("GetPageMode", function (data) { return pdfManager.ensureCatalog("pageMode"); }); handler.on("GetViewerPreferences", function (data) { return pdfManager.ensureCatalog("viewerPreferences"); }); handler.on("GetOpenAction", function (data) { return pdfManager.ensureCatalog("openAction"); }); handler.on("GetAttachments", function (data) { return pdfManager.ensureCatalog("attachments"); }); handler.on("GetDocJSActions", function (data) { return pdfManager.ensureCatalog("jsActions"); }); handler.on("GetPageJSActions", function ({ pageIndex }) { return pdfManager.getPage(pageIndex).then(function (page) { return pdfManager.ensure(page, "jsActions"); }); }); handler.on("GetOutline", function (data) { return pdfManager.ensureCatalog("documentOutline"); }); handler.on("GetOptionalContentConfig", function (data) { return pdfManager.ensureCatalog("optionalContentConfig"); }); handler.on("GetPermissions", function (data) { return pdfManager.ensureCatalog("permissions"); }); handler.on("GetMetadata", function (data) { return Promise.all([pdfManager.ensureDoc("documentInfo"), pdfManager.ensureCatalog("metadata")]); }); handler.on("GetMarkInfo", function (data) { return pdfManager.ensureCatalog("markInfo"); }); handler.on("GetData", function (data) { return pdfManager.requestLoadedStream().then(function (stream) { return stream.bytes; }); }); handler.on("GetAnnotations", function ({ pageIndex, intent }) { return pdfManager.getPage(pageIndex).then(function (page) { const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`); startWorkerTask(task); return page.getAnnotationsData(handler, task, intent).then(data => { finishWorkerTask(task); return data; }, reason => { finishWorkerTask(task); throw reason; }); }); }); handler.on("GetFieldObjects", function (data) { return pdfManager.ensureDoc("fieldObjects"); }); handler.on("HasJSActions", function (data) { return pdfManager.ensureDoc("hasJSActions"); }); handler.on("GetCalculationOrderIds", function (data) { return pdfManager.ensureDoc("calculationOrderIds"); }); handler.on("SaveDocument", async function ({ isPureXfa, numPages, annotationStorage, filename }) { const globalPromises = [pdfManager.requestLoadedStream(), pdfManager.ensureCatalog("acroForm"), pdfManager.ensureCatalog("acroFormRef"), pdfManager.ensureDoc("startXRef"), pdfManager.ensureDoc("xref"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("structTreeRoot")]; const promises = []; const newAnnotationsByPage = !isPureXfa ? getNewAnnotationsMap(annotationStorage) : null; const [stream, acroForm, acroFormRef, startXRef, xref, linearization, _structTreeRoot] = await Promise.all(globalPromises); const catalogRef = xref.trailer.getRaw("Root") || null; let structTreeRoot; if (newAnnotationsByPage) { if (!_structTreeRoot) { if (await StructTreeRoot.canCreateStructureTree({ catalogRef, pdfManager, newAnnotationsByPage })) { structTreeRoot = null; } } else if (await _structTreeRoot.canUpdateStructTree({ pdfManager, xref, newAnnotationsByPage })) { structTreeRoot = _structTreeRoot; } const imagePromises = AnnotationFactory.generateImages(annotationStorage.values(), xref, pdfManager.evaluatorOptions.isOffscreenCanvasSupported); const newAnnotationPromises = structTreeRoot === undefined ? promises : []; for (const [pageIndex, annotations] of newAnnotationsByPage) { newAnnotationPromises.push(pdfManager.getPage(pageIndex).then(page => { const task = new WorkerTask(`Save (editor): page ${pageIndex}`); return page.saveNewAnnotations(handler, task, annotations, imagePromises).finally(function () { finishWorkerTask(task); }); })); } if (structTreeRoot === null) { promises.push(Promise.all(newAnnotationPromises).then(async newRefs => { await StructTreeRoot.createStructureTree({ newAnnotationsByPage, xref, catalogRef, pdfManager, newRefs }); return newRefs; })); } else if (structTreeRoot) { promises.push(Promise.all(newAnnotationPromises).then(async newRefs => { await structTreeRoot.updateStructureTree({ newAnnotationsByPage, pdfManager, newRefs }); return newRefs; })); } } if (isPureXfa) { promises.push(pdfManager.serializeXfaData(annotationStorage)); } else { for (let pageIndex = 0; pageIndex < numPages; pageIndex++) { promises.push(pdfManager.getPage(pageIndex).then(function (page) { const task = new WorkerTask(`Save: page ${pageIndex}`); return page.save(handler, task, annotationStorage).finally(function () { finishWorkerTask(task); }); })); } } const refs = await Promise.all(promises); let newRefs = []; let xfaData = null; if (isPureXfa) { xfaData = refs[0]; if (!xfaData) { return stream.bytes; } } else { newRefs = refs.flat(2); if (newRefs.length === 0) { return stream.bytes; } } const needAppearances = acroFormRef && acroForm instanceof Dict && newRefs.some(ref => ref.needAppearances); const xfa = acroForm instanceof Dict && acroForm.get("XFA") || null; let xfaDatasetsRef = null; let hasXfaDatasetsEntry = false; if (Array.isArray(xfa)) { for (let i = 0, ii = xfa.length; i < ii; i += 2) { if (xfa[i] === "datasets") { xfaDatasetsRef = xfa[i + 1]; hasXfaDatasetsEntry = true; } } if (xfaDatasetsRef === null) { xfaDatasetsRef = xref.getNewTemporaryRef(); } } else if (xfa) { warn("Unsupported XFA type."); } let newXrefInfo = Object.create(null); if (xref.trailer) { const infoObj = Object.create(null); const xrefInfo = xref.trailer.get("Info") || null; if (xrefInfo instanceof Dict) { xrefInfo.forEach((key, value) => { if (typeof value === "string") { infoObj[key] = stringToPDFString(value); } }); } newXrefInfo = { rootRef: catalogRef, encryptRef: xref.trailer.getRaw("Encrypt") || null, newRef: xref.getNewTemporaryRef(), infoRef: xref.trailer.getRaw("Info") || null, info: infoObj, fileIds: xref.trailer.get("ID") || null, startXRef: linearization ? startXRef : xref.lastXRefStreamPos ?? startXRef, filename }; } return incrementalUpdate({ originalData: stream.bytes, xrefInfo: newXrefInfo, newRefs, xref, hasXfa: !!xfa, xfaDatasetsRef, hasXfaDatasetsEntry, needAppearances, acroFormRef, acroForm, xfaData }).finally(() => { xref.resetNewTemporaryRef(); }); }); handler.on("GetOperatorList", function (data, sink) { const pageIndex = data.pageIndex; pdfManager.getPage(pageIndex).then(function (page) { const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`); startWorkerTask(task); const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; page.getOperatorList({ handler, sink, task, intent: data.intent, cacheKey: data.cacheKey, annotationStorage: data.annotationStorage }).then(function (operatorListInfo) { finishWorkerTask(task); if (start) { info(`page=${pageIndex + 1} - getOperatorList: time=` + `${Date.now() - start}ms, len=${operatorListInfo.length}`); } sink.close(); }, function (reason) { finishWorkerTask(task); if (task.terminated) { return; } sink.error(reason); }); }); }); handler.on("GetTextContent", function (data, sink) { const { pageIndex, includeMarkedContent, disableNormalization } = data; pdfManager.getPage(pageIndex).then(function (page) { const task = new WorkerTask("GetTextContent: page " + pageIndex); startWorkerTask(task); const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; page.extractTextContent({ handler, task, sink, includeMarkedContent, disableNormalization }).then(function () { finishWorkerTask(task); if (start) { info(`page=${pageIndex + 1} - getTextContent: time=` + `${Date.now() - start}ms`); } sink.close(); }, function (reason) { finishWorkerTask(task); if (task.terminated) { return; } sink.error(reason); }); }); }); handler.on("GetStructTree", function (data) { return pdfManager.getPage(data.pageIndex).then(function (page) { return pdfManager.ensure(page, "getStructTree"); }); }); handler.on("FontFallback", function (data) { return pdfManager.fontFallback(data.id, handler); }); handler.on("Cleanup", function (data) { return pdfManager.cleanup(true); }); handler.on("Terminate", function (data) { terminated = true; const waitOn = []; if (pdfManager) { pdfManager.terminate(new AbortException("Worker was terminated.")); const cleanupPromise = pdfManager.cleanup(); waitOn.push(cleanupPromise); pdfManager = null; } else { clearGlobalCaches(); } if (cancelXHRs) { cancelXHRs(new AbortException("Worker was terminated.")); } for (const task of WorkerTasks) { waitOn.push(task.finished); task.terminate(); } return Promise.all(waitOn).then(function () { handler.destroy(); handler = null; }); }); handler.on("Ready", function (data) { setupDoc(docParams); docParams = null; }); return workerHandlerName; } static initializeFromPort(port) { const handler = new MessageHandler("worker", "main", port); WorkerMessageHandler.setup(handler, port); handler.send("ready", null); } } function isMessagePort(maybePort) { return typeof maybePort.postMessage === "function" && "onmessage" in maybePort; } if (typeof window === "undefined" && !isNodeJS && typeof self !== "undefined" && isMessagePort(self)) { WorkerMessageHandler.initializeFromPort(self); } ;// CONCATENATED MODULE: ./src/pdf.worker.js const pdfjsVersion = '4.0.269'; const pdfjsBuild = 'f4b396f6c'; var __webpack_exports__WorkerMessageHandler = __webpack_exports__.WorkerMessageHandler; export { __webpack_exports__WorkerMessageHandler as WorkerMessageHandler }; //# sourceMappingURL=pdf.worker.mjs.map ================================================ FILE: public/assets/lib/vendor/three/FontLoader.js ================================================ // @ts-nocheck import { FileLoader, Loader, ShapePath } from './three.module.js'; class FontLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); loader.load( url, function ( text ) { const font = scope.parse( JSON.parse( text ) ); if ( onLoad ) onLoad( font ); }, onProgress, onError ); } parse( json ) { return new Font( json ); } } // class Font { constructor( data ) { this.isFont = true; this.type = 'Font'; this.data = data; } generateShapes( text, size = 100 ) { const shapes = []; const paths = createPaths( text, size, this.data ); for ( let p = 0, pl = paths.length; p < pl; p ++ ) { shapes.push( ...paths[ p ].toShapes() ); } return shapes; } } function createPaths( text, size, data ) { const chars = Array.from( text ); const scale = size / data.resolution; const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; const paths = []; let offsetX = 0, offsetY = 0; for ( let i = 0; i < chars.length; i ++ ) { const char = chars[ i ]; if ( char === '\n' ) { offsetX = 0; offsetY -= line_height; } else { const ret = createPath( char, scale, offsetX, offsetY, data ); offsetX += ret.offsetX; paths.push( ret.path ); } } return paths; } function createPath( char, scale, offsetX, offsetY, data ) { const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; if ( ! glyph ) { console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); return; } const path = new ShapePath(); let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; if ( glyph.o ) { const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); for ( let i = 0, l = outline.length; i < l; ) { const action = outline[ i ++ ]; switch ( action ) { case 'm': // moveTo x = outline[ i ++ ] * scale + offsetX; y = outline[ i ++ ] * scale + offsetY; path.moveTo( x, y ); break; case 'l': // lineTo x = outline[ i ++ ] * scale + offsetX; y = outline[ i ++ ] * scale + offsetY; path.lineTo( x, y ); break; case 'q': // quadraticCurveTo cpx = outline[ i ++ ] * scale + offsetX; cpy = outline[ i ++ ] * scale + offsetY; cpx1 = outline[ i ++ ] * scale + offsetX; cpy1 = outline[ i ++ ] * scale + offsetY; path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); break; case 'b': // bezierCurveTo cpx = outline[ i ++ ] * scale + offsetX; cpy = outline[ i ++ ] * scale + offsetY; cpx1 = outline[ i ++ ] * scale + offsetX; cpy1 = outline[ i ++ ] * scale + offsetY; cpx2 = outline[ i ++ ] * scale + offsetX; cpy2 = outline[ i ++ ] * scale + offsetY; path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); break; } } } return { offsetX: glyph.ha * scale, path: path }; } export { FontLoader, Font }; ================================================ FILE: public/assets/lib/vendor/three/OrbitControls.js ================================================ // @ts-nocheck import { EventDispatcher, MOUSE, Quaternion, Spherical, TOUCH, Vector2, Vector3, Plane, Ray, MathUtils } from './three.module.js'; // OrbitControls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // // Orbit - left mouse / touch: one-finger move // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move const _changeEvent = { type: 'change' }; const _startEvent = { type: 'start' }; const _endEvent = { type: 'end' }; const _ray = new Ray(); const _plane = new Plane(); const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); class OrbitControls extends EventDispatcher { constructor( object, domElement ) { super(); this.object = object; this.domElement = domElement; this.domElement.style.touchAction = 'none'; // disable touch scroll // Set to false to disable this control this.enabled = true; // "target" sets the location of focus, where the object orbits around this.target = new Vector3(); // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect this.cursor = new Vector3(); // How far you can dolly in and out ( PerspectiveCamera only ) this.minDistance = 0; this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only ) this.minZoom = 0; this.maxZoom = Infinity; // Limit camera target within a spherical area around the cursor this.minTargetRadius = 0; this.maxTargetRadius = Infinity; // How far you can orbit vertically, upper and lower limits. // Range is 0 to Math.PI radians. this.minPolarAngle = 0; // radians this.maxPolarAngle = Math.PI; // radians // How far you can orbit horizontally, upper and lower limits. // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) this.minAzimuthAngle = - Infinity; // radians this.maxAzimuthAngle = Infinity; // radians // Set to true to enable damping (inertia) // If damping is enabled, you must call controls.update() in your animation loop this.enableDamping = false; this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. // Set to false to disable zooming this.enableZoom = true; this.zoomSpeed = 1.0; // Set to false to disable rotating this.enableRotate = true; this.rotateSpeed = 1.0; // Set to false to disable panning this.enablePan = true; this.panSpeed = 1.0; this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up this.keyPanSpeed = 7.0; // pixels moved per arrow key push this.zoomToCursor = false; // Set to true to automatically rotate around the target // If auto-rotate is enabled, you must call controls.update() in your animation loop this.autoRotate = false; this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 // The four arrow keys this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; // Mouse buttons this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; // Touch fingers this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; // for reset this.target0 = this.target.clone(); this.position0 = this.object.position.clone(); this.zoom0 = this.object.zoom; // the target DOM element for key events this._domElementKeyEvents = null; // // public methods // this.getPolarAngle = function () { return spherical.phi; }; this.getAzimuthalAngle = function () { return spherical.theta; }; this.getDistance = function () { return this.object.position.distanceTo( this.target ); }; this.listenToKeyEvents = function ( domElement ) { domElement.addEventListener( 'keydown', onKeyDown ); this._domElementKeyEvents = domElement; }; this.stopListenToKeyEvents = function () { this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); this._domElementKeyEvents = null; }; this.saveState = function () { scope.target0.copy( scope.target ); scope.position0.copy( scope.object.position ); scope.zoom0 = scope.object.zoom; }; this.reset = function () { scope.target.copy( scope.target0 ); scope.object.position.copy( scope.position0 ); scope.object.zoom = scope.zoom0; scope.object.updateProjectionMatrix(); scope.dispatchEvent( _changeEvent ); scope.update(); state = STATE.NONE; }; // this method is exposed, but perhaps it would be better if we can make it private... this.update = function () { const offset = new Vector3(); // so camera.up is the orbit axis const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); const quatInverse = quat.clone().invert(); const lastPosition = new Vector3(); const lastQuaternion = new Quaternion(); const lastTargetPosition = new Vector3(); const twoPI = 2 * Math.PI; return function update( deltaTime = null ) { const position = scope.object.position; offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space offset.applyQuaternion( quat ); // angle from z-axis around y-axis spherical.setFromVector3( offset ); if ( scope.autoRotate && state === STATE.NONE ) { rotateLeft( getAutoRotationAngle( deltaTime ) ); } if ( scope.enableDamping ) { spherical.theta += sphericalDelta.theta * scope.dampingFactor; spherical.phi += sphericalDelta.phi * scope.dampingFactor; } else { spherical.theta += sphericalDelta.theta; spherical.phi += sphericalDelta.phi; } // restrict theta to be between desired limits let min = scope.minAzimuthAngle; let max = scope.maxAzimuthAngle; if ( isFinite( min ) && isFinite( max ) ) { if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; if ( min <= max ) { spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); } else { spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta ); } } // restrict phi to be between desired limits spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); spherical.makeSafe(); // move target to panned location if ( scope.enableDamping === true ) { scope.target.addScaledVector( panOffset, scope.dampingFactor ); } else { scope.target.add( panOffset ); } // Limit the target distance from the cursor to create a sphere around the center of interest scope.target.sub( scope.cursor ); scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius ); scope.target.add( scope.cursor ); // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera // we adjust zoom later in these cases if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) { spherical.radius = clampDistance( spherical.radius ); } else { spherical.radius = clampDistance( spherical.radius * scale ); } offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space offset.applyQuaternion( quatInverse ); position.copy( scope.target ).add( offset ); scope.object.lookAt( scope.target ); if ( scope.enableDamping === true ) { sphericalDelta.theta *= ( 1 - scope.dampingFactor ); sphericalDelta.phi *= ( 1 - scope.dampingFactor ); panOffset.multiplyScalar( 1 - scope.dampingFactor ); } else { sphericalDelta.set( 0, 0, 0 ); panOffset.set( 0, 0, 0 ); } // adjust camera position let zoomChanged = false; if ( scope.zoomToCursor && performCursorZoom ) { let newRadius = null; if ( scope.object.isPerspectiveCamera ) { // move the camera down the pointer ray // this method avoids floating point error const prevRadius = offset.length(); newRadius = clampDistance( prevRadius * scale ); const radiusDelta = prevRadius - newRadius; scope.object.position.addScaledVector( dollyDirection, radiusDelta ); scope.object.updateMatrixWorld(); } else if ( scope.object.isOrthographicCamera ) { // adjust the ortho camera position based on zoom changes const mouseBefore = new Vector3( mouse.x, mouse.y, 0 ); mouseBefore.unproject( scope.object ); scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); scope.object.updateProjectionMatrix(); zoomChanged = true; const mouseAfter = new Vector3( mouse.x, mouse.y, 0 ); mouseAfter.unproject( scope.object ); scope.object.position.sub( mouseAfter ).add( mouseBefore ); scope.object.updateMatrixWorld(); newRadius = offset.length(); } else { console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); scope.zoomToCursor = false; } // handle the placement of the target if ( newRadius !== null ) { if ( this.screenSpacePanning ) { // position the orbit target in front of the new camera position scope.target.set( 0, 0, - 1 ) .transformDirection( scope.object.matrix ) .multiplyScalar( newRadius ) .add( scope.object.position ); } else { // get the ray and translation plane to compute target _ray.origin.copy( scope.object.position ); _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix ); // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid // extremely large values if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) { object.lookAt( scope.target ); } else { _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target ); _ray.intersectPlane( _plane, scope.target ); } } } } else if ( scope.object.isOrthographicCamera ) { scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) ); scope.object.updateProjectionMatrix(); zoomChanged = true; } scale = 1; performCursorZoom = false; // update condition is: // min(camera displacement, camera rotation in radians)^2 > EPS // using small-angle approximation cos(x/2) = 1 - x^2 / 8 if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS || lastTargetPosition.distanceToSquared( scope.target ) > 0 ) { scope.dispatchEvent( _changeEvent ); lastPosition.copy( scope.object.position ); lastQuaternion.copy( scope.object.quaternion ); lastTargetPosition.copy( scope.target ); return true; } return false; }; }(); this.dispose = function () { scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); scope.domElement.removeEventListener( 'wheel', onMouseWheel ); scope.domElement.removeEventListener( 'pointermove', onPointerMove ); scope.domElement.removeEventListener( 'pointerup', onPointerUp ); if ( scope._domElementKeyEvents !== null ) { scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); scope._domElementKeyEvents = null; } //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? }; // // internals // const scope = this; const STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_PAN: 4, TOUCH_DOLLY_PAN: 5, TOUCH_DOLLY_ROTATE: 6 }; let state = STATE.NONE; const EPS = 0.000001; // current position in spherical coordinates const spherical = new Spherical(); const sphericalDelta = new Spherical(); let scale = 1; const panOffset = new Vector3(); const rotateStart = new Vector2(); const rotateEnd = new Vector2(); const rotateDelta = new Vector2(); const panStart = new Vector2(); const panEnd = new Vector2(); const panDelta = new Vector2(); const dollyStart = new Vector2(); const dollyEnd = new Vector2(); const dollyDelta = new Vector2(); const dollyDirection = new Vector3(); const mouse = new Vector2(); let performCursorZoom = false; const pointers = []; const pointerPositions = {}; function getAutoRotationAngle( deltaTime ) { if ( deltaTime !== null ) { return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime; } else { return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; } } function getZoomScale( delta ) { const normalized_delta = Math.abs( delta ) / ( 100 * ( window.devicePixelRatio | 0 ) ); return Math.pow( 0.95, scope.zoomSpeed * normalized_delta ); } function rotateLeft( angle ) { sphericalDelta.theta -= angle; } function rotateUp( angle ) { sphericalDelta.phi -= angle; } const panLeft = function () { const v = new Vector3(); return function panLeft( distance, objectMatrix ) { v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix v.multiplyScalar( - distance ); panOffset.add( v ); }; }(); const panUp = function () { const v = new Vector3(); return function panUp( distance, objectMatrix ) { if ( scope.screenSpacePanning === true ) { v.setFromMatrixColumn( objectMatrix, 1 ); } else { v.setFromMatrixColumn( objectMatrix, 0 ); v.crossVectors( scope.object.up, v ); } v.multiplyScalar( distance ); panOffset.add( v ); }; }(); // deltaX and deltaY are in pixels; right and down are positive const pan = function () { const offset = new Vector3(); return function pan( deltaX, deltaY ) { const element = scope.domElement; if ( scope.object.isPerspectiveCamera ) { // perspective const position = scope.object.position; offset.copy( position ).sub( scope.target ); let targetDistance = offset.length(); // half of the fov is center to top of screen targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); } else if ( scope.object.isOrthographicCamera ) { // orthographic panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); } else { // camera neither orthographic nor perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); scope.enablePan = false; } }; }(); function dollyOut( dollyScale ) { if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { scale /= dollyScale; } else { console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); scope.enableZoom = false; } } function dollyIn( dollyScale ) { if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) { scale *= dollyScale; } else { console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); scope.enableZoom = false; } } function updateZoomParameters( x, y ) { if ( ! scope.zoomToCursor ) { return; } performCursorZoom = true; const rect = scope.domElement.getBoundingClientRect(); const dx = x - rect.left; const dy = y - rect.top; const w = rect.width; const h = rect.height; mouse.x = ( dx / w ) * 2 - 1; mouse.y = - ( dy / h ) * 2 + 1; dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize(); } function clampDistance( dist ) { return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) ); } // // event callbacks - update the object state // function handleMouseDownRotate( event ) { rotateStart.set( event.clientX, event.clientY ); } function handleMouseDownDolly( event ) { updateZoomParameters( event.clientX, event.clientX ); dollyStart.set( event.clientX, event.clientY ); } function handleMouseDownPan( event ) { panStart.set( event.clientX, event.clientY ); } function handleMouseMoveRotate( event ) { rotateEnd.set( event.clientX, event.clientY ); rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); const element = scope.domElement; rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); rotateStart.copy( rotateEnd ); scope.update(); } function handleMouseMoveDolly( event ) { dollyEnd.set( event.clientX, event.clientY ); dollyDelta.subVectors( dollyEnd, dollyStart ); if ( dollyDelta.y > 0 ) { dollyOut( getZoomScale( dollyDelta.y ) ); } else if ( dollyDelta.y < 0 ) { dollyIn( getZoomScale( dollyDelta.y ) ); } dollyStart.copy( dollyEnd ); scope.update(); } function handleMouseMovePan( event ) { panEnd.set( event.clientX, event.clientY ); panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); scope.update(); } function handleMouseWheel( event ) { updateZoomParameters( event.clientX, event.clientY ); if ( event.deltaY < 0 ) { dollyIn( getZoomScale( event.deltaY ) ); } else if ( event.deltaY > 0 ) { dollyOut( getZoomScale( event.deltaY ) ); } scope.update(); } function handleKeyDown( event ) { let needsUpdate = false; switch ( event.code ) { case scope.keys.UP: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); } else { pan( 0, scope.keyPanSpeed ); } needsUpdate = true; break; case scope.keys.BOTTOM: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); } else { pan( 0, - scope.keyPanSpeed ); } needsUpdate = true; break; case scope.keys.LEFT: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); } else { pan( scope.keyPanSpeed, 0 ); } needsUpdate = true; break; case scope.keys.RIGHT: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); } else { pan( - scope.keyPanSpeed, 0 ); } needsUpdate = true; break; } if ( needsUpdate ) { // prevent the browser from scrolling on cursor keys event.preventDefault(); scope.update(); } } function handleTouchStartRotate( event ) { if ( pointers.length === 1 ) { rotateStart.set( event.pageX, event.pageY ); } else { const position = getSecondPointerPosition( event ); const x = 0.5 * ( event.pageX + position.x ); const y = 0.5 * ( event.pageY + position.y ); rotateStart.set( x, y ); } } function handleTouchStartPan( event ) { if ( pointers.length === 1 ) { panStart.set( event.pageX, event.pageY ); } else { const position = getSecondPointerPosition( event ); const x = 0.5 * ( event.pageX + position.x ); const y = 0.5 * ( event.pageY + position.y ); panStart.set( x, y ); } } function handleTouchStartDolly( event ) { const position = getSecondPointerPosition( event ); const dx = event.pageX - position.x; const dy = event.pageY - position.y; const distance = Math.sqrt( dx * dx + dy * dy ); dollyStart.set( 0, distance ); } function handleTouchStartDollyPan( event ) { if ( scope.enableZoom ) handleTouchStartDolly( event ); if ( scope.enablePan ) handleTouchStartPan( event ); } function handleTouchStartDollyRotate( event ) { if ( scope.enableZoom ) handleTouchStartDolly( event ); if ( scope.enableRotate ) handleTouchStartRotate( event ); } function handleTouchMoveRotate( event ) { if ( pointers.length == 1 ) { rotateEnd.set( event.pageX, event.pageY ); } else { const position = getSecondPointerPosition( event ); const x = 0.5 * ( event.pageX + position.x ); const y = 0.5 * ( event.pageY + position.y ); rotateEnd.set( x, y ); } rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); const element = scope.domElement; rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); rotateStart.copy( rotateEnd ); } function handleTouchMovePan( event ) { if ( pointers.length === 1 ) { panEnd.set( event.pageX, event.pageY ); } else { const position = getSecondPointerPosition( event ); const x = 0.5 * ( event.pageX + position.x ); const y = 0.5 * ( event.pageY + position.y ); panEnd.set( x, y ); } panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); pan( panDelta.x, panDelta.y ); panStart.copy( panEnd ); } function handleTouchMoveDolly( event ) { const position = getSecondPointerPosition( event ); const dx = event.pageX - position.x; const dy = event.pageY - position.y; const distance = Math.sqrt( dx * dx + dy * dy ); dollyEnd.set( 0, distance ); dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); dollyOut( dollyDelta.y ); dollyStart.copy( dollyEnd ); const centerX = ( event.pageX + position.x ) * 0.5; const centerY = ( event.pageY + position.y ) * 0.5; updateZoomParameters( centerX, centerY ); } function handleTouchMoveDollyPan( event ) { if ( scope.enableZoom ) handleTouchMoveDolly( event ); if ( scope.enablePan ) handleTouchMovePan( event ); } function handleTouchMoveDollyRotate( event ) { if ( scope.enableZoom ) handleTouchMoveDolly( event ); if ( scope.enableRotate ) handleTouchMoveRotate( event ); } // // event handlers - FSM: listen for events and reset state // function onPointerDown( event ) { if ( scope.enabled === false ) return; if ( pointers.length === 0 ) { scope.domElement.setPointerCapture( event.pointerId ); scope.domElement.addEventListener( 'pointermove', onPointerMove ); scope.domElement.addEventListener( 'pointerup', onPointerUp ); } // addPointer( event ); if ( event.pointerType === 'touch' ) { onTouchStart( event ); } else { onMouseDown( event ); } } function onPointerMove( event ) { if ( scope.enabled === false ) return; if ( event.pointerType === 'touch' ) { onTouchMove( event ); } else { onMouseMove( event ); } } function onPointerUp( event ) { removePointer( event ); if ( pointers.length === 0 ) { scope.domElement.releasePointerCapture( event.pointerId ); scope.domElement.removeEventListener( 'pointermove', onPointerMove ); scope.domElement.removeEventListener( 'pointerup', onPointerUp ); } scope.dispatchEvent( _endEvent ); state = STATE.NONE; } function onMouseDown( event ) { let mouseAction; switch ( event.button ) { case 0: mouseAction = scope.mouseButtons.LEFT; break; case 1: mouseAction = scope.mouseButtons.MIDDLE; break; case 2: mouseAction = scope.mouseButtons.RIGHT; break; default: mouseAction = - 1; } switch ( mouseAction ) { case MOUSE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseDownDolly( event ); state = STATE.DOLLY; break; case MOUSE.ROTATE: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; } else { if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; } break; case MOUSE.PAN: if ( event.ctrlKey || event.metaKey || event.shiftKey ) { if ( scope.enableRotate === false ) return; handleMouseDownRotate( event ); state = STATE.ROTATE; } else { if ( scope.enablePan === false ) return; handleMouseDownPan( event ); state = STATE.PAN; } break; default: state = STATE.NONE; } if ( state !== STATE.NONE ) { scope.dispatchEvent( _startEvent ); } } function onMouseMove( event ) { switch ( state ) { case STATE.ROTATE: if ( scope.enableRotate === false ) return; handleMouseMoveRotate( event ); break; case STATE.DOLLY: if ( scope.enableZoom === false ) return; handleMouseMoveDolly( event ); break; case STATE.PAN: if ( scope.enablePan === false ) return; handleMouseMovePan( event ); break; } } function onMouseWheel( event ) { if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; event.preventDefault(); scope.dispatchEvent( _startEvent ); handleMouseWheel( event ); scope.dispatchEvent( _endEvent ); } function onKeyDown( event ) { if ( scope.enabled === false || scope.enablePan === false ) return; handleKeyDown( event ); } function onTouchStart( event ) { trackPointer( event ); switch ( pointers.length ) { case 1: switch ( scope.touches.ONE ) { case TOUCH.ROTATE: if ( scope.enableRotate === false ) return; handleTouchStartRotate( event ); state = STATE.TOUCH_ROTATE; break; case TOUCH.PAN: if ( scope.enablePan === false ) return; handleTouchStartPan( event ); state = STATE.TOUCH_PAN; break; default: state = STATE.NONE; } break; case 2: switch ( scope.touches.TWO ) { case TOUCH.DOLLY_PAN: if ( scope.enableZoom === false && scope.enablePan === false ) return; handleTouchStartDollyPan( event ); state = STATE.TOUCH_DOLLY_PAN; break; case TOUCH.DOLLY_ROTATE: if ( scope.enableZoom === false && scope.enableRotate === false ) return; handleTouchStartDollyRotate( event ); state = STATE.TOUCH_DOLLY_ROTATE; break; default: state = STATE.NONE; } break; default: state = STATE.NONE; } if ( state !== STATE.NONE ) { scope.dispatchEvent( _startEvent ); } } function onTouchMove( event ) { trackPointer( event ); switch ( state ) { case STATE.TOUCH_ROTATE: if ( scope.enableRotate === false ) return; handleTouchMoveRotate( event ); scope.update(); break; case STATE.TOUCH_PAN: if ( scope.enablePan === false ) return; handleTouchMovePan( event ); scope.update(); break; case STATE.TOUCH_DOLLY_PAN: if ( scope.enableZoom === false && scope.enablePan === false ) return; handleTouchMoveDollyPan( event ); scope.update(); break; case STATE.TOUCH_DOLLY_ROTATE: if ( scope.enableZoom === false && scope.enableRotate === false ) return; handleTouchMoveDollyRotate( event ); scope.update(); break; default: state = STATE.NONE; } } function onContextMenu( event ) { if ( scope.enabled === false ) return; event.preventDefault(); } function addPointer( event ) { pointers.push( event.pointerId ); } function removePointer( event ) { delete pointerPositions[ event.pointerId ]; for ( let i = 0; i < pointers.length; i ++ ) { if ( pointers[ i ] == event.pointerId ) { pointers.splice( i, 1 ); return; } } } function trackPointer( event ) { let position = pointerPositions[ event.pointerId ]; if ( position === undefined ) { position = new Vector2(); pointerPositions[ event.pointerId ] = position; } position.set( event.pageX, event.pageY ); } function getSecondPointerPosition( event ) { const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ]; return pointerPositions[ pointerId ]; } // scope.domElement.addEventListener( 'contextmenu', onContextMenu ); scope.domElement.addEventListener( 'pointerdown', onPointerDown ); scope.domElement.addEventListener( 'pointercancel', onPointerUp ); scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); // force an update at start this.update(); } } export { OrbitControls }; ================================================ FILE: public/assets/lib/vendor/three/Projector.js ================================================ // @ts-nocheck import { Box3, Color, DoubleSide, Frustum, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from './three.module.js'; class RenderableObject { constructor() { this.id = 0; this.object = null; this.z = 0; this.renderOrder = 0; } } // class RenderableFace { constructor() { this.id = 0; this.v1 = new RenderableVertex(); this.v2 = new RenderableVertex(); this.v3 = new RenderableVertex(); this.normalModel = new Vector3(); this.vertexNormalsModel = [ new Vector3(), new Vector3(), new Vector3() ]; this.vertexNormalsLength = 0; this.color = new Color(); this.material = null; this.uvs = [ new Vector2(), new Vector2(), new Vector2() ]; this.z = 0; this.renderOrder = 0; } } // class RenderableVertex { constructor() { this.position = new Vector3(); this.positionWorld = new Vector3(); this.positionScreen = new Vector4(); this.visible = true; } copy( vertex ) { this.positionWorld.copy( vertex.positionWorld ); this.positionScreen.copy( vertex.positionScreen ); } } // class RenderableLine { constructor() { this.id = 0; this.v1 = new RenderableVertex(); this.v2 = new RenderableVertex(); this.vertexColors = [ new Color(), new Color() ]; this.material = null; this.z = 0; this.renderOrder = 0; } } // class RenderableSprite { constructor() { this.id = 0; this.object = null; this.x = 0; this.y = 0; this.z = 0; this.rotation = 0; this.scale = new Vector2(); this.material = null; this.renderOrder = 0; } } // class Projector { constructor() { let _object, _objectCount, _objectPoolLength = 0, _vertex, _vertexCount, _vertexPoolLength = 0, _face, _faceCount, _facePoolLength = 0, _line, _lineCount, _linePoolLength = 0, _sprite, _spriteCount, _spritePoolLength = 0, _modelMatrix; const _renderData = { objects: [], lights: [], elements: [] }, _vector3 = new Vector3(), _vector4 = new Vector4(), _clipBox = new Box3( new Vector3( - 1, - 1, - 1 ), new Vector3( 1, 1, 1 ) ), _boundingBox = new Box3(), _points3 = new Array( 3 ), _viewMatrix = new Matrix4(), _viewProjectionMatrix = new Matrix4(), _modelViewProjectionMatrix = new Matrix4(), _frustum = new Frustum(), _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = []; // function RenderList() { const normals = []; const colors = []; const uvs = []; let object = null; const normalMatrix = new Matrix3(); function setObject( value ) { object = value; normalMatrix.getNormalMatrix( object.matrixWorld ); normals.length = 0; colors.length = 0; uvs.length = 0; } function projectVertex( vertex ) { const position = vertex.position; const positionWorld = vertex.positionWorld; const positionScreen = vertex.positionScreen; positionWorld.copy( position ).applyMatrix4( _modelMatrix ); positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); const invW = 1 / positionScreen.w; positionScreen.x *= invW; positionScreen.y *= invW; positionScreen.z *= invW; vertex.visible = positionScreen.x >= - 1 && positionScreen.x <= 1 && positionScreen.y >= - 1 && positionScreen.y <= 1 && positionScreen.z >= - 1 && positionScreen.z <= 1; } function pushVertex( x, y, z ) { _vertex = getNextVertexInPool(); _vertex.position.set( x, y, z ); projectVertex( _vertex ); } function pushNormal( x, y, z ) { normals.push( x, y, z ); } function pushColor( r, g, b ) { colors.push( r, g, b ); } function pushUv( x, y ) { uvs.push( x, y ); } function checkTriangleVisibility( v1, v2, v3 ) { if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true; _points3[ 0 ] = v1.positionScreen; _points3[ 1 ] = v2.positionScreen; _points3[ 2 ] = v3.positionScreen; return _clipBox.intersectsBox( _boundingBox.setFromPoints( _points3 ) ); } function checkBackfaceCulling( v1, v2, v3 ) { return ( ( v3.positionScreen.x - v1.positionScreen.x ) * ( v2.positionScreen.y - v1.positionScreen.y ) - ( v3.positionScreen.y - v1.positionScreen.y ) * ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; } function pushLine( a, b ) { const v1 = _vertexPool[ a ]; const v2 = _vertexPool[ b ]; // Clip v1.positionScreen.copy( v1.position ).applyMatrix4( _modelViewProjectionMatrix ); v2.positionScreen.copy( v2.position ).applyMatrix4( _modelViewProjectionMatrix ); if ( clipLine( v1.positionScreen, v2.positionScreen ) === true ) { // Perform the perspective divide v1.positionScreen.multiplyScalar( 1 / v1.positionScreen.w ); v2.positionScreen.multiplyScalar( 1 / v2.positionScreen.w ); _line = getNextLineInPool(); _line.id = object.id; _line.v1.copy( v1 ); _line.v2.copy( v2 ); _line.z = Math.max( v1.positionScreen.z, v2.positionScreen.z ); _line.renderOrder = object.renderOrder; _line.material = object.material; if ( object.material.vertexColors ) { _line.vertexColors[ 0 ].fromArray( colors, a * 3 ); _line.vertexColors[ 1 ].fromArray( colors, b * 3 ); } _renderData.elements.push( _line ); } } function pushTriangle( a, b, c, material ) { const v1 = _vertexPool[ a ]; const v2 = _vertexPool[ b ]; const v3 = _vertexPool[ c ]; if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return; if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) { _face = getNextFaceInPool(); _face.id = object.id; _face.v1.copy( v1 ); _face.v2.copy( v2 ); _face.v3.copy( v3 ); _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; _face.renderOrder = object.renderOrder; // face normal _vector3.subVectors( v3.position, v2.position ); _vector4.subVectors( v1.position, v2.position ); _vector3.cross( _vector4 ); _face.normalModel.copy( _vector3 ); _face.normalModel.applyMatrix3( normalMatrix ).normalize(); for ( let i = 0; i < 3; i ++ ) { const normal = _face.vertexNormalsModel[ i ]; normal.fromArray( normals, arguments[ i ] * 3 ); normal.applyMatrix3( normalMatrix ).normalize(); const uv = _face.uvs[ i ]; uv.fromArray( uvs, arguments[ i ] * 2 ); } _face.vertexNormalsLength = 3; _face.material = material; if ( material.vertexColors ) { _face.color.fromArray( colors, a * 3 ); } _renderData.elements.push( _face ); } } return { setObject: setObject, projectVertex: projectVertex, checkTriangleVisibility: checkTriangleVisibility, checkBackfaceCulling: checkBackfaceCulling, pushVertex: pushVertex, pushNormal: pushNormal, pushColor: pushColor, pushUv: pushUv, pushLine: pushLine, pushTriangle: pushTriangle }; } const renderList = new RenderList(); function projectObject( object ) { if ( object.visible === false ) return; if ( object.isLight ) { _renderData.lights.push( object ); } else if ( object.isMesh || object.isLine || object.isPoints ) { if ( object.material.visible === false ) return; if ( object.frustumCulled === true && _frustum.intersectsObject( object ) === false ) return; addObject( object ); } else if ( object.isSprite ) { if ( object.material.visible === false ) return; if ( object.frustumCulled === true && _frustum.intersectsSprite( object ) === false ) return; addObject( object ); } const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { projectObject( children[ i ] ); } } function addObject( object ) { _object = getNextObjectInPool(); _object.id = object.id; _object.object = object; _vector3.setFromMatrixPosition( object.matrixWorld ); _vector3.applyMatrix4( _viewProjectionMatrix ); _object.z = _vector3.z; _object.renderOrder = object.renderOrder; _renderData.objects.push( _object ); } this.projectScene = function ( scene, camera, sortObjects, sortElements ) { _faceCount = 0; _lineCount = 0; _spriteCount = 0; _renderData.elements.length = 0; if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); _viewMatrix.copy( camera.matrixWorldInverse ); _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); _frustum.setFromProjectionMatrix( _viewProjectionMatrix ); // _objectCount = 0; _renderData.objects.length = 0; _renderData.lights.length = 0; projectObject( scene ); if ( sortObjects === true ) { _renderData.objects.sort( painterSort ); } // const objects = _renderData.objects; for ( let o = 0, ol = objects.length; o < ol; o ++ ) { const object = objects[ o ].object; const geometry = object.geometry; renderList.setObject( object ); _modelMatrix = object.matrixWorld; _vertexCount = 0; if ( object.isMesh ) { let material = object.material; const isMultiMaterial = Array.isArray( material ); const attributes = geometry.attributes; const groups = geometry.groups; if ( attributes.position === undefined ) continue; const positions = attributes.position.array; for ( let i = 0, l = positions.length; i < l; i += 3 ) { let x = positions[ i ]; let y = positions[ i + 1 ]; let z = positions[ i + 2 ]; const morphTargets = geometry.morphAttributes.position; if ( morphTargets !== undefined ) { const morphTargetsRelative = geometry.morphTargetsRelative; const morphInfluences = object.morphTargetInfluences; for ( let t = 0, tl = morphTargets.length; t < tl; t ++ ) { const influence = morphInfluences[ t ]; if ( influence === 0 ) continue; const target = morphTargets[ t ]; if ( morphTargetsRelative ) { x += target.getX( i / 3 ) * influence; y += target.getY( i / 3 ) * influence; z += target.getZ( i / 3 ) * influence; } else { x += ( target.getX( i / 3 ) - positions[ i ] ) * influence; y += ( target.getY( i / 3 ) - positions[ i + 1 ] ) * influence; z += ( target.getZ( i / 3 ) - positions[ i + 2 ] ) * influence; } } } renderList.pushVertex( x, y, z ); } if ( attributes.normal !== undefined ) { const normals = attributes.normal.array; for ( let i = 0, l = normals.length; i < l; i += 3 ) { renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); } } if ( attributes.color !== undefined ) { const colors = attributes.color.array; for ( let i = 0, l = colors.length; i < l; i += 3 ) { renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); } } if ( attributes.uv !== undefined ) { const uvs = attributes.uv.array; for ( let i = 0, l = uvs.length; i < l; i += 2 ) { renderList.pushUv( uvs[ i ], uvs[ i + 1 ] ); } } if ( geometry.index !== null ) { const indices = geometry.index.array; if ( groups.length > 0 ) { for ( let g = 0; g < groups.length; g ++ ) { const group = groups[ g ]; material = isMultiMaterial === true ? object.material[ group.materialIndex ] : object.material; if ( material === undefined ) continue; for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); } } } else { for ( let i = 0, l = indices.length; i < l; i += 3 ) { renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); } } } else { if ( groups.length > 0 ) { for ( let g = 0; g < groups.length; g ++ ) { const group = groups[ g ]; material = isMultiMaterial === true ? object.material[ group.materialIndex ] : object.material; if ( material === undefined ) continue; for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { renderList.pushTriangle( i, i + 1, i + 2, material ); } } } else { for ( let i = 0, l = positions.length / 3; i < l; i += 3 ) { renderList.pushTriangle( i, i + 1, i + 2, material ); } } } } else if ( object.isLine ) { _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); const attributes = geometry.attributes; if ( attributes.position !== undefined ) { const positions = attributes.position.array; for ( let i = 0, l = positions.length; i < l; i += 3 ) { renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); } if ( attributes.color !== undefined ) { const colors = attributes.color.array; for ( let i = 0, l = colors.length; i < l; i += 3 ) { renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); } } if ( geometry.index !== null ) { const indices = geometry.index.array; for ( let i = 0, l = indices.length; i < l; i += 2 ) { renderList.pushLine( indices[ i ], indices[ i + 1 ] ); } } else { const step = object.isLineSegments ? 2 : 1; for ( let i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) { renderList.pushLine( i, i + 1 ); } } } } else if ( object.isPoints ) { _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); const attributes = geometry.attributes; if ( attributes.position !== undefined ) { const positions = attributes.position.array; for ( let i = 0, l = positions.length; i < l; i += 3 ) { _vector4.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ], 1 ); _vector4.applyMatrix4( _modelViewProjectionMatrix ); pushPoint( _vector4, object, camera ); } } } else if ( object.isSprite ) { object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); _vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); _vector4.applyMatrix4( _viewProjectionMatrix ); pushPoint( _vector4, object, camera ); } } if ( sortElements === true ) { _renderData.elements.sort( painterSort ); } return _renderData; }; function pushPoint( _vector4, object, camera ) { const invW = 1 / _vector4.w; _vector4.z *= invW; if ( _vector4.z >= - 1 && _vector4.z <= 1 ) { _sprite = getNextSpriteInPool(); _sprite.id = object.id; _sprite.x = _vector4.x * invW; _sprite.y = _vector4.y * invW; _sprite.z = _vector4.z; _sprite.renderOrder = object.renderOrder; _sprite.object = object; _sprite.rotation = object.rotation; _sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) ); _sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) ); _sprite.material = object.material; _renderData.elements.push( _sprite ); } } // Pools function getNextObjectInPool() { if ( _objectCount === _objectPoolLength ) { const object = new RenderableObject(); _objectPool.push( object ); _objectPoolLength ++; _objectCount ++; return object; } return _objectPool[ _objectCount ++ ]; } function getNextVertexInPool() { if ( _vertexCount === _vertexPoolLength ) { const vertex = new RenderableVertex(); _vertexPool.push( vertex ); _vertexPoolLength ++; _vertexCount ++; return vertex; } return _vertexPool[ _vertexCount ++ ]; } function getNextFaceInPool() { if ( _faceCount === _facePoolLength ) { const face = new RenderableFace(); _facePool.push( face ); _facePoolLength ++; _faceCount ++; return face; } return _facePool[ _faceCount ++ ]; } function getNextLineInPool() { if ( _lineCount === _linePoolLength ) { const line = new RenderableLine(); _linePool.push( line ); _linePoolLength ++; _lineCount ++; return line; } return _linePool[ _lineCount ++ ]; } function getNextSpriteInPool() { if ( _spriteCount === _spritePoolLength ) { const sprite = new RenderableSprite(); _spritePool.push( sprite ); _spritePoolLength ++; _spriteCount ++; return sprite; } return _spritePool[ _spriteCount ++ ]; } // function painterSort( a, b ) { if ( a.renderOrder !== b.renderOrder ) { return a.renderOrder - b.renderOrder; } else if ( a.z !== b.z ) { return b.z - a.z; } else if ( a.id !== b.id ) { return a.id - b.id; } else { return 0; } } function clipLine( s1, s2 ) { let alpha1 = 0, alpha2 = 1; // Calculate the boundary coordinate of each vertex for the near and far clip planes, // Z = -1 and Z = +1, respectively. const bc1near = s1.z + s1.w, bc2near = s2.z + s2.w, bc1far = - s1.z + s1.w, bc2far = - s2.z + s2.w; if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { // Both vertices lie entirely within all clip planes. return true; } else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) { // Both vertices lie entirely outside one of the clip planes. return false; } else { // The line segment spans at least one clip plane. if ( bc1near < 0 ) { // v1 lies outside the near plane, v2 inside alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); } else if ( bc2near < 0 ) { // v2 lies outside the near plane, v1 inside alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); } if ( bc1far < 0 ) { // v1 lies outside the far plane, v2 inside alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); } else if ( bc2far < 0 ) { // v2 lies outside the far plane, v2 inside alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); } if ( alpha2 < alpha1 ) { // The line segment spans two boundaries, but is outside both of them. // (This can't happen when we're only clipping against just near/far but good // to leave the check here for future usage if other clip planes are added.) return false; } else { // Update the s1 and s2 vertices to match the clipped line segment. s1.lerp( s2, alpha1 ); s2.lerp( s1, 1 - alpha2 ); return true; } } } } } export { RenderableObject, RenderableFace, RenderableVertex, RenderableLine, RenderableSprite, Projector }; ================================================ FILE: public/assets/lib/vendor/three/TextGeometry.js ================================================ /** * Text = 3D Text * * parameters = { * font: <THREE.Font>, // font * * size: <float>, // size of the text * depth: <float>, // thickness to extrude text * curveSegments: <int>, // number of points on the curves * * bevelEnabled: <bool>, // turn on bevel * bevelThickness: <float>, // how deep into text bevel goes * bevelSize: <float>, // how far from text outline (including bevelOffset) is bevel * bevelOffset: <float> // how far from text outline does bevel start * } */ import { ExtrudeGeometry } from './three.module.js'; class TextGeometry extends ExtrudeGeometry { constructor( text, parameters = {} ) { const font = parameters.font; if ( font === undefined ) { super(); // generate default extrude geometry } else { const shapes = font.generateShapes( text, parameters.size ); // translate parameters to ExtrudeGeometry API if ( parameters.depth === undefined && parameters.height !== undefined ) { console.warn( 'THREE.TextGeometry: .height is now depreciated. Please use .depth instead' ); // @deprecated, r163 } parameters.depth = parameters.depth !== undefined ? parameters.depth : parameters.height !== undefined ? parameters.height : 50; // defaults if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; super( shapes, parameters ); } this.type = 'TextGeometry'; } } export { TextGeometry }; ================================================ FILE: public/assets/lib/vendor/three/three.module.js ================================================ // @ts-nocheck /** * @license * Copyright 2010-2023 Three.js Authors * SPDX-License-Identifier: MIT */ const REVISION = '160'; const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; const CullFaceNone = 0; const CullFaceBack = 1; const CullFaceFront = 2; const CullFaceFrontBack = 3; const BasicShadowMap = 0; const PCFShadowMap = 1; const PCFSoftShadowMap = 2; const VSMShadowMap = 3; const FrontSide = 0; const BackSide = 1; const DoubleSide = 2; const TwoPassDoubleSide = 2; // r149 const NoBlending = 0; const NormalBlending = 1; const AdditiveBlending = 2; const SubtractiveBlending = 3; const MultiplyBlending = 4; const CustomBlending = 5; const AddEquation = 100; const SubtractEquation = 101; const ReverseSubtractEquation = 102; const MinEquation = 103; const MaxEquation = 104; const ZeroFactor = 200; const OneFactor = 201; const SrcColorFactor = 202; const OneMinusSrcColorFactor = 203; const SrcAlphaFactor = 204; const OneMinusSrcAlphaFactor = 205; const DstAlphaFactor = 206; const OneMinusDstAlphaFactor = 207; const DstColorFactor = 208; const OneMinusDstColorFactor = 209; const SrcAlphaSaturateFactor = 210; const ConstantColorFactor = 211; const OneMinusConstantColorFactor = 212; const ConstantAlphaFactor = 213; const OneMinusConstantAlphaFactor = 214; const NeverDepth = 0; const AlwaysDepth = 1; const LessDepth = 2; const LessEqualDepth = 3; const EqualDepth = 4; const GreaterEqualDepth = 5; const GreaterDepth = 6; const NotEqualDepth = 7; const MultiplyOperation = 0; const MixOperation = 1; const AddOperation = 2; const NoToneMapping = 0; const LinearToneMapping = 1; const ReinhardToneMapping = 2; const CineonToneMapping = 3; const ACESFilmicToneMapping = 4; const CustomToneMapping = 5; const AgXToneMapping = 6; const AttachedBindMode = 'attached'; const DetachedBindMode = 'detached'; const UVMapping = 300; const CubeReflectionMapping = 301; const CubeRefractionMapping = 302; const EquirectangularReflectionMapping = 303; const EquirectangularRefractionMapping = 304; const CubeUVReflectionMapping = 306; const RepeatWrapping = 1000; const ClampToEdgeWrapping = 1001; const MirroredRepeatWrapping = 1002; const NearestFilter = 1003; const NearestMipmapNearestFilter = 1004; const NearestMipMapNearestFilter = 1004; const NearestMipmapLinearFilter = 1005; const NearestMipMapLinearFilter = 1005; const LinearFilter = 1006; const LinearMipmapNearestFilter = 1007; const LinearMipMapNearestFilter = 1007; const LinearMipmapLinearFilter = 1008; const LinearMipMapLinearFilter = 1008; const UnsignedByteType = 1009; const ByteType = 1010; const ShortType = 1011; const UnsignedShortType = 1012; const IntType = 1013; const UnsignedIntType = 1014; const FloatType = 1015; const HalfFloatType = 1016; const UnsignedShort4444Type = 1017; const UnsignedShort5551Type = 1018; const UnsignedInt248Type = 1020; const AlphaFormat = 1021; const RGBAFormat = 1023; const LuminanceFormat = 1024; const LuminanceAlphaFormat = 1025; const DepthFormat = 1026; const DepthStencilFormat = 1027; const RedFormat = 1028; const RedIntegerFormat = 1029; const RGFormat = 1030; const RGIntegerFormat = 1031; const RGBAIntegerFormat = 1033; const RGB_S3TC_DXT1_Format = 33776; const RGBA_S3TC_DXT1_Format = 33777; const RGBA_S3TC_DXT3_Format = 33778; const RGBA_S3TC_DXT5_Format = 33779; const RGB_PVRTC_4BPPV1_Format = 35840; const RGB_PVRTC_2BPPV1_Format = 35841; const RGBA_PVRTC_4BPPV1_Format = 35842; const RGBA_PVRTC_2BPPV1_Format = 35843; const RGB_ETC1_Format = 36196; const RGB_ETC2_Format = 37492; const RGBA_ETC2_EAC_Format = 37496; const RGBA_ASTC_4x4_Format = 37808; const RGBA_ASTC_5x4_Format = 37809; const RGBA_ASTC_5x5_Format = 37810; const RGBA_ASTC_6x5_Format = 37811; const RGBA_ASTC_6x6_Format = 37812; const RGBA_ASTC_8x5_Format = 37813; const RGBA_ASTC_8x6_Format = 37814; const RGBA_ASTC_8x8_Format = 37815; const RGBA_ASTC_10x5_Format = 37816; const RGBA_ASTC_10x6_Format = 37817; const RGBA_ASTC_10x8_Format = 37818; const RGBA_ASTC_10x10_Format = 37819; const RGBA_ASTC_12x10_Format = 37820; const RGBA_ASTC_12x12_Format = 37821; const RGBA_BPTC_Format = 36492; const RGB_BPTC_SIGNED_Format = 36494; const RGB_BPTC_UNSIGNED_Format = 36495; const RED_RGTC1_Format = 36283; const SIGNED_RED_RGTC1_Format = 36284; const RED_GREEN_RGTC2_Format = 36285; const SIGNED_RED_GREEN_RGTC2_Format = 36286; const LoopOnce = 2200; const LoopRepeat = 2201; const LoopPingPong = 2202; const InterpolateDiscrete = 2300; const InterpolateLinear = 2301; const InterpolateSmooth = 2302; const ZeroCurvatureEnding = 2400; const ZeroSlopeEnding = 2401; const WrapAroundEnding = 2402; const NormalAnimationBlendMode = 2500; const AdditiveAnimationBlendMode = 2501; const TrianglesDrawMode = 0; const TriangleStripDrawMode = 1; const TriangleFanDrawMode = 2; /** @deprecated Use LinearSRGBColorSpace or NoColorSpace in three.js r152+. */ const LinearEncoding = 3000; /** @deprecated Use SRGBColorSpace in three.js r152+. */ const sRGBEncoding = 3001; const BasicDepthPacking = 3200; const RGBADepthPacking = 3201; const TangentSpaceNormalMap = 0; const ObjectSpaceNormalMap = 1; // Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. const NoColorSpace = ''; const SRGBColorSpace = 'srgb'; const LinearSRGBColorSpace = 'srgb-linear'; const DisplayP3ColorSpace = 'display-p3'; const LinearDisplayP3ColorSpace = 'display-p3-linear'; const LinearTransfer = 'linear'; const SRGBTransfer = 'srgb'; const Rec709Primaries = 'rec709'; const P3Primaries = 'p3'; const ZeroStencilOp = 0; const KeepStencilOp = 7680; const ReplaceStencilOp = 7681; const IncrementStencilOp = 7682; const DecrementStencilOp = 7683; const IncrementWrapStencilOp = 34055; const DecrementWrapStencilOp = 34056; const InvertStencilOp = 5386; const NeverStencilFunc = 512; const LessStencilFunc = 513; const EqualStencilFunc = 514; const LessEqualStencilFunc = 515; const GreaterStencilFunc = 516; const NotEqualStencilFunc = 517; const GreaterEqualStencilFunc = 518; const AlwaysStencilFunc = 519; const NeverCompare = 512; const LessCompare = 513; const EqualCompare = 514; const LessEqualCompare = 515; const GreaterCompare = 516; const NotEqualCompare = 517; const GreaterEqualCompare = 518; const AlwaysCompare = 519; const StaticDrawUsage = 35044; const DynamicDrawUsage = 35048; const StreamDrawUsage = 35040; const StaticReadUsage = 35045; const DynamicReadUsage = 35049; const StreamReadUsage = 35041; const StaticCopyUsage = 35046; const DynamicCopyUsage = 35050; const StreamCopyUsage = 35042; const GLSL1 = '100'; const GLSL3 = '300 es'; const _SRGBAFormat = 1035; // fallback for WebGL 1 const WebGLCoordinateSystem = 2000; const WebGPUCoordinateSystem = 2001; /** * https://github.com/mrdoob/eventdispatcher.js/ */ class EventDispatcher { addEventListener( type, listener ) { if ( this._listeners === undefined ) this._listeners = {}; const listeners = this._listeners; if ( listeners[ type ] === undefined ) { listeners[ type ] = []; } if ( listeners[ type ].indexOf( listener ) === - 1 ) { listeners[ type ].push( listener ); } } hasEventListener( type, listener ) { if ( this._listeners === undefined ) return false; const listeners = this._listeners; return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; } removeEventListener( type, listener ) { if ( this._listeners === undefined ) return; const listeners = this._listeners; const listenerArray = listeners[ type ]; if ( listenerArray !== undefined ) { const index = listenerArray.indexOf( listener ); if ( index !== - 1 ) { listenerArray.splice( index, 1 ); } } } dispatchEvent( event ) { if ( this._listeners === undefined ) return; const listeners = this._listeners; const listenerArray = listeners[ event.type ]; if ( listenerArray !== undefined ) { event.target = this; // Make a copy, in case listeners are removed while iterating. const array = listenerArray.slice( 0 ); for ( let i = 0, l = array.length; i < l; i ++ ) { array[ i ].call( this, event ); } event.target = null; } } } const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; let _seed = 1234567; const DEG2RAD = Math.PI / 180; const RAD2DEG = 180 / Math.PI; // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 function generateUUID() { const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; const d3 = Math.random() * 0xffffffff | 0; const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; // .toLowerCase() here flattens concatenated strings to save heap memory space. return uuid.toLowerCase(); } function clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } // compute euclidean modulo of m % n // https://en.wikipedia.org/wiki/Modulo_operation function euclideanModulo( n, m ) { return ( ( n % m ) + m ) % m; } // Linear mapping from range <a1, a2> to range <b1, b2> function mapLinear( x, a1, a2, b1, b2 ) { return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); } // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ function inverseLerp( x, y, value ) { if ( x !== y ) { return ( value - x ) / ( y - x ); } else { return 0; } } // https://en.wikipedia.org/wiki/Linear_interpolation function lerp( x, y, t ) { return ( 1 - t ) * x + t * y; } // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ function damp( x, y, lambda, dt ) { return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); } // https://www.desmos.com/calculator/vcsjnyz7x4 function pingpong( x, length = 1 ) { return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); } // http://en.wikipedia.org/wiki/Smoothstep function smoothstep( x, min, max ) { if ( x <= min ) return 0; if ( x >= max ) return 1; x = ( x - min ) / ( max - min ); return x * x * ( 3 - 2 * x ); } function smootherstep( x, min, max ) { if ( x <= min ) return 0; if ( x >= max ) return 1; x = ( x - min ) / ( max - min ); return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); } // Random integer from <low, high> interval function randInt( low, high ) { return low + Math.floor( Math.random() * ( high - low + 1 ) ); } // Random float from <low, high> interval function randFloat( low, high ) { return low + Math.random() * ( high - low ); } // Random float from <-range/2, range/2> interval function randFloatSpread( range ) { return range * ( 0.5 - Math.random() ); } // Deterministic pseudo-random float in the interval [ 0, 1 ] function seededRandom( s ) { if ( s !== undefined ) _seed = s; // Mulberry32 generator let t = _seed += 0x6D2B79F5; t = Math.imul( t ^ t >>> 15, t | 1 ); t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; } function degToRad( degrees ) { return degrees * DEG2RAD; } function radToDeg( radians ) { return radians * RAD2DEG; } function isPowerOfTwo( value ) { return ( value & ( value - 1 ) ) === 0 && value !== 0; } function ceilPowerOfTwo( value ) { return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); } function floorPowerOfTwo( value ) { return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); } function setQuaternionFromProperEuler( q, a, b, c, order ) { // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles // rotations are applied to the axes in the order specified by 'order' // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' // angles are in radians const cos = Math.cos; const sin = Math.sin; const c2 = cos( b / 2 ); const s2 = sin( b / 2 ); const c13 = cos( ( a + c ) / 2 ); const s13 = sin( ( a + c ) / 2 ); const c1_3 = cos( ( a - c ) / 2 ); const s1_3 = sin( ( a - c ) / 2 ); const c3_1 = cos( ( c - a ) / 2 ); const s3_1 = sin( ( c - a ) / 2 ); switch ( order ) { case 'XYX': q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); break; case 'YZY': q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); break; case 'ZXZ': q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); break; case 'XZX': q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); break; case 'YXY': q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); break; case 'ZYZ': q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); break; default: console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); } } function denormalize( value, array ) { switch ( array.constructor ) { case Float32Array: return value; case Uint32Array: return value / 4294967295.0; case Uint16Array: return value / 65535.0; case Uint8Array: return value / 255.0; case Int32Array: return Math.max( value / 2147483647.0, - 1.0 ); case Int16Array: return Math.max( value / 32767.0, - 1.0 ); case Int8Array: return Math.max( value / 127.0, - 1.0 ); default: throw new Error( 'Invalid component type.' ); } } function normalize( value, array ) { switch ( array.constructor ) { case Float32Array: return value; case Uint32Array: return Math.round( value * 4294967295.0 ); case Uint16Array: return Math.round( value * 65535.0 ); case Uint8Array: return Math.round( value * 255.0 ); case Int32Array: return Math.round( value * 2147483647.0 ); case Int16Array: return Math.round( value * 32767.0 ); case Int8Array: return Math.round( value * 127.0 ); default: throw new Error( 'Invalid component type.' ); } } const MathUtils = { DEG2RAD: DEG2RAD, RAD2DEG: RAD2DEG, generateUUID: generateUUID, clamp: clamp, euclideanModulo: euclideanModulo, mapLinear: mapLinear, inverseLerp: inverseLerp, lerp: lerp, damp: damp, pingpong: pingpong, smoothstep: smoothstep, smootherstep: smootherstep, randInt: randInt, randFloat: randFloat, randFloatSpread: randFloatSpread, seededRandom: seededRandom, degToRad: degToRad, radToDeg: radToDeg, isPowerOfTwo: isPowerOfTwo, ceilPowerOfTwo: ceilPowerOfTwo, floorPowerOfTwo: floorPowerOfTwo, setQuaternionFromProperEuler: setQuaternionFromProperEuler, normalize: normalize, denormalize: denormalize }; class Vector2 { constructor( x = 0, y = 0 ) { Vector2.prototype.isVector2 = true; this.x = x; this.y = y; } get width() { return this.x; } set width( value ) { this.x = value; } get height() { return this.y; } set height( value ) { this.y = value; } set( x, y ) { this.x = x; this.y = y; return this; } setScalar( scalar ) { this.x = scalar; this.y = scalar; return this; } setX( x ) { this.x = x; return this; } setY( y ) { this.y = y; return this; } setComponent( index, value ) { switch ( index ) { case 0: this.x = value; break; case 1: this.y = value; break; default: throw new Error( 'index is out of range: ' + index ); } return this; } getComponent( index ) { switch ( index ) { case 0: return this.x; case 1: return this.y; default: throw new Error( 'index is out of range: ' + index ); } } clone() { return new this.constructor( this.x, this.y ); } copy( v ) { this.x = v.x; this.y = v.y; return this; } add( v ) { this.x += v.x; this.y += v.y; return this; } addScalar( s ) { this.x += s; this.y += s; return this; } addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; return this; } addScaledVector( v, s ) { this.x += v.x * s; this.y += v.y * s; return this; } sub( v ) { this.x -= v.x; this.y -= v.y; return this; } subScalar( s ) { this.x -= s; this.y -= s; return this; } subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; return this; } multiply( v ) { this.x *= v.x; this.y *= v.y; return this; } multiplyScalar( scalar ) { this.x *= scalar; this.y *= scalar; return this; } divide( v ) { this.x /= v.x; this.y /= v.y; return this; } divideScalar( scalar ) { return this.multiplyScalar( 1 / scalar ); } applyMatrix3( m ) { const x = this.x, y = this.y; const e = m.elements; this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; return this; } min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); return this; } max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); return this; } clamp( min, max ) { // assumes min < max, componentwise this.x = Math.max( min.x, Math.min( max.x, this.x ) ); this.y = Math.max( min.y, Math.min( max.y, this.y ) ); return this; } clampScalar( minVal, maxVal ) { this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); return this; } clampLength( min, max ) { const length = this.length(); return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); } floor() { this.x = Math.floor( this.x ); this.y = Math.floor( this.y ); return this; } ceil() { this.x = Math.ceil( this.x ); this.y = Math.ceil( this.y ); return this; } round() { this.x = Math.round( this.x ); this.y = Math.round( this.y ); return this; } roundToZero() { this.x = Math.trunc( this.x ); this.y = Math.trunc( this.y ); return this; } negate() { this.x = - this.x; this.y = - this.y; return this; } dot( v ) { return this.x * v.x + this.y * v.y; } cross( v ) { return this.x * v.y - this.y * v.x; } lengthSq() { return this.x * this.x + this.y * this.y; } length() { return Math.sqrt( this.x * this.x + this.y * this.y ); } manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ); } normalize() { return this.divideScalar( this.length() || 1 ); } angle() { // computes the angle in radians with respect to the positive x-axis const angle = Math.atan2( - this.y, - this.x ) + Math.PI; return angle; } angleTo( v ) { const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); if ( denominator === 0 ) return Math.PI / 2; const theta = this.dot( v ) / denominator; // clamp, to handle numerical problems return Math.acos( clamp( theta, - 1, 1 ) ); } distanceTo( v ) { return Math.sqrt( this.distanceToSquared( v ) ); } distanceToSquared( v ) { const dx = this.x - v.x, dy = this.y - v.y; return dx * dx + dy * dy; } manhattanDistanceTo( v ) { return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); } setLength( length ) { return this.normalize().multiplyScalar( length ); } lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; this.y += ( v.y - this.y ) * alpha; return this; } lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; this.y = v1.y + ( v2.y - v1.y ) * alpha; return this; } equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) ); } fromArray( array, offset = 0 ) { this.x = array[ offset ]; this.y = array[ offset + 1 ]; return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this.x; array[ offset + 1 ] = this.y; return array; } fromBufferAttribute( attribute, index ) { this.x = attribute.getX( index ); this.y = attribute.getY( index ); return this; } rotateAround( center, angle ) { const c = Math.cos( angle ), s = Math.sin( angle ); const x = this.x - center.x; const y = this.y - center.y; this.x = x * c - y * s + center.x; this.y = x * s + y * c + center.y; return this; } random() { this.x = Math.random(); this.y = Math.random(); return this; } *[ Symbol.iterator ]() { yield this.x; yield this.y; } } class Matrix3 { constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { Matrix3.prototype.isMatrix3 = true; this.elements = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; if ( n11 !== undefined ) { this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); } } set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { const te = this.elements; te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; return this; } identity() { this.set( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); return this; } copy( m ) { const te = this.elements; const me = m.elements; te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; return this; } extractBasis( xAxis, yAxis, zAxis ) { xAxis.setFromMatrix3Column( this, 0 ); yAxis.setFromMatrix3Column( this, 1 ); zAxis.setFromMatrix3Column( this, 2 ); return this; } setFromMatrix4( m ) { const me = m.elements; this.set( me[ 0 ], me[ 4 ], me[ 8 ], me[ 1 ], me[ 5 ], me[ 9 ], me[ 2 ], me[ 6 ], me[ 10 ] ); return this; } multiply( m ) { return this.multiplyMatrices( this, m ); } premultiply( m ) { return this.multiplyMatrices( m, this ); } multiplyMatrices( a, b ) { const ae = a.elements; const be = b.elements; const te = this.elements; const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; return this; } multiplyScalar( s ) { const te = this.elements; te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; return this; } determinant() { const te = this.elements; const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; } invert() { const te = this.elements, n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], t11 = n33 * n22 - n32 * n23, t12 = n32 * n13 - n33 * n12, t13 = n23 * n12 - n22 * n13, det = n11 * t11 + n21 * t12 + n31 * t13; if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); const detInv = 1 / det; te[ 0 ] = t11 * detInv; te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; te[ 3 ] = t12 * detInv; te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; te[ 6 ] = t13 * detInv; te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; return this; } transpose() { let tmp; const m = this.elements; tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; return this; } getNormalMatrix( matrix4 ) { return this.setFromMatrix4( matrix4 ).invert().transpose(); } transposeIntoArray( r ) { const m = this.elements; r[ 0 ] = m[ 0 ]; r[ 1 ] = m[ 3 ]; r[ 2 ] = m[ 6 ]; r[ 3 ] = m[ 1 ]; r[ 4 ] = m[ 4 ]; r[ 5 ] = m[ 7 ]; r[ 6 ] = m[ 2 ]; r[ 7 ] = m[ 5 ]; r[ 8 ] = m[ 8 ]; return this; } setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { const c = Math.cos( rotation ); const s = Math.sin( rotation ); this.set( sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, 0, 0, 1 ); return this; } // scale( sx, sy ) { this.premultiply( _m3.makeScale( sx, sy ) ); return this; } rotate( theta ) { this.premultiply( _m3.makeRotation( - theta ) ); return this; } translate( tx, ty ) { this.premultiply( _m3.makeTranslation( tx, ty ) ); return this; } // for 2D Transforms makeTranslation( x, y ) { if ( x.isVector2 ) { this.set( 1, 0, x.x, 0, 1, x.y, 0, 0, 1 ); } else { this.set( 1, 0, x, 0, 1, y, 0, 0, 1 ); } return this; } makeRotation( theta ) { // counterclockwise const c = Math.cos( theta ); const s = Math.sin( theta ); this.set( c, - s, 0, s, c, 0, 0, 0, 1 ); return this; } makeScale( x, y ) { this.set( x, 0, 0, 0, y, 0, 0, 0, 1 ); return this; } // equals( matrix ) { const te = this.elements; const me = matrix.elements; for ( let i = 0; i < 9; i ++ ) { if ( te[ i ] !== me[ i ] ) return false; } return true; } fromArray( array, offset = 0 ) { for ( let i = 0; i < 9; i ++ ) { this.elements[ i ] = array[ i + offset ]; } return this; } toArray( array = [], offset = 0 ) { const te = this.elements; array[ offset ] = te[ 0 ]; array[ offset + 1 ] = te[ 1 ]; array[ offset + 2 ] = te[ 2 ]; array[ offset + 3 ] = te[ 3 ]; array[ offset + 4 ] = te[ 4 ]; array[ offset + 5 ] = te[ 5 ]; array[ offset + 6 ] = te[ 6 ]; array[ offset + 7 ] = te[ 7 ]; array[ offset + 8 ] = te[ 8 ]; return array; } clone() { return new this.constructor().fromArray( this.elements ); } } const _m3 = /*@__PURE__*/ new Matrix3(); function arrayNeedsUint32( array ) { // assumes larger values usually on last for ( let i = array.length - 1; i >= 0; -- i ) { if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 } return false; } const TYPED_ARRAYS = { Int8Array: Int8Array, Uint8Array: Uint8Array, Uint8ClampedArray: Uint8ClampedArray, Int16Array: Int16Array, Uint16Array: Uint16Array, Int32Array: Int32Array, Uint32Array: Uint32Array, Float32Array: Float32Array, Float64Array: Float64Array }; function getTypedArray( type, buffer ) { return new TYPED_ARRAYS[ type ]( buffer ); } function createElementNS( name ) { return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); } function createCanvasElement() { const canvas = createElementNS( 'canvas' ); canvas.style.display = 'block'; return canvas; } const _cache = {}; function warnOnce( message ) { if ( message in _cache ) return; _cache[ message ] = true; console.warn( message ); } /** * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping * or clipping. Based on W3C specifications for sRGB and Display P3, * and ICC specifications for the D50 connection space. Values in/out * are _linear_ sRGB and _linear_ Display P3. * * Note that both sRGB and Display P3 use the sRGB transfer functions. * * Reference: * - http://www.russellcottrell.com/photo/matrixCalculator.htm */ const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( 0.8224621, 0.177538, 0.0, 0.0331941, 0.9668058, 0.0, 0.0170827, 0.0723974, 0.9105199, ); const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().set( 1.2249401, - 0.2249404, 0.0, - 0.0420569, 1.0420571, 0.0, - 0.0196376, - 0.0786361, 1.0982735 ); /** * Defines supported color spaces by transfer function and primaries, * and provides conversions to/from the Linear-sRGB reference space. */ const COLOR_SPACES = { [ LinearSRGBColorSpace ]: { transfer: LinearTransfer, primaries: Rec709Primaries, toReference: ( color ) => color, fromReference: ( color ) => color, }, [ SRGBColorSpace ]: { transfer: SRGBTransfer, primaries: Rec709Primaries, toReference: ( color ) => color.convertSRGBToLinear(), fromReference: ( color ) => color.convertLinearToSRGB(), }, [ LinearDisplayP3ColorSpace ]: { transfer: LinearTransfer, primaries: P3Primaries, toReference: ( color ) => color.applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ), }, [ DisplayP3ColorSpace ]: { transfer: SRGBTransfer, primaries: P3Primaries, toReference: ( color ) => color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ), fromReference: ( color ) => color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(), }, }; const SUPPORTED_WORKING_COLOR_SPACES = new Set( [ LinearSRGBColorSpace, LinearDisplayP3ColorSpace ] ); const ColorManagement = { enabled: true, _workingColorSpace: LinearSRGBColorSpace, get workingColorSpace() { return this._workingColorSpace; }, set workingColorSpace( colorSpace ) { if ( ! SUPPORTED_WORKING_COLOR_SPACES.has( colorSpace ) ) { throw new Error( `Unsupported working color space, "${ colorSpace }".` ); } this._workingColorSpace = colorSpace; }, convert: function ( color, sourceColorSpace, targetColorSpace ) { if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { return color; } const sourceToReference = COLOR_SPACES[ sourceColorSpace ].toReference; const targetFromReference = COLOR_SPACES[ targetColorSpace ].fromReference; return targetFromReference( sourceToReference( color ) ); }, fromWorkingColorSpace: function ( color, targetColorSpace ) { return this.convert( color, this._workingColorSpace, targetColorSpace ); }, toWorkingColorSpace: function ( color, sourceColorSpace ) { return this.convert( color, sourceColorSpace, this._workingColorSpace ); }, getPrimaries: function ( colorSpace ) { return COLOR_SPACES[ colorSpace ].primaries; }, getTransfer: function ( colorSpace ) { if ( colorSpace === NoColorSpace ) return LinearTransfer; return COLOR_SPACES[ colorSpace ].transfer; }, }; function SRGBToLinear( c ) { return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); } function LinearToSRGB( c ) { return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; } let _canvas; class ImageUtils { static getDataURL( image ) { if ( /^data:/i.test( image.src ) ) { return image.src; } if ( typeof HTMLCanvasElement === 'undefined' ) { return image.src; } let canvas; if ( image instanceof HTMLCanvasElement ) { canvas = image; } else { if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); _canvas.width = image.width; _canvas.height = image.height; const context = _canvas.getContext( '2d' ); if ( image instanceof ImageData ) { context.putImageData( image, 0, 0 ); } else { context.drawImage( image, 0, 0, image.width, image.height ); } canvas = _canvas; } if ( canvas.width > 2048 || canvas.height > 2048 ) { console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); return canvas.toDataURL( 'image/jpeg', 0.6 ); } else { return canvas.toDataURL( 'image/png' ); } } static sRGBToLinear( image ) { if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { const canvas = createElementNS( 'canvas' ); canvas.width = image.width; canvas.height = image.height; const context = canvas.getContext( '2d' ); context.drawImage( image, 0, 0, image.width, image.height ); const imageData = context.getImageData( 0, 0, image.width, image.height ); const data = imageData.data; for ( let i = 0; i < data.length; i ++ ) { data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; } context.putImageData( imageData, 0, 0 ); return canvas; } else if ( image.data ) { const data = image.data.slice( 0 ); for ( let i = 0; i < data.length; i ++ ) { if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); } else { // assuming float data[ i ] = SRGBToLinear( data[ i ] ); } } return { data: data, width: image.width, height: image.height }; } else { console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); return image; } } } let _sourceId = 0; class Source { constructor( data = null ) { this.isSource = true; Object.defineProperty( this, 'id', { value: _sourceId ++ } ); this.uuid = generateUUID(); this.data = data; this.version = 0; } set needsUpdate( value ) { if ( value === true ) this.version ++; } toJSON( meta ) { const isRootObject = ( meta === undefined || typeof meta === 'string' ); if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { return meta.images[ this.uuid ]; } const output = { uuid: this.uuid, url: '' }; const data = this.data; if ( data !== null ) { let url; if ( Array.isArray( data ) ) { // cube texture url = []; for ( let i = 0, l = data.length; i < l; i ++ ) { if ( data[ i ].isDataTexture ) { url.push( serializeImage( data[ i ].image ) ); } else { url.push( serializeImage( data[ i ] ) ); } } } else { // texture url = serializeImage( data ); } output.url = url; } if ( ! isRootObject ) { meta.images[ this.uuid ] = output; } return output; } } function serializeImage( image ) { if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { // default images return ImageUtils.getDataURL( image ); } else { if ( image.data ) { // images of DataTexture return { data: Array.from( image.data ), width: image.width, height: image.height, type: image.data.constructor.name }; } else { console.warn( 'THREE.Texture: Unable to serialize Texture.' ); return {}; } } } let _textureId = 0; class Texture extends EventDispatcher { constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { super(); this.isTexture = true; Object.defineProperty( this, 'id', { value: _textureId ++ } ); this.uuid = generateUUID(); this.name = ''; this.source = new Source( image ); this.mipmaps = []; this.mapping = mapping; this.channel = 0; this.wrapS = wrapS; this.wrapT = wrapT; this.magFilter = magFilter; this.minFilter = minFilter; this.anisotropy = anisotropy; this.format = format; this.internalFormat = null; this.type = type; this.offset = new Vector2( 0, 0 ); this.repeat = new Vector2( 1, 1 ); this.center = new Vector2( 0, 0 ); this.rotation = 0; this.matrixAutoUpdate = true; this.matrix = new Matrix3(); this.generateMipmaps = true; this.premultiplyAlpha = false; this.flipY = true; this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) if ( typeof colorSpace === 'string' ) { this.colorSpace = colorSpace; } else { // @deprecated, r152 warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); this.colorSpace = colorSpace === sRGBEncoding ? SRGBColorSpace : NoColorSpace; } this.userData = {}; this.version = 0; this.onUpdate = null; this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) } get image() { return this.source.data; } set image( value = null ) { this.source.data = value; } updateMatrix() { this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); } clone() { return new this.constructor().copy( this ); } copy( source ) { this.name = source.name; this.source = source.source; this.mipmaps = source.mipmaps.slice( 0 ); this.mapping = source.mapping; this.channel = source.channel; this.wrapS = source.wrapS; this.wrapT = source.wrapT; this.magFilter = source.magFilter; this.minFilter = source.minFilter; this.anisotropy = source.anisotropy; this.format = source.format; this.internalFormat = source.internalFormat; this.type = source.type; this.offset.copy( source.offset ); this.repeat.copy( source.repeat ); this.center.copy( source.center ); this.rotation = source.rotation; this.matrixAutoUpdate = source.matrixAutoUpdate; this.matrix.copy( source.matrix ); this.generateMipmaps = source.generateMipmaps; this.premultiplyAlpha = source.premultiplyAlpha; this.flipY = source.flipY; this.unpackAlignment = source.unpackAlignment; this.colorSpace = source.colorSpace; this.userData = JSON.parse( JSON.stringify( source.userData ) ); this.needsUpdate = true; return this; } toJSON( meta ) { const isRootObject = ( meta === undefined || typeof meta === 'string' ); if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { return meta.textures[ this.uuid ]; } const output = { metadata: { version: 4.6, type: 'Texture', generator: 'Texture.toJSON' }, uuid: this.uuid, name: this.name, image: this.source.toJSON( meta ).uuid, mapping: this.mapping, channel: this.channel, repeat: [ this.repeat.x, this.repeat.y ], offset: [ this.offset.x, this.offset.y ], center: [ this.center.x, this.center.y ], rotation: this.rotation, wrap: [ this.wrapS, this.wrapT ], format: this.format, internalFormat: this.internalFormat, type: this.type, colorSpace: this.colorSpace, minFilter: this.minFilter, magFilter: this.magFilter, anisotropy: this.anisotropy, flipY: this.flipY, generateMipmaps: this.generateMipmaps, premultiplyAlpha: this.premultiplyAlpha, unpackAlignment: this.unpackAlignment }; if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; if ( ! isRootObject ) { meta.textures[ this.uuid ] = output; } return output; } dispose() { this.dispatchEvent( { type: 'dispose' } ); } transformUv( uv ) { if ( this.mapping !== UVMapping ) return uv; uv.applyMatrix3( this.matrix ); if ( uv.x < 0 || uv.x > 1 ) { switch ( this.wrapS ) { case RepeatWrapping: uv.x = uv.x - Math.floor( uv.x ); break; case ClampToEdgeWrapping: uv.x = uv.x < 0 ? 0 : 1; break; case MirroredRepeatWrapping: if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { uv.x = Math.ceil( uv.x ) - uv.x; } else { uv.x = uv.x - Math.floor( uv.x ); } break; } } if ( uv.y < 0 || uv.y > 1 ) { switch ( this.wrapT ) { case RepeatWrapping: uv.y = uv.y - Math.floor( uv.y ); break; case ClampToEdgeWrapping: uv.y = uv.y < 0 ? 0 : 1; break; case MirroredRepeatWrapping: if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { uv.y = Math.ceil( uv.y ) - uv.y; } else { uv.y = uv.y - Math.floor( uv.y ); } break; } } if ( this.flipY ) { uv.y = 1 - uv.y; } return uv; } set needsUpdate( value ) { if ( value === true ) { this.version ++; this.source.needsUpdate = true; } } get encoding() { // @deprecated, r152 warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); return this.colorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; } set encoding( encoding ) { // @deprecated, r152 warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); this.colorSpace = encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; } } Texture.DEFAULT_IMAGE = null; Texture.DEFAULT_MAPPING = UVMapping; Texture.DEFAULT_ANISOTROPY = 1; class Vector4 { constructor( x = 0, y = 0, z = 0, w = 1 ) { Vector4.prototype.isVector4 = true; this.x = x; this.y = y; this.z = z; this.w = w; } get width() { return this.z; } set width( value ) { this.z = value; } get height() { return this.w; } set height( value ) { this.w = value; } set( x, y, z, w ) { this.x = x; this.y = y; this.z = z; this.w = w; return this; } setScalar( scalar ) { this.x = scalar; this.y = scalar; this.z = scalar; this.w = scalar; return this; } setX( x ) { this.x = x; return this; } setY( y ) { this.y = y; return this; } setZ( z ) { this.z = z; return this; } setW( w ) { this.w = w; return this; } setComponent( index, value ) { switch ( index ) { case 0: this.x = value; break; case 1: this.y = value; break; case 2: this.z = value; break; case 3: this.w = value; break; default: throw new Error( 'index is out of range: ' + index ); } return this; } getComponent( index ) { switch ( index ) { case 0: return this.x; case 1: return this.y; case 2: return this.z; case 3: return this.w; default: throw new Error( 'index is out of range: ' + index ); } } clone() { return new this.constructor( this.x, this.y, this.z, this.w ); } copy( v ) { this.x = v.x; this.y = v.y; this.z = v.z; this.w = ( v.w !== undefined ) ? v.w : 1; return this; } add( v ) { this.x += v.x; this.y += v.y; this.z += v.z; this.w += v.w; return this; } addScalar( s ) { this.x += s; this.y += s; this.z += s; this.w += s; return this; } addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; this.w = a.w + b.w; return this; } addScaledVector( v, s ) { this.x += v.x * s; this.y += v.y * s; this.z += v.z * s; this.w += v.w * s; return this; } sub( v ) { this.x -= v.x; this.y -= v.y; this.z -= v.z; this.w -= v.w; return this; } subScalar( s ) { this.x -= s; this.y -= s; this.z -= s; this.w -= s; return this; } subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; this.w = a.w - b.w; return this; } multiply( v ) { this.x *= v.x; this.y *= v.y; this.z *= v.z; this.w *= v.w; return this; } multiplyScalar( scalar ) { this.x *= scalar; this.y *= scalar; this.z *= scalar; this.w *= scalar; return this; } applyMatrix4( m ) { const x = this.x, y = this.y, z = this.z, w = this.w; const e = m.elements; this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; return this; } divideScalar( scalar ) { return this.multiplyScalar( 1 / scalar ); } setAxisAngleFromQuaternion( q ) { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm // q is assumed to be normalized this.w = 2 * Math.acos( q.w ); const s = Math.sqrt( 1 - q.w * q.w ); if ( s < 0.0001 ) { this.x = 1; this.y = 0; this.z = 0; } else { this.x = q.x / s; this.y = q.y / s; this.z = q.z / s; } return this; } setAxisAngleFromRotationMatrix( m ) { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) let angle, x, y, z; // variables for result const epsilon = 0.01, // margin to allow for rounding errors epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees te = m.elements, m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; if ( ( Math.abs( m12 - m21 ) < epsilon ) && ( Math.abs( m13 - m31 ) < epsilon ) && ( Math.abs( m23 - m32 ) < epsilon ) ) { // singularity found // first check for identity matrix which must have +1 for all terms // in leading diagonal and zero in other terms if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && ( Math.abs( m13 + m31 ) < epsilon2 ) && ( Math.abs( m23 + m32 ) < epsilon2 ) && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { // this singularity is identity matrix so angle = 0 this.set( 1, 0, 0, 0 ); return this; // zero angle, arbitrary axis } // otherwise this singularity is angle = 180 angle = Math.PI; const xx = ( m11 + 1 ) / 2; const yy = ( m22 + 1 ) / 2; const zz = ( m33 + 1 ) / 2; const xy = ( m12 + m21 ) / 4; const xz = ( m13 + m31 ) / 4; const yz = ( m23 + m32 ) / 4; if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term if ( xx < epsilon ) { x = 0; y = 0.707106781; z = 0.707106781; } else { x = Math.sqrt( xx ); y = xy / x; z = xz / x; } } else if ( yy > zz ) { // m22 is the largest diagonal term if ( yy < epsilon ) { x = 0.707106781; y = 0; z = 0.707106781; } else { y = Math.sqrt( yy ); x = xy / y; z = yz / y; } } else { // m33 is the largest diagonal term so base result on this if ( zz < epsilon ) { x = 0.707106781; y = 0.707106781; z = 0; } else { z = Math.sqrt( zz ); x = xz / z; y = yz / z; } } this.set( x, y, z, angle ); return this; // return 180 deg rotation } // as we have reached here there are no singularities so we can handle normally let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + ( m13 - m31 ) * ( m13 - m31 ) + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize if ( Math.abs( s ) < 0.001 ) s = 1; // prevent divide by zero, should not happen if matrix is orthogonal and should be // caught by singularity test above, but I've left it in just in case this.x = ( m32 - m23 ) / s; this.y = ( m13 - m31 ) / s; this.z = ( m21 - m12 ) / s; this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); return this; } min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); this.z = Math.min( this.z, v.z ); this.w = Math.min( this.w, v.w ); return this; } max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); this.z = Math.max( this.z, v.z ); this.w = Math.max( this.w, v.w ); return this; } clamp( min, max ) { // assumes min < max, componentwise this.x = Math.max( min.x, Math.min( max.x, this.x ) ); this.y = Math.max( min.y, Math.min( max.y, this.y ) ); this.z = Math.max( min.z, Math.min( max.z, this.z ) ); this.w = Math.max( min.w, Math.min( max.w, this.w ) ); return this; } clampScalar( minVal, maxVal ) { this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); return this; } clampLength( min, max ) { const length = this.length(); return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); } floor() { this.x = Math.floor( this.x ); this.y = Math.floor( this.y ); this.z = Math.floor( this.z ); this.w = Math.floor( this.w ); return this; } ceil() { this.x = Math.ceil( this.x ); this.y = Math.ceil( this.y ); this.z = Math.ceil( this.z ); this.w = Math.ceil( this.w ); return this; } round() { this.x = Math.round( this.x ); this.y = Math.round( this.y ); this.z = Math.round( this.z ); this.w = Math.round( this.w ); return this; } roundToZero() { this.x = Math.trunc( this.x ); this.y = Math.trunc( this.y ); this.z = Math.trunc( this.z ); this.w = Math.trunc( this.w ); return this; } negate() { this.x = - this.x; this.y = - this.y; this.z = - this.z; this.w = - this.w; return this; } dot( v ) { return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; } lengthSq() { return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; } length() { return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); } manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); } normalize() { return this.divideScalar( this.length() || 1 ); } setLength( length ) { return this.normalize().multiplyScalar( length ); } lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; this.y += ( v.y - this.y ) * alpha; this.z += ( v.z - this.z ) * alpha; this.w += ( v.w - this.w ) * alpha; return this; } lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; this.y = v1.y + ( v2.y - v1.y ) * alpha; this.z = v1.z + ( v2.z - v1.z ) * alpha; this.w = v1.w + ( v2.w - v1.w ) * alpha; return this; } equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); } fromArray( array, offset = 0 ) { this.x = array[ offset ]; this.y = array[ offset + 1 ]; this.z = array[ offset + 2 ]; this.w = array[ offset + 3 ]; return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this.x; array[ offset + 1 ] = this.y; array[ offset + 2 ] = this.z; array[ offset + 3 ] = this.w; return array; } fromBufferAttribute( attribute, index ) { this.x = attribute.getX( index ); this.y = attribute.getY( index ); this.z = attribute.getZ( index ); this.w = attribute.getW( index ); return this; } random() { this.x = Math.random(); this.y = Math.random(); this.z = Math.random(); this.w = Math.random(); return this; } *[ Symbol.iterator ]() { yield this.x; yield this.y; yield this.z; yield this.w; } } /* In options, we can specify: * Texture parameters for an auto-generated target texture * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers */ class RenderTarget extends EventDispatcher { constructor( width = 1, height = 1, options = {} ) { super(); this.isRenderTarget = true; this.width = width; this.height = height; this.depth = 1; this.scissor = new Vector4( 0, 0, width, height ); this.scissorTest = false; this.viewport = new Vector4( 0, 0, width, height ); const image = { width: width, height: height, depth: 1 }; if ( options.encoding !== undefined ) { // @deprecated, r152 warnOnce( 'THREE.WebGLRenderTarget: option.encoding has been replaced by option.colorSpace.' ); options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; } options = Object.assign( { generateMipmaps: false, internalFormat: null, minFilter: LinearFilter, depthBuffer: true, stencilBuffer: false, depthTexture: null, samples: 0 }, options ); this.texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); this.texture.isRenderTargetTexture = true; this.texture.flipY = false; this.texture.generateMipmaps = options.generateMipmaps; this.texture.internalFormat = options.internalFormat; this.depthBuffer = options.depthBuffer; this.stencilBuffer = options.stencilBuffer; this.depthTexture = options.depthTexture; this.samples = options.samples; } setSize( width, height, depth = 1 ) { if ( this.width !== width || this.height !== height || this.depth !== depth ) { this.width = width; this.height = height; this.depth = depth; this.texture.image.width = width; this.texture.image.height = height; this.texture.image.depth = depth; this.dispose(); } this.viewport.set( 0, 0, width, height ); this.scissor.set( 0, 0, width, height ); } clone() { return new this.constructor().copy( this ); } copy( source ) { this.width = source.width; this.height = source.height; this.depth = source.depth; this.scissor.copy( source.scissor ); this.scissorTest = source.scissorTest; this.viewport.copy( source.viewport ); this.texture = source.texture.clone(); this.texture.isRenderTargetTexture = true; // ensure image object is not shared, see #20328 const image = Object.assign( {}, source.texture.image ); this.texture.source = new Source( image ); this.depthBuffer = source.depthBuffer; this.stencilBuffer = source.stencilBuffer; if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); this.samples = source.samples; return this; } dispose() { this.dispatchEvent( { type: 'dispose' } ); } } class WebGLRenderTarget extends RenderTarget { constructor( width = 1, height = 1, options = {} ) { super( width, height, options ); this.isWebGLRenderTarget = true; } } class DataArrayTexture extends Texture { constructor( data = null, width = 1, height = 1, depth = 1 ) { super( null ); this.isDataArrayTexture = true; this.image = { data, width, height, depth }; this.magFilter = NearestFilter; this.minFilter = NearestFilter; this.wrapR = ClampToEdgeWrapping; this.generateMipmaps = false; this.flipY = false; this.unpackAlignment = 1; } } class WebGLArrayRenderTarget extends WebGLRenderTarget { constructor( width = 1, height = 1, depth = 1, options = {} ) { super( width, height, options ); this.isWebGLArrayRenderTarget = true; this.depth = depth; this.texture = new DataArrayTexture( null, width, height, depth ); this.texture.isRenderTargetTexture = true; } } class Data3DTexture extends Texture { constructor( data = null, width = 1, height = 1, depth = 1 ) { // We're going to add .setXXX() methods for setting properties later. // Users can still set in DataTexture3D directly. // // const texture = new THREE.DataTexture3D( data, width, height, depth ); // texture.anisotropy = 16; // // See #14839 super( null ); this.isData3DTexture = true; this.image = { data, width, height, depth }; this.magFilter = NearestFilter; this.minFilter = NearestFilter; this.wrapR = ClampToEdgeWrapping; this.generateMipmaps = false; this.flipY = false; this.unpackAlignment = 1; } } class WebGL3DRenderTarget extends WebGLRenderTarget { constructor( width = 1, height = 1, depth = 1, options = {} ) { super( width, height, options ); this.isWebGL3DRenderTarget = true; this.depth = depth; this.texture = new Data3DTexture( null, width, height, depth ); this.texture.isRenderTargetTexture = true; } } class WebGLMultipleRenderTargets extends WebGLRenderTarget { constructor( width = 1, height = 1, count = 1, options = {} ) { super( width, height, options ); this.isWebGLMultipleRenderTargets = true; const texture = this.texture; this.texture = []; for ( let i = 0; i < count; i ++ ) { this.texture[ i ] = texture.clone(); this.texture[ i ].isRenderTargetTexture = true; } } setSize( width, height, depth = 1 ) { if ( this.width !== width || this.height !== height || this.depth !== depth ) { this.width = width; this.height = height; this.depth = depth; for ( let i = 0, il = this.texture.length; i < il; i ++ ) { this.texture[ i ].image.width = width; this.texture[ i ].image.height = height; this.texture[ i ].image.depth = depth; } this.dispose(); } this.viewport.set( 0, 0, width, height ); this.scissor.set( 0, 0, width, height ); } copy( source ) { this.dispose(); this.width = source.width; this.height = source.height; this.depth = source.depth; this.scissor.copy( source.scissor ); this.scissorTest = source.scissorTest; this.viewport.copy( source.viewport ); this.depthBuffer = source.depthBuffer; this.stencilBuffer = source.stencilBuffer; if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); this.texture.length = 0; for ( let i = 0, il = source.texture.length; i < il; i ++ ) { this.texture[ i ] = source.texture[ i ].clone(); this.texture[ i ].isRenderTargetTexture = true; } return this; } } class Quaternion { constructor( x = 0, y = 0, z = 0, w = 1 ) { this.isQuaternion = true; this._x = x; this._y = y; this._z = z; this._w = w; } static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { // fuzz-free, array-based Quaternion SLERP operation let x0 = src0[ srcOffset0 + 0 ], y0 = src0[ srcOffset0 + 1 ], z0 = src0[ srcOffset0 + 2 ], w0 = src0[ srcOffset0 + 3 ]; const x1 = src1[ srcOffset1 + 0 ], y1 = src1[ srcOffset1 + 1 ], z1 = src1[ srcOffset1 + 2 ], w1 = src1[ srcOffset1 + 3 ]; if ( t === 0 ) { dst[ dstOffset + 0 ] = x0; dst[ dstOffset + 1 ] = y0; dst[ dstOffset + 2 ] = z0; dst[ dstOffset + 3 ] = w0; return; } if ( t === 1 ) { dst[ dstOffset + 0 ] = x1; dst[ dstOffset + 1 ] = y1; dst[ dstOffset + 2 ] = z1; dst[ dstOffset + 3 ] = w1; return; } if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { let s = 1 - t; const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, dir = ( cos >= 0 ? 1 : - 1 ), sqrSin = 1 - cos * cos; // Skip the Slerp for tiny steps to avoid numeric problems: if ( sqrSin > Number.EPSILON ) { const sin = Math.sqrt( sqrSin ), len = Math.atan2( sin, cos * dir ); s = Math.sin( s * len ) / sin; t = Math.sin( t * len ) / sin; } const tDir = t * dir; x0 = x0 * s + x1 * tDir; y0 = y0 * s + y1 * tDir; z0 = z0 * s + z1 * tDir; w0 = w0 * s + w1 * tDir; // Normalize in case we just did a lerp: if ( s === 1 - t ) { const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); x0 *= f; y0 *= f; z0 *= f; w0 *= f; } } dst[ dstOffset ] = x0; dst[ dstOffset + 1 ] = y0; dst[ dstOffset + 2 ] = z0; dst[ dstOffset + 3 ] = w0; } static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { const x0 = src0[ srcOffset0 ]; const y0 = src0[ srcOffset0 + 1 ]; const z0 = src0[ srcOffset0 + 2 ]; const w0 = src0[ srcOffset0 + 3 ]; const x1 = src1[ srcOffset1 ]; const y1 = src1[ srcOffset1 + 1 ]; const z1 = src1[ srcOffset1 + 2 ]; const w1 = src1[ srcOffset1 + 3 ]; dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; return dst; } get x() { return this._x; } set x( value ) { this._x = value; this._onChangeCallback(); } get y() { return this._y; } set y( value ) { this._y = value; this._onChangeCallback(); } get z() { return this._z; } set z( value ) { this._z = value; this._onChangeCallback(); } get w() { return this._w; } set w( value ) { this._w = value; this._onChangeCallback(); } set( x, y, z, w ) { this._x = x; this._y = y; this._z = z; this._w = w; this._onChangeCallback(); return this; } clone() { return new this.constructor( this._x, this._y, this._z, this._w ); } copy( quaternion ) { this._x = quaternion.x; this._y = quaternion.y; this._z = quaternion.z; this._w = quaternion.w; this._onChangeCallback(); return this; } setFromEuler( euler, update = true ) { const x = euler._x, y = euler._y, z = euler._z, order = euler._order; // http://www.mathworks.com/matlabcentral/fileexchange/ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ // content/SpinCalc.m const cos = Math.cos; const sin = Math.sin; const c1 = cos( x / 2 ); const c2 = cos( y / 2 ); const c3 = cos( z / 2 ); const s1 = sin( x / 2 ); const s2 = sin( y / 2 ); const s3 = sin( z / 2 ); switch ( order ) { case 'XYZ': this._x = s1 * c2 * c3 + c1 * s2 * s3; this._y = c1 * s2 * c3 - s1 * c2 * s3; this._z = c1 * c2 * s3 + s1 * s2 * c3; this._w = c1 * c2 * c3 - s1 * s2 * s3; break; case 'YXZ': this._x = s1 * c2 * c3 + c1 * s2 * s3; this._y = c1 * s2 * c3 - s1 * c2 * s3; this._z = c1 * c2 * s3 - s1 * s2 * c3; this._w = c1 * c2 * c3 + s1 * s2 * s3; break; case 'ZXY': this._x = s1 * c2 * c3 - c1 * s2 * s3; this._y = c1 * s2 * c3 + s1 * c2 * s3; this._z = c1 * c2 * s3 + s1 * s2 * c3; this._w = c1 * c2 * c3 - s1 * s2 * s3; break; case 'ZYX': this._x = s1 * c2 * c3 - c1 * s2 * s3; this._y = c1 * s2 * c3 + s1 * c2 * s3; this._z = c1 * c2 * s3 - s1 * s2 * c3; this._w = c1 * c2 * c3 + s1 * s2 * s3; break; case 'YZX': this._x = s1 * c2 * c3 + c1 * s2 * s3; this._y = c1 * s2 * c3 + s1 * c2 * s3; this._z = c1 * c2 * s3 - s1 * s2 * c3; this._w = c1 * c2 * c3 - s1 * s2 * s3; break; case 'XZY': this._x = s1 * c2 * c3 - c1 * s2 * s3; this._y = c1 * s2 * c3 - s1 * c2 * s3; this._z = c1 * c2 * s3 + s1 * s2 * c3; this._w = c1 * c2 * c3 + s1 * s2 * s3; break; default: console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); } if ( update === true ) this._onChangeCallback(); return this; } setFromAxisAngle( axis, angle ) { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm // assumes axis is normalized const halfAngle = angle / 2, s = Math.sin( halfAngle ); this._x = axis.x * s; this._y = axis.y * s; this._z = axis.z * s; this._w = Math.cos( halfAngle ); this._onChangeCallback(); return this; } setFromRotationMatrix( m ) { // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) const te = m.elements, m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], trace = m11 + m22 + m33; if ( trace > 0 ) { const s = 0.5 / Math.sqrt( trace + 1.0 ); this._w = 0.25 / s; this._x = ( m32 - m23 ) * s; this._y = ( m13 - m31 ) * s; this._z = ( m21 - m12 ) * s; } else if ( m11 > m22 && m11 > m33 ) { const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); this._w = ( m32 - m23 ) / s; this._x = 0.25 * s; this._y = ( m12 + m21 ) / s; this._z = ( m13 + m31 ) / s; } else if ( m22 > m33 ) { const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); this._w = ( m13 - m31 ) / s; this._x = ( m12 + m21 ) / s; this._y = 0.25 * s; this._z = ( m23 + m32 ) / s; } else { const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); this._w = ( m21 - m12 ) / s; this._x = ( m13 + m31 ) / s; this._y = ( m23 + m32 ) / s; this._z = 0.25 * s; } this._onChangeCallback(); return this; } setFromUnitVectors( vFrom, vTo ) { // assumes direction vectors vFrom and vTo are normalized let r = vFrom.dot( vTo ) + 1; if ( r < Number.EPSILON ) { // vFrom and vTo point in opposite directions r = 0; if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { this._x = - vFrom.y; this._y = vFrom.x; this._z = 0; this._w = r; } else { this._x = 0; this._y = - vFrom.z; this._z = vFrom.y; this._w = r; } } else { // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; this._w = r; } return this.normalize(); } angleTo( q ) { return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); } rotateTowards( q, step ) { const angle = this.angleTo( q ); if ( angle === 0 ) return this; const t = Math.min( 1, step / angle ); this.slerp( q, t ); return this; } identity() { return this.set( 0, 0, 0, 1 ); } invert() { // quaternion is assumed to have unit length return this.conjugate(); } conjugate() { this._x *= - 1; this._y *= - 1; this._z *= - 1; this._onChangeCallback(); return this; } dot( v ) { return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; } lengthSq() { return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; } length() { return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); } normalize() { let l = this.length(); if ( l === 0 ) { this._x = 0; this._y = 0; this._z = 0; this._w = 1; } else { l = 1 / l; this._x = this._x * l; this._y = this._y * l; this._z = this._z * l; this._w = this._w * l; } this._onChangeCallback(); return this; } multiply( q ) { return this.multiplyQuaternions( this, q ); } premultiply( q ) { return this.multiplyQuaternions( q, this ); } multiplyQuaternions( a, b ) { // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; this._onChangeCallback(); return this; } slerp( qb, t ) { if ( t === 0 ) return this; if ( t === 1 ) return this.copy( qb ); const x = this._x, y = this._y, z = this._z, w = this._w; // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; if ( cosHalfTheta < 0 ) { this._w = - qb._w; this._x = - qb._x; this._y = - qb._y; this._z = - qb._z; cosHalfTheta = - cosHalfTheta; } else { this.copy( qb ); } if ( cosHalfTheta >= 1.0 ) { this._w = w; this._x = x; this._y = y; this._z = z; return this; } const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; if ( sqrSinHalfTheta <= Number.EPSILON ) { const s = 1 - t; this._w = s * w + t * this._w; this._x = s * x + t * this._x; this._y = s * y + t * this._y; this._z = s * z + t * this._z; this.normalize(); // normalize calls _onChangeCallback() return this; } const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; this._w = ( w * ratioA + this._w * ratioB ); this._x = ( x * ratioA + this._x * ratioB ); this._y = ( y * ratioA + this._y * ratioB ); this._z = ( z * ratioA + this._z * ratioB ); this._onChangeCallback(); return this; } slerpQuaternions( qa, qb, t ) { return this.copy( qa ).slerp( qb, t ); } random() { // Derived from http://planning.cs.uiuc.edu/node198.html // Note, this source uses w, x, y, z ordering, // so we swap the order below. const u1 = Math.random(); const sqrt1u1 = Math.sqrt( 1 - u1 ); const sqrtu1 = Math.sqrt( u1 ); const u2 = 2 * Math.PI * Math.random(); const u3 = 2 * Math.PI * Math.random(); return this.set( sqrt1u1 * Math.cos( u2 ), sqrtu1 * Math.sin( u3 ), sqrtu1 * Math.cos( u3 ), sqrt1u1 * Math.sin( u2 ), ); } equals( quaternion ) { return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); } fromArray( array, offset = 0 ) { this._x = array[ offset ]; this._y = array[ offset + 1 ]; this._z = array[ offset + 2 ]; this._w = array[ offset + 3 ]; this._onChangeCallback(); return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this._x; array[ offset + 1 ] = this._y; array[ offset + 2 ] = this._z; array[ offset + 3 ] = this._w; return array; } fromBufferAttribute( attribute, index ) { this._x = attribute.getX( index ); this._y = attribute.getY( index ); this._z = attribute.getZ( index ); this._w = attribute.getW( index ); this._onChangeCallback(); return this; } toJSON() { return this.toArray(); } _onChange( callback ) { this._onChangeCallback = callback; return this; } _onChangeCallback() {} *[ Symbol.iterator ]() { yield this._x; yield this._y; yield this._z; yield this._w; } } class Vector3 { constructor( x = 0, y = 0, z = 0 ) { Vector3.prototype.isVector3 = true; this.x = x; this.y = y; this.z = z; } set( x, y, z ) { if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) this.x = x; this.y = y; this.z = z; return this; } setScalar( scalar ) { this.x = scalar; this.y = scalar; this.z = scalar; return this; } setX( x ) { this.x = x; return this; } setY( y ) { this.y = y; return this; } setZ( z ) { this.z = z; return this; } setComponent( index, value ) { switch ( index ) { case 0: this.x = value; break; case 1: this.y = value; break; case 2: this.z = value; break; default: throw new Error( 'index is out of range: ' + index ); } return this; } getComponent( index ) { switch ( index ) { case 0: return this.x; case 1: return this.y; case 2: return this.z; default: throw new Error( 'index is out of range: ' + index ); } } clone() { return new this.constructor( this.x, this.y, this.z ); } copy( v ) { this.x = v.x; this.y = v.y; this.z = v.z; return this; } add( v ) { this.x += v.x; this.y += v.y; this.z += v.z; return this; } addScalar( s ) { this.x += s; this.y += s; this.z += s; return this; } addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; return this; } addScaledVector( v, s ) { this.x += v.x * s; this.y += v.y * s; this.z += v.z * s; return this; } sub( v ) { this.x -= v.x; this.y -= v.y; this.z -= v.z; return this; } subScalar( s ) { this.x -= s; this.y -= s; this.z -= s; return this; } subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; return this; } multiply( v ) { this.x *= v.x; this.y *= v.y; this.z *= v.z; return this; } multiplyScalar( scalar ) { this.x *= scalar; this.y *= scalar; this.z *= scalar; return this; } multiplyVectors( a, b ) { this.x = a.x * b.x; this.y = a.y * b.y; this.z = a.z * b.z; return this; } applyEuler( euler ) { return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); } applyAxisAngle( axis, angle ) { return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); } applyMatrix3( m ) { const x = this.x, y = this.y, z = this.z; const e = m.elements; this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; return this; } applyNormalMatrix( m ) { return this.applyMatrix3( m ).normalize(); } applyMatrix4( m ) { const x = this.x, y = this.y, z = this.z; const e = m.elements; const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; return this; } applyQuaternion( q ) { // quaternion q is assumed to have unit length const vx = this.x, vy = this.y, vz = this.z; const qx = q.x, qy = q.y, qz = q.z, qw = q.w; // t = 2 * cross( q.xyz, v ); const tx = 2 * ( qy * vz - qz * vy ); const ty = 2 * ( qz * vx - qx * vz ); const tz = 2 * ( qx * vy - qy * vx ); // v + q.w * t + cross( q.xyz, t ); this.x = vx + qw * tx + qy * tz - qz * ty; this.y = vy + qw * ty + qz * tx - qx * tz; this.z = vz + qw * tz + qx * ty - qy * tx; return this; } project( camera ) { return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); } unproject( camera ) { return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); } transformDirection( m ) { // input: THREE.Matrix4 affine matrix // vector interpreted as a direction const x = this.x, y = this.y, z = this.z; const e = m.elements; this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; return this.normalize(); } divide( v ) { this.x /= v.x; this.y /= v.y; this.z /= v.z; return this; } divideScalar( scalar ) { return this.multiplyScalar( 1 / scalar ); } min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); this.z = Math.min( this.z, v.z ); return this; } max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); this.z = Math.max( this.z, v.z ); return this; } clamp( min, max ) { // assumes min < max, componentwise this.x = Math.max( min.x, Math.min( max.x, this.x ) ); this.y = Math.max( min.y, Math.min( max.y, this.y ) ); this.z = Math.max( min.z, Math.min( max.z, this.z ) ); return this; } clampScalar( minVal, maxVal ) { this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); return this; } clampLength( min, max ) { const length = this.length(); return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); } floor() { this.x = Math.floor( this.x ); this.y = Math.floor( this.y ); this.z = Math.floor( this.z ); return this; } ceil() { this.x = Math.ceil( this.x ); this.y = Math.ceil( this.y ); this.z = Math.ceil( this.z ); return this; } round() { this.x = Math.round( this.x ); this.y = Math.round( this.y ); this.z = Math.round( this.z ); return this; } roundToZero() { this.x = Math.trunc( this.x ); this.y = Math.trunc( this.y ); this.z = Math.trunc( this.z ); return this; } negate() { this.x = - this.x; this.y = - this.y; this.z = - this.z; return this; } dot( v ) { return this.x * v.x + this.y * v.y + this.z * v.z; } // TODO lengthSquared? lengthSq() { return this.x * this.x + this.y * this.y + this.z * this.z; } length() { return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); } manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); } normalize() { return this.divideScalar( this.length() || 1 ); } setLength( length ) { return this.normalize().multiplyScalar( length ); } lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; this.y += ( v.y - this.y ) * alpha; this.z += ( v.z - this.z ) * alpha; return this; } lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; this.y = v1.y + ( v2.y - v1.y ) * alpha; this.z = v1.z + ( v2.z - v1.z ) * alpha; return this; } cross( v ) { return this.crossVectors( this, v ); } crossVectors( a, b ) { const ax = a.x, ay = a.y, az = a.z; const bx = b.x, by = b.y, bz = b.z; this.x = ay * bz - az * by; this.y = az * bx - ax * bz; this.z = ax * by - ay * bx; return this; } projectOnVector( v ) { const denominator = v.lengthSq(); if ( denominator === 0 ) return this.set( 0, 0, 0 ); const scalar = v.dot( this ) / denominator; return this.copy( v ).multiplyScalar( scalar ); } projectOnPlane( planeNormal ) { _vector$c.copy( this ).projectOnVector( planeNormal ); return this.sub( _vector$c ); } reflect( normal ) { // reflect incident vector off plane orthogonal to normal // normal is assumed to have unit length return this.sub( _vector$c.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); } angleTo( v ) { const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); if ( denominator === 0 ) return Math.PI / 2; const theta = this.dot( v ) / denominator; // clamp, to handle numerical problems return Math.acos( clamp( theta, - 1, 1 ) ); } distanceTo( v ) { return Math.sqrt( this.distanceToSquared( v ) ); } distanceToSquared( v ) { const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; return dx * dx + dy * dy + dz * dz; } manhattanDistanceTo( v ) { return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); } setFromSpherical( s ) { return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); } setFromSphericalCoords( radius, phi, theta ) { const sinPhiRadius = Math.sin( phi ) * radius; this.x = sinPhiRadius * Math.sin( theta ); this.y = Math.cos( phi ) * radius; this.z = sinPhiRadius * Math.cos( theta ); return this; } setFromCylindrical( c ) { return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); } setFromCylindricalCoords( radius, theta, y ) { this.x = radius * Math.sin( theta ); this.y = y; this.z = radius * Math.cos( theta ); return this; } setFromMatrixPosition( m ) { const e = m.elements; this.x = e[ 12 ]; this.y = e[ 13 ]; this.z = e[ 14 ]; return this; } setFromMatrixScale( m ) { const sx = this.setFromMatrixColumn( m, 0 ).length(); const sy = this.setFromMatrixColumn( m, 1 ).length(); const sz = this.setFromMatrixColumn( m, 2 ).length(); this.x = sx; this.y = sy; this.z = sz; return this; } setFromMatrixColumn( m, index ) { return this.fromArray( m.elements, index * 4 ); } setFromMatrix3Column( m, index ) { return this.fromArray( m.elements, index * 3 ); } setFromEuler( e ) { this.x = e._x; this.y = e._y; this.z = e._z; return this; } setFromColor( c ) { this.x = c.r; this.y = c.g; this.z = c.b; return this; } equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); } fromArray( array, offset = 0 ) { this.x = array[ offset ]; this.y = array[ offset + 1 ]; this.z = array[ offset + 2 ]; return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this.x; array[ offset + 1 ] = this.y; array[ offset + 2 ] = this.z; return array; } fromBufferAttribute( attribute, index ) { this.x = attribute.getX( index ); this.y = attribute.getY( index ); this.z = attribute.getZ( index ); return this; } random() { this.x = Math.random(); this.y = Math.random(); this.z = Math.random(); return this; } randomDirection() { // Derived from https://mathworld.wolfram.com/SpherePointPicking.html const u = ( Math.random() - 0.5 ) * 2; const t = Math.random() * Math.PI * 2; const f = Math.sqrt( 1 - u ** 2 ); this.x = f * Math.cos( t ); this.y = f * Math.sin( t ); this.z = u; return this; } *[ Symbol.iterator ]() { yield this.x; yield this.y; yield this.z; } } const _vector$c = /*@__PURE__*/ new Vector3(); const _quaternion$4 = /*@__PURE__*/ new Quaternion(); class Box3 { constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { this.isBox3 = true; this.min = min; this.max = max; } set( min, max ) { this.min.copy( min ); this.max.copy( max ); return this; } setFromArray( array ) { this.makeEmpty(); for ( let i = 0, il = array.length; i < il; i += 3 ) { this.expandByPoint( _vector$b.fromArray( array, i ) ); } return this; } setFromBufferAttribute( attribute ) { this.makeEmpty(); for ( let i = 0, il = attribute.count; i < il; i ++ ) { this.expandByPoint( _vector$b.fromBufferAttribute( attribute, i ) ); } return this; } setFromPoints( points ) { this.makeEmpty(); for ( let i = 0, il = points.length; i < il; i ++ ) { this.expandByPoint( points[ i ] ); } return this; } setFromCenterAndSize( center, size ) { const halfSize = _vector$b.copy( size ).multiplyScalar( 0.5 ); this.min.copy( center ).sub( halfSize ); this.max.copy( center ).add( halfSize ); return this; } setFromObject( object, precise = false ) { this.makeEmpty(); return this.expandByObject( object, precise ); } clone() { return new this.constructor().copy( this ); } copy( box ) { this.min.copy( box.min ); this.max.copy( box.max ); return this; } makeEmpty() { this.min.x = this.min.y = this.min.z = + Infinity; this.max.x = this.max.y = this.max.z = - Infinity; return this; } isEmpty() { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); } getCenter( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } getSize( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); } expandByPoint( point ) { this.min.min( point ); this.max.max( point ); return this; } expandByVector( vector ) { this.min.sub( vector ); this.max.add( vector ); return this; } expandByScalar( scalar ) { this.min.addScalar( - scalar ); this.max.addScalar( scalar ); return this; } expandByObject( object, precise = false ) { // Computes the world-axis-aligned bounding box of an object (including its children), // accounting for both the object's, and children's, world transforms object.updateWorldMatrix( false, false ); const geometry = object.geometry; if ( geometry !== undefined ) { const positionAttribute = geometry.getAttribute( 'position' ); // precise AABB computation based on vertex data requires at least a position attribute. // instancing isn't supported so far and uses the normal (conservative) code path. if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { if ( object.isMesh === true ) { object.getVertexPosition( i, _vector$b ); } else { _vector$b.fromBufferAttribute( positionAttribute, i ); } _vector$b.applyMatrix4( object.matrixWorld ); this.expandByPoint( _vector$b ); } } else { if ( object.boundingBox !== undefined ) { // object-level bounding box if ( object.boundingBox === null ) { object.computeBoundingBox(); } _box$4.copy( object.boundingBox ); } else { // geometry-level bounding box if ( geometry.boundingBox === null ) { geometry.computeBoundingBox(); } _box$4.copy( geometry.boundingBox ); } _box$4.applyMatrix4( object.matrixWorld ); this.union( _box$4 ); } } const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { this.expandByObject( children[ i ], precise ); } return this; } containsPoint( point ) { return point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y || point.z < this.min.z || point.z > this.max.z ? false : true; } containsBox( box ) { return this.min.x <= box.min.x && box.max.x <= this.max.x && this.min.y <= box.min.y && box.max.y <= this.max.y && this.min.z <= box.min.z && box.max.z <= this.max.z; } getParameter( point, target ) { // This can potentially have a divide by zero if the box // has a size dimension of 0. return target.set( ( point.x - this.min.x ) / ( this.max.x - this.min.x ), ( point.y - this.min.y ) / ( this.max.y - this.min.y ), ( point.z - this.min.z ) / ( this.max.z - this.min.z ) ); } intersectsBox( box ) { // using 6 splitting planes to rule out intersections. return box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y || box.max.z < this.min.z || box.min.z > this.max.z ? false : true; } intersectsSphere( sphere ) { // Find the point on the AABB closest to the sphere center. this.clampPoint( sphere.center, _vector$b ); // If that point is inside the sphere, the AABB and sphere intersect. return _vector$b.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); } intersectsPlane( plane ) { // We compute the minimum and maximum dot product values. If those values // are on the same side (back or front) of the plane, then there is no intersection. let min, max; if ( plane.normal.x > 0 ) { min = plane.normal.x * this.min.x; max = plane.normal.x * this.max.x; } else { min = plane.normal.x * this.max.x; max = plane.normal.x * this.min.x; } if ( plane.normal.y > 0 ) { min += plane.normal.y * this.min.y; max += plane.normal.y * this.max.y; } else { min += plane.normal.y * this.max.y; max += plane.normal.y * this.min.y; } if ( plane.normal.z > 0 ) { min += plane.normal.z * this.min.z; max += plane.normal.z * this.max.z; } else { min += plane.normal.z * this.max.z; max += plane.normal.z * this.min.z; } return ( min <= - plane.constant && max >= - plane.constant ); } intersectsTriangle( triangle ) { if ( this.isEmpty() ) { return false; } // compute box center and extents this.getCenter( _center ); _extents.subVectors( this.max, _center ); // translate triangle to aabb origin _v0$2.subVectors( triangle.a, _center ); _v1$7.subVectors( triangle.b, _center ); _v2$4.subVectors( triangle.c, _center ); // compute edge vectors for triangle _f0.subVectors( _v1$7, _v0$2 ); _f1.subVectors( _v2$4, _v1$7 ); _f2.subVectors( _v0$2, _v2$4 ); // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) let axes = [ 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 ]; if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { return false; } // test 3 face normals from the aabb axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { return false; } // finally testing the face normal of the triangle // use already existing triangle edge vectors here _triangleNormal.crossVectors( _f0, _f1 ); axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; return satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ); } clampPoint( point, target ) { return target.copy( point ).clamp( this.min, this.max ); } distanceToPoint( point ) { return this.clampPoint( point, _vector$b ).distanceTo( point ); } getBoundingSphere( target ) { if ( this.isEmpty() ) { target.makeEmpty(); } else { this.getCenter( target.center ); target.radius = this.getSize( _vector$b ).length() * 0.5; } return target; } intersect( box ) { this.min.max( box.min ); this.max.min( box.max ); // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. if ( this.isEmpty() ) this.makeEmpty(); return this; } union( box ) { this.min.min( box.min ); this.max.max( box.max ); return this; } applyMatrix4( matrix ) { // transform of empty box is an empty box. if ( this.isEmpty() ) return this; // NOTE: I am using a binary pattern to specify all 2^3 combinations below _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 this.setFromPoints( _points ); return this; } translate( offset ) { this.min.add( offset ); this.max.add( offset ); return this; } equals( box ) { return box.min.equals( this.min ) && box.max.equals( this.max ); } } const _points = [ /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3(), /*@__PURE__*/ new Vector3() ]; const _vector$b = /*@__PURE__*/ new Vector3(); const _box$4 = /*@__PURE__*/ new Box3(); // triangle centered vertices const _v0$2 = /*@__PURE__*/ new Vector3(); const _v1$7 = /*@__PURE__*/ new Vector3(); const _v2$4 = /*@__PURE__*/ new Vector3(); // triangle edge vectors const _f0 = /*@__PURE__*/ new Vector3(); const _f1 = /*@__PURE__*/ new Vector3(); const _f2 = /*@__PURE__*/ new Vector3(); const _center = /*@__PURE__*/ new Vector3(); const _extents = /*@__PURE__*/ new Vector3(); const _triangleNormal = /*@__PURE__*/ new Vector3(); const _testAxis = /*@__PURE__*/ new Vector3(); function satForAxes( axes, v0, v1, v2, extents ) { for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { _testAxis.fromArray( axes, i ); // project the aabb onto the separating axis const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); // project all 3 vertices of the triangle onto the separating axis const p0 = v0.dot( _testAxis ); const p1 = v1.dot( _testAxis ); const p2 = v2.dot( _testAxis ); // actual test, basically see if either of the most extreme of the triangle points intersects r if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { // points of the projected triangle are outside the projected half-length of the aabb // the axis is separating and we can exit return false; } } return true; } const _box$3 = /*@__PURE__*/ new Box3(); const _v1$6 = /*@__PURE__*/ new Vector3(); const _v2$3 = /*@__PURE__*/ new Vector3(); class Sphere { constructor( center = new Vector3(), radius = - 1 ) { this.isSphere = true; this.center = center; this.radius = radius; } set( center, radius ) { this.center.copy( center ); this.radius = radius; return this; } setFromPoints( points, optionalCenter ) { const center = this.center; if ( optionalCenter !== undefined ) { center.copy( optionalCenter ); } else { _box$3.setFromPoints( points ).getCenter( center ); } let maxRadiusSq = 0; for ( let i = 0, il = points.length; i < il; i ++ ) { maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); } this.radius = Math.sqrt( maxRadiusSq ); return this; } copy( sphere ) { this.center.copy( sphere.center ); this.radius = sphere.radius; return this; } isEmpty() { return ( this.radius < 0 ); } makeEmpty() { this.center.set( 0, 0, 0 ); this.radius = - 1; return this; } containsPoint( point ) { return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); } distanceToPoint( point ) { return ( point.distanceTo( this.center ) - this.radius ); } intersectsSphere( sphere ) { const radiusSum = this.radius + sphere.radius; return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); } intersectsBox( box ) { return box.intersectsSphere( this ); } intersectsPlane( plane ) { return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; } clampPoint( point, target ) { const deltaLengthSq = this.center.distanceToSquared( point ); target.copy( point ); if ( deltaLengthSq > ( this.radius * this.radius ) ) { target.sub( this.center ).normalize(); target.multiplyScalar( this.radius ).add( this.center ); } return target; } getBoundingBox( target ) { if ( this.isEmpty() ) { // Empty sphere produces empty bounding box target.makeEmpty(); return target; } target.set( this.center, this.center ); target.expandByScalar( this.radius ); return target; } applyMatrix4( matrix ) { this.center.applyMatrix4( matrix ); this.radius = this.radius * matrix.getMaxScaleOnAxis(); return this; } translate( offset ) { this.center.add( offset ); return this; } expandByPoint( point ) { if ( this.isEmpty() ) { this.center.copy( point ); this.radius = 0; return this; } _v1$6.subVectors( point, this.center ); const lengthSq = _v1$6.lengthSq(); if ( lengthSq > ( this.radius * this.radius ) ) { // calculate the minimal sphere const length = Math.sqrt( lengthSq ); const delta = ( length - this.radius ) * 0.5; this.center.addScaledVector( _v1$6, delta / length ); this.radius += delta; } return this; } union( sphere ) { if ( sphere.isEmpty() ) { return this; } if ( this.isEmpty() ) { this.copy( sphere ); return this; } if ( this.center.equals( sphere.center ) === true ) { this.radius = Math.max( this.radius, sphere.radius ); } else { _v2$3.subVectors( sphere.center, this.center ).setLength( sphere.radius ); this.expandByPoint( _v1$6.copy( sphere.center ).add( _v2$3 ) ); this.expandByPoint( _v1$6.copy( sphere.center ).sub( _v2$3 ) ); } return this; } equals( sphere ) { return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); } clone() { return new this.constructor().copy( this ); } } const _vector$a = /*@__PURE__*/ new Vector3(); const _segCenter = /*@__PURE__*/ new Vector3(); const _segDir = /*@__PURE__*/ new Vector3(); const _diff = /*@__PURE__*/ new Vector3(); const _edge1 = /*@__PURE__*/ new Vector3(); const _edge2 = /*@__PURE__*/ new Vector3(); const _normal$1 = /*@__PURE__*/ new Vector3(); class Ray { constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { this.origin = origin; this.direction = direction; } set( origin, direction ) { this.origin.copy( origin ); this.direction.copy( direction ); return this; } copy( ray ) { this.origin.copy( ray.origin ); this.direction.copy( ray.direction ); return this; } at( t, target ) { return target.copy( this.origin ).addScaledVector( this.direction, t ); } lookAt( v ) { this.direction.copy( v ).sub( this.origin ).normalize(); return this; } recast( t ) { this.origin.copy( this.at( t, _vector$a ) ); return this; } closestPointToPoint( point, target ) { target.subVectors( point, this.origin ); const directionDistance = target.dot( this.direction ); if ( directionDistance < 0 ) { return target.copy( this.origin ); } return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); } distanceToPoint( point ) { return Math.sqrt( this.distanceSqToPoint( point ) ); } distanceSqToPoint( point ) { const directionDistance = _vector$a.subVectors( point, this.origin ).dot( this.direction ); // point behind the ray if ( directionDistance < 0 ) { return this.origin.distanceToSquared( point ); } _vector$a.copy( this.origin ).addScaledVector( this.direction, directionDistance ); return _vector$a.distanceToSquared( point ); } distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h // It returns the min distance between the ray and the segment // defined by v0 and v1 // It can also set two optional targets : // - The closest point on the ray // - The closest point on the segment _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); _segDir.copy( v1 ).sub( v0 ).normalize(); _diff.copy( this.origin ).sub( _segCenter ); const segExtent = v0.distanceTo( v1 ) * 0.5; const a01 = - this.direction.dot( _segDir ); const b0 = _diff.dot( this.direction ); const b1 = - _diff.dot( _segDir ); const c = _diff.lengthSq(); const det = Math.abs( 1 - a01 * a01 ); let s0, s1, sqrDist, extDet; if ( det > 0 ) { // The ray and segment are not parallel. s0 = a01 * b1 - b0; s1 = a01 * b0 - b1; extDet = segExtent * det; if ( s0 >= 0 ) { if ( s1 >= - extDet ) { if ( s1 <= extDet ) { // region 0 // Minimum at interior points of ray and segment. const invDet = 1 / det; s0 *= invDet; s1 *= invDet; sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; } else { // region 1 s1 = segExtent; s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; } } else { // region 5 s1 = - segExtent; s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; } } else { if ( s1 <= - extDet ) { // region 4 s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; } else if ( s1 <= extDet ) { // region 3 s0 = 0; s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); sqrDist = s1 * ( s1 + 2 * b1 ) + c; } else { // region 2 s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; } } } else { // Ray and segment are parallel. s1 = ( a01 > 0 ) ? - segExtent : segExtent; s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; } if ( optionalPointOnRay ) { optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); } if ( optionalPointOnSegment ) { optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); } return sqrDist; } intersectSphere( sphere, target ) { _vector$a.subVectors( sphere.center, this.origin ); const tca = _vector$a.dot( this.direction ); const d2 = _vector$a.dot( _vector$a ) - tca * tca; const radius2 = sphere.radius * sphere.radius; if ( d2 > radius2 ) return null; const thc = Math.sqrt( radius2 - d2 ); // t0 = first intersect point - entrance on front of sphere const t0 = tca - thc; // t1 = second intersect point - exit point on back of sphere const t1 = tca + thc; // test to see if t1 is behind the ray - if so, return null if ( t1 < 0 ) return null; // test to see if t0 is behind the ray: // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, // in order to always return an intersect point that is in front of the ray. if ( t0 < 0 ) return this.at( t1, target ); // else t0 is in front of the ray, so return the first collision point scaled by t0 return this.at( t0, target ); } intersectsSphere( sphere ) { return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); } distanceToPlane( plane ) { const denominator = plane.normal.dot( this.direction ); if ( denominator === 0 ) { // line is coplanar, return origin if ( plane.distanceToPoint( this.origin ) === 0 ) { return 0; } // Null is preferable to undefined since undefined means.... it is undefined return null; } const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; // Return if the ray never intersects the plane return t >= 0 ? t : null; } intersectPlane( plane, target ) { const t = this.distanceToPlane( plane ); if ( t === null ) { return null; } return this.at( t, target ); } intersectsPlane( plane ) { // check if the ray lies on the plane first const distToPoint = plane.distanceToPoint( this.origin ); if ( distToPoint === 0 ) { return true; } const denominator = plane.normal.dot( this.direction ); if ( denominator * distToPoint < 0 ) { return true; } // ray origin is behind the plane (and is pointing behind it) return false; } intersectBox( box, target ) { let tmin, tmax, tymin, tymax, tzmin, tzmax; const invdirx = 1 / this.direction.x, invdiry = 1 / this.direction.y, invdirz = 1 / this.direction.z; const origin = this.origin; if ( invdirx >= 0 ) { tmin = ( box.min.x - origin.x ) * invdirx; tmax = ( box.max.x - origin.x ) * invdirx; } else { tmin = ( box.max.x - origin.x ) * invdirx; tmax = ( box.min.x - origin.x ) * invdirx; } if ( invdiry >= 0 ) { tymin = ( box.min.y - origin.y ) * invdiry; tymax = ( box.max.y - origin.y ) * invdiry; } else { tymin = ( box.max.y - origin.y ) * invdiry; tymax = ( box.min.y - origin.y ) * invdiry; } if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; if ( invdirz >= 0 ) { tzmin = ( box.min.z - origin.z ) * invdirz; tzmax = ( box.max.z - origin.z ) * invdirz; } else { tzmin = ( box.max.z - origin.z ) * invdirz; tzmax = ( box.min.z - origin.z ) * invdirz; } if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; //return point closest to the ray (positive side) if ( tmax < 0 ) return null; return this.at( tmin >= 0 ? tmin : tmax, target ); } intersectsBox( box ) { return this.intersectBox( box, _vector$a ) !== null; } intersectTriangle( a, b, c, backfaceCulling, target ) { // Compute the offset origin, edges, and normal. // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h _edge1.subVectors( b, a ); _edge2.subVectors( c, a ); _normal$1.crossVectors( _edge1, _edge2 ); // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) let DdN = this.direction.dot( _normal$1 ); let sign; if ( DdN > 0 ) { if ( backfaceCulling ) return null; sign = 1; } else if ( DdN < 0 ) { sign = - 1; DdN = - DdN; } else { return null; } _diff.subVectors( this.origin, a ); const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); // b1 < 0, no intersection if ( DdQxE2 < 0 ) { return null; } const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); // b2 < 0, no intersection if ( DdE1xQ < 0 ) { return null; } // b1+b2 > 1, no intersection if ( DdQxE2 + DdE1xQ > DdN ) { return null; } // Line intersects triangle, check if ray does. const QdN = - sign * _diff.dot( _normal$1 ); // t < 0, no intersection if ( QdN < 0 ) { return null; } // Ray intersects triangle. return this.at( QdN / DdN, target ); } applyMatrix4( matrix4 ) { this.origin.applyMatrix4( matrix4 ); this.direction.transformDirection( matrix4 ); return this; } equals( ray ) { return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); } clone() { return new this.constructor().copy( this ); } } class Matrix4 { constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { Matrix4.prototype.isMatrix4 = true; this.elements = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; if ( n11 !== undefined ) { this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); } } set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { const te = this.elements; te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; return this; } identity() { this.set( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); return this; } clone() { return new Matrix4().fromArray( this.elements ); } copy( m ) { const te = this.elements; const me = m.elements; te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; return this; } copyPosition( m ) { const te = this.elements, me = m.elements; te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; return this; } setFromMatrix3( m ) { const me = m.elements; this.set( me[ 0 ], me[ 3 ], me[ 6 ], 0, me[ 1 ], me[ 4 ], me[ 7 ], 0, me[ 2 ], me[ 5 ], me[ 8 ], 0, 0, 0, 0, 1 ); return this; } extractBasis( xAxis, yAxis, zAxis ) { xAxis.setFromMatrixColumn( this, 0 ); yAxis.setFromMatrixColumn( this, 1 ); zAxis.setFromMatrixColumn( this, 2 ); return this; } makeBasis( xAxis, yAxis, zAxis ) { this.set( xAxis.x, yAxis.x, zAxis.x, 0, xAxis.y, yAxis.y, zAxis.y, 0, xAxis.z, yAxis.z, zAxis.z, 0, 0, 0, 0, 1 ); return this; } extractRotation( m ) { // this method does not support reflection matrices const te = this.elements; const me = m.elements; const scaleX = 1 / _v1$5.setFromMatrixColumn( m, 0 ).length(); const scaleY = 1 / _v1$5.setFromMatrixColumn( m, 1 ).length(); const scaleZ = 1 / _v1$5.setFromMatrixColumn( m, 2 ).length(); te[ 0 ] = me[ 0 ] * scaleX; te[ 1 ] = me[ 1 ] * scaleX; te[ 2 ] = me[ 2 ] * scaleX; te[ 3 ] = 0; te[ 4 ] = me[ 4 ] * scaleY; te[ 5 ] = me[ 5 ] * scaleY; te[ 6 ] = me[ 6 ] * scaleY; te[ 7 ] = 0; te[ 8 ] = me[ 8 ] * scaleZ; te[ 9 ] = me[ 9 ] * scaleZ; te[ 10 ] = me[ 10 ] * scaleZ; te[ 11 ] = 0; te[ 12 ] = 0; te[ 13 ] = 0; te[ 14 ] = 0; te[ 15 ] = 1; return this; } makeRotationFromEuler( euler ) { const te = this.elements; const x = euler.x, y = euler.y, z = euler.z; const a = Math.cos( x ), b = Math.sin( x ); const c = Math.cos( y ), d = Math.sin( y ); const e = Math.cos( z ), f = Math.sin( z ); if ( euler.order === 'XYZ' ) { const ae = a * e, af = a * f, be = b * e, bf = b * f; te[ 0 ] = c * e; te[ 4 ] = - c * f; te[ 8 ] = d; te[ 1 ] = af + be * d; te[ 5 ] = ae - bf * d; te[ 9 ] = - b * c; te[ 2 ] = bf - ae * d; te[ 6 ] = be + af * d; te[ 10 ] = a * c; } else if ( euler.order === 'YXZ' ) { const ce = c * e, cf = c * f, de = d * e, df = d * f; te[ 0 ] = ce + df * b; te[ 4 ] = de * b - cf; te[ 8 ] = a * d; te[ 1 ] = a * f; te[ 5 ] = a * e; te[ 9 ] = - b; te[ 2 ] = cf * b - de; te[ 6 ] = df + ce * b; te[ 10 ] = a * c; } else if ( euler.order === 'ZXY' ) { const ce = c * e, cf = c * f, de = d * e, df = d * f; te[ 0 ] = ce - df * b; te[ 4 ] = - a * f; te[ 8 ] = de + cf * b; te[ 1 ] = cf + de * b; te[ 5 ] = a * e; te[ 9 ] = df - ce * b; te[ 2 ] = - a * d; te[ 6 ] = b; te[ 10 ] = a * c; } else if ( euler.order === 'ZYX' ) { const ae = a * e, af = a * f, be = b * e, bf = b * f; te[ 0 ] = c * e; te[ 4 ] = be * d - af; te[ 8 ] = ae * d + bf; te[ 1 ] = c * f; te[ 5 ] = bf * d + ae; te[ 9 ] = af * d - be; te[ 2 ] = - d; te[ 6 ] = b * c; te[ 10 ] = a * c; } else if ( euler.order === 'YZX' ) { const ac = a * c, ad = a * d, bc = b * c, bd = b * d; te[ 0 ] = c * e; te[ 4 ] = bd - ac * f; te[ 8 ] = bc * f + ad; te[ 1 ] = f; te[ 5 ] = a * e; te[ 9 ] = - b * e; te[ 2 ] = - d * e; te[ 6 ] = ad * f + bc; te[ 10 ] = ac - bd * f; } else if ( euler.order === 'XZY' ) { const ac = a * c, ad = a * d, bc = b * c, bd = b * d; te[ 0 ] = c * e; te[ 4 ] = - f; te[ 8 ] = d * e; te[ 1 ] = ac * f + bd; te[ 5 ] = a * e; te[ 9 ] = ad * f - bc; te[ 2 ] = bc * f - ad; te[ 6 ] = b * e; te[ 10 ] = bd * f + ac; } // bottom row te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; // last column te[ 12 ] = 0; te[ 13 ] = 0; te[ 14 ] = 0; te[ 15 ] = 1; return this; } makeRotationFromQuaternion( q ) { return this.compose( _zero, q, _one ); } lookAt( eye, target, up ) { const te = this.elements; _z.subVectors( eye, target ); if ( _z.lengthSq() === 0 ) { // eye and target are in the same position _z.z = 1; } _z.normalize(); _x.crossVectors( up, _z ); if ( _x.lengthSq() === 0 ) { // up and z are parallel if ( Math.abs( up.z ) === 1 ) { _z.x += 0.0001; } else { _z.z += 0.0001; } _z.normalize(); _x.crossVectors( up, _z ); } _x.normalize(); _y.crossVectors( _z, _x ); te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; return this; } multiply( m ) { return this.multiplyMatrices( this, m ); } premultiply( m ) { return this.multiplyMatrices( m, this ); } multiplyMatrices( a, b ) { const ae = a.elements; const be = b.elements; const te = this.elements; const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; return this; } multiplyScalar( s ) { const te = this.elements; te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; return this; } determinant() { const te = this.elements; const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; //TODO: make this more efficient //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) return ( n41 * ( + n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34 ) + n42 * ( + n11 * n23 * n34 - n11 * n24 * n33 + n14 * n21 * n33 - n13 * n21 * n34 + n13 * n24 * n31 - n14 * n23 * n31 ) + n43 * ( + n11 * n24 * n32 - n11 * n22 * n34 - n14 * n21 * n32 + n12 * n21 * n34 + n14 * n22 * n31 - n12 * n24 * n31 ) + n44 * ( - n13 * n22 * n31 - n11 * n23 * n32 + n11 * n22 * n33 + n13 * n21 * n32 - n12 * n21 * n33 + n12 * n23 * n31 ) ); } transpose() { const te = this.elements; let tmp; tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; return this; } setPosition( x, y, z ) { const te = this.elements; if ( x.isVector3 ) { te[ 12 ] = x.x; te[ 13 ] = x.y; te[ 14 ] = x.z; } else { te[ 12 ] = x; te[ 13 ] = y; te[ 14 ] = z; } return this; } invert() { // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm const te = this.elements, n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); const detInv = 1 / det; te[ 0 ] = t11 * detInv; te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; te[ 4 ] = t12 * detInv; te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; te[ 8 ] = t13 * detInv; te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; te[ 12 ] = t14 * detInv; te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; return this; } scale( v ) { const te = this.elements; const x = v.x, y = v.y, z = v.z; te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; return this; } getMaxScaleOnAxis() { const te = this.elements; const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); } makeTranslation( x, y, z ) { if ( x.isVector3 ) { this.set( 1, 0, 0, x.x, 0, 1, 0, x.y, 0, 0, 1, x.z, 0, 0, 0, 1 ); } else { this.set( 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1 ); } return this; } makeRotationX( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); this.set( 1, 0, 0, 0, 0, c, - s, 0, 0, s, c, 0, 0, 0, 0, 1 ); return this; } makeRotationY( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); this.set( c, 0, s, 0, 0, 1, 0, 0, - s, 0, c, 0, 0, 0, 0, 1 ); return this; } makeRotationZ( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); this.set( c, - s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); return this; } makeRotationAxis( axis, angle ) { // Based on http://www.gamedev.net/reference/articles/article1199.asp const c = Math.cos( angle ); const s = Math.sin( angle ); const t = 1 - c; const x = axis.x, y = axis.y, z = axis.z; const tx = t * x, ty = t * y; this.set( tx * x + c, tx * y - s * z, tx * z + s * y, 0, tx * y + s * z, ty * y + c, ty * z - s * x, 0, tx * z - s * y, ty * z + s * x, t * z * z + c, 0, 0, 0, 0, 1 ); return this; } makeScale( x, y, z ) { this.set( x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 ); return this; } makeShear( xy, xz, yx, yz, zx, zy ) { this.set( 1, yx, zx, 0, xy, 1, zy, 0, xz, yz, 1, 0, 0, 0, 0, 1 ); return this; } compose( position, quaternion, scale ) { const te = this.elements; const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; const sx = scale.x, sy = scale.y, sz = scale.z; te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; te[ 1 ] = ( xy + wz ) * sx; te[ 2 ] = ( xz - wy ) * sx; te[ 3 ] = 0; te[ 4 ] = ( xy - wz ) * sy; te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; te[ 6 ] = ( yz + wx ) * sy; te[ 7 ] = 0; te[ 8 ] = ( xz + wy ) * sz; te[ 9 ] = ( yz - wx ) * sz; te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; te[ 11 ] = 0; te[ 12 ] = position.x; te[ 13 ] = position.y; te[ 14 ] = position.z; te[ 15 ] = 1; return this; } decompose( position, quaternion, scale ) { const te = this.elements; let sx = _v1$5.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); const sy = _v1$5.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); const sz = _v1$5.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); // if determine is negative, we need to invert one scale const det = this.determinant(); if ( det < 0 ) sx = - sx; position.x = te[ 12 ]; position.y = te[ 13 ]; position.z = te[ 14 ]; // scale the rotation part _m1$2.copy( this ); const invSX = 1 / sx; const invSY = 1 / sy; const invSZ = 1 / sz; _m1$2.elements[ 0 ] *= invSX; _m1$2.elements[ 1 ] *= invSX; _m1$2.elements[ 2 ] *= invSX; _m1$2.elements[ 4 ] *= invSY; _m1$2.elements[ 5 ] *= invSY; _m1$2.elements[ 6 ] *= invSY; _m1$2.elements[ 8 ] *= invSZ; _m1$2.elements[ 9 ] *= invSZ; _m1$2.elements[ 10 ] *= invSZ; quaternion.setFromRotationMatrix( _m1$2 ); scale.x = sx; scale.y = sy; scale.z = sz; return this; } makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { const te = this.elements; const x = 2 * near / ( right - left ); const y = 2 * near / ( top - bottom ); const a = ( right + left ) / ( right - left ); const b = ( top + bottom ) / ( top - bottom ); let c, d; if ( coordinateSystem === WebGLCoordinateSystem ) { c = - ( far + near ) / ( far - near ); d = ( - 2 * far * near ) / ( far - near ); } else if ( coordinateSystem === WebGPUCoordinateSystem ) { c = - far / ( far - near ); d = ( - far * near ) / ( far - near ); } else { throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); } te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; return this; } makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { const te = this.elements; const w = 1.0 / ( right - left ); const h = 1.0 / ( top - bottom ); const p = 1.0 / ( far - near ); const x = ( right + left ) * w; const y = ( top + bottom ) * h; let z, zInv; if ( coordinateSystem === WebGLCoordinateSystem ) { z = ( far + near ) * p; zInv = - 2 * p; } else if ( coordinateSystem === WebGPUCoordinateSystem ) { z = near * p; zInv = - 1 * p; } else { throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); } te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = zInv; te[ 14 ] = - z; te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; return this; } equals( matrix ) { const te = this.elements; const me = matrix.elements; for ( let i = 0; i < 16; i ++ ) { if ( te[ i ] !== me[ i ] ) return false; } return true; } fromArray( array, offset = 0 ) { for ( let i = 0; i < 16; i ++ ) { this.elements[ i ] = array[ i + offset ]; } return this; } toArray( array = [], offset = 0 ) { const te = this.elements; array[ offset ] = te[ 0 ]; array[ offset + 1 ] = te[ 1 ]; array[ offset + 2 ] = te[ 2 ]; array[ offset + 3 ] = te[ 3 ]; array[ offset + 4 ] = te[ 4 ]; array[ offset + 5 ] = te[ 5 ]; array[ offset + 6 ] = te[ 6 ]; array[ offset + 7 ] = te[ 7 ]; array[ offset + 8 ] = te[ 8 ]; array[ offset + 9 ] = te[ 9 ]; array[ offset + 10 ] = te[ 10 ]; array[ offset + 11 ] = te[ 11 ]; array[ offset + 12 ] = te[ 12 ]; array[ offset + 13 ] = te[ 13 ]; array[ offset + 14 ] = te[ 14 ]; array[ offset + 15 ] = te[ 15 ]; return array; } } const _v1$5 = /*@__PURE__*/ new Vector3(); const _m1$2 = /*@__PURE__*/ new Matrix4(); const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); const _x = /*@__PURE__*/ new Vector3(); const _y = /*@__PURE__*/ new Vector3(); const _z = /*@__PURE__*/ new Vector3(); const _matrix$1 = /*@__PURE__*/ new Matrix4(); const _quaternion$3 = /*@__PURE__*/ new Quaternion(); class Euler { constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { this.isEuler = true; this._x = x; this._y = y; this._z = z; this._order = order; } get x() { return this._x; } set x( value ) { this._x = value; this._onChangeCallback(); } get y() { return this._y; } set y( value ) { this._y = value; this._onChangeCallback(); } get z() { return this._z; } set z( value ) { this._z = value; this._onChangeCallback(); } get order() { return this._order; } set order( value ) { this._order = value; this._onChangeCallback(); } set( x, y, z, order = this._order ) { this._x = x; this._y = y; this._z = z; this._order = order; this._onChangeCallback(); return this; } clone() { return new this.constructor( this._x, this._y, this._z, this._order ); } copy( euler ) { this._x = euler._x; this._y = euler._y; this._z = euler._z; this._order = euler._order; this._onChangeCallback(); return this; } setFromRotationMatrix( m, order = this._order, update = true ) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) const te = m.elements; const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; switch ( order ) { case 'XYZ': this._y = Math.asin( clamp( m13, - 1, 1 ) ); if ( Math.abs( m13 ) < 0.9999999 ) { this._x = Math.atan2( - m23, m33 ); this._z = Math.atan2( - m12, m11 ); } else { this._x = Math.atan2( m32, m22 ); this._z = 0; } break; case 'YXZ': this._x = Math.asin( - clamp( m23, - 1, 1 ) ); if ( Math.abs( m23 ) < 0.9999999 ) { this._y = Math.atan2( m13, m33 ); this._z = Math.atan2( m21, m22 ); } else { this._y = Math.atan2( - m31, m11 ); this._z = 0; } break; case 'ZXY': this._x = Math.asin( clamp( m32, - 1, 1 ) ); if ( Math.abs( m32 ) < 0.9999999 ) { this._y = Math.atan2( - m31, m33 ); this._z = Math.atan2( - m12, m22 ); } else { this._y = 0; this._z = Math.atan2( m21, m11 ); } break; case 'ZYX': this._y = Math.asin( - clamp( m31, - 1, 1 ) ); if ( Math.abs( m31 ) < 0.9999999 ) { this._x = Math.atan2( m32, m33 ); this._z = Math.atan2( m21, m11 ); } else { this._x = 0; this._z = Math.atan2( - m12, m22 ); } break; case 'YZX': this._z = Math.asin( clamp( m21, - 1, 1 ) ); if ( Math.abs( m21 ) < 0.9999999 ) { this._x = Math.atan2( - m23, m22 ); this._y = Math.atan2( - m31, m11 ); } else { this._x = 0; this._y = Math.atan2( m13, m33 ); } break; case 'XZY': this._z = Math.asin( - clamp( m12, - 1, 1 ) ); if ( Math.abs( m12 ) < 0.9999999 ) { this._x = Math.atan2( m32, m22 ); this._y = Math.atan2( m13, m11 ); } else { this._x = Math.atan2( - m23, m33 ); this._y = 0; } break; default: console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); } this._order = order; if ( update === true ) this._onChangeCallback(); return this; } setFromQuaternion( q, order, update ) { _matrix$1.makeRotationFromQuaternion( q ); return this.setFromRotationMatrix( _matrix$1, order, update ); } setFromVector3( v, order = this._order ) { return this.set( v.x, v.y, v.z, order ); } reorder( newOrder ) { // WARNING: this discards revolution information -bhouston _quaternion$3.setFromEuler( this ); return this.setFromQuaternion( _quaternion$3, newOrder ); } equals( euler ) { return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); } fromArray( array ) { this._x = array[ 0 ]; this._y = array[ 1 ]; this._z = array[ 2 ]; if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; this._onChangeCallback(); return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this._x; array[ offset + 1 ] = this._y; array[ offset + 2 ] = this._z; array[ offset + 3 ] = this._order; return array; } _onChange( callback ) { this._onChangeCallback = callback; return this; } _onChangeCallback() {} *[ Symbol.iterator ]() { yield this._x; yield this._y; yield this._z; yield this._order; } } Euler.DEFAULT_ORDER = 'XYZ'; class Layers { constructor() { this.mask = 1 | 0; } set( channel ) { this.mask = ( 1 << channel | 0 ) >>> 0; } enable( channel ) { this.mask |= 1 << channel | 0; } enableAll() { this.mask = 0xffffffff | 0; } toggle( channel ) { this.mask ^= 1 << channel | 0; } disable( channel ) { this.mask &= ~ ( 1 << channel | 0 ); } disableAll() { this.mask = 0; } test( layers ) { return ( this.mask & layers.mask ) !== 0; } isEnabled( channel ) { return ( this.mask & ( 1 << channel | 0 ) ) !== 0; } } let _object3DId = 0; const _v1$4 = /*@__PURE__*/ new Vector3(); const _q1 = /*@__PURE__*/ new Quaternion(); const _m1$1 = /*@__PURE__*/ new Matrix4(); const _target = /*@__PURE__*/ new Vector3(); const _position$3 = /*@__PURE__*/ new Vector3(); const _scale$2 = /*@__PURE__*/ new Vector3(); const _quaternion$2 = /*@__PURE__*/ new Quaternion(); const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); const _addedEvent = { type: 'added' }; const _removedEvent = { type: 'removed' }; class Object3D extends EventDispatcher { constructor() { super(); this.isObject3D = true; Object.defineProperty( this, 'id', { value: _object3DId ++ } ); this.uuid = generateUUID(); this.name = ''; this.type = 'Object3D'; this.parent = null; this.children = []; this.up = Object3D.DEFAULT_UP.clone(); const position = new Vector3(); const rotation = new Euler(); const quaternion = new Quaternion(); const scale = new Vector3( 1, 1, 1 ); function onRotationChange() { quaternion.setFromEuler( rotation, false ); } function onQuaternionChange() { rotation.setFromQuaternion( quaternion, undefined, false ); } rotation._onChange( onRotationChange ); quaternion._onChange( onQuaternionChange ); Object.defineProperties( this, { position: { configurable: true, enumerable: true, value: position }, rotation: { configurable: true, enumerable: true, value: rotation }, quaternion: { configurable: true, enumerable: true, value: quaternion }, scale: { configurable: true, enumerable: true, value: scale }, modelViewMatrix: { value: new Matrix4() }, normalMatrix: { value: new Matrix3() } } ); this.matrix = new Matrix4(); this.matrixWorld = new Matrix4(); this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer this.matrixWorldNeedsUpdate = false; this.layers = new Layers(); this.visible = true; this.castShadow = false; this.receiveShadow = false; this.frustumCulled = true; this.renderOrder = 0; this.animations = []; this.userData = {}; } onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} applyMatrix4( matrix ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); this.matrix.premultiply( matrix ); this.matrix.decompose( this.position, this.quaternion, this.scale ); } applyQuaternion( q ) { this.quaternion.premultiply( q ); return this; } setRotationFromAxisAngle( axis, angle ) { // assumes axis is normalized this.quaternion.setFromAxisAngle( axis, angle ); } setRotationFromEuler( euler ) { this.quaternion.setFromEuler( euler, true ); } setRotationFromMatrix( m ) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) this.quaternion.setFromRotationMatrix( m ); } setRotationFromQuaternion( q ) { // assumes q is normalized this.quaternion.copy( q ); } rotateOnAxis( axis, angle ) { // rotate object on axis in object space // axis is assumed to be normalized _q1.setFromAxisAngle( axis, angle ); this.quaternion.multiply( _q1 ); return this; } rotateOnWorldAxis( axis, angle ) { // rotate object on axis in world space // axis is assumed to be normalized // method assumes no rotated parent _q1.setFromAxisAngle( axis, angle ); this.quaternion.premultiply( _q1 ); return this; } rotateX( angle ) { return this.rotateOnAxis( _xAxis, angle ); } rotateY( angle ) { return this.rotateOnAxis( _yAxis, angle ); } rotateZ( angle ) { return this.rotateOnAxis( _zAxis, angle ); } translateOnAxis( axis, distance ) { // translate object by distance along axis in object space // axis is assumed to be normalized _v1$4.copy( axis ).applyQuaternion( this.quaternion ); this.position.add( _v1$4.multiplyScalar( distance ) ); return this; } translateX( distance ) { return this.translateOnAxis( _xAxis, distance ); } translateY( distance ) { return this.translateOnAxis( _yAxis, distance ); } translateZ( distance ) { return this.translateOnAxis( _zAxis, distance ); } localToWorld( vector ) { this.updateWorldMatrix( true, false ); return vector.applyMatrix4( this.matrixWorld ); } worldToLocal( vector ) { this.updateWorldMatrix( true, false ); return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() ); } lookAt( x, y, z ) { // This method does not support objects having non-uniformly-scaled parent(s) if ( x.isVector3 ) { _target.copy( x ); } else { _target.set( x, y, z ); } const parent = this.parent; this.updateWorldMatrix( true, false ); _position$3.setFromMatrixPosition( this.matrixWorld ); if ( this.isCamera || this.isLight ) { _m1$1.lookAt( _position$3, _target, this.up ); } else { _m1$1.lookAt( _target, _position$3, this.up ); } this.quaternion.setFromRotationMatrix( _m1$1 ); if ( parent ) { _m1$1.extractRotation( parent.matrixWorld ); _q1.setFromRotationMatrix( _m1$1 ); this.quaternion.premultiply( _q1.invert() ); } } add( object ) { if ( arguments.length > 1 ) { for ( let i = 0; i < arguments.length; i ++ ) { this.add( arguments[ i ] ); } return this; } if ( object === this ) { console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); return this; } if ( object && object.isObject3D ) { if ( object.parent !== null ) { object.parent.remove( object ); } object.parent = this; this.children.push( object ); object.dispatchEvent( _addedEvent ); } else { console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); } return this; } remove( object ) { if ( arguments.length > 1 ) { for ( let i = 0; i < arguments.length; i ++ ) { this.remove( arguments[ i ] ); } return this; } const index = this.children.indexOf( object ); if ( index !== - 1 ) { object.parent = null; this.children.splice( index, 1 ); object.dispatchEvent( _removedEvent ); } return this; } removeFromParent() { const parent = this.parent; if ( parent !== null ) { parent.remove( this ); } return this; } clear() { return this.remove( ... this.children ); } attach( object ) { // adds object as a child of this, while maintaining the object's world transform // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) this.updateWorldMatrix( true, false ); _m1$1.copy( this.matrixWorld ).invert(); if ( object.parent !== null ) { object.parent.updateWorldMatrix( true, false ); _m1$1.multiply( object.parent.matrixWorld ); } object.applyMatrix4( _m1$1 ); this.add( object ); object.updateWorldMatrix( false, true ); return this; } getObjectById( id ) { return this.getObjectByProperty( 'id', id ); } getObjectByName( name ) { return this.getObjectByProperty( 'name', name ); } getObjectByProperty( name, value ) { if ( this[ name ] === value ) return this; for ( let i = 0, l = this.children.length; i < l; i ++ ) { const child = this.children[ i ]; const object = child.getObjectByProperty( name, value ); if ( object !== undefined ) { return object; } } return undefined; } getObjectsByProperty( name, value, result = [] ) { if ( this[ name ] === value ) result.push( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].getObjectsByProperty( name, value, result ); } return result; } getWorldPosition( target ) { this.updateWorldMatrix( true, false ); return target.setFromMatrixPosition( this.matrixWorld ); } getWorldQuaternion( target ) { this.updateWorldMatrix( true, false ); this.matrixWorld.decompose( _position$3, target, _scale$2 ); return target; } getWorldScale( target ) { this.updateWorldMatrix( true, false ); this.matrixWorld.decompose( _position$3, _quaternion$2, target ); return target; } getWorldDirection( target ) { this.updateWorldMatrix( true, false ); const e = this.matrixWorld.elements; return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); } raycast( /* raycaster, intersects */ ) {} traverse( callback ) { callback( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].traverse( callback ); } } traverseVisible( callback ) { if ( this.visible === false ) return; callback( this ); const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { children[ i ].traverseVisible( callback ); } } traverseAncestors( callback ) { const parent = this.parent; if ( parent !== null ) { callback( parent ); parent.traverseAncestors( callback ); } } updateMatrix() { this.matrix.compose( this.position, this.quaternion, this.scale ); this.matrixWorldNeedsUpdate = true; } updateMatrixWorld( force ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.matrixWorldNeedsUpdate || force ) { if ( this.parent === null ) { this.matrixWorld.copy( this.matrix ); } else { this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); } this.matrixWorldNeedsUpdate = false; force = true; } // update children const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { const child = children[ i ]; if ( child.matrixWorldAutoUpdate === true || force === true ) { child.updateMatrixWorld( force ); } } } updateWorldMatrix( updateParents, updateChildren ) { const parent = this.parent; if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { parent.updateWorldMatrix( true, false ); } if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.parent === null ) { this.matrixWorld.copy( this.matrix ); } else { this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); } // update children if ( updateChildren === true ) { const children = this.children; for ( let i = 0, l = children.length; i < l; i ++ ) { const child = children[ i ]; if ( child.matrixWorldAutoUpdate === true ) { child.updateWorldMatrix( false, true ); } } } } toJSON( meta ) { // meta is a string when called from JSON.stringify const isRootObject = ( meta === undefined || typeof meta === 'string' ); const output = {}; // meta is a hash used to collect geometries, materials. // not providing it implies that this is the root object // being serialized. if ( isRootObject ) { // initialize meta obj meta = { geometries: {}, materials: {}, textures: {}, images: {}, shapes: {}, skeletons: {}, animations: {}, nodes: {} }; output.metadata = { version: 4.6, type: 'Object', generator: 'Object3D.toJSON' }; } // standard Object3D serialization const object = {}; object.uuid = this.uuid; object.type = this.type; if ( this.name !== '' ) object.name = this.name; if ( this.castShadow === true ) object.castShadow = true; if ( this.receiveShadow === true ) object.receiveShadow = true; if ( this.visible === false ) object.visible = false; if ( this.frustumCulled === false ) object.frustumCulled = false; if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; object.layers = this.layers.mask; object.matrix = this.matrix.toArray(); object.up = this.up.toArray(); if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; // object specific properties if ( this.isInstancedMesh ) { object.type = 'InstancedMesh'; object.count = this.count; object.instanceMatrix = this.instanceMatrix.toJSON(); if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); } if ( this.isBatchedMesh ) { object.type = 'BatchedMesh'; object.perObjectFrustumCulled = this.perObjectFrustumCulled; object.sortObjects = this.sortObjects; object.drawRanges = this._drawRanges; object.reservedRanges = this._reservedRanges; object.visibility = this._visibility; object.active = this._active; object.bounds = this._bounds.map( bound => ( { boxInitialized: bound.boxInitialized, boxMin: bound.box.min.toArray(), boxMax: bound.box.max.toArray(), sphereInitialized: bound.sphereInitialized, sphereRadius: bound.sphere.radius, sphereCenter: bound.sphere.center.toArray() } ) ); object.maxGeometryCount = this._maxGeometryCount; object.maxVertexCount = this._maxVertexCount; object.maxIndexCount = this._maxIndexCount; object.geometryInitialized = this._geometryInitialized; object.geometryCount = this._geometryCount; object.matricesTexture = this._matricesTexture.toJSON( meta ); if ( this.boundingSphere !== null ) { object.boundingSphere = { center: object.boundingSphere.center.toArray(), radius: object.boundingSphere.radius }; } if ( this.boundingBox !== null ) { object.boundingBox = { min: object.boundingBox.min.toArray(), max: object.boundingBox.max.toArray() }; } } // function serialize( library, element ) { if ( library[ element.uuid ] === undefined ) { library[ element.uuid ] = element.toJSON( meta ); } return element.uuid; } if ( this.isScene ) { if ( this.background ) { if ( this.background.isColor ) { object.background = this.background.toJSON(); } else if ( this.background.isTexture ) { object.background = this.background.toJSON( meta ).uuid; } } if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { object.environment = this.environment.toJSON( meta ).uuid; } } else if ( this.isMesh || this.isLine || this.isPoints ) { object.geometry = serialize( meta.geometries, this.geometry ); const parameters = this.geometry.parameters; if ( parameters !== undefined && parameters.shapes !== undefined ) { const shapes = parameters.shapes; if ( Array.isArray( shapes ) ) { for ( let i = 0, l = shapes.length; i < l; i ++ ) { const shape = shapes[ i ]; serialize( meta.shapes, shape ); } } else { serialize( meta.shapes, shapes ); } } } if ( this.isSkinnedMesh ) { object.bindMode = this.bindMode; object.bindMatrix = this.bindMatrix.toArray(); if ( this.skeleton !== undefined ) { serialize( meta.skeletons, this.skeleton ); object.skeleton = this.skeleton.uuid; } } if ( this.material !== undefined ) { if ( Array.isArray( this.material ) ) { const uuids = []; for ( let i = 0, l = this.material.length; i < l; i ++ ) { uuids.push( serialize( meta.materials, this.material[ i ] ) ); } object.material = uuids; } else { object.material = serialize( meta.materials, this.material ); } } // if ( this.children.length > 0 ) { object.children = []; for ( let i = 0; i < this.children.length; i ++ ) { object.children.push( this.children[ i ].toJSON( meta ).object ); } } // if ( this.animations.length > 0 ) { object.animations = []; for ( let i = 0; i < this.animations.length; i ++ ) { const animation = this.animations[ i ]; object.animations.push( serialize( meta.animations, animation ) ); } } if ( isRootObject ) { const geometries = extractFromCache( meta.geometries ); const materials = extractFromCache( meta.materials ); const textures = extractFromCache( meta.textures ); const images = extractFromCache( meta.images ); const shapes = extractFromCache( meta.shapes ); const skeletons = extractFromCache( meta.skeletons ); const animations = extractFromCache( meta.animations ); const nodes = extractFromCache( meta.nodes ); if ( geometries.length > 0 ) output.geometries = geometries; if ( materials.length > 0 ) output.materials = materials; if ( textures.length > 0 ) output.textures = textures; if ( images.length > 0 ) output.images = images; if ( shapes.length > 0 ) output.shapes = shapes; if ( skeletons.length > 0 ) output.skeletons = skeletons; if ( animations.length > 0 ) output.animations = animations; if ( nodes.length > 0 ) output.nodes = nodes; } output.object = object; return output; // extract data from the cache hash // remove metadata on each item // and return as array function extractFromCache( cache ) { const values = []; for ( const key in cache ) { const data = cache[ key ]; delete data.metadata; values.push( data ); } return values; } } clone( recursive ) { return new this.constructor().copy( this, recursive ); } copy( source, recursive = true ) { this.name = source.name; this.up.copy( source.up ); this.position.copy( source.position ); this.rotation.order = source.rotation.order; this.quaternion.copy( source.quaternion ); this.scale.copy( source.scale ); this.matrix.copy( source.matrix ); this.matrixWorld.copy( source.matrixWorld ); this.matrixAutoUpdate = source.matrixAutoUpdate; this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; this.layers.mask = source.layers.mask; this.visible = source.visible; this.castShadow = source.castShadow; this.receiveShadow = source.receiveShadow; this.frustumCulled = source.frustumCulled; this.renderOrder = source.renderOrder; this.animations = source.animations.slice(); this.userData = JSON.parse( JSON.stringify( source.userData ) ); if ( recursive === true ) { for ( let i = 0; i < source.children.length; i ++ ) { const child = source.children[ i ]; this.add( child.clone() ); } } return this; } } Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; const _v0$1 = /*@__PURE__*/ new Vector3(); const _v1$3 = /*@__PURE__*/ new Vector3(); const _v2$2 = /*@__PURE__*/ new Vector3(); const _v3$1 = /*@__PURE__*/ new Vector3(); const _vab = /*@__PURE__*/ new Vector3(); const _vac = /*@__PURE__*/ new Vector3(); const _vbc = /*@__PURE__*/ new Vector3(); const _vap = /*@__PURE__*/ new Vector3(); const _vbp = /*@__PURE__*/ new Vector3(); const _vcp = /*@__PURE__*/ new Vector3(); let warnedGetUV = false; class Triangle { constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { this.a = a; this.b = b; this.c = c; } static getNormal( a, b, c, target ) { target.subVectors( c, b ); _v0$1.subVectors( a, b ); target.cross( _v0$1 ); const targetLengthSq = target.lengthSq(); if ( targetLengthSq > 0 ) { return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); } return target.set( 0, 0, 0 ); } // static/instance method to calculate barycentric coordinates // based on: http://www.blackpawn.com/texts/pointinpoly/default.html static getBarycoord( point, a, b, c, target ) { _v0$1.subVectors( c, a ); _v1$3.subVectors( b, a ); _v2$2.subVectors( point, a ); const dot00 = _v0$1.dot( _v0$1 ); const dot01 = _v0$1.dot( _v1$3 ); const dot02 = _v0$1.dot( _v2$2 ); const dot11 = _v1$3.dot( _v1$3 ); const dot12 = _v1$3.dot( _v2$2 ); const denom = ( dot00 * dot11 - dot01 * dot01 ); // collinear or singular triangle if ( denom === 0 ) { target.set( 0, 0, 0 ); return null; } const invDenom = 1 / denom; const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; // barycentric coordinates must always sum to 1 return target.set( 1 - u - v, v, u ); } static containsPoint( point, a, b, c ) { // if the triangle is degenerate then we can't contain a point if ( this.getBarycoord( point, a, b, c, _v3$1 ) === null ) { return false; } return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); } static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) { // @deprecated, r151 if ( warnedGetUV === false ) { console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); warnedGetUV = true; } return this.getInterpolation( point, p1, p2, p3, uv1, uv2, uv3, target ); } static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { if ( this.getBarycoord( point, p1, p2, p3, _v3$1 ) === null ) { target.x = 0; target.y = 0; if ( 'z' in target ) target.z = 0; if ( 'w' in target ) target.w = 0; return null; } target.setScalar( 0 ); target.addScaledVector( v1, _v3$1.x ); target.addScaledVector( v2, _v3$1.y ); target.addScaledVector( v3, _v3$1.z ); return target; } static isFrontFacing( a, b, c, direction ) { _v0$1.subVectors( c, b ); _v1$3.subVectors( a, b ); // strictly front facing return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; } set( a, b, c ) { this.a.copy( a ); this.b.copy( b ); this.c.copy( c ); return this; } setFromPointsAndIndices( points, i0, i1, i2 ) { this.a.copy( points[ i0 ] ); this.b.copy( points[ i1 ] ); this.c.copy( points[ i2 ] ); return this; } setFromAttributeAndIndices( attribute, i0, i1, i2 ) { this.a.fromBufferAttribute( attribute, i0 ); this.b.fromBufferAttribute( attribute, i1 ); this.c.fromBufferAttribute( attribute, i2 ); return this; } clone() { return new this.constructor().copy( this ); } copy( triangle ) { this.a.copy( triangle.a ); this.b.copy( triangle.b ); this.c.copy( triangle.c ); return this; } getArea() { _v0$1.subVectors( this.c, this.b ); _v1$3.subVectors( this.a, this.b ); return _v0$1.cross( _v1$3 ).length() * 0.5; } getMidpoint( target ) { return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); } getNormal( target ) { return Triangle.getNormal( this.a, this.b, this.c, target ); } getPlane( target ) { return target.setFromCoplanarPoints( this.a, this.b, this.c ); } getBarycoord( point, target ) { return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); } getUV( point, uv1, uv2, uv3, target ) { // @deprecated, r151 if ( warnedGetUV === false ) { console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); warnedGetUV = true; } return Triangle.getInterpolation( point, this.a, this.b, this.c, uv1, uv2, uv3, target ); } getInterpolation( point, v1, v2, v3, target ) { return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); } containsPoint( point ) { return Triangle.containsPoint( point, this.a, this.b, this.c ); } isFrontFacing( direction ) { return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); } intersectsBox( box ) { return box.intersectsTriangle( this ); } closestPointToPoint( p, target ) { const a = this.a, b = this.b, c = this.c; let v, w; // algorithm thanks to Real-Time Collision Detection by Christer Ericson, // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., // under the accompanying license; see chapter 5.1.5 for detailed explanation. // basically, we're distinguishing which of the voronoi regions of the triangle // the point lies in with the minimum amount of redundant computation. _vab.subVectors( b, a ); _vac.subVectors( c, a ); _vap.subVectors( p, a ); const d1 = _vab.dot( _vap ); const d2 = _vac.dot( _vap ); if ( d1 <= 0 && d2 <= 0 ) { // vertex region of A; barycentric coords (1, 0, 0) return target.copy( a ); } _vbp.subVectors( p, b ); const d3 = _vab.dot( _vbp ); const d4 = _vac.dot( _vbp ); if ( d3 >= 0 && d4 <= d3 ) { // vertex region of B; barycentric coords (0, 1, 0) return target.copy( b ); } const vc = d1 * d4 - d3 * d2; if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { v = d1 / ( d1 - d3 ); // edge region of AB; barycentric coords (1-v, v, 0) return target.copy( a ).addScaledVector( _vab, v ); } _vcp.subVectors( p, c ); const d5 = _vab.dot( _vcp ); const d6 = _vac.dot( _vcp ); if ( d6 >= 0 && d5 <= d6 ) { // vertex region of C; barycentric coords (0, 0, 1) return target.copy( c ); } const vb = d5 * d2 - d1 * d6; if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { w = d2 / ( d2 - d6 ); // edge region of AC; barycentric coords (1-w, 0, w) return target.copy( a ).addScaledVector( _vac, w ); } const va = d3 * d6 - d5 * d4; if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { _vbc.subVectors( c, b ); w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); // edge region of BC; barycentric coords (0, 1-w, w) return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC } // face region const denom = 1 / ( va + vb + vc ); // u = va * denom v = vb * denom; w = vc * denom; return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); } equals( triangle ) { return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); } } const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; const _hslA = { h: 0, s: 0, l: 0 }; const _hslB = { h: 0, s: 0, l: 0 }; function hue2rgb( p, q, t ) { if ( t < 0 ) t += 1; if ( t > 1 ) t -= 1; if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; if ( t < 1 / 2 ) return q; if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); return p; } class Color { constructor( r, g, b ) { this.isColor = true; this.r = 1; this.g = 1; this.b = 1; return this.set( r, g, b ); } set( r, g, b ) { if ( g === undefined && b === undefined ) { // r is THREE.Color, hex or string const value = r; if ( value && value.isColor ) { this.copy( value ); } else if ( typeof value === 'number' ) { this.setHex( value ); } else if ( typeof value === 'string' ) { this.setStyle( value ); } } else { this.setRGB( r, g, b ); } return this; } setScalar( scalar ) { this.r = scalar; this.g = scalar; this.b = scalar; return this; } setHex( hex, colorSpace = SRGBColorSpace ) { hex = Math.floor( hex ); this.r = ( hex >> 16 & 255 ) / 255; this.g = ( hex >> 8 & 255 ) / 255; this.b = ( hex & 255 ) / 255; ColorManagement.toWorkingColorSpace( this, colorSpace ); return this; } setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { this.r = r; this.g = g; this.b = b; ColorManagement.toWorkingColorSpace( this, colorSpace ); return this; } setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { // h,s,l ranges are in 0.0 - 1.0 h = euclideanModulo( h, 1 ); s = clamp( s, 0, 1 ); l = clamp( l, 0, 1 ); if ( s === 0 ) { this.r = this.g = this.b = l; } else { const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); const q = ( 2 * l ) - p; this.r = hue2rgb( q, p, h + 1 / 3 ); this.g = hue2rgb( q, p, h ); this.b = hue2rgb( q, p, h - 1 / 3 ); } ColorManagement.toWorkingColorSpace( this, colorSpace ); return this; } setStyle( style, colorSpace = SRGBColorSpace ) { function handleAlpha( string ) { if ( string === undefined ) return; if ( parseFloat( string ) < 1 ) { console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); } } let m; if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { // rgb / hsl let color; const name = m[ 1 ]; const components = m[ 2 ]; switch ( name ) { case 'rgb': case 'rgba': if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { // rgb(255,0,0) rgba(255,0,0,0.5) handleAlpha( color[ 4 ] ); return this.setRGB( Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, colorSpace ); } if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) handleAlpha( color[ 4 ] ); return this.setRGB( Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, colorSpace ); } break; case 'hsl': case 'hsla': if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { // hsl(120,50%,50%) hsla(120,50%,50%,0.5) handleAlpha( color[ 4 ] ); return this.setHSL( parseFloat( color[ 1 ] ) / 360, parseFloat( color[ 2 ] ) / 100, parseFloat( color[ 3 ] ) / 100, colorSpace ); } break; default: console.warn( 'THREE.Color: Unknown color model ' + style ); } } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { // hex color const hex = m[ 1 ]; const size = hex.length; if ( size === 3 ) { // #ff0 return this.setRGB( parseInt( hex.charAt( 0 ), 16 ) / 15, parseInt( hex.charAt( 1 ), 16 ) / 15, parseInt( hex.charAt( 2 ), 16 ) / 15, colorSpace ); } else if ( size === 6 ) { // #ff0000 return this.setHex( parseInt( hex, 16 ), colorSpace ); } else { console.warn( 'THREE.Color: Invalid hex color ' + style ); } } else if ( style && style.length > 0 ) { return this.setColorName( style, colorSpace ); } return this; } setColorName( style, colorSpace = SRGBColorSpace ) { // color keywords const hex = _colorKeywords[ style.toLowerCase() ]; if ( hex !== undefined ) { // red this.setHex( hex, colorSpace ); } else { // unknown color console.warn( 'THREE.Color: Unknown color ' + style ); } return this; } clone() { return new this.constructor( this.r, this.g, this.b ); } copy( color ) { this.r = color.r; this.g = color.g; this.b = color.b; return this; } copySRGBToLinear( color ) { this.r = SRGBToLinear( color.r ); this.g = SRGBToLinear( color.g ); this.b = SRGBToLinear( color.b ); return this; } copyLinearToSRGB( color ) { this.r = LinearToSRGB( color.r ); this.g = LinearToSRGB( color.g ); this.b = LinearToSRGB( color.b ); return this; } convertSRGBToLinear() { this.copySRGBToLinear( this ); return this; } convertLinearToSRGB() { this.copyLinearToSRGB( this ); return this; } getHex( colorSpace = SRGBColorSpace ) { ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); } getHexString( colorSpace = SRGBColorSpace ) { return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); } getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { // h,s,l ranges are in 0.0 - 1.0 ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); const r = _color.r, g = _color.g, b = _color.b; const max = Math.max( r, g, b ); const min = Math.min( r, g, b ); let hue, saturation; const lightness = ( min + max ) / 2.0; if ( min === max ) { hue = 0; saturation = 0; } else { const delta = max - min; saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); switch ( max ) { case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; case g: hue = ( b - r ) / delta + 2; break; case b: hue = ( r - g ) / delta + 4; break; } hue /= 6; } target.h = hue; target.s = saturation; target.l = lightness; return target; } getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); target.r = _color.r; target.g = _color.g; target.b = _color.b; return target; } getStyle( colorSpace = SRGBColorSpace ) { ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); const r = _color.r, g = _color.g, b = _color.b; if ( colorSpace !== SRGBColorSpace ) { // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; } return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; } offsetHSL( h, s, l ) { this.getHSL( _hslA ); return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); } add( color ) { this.r += color.r; this.g += color.g; this.b += color.b; return this; } addColors( color1, color2 ) { this.r = color1.r + color2.r; this.g = color1.g + color2.g; this.b = color1.b + color2.b; return this; } addScalar( s ) { this.r += s; this.g += s; this.b += s; return this; } sub( color ) { this.r = Math.max( 0, this.r - color.r ); this.g = Math.max( 0, this.g - color.g ); this.b = Math.max( 0, this.b - color.b ); return this; } multiply( color ) { this.r *= color.r; this.g *= color.g; this.b *= color.b; return this; } multiplyScalar( s ) { this.r *= s; this.g *= s; this.b *= s; return this; } lerp( color, alpha ) { this.r += ( color.r - this.r ) * alpha; this.g += ( color.g - this.g ) * alpha; this.b += ( color.b - this.b ) * alpha; return this; } lerpColors( color1, color2, alpha ) { this.r = color1.r + ( color2.r - color1.r ) * alpha; this.g = color1.g + ( color2.g - color1.g ) * alpha; this.b = color1.b + ( color2.b - color1.b ) * alpha; return this; } lerpHSL( color, alpha ) { this.getHSL( _hslA ); color.getHSL( _hslB ); const h = lerp( _hslA.h, _hslB.h, alpha ); const s = lerp( _hslA.s, _hslB.s, alpha ); const l = lerp( _hslA.l, _hslB.l, alpha ); this.setHSL( h, s, l ); return this; } setFromVector3( v ) { this.r = v.x; this.g = v.y; this.b = v.z; return this; } applyMatrix3( m ) { const r = this.r, g = this.g, b = this.b; const e = m.elements; this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; return this; } equals( c ) { return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); } fromArray( array, offset = 0 ) { this.r = array[ offset ]; this.g = array[ offset + 1 ]; this.b = array[ offset + 2 ]; return this; } toArray( array = [], offset = 0 ) { array[ offset ] = this.r; array[ offset + 1 ] = this.g; array[ offset + 2 ] = this.b; return array; } fromBufferAttribute( attribute, index ) { this.r = attribute.getX( index ); this.g = attribute.getY( index ); this.b = attribute.getZ( index ); return this; } toJSON() { return this.getHex(); } *[ Symbol.iterator ]() { yield this.r; yield this.g; yield this.b; } } const _color = /*@__PURE__*/ new Color(); Color.NAMES = _colorKeywords; let _materialId = 0; class Material extends EventDispatcher { constructor() { super(); this.isMaterial = true; Object.defineProperty( this, 'id', { value: _materialId ++ } ); this.uuid = generateUUID(); this.name = ''; this.type = 'Material'; this.blending = NormalBlending; this.side = FrontSide; this.vertexColors = false; this.opacity = 1; this.transparent = false; this.alphaHash = false; this.blendSrc = SrcAlphaFactor; this.blendDst = OneMinusSrcAlphaFactor; this.blendEquation = AddEquation; this.blendSrcAlpha = null; this.blendDstAlpha = null; this.blendEquationAlpha = null; this.blendColor = new Color( 0, 0, 0 ); this.blendAlpha = 0; this.depthFunc = LessEqualDepth; this.depthTest = true; this.depthWrite = true; this.stencilWriteMask = 0xff; this.stencilFunc = AlwaysStencilFunc; this.stencilRef = 0; this.stencilFuncMask = 0xff; this.stencilFail = KeepStencilOp; this.stencilZFail = KeepStencilOp; this.stencilZPass = KeepStencilOp; this.stencilWrite = false; this.clippingPlanes = null; this.clipIntersection = false; this.clipShadows = false; this.shadowSide = null; this.colorWrite = true; this.precision = null; // override the renderer's default precision for this material this.polygonOffset = false; this.polygonOffsetFactor = 0; this.polygonOffsetUnits = 0; this.dithering = false; this.alphaToCoverage = false; this.premultipliedAlpha = false; this.forceSinglePass = false; this.visible = true; this.toneMapped = true; this.userData = {}; this.version = 0; this._alphaTest = 0; } get alphaTest() { return this._alphaTest; } set alphaTest( value ) { if ( this._alphaTest > 0 !== value > 0 ) { this.version ++; } this._alphaTest = value; } onBuild( /* shaderobject, renderer */ ) {} onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} onBeforeCompile( /* shaderobject, renderer */ ) {} customProgramCacheKey() { return this.onBeforeCompile.toString(); } setValues( values ) { if ( values === undefined ) return; for ( const key in values ) { const newValue = values[ key ]; if ( newValue === undefined ) { console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); continue; } const currentValue = this[ key ]; if ( currentValue === undefined ) { console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); continue; } if ( currentValue && currentValue.isColor ) { currentValue.set( newValue ); } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { currentValue.copy( newValue ); } else { this[ key ] = newValue; } } } toJSON( meta ) { const isRootObject = ( meta === undefined || typeof meta === 'string' ); if ( isRootObject ) { meta = { textures: {}, images: {} }; } const data = { metadata: { version: 4.6, type: 'Material', generator: 'Material.toJSON' } }; // standard Material serialization data.uuid = this.uuid; data.type = this.type; if ( this.name !== '' ) data.name = this.name; if ( this.color && this.color.isColor ) data.color = this.color.getHex(); if ( this.roughness !== undefined ) data.roughness = this.roughness; if ( this.metalness !== undefined ) data.metalness = this.metalness; if ( this.sheen !== undefined ) data.sheen = this.sheen; if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); if ( this.shininess !== undefined ) data.shininess = this.shininess; if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; } if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; } if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); } if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; } if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; } if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; } if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; if ( this.lightMap && this.lightMap.isTexture ) { data.lightMap = this.lightMap.toJSON( meta ).uuid; data.lightMapIntensity = this.lightMapIntensity; } if ( this.aoMap && this.aoMap.isTexture ) { data.aoMap = this.aoMap.toJSON( meta ).uuid; data.aoMapIntensity = this.aoMapIntensity; } if ( this.bumpMap && this.bumpMap.isTexture ) { data.bumpMap = this.bumpMap.toJSON( meta ).uuid; data.bumpScale = this.bumpScale; } if ( this.normalMap && this.normalMap.isTexture ) { data.normalMap = this.normalMap.toJSON( meta ).uuid; data.normalMapType = this.normalMapType; data.normalScale = this.normalScale.toArray(); } if ( this.displacementMap && this.displacementMap.isTexture ) { data.displacementMap = this.displacementMap.toJSON( meta ).uuid; data.displacementScale = this.displacementScale; data.displacementBias = this.displacementBias; } if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; if ( this.envMap && this.envMap.isTexture ) { data.envMap = this.envMap.toJSON( meta ).uuid; if ( this.combine !== undefined ) data.combine = this.combine; } if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; if ( this.gradientMap && this.gradientMap.isTexture ) { data.gradientMap = this.gradientMap.toJSON( meta ).uuid; } if ( this.transmission !== undefined ) data.transmission = this.transmission; if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; if ( this.thickness !== undefined ) data.thickness = this.thickness; if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); if ( this.size !== undefined ) data.size = this.size; if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; if ( this.blending !== NormalBlending ) data.blending = this.blending; if ( this.side !== FrontSide ) data.side = this.side; if ( this.vertexColors === true ) data.vertexColors = true; if ( this.opacity < 1 ) data.opacity = this.opacity; if ( this.transparent === true ) data.transparent = true; if ( this.blendSrc !== SrcAlphaFactor ) data.blendSrc = this.blendSrc; if ( this.blendDst !== OneMinusSrcAlphaFactor ) data.blendDst = this.blendDst; if ( this.blendEquation !== AddEquation ) data.blendEquation = this.blendEquation; if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha; if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha; if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha; if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex(); if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha; if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc; if ( this.depthTest === false ) data.depthTest = this.depthTest; if ( this.depthWrite === false ) data.depthWrite = this.depthWrite; if ( this.colorWrite === false ) data.colorWrite = this.colorWrite; if ( this.stencilWriteMask !== 0xff ) data.stencilWriteMask = this.stencilWriteMask; if ( this.stencilFunc !== AlwaysStencilFunc ) data.stencilFunc = this.stencilFunc; if ( this.stencilRef !== 0 ) data.stencilRef = this.stencilRef; if ( this.stencilFuncMask !== 0xff ) data.stencilFuncMask = this.stencilFuncMask; if ( this.stencilFail !== KeepStencilOp ) data.stencilFail = this.stencilFail; if ( this.stencilZFail !== KeepStencilOp ) data.stencilZFail = this.stencilZFail; if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; // rotation (SpriteMaterial) if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; if ( this.polygonOffset === true ) data.polygonOffset = true; if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; if ( this.scale !== undefined ) data.scale = this.scale; if ( this.dithering === true ) data.dithering = true; if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; if ( this.alphaHash === true ) data.alphaHash = true; if ( this.alphaToCoverage === true ) data.alphaToCoverage = true; if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = true; if ( this.forceSinglePass === true ) data.forceSinglePass = true; if ( this.wireframe === true ) data.wireframe = true; if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; if ( this.flatShading === true ) data.flatShading = true; if ( this.visible === false ) data.visible = false; if ( this.toneMapped === false ) data.toneMapped = false; if ( this.fog === false ) data.fog = false; if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; // TODO: Copied from Object3D.toJSON function extractFromCache( cache ) { const values = []; for ( const key in cache ) { const data = cache[ key ]; delete data.metadata; values.push( data ); } return values; } if ( isRootObject ) { const textures = extractFromCache( meta.textures ); const images = extractFromCache( meta.images ); if ( textures.length > 0 ) data.textures = textures; if ( images.length > 0 ) data.images = images; } return data; } clone() { return new this.constructor().copy( this ); } copy( source ) { this.name = source.name; this.blending = source.blending; this.side = source.side; this.vertexColors = source.vertexColors; this.opacity = source.opacity; this.transparent = source.transparent; this.blendSrc = source.blendSrc; this.blendDst = source.blendDst; this.blendEquation = source.blendEquation; this.blendSrcAlpha = source.blendSrcAlpha; this.blendDstAlpha = source.blendDstAlpha; this.blendEquationAlpha = source.blendEquationAlpha; this.blendColor.copy( source.blendColor ); this.blendAlpha = source.blendAlpha; this.depthFunc = source.depthFunc; this.depthTest = source.depthTest; this.depthWrite = source.depthWrite; this.stencilWriteMask = source.stencilWriteMask; this.stencilFunc = source.stencilFunc; this.stencilRef = source.stencilRef; this.stencilFuncMask = source.stencilFuncMask; this.stencilFail = source.stencilFail; this.stencilZFail = source.stencilZFail; this.stencilZPass = source.stencilZPass; this.stencilWrite = source.stencilWrite; const srcPlanes = source.clippingPlanes; let dstPlanes = null; if ( srcPlanes !== null ) { const n = srcPlanes.length; dstPlanes = new Array( n ); for ( let i = 0; i !== n; ++ i ) { dstPlanes[ i ] = srcPlanes[ i ].clone(); } } this.clippingPlanes = dstPlanes; this.clipIntersection = source.clipIntersection; this.clipShadows = source.clipShadows; this.shadowSide = source.shadowSide; this.colorWrite = source.colorWrite; this.precision = source.precision; this.polygonOffset = source.polygonOffset; this.polygonOffsetFactor = source.polygonOffsetFactor; this.polygonOffsetUnits = source.polygonOffsetUnits; this.dithering = source.dithering; this.alphaTest = source.alphaTest; this.alphaHash = source.alphaHash; this.alphaToCoverage = source.alphaToCoverage; this.premultipliedAlpha = source.premultipliedAlpha; this.forceSinglePass = source.forceSinglePass; this.visible = source.visible; this.toneMapped = source.toneMapped; this.userData = JSON.parse( JSON.stringify( source.userData ) ); return this; } dispose() { this.dispatchEvent( { type: 'dispose' } ); } set needsUpdate( value ) { if ( value === true ) this.version ++; } } class MeshBasicMaterial extends Material { constructor( parameters ) { super(); this.isMeshBasicMaterial = true; this.type = 'MeshBasicMaterial'; this.color = new Color( 0xffffff ); // emissive this.map = null; this.lightMap = null; this.lightMapIntensity = 1.0; this.aoMap = null; this.aoMapIntensity = 1.0; this.specularMap = null; this.alphaMap = null; this.envMap = null; this.combine = MultiplyOperation; this.reflectivity = 1; this.refractionRatio = 0.98; this.wireframe = false; this.wireframeLinewidth = 1; this.wireframeLinecap = 'round'; this.wireframeLinejoin = 'round'; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.lightMap = source.lightMap; this.lightMapIntensity = source.lightMapIntensity; this.aoMap = source.aoMap; this.aoMapIntensity = source.aoMapIntensity; this.specularMap = source.specularMap; this.alphaMap = source.alphaMap; this.envMap = source.envMap; this.combine = source.combine; this.reflectivity = source.reflectivity; this.refractionRatio = source.refractionRatio; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.wireframeLinecap = source.wireframeLinecap; this.wireframeLinejoin = source.wireframeLinejoin; this.fog = source.fog; return this; } } // Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf const _tables = /*@__PURE__*/ _generateTables(); function _generateTables() { // float32 to float16 helpers const buffer = new ArrayBuffer( 4 ); const floatView = new Float32Array( buffer ); const uint32View = new Uint32Array( buffer ); const baseTable = new Uint32Array( 512 ); const shiftTable = new Uint32Array( 512 ); for ( let i = 0; i < 256; ++ i ) { const e = i - 127; // very small number (0, -0) if ( e < - 27 ) { baseTable[ i ] = 0x0000; baseTable[ i | 0x100 ] = 0x8000; shiftTable[ i ] = 24; shiftTable[ i | 0x100 ] = 24; // small number (denorm) } else if ( e < - 14 ) { baseTable[ i ] = 0x0400 >> ( - e - 14 ); baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; shiftTable[ i ] = - e - 1; shiftTable[ i | 0x100 ] = - e - 1; // normal number } else if ( e <= 15 ) { baseTable[ i ] = ( e + 15 ) << 10; baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; shiftTable[ i ] = 13; shiftTable[ i | 0x100 ] = 13; // large number (Infinity, -Infinity) } else if ( e < 128 ) { baseTable[ i ] = 0x7c00; baseTable[ i | 0x100 ] = 0xfc00; shiftTable[ i ] = 24; shiftTable[ i | 0x100 ] = 24; // stay (NaN, Infinity, -Infinity) } else { baseTable[ i ] = 0x7c00; baseTable[ i | 0x100 ] = 0xfc00; shiftTable[ i ] = 13; shiftTable[ i | 0x100 ] = 13; } } // float16 to float32 helpers const mantissaTable = new Uint32Array( 2048 ); const exponentTable = new Uint32Array( 64 ); const offsetTable = new Uint32Array( 64 ); for ( let i = 1; i < 1024; ++ i ) { let m = i << 13; // zero pad mantissa bits let e = 0; // zero exponent // normalized while ( ( m & 0x00800000 ) === 0 ) { m <<= 1; e -= 0x00800000; // decrement exponent } m &= ~ 0x00800000; // clear leading 1 bit e += 0x38800000; // adjust bias mantissaTable[ i ] = m | e; } for ( let i = 1024; i < 2048; ++ i ) { mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); } for ( let i = 1; i < 31; ++ i ) { exponentTable[ i ] = i << 23; } exponentTable[ 31 ] = 0x47800000; exponentTable[ 32 ] = 0x80000000; for ( let i = 33; i < 63; ++ i ) { exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); } exponentTable[ 63 ] = 0xc7800000; for ( let i = 1; i < 64; ++ i ) { if ( i !== 32 ) { offsetTable[ i ] = 1024; } } return { floatView: floatView, uint32View: uint32View, baseTable: baseTable, shiftTable: shiftTable, mantissaTable: mantissaTable, exponentTable: exponentTable, offsetTable: offsetTable }; } // float32 to float16 function toHalfFloat( val ) { if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); val = clamp( val, - 65504, 65504 ); _tables.floatView[ 0 ] = val; const f = _tables.uint32View[ 0 ]; const e = ( f >> 23 ) & 0x1ff; return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); } // float16 to float32 function fromHalfFloat( val ) { const m = val >> 10; _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; return _tables.floatView[ 0 ]; } const DataUtils = { toHalfFloat: toHalfFloat, fromHalfFloat: fromHalfFloat, }; const _vector$9 = /*@__PURE__*/ new Vector3(); const _vector2$1 = /*@__PURE__*/ new Vector2(); class BufferAttribute { constructor( array, itemSize, normalized = false ) { if ( Array.isArray( array ) ) { throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); } this.isBufferAttribute = true; this.name = ''; this.array = array; this.itemSize = itemSize; this.count = array !== undefined ? array.length / itemSize : 0; this.normalized = normalized; this.usage = StaticDrawUsage; this._updateRange = { offset: 0, count: - 1 }; this.updateRanges = []; this.gpuType = FloatType; this.version = 0; } onUploadCallback() {} set needsUpdate( value ) { if ( value === true ) this.version ++; } get updateRange() { console.warn( 'THREE.BufferAttribute: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead.' ); // @deprecated, r159 return this._updateRange; } setUsage( value ) { this.usage = value; return this; } addUpdateRange( start, count ) { this.updateRanges.push( { start, count } ); } clearUpdateRanges() { this.updateRanges.length = 0; } copy( source ) { this.name = source.name; this.array = new source.array.constructor( source.array ); this.itemSize = source.itemSize; this.count = source.count; this.normalized = source.normalized; this.usage = source.usage; this.gpuType = source.gpuType; return this; } copyAt( index1, attribute, index2 ) { index1 *= this.itemSize; index2 *= attribute.itemSize; for ( let i = 0, l = this.itemSize; i < l; i ++ ) { this.array[ index1 + i ] = attribute.array[ index2 + i ]; } return this; } copyArray( array ) { this.array.set( array ); return this; } applyMatrix3( m ) { if ( this.itemSize === 2 ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector2$1.fromBufferAttribute( this, i ); _vector2$1.applyMatrix3( m ); this.setXY( i, _vector2$1.x, _vector2$1.y ); } } else if ( this.itemSize === 3 ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$9.fromBufferAttribute( this, i ); _vector$9.applyMatrix3( m ); this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); } } return this; } applyMatrix4( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$9.fromBufferAttribute( this, i ); _vector$9.applyMatrix4( m ); this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); } return this; } applyNormalMatrix( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$9.fromBufferAttribute( this, i ); _vector$9.applyNormalMatrix( m ); this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); } return this; } transformDirection( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$9.fromBufferAttribute( this, i ); _vector$9.transformDirection( m ); this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); } return this; } set( value, offset = 0 ) { // Matching BufferAttribute constructor, do not normalize the array. this.array.set( value, offset ); return this; } getComponent( index, component ) { let value = this.array[ index * this.itemSize + component ]; if ( this.normalized ) value = denormalize( value, this.array ); return value; } setComponent( index, component, value ) { if ( this.normalized ) value = normalize( value, this.array ); this.array[ index * this.itemSize + component ] = value; return this; } getX( index ) { let x = this.array[ index * this.itemSize ]; if ( this.normalized ) x = denormalize( x, this.array ); return x; } setX( index, x ) { if ( this.normalized ) x = normalize( x, this.array ); this.array[ index * this.itemSize ] = x; return this; } getY( index ) { let y = this.array[ index * this.itemSize + 1 ]; if ( this.normalized ) y = denormalize( y, this.array ); return y; } setY( index, y ) { if ( this.normalized ) y = normalize( y, this.array ); this.array[ index * this.itemSize + 1 ] = y; return this; } getZ( index ) { let z = this.array[ index * this.itemSize + 2 ]; if ( this.normalized ) z = denormalize( z, this.array ); return z; } setZ( index, z ) { if ( this.normalized ) z = normalize( z, this.array ); this.array[ index * this.itemSize + 2 ] = z; return this; } getW( index ) { let w = this.array[ index * this.itemSize + 3 ]; if ( this.normalized ) w = denormalize( w, this.array ); return w; } setW( index, w ) { if ( this.normalized ) w = normalize( w, this.array ); this.array[ index * this.itemSize + 3 ] = w; return this; } setXY( index, x, y ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); } this.array[ index + 0 ] = x; this.array[ index + 1 ] = y; return this; } setXYZ( index, x, y, z ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); } this.array[ index + 0 ] = x; this.array[ index + 1 ] = y; this.array[ index + 2 ] = z; return this; } setXYZW( index, x, y, z, w ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); w = normalize( w, this.array ); } this.array[ index + 0 ] = x; this.array[ index + 1 ] = y; this.array[ index + 2 ] = z; this.array[ index + 3 ] = w; return this; } onUpload( callback ) { this.onUploadCallback = callback; return this; } clone() { return new this.constructor( this.array, this.itemSize ).copy( this ); } toJSON() { const data = { itemSize: this.itemSize, type: this.array.constructor.name, array: Array.from( this.array ), normalized: this.normalized }; if ( this.name !== '' ) data.name = this.name; if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; return data; } } // class Int8BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Int8Array( array ), itemSize, normalized ); } } class Uint8BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Uint8Array( array ), itemSize, normalized ); } } class Uint8ClampedBufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Uint8ClampedArray( array ), itemSize, normalized ); } } class Int16BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Int16Array( array ), itemSize, normalized ); } } class Uint16BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Uint16Array( array ), itemSize, normalized ); } } class Int32BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Int32Array( array ), itemSize, normalized ); } } class Uint32BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Uint32Array( array ), itemSize, normalized ); } } class Float16BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Uint16Array( array ), itemSize, normalized ); this.isFloat16BufferAttribute = true; } getX( index ) { let x = fromHalfFloat( this.array[ index * this.itemSize ] ); if ( this.normalized ) x = denormalize( x, this.array ); return x; } setX( index, x ) { if ( this.normalized ) x = normalize( x, this.array ); this.array[ index * this.itemSize ] = toHalfFloat( x ); return this; } getY( index ) { let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); if ( this.normalized ) y = denormalize( y, this.array ); return y; } setY( index, y ) { if ( this.normalized ) y = normalize( y, this.array ); this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); return this; } getZ( index ) { let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); if ( this.normalized ) z = denormalize( z, this.array ); return z; } setZ( index, z ) { if ( this.normalized ) z = normalize( z, this.array ); this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); return this; } getW( index ) { let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); if ( this.normalized ) w = denormalize( w, this.array ); return w; } setW( index, w ) { if ( this.normalized ) w = normalize( w, this.array ); this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); return this; } setXY( index, x, y ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); } this.array[ index + 0 ] = toHalfFloat( x ); this.array[ index + 1 ] = toHalfFloat( y ); return this; } setXYZ( index, x, y, z ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); } this.array[ index + 0 ] = toHalfFloat( x ); this.array[ index + 1 ] = toHalfFloat( y ); this.array[ index + 2 ] = toHalfFloat( z ); return this; } setXYZW( index, x, y, z, w ) { index *= this.itemSize; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); w = normalize( w, this.array ); } this.array[ index + 0 ] = toHalfFloat( x ); this.array[ index + 1 ] = toHalfFloat( y ); this.array[ index + 2 ] = toHalfFloat( z ); this.array[ index + 3 ] = toHalfFloat( w ); return this; } } class Float32BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Float32Array( array ), itemSize, normalized ); } } class Float64BufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized ) { super( new Float64Array( array ), itemSize, normalized ); } } let _id$2 = 0; const _m1 = /*@__PURE__*/ new Matrix4(); const _obj = /*@__PURE__*/ new Object3D(); const _offset = /*@__PURE__*/ new Vector3(); const _box$2 = /*@__PURE__*/ new Box3(); const _boxMorphTargets = /*@__PURE__*/ new Box3(); const _vector$8 = /*@__PURE__*/ new Vector3(); class BufferGeometry extends EventDispatcher { constructor() { super(); this.isBufferGeometry = true; Object.defineProperty( this, 'id', { value: _id$2 ++ } ); this.uuid = generateUUID(); this.name = ''; this.type = 'BufferGeometry'; this.index = null; this.attributes = {}; this.morphAttributes = {}; this.morphTargetsRelative = false; this.groups = []; this.boundingBox = null; this.boundingSphere = null; this.drawRange = { start: 0, count: Infinity }; this.userData = {}; } getIndex() { return this.index; } setIndex( index ) { if ( Array.isArray( index ) ) { this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); } else { this.index = index; } return this; } getAttribute( name ) { return this.attributes[ name ]; } setAttribute( name, attribute ) { this.attributes[ name ] = attribute; return this; } deleteAttribute( name ) { delete this.attributes[ name ]; return this; } hasAttribute( name ) { return this.attributes[ name ] !== undefined; } addGroup( start, count, materialIndex = 0 ) { this.groups.push( { start: start, count: count, materialIndex: materialIndex } ); } clearGroups() { this.groups = []; } setDrawRange( start, count ) { this.drawRange.start = start; this.drawRange.count = count; } applyMatrix4( matrix ) { const position = this.attributes.position; if ( position !== undefined ) { position.applyMatrix4( matrix ); position.needsUpdate = true; } const normal = this.attributes.normal; if ( normal !== undefined ) { const normalMatrix = new Matrix3().getNormalMatrix( matrix ); normal.applyNormalMatrix( normalMatrix ); normal.needsUpdate = true; } const tangent = this.attributes.tangent; if ( tangent !== undefined ) { tangent.transformDirection( matrix ); tangent.needsUpdate = true; } if ( this.boundingBox !== null ) { this.computeBoundingBox(); } if ( this.boundingSphere !== null ) { this.computeBoundingSphere(); } return this; } applyQuaternion( q ) { _m1.makeRotationFromQuaternion( q ); this.applyMatrix4( _m1 ); return this; } rotateX( angle ) { // rotate geometry around world x-axis _m1.makeRotationX( angle ); this.applyMatrix4( _m1 ); return this; } rotateY( angle ) { // rotate geometry around world y-axis _m1.makeRotationY( angle ); this.applyMatrix4( _m1 ); return this; } rotateZ( angle ) { // rotate geometry around world z-axis _m1.makeRotationZ( angle ); this.applyMatrix4( _m1 ); return this; } translate( x, y, z ) { // translate geometry _m1.makeTranslation( x, y, z ); this.applyMatrix4( _m1 ); return this; } scale( x, y, z ) { // scale geometry _m1.makeScale( x, y, z ); this.applyMatrix4( _m1 ); return this; } lookAt( vector ) { _obj.lookAt( vector ); _obj.updateMatrix(); this.applyMatrix4( _obj.matrix ); return this; } center() { this.computeBoundingBox(); this.boundingBox.getCenter( _offset ).negate(); this.translate( _offset.x, _offset.y, _offset.z ); return this; } setFromPoints( points ) { const position = []; for ( let i = 0, l = points.length; i < l; i ++ ) { const point = points[ i ]; position.push( point.x, point.y, point.z || 0 ); } this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); return this; } computeBoundingBox() { if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; if ( position && position.isGLBufferAttribute ) { console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this ); this.boundingBox.set( new Vector3( - Infinity, - Infinity, - Infinity ), new Vector3( + Infinity, + Infinity, + Infinity ) ); return; } if ( position !== undefined ) { this.boundingBox.setFromBufferAttribute( position ); // process morph attributes if present if ( morphAttributesPosition ) { for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { const morphAttribute = morphAttributesPosition[ i ]; _box$2.setFromBufferAttribute( morphAttribute ); if ( this.morphTargetsRelative ) { _vector$8.addVectors( this.boundingBox.min, _box$2.min ); this.boundingBox.expandByPoint( _vector$8 ); _vector$8.addVectors( this.boundingBox.max, _box$2.max ); this.boundingBox.expandByPoint( _vector$8 ); } else { this.boundingBox.expandByPoint( _box$2.min ); this.boundingBox.expandByPoint( _box$2.max ); } } } } else { this.boundingBox.makeEmpty(); } if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); } } computeBoundingSphere() { if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; if ( position && position.isGLBufferAttribute ) { console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this ); this.boundingSphere.set( new Vector3(), Infinity ); return; } if ( position ) { // first, find the center of the bounding sphere const center = this.boundingSphere.center; _box$2.setFromBufferAttribute( position ); // process morph attributes if present if ( morphAttributesPosition ) { for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { const morphAttribute = morphAttributesPosition[ i ]; _boxMorphTargets.setFromBufferAttribute( morphAttribute ); if ( this.morphTargetsRelative ) { _vector$8.addVectors( _box$2.min, _boxMorphTargets.min ); _box$2.expandByPoint( _vector$8 ); _vector$8.addVectors( _box$2.max, _boxMorphTargets.max ); _box$2.expandByPoint( _vector$8 ); } else { _box$2.expandByPoint( _boxMorphTargets.min ); _box$2.expandByPoint( _boxMorphTargets.max ); } } } _box$2.getCenter( center ); // second, try to find a boundingSphere with a radius smaller than the // boundingSphere of the boundingBox: sqrt(3) smaller in the best case let maxRadiusSq = 0; for ( let i = 0, il = position.count; i < il; i ++ ) { _vector$8.fromBufferAttribute( position, i ); maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); } // process morph attributes if present if ( morphAttributesPosition ) { for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { const morphAttribute = morphAttributesPosition[ i ]; const morphTargetsRelative = this.morphTargetsRelative; for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { _vector$8.fromBufferAttribute( morphAttribute, j ); if ( morphTargetsRelative ) { _offset.fromBufferAttribute( position, j ); _vector$8.add( _offset ); } maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); } } } this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); if ( isNaN( this.boundingSphere.radius ) ) { console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); } } } computeTangents() { const index = this.index; const attributes = this.attributes; // based on http://www.terathon.com/code/tangent.html // (per vertex tangents) if ( index === null || attributes.position === undefined || attributes.normal === undefined || attributes.uv === undefined ) { console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); return; } const indices = index.array; const positions = attributes.position.array; const normals = attributes.normal.array; const uvs = attributes.uv.array; const nVertices = positions.length / 3; if ( this.hasAttribute( 'tangent' ) === false ) { this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) ); } const tangents = this.getAttribute( 'tangent' ).array; const tan1 = [], tan2 = []; for ( let i = 0; i < nVertices; i ++ ) { tan1[ i ] = new Vector3(); tan2[ i ] = new Vector3(); } const vA = new Vector3(), vB = new Vector3(), vC = new Vector3(), uvA = new Vector2(), uvB = new Vector2(), uvC = new Vector2(), sdir = new Vector3(), tdir = new Vector3(); function handleTriangle( a, b, c ) { vA.fromArray( positions, a * 3 ); vB.fromArray( positions, b * 3 ); vC.fromArray( positions, c * 3 ); uvA.fromArray( uvs, a * 2 ); uvB.fromArray( uvs, b * 2 ); uvC.fromArray( uvs, c * 2 ); vB.sub( vA ); vC.sub( vA ); uvB.sub( uvA ); uvC.sub( uvA ); const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); // silently ignore degenerate uv triangles having coincident or colinear vertices if ( ! isFinite( r ) ) return; sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); tan1[ a ].add( sdir ); tan1[ b ].add( sdir ); tan1[ c ].add( sdir ); tan2[ a ].add( tdir ); tan2[ b ].add( tdir ); tan2[ c ].add( tdir ); } let groups = this.groups; if ( groups.length === 0 ) { groups = [ { start: 0, count: indices.length } ]; } for ( let i = 0, il = groups.length; i < il; ++ i ) { const group = groups[ i ]; const start = group.start; const count = group.count; for ( let j = start, jl = start + count; j < jl; j += 3 ) { handleTriangle( indices[ j + 0 ], indices[ j + 1 ], indices[ j + 2 ] ); } } const tmp = new Vector3(), tmp2 = new Vector3(); const n = new Vector3(), n2 = new Vector3(); function handleVertex( v ) { n.fromArray( normals, v * 3 ); n2.copy( n ); const t = tan1[ v ]; // Gram-Schmidt orthogonalize tmp.copy( t ); tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); // Calculate handedness tmp2.crossVectors( n2, t ); const test = tmp2.dot( tan2[ v ] ); const w = ( test < 0.0 ) ? - 1.0 : 1.0; tangents[ v * 4 ] = tmp.x; tangents[ v * 4 + 1 ] = tmp.y; tangents[ v * 4 + 2 ] = tmp.z; tangents[ v * 4 + 3 ] = w; } for ( let i = 0, il = groups.length; i < il; ++ i ) { const group = groups[ i ]; const start = group.start; const count = group.count; for ( let j = start, jl = start + count; j < jl; j += 3 ) { handleVertex( indices[ j + 0 ] ); handleVertex( indices[ j + 1 ] ); handleVertex( indices[ j + 2 ] ); } } } computeVertexNormals() { const index = this.index; const positionAttribute = this.getAttribute( 'position' ); if ( positionAttribute !== undefined ) { let normalAttribute = this.getAttribute( 'normal' ); if ( normalAttribute === undefined ) { normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); this.setAttribute( 'normal', normalAttribute ); } else { // reset existing normals to zero for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { normalAttribute.setXYZ( i, 0, 0, 0 ); } } const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); const cb = new Vector3(), ab = new Vector3(); // indexed elements if ( index ) { for ( let i = 0, il = index.count; i < il; i += 3 ) { const vA = index.getX( i + 0 ); const vB = index.getX( i + 1 ); const vC = index.getX( i + 2 ); pA.fromBufferAttribute( positionAttribute, vA ); pB.fromBufferAttribute( positionAttribute, vB ); pC.fromBufferAttribute( positionAttribute, vC ); cb.subVectors( pC, pB ); ab.subVectors( pA, pB ); cb.cross( ab ); nA.fromBufferAttribute( normalAttribute, vA ); nB.fromBufferAttribute( normalAttribute, vB ); nC.fromBufferAttribute( normalAttribute, vC ); nA.add( cb ); nB.add( cb ); nC.add( cb ); normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); } } else { // non-indexed elements (unconnected triangle soup) for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { pA.fromBufferAttribute( positionAttribute, i + 0 ); pB.fromBufferAttribute( positionAttribute, i + 1 ); pC.fromBufferAttribute( positionAttribute, i + 2 ); cb.subVectors( pC, pB ); ab.subVectors( pA, pB ); cb.cross( ab ); normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); } } this.normalizeNormals(); normalAttribute.needsUpdate = true; } } normalizeNormals() { const normals = this.attributes.normal; for ( let i = 0, il = normals.count; i < il; i ++ ) { _vector$8.fromBufferAttribute( normals, i ); _vector$8.normalize(); normals.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); } } toNonIndexed() { function convertBufferAttribute( attribute, indices ) { const array = attribute.array; const itemSize = attribute.itemSize; const normalized = attribute.normalized; const array2 = new array.constructor( indices.length * itemSize ); let index = 0, index2 = 0; for ( let i = 0, l = indices.length; i < l; i ++ ) { if ( attribute.isInterleavedBufferAttribute ) { index = indices[ i ] * attribute.data.stride + attribute.offset; } else { index = indices[ i ] * itemSize; } for ( let j = 0; j < itemSize; j ++ ) { array2[ index2 ++ ] = array[ index ++ ]; } } return new BufferAttribute( array2, itemSize, normalized ); } // if ( this.index === null ) { console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); return this; } const geometry2 = new BufferGeometry(); const indices = this.index.array; const attributes = this.attributes; // attributes for ( const name in attributes ) { const attribute = attributes[ name ]; const newAttribute = convertBufferAttribute( attribute, indices ); geometry2.setAttribute( name, newAttribute ); } // morph attributes const morphAttributes = this.morphAttributes; for ( const name in morphAttributes ) { const morphArray = []; const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { const attribute = morphAttribute[ i ]; const newAttribute = convertBufferAttribute( attribute, indices ); morphArray.push( newAttribute ); } geometry2.morphAttributes[ name ] = morphArray; } geometry2.morphTargetsRelative = this.morphTargetsRelative; // groups const groups = this.groups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; geometry2.addGroup( group.start, group.count, group.materialIndex ); } return geometry2; } toJSON() { const data = { metadata: { version: 4.6, type: 'BufferGeometry', generator: 'BufferGeometry.toJSON' } }; // standard BufferGeometry serialization data.uuid = this.uuid; data.type = this.type; if ( this.name !== '' ) data.name = this.name; if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; if ( this.parameters !== undefined ) { const parameters = this.parameters; for ( const key in parameters ) { if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; } return data; } // for simplicity the code assumes attributes are not shared across geometries, see #15811 data.data = { attributes: {} }; const index = this.index; if ( index !== null ) { data.data.index = { type: index.array.constructor.name, array: Array.prototype.slice.call( index.array ) }; } const attributes = this.attributes; for ( const key in attributes ) { const attribute = attributes[ key ]; data.data.attributes[ key ] = attribute.toJSON( data.data ); } const morphAttributes = {}; let hasMorphAttributes = false; for ( const key in this.morphAttributes ) { const attributeArray = this.morphAttributes[ key ]; const array = []; for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { const attribute = attributeArray[ i ]; array.push( attribute.toJSON( data.data ) ); } if ( array.length > 0 ) { morphAttributes[ key ] = array; hasMorphAttributes = true; } } if ( hasMorphAttributes ) { data.data.morphAttributes = morphAttributes; data.data.morphTargetsRelative = this.morphTargetsRelative; } const groups = this.groups; if ( groups.length > 0 ) { data.data.groups = JSON.parse( JSON.stringify( groups ) ); } const boundingSphere = this.boundingSphere; if ( boundingSphere !== null ) { data.data.boundingSphere = { center: boundingSphere.center.toArray(), radius: boundingSphere.radius }; } return data; } clone() { return new this.constructor().copy( this ); } copy( source ) { // reset this.index = null; this.attributes = {}; this.morphAttributes = {}; this.groups = []; this.boundingBox = null; this.boundingSphere = null; // used for storing cloned, shared data const data = {}; // name this.name = source.name; // index const index = source.index; if ( index !== null ) { this.setIndex( index.clone( data ) ); } // attributes const attributes = source.attributes; for ( const name in attributes ) { const attribute = attributes[ name ]; this.setAttribute( name, attribute.clone( data ) ); } // morph attributes const morphAttributes = source.morphAttributes; for ( const name in morphAttributes ) { const array = []; const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { array.push( morphAttribute[ i ].clone( data ) ); } this.morphAttributes[ name ] = array; } this.morphTargetsRelative = source.morphTargetsRelative; // groups const groups = source.groups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; this.addGroup( group.start, group.count, group.materialIndex ); } // bounding box const boundingBox = source.boundingBox; if ( boundingBox !== null ) { this.boundingBox = boundingBox.clone(); } // bounding sphere const boundingSphere = source.boundingSphere; if ( boundingSphere !== null ) { this.boundingSphere = boundingSphere.clone(); } // draw range this.drawRange.start = source.drawRange.start; this.drawRange.count = source.drawRange.count; // user data this.userData = source.userData; return this; } dispose() { this.dispatchEvent( { type: 'dispose' } ); } } const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); const _ray$3 = /*@__PURE__*/ new Ray(); const _sphere$6 = /*@__PURE__*/ new Sphere(); const _sphereHitAt = /*@__PURE__*/ new Vector3(); const _vA$1 = /*@__PURE__*/ new Vector3(); const _vB$1 = /*@__PURE__*/ new Vector3(); const _vC$1 = /*@__PURE__*/ new Vector3(); const _tempA = /*@__PURE__*/ new Vector3(); const _morphA = /*@__PURE__*/ new Vector3(); const _uvA$1 = /*@__PURE__*/ new Vector2(); const _uvB$1 = /*@__PURE__*/ new Vector2(); const _uvC$1 = /*@__PURE__*/ new Vector2(); const _normalA = /*@__PURE__*/ new Vector3(); const _normalB = /*@__PURE__*/ new Vector3(); const _normalC = /*@__PURE__*/ new Vector3(); const _intersectionPoint = /*@__PURE__*/ new Vector3(); const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); class Mesh extends Object3D { constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { super(); this.isMesh = true; this.type = 'Mesh'; this.geometry = geometry; this.material = material; this.updateMorphTargets(); } copy( source, recursive ) { super.copy( source, recursive ); if ( source.morphTargetInfluences !== undefined ) { this.morphTargetInfluences = source.morphTargetInfluences.slice(); } if ( source.morphTargetDictionary !== undefined ) { this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); } this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; this.geometry = source.geometry; return this; } updateMorphTargets() { const geometry = this.geometry; const morphAttributes = geometry.morphAttributes; const keys = Object.keys( morphAttributes ); if ( keys.length > 0 ) { const morphAttribute = morphAttributes[ keys[ 0 ] ]; if ( morphAttribute !== undefined ) { this.morphTargetInfluences = []; this.morphTargetDictionary = {}; for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { const name = morphAttribute[ m ].name || String( m ); this.morphTargetInfluences.push( 0 ); this.morphTargetDictionary[ name ] = m; } } } } getVertexPosition( index, target ) { const geometry = this.geometry; const position = geometry.attributes.position; const morphPosition = geometry.morphAttributes.position; const morphTargetsRelative = geometry.morphTargetsRelative; target.fromBufferAttribute( position, index ); const morphInfluences = this.morphTargetInfluences; if ( morphPosition && morphInfluences ) { _morphA.set( 0, 0, 0 ); for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { const influence = morphInfluences[ i ]; const morphAttribute = morphPosition[ i ]; if ( influence === 0 ) continue; _tempA.fromBufferAttribute( morphAttribute, index ); if ( morphTargetsRelative ) { _morphA.addScaledVector( _tempA, influence ); } else { _morphA.addScaledVector( _tempA.sub( target ), influence ); } } target.add( _morphA ); } return target; } raycast( raycaster, intersects ) { const geometry = this.geometry; const material = this.material; const matrixWorld = this.matrixWorld; if ( material === undefined ) return; // test with bounding sphere in world space if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _sphere$6.copy( geometry.boundingSphere ); _sphere$6.applyMatrix4( matrixWorld ); // check distance from ray origin to bounding sphere _ray$3.copy( raycaster.ray ).recast( raycaster.near ); if ( _sphere$6.containsPoint( _ray$3.origin ) === false ) { if ( _ray$3.intersectSphere( _sphere$6, _sphereHitAt ) === null ) return; if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; } // convert ray to local space of mesh _inverseMatrix$3.copy( matrixWorld ).invert(); _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); // test with bounding box in local space if ( geometry.boundingBox !== null ) { if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; } // test for intersections with geometry this._computeIntersections( raycaster, intersects, _ray$3 ); } _computeIntersections( raycaster, intersects, rayLocalSpace ) { let intersection; const geometry = this.geometry; const material = this.material; const index = geometry.index; const position = geometry.attributes.position; const uv = geometry.attributes.uv; const uv1 = geometry.attributes.uv1; const normal = geometry.attributes.normal; const groups = geometry.groups; const drawRange = geometry.drawRange; if ( index !== null ) { // indexed buffer geometry if ( Array.isArray( material ) ) { for ( let i = 0, il = groups.length; i < il; i ++ ) { const group = groups[ i ]; const groupMaterial = material[ group.materialIndex ]; const start = Math.max( group.start, drawRange.start ); const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); for ( let j = start, jl = end; j < jl; j += 3 ) { const a = index.getX( j ); const b = index.getX( j + 1 ); const c = index.getX( j + 2 ); intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); if ( intersection ) { intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics intersection.face.materialIndex = group.materialIndex; intersects.push( intersection ); } } } } else { const start = Math.max( 0, drawRange.start ); const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, il = end; i < il; i += 3 ) { const a = index.getX( i ); const b = index.getX( i + 1 ); const c = index.getX( i + 2 ); intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); if ( intersection ) { intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics intersects.push( intersection ); } } } } else if ( position !== undefined ) { // non-indexed buffer geometry if ( Array.isArray( material ) ) { for ( let i = 0, il = groups.length; i < il; i ++ ) { const group = groups[ i ]; const groupMaterial = material[ group.materialIndex ]; const start = Math.max( group.start, drawRange.start ); const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); for ( let j = start, jl = end; j < jl; j += 3 ) { const a = j; const b = j + 1; const c = j + 2; intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); if ( intersection ) { intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics intersection.face.materialIndex = group.materialIndex; intersects.push( intersection ); } } } } else { const start = Math.max( 0, drawRange.start ); const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, il = end; i < il; i += 3 ) { const a = i; const b = i + 1; const c = i + 2; intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); if ( intersection ) { intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics intersects.push( intersection ); } } } } } } function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { let intersect; if ( material.side === BackSide ) { intersect = ray.intersectTriangle( pC, pB, pA, true, point ); } else { intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); } if ( intersect === null ) return null; _intersectionPointWorld.copy( point ); _intersectionPointWorld.applyMatrix4( object.matrixWorld ); const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); if ( distance < raycaster.near || distance > raycaster.far ) return null; return { distance: distance, point: _intersectionPointWorld.clone(), object: object }; } function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { object.getVertexPosition( a, _vA$1 ); object.getVertexPosition( b, _vB$1 ); object.getVertexPosition( c, _vC$1 ); const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); if ( intersection ) { if ( uv ) { _uvA$1.fromBufferAttribute( uv, a ); _uvB$1.fromBufferAttribute( uv, b ); _uvC$1.fromBufferAttribute( uv, c ); intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); } if ( uv1 ) { _uvA$1.fromBufferAttribute( uv1, a ); _uvB$1.fromBufferAttribute( uv1, b ); _uvC$1.fromBufferAttribute( uv1, c ); intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); intersection.uv2 = intersection.uv1; // @deprecated, r152 } if ( normal ) { _normalA.fromBufferAttribute( normal, a ); _normalB.fromBufferAttribute( normal, b ); _normalC.fromBufferAttribute( normal, c ); intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); if ( intersection.normal.dot( ray.direction ) > 0 ) { intersection.normal.multiplyScalar( - 1 ); } } const face = { a: a, b: b, c: c, normal: new Vector3(), materialIndex: 0 }; Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); intersection.face = face; } return intersection; } class BoxGeometry extends BufferGeometry { constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { super(); this.type = 'BoxGeometry'; this.parameters = { width: width, height: height, depth: depth, widthSegments: widthSegments, heightSegments: heightSegments, depthSegments: depthSegments }; const scope = this; // segments widthSegments = Math.floor( widthSegments ); heightSegments = Math.floor( heightSegments ); depthSegments = Math.floor( depthSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables let numberOfVertices = 0; let groupStart = 0; // build each side of the box geometry buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { const segmentWidth = width / gridX; const segmentHeight = height / gridY; const widthHalf = width / 2; const heightHalf = height / 2; const depthHalf = depth / 2; const gridX1 = gridX + 1; const gridY1 = gridY + 1; let vertexCounter = 0; let groupCount = 0; const vector = new Vector3(); // generate vertices, normals and uvs for ( let iy = 0; iy < gridY1; iy ++ ) { const y = iy * segmentHeight - heightHalf; for ( let ix = 0; ix < gridX1; ix ++ ) { const x = ix * segmentWidth - widthHalf; // set values to correct vector component vector[ u ] = x * udir; vector[ v ] = y * vdir; vector[ w ] = depthHalf; // now apply vector to vertex buffer vertices.push( vector.x, vector.y, vector.z ); // set values to correct vector component vector[ u ] = 0; vector[ v ] = 0; vector[ w ] = depth > 0 ? 1 : - 1; // now apply vector to normal buffer normals.push( vector.x, vector.y, vector.z ); // uvs uvs.push( ix / gridX ); uvs.push( 1 - ( iy / gridY ) ); // counters vertexCounter += 1; } } // indices // 1. you need three indices to draw a single face // 2. a single segment consists of two faces // 3. so we need to generate six (2*3) indices per segment for ( let iy = 0; iy < gridY; iy ++ ) { for ( let ix = 0; ix < gridX; ix ++ ) { const a = numberOfVertices + ix + gridX1 * iy; const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; // faces indices.push( a, b, d ); indices.push( b, c, d ); // increase counter groupCount += 6; } } // add a group to the geometry. this will ensure multi material support scope.addGroup( groupStart, groupCount, materialIndex ); // calculate new start value for groups groupStart += groupCount; // update total number of vertices numberOfVertices += vertexCounter; } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); } } /** * Uniform Utilities */ function cloneUniforms( src ) { const dst = {}; for ( const u in src ) { dst[ u ] = {}; for ( const p in src[ u ] ) { const property = src[ u ][ p ]; if ( property && ( property.isColor || property.isMatrix3 || property.isMatrix4 || property.isVector2 || property.isVector3 || property.isVector4 || property.isTexture || property.isQuaternion ) ) { if ( property.isRenderTargetTexture ) { console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); dst[ u ][ p ] = null; } else { dst[ u ][ p ] = property.clone(); } } else if ( Array.isArray( property ) ) { dst[ u ][ p ] = property.slice(); } else { dst[ u ][ p ] = property; } } } return dst; } function mergeUniforms( uniforms ) { const merged = {}; for ( let u = 0; u < uniforms.length; u ++ ) { const tmp = cloneUniforms( uniforms[ u ] ); for ( const p in tmp ) { merged[ p ] = tmp[ p ]; } } return merged; } function cloneUniformsGroups( src ) { const dst = []; for ( let u = 0; u < src.length; u ++ ) { dst.push( src[ u ].clone() ); } return dst; } function getUnlitUniformColorSpace( renderer ) { if ( renderer.getRenderTarget() === null ) { // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 return renderer.outputColorSpace; } return ColorManagement.workingColorSpace; } // Legacy const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; class ShaderMaterial extends Material { constructor( parameters ) { super(); this.isShaderMaterial = true; this.type = 'ShaderMaterial'; this.defines = {}; this.uniforms = {}; this.uniformsGroups = []; this.vertexShader = default_vertex; this.fragmentShader = default_fragment; this.linewidth = 1; this.wireframe = false; this.wireframeLinewidth = 1; this.fog = false; // set to use scene fog this.lights = false; // set to use scene lights this.clipping = false; // set to use user-defined clipping planes this.forceSinglePass = true; this.extensions = { derivatives: false, // set to use derivatives fragDepth: false, // set to use fragment depth values drawBuffers: false, // set to use draw buffers shaderTextureLOD: false, // set to use shader texture LOD clipCullDistance: false // set to use vertex shader clipping }; // When rendered geometry doesn't include these attributes but the material does, // use these default values in WebGL. This avoids errors when buffer data is missing. this.defaultAttributeValues = { 'color': [ 1, 1, 1 ], 'uv': [ 0, 0 ], 'uv1': [ 0, 0 ] }; this.index0AttributeName = undefined; this.uniformsNeedUpdate = false; this.glslVersion = null; if ( parameters !== undefined ) { this.setValues( parameters ); } } copy( source ) { super.copy( source ); this.fragmentShader = source.fragmentShader; this.vertexShader = source.vertexShader; this.uniforms = cloneUniforms( source.uniforms ); this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); this.defines = Object.assign( {}, source.defines ); this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.fog = source.fog; this.lights = source.lights; this.clipping = source.clipping; this.extensions = Object.assign( {}, source.extensions ); this.glslVersion = source.glslVersion; return this; } toJSON( meta ) { const data = super.toJSON( meta ); data.glslVersion = this.glslVersion; data.uniforms = {}; for ( const name in this.uniforms ) { const uniform = this.uniforms[ name ]; const value = uniform.value; if ( value && value.isTexture ) { data.uniforms[ name ] = { type: 't', value: value.toJSON( meta ).uuid }; } else if ( value && value.isColor ) { data.uniforms[ name ] = { type: 'c', value: value.getHex() }; } else if ( value && value.isVector2 ) { data.uniforms[ name ] = { type: 'v2', value: value.toArray() }; } else if ( value && value.isVector3 ) { data.uniforms[ name ] = { type: 'v3', value: value.toArray() }; } else if ( value && value.isVector4 ) { data.uniforms[ name ] = { type: 'v4', value: value.toArray() }; } else if ( value && value.isMatrix3 ) { data.uniforms[ name ] = { type: 'm3', value: value.toArray() }; } else if ( value && value.isMatrix4 ) { data.uniforms[ name ] = { type: 'm4', value: value.toArray() }; } else { data.uniforms[ name ] = { value: value }; // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far } } if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; data.vertexShader = this.vertexShader; data.fragmentShader = this.fragmentShader; data.lights = this.lights; data.clipping = this.clipping; const extensions = {}; for ( const key in this.extensions ) { if ( this.extensions[ key ] === true ) extensions[ key ] = true; } if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; return data; } } class Camera extends Object3D { constructor() { super(); this.isCamera = true; this.type = 'Camera'; this.matrixWorldInverse = new Matrix4(); this.projectionMatrix = new Matrix4(); this.projectionMatrixInverse = new Matrix4(); this.coordinateSystem = WebGLCoordinateSystem; } copy( source, recursive ) { super.copy( source, recursive ); this.matrixWorldInverse.copy( source.matrixWorldInverse ); this.projectionMatrix.copy( source.projectionMatrix ); this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); this.coordinateSystem = source.coordinateSystem; return this; } getWorldDirection( target ) { return super.getWorldDirection( target ).negate(); } updateMatrixWorld( force ) { super.updateMatrixWorld( force ); this.matrixWorldInverse.copy( this.matrixWorld ).invert(); } updateWorldMatrix( updateParents, updateChildren ) { super.updateWorldMatrix( updateParents, updateChildren ); this.matrixWorldInverse.copy( this.matrixWorld ).invert(); } clone() { return new this.constructor().copy( this ); } } class PerspectiveCamera extends Camera { constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { super(); this.isPerspectiveCamera = true; this.type = 'PerspectiveCamera'; this.fov = fov; this.zoom = 1; this.near = near; this.far = far; this.focus = 10; this.aspect = aspect; this.view = null; this.filmGauge = 35; // width of the film (default in millimeters) this.filmOffset = 0; // horizontal film offset (same unit as gauge) this.updateProjectionMatrix(); } copy( source, recursive ) { super.copy( source, recursive ); this.fov = source.fov; this.zoom = source.zoom; this.near = source.near; this.far = source.far; this.focus = source.focus; this.aspect = source.aspect; this.view = source.view === null ? null : Object.assign( {}, source.view ); this.filmGauge = source.filmGauge; this.filmOffset = source.filmOffset; return this; } /** * Sets the FOV by focal length in respect to the current .filmGauge. * * The default film gauge is 35, so that the focal length can be specified for * a 35mm (full frame) camera. * * Values for focal length and film gauge must have the same unit. */ setFocalLength( focalLength ) { /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); this.updateProjectionMatrix(); } /** * Calculates the focal length from the current .fov and .filmGauge. */ getFocalLength() { const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); return 0.5 * this.getFilmHeight() / vExtentSlope; } getEffectiveFOV() { return RAD2DEG * 2 * Math.atan( Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); } getFilmWidth() { // film not completely covered in portrait format (aspect < 1) return this.filmGauge * Math.min( this.aspect, 1 ); } getFilmHeight() { // film not completely covered in landscape format (aspect > 1) return this.filmGauge / Math.max( this.aspect, 1 ); } /** * Sets an offset in a larger frustum. This is useful for multi-window or * multi-monitor/multi-machine setups. * * For example, if you have 3x2 monitors and each monitor is 1920x1080 and * the monitors are in grid like this * * +---+---+---+ * | A | B | C | * +---+---+---+ * | D | E | F | * +---+---+---+ * * then for each monitor you would call it like this * * const w = 1920; * const h = 1080; * const fullWidth = w * 3; * const fullHeight = h * 2; * * --A-- * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); * --B-- * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); * --C-- * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); * --D-- * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); * --E-- * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); * --F-- * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); * * Note there is no reason monitors have to be the same size or in a grid. */ setViewOffset( fullWidth, fullHeight, x, y, width, height ) { this.aspect = fullWidth / fullHeight; if ( this.view === null ) { this.view = { enabled: true, fullWidth: 1, fullHeight: 1, offsetX: 0, offsetY: 0, width: 1, height: 1 }; } this.view.enabled = true; this.view.fullWidth = fullWidth; this.view.fullHeight = fullHeight; this.view.offsetX = x; this.view.offsetY = y; this.view.width = width; this.view.height = height; this.updateProjectionMatrix(); } clearViewOffset() { if ( this.view !== null ) { this.view.enabled = false; } this.updateProjectionMatrix(); } updateProjectionMatrix() { const near = this.near; let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; let height = 2 * top; let width = this.aspect * height; let left = - 0.5 * width; const view = this.view; if ( this.view !== null && this.view.enabled ) { const fullWidth = view.fullWidth, fullHeight = view.fullHeight; left += view.offsetX * width / fullWidth; top -= view.offsetY * height / fullHeight; width *= view.width / fullWidth; height *= view.height / fullHeight; } const skew = this.filmOffset; if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem ); this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); } toJSON( meta ) { const data = super.toJSON( meta ); data.object.fov = this.fov; data.object.zoom = this.zoom; data.object.near = this.near; data.object.far = this.far; data.object.focus = this.focus; data.object.aspect = this.aspect; if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); data.object.filmGauge = this.filmGauge; data.object.filmOffset = this.filmOffset; return data; } } const fov = - 90; // negative fov is not an error const aspect = 1; class CubeCamera extends Object3D { constructor( near, far, renderTarget ) { super(); this.type = 'CubeCamera'; this.renderTarget = renderTarget; this.coordinateSystem = null; this.activeMipmapLevel = 0; const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); cameraPX.layers = this.layers; this.add( cameraPX ); const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); cameraNX.layers = this.layers; this.add( cameraNX ); const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); cameraPY.layers = this.layers; this.add( cameraPY ); const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); cameraNY.layers = this.layers; this.add( cameraNY ); const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); cameraPZ.layers = this.layers; this.add( cameraPZ ); const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); cameraNZ.layers = this.layers; this.add( cameraNZ ); } updateCoordinateSystem() { const coordinateSystem = this.coordinateSystem; const cameras = this.children.concat(); const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; for ( const camera of cameras ) this.remove( camera ); if ( coordinateSystem === WebGLCoordinateSystem ) { cameraPX.up.set( 0, 1, 0 ); cameraPX.lookAt( 1, 0, 0 ); cameraNX.up.set( 0, 1, 0 ); cameraNX.lookAt( - 1, 0, 0 ); cameraPY.up.set( 0, 0, - 1 ); cameraPY.lookAt( 0, 1, 0 ); cameraNY.up.set( 0, 0, 1 ); cameraNY.lookAt( 0, - 1, 0 ); cameraPZ.up.set( 0, 1, 0 ); cameraPZ.lookAt( 0, 0, 1 ); cameraNZ.up.set( 0, 1, 0 ); cameraNZ.lookAt( 0, 0, - 1 ); } else if ( coordinateSystem === WebGPUCoordinateSystem ) { cameraPX.up.set( 0, - 1, 0 ); cameraPX.lookAt( - 1, 0, 0 ); cameraNX.up.set( 0, - 1, 0 ); cameraNX.lookAt( 1, 0, 0 ); cameraPY.up.set( 0, 0, 1 ); cameraPY.lookAt( 0, 1, 0 ); cameraNY.up.set( 0, 0, - 1 ); cameraNY.lookAt( 0, - 1, 0 ); cameraPZ.up.set( 0, - 1, 0 ); cameraPZ.lookAt( 0, 0, 1 ); cameraNZ.up.set( 0, - 1, 0 ); cameraNZ.lookAt( 0, 0, - 1 ); } else { throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); } for ( const camera of cameras ) { this.add( camera ); camera.updateMatrixWorld(); } } update( renderer, scene ) { if ( this.parent === null ) this.updateMatrixWorld(); const { renderTarget, activeMipmapLevel } = this; if ( this.coordinateSystem !== renderer.coordinateSystem ) { this.coordinateSystem = renderer.coordinateSystem; this.updateCoordinateSystem(); } const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; const currentRenderTarget = renderer.getRenderTarget(); const currentActiveCubeFace = renderer.getActiveCubeFace(); const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); const currentXrEnabled = renderer.xr.enabled; renderer.xr.enabled = false; const generateMipmaps = renderTarget.texture.generateMipmaps; renderTarget.texture.generateMipmaps = false; renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); renderer.render( scene, cameraPX ); renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); renderer.render( scene, cameraNX ); renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); renderer.render( scene, cameraPY ); renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); renderer.render( scene, cameraNY ); renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); renderer.render( scene, cameraPZ ); // mipmaps are generated during the last call of render() // at this point, all sides of the cube render target are defined renderTarget.texture.generateMipmaps = generateMipmaps; renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); renderer.render( scene, cameraNZ ); renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); renderer.xr.enabled = currentXrEnabled; renderTarget.texture.needsPMREMUpdate = true; } } class CubeTexture extends Texture { constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { images = images !== undefined ? images : []; mapping = mapping !== undefined ? mapping : CubeReflectionMapping; super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); this.isCubeTexture = true; this.flipY = false; } get images() { return this.image; } set images( value ) { this.image = value; } } class WebGLCubeRenderTarget extends WebGLRenderTarget { constructor( size = 1, options = {} ) { super( size, size, options ); this.isWebGLCubeRenderTarget = true; const image = { width: size, height: size, depth: 1 }; const images = [ image, image, image, image, image, image ]; if ( options.encoding !== undefined ) { // @deprecated, r152 warnOnce( 'THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace.' ); options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; } this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). this.texture.isRenderTargetTexture = true; this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; } fromEquirectangularTexture( renderer, texture ) { this.texture.type = texture.type; this.texture.colorSpace = texture.colorSpace; this.texture.generateMipmaps = texture.generateMipmaps; this.texture.minFilter = texture.minFilter; this.texture.magFilter = texture.magFilter; const shader = { uniforms: { tEquirect: { value: null }, }, vertexShader: /* glsl */` varying vec3 vWorldDirection; vec3 transformDirection( in vec3 dir, in mat4 matrix ) { return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); } void main() { vWorldDirection = transformDirection( position, modelMatrix ); #include <begin_vertex> #include <project_vertex> } `, fragmentShader: /* glsl */` uniform sampler2D tEquirect; varying vec3 vWorldDirection; #include <common> void main() { vec3 direction = normalize( vWorldDirection ); vec2 sampleUV = equirectUv( direction ); gl_FragColor = texture2D( tEquirect, sampleUV ); } ` }; const geometry = new BoxGeometry( 5, 5, 5 ); const material = new ShaderMaterial( { name: 'CubemapFromEquirect', uniforms: cloneUniforms( shader.uniforms ), vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader, side: BackSide, blending: NoBlending } ); material.uniforms.tEquirect.value = texture; const mesh = new Mesh( geometry, material ); const currentMinFilter = texture.minFilter; // Avoid blurred poles if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; const camera = new CubeCamera( 1, 10, this ); camera.update( renderer, mesh ); texture.minFilter = currentMinFilter; mesh.geometry.dispose(); mesh.material.dispose(); return this; } clear( renderer, color, depth, stencil ) { const currentRenderTarget = renderer.getRenderTarget(); for ( let i = 0; i < 6; i ++ ) { renderer.setRenderTarget( this, i ); renderer.clear( color, depth, stencil ); } renderer.setRenderTarget( currentRenderTarget ); } } const _vector1 = /*@__PURE__*/ new Vector3(); const _vector2 = /*@__PURE__*/ new Vector3(); const _normalMatrix = /*@__PURE__*/ new Matrix3(); class Plane { constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { this.isPlane = true; // normal is assumed to be normalized this.normal = normal; this.constant = constant; } set( normal, constant ) { this.normal.copy( normal ); this.constant = constant; return this; } setComponents( x, y, z, w ) { this.normal.set( x, y, z ); this.constant = w; return this; } setFromNormalAndCoplanarPoint( normal, point ) { this.normal.copy( normal ); this.constant = - point.dot( this.normal ); return this; } setFromCoplanarPoints( a, b, c ) { const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? this.setFromNormalAndCoplanarPoint( normal, a ); return this; } copy( plane ) { this.normal.copy( plane.normal ); this.constant = plane.constant; return this; } normalize() { // Note: will lead to a divide by zero if the plane is invalid. const inverseNormalLength = 1.0 / this.normal.length(); this.normal.multiplyScalar( inverseNormalLength ); this.constant *= inverseNormalLength; return this; } negate() { this.constant *= - 1; this.normal.negate(); return this; } distanceToPoint( point ) { return this.normal.dot( point ) + this.constant; } distanceToSphere( sphere ) { return this.distanceToPoint( sphere.center ) - sphere.radius; } projectPoint( point, target ) { return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); } intersectLine( line, target ) { const direction = line.delta( _vector1 ); const denominator = this.normal.dot( direction ); if ( denominator === 0 ) { // line is coplanar, return origin if ( this.distanceToPoint( line.start ) === 0 ) { return target.copy( line.start ); } // Unsure if this is the correct method to handle this case. return null; } const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; if ( t < 0 || t > 1 ) { return null; } return target.copy( line.start ).addScaledVector( direction, t ); } intersectsLine( line ) { // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. const startSign = this.distanceToPoint( line.start ); const endSign = this.distanceToPoint( line.end ); return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); } intersectsBox( box ) { return box.intersectsPlane( this ); } intersectsSphere( sphere ) { return sphere.intersectsPlane( this ); } coplanarPoint( target ) { return target.copy( this.normal ).multiplyScalar( - this.constant ); } applyMatrix4( matrix, optionalNormalMatrix ) { const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); this.constant = - referencePoint.dot( normal ); return this; } translate( offset ) { this.constant -= offset.dot( this.normal ); return this; } equals( plane ) { return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); } clone() { return new this.constructor().copy( this ); } } const _sphere$5 = /*@__PURE__*/ new Sphere(); const _vector$7 = /*@__PURE__*/ new Vector3(); class Frustum { constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { this.planes = [ p0, p1, p2, p3, p4, p5 ]; } set( p0, p1, p2, p3, p4, p5 ) { const planes = this.planes; planes[ 0 ].copy( p0 ); planes[ 1 ].copy( p1 ); planes[ 2 ].copy( p2 ); planes[ 3 ].copy( p3 ); planes[ 4 ].copy( p4 ); planes[ 5 ].copy( p5 ); return this; } copy( frustum ) { const planes = this.planes; for ( let i = 0; i < 6; i ++ ) { planes[ i ].copy( frustum.planes[ i ] ); } return this; } setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem ) { const planes = this.planes; const me = m.elements; const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); if ( coordinateSystem === WebGLCoordinateSystem ) { planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); } else if ( coordinateSystem === WebGPUCoordinateSystem ) { planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); } else { throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); } return this; } intersectsObject( object ) { if ( object.boundingSphere !== undefined ) { if ( object.boundingSphere === null ) object.computeBoundingSphere(); _sphere$5.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); } else { const geometry = object.geometry; if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _sphere$5.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); } return this.intersectsSphere( _sphere$5 ); } intersectsSprite( sprite ) { _sphere$5.center.set( 0, 0, 0 ); _sphere$5.radius = 0.7071067811865476; _sphere$5.applyMatrix4( sprite.matrixWorld ); return this.intersectsSphere( _sphere$5 ); } intersectsSphere( sphere ) { const planes = this.planes; const center = sphere.center; const negRadius = - sphere.radius; for ( let i = 0; i < 6; i ++ ) { const distance = planes[ i ].distanceToPoint( center ); if ( distance < negRadius ) { return false; } } return true; } intersectsBox( box ) { const planes = this.planes; for ( let i = 0; i < 6; i ++ ) { const plane = planes[ i ]; // corner at max distance _vector$7.x = plane.normal.x > 0 ? box.max.x : box.min.x; _vector$7.y = plane.normal.y > 0 ? box.max.y : box.min.y; _vector$7.z = plane.normal.z > 0 ? box.max.z : box.min.z; if ( plane.distanceToPoint( _vector$7 ) < 0 ) { return false; } } return true; } containsPoint( point ) { const planes = this.planes; for ( let i = 0; i < 6; i ++ ) { if ( planes[ i ].distanceToPoint( point ) < 0 ) { return false; } } return true; } clone() { return new this.constructor().copy( this ); } } function WebGLAnimation() { let context = null; let isAnimating = false; let animationLoop = null; let requestId = null; function onAnimationFrame( time, frame ) { animationLoop( time, frame ); requestId = context.requestAnimationFrame( onAnimationFrame ); } return { start: function () { if ( isAnimating === true ) return; if ( animationLoop === null ) return; requestId = context.requestAnimationFrame( onAnimationFrame ); isAnimating = true; }, stop: function () { context.cancelAnimationFrame( requestId ); isAnimating = false; }, setAnimationLoop: function ( callback ) { animationLoop = callback; }, setContext: function ( value ) { context = value; } }; } function WebGLAttributes( gl, capabilities ) { const isWebGL2 = capabilities.isWebGL2; const buffers = new WeakMap(); function createBuffer( attribute, bufferType ) { const array = attribute.array; const usage = attribute.usage; const size = array.byteLength; const buffer = gl.createBuffer(); gl.bindBuffer( bufferType, buffer ); gl.bufferData( bufferType, array, usage ); attribute.onUploadCallback(); let type; if ( array instanceof Float32Array ) { type = gl.FLOAT; } else if ( array instanceof Uint16Array ) { if ( attribute.isFloat16BufferAttribute ) { if ( isWebGL2 ) { type = gl.HALF_FLOAT; } else { throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); } } else { type = gl.UNSIGNED_SHORT; } } else if ( array instanceof Int16Array ) { type = gl.SHORT; } else if ( array instanceof Uint32Array ) { type = gl.UNSIGNED_INT; } else if ( array instanceof Int32Array ) { type = gl.INT; } else if ( array instanceof Int8Array ) { type = gl.BYTE; } else if ( array instanceof Uint8Array ) { type = gl.UNSIGNED_BYTE; } else if ( array instanceof Uint8ClampedArray ) { type = gl.UNSIGNED_BYTE; } else { throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); } return { buffer: buffer, type: type, bytesPerElement: array.BYTES_PER_ELEMENT, version: attribute.version, size: size }; } function updateBuffer( buffer, attribute, bufferType ) { const array = attribute.array; const updateRange = attribute._updateRange; // deprecated const updateRanges = attribute.updateRanges; gl.bindBuffer( bufferType, buffer ); if ( updateRange.count === - 1 && updateRanges.length === 0 ) { // Not using update ranges gl.bufferSubData( bufferType, 0, array ); } if ( updateRanges.length !== 0 ) { for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { const range = updateRanges[ i ]; if ( isWebGL2 ) { gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, array, range.start, range.count ); } else { gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, array.subarray( range.start, range.start + range.count ) ); } } attribute.clearUpdateRanges(); } // deprecated if ( updateRange.count !== - 1 ) { if ( isWebGL2 ) { gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, array, updateRange.offset, updateRange.count ); } else { gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); } updateRange.count = - 1; // reset range } attribute.onUploadCallback(); } // function get( attribute ) { if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; return buffers.get( attribute ); } function remove( attribute ) { if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; const data = buffers.get( attribute ); if ( data ) { gl.deleteBuffer( data.buffer ); buffers.delete( attribute ); } } function update( attribute, bufferType ) { if ( attribute.isGLBufferAttribute ) { const cached = buffers.get( attribute ); if ( ! cached || cached.version < attribute.version ) { buffers.set( attribute, { buffer: attribute.buffer, type: attribute.type, bytesPerElement: attribute.elementSize, version: attribute.version } ); } return; } if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; const data = buffers.get( attribute ); if ( data === undefined ) { buffers.set( attribute, createBuffer( attribute, bufferType ) ); } else if ( data.version < attribute.version ) { if ( data.size !== attribute.array.byteLength ) { throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); } updateBuffer( data.buffer, attribute, bufferType ); data.version = attribute.version; } } return { get: get, remove: remove, update: update }; } class PlaneGeometry extends BufferGeometry { constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { super(); this.type = 'PlaneGeometry'; this.parameters = { width: width, height: height, widthSegments: widthSegments, heightSegments: heightSegments }; const width_half = width / 2; const height_half = height / 2; const gridX = Math.floor( widthSegments ); const gridY = Math.floor( heightSegments ); const gridX1 = gridX + 1; const gridY1 = gridY + 1; const segment_width = width / gridX; const segment_height = height / gridY; // const indices = []; const vertices = []; const normals = []; const uvs = []; for ( let iy = 0; iy < gridY1; iy ++ ) { const y = iy * segment_height - height_half; for ( let ix = 0; ix < gridX1; ix ++ ) { const x = ix * segment_width - width_half; vertices.push( x, - y, 0 ); normals.push( 0, 0, 1 ); uvs.push( ix / gridX ); uvs.push( 1 - ( iy / gridY ) ); } } for ( let iy = 0; iy < gridY; iy ++ ) { for ( let ix = 0; ix < gridX; ix ++ ) { const a = ix + gridX1 * iy; const b = ix + gridX1 * ( iy + 1 ); const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); const d = ( ix + 1 ) + gridX1 * iy; indices.push( a, b, d ); indices.push( b, c, d ); } } this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); } } var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; var alphatest_fragment = "#ifdef USE_ALPHATEST\n\tif ( diffuseColor.a < alphaTest ) discard;\n#endif"; var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; var batching_pars_vertex = "#ifdef USE_BATCHING\n\tattribute float batchId;\n\tuniform highp sampler2D batchingTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( batchId );\n#endif"; var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif"; var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; var colorspace_pars_fragment = "\nconst mat3 LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = mat3(\n\tvec3( 0.8224621, 0.177538, 0.0 ),\n\tvec3( 0.0331941, 0.9668058, 0.0 ),\n\tvec3( 0.0170827, 0.0723974, 0.9105199 )\n);\nconst mat3 LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.2249401, - 0.2249404, 0.0 ),\n\tvec3( - 0.0420569, 1.0420571, 0.0 ),\n\tvec3( - 0.0196376, - 0.0786361, 1.0982735 )\n);\nvec4 LinearSRGBToLinearDisplayP3( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_SRGB_TO_LINEAR_DISPLAY_P3, value.a );\n}\nvec4 LinearDisplayP3ToLinearSRGB( in vec4 value ) {\n\treturn vec4( value.rgb * LINEAR_DISPLAY_P3_TO_LINEAR_SRGB, value.a );\n}\nvec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn sRGBTransferOETF( value );\n}"; var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor *= toneMappingExposure;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\treturn color;\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n}"; const vertex$g = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\tgl_Position.z = gl_Position.w;\n}"; const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include <cube_uv_reflection_fragment>\nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n}"; const vertex$f = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\tgl_Position.z = gl_Position.w;\n}"; const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n}"; const vertex$e = "#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvHighPrecisionZW = gl_Position.zw;\n}"; const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <logdepthbuf_fragment>\n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}"; const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main () {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; const vertex$c = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n}"; const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n}"; const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include <common>\n#include <uv_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}"; const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include <common>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}"; const vertex$a = "#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinbase_vertex>\n\t\t#include <skinnormal_vertex>\n\t\t#include <defaultnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <fog_vertex>\n}"; const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <specularmap_fragment>\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include <aomap_fragment>\n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include <envmap_fragment>\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}"; const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_lambert_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <specularmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_lambert_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <color_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n\tvViewPosition = - mvPosition.xyz;\n}"; const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <fog_pars_fragment>\n#include <normal_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include <packing>\n#include <uv_pars_fragment>\n#include <normal_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\t#include <logdepthbuf_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}"; const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_phong_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <specularmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_phong_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <iridescence_fragment>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_physical_pars_fragment>\n#include <transmission_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_pars_fragment>\n#include <iridescence_pars_fragment>\n#include <roughnessmap_pars_fragment>\n#include <metalnessmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <roughnessmap_fragment>\n\t#include <metalnessmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_physical_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include <transmission_fragment>\n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <normal_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <normal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}"; const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <gradientmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <normal_pars_fragment>\n#include <lights_toon_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_toon_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}"; const vertex$3 = "uniform float size;\nuniform float scale;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include <color_vertex>\n\t#include <morphcolor_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <fog_vertex>\n}"; const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <color_pars_fragment>\n#include <map_particle_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_particle_fragment>\n\t#include <color_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}"; const vertex$2 = "#include <common>\n#include <batching_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <shadowmap_pars_vertex>\nvoid main() {\n\t#include <batching_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}"; const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <logdepthbuf_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\nvoid main() {\n\t#include <logdepthbuf_fragment>\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n}"; const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}"; const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\t#include <opaque_fragment>\n\t#include <tonemapping_fragment>\n\t#include <colorspace_fragment>\n\t#include <fog_fragment>\n}"; const ShaderChunk = { alphahash_fragment: alphahash_fragment, alphahash_pars_fragment: alphahash_pars_fragment, alphamap_fragment: alphamap_fragment, alphamap_pars_fragment: alphamap_pars_fragment, alphatest_fragment: alphatest_fragment, alphatest_pars_fragment: alphatest_pars_fragment, aomap_fragment: aomap_fragment, aomap_pars_fragment: aomap_pars_fragment, batching_pars_vertex: batching_pars_vertex, batching_vertex: batching_vertex, begin_vertex: begin_vertex, beginnormal_vertex: beginnormal_vertex, bsdfs: bsdfs, iridescence_fragment: iridescence_fragment, bumpmap_pars_fragment: bumpmap_pars_fragment, clipping_planes_fragment: clipping_planes_fragment, clipping_planes_pars_fragment: clipping_planes_pars_fragment, clipping_planes_pars_vertex: clipping_planes_pars_vertex, clipping_planes_vertex: clipping_planes_vertex, color_fragment: color_fragment, color_pars_fragment: color_pars_fragment, color_pars_vertex: color_pars_vertex, color_vertex: color_vertex, common: common, cube_uv_reflection_fragment: cube_uv_reflection_fragment, defaultnormal_vertex: defaultnormal_vertex, displacementmap_pars_vertex: displacementmap_pars_vertex, displacementmap_vertex: displacementmap_vertex, emissivemap_fragment: emissivemap_fragment, emissivemap_pars_fragment: emissivemap_pars_fragment, colorspace_fragment: colorspace_fragment, colorspace_pars_fragment: colorspace_pars_fragment, envmap_fragment: envmap_fragment, envmap_common_pars_fragment: envmap_common_pars_fragment, envmap_pars_fragment: envmap_pars_fragment, envmap_pars_vertex: envmap_pars_vertex, envmap_physical_pars_fragment: envmap_physical_pars_fragment, envmap_vertex: envmap_vertex, fog_vertex: fog_vertex, fog_pars_vertex: fog_pars_vertex, fog_fragment: fog_fragment, fog_pars_fragment: fog_pars_fragment, gradientmap_pars_fragment: gradientmap_pars_fragment, lightmap_fragment: lightmap_fragment, lightmap_pars_fragment: lightmap_pars_fragment, lights_lambert_fragment: lights_lambert_fragment, lights_lambert_pars_fragment: lights_lambert_pars_fragment, lights_pars_begin: lights_pars_begin, lights_toon_fragment: lights_toon_fragment, lights_toon_pars_fragment: lights_toon_pars_fragment, lights_phong_fragment: lights_phong_fragment, lights_phong_pars_fragment: lights_phong_pars_fragment, lights_physical_fragment: lights_physical_fragment, lights_physical_pars_fragment: lights_physical_pars_fragment, lights_fragment_begin: lights_fragment_begin, lights_fragment_maps: lights_fragment_maps, lights_fragment_end: lights_fragment_end, logdepthbuf_fragment: logdepthbuf_fragment, logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, logdepthbuf_vertex: logdepthbuf_vertex, map_fragment: map_fragment, map_pars_fragment: map_pars_fragment, map_particle_fragment: map_particle_fragment, map_particle_pars_fragment: map_particle_pars_fragment, metalnessmap_fragment: metalnessmap_fragment, metalnessmap_pars_fragment: metalnessmap_pars_fragment, morphcolor_vertex: morphcolor_vertex, morphnormal_vertex: morphnormal_vertex, morphtarget_pars_vertex: morphtarget_pars_vertex, morphtarget_vertex: morphtarget_vertex, normal_fragment_begin: normal_fragment_begin, normal_fragment_maps: normal_fragment_maps, normal_pars_fragment: normal_pars_fragment, normal_pars_vertex: normal_pars_vertex, normal_vertex: normal_vertex, normalmap_pars_fragment: normalmap_pars_fragment, clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, clearcoat_pars_fragment: clearcoat_pars_fragment, iridescence_pars_fragment: iridescence_pars_fragment, opaque_fragment: opaque_fragment, packing: packing, premultiplied_alpha_fragment: premultiplied_alpha_fragment, project_vertex: project_vertex, dithering_fragment: dithering_fragment, dithering_pars_fragment: dithering_pars_fragment, roughnessmap_fragment: roughnessmap_fragment, roughnessmap_pars_fragment: roughnessmap_pars_fragment, shadowmap_pars_fragment: shadowmap_pars_fragment, shadowmap_pars_vertex: shadowmap_pars_vertex, shadowmap_vertex: shadowmap_vertex, shadowmask_pars_fragment: shadowmask_pars_fragment, skinbase_vertex: skinbase_vertex, skinning_pars_vertex: skinning_pars_vertex, skinning_vertex: skinning_vertex, skinnormal_vertex: skinnormal_vertex, specularmap_fragment: specularmap_fragment, specularmap_pars_fragment: specularmap_pars_fragment, tonemapping_fragment: tonemapping_fragment, tonemapping_pars_fragment: tonemapping_pars_fragment, transmission_fragment: transmission_fragment, transmission_pars_fragment: transmission_pars_fragment, uv_pars_fragment: uv_pars_fragment, uv_pars_vertex: uv_pars_vertex, uv_vertex: uv_vertex, worldpos_vertex: worldpos_vertex, background_vert: vertex$h, background_frag: fragment$h, backgroundCube_vert: vertex$g, backgroundCube_frag: fragment$g, cube_vert: vertex$f, cube_frag: fragment$f, depth_vert: vertex$e, depth_frag: fragment$e, distanceRGBA_vert: vertex$d, distanceRGBA_frag: fragment$d, equirect_vert: vertex$c, equirect_frag: fragment$c, linedashed_vert: vertex$b, linedashed_frag: fragment$b, meshbasic_vert: vertex$a, meshbasic_frag: fragment$a, meshlambert_vert: vertex$9, meshlambert_frag: fragment$9, meshmatcap_vert: vertex$8, meshmatcap_frag: fragment$8, meshnormal_vert: vertex$7, meshnormal_frag: fragment$7, meshphong_vert: vertex$6, meshphong_frag: fragment$6, meshphysical_vert: vertex$5, meshphysical_frag: fragment$5, meshtoon_vert: vertex$4, meshtoon_frag: fragment$4, points_vert: vertex$3, points_frag: fragment$3, shadow_vert: vertex$2, shadow_frag: fragment$2, sprite_vert: vertex$1, sprite_frag: fragment$1 }; /** * Uniforms library for shared webgl shaders */ const UniformsLib = { common: { diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, opacity: { value: 1.0 }, map: { value: null }, mapTransform: { value: /*@__PURE__*/ new Matrix3() }, alphaMap: { value: null }, alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, alphaTest: { value: 0 } }, specularmap: { specularMap: { value: null }, specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, envmap: { envMap: { value: null }, flipEnvMap: { value: - 1 }, reflectivity: { value: 1.0 }, // basic, lambert, phong ior: { value: 1.5 }, // physical refractionRatio: { value: 0.98 }, // basic, lambert, phong }, aomap: { aoMap: { value: null }, aoMapIntensity: { value: 1 }, aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, lightmap: { lightMap: { value: null }, lightMapIntensity: { value: 1 }, lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, bumpmap: { bumpMap: { value: null }, bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, bumpScale: { value: 1 } }, normalmap: { normalMap: { value: null }, normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } }, displacementmap: { displacementMap: { value: null }, displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, displacementScale: { value: 1 }, displacementBias: { value: 0 } }, emissivemap: { emissiveMap: { value: null }, emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, metalnessmap: { metalnessMap: { value: null }, metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, roughnessmap: { roughnessMap: { value: null }, roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } }, gradientmap: { gradientMap: { value: null } }, fog: { fogDensity: { value: 0.00025 }, fogNear: { value: 1 }, fogFar: { value: 2000 }, fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } }, lights: { ambientLightColor: { value: [] }, lightProbe: { value: [] }, directionalLights: { value: [], properties: { direction: {}, color: {} } }, directionalLightShadows: { value: [], properties: { shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, shadowMapSize: {} } }, directionalShadowMap: { value: [] }, directionalShadowMatrix: { value: [] }, spotLights: { value: [], properties: { color: {}, position: {}, direction: {}, distance: {}, coneCos: {}, penumbraCos: {}, decay: {} } }, spotLightShadows: { value: [], properties: { shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, shadowMapSize: {} } }, spotLightMap: { value: [] }, spotShadowMap: { value: [] }, spotLightMatrix: { value: [] }, pointLights: { value: [], properties: { color: {}, position: {}, decay: {}, distance: {} } }, pointLightShadows: { value: [], properties: { shadowBias: {}, shadowNormalBias: {}, shadowRadius: {}, shadowMapSize: {}, shadowCameraNear: {}, shadowCameraFar: {} } }, pointShadowMap: { value: [] }, pointShadowMatrix: { value: [] }, hemisphereLights: { value: [], properties: { direction: {}, skyColor: {}, groundColor: {} } }, // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src rectAreaLights: { value: [], properties: { color: {}, position: {}, width: {}, height: {} } }, ltc_1: { value: null }, ltc_2: { value: null } }, points: { diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, opacity: { value: 1.0 }, size: { value: 1.0 }, scale: { value: 1.0 }, map: { value: null }, alphaMap: { value: null }, alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, alphaTest: { value: 0 }, uvTransform: { value: /*@__PURE__*/ new Matrix3() } }, sprite: { diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, opacity: { value: 1.0 }, center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, rotation: { value: 0.0 }, map: { value: null }, mapTransform: { value: /*@__PURE__*/ new Matrix3() }, alphaMap: { value: null }, alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, alphaTest: { value: 0 } } }; const ShaderLib = { basic: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.fog ] ), vertexShader: ShaderChunk.meshbasic_vert, fragmentShader: ShaderChunk.meshbasic_frag }, lambert: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.emissivemap, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, UniformsLib.fog, UniformsLib.lights, { emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } ] ), vertexShader: ShaderChunk.meshlambert_vert, fragmentShader: ShaderChunk.meshlambert_frag }, phong: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.specularmap, UniformsLib.envmap, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.emissivemap, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, UniformsLib.fog, UniformsLib.lights, { emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, shininess: { value: 30 } } ] ), vertexShader: ShaderChunk.meshphong_vert, fragmentShader: ShaderChunk.meshphong_frag }, standard: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.envmap, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.emissivemap, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, UniformsLib.roughnessmap, UniformsLib.metalnessmap, UniformsLib.fog, UniformsLib.lights, { emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, roughness: { value: 1.0 }, metalness: { value: 0.0 }, envMapIntensity: { value: 1 } // temporary } ] ), vertexShader: ShaderChunk.meshphysical_vert, fragmentShader: ShaderChunk.meshphysical_frag }, toon: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.aomap, UniformsLib.lightmap, UniformsLib.emissivemap, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, UniformsLib.gradientmap, UniformsLib.fog, UniformsLib.lights, { emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } ] ), vertexShader: ShaderChunk.meshtoon_vert, fragmentShader: ShaderChunk.meshtoon_frag }, matcap: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, UniformsLib.fog, { matcap: { value: null } } ] ), vertexShader: ShaderChunk.meshmatcap_vert, fragmentShader: ShaderChunk.meshmatcap_frag }, points: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.points, UniformsLib.fog ] ), vertexShader: ShaderChunk.points_vert, fragmentShader: ShaderChunk.points_frag }, dashed: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.fog, { scale: { value: 1 }, dashSize: { value: 1 }, totalSize: { value: 2 } } ] ), vertexShader: ShaderChunk.linedashed_vert, fragmentShader: ShaderChunk.linedashed_frag }, depth: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.displacementmap ] ), vertexShader: ShaderChunk.depth_vert, fragmentShader: ShaderChunk.depth_frag }, normal: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.bumpmap, UniformsLib.normalmap, UniformsLib.displacementmap, { opacity: { value: 1.0 } } ] ), vertexShader: ShaderChunk.meshnormal_vert, fragmentShader: ShaderChunk.meshnormal_frag }, sprite: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.sprite, UniformsLib.fog ] ), vertexShader: ShaderChunk.sprite_vert, fragmentShader: ShaderChunk.sprite_frag }, background: { uniforms: { uvTransform: { value: /*@__PURE__*/ new Matrix3() }, t2D: { value: null }, backgroundIntensity: { value: 1 } }, vertexShader: ShaderChunk.background_vert, fragmentShader: ShaderChunk.background_frag }, backgroundCube: { uniforms: { envMap: { value: null }, flipEnvMap: { value: - 1 }, backgroundBlurriness: { value: 0 }, backgroundIntensity: { value: 1 } }, vertexShader: ShaderChunk.backgroundCube_vert, fragmentShader: ShaderChunk.backgroundCube_frag }, cube: { uniforms: { tCube: { value: null }, tFlip: { value: - 1 }, opacity: { value: 1.0 } }, vertexShader: ShaderChunk.cube_vert, fragmentShader: ShaderChunk.cube_frag }, equirect: { uniforms: { tEquirect: { value: null }, }, vertexShader: ShaderChunk.equirect_vert, fragmentShader: ShaderChunk.equirect_frag }, distanceRGBA: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.common, UniformsLib.displacementmap, { referencePosition: { value: /*@__PURE__*/ new Vector3() }, nearDistance: { value: 1 }, farDistance: { value: 1000 } } ] ), vertexShader: ShaderChunk.distanceRGBA_vert, fragmentShader: ShaderChunk.distanceRGBA_frag }, shadow: { uniforms: /*@__PURE__*/ mergeUniforms( [ UniformsLib.lights, UniformsLib.fog, { color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, opacity: { value: 1.0 } }, ] ), vertexShader: ShaderChunk.shadow_vert, fragmentShader: ShaderChunk.shadow_frag } }; ShaderLib.physical = { uniforms: /*@__PURE__*/ mergeUniforms( [ ShaderLib.standard.uniforms, { clearcoat: { value: 0 }, clearcoatMap: { value: null }, clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, clearcoatNormalMap: { value: null }, clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, clearcoatRoughness: { value: 0 }, clearcoatRoughnessMap: { value: null }, clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, iridescence: { value: 0 }, iridescenceMap: { value: null }, iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, iridescenceIOR: { value: 1.3 }, iridescenceThicknessMinimum: { value: 100 }, iridescenceThicknessMaximum: { value: 400 }, iridescenceThicknessMap: { value: null }, iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, sheen: { value: 0 }, sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, sheenColorMap: { value: null }, sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, sheenRoughness: { value: 1 }, sheenRoughnessMap: { value: null }, sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, transmission: { value: 0 }, transmissionMap: { value: null }, transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, transmissionSamplerMap: { value: null }, thickness: { value: 0 }, thicknessMap: { value: null }, thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, attenuationDistance: { value: 0 }, attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, specularColorMap: { value: null }, specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, specularIntensity: { value: 1 }, specularIntensityMap: { value: null }, specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, anisotropyMap: { value: null }, anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, } ] ), vertexShader: ShaderChunk.meshphysical_vert, fragmentShader: ShaderChunk.meshphysical_frag }; const _rgb = { r: 0, b: 0, g: 0 }; function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { const clearColor = new Color( 0x000000 ); let clearAlpha = alpha === true ? 0 : 1; let planeMesh; let boxMesh; let currentBackground = null; let currentBackgroundVersion = 0; let currentTonemapping = null; function render( renderList, scene ) { let forceClear = false; let background = scene.isScene === true ? scene.background : null; if ( background && background.isTexture ) { const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); } if ( background === null ) { setClear( clearColor, clearAlpha ); } else if ( background && background.isColor ) { setClear( background, 1 ); forceClear = true; } const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); if ( environmentBlendMode === 'additive' ) { state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); } else if ( environmentBlendMode === 'alpha-blend' ) { state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); } if ( renderer.autoClear || forceClear ) { renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); } if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { if ( boxMesh === undefined ) { boxMesh = new Mesh( new BoxGeometry( 1, 1, 1 ), new ShaderMaterial( { name: 'BackgroundCubeMaterial', uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), vertexShader: ShaderLib.backgroundCube.vertexShader, fragmentShader: ShaderLib.backgroundCube.fragmentShader, side: BackSide, depthTest: false, depthWrite: false, fog: false } ) ); boxMesh.geometry.deleteAttribute( 'normal' ); boxMesh.geometry.deleteAttribute( 'uv' ); boxMesh.onBeforeRender = function ( renderer, scene, camera ) { this.matrixWorld.copyPosition( camera.matrixWorld ); }; // add "envMap" material property so the renderer can evaluate it like for built-in materials Object.defineProperty( boxMesh.material, 'envMap', { get: function () { return this.uniforms.envMap.value; } } ); objects.update( boxMesh ); } boxMesh.material.uniforms.envMap.value = background; boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; if ( currentBackground !== background || currentBackgroundVersion !== background.version || currentTonemapping !== renderer.toneMapping ) { boxMesh.material.needsUpdate = true; currentBackground = background; currentBackgroundVersion = background.version; currentTonemapping = renderer.toneMapping; } boxMesh.layers.enableAll(); // push to the pre-sorted opaque render list renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); } else if ( background && background.isTexture ) { if ( planeMesh === undefined ) { planeMesh = new Mesh( new PlaneGeometry( 2, 2 ), new ShaderMaterial( { name: 'BackgroundMaterial', uniforms: cloneUniforms( ShaderLib.background.uniforms ), vertexShader: ShaderLib.background.vertexShader, fragmentShader: ShaderLib.background.fragmentShader, side: FrontSide, depthTest: false, depthWrite: false, fog: false } ) ); planeMesh.geometry.deleteAttribute( 'normal' ); // add "map" material property so the renderer can evaluate it like for built-in materials Object.defineProperty( planeMesh.material, 'map', { get: function () { return this.uniforms.t2D.value; } } ); objects.update( planeMesh ); } planeMesh.material.uniforms.t2D.value = background; planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; if ( background.matrixAutoUpdate === true ) { background.updateMatrix(); } planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); if ( currentBackground !== background || currentBackgroundVersion !== background.version || currentTonemapping !== renderer.toneMapping ) { planeMesh.material.needsUpdate = true; currentBackground = background; currentBackgroundVersion = background.version; currentTonemapping = renderer.toneMapping; } planeMesh.layers.enableAll(); // push to the pre-sorted opaque render list renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); } } function setClear( color, alpha ) { color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); } return { getClearColor: function () { return clearColor; }, setClearColor: function ( color, alpha = 1 ) { clearColor.set( color ); clearAlpha = alpha; setClear( clearColor, clearAlpha ); }, getClearAlpha: function () { return clearAlpha; }, setClearAlpha: function ( alpha ) { clearAlpha = alpha; setClear( clearColor, clearAlpha ); }, render: render }; } function WebGLBindingStates( gl, extensions, attributes, capabilities ) { const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); const vaoAvailable = capabilities.isWebGL2 || extension !== null; const bindingStates = {}; const defaultState = createBindingState( null ); let currentState = defaultState; let forceUpdate = false; function setup( object, material, program, geometry, index ) { let updateBuffers = false; if ( vaoAvailable ) { const state = getBindingState( geometry, program, material ); if ( currentState !== state ) { currentState = state; bindVertexArrayObject( currentState.object ); } updateBuffers = needsUpdate( object, geometry, program, index ); if ( updateBuffers ) saveCache( object, geometry, program, index ); } else { const wireframe = ( material.wireframe === true ); if ( currentState.geometry !== geometry.id || currentState.program !== program.id || currentState.wireframe !== wireframe ) { currentState.geometry = geometry.id; currentState.program = program.id; currentState.wireframe = wireframe; updateBuffers = true; } } if ( index !== null ) { attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); } if ( updateBuffers || forceUpdate ) { forceUpdate = false; setupVertexAttributes( object, material, program, geometry ); if ( index !== null ) { gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); } } } function createVertexArrayObject() { if ( capabilities.isWebGL2 ) return gl.createVertexArray(); return extension.createVertexArrayOES(); } function bindVertexArrayObject( vao ) { if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); return extension.bindVertexArrayOES( vao ); } function deleteVertexArrayObject( vao ) { if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); return extension.deleteVertexArrayOES( vao ); } function getBindingState( geometry, program, material ) { const wireframe = ( material.wireframe === true ); let programMap = bindingStates[ geometry.id ]; if ( programMap === undefined ) { programMap = {}; bindingStates[ geometry.id ] = programMap; } let stateMap = programMap[ program.id ]; if ( stateMap === undefined ) { stateMap = {}; programMap[ program.id ] = stateMap; } let state = stateMap[ wireframe ]; if ( state === undefined ) { state = createBindingState( createVertexArrayObject() ); stateMap[ wireframe ] = state; } return state; } function createBindingState( vao ) { const newAttributes = []; const enabledAttributes = []; const attributeDivisors = []; for ( let i = 0; i < maxVertexAttributes; i ++ ) { newAttributes[ i ] = 0; enabledAttributes[ i ] = 0; attributeDivisors[ i ] = 0; } return { // for backward compatibility on non-VAO support browser geometry: null, program: null, wireframe: false, newAttributes: newAttributes, enabledAttributes: enabledAttributes, attributeDivisors: attributeDivisors, object: vao, attributes: {}, index: null }; } function needsUpdate( object, geometry, program, index ) { const cachedAttributes = currentState.attributes; const geometryAttributes = geometry.attributes; let attributesNum = 0; const programAttributes = program.getAttributes(); for ( const name in programAttributes ) { const programAttribute = programAttributes[ name ]; if ( programAttribute.location >= 0 ) { const cachedAttribute = cachedAttributes[ name ]; let geometryAttribute = geometryAttributes[ name ]; if ( geometryAttribute === undefined ) { if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; } if ( cachedAttribute === undefined ) return true; if ( cachedAttribute.attribute !== geometryAttribute ) return true; if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; attributesNum ++; } } if ( currentState.attributesNum !== attributesNum ) return true; if ( currentState.index !== index ) return true; return false; } function saveCache( object, geometry, program, index ) { const cache = {}; const attributes = geometry.attributes; let attributesNum = 0; const programAttributes = program.getAttributes(); for ( const name in programAttributes ) { const programAttribute = programAttributes[ name ]; if ( programAttribute.location >= 0 ) { let attribute = attributes[ name ]; if ( attribute === undefined ) { if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; } const data = {}; data.attribute = attribute; if ( attribute && attribute.data ) { data.data = attribute.data; } cache[ name ] = data; attributesNum ++; } } currentState.attributes = cache; currentState.attributesNum = attributesNum; currentState.index = index; } function initAttributes() { const newAttributes = currentState.newAttributes; for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { newAttributes[ i ] = 0; } } function enableAttribute( attribute ) { enableAttributeAndDivisor( attribute, 0 ); } function enableAttributeAndDivisor( attribute, meshPerAttribute ) { const newAttributes = currentState.newAttributes; const enabledAttributes = currentState.enabledAttributes; const attributeDivisors = currentState.attributeDivisors; newAttributes[ attribute ] = 1; if ( enabledAttributes[ attribute ] === 0 ) { gl.enableVertexAttribArray( attribute ); enabledAttributes[ attribute ] = 1; } if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); attributeDivisors[ attribute ] = meshPerAttribute; } } function disableUnusedAttributes() { const newAttributes = currentState.newAttributes; const enabledAttributes = currentState.enabledAttributes; for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { gl.disableVertexAttribArray( i ); enabledAttributes[ i ] = 0; } } } function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { if ( integer === true ) { gl.vertexAttribIPointer( index, size, type, stride, offset ); } else { gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); } } function setupVertexAttributes( object, material, program, geometry ) { if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; } initAttributes(); const geometryAttributes = geometry.attributes; const programAttributes = program.getAttributes(); const materialDefaultAttributeValues = material.defaultAttributeValues; for ( const name in programAttributes ) { const programAttribute = programAttributes[ name ]; if ( programAttribute.location >= 0 ) { let geometryAttribute = geometryAttributes[ name ]; if ( geometryAttribute === undefined ) { if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; } if ( geometryAttribute !== undefined ) { const normalized = geometryAttribute.normalized; const size = geometryAttribute.itemSize; const attribute = attributes.get( geometryAttribute ); // TODO Attribute may not be available on context restore if ( attribute === undefined ) continue; const buffer = attribute.buffer; const type = attribute.type; const bytesPerElement = attribute.bytesPerElement; // check for integer attributes (WebGL 2 only) const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); if ( geometryAttribute.isInterleavedBufferAttribute ) { const data = geometryAttribute.data; const stride = data.stride; const offset = geometryAttribute.offset; if ( data.isInstancedInterleavedBuffer ) { for ( let i = 0; i < programAttribute.locationSize; i ++ ) { enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); } if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { geometry._maxInstanceCount = data.meshPerAttribute * data.count; } } else { for ( let i = 0; i < programAttribute.locationSize; i ++ ) { enableAttribute( programAttribute.location + i ); } } gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); for ( let i = 0; i < programAttribute.locationSize; i ++ ) { vertexAttribPointer( programAttribute.location + i, size / programAttribute.locationSize, type, normalized, stride * bytesPerElement, ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, integer ); } } else { if ( geometryAttribute.isInstancedBufferAttribute ) { for ( let i = 0; i < programAttribute.locationSize; i ++ ) { enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); } if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; } } else { for ( let i = 0; i < programAttribute.locationSize; i ++ ) { enableAttribute( programAttribute.location + i ); } } gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); for ( let i = 0; i < programAttribute.locationSize; i ++ ) { vertexAttribPointer( programAttribute.location + i, size / programAttribute.locationSize, type, normalized, size * bytesPerElement, ( size / programAttribute.locationSize ) * i * bytesPerElement, integer ); } } } else if ( materialDefaultAttributeValues !== undefined ) { const value = materialDefaultAttributeValues[ name ]; if ( value !== undefined ) { switch ( value.length ) { case 2: gl.vertexAttrib2fv( programAttribute.location, value ); break; case 3: gl.vertexAttrib3fv( programAttribute.location, value ); break; case 4: gl.vertexAttrib4fv( programAttribute.location, value ); break; default: gl.vertexAttrib1fv( programAttribute.location, value ); } } } } } disableUnusedAttributes(); } function dispose() { reset(); for ( const geometryId in bindingStates ) { const programMap = bindingStates[ geometryId ]; for ( const programId in programMap ) { const stateMap = programMap[ programId ]; for ( const wireframe in stateMap ) { deleteVertexArrayObject( stateMap[ wireframe ].object ); delete stateMap[ wireframe ]; } delete programMap[ programId ]; } delete bindingStates[ geometryId ]; } } function releaseStatesOfGeometry( geometry ) { if ( bindingStates[ geometry.id ] === undefined ) return; const programMap = bindingStates[ geometry.id ]; for ( const programId in programMap ) { const stateMap = programMap[ programId ]; for ( const wireframe in stateMap ) { deleteVertexArrayObject( stateMap[ wireframe ].object ); delete stateMap[ wireframe ]; } delete programMap[ programId ]; } delete bindingStates[ geometry.id ]; } function releaseStatesOfProgram( program ) { for ( const geometryId in bindingStates ) { const programMap = bindingStates[ geometryId ]; if ( programMap[ program.id ] === undefined ) continue; const stateMap = programMap[ program.id ]; for ( const wireframe in stateMap ) { deleteVertexArrayObject( stateMap[ wireframe ].object ); delete stateMap[ wireframe ]; } delete programMap[ program.id ]; } } function reset() { resetDefaultState(); forceUpdate = true; if ( currentState === defaultState ) return; currentState = defaultState; bindVertexArrayObject( currentState.object ); } // for backward-compatibility function resetDefaultState() { defaultState.geometry = null; defaultState.program = null; defaultState.wireframe = false; } return { setup: setup, reset: reset, resetDefaultState: resetDefaultState, dispose: dispose, releaseStatesOfGeometry: releaseStatesOfGeometry, releaseStatesOfProgram: releaseStatesOfProgram, initAttributes: initAttributes, enableAttribute: enableAttribute, disableUnusedAttributes: disableUnusedAttributes }; } function WebGLBufferRenderer( gl, extensions, info, capabilities ) { const isWebGL2 = capabilities.isWebGL2; let mode; function setMode( value ) { mode = value; } function render( start, count ) { gl.drawArrays( mode, start, count ); info.update( count, mode, 1 ); } function renderInstances( start, count, primcount ) { if ( primcount === 0 ) return; let extension, methodName; if ( isWebGL2 ) { extension = gl; methodName = 'drawArraysInstanced'; } else { extension = extensions.get( 'ANGLE_instanced_arrays' ); methodName = 'drawArraysInstancedANGLE'; if ( extension === null ) { console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); return; } } extension[ methodName ]( mode, start, count, primcount ); info.update( count, mode, primcount ); } function renderMultiDraw( starts, counts, drawCount ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); if ( extension === null ) { for ( let i = 0; i < drawCount; i ++ ) { this.render( starts[ i ], counts[ i ] ); } } else { extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); let elementCount = 0; for ( let i = 0; i < drawCount; i ++ ) { elementCount += counts[ i ]; } info.update( elementCount, mode, 1 ); } } // this.setMode = setMode; this.render = render; this.renderInstances = renderInstances; this.renderMultiDraw = renderMultiDraw; } function WebGLCapabilities( gl, extensions, parameters ) { let maxAnisotropy; function getMaxAnisotropy() { if ( maxAnisotropy !== undefined ) return maxAnisotropy; if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); } else { maxAnisotropy = 0; } return maxAnisotropy; } function getMaxPrecision( precision ) { if ( precision === 'highp' ) { if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { return 'highp'; } precision = 'mediump'; } if ( precision === 'mediump' ) { if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { return 'mediump'; } } return 'lowp'; } const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; const maxPrecision = getMaxPrecision( precision ); if ( maxPrecision !== precision ) { console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); precision = maxPrecision; } const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); const vertexTextures = maxVertexTextures > 0; const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); const floatVertexTextures = vertexTextures && floatFragmentTextures; const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; return { isWebGL2: isWebGL2, drawBuffers: drawBuffers, getMaxAnisotropy: getMaxAnisotropy, getMaxPrecision: getMaxPrecision, precision: precision, logarithmicDepthBuffer: logarithmicDepthBuffer, maxTextures: maxTextures, maxVertexTextures: maxVertexTextures, maxTextureSize: maxTextureSize, maxCubemapSize: maxCubemapSize, maxAttributes: maxAttributes, maxVertexUniforms: maxVertexUniforms, maxVaryings: maxVaryings, maxFragmentUniforms: maxFragmentUniforms, vertexTextures: vertexTextures, floatFragmentTextures: floatFragmentTextures, floatVertexTextures: floatVertexTextures, maxSamples: maxSamples }; } function WebGLClipping( properties ) { const scope = this; let globalState = null, numGlobalPlanes = 0, localClippingEnabled = false, renderingShadows = false; const plane = new Plane(), viewNormalMatrix = new Matrix3(), uniform = { value: null, needsUpdate: false }; this.uniform = uniform; this.numPlanes = 0; this.numIntersection = 0; this.init = function ( planes, enableLocalClipping ) { const enabled = planes.length !== 0 || enableLocalClipping || // enable state of previous frame - the clipping code has to // run another frame in order to reset the state: numGlobalPlanes !== 0 || localClippingEnabled; localClippingEnabled = enableLocalClipping; numGlobalPlanes = planes.length; return enabled; }; this.beginShadows = function () { renderingShadows = true; projectPlanes( null ); }; this.endShadows = function () { renderingShadows = false; }; this.setGlobalState = function ( planes, camera ) { globalState = projectPlanes( planes, camera, 0 ); }; this.setState = function ( material, camera, useCache ) { const planes = material.clippingPlanes, clipIntersection = material.clipIntersection, clipShadows = material.clipShadows; const materialProperties = properties.get( material ); if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { // there's no local clipping if ( renderingShadows ) { // there's no global clipping projectPlanes( null ); } else { resetGlobalState(); } } else { const nGlobal = renderingShadows ? 0 : numGlobalPlanes, lGlobal = nGlobal * 4; let dstArray = materialProperties.clippingState || null; uniform.value = dstArray; // ensure unique state dstArray = projectPlanes( planes, camera, lGlobal, useCache ); for ( let i = 0; i !== lGlobal; ++ i ) { dstArray[ i ] = globalState[ i ]; } materialProperties.clippingState = dstArray; this.numIntersection = clipIntersection ? this.numPlanes : 0; this.numPlanes += nGlobal; } }; function resetGlobalState() { if ( uniform.value !== globalState ) { uniform.value = globalState; uniform.needsUpdate = numGlobalPlanes > 0; } scope.numPlanes = numGlobalPlanes; scope.numIntersection = 0; } function projectPlanes( planes, camera, dstOffset, skipTransform ) { const nPlanes = planes !== null ? planes.length : 0; let dstArray = null; if ( nPlanes !== 0 ) { dstArray = uniform.value; if ( skipTransform !== true || dstArray === null ) { const flatSize = dstOffset + nPlanes * 4, viewMatrix = camera.matrixWorldInverse; viewNormalMatrix.getNormalMatrix( viewMatrix ); if ( dstArray === null || dstArray.length < flatSize ) { dstArray = new Float32Array( flatSize ); } for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); plane.normal.toArray( dstArray, i4 ); dstArray[ i4 + 3 ] = plane.constant; } } uniform.value = dstArray; uniform.needsUpdate = true; } scope.numPlanes = nPlanes; scope.numIntersection = 0; return dstArray; } } function WebGLCubeMaps( renderer ) { let cubemaps = new WeakMap(); function mapTextureMapping( texture, mapping ) { if ( mapping === EquirectangularReflectionMapping ) { texture.mapping = CubeReflectionMapping; } else if ( mapping === EquirectangularRefractionMapping ) { texture.mapping = CubeRefractionMapping; } return texture; } function get( texture ) { if ( texture && texture.isTexture ) { const mapping = texture.mapping; if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { if ( cubemaps.has( texture ) ) { const cubemap = cubemaps.get( texture ).texture; return mapTextureMapping( cubemap, texture.mapping ); } else { const image = texture.image; if ( image && image.height > 0 ) { const renderTarget = new WebGLCubeRenderTarget( image.height / 2 ); renderTarget.fromEquirectangularTexture( renderer, texture ); cubemaps.set( texture, renderTarget ); texture.addEventListener( 'dispose', onTextureDispose ); return mapTextureMapping( renderTarget.texture, texture.mapping ); } else { // image not yet ready. try the conversion next frame return null; } } } } return texture; } function onTextureDispose( event ) { const texture = event.target; texture.removeEventListener( 'dispose', onTextureDispose ); const cubemap = cubemaps.get( texture ); if ( cubemap !== undefined ) { cubemaps.delete( texture ); cubemap.dispose(); } } function dispose() { cubemaps = new WeakMap(); } return { get: get, dispose: dispose }; } class OrthographicCamera extends Camera { constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { super(); this.isOrthographicCamera = true; this.type = 'OrthographicCamera'; this.zoom = 1; this.view = null; this.left = left; this.right = right; this.top = top; this.bottom = bottom; this.near = near; this.far = far; this.updateProjectionMatrix(); } copy( source, recursive ) { super.copy( source, recursive ); this.left = source.left; this.right = source.right; this.top = source.top; this.bottom = source.bottom; this.near = source.near; this.far = source.far; this.zoom = source.zoom; this.view = source.view === null ? null : Object.assign( {}, source.view ); return this; } setViewOffset( fullWidth, fullHeight, x, y, width, height ) { if ( this.view === null ) { this.view = { enabled: true, fullWidth: 1, fullHeight: 1, offsetX: 0, offsetY: 0, width: 1, height: 1 }; } this.view.enabled = true; this.view.fullWidth = fullWidth; this.view.fullHeight = fullHeight; this.view.offsetX = x; this.view.offsetY = y; this.view.width = width; this.view.height = height; this.updateProjectionMatrix(); } clearViewOffset() { if ( this.view !== null ) { this.view.enabled = false; } this.updateProjectionMatrix(); } updateProjectionMatrix() { const dx = ( this.right - this.left ) / ( 2 * this.zoom ); const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); const cx = ( this.right + this.left ) / 2; const cy = ( this.top + this.bottom ) / 2; let left = cx - dx; let right = cx + dx; let top = cy + dy; let bottom = cy - dy; if ( this.view !== null && this.view.enabled ) { const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; left += scaleW * this.view.offsetX; right = left + scaleW * this.view.width; top -= scaleH * this.view.offsetY; bottom = top - scaleH * this.view.height; } this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem ); this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); } toJSON( meta ) { const data = super.toJSON( meta ); data.object.zoom = this.zoom; data.object.left = this.left; data.object.right = this.right; data.object.top = this.top; data.object.bottom = this.bottom; data.object.near = this.near; data.object.far = this.far; if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); return data; } } const LOD_MIN = 4; // The standard deviations (radians) associated with the extra mips. These are // chosen to approximate a Trowbridge-Reitz distribution function times the // geometric shadowing function. These sigma values squared must match the // variance #defines in cube_uv_reflection_fragment.glsl.js. const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; // The maximum length of the blur for loop. Smaller sigmas will use fewer // samples and exit early, but not recompile the shader. const MAX_SAMPLES = 20; const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); const _clearColor = /*@__PURE__*/ new Color(); let _oldTarget = null; let _oldActiveCubeFace = 0; let _oldActiveMipmapLevel = 0; // Golden Ratio const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; const INV_PHI = 1 / PHI; // Vertices of a dodecahedron (except the opposites, which represent the // same axis), used as axis directions evenly spread on a sphere. const _axisDirections = [ /*@__PURE__*/ new Vector3( 1, 1, 1 ), /*@__PURE__*/ new Vector3( - 1, 1, 1 ), /*@__PURE__*/ new Vector3( 1, 1, - 1 ), /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; /** * This class generates a Prefiltered, Mipmapped Radiance Environment Map * (PMREM) from a cubeMap environment texture. This allows different levels of * blur to be quickly accessed based on material roughness. It is packed into a * special CubeUV format that allows us to perform custom interpolation so that * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap * chain, it only goes down to the LOD_MIN level (above), and then creates extra * even more filtered 'mips' at the same LOD_MIN resolution, associated with * higher roughness levels. In this way we maintain resolution to smoothly * interpolate diffuse lighting while limiting sampling computation. * * Paper: Fast, Accurate Image-Based Lighting * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view */ class PMREMGenerator { constructor( renderer ) { this._renderer = renderer; this._pingPongRenderTarget = null; this._lodMax = 0; this._cubeSize = 0; this._lodPlanes = []; this._sizeLods = []; this._sigmas = []; this._blurMaterial = null; this._cubemapMaterial = null; this._equirectMaterial = null; this._compileMaterial( this._blurMaterial ); } /** * Generates a PMREM from a supplied Scene, which can be faster than using an * image if networking bandwidth is low. Optional sigma specifies a blur radius * in radians to be applied to the scene before PMREM generation. Optional near * and far planes ensure the scene is rendered in its entirety (the cubeCamera * is placed at the origin). */ fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { _oldTarget = this._renderer.getRenderTarget(); _oldActiveCubeFace = this._renderer.getActiveCubeFace(); _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); this._setSize( 256 ); const cubeUVRenderTarget = this._allocateTargets(); cubeUVRenderTarget.depthBuffer = true; this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); if ( sigma > 0 ) { this._blur( cubeUVRenderTarget, 0, 0, sigma ); } this._applyPMREM( cubeUVRenderTarget ); this._cleanup( cubeUVRenderTarget ); return cubeUVRenderTarget; } /** * Generates a PMREM from an equirectangular texture, which can be either LDR * or HDR. The ideal input image size is 1k (1024 x 512), * as this matches best with the 256 x 256 cubemap output. */ fromEquirectangular( equirectangular, renderTarget = null ) { return this._fromTexture( equirectangular, renderTarget ); } /** * Generates a PMREM from an cubemap texture, which can be either LDR * or HDR. The ideal input cube size is 256 x 256, * as this matches best with the 256 x 256 cubemap output. */ fromCubemap( cubemap, renderTarget = null ) { return this._fromTexture( cubemap, renderTarget ); } /** * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during * your texture's network fetch for increased concurrency. */ compileCubemapShader() { if ( this._cubemapMaterial === null ) { this._cubemapMaterial = _getCubemapMaterial(); this._compileMaterial( this._cubemapMaterial ); } } /** * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during * your texture's network fetch for increased concurrency. */ compileEquirectangularShader() { if ( this._equirectMaterial === null ) { this._equirectMaterial = _getEquirectMaterial(); this._compileMaterial( this._equirectMaterial ); } } /** * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on * one of them will cause any others to also become unusable. */ dispose() { this._dispose(); if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); } // private interface _setSize( cubeSize ) { this._lodMax = Math.floor( Math.log2( cubeSize ) ); this._cubeSize = Math.pow( 2, this._lodMax ); } _dispose() { if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); for ( let i = 0; i < this._lodPlanes.length; i ++ ) { this._lodPlanes[ i ].dispose(); } } _cleanup( outputTarget ) { this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); outputTarget.scissorTest = false; _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); } _fromTexture( texture, renderTarget ) { if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); } else { // Equirectangular this._setSize( texture.image.width / 4 ); } _oldTarget = this._renderer.getRenderTarget(); _oldActiveCubeFace = this._renderer.getActiveCubeFace(); _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); const cubeUVRenderTarget = renderTarget || this._allocateTargets(); this._textureToCubeUV( texture, cubeUVRenderTarget ); this._applyPMREM( cubeUVRenderTarget ); this._cleanup( cubeUVRenderTarget ); return cubeUVRenderTarget; } _allocateTargets() { const width = 3 * Math.max( this._cubeSize, 16 * 7 ); const height = 4 * this._cubeSize; const params = { magFilter: LinearFilter, minFilter: LinearFilter, generateMipmaps: false, type: HalfFloatType, format: RGBAFormat, colorSpace: LinearSRGBColorSpace, depthBuffer: false }; const cubeUVRenderTarget = _createRenderTarget( width, height, params ); if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { if ( this._pingPongRenderTarget !== null ) { this._dispose(); } this._pingPongRenderTarget = _createRenderTarget( width, height, params ); const { _lodMax } = this; ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); this._blurMaterial = _getBlurShader( _lodMax, width, height ); } return cubeUVRenderTarget; } _compileMaterial( material ) { const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); this._renderer.compile( tmpMesh, _flatCamera ); } _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { const fov = 90; const aspect = 1; const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); const upSign = [ 1, - 1, 1, 1, 1, 1 ]; const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; const renderer = this._renderer; const originalAutoClear = renderer.autoClear; const toneMapping = renderer.toneMapping; renderer.getClearColor( _clearColor ); renderer.toneMapping = NoToneMapping; renderer.autoClear = false; const backgroundMaterial = new MeshBasicMaterial( { name: 'PMREM.Background', side: BackSide, depthWrite: false, depthTest: false, } ); const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); let useSolidColor = false; const background = scene.background; if ( background ) { if ( background.isColor ) { backgroundMaterial.color.copy( background ); scene.background = null; useSolidColor = true; } } else { backgroundMaterial.color.copy( _clearColor ); useSolidColor = true; } for ( let i = 0; i < 6; i ++ ) { const col = i % 3; if ( col === 0 ) { cubeCamera.up.set( 0, upSign[ i ], 0 ); cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); } else if ( col === 1 ) { cubeCamera.up.set( 0, 0, upSign[ i ] ); cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); } else { cubeCamera.up.set( 0, upSign[ i ], 0 ); cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); } const size = this._cubeSize; _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); renderer.setRenderTarget( cubeUVRenderTarget ); if ( useSolidColor ) { renderer.render( backgroundBox, cubeCamera ); } renderer.render( scene, cubeCamera ); } backgroundBox.geometry.dispose(); backgroundBox.material.dispose(); renderer.toneMapping = toneMapping; renderer.autoClear = originalAutoClear; scene.background = background; } _textureToCubeUV( texture, cubeUVRenderTarget ) { const renderer = this._renderer; const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); if ( isCubeTexture ) { if ( this._cubemapMaterial === null ) { this._cubemapMaterial = _getCubemapMaterial(); } this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; } else { if ( this._equirectMaterial === null ) { this._equirectMaterial = _getEquirectMaterial(); } } const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; const mesh = new Mesh( this._lodPlanes[ 0 ], material ); const uniforms = material.uniforms; uniforms[ 'envMap' ].value = texture; const size = this._cubeSize; _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); renderer.setRenderTarget( cubeUVRenderTarget ); renderer.render( mesh, _flatCamera ); } _applyPMREM( cubeUVRenderTarget ) { const renderer = this._renderer; const autoClear = renderer.autoClear; renderer.autoClear = false; for ( let i = 1; i < this._lodPlanes.length; i ++ ) { const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); } renderer.autoClear = autoClear; } /** * This is a two-pass Gaussian blur for a cubemap. Normally this is done * vertically and horizontally, but this breaks down on a cube. Here we apply * the blur latitudinally (around the poles), and then longitudinally (towards * the poles) to approximate the orthogonally-separable blur. It is least * accurate at the poles, but still does a decent job. */ _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { const pingPongRenderTarget = this._pingPongRenderTarget; this._halfBlur( cubeUVRenderTarget, pingPongRenderTarget, lodIn, lodOut, sigma, 'latitudinal', poleAxis ); this._halfBlur( pingPongRenderTarget, cubeUVRenderTarget, lodOut, lodOut, sigma, 'longitudinal', poleAxis ); } _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { const renderer = this._renderer; const blurMaterial = this._blurMaterial; if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { console.error( 'blur direction must be either latitudinal or longitudinal!' ); } // Number of standard deviations at which to cut off the discrete approximation. const STANDARD_DEVIATIONS = 3; const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); const blurUniforms = blurMaterial.uniforms; const pixels = this._sizeLods[ lodIn ] - 1; const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); const sigmaPixels = sigmaRadians / radiansPerPixel; const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; if ( samples > MAX_SAMPLES ) { console.warn( `sigmaRadians, ${ sigmaRadians}, is too large and will clip, as it requested ${ samples} samples when the maximum is set to ${MAX_SAMPLES}` ); } const weights = []; let sum = 0; for ( let i = 0; i < MAX_SAMPLES; ++ i ) { const x = i / sigmaPixels; const weight = Math.exp( - x * x / 2 ); weights.push( weight ); if ( i === 0 ) { sum += weight; } else if ( i < samples ) { sum += 2 * weight; } } for ( let i = 0; i < weights.length; i ++ ) { weights[ i ] = weights[ i ] / sum; } blurUniforms[ 'envMap' ].value = targetIn.texture; blurUniforms[ 'samples' ].value = samples; blurUniforms[ 'weights' ].value = weights; blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; if ( poleAxis ) { blurUniforms[ 'poleAxis' ].value = poleAxis; } const { _lodMax } = this; blurUniforms[ 'dTheta' ].value = radiansPerPixel; blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; const outputSize = this._sizeLods[ lodOut ]; const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); const y = 4 * ( this._cubeSize - outputSize ); _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); renderer.setRenderTarget( targetOut ); renderer.render( blurMesh, _flatCamera ); } } function _createPlanes( lodMax ) { const lodPlanes = []; const sizeLods = []; const sigmas = []; let lod = lodMax; const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; for ( let i = 0; i < totalLods; i ++ ) { const sizeLod = Math.pow( 2, lod ); sizeLods.push( sizeLod ); let sigma = 1.0 / sizeLod; if ( i > lodMax - LOD_MIN ) { sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; } else if ( i === 0 ) { sigma = 0; } sigmas.push( sigma ); const texelSize = 1.0 / ( sizeLod - 2 ); const min = - texelSize; const max = 1 + texelSize; const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; const cubeFaces = 6; const vertices = 6; const positionSize = 3; const uvSize = 2; const faceIndexSize = 1; const position = new Float32Array( positionSize * vertices * cubeFaces ); const uv = new Float32Array( uvSize * vertices * cubeFaces ); const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); for ( let face = 0; face < cubeFaces; face ++ ) { const x = ( face % 3 ) * 2 / 3 - 1; const y = face > 2 ? 0 : - 1; const coordinates = [ x, y, 0, x + 2 / 3, y, 0, x + 2 / 3, y + 1, 0, x, y, 0, x + 2 / 3, y + 1, 0, x, y + 1, 0 ]; position.set( coordinates, positionSize * vertices * face ); uv.set( uv1, uvSize * vertices * face ); const fill = [ face, face, face, face, face, face ]; faceIndex.set( fill, faceIndexSize * vertices * face ); } const planes = new BufferGeometry(); planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); lodPlanes.push( planes ); if ( lod > LOD_MIN ) { lod --; } } return { lodPlanes, sizeLods, sigmas }; } function _createRenderTarget( width, height, params ) { const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; cubeUVRenderTarget.scissorTest = true; return cubeUVRenderTarget; } function _setViewport( target, x, y, width, height ) { target.viewport.set( x, y, width, height ); target.scissor.set( x, y, width, height ); } function _getBlurShader( lodMax, width, height ) { const weights = new Float32Array( MAX_SAMPLES ); const poleAxis = new Vector3( 0, 1, 0 ); const shaderMaterial = new ShaderMaterial( { name: 'SphericalGaussianBlur', defines: { 'n': MAX_SAMPLES, 'CUBEUV_TEXEL_WIDTH': 1.0 / width, 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, 'CUBEUV_MAX_MIP': `${lodMax}.0`, }, uniforms: { 'envMap': { value: null }, 'samples': { value: 1 }, 'weights': { value: weights }, 'latitudinal': { value: false }, 'dTheta': { value: 0 }, 'mipInt': { value: 0 }, 'poleAxis': { value: poleAxis } }, vertexShader: _getCommonVertexShader(), fragmentShader: /* glsl */` precision mediump float; precision mediump int; varying vec3 vOutputDirection; uniform sampler2D envMap; uniform int samples; uniform float weights[ n ]; uniform bool latitudinal; uniform float dTheta; uniform float mipInt; uniform vec3 poleAxis; #define ENVMAP_TYPE_CUBE_UV #include <cube_uv_reflection_fragment> vec3 getSample( float theta, vec3 axis ) { float cosTheta = cos( theta ); // Rodrigues' axis-angle rotation vec3 sampleDirection = vOutputDirection * cosTheta + cross( axis, vOutputDirection ) * sin( theta ) + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); return bilinearCubeUV( envMap, sampleDirection, mipInt ); } void main() { vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); if ( all( equal( axis, vec3( 0.0 ) ) ) ) { axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); } axis = normalize( axis ); gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); for ( int i = 1; i < n; i++ ) { if ( i >= samples ) { break; } float theta = dTheta * float( i ); gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); } } `, blending: NoBlending, depthTest: false, depthWrite: false } ); return shaderMaterial; } function _getEquirectMaterial() { return new ShaderMaterial( { name: 'EquirectangularToCubeUV', uniforms: { 'envMap': { value: null } }, vertexShader: _getCommonVertexShader(), fragmentShader: /* glsl */` precision mediump float; precision mediump int; varying vec3 vOutputDirection; uniform sampler2D envMap; #include <common> void main() { vec3 outputDirection = normalize( vOutputDirection ); vec2 uv = equirectUv( outputDirection ); gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); } `, blending: NoBlending, depthTest: false, depthWrite: false } ); } function _getCubemapMaterial() { return new ShaderMaterial( { name: 'CubemapToCubeUV', uniforms: { 'envMap': { value: null }, 'flipEnvMap': { value: - 1 } }, vertexShader: _getCommonVertexShader(), fragmentShader: /* glsl */` precision mediump float; precision mediump int; uniform float flipEnvMap; varying vec3 vOutputDirection; uniform samplerCube envMap; void main() { gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); } `, blending: NoBlending, depthTest: false, depthWrite: false } ); } function _getCommonVertexShader() { return /* glsl */` precision mediump float; precision mediump int; attribute float faceIndex; varying vec3 vOutputDirection; // RH coordinate system; PMREM face-indexing convention vec3 getDirection( vec2 uv, float face ) { uv = 2.0 * uv - 1.0; vec3 direction = vec3( uv, 1.0 ); if ( face == 0.0 ) { direction = direction.zyx; // ( 1, v, u ) pos x } else if ( face == 1.0 ) { direction = direction.xzy; direction.xz *= -1.0; // ( -u, 1, -v ) pos y } else if ( face == 2.0 ) { direction.x *= -1.0; // ( -u, v, 1 ) pos z } else if ( face == 3.0 ) { direction = direction.zyx; direction.xz *= -1.0; // ( -1, v, -u ) neg x } else if ( face == 4.0 ) { direction = direction.xzy; direction.xy *= -1.0; // ( -u, -1, v ) neg y } else if ( face == 5.0 ) { direction.z *= -1.0; // ( u, v, -1 ) neg z } return direction; } void main() { vOutputDirection = getDirection( uv, faceIndex ); gl_Position = vec4( position, 1.0 ); } `; } function WebGLCubeUVMaps( renderer ) { let cubeUVmaps = new WeakMap(); let pmremGenerator = null; function get( texture ) { if ( texture && texture.isTexture ) { const mapping = texture.mapping; const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); // equirect/cube map to cubeUV conversion if ( isEquirectMap || isCubeMap ) { if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { texture.needsPMREMUpdate = false; let renderTarget = cubeUVmaps.get( texture ); if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); cubeUVmaps.set( texture, renderTarget ); return renderTarget.texture; } else { if ( cubeUVmaps.has( texture ) ) { return cubeUVmaps.get( texture ).texture; } else { const image = texture.image; if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); cubeUVmaps.set( texture, renderTarget ); texture.addEventListener( 'dispose', onTextureDispose ); return renderTarget.texture; } else { // image not yet ready. try the conversion next frame return null; } } } } } return texture; } function isCubeTextureComplete( image ) { let count = 0; const length = 6; for ( let i = 0; i < length; i ++ ) { if ( image[ i ] !== undefined ) count ++; } return count === length; } function onTextureDispose( event ) { const texture = event.target; texture.removeEventListener( 'dispose', onTextureDispose ); const cubemapUV = cubeUVmaps.get( texture ); if ( cubemapUV !== undefined ) { cubeUVmaps.delete( texture ); cubemapUV.dispose(); } } function dispose() { cubeUVmaps = new WeakMap(); if ( pmremGenerator !== null ) { pmremGenerator.dispose(); pmremGenerator = null; } } return { get: get, dispose: dispose }; } function WebGLExtensions( gl ) { const extensions = {}; function getExtension( name ) { if ( extensions[ name ] !== undefined ) { return extensions[ name ]; } let extension; switch ( name ) { case 'WEBGL_depth_texture': extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); break; case 'EXT_texture_filter_anisotropic': extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); break; case 'WEBGL_compressed_texture_s3tc': extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); break; case 'WEBGL_compressed_texture_pvrtc': extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); break; default: extension = gl.getExtension( name ); } extensions[ name ] = extension; return extension; } return { has: function ( name ) { return getExtension( name ) !== null; }, init: function ( capabilities ) { if ( capabilities.isWebGL2 ) { getExtension( 'EXT_color_buffer_float' ); getExtension( 'WEBGL_clip_cull_distance' ); } else { getExtension( 'WEBGL_depth_texture' ); getExtension( 'OES_texture_float' ); getExtension( 'OES_texture_half_float' ); getExtension( 'OES_texture_half_float_linear' ); getExtension( 'OES_standard_derivatives' ); getExtension( 'OES_element_index_uint' ); getExtension( 'OES_vertex_array_object' ); getExtension( 'ANGLE_instanced_arrays' ); } getExtension( 'OES_texture_float_linear' ); getExtension( 'EXT_color_buffer_half_float' ); getExtension( 'WEBGL_multisampled_render_to_texture' ); }, get: function ( name ) { const extension = getExtension( name ); if ( extension === null ) { console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); } return extension; } }; } function WebGLGeometries( gl, attributes, info, bindingStates ) { const geometries = {}; const wireframeAttributes = new WeakMap(); function onGeometryDispose( event ) { const geometry = event.target; if ( geometry.index !== null ) { attributes.remove( geometry.index ); } for ( const name in geometry.attributes ) { attributes.remove( geometry.attributes[ name ] ); } for ( const name in geometry.morphAttributes ) { const array = geometry.morphAttributes[ name ]; for ( let i = 0, l = array.length; i < l; i ++ ) { attributes.remove( array[ i ] ); } } geometry.removeEventListener( 'dispose', onGeometryDispose ); delete geometries[ geometry.id ]; const attribute = wireframeAttributes.get( geometry ); if ( attribute ) { attributes.remove( attribute ); wireframeAttributes.delete( geometry ); } bindingStates.releaseStatesOfGeometry( geometry ); if ( geometry.isInstancedBufferGeometry === true ) { delete geometry._maxInstanceCount; } // info.memory.geometries --; } function get( object, geometry ) { if ( geometries[ geometry.id ] === true ) return geometry; geometry.addEventListener( 'dispose', onGeometryDispose ); geometries[ geometry.id ] = true; info.memory.geometries ++; return geometry; } function update( geometry ) { const geometryAttributes = geometry.attributes; // Updating index buffer in VAO now. See WebGLBindingStates. for ( const name in geometryAttributes ) { attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); } // morph targets const morphAttributes = geometry.morphAttributes; for ( const name in morphAttributes ) { const array = morphAttributes[ name ]; for ( let i = 0, l = array.length; i < l; i ++ ) { attributes.update( array[ i ], gl.ARRAY_BUFFER ); } } } function updateWireframeAttribute( geometry ) { const indices = []; const geometryIndex = geometry.index; const geometryPosition = geometry.attributes.position; let version = 0; if ( geometryIndex !== null ) { const array = geometryIndex.array; version = geometryIndex.version; for ( let i = 0, l = array.length; i < l; i += 3 ) { const a = array[ i + 0 ]; const b = array[ i + 1 ]; const c = array[ i + 2 ]; indices.push( a, b, b, c, c, a ); } } else if ( geometryPosition !== undefined ) { const array = geometryPosition.array; version = geometryPosition.version; for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { const a = i + 0; const b = i + 1; const c = i + 2; indices.push( a, b, b, c, c, a ); } } else { return; } const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); attribute.version = version; // Updating index buffer in VAO now. See WebGLBindingStates // const previousAttribute = wireframeAttributes.get( geometry ); if ( previousAttribute ) attributes.remove( previousAttribute ); // wireframeAttributes.set( geometry, attribute ); } function getWireframeAttribute( geometry ) { const currentAttribute = wireframeAttributes.get( geometry ); if ( currentAttribute ) { const geometryIndex = geometry.index; if ( geometryIndex !== null ) { // if the attribute is obsolete, create a new one if ( currentAttribute.version < geometryIndex.version ) { updateWireframeAttribute( geometry ); } } } else { updateWireframeAttribute( geometry ); } return wireframeAttributes.get( geometry ); } return { get: get, update: update, getWireframeAttribute: getWireframeAttribute }; } function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { const isWebGL2 = capabilities.isWebGL2; let mode; function setMode( value ) { mode = value; } let type, bytesPerElement; function setIndex( value ) { type = value.type; bytesPerElement = value.bytesPerElement; } function render( start, count ) { gl.drawElements( mode, count, type, start * bytesPerElement ); info.update( count, mode, 1 ); } function renderInstances( start, count, primcount ) { if ( primcount === 0 ) return; let extension, methodName; if ( isWebGL2 ) { extension = gl; methodName = 'drawElementsInstanced'; } else { extension = extensions.get( 'ANGLE_instanced_arrays' ); methodName = 'drawElementsInstancedANGLE'; if ( extension === null ) { console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); return; } } extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); info.update( count, mode, primcount ); } function renderMultiDraw( starts, counts, drawCount ) { if ( drawCount === 0 ) return; const extension = extensions.get( 'WEBGL_multi_draw' ); if ( extension === null ) { for ( let i = 0; i < drawCount; i ++ ) { this.render( starts[ i ] / bytesPerElement, counts[ i ] ); } } else { extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); let elementCount = 0; for ( let i = 0; i < drawCount; i ++ ) { elementCount += counts[ i ]; } info.update( elementCount, mode, 1 ); } } // this.setMode = setMode; this.setIndex = setIndex; this.render = render; this.renderInstances = renderInstances; this.renderMultiDraw = renderMultiDraw; } function WebGLInfo( gl ) { const memory = { geometries: 0, textures: 0 }; const render = { frame: 0, calls: 0, triangles: 0, points: 0, lines: 0 }; function update( count, mode, instanceCount ) { render.calls ++; switch ( mode ) { case gl.TRIANGLES: render.triangles += instanceCount * ( count / 3 ); break; case gl.LINES: render.lines += instanceCount * ( count / 2 ); break; case gl.LINE_STRIP: render.lines += instanceCount * ( count - 1 ); break; case gl.LINE_LOOP: render.lines += instanceCount * count; break; case gl.POINTS: render.points += instanceCount * count; break; default: console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); break; } } function reset() { render.calls = 0; render.triangles = 0; render.points = 0; render.lines = 0; } return { memory: memory, render: render, programs: null, autoReset: true, reset: reset, update: update }; } function numericalSort( a, b ) { return a[ 0 ] - b[ 0 ]; } function absNumericalSort( a, b ) { return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); } function WebGLMorphtargets( gl, capabilities, textures ) { const influencesList = {}; const morphInfluences = new Float32Array( 8 ); const morphTextures = new WeakMap(); const morph = new Vector4(); const workInfluences = []; for ( let i = 0; i < 8; i ++ ) { workInfluences[ i ] = [ i, 0 ]; } function update( object, geometry, program ) { const objectInfluences = object.morphTargetInfluences; if ( capabilities.isWebGL2 === true ) { // instead of using attributes, the WebGL 2 code path encodes morph targets // into an array of data textures. Each layer represents a single morph target. const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; let entry = morphTextures.get( geometry ); if ( entry === undefined || entry.count !== morphTargetsCount ) { if ( entry !== undefined ) entry.texture.dispose(); const hasMorphPosition = geometry.morphAttributes.position !== undefined; const hasMorphNormals = geometry.morphAttributes.normal !== undefined; const hasMorphColors = geometry.morphAttributes.color !== undefined; const morphTargets = geometry.morphAttributes.position || []; const morphNormals = geometry.morphAttributes.normal || []; const morphColors = geometry.morphAttributes.color || []; let vertexDataCount = 0; if ( hasMorphPosition === true ) vertexDataCount = 1; if ( hasMorphNormals === true ) vertexDataCount = 2; if ( hasMorphColors === true ) vertexDataCount = 3; let width = geometry.attributes.position.count * vertexDataCount; let height = 1; if ( width > capabilities.maxTextureSize ) { height = Math.ceil( width / capabilities.maxTextureSize ); width = capabilities.maxTextureSize; } const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); texture.type = FloatType; texture.needsUpdate = true; // fill buffer const vertexDataStride = vertexDataCount * 4; for ( let i = 0; i < morphTargetsCount; i ++ ) { const morphTarget = morphTargets[ i ]; const morphNormal = morphNormals[ i ]; const morphColor = morphColors[ i ]; const offset = width * height * 4 * i; for ( let j = 0; j < morphTarget.count; j ++ ) { const stride = j * vertexDataStride; if ( hasMorphPosition === true ) { morph.fromBufferAttribute( morphTarget, j ); buffer[ offset + stride + 0 ] = morph.x; buffer[ offset + stride + 1 ] = morph.y; buffer[ offset + stride + 2 ] = morph.z; buffer[ offset + stride + 3 ] = 0; } if ( hasMorphNormals === true ) { morph.fromBufferAttribute( morphNormal, j ); buffer[ offset + stride + 4 ] = morph.x; buffer[ offset + stride + 5 ] = morph.y; buffer[ offset + stride + 6 ] = morph.z; buffer[ offset + stride + 7 ] = 0; } if ( hasMorphColors === true ) { morph.fromBufferAttribute( morphColor, j ); buffer[ offset + stride + 8 ] = morph.x; buffer[ offset + stride + 9 ] = morph.y; buffer[ offset + stride + 10 ] = morph.z; buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; } } } entry = { count: morphTargetsCount, texture: texture, size: new Vector2( width, height ) }; morphTextures.set( geometry, entry ); function disposeTexture() { texture.dispose(); morphTextures.delete( geometry ); geometry.removeEventListener( 'dispose', disposeTexture ); } geometry.addEventListener( 'dispose', disposeTexture ); } // let morphInfluencesSum = 0; for ( let i = 0; i < objectInfluences.length; i ++ ) { morphInfluencesSum += objectInfluences[ i ]; } const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); } else { // When object doesn't have morph target influences defined, we treat it as a 0-length array // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences const length = objectInfluences === undefined ? 0 : objectInfluences.length; let influences = influencesList[ geometry.id ]; if ( influences === undefined || influences.length !== length ) { // initialise list influences = []; for ( let i = 0; i < length; i ++ ) { influences[ i ] = [ i, 0 ]; } influencesList[ geometry.id ] = influences; } // Collect influences for ( let i = 0; i < length; i ++ ) { const influence = influences[ i ]; influence[ 0 ] = i; influence[ 1 ] = objectInfluences[ i ]; } influences.sort( absNumericalSort ); for ( let i = 0; i < 8; i ++ ) { if ( i < length && influences[ i ][ 1 ] ) { workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; } else { workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; workInfluences[ i ][ 1 ] = 0; } } workInfluences.sort( numericalSort ); const morphTargets = geometry.morphAttributes.position; const morphNormals = geometry.morphAttributes.normal; let morphInfluencesSum = 0; for ( let i = 0; i < 8; i ++ ) { const influence = workInfluences[ i ]; const index = influence[ 0 ]; const value = influence[ 1 ]; if ( index !== Number.MAX_SAFE_INTEGER && value ) { if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); } if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); } morphInfluences[ i ] = value; morphInfluencesSum += value; } else { if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { geometry.deleteAttribute( 'morphTarget' + i ); } if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { geometry.deleteAttribute( 'morphNormal' + i ); } morphInfluences[ i ] = 0; } } // GLSL shader uses formula baseinfluence * base + sum(target * influence) // This allows us to switch between absolute morphs and relative morphs without changing shader code // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); } } return { update: update }; } function WebGLObjects( gl, geometries, attributes, info ) { let updateMap = new WeakMap(); function update( object ) { const frame = info.render.frame; const geometry = object.geometry; const buffergeometry = geometries.get( object, geometry ); // Update once per frame if ( updateMap.get( buffergeometry ) !== frame ) { geometries.update( buffergeometry ); updateMap.set( buffergeometry, frame ); } if ( object.isInstancedMesh ) { if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { object.addEventListener( 'dispose', onInstancedMeshDispose ); } if ( updateMap.get( object ) !== frame ) { attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); if ( object.instanceColor !== null ) { attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); } updateMap.set( object, frame ); } } if ( object.isSkinnedMesh ) { const skeleton = object.skeleton; if ( updateMap.get( skeleton ) !== frame ) { skeleton.update(); updateMap.set( skeleton, frame ); } } return buffergeometry; } function dispose() { updateMap = new WeakMap(); } function onInstancedMeshDispose( event ) { const instancedMesh = event.target; instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); attributes.remove( instancedMesh.instanceMatrix ); if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); } return { update: update, dispose: dispose }; } class DepthTexture extends Texture { constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { format = format !== undefined ? format : DepthFormat; if ( format !== DepthFormat && format !== DepthStencilFormat ) { throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); } if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); this.isDepthTexture = true; this.image = { width: width, height: height }; this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; this.flipY = false; this.generateMipmaps = false; this.compareFunction = null; } copy( source ) { super.copy( source ); this.compareFunction = source.compareFunction; return this; } toJSON( meta ) { const data = super.toJSON( meta ); if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; return data; } } /** * Uniforms of a program. * Those form a tree structure with a special top-level container for the root, * which you get by calling 'new WebGLUniforms( gl, program )'. * * * Properties of inner nodes including the top-level container: * * .seq - array of nested uniforms * .map - nested uniforms by name * * * Methods of all nodes except the top-level container: * * .setValue( gl, value, [textures] ) * * uploads a uniform value(s) * the 'textures' parameter is needed for sampler uniforms * * * Static methods of the top-level container (textures factorizations): * * .upload( gl, seq, values, textures ) * * sets uniforms in 'seq' to 'values[id].value' * * .seqWithValue( seq, values ) : filteredSeq * * filters 'seq' entries with corresponding entry in values * * * Methods of the top-level container (textures factorizations): * * .setValue( gl, name, value, textures ) * * sets uniform with name 'name' to 'value' * * .setOptional( gl, obj, prop ) * * like .set for an optional property of the object * */ const emptyTexture = /*@__PURE__*/ new Texture(); const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); emptyShadowTexture.compareFunction = LessEqualCompare; const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); // --- Utilities --- // Array Caches (provide typed arrays for temporary by size) const arrayCacheF32 = []; const arrayCacheI32 = []; // Float32Array caches used for uploading Matrix uniforms const mat4array = new Float32Array( 16 ); const mat3array = new Float32Array( 9 ); const mat2array = new Float32Array( 4 ); // Flattening for arrays of vectors and matrices function flatten( array, nBlocks, blockSize ) { const firstElem = array[ 0 ]; if ( firstElem <= 0 || firstElem > 0 ) return array; // unoptimized: ! isNaN( firstElem ) // see http://jacksondunstan.com/articles/983 const n = nBlocks * blockSize; let r = arrayCacheF32[ n ]; if ( r === undefined ) { r = new Float32Array( n ); arrayCacheF32[ n ] = r; } if ( nBlocks !== 0 ) { firstElem.toArray( r, 0 ); for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { offset += blockSize; array[ i ].toArray( r, offset ); } } return r; } function arraysEqual( a, b ) { if ( a.length !== b.length ) return false; for ( let i = 0, l = a.length; i < l; i ++ ) { if ( a[ i ] !== b[ i ] ) return false; } return true; } function copyArray( a, b ) { for ( let i = 0, l = b.length; i < l; i ++ ) { a[ i ] = b[ i ]; } } // Texture unit allocation function allocTexUnits( textures, n ) { let r = arrayCacheI32[ n ]; if ( r === undefined ) { r = new Int32Array( n ); arrayCacheI32[ n ] = r; } for ( let i = 0; i !== n; ++ i ) { r[ i ] = textures.allocateTextureUnit(); } return r; } // --- Setters --- // Note: Defining these methods externally, because they come in a bunch // and this way their names minify. // Single scalar function setValueV1f( gl, v ) { const cache = this.cache; if ( cache[ 0 ] === v ) return; gl.uniform1f( this.addr, v ); cache[ 0 ] = v; } // Single float vector (from flat array or THREE.VectorN) function setValueV2f( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { gl.uniform2f( this.addr, v.x, v.y ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform2fv( this.addr, v ); copyArray( cache, v ); } } function setValueV3f( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { gl.uniform3f( this.addr, v.x, v.y, v.z ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; } } else if ( v.r !== undefined ) { if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { gl.uniform3f( this.addr, v.r, v.g, v.b ); cache[ 0 ] = v.r; cache[ 1 ] = v.g; cache[ 2 ] = v.b; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform3fv( this.addr, v ); copyArray( cache, v ); } } function setValueV4f( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; cache[ 3 ] = v.w; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform4fv( this.addr, v ); copyArray( cache, v ); } } // Single matrix (from flat array or THREE.MatrixN) function setValueM2( gl, v ) { const cache = this.cache; const elements = v.elements; if ( elements === undefined ) { if ( arraysEqual( cache, v ) ) return; gl.uniformMatrix2fv( this.addr, false, v ); copyArray( cache, v ); } else { if ( arraysEqual( cache, elements ) ) return; mat2array.set( elements ); gl.uniformMatrix2fv( this.addr, false, mat2array ); copyArray( cache, elements ); } } function setValueM3( gl, v ) { const cache = this.cache; const elements = v.elements; if ( elements === undefined ) { if ( arraysEqual( cache, v ) ) return; gl.uniformMatrix3fv( this.addr, false, v ); copyArray( cache, v ); } else { if ( arraysEqual( cache, elements ) ) return; mat3array.set( elements ); gl.uniformMatrix3fv( this.addr, false, mat3array ); copyArray( cache, elements ); } } function setValueM4( gl, v ) { const cache = this.cache; const elements = v.elements; if ( elements === undefined ) { if ( arraysEqual( cache, v ) ) return; gl.uniformMatrix4fv( this.addr, false, v ); copyArray( cache, v ); } else { if ( arraysEqual( cache, elements ) ) return; mat4array.set( elements ); gl.uniformMatrix4fv( this.addr, false, mat4array ); copyArray( cache, elements ); } } // Single integer / boolean function setValueV1i( gl, v ) { const cache = this.cache; if ( cache[ 0 ] === v ) return; gl.uniform1i( this.addr, v ); cache[ 0 ] = v; } // Single integer / boolean vector (from flat array or THREE.VectorN) function setValueV2i( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { gl.uniform2i( this.addr, v.x, v.y ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform2iv( this.addr, v ); copyArray( cache, v ); } } function setValueV3i( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { gl.uniform3i( this.addr, v.x, v.y, v.z ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform3iv( this.addr, v ); copyArray( cache, v ); } } function setValueV4i( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; cache[ 3 ] = v.w; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform4iv( this.addr, v ); copyArray( cache, v ); } } // Single unsigned integer function setValueV1ui( gl, v ) { const cache = this.cache; if ( cache[ 0 ] === v ) return; gl.uniform1ui( this.addr, v ); cache[ 0 ] = v; } // Single unsigned integer vector (from flat array or THREE.VectorN) function setValueV2ui( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { gl.uniform2ui( this.addr, v.x, v.y ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform2uiv( this.addr, v ); copyArray( cache, v ); } } function setValueV3ui( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { gl.uniform3ui( this.addr, v.x, v.y, v.z ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform3uiv( this.addr, v ); copyArray( cache, v ); } } function setValueV4ui( gl, v ) { const cache = this.cache; if ( v.x !== undefined ) { if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); cache[ 0 ] = v.x; cache[ 1 ] = v.y; cache[ 2 ] = v.z; cache[ 3 ] = v.w; } } else { if ( arraysEqual( cache, v ) ) return; gl.uniform4uiv( this.addr, v ); copyArray( cache, v ); } } // Single texture (2D / Cube) function setValueT1( gl, v, textures ) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if ( cache[ 0 ] !== unit ) { gl.uniform1i( this.addr, unit ); cache[ 0 ] = unit; } const emptyTexture2D = ( this.type === gl.SAMPLER_2D_SHADOW ) ? emptyShadowTexture : emptyTexture; textures.setTexture2D( v || emptyTexture2D, unit ); } function setValueT3D1( gl, v, textures ) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if ( cache[ 0 ] !== unit ) { gl.uniform1i( this.addr, unit ); cache[ 0 ] = unit; } textures.setTexture3D( v || empty3dTexture, unit ); } function setValueT6( gl, v, textures ) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if ( cache[ 0 ] !== unit ) { gl.uniform1i( this.addr, unit ); cache[ 0 ] = unit; } textures.setTextureCube( v || emptyCubeTexture, unit ); } function setValueT2DArray1( gl, v, textures ) { const cache = this.cache; const unit = textures.allocateTextureUnit(); if ( cache[ 0 ] !== unit ) { gl.uniform1i( this.addr, unit ); cache[ 0 ] = unit; } textures.setTexture2DArray( v || emptyArrayTexture, unit ); } // Helper to pick the right setter for the singular case function getSingularSetter( type ) { switch ( type ) { case 0x1406: return setValueV1f; // FLOAT case 0x8b50: return setValueV2f; // _VEC2 case 0x8b51: return setValueV3f; // _VEC3 case 0x8b52: return setValueV4f; // _VEC4 case 0x8b5a: return setValueM2; // _MAT2 case 0x8b5b: return setValueM3; // _MAT3 case 0x8b5c: return setValueM4; // _MAT4 case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 case 0x1405: return setValueV1ui; // UINT case 0x8dc6: return setValueV2ui; // _VEC2 case 0x8dc7: return setValueV3ui; // _VEC3 case 0x8dc8: return setValueV4ui; // _VEC4 case 0x8b5e: // SAMPLER_2D case 0x8d66: // SAMPLER_EXTERNAL_OES case 0x8dca: // INT_SAMPLER_2D case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D case 0x8b62: // SAMPLER_2D_SHADOW return setValueT1; case 0x8b5f: // SAMPLER_3D case 0x8dcb: // INT_SAMPLER_3D case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D return setValueT3D1; case 0x8b60: // SAMPLER_CUBE case 0x8dcc: // INT_SAMPLER_CUBE case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE case 0x8dc5: // SAMPLER_CUBE_SHADOW return setValueT6; case 0x8dc1: // SAMPLER_2D_ARRAY case 0x8dcf: // INT_SAMPLER_2D_ARRAY case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW return setValueT2DArray1; } } // Array of scalars function setValueV1fArray( gl, v ) { gl.uniform1fv( this.addr, v ); } // Array of vectors (from flat array or array of THREE.VectorN) function setValueV2fArray( gl, v ) { const data = flatten( v, this.size, 2 ); gl.uniform2fv( this.addr, data ); } function setValueV3fArray( gl, v ) { const data = flatten( v, this.size, 3 ); gl.uniform3fv( this.addr, data ); } function setValueV4fArray( gl, v ) { const data = flatten( v, this.size, 4 ); gl.uniform4fv( this.addr, data ); } // Array of matrices (from flat array or array of THREE.MatrixN) function setValueM2Array( gl, v ) { const data = flatten( v, this.size, 4 ); gl.uniformMatrix2fv( this.addr, false, data ); } function setValueM3Array( gl, v ) { const data = flatten( v, this.size, 9 ); gl.uniformMatrix3fv( this.addr, false, data ); } function setValueM4Array( gl, v ) { const data = flatten( v, this.size, 16 ); gl.uniformMatrix4fv( this.addr, false, data ); } // Array of integer / boolean function setValueV1iArray( gl, v ) { gl.uniform1iv( this.addr, v ); } // Array of integer / boolean vectors (from flat array) function setValueV2iArray( gl, v ) { gl.uniform2iv( this.addr, v ); } function setValueV3iArray( gl, v ) { gl.uniform3iv( this.addr, v ); } function setValueV4iArray( gl, v ) { gl.uniform4iv( this.addr, v ); } // Array of unsigned integer function setValueV1uiArray( gl, v ) { gl.uniform1uiv( this.addr, v ); } // Array of unsigned integer vectors (from flat array) function setValueV2uiArray( gl, v ) { gl.uniform2uiv( this.addr, v ); } function setValueV3uiArray( gl, v ) { gl.uniform3uiv( this.addr, v ); } function setValueV4uiArray( gl, v ) { gl.uniform4uiv( this.addr, v ); } // Array of textures (2D / 3D / Cube / 2DArray) function setValueT1Array( gl, v, textures ) { const cache = this.cache; const n = v.length; const units = allocTexUnits( textures, n ); if ( ! arraysEqual( cache, units ) ) { gl.uniform1iv( this.addr, units ); copyArray( cache, units ); } for ( let i = 0; i !== n; ++ i ) { textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); } } function setValueT3DArray( gl, v, textures ) { const cache = this.cache; const n = v.length; const units = allocTexUnits( textures, n ); if ( ! arraysEqual( cache, units ) ) { gl.uniform1iv( this.addr, units ); copyArray( cache, units ); } for ( let i = 0; i !== n; ++ i ) { textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); } } function setValueT6Array( gl, v, textures ) { const cache = this.cache; const n = v.length; const units = allocTexUnits( textures, n ); if ( ! arraysEqual( cache, units ) ) { gl.uniform1iv( this.addr, units ); copyArray( cache, units ); } for ( let i = 0; i !== n; ++ i ) { textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); } } function setValueT2DArrayArray( gl, v, textures ) { const cache = this.cache; const n = v.length; const units = allocTexUnits( textures, n ); if ( ! arraysEqual( cache, units ) ) { gl.uniform1iv( this.addr, units ); copyArray( cache, units ); } for ( let i = 0; i !== n; ++ i ) { textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); } } // Helper to pick the right setter for a pure (bottom-level) array function getPureArraySetter( type ) { switch ( type ) { case 0x1406: return setValueV1fArray; // FLOAT case 0x8b50: return setValueV2fArray; // _VEC2 case 0x8b51: return setValueV3fArray; // _VEC3 case 0x8b52: return setValueV4fArray; // _VEC4 case 0x8b5a: return setValueM2Array; // _MAT2 case 0x8b5b: return setValueM3Array; // _MAT3 case 0x8b5c: return setValueM4Array; // _MAT4 case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 case 0x1405: return setValueV1uiArray; // UINT case 0x8dc6: return setValueV2uiArray; // _VEC2 case 0x8dc7: return setValueV3uiArray; // _VEC3 case 0x8dc8: return setValueV4uiArray; // _VEC4 case 0x8b5e: // SAMPLER_2D case 0x8d66: // SAMPLER_EXTERNAL_OES case 0x8dca: // INT_SAMPLER_2D case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D case 0x8b62: // SAMPLER_2D_SHADOW return setValueT1Array; case 0x8b5f: // SAMPLER_3D case 0x8dcb: // INT_SAMPLER_3D case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D return setValueT3DArray; case 0x8b60: // SAMPLER_CUBE case 0x8dcc: // INT_SAMPLER_CUBE case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE case 0x8dc5: // SAMPLER_CUBE_SHADOW return setValueT6Array; case 0x8dc1: // SAMPLER_2D_ARRAY case 0x8dcf: // INT_SAMPLER_2D_ARRAY case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW return setValueT2DArrayArray; } } // --- Uniform Classes --- class SingleUniform { constructor( id, activeInfo, addr ) { this.id = id; this.addr = addr; this.cache = []; this.type = activeInfo.type; this.setValue = getSingularSetter( activeInfo.type ); // this.path = activeInfo.name; // DEBUG } } class PureArrayUniform { constructor( id, activeInfo, addr ) { this.id = id; this.addr = addr; this.cache = []; this.type = activeInfo.type; this.size = activeInfo.size; this.setValue = getPureArraySetter( activeInfo.type ); // this.path = activeInfo.name; // DEBUG } } class StructuredUniform { constructor( id ) { this.id = id; this.seq = []; this.map = {}; } setValue( gl, value, textures ) { const seq = this.seq; for ( let i = 0, n = seq.length; i !== n; ++ i ) { const u = seq[ i ]; u.setValue( gl, value[ u.id ], textures ); } } } // --- Top-level --- // Parser - builds up the property tree from the path strings const RePathPart = /(\w+)(\])?(\[|\.)?/g; // extracts // - the identifier (member name or array index) // - followed by an optional right bracket (found when array index) // - followed by an optional left bracket or dot (type of subscript) // // Note: These portions can be read in a non-overlapping fashion and // allow straightforward parsing of the hierarchy that WebGL encodes // in the uniform names. function addUniform( container, uniformObject ) { container.seq.push( uniformObject ); container.map[ uniformObject.id ] = uniformObject; } function parseUniform( activeInfo, addr, container ) { const path = activeInfo.name, pathLength = path.length; // reset RegExp object, because of the early exit of a previous run RePathPart.lastIndex = 0; while ( true ) { const match = RePathPart.exec( path ), matchEnd = RePathPart.lastIndex; let id = match[ 1 ]; const idIsIndex = match[ 2 ] === ']', subscript = match[ 3 ]; if ( idIsIndex ) id = id | 0; // convert to integer if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { // bare name or "pure" bottom-level array "[0]" suffix addUniform( container, subscript === undefined ? new SingleUniform( id, activeInfo, addr ) : new PureArrayUniform( id, activeInfo, addr ) ); break; } else { // step into inner node / create it in case it doesn't exist const map = container.map; let next = map[ id ]; if ( next === undefined ) { next = new StructuredUniform( id ); addUniform( container, next ); } container = next; } } } // Root Container class WebGLUniforms { constructor( gl, program ) { this.seq = []; this.map = {}; const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); for ( let i = 0; i < n; ++ i ) { const info = gl.getActiveUniform( program, i ), addr = gl.getUniformLocation( program, info.name ); parseUniform( info, addr, this ); } } setValue( gl, name, value, textures ) { const u = this.map[ name ]; if ( u !== undefined ) u.setValue( gl, value, textures ); } setOptional( gl, object, name ) { const v = object[ name ]; if ( v !== undefined ) this.setValue( gl, name, v ); } static upload( gl, seq, values, textures ) { for ( let i = 0, n = seq.length; i !== n; ++ i ) { const u = seq[ i ], v = values[ u.id ]; if ( v.needsUpdate !== false ) { // note: always updating when .needsUpdate is undefined u.setValue( gl, v.value, textures ); } } } static seqWithValue( seq, values ) { const r = []; for ( let i = 0, n = seq.length; i !== n; ++ i ) { const u = seq[ i ]; if ( u.id in values ) r.push( u ); } return r; } } function WebGLShader( gl, type, string ) { const shader = gl.createShader( type ); gl.shaderSource( shader, string ); gl.compileShader( shader ); return shader; } // From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ const COMPLETION_STATUS_KHR = 0x91B1; let programIdCount = 0; function handleSource( string, errorLine ) { const lines = string.split( '\n' ); const lines2 = []; const from = Math.max( errorLine - 6, 0 ); const to = Math.min( errorLine + 6, lines.length ); for ( let i = from; i < to; i ++ ) { const line = i + 1; lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); } return lines2.join( '\n' ); } function getEncodingComponents( colorSpace ) { const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); const encodingPrimaries = ColorManagement.getPrimaries( colorSpace ); let gamutMapping; if ( workingPrimaries === encodingPrimaries ) { gamutMapping = ''; } else if ( workingPrimaries === P3Primaries && encodingPrimaries === Rec709Primaries ) { gamutMapping = 'LinearDisplayP3ToLinearSRGB'; } else if ( workingPrimaries === Rec709Primaries && encodingPrimaries === P3Primaries ) { gamutMapping = 'LinearSRGBToLinearDisplayP3'; } switch ( colorSpace ) { case LinearSRGBColorSpace: case LinearDisplayP3ColorSpace: return [ gamutMapping, 'LinearTransferOETF' ]; case SRGBColorSpace: case DisplayP3ColorSpace: return [ gamutMapping, 'sRGBTransferOETF' ]; default: console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); return [ gamutMapping, 'LinearTransferOETF' ]; } } function getShaderErrors( gl, shader, type ) { const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); const errors = gl.getShaderInfoLog( shader ).trim(); if ( status && errors === '' ) return ''; const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); if ( errorMatches ) { // --enable-privileged-webgl-extension // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); const errorLine = parseInt( errorMatches[ 1 ] ); return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); } else { return errors; } } function getTexelEncodingFunction( functionName, colorSpace ) { const components = getEncodingComponents( colorSpace ); return `vec4 ${functionName}( vec4 value ) { return ${components[ 0 ]}( ${components[ 1 ]}( value ) ); }`; } function getToneMappingFunction( functionName, toneMapping ) { let toneMappingName; switch ( toneMapping ) { case LinearToneMapping: toneMappingName = 'Linear'; break; case ReinhardToneMapping: toneMappingName = 'Reinhard'; break; case CineonToneMapping: toneMappingName = 'OptimizedCineon'; break; case ACESFilmicToneMapping: toneMappingName = 'ACESFilmic'; break; case AgXToneMapping: toneMappingName = 'AgX'; break; case CustomToneMapping: toneMappingName = 'Custom'; break; default: console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); toneMappingName = 'Linear'; } return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; } function generateExtensions( parameters ) { const chunks = [ ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' ]; return chunks.filter( filterEmptyLine ).join( '\n' ); } function generateVertexExtensions( parameters ) { const chunks = [ parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '' ]; return chunks.filter( filterEmptyLine ).join( '\n' ); } function generateDefines( defines ) { const chunks = []; for ( const name in defines ) { const value = defines[ name ]; if ( value === false ) continue; chunks.push( '#define ' + name + ' ' + value ); } return chunks.join( '\n' ); } function fetchAttributeLocations( gl, program ) { const attributes = {}; const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); for ( let i = 0; i < n; i ++ ) { const info = gl.getActiveAttrib( program, i ); const name = info.name; let locationSize = 1; if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); attributes[ name ] = { type: info.type, location: gl.getAttribLocation( program, name ), locationSize: locationSize }; } return attributes; } function filterEmptyLine( string ) { return string !== ''; } function replaceLightNums( string, parameters ) { const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; return string .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); } function replaceClippingPlaneNums( string, parameters ) { return string .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); } // Resolve Includes const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; function resolveIncludes( string ) { return string.replace( includePattern, includeReplacer ); } const shaderChunkMap = new Map( [ [ 'encodings_fragment', 'colorspace_fragment' ], // @deprecated, r154 [ 'encodings_pars_fragment', 'colorspace_pars_fragment' ], // @deprecated, r154 [ 'output_fragment', 'opaque_fragment' ], // @deprecated, r154 ] ); function includeReplacer( match, include ) { let string = ShaderChunk[ include ]; if ( string === undefined ) { const newInclude = shaderChunkMap.get( include ); if ( newInclude !== undefined ) { string = ShaderChunk[ newInclude ]; console.warn( 'THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); } else { throw new Error( 'Can not resolve #include <' + include + '>' ); } } return resolveIncludes( string ); } // Unroll Loops const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; function unrollLoops( string ) { return string.replace( unrollLoopPattern, loopReplacer ); } function loopReplacer( match, start, end, snippet ) { let string = ''; for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { string += snippet .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) .replace( /UNROLLED_LOOP_INDEX/g, i ); } return string; } // function generatePrecision( parameters ) { let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;'; if ( parameters.precision === 'highp' ) { precisionstring += '\n#define HIGH_PRECISION'; } else if ( parameters.precision === 'mediump' ) { precisionstring += '\n#define MEDIUM_PRECISION'; } else if ( parameters.precision === 'lowp' ) { precisionstring += '\n#define LOW_PRECISION'; } return precisionstring; } function generateShadowMapTypeDefine( parameters ) { let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; if ( parameters.shadowMapType === PCFShadowMap ) { shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; } else if ( parameters.shadowMapType === VSMShadowMap ) { shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; } return shadowMapTypeDefine; } function generateEnvMapTypeDefine( parameters ) { let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; if ( parameters.envMap ) { switch ( parameters.envMapMode ) { case CubeReflectionMapping: case CubeRefractionMapping: envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; break; case CubeUVReflectionMapping: envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; break; } } return envMapTypeDefine; } function generateEnvMapModeDefine( parameters ) { let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; if ( parameters.envMap ) { switch ( parameters.envMapMode ) { case CubeRefractionMapping: envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; break; } } return envMapModeDefine; } function generateEnvMapBlendingDefine( parameters ) { let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; if ( parameters.envMap ) { switch ( parameters.combine ) { case MultiplyOperation: envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; break; case MixOperation: envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; break; case AddOperation: envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; break; } } return envMapBlendingDefine; } function generateCubeUVSize( parameters ) { const imageHeight = parameters.envMapCubeUVHeight; if ( imageHeight === null ) return null; const maxMip = Math.log2( imageHeight ) - 2; const texelHeight = 1.0 / imageHeight; const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); return { texelWidth, texelHeight, maxMip }; } function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { // TODO Send this event to Three.js DevTools // console.log( 'WebGLProgram', cacheKey ); const gl = renderer.getContext(); const defines = parameters.defines; let vertexShader = parameters.vertexShader; let fragmentShader = parameters.fragmentShader; const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); const envMapModeDefine = generateEnvMapModeDefine( parameters ); const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); const envMapCubeUVSize = generateCubeUVSize( parameters ); const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); const customVertexExtensions = generateVertexExtensions( parameters ); const customDefines = generateDefines( defines ); const program = gl.createProgram(); let prefixVertex, prefixFragment; let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; if ( parameters.isRawShaderMaterial ) { prefixVertex = [ '#define SHADER_TYPE ' + parameters.shaderType, '#define SHADER_NAME ' + parameters.shaderName, customDefines ].filter( filterEmptyLine ).join( '\n' ); if ( prefixVertex.length > 0 ) { prefixVertex += '\n'; } prefixFragment = [ customExtensions, '#define SHADER_TYPE ' + parameters.shaderType, '#define SHADER_NAME ' + parameters.shaderName, customDefines ].filter( filterEmptyLine ).join( '\n' ); if ( prefixFragment.length > 0 ) { prefixFragment += '\n'; } } else { prefixVertex = [ generatePrecision( parameters ), '#define SHADER_TYPE ' + parameters.shaderType, '#define SHADER_NAME ' + parameters.shaderName, customDefines, parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', parameters.batching ? '#define USE_BATCHING' : '', parameters.instancing ? '#define USE_INSTANCING' : '', parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', parameters.useFog && parameters.fog ? '#define USE_FOG' : '', parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', parameters.map ? '#define USE_MAP' : '', parameters.envMap ? '#define USE_ENVMAP' : '', parameters.envMap ? '#define ' + envMapModeDefine : '', parameters.lightMap ? '#define USE_LIGHTMAP' : '', parameters.aoMap ? '#define USE_AOMAP' : '', parameters.bumpMap ? '#define USE_BUMPMAP' : '', parameters.normalMap ? '#define USE_NORMALMAP' : '', parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', parameters.anisotropy ? '#define USE_ANISOTROPY' : '', parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', parameters.specularMap ? '#define USE_SPECULARMAP' : '', parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', parameters.alphaMap ? '#define USE_ALPHAMAP' : '', parameters.alphaHash ? '#define USE_ALPHAHASH' : '', parameters.transmission ? '#define USE_TRANSMISSION' : '', parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', // parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', // parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', parameters.vertexColors ? '#define USE_COLOR' : '', parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', parameters.vertexUv1s ? '#define USE_UV1' : '', parameters.vertexUv2s ? '#define USE_UV2' : '', parameters.vertexUv3s ? '#define USE_UV3' : '', parameters.pointsUvs ? '#define USE_POINTS_UV' : '', parameters.flatShading ? '#define FLAT_SHADED' : '', parameters.skinning ? '#define USE_SKINNING' : '', parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', parameters.doubleSided ? '#define DOUBLE_SIDED' : '', parameters.flipSided ? '#define FLIP_SIDED' : '', parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', 'uniform mat4 modelMatrix;', 'uniform mat4 modelViewMatrix;', 'uniform mat4 projectionMatrix;', 'uniform mat4 viewMatrix;', 'uniform mat3 normalMatrix;', 'uniform vec3 cameraPosition;', 'uniform bool isOrthographic;', '#ifdef USE_INSTANCING', ' attribute mat4 instanceMatrix;', '#endif', '#ifdef USE_INSTANCING_COLOR', ' attribute vec3 instanceColor;', '#endif', 'attribute vec3 position;', 'attribute vec3 normal;', 'attribute vec2 uv;', '#ifdef USE_UV1', ' attribute vec2 uv1;', '#endif', '#ifdef USE_UV2', ' attribute vec2 uv2;', '#endif', '#ifdef USE_UV3', ' attribute vec2 uv3;', '#endif', '#ifdef USE_TANGENT', ' attribute vec4 tangent;', '#endif', '#if defined( USE_COLOR_ALPHA )', ' attribute vec4 color;', '#elif defined( USE_COLOR )', ' attribute vec3 color;', '#endif', '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', ' attribute vec3 morphTarget0;', ' attribute vec3 morphTarget1;', ' attribute vec3 morphTarget2;', ' attribute vec3 morphTarget3;', ' #ifdef USE_MORPHNORMALS', ' attribute vec3 morphNormal0;', ' attribute vec3 morphNormal1;', ' attribute vec3 morphNormal2;', ' attribute vec3 morphNormal3;', ' #else', ' attribute vec3 morphTarget4;', ' attribute vec3 morphTarget5;', ' attribute vec3 morphTarget6;', ' attribute vec3 morphTarget7;', ' #endif', '#endif', '#ifdef USE_SKINNING', ' attribute vec4 skinIndex;', ' attribute vec4 skinWeight;', '#endif', '\n' ].filter( filterEmptyLine ).join( '\n' ); prefixFragment = [ customExtensions, generatePrecision( parameters ), '#define SHADER_TYPE ' + parameters.shaderType, '#define SHADER_NAME ' + parameters.shaderName, customDefines, parameters.useFog && parameters.fog ? '#define USE_FOG' : '', parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', parameters.map ? '#define USE_MAP' : '', parameters.matcap ? '#define USE_MATCAP' : '', parameters.envMap ? '#define USE_ENVMAP' : '', parameters.envMap ? '#define ' + envMapTypeDefine : '', parameters.envMap ? '#define ' + envMapModeDefine : '', parameters.envMap ? '#define ' + envMapBlendingDefine : '', envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', parameters.lightMap ? '#define USE_LIGHTMAP' : '', parameters.aoMap ? '#define USE_AOMAP' : '', parameters.bumpMap ? '#define USE_BUMPMAP' : '', parameters.normalMap ? '#define USE_NORMALMAP' : '', parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', parameters.anisotropy ? '#define USE_ANISOTROPY' : '', parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', parameters.clearcoat ? '#define USE_CLEARCOAT' : '', parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', parameters.iridescence ? '#define USE_IRIDESCENCE' : '', parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', parameters.specularMap ? '#define USE_SPECULARMAP' : '', parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', parameters.alphaMap ? '#define USE_ALPHAMAP' : '', parameters.alphaTest ? '#define USE_ALPHATEST' : '', parameters.alphaHash ? '#define USE_ALPHAHASH' : '', parameters.sheen ? '#define USE_SHEEN' : '', parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', parameters.transmission ? '#define USE_TRANSMISSION' : '', parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', parameters.vertexUv1s ? '#define USE_UV1' : '', parameters.vertexUv2s ? '#define USE_UV2' : '', parameters.vertexUv3s ? '#define USE_UV3' : '', parameters.pointsUvs ? '#define USE_POINTS_UV' : '', parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', parameters.flatShading ? '#define FLAT_SHADED' : '', parameters.doubleSided ? '#define DOUBLE_SIDED' : '', parameters.flipSided ? '#define FLIP_SIDED' : '', parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', 'uniform mat4 viewMatrix;', 'uniform vec3 cameraPosition;', 'uniform bool isOrthographic;', ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', parameters.dithering ? '#define DITHERING' : '', parameters.opaque ? '#define OPAQUE' : '', ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', '\n' ].filter( filterEmptyLine ).join( '\n' ); } vertexShader = resolveIncludes( vertexShader ); vertexShader = replaceLightNums( vertexShader, parameters ); vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); fragmentShader = resolveIncludes( fragmentShader ); fragmentShader = replaceLightNums( fragmentShader, parameters ); fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); vertexShader = unrollLoops( vertexShader ); fragmentShader = unrollLoops( fragmentShader ); if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { // GLSL 3.0 conversion for built-in materials and ShaderMaterial versionString = '#version 300 es\n'; prefixVertex = [ customVertexExtensions, 'precision mediump sampler2DArray;', '#define attribute in', '#define varying out', '#define texture2D texture' ].join( '\n' ) + '\n' + prefixVertex; prefixFragment = [ 'precision mediump sampler2DArray;', '#define varying in', ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', '#define gl_FragDepthEXT gl_FragDepth', '#define texture2D texture', '#define textureCube texture', '#define texture2DProj textureProj', '#define texture2DLodEXT textureLod', '#define texture2DProjLodEXT textureProjLod', '#define textureCubeLodEXT textureLod', '#define texture2DGradEXT textureGrad', '#define texture2DProjGradEXT textureProjGrad', '#define textureCubeGradEXT textureGrad' ].join( '\n' ) + '\n' + prefixFragment; } const vertexGlsl = versionString + prefixVertex + vertexShader; const fragmentGlsl = versionString + prefixFragment + fragmentShader; // console.log( '*VERTEX*', vertexGlsl ); // console.log( '*FRAGMENT*', fragmentGlsl ); const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); gl.attachShader( program, glVertexShader ); gl.attachShader( program, glFragmentShader ); // Force a particular attribute to index 0. if ( parameters.index0AttributeName !== undefined ) { gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); } else if ( parameters.morphTargets === true ) { // programs with morphTargets displace position out of attribute 0 gl.bindAttribLocation( program, 0, 'position' ); } gl.linkProgram( program ); function onFirstUse( self ) { // check for link errors if ( renderer.debug.checkShaderErrors ) { const programLog = gl.getProgramInfoLog( program ).trim(); const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); let runnable = true; let haveDiagnostics = true; if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { runnable = false; if ( typeof renderer.debug.onShaderError === 'function' ) { renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); } else { // default error reporting const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); console.error( 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + 'Program Info Log: ' + programLog + '\n' + vertexErrors + '\n' + fragmentErrors ); } } else if ( programLog !== '' ) { console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); } else if ( vertexLog === '' || fragmentLog === '' ) { haveDiagnostics = false; } if ( haveDiagnostics ) { self.diagnostics = { runnable: runnable, programLog: programLog, vertexShader: { log: vertexLog, prefix: prefixVertex }, fragmentShader: { log: fragmentLog, prefix: prefixFragment } }; } } // Clean up // Crashes in iOS9 and iOS10. #18402 // gl.detachShader( program, glVertexShader ); // gl.detachShader( program, glFragmentShader ); gl.deleteShader( glVertexShader ); gl.deleteShader( glFragmentShader ); cachedUniforms = new WebGLUniforms( gl, program ); cachedAttributes = fetchAttributeLocations( gl, program ); } // set up caching for uniform locations let cachedUniforms; this.getUniforms = function () { if ( cachedUniforms === undefined ) { // Populates cachedUniforms and cachedAttributes onFirstUse( this ); } return cachedUniforms; }; // set up caching for attribute locations let cachedAttributes; this.getAttributes = function () { if ( cachedAttributes === undefined ) { // Populates cachedAttributes and cachedUniforms onFirstUse( this ); } return cachedAttributes; }; // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, // flag the program as ready immediately. It may cause a stall when it's first used. let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); this.isReady = function () { if ( programReady === false ) { programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); } return programReady; }; // free resource this.destroy = function () { bindingStates.releaseStatesOfProgram( this ); gl.deleteProgram( program ); this.program = undefined; }; // this.type = parameters.shaderType; this.name = parameters.shaderName; this.id = programIdCount ++; this.cacheKey = cacheKey; this.usedTimes = 1; this.program = program; this.vertexShader = glVertexShader; this.fragmentShader = glFragmentShader; return this; } let _id$1 = 0; class WebGLShaderCache { constructor() { this.shaderCache = new Map(); this.materialCache = new Map(); } update( material ) { const vertexShader = material.vertexShader; const fragmentShader = material.fragmentShader; const vertexShaderStage = this._getShaderStage( vertexShader ); const fragmentShaderStage = this._getShaderStage( fragmentShader ); const materialShaders = this._getShaderCacheForMaterial( material ); if ( materialShaders.has( vertexShaderStage ) === false ) { materialShaders.add( vertexShaderStage ); vertexShaderStage.usedTimes ++; } if ( materialShaders.has( fragmentShaderStage ) === false ) { materialShaders.add( fragmentShaderStage ); fragmentShaderStage.usedTimes ++; } return this; } remove( material ) { const materialShaders = this.materialCache.get( material ); for ( const shaderStage of materialShaders ) { shaderStage.usedTimes --; if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); } this.materialCache.delete( material ); return this; } getVertexShaderID( material ) { return this._getShaderStage( material.vertexShader ).id; } getFragmentShaderID( material ) { return this._getShaderStage( material.fragmentShader ).id; } dispose() { this.shaderCache.clear(); this.materialCache.clear(); } _getShaderCacheForMaterial( material ) { const cache = this.materialCache; let set = cache.get( material ); if ( set === undefined ) { set = new Set(); cache.set( material, set ); } return set; } _getShaderStage( code ) { const cache = this.shaderCache; let stage = cache.get( code ); if ( stage === undefined ) { stage = new WebGLShaderStage( code ); cache.set( code, stage ); } return stage; } } class WebGLShaderStage { constructor( code ) { this.id = _id$1 ++; this.code = code; this.usedTimes = 0; } } function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { const _programLayers = new Layers(); const _customShaders = new WebGLShaderCache(); const programs = []; const IS_WEBGL2 = capabilities.isWebGL2; const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; let precision = capabilities.precision; const shaderIDs = { MeshDepthMaterial: 'depth', MeshDistanceMaterial: 'distanceRGBA', MeshNormalMaterial: 'normal', MeshBasicMaterial: 'basic', MeshLambertMaterial: 'lambert', MeshPhongMaterial: 'phong', MeshToonMaterial: 'toon', MeshStandardMaterial: 'physical', MeshPhysicalMaterial: 'physical', MeshMatcapMaterial: 'matcap', LineBasicMaterial: 'basic', LineDashedMaterial: 'dashed', PointsMaterial: 'points', ShadowMaterial: 'shadow', SpriteMaterial: 'sprite' }; function getChannel( value ) { if ( value === 0 ) return 'uv'; return `uv${ value }`; } function getParameters( material, lights, shadows, scene, object ) { const fog = scene.fog; const geometry = object.geometry; const environment = material.isMeshStandardMaterial ? scene.environment : null; const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; const shaderID = shaderIDs[ material.type ]; // heuristics to create shader parameters according to lights in the scene // (not to blow over maxLights budget) if ( material.precision !== null ) { precision = capabilities.getMaxPrecision( material.precision ); if ( precision !== material.precision ) { console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); } } // const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; let morphTextureStride = 0; if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; // let vertexShader, fragmentShader; let customVertexShaderID, customFragmentShaderID; if ( shaderID ) { const shader = ShaderLib[ shaderID ]; vertexShader = shader.vertexShader; fragmentShader = shader.fragmentShader; } else { vertexShader = material.vertexShader; fragmentShader = material.fragmentShader; _customShaders.update( material ); customVertexShaderID = _customShaders.getVertexShaderID( material ); customFragmentShaderID = _customShaders.getFragmentShaderID( material ); } const currentRenderTarget = renderer.getRenderTarget(); const IS_INSTANCEDMESH = object.isInstancedMesh === true; const IS_BATCHEDMESH = object.isBatchedMesh === true; const HAS_MAP = !! material.map; const HAS_MATCAP = !! material.matcap; const HAS_ENVMAP = !! envMap; const HAS_AOMAP = !! material.aoMap; const HAS_LIGHTMAP = !! material.lightMap; const HAS_BUMPMAP = !! material.bumpMap; const HAS_NORMALMAP = !! material.normalMap; const HAS_DISPLACEMENTMAP = !! material.displacementMap; const HAS_EMISSIVEMAP = !! material.emissiveMap; const HAS_METALNESSMAP = !! material.metalnessMap; const HAS_ROUGHNESSMAP = !! material.roughnessMap; const HAS_ANISOTROPY = material.anisotropy > 0; const HAS_CLEARCOAT = material.clearcoat > 0; const HAS_IRIDESCENCE = material.iridescence > 0; const HAS_SHEEN = material.sheen > 0; const HAS_TRANSMISSION = material.transmission > 0; const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; const HAS_SPECULARMAP = !! material.specularMap; const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; const HAS_GRADIENTMAP = !! material.gradientMap; const HAS_ALPHAMAP = !! material.alphaMap; const HAS_ALPHATEST = material.alphaTest > 0; const HAS_ALPHAHASH = !! material.alphaHash; const HAS_EXTENSIONS = !! material.extensions; const HAS_ATTRIBUTE_UV1 = !! geometry.attributes.uv1; const HAS_ATTRIBUTE_UV2 = !! geometry.attributes.uv2; const HAS_ATTRIBUTE_UV3 = !! geometry.attributes.uv3; let toneMapping = NoToneMapping; if ( material.toneMapped ) { if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { toneMapping = renderer.toneMapping; } } const parameters = { isWebGL2: IS_WEBGL2, shaderID: shaderID, shaderType: material.type, shaderName: material.name, vertexShader: vertexShader, fragmentShader: fragmentShader, defines: material.defines, customVertexShaderID: customVertexShaderID, customFragmentShaderID: customFragmentShaderID, isRawShaderMaterial: material.isRawShaderMaterial === true, glslVersion: material.glslVersion, precision: precision, batching: IS_BATCHEDMESH, instancing: IS_INSTANCEDMESH, instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), map: HAS_MAP, matcap: HAS_MATCAP, envMap: HAS_ENVMAP, envMapMode: HAS_ENVMAP && envMap.mapping, envMapCubeUVHeight: envMapCubeUVHeight, aoMap: HAS_AOMAP, lightMap: HAS_LIGHTMAP, bumpMap: HAS_BUMPMAP, normalMap: HAS_NORMALMAP, displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, emissiveMap: HAS_EMISSIVEMAP, normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, metalnessMap: HAS_METALNESSMAP, roughnessMap: HAS_ROUGHNESSMAP, anisotropy: HAS_ANISOTROPY, anisotropyMap: HAS_ANISOTROPYMAP, clearcoat: HAS_CLEARCOAT, clearcoatMap: HAS_CLEARCOATMAP, clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, iridescence: HAS_IRIDESCENCE, iridescenceMap: HAS_IRIDESCENCEMAP, iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, sheen: HAS_SHEEN, sheenColorMap: HAS_SHEEN_COLORMAP, sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, specularMap: HAS_SPECULARMAP, specularColorMap: HAS_SPECULAR_COLORMAP, specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, transmission: HAS_TRANSMISSION, transmissionMap: HAS_TRANSMISSIONMAP, thicknessMap: HAS_THICKNESSMAP, gradientMap: HAS_GRADIENTMAP, opaque: material.transparent === false && material.blending === NormalBlending, alphaMap: HAS_ALPHAMAP, alphaTest: HAS_ALPHATEST, alphaHash: HAS_ALPHAHASH, combine: material.combine, // mapUv: HAS_MAP && getChannel( material.map.channel ), aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), // vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), vertexColors: material.vertexColors, vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, vertexUv1s: HAS_ATTRIBUTE_UV1, vertexUv2s: HAS_ATTRIBUTE_UV2, vertexUv3s: HAS_ATTRIBUTE_UV3, pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), fog: !! fog, useFog: material.fog === true, fogExp2: ( fog && fog.isFogExp2 ), flatShading: material.flatShading === true, sizeAttenuation: material.sizeAttenuation === true, logarithmicDepthBuffer: logarithmicDepthBuffer, skinning: object.isSkinnedMesh === true, morphTargets: geometry.morphAttributes.position !== undefined, morphNormals: geometry.morphAttributes.normal !== undefined, morphColors: geometry.morphAttributes.color !== undefined, morphTargetsCount: morphTargetsCount, morphTextureStride: morphTextureStride, numDirLights: lights.directional.length, numPointLights: lights.point.length, numSpotLights: lights.spot.length, numSpotLightMaps: lights.spotLightMap.length, numRectAreaLights: lights.rectArea.length, numHemiLights: lights.hemi.length, numDirLightShadows: lights.directionalShadowMap.length, numPointLightShadows: lights.pointShadowMap.length, numSpotLightShadows: lights.spotShadowMap.length, numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, numLightProbes: lights.numLightProbes, numClippingPlanes: clipping.numPlanes, numClipIntersection: clipping.numIntersection, dithering: material.dithering, shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, shadowMapType: renderer.shadowMap.type, toneMapping: toneMapping, useLegacyLights: renderer._useLegacyLights, decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), premultipliedAlpha: material.premultipliedAlpha, doubleSided: material.side === DoubleSide, flipSided: material.side === BackSide, useDepthPacking: material.depthPacking >= 0, depthPacking: material.depthPacking || 0, index0AttributeName: material.index0AttributeName, extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance && extensions.has( 'WEBGL_clip_cull_distance' ), rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), customProgramCacheKey: material.customProgramCacheKey() }; return parameters; } function getProgramCacheKey( parameters ) { const array = []; if ( parameters.shaderID ) { array.push( parameters.shaderID ); } else { array.push( parameters.customVertexShaderID ); array.push( parameters.customFragmentShaderID ); } if ( parameters.defines !== undefined ) { for ( const name in parameters.defines ) { array.push( name ); array.push( parameters.defines[ name ] ); } } if ( parameters.isRawShaderMaterial === false ) { getProgramCacheKeyParameters( array, parameters ); getProgramCacheKeyBooleans( array, parameters ); array.push( renderer.outputColorSpace ); } array.push( parameters.customProgramCacheKey ); return array.join(); } function getProgramCacheKeyParameters( array, parameters ) { array.push( parameters.precision ); array.push( parameters.outputColorSpace ); array.push( parameters.envMapMode ); array.push( parameters.envMapCubeUVHeight ); array.push( parameters.mapUv ); array.push( parameters.alphaMapUv ); array.push( parameters.lightMapUv ); array.push( parameters.aoMapUv ); array.push( parameters.bumpMapUv ); array.push( parameters.normalMapUv ); array.push( parameters.displacementMapUv ); array.push( parameters.emissiveMapUv ); array.push( parameters.metalnessMapUv ); array.push( parameters.roughnessMapUv ); array.push( parameters.anisotropyMapUv ); array.push( parameters.clearcoatMapUv ); array.push( parameters.clearcoatNormalMapUv ); array.push( parameters.clearcoatRoughnessMapUv ); array.push( parameters.iridescenceMapUv ); array.push( parameters.iridescenceThicknessMapUv ); array.push( parameters.sheenColorMapUv ); array.push( parameters.sheenRoughnessMapUv ); array.push( parameters.specularMapUv ); array.push( parameters.specularColorMapUv ); array.push( parameters.specularIntensityMapUv ); array.push( parameters.transmissionMapUv ); array.push( parameters.thicknessMapUv ); array.push( parameters.combine ); array.push( parameters.fogExp2 ); array.push( parameters.sizeAttenuation ); array.push( parameters.morphTargetsCount ); array.push( parameters.morphAttributeCount ); array.push( parameters.numDirLights ); array.push( parameters.numPointLights ); array.push( parameters.numSpotLights ); array.push( parameters.numSpotLightMaps ); array.push( parameters.numHemiLights ); array.push( parameters.numRectAreaLights ); array.push( parameters.numDirLightShadows ); array.push( parameters.numPointLightShadows ); array.push( parameters.numSpotLightShadows ); array.push( parameters.numSpotLightShadowsWithMaps ); array.push( parameters.numLightProbes ); array.push( parameters.shadowMapType ); array.push( parameters.toneMapping ); array.push( parameters.numClippingPlanes ); array.push( parameters.numClipIntersection ); array.push( parameters.depthPacking ); } function getProgramCacheKeyBooleans( array, parameters ) { _programLayers.disableAll(); if ( parameters.isWebGL2 ) _programLayers.enable( 0 ); if ( parameters.supportsVertexTextures ) _programLayers.enable( 1 ); if ( parameters.instancing ) _programLayers.enable( 2 ); if ( parameters.instancingColor ) _programLayers.enable( 3 ); if ( parameters.matcap ) _programLayers.enable( 4 ); if ( parameters.envMap ) _programLayers.enable( 5 ); if ( parameters.normalMapObjectSpace ) _programLayers.enable( 6 ); if ( parameters.normalMapTangentSpace ) _programLayers.enable( 7 ); if ( parameters.clearcoat ) _programLayers.enable( 8 ); if ( parameters.iridescence ) _programLayers.enable( 9 ); if ( parameters.alphaTest ) _programLayers.enable( 10 ); if ( parameters.vertexColors ) _programLayers.enable( 11 ); if ( parameters.vertexAlphas ) _programLayers.enable( 12 ); if ( parameters.vertexUv1s ) _programLayers.enable( 13 ); if ( parameters.vertexUv2s ) _programLayers.enable( 14 ); if ( parameters.vertexUv3s ) _programLayers.enable( 15 ); if ( parameters.vertexTangents ) _programLayers.enable( 16 ); if ( parameters.anisotropy ) _programLayers.enable( 17 ); if ( parameters.alphaHash ) _programLayers.enable( 18 ); if ( parameters.batching ) _programLayers.enable( 19 ); array.push( _programLayers.mask ); _programLayers.disableAll(); if ( parameters.fog ) _programLayers.enable( 0 ); if ( parameters.useFog ) _programLayers.enable( 1 ); if ( parameters.flatShading ) _programLayers.enable( 2 ); if ( parameters.logarithmicDepthBuffer ) _programLayers.enable( 3 ); if ( parameters.skinning ) _programLayers.enable( 4 ); if ( parameters.morphTargets ) _programLayers.enable( 5 ); if ( parameters.morphNormals ) _programLayers.enable( 6 ); if ( parameters.morphColors ) _programLayers.enable( 7 ); if ( parameters.premultipliedAlpha ) _programLayers.enable( 8 ); if ( parameters.shadowMapEnabled ) _programLayers.enable( 9 ); if ( parameters.useLegacyLights ) _programLayers.enable( 10 ); if ( parameters.doubleSided ) _programLayers.enable( 11 ); if ( parameters.flipSided ) _programLayers.enable( 12 ); if ( parameters.useDepthPacking ) _programLayers.enable( 13 ); if ( parameters.dithering ) _programLayers.enable( 14 ); if ( parameters.transmission ) _programLayers.enable( 15 ); if ( parameters.sheen ) _programLayers.enable( 16 ); if ( parameters.opaque ) _programLayers.enable( 17 ); if ( parameters.pointsUvs ) _programLayers.enable( 18 ); if ( parameters.decodeVideoTexture ) _programLayers.enable( 19 ); array.push( _programLayers.mask ); } function getUniforms( material ) { const shaderID = shaderIDs[ material.type ]; let uniforms; if ( shaderID ) { const shader = ShaderLib[ shaderID ]; uniforms = UniformsUtils.clone( shader.uniforms ); } else { uniforms = material.uniforms; } return uniforms; } function acquireProgram( parameters, cacheKey ) { let program; // Check if code has been already compiled for ( let p = 0, pl = programs.length; p < pl; p ++ ) { const preexistingProgram = programs[ p ]; if ( preexistingProgram.cacheKey === cacheKey ) { program = preexistingProgram; ++ program.usedTimes; break; } } if ( program === undefined ) { program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); programs.push( program ); } return program; } function releaseProgram( program ) { if ( -- program.usedTimes === 0 ) { // Remove from unordered set const i = programs.indexOf( program ); programs[ i ] = programs[ programs.length - 1 ]; programs.pop(); // Free WebGL resources program.destroy(); } } function releaseShaderCache( material ) { _customShaders.remove( material ); } function dispose() { _customShaders.dispose(); } return { getParameters: getParameters, getProgramCacheKey: getProgramCacheKey, getUniforms: getUniforms, acquireProgram: acquireProgram, releaseProgram: releaseProgram, releaseShaderCache: releaseShaderCache, // Exposed for resource monitoring & error feedback via renderer.info: programs: programs, dispose: dispose }; } function WebGLProperties() { let properties = new WeakMap(); function get( object ) { let map = properties.get( object ); if ( map === undefined ) { map = {}; properties.set( object, map ); } return map; } function remove( object ) { properties.delete( object ); } function update( object, key, value ) { properties.get( object )[ key ] = value; } function dispose() { properties = new WeakMap(); } return { get: get, remove: remove, update: update, dispose: dispose }; } function painterSortStable( a, b ) { if ( a.groupOrder !== b.groupOrder ) { return a.groupOrder - b.groupOrder; } else if ( a.renderOrder !== b.renderOrder ) { return a.renderOrder - b.renderOrder; } else if ( a.material.id !== b.material.id ) { return a.material.id - b.material.id; } else if ( a.z !== b.z ) { return a.z - b.z; } else { return a.id - b.id; } } function reversePainterSortStable( a, b ) { if ( a.groupOrder !== b.groupOrder ) { return a.groupOrder - b.groupOrder; } else if ( a.renderOrder !== b.renderOrder ) { return a.renderOrder - b.renderOrder; } else if ( a.z !== b.z ) { return b.z - a.z; } else { return a.id - b.id; } } function WebGLRenderList() { const renderItems = []; let renderItemsIndex = 0; const opaque = []; const transmissive = []; const transparent = []; function init() { renderItemsIndex = 0; opaque.length = 0; transmissive.length = 0; transparent.length = 0; } function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { let renderItem = renderItems[ renderItemsIndex ]; if ( renderItem === undefined ) { renderItem = { id: object.id, object: object, geometry: geometry, material: material, groupOrder: groupOrder, renderOrder: object.renderOrder, z: z, group: group }; renderItems[ renderItemsIndex ] = renderItem; } else { renderItem.id = object.id; renderItem.object = object; renderItem.geometry = geometry; renderItem.material = material; renderItem.groupOrder = groupOrder; renderItem.renderOrder = object.renderOrder; renderItem.z = z; renderItem.group = group; } renderItemsIndex ++; return renderItem; } function push( object, geometry, material, groupOrder, z, group ) { const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); if ( material.transmission > 0.0 ) { transmissive.push( renderItem ); } else if ( material.transparent === true ) { transparent.push( renderItem ); } else { opaque.push( renderItem ); } } function unshift( object, geometry, material, groupOrder, z, group ) { const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); if ( material.transmission > 0.0 ) { transmissive.unshift( renderItem ); } else if ( material.transparent === true ) { transparent.unshift( renderItem ); } else { opaque.unshift( renderItem ); } } function sort( customOpaqueSort, customTransparentSort ) { if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); } function finish() { // Clear references from inactive renderItems in the list for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { const renderItem = renderItems[ i ]; if ( renderItem.id === null ) break; renderItem.id = null; renderItem.object = null; renderItem.geometry = null; renderItem.material = null; renderItem.group = null; } } return { opaque: opaque, transmissive: transmissive, transparent: transparent, init: init, push: push, unshift: unshift, finish: finish, sort: sort }; } function WebGLRenderLists() { let lists = new WeakMap(); function get( scene, renderCallDepth ) { const listArray = lists.get( scene ); let list; if ( listArray === undefined ) { list = new WebGLRenderList(); lists.set( scene, [ list ] ); } else { if ( renderCallDepth >= listArray.length ) { list = new WebGLRenderList(); listArray.push( list ); } else { list = listArray[ renderCallDepth ]; } } return list; } function dispose() { lists = new WeakMap(); } return { get: get, dispose: dispose }; } function UniformsCache() { const lights = {}; return { get: function ( light ) { if ( lights[ light.id ] !== undefined ) { return lights[ light.id ]; } let uniforms; switch ( light.type ) { case 'DirectionalLight': uniforms = { direction: new Vector3(), color: new Color() }; break; case 'SpotLight': uniforms = { position: new Vector3(), direction: new Vector3(), color: new Color(), distance: 0, coneCos: 0, penumbraCos: 0, decay: 0 }; break; case 'PointLight': uniforms = { position: new Vector3(), color: new Color(), distance: 0, decay: 0 }; break; case 'HemisphereLight': uniforms = { direction: new Vector3(), skyColor: new Color(), groundColor: new Color() }; break; case 'RectAreaLight': uniforms = { color: new Color(), position: new Vector3(), halfWidth: new Vector3(), halfHeight: new Vector3() }; break; } lights[ light.id ] = uniforms; return uniforms; } }; } function ShadowUniformsCache() { const lights = {}; return { get: function ( light ) { if ( lights[ light.id ] !== undefined ) { return lights[ light.id ]; } let uniforms; switch ( light.type ) { case 'DirectionalLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2() }; break; case 'SpotLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2() }; break; case 'PointLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2(), shadowCameraNear: 1, shadowCameraFar: 1000 }; break; // TODO (abelnation): set RectAreaLight shadow uniforms } lights[ light.id ] = uniforms; return uniforms; } }; } let nextVersion = 0; function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); } function WebGLLights( extensions, capabilities ) { const cache = new UniformsCache(); const shadowCache = ShadowUniformsCache(); const state = { version: 0, hash: { directionalLength: - 1, pointLength: - 1, spotLength: - 1, rectAreaLength: - 1, hemiLength: - 1, numDirectionalShadows: - 1, numPointShadows: - 1, numSpotShadows: - 1, numSpotMaps: - 1, numLightProbes: - 1 }, ambient: [ 0, 0, 0 ], probe: [], directional: [], directionalShadow: [], directionalShadowMap: [], directionalShadowMatrix: [], spot: [], spotLightMap: [], spotShadow: [], spotShadowMap: [], spotLightMatrix: [], rectArea: [], rectAreaLTC1: null, rectAreaLTC2: null, point: [], pointShadow: [], pointShadowMap: [], pointShadowMatrix: [], hemi: [], numSpotLightShadowsWithMaps: 0, numLightProbes: 0 }; for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); const vector3 = new Vector3(); const matrix4 = new Matrix4(); const matrix42 = new Matrix4(); function setup( lights, useLegacyLights ) { let r = 0, g = 0, b = 0; for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); let directionalLength = 0; let pointLength = 0; let spotLength = 0; let rectAreaLength = 0; let hemiLength = 0; let numDirectionalShadows = 0; let numPointShadows = 0; let numSpotShadows = 0; let numSpotMaps = 0; let numSpotShadowsWithMaps = 0; let numLightProbes = 0; // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] lights.sort( shadowCastingAndTexturingLightsFirst ); // artist-friendly light intensity scaling factor const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; for ( let i = 0, l = lights.length; i < l; i ++ ) { const light = lights[ i ]; const color = light.color; const intensity = light.intensity; const distance = light.distance; const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; if ( light.isAmbientLight ) { r += color.r * intensity * scaleFactor; g += color.g * intensity * scaleFactor; b += color.b * intensity * scaleFactor; } else if ( light.isLightProbe ) { for ( let j = 0; j < 9; j ++ ) { state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); } numLightProbes ++; } else if ( light.isDirectionalLight ) { const uniforms = cache.get( light ); uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); if ( light.castShadow ) { const shadow = light.shadow; const shadowUniforms = shadowCache.get( light ); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; state.directionalShadow[ directionalLength ] = shadowUniforms; state.directionalShadowMap[ directionalLength ] = shadowMap; state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; numDirectionalShadows ++; } state.directional[ directionalLength ] = uniforms; directionalLength ++; } else if ( light.isSpotLight ) { const uniforms = cache.get( light ); uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); uniforms.distance = distance; uniforms.coneCos = Math.cos( light.angle ); uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); uniforms.decay = light.decay; state.spot[ spotLength ] = uniforms; const shadow = light.shadow; if ( light.map ) { state.spotLightMap[ numSpotMaps ] = light.map; numSpotMaps ++; // make sure the lightMatrix is up to date // TODO : do it if required only shadow.updateMatrices( light ); if ( light.castShadow ) numSpotShadowsWithMaps ++; } state.spotLightMatrix[ spotLength ] = shadow.matrix; if ( light.castShadow ) { const shadowUniforms = shadowCache.get( light ); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; state.spotShadow[ spotLength ] = shadowUniforms; state.spotShadowMap[ spotLength ] = shadowMap; numSpotShadows ++; } spotLength ++; } else if ( light.isRectAreaLight ) { const uniforms = cache.get( light ); uniforms.color.copy( color ).multiplyScalar( intensity ); uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); state.rectArea[ rectAreaLength ] = uniforms; rectAreaLength ++; } else if ( light.isPointLight ) { const uniforms = cache.get( light ); uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); uniforms.distance = light.distance; uniforms.decay = light.decay; if ( light.castShadow ) { const shadow = light.shadow; const shadowUniforms = shadowCache.get( light ); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; shadowUniforms.shadowCameraNear = shadow.camera.near; shadowUniforms.shadowCameraFar = shadow.camera.far; state.pointShadow[ pointLength ] = shadowUniforms; state.pointShadowMap[ pointLength ] = shadowMap; state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; numPointShadows ++; } state.point[ pointLength ] = uniforms; pointLength ++; } else if ( light.isHemisphereLight ) { const uniforms = cache.get( light ); uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); state.hemi[ hemiLength ] = uniforms; hemiLength ++; } } if ( rectAreaLength > 0 ) { if ( capabilities.isWebGL2 ) { // WebGL 2 if ( extensions.has( 'OES_texture_float_linear' ) === true ) { state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; } else { state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; } } else { // WebGL 1 if ( extensions.has( 'OES_texture_float_linear' ) === true ) { state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; } else { console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); } } } state.ambient[ 0 ] = r; state.ambient[ 1 ] = g; state.ambient[ 2 ] = b; const hash = state.hash; if ( hash.directionalLength !== directionalLength || hash.pointLength !== pointLength || hash.spotLength !== spotLength || hash.rectAreaLength !== rectAreaLength || hash.hemiLength !== hemiLength || hash.numDirectionalShadows !== numDirectionalShadows || hash.numPointShadows !== numPointShadows || hash.numSpotShadows !== numSpotShadows || hash.numSpotMaps !== numSpotMaps || hash.numLightProbes !== numLightProbes ) { state.directional.length = directionalLength; state.spot.length = spotLength; state.rectArea.length = rectAreaLength; state.point.length = pointLength; state.hemi.length = hemiLength; state.directionalShadow.length = numDirectionalShadows; state.directionalShadowMap.length = numDirectionalShadows; state.pointShadow.length = numPointShadows; state.pointShadowMap.length = numPointShadows; state.spotShadow.length = numSpotShadows; state.spotShadowMap.length = numSpotShadows; state.directionalShadowMatrix.length = numDirectionalShadows; state.pointShadowMatrix.length = numPointShadows; state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; state.spotLightMap.length = numSpotMaps; state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; state.numLightProbes = numLightProbes; hash.directionalLength = directionalLength; hash.pointLength = pointLength; hash.spotLength = spotLength; hash.rectAreaLength = rectAreaLength; hash.hemiLength = hemiLength; hash.numDirectionalShadows = numDirectionalShadows; hash.numPointShadows = numPointShadows; hash.numSpotShadows = numSpotShadows; hash.numSpotMaps = numSpotMaps; hash.numLightProbes = numLightProbes; state.version = nextVersion ++; } } function setupView( lights, camera ) { let directionalLength = 0; let pointLength = 0; let spotLength = 0; let rectAreaLength = 0; let hemiLength = 0; const viewMatrix = camera.matrixWorldInverse; for ( let i = 0, l = lights.length; i < l; i ++ ) { const light = lights[ i ]; if ( light.isDirectionalLight ) { const uniforms = state.directional[ directionalLength ]; uniforms.direction.setFromMatrixPosition( light.matrixWorld ); vector3.setFromMatrixPosition( light.target.matrixWorld ); uniforms.direction.sub( vector3 ); uniforms.direction.transformDirection( viewMatrix ); directionalLength ++; } else if ( light.isSpotLight ) { const uniforms = state.spot[ spotLength ]; uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.position.applyMatrix4( viewMatrix ); uniforms.direction.setFromMatrixPosition( light.matrixWorld ); vector3.setFromMatrixPosition( light.target.matrixWorld ); uniforms.direction.sub( vector3 ); uniforms.direction.transformDirection( viewMatrix ); spotLength ++; } else if ( light.isRectAreaLight ) { const uniforms = state.rectArea[ rectAreaLength ]; uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.position.applyMatrix4( viewMatrix ); // extract local rotation of light to derive width/height half vectors matrix42.identity(); matrix4.copy( light.matrixWorld ); matrix4.premultiply( viewMatrix ); matrix42.extractRotation( matrix4 ); uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); uniforms.halfWidth.applyMatrix4( matrix42 ); uniforms.halfHeight.applyMatrix4( matrix42 ); rectAreaLength ++; } else if ( light.isPointLight ) { const uniforms = state.point[ pointLength ]; uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.position.applyMatrix4( viewMatrix ); pointLength ++; } else if ( light.isHemisphereLight ) { const uniforms = state.hemi[ hemiLength ]; uniforms.direction.setFromMatrixPosition( light.matrixWorld ); uniforms.direction.transformDirection( viewMatrix ); hemiLength ++; } } } return { setup: setup, setupView: setupView, state: state }; } function WebGLRenderState( extensions, capabilities ) { const lights = new WebGLLights( extensions, capabilities ); const lightsArray = []; const shadowsArray = []; function init() { lightsArray.length = 0; shadowsArray.length = 0; } function pushLight( light ) { lightsArray.push( light ); } function pushShadow( shadowLight ) { shadowsArray.push( shadowLight ); } function setupLights( useLegacyLights ) { lights.setup( lightsArray, useLegacyLights ); } function setupLightsView( camera ) { lights.setupView( lightsArray, camera ); } const state = { lightsArray: lightsArray, shadowsArray: shadowsArray, lights: lights }; return { init: init, state: state, setupLights: setupLights, setupLightsView: setupLightsView, pushLight: pushLight, pushShadow: pushShadow }; } function WebGLRenderStates( extensions, capabilities ) { let renderStates = new WeakMap(); function get( scene, renderCallDepth = 0 ) { const renderStateArray = renderStates.get( scene ); let renderState; if ( renderStateArray === undefined ) { renderState = new WebGLRenderState( extensions, capabilities ); renderStates.set( scene, [ renderState ] ); } else { if ( renderCallDepth >= renderStateArray.length ) { renderState = new WebGLRenderState( extensions, capabilities ); renderStateArray.push( renderState ); } else { renderState = renderStateArray[ renderCallDepth ]; } } return renderState; } function dispose() { renderStates = new WeakMap(); } return { get: get, dispose: dispose }; } class MeshDepthMaterial extends Material { constructor( parameters ) { super(); this.isMeshDepthMaterial = true; this.type = 'MeshDepthMaterial'; this.depthPacking = BasicDepthPacking; this.map = null; this.alphaMap = null; this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.wireframe = false; this.wireframeLinewidth = 1; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.depthPacking = source.depthPacking; this.map = source.map; this.alphaMap = source.alphaMap; this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; return this; } } class MeshDistanceMaterial extends Material { constructor( parameters ) { super(); this.isMeshDistanceMaterial = true; this.type = 'MeshDistanceMaterial'; this.map = null; this.alphaMap = null; this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.map = source.map; this.alphaMap = source.alphaMap; this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; return this; } } const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; function WebGLShadowMap( _renderer, _objects, _capabilities ) { let _frustum = new Frustum(); const _shadowMapSize = new Vector2(), _viewportSize = new Vector2(), _viewport = new Vector4(), _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), _distanceMaterial = new MeshDistanceMaterial(), _materialCache = {}, _maxTextureSize = _capabilities.maxTextureSize; const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; const shadowMaterialVertical = new ShaderMaterial( { defines: { VSM_SAMPLES: 8 }, uniforms: { shadow_pass: { value: null }, resolution: { value: new Vector2() }, radius: { value: 4.0 } }, vertexShader: vertex, fragmentShader: fragment } ); const shadowMaterialHorizontal = shadowMaterialVertical.clone(); shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; const fullScreenTri = new BufferGeometry(); fullScreenTri.setAttribute( 'position', new BufferAttribute( new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), 3 ) ); const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); const scope = this; this.enabled = false; this.autoUpdate = true; this.needsUpdate = false; this.type = PCFShadowMap; let _previousType = this.type; this.render = function ( lights, scene, camera ) { if ( scope.enabled === false ) return; if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; if ( lights.length === 0 ) return; const currentRenderTarget = _renderer.getRenderTarget(); const activeCubeFace = _renderer.getActiveCubeFace(); const activeMipmapLevel = _renderer.getActiveMipmapLevel(); const _state = _renderer.state; // Set GL state for depth map. _state.setBlending( NoBlending ); _state.buffers.color.setClear( 1, 1, 1, 1 ); _state.buffers.depth.setTest( true ); _state.setScissorTest( false ); // check for shadow map type changes const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); // render depth map for ( let i = 0, il = lights.length; i < il; i ++ ) { const light = lights[ i ]; const shadow = light.shadow; if ( shadow === undefined ) { console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); continue; } if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; _shadowMapSize.copy( shadow.mapSize ); const shadowFrameExtents = shadow.getFrameExtents(); _shadowMapSize.multiply( shadowFrameExtents ); _viewportSize.copy( shadow.mapSize ); if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { if ( _shadowMapSize.x > _maxTextureSize ) { _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; shadow.mapSize.x = _viewportSize.x; } if ( _shadowMapSize.y > _maxTextureSize ) { _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; shadow.mapSize.y = _viewportSize.y; } } if ( shadow.map === null || toVSM === true || fromVSM === true ) { const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; if ( shadow.map !== null ) { shadow.map.dispose(); } shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); shadow.map.texture.name = light.name + '.shadowMap'; shadow.camera.updateProjectionMatrix(); } _renderer.setRenderTarget( shadow.map ); _renderer.clear(); const viewportCount = shadow.getViewportCount(); for ( let vp = 0; vp < viewportCount; vp ++ ) { const viewport = shadow.getViewport( vp ); _viewport.set( _viewportSize.x * viewport.x, _viewportSize.y * viewport.y, _viewportSize.x * viewport.z, _viewportSize.y * viewport.w ); _state.viewport( _viewport ); shadow.updateMatrices( light, vp ); _frustum = shadow.getFrustum(); renderObject( scene, camera, shadow.camera, light, this.type ); } // do blur pass for VSM if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { VSMPass( shadow, camera ); } shadow.needsUpdate = false; } _previousType = this.type; scope.needsUpdate = false; _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); }; function VSMPass( shadow, camera ) { const geometry = _objects.update( fullScreenMesh ); if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; shadowMaterialVertical.needsUpdate = true; shadowMaterialHorizontal.needsUpdate = true; } if ( shadow.mapPass === null ) { shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); } // vertical pass shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; shadowMaterialVertical.uniforms.radius.value = shadow.radius; _renderer.setRenderTarget( shadow.mapPass ); _renderer.clear(); _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); // horizontal pass shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; _renderer.setRenderTarget( shadow.map ); _renderer.clear(); _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); } function getDepthMaterial( object, material, light, type ) { let result = null; const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; if ( customMaterial !== undefined ) { result = customMaterial; } else { result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || ( material.displacementMap && material.displacementScale !== 0 ) || ( material.alphaMap && material.alphaTest > 0 ) || ( material.map && material.alphaTest > 0 ) ) { // in this case we need a unique material instance reflecting the // appropriate state const keyA = result.uuid, keyB = material.uuid; let materialsForVariant = _materialCache[ keyA ]; if ( materialsForVariant === undefined ) { materialsForVariant = {}; _materialCache[ keyA ] = materialsForVariant; } let cachedMaterial = materialsForVariant[ keyB ]; if ( cachedMaterial === undefined ) { cachedMaterial = result.clone(); materialsForVariant[ keyB ] = cachedMaterial; material.addEventListener( 'dispose', onMaterialDispose ); } result = cachedMaterial; } } result.visible = material.visible; result.wireframe = material.wireframe; if ( type === VSMShadowMap ) { result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; } else { result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; } result.alphaMap = material.alphaMap; result.alphaTest = material.alphaTest; result.map = material.map; result.clipShadows = material.clipShadows; result.clippingPlanes = material.clippingPlanes; result.clipIntersection = material.clipIntersection; result.displacementMap = material.displacementMap; result.displacementScale = material.displacementScale; result.displacementBias = material.displacementBias; result.wireframeLinewidth = material.wireframeLinewidth; result.linewidth = material.linewidth; if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { const materialProperties = _renderer.properties.get( result ); materialProperties.light = light; } return result; } function renderObject( object, camera, shadowCamera, light, type ) { if ( object.visible === false ) return; const visible = object.layers.test( camera.layers ); if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); const geometry = _objects.update( object ); const material = object.material; if ( Array.isArray( material ) ) { const groups = geometry.groups; for ( let k = 0, kl = groups.length; k < kl; k ++ ) { const group = groups[ k ]; const groupMaterial = material[ group.materialIndex ]; if ( groupMaterial && groupMaterial.visible ) { const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); } } } else if ( material.visible ) { const depthMaterial = getDepthMaterial( object, material, light, type ); object.onBeforeShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); object.onAfterShadow( _renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); } } } const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { renderObject( children[ i ], camera, shadowCamera, light, type ); } } function onMaterialDispose( event ) { const material = event.target; material.removeEventListener( 'dispose', onMaterialDispose ); // make sure to remove the unique distance/depth materials used for shadow map rendering for ( const id in _materialCache ) { const cache = _materialCache[ id ]; const uuid = event.target.uuid; if ( uuid in cache ) { const shadowMaterial = cache[ uuid ]; shadowMaterial.dispose(); delete cache[ uuid ]; } } } } function WebGLState( gl, extensions, capabilities ) { const isWebGL2 = capabilities.isWebGL2; function ColorBuffer() { let locked = false; const color = new Vector4(); let currentColorMask = null; const currentColorClear = new Vector4( 0, 0, 0, 0 ); return { setMask: function ( colorMask ) { if ( currentColorMask !== colorMask && ! locked ) { gl.colorMask( colorMask, colorMask, colorMask, colorMask ); currentColorMask = colorMask; } }, setLocked: function ( lock ) { locked = lock; }, setClear: function ( r, g, b, a, premultipliedAlpha ) { if ( premultipliedAlpha === true ) { r *= a; g *= a; b *= a; } color.set( r, g, b, a ); if ( currentColorClear.equals( color ) === false ) { gl.clearColor( r, g, b, a ); currentColorClear.copy( color ); } }, reset: function () { locked = false; currentColorMask = null; currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state } }; } function DepthBuffer() { let locked = false; let currentDepthMask = null; let currentDepthFunc = null; let currentDepthClear = null; return { setTest: function ( depthTest ) { if ( depthTest ) { enable( gl.DEPTH_TEST ); } else { disable( gl.DEPTH_TEST ); } }, setMask: function ( depthMask ) { if ( currentDepthMask !== depthMask && ! locked ) { gl.depthMask( depthMask ); currentDepthMask = depthMask; } }, setFunc: function ( depthFunc ) { if ( currentDepthFunc !== depthFunc ) { switch ( depthFunc ) { case NeverDepth: gl.depthFunc( gl.NEVER ); break; case AlwaysDepth: gl.depthFunc( gl.ALWAYS ); break; case LessDepth: gl.depthFunc( gl.LESS ); break; case LessEqualDepth: gl.depthFunc( gl.LEQUAL ); break; case EqualDepth: gl.depthFunc( gl.EQUAL ); break; case GreaterEqualDepth: gl.depthFunc( gl.GEQUAL ); break; case GreaterDepth: gl.depthFunc( gl.GREATER ); break; case NotEqualDepth: gl.depthFunc( gl.NOTEQUAL ); break; default: gl.depthFunc( gl.LEQUAL ); } currentDepthFunc = depthFunc; } }, setLocked: function ( lock ) { locked = lock; }, setClear: function ( depth ) { if ( currentDepthClear !== depth ) { gl.clearDepth( depth ); currentDepthClear = depth; } }, reset: function () { locked = false; currentDepthMask = null; currentDepthFunc = null; currentDepthClear = null; } }; } function StencilBuffer() { let locked = false; let currentStencilMask = null; let currentStencilFunc = null; let currentStencilRef = null; let currentStencilFuncMask = null; let currentStencilFail = null; let currentStencilZFail = null; let currentStencilZPass = null; let currentStencilClear = null; return { setTest: function ( stencilTest ) { if ( ! locked ) { if ( stencilTest ) { enable( gl.STENCIL_TEST ); } else { disable( gl.STENCIL_TEST ); } } }, setMask: function ( stencilMask ) { if ( currentStencilMask !== stencilMask && ! locked ) { gl.stencilMask( stencilMask ); currentStencilMask = stencilMask; } }, setFunc: function ( stencilFunc, stencilRef, stencilMask ) { if ( currentStencilFunc !== stencilFunc || currentStencilRef !== stencilRef || currentStencilFuncMask !== stencilMask ) { gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); currentStencilFunc = stencilFunc; currentStencilRef = stencilRef; currentStencilFuncMask = stencilMask; } }, setOp: function ( stencilFail, stencilZFail, stencilZPass ) { if ( currentStencilFail !== stencilFail || currentStencilZFail !== stencilZFail || currentStencilZPass !== stencilZPass ) { gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); currentStencilFail = stencilFail; currentStencilZFail = stencilZFail; currentStencilZPass = stencilZPass; } }, setLocked: function ( lock ) { locked = lock; }, setClear: function ( stencil ) { if ( currentStencilClear !== stencil ) { gl.clearStencil( stencil ); currentStencilClear = stencil; } }, reset: function () { locked = false; currentStencilMask = null; currentStencilFunc = null; currentStencilRef = null; currentStencilFuncMask = null; currentStencilFail = null; currentStencilZFail = null; currentStencilZPass = null; currentStencilClear = null; } }; } // const colorBuffer = new ColorBuffer(); const depthBuffer = new DepthBuffer(); const stencilBuffer = new StencilBuffer(); const uboBindings = new WeakMap(); const uboProgramMap = new WeakMap(); let enabledCapabilities = {}; let currentBoundFramebuffers = {}; let currentDrawbuffers = new WeakMap(); let defaultDrawbuffers = []; let currentProgram = null; let currentBlendingEnabled = false; let currentBlending = null; let currentBlendEquation = null; let currentBlendSrc = null; let currentBlendDst = null; let currentBlendEquationAlpha = null; let currentBlendSrcAlpha = null; let currentBlendDstAlpha = null; let currentBlendColor = new Color( 0, 0, 0 ); let currentBlendAlpha = 0; let currentPremultipledAlpha = false; let currentFlipSided = null; let currentCullFace = null; let currentLineWidth = null; let currentPolygonOffsetFactor = null; let currentPolygonOffsetUnits = null; const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); let lineWidthAvailable = false; let version = 0; const glVersion = gl.getParameter( gl.VERSION ); if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); lineWidthAvailable = ( version >= 1.0 ); } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); lineWidthAvailable = ( version >= 2.0 ); } let currentTextureSlot = null; let currentBoundTextures = {}; const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); const viewportParam = gl.getParameter( gl.VIEWPORT ); const currentScissor = new Vector4().fromArray( scissorParam ); const currentViewport = new Vector4().fromArray( viewportParam ); function createTexture( type, target, count, dimensions ) { const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. const texture = gl.createTexture(); gl.bindTexture( type, texture ); gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); for ( let i = 0; i < count; i ++ ) { if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); } else { gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); } } return texture; } const emptyTextures = {}; emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); if ( isWebGL2 ) { emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); } // init colorBuffer.setClear( 0, 0, 0, 1 ); depthBuffer.setClear( 1 ); stencilBuffer.setClear( 0 ); enable( gl.DEPTH_TEST ); depthBuffer.setFunc( LessEqualDepth ); setFlipSided( false ); setCullFace( CullFaceBack ); enable( gl.CULL_FACE ); setBlending( NoBlending ); // function enable( id ) { if ( enabledCapabilities[ id ] !== true ) { gl.enable( id ); enabledCapabilities[ id ] = true; } } function disable( id ) { if ( enabledCapabilities[ id ] !== false ) { gl.disable( id ); enabledCapabilities[ id ] = false; } } function bindFramebuffer( target, framebuffer ) { if ( currentBoundFramebuffers[ target ] !== framebuffer ) { gl.bindFramebuffer( target, framebuffer ); currentBoundFramebuffers[ target ] = framebuffer; if ( isWebGL2 ) { // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER if ( target === gl.DRAW_FRAMEBUFFER ) { currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; } if ( target === gl.FRAMEBUFFER ) { currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; } } return true; } return false; } function drawBuffers( renderTarget, framebuffer ) { let drawBuffers = defaultDrawbuffers; let needsUpdate = false; if ( renderTarget ) { drawBuffers = currentDrawbuffers.get( framebuffer ); if ( drawBuffers === undefined ) { drawBuffers = []; currentDrawbuffers.set( framebuffer, drawBuffers ); } if ( renderTarget.isWebGLMultipleRenderTargets ) { const textures = renderTarget.texture; if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { for ( let i = 0, il = textures.length; i < il; i ++ ) { drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; } drawBuffers.length = textures.length; needsUpdate = true; } } else { if ( drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { drawBuffers[ 0 ] = gl.COLOR_ATTACHMENT0; needsUpdate = true; } } } else { if ( drawBuffers[ 0 ] !== gl.BACK ) { drawBuffers[ 0 ] = gl.BACK; needsUpdate = true; } } if ( needsUpdate ) { if ( capabilities.isWebGL2 ) { gl.drawBuffers( drawBuffers ); } else { extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); } } } function useProgram( program ) { if ( currentProgram !== program ) { gl.useProgram( program ); currentProgram = program; return true; } return false; } const equationToGL = { [ AddEquation ]: gl.FUNC_ADD, [ SubtractEquation ]: gl.FUNC_SUBTRACT, [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT }; if ( isWebGL2 ) { equationToGL[ MinEquation ] = gl.MIN; equationToGL[ MaxEquation ] = gl.MAX; } else { const extension = extensions.get( 'EXT_blend_minmax' ); if ( extension !== null ) { equationToGL[ MinEquation ] = extension.MIN_EXT; equationToGL[ MaxEquation ] = extension.MAX_EXT; } } const factorToGL = { [ ZeroFactor ]: gl.ZERO, [ OneFactor ]: gl.ONE, [ SrcColorFactor ]: gl.SRC_COLOR, [ SrcAlphaFactor ]: gl.SRC_ALPHA, [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, [ DstColorFactor ]: gl.DST_COLOR, [ DstAlphaFactor ]: gl.DST_ALPHA, [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, [ ConstantColorFactor ]: gl.CONSTANT_COLOR, [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA }; function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { if ( blending === NoBlending ) { if ( currentBlendingEnabled === true ) { disable( gl.BLEND ); currentBlendingEnabled = false; } return; } if ( currentBlendingEnabled === false ) { enable( gl.BLEND ); currentBlendingEnabled = true; } if ( blending !== CustomBlending ) { if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { gl.blendEquation( gl.FUNC_ADD ); currentBlendEquation = AddEquation; currentBlendEquationAlpha = AddEquation; } if ( premultipliedAlpha ) { switch ( blending ) { case NormalBlending: gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); break; case AdditiveBlending: gl.blendFunc( gl.ONE, gl.ONE ); break; case SubtractiveBlending: gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); break; case MultiplyBlending: gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); break; default: console.error( 'THREE.WebGLState: Invalid blending: ', blending ); break; } } else { switch ( blending ) { case NormalBlending: gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); break; case AdditiveBlending: gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); break; case SubtractiveBlending: gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); break; case MultiplyBlending: gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); break; default: console.error( 'THREE.WebGLState: Invalid blending: ', blending ); break; } } currentBlendSrc = null; currentBlendDst = null; currentBlendSrcAlpha = null; currentBlendDstAlpha = null; currentBlendColor.set( 0, 0, 0 ); currentBlendAlpha = 0; currentBlending = blending; currentPremultipledAlpha = premultipliedAlpha; } return; } // custom blending blendEquationAlpha = blendEquationAlpha || blendEquation; blendSrcAlpha = blendSrcAlpha || blendSrc; blendDstAlpha = blendDstAlpha || blendDst; if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); currentBlendEquation = blendEquation; currentBlendEquationAlpha = blendEquationAlpha; } if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); currentBlendSrc = blendSrc; currentBlendDst = blendDst; currentBlendSrcAlpha = blendSrcAlpha; currentBlendDstAlpha = blendDstAlpha; } if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); currentBlendColor.copy( blendColor ); currentBlendAlpha = blendAlpha; } currentBlending = blending; currentPremultipledAlpha = false; } function setMaterial( material, frontFaceCW ) { material.side === DoubleSide ? disable( gl.CULL_FACE ) : enable( gl.CULL_FACE ); let flipSided = ( material.side === BackSide ); if ( frontFaceCW ) flipSided = ! flipSided; setFlipSided( flipSided ); ( material.blending === NormalBlending && material.transparent === false ) ? setBlending( NoBlending ) : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); depthBuffer.setFunc( material.depthFunc ); depthBuffer.setTest( material.depthTest ); depthBuffer.setMask( material.depthWrite ); colorBuffer.setMask( material.colorWrite ); const stencilWrite = material.stencilWrite; stencilBuffer.setTest( stencilWrite ); if ( stencilWrite ) { stencilBuffer.setMask( material.stencilWriteMask ); stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); } setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); material.alphaToCoverage === true ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); } // function setFlipSided( flipSided ) { if ( currentFlipSided !== flipSided ) { if ( flipSided ) { gl.frontFace( gl.CW ); } else { gl.frontFace( gl.CCW ); } currentFlipSided = flipSided; } } function setCullFace( cullFace ) { if ( cullFace !== CullFaceNone ) { enable( gl.CULL_FACE ); if ( cullFace !== currentCullFace ) { if ( cullFace === CullFaceBack ) { gl.cullFace( gl.BACK ); } else if ( cullFace === CullFaceFront ) { gl.cullFace( gl.FRONT ); } else { gl.cullFace( gl.FRONT_AND_BACK ); } } } else { disable( gl.CULL_FACE ); } currentCullFace = cullFace; } function setLineWidth( width ) { if ( width !== currentLineWidth ) { if ( lineWidthAvailable ) gl.lineWidth( width ); currentLineWidth = width; } } function setPolygonOffset( polygonOffset, factor, units ) { if ( polygonOffset ) { enable( gl.POLYGON_OFFSET_FILL ); if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { gl.polygonOffset( factor, units ); currentPolygonOffsetFactor = factor; currentPolygonOffsetUnits = units; } } else { disable( gl.POLYGON_OFFSET_FILL ); } } function setScissorTest( scissorTest ) { if ( scissorTest ) { enable( gl.SCISSOR_TEST ); } else { disable( gl.SCISSOR_TEST ); } } // texture function activeTexture( webglSlot ) { if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; if ( currentTextureSlot !== webglSlot ) { gl.activeTexture( webglSlot ); currentTextureSlot = webglSlot; } } function bindTexture( webglType, webglTexture, webglSlot ) { if ( webglSlot === undefined ) { if ( currentTextureSlot === null ) { webglSlot = gl.TEXTURE0 + maxTextures - 1; } else { webglSlot = currentTextureSlot; } } let boundTexture = currentBoundTextures[ webglSlot ]; if ( boundTexture === undefined ) { boundTexture = { type: undefined, texture: undefined }; currentBoundTextures[ webglSlot ] = boundTexture; } if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { if ( currentTextureSlot !== webglSlot ) { gl.activeTexture( webglSlot ); currentTextureSlot = webglSlot; } gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); boundTexture.type = webglType; boundTexture.texture = webglTexture; } } function unbindTexture() { const boundTexture = currentBoundTextures[ currentTextureSlot ]; if ( boundTexture !== undefined && boundTexture.type !== undefined ) { gl.bindTexture( boundTexture.type, null ); boundTexture.type = undefined; boundTexture.texture = undefined; } } function compressedTexImage2D() { try { gl.compressedTexImage2D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function compressedTexImage3D() { try { gl.compressedTexImage3D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texSubImage2D() { try { gl.texSubImage2D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texSubImage3D() { try { gl.texSubImage3D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function compressedTexSubImage2D() { try { gl.compressedTexSubImage2D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function compressedTexSubImage3D() { try { gl.compressedTexSubImage3D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texStorage2D() { try { gl.texStorage2D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texStorage3D() { try { gl.texStorage3D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texImage2D() { try { gl.texImage2D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } function texImage3D() { try { gl.texImage3D.apply( gl, arguments ); } catch ( error ) { console.error( 'THREE.WebGLState:', error ); } } // function scissor( scissor ) { if ( currentScissor.equals( scissor ) === false ) { gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); currentScissor.copy( scissor ); } } function viewport( viewport ) { if ( currentViewport.equals( viewport ) === false ) { gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); currentViewport.copy( viewport ); } } function updateUBOMapping( uniformsGroup, program ) { let mapping = uboProgramMap.get( program ); if ( mapping === undefined ) { mapping = new WeakMap(); uboProgramMap.set( program, mapping ); } let blockIndex = mapping.get( uniformsGroup ); if ( blockIndex === undefined ) { blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); mapping.set( uniformsGroup, blockIndex ); } } function uniformBlockBinding( uniformsGroup, program ) { const mapping = uboProgramMap.get( program ); const blockIndex = mapping.get( uniformsGroup ); if ( uboBindings.get( program ) !== blockIndex ) { // bind shader specific block index to global block point gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); uboBindings.set( program, blockIndex ); } } // function reset() { // reset state gl.disable( gl.BLEND ); gl.disable( gl.CULL_FACE ); gl.disable( gl.DEPTH_TEST ); gl.disable( gl.POLYGON_OFFSET_FILL ); gl.disable( gl.SCISSOR_TEST ); gl.disable( gl.STENCIL_TEST ); gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); gl.blendEquation( gl.FUNC_ADD ); gl.blendFunc( gl.ONE, gl.ZERO ); gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); gl.blendColor( 0, 0, 0, 0 ); gl.colorMask( true, true, true, true ); gl.clearColor( 0, 0, 0, 0 ); gl.depthMask( true ); gl.depthFunc( gl.LESS ); gl.clearDepth( 1 ); gl.stencilMask( 0xffffffff ); gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); gl.clearStencil( 0 ); gl.cullFace( gl.BACK ); gl.frontFace( gl.CCW ); gl.polygonOffset( 0, 0 ); gl.activeTexture( gl.TEXTURE0 ); gl.bindFramebuffer( gl.FRAMEBUFFER, null ); if ( isWebGL2 === true ) { gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); } gl.useProgram( null ); gl.lineWidth( 1 ); gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); // reset internals enabledCapabilities = {}; currentTextureSlot = null; currentBoundTextures = {}; currentBoundFramebuffers = {}; currentDrawbuffers = new WeakMap(); defaultDrawbuffers = []; currentProgram = null; currentBlendingEnabled = false; currentBlending = null; currentBlendEquation = null; currentBlendSrc = null; currentBlendDst = null; currentBlendEquationAlpha = null; currentBlendSrcAlpha = null; currentBlendDstAlpha = null; currentBlendColor = new Color( 0, 0, 0 ); currentBlendAlpha = 0; currentPremultipledAlpha = false; currentFlipSided = null; currentCullFace = null; currentLineWidth = null; currentPolygonOffsetFactor = null; currentPolygonOffsetUnits = null; currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); colorBuffer.reset(); depthBuffer.reset(); stencilBuffer.reset(); } return { buffers: { color: colorBuffer, depth: depthBuffer, stencil: stencilBuffer }, enable: enable, disable: disable, bindFramebuffer: bindFramebuffer, drawBuffers: drawBuffers, useProgram: useProgram, setBlending: setBlending, setMaterial: setMaterial, setFlipSided: setFlipSided, setCullFace: setCullFace, setLineWidth: setLineWidth, setPolygonOffset: setPolygonOffset, setScissorTest: setScissorTest, activeTexture: activeTexture, bindTexture: bindTexture, unbindTexture: unbindTexture, compressedTexImage2D: compressedTexImage2D, compressedTexImage3D: compressedTexImage3D, texImage2D: texImage2D, texImage3D: texImage3D, updateUBOMapping: updateUBOMapping, uniformBlockBinding: uniformBlockBinding, texStorage2D: texStorage2D, texStorage3D: texStorage3D, texSubImage2D: texSubImage2D, texSubImage3D: texSubImage3D, compressedTexSubImage2D: compressedTexSubImage2D, compressedTexSubImage3D: compressedTexSubImage3D, scissor: scissor, viewport: viewport, reset: reset }; } function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { const isWebGL2 = capabilities.isWebGL2; const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); const _videoTextures = new WeakMap(); let _canvas; const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). let useOffscreenCanvas = false; try { useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' // eslint-disable-next-line compat/compat && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; } catch ( err ) { // Ignore any errors } function createCanvas( width, height ) { // Use OffscreenCanvas when available. Specially needed in web workers return useOffscreenCanvas ? // eslint-disable-next-line compat/compat new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); } function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { let scale = 1; // handle case if texture exceeds max size if ( image.width > maxSize || image.height > maxSize ) { scale = maxSize / Math.max( image.width, image.height ); } // only perform resize if necessary if ( scale < 1 || needsPowerOfTwo === true ) { // only perform resize for certain image types if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; const width = floor( scale * image.width ); const height = floor( scale * image.height ); if ( _canvas === undefined ) _canvas = createCanvas( width, height ); // cube textures can't reuse the same canvas const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; canvas.width = width; canvas.height = height; const context = canvas.getContext( '2d' ); context.drawImage( image, 0, 0, width, height ); console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' ); return canvas; } else { if ( 'data' in image ) { console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' ); } return image; } } return image; } function isPowerOfTwo$1( image ) { return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ); } function textureNeedsPowerOfTwo( texture ) { if ( isWebGL2 ) return false; return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); } function textureNeedsGenerateMipmaps( texture, supportsMips ) { return texture.generateMipmaps && supportsMips && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; } function generateMipmap( target ) { _gl.generateMipmap( target ); } function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { if ( isWebGL2 === false ) return glFormat; if ( internalFormatName !== null ) { if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); } let internalFormat = glFormat; if ( glFormat === _gl.RED ) { if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; } if ( glFormat === _gl.RED_INTEGER ) { if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; if ( glType === _gl.INT ) internalFormat = _gl.R32I; } if ( glFormat === _gl.RG ) { if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; } if ( glFormat === _gl.RGBA ) { const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; } if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { extensions.get( 'EXT_color_buffer_float' ); } return internalFormat; } function getMipLevels( texture, image, supportsMips ) { if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { return Math.log2( Math.max( image.width, image.height ) ) + 1; } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { // user-defined mipmaps return texture.mipmaps.length; } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { return image.mipmaps.length; } else { // texture without mipmaps (only base level) return 1; } } // Fallback filters for non-power-of-2 textures function filterFallback( f ) { if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { return _gl.NEAREST; } return _gl.LINEAR; } // function onTextureDispose( event ) { const texture = event.target; texture.removeEventListener( 'dispose', onTextureDispose ); deallocateTexture( texture ); if ( texture.isVideoTexture ) { _videoTextures.delete( texture ); } } function onRenderTargetDispose( event ) { const renderTarget = event.target; renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); deallocateRenderTarget( renderTarget ); } // function deallocateTexture( texture ) { const textureProperties = properties.get( texture ); if ( textureProperties.__webglInit === undefined ) return; // check if it's necessary to remove the WebGLTexture object const source = texture.source; const webglTextures = _sources.get( source ); if ( webglTextures ) { const webglTexture = webglTextures[ textureProperties.__cacheKey ]; webglTexture.usedTimes --; // the WebGLTexture object is not used anymore, remove it if ( webglTexture.usedTimes === 0 ) { deleteTexture( texture ); } // remove the weak map entry if no WebGLTexture uses the source anymore if ( Object.keys( webglTextures ).length === 0 ) { _sources.delete( source ); } } properties.remove( texture ); } function deleteTexture( texture ) { const textureProperties = properties.get( texture ); _gl.deleteTexture( textureProperties.__webglTexture ); const source = texture.source; const webglTextures = _sources.get( source ); delete webglTextures[ textureProperties.__cacheKey ]; info.memory.textures --; } function deallocateRenderTarget( renderTarget ) { const texture = renderTarget.texture; const renderTargetProperties = properties.get( renderTarget ); const textureProperties = properties.get( texture ); if ( textureProperties.__webglTexture !== undefined ) { _gl.deleteTexture( textureProperties.__webglTexture ); info.memory.textures --; } if ( renderTarget.depthTexture ) { renderTarget.depthTexture.dispose(); } if ( renderTarget.isWebGLCubeRenderTarget ) { for ( let i = 0; i < 6; i ++ ) { if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); } else { _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); } if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); } } else { if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); } else { _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); } if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); if ( renderTargetProperties.__webglColorRenderbuffer ) { for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); } } if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); } if ( renderTarget.isWebGLMultipleRenderTargets ) { for ( let i = 0, il = texture.length; i < il; i ++ ) { const attachmentProperties = properties.get( texture[ i ] ); if ( attachmentProperties.__webglTexture ) { _gl.deleteTexture( attachmentProperties.__webglTexture ); info.memory.textures --; } properties.remove( texture[ i ] ); } } properties.remove( texture ); properties.remove( renderTarget ); } // let textureUnits = 0; function resetTextureUnits() { textureUnits = 0; } function allocateTextureUnit() { const textureUnit = textureUnits; if ( textureUnit >= capabilities.maxTextures ) { console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); } textureUnits += 1; return textureUnit; } function getTextureCacheKey( texture ) { const array = []; array.push( texture.wrapS ); array.push( texture.wrapT ); array.push( texture.wrapR || 0 ); array.push( texture.magFilter ); array.push( texture.minFilter ); array.push( texture.anisotropy ); array.push( texture.internalFormat ); array.push( texture.format ); array.push( texture.type ); array.push( texture.generateMipmaps ); array.push( texture.premultiplyAlpha ); array.push( texture.flipY ); array.push( texture.unpackAlignment ); array.push( texture.colorSpace ); return array.join(); } // function setTexture2D( texture, slot ) { const textureProperties = properties.get( texture ); if ( texture.isVideoTexture ) updateVideoTexture( texture ); if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { const image = texture.image; if ( image === null ) { console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); } else if ( image.complete === false ) { console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); } else { uploadTexture( textureProperties, texture, slot ); return; } } state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); } function setTexture2DArray( texture, slot ) { const textureProperties = properties.get( texture ); if ( texture.version > 0 && textureProperties.__version !== texture.version ) { uploadTexture( textureProperties, texture, slot ); return; } state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); } function setTexture3D( texture, slot ) { const textureProperties = properties.get( texture ); if ( texture.version > 0 && textureProperties.__version !== texture.version ) { uploadTexture( textureProperties, texture, slot ); return; } state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); } function setTextureCube( texture, slot ) { const textureProperties = properties.get( texture ); if ( texture.version > 0 && textureProperties.__version !== texture.version ) { uploadCubeTexture( textureProperties, texture, slot ); return; } state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); } const wrappingToGL = { [ RepeatWrapping ]: _gl.REPEAT, [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT }; const filterToGL = { [ NearestFilter ]: _gl.NEAREST, [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, [ LinearFilter ]: _gl.LINEAR, [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR }; const compareToGL = { [ NeverCompare ]: _gl.NEVER, [ AlwaysCompare ]: _gl.ALWAYS, [ LessCompare ]: _gl.LESS, [ LessEqualCompare ]: _gl.LEQUAL, [ EqualCompare ]: _gl.EQUAL, [ GreaterEqualCompare ]: _gl.GEQUAL, [ GreaterCompare ]: _gl.GREATER, [ NotEqualCompare ]: _gl.NOTEQUAL }; function setTextureParameters( textureType, texture, supportsMips ) { if ( supportsMips ) { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); } _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); } else { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); } if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); } _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); } } if ( texture.compareFunction ) { _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); } if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); if ( texture.magFilter === NearestFilter ) return; if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); properties.get( texture ).__currentAnisotropy = texture.anisotropy; } } } function initTexture( textureProperties, texture ) { let forceUpload = false; if ( textureProperties.__webglInit === undefined ) { textureProperties.__webglInit = true; texture.addEventListener( 'dispose', onTextureDispose ); } // create Source <-> WebGLTextures mapping if necessary const source = texture.source; let webglTextures = _sources.get( source ); if ( webglTextures === undefined ) { webglTextures = {}; _sources.set( source, webglTextures ); } // check if there is already a WebGLTexture object for the given texture parameters const textureCacheKey = getTextureCacheKey( texture ); if ( textureCacheKey !== textureProperties.__cacheKey ) { // if not, create a new instance of WebGLTexture if ( webglTextures[ textureCacheKey ] === undefined ) { // create new entry webglTextures[ textureCacheKey ] = { texture: _gl.createTexture(), usedTimes: 0 }; info.memory.textures ++; // when a new instance of WebGLTexture was created, a texture upload is required // even if the image contents are identical forceUpload = true; } webglTextures[ textureCacheKey ].usedTimes ++; // every time the texture cache key changes, it's necessary to check if an instance of // WebGLTexture can be deleted in order to avoid a memory leak. const webglTexture = webglTextures[ textureProperties.__cacheKey ]; if ( webglTexture !== undefined ) { webglTextures[ textureProperties.__cacheKey ].usedTimes --; if ( webglTexture.usedTimes === 0 ) { deleteTexture( texture ); } } // store references to cache key and WebGLTexture object textureProperties.__cacheKey = textureCacheKey; textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; } return forceUpload; } function uploadTexture( textureProperties, texture, slot ) { let textureType = _gl.TEXTURE_2D; if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; const forceUpload = initTexture( textureProperties, texture ); const source = texture.source; state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); const sourceProperties = properties.get( source ); if ( source.version !== sourceProperties.__version || forceUpload === true ) { state.activeTexture( _gl.TEXTURE0 + slot ); const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; let image = resizeImage( texture.image, needsPowerOfTwo, false, capabilities.maxTextureSize ); image = verifyColorSpace( texture, image ); const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, glFormat = utils.convert( texture.format, texture.colorSpace ); let glType = utils.convert( texture.type ), glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); setTextureParameters( textureType, texture, supportsMips ); let mipmap; const mipmaps = texture.mipmaps; const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true && glInternalFormat !== RGB_ETC1_Format ); const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); const levels = getMipLevels( texture, image, supportsMips ); if ( texture.isDepthTexture ) { // populate depth texture with dummy data glInternalFormat = _gl.DEPTH_COMPONENT; if ( isWebGL2 ) { if ( texture.type === FloatType ) { glInternalFormat = _gl.DEPTH_COMPONENT32F; } else if ( texture.type === UnsignedIntType ) { glInternalFormat = _gl.DEPTH_COMPONENT24; } else if ( texture.type === UnsignedInt248Type ) { glInternalFormat = _gl.DEPTH24_STENCIL8; } else { glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D } } else { if ( texture.type === FloatType ) { console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); } } // validation checks for WebGL 1 if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); texture.type = UnsignedIntType; glType = utils.convert( texture.type ); } } if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { // Depth stencil textures need the DEPTH_STENCIL internal format // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) glInternalFormat = _gl.DEPTH_STENCIL; // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) if ( texture.type !== UnsignedInt248Type ) { console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); texture.type = UnsignedInt248Type; glType = utils.convert( texture.type ); } } // if ( allocateMemory ) { if ( useTexStorage ) { state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); } else { state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); } } } else if ( texture.isDataTexture ) { // use manually created mipmaps if available // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels if ( mipmaps.length > 0 && supportsMips ) { if ( useTexStorage && allocateMemory ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); } for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); } else { state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } } texture.generateMipmaps = false; } else { if ( useTexStorage ) { if ( allocateMemory ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); } state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); } else { state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); } } } else if ( texture.isCompressedTexture ) { if ( texture.isCompressedArrayTexture ) { if ( useTexStorage && allocateMemory ) { state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); } for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; if ( texture.format !== RGBAFormat ) { if ( glFormat !== null ) { if ( useTexStorage ) { state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); } else { state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); } } else { console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); } } else { if ( useTexStorage ) { state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); } else { state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); } } } } else { if ( useTexStorage && allocateMemory ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); } for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; if ( texture.format !== RGBAFormat ) { if ( glFormat !== null ) { if ( useTexStorage ) { state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); } else { state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); } } else { console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); } } else { if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); } else { state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } } } } } else if ( texture.isDataArrayTexture ) { if ( useTexStorage ) { if ( allocateMemory ) { state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); } state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); } else { state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); } } else if ( texture.isData3DTexture ) { if ( useTexStorage ) { if ( allocateMemory ) { state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); } state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); } else { state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); } } else if ( texture.isFramebufferTexture ) { if ( allocateMemory ) { if ( useTexStorage ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); } else { let width = image.width, height = image.height; for ( let i = 0; i < levels; i ++ ) { state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); width >>= 1; height >>= 1; } } } } else { // regular Texture (image, video, canvas) // use manually created mipmaps if available // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels if ( mipmaps.length > 0 && supportsMips ) { if ( useTexStorage && allocateMemory ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); } for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); } else { state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); } } texture.generateMipmaps = false; } else { if ( useTexStorage ) { if ( allocateMemory ) { state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); } state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); } else { state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); } } } if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { generateMipmap( textureType ); } sourceProperties.__version = source.version; if ( texture.onUpdate ) texture.onUpdate( texture ); } textureProperties.__version = texture.version; } function uploadCubeTexture( textureProperties, texture, slot ) { if ( texture.image.length !== 6 ) return; const forceUpload = initTexture( textureProperties, texture ); const source = texture.source; state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); const sourceProperties = properties.get( source ); if ( source.version !== sourceProperties.__version || forceUpload === true ) { state.activeTexture( _gl.TEXTURE0 + slot ); const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); const cubeImage = []; for ( let i = 0; i < 6; i ++ ) { if ( ! isCompressed && ! isDataTexture ) { cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, capabilities.maxCubemapSize ); } else { cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; } cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); } const image = cubeImage[ 0 ], supportsMips = isPowerOfTwo$1( image ) || isWebGL2, glFormat = utils.convert( texture.format, texture.colorSpace ), glType = utils.convert( texture.type ), glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); let levels = getMipLevels( texture, image, supportsMips ); setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); let mipmaps; if ( isCompressed ) { if ( useTexStorage && allocateMemory ) { state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); } for ( let i = 0; i < 6; i ++ ) { mipmaps = cubeImage[ i ].mipmaps; for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; if ( texture.format !== RGBAFormat ) { if ( glFormat !== null ) { if ( useTexStorage ) { state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); } else { state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); } } else { console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); } } else { if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } } } } } else { mipmaps = texture.mipmaps; if ( useTexStorage && allocateMemory ) { // TODO: Uniformly handle mipmap definitions // Normal textures and compressed cube textures define base level + mips with their mipmap array // Uncompressed cube textures use their mipmap array only for mips (no base level) if ( mipmaps.length > 0 ) levels ++; state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, cubeImage[ 0 ].width, cubeImage[ 0 ].height ); } for ( let i = 0; i < 6; i ++ ) { if ( isDataTexture ) { if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); } for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; const mipmapImage = mipmap.image[ i ].image; if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); } } } else { if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); } for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; if ( useTexStorage ) { state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); } } } } } if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { // We assume images for cube map have the same size. generateMipmap( _gl.TEXTURE_CUBE_MAP ); } sourceProperties.__version = source.version; if ( texture.onUpdate ) texture.onUpdate( texture ); } textureProperties.__version = texture.version; } // Render targets // Setup storage for target texture and bind it to correct framebuffer function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); const renderTargetProperties = properties.get( renderTarget ); if ( ! renderTargetProperties.__hasExternalTextures ) { const width = Math.max( 1, renderTarget.width >> level ); const height = Math.max( 1, renderTarget.height >> level ); if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); } else { state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); } } state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, level ); } state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } // Setup storage for internal depth/stencil buffers and bind to correct framebuffer function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { let glInternalFormat = ( isWebGL2 === true ) ? _gl.DEPTH_COMPONENT24 : _gl.DEPTH_COMPONENT16; if ( isMultisample || useMultisampledRTT( renderTarget ) ) { const depthTexture = renderTarget.depthTexture; if ( depthTexture && depthTexture.isDepthTexture ) { if ( depthTexture.type === FloatType ) { glInternalFormat = _gl.DEPTH_COMPONENT32F; } else if ( depthTexture.type === UnsignedIntType ) { glInternalFormat = _gl.DEPTH_COMPONENT24; } } const samples = getRenderTargetSamples( renderTarget ); if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } } else { _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); } _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { const samples = getRenderTargetSamples( renderTarget ); if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); } else if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); } else { _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); } _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); } else { const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; for ( let i = 0; i < textures.length; i ++ ) { const texture = textures[ i ]; const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); const samples = getRenderTargetSamples( renderTarget ); if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); } else { _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); } } } _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); } // Setup resources for a Depth Texture for a FBO (needs an extension) function setupDepthTexture( framebuffer, renderTarget ) { const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); } // upload an empty depth texture with framebuffer size if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || renderTarget.depthTexture.image.width !== renderTarget.width || renderTarget.depthTexture.image.height !== renderTarget.height ) { renderTarget.depthTexture.image.width = renderTarget.width; renderTarget.depthTexture.image.height = renderTarget.height; renderTarget.depthTexture.needsUpdate = true; } setTexture2D( renderTarget.depthTexture, 0 ); const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; const samples = getRenderTargetSamples( renderTarget ); if ( renderTarget.depthTexture.format === DepthFormat ) { if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); } else { _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); } } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { if ( useMultisampledRTT( renderTarget ) ) { multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); } else { _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); } } else { throw new Error( 'Unknown depthTexture format' ); } } // Setup GL resources for a non-texture depth buffer function setupDepthRenderbuffer( renderTarget ) { const renderTargetProperties = properties.get( renderTarget ); const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); } else { if ( isCube ) { renderTargetProperties.__webglDepthbuffer = []; for ( let i = 0; i < 6; i ++ ) { state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); } } else { state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); } } state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } // rebind framebuffer with external textures function rebindTextures( renderTarget, colorTexture, depthTexture ) { const renderTargetProperties = properties.get( renderTarget ); if ( colorTexture !== undefined ) { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); } if ( depthTexture !== undefined ) { setupDepthRenderbuffer( renderTarget ); } } // Set up GL resources for the render target function setupRenderTarget( renderTarget ) { const texture = renderTarget.texture; const renderTargetProperties = properties.get( renderTarget ); const textureProperties = properties.get( texture ); renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); if ( renderTarget.isWebGLMultipleRenderTargets !== true ) { if ( textureProperties.__webglTexture === undefined ) { textureProperties.__webglTexture = _gl.createTexture(); } textureProperties.__version = texture.version; info.memory.textures ++; } const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; // Setup framebuffer if ( isCube ) { renderTargetProperties.__webglFramebuffer = []; for ( let i = 0; i < 6; i ++ ) { if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { renderTargetProperties.__webglFramebuffer[ i ] = []; for ( let level = 0; level < texture.mipmaps.length; level ++ ) { renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); } } else { renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); } } } else { if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { renderTargetProperties.__webglFramebuffer = []; for ( let level = 0; level < texture.mipmaps.length; level ++ ) { renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); } } else { renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); } if ( isMultipleRenderTargets ) { if ( capabilities.drawBuffers ) { const textures = renderTarget.texture; for ( let i = 0, il = textures.length; i < il; i ++ ) { const attachmentProperties = properties.get( textures[ i ] ); if ( attachmentProperties.__webglTexture === undefined ) { attachmentProperties.__webglTexture = _gl.createTexture(); info.memory.textures ++; } } } else { console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); } } if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { const textures = isMultipleRenderTargets ? texture : [ texture ]; renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); renderTargetProperties.__webglColorRenderbuffer = []; state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); for ( let i = 0; i < textures.length; i ++ ) { const texture = textures[ i ]; renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); const glFormat = utils.convert( texture.format, texture.colorSpace ); const glType = utils.convert( texture.type ); const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); const samples = getRenderTargetSamples( renderTarget ); _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); } _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); if ( renderTarget.depthBuffer ) { renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); } state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } } // Setup color buffer if ( isCube ) { state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); for ( let i = 0; i < 6; i ++ ) { if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { for ( let level = 0; level < texture.mipmaps.length; level ++ ) { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); } } else { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); } } if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { generateMipmap( _gl.TEXTURE_CUBE_MAP ); } state.unbindTexture(); } else if ( isMultipleRenderTargets ) { const textures = renderTarget.texture; for ( let i = 0, il = textures.length; i < il; i ++ ) { const attachment = textures[ i ]; const attachmentProperties = properties.get( attachment ); state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 ); if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { generateMipmap( _gl.TEXTURE_2D ); } } state.unbindTexture(); } else { let glTextureType = _gl.TEXTURE_2D; if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { if ( isWebGL2 ) { glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; } else { console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); } } state.bindTexture( glTextureType, textureProperties.__webglTexture ); setTextureParameters( glTextureType, texture, supportsMips ); if ( isWebGL2 && texture.mipmaps && texture.mipmaps.length > 0 ) { for ( let level = 0; level < texture.mipmaps.length; level ++ ) { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); } } else { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); } if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { generateMipmap( glTextureType ); } state.unbindTexture(); } // Setup depth and stencil buffers if ( renderTarget.depthBuffer ) { setupDepthRenderbuffer( renderTarget ); } } function updateRenderTargetMipmap( renderTarget ) { const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; for ( let i = 0, il = textures.length; i < il; i ++ ) { const texture = textures[ i ]; if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; const webglTexture = properties.get( texture ).__webglTexture; state.bindTexture( target, webglTexture ); generateMipmap( target ); state.unbindTexture(); } } } function updateMultisampleRenderTarget( renderTarget ) { if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { const textures = renderTarget.isWebGLMultipleRenderTargets ? renderTarget.texture : [ renderTarget.texture ]; const width = renderTarget.width; const height = renderTarget.height; let mask = _gl.COLOR_BUFFER_BIT; const invalidationArray = []; const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; const renderTargetProperties = properties.get( renderTarget ); const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); // If MRT we need to remove FBO attachments if ( isMultipleRenderTargets ) { for ( let i = 0; i < textures.length; i ++ ) { state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); } } state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); for ( let i = 0; i < textures.length; i ++ ) { invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); if ( renderTarget.depthBuffer ) { invalidationArray.push( depthStyle ); } const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; if ( ignoreDepthValues === false ) { if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; } if ( isMultipleRenderTargets ) { _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); } if ( ignoreDepthValues === true ) { _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); } if ( isMultipleRenderTargets ) { const webglTexture = properties.get( textures[ i ] ).__webglTexture; _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); } _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); if ( supportsInvalidateFramebuffer ) { _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); } } state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments if ( isMultipleRenderTargets ) { for ( let i = 0; i < textures.length; i ++ ) { state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); const webglTexture = properties.get( textures[ i ] ).__webglTexture; state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); } } state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); } } function getRenderTargetSamples( renderTarget ) { return Math.min( capabilities.maxSamples, renderTarget.samples ); } function useMultisampledRTT( renderTarget ) { const renderTargetProperties = properties.get( renderTarget ); return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; } function updateVideoTexture( texture ) { const frame = info.render.frame; // Check the last frame we updated the VideoTexture if ( _videoTextures.get( texture ) !== frame ) { _videoTextures.set( texture, frame ); texture.update(); } } function verifyColorSpace( texture, image ) { const colorSpace = texture.colorSpace; const format = texture.format; const type = texture.type; if ( texture.isCompressedTexture === true || texture.isVideoTexture === true || texture.format === _SRGBAFormat ) return image; if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { // sRGB if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { if ( isWebGL2 === false ) { // in WebGL 1, try to use EXT_sRGB extension and unsized formats if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { texture.format = _SRGBAFormat; // it's not possible to generate mips in WebGL 1 with this extension texture.minFilter = LinearFilter; texture.generateMipmaps = false; } else { // slow fallback (CPU decode) image = ImageUtils.sRGBToLinear( image ); } } else { // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format if ( format !== RGBAFormat || type !== UnsignedByteType ) { console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); } } } else { console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); } } return image; } // this.allocateTextureUnit = allocateTextureUnit; this.resetTextureUnits = resetTextureUnits; this.setTexture2D = setTexture2D; this.setTexture2DArray = setTexture2DArray; this.setTexture3D = setTexture3D; this.setTextureCube = setTextureCube; this.rebindTextures = rebindTextures; this.setupRenderTarget = setupRenderTarget; this.updateRenderTargetMipmap = updateRenderTargetMipmap; this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; this.setupDepthRenderbuffer = setupDepthRenderbuffer; this.setupFrameBufferTexture = setupFrameBufferTexture; this.useMultisampledRTT = useMultisampledRTT; } function WebGLUtils( gl, extensions, capabilities ) { const isWebGL2 = capabilities.isWebGL2; function convert( p, colorSpace = NoColorSpace ) { let extension; const transfer = ColorManagement.getTransfer( colorSpace ); if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; if ( p === ByteType ) return gl.BYTE; if ( p === ShortType ) return gl.SHORT; if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; if ( p === IntType ) return gl.INT; if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; if ( p === FloatType ) return gl.FLOAT; if ( p === HalfFloatType ) { if ( isWebGL2 ) return gl.HALF_FLOAT; extension = extensions.get( 'OES_texture_half_float' ); if ( extension !== null ) { return extension.HALF_FLOAT_OES; } else { return null; } } if ( p === AlphaFormat ) return gl.ALPHA; if ( p === RGBAFormat ) return gl.RGBA; if ( p === LuminanceFormat ) return gl.LUMINANCE; if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; // WebGL 1 sRGB fallback if ( p === _SRGBAFormat ) { extension = extensions.get( 'EXT_sRGB' ); if ( extension !== null ) { return extension.SRGB_ALPHA_EXT; } else { return null; } } // WebGL2 formats. if ( p === RedFormat ) return gl.RED; if ( p === RedIntegerFormat ) return gl.RED_INTEGER; if ( p === RGFormat ) return gl.RG; if ( p === RGIntegerFormat ) return gl.RG_INTEGER; if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; // S3TC if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { if ( transfer === SRGBTransfer ) { extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); if ( extension !== null ) { if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; } else { return null; } } else { extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); if ( extension !== null ) { if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; } else { return null; } } } // PVRTC if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); if ( extension !== null ) { if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; } else { return null; } } // ETC1 if ( p === RGB_ETC1_Format ) { extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); if ( extension !== null ) { return extension.COMPRESSED_RGB_ETC1_WEBGL; } else { return null; } } // ETC2 if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { extension = extensions.get( 'WEBGL_compressed_texture_etc' ); if ( extension !== null ) { if ( p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; } else { return null; } } // ASTC if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { extension = extensions.get( 'WEBGL_compressed_texture_astc' ); if ( extension !== null ) { if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; } else { return null; } } // BPTC if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { extension = extensions.get( 'EXT_texture_compression_bptc' ); if ( extension !== null ) { if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; } else { return null; } } // RGTC if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { extension = extensions.get( 'EXT_texture_compression_rgtc' ); if ( extension !== null ) { if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; } else { return null; } } // if ( p === UnsignedInt248Type ) { if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; extension = extensions.get( 'WEBGL_depth_texture' ); if ( extension !== null ) { return extension.UNSIGNED_INT_24_8_WEBGL; } else { return null; } } // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) return ( gl[ p ] !== undefined ) ? gl[ p ] : null; } return { convert: convert }; } class ArrayCamera extends PerspectiveCamera { constructor( array = [] ) { super(); this.isArrayCamera = true; this.cameras = array; } } class Group extends Object3D { constructor() { super(); this.isGroup = true; this.type = 'Group'; } } const _moveEvent = { type: 'move' }; class WebXRController { constructor() { this._targetRay = null; this._grip = null; this._hand = null; } getHandSpace() { if ( this._hand === null ) { this._hand = new Group(); this._hand.matrixAutoUpdate = false; this._hand.visible = false; this._hand.joints = {}; this._hand.inputState = { pinching: false }; } return this._hand; } getTargetRaySpace() { if ( this._targetRay === null ) { this._targetRay = new Group(); this._targetRay.matrixAutoUpdate = false; this._targetRay.visible = false; this._targetRay.hasLinearVelocity = false; this._targetRay.linearVelocity = new Vector3(); this._targetRay.hasAngularVelocity = false; this._targetRay.angularVelocity = new Vector3(); } return this._targetRay; } getGripSpace() { if ( this._grip === null ) { this._grip = new Group(); this._grip.matrixAutoUpdate = false; this._grip.visible = false; this._grip.hasLinearVelocity = false; this._grip.linearVelocity = new Vector3(); this._grip.hasAngularVelocity = false; this._grip.angularVelocity = new Vector3(); } return this._grip; } dispatchEvent( event ) { if ( this._targetRay !== null ) { this._targetRay.dispatchEvent( event ); } if ( this._grip !== null ) { this._grip.dispatchEvent( event ); } if ( this._hand !== null ) { this._hand.dispatchEvent( event ); } return this; } connect( inputSource ) { if ( inputSource && inputSource.hand ) { const hand = this._hand; if ( hand ) { for ( const inputjoint of inputSource.hand.values() ) { // Initialize hand with joints when connected this._getHandJoint( hand, inputjoint ); } } } this.dispatchEvent( { type: 'connected', data: inputSource } ); return this; } disconnect( inputSource ) { this.dispatchEvent( { type: 'disconnected', data: inputSource } ); if ( this._targetRay !== null ) { this._targetRay.visible = false; } if ( this._grip !== null ) { this._grip.visible = false; } if ( this._hand !== null ) { this._hand.visible = false; } return this; } update( inputSource, frame, referenceSpace ) { let inputPose = null; let gripPose = null; let handPose = null; const targetRay = this._targetRay; const grip = this._grip; const hand = this._hand; if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { if ( hand && inputSource.hand ) { handPose = true; for ( const inputjoint of inputSource.hand.values() ) { // Update the joints groups with the XRJoint poses const jointPose = frame.getJointPose( inputjoint, referenceSpace ); // The transform of this joint will be updated with the joint pose on each frame const joint = this._getHandJoint( hand, inputjoint ); if ( jointPose !== null ) { joint.matrix.fromArray( jointPose.transform.matrix ); joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); joint.matrixWorldNeedsUpdate = true; joint.jointRadius = jointPose.radius; } joint.visible = jointPose !== null; } // Custom events // Check pinchz const indexTip = hand.joints[ 'index-finger-tip' ]; const thumbTip = hand.joints[ 'thumb-tip' ]; const distance = indexTip.position.distanceTo( thumbTip.position ); const distanceToPinch = 0.02; const threshold = 0.005; if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { hand.inputState.pinching = false; this.dispatchEvent( { type: 'pinchend', handedness: inputSource.handedness, target: this } ); } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { hand.inputState.pinching = true; this.dispatchEvent( { type: 'pinchstart', handedness: inputSource.handedness, target: this } ); } } else { if ( grip !== null && inputSource.gripSpace ) { gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); if ( gripPose !== null ) { grip.matrix.fromArray( gripPose.transform.matrix ); grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); grip.matrixWorldNeedsUpdate = true; if ( gripPose.linearVelocity ) { grip.hasLinearVelocity = true; grip.linearVelocity.copy( gripPose.linearVelocity ); } else { grip.hasLinearVelocity = false; } if ( gripPose.angularVelocity ) { grip.hasAngularVelocity = true; grip.angularVelocity.copy( gripPose.angularVelocity ); } else { grip.hasAngularVelocity = false; } } } } if ( targetRay !== null ) { inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it if ( inputPose === null && gripPose !== null ) { inputPose = gripPose; } if ( inputPose !== null ) { targetRay.matrix.fromArray( inputPose.transform.matrix ); targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); targetRay.matrixWorldNeedsUpdate = true; if ( inputPose.linearVelocity ) { targetRay.hasLinearVelocity = true; targetRay.linearVelocity.copy( inputPose.linearVelocity ); } else { targetRay.hasLinearVelocity = false; } if ( inputPose.angularVelocity ) { targetRay.hasAngularVelocity = true; targetRay.angularVelocity.copy( inputPose.angularVelocity ); } else { targetRay.hasAngularVelocity = false; } this.dispatchEvent( _moveEvent ); } } } if ( targetRay !== null ) { targetRay.visible = ( inputPose !== null ); } if ( grip !== null ) { grip.visible = ( gripPose !== null ); } if ( hand !== null ) { hand.visible = ( handPose !== null ); } return this; } // private method _getHandJoint( hand, inputjoint ) { if ( hand.joints[ inputjoint.jointName ] === undefined ) { const joint = new Group(); joint.matrixAutoUpdate = false; joint.visible = false; hand.joints[ inputjoint.jointName ] = joint; hand.add( joint ); } return hand.joints[ inputjoint.jointName ]; } } class WebXRManager extends EventDispatcher { constructor( renderer, gl ) { super(); const scope = this; let session = null; let framebufferScaleFactor = 1.0; let referenceSpace = null; let referenceSpaceType = 'local-floor'; // Set default foveation to maximum. let foveation = 1.0; let customReferenceSpace = null; let pose = null; let glBinding = null; let glProjLayer = null; let glBaseLayer = null; let xrFrame = null; const attributes = gl.getContextAttributes(); let initialRenderTarget = null; let newRenderTarget = null; const controllers = []; const controllerInputSources = []; const currentSize = new Vector2(); let currentPixelRatio = null; // const cameraL = new PerspectiveCamera(); cameraL.layers.enable( 1 ); cameraL.viewport = new Vector4(); const cameraR = new PerspectiveCamera(); cameraR.layers.enable( 2 ); cameraR.viewport = new Vector4(); const cameras = [ cameraL, cameraR ]; const cameraXR = new ArrayCamera(); cameraXR.layers.enable( 1 ); cameraXR.layers.enable( 2 ); let _currentDepthNear = null; let _currentDepthFar = null; // this.cameraAutoUpdate = true; this.enabled = false; this.isPresenting = false; this.getController = function ( index ) { let controller = controllers[ index ]; if ( controller === undefined ) { controller = new WebXRController(); controllers[ index ] = controller; } return controller.getTargetRaySpace(); }; this.getControllerGrip = function ( index ) { let controller = controllers[ index ]; if ( controller === undefined ) { controller = new WebXRController(); controllers[ index ] = controller; } return controller.getGripSpace(); }; this.getHand = function ( index ) { let controller = controllers[ index ]; if ( controller === undefined ) { controller = new WebXRController(); controllers[ index ] = controller; } return controller.getHandSpace(); }; // function onSessionEvent( event ) { const controllerIndex = controllerInputSources.indexOf( event.inputSource ); if ( controllerIndex === - 1 ) { return; } const controller = controllers[ controllerIndex ]; if ( controller !== undefined ) { controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); controller.dispatchEvent( { type: event.type, data: event.inputSource } ); } } function onSessionEnd() { session.removeEventListener( 'select', onSessionEvent ); session.removeEventListener( 'selectstart', onSessionEvent ); session.removeEventListener( 'selectend', onSessionEvent ); session.removeEventListener( 'squeeze', onSessionEvent ); session.removeEventListener( 'squeezestart', onSessionEvent ); session.removeEventListener( 'squeezeend', onSessionEvent ); session.removeEventListener( 'end', onSessionEnd ); session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); for ( let i = 0; i < controllers.length; i ++ ) { const inputSource = controllerInputSources[ i ]; if ( inputSource === null ) continue; controllerInputSources[ i ] = null; controllers[ i ].disconnect( inputSource ); } _currentDepthNear = null; _currentDepthFar = null; // restore framebuffer/rendering state renderer.setRenderTarget( initialRenderTarget ); glBaseLayer = null; glProjLayer = null; glBinding = null; session = null; newRenderTarget = null; // animation.stop(); scope.isPresenting = false; renderer.setPixelRatio( currentPixelRatio ); renderer.setSize( currentSize.width, currentSize.height, false ); scope.dispatchEvent( { type: 'sessionend' } ); } this.setFramebufferScaleFactor = function ( value ) { framebufferScaleFactor = value; if ( scope.isPresenting === true ) { console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); } }; this.setReferenceSpaceType = function ( value ) { referenceSpaceType = value; if ( scope.isPresenting === true ) { console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); } }; this.getReferenceSpace = function () { return customReferenceSpace || referenceSpace; }; this.setReferenceSpace = function ( space ) { customReferenceSpace = space; }; this.getBaseLayer = function () { return glProjLayer !== null ? glProjLayer : glBaseLayer; }; this.getBinding = function () { return glBinding; }; this.getFrame = function () { return xrFrame; }; this.getSession = function () { return session; }; this.setSession = async function ( value ) { session = value; if ( session !== null ) { initialRenderTarget = renderer.getRenderTarget(); session.addEventListener( 'select', onSessionEvent ); session.addEventListener( 'selectstart', onSessionEvent ); session.addEventListener( 'selectend', onSessionEvent ); session.addEventListener( 'squeeze', onSessionEvent ); session.addEventListener( 'squeezestart', onSessionEvent ); session.addEventListener( 'squeezeend', onSessionEvent ); session.addEventListener( 'end', onSessionEnd ); session.addEventListener( 'inputsourceschange', onInputSourcesChange ); if ( attributes.xrCompatible !== true ) { await gl.makeXRCompatible(); } currentPixelRatio = renderer.getPixelRatio(); renderer.getSize( currentSize ); if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { const layerInit = { antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, alpha: true, depth: attributes.depth, stencil: attributes.stencil, framebufferScaleFactor: framebufferScaleFactor }; glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); session.updateRenderState( { baseLayer: glBaseLayer } ); renderer.setPixelRatio( 1 ); renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); newRenderTarget = new WebGLRenderTarget( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, { format: RGBAFormat, type: UnsignedByteType, colorSpace: renderer.outputColorSpace, stencilBuffer: attributes.stencil } ); } else { let depthFormat = null; let depthType = null; let glDepthFormat = null; if ( attributes.depth ) { glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; } const projectionlayerInit = { colorFormat: gl.RGBA8, depthFormat: glDepthFormat, scaleFactor: framebufferScaleFactor }; glBinding = new XRWebGLBinding( session, gl ); glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); session.updateRenderState( { layers: [ glProjLayer ] } ); renderer.setPixelRatio( 1 ); renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); newRenderTarget = new WebGLRenderTarget( glProjLayer.textureWidth, glProjLayer.textureHeight, { format: RGBAFormat, type: UnsignedByteType, depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), stencilBuffer: attributes.stencil, colorSpace: renderer.outputColorSpace, samples: attributes.antialias ? 4 : 0 } ); const renderTargetProperties = renderer.properties.get( newRenderTarget ); renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; } newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 this.setFoveation( foveation ); customReferenceSpace = null; referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); animation.setContext( session ); animation.start(); scope.isPresenting = true; scope.dispatchEvent( { type: 'sessionstart' } ); } }; this.getEnvironmentBlendMode = function () { if ( session !== null ) { return session.environmentBlendMode; } }; function onInputSourcesChange( event ) { // Notify disconnected for ( let i = 0; i < event.removed.length; i ++ ) { const inputSource = event.removed[ i ]; const index = controllerInputSources.indexOf( inputSource ); if ( index >= 0 ) { controllerInputSources[ index ] = null; controllers[ index ].disconnect( inputSource ); } } // Notify connected for ( let i = 0; i < event.added.length; i ++ ) { const inputSource = event.added[ i ]; let controllerIndex = controllerInputSources.indexOf( inputSource ); if ( controllerIndex === - 1 ) { // Assign input source a controller that currently has no input source for ( let i = 0; i < controllers.length; i ++ ) { if ( i >= controllerInputSources.length ) { controllerInputSources.push( inputSource ); controllerIndex = i; break; } else if ( controllerInputSources[ i ] === null ) { controllerInputSources[ i ] = inputSource; controllerIndex = i; break; } } // If all controllers do currently receive input we ignore new ones if ( controllerIndex === - 1 ) break; } const controller = controllers[ controllerIndex ]; if ( controller ) { controller.connect( inputSource ); } } } // const cameraLPos = new Vector3(); const cameraRPos = new Vector3(); /** * Assumes 2 cameras that are parallel and share an X-axis, and that * the cameras' projection and world matrices have already been set. * And that near and far planes are identical for both cameras. * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 */ function setProjectionFromUnion( camera, cameraL, cameraR ) { cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); const ipd = cameraLPos.distanceTo( cameraRPos ); const projL = cameraL.projectionMatrix.elements; const projR = cameraR.projectionMatrix.elements; // VR systems will have identical far and near planes, and // most likely identical top and bottom frustum extents. // Use the left camera for these values. const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; const left = near * leftFov; const right = near * rightFov; // Calculate the new camera's position offset from the // left camera. xOffset should be roughly half `ipd`. const zOffset = ipd / ( - leftFov + rightFov ); const xOffset = zOffset * - leftFov; // TODO: Better way to apply this offset? cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); camera.translateX( xOffset ); camera.translateZ( zOffset ); camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); // Find the union of the frustum values of the cameras and scale // the values so that the near plane's position does not change in world space, // although must now be relative to the new union camera. const near2 = near + zOffset; const far2 = far + zOffset; const left2 = left - xOffset; const right2 = right + ( ipd - xOffset ); const top2 = topFov * far / far2 * near2; const bottom2 = bottomFov * far / far2 * near2; camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); } function updateCamera( camera, parent ) { if ( parent === null ) { camera.matrixWorld.copy( camera.matrix ); } else { camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); } camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); } this.updateCamera = function ( camera ) { if ( session === null ) return; cameraXR.near = cameraR.near = cameraL.near = camera.near; cameraXR.far = cameraR.far = cameraL.far = camera.far; if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { // Note that the new renderState won't apply until the next frame. See #18320 session.updateRenderState( { depthNear: cameraXR.near, depthFar: cameraXR.far } ); _currentDepthNear = cameraXR.near; _currentDepthFar = cameraXR.far; } const parent = camera.parent; const cameras = cameraXR.cameras; updateCamera( cameraXR, parent ); for ( let i = 0; i < cameras.length; i ++ ) { updateCamera( cameras[ i ], parent ); } // update projection matrix for proper view frustum culling if ( cameras.length === 2 ) { setProjectionFromUnion( cameraXR, cameraL, cameraR ); } else { // assume single camera setup (AR) cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); } // update user camera and its children updateUserCamera( camera, cameraXR, parent ); }; function updateUserCamera( camera, cameraXR, parent ) { if ( parent === null ) { camera.matrix.copy( cameraXR.matrixWorld ); } else { camera.matrix.copy( parent.matrixWorld ); camera.matrix.invert(); camera.matrix.multiply( cameraXR.matrixWorld ); } camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); camera.updateMatrixWorld( true ); camera.projectionMatrix.copy( cameraXR.projectionMatrix ); camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); if ( camera.isPerspectiveCamera ) { camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); camera.zoom = 1; } } this.getCamera = function () { return cameraXR; }; this.getFoveation = function () { if ( glProjLayer === null && glBaseLayer === null ) { return undefined; } return foveation; }; this.setFoveation = function ( value ) { // 0 = no foveation = full resolution // 1 = maximum foveation = the edges render at lower resolution foveation = value; if ( glProjLayer !== null ) { glProjLayer.fixedFoveation = value; } if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { glBaseLayer.fixedFoveation = value; } }; // Animation Loop let onAnimationFrameCallback = null; function onAnimationFrame( time, frame ) { pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); xrFrame = frame; if ( pose !== null ) { const views = pose.views; if ( glBaseLayer !== null ) { renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); renderer.setRenderTarget( newRenderTarget ); } let cameraXRNeedsUpdate = false; // check if it's necessary to rebuild cameraXR's camera list if ( views.length !== cameraXR.cameras.length ) { cameraXR.cameras.length = 0; cameraXRNeedsUpdate = true; } for ( let i = 0; i < views.length; i ++ ) { const view = views[ i ]; let viewport = null; if ( glBaseLayer !== null ) { viewport = glBaseLayer.getViewport( view ); } else { const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); viewport = glSubImage.viewport; // For side-by-side projection, we only produce a single texture for both eyes. if ( i === 0 ) { renderer.setRenderTargetTextures( newRenderTarget, glSubImage.colorTexture, glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); renderer.setRenderTarget( newRenderTarget ); } } let camera = cameras[ i ]; if ( camera === undefined ) { camera = new PerspectiveCamera(); camera.layers.enable( i ); camera.viewport = new Vector4(); cameras[ i ] = camera; } camera.matrix.fromArray( view.transform.matrix ); camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); camera.projectionMatrix.fromArray( view.projectionMatrix ); camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); if ( i === 0 ) { cameraXR.matrix.copy( camera.matrix ); cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); } if ( cameraXRNeedsUpdate === true ) { cameraXR.cameras.push( camera ); } } } // for ( let i = 0; i < controllers.length; i ++ ) { const inputSource = controllerInputSources[ i ]; const controller = controllers[ i ]; if ( inputSource !== null && controller !== undefined ) { controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); } } if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); if ( frame.detectedPlanes ) { scope.dispatchEvent( { type: 'planesdetected', data: frame } ); } xrFrame = null; } const animation = new WebGLAnimation(); animation.setAnimationLoop( onAnimationFrame ); this.setAnimationLoop = function ( callback ) { onAnimationFrameCallback = callback; }; this.dispose = function () {}; } } function WebGLMaterials( renderer, properties ) { function refreshTransformUniform( map, uniform ) { if ( map.matrixAutoUpdate === true ) { map.updateMatrix(); } uniform.value.copy( map.matrix ); } function refreshFogUniforms( uniforms, fog ) { fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); if ( fog.isFog ) { uniforms.fogNear.value = fog.near; uniforms.fogFar.value = fog.far; } else if ( fog.isFogExp2 ) { uniforms.fogDensity.value = fog.density; } } function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { if ( material.isMeshBasicMaterial ) { refreshUniformsCommon( uniforms, material ); } else if ( material.isMeshLambertMaterial ) { refreshUniformsCommon( uniforms, material ); } else if ( material.isMeshToonMaterial ) { refreshUniformsCommon( uniforms, material ); refreshUniformsToon( uniforms, material ); } else if ( material.isMeshPhongMaterial ) { refreshUniformsCommon( uniforms, material ); refreshUniformsPhong( uniforms, material ); } else if ( material.isMeshStandardMaterial ) { refreshUniformsCommon( uniforms, material ); refreshUniformsStandard( uniforms, material ); if ( material.isMeshPhysicalMaterial ) { refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); } } else if ( material.isMeshMatcapMaterial ) { refreshUniformsCommon( uniforms, material ); refreshUniformsMatcap( uniforms, material ); } else if ( material.isMeshDepthMaterial ) { refreshUniformsCommon( uniforms, material ); } else if ( material.isMeshDistanceMaterial ) { refreshUniformsCommon( uniforms, material ); refreshUniformsDistance( uniforms, material ); } else if ( material.isMeshNormalMaterial ) { refreshUniformsCommon( uniforms, material ); } else if ( material.isLineBasicMaterial ) { refreshUniformsLine( uniforms, material ); if ( material.isLineDashedMaterial ) { refreshUniformsDash( uniforms, material ); } } else if ( material.isPointsMaterial ) { refreshUniformsPoints( uniforms, material, pixelRatio, height ); } else if ( material.isSpriteMaterial ) { refreshUniformsSprites( uniforms, material ); } else if ( material.isShadowMaterial ) { uniforms.color.value.copy( material.color ); uniforms.opacity.value = material.opacity; } else if ( material.isShaderMaterial ) { material.uniformsNeedUpdate = false; // #15581 } } function refreshUniformsCommon( uniforms, material ) { uniforms.opacity.value = material.opacity; if ( material.color ) { uniforms.diffuse.value.copy( material.color ); } if ( material.emissive ) { uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); } if ( material.map ) { uniforms.map.value = material.map; refreshTransformUniform( material.map, uniforms.mapTransform ); } if ( material.alphaMap ) { uniforms.alphaMap.value = material.alphaMap; refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); } if ( material.bumpMap ) { uniforms.bumpMap.value = material.bumpMap; refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); uniforms.bumpScale.value = material.bumpScale; if ( material.side === BackSide ) { uniforms.bumpScale.value *= - 1; } } if ( material.normalMap ) { uniforms.normalMap.value = material.normalMap; refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); uniforms.normalScale.value.copy( material.normalScale ); if ( material.side === BackSide ) { uniforms.normalScale.value.negate(); } } if ( material.displacementMap ) { uniforms.displacementMap.value = material.displacementMap; refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); uniforms.displacementScale.value = material.displacementScale; uniforms.displacementBias.value = material.displacementBias; } if ( material.emissiveMap ) { uniforms.emissiveMap.value = material.emissiveMap; refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); } if ( material.specularMap ) { uniforms.specularMap.value = material.specularMap; refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); } if ( material.alphaTest > 0 ) { uniforms.alphaTest.value = material.alphaTest; } const envMap = properties.get( material ).envMap; if ( envMap ) { uniforms.envMap.value = envMap; uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; uniforms.reflectivity.value = material.reflectivity; uniforms.ior.value = material.ior; uniforms.refractionRatio.value = material.refractionRatio; } if ( material.lightMap ) { uniforms.lightMap.value = material.lightMap; // artist-friendly light intensity scaling factor const scaleFactor = ( renderer._useLegacyLights === true ) ? Math.PI : 1; uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); } if ( material.aoMap ) { uniforms.aoMap.value = material.aoMap; uniforms.aoMapIntensity.value = material.aoMapIntensity; refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); } } function refreshUniformsLine( uniforms, material ) { uniforms.diffuse.value.copy( material.color ); uniforms.opacity.value = material.opacity; if ( material.map ) { uniforms.map.value = material.map; refreshTransformUniform( material.map, uniforms.mapTransform ); } } function refreshUniformsDash( uniforms, material ) { uniforms.dashSize.value = material.dashSize; uniforms.totalSize.value = material.dashSize + material.gapSize; uniforms.scale.value = material.scale; } function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { uniforms.diffuse.value.copy( material.color ); uniforms.opacity.value = material.opacity; uniforms.size.value = material.size * pixelRatio; uniforms.scale.value = height * 0.5; if ( material.map ) { uniforms.map.value = material.map; refreshTransformUniform( material.map, uniforms.uvTransform ); } if ( material.alphaMap ) { uniforms.alphaMap.value = material.alphaMap; refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); } if ( material.alphaTest > 0 ) { uniforms.alphaTest.value = material.alphaTest; } } function refreshUniformsSprites( uniforms, material ) { uniforms.diffuse.value.copy( material.color ); uniforms.opacity.value = material.opacity; uniforms.rotation.value = material.rotation; if ( material.map ) { uniforms.map.value = material.map; refreshTransformUniform( material.map, uniforms.mapTransform ); } if ( material.alphaMap ) { uniforms.alphaMap.value = material.alphaMap; refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); } if ( material.alphaTest > 0 ) { uniforms.alphaTest.value = material.alphaTest; } } function refreshUniformsPhong( uniforms, material ) { uniforms.specular.value.copy( material.specular ); uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) } function refreshUniformsToon( uniforms, material ) { if ( material.gradientMap ) { uniforms.gradientMap.value = material.gradientMap; } } function refreshUniformsStandard( uniforms, material ) { uniforms.metalness.value = material.metalness; if ( material.metalnessMap ) { uniforms.metalnessMap.value = material.metalnessMap; refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); } uniforms.roughness.value = material.roughness; if ( material.roughnessMap ) { uniforms.roughnessMap.value = material.roughnessMap; refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); } const envMap = properties.get( material ).envMap; if ( envMap ) { //uniforms.envMap.value = material.envMap; // part of uniforms common uniforms.envMapIntensity.value = material.envMapIntensity; } } function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { uniforms.ior.value = material.ior; // also part of uniforms common if ( material.sheen > 0 ) { uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); uniforms.sheenRoughness.value = material.sheenRoughness; if ( material.sheenColorMap ) { uniforms.sheenColorMap.value = material.sheenColorMap; refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); } if ( material.sheenRoughnessMap ) { uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); } } if ( material.clearcoat > 0 ) { uniforms.clearcoat.value = material.clearcoat; uniforms.clearcoatRoughness.value = material.clearcoatRoughness; if ( material.clearcoatMap ) { uniforms.clearcoatMap.value = material.clearcoatMap; refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); } if ( material.clearcoatRoughnessMap ) { uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); } if ( material.clearcoatNormalMap ) { uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); if ( material.side === BackSide ) { uniforms.clearcoatNormalScale.value.negate(); } } } if ( material.iridescence > 0 ) { uniforms.iridescence.value = material.iridescence; uniforms.iridescenceIOR.value = material.iridescenceIOR; uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; if ( material.iridescenceMap ) { uniforms.iridescenceMap.value = material.iridescenceMap; refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); } if ( material.iridescenceThicknessMap ) { uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); } } if ( material.transmission > 0 ) { uniforms.transmission.value = material.transmission; uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); if ( material.transmissionMap ) { uniforms.transmissionMap.value = material.transmissionMap; refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); } uniforms.thickness.value = material.thickness; if ( material.thicknessMap ) { uniforms.thicknessMap.value = material.thicknessMap; refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); } uniforms.attenuationDistance.value = material.attenuationDistance; uniforms.attenuationColor.value.copy( material.attenuationColor ); } if ( material.anisotropy > 0 ) { uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); if ( material.anisotropyMap ) { uniforms.anisotropyMap.value = material.anisotropyMap; refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); } } uniforms.specularIntensity.value = material.specularIntensity; uniforms.specularColor.value.copy( material.specularColor ); if ( material.specularColorMap ) { uniforms.specularColorMap.value = material.specularColorMap; refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); } if ( material.specularIntensityMap ) { uniforms.specularIntensityMap.value = material.specularIntensityMap; refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); } } function refreshUniformsMatcap( uniforms, material ) { if ( material.matcap ) { uniforms.matcap.value = material.matcap; } } function refreshUniformsDistance( uniforms, material ) { const light = properties.get( material ).light; uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); uniforms.nearDistance.value = light.shadow.camera.near; uniforms.farDistance.value = light.shadow.camera.far; } return { refreshFogUniforms: refreshFogUniforms, refreshMaterialUniforms: refreshMaterialUniforms }; } function WebGLUniformsGroups( gl, info, capabilities, state ) { let buffers = {}; let updateList = {}; let allocatedBindingPoints = []; const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program function bind( uniformsGroup, program ) { const webglProgram = program.program; state.uniformBlockBinding( uniformsGroup, webglProgram ); } function update( uniformsGroup, program ) { let buffer = buffers[ uniformsGroup.id ]; if ( buffer === undefined ) { prepareUniformsGroup( uniformsGroup ); buffer = createBuffer( uniformsGroup ); buffers[ uniformsGroup.id ] = buffer; uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); } // ensure to update the binding points/block indices mapping for this program const webglProgram = program.program; state.updateUBOMapping( uniformsGroup, webglProgram ); // update UBO once per frame const frame = info.render.frame; if ( updateList[ uniformsGroup.id ] !== frame ) { updateBufferData( uniformsGroup ); updateList[ uniformsGroup.id ] = frame; } } function createBuffer( uniformsGroup ) { // the setup of an UBO is independent of a particular shader program but global const bindingPointIndex = allocateBindingPointIndex(); uniformsGroup.__bindingPointIndex = bindingPointIndex; const buffer = gl.createBuffer(); const size = uniformsGroup.__size; const usage = uniformsGroup.usage; gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); gl.bindBuffer( gl.UNIFORM_BUFFER, null ); gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); return buffer; } function allocateBindingPointIndex() { for ( let i = 0; i < maxBindingPoints; i ++ ) { if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { allocatedBindingPoints.push( i ); return i; } } console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); return 0; } function updateBufferData( uniformsGroup ) { const buffer = buffers[ uniformsGroup.id ]; const uniforms = uniformsGroup.uniforms; const cache = uniformsGroup.__cache; gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); for ( let i = 0, il = uniforms.length; i < il; i ++ ) { const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { const uniform = uniformArray[ j ]; if ( hasUniformChanged( uniform, i, j, cache ) === true ) { const offset = uniform.__offset; const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; let arrayOffset = 0; for ( let k = 0; k < values.length; k ++ ) { const value = values[ k ]; const info = getUniformSize( value ); // TODO add integer and struct support if ( typeof value === 'number' || typeof value === 'boolean' ) { uniform.__data[ 0 ] = value; gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); } else if ( value.isMatrix3 ) { // manually converting 3x3 to 3x4 uniform.__data[ 0 ] = value.elements[ 0 ]; uniform.__data[ 1 ] = value.elements[ 1 ]; uniform.__data[ 2 ] = value.elements[ 2 ]; uniform.__data[ 3 ] = 0; uniform.__data[ 4 ] = value.elements[ 3 ]; uniform.__data[ 5 ] = value.elements[ 4 ]; uniform.__data[ 6 ] = value.elements[ 5 ]; uniform.__data[ 7 ] = 0; uniform.__data[ 8 ] = value.elements[ 6 ]; uniform.__data[ 9 ] = value.elements[ 7 ]; uniform.__data[ 10 ] = value.elements[ 8 ]; uniform.__data[ 11 ] = 0; } else { value.toArray( uniform.__data, arrayOffset ); arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; } } gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); } } } gl.bindBuffer( gl.UNIFORM_BUFFER, null ); } function hasUniformChanged( uniform, index, indexArray, cache ) { const value = uniform.value; const indexString = index + '_' + indexArray; if ( cache[ indexString ] === undefined ) { // cache entry does not exist so far if ( typeof value === 'number' || typeof value === 'boolean' ) { cache[ indexString ] = value; } else { cache[ indexString ] = value.clone(); } return true; } else { const cachedObject = cache[ indexString ]; // compare current value with cached entry if ( typeof value === 'number' || typeof value === 'boolean' ) { if ( cachedObject !== value ) { cache[ indexString ] = value; return true; } } else { if ( cachedObject.equals( value ) === false ) { cachedObject.copy( value ); return true; } } } return false; } function prepareUniformsGroup( uniformsGroup ) { // determine total buffer size according to the STD140 layout // Hint: STD140 is the only supported layout in WebGL 2 const uniforms = uniformsGroup.uniforms; let offset = 0; // global buffer offset in bytes const chunkSize = 16; // size of a chunk in bytes for ( let i = 0, l = uniforms.length; i < l; i ++ ) { const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { const uniform = uniformArray[ j ]; const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; for ( let k = 0, kl = values.length; k < kl; k ++ ) { const value = values[ k ]; const info = getUniformSize( value ); // Calculate the chunk offset const chunkOffsetUniform = offset % chunkSize; // Check for chunk overflow if ( chunkOffsetUniform !== 0 && ( chunkSize - chunkOffsetUniform ) < info.boundary ) { // Add padding and adjust offset offset += ( chunkSize - chunkOffsetUniform ); } // the following two properties will be used for partial buffer updates uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); uniform.__offset = offset; // Update the global offset offset += info.storage; } } } // ensure correct final padding const chunkOffset = offset % chunkSize; if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); // uniformsGroup.__size = offset; uniformsGroup.__cache = {}; return this; } function getUniformSize( value ) { const info = { boundary: 0, // bytes storage: 0 // bytes }; // determine sizes according to STD140 if ( typeof value === 'number' || typeof value === 'boolean' ) { // float/int/bool info.boundary = 4; info.storage = 4; } else if ( value.isVector2 ) { // vec2 info.boundary = 8; info.storage = 8; } else if ( value.isVector3 || value.isColor ) { // vec3 info.boundary = 16; info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes } else if ( value.isVector4 ) { // vec4 info.boundary = 16; info.storage = 16; } else if ( value.isMatrix3 ) { // mat3 (in STD140 a 3x3 matrix is represented as 3x4) info.boundary = 48; info.storage = 48; } else if ( value.isMatrix4 ) { // mat4 info.boundary = 64; info.storage = 64; } else if ( value.isTexture ) { console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); } else { console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); } return info; } function onUniformsGroupsDispose( event ) { const uniformsGroup = event.target; uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); allocatedBindingPoints.splice( index, 1 ); gl.deleteBuffer( buffers[ uniformsGroup.id ] ); delete buffers[ uniformsGroup.id ]; delete updateList[ uniformsGroup.id ]; } function dispose() { for ( const id in buffers ) { gl.deleteBuffer( buffers[ id ] ); } allocatedBindingPoints = []; buffers = {}; updateList = {}; } return { bind: bind, update: update, dispose: dispose }; } class WebGLRenderer { constructor( parameters = {} ) { const { canvas = createCanvasElement(), context = null, depth = true, stencil = true, alpha = false, antialias = false, premultipliedAlpha = true, preserveDrawingBuffer = false, powerPreference = 'default', failIfMajorPerformanceCaveat = false, } = parameters; this.isWebGLRenderer = true; let _alpha; if ( context !== null ) { _alpha = context.getContextAttributes().alpha; } else { _alpha = alpha; } const uintClearColor = new Uint32Array( 4 ); const intClearColor = new Int32Array( 4 ); let currentRenderList = null; let currentRenderState = null; // render() can be called from within a callback triggered by another render. // We track this so that the nested render call gets its list and state isolated from the parent render call. const renderListStack = []; const renderStateStack = []; // public properties this.domElement = canvas; // Debug configuration container this.debug = { /** * Enables error checking and reporting when shader programs are being compiled * @type {boolean} */ checkShaderErrors: true, /** * Callback for custom error reporting. * @type {?Function} */ onShaderError: null }; // clearing this.autoClear = true; this.autoClearColor = true; this.autoClearDepth = true; this.autoClearStencil = true; // scene graph this.sortObjects = true; // user-defined clipping this.clippingPlanes = []; this.localClippingEnabled = false; // physically based shading this._outputColorSpace = SRGBColorSpace; // physical lights this._useLegacyLights = false; // tone mapping this.toneMapping = NoToneMapping; this.toneMappingExposure = 1.0; // internal properties const _this = this; let _isContextLost = false; // internal state cache let _currentActiveCubeFace = 0; let _currentActiveMipmapLevel = 0; let _currentRenderTarget = null; let _currentMaterialId = - 1; let _currentCamera = null; const _currentViewport = new Vector4(); const _currentScissor = new Vector4(); let _currentScissorTest = null; const _currentClearColor = new Color( 0x000000 ); let _currentClearAlpha = 0; // let _width = canvas.width; let _height = canvas.height; let _pixelRatio = 1; let _opaqueSort = null; let _transparentSort = null; const _viewport = new Vector4( 0, 0, _width, _height ); const _scissor = new Vector4( 0, 0, _width, _height ); let _scissorTest = false; // frustum const _frustum = new Frustum(); // clipping let _clippingEnabled = false; let _localClippingEnabled = false; // transmission let _transmissionRenderTarget = null; // camera matrices cache const _projScreenMatrix = new Matrix4(); const _vector2 = new Vector2(); const _vector3 = new Vector3(); const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; function getTargetPixelRatio() { return _currentRenderTarget === null ? _pixelRatio : 1; } // initialize let _gl = context; function getContext( contextNames, contextAttributes ) { for ( let i = 0; i < contextNames.length; i ++ ) { const contextName = contextNames[ i ]; const context = canvas.getContext( contextName, contextAttributes ); if ( context !== null ) return context; } return null; } try { const contextAttributes = { alpha: true, depth, stencil, antialias, premultipliedAlpha, preserveDrawingBuffer, powerPreference, failIfMajorPerformanceCaveat, }; // OffscreenCanvas does not have setAttribute, see #22811 if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); // event listeners must be registered before WebGL context is created, see #12753 canvas.addEventListener( 'webglcontextlost', onContextLost, false ); canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); if ( _gl === null ) { const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; if ( _this.isWebGL1Renderer === true ) { contextNames.shift(); } _gl = getContext( contextNames, contextAttributes ); if ( _gl === null ) { if ( getContext( contextNames ) ) { throw new Error( 'Error creating WebGL context with your selected attributes.' ); } else { throw new Error( 'Error creating WebGL context.' ); } } } if ( typeof WebGLRenderingContext !== 'undefined' && _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); } // Some experimental-webgl implementations do not have getShaderPrecisionFormat if ( _gl.getShaderPrecisionFormat === undefined ) { _gl.getShaderPrecisionFormat = function () { return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; }; } } catch ( error ) { console.error( 'THREE.WebGLRenderer: ' + error.message ); throw error; } let extensions, capabilities, state, info; let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; let programCache, materials, renderLists, renderStates, clipping, shadowMap; let background, morphtargets, bufferRenderer, indexedBufferRenderer; let utils, bindingStates, uniformsGroups; function initGLContext() { extensions = new WebGLExtensions( _gl ); capabilities = new WebGLCapabilities( _gl, extensions, parameters ); extensions.init( capabilities ); utils = new WebGLUtils( _gl, extensions, capabilities ); state = new WebGLState( _gl, extensions, capabilities ); info = new WebGLInfo( _gl ); properties = new WebGLProperties(); textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); cubemaps = new WebGLCubeMaps( _this ); cubeuvmaps = new WebGLCubeUVMaps( _this ); attributes = new WebGLAttributes( _gl, capabilities ); bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); objects = new WebGLObjects( _gl, geometries, attributes, info ); morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); clipping = new WebGLClipping( properties ); programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); materials = new WebGLMaterials( _this, properties ); renderLists = new WebGLRenderLists(); renderStates = new WebGLRenderStates( extensions, capabilities ); background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); shadowMap = new WebGLShadowMap( _this, objects, capabilities ); uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); info.programs = programCache.programs; _this.capabilities = capabilities; _this.extensions = extensions; _this.properties = properties; _this.renderLists = renderLists; _this.shadowMap = shadowMap; _this.state = state; _this.info = info; } initGLContext(); // xr const xr = new WebXRManager( _this, _gl ); this.xr = xr; // API this.getContext = function () { return _gl; }; this.getContextAttributes = function () { return _gl.getContextAttributes(); }; this.forceContextLoss = function () { const extension = extensions.get( 'WEBGL_lose_context' ); if ( extension ) extension.loseContext(); }; this.forceContextRestore = function () { const extension = extensions.get( 'WEBGL_lose_context' ); if ( extension ) extension.restoreContext(); }; this.getPixelRatio = function () { return _pixelRatio; }; this.setPixelRatio = function ( value ) { if ( value === undefined ) return; _pixelRatio = value; this.setSize( _width, _height, false ); }; this.getSize = function ( target ) { return target.set( _width, _height ); }; this.setSize = function ( width, height, updateStyle = true ) { if ( xr.isPresenting ) { console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); return; } _width = width; _height = height; canvas.width = Math.floor( width * _pixelRatio ); canvas.height = Math.floor( height * _pixelRatio ); if ( updateStyle === true ) { canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; } this.setViewport( 0, 0, width, height ); }; this.getDrawingBufferSize = function ( target ) { return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); }; this.setDrawingBufferSize = function ( width, height, pixelRatio ) { _width = width; _height = height; _pixelRatio = pixelRatio; canvas.width = Math.floor( width * pixelRatio ); canvas.height = Math.floor( height * pixelRatio ); this.setViewport( 0, 0, width, height ); }; this.getCurrentViewport = function ( target ) { return target.copy( _currentViewport ); }; this.getViewport = function ( target ) { return target.copy( _viewport ); }; this.setViewport = function ( x, y, width, height ) { if ( x.isVector4 ) { _viewport.set( x.x, x.y, x.z, x.w ); } else { _viewport.set( x, y, width, height ); } state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() ); }; this.getScissor = function ( target ) { return target.copy( _scissor ); }; this.setScissor = function ( x, y, width, height ) { if ( x.isVector4 ) { _scissor.set( x.x, x.y, x.z, x.w ); } else { _scissor.set( x, y, width, height ); } state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() ); }; this.getScissorTest = function () { return _scissorTest; }; this.setScissorTest = function ( boolean ) { state.setScissorTest( _scissorTest = boolean ); }; this.setOpaqueSort = function ( method ) { _opaqueSort = method; }; this.setTransparentSort = function ( method ) { _transparentSort = method; }; // Clearing this.getClearColor = function ( target ) { return target.copy( background.getClearColor() ); }; this.setClearColor = function () { background.setClearColor.apply( background, arguments ); }; this.getClearAlpha = function () { return background.getClearAlpha(); }; this.setClearAlpha = function () { background.setClearAlpha.apply( background, arguments ); }; this.clear = function ( color = true, depth = true, stencil = true ) { let bits = 0; if ( color ) { // check if we're trying to clear an integer target let isIntegerFormat = false; if ( _currentRenderTarget !== null ) { const targetFormat = _currentRenderTarget.texture.format; isIntegerFormat = targetFormat === RGBAIntegerFormat || targetFormat === RGIntegerFormat || targetFormat === RedIntegerFormat; } // use the appropriate clear functions to clear the target if it's a signed // or unsigned integer target if ( isIntegerFormat ) { const targetType = _currentRenderTarget.texture.type; const isUnsignedType = targetType === UnsignedByteType || targetType === UnsignedIntType || targetType === UnsignedShortType || targetType === UnsignedInt248Type || targetType === UnsignedShort4444Type || targetType === UnsignedShort5551Type; const clearColor = background.getClearColor(); const a = background.getClearAlpha(); const r = clearColor.r; const g = clearColor.g; const b = clearColor.b; if ( isUnsignedType ) { uintClearColor[ 0 ] = r; uintClearColor[ 1 ] = g; uintClearColor[ 2 ] = b; uintClearColor[ 3 ] = a; _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); } else { intClearColor[ 0 ] = r; intClearColor[ 1 ] = g; intClearColor[ 2 ] = b; intClearColor[ 3 ] = a; _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); } } else { bits |= _gl.COLOR_BUFFER_BIT; } } if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; if ( stencil ) { bits |= _gl.STENCIL_BUFFER_BIT; this.state.buffers.stencil.setMask( 0xffffffff ); } _gl.clear( bits ); }; this.clearColor = function () { this.clear( true, false, false ); }; this.clearDepth = function () { this.clear( false, true, false ); }; this.clearStencil = function () { this.clear( false, false, true ); }; // this.dispose = function () { canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); renderLists.dispose(); renderStates.dispose(); properties.dispose(); cubemaps.dispose(); cubeuvmaps.dispose(); objects.dispose(); bindingStates.dispose(); uniformsGroups.dispose(); programCache.dispose(); xr.dispose(); xr.removeEventListener( 'sessionstart', onXRSessionStart ); xr.removeEventListener( 'sessionend', onXRSessionEnd ); if ( _transmissionRenderTarget ) { _transmissionRenderTarget.dispose(); _transmissionRenderTarget = null; } animation.stop(); }; // Events function onContextLost( event ) { event.preventDefault(); console.log( 'THREE.WebGLRenderer: Context Lost.' ); _isContextLost = true; } function onContextRestore( /* event */ ) { console.log( 'THREE.WebGLRenderer: Context Restored.' ); _isContextLost = false; const infoAutoReset = info.autoReset; const shadowMapEnabled = shadowMap.enabled; const shadowMapAutoUpdate = shadowMap.autoUpdate; const shadowMapNeedsUpdate = shadowMap.needsUpdate; const shadowMapType = shadowMap.type; initGLContext(); info.autoReset = infoAutoReset; shadowMap.enabled = shadowMapEnabled; shadowMap.autoUpdate = shadowMapAutoUpdate; shadowMap.needsUpdate = shadowMapNeedsUpdate; shadowMap.type = shadowMapType; } function onContextCreationError( event ) { console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); } function onMaterialDispose( event ) { const material = event.target; material.removeEventListener( 'dispose', onMaterialDispose ); deallocateMaterial( material ); } // Buffer deallocation function deallocateMaterial( material ) { releaseMaterialProgramReferences( material ); properties.remove( material ); } function releaseMaterialProgramReferences( material ) { const programs = properties.get( material ).programs; if ( programs !== undefined ) { programs.forEach( function ( program ) { programCache.releaseProgram( program ); } ); if ( material.isShaderMaterial ) { programCache.releaseShaderCache( material ); } } } // Buffer rendering this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); const program = setProgram( camera, scene, geometry, material, object ); state.setMaterial( material, frontFaceCW ); // let index = geometry.index; let rangeFactor = 1; if ( material.wireframe === true ) { index = geometries.getWireframeAttribute( geometry ); if ( index === undefined ) return; rangeFactor = 2; } // const drawRange = geometry.drawRange; const position = geometry.attributes.position; let drawStart = drawRange.start * rangeFactor; let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; if ( group !== null ) { drawStart = Math.max( drawStart, group.start * rangeFactor ); drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); } if ( index !== null ) { drawStart = Math.max( drawStart, 0 ); drawEnd = Math.min( drawEnd, index.count ); } else if ( position !== undefined && position !== null ) { drawStart = Math.max( drawStart, 0 ); drawEnd = Math.min( drawEnd, position.count ); } const drawCount = drawEnd - drawStart; if ( drawCount < 0 || drawCount === Infinity ) return; // bindingStates.setup( object, material, program, geometry, index ); let attribute; let renderer = bufferRenderer; if ( index !== null ) { attribute = attributes.get( index ); renderer = indexedBufferRenderer; renderer.setIndex( attribute ); } // if ( object.isMesh ) { if ( material.wireframe === true ) { state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); renderer.setMode( _gl.LINES ); } else { renderer.setMode( _gl.TRIANGLES ); } } else if ( object.isLine ) { let lineWidth = material.linewidth; if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material state.setLineWidth( lineWidth * getTargetPixelRatio() ); if ( object.isLineSegments ) { renderer.setMode( _gl.LINES ); } else if ( object.isLineLoop ) { renderer.setMode( _gl.LINE_LOOP ); } else { renderer.setMode( _gl.LINE_STRIP ); } } else if ( object.isPoints ) { renderer.setMode( _gl.POINTS ); } else if ( object.isSprite ) { renderer.setMode( _gl.TRIANGLES ); } if ( object.isBatchedMesh ) { renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); } else if ( object.isInstancedMesh ) { renderer.renderInstances( drawStart, drawCount, object.count ); } else if ( geometry.isInstancedBufferGeometry ) { const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); renderer.renderInstances( drawStart, drawCount, instanceCount ); } else { renderer.render( drawStart, drawCount ); } }; // Compile function prepareMaterial( material, scene, object ) { if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { material.side = BackSide; material.needsUpdate = true; getProgram( material, scene, object ); material.side = FrontSide; material.needsUpdate = true; getProgram( material, scene, object ); material.side = DoubleSide; } else { getProgram( material, scene, object ); } } this.compile = function ( scene, camera, targetScene = null ) { if ( targetScene === null ) targetScene = scene; currentRenderState = renderStates.get( targetScene ); currentRenderState.init(); renderStateStack.push( currentRenderState ); // gather lights from both the target scene and the new object that will be added to the scene. targetScene.traverseVisible( function ( object ) { if ( object.isLight && object.layers.test( camera.layers ) ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } } ); if ( scene !== targetScene ) { scene.traverseVisible( function ( object ) { if ( object.isLight && object.layers.test( camera.layers ) ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } } ); } currentRenderState.setupLights( _this._useLegacyLights ); // Only initialize materials in the new scene, not the targetScene. const materials = new Set(); scene.traverse( function ( object ) { const material = object.material; if ( material ) { if ( Array.isArray( material ) ) { for ( let i = 0; i < material.length; i ++ ) { const material2 = material[ i ]; prepareMaterial( material2, targetScene, object ); materials.add( material2 ); } } else { prepareMaterial( material, targetScene, object ); materials.add( material ); } } } ); renderStateStack.pop(); currentRenderState = null; return materials; }; // compileAsync this.compileAsync = function ( scene, camera, targetScene = null ) { const materials = this.compile( scene, camera, targetScene ); // Wait for all the materials in the new object to indicate that they're // ready to be used before resolving the promise. return new Promise( ( resolve ) => { function checkMaterialsReady() { materials.forEach( function ( material ) { const materialProperties = properties.get( material ); const program = materialProperties.currentProgram; if ( program.isReady() ) { // remove any programs that report they're ready to use from the list materials.delete( material ); } } ); // once the list of compiling materials is empty, call the callback if ( materials.size === 0 ) { resolve( scene ); return; } // if some materials are still not ready, wait a bit and check again setTimeout( checkMaterialsReady, 10 ); } if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { // If we can check the compilation status of the materials without // blocking then do so right away. checkMaterialsReady(); } else { // Otherwise start by waiting a bit to give the materials we just // initialized a chance to finish. setTimeout( checkMaterialsReady, 10 ); } } ); }; // Animation Loop let onAnimationFrameCallback = null; function onAnimationFrame( time ) { if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); } function onXRSessionStart() { animation.stop(); } function onXRSessionEnd() { animation.start(); } const animation = new WebGLAnimation(); animation.setAnimationLoop( onAnimationFrame ); if ( typeof self !== 'undefined' ) animation.setContext( self ); this.setAnimationLoop = function ( callback ) { onAnimationFrameCallback = callback; xr.setAnimationLoop( callback ); ( callback === null ) ? animation.stop() : animation.start(); }; xr.addEventListener( 'sessionstart', onXRSessionStart ); xr.addEventListener( 'sessionend', onXRSessionEnd ); // Rendering this.render = function ( scene, camera ) { if ( camera !== undefined && camera.isCamera !== true ) { console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); return; } if ( _isContextLost === true ) return; // update scene graph if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); // update camera matrices and frustum if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); if ( xr.enabled === true && xr.isPresenting === true ) { if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); camera = xr.getCamera(); // use XR camera for rendering } // if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); currentRenderState = renderStates.get( scene, renderStateStack.length ); currentRenderState.init(); renderStateStack.push( currentRenderState ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); _frustum.setFromProjectionMatrix( _projScreenMatrix ); _localClippingEnabled = this.localClippingEnabled; _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); currentRenderList = renderLists.get( scene, renderListStack.length ); currentRenderList.init(); renderListStack.push( currentRenderList ); projectObject( scene, camera, 0, _this.sortObjects ); currentRenderList.finish(); if ( _this.sortObjects === true ) { currentRenderList.sort( _opaqueSort, _transparentSort ); } // this.info.render.frame ++; if ( _clippingEnabled === true ) clipping.beginShadows(); const shadowsArray = currentRenderState.state.shadowsArray; shadowMap.render( shadowsArray, scene, camera ); if ( _clippingEnabled === true ) clipping.endShadows(); // if ( this.info.autoReset === true ) this.info.reset(); // background.render( currentRenderList, scene ); // render scene currentRenderState.setupLights( _this._useLegacyLights ); if ( camera.isArrayCamera ) { const cameras = camera.cameras; for ( let i = 0, l = cameras.length; i < l; i ++ ) { const camera2 = cameras[ i ]; renderScene( currentRenderList, scene, camera2, camera2.viewport ); } } else { renderScene( currentRenderList, scene, camera ); } // if ( _currentRenderTarget !== null ) { // resolve multisample renderbuffers to a single-sample texture if necessary textures.updateMultisampleRenderTarget( _currentRenderTarget ); // Generate mipmap if we're using any kind of mipmap filtering textures.updateRenderTargetMipmap( _currentRenderTarget ); } // if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); // _gl.finish(); bindingStates.resetDefaultState(); _currentMaterialId = - 1; _currentCamera = null; renderStateStack.pop(); if ( renderStateStack.length > 0 ) { currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; } else { currentRenderState = null; } renderListStack.pop(); if ( renderListStack.length > 0 ) { currentRenderList = renderListStack[ renderListStack.length - 1 ]; } else { currentRenderList = null; } }; function projectObject( object, camera, groupOrder, sortObjects ) { if ( object.visible === false ) return; const visible = object.layers.test( camera.layers ); if ( visible ) { if ( object.isGroup ) { groupOrder = object.renderOrder; } else if ( object.isLOD ) { if ( object.autoUpdate === true ) object.update( camera ); } else if ( object.isLight ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } else if ( object.isSprite ) { if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { if ( sortObjects ) { _vector3.setFromMatrixPosition( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); } const geometry = objects.update( object ); const material = object.material; if ( material.visible ) { currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); } } } else if ( object.isMesh || object.isLine || object.isPoints ) { if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { const geometry = objects.update( object ); const material = object.material; if ( sortObjects ) { if ( object.boundingSphere !== undefined ) { if ( object.boundingSphere === null ) object.computeBoundingSphere(); _vector3.copy( object.boundingSphere.center ); } else { if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _vector3.copy( geometry.boundingSphere.center ); } _vector3 .applyMatrix4( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); } if ( Array.isArray( material ) ) { const groups = geometry.groups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; const groupMaterial = material[ group.materialIndex ]; if ( groupMaterial && groupMaterial.visible ) { currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); } } } else if ( material.visible ) { currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); } } } } const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { projectObject( children[ i ], camera, groupOrder, sortObjects ); } } function renderScene( currentRenderList, scene, camera, viewport ) { const opaqueObjects = currentRenderList.opaque; const transmissiveObjects = currentRenderList.transmissive; const transparentObjects = currentRenderList.transparent; currentRenderState.setupLightsView( camera ); if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); // Ensure depth buffer writing is enabled so it can be cleared on next render state.buffers.depth.setTest( true ); state.buffers.depth.setMask( true ); state.buffers.color.setMask( true ); state.setPolygonOffset( false ); } function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; if ( overrideMaterial !== null ) { return; } const isWebGL2 = capabilities.isWebGL2; if ( _transmissionRenderTarget === null ) { _transmissionRenderTarget = new WebGLRenderTarget( 1, 1, { generateMipmaps: true, type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, minFilter: LinearMipmapLinearFilter, samples: ( isWebGL2 ) ? 4 : 0 } ); // debug /* const geometry = new PlaneGeometry(); const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); const mesh = new Mesh( geometry, material ); scene.add( mesh ); */ } _this.getDrawingBufferSize( _vector2 ); if ( isWebGL2 ) { _transmissionRenderTarget.setSize( _vector2.x, _vector2.y ); } else { _transmissionRenderTarget.setSize( floorPowerOfTwo( _vector2.x ), floorPowerOfTwo( _vector2.y ) ); } // const currentRenderTarget = _this.getRenderTarget(); _this.setRenderTarget( _transmissionRenderTarget ); _this.getClearColor( _currentClearColor ); _currentClearAlpha = _this.getClearAlpha(); if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); _this.clear(); // Turn off the features which can affect the frag color for opaque objects pass. // Otherwise they are applied twice in opaque objects pass and transmission objects pass. const currentToneMapping = _this.toneMapping; _this.toneMapping = NoToneMapping; renderObjects( opaqueObjects, scene, camera ); textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); textures.updateRenderTargetMipmap( _transmissionRenderTarget ); let renderTargetNeedsUpdate = false; for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { const renderItem = transmissiveObjects[ i ]; const object = renderItem.object; const geometry = renderItem.geometry; const material = renderItem.material; const group = renderItem.group; if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { const currentSide = material.side; material.side = BackSide; material.needsUpdate = true; renderObject( object, scene, camera, geometry, material, group ); material.side = currentSide; material.needsUpdate = true; renderTargetNeedsUpdate = true; } } if ( renderTargetNeedsUpdate === true ) { textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); textures.updateRenderTargetMipmap( _transmissionRenderTarget ); } _this.setRenderTarget( currentRenderTarget ); _this.setClearColor( _currentClearColor, _currentClearAlpha ); _this.toneMapping = currentToneMapping; } function renderObjects( renderList, scene, camera ) { const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; for ( let i = 0, l = renderList.length; i < l; i ++ ) { const renderItem = renderList[ i ]; const object = renderItem.object; const geometry = renderItem.geometry; const material = overrideMaterial === null ? renderItem.material : overrideMaterial; const group = renderItem.group; if ( object.layers.test( camera.layers ) ) { renderObject( object, scene, camera, geometry, material, group ); } } } function renderObject( object, scene, camera, geometry, material, group ) { object.onBeforeRender( _this, scene, camera, geometry, material, group ); object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); material.onBeforeRender( _this, scene, camera, geometry, object, group ); if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { material.side = BackSide; material.needsUpdate = true; _this.renderBufferDirect( camera, scene, geometry, material, object, group ); material.side = FrontSide; material.needsUpdate = true; _this.renderBufferDirect( camera, scene, geometry, material, object, group ); material.side = DoubleSide; } else { _this.renderBufferDirect( camera, scene, geometry, material, object, group ); } object.onAfterRender( _this, scene, camera, geometry, material, group ); } function getProgram( material, scene, object ) { if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... const materialProperties = properties.get( material ); const lights = currentRenderState.state.lights; const shadowsArray = currentRenderState.state.shadowsArray; const lightsStateVersion = lights.state.version; const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); const programCacheKey = programCache.getProgramCacheKey( parameters ); let programs = materialProperties.programs; // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; materialProperties.fog = scene.fog; materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); if ( programs === undefined ) { // new material material.addEventListener( 'dispose', onMaterialDispose ); programs = new Map(); materialProperties.programs = programs; } let program = programs.get( programCacheKey ); if ( program !== undefined ) { // early out if program and light state is identical if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { updateCommonMaterialProperties( material, parameters ); return program; } } else { parameters.uniforms = programCache.getUniforms( material ); material.onBuild( object, parameters, _this ); material.onBeforeCompile( parameters, _this ); program = programCache.acquireProgram( parameters, programCacheKey ); programs.set( programCacheKey, program ); materialProperties.uniforms = parameters.uniforms; } const uniforms = materialProperties.uniforms; if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { uniforms.clippingPlanes = clipping.uniform; } updateCommonMaterialProperties( material, parameters ); // store the light setup it was created for materialProperties.needsLights = materialNeedsLights( material ); materialProperties.lightsStateVersion = lightsStateVersion; if ( materialProperties.needsLights ) { // wire up the material to this renderer's lighting state uniforms.ambientLightColor.value = lights.state.ambient; uniforms.lightProbe.value = lights.state.probe; uniforms.directionalLights.value = lights.state.directional; uniforms.directionalLightShadows.value = lights.state.directionalShadow; uniforms.spotLights.value = lights.state.spot; uniforms.spotLightShadows.value = lights.state.spotShadow; uniforms.rectAreaLights.value = lights.state.rectArea; uniforms.ltc_1.value = lights.state.rectAreaLTC1; uniforms.ltc_2.value = lights.state.rectAreaLTC2; uniforms.pointLights.value = lights.state.point; uniforms.pointLightShadows.value = lights.state.pointShadow; uniforms.hemisphereLights.value = lights.state.hemi; uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; uniforms.spotShadowMap.value = lights.state.spotShadowMap; uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; uniforms.spotLightMap.value = lights.state.spotLightMap; uniforms.pointShadowMap.value = lights.state.pointShadowMap; uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; // TODO (abelnation): add area lights shadow info to uniforms } materialProperties.currentProgram = program; materialProperties.uniformsList = null; return program; } function getUniformList( materialProperties ) { if ( materialProperties.uniformsList === null ) { const progUniforms = materialProperties.currentProgram.getUniforms(); materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); } return materialProperties.uniformsList; } function updateCommonMaterialProperties( material, parameters ) { const materialProperties = properties.get( material ); materialProperties.outputColorSpace = parameters.outputColorSpace; materialProperties.batching = parameters.batching; materialProperties.instancing = parameters.instancing; materialProperties.instancingColor = parameters.instancingColor; materialProperties.skinning = parameters.skinning; materialProperties.morphTargets = parameters.morphTargets; materialProperties.morphNormals = parameters.morphNormals; materialProperties.morphColors = parameters.morphColors; materialProperties.morphTargetsCount = parameters.morphTargetsCount; materialProperties.numClippingPlanes = parameters.numClippingPlanes; materialProperties.numIntersection = parameters.numClipIntersection; materialProperties.vertexAlphas = parameters.vertexAlphas; materialProperties.vertexTangents = parameters.vertexTangents; materialProperties.toneMapping = parameters.toneMapping; } function setProgram( camera, scene, geometry, material, object ) { if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... textures.resetTextureUnits(); const fog = scene.fog; const environment = material.isMeshStandardMaterial ? scene.environment : null; const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); const morphTargets = !! geometry.morphAttributes.position; const morphNormals = !! geometry.morphAttributes.normal; const morphColors = !! geometry.morphAttributes.color; let toneMapping = NoToneMapping; if ( material.toneMapped ) { if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { toneMapping = _this.toneMapping; } } const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; const materialProperties = properties.get( material ); const lights = currentRenderState.state.lights; if ( _clippingEnabled === true ) { if ( _localClippingEnabled === true || camera !== _currentCamera ) { const useCache = camera === _currentCamera && material.id === _currentMaterialId; // we might want to call this function with some ClippingGroup // object instead of the material, once it becomes feasible // (#8465, #8379) clipping.setState( material, camera, useCache ); } } // let needsProgramChange = false; if ( material.version === materialProperties.__version ) { if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { needsProgramChange = true; } else if ( materialProperties.outputColorSpace !== colorSpace ) { needsProgramChange = true; } else if ( object.isBatchedMesh && materialProperties.batching === false ) { needsProgramChange = true; } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { needsProgramChange = true; } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { needsProgramChange = true; } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { needsProgramChange = true; } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { needsProgramChange = true; } else if ( materialProperties.envMap !== envMap ) { needsProgramChange = true; } else if ( material.fog === true && materialProperties.fog !== fog ) { needsProgramChange = true; } else if ( materialProperties.numClippingPlanes !== undefined && ( materialProperties.numClippingPlanes !== clipping.numPlanes || materialProperties.numIntersection !== clipping.numIntersection ) ) { needsProgramChange = true; } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { needsProgramChange = true; } else if ( materialProperties.vertexTangents !== vertexTangents ) { needsProgramChange = true; } else if ( materialProperties.morphTargets !== morphTargets ) { needsProgramChange = true; } else if ( materialProperties.morphNormals !== morphNormals ) { needsProgramChange = true; } else if ( materialProperties.morphColors !== morphColors ) { needsProgramChange = true; } else if ( materialProperties.toneMapping !== toneMapping ) { needsProgramChange = true; } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { needsProgramChange = true; } } else { needsProgramChange = true; materialProperties.__version = material.version; } // let program = materialProperties.currentProgram; if ( needsProgramChange === true ) { program = getProgram( material, scene, object ); } let refreshProgram = false; let refreshMaterial = false; let refreshLights = false; const p_uniforms = program.getUniforms(), m_uniforms = materialProperties.uniforms; if ( state.useProgram( program.program ) ) { refreshProgram = true; refreshMaterial = true; refreshLights = true; } if ( material.id !== _currentMaterialId ) { _currentMaterialId = material.id; refreshMaterial = true; } if ( refreshProgram || _currentCamera !== camera ) { // common camera uniforms p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); const uCamPos = p_uniforms.map.cameraPosition; if ( uCamPos !== undefined ) { uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); } if ( capabilities.logarithmicDepthBuffer ) { p_uniforms.setValue( _gl, 'logDepthBufFC', 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); } // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 if ( material.isMeshPhongMaterial || material.isMeshToonMaterial || material.isMeshLambertMaterial || material.isMeshBasicMaterial || material.isMeshStandardMaterial || material.isShaderMaterial ) { p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); } if ( _currentCamera !== camera ) { _currentCamera = camera; // lighting uniforms depend on the camera so enforce an update // now, in case this material supports lights - or later, when // the next material that does gets activated: refreshMaterial = true; // set to true on material change refreshLights = true; // remains set until update done } } // skinning and morph target uniforms must be set even if material didn't change // auto-setting of texture unit for bone and morph texture must go before other textures // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures if ( object.isSkinnedMesh ) { p_uniforms.setOptional( _gl, object, 'bindMatrix' ); p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); const skeleton = object.skeleton; if ( skeleton ) { if ( capabilities.floatVertexTextures ) { if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); } else { console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); } } } if ( object.isBatchedMesh ) { p_uniforms.setOptional( _gl, object, 'batchingTexture' ); p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); } const morphAttributes = geometry.morphAttributes; if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { morphtargets.update( object, geometry, program ); } if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { materialProperties.receiveShadow = object.receiveShadow; p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); } // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 if ( material.isMeshGouraudMaterial && material.envMap !== null ) { m_uniforms.envMap.value = envMap; m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; } if ( refreshMaterial ) { p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); if ( materialProperties.needsLights ) { // the current material requires lighting info // note: all lighting uniforms are always set correctly // they simply reference the renderer's state for their // values // // use the current material's .needsUpdate flags to set // the GL state when required markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); } // refresh uniforms common to several materials if ( fog && material.fog === true ) { materials.refreshFogUniforms( m_uniforms, fog ); } materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); } if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); material.uniformsNeedUpdate = false; } if ( material.isSpriteMaterial ) { p_uniforms.setValue( _gl, 'center', object.center ); } // common matrices p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); // UBOs if ( material.isShaderMaterial || material.isRawShaderMaterial ) { const groups = material.uniformsGroups; for ( let i = 0, l = groups.length; i < l; i ++ ) { if ( capabilities.isWebGL2 ) { const group = groups[ i ]; uniformsGroups.update( group, program ); uniformsGroups.bind( group, program ); } else { console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); } } } return program; } // If uniforms are marked as clean, they don't need to be loaded to the GPU. function markUniformsLightsNeedsUpdate( uniforms, value ) { uniforms.ambientLightColor.needsUpdate = value; uniforms.lightProbe.needsUpdate = value; uniforms.directionalLights.needsUpdate = value; uniforms.directionalLightShadows.needsUpdate = value; uniforms.pointLights.needsUpdate = value; uniforms.pointLightShadows.needsUpdate = value; uniforms.spotLights.needsUpdate = value; uniforms.spotLightShadows.needsUpdate = value; uniforms.rectAreaLights.needsUpdate = value; uniforms.hemisphereLights.needsUpdate = value; } function materialNeedsLights( material ) { return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial || material.isShadowMaterial || ( material.isShaderMaterial && material.lights === true ); } this.getActiveCubeFace = function () { return _currentActiveCubeFace; }; this.getActiveMipmapLevel = function () { return _currentActiveMipmapLevel; }; this.getRenderTarget = function () { return _currentRenderTarget; }; this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { properties.get( renderTarget.texture ).__webglTexture = colorTexture; properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; const renderTargetProperties = properties.get( renderTarget ); renderTargetProperties.__hasExternalTextures = true; if ( renderTargetProperties.__hasExternalTextures ) { renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); renderTargetProperties.__useRenderToTexture = false; } } } }; this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { const renderTargetProperties = properties.get( renderTarget ); renderTargetProperties.__webglFramebuffer = defaultFramebuffer; renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; }; this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { _currentRenderTarget = renderTarget; _currentActiveCubeFace = activeCubeFace; _currentActiveMipmapLevel = activeMipmapLevel; let useDefaultFramebuffer = true; let framebuffer = null; let isCube = false; let isRenderTarget3D = false; if ( renderTarget ) { const renderTargetProperties = properties.get( renderTarget ); if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { // We need to make sure to rebind the framebuffer. state.bindFramebuffer( _gl.FRAMEBUFFER, null ); useDefaultFramebuffer = false; } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { textures.setupRenderTarget( renderTarget ); } else if ( renderTargetProperties.__hasExternalTextures ) { // Color and depth texture must be rebound in order for the swapchain to update. textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); } const texture = renderTarget.texture; if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { isRenderTarget3D = true; } const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( renderTarget.isWebGLCubeRenderTarget ) { if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; } else { framebuffer = __webglFramebuffer[ activeCubeFace ]; } isCube = true; } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; } else { if ( Array.isArray( __webglFramebuffer ) ) { framebuffer = __webglFramebuffer[ activeMipmapLevel ]; } else { framebuffer = __webglFramebuffer; } } _currentViewport.copy( renderTarget.viewport ); _currentScissor.copy( renderTarget.scissor ); _currentScissorTest = renderTarget.scissorTest; } else { _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); _currentScissorTest = _scissorTest; } const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { state.drawBuffers( renderTarget, framebuffer ); } state.viewport( _currentViewport ); state.scissor( _currentScissor ); state.setScissorTest( _currentScissorTest ); if ( isCube ) { const textureProperties = properties.get( renderTarget.texture ); _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); } else if ( isRenderTarget3D ) { const textureProperties = properties.get( renderTarget.texture ); const layer = activeCubeFace || 0; _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); } _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings }; this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); return; } let framebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { framebuffer = framebuffer[ activeCubeFaceIndex ]; } if ( framebuffer ) { state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); try { const texture = renderTarget.texture; const textureFormat = texture.format; const textureType = texture.type; if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); return; } const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox ! halfFloatSupportedByExt ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); return; } // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); } } finally { // restore framebuffer of current render target if necessary const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); } } }; this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { const levelScale = Math.pow( 2, - level ); const width = Math.floor( texture.image.width * levelScale ); const height = Math.floor( texture.image.height * levelScale ); textures.setTexture2D( texture, 0 ); _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); state.unbindTexture(); }; this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { const width = srcTexture.image.width; const height = srcTexture.image.height; const glFormat = utils.convert( dstTexture.format ); const glType = utils.convert( dstTexture.type ); textures.setTexture2D( dstTexture, 0 ); // As another texture upload may have changed pixelStorei // parameters, make sure they are correct for the dstTexture _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); if ( srcTexture.isDataTexture ) { _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); } else { if ( srcTexture.isCompressedTexture ) { _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); } else { _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); } } // Generate mipmaps only when copying level 0 if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); state.unbindTexture(); }; this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { if ( _this.isWebGL1Renderer ) { console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); return; } const width = sourceBox.max.x - sourceBox.min.x + 1; const height = sourceBox.max.y - sourceBox.min.y + 1; const depth = sourceBox.max.z - sourceBox.min.z + 1; const glFormat = utils.convert( dstTexture.format ); const glType = utils.convert( dstTexture.type ); let glTarget; if ( dstTexture.isData3DTexture ) { textures.setTexture3D( dstTexture, 0 ); glTarget = _gl.TEXTURE_3D; } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { textures.setTexture2DArray( dstTexture, 0 ); glTarget = _gl.TEXTURE_2D_ARRAY; } else { console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); return; } _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ level ] : srcTexture.image; _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); } else { if ( srcTexture.isCompressedArrayTexture ) { console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' ); _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); } else { _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); } } _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); // Generate mipmaps only when copying level 0 if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); state.unbindTexture(); }; this.initTexture = function ( texture ) { if ( texture.isCubeTexture ) { textures.setTextureCube( texture, 0 ); } else if ( texture.isData3DTexture ) { textures.setTexture3D( texture, 0 ); } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { textures.setTexture2DArray( texture, 0 ); } else { textures.setTexture2D( texture, 0 ); } state.unbindTexture(); }; this.resetState = function () { _currentActiveCubeFace = 0; _currentActiveMipmapLevel = 0; _currentRenderTarget = null; state.reset(); bindingStates.reset(); }; if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); } } get coordinateSystem() { return WebGLCoordinateSystem; } get outputColorSpace() { return this._outputColorSpace; } set outputColorSpace( colorSpace ) { this._outputColorSpace = colorSpace; const gl = this.getContext(); gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; } get outputEncoding() { // @deprecated, r152 console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); return this.outputColorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; } set outputEncoding( encoding ) { // @deprecated, r152 console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); this.outputColorSpace = encoding === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace; } get useLegacyLights() { // @deprecated, r155 console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); return this._useLegacyLights; } set useLegacyLights( value ) { // @deprecated, r155 console.warn( 'THREE.WebGLRenderer: The property .useLegacyLights has been deprecated. Migrate your lighting according to the following guide: https://discourse.threejs.org/t/updates-to-lighting-in-three-js-r155/53733.' ); this._useLegacyLights = value; } } class WebGL1Renderer extends WebGLRenderer {} WebGL1Renderer.prototype.isWebGL1Renderer = true; class FogExp2 { constructor( color, density = 0.00025 ) { this.isFogExp2 = true; this.name = ''; this.color = new Color( color ); this.density = density; } clone() { return new FogExp2( this.color, this.density ); } toJSON( /* meta */ ) { return { type: 'FogExp2', name: this.name, color: this.color.getHex(), density: this.density }; } } class Fog { constructor( color, near = 1, far = 1000 ) { this.isFog = true; this.name = ''; this.color = new Color( color ); this.near = near; this.far = far; } clone() { return new Fog( this.color, this.near, this.far ); } toJSON( /* meta */ ) { return { type: 'Fog', name: this.name, color: this.color.getHex(), near: this.near, far: this.far }; } } class Scene extends Object3D { constructor() { super(); this.isScene = true; this.type = 'Scene'; this.background = null; this.environment = null; this.fog = null; this.backgroundBlurriness = 0; this.backgroundIntensity = 1; this.overrideMaterial = null; if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); } } copy( source, recursive ) { super.copy( source, recursive ); if ( source.background !== null ) this.background = source.background.clone(); if ( source.environment !== null ) this.environment = source.environment.clone(); if ( source.fog !== null ) this.fog = source.fog.clone(); this.backgroundBlurriness = source.backgroundBlurriness; this.backgroundIntensity = source.backgroundIntensity; if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); this.matrixAutoUpdate = source.matrixAutoUpdate; return this; } toJSON( meta ) { const data = super.toJSON( meta ); if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; return data; } } class InterleavedBuffer { constructor( array, stride ) { this.isInterleavedBuffer = true; this.array = array; this.stride = stride; this.count = array !== undefined ? array.length / stride : 0; this.usage = StaticDrawUsage; this._updateRange = { offset: 0, count: - 1 }; this.updateRanges = []; this.version = 0; this.uuid = generateUUID(); } onUploadCallback() {} set needsUpdate( value ) { if ( value === true ) this.version ++; } get updateRange() { console.warn( 'THREE.InterleavedBuffer: updateRange() is deprecated and will be removed in r169. Use addUpdateRange() instead.' ); // @deprecated, r159 return this._updateRange; } setUsage( value ) { this.usage = value; return this; } addUpdateRange( start, count ) { this.updateRanges.push( { start, count } ); } clearUpdateRanges() { this.updateRanges.length = 0; } copy( source ) { this.array = new source.array.constructor( source.array ); this.count = source.count; this.stride = source.stride; this.usage = source.usage; return this; } copyAt( index1, attribute, index2 ) { index1 *= this.stride; index2 *= attribute.stride; for ( let i = 0, l = this.stride; i < l; i ++ ) { this.array[ index1 + i ] = attribute.array[ index2 + i ]; } return this; } set( value, offset = 0 ) { this.array.set( value, offset ); return this; } clone( data ) { if ( data.arrayBuffers === undefined ) { data.arrayBuffers = {}; } if ( this.array.buffer._uuid === undefined ) { this.array.buffer._uuid = generateUUID(); } if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; } const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); const ib = new this.constructor( array, this.stride ); ib.setUsage( this.usage ); return ib; } onUpload( callback ) { this.onUploadCallback = callback; return this; } toJSON( data ) { if ( data.arrayBuffers === undefined ) { data.arrayBuffers = {}; } // generate UUID for array buffer if necessary if ( this.array.buffer._uuid === undefined ) { this.array.buffer._uuid = generateUUID(); } if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); } // return { uuid: this.uuid, buffer: this.array.buffer._uuid, type: this.array.constructor.name, stride: this.stride }; } } const _vector$6 = /*@__PURE__*/ new Vector3(); class InterleavedBufferAttribute { constructor( interleavedBuffer, itemSize, offset, normalized = false ) { this.isInterleavedBufferAttribute = true; this.name = ''; this.data = interleavedBuffer; this.itemSize = itemSize; this.offset = offset; this.normalized = normalized; } get count() { return this.data.count; } get array() { return this.data.array; } set needsUpdate( value ) { this.data.needsUpdate = value; } applyMatrix4( m ) { for ( let i = 0, l = this.data.count; i < l; i ++ ) { _vector$6.fromBufferAttribute( this, i ); _vector$6.applyMatrix4( m ); this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z ); } return this; } applyNormalMatrix( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$6.fromBufferAttribute( this, i ); _vector$6.applyNormalMatrix( m ); this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z ); } return this; } transformDirection( m ) { for ( let i = 0, l = this.count; i < l; i ++ ) { _vector$6.fromBufferAttribute( this, i ); _vector$6.transformDirection( m ); this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z ); } return this; } setX( index, x ) { if ( this.normalized ) x = normalize( x, this.array ); this.data.array[ index * this.data.stride + this.offset ] = x; return this; } setY( index, y ) { if ( this.normalized ) y = normalize( y, this.array ); this.data.array[ index * this.data.stride + this.offset + 1 ] = y; return this; } setZ( index, z ) { if ( this.normalized ) z = normalize( z, this.array ); this.data.array[ index * this.data.stride + this.offset + 2 ] = z; return this; } setW( index, w ) { if ( this.normalized ) w = normalize( w, this.array ); this.data.array[ index * this.data.stride + this.offset + 3 ] = w; return this; } getX( index ) { let x = this.data.array[ index * this.data.stride + this.offset ]; if ( this.normalized ) x = denormalize( x, this.array ); return x; } getY( index ) { let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; if ( this.normalized ) y = denormalize( y, this.array ); return y; } getZ( index ) { let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; if ( this.normalized ) z = denormalize( z, this.array ); return z; } getW( index ) { let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; if ( this.normalized ) w = denormalize( w, this.array ); return w; } setXY( index, x, y ) { index = index * this.data.stride + this.offset; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); } this.data.array[ index + 0 ] = x; this.data.array[ index + 1 ] = y; return this; } setXYZ( index, x, y, z ) { index = index * this.data.stride + this.offset; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); } this.data.array[ index + 0 ] = x; this.data.array[ index + 1 ] = y; this.data.array[ index + 2 ] = z; return this; } setXYZW( index, x, y, z, w ) { index = index * this.data.stride + this.offset; if ( this.normalized ) { x = normalize( x, this.array ); y = normalize( y, this.array ); z = normalize( z, this.array ); w = normalize( w, this.array ); } this.data.array[ index + 0 ] = x; this.data.array[ index + 1 ] = y; this.data.array[ index + 2 ] = z; this.data.array[ index + 3 ] = w; return this; } clone( data ) { if ( data === undefined ) { console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); const array = []; for ( let i = 0; i < this.count; i ++ ) { const index = i * this.data.stride + this.offset; for ( let j = 0; j < this.itemSize; j ++ ) { array.push( this.data.array[ index + j ] ); } } return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); } else { if ( data.interleavedBuffers === undefined ) { data.interleavedBuffers = {}; } if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); } return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); } } toJSON( data ) { if ( data === undefined ) { console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); const array = []; for ( let i = 0; i < this.count; i ++ ) { const index = i * this.data.stride + this.offset; for ( let j = 0; j < this.itemSize; j ++ ) { array.push( this.data.array[ index + j ] ); } } // de-interleave data and save it as an ordinary buffer attribute for now return { itemSize: this.itemSize, type: this.array.constructor.name, array: array, normalized: this.normalized }; } else { // save as true interleaved attribute if ( data.interleavedBuffers === undefined ) { data.interleavedBuffers = {}; } if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); } return { isInterleavedBufferAttribute: true, itemSize: this.itemSize, data: this.data.uuid, offset: this.offset, normalized: this.normalized }; } } } class SpriteMaterial extends Material { constructor( parameters ) { super(); this.isSpriteMaterial = true; this.type = 'SpriteMaterial'; this.color = new Color( 0xffffff ); this.map = null; this.alphaMap = null; this.rotation = 0; this.sizeAttenuation = true; this.transparent = true; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.alphaMap = source.alphaMap; this.rotation = source.rotation; this.sizeAttenuation = source.sizeAttenuation; this.fog = source.fog; return this; } } let _geometry; const _intersectPoint = /*@__PURE__*/ new Vector3(); const _worldScale = /*@__PURE__*/ new Vector3(); const _mvPosition = /*@__PURE__*/ new Vector3(); const _alignedPosition = /*@__PURE__*/ new Vector2(); const _rotatedPosition = /*@__PURE__*/ new Vector2(); const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); const _vA = /*@__PURE__*/ new Vector3(); const _vB = /*@__PURE__*/ new Vector3(); const _vC = /*@__PURE__*/ new Vector3(); const _uvA = /*@__PURE__*/ new Vector2(); const _uvB = /*@__PURE__*/ new Vector2(); const _uvC = /*@__PURE__*/ new Vector2(); class Sprite extends Object3D { constructor( material = new SpriteMaterial() ) { super(); this.isSprite = true; this.type = 'Sprite'; if ( _geometry === undefined ) { _geometry = new BufferGeometry(); const float32Array = new Float32Array( [ - 0.5, - 0.5, 0, 0, 0, 0.5, - 0.5, 0, 1, 0, 0.5, 0.5, 0, 1, 1, - 0.5, 0.5, 0, 0, 1 ] ); const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); } this.geometry = _geometry; this.material = material; this.center = new Vector2( 0.5, 0.5 ); } raycast( raycaster, intersects ) { if ( raycaster.camera === null ) { console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); } _worldScale.setFromMatrixScale( this.matrixWorld ); _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { _worldScale.multiplyScalar( - _mvPosition.z ); } const rotation = this.material.rotation; let sin, cos; if ( rotation !== 0 ) { cos = Math.cos( rotation ); sin = Math.sin( rotation ); } const center = this.center; transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); _uvA.set( 0, 0 ); _uvB.set( 1, 0 ); _uvC.set( 1, 1 ); // check first triangle let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); if ( intersect === null ) { // check second triangle transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); _uvB.set( 0, 1 ); intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); if ( intersect === null ) { return; } } const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); if ( distance < raycaster.near || distance > raycaster.far ) return; intersects.push( { distance: distance, point: _intersectPoint.clone(), uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), face: null, object: this } ); } copy( source, recursive ) { super.copy( source, recursive ); if ( source.center !== undefined ) this.center.copy( source.center ); this.material = source.material; return this; } } function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { // compute position in camera space _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); // to check if rotation is not zero if ( sin !== undefined ) { _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); } else { _rotatedPosition.copy( _alignedPosition ); } vertexPosition.copy( mvPosition ); vertexPosition.x += _rotatedPosition.x; vertexPosition.y += _rotatedPosition.y; // transform to world space vertexPosition.applyMatrix4( _viewWorldMatrix ); } const _v1$2 = /*@__PURE__*/ new Vector3(); const _v2$1 = /*@__PURE__*/ new Vector3(); class LOD extends Object3D { constructor() { super(); this._currentLevel = 0; this.type = 'LOD'; Object.defineProperties( this, { levels: { enumerable: true, value: [] }, isLOD: { value: true, } } ); this.autoUpdate = true; } copy( source ) { super.copy( source, false ); const levels = source.levels; for ( let i = 0, l = levels.length; i < l; i ++ ) { const level = levels[ i ]; this.addLevel( level.object.clone(), level.distance, level.hysteresis ); } this.autoUpdate = source.autoUpdate; return this; } addLevel( object, distance = 0, hysteresis = 0 ) { distance = Math.abs( distance ); const levels = this.levels; let l; for ( l = 0; l < levels.length; l ++ ) { if ( distance < levels[ l ].distance ) { break; } } levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); this.add( object ); return this; } getCurrentLevel() { return this._currentLevel; } getObjectForDistance( distance ) { const levels = this.levels; if ( levels.length > 0 ) { let i, l; for ( i = 1, l = levels.length; i < l; i ++ ) { let levelDistance = levels[ i ].distance; if ( levels[ i ].object.visible ) { levelDistance -= levelDistance * levels[ i ].hysteresis; } if ( distance < levelDistance ) { break; } } return levels[ i - 1 ].object; } return null; } raycast( raycaster, intersects ) { const levels = this.levels; if ( levels.length > 0 ) { _v1$2.setFromMatrixPosition( this.matrixWorld ); const distance = raycaster.ray.origin.distanceTo( _v1$2 ); this.getObjectForDistance( distance ).raycast( raycaster, intersects ); } } update( camera ) { const levels = this.levels; if ( levels.length > 1 ) { _v1$2.setFromMatrixPosition( camera.matrixWorld ); _v2$1.setFromMatrixPosition( this.matrixWorld ); const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; levels[ 0 ].object.visible = true; let i, l; for ( i = 1, l = levels.length; i < l; i ++ ) { let levelDistance = levels[ i ].distance; if ( levels[ i ].object.visible ) { levelDistance -= levelDistance * levels[ i ].hysteresis; } if ( distance >= levelDistance ) { levels[ i - 1 ].object.visible = false; levels[ i ].object.visible = true; } else { break; } } this._currentLevel = i - 1; for ( ; i < l; i ++ ) { levels[ i ].object.visible = false; } } } toJSON( meta ) { const data = super.toJSON( meta ); if ( this.autoUpdate === false ) data.object.autoUpdate = false; data.object.levels = []; const levels = this.levels; for ( let i = 0, l = levels.length; i < l; i ++ ) { const level = levels[ i ]; data.object.levels.push( { object: level.object.uuid, distance: level.distance, hysteresis: level.hysteresis } ); } return data; } } const _basePosition = /*@__PURE__*/ new Vector3(); const _skinIndex = /*@__PURE__*/ new Vector4(); const _skinWeight = /*@__PURE__*/ new Vector4(); const _vector3 = /*@__PURE__*/ new Vector3(); const _matrix4 = /*@__PURE__*/ new Matrix4(); const _vertex = /*@__PURE__*/ new Vector3(); const _sphere$4 = /*@__PURE__*/ new Sphere(); const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); const _ray$2 = /*@__PURE__*/ new Ray(); class SkinnedMesh extends Mesh { constructor( geometry, material ) { super( geometry, material ); this.isSkinnedMesh = true; this.type = 'SkinnedMesh'; this.bindMode = AttachedBindMode; this.bindMatrix = new Matrix4(); this.bindMatrixInverse = new Matrix4(); this.boundingBox = null; this.boundingSphere = null; } computeBoundingBox() { const geometry = this.geometry; if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } this.boundingBox.makeEmpty(); const positionAttribute = geometry.getAttribute( 'position' ); for ( let i = 0; i < positionAttribute.count; i ++ ) { this.getVertexPosition( i, _vertex ); this.boundingBox.expandByPoint( _vertex ); } } computeBoundingSphere() { const geometry = this.geometry; if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } this.boundingSphere.makeEmpty(); const positionAttribute = geometry.getAttribute( 'position' ); for ( let i = 0; i < positionAttribute.count; i ++ ) { this.getVertexPosition( i, _vertex ); this.boundingSphere.expandByPoint( _vertex ); } } copy( source, recursive ) { super.copy( source, recursive ); this.bindMode = source.bindMode; this.bindMatrix.copy( source.bindMatrix ); this.bindMatrixInverse.copy( source.bindMatrixInverse ); this.skeleton = source.skeleton; if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); return this; } raycast( raycaster, intersects ) { const material = this.material; const matrixWorld = this.matrixWorld; if ( material === undefined ) return; // test with bounding sphere in world space if ( this.boundingSphere === null ) this.computeBoundingSphere(); _sphere$4.copy( this.boundingSphere ); _sphere$4.applyMatrix4( matrixWorld ); if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; // convert ray to local space of skinned mesh _inverseMatrix$2.copy( matrixWorld ).invert(); _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); // test with bounding box in local space if ( this.boundingBox !== null ) { if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; } // test for intersections with geometry this._computeIntersections( raycaster, intersects, _ray$2 ); } getVertexPosition( index, target ) { super.getVertexPosition( index, target ); this.applyBoneTransform( index, target ); return target; } bind( skeleton, bindMatrix ) { this.skeleton = skeleton; if ( bindMatrix === undefined ) { this.updateMatrixWorld( true ); this.skeleton.calculateInverses(); bindMatrix = this.matrixWorld; } this.bindMatrix.copy( bindMatrix ); this.bindMatrixInverse.copy( bindMatrix ).invert(); } pose() { this.skeleton.pose(); } normalizeSkinWeights() { const vector = new Vector4(); const skinWeight = this.geometry.attributes.skinWeight; for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { vector.fromBufferAttribute( skinWeight, i ); const scale = 1.0 / vector.manhattanLength(); if ( scale !== Infinity ) { vector.multiplyScalar( scale ); } else { vector.set( 1, 0, 0, 0 ); // do something reasonable } skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); } } updateMatrixWorld( force ) { super.updateMatrixWorld( force ); if ( this.bindMode === AttachedBindMode ) { this.bindMatrixInverse.copy( this.matrixWorld ).invert(); } else if ( this.bindMode === DetachedBindMode ) { this.bindMatrixInverse.copy( this.bindMatrix ).invert(); } else { console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); } } applyBoneTransform( index, vector ) { const skeleton = this.skeleton; const geometry = this.geometry; _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); _basePosition.copy( vector ).applyMatrix4( this.bindMatrix ); vector.set( 0, 0, 0 ); for ( let i = 0; i < 4; i ++ ) { const weight = _skinWeight.getComponent( i ); if ( weight !== 0 ) { const boneIndex = _skinIndex.getComponent( i ); _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); } } return vector.applyMatrix4( this.bindMatrixInverse ); } boneTransform( index, vector ) { // @deprecated, r151 console.warn( 'THREE.SkinnedMesh: .boneTransform() was renamed to .applyBoneTransform() in r151.' ); return this.applyBoneTransform( index, vector ); } } class Bone extends Object3D { constructor() { super(); this.isBone = true; this.type = 'Bone'; } } class DataTexture extends Texture { constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); this.isDataTexture = true; this.image = { data: data, width: width, height: height }; this.generateMipmaps = false; this.flipY = false; this.unpackAlignment = 1; } } const _offsetMatrix = /*@__PURE__*/ new Matrix4(); const _identityMatrix$1 = /*@__PURE__*/ new Matrix4(); class Skeleton { constructor( bones = [], boneInverses = [] ) { this.uuid = generateUUID(); this.bones = bones.slice( 0 ); this.boneInverses = boneInverses; this.boneMatrices = null; this.boneTexture = null; this.init(); } init() { const bones = this.bones; const boneInverses = this.boneInverses; this.boneMatrices = new Float32Array( bones.length * 16 ); // calculate inverse bone matrices if necessary if ( boneInverses.length === 0 ) { this.calculateInverses(); } else { // handle special case if ( bones.length !== boneInverses.length ) { console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); this.boneInverses = []; for ( let i = 0, il = this.bones.length; i < il; i ++ ) { this.boneInverses.push( new Matrix4() ); } } } } calculateInverses() { this.boneInverses.length = 0; for ( let i = 0, il = this.bones.length; i < il; i ++ ) { const inverse = new Matrix4(); if ( this.bones[ i ] ) { inverse.copy( this.bones[ i ].matrixWorld ).invert(); } this.boneInverses.push( inverse ); } } pose() { // recover the bind-time world matrices for ( let i = 0, il = this.bones.length; i < il; i ++ ) { const bone = this.bones[ i ]; if ( bone ) { bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); } } // compute the local matrices, positions, rotations and scales for ( let i = 0, il = this.bones.length; i < il; i ++ ) { const bone = this.bones[ i ]; if ( bone ) { if ( bone.parent && bone.parent.isBone ) { bone.matrix.copy( bone.parent.matrixWorld ).invert(); bone.matrix.multiply( bone.matrixWorld ); } else { bone.matrix.copy( bone.matrixWorld ); } bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); } } } update() { const bones = this.bones; const boneInverses = this.boneInverses; const boneMatrices = this.boneMatrices; const boneTexture = this.boneTexture; // flatten bone matrices to array for ( let i = 0, il = bones.length; i < il; i ++ ) { // compute the offset between the current and the original transform const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix$1; _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); _offsetMatrix.toArray( boneMatrices, i * 16 ); } if ( boneTexture !== null ) { boneTexture.needsUpdate = true; } } clone() { return new Skeleton( this.bones, this.boneInverses ); } computeBoneTexture() { // layout (1 matrix = 4 pixels) // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix size = Math.ceil( size / 4 ) * 4; size = Math.max( size, 4 ); const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel boneMatrices.set( this.boneMatrices ); // copy current values const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); boneTexture.needsUpdate = true; this.boneMatrices = boneMatrices; this.boneTexture = boneTexture; return this; } getBoneByName( name ) { for ( let i = 0, il = this.bones.length; i < il; i ++ ) { const bone = this.bones[ i ]; if ( bone.name === name ) { return bone; } } return undefined; } dispose( ) { if ( this.boneTexture !== null ) { this.boneTexture.dispose(); this.boneTexture = null; } } fromJSON( json, bones ) { this.uuid = json.uuid; for ( let i = 0, l = json.bones.length; i < l; i ++ ) { const uuid = json.bones[ i ]; let bone = bones[ uuid ]; if ( bone === undefined ) { console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); bone = new Bone(); } this.bones.push( bone ); this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); } this.init(); return this; } toJSON() { const data = { metadata: { version: 4.6, type: 'Skeleton', generator: 'Skeleton.toJSON' }, bones: [], boneInverses: [] }; data.uuid = this.uuid; const bones = this.bones; const boneInverses = this.boneInverses; for ( let i = 0, l = bones.length; i < l; i ++ ) { const bone = bones[ i ]; data.bones.push( bone.uuid ); const boneInverse = boneInverses[ i ]; data.boneInverses.push( boneInverse.toArray() ); } return data; } } class InstancedBufferAttribute extends BufferAttribute { constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { super( array, itemSize, normalized ); this.isInstancedBufferAttribute = true; this.meshPerAttribute = meshPerAttribute; } copy( source ) { super.copy( source ); this.meshPerAttribute = source.meshPerAttribute; return this; } toJSON() { const data = super.toJSON(); data.meshPerAttribute = this.meshPerAttribute; data.isInstancedBufferAttribute = true; return data; } } const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); const _instanceIntersects = []; const _box3 = /*@__PURE__*/ new Box3(); const _identity = /*@__PURE__*/ new Matrix4(); const _mesh$1 = /*@__PURE__*/ new Mesh(); const _sphere$3 = /*@__PURE__*/ new Sphere(); class InstancedMesh extends Mesh { constructor( geometry, material, count ) { super( geometry, material ); this.isInstancedMesh = true; this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); this.instanceColor = null; this.count = count; this.boundingBox = null; this.boundingSphere = null; for ( let i = 0; i < count; i ++ ) { this.setMatrixAt( i, _identity ); } } computeBoundingBox() { const geometry = this.geometry; const count = this.count; if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } if ( geometry.boundingBox === null ) { geometry.computeBoundingBox(); } this.boundingBox.makeEmpty(); for ( let i = 0; i < count; i ++ ) { this.getMatrixAt( i, _instanceLocalMatrix ); _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); this.boundingBox.union( _box3 ); } } computeBoundingSphere() { const geometry = this.geometry; const count = this.count; if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } if ( geometry.boundingSphere === null ) { geometry.computeBoundingSphere(); } this.boundingSphere.makeEmpty(); for ( let i = 0; i < count; i ++ ) { this.getMatrixAt( i, _instanceLocalMatrix ); _sphere$3.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); this.boundingSphere.union( _sphere$3 ); } } copy( source, recursive ) { super.copy( source, recursive ); this.instanceMatrix.copy( source.instanceMatrix ); if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); this.count = source.count; if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); return this; } getColorAt( index, color ) { color.fromArray( this.instanceColor.array, index * 3 ); } getMatrixAt( index, matrix ) { matrix.fromArray( this.instanceMatrix.array, index * 16 ); } raycast( raycaster, intersects ) { const matrixWorld = this.matrixWorld; const raycastTimes = this.count; _mesh$1.geometry = this.geometry; _mesh$1.material = this.material; if ( _mesh$1.material === undefined ) return; // test with bounding sphere first if ( this.boundingSphere === null ) this.computeBoundingSphere(); _sphere$3.copy( this.boundingSphere ); _sphere$3.applyMatrix4( matrixWorld ); if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; // now test each instance for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { // calculate the world matrix for each instance this.getMatrixAt( instanceId, _instanceLocalMatrix ); _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); // the mesh represents this single instance _mesh$1.matrixWorld = _instanceWorldMatrix; _mesh$1.raycast( raycaster, _instanceIntersects ); // process the result of raycast for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { const intersect = _instanceIntersects[ i ]; intersect.instanceId = instanceId; intersect.object = this; intersects.push( intersect ); } _instanceIntersects.length = 0; } } setColorAt( index, color ) { if ( this.instanceColor === null ) { this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); } color.toArray( this.instanceColor.array, index * 3 ); } setMatrixAt( index, matrix ) { matrix.toArray( this.instanceMatrix.array, index * 16 ); } updateMorphTargets() { } dispose() { this.dispatchEvent( { type: 'dispose' } ); } } function sortOpaque( a, b ) { return a.z - b.z; } function sortTransparent( a, b ) { return b.z - a.z; } class MultiDrawRenderList { constructor() { this.index = 0; this.pool = []; this.list = []; } push( drawRange, z ) { const pool = this.pool; const list = this.list; if ( this.index >= pool.length ) { pool.push( { start: - 1, count: - 1, z: - 1, } ); } const item = pool[ this.index ]; list.push( item ); this.index ++; item.start = drawRange.start; item.count = drawRange.count; item.z = z; } reset() { this.list.length = 0; this.index = 0; } } const ID_ATTR_NAME = 'batchId'; const _matrix = /*@__PURE__*/ new Matrix4(); const _invMatrixWorld = /*@__PURE__*/ new Matrix4(); const _identityMatrix = /*@__PURE__*/ new Matrix4(); const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); const _frustum = /*@__PURE__*/ new Frustum(); const _box$1 = /*@__PURE__*/ new Box3(); const _sphere$2 = /*@__PURE__*/ new Sphere(); const _vector$5 = /*@__PURE__*/ new Vector3(); const _renderList = /*@__PURE__*/ new MultiDrawRenderList(); const _mesh = /*@__PURE__*/ new Mesh(); const _batchIntersects = []; // @TODO: SkinnedMesh support? // @TODO: geometry.groups support? // @TODO: geometry.drawRange support? // @TODO: geometry.morphAttributes support? // @TODO: Support uniform parameter per geometry // @TODO: Add an "optimize" function to pack geometry and remove data gaps // copies data from attribute "src" into "target" starting at "targetOffset" function copyAttributeData( src, target, targetOffset = 0 ) { const itemSize = target.itemSize; if ( src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor ) { // use the component getters and setters if the array data cannot // be copied directly const vertexCount = src.count; for ( let i = 0; i < vertexCount; i ++ ) { for ( let c = 0; c < itemSize; c ++ ) { target.setComponent( i + targetOffset, c, src.getComponent( i, c ) ); } } } else { // faster copy approach using typed array set function target.array.set( src.array, targetOffset * itemSize ); } target.needsUpdate = true; } class BatchedMesh extends Mesh { get maxGeometryCount() { return this._maxGeometryCount; } constructor( maxGeometryCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { super( new BufferGeometry(), material ); this.isBatchedMesh = true; this.perObjectFrustumCulled = true; this.sortObjects = true; this.boundingBox = null; this.boundingSphere = null; this.customSort = null; this._drawRanges = []; this._reservedRanges = []; this._visibility = []; this._active = []; this._bounds = []; this._maxGeometryCount = maxGeometryCount; this._maxVertexCount = maxVertexCount; this._maxIndexCount = maxIndexCount; this._geometryInitialized = false; this._geometryCount = 0; this._multiDrawCounts = new Int32Array( maxGeometryCount ); this._multiDrawStarts = new Int32Array( maxGeometryCount ); this._multiDrawCount = 0; this._visibilityChanged = true; // Local matrix per geometry by using data texture this._matricesTexture = null; this._initMatricesTexture(); } _initMatricesTexture() { // layout (1 matrix = 4 pixels) // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8) // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16) // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) let size = Math.sqrt( this._maxGeometryCount * 4 ); // 4 pixels needed for 1 matrix size = Math.ceil( size / 4 ) * 4; size = Math.max( size, 4 ); const matricesArray = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel const matricesTexture = new DataTexture( matricesArray, size, size, RGBAFormat, FloatType ); this._matricesTexture = matricesTexture; } _initializeGeometry( reference ) { const geometry = this.geometry; const maxVertexCount = this._maxVertexCount; const maxGeometryCount = this._maxGeometryCount; const maxIndexCount = this._maxIndexCount; if ( this._geometryInitialized === false ) { for ( const attributeName in reference.attributes ) { const srcAttribute = reference.getAttribute( attributeName ); const { array, itemSize, normalized } = srcAttribute; const dstArray = new array.constructor( maxVertexCount * itemSize ); const dstAttribute = new srcAttribute.constructor( dstArray, itemSize, normalized ); dstAttribute.setUsage( srcAttribute.usage ); geometry.setAttribute( attributeName, dstAttribute ); } if ( reference.getIndex() !== null ) { const indexArray = maxVertexCount > 65536 ? new Uint32Array( maxIndexCount ) : new Uint16Array( maxIndexCount ); geometry.setIndex( new BufferAttribute( indexArray, 1 ) ); } const idArray = maxGeometryCount > 65536 ? new Uint32Array( maxVertexCount ) : new Uint16Array( maxVertexCount ); geometry.setAttribute( ID_ATTR_NAME, new BufferAttribute( idArray, 1 ) ); this._geometryInitialized = true; } } // Make sure the geometry is compatible with the existing combined geometry atributes _validateGeometry( geometry ) { // check that the geometry doesn't have a version of our reserved id attribute if ( geometry.getAttribute( ID_ATTR_NAME ) ) { throw new Error( `BatchedMesh: Geometry cannot use attribute "${ ID_ATTR_NAME }"` ); } // check to ensure the geometries are using consistent attributes and indices const batchGeometry = this.geometry; if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) { throw new Error( 'BatchedMesh: All geometries must consistently have "index".' ); } for ( const attributeName in batchGeometry.attributes ) { if ( attributeName === ID_ATTR_NAME ) { continue; } if ( ! geometry.hasAttribute( attributeName ) ) { throw new Error( `BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); } const srcAttribute = geometry.getAttribute( attributeName ); const dstAttribute = batchGeometry.getAttribute( attributeName ); if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) { throw new Error( 'BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); } } } setCustomSort( func ) { this.customSort = func; return this; } computeBoundingBox() { if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } const geometryCount = this._geometryCount; const boundingBox = this.boundingBox; const active = this._active; boundingBox.makeEmpty(); for ( let i = 0; i < geometryCount; i ++ ) { if ( active[ i ] === false ) continue; this.getMatrixAt( i, _matrix ); this.getBoundingBoxAt( i, _box$1 ).applyMatrix4( _matrix ); boundingBox.union( _box$1 ); } } computeBoundingSphere() { if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } const geometryCount = this._geometryCount; const boundingSphere = this.boundingSphere; const active = this._active; boundingSphere.makeEmpty(); for ( let i = 0; i < geometryCount; i ++ ) { if ( active[ i ] === false ) continue; this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( i, _sphere$2 ).applyMatrix4( _matrix ); boundingSphere.union( _sphere$2 ); } } addGeometry( geometry, vertexCount = - 1, indexCount = - 1 ) { this._initializeGeometry( geometry ); this._validateGeometry( geometry ); // ensure we're not over geometry if ( this._geometryCount >= this._maxGeometryCount ) { throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); } // get the necessary range fo the geometry const reservedRange = { vertexStart: - 1, vertexCount: - 1, indexStart: - 1, indexCount: - 1, }; let lastRange = null; const reservedRanges = this._reservedRanges; const drawRanges = this._drawRanges; const bounds = this._bounds; if ( this._geometryCount !== 0 ) { lastRange = reservedRanges[ reservedRanges.length - 1 ]; } if ( vertexCount === - 1 ) { reservedRange.vertexCount = geometry.getAttribute( 'position' ).count; } else { reservedRange.vertexCount = vertexCount; } if ( lastRange === null ) { reservedRange.vertexStart = 0; } else { reservedRange.vertexStart = lastRange.vertexStart + lastRange.vertexCount; } const index = geometry.getIndex(); const hasIndex = index !== null; if ( hasIndex ) { if ( indexCount === - 1 ) { reservedRange.indexCount = index.count; } else { reservedRange.indexCount = indexCount; } if ( lastRange === null ) { reservedRange.indexStart = 0; } else { reservedRange.indexStart = lastRange.indexStart + lastRange.indexCount; } } if ( reservedRange.indexStart !== - 1 && reservedRange.indexStart + reservedRange.indexCount > this._maxIndexCount || reservedRange.vertexStart + reservedRange.vertexCount > this._maxVertexCount ) { throw new Error( 'BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); } const visibility = this._visibility; const active = this._active; const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; // push new visibility states visibility.push( true ); active.push( true ); // update id const geometryId = this._geometryCount; this._geometryCount ++; // initialize matrix information _identityMatrix.toArray( matricesArray, geometryId * 16 ); matricesTexture.needsUpdate = true; // add the reserved range and draw range objects reservedRanges.push( reservedRange ); drawRanges.push( { start: hasIndex ? reservedRange.indexStart : reservedRange.vertexStart, count: - 1 } ); bounds.push( { boxInitialized: false, box: new Box3(), sphereInitialized: false, sphere: new Sphere() } ); // set the id for the geometry const idAttribute = this.geometry.getAttribute( ID_ATTR_NAME ); for ( let i = 0; i < reservedRange.vertexCount; i ++ ) { idAttribute.setX( reservedRange.vertexStart + i, geometryId ); } idAttribute.needsUpdate = true; // update the geometry this.setGeometryAt( geometryId, geometry ); return geometryId; } setGeometryAt( id, geometry ) { if ( id >= this._geometryCount ) { throw new Error( 'BatchedMesh: Maximum geometry count reached.' ); } this._validateGeometry( geometry ); const batchGeometry = this.geometry; const hasIndex = batchGeometry.getIndex() !== null; const dstIndex = batchGeometry.getIndex(); const srcIndex = geometry.getIndex(); const reservedRange = this._reservedRanges[ id ]; if ( hasIndex && srcIndex.count > reservedRange.indexCount || geometry.attributes.position.count > reservedRange.vertexCount ) { throw new Error( 'BatchedMesh: Reserved space not large enough for provided geometry.' ); } // copy geometry over const vertexStart = reservedRange.vertexStart; const vertexCount = reservedRange.vertexCount; for ( const attributeName in batchGeometry.attributes ) { if ( attributeName === ID_ATTR_NAME ) { continue; } // copy attribute data const srcAttribute = geometry.getAttribute( attributeName ); const dstAttribute = batchGeometry.getAttribute( attributeName ); copyAttributeData( srcAttribute, dstAttribute, vertexStart ); // fill the rest in with zeroes const itemSize = srcAttribute.itemSize; for ( let i = srcAttribute.count, l = vertexCount; i < l; i ++ ) { const index = vertexStart + i; for ( let c = 0; c < itemSize; c ++ ) { dstAttribute.setComponent( index, c, 0 ); } } dstAttribute.needsUpdate = true; } // copy index if ( hasIndex ) { const indexStart = reservedRange.indexStart; // copy index data over for ( let i = 0; i < srcIndex.count; i ++ ) { dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) ); } // fill the rest in with zeroes for ( let i = srcIndex.count, l = reservedRange.indexCount; i < l; i ++ ) { dstIndex.setX( indexStart + i, vertexStart ); } dstIndex.needsUpdate = true; } // store the bounding boxes const bound = this._bounds[ id ]; if ( geometry.boundingBox !== null ) { bound.box.copy( geometry.boundingBox ); bound.boxInitialized = true; } else { bound.boxInitialized = false; } if ( geometry.boundingSphere !== null ) { bound.sphere.copy( geometry.boundingSphere ); bound.sphereInitialized = true; } else { bound.sphereInitialized = false; } // set drawRange count const drawRange = this._drawRanges[ id ]; const posAttr = geometry.getAttribute( 'position' ); drawRange.count = hasIndex ? srcIndex.count : posAttr.count; this._visibilityChanged = true; return id; } deleteGeometry( geometryId ) { // Note: User needs to call optimize() afterward to pack the data. const active = this._active; if ( geometryId >= active.length || active[ geometryId ] === false ) { return this; } active[ geometryId ] = false; this._visibilityChanged = true; return this; } // get bounding box and compute it if it doesn't exist getBoundingBoxAt( id, target ) { const active = this._active; if ( active[ id ] === false ) { return this; } // compute bounding box const bound = this._bounds[ id ]; const box = bound.box; const geometry = this.geometry; if ( bound.boxInitialized === false ) { box.makeEmpty(); const index = geometry.index; const position = geometry.attributes.position; const drawRange = this._drawRanges[ id ]; for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { let iv = i; if ( index ) { iv = index.getX( iv ); } box.expandByPoint( _vector$5.fromBufferAttribute( position, iv ) ); } bound.boxInitialized = true; } target.copy( box ); return target; } // get bounding sphere and compute it if it doesn't exist getBoundingSphereAt( id, target ) { const active = this._active; if ( active[ id ] === false ) { return this; } // compute bounding sphere const bound = this._bounds[ id ]; const sphere = bound.sphere; const geometry = this.geometry; if ( bound.sphereInitialized === false ) { sphere.makeEmpty(); this.getBoundingBoxAt( id, _box$1 ); _box$1.getCenter( sphere.center ); const index = geometry.index; const position = geometry.attributes.position; const drawRange = this._drawRanges[ id ]; let maxRadiusSq = 0; for ( let i = drawRange.start, l = drawRange.start + drawRange.count; i < l; i ++ ) { let iv = i; if ( index ) { iv = index.getX( iv ); } _vector$5.fromBufferAttribute( position, iv ); maxRadiusSq = Math.max( maxRadiusSq, sphere.center.distanceToSquared( _vector$5 ) ); } sphere.radius = Math.sqrt( maxRadiusSq ); bound.sphereInitialized = true; } target.copy( sphere ); return target; } setMatrixAt( geometryId, matrix ) { // @TODO: Map geometryId to index of the arrays because // optimize() can make geometryId mismatch the index const active = this._active; const matricesTexture = this._matricesTexture; const matricesArray = this._matricesTexture.image.data; const geometryCount = this._geometryCount; if ( geometryId >= geometryCount || active[ geometryId ] === false ) { return this; } matrix.toArray( matricesArray, geometryId * 16 ); matricesTexture.needsUpdate = true; return this; } getMatrixAt( geometryId, matrix ) { const active = this._active; const matricesArray = this._matricesTexture.image.data; const geometryCount = this._geometryCount; if ( geometryId >= geometryCount || active[ geometryId ] === false ) { return null; } return matrix.fromArray( matricesArray, geometryId * 16 ); } setVisibleAt( geometryId, value ) { const visibility = this._visibility; const active = this._active; const geometryCount = this._geometryCount; // if the geometry is out of range, not active, or visibility state // does not change then return early if ( geometryId >= geometryCount || active[ geometryId ] === false || visibility[ geometryId ] === value ) { return this; } visibility[ geometryId ] = value; this._visibilityChanged = true; return this; } getVisibleAt( geometryId ) { const visibility = this._visibility; const active = this._active; const geometryCount = this._geometryCount; // return early if the geometry is out of range or not active if ( geometryId >= geometryCount || active[ geometryId ] === false ) { return false; } return visibility[ geometryId ]; } raycast( raycaster, intersects ) { const visibility = this._visibility; const active = this._active; const drawRanges = this._drawRanges; const geometryCount = this._geometryCount; const matrixWorld = this.matrixWorld; const batchGeometry = this.geometry; // iterate over each geometry _mesh.material = this.material; _mesh.geometry.index = batchGeometry.index; _mesh.geometry.attributes = batchGeometry.attributes; if ( _mesh.geometry.boundingBox === null ) { _mesh.geometry.boundingBox = new Box3(); } if ( _mesh.geometry.boundingSphere === null ) { _mesh.geometry.boundingSphere = new Sphere(); } for ( let i = 0; i < geometryCount; i ++ ) { if ( ! visibility[ i ] || ! active[ i ] ) { continue; } const drawRange = drawRanges[ i ]; _mesh.geometry.setDrawRange( drawRange.start, drawRange.count ); // ge the intersects this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); this.getBoundingBoxAt( i, _mesh.geometry.boundingBox ); this.getBoundingSphereAt( i, _mesh.geometry.boundingSphere ); _mesh.raycast( raycaster, _batchIntersects ); // add batch id to the intersects for ( let j = 0, l = _batchIntersects.length; j < l; j ++ ) { const intersect = _batchIntersects[ j ]; intersect.object = this; intersect.batchId = i; intersects.push( intersect ); } _batchIntersects.length = 0; } _mesh.material = null; _mesh.geometry.index = null; _mesh.geometry.attributes = {}; _mesh.geometry.setDrawRange( 0, Infinity ); } copy( source ) { super.copy( source ); this.geometry = source.geometry.clone(); this.perObjectFrustumCulled = source.perObjectFrustumCulled; this.sortObjects = source.sortObjects; this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; this._drawRanges = source._drawRanges.map( range => ( { ...range } ) ); this._reservedRanges = source._reservedRanges.map( range => ( { ...range } ) ); this._visibility = source._visibility.slice(); this._active = source._active.slice(); this._bounds = source._bounds.map( bound => ( { boxInitialized: bound.boxInitialized, box: bound.box.clone(), sphereInitialized: bound.sphereInitialized, sphere: bound.sphere.clone() } ) ); this._maxGeometryCount = source._maxGeometryCount; this._maxVertexCount = source._maxVertexCount; this._maxIndexCount = source._maxIndexCount; this._geometryInitialized = source._geometryInitialized; this._geometryCount = source._geometryCount; this._multiDrawCounts = source._multiDrawCounts.slice(); this._multiDrawStarts = source._multiDrawStarts.slice(); this._matricesTexture = source._matricesTexture.clone(); this._matricesTexture.image.data = this._matricesTexture.image.slice(); return this; } dispose() { // Assuming the geometry is not shared with other meshes this.geometry.dispose(); this._matricesTexture.dispose(); this._matricesTexture = null; return this; } onBeforeRender( renderer, scene, camera, geometry, material/*, _group*/ ) { // if visibility has not changed and frustum culling and object sorting is not required // then skip iterating over all items if ( ! this._visibilityChanged && ! this.perObjectFrustumCulled && ! this.sortObjects ) { return; } // the indexed version of the multi draw function requires specifying the start // offset in bytes. const index = geometry.getIndex(); const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; const visibility = this._visibility; const multiDrawStarts = this._multiDrawStarts; const multiDrawCounts = this._multiDrawCounts; const drawRanges = this._drawRanges; const perObjectFrustumCulled = this.perObjectFrustumCulled; // prepare the frustum in the local frame if ( perObjectFrustumCulled ) { _projScreenMatrix$2 .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) .multiply( this.matrixWorld ); _frustum.setFromProjectionMatrix( _projScreenMatrix$2, renderer.isWebGPURenderer ? WebGPUCoordinateSystem : WebGLCoordinateSystem ); } let count = 0; if ( this.sortObjects ) { // get the camera position in the local frame _invMatrixWorld.copy( this.matrixWorld ).invert(); _vector$5.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _invMatrixWorld ); for ( let i = 0, l = visibility.length; i < l; i ++ ) { if ( visibility[ i ] ) { // get the bounds in world space this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( i, _sphere$2 ).applyMatrix4( _matrix ); // determine whether the batched geometry is within the frustum let culled = false; if ( perObjectFrustumCulled ) { culled = ! _frustum.intersectsSphere( _sphere$2 ); } if ( ! culled ) { // get the distance from camera used for sorting const z = _vector$5.distanceTo( _sphere$2.center ); _renderList.push( drawRanges[ i ], z ); } } } // Sort the draw ranges and prep for rendering const list = _renderList.list; const customSort = this.customSort; if ( customSort === null ) { list.sort( material.transparent ? sortTransparent : sortOpaque ); } else { customSort.call( this, list, camera ); } for ( let i = 0, l = list.length; i < l; i ++ ) { const item = list[ i ]; multiDrawStarts[ count ] = item.start * bytesPerElement; multiDrawCounts[ count ] = item.count; count ++; } _renderList.reset(); } else { for ( let i = 0, l = visibility.length; i < l; i ++ ) { if ( visibility[ i ] ) { // determine whether the batched geometry is within the frustum let culled = false; if ( perObjectFrustumCulled ) { // get the bounds in world space this.getMatrixAt( i, _matrix ); this.getBoundingSphereAt( i, _sphere$2 ).applyMatrix4( _matrix ); culled = ! _frustum.intersectsSphere( _sphere$2 ); } if ( ! culled ) { const range = drawRanges[ i ]; multiDrawStarts[ count ] = range.start * bytesPerElement; multiDrawCounts[ count ] = range.count; count ++; } } } } this._multiDrawCount = count; this._visibilityChanged = false; } onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial/* , group */ ) { this.onBeforeRender( renderer, null, shadowCamera, geometry, depthMaterial ); } } class LineBasicMaterial extends Material { constructor( parameters ) { super(); this.isLineBasicMaterial = true; this.type = 'LineBasicMaterial'; this.color = new Color( 0xffffff ); this.map = null; this.linewidth = 1; this.linecap = 'round'; this.linejoin = 'round'; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.linewidth = source.linewidth; this.linecap = source.linecap; this.linejoin = source.linejoin; this.fog = source.fog; return this; } } const _start$1 = /*@__PURE__*/ new Vector3(); const _end$1 = /*@__PURE__*/ new Vector3(); const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); const _ray$1 = /*@__PURE__*/ new Ray(); const _sphere$1 = /*@__PURE__*/ new Sphere(); class Line extends Object3D { constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { super(); this.isLine = true; this.type = 'Line'; this.geometry = geometry; this.material = material; this.updateMorphTargets(); } copy( source, recursive ) { super.copy( source, recursive ); this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; this.geometry = source.geometry; return this; } computeLineDistances() { const geometry = this.geometry; // we assume non-indexed geometry if ( geometry.index === null ) { const positionAttribute = geometry.attributes.position; const lineDistances = [ 0 ]; for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { _start$1.fromBufferAttribute( positionAttribute, i - 1 ); _end$1.fromBufferAttribute( positionAttribute, i ); lineDistances[ i ] = lineDistances[ i - 1 ]; lineDistances[ i ] += _start$1.distanceTo( _end$1 ); } geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); } else { console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); } return this; } raycast( raycaster, intersects ) { const geometry = this.geometry; const matrixWorld = this.matrixWorld; const threshold = raycaster.params.Line.threshold; const drawRange = geometry.drawRange; // Checking boundingSphere distance to ray if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _sphere$1.copy( geometry.boundingSphere ); _sphere$1.applyMatrix4( matrixWorld ); _sphere$1.radius += threshold; if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; // _inverseMatrix$1.copy( matrixWorld ).invert(); _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); const localThresholdSq = localThreshold * localThreshold; const vStart = new Vector3(); const vEnd = new Vector3(); const interSegment = new Vector3(); const interRay = new Vector3(); const step = this.isLineSegments ? 2 : 1; const index = geometry.index; const attributes = geometry.attributes; const positionAttribute = attributes.position; if ( index !== null ) { const start = Math.max( 0, drawRange.start ); const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, l = end - 1; i < l; i += step ) { const a = index.getX( i ); const b = index.getX( i + 1 ); vStart.fromBufferAttribute( positionAttribute, a ); vEnd.fromBufferAttribute( positionAttribute, b ); const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); if ( distSq > localThresholdSq ) continue; interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation const distance = raycaster.ray.origin.distanceTo( interRay ); if ( distance < raycaster.near || distance > raycaster.far ) continue; intersects.push( { distance: distance, // What do we want? intersection point on the ray or on the segment?? // point: raycaster.ray.at( distance ), point: interSegment.clone().applyMatrix4( this.matrixWorld ), index: i, face: null, faceIndex: null, object: this } ); } } else { const start = Math.max( 0, drawRange.start ); const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, l = end - 1; i < l; i += step ) { vStart.fromBufferAttribute( positionAttribute, i ); vEnd.fromBufferAttribute( positionAttribute, i + 1 ); const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); if ( distSq > localThresholdSq ) continue; interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation const distance = raycaster.ray.origin.distanceTo( interRay ); if ( distance < raycaster.near || distance > raycaster.far ) continue; intersects.push( { distance: distance, // What do we want? intersection point on the ray or on the segment?? // point: raycaster.ray.at( distance ), point: interSegment.clone().applyMatrix4( this.matrixWorld ), index: i, face: null, faceIndex: null, object: this } ); } } } updateMorphTargets() { const geometry = this.geometry; const morphAttributes = geometry.morphAttributes; const keys = Object.keys( morphAttributes ); if ( keys.length > 0 ) { const morphAttribute = morphAttributes[ keys[ 0 ] ]; if ( morphAttribute !== undefined ) { this.morphTargetInfluences = []; this.morphTargetDictionary = {}; for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { const name = morphAttribute[ m ].name || String( m ); this.morphTargetInfluences.push( 0 ); this.morphTargetDictionary[ name ] = m; } } } } } const _start = /*@__PURE__*/ new Vector3(); const _end = /*@__PURE__*/ new Vector3(); class LineSegments extends Line { constructor( geometry, material ) { super( geometry, material ); this.isLineSegments = true; this.type = 'LineSegments'; } computeLineDistances() { const geometry = this.geometry; // we assume non-indexed geometry if ( geometry.index === null ) { const positionAttribute = geometry.attributes.position; const lineDistances = []; for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { _start.fromBufferAttribute( positionAttribute, i ); _end.fromBufferAttribute( positionAttribute, i + 1 ); lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); } geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); } else { console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); } return this; } } class LineLoop extends Line { constructor( geometry, material ) { super( geometry, material ); this.isLineLoop = true; this.type = 'LineLoop'; } } class PointsMaterial extends Material { constructor( parameters ) { super(); this.isPointsMaterial = true; this.type = 'PointsMaterial'; this.color = new Color( 0xffffff ); this.map = null; this.alphaMap = null; this.size = 1; this.sizeAttenuation = true; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.alphaMap = source.alphaMap; this.size = source.size; this.sizeAttenuation = source.sizeAttenuation; this.fog = source.fog; return this; } } const _inverseMatrix = /*@__PURE__*/ new Matrix4(); const _ray = /*@__PURE__*/ new Ray(); const _sphere = /*@__PURE__*/ new Sphere(); const _position$2 = /*@__PURE__*/ new Vector3(); class Points extends Object3D { constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { super(); this.isPoints = true; this.type = 'Points'; this.geometry = geometry; this.material = material; this.updateMorphTargets(); } copy( source, recursive ) { super.copy( source, recursive ); this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; this.geometry = source.geometry; return this; } raycast( raycaster, intersects ) { const geometry = this.geometry; const matrixWorld = this.matrixWorld; const threshold = raycaster.params.Points.threshold; const drawRange = geometry.drawRange; // Checking boundingSphere distance to ray if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _sphere.copy( geometry.boundingSphere ); _sphere.applyMatrix4( matrixWorld ); _sphere.radius += threshold; if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; // _inverseMatrix.copy( matrixWorld ).invert(); _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); const localThresholdSq = localThreshold * localThreshold; const index = geometry.index; const attributes = geometry.attributes; const positionAttribute = attributes.position; if ( index !== null ) { const start = Math.max( 0, drawRange.start ); const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, il = end; i < il; i ++ ) { const a = index.getX( i ); _position$2.fromBufferAttribute( positionAttribute, a ); testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); } } else { const start = Math.max( 0, drawRange.start ); const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); for ( let i = start, l = end; i < l; i ++ ) { _position$2.fromBufferAttribute( positionAttribute, i ); testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); } } } updateMorphTargets() { const geometry = this.geometry; const morphAttributes = geometry.morphAttributes; const keys = Object.keys( morphAttributes ); if ( keys.length > 0 ) { const morphAttribute = morphAttributes[ keys[ 0 ] ]; if ( morphAttribute !== undefined ) { this.morphTargetInfluences = []; this.morphTargetDictionary = {}; for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { const name = morphAttribute[ m ].name || String( m ); this.morphTargetInfluences.push( 0 ); this.morphTargetDictionary[ name ] = m; } } } } } function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { const rayPointDistanceSq = _ray.distanceSqToPoint( point ); if ( rayPointDistanceSq < localThresholdSq ) { const intersectPoint = new Vector3(); _ray.closestPointToPoint( point, intersectPoint ); intersectPoint.applyMatrix4( matrixWorld ); const distance = raycaster.ray.origin.distanceTo( intersectPoint ); if ( distance < raycaster.near || distance > raycaster.far ) return; intersects.push( { distance: distance, distanceToRay: Math.sqrt( rayPointDistanceSq ), point: intersectPoint, index: index, face: null, object: object } ); } } class VideoTexture extends Texture { constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); this.isVideoTexture = true; this.minFilter = minFilter !== undefined ? minFilter : LinearFilter; this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; this.generateMipmaps = false; const scope = this; function updateVideo() { scope.needsUpdate = true; video.requestVideoFrameCallback( updateVideo ); } if ( 'requestVideoFrameCallback' in video ) { video.requestVideoFrameCallback( updateVideo ); } } clone() { return new this.constructor( this.image ).copy( this ); } update() { const video = this.image; const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { this.needsUpdate = true; } } } class FramebufferTexture extends Texture { constructor( width, height ) { super( { width, height } ); this.isFramebufferTexture = true; this.magFilter = NearestFilter; this.minFilter = NearestFilter; this.generateMipmaps = false; this.needsUpdate = true; } } class CompressedTexture extends Texture { constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); this.isCompressedTexture = true; this.image = { width: width, height: height }; this.mipmaps = mipmaps; // no flipping for cube textures // (also flipping doesn't work for compressed textures ) this.flipY = false; // can't generate mipmaps for compressed textures // mips must be embedded in DDS files this.generateMipmaps = false; } } class CompressedArrayTexture extends CompressedTexture { constructor( mipmaps, width, height, depth, format, type ) { super( mipmaps, width, height, format, type ); this.isCompressedArrayTexture = true; this.image.depth = depth; this.wrapR = ClampToEdgeWrapping; } } class CompressedCubeTexture extends CompressedTexture { constructor( images, format, type ) { super( undefined, images[ 0 ].width, images[ 0 ].height, format, type, CubeReflectionMapping ); this.isCompressedCubeTexture = true; this.isCubeTexture = true; this.image = images; } } class CanvasTexture extends Texture { constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); this.isCanvasTexture = true; this.needsUpdate = true; } } /** * Extensible curve object. * * Some common of curve methods: * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) * .getPoints(), .getSpacedPoints() * .getLength() * .updateArcLengths() * * This following curves inherit from THREE.Curve: * * -- 2D curves -- * THREE.ArcCurve * THREE.CubicBezierCurve * THREE.EllipseCurve * THREE.LineCurve * THREE.QuadraticBezierCurve * THREE.SplineCurve * * -- 3D curves -- * THREE.CatmullRomCurve3 * THREE.CubicBezierCurve3 * THREE.LineCurve3 * THREE.QuadraticBezierCurve3 * * A series of curves can be represented as a THREE.CurvePath. * **/ class Curve { constructor() { this.type = 'Curve'; this.arcLengthDivisions = 200; } // Virtual base class method to overwrite and implement in subclasses // - t [0 .. 1] getPoint( /* t, optionalTarget */ ) { console.warn( 'THREE.Curve: .getPoint() not implemented.' ); return null; } // Get point at relative position in curve according to arc length // - u [0 .. 1] getPointAt( u, optionalTarget ) { const t = this.getUtoTmapping( u ); return this.getPoint( t, optionalTarget ); } // Get sequence of points using getPoint( t ) getPoints( divisions = 5 ) { const points = []; for ( let d = 0; d <= divisions; d ++ ) { points.push( this.getPoint( d / divisions ) ); } return points; } // Get sequence of points using getPointAt( u ) getSpacedPoints( divisions = 5 ) { const points = []; for ( let d = 0; d <= divisions; d ++ ) { points.push( this.getPointAt( d / divisions ) ); } return points; } // Get total curve arc length getLength() { const lengths = this.getLengths(); return lengths[ lengths.length - 1 ]; } // Get list of cumulative segment lengths getLengths( divisions = this.arcLengthDivisions ) { if ( this.cacheArcLengths && ( this.cacheArcLengths.length === divisions + 1 ) && ! this.needsUpdate ) { return this.cacheArcLengths; } this.needsUpdate = false; const cache = []; let current, last = this.getPoint( 0 ); let sum = 0; cache.push( 0 ); for ( let p = 1; p <= divisions; p ++ ) { current = this.getPoint( p / divisions ); sum += current.distanceTo( last ); cache.push( sum ); last = current; } this.cacheArcLengths = cache; return cache; // { sums: cache, sum: sum }; Sum is in the last element. } updateArcLengths() { this.needsUpdate = true; this.getLengths(); } // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant getUtoTmapping( u, distance ) { const arcLengths = this.getLengths(); let i = 0; const il = arcLengths.length; let targetArcLength; // The targeted u distance value to get if ( distance ) { targetArcLength = distance; } else { targetArcLength = u * arcLengths[ il - 1 ]; } // binary search for the index with largest value smaller than target u distance let low = 0, high = il - 1, comparison; while ( low <= high ) { i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats comparison = arcLengths[ i ] - targetArcLength; if ( comparison < 0 ) { low = i + 1; } else if ( comparison > 0 ) { high = i - 1; } else { high = i; break; // DONE } } i = high; if ( arcLengths[ i ] === targetArcLength ) { return i / ( il - 1 ); } // we could get finer grain at lengths, or use simple interpolation between two points const lengthBefore = arcLengths[ i ]; const lengthAfter = arcLengths[ i + 1 ]; const segmentLength = lengthAfter - lengthBefore; // determine where we are between the 'before' and 'after' points const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; // add that fractional amount to t const t = ( i + segmentFraction ) / ( il - 1 ); return t; } // Returns a unit vector tangent at t // In case any sub curve does not implement its tangent derivation, // 2 points a small delta apart will be used to find its gradient // which seems to give a reasonable approximation getTangent( t, optionalTarget ) { const delta = 0.0001; let t1 = t - delta; let t2 = t + delta; // Capping in case of danger if ( t1 < 0 ) t1 = 0; if ( t2 > 1 ) t2 = 1; const pt1 = this.getPoint( t1 ); const pt2 = this.getPoint( t2 ); const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); tangent.copy( pt2 ).sub( pt1 ).normalize(); return tangent; } getTangentAt( u, optionalTarget ) { const t = this.getUtoTmapping( u ); return this.getTangent( t, optionalTarget ); } computeFrenetFrames( segments, closed ) { // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf const normal = new Vector3(); const tangents = []; const normals = []; const binormals = []; const vec = new Vector3(); const mat = new Matrix4(); // compute the tangent vectors for each segment on the curve for ( let i = 0; i <= segments; i ++ ) { const u = i / segments; tangents[ i ] = this.getTangentAt( u, new Vector3() ); } // select an initial normal vector perpendicular to the first tangent vector, // and in the direction of the minimum tangent xyz component normals[ 0 ] = new Vector3(); binormals[ 0 ] = new Vector3(); let min = Number.MAX_VALUE; const tx = Math.abs( tangents[ 0 ].x ); const ty = Math.abs( tangents[ 0 ].y ); const tz = Math.abs( tangents[ 0 ].z ); if ( tx <= min ) { min = tx; normal.set( 1, 0, 0 ); } if ( ty <= min ) { min = ty; normal.set( 0, 1, 0 ); } if ( tz <= min ) { normal.set( 0, 0, 1 ); } vec.crossVectors( tangents[ 0 ], normal ).normalize(); normals[ 0 ].crossVectors( tangents[ 0 ], vec ); binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); // compute the slowly-varying normal and binormal vectors for each segment on the curve for ( let i = 1; i <= segments; i ++ ) { normals[ i ] = normals[ i - 1 ].clone(); binormals[ i ] = binormals[ i - 1 ].clone(); vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); if ( vec.length() > Number.EPSILON ) { vec.normalize(); const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); } binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); } // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same if ( closed === true ) { let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); theta /= segments; if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { theta = - theta; } for ( let i = 1; i <= segments; i ++ ) { // twist a little... normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); } } return { tangents: tangents, normals: normals, binormals: binormals }; } clone() { return new this.constructor().copy( this ); } copy( source ) { this.arcLengthDivisions = source.arcLengthDivisions; return this; } toJSON() { const data = { metadata: { version: 4.6, type: 'Curve', generator: 'Curve.toJSON' } }; data.arcLengthDivisions = this.arcLengthDivisions; data.type = this.type; return data; } fromJSON( json ) { this.arcLengthDivisions = json.arcLengthDivisions; return this; } } class EllipseCurve extends Curve { constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { super(); this.isEllipseCurve = true; this.type = 'EllipseCurve'; this.aX = aX; this.aY = aY; this.xRadius = xRadius; this.yRadius = yRadius; this.aStartAngle = aStartAngle; this.aEndAngle = aEndAngle; this.aClockwise = aClockwise; this.aRotation = aRotation; } getPoint( t, optionalTarget ) { const point = optionalTarget || new Vector2(); const twoPi = Math.PI * 2; let deltaAngle = this.aEndAngle - this.aStartAngle; const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; // ensures that deltaAngle is 0 .. 2 PI while ( deltaAngle < 0 ) deltaAngle += twoPi; while ( deltaAngle > twoPi ) deltaAngle -= twoPi; if ( deltaAngle < Number.EPSILON ) { if ( samePoints ) { deltaAngle = 0; } else { deltaAngle = twoPi; } } if ( this.aClockwise === true && ! samePoints ) { if ( deltaAngle === twoPi ) { deltaAngle = - twoPi; } else { deltaAngle = deltaAngle - twoPi; } } const angle = this.aStartAngle + t * deltaAngle; let x = this.aX + this.xRadius * Math.cos( angle ); let y = this.aY + this.yRadius * Math.sin( angle ); if ( this.aRotation !== 0 ) { const cos = Math.cos( this.aRotation ); const sin = Math.sin( this.aRotation ); const tx = x - this.aX; const ty = y - this.aY; // Rotate the point about the center of the ellipse. x = tx * cos - ty * sin + this.aX; y = tx * sin + ty * cos + this.aY; } return point.set( x, y ); } copy( source ) { super.copy( source ); this.aX = source.aX; this.aY = source.aY; this.xRadius = source.xRadius; this.yRadius = source.yRadius; this.aStartAngle = source.aStartAngle; this.aEndAngle = source.aEndAngle; this.aClockwise = source.aClockwise; this.aRotation = source.aRotation; return this; } toJSON() { const data = super.toJSON(); data.aX = this.aX; data.aY = this.aY; data.xRadius = this.xRadius; data.yRadius = this.yRadius; data.aStartAngle = this.aStartAngle; data.aEndAngle = this.aEndAngle; data.aClockwise = this.aClockwise; data.aRotation = this.aRotation; return data; } fromJSON( json ) { super.fromJSON( json ); this.aX = json.aX; this.aY = json.aY; this.xRadius = json.xRadius; this.yRadius = json.yRadius; this.aStartAngle = json.aStartAngle; this.aEndAngle = json.aEndAngle; this.aClockwise = json.aClockwise; this.aRotation = json.aRotation; return this; } } class ArcCurve extends EllipseCurve { constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); this.isArcCurve = true; this.type = 'ArcCurve'; } } /** * Centripetal CatmullRom Curve - which is useful for avoiding * cusps and self-intersections in non-uniform catmull rom curves. * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf * * curve.type accepts centripetal(default), chordal and catmullrom * curve.tension is used for catmullrom which defaults to 0.5 */ /* Based on an optimized c++ solution in - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - http://ideone.com/NoEbVM This CubicPoly class could be used for reusing some variables and calculations, but for three.js curve use, it could be possible inlined and flatten into a single function call which can be placed in CurveUtils. */ function CubicPoly() { let c0 = 0, c1 = 0, c2 = 0, c3 = 0; /* * Compute coefficients for a cubic polynomial * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 * such that * p(0) = x0, p(1) = x1 * and * p'(0) = t0, p'(1) = t1. */ function init( x0, x1, t0, t1 ) { c0 = x0; c1 = t0; c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; c3 = 2 * x0 - 2 * x1 + t0 + t1; } return { initCatmullRom: function ( x0, x1, x2, x3, tension ) { init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); }, initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { // compute tangents when parameterized in [t1,t2] let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; // rescale tangents for parametrization in [0,1] t1 *= dt1; t2 *= dt1; init( x1, x2, t1, t2 ); }, calc: function ( t ) { const t2 = t * t; const t3 = t2 * t; return c0 + c1 * t + c2 * t2 + c3 * t3; } }; } // const tmp = /*@__PURE__*/ new Vector3(); const px = /*@__PURE__*/ new CubicPoly(); const py = /*@__PURE__*/ new CubicPoly(); const pz = /*@__PURE__*/ new CubicPoly(); class CatmullRomCurve3 extends Curve { constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { super(); this.isCatmullRomCurve3 = true; this.type = 'CatmullRomCurve3'; this.points = points; this.closed = closed; this.curveType = curveType; this.tension = tension; } getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; const points = this.points; const l = points.length; const p = ( l - ( this.closed ? 0 : 1 ) ) * t; let intPoint = Math.floor( p ); let weight = p - intPoint; if ( this.closed ) { intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; } else if ( weight === 0 && intPoint === l - 1 ) { intPoint = l - 2; weight = 1; } let p0, p3; // 4 points (p1 & p2 defined below) if ( this.closed || intPoint > 0 ) { p0 = points[ ( intPoint - 1 ) % l ]; } else { // extrapolate first point tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); p0 = tmp; } const p1 = points[ intPoint % l ]; const p2 = points[ ( intPoint + 1 ) % l ]; if ( this.closed || intPoint + 2 < l ) { p3 = points[ ( intPoint + 2 ) % l ]; } else { // extrapolate last point tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); p3 = tmp; } if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { // init Centripetal / Chordal Catmull-Rom const pow = this.curveType === 'chordal' ? 0.5 : 0.25; let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); // safety check for repeated points if ( dt1 < 1e-4 ) dt1 = 1.0; if ( dt0 < 1e-4 ) dt0 = dt1; if ( dt2 < 1e-4 ) dt2 = dt1; px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); } else if ( this.curveType === 'catmullrom' ) { px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); } point.set( px.calc( weight ), py.calc( weight ), pz.calc( weight ) ); return point; } copy( source ) { super.copy( source ); this.points = []; for ( let i = 0, l = source.points.length; i < l; i ++ ) { const point = source.points[ i ]; this.points.push( point.clone() ); } this.closed = source.closed; this.curveType = source.curveType; this.tension = source.tension; return this; } toJSON() { const data = super.toJSON(); data.points = []; for ( let i = 0, l = this.points.length; i < l; i ++ ) { const point = this.points[ i ]; data.points.push( point.toArray() ); } data.closed = this.closed; data.curveType = this.curveType; data.tension = this.tension; return data; } fromJSON( json ) { super.fromJSON( json ); this.points = []; for ( let i = 0, l = json.points.length; i < l; i ++ ) { const point = json.points[ i ]; this.points.push( new Vector3().fromArray( point ) ); } this.closed = json.closed; this.curveType = json.curveType; this.tension = json.tension; return this; } } /** * Bezier Curves formulas obtained from * https://en.wikipedia.org/wiki/B%C3%A9zier_curve */ function CatmullRom( t, p0, p1, p2, p3 ) { const v0 = ( p2 - p0 ) * 0.5; const v1 = ( p3 - p1 ) * 0.5; const t2 = t * t; const t3 = t * t2; return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; } // function QuadraticBezierP0( t, p ) { const k = 1 - t; return k * k * p; } function QuadraticBezierP1( t, p ) { return 2 * ( 1 - t ) * t * p; } function QuadraticBezierP2( t, p ) { return t * t * p; } function QuadraticBezier( t, p0, p1, p2 ) { return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + QuadraticBezierP2( t, p2 ); } // function CubicBezierP0( t, p ) { const k = 1 - t; return k * k * k * p; } function CubicBezierP1( t, p ) { const k = 1 - t; return 3 * k * k * t * p; } function CubicBezierP2( t, p ) { return 3 * ( 1 - t ) * t * t * p; } function CubicBezierP3( t, p ) { return t * t * t * p; } function CubicBezier( t, p0, p1, p2, p3 ) { return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + CubicBezierP3( t, p3 ); } class CubicBezierCurve extends Curve { constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { super(); this.isCubicBezierCurve = true; this.type = 'CubicBezierCurve'; this.v0 = v0; this.v1 = v1; this.v2 = v2; this.v3 = v3; } getPoint( t, optionalTarget = new Vector2() ) { const point = optionalTarget; const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; point.set( CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) ); return point; } copy( source ) { super.copy( source ); this.v0.copy( source.v0 ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); this.v3.copy( source.v3 ); return this; } toJSON() { const data = super.toJSON(); data.v0 = this.v0.toArray(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); data.v3 = this.v3.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v0.fromArray( json.v0 ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); this.v3.fromArray( json.v3 ); return this; } } class CubicBezierCurve3 extends Curve { constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { super(); this.isCubicBezierCurve3 = true; this.type = 'CubicBezierCurve3'; this.v0 = v0; this.v1 = v1; this.v2 = v2; this.v3 = v3; } getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; point.set( CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) ); return point; } copy( source ) { super.copy( source ); this.v0.copy( source.v0 ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); this.v3.copy( source.v3 ); return this; } toJSON() { const data = super.toJSON(); data.v0 = this.v0.toArray(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); data.v3 = this.v3.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v0.fromArray( json.v0 ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); this.v3.fromArray( json.v3 ); return this; } } class LineCurve extends Curve { constructor( v1 = new Vector2(), v2 = new Vector2() ) { super(); this.isLineCurve = true; this.type = 'LineCurve'; this.v1 = v1; this.v2 = v2; } getPoint( t, optionalTarget = new Vector2() ) { const point = optionalTarget; if ( t === 1 ) { point.copy( this.v2 ); } else { point.copy( this.v2 ).sub( this.v1 ); point.multiplyScalar( t ).add( this.v1 ); } return point; } // Line curve is linear, so we can overwrite default getPointAt getPointAt( u, optionalTarget ) { return this.getPoint( u, optionalTarget ); } getTangent( t, optionalTarget = new Vector2() ) { return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); } getTangentAt( u, optionalTarget ) { return this.getTangent( u, optionalTarget ); } copy( source ) { super.copy( source ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); return this; } toJSON() { const data = super.toJSON(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); return this; } } class LineCurve3 extends Curve { constructor( v1 = new Vector3(), v2 = new Vector3() ) { super(); this.isLineCurve3 = true; this.type = 'LineCurve3'; this.v1 = v1; this.v2 = v2; } getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; if ( t === 1 ) { point.copy( this.v2 ); } else { point.copy( this.v2 ).sub( this.v1 ); point.multiplyScalar( t ).add( this.v1 ); } return point; } // Line curve is linear, so we can overwrite default getPointAt getPointAt( u, optionalTarget ) { return this.getPoint( u, optionalTarget ); } getTangent( t, optionalTarget = new Vector3() ) { return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); } getTangentAt( u, optionalTarget ) { return this.getTangent( u, optionalTarget ); } copy( source ) { super.copy( source ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); return this; } toJSON() { const data = super.toJSON(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); return this; } } class QuadraticBezierCurve extends Curve { constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { super(); this.isQuadraticBezierCurve = true; this.type = 'QuadraticBezierCurve'; this.v0 = v0; this.v1 = v1; this.v2 = v2; } getPoint( t, optionalTarget = new Vector2() ) { const point = optionalTarget; const v0 = this.v0, v1 = this.v1, v2 = this.v2; point.set( QuadraticBezier( t, v0.x, v1.x, v2.x ), QuadraticBezier( t, v0.y, v1.y, v2.y ) ); return point; } copy( source ) { super.copy( source ); this.v0.copy( source.v0 ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); return this; } toJSON() { const data = super.toJSON(); data.v0 = this.v0.toArray(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v0.fromArray( json.v0 ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); return this; } } class QuadraticBezierCurve3 extends Curve { constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { super(); this.isQuadraticBezierCurve3 = true; this.type = 'QuadraticBezierCurve3'; this.v0 = v0; this.v1 = v1; this.v2 = v2; } getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; const v0 = this.v0, v1 = this.v1, v2 = this.v2; point.set( QuadraticBezier( t, v0.x, v1.x, v2.x ), QuadraticBezier( t, v0.y, v1.y, v2.y ), QuadraticBezier( t, v0.z, v1.z, v2.z ) ); return point; } copy( source ) { super.copy( source ); this.v0.copy( source.v0 ); this.v1.copy( source.v1 ); this.v2.copy( source.v2 ); return this; } toJSON() { const data = super.toJSON(); data.v0 = this.v0.toArray(); data.v1 = this.v1.toArray(); data.v2 = this.v2.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.v0.fromArray( json.v0 ); this.v1.fromArray( json.v1 ); this.v2.fromArray( json.v2 ); return this; } } class SplineCurve extends Curve { constructor( points = [] ) { super(); this.isSplineCurve = true; this.type = 'SplineCurve'; this.points = points; } getPoint( t, optionalTarget = new Vector2() ) { const point = optionalTarget; const points = this.points; const p = ( points.length - 1 ) * t; const intPoint = Math.floor( p ); const weight = p - intPoint; const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; const p1 = points[ intPoint ]; const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; point.set( CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) ); return point; } copy( source ) { super.copy( source ); this.points = []; for ( let i = 0, l = source.points.length; i < l; i ++ ) { const point = source.points[ i ]; this.points.push( point.clone() ); } return this; } toJSON() { const data = super.toJSON(); data.points = []; for ( let i = 0, l = this.points.length; i < l; i ++ ) { const point = this.points[ i ]; data.points.push( point.toArray() ); } return data; } fromJSON( json ) { super.fromJSON( json ); this.points = []; for ( let i = 0, l = json.points.length; i < l; i ++ ) { const point = json.points[ i ]; this.points.push( new Vector2().fromArray( point ) ); } return this; } } var Curves = /*#__PURE__*/Object.freeze({ __proto__: null, ArcCurve: ArcCurve, CatmullRomCurve3: CatmullRomCurve3, CubicBezierCurve: CubicBezierCurve, CubicBezierCurve3: CubicBezierCurve3, EllipseCurve: EllipseCurve, LineCurve: LineCurve, LineCurve3: LineCurve3, QuadraticBezierCurve: QuadraticBezierCurve, QuadraticBezierCurve3: QuadraticBezierCurve3, SplineCurve: SplineCurve }); /************************************************************** * Curved Path - a curve path is simply a array of connected * curves, but retains the api of a curve **************************************************************/ class CurvePath extends Curve { constructor() { super(); this.type = 'CurvePath'; this.curves = []; this.autoClose = false; // Automatically closes the path } add( curve ) { this.curves.push( curve ); } closePath() { // Add a line curve if start and end of lines are not connected const startPoint = this.curves[ 0 ].getPoint( 0 ); const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); if ( ! startPoint.equals( endPoint ) ) { const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); } return this; } // To get accurate point with reference to // entire path distance at time t, // following has to be done: // 1. Length of each sub path have to be known // 2. Locate and identify type of curve // 3. Get t for the curve // 4. Return curve.getPointAt(t') getPoint( t, optionalTarget ) { const d = t * this.getLength(); const curveLengths = this.getCurveLengths(); let i = 0; // To think about boundaries points. while ( i < curveLengths.length ) { if ( curveLengths[ i ] >= d ) { const diff = curveLengths[ i ] - d; const curve = this.curves[ i ]; const segmentLength = curve.getLength(); const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; return curve.getPointAt( u, optionalTarget ); } i ++; } return null; // loop where sum != 0, sum > d , sum+1 <d } // We cannot use the default THREE.Curve getPoint() with getLength() because in // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath // getPoint() depends on getLength getLength() { const lens = this.getCurveLengths(); return lens[ lens.length - 1 ]; } // cacheLengths must be recalculated. updateArcLengths() { this.needsUpdate = true; this.cacheLengths = null; this.getCurveLengths(); } // Compute lengths and cache them // We cannot overwrite getLengths() because UtoT mapping uses it. getCurveLengths() { // We use cache values if curves and cache array are same length if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { return this.cacheLengths; } // Get length of sub-curve // Push sums into cached array const lengths = []; let sums = 0; for ( let i = 0, l = this.curves.length; i < l; i ++ ) { sums += this.curves[ i ].getLength(); lengths.push( sums ); } this.cacheLengths = lengths; return lengths; } getSpacedPoints( divisions = 40 ) { const points = []; for ( let i = 0; i <= divisions; i ++ ) { points.push( this.getPoint( i / divisions ) ); } if ( this.autoClose ) { points.push( points[ 0 ] ); } return points; } getPoints( divisions = 12 ) { const points = []; let last; for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { const curve = curves[ i ]; const resolution = curve.isEllipseCurve ? divisions * 2 : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 : curve.isSplineCurve ? divisions * curve.points.length : divisions; const pts = curve.getPoints( resolution ); for ( let j = 0; j < pts.length; j ++ ) { const point = pts[ j ]; if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates points.push( point ); last = point; } } if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { points.push( points[ 0 ] ); } return points; } copy( source ) { super.copy( source ); this.curves = []; for ( let i = 0, l = source.curves.length; i < l; i ++ ) { const curve = source.curves[ i ]; this.curves.push( curve.clone() ); } this.autoClose = source.autoClose; return this; } toJSON() { const data = super.toJSON(); data.autoClose = this.autoClose; data.curves = []; for ( let i = 0, l = this.curves.length; i < l; i ++ ) { const curve = this.curves[ i ]; data.curves.push( curve.toJSON() ); } return data; } fromJSON( json ) { super.fromJSON( json ); this.autoClose = json.autoClose; this.curves = []; for ( let i = 0, l = json.curves.length; i < l; i ++ ) { const curve = json.curves[ i ]; this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); } return this; } } class Path extends CurvePath { constructor( points ) { super(); this.type = 'Path'; this.currentPoint = new Vector2(); if ( points ) { this.setFromPoints( points ); } } setFromPoints( points ) { this.moveTo( points[ 0 ].x, points[ 0 ].y ); for ( let i = 1, l = points.length; i < l; i ++ ) { this.lineTo( points[ i ].x, points[ i ].y ); } return this; } moveTo( x, y ) { this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? return this; } lineTo( x, y ) { const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); this.curves.push( curve ); this.currentPoint.set( x, y ); return this; } quadraticCurveTo( aCPx, aCPy, aX, aY ) { const curve = new QuadraticBezierCurve( this.currentPoint.clone(), new Vector2( aCPx, aCPy ), new Vector2( aX, aY ) ); this.curves.push( curve ); this.currentPoint.set( aX, aY ); return this; } bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { const curve = new CubicBezierCurve( this.currentPoint.clone(), new Vector2( aCP1x, aCP1y ), new Vector2( aCP2x, aCP2y ), new Vector2( aX, aY ) ); this.curves.push( curve ); this.currentPoint.set( aX, aY ); return this; } splineThru( pts /*Array of Vector*/ ) { const npts = [ this.currentPoint.clone() ].concat( pts ); const curve = new SplineCurve( npts ); this.curves.push( curve ); this.currentPoint.copy( pts[ pts.length - 1 ] ); return this; } arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { const x0 = this.currentPoint.x; const y0 = this.currentPoint.y; this.absarc( aX + x0, aY + y0, aRadius, aStartAngle, aEndAngle, aClockwise ); return this; } absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); return this; } ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { const x0 = this.currentPoint.x; const y0 = this.currentPoint.y; this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); return this; } absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); if ( this.curves.length > 0 ) { // if a previous curve is present, attempt to join const firstPoint = curve.getPoint( 0 ); if ( ! firstPoint.equals( this.currentPoint ) ) { this.lineTo( firstPoint.x, firstPoint.y ); } } this.curves.push( curve ); const lastPoint = curve.getPoint( 1 ); this.currentPoint.copy( lastPoint ); return this; } copy( source ) { super.copy( source ); this.currentPoint.copy( source.currentPoint ); return this; } toJSON() { const data = super.toJSON(); data.currentPoint = this.currentPoint.toArray(); return data; } fromJSON( json ) { super.fromJSON( json ); this.currentPoint.fromArray( json.currentPoint ); return this; } } class LatheGeometry extends BufferGeometry { constructor( points = [ new Vector2( 0, - 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { super(); this.type = 'LatheGeometry'; this.parameters = { points: points, segments: segments, phiStart: phiStart, phiLength: phiLength }; segments = Math.floor( segments ); // clamp phiLength so it's in range of [ 0, 2PI ] phiLength = clamp( phiLength, 0, Math.PI * 2 ); // buffers const indices = []; const vertices = []; const uvs = []; const initNormals = []; const normals = []; // helper variables const inverseSegments = 1.0 / segments; const vertex = new Vector3(); const uv = new Vector2(); const normal = new Vector3(); const curNormal = new Vector3(); const prevNormal = new Vector3(); let dx = 0; let dy = 0; // pre-compute normals for initial "meridian" for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { switch ( j ) { case 0: // special handling for 1st vertex on path dx = points[ j + 1 ].x - points[ j ].x; dy = points[ j + 1 ].y - points[ j ].y; normal.x = dy * 1.0; normal.y = - dx; normal.z = dy * 0.0; prevNormal.copy( normal ); normal.normalize(); initNormals.push( normal.x, normal.y, normal.z ); break; case ( points.length - 1 ): // special handling for last Vertex on path initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); break; default: // default handling for all vertices in between dx = points[ j + 1 ].x - points[ j ].x; dy = points[ j + 1 ].y - points[ j ].y; normal.x = dy * 1.0; normal.y = - dx; normal.z = dy * 0.0; curNormal.copy( normal ); normal.x += prevNormal.x; normal.y += prevNormal.y; normal.z += prevNormal.z; normal.normalize(); initNormals.push( normal.x, normal.y, normal.z ); prevNormal.copy( curNormal ); } } // generate vertices, uvs and normals for ( let i = 0; i <= segments; i ++ ) { const phi = phiStart + i * inverseSegments * phiLength; const sin = Math.sin( phi ); const cos = Math.cos( phi ); for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { // vertex vertex.x = points[ j ].x * sin; vertex.y = points[ j ].y; vertex.z = points[ j ].x * cos; vertices.push( vertex.x, vertex.y, vertex.z ); // uv uv.x = i / segments; uv.y = j / ( points.length - 1 ); uvs.push( uv.x, uv.y ); // normal const x = initNormals[ 3 * j + 0 ] * sin; const y = initNormals[ 3 * j + 1 ]; const z = initNormals[ 3 * j + 0 ] * cos; normals.push( x, y, z ); } } // indices for ( let i = 0; i < segments; i ++ ) { for ( let j = 0; j < ( points.length - 1 ); j ++ ) { const base = j + i * points.length; const a = base; const b = base + points.length; const c = base + points.length + 1; const d = base + 1; // faces indices.push( a, b, d ); indices.push( c, d, b ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); } } class CapsuleGeometry extends LatheGeometry { constructor( radius = 1, length = 1, capSegments = 4, radialSegments = 8 ) { const path = new Path(); path.absarc( 0, - length / 2, radius, Math.PI * 1.5, 0 ); path.absarc( 0, length / 2, radius, 0, Math.PI * 0.5 ); super( path.getPoints( capSegments ), radialSegments ); this.type = 'CapsuleGeometry'; this.parameters = { radius: radius, length: length, capSegments: capSegments, radialSegments: radialSegments, }; } static fromJSON( data ) { return new CapsuleGeometry( data.radius, data.length, data.capSegments, data.radialSegments ); } } class CircleGeometry extends BufferGeometry { constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'CircleGeometry'; this.parameters = { radius: radius, segments: segments, thetaStart: thetaStart, thetaLength: thetaLength }; segments = Math.max( 3, segments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables const vertex = new Vector3(); const uv = new Vector2(); // center point vertices.push( 0, 0, 0 ); normals.push( 0, 0, 1 ); uvs.push( 0.5, 0.5 ); for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { const segment = thetaStart + s / segments * thetaLength; // vertex vertex.x = radius * Math.cos( segment ); vertex.y = radius * Math.sin( segment ); vertices.push( vertex.x, vertex.y, vertex.z ); // normal normals.push( 0, 0, 1 ); // uvs uv.x = ( vertices[ i ] / radius + 1 ) / 2; uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; uvs.push( uv.x, uv.y ); } // indices for ( let i = 1; i <= segments; i ++ ) { indices.push( i, i + 1, 0 ); } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); } } class CylinderGeometry extends BufferGeometry { constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'CylinderGeometry'; this.parameters = { radiusTop: radiusTop, radiusBottom: radiusBottom, height: height, radialSegments: radialSegments, heightSegments: heightSegments, openEnded: openEnded, thetaStart: thetaStart, thetaLength: thetaLength }; const scope = this; radialSegments = Math.floor( radialSegments ); heightSegments = Math.floor( heightSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables let index = 0; const indexArray = []; const halfHeight = height / 2; let groupStart = 0; // generate geometry generateTorso(); if ( openEnded === false ) { if ( radiusTop > 0 ) generateCap( true ); if ( radiusBottom > 0 ) generateCap( false ); } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); function generateTorso() { const normal = new Vector3(); const vertex = new Vector3(); let groupCount = 0; // this will be used to calculate the normal const slope = ( radiusBottom - radiusTop ) / height; // generate vertices, normals and uvs for ( let y = 0; y <= heightSegments; y ++ ) { const indexRow = []; const v = y / heightSegments; // calculate the radius of the current row const radius = v * ( radiusBottom - radiusTop ) + radiusTop; for ( let x = 0; x <= radialSegments; x ++ ) { const u = x / radialSegments; const theta = u * thetaLength + thetaStart; const sinTheta = Math.sin( theta ); const cosTheta = Math.cos( theta ); // vertex vertex.x = radius * sinTheta; vertex.y = - v * height + halfHeight; vertex.z = radius * cosTheta; vertices.push( vertex.x, vertex.y, vertex.z ); // normal normal.set( sinTheta, slope, cosTheta ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( u, 1 - v ); // save index of vertex in respective row indexRow.push( index ++ ); } // now save vertices of the row in our index array indexArray.push( indexRow ); } // generate indices for ( let x = 0; x < radialSegments; x ++ ) { for ( let y = 0; y < heightSegments; y ++ ) { // we use the index array to access the correct indices const a = indexArray[ y ][ x ]; const b = indexArray[ y + 1 ][ x ]; const c = indexArray[ y + 1 ][ x + 1 ]; const d = indexArray[ y ][ x + 1 ]; // faces indices.push( a, b, d ); indices.push( b, c, d ); // update group counter groupCount += 6; } } // add a group to the geometry. this will ensure multi material support scope.addGroup( groupStart, groupCount, 0 ); // calculate new start value for groups groupStart += groupCount; } function generateCap( top ) { // save the index of the first center vertex const centerIndexStart = index; const uv = new Vector2(); const vertex = new Vector3(); let groupCount = 0; const radius = ( top === true ) ? radiusTop : radiusBottom; const sign = ( top === true ) ? 1 : - 1; // first we generate the center vertex data of the cap. // because the geometry needs one set of uvs per face, // we must generate a center vertex per face/segment for ( let x = 1; x <= radialSegments; x ++ ) { // vertex vertices.push( 0, halfHeight * sign, 0 ); // normal normals.push( 0, sign, 0 ); // uv uvs.push( 0.5, 0.5 ); // increase index index ++; } // save the index of the last center vertex const centerIndexEnd = index; // now we generate the surrounding vertices, normals and uvs for ( let x = 0; x <= radialSegments; x ++ ) { const u = x / radialSegments; const theta = u * thetaLength + thetaStart; const cosTheta = Math.cos( theta ); const sinTheta = Math.sin( theta ); // vertex vertex.x = radius * sinTheta; vertex.y = halfHeight * sign; vertex.z = radius * cosTheta; vertices.push( vertex.x, vertex.y, vertex.z ); // normal normals.push( 0, sign, 0 ); // uv uv.x = ( cosTheta * 0.5 ) + 0.5; uv.y = ( sinTheta * 0.5 * sign ) + 0.5; uvs.push( uv.x, uv.y ); // increase index index ++; } // generate indices for ( let x = 0; x < radialSegments; x ++ ) { const c = centerIndexStart + x; const i = centerIndexEnd + x; if ( top === true ) { // face top indices.push( i, i + 1, c ); } else { // face bottom indices.push( i + 1, i, c ); } groupCount += 3; } // add a group to the geometry. this will ensure multi material support scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); // calculate new start value for groups groupStart += groupCount; } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } } class ConeGeometry extends CylinderGeometry { constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); this.type = 'ConeGeometry'; this.parameters = { radius: radius, height: height, radialSegments: radialSegments, heightSegments: heightSegments, openEnded: openEnded, thetaStart: thetaStart, thetaLength: thetaLength }; } static fromJSON( data ) { return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } } class PolyhedronGeometry extends BufferGeometry { constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { super(); this.type = 'PolyhedronGeometry'; this.parameters = { vertices: vertices, indices: indices, radius: radius, detail: detail }; // default buffer data const vertexBuffer = []; const uvBuffer = []; // the subdivision creates the vertex buffer data subdivide( detail ); // all vertices should lie on a conceptual sphere with a given radius applyRadius( radius ); // finally, create the uv data generateUVs(); // build non-indexed geometry this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); if ( detail === 0 ) { this.computeVertexNormals(); // flat normals } else { this.normalizeNormals(); // smooth normals } // helper functions function subdivide( detail ) { const a = new Vector3(); const b = new Vector3(); const c = new Vector3(); // iterate over all faces and apply a subdivision with the given detail value for ( let i = 0; i < indices.length; i += 3 ) { // get the vertices of the face getVertexByIndex( indices[ i + 0 ], a ); getVertexByIndex( indices[ i + 1 ], b ); getVertexByIndex( indices[ i + 2 ], c ); // perform subdivision subdivideFace( a, b, c, detail ); } } function subdivideFace( a, b, c, detail ) { const cols = detail + 1; // we use this multidimensional array as a data structure for creating the subdivision const v = []; // construct all of the vertices for this subdivision for ( let i = 0; i <= cols; i ++ ) { v[ i ] = []; const aj = a.clone().lerp( c, i / cols ); const bj = b.clone().lerp( c, i / cols ); const rows = cols - i; for ( let j = 0; j <= rows; j ++ ) { if ( j === 0 && i === cols ) { v[ i ][ j ] = aj; } else { v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); } } } // construct all of the faces for ( let i = 0; i < cols; i ++ ) { for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { const k = Math.floor( j / 2 ); if ( j % 2 === 0 ) { pushVertex( v[ i ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k ] ); pushVertex( v[ i ][ k ] ); } else { pushVertex( v[ i ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k + 1 ] ); pushVertex( v[ i + 1 ][ k ] ); } } } } function applyRadius( radius ) { const vertex = new Vector3(); // iterate over the entire buffer and apply the radius to each vertex for ( let i = 0; i < vertexBuffer.length; i += 3 ) { vertex.x = vertexBuffer[ i + 0 ]; vertex.y = vertexBuffer[ i + 1 ]; vertex.z = vertexBuffer[ i + 2 ]; vertex.normalize().multiplyScalar( radius ); vertexBuffer[ i + 0 ] = vertex.x; vertexBuffer[ i + 1 ] = vertex.y; vertexBuffer[ i + 2 ] = vertex.z; } } function generateUVs() { const vertex = new Vector3(); for ( let i = 0; i < vertexBuffer.length; i += 3 ) { vertex.x = vertexBuffer[ i + 0 ]; vertex.y = vertexBuffer[ i + 1 ]; vertex.z = vertexBuffer[ i + 2 ]; const u = azimuth( vertex ) / 2 / Math.PI + 0.5; const v = inclination( vertex ) / Math.PI + 0.5; uvBuffer.push( u, 1 - v ); } correctUVs(); correctSeam(); } function correctSeam() { // handle case when face straddles the seam, see #3269 for ( let i = 0; i < uvBuffer.length; i += 6 ) { // uv data of a single face const x0 = uvBuffer[ i + 0 ]; const x1 = uvBuffer[ i + 2 ]; const x2 = uvBuffer[ i + 4 ]; const max = Math.max( x0, x1, x2 ); const min = Math.min( x0, x1, x2 ); // 0.9 is somewhat arbitrary if ( max > 0.9 && min < 0.1 ) { if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; } } } function pushVertex( vertex ) { vertexBuffer.push( vertex.x, vertex.y, vertex.z ); } function getVertexByIndex( index, vertex ) { const stride = index * 3; vertex.x = vertices[ stride + 0 ]; vertex.y = vertices[ stride + 1 ]; vertex.z = vertices[ stride + 2 ]; } function correctUVs() { const a = new Vector3(); const b = new Vector3(); const c = new Vector3(); const centroid = new Vector3(); const uvA = new Vector2(); const uvB = new Vector2(); const uvC = new Vector2(); for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); const azi = azimuth( centroid ); correctUV( uvA, j + 0, a, azi ); correctUV( uvB, j + 2, b, azi ); correctUV( uvC, j + 4, c, azi ); } } function correctUV( uv, stride, vector, azimuth ) { if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { uvBuffer[ stride ] = uv.x - 1; } if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; } } // Angle around the Y axis, counter-clockwise when looking from above. function azimuth( vector ) { return Math.atan2( vector.z, - vector.x ); } // Angle above the XZ plane. function inclination( vector ) { return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); } } class DodecahedronGeometry extends PolyhedronGeometry { constructor( radius = 1, detail = 0 ) { const t = ( 1 + Math.sqrt( 5 ) ) / 2; const r = 1 / t; const vertices = [ // (±1, ±1, ±1) - 1, - 1, - 1, - 1, - 1, 1, - 1, 1, - 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, 1, 1, 1, - 1, 1, 1, 1, // (0, ±1/φ, ±φ) 0, - r, - t, 0, - r, t, 0, r, - t, 0, r, t, // (±1/φ, ±φ, 0) - r, - t, 0, - r, t, 0, r, - t, 0, r, t, 0, // (±φ, 0, ±1/φ) - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, r ]; const indices = [ 3, 11, 7, 3, 7, 15, 3, 15, 13, 7, 19, 17, 7, 17, 6, 7, 6, 15, 17, 4, 8, 17, 8, 10, 17, 10, 6, 8, 0, 16, 8, 16, 2, 8, 2, 10, 0, 12, 1, 0, 1, 18, 0, 18, 16, 6, 10, 2, 6, 2, 13, 6, 13, 15, 2, 16, 18, 2, 18, 3, 2, 3, 13, 18, 1, 9, 18, 9, 11, 18, 11, 3, 4, 14, 12, 4, 12, 0, 4, 0, 8, 11, 9, 5, 11, 5, 19, 11, 19, 7, 19, 5, 14, 19, 14, 4, 19, 4, 17, 1, 12, 14, 1, 14, 5, 1, 5, 9 ]; super( vertices, indices, radius, detail ); this.type = 'DodecahedronGeometry'; this.parameters = { radius: radius, detail: detail }; } static fromJSON( data ) { return new DodecahedronGeometry( data.radius, data.detail ); } } const _v0 = /*@__PURE__*/ new Vector3(); const _v1$1 = /*@__PURE__*/ new Vector3(); const _normal = /*@__PURE__*/ new Vector3(); const _triangle = /*@__PURE__*/ new Triangle(); class EdgesGeometry extends BufferGeometry { constructor( geometry = null, thresholdAngle = 1 ) { super(); this.type = 'EdgesGeometry'; this.parameters = { geometry: geometry, thresholdAngle: thresholdAngle }; if ( geometry !== null ) { const precisionPoints = 4; const precision = Math.pow( 10, precisionPoints ); const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); const indexAttr = geometry.getIndex(); const positionAttr = geometry.getAttribute( 'position' ); const indexCount = indexAttr ? indexAttr.count : positionAttr.count; const indexArr = [ 0, 0, 0 ]; const vertKeys = [ 'a', 'b', 'c' ]; const hashes = new Array( 3 ); const edgeData = {}; const vertices = []; for ( let i = 0; i < indexCount; i += 3 ) { if ( indexAttr ) { indexArr[ 0 ] = indexAttr.getX( i ); indexArr[ 1 ] = indexAttr.getX( i + 1 ); indexArr[ 2 ] = indexAttr.getX( i + 2 ); } else { indexArr[ 0 ] = i; indexArr[ 1 ] = i + 1; indexArr[ 2 ] = i + 2; } const { a, b, c } = _triangle; a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); _triangle.getNormal( _normal ); // create hashes for the edge from the vertices hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; // skip degenerate triangles if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { continue; } // iterate over every edge for ( let j = 0; j < 3; j ++ ) { // get the first and next vertex making up the edge const jNext = ( j + 1 ) % 3; const vecHash0 = hashes[ j ]; const vecHash1 = hashes[ jNext ]; const v0 = _triangle[ vertKeys[ j ] ]; const v1 = _triangle[ vertKeys[ jNext ] ]; const hash = `${ vecHash0 }_${ vecHash1 }`; const reverseHash = `${ vecHash1 }_${ vecHash0 }`; if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { // if we found a sibling edge add it into the vertex array if // it meets the angle threshold and delete the edge from the map. if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { vertices.push( v0.x, v0.y, v0.z ); vertices.push( v1.x, v1.y, v1.z ); } edgeData[ reverseHash ] = null; } else if ( ! ( hash in edgeData ) ) { // if we've already got an edge here then skip adding a new one edgeData[ hash ] = { index0: indexArr[ j ], index1: indexArr[ jNext ], normal: _normal.clone(), }; } } } // iterate over all remaining, unmatched edges and add them to the vertex array for ( const key in edgeData ) { if ( edgeData[ key ] ) { const { index0, index1 } = edgeData[ key ]; _v0.fromBufferAttribute( positionAttr, index0 ); _v1$1.fromBufferAttribute( positionAttr, index1 ); vertices.push( _v0.x, _v0.y, _v0.z ); vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); } } this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } } class Shape extends Path { constructor( points ) { super( points ); this.uuid = generateUUID(); this.type = 'Shape'; this.holes = []; } getPointsHoles( divisions ) { const holesPts = []; for ( let i = 0, l = this.holes.length; i < l; i ++ ) { holesPts[ i ] = this.holes[ i ].getPoints( divisions ); } return holesPts; } // get points of shape and holes (keypoints based on segments parameter) extractPoints( divisions ) { return { shape: this.getPoints( divisions ), holes: this.getPointsHoles( divisions ) }; } copy( source ) { super.copy( source ); this.holes = []; for ( let i = 0, l = source.holes.length; i < l; i ++ ) { const hole = source.holes[ i ]; this.holes.push( hole.clone() ); } return this; } toJSON() { const data = super.toJSON(); data.uuid = this.uuid; data.holes = []; for ( let i = 0, l = this.holes.length; i < l; i ++ ) { const hole = this.holes[ i ]; data.holes.push( hole.toJSON() ); } return data; } fromJSON( json ) { super.fromJSON( json ); this.uuid = json.uuid; this.holes = []; for ( let i = 0, l = json.holes.length; i < l; i ++ ) { const hole = json.holes[ i ]; this.holes.push( new Path().fromJSON( hole ) ); } return this; } } /** * Port from https://github.com/mapbox/earcut (v2.2.4) */ const Earcut = { triangulate: function ( data, holeIndices, dim = 2 ) { const hasHoles = holeIndices && holeIndices.length; const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; let outerNode = linkedList( data, 0, outerLen, dim, true ); const triangles = []; if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; let minX, minY, maxX, maxY, x, y, invSize; if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox if ( data.length > 80 * dim ) { minX = maxX = data[ 0 ]; minY = maxY = data[ 1 ]; for ( let i = dim; i < outerLen; i += dim ) { x = data[ i ]; y = data[ i + 1 ]; if ( x < minX ) minX = x; if ( y < minY ) minY = y; if ( x > maxX ) maxX = x; if ( y > maxY ) maxY = y; } // minX, minY and invSize are later used to transform coords into integers for z-order calculation invSize = Math.max( maxX - minX, maxY - minY ); invSize = invSize !== 0 ? 32767 / invSize : 0; } earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); return triangles; } }; // create a circular doubly linked list from polygon points in the specified winding order function linkedList( data, start, end, dim, clockwise ) { let i, last; if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); } else { for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); } if ( last && equals( last, last.next ) ) { removeNode( last ); last = last.next; } return last; } // eliminate colinear or duplicate points function filterPoints( start, end ) { if ( ! start ) return start; if ( ! end ) end = start; let p = start, again; do { again = false; if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { removeNode( p ); p = end = p.prev; if ( p === p.next ) break; again = true; } else { p = p.next; } } while ( again || p !== end ); return end; } // main ear slicing loop which triangulates a polygon (given as a linked list) function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { if ( ! ear ) return; // interlink polygon nodes in z-order if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); let stop = ear, prev, next; // iterate through ears, slicing them one by one while ( ear.prev !== ear.next ) { prev = ear.prev; next = ear.next; if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { // cut off the triangle triangles.push( prev.i / dim | 0 ); triangles.push( ear.i / dim | 0 ); triangles.push( next.i / dim | 0 ); removeNode( ear ); // skipping the next vertex leads to less sliver triangles ear = next.next; stop = next.next; continue; } ear = next; // if we looped through the whole remaining polygon and can't find any more ears if ( ear === stop ) { // try filtering points and slicing again if ( ! pass ) { earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); // if this didn't work, try curing all small self-intersections locally } else if ( pass === 1 ) { ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); // as a last resort, try splitting the remaining polygon into two } else if ( pass === 2 ) { splitEarcut( ear, triangles, dim, minX, minY, invSize ); } break; } } } // check whether a polygon node forms a valid ear with adjacent nodes function isEar( ear ) { const a = ear.prev, b = ear, c = ear.next; if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear // now make sure we don't have other points inside the potential ear const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; // triangle bbox; min & max are calculated like this for speed const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); let p = c.next; while ( p !== a ) { if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; p = p.next; } return true; } function isEarHashed( ear, minX, minY, invSize ) { const a = ear.prev, b = ear, c = ear.next; if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; // triangle bbox; min & max are calculated like this for speed const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); // z-order range for the current triangle bbox; const minZ = zOrder( x0, y0, minX, minY, invSize ), maxZ = zOrder( x1, y1, minX, minY, invSize ); let p = ear.prevZ, n = ear.nextZ; // look for points inside the triangle in both directions while ( p && p.z >= minZ && n && n.z <= maxZ ) { if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; p = p.prevZ; if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; n = n.nextZ; } // look for remaining points in decreasing z-order while ( p && p.z >= minZ ) { if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; p = p.prevZ; } // look for remaining points in increasing z-order while ( n && n.z <= maxZ ) { if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; n = n.nextZ; } return true; } // go through all polygon nodes and cure small local self-intersections function cureLocalIntersections( start, triangles, dim ) { let p = start; do { const a = p.prev, b = p.next.next; if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { triangles.push( a.i / dim | 0 ); triangles.push( p.i / dim | 0 ); triangles.push( b.i / dim | 0 ); // remove two nodes involved removeNode( p ); removeNode( p.next ); p = start = b; } p = p.next; } while ( p !== start ); return filterPoints( p ); } // try splitting polygon into two and triangulate them independently function splitEarcut( start, triangles, dim, minX, minY, invSize ) { // look for a valid diagonal that divides the polygon into two let a = start; do { let b = a.next.next; while ( b !== a.prev ) { if ( a.i !== b.i && isValidDiagonal( a, b ) ) { // split the polygon in two by the diagonal let c = splitPolygon( a, b ); // filter colinear points around the cuts a = filterPoints( a, a.next ); c = filterPoints( c, c.next ); // run earcut on each half earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); return; } b = b.next; } a = a.next; } while ( a !== start ); } // link every hole into the outer loop, producing a single-ring polygon without holes function eliminateHoles( data, holeIndices, outerNode, dim ) { const queue = []; let i, len, start, end, list; for ( i = 0, len = holeIndices.length; i < len; i ++ ) { start = holeIndices[ i ] * dim; end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; list = linkedList( data, start, end, dim, false ); if ( list === list.next ) list.steiner = true; queue.push( getLeftmost( list ) ); } queue.sort( compareX ); // process holes from left to right for ( i = 0; i < queue.length; i ++ ) { outerNode = eliminateHole( queue[ i ], outerNode ); } return outerNode; } function compareX( a, b ) { return a.x - b.x; } // find a bridge between vertices that connects hole with an outer ring and link it function eliminateHole( hole, outerNode ) { const bridge = findHoleBridge( hole, outerNode ); if ( ! bridge ) { return outerNode; } const bridgeReverse = splitPolygon( bridge, hole ); // filter collinear points around the cuts filterPoints( bridgeReverse, bridgeReverse.next ); return filterPoints( bridge, bridge.next ); } // David Eberly's algorithm for finding a bridge between hole and outer polygon function findHoleBridge( hole, outerNode ) { let p = outerNode, qx = - Infinity, m; const hx = hole.x, hy = hole.y; // find a segment intersected by a ray from the hole's leftmost point to the left; // segment's endpoint with lesser x will be potential connection point do { if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); if ( x <= hx && x > qx ) { qx = x; m = p.x < p.next.x ? p : p.next; if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint } } p = p.next; } while ( p !== outerNode ); if ( ! m ) return null; // look for points inside the triangle of hole point, segment intersection and endpoint; // if there are no points found, we have a valid connection; // otherwise choose the point of the minimum angle with the ray as connection point const stop = m, mx = m.x, my = m.y; let tanMin = Infinity, tan; p = m; do { if ( hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { m = p; tanMin = tan; } } p = p.next; } while ( p !== stop ); return m; } // whether sector in vertex m contains sector in vertex p in the same coordinates function sectorContainsSector( m, p ) { return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; } // interlink polygon nodes in z-order function indexCurve( start, minX, minY, invSize ) { let p = start; do { if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); p.prevZ = p.prev; p.nextZ = p.next; p = p.next; } while ( p !== start ); p.prevZ.nextZ = null; p.prevZ = null; sortLinked( p ); } // Simon Tatham's linked list merge sort algorithm // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html function sortLinked( list ) { let i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1; do { p = list; list = null; tail = null; numMerges = 0; while ( p ) { numMerges ++; q = p; pSize = 0; for ( i = 0; i < inSize; i ++ ) { pSize ++; q = q.nextZ; if ( ! q ) break; } qSize = inSize; while ( pSize > 0 || ( qSize > 0 && q ) ) { if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { e = p; p = p.nextZ; pSize --; } else { e = q; q = q.nextZ; qSize --; } if ( tail ) tail.nextZ = e; else list = e; e.prevZ = tail; tail = e; } p = q; } tail.nextZ = null; inSize *= 2; } while ( numMerges > 1 ); return list; } // z-order of a point given coords and inverse of the longer side of data bbox function zOrder( x, y, minX, minY, invSize ) { // coords are transformed into non-negative 15-bit integer range x = ( x - minX ) * invSize | 0; y = ( y - minY ) * invSize | 0; x = ( x | ( x << 8 ) ) & 0x00FF00FF; x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; x = ( x | ( x << 2 ) ) & 0x33333333; x = ( x | ( x << 1 ) ) & 0x55555555; y = ( y | ( y << 8 ) ) & 0x00FF00FF; y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; y = ( y | ( y << 2 ) ) & 0x33333333; y = ( y | ( y << 1 ) ) & 0x55555555; return x | ( y << 1 ); } // find the leftmost node of a polygon ring function getLeftmost( start ) { let p = start, leftmost = start; do { if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; p = p.next; } while ( p !== start ); return leftmost; } // check if a point lies within a convex triangle function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); } // check if a diagonal between two polygon nodes is valid (lies in polygon interior) function isValidDiagonal( a, b ) { return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case } // signed area of a triangle function area( p, q, r ) { return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); } // check if two points are equal function equals( p1, p2 ) { return p1.x === p2.x && p1.y === p2.y; } // check if two segments intersect function intersects( p1, q1, p2, q2 ) { const o1 = sign( area( p1, q1, p2 ) ); const o2 = sign( area( p1, q1, q2 ) ); const o3 = sign( area( p2, q2, p1 ) ); const o4 = sign( area( p2, q2, q1 ) ); if ( o1 !== o2 && o3 !== o4 ) return true; // general case if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 return false; } // for collinear points p, q, r, check if point q lies on segment pr function onSegment( p, q, r ) { return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); } function sign( num ) { return num > 0 ? 1 : num < 0 ? - 1 : 0; } // check if a polygon diagonal intersects any polygon segments function intersectsPolygon( a, b ) { let p = a; do { if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects( p, p.next, a, b ) ) return true; p = p.next; } while ( p !== a ); return false; } // check if a polygon diagonal is locally inside the polygon function locallyInside( a, b ) { return area( a.prev, a, a.next ) < 0 ? area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; } // check if the middle point of a polygon diagonal is inside the polygon function middleInside( a, b ) { let p = a, inside = false; const px = ( a.x + b.x ) / 2, py = ( a.y + b.y ) / 2; do { if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) inside = ! inside; p = p.next; } while ( p !== a ); return inside; } // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; // if one belongs to the outer ring and another to a hole, it merges it into a single ring function splitPolygon( a, b ) { const a2 = new Node( a.i, a.x, a.y ), b2 = new Node( b.i, b.x, b.y ), an = a.next, bp = b.prev; a.next = b; b.prev = a; a2.next = an; an.prev = a2; b2.next = a2; a2.prev = b2; bp.next = b2; b2.prev = bp; return b2; } // create a node and optionally link it with previous one (in a circular doubly linked list) function insertNode( i, x, y, last ) { const p = new Node( i, x, y ); if ( ! last ) { p.prev = p; p.next = p; } else { p.next = last.next; p.prev = last; last.next.prev = p; last.next = p; } return p; } function removeNode( p ) { p.next.prev = p.prev; p.prev.next = p.next; if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; } function Node( i, x, y ) { // vertex index in coordinates array this.i = i; // vertex coordinates this.x = x; this.y = y; // previous and next vertex nodes in a polygon ring this.prev = null; this.next = null; // z-order curve value this.z = 0; // previous and next nodes in z-order this.prevZ = null; this.nextZ = null; // indicates whether this is a steiner point this.steiner = false; } function signedArea( data, start, end, dim ) { let sum = 0; for ( let i = start, j = end - dim; i < end; i += dim ) { sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); j = i; } return sum; } class ShapeUtils { // calculate area of the contour polygon static area( contour ) { const n = contour.length; let a = 0.0; for ( let p = n - 1, q = 0; q < n; p = q ++ ) { a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; } return a * 0.5; } static isClockWise( pts ) { return ShapeUtils.area( pts ) < 0; } static triangulateShape( contour, holes ) { const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] const holeIndices = []; // array of hole indices const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] removeDupEndPts( contour ); addContour( vertices, contour ); // let holeIndex = contour.length; holes.forEach( removeDupEndPts ); for ( let i = 0; i < holes.length; i ++ ) { holeIndices.push( holeIndex ); holeIndex += holes[ i ].length; addContour( vertices, holes[ i ] ); } // const triangles = Earcut.triangulate( vertices, holeIndices ); // for ( let i = 0; i < triangles.length; i += 3 ) { faces.push( triangles.slice( i, i + 3 ) ); } return faces; } } function removeDupEndPts( points ) { const l = points.length; if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { points.pop(); } } function addContour( vertices, contour ) { for ( let i = 0; i < contour.length; i ++ ) { vertices.push( contour[ i ].x ); vertices.push( contour[ i ].y ); } } /** * Creates extruded geometry from a path shape. * * parameters = { * * curveSegments: <int>, // number of points on the curves * steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too * depth: <float>, // Depth to extrude the shape * * bevelEnabled: <bool>, // turn on bevel * bevelThickness: <float>, // how deep into the original shape bevel goes * bevelSize: <float>, // how far from shape outline (including bevelOffset) is bevel * bevelOffset: <float>, // how far from shape outline does bevel start * bevelSegments: <int>, // number of bevel layers * * extrudePath: <THREE.Curve> // curve to extrude shape along * * UVGenerator: <Object> // object that provides UV generator functions * * } */ class ExtrudeGeometry extends BufferGeometry { constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { super(); this.type = 'ExtrudeGeometry'; this.parameters = { shapes: shapes, options: options }; shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; const scope = this; const verticesArray = []; const uvArray = []; for ( let i = 0, l = shapes.length; i < l; i ++ ) { const shape = shapes[ i ]; addShape( shape ); } // build geometry this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); this.computeVertexNormals(); // functions function addShape( shape ) { const placeholder = []; // options const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; const steps = options.steps !== undefined ? options.steps : 1; const depth = options.depth !== undefined ? options.depth : 1; let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; const extrudePath = options.extrudePath; const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; // let extrudePts, extrudeByPath = false; let splineTube, binormal, normal, position2; if ( extrudePath ) { extrudePts = extrudePath.getSpacedPoints( steps ); extrudeByPath = true; bevelEnabled = false; // bevels not supported for path extrusion // SETUP TNB variables // TODO1 - have a .isClosed in spline? splineTube = extrudePath.computeFrenetFrames( steps, false ); // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); binormal = new Vector3(); normal = new Vector3(); position2 = new Vector3(); } // Safeguards if bevels are not enabled if ( ! bevelEnabled ) { bevelSegments = 0; bevelThickness = 0; bevelSize = 0; bevelOffset = 0; } // Variables initialization const shapePoints = shape.extractPoints( curveSegments ); let vertices = shapePoints.shape; const holes = shapePoints.holes; const reverse = ! ShapeUtils.isClockWise( vertices ); if ( reverse ) { vertices = vertices.reverse(); // Maybe we should also check if holes are in the opposite direction, just to be safe ... for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; if ( ShapeUtils.isClockWise( ahole ) ) { holes[ h ] = ahole.reverse(); } } } const faces = ShapeUtils.triangulateShape( vertices, holes ); /* Vertices */ const contour = vertices; // vertices has all points but contour has only points of circumference for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; vertices = vertices.concat( ahole ); } function scalePt2( pt, vec, size ) { if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); return pt.clone().addScaledVector( vec, size ); } const vlen = vertices.length, flen = faces.length; // Find directions for point movement function getBevelVec( inPt, inPrev, inNext ) { // computes for inPt the corresponding point inPt' on a new contour // shifted by 1 unit (length of normalized vector) to the left // if we walk along contour clockwise, this new contour is outside the old one // // inPt' is the intersection of the two lines parallel to the two // adjacent edges of inPt at a distance of 1 unit on the left side. let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt // good reading for geometry algorithms (here: line-line intersection) // http://geomalgorithms.com/a05-_intersect-1.html const v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; const v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); // check for collinear edges const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); if ( Math.abs( collinear0 ) > Number.EPSILON ) { // not collinear // length of vectors for normalizing const v_prev_len = Math.sqrt( v_prev_lensq ); const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); // shift adjacent points by unit vectors to the left const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); // scaling factor for v_prev to intersection point const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / ( v_prev_x * v_next_y - v_prev_y * v_next_x ); // vector from inPt to intersection point v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); // Don't normalize!, otherwise sharp corners become ugly // but prevent crazy spikes const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); if ( v_trans_lensq <= 2 ) { return new Vector2( v_trans_x, v_trans_y ); } else { shrink_by = Math.sqrt( v_trans_lensq / 2 ); } } else { // handle special case of collinear edges let direction_eq = false; // assumes: opposite if ( v_prev_x > Number.EPSILON ) { if ( v_next_x > Number.EPSILON ) { direction_eq = true; } } else { if ( v_prev_x < - Number.EPSILON ) { if ( v_next_x < - Number.EPSILON ) { direction_eq = true; } } else { if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { direction_eq = true; } } } if ( direction_eq ) { // console.log("Warning: lines are a straight sequence"); v_trans_x = - v_prev_y; v_trans_y = v_prev_x; shrink_by = Math.sqrt( v_prev_lensq ); } else { // console.log("Warning: lines are a straight spike"); v_trans_x = v_prev_x; v_trans_y = v_prev_y; shrink_by = Math.sqrt( v_prev_lensq / 2 ); } } return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); } const contourMovements = []; for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { if ( j === il ) j = 0; if ( k === il ) k = 0; // (j)---(i)---(k) // console.log('i,j,k', i, j , k) contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); } const holesMovements = []; let oneHoleMovements, verticesMovements = contourMovements.concat(); for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; oneHoleMovements = []; for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { if ( j === il ) j = 0; if ( k === il ) k = 0; // (j)---(i)---(k) oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); } holesMovements.push( oneHoleMovements ); verticesMovements = verticesMovements.concat( oneHoleMovements ); } // Loop bevelSegments, 1 for the front, 1 for the back for ( let b = 0; b < bevelSegments; b ++ ) { //for ( b = bevelSegments; b > 0; b -- ) { const t = b / bevelSegments; const z = bevelThickness * Math.cos( t * Math.PI / 2 ); const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; // contract shape for ( let i = 0, il = contour.length; i < il; i ++ ) { const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); v( vert.x, vert.y, - z ); } // expand holes for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; oneHoleMovements = holesMovements[ h ]; for ( let i = 0, il = ahole.length; i < il; i ++ ) { const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); v( vert.x, vert.y, - z ); } } } const bs = bevelSize + bevelOffset; // Back facing vertices for ( let i = 0; i < vlen; i ++ ) { const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; if ( ! extrudeByPath ) { v( vert.x, vert.y, 0 ); } else { // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); v( position2.x, position2.y, position2.z ); } } // Add stepped vertices... // Including front facing vertices for ( let s = 1; s <= steps; s ++ ) { for ( let i = 0; i < vlen; i ++ ) { const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; if ( ! extrudeByPath ) { v( vert.x, vert.y, depth / steps * s ); } else { // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); v( position2.x, position2.y, position2.z ); } } } // Add bevel segments planes //for ( b = 1; b <= bevelSegments; b ++ ) { for ( let b = bevelSegments - 1; b >= 0; b -- ) { const t = b / bevelSegments; const z = bevelThickness * Math.cos( t * Math.PI / 2 ); const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; // contract shape for ( let i = 0, il = contour.length; i < il; i ++ ) { const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); v( vert.x, vert.y, depth + z ); } // expand holes for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; oneHoleMovements = holesMovements[ h ]; for ( let i = 0, il = ahole.length; i < il; i ++ ) { const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); if ( ! extrudeByPath ) { v( vert.x, vert.y, depth + z ); } else { v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); } } } } /* Faces */ // Top and bottom faces buildLidFaces(); // Sides faces buildSideFaces(); ///// Internal functions function buildLidFaces() { const start = verticesArray.length / 3; if ( bevelEnabled ) { let layer = 0; // steps + 1 let offset = vlen * layer; // Bottom faces for ( let i = 0; i < flen; i ++ ) { const face = faces[ i ]; f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); } layer = steps + bevelSegments * 2; offset = vlen * layer; // Top faces for ( let i = 0; i < flen; i ++ ) { const face = faces[ i ]; f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); } } else { // Bottom faces for ( let i = 0; i < flen; i ++ ) { const face = faces[ i ]; f3( face[ 2 ], face[ 1 ], face[ 0 ] ); } // Top faces for ( let i = 0; i < flen; i ++ ) { const face = faces[ i ]; f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); } } scope.addGroup( start, verticesArray.length / 3 - start, 0 ); } // Create faces for the z-sides of the shape function buildSideFaces() { const start = verticesArray.length / 3; let layeroffset = 0; sidewalls( contour, layeroffset ); layeroffset += contour.length; for ( let h = 0, hl = holes.length; h < hl; h ++ ) { const ahole = holes[ h ]; sidewalls( ahole, layeroffset ); //, true layeroffset += ahole.length; } scope.addGroup( start, verticesArray.length / 3 - start, 1 ); } function sidewalls( contour, layeroffset ) { let i = contour.length; while ( -- i >= 0 ) { const j = i; let k = i - 1; if ( k < 0 ) k = contour.length - 1; //console.log('b', i,j, i-1, k,vertices.length); for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { const slen1 = vlen * s; const slen2 = vlen * ( s + 1 ); const a = layeroffset + j + slen1, b = layeroffset + k + slen1, c = layeroffset + k + slen2, d = layeroffset + j + slen2; f4( a, b, c, d ); } } } function v( x, y, z ) { placeholder.push( x ); placeholder.push( y ); placeholder.push( z ); } function f3( a, b, c ) { addVertex( a ); addVertex( b ); addVertex( c ); const nextIndex = verticesArray.length / 3; const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); addUV( uvs[ 0 ] ); addUV( uvs[ 1 ] ); addUV( uvs[ 2 ] ); } function f4( a, b, c, d ) { addVertex( a ); addVertex( b ); addVertex( d ); addVertex( b ); addVertex( c ); addVertex( d ); const nextIndex = verticesArray.length / 3; const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); addUV( uvs[ 0 ] ); addUV( uvs[ 1 ] ); addUV( uvs[ 3 ] ); addUV( uvs[ 1 ] ); addUV( uvs[ 2 ] ); addUV( uvs[ 3 ] ); } function addVertex( index ) { verticesArray.push( placeholder[ index * 3 + 0 ] ); verticesArray.push( placeholder[ index * 3 + 1 ] ); verticesArray.push( placeholder[ index * 3 + 2 ] ); } function addUV( vector2 ) { uvArray.push( vector2.x ); uvArray.push( vector2.y ); } } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } toJSON() { const data = super.toJSON(); const shapes = this.parameters.shapes; const options = this.parameters.options; return toJSON$1( shapes, options, data ); } static fromJSON( data, shapes ) { const geometryShapes = []; for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { const shape = shapes[ data.shapes[ j ] ]; geometryShapes.push( shape ); } const extrudePath = data.options.extrudePath; if ( extrudePath !== undefined ) { data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); } return new ExtrudeGeometry( geometryShapes, data.options ); } } const WorldUVGenerator = { generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { const a_x = vertices[ indexA * 3 ]; const a_y = vertices[ indexA * 3 + 1 ]; const b_x = vertices[ indexB * 3 ]; const b_y = vertices[ indexB * 3 + 1 ]; const c_x = vertices[ indexC * 3 ]; const c_y = vertices[ indexC * 3 + 1 ]; return [ new Vector2( a_x, a_y ), new Vector2( b_x, b_y ), new Vector2( c_x, c_y ) ]; }, generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { const a_x = vertices[ indexA * 3 ]; const a_y = vertices[ indexA * 3 + 1 ]; const a_z = vertices[ indexA * 3 + 2 ]; const b_x = vertices[ indexB * 3 ]; const b_y = vertices[ indexB * 3 + 1 ]; const b_z = vertices[ indexB * 3 + 2 ]; const c_x = vertices[ indexC * 3 ]; const c_y = vertices[ indexC * 3 + 1 ]; const c_z = vertices[ indexC * 3 + 2 ]; const d_x = vertices[ indexD * 3 ]; const d_y = vertices[ indexD * 3 + 1 ]; const d_z = vertices[ indexD * 3 + 2 ]; if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { return [ new Vector2( a_x, 1 - a_z ), new Vector2( b_x, 1 - b_z ), new Vector2( c_x, 1 - c_z ), new Vector2( d_x, 1 - d_z ) ]; } else { return [ new Vector2( a_y, 1 - a_z ), new Vector2( b_y, 1 - b_z ), new Vector2( c_y, 1 - c_z ), new Vector2( d_y, 1 - d_z ) ]; } } }; function toJSON$1( shapes, options, data ) { data.shapes = []; if ( Array.isArray( shapes ) ) { for ( let i = 0, l = shapes.length; i < l; i ++ ) { const shape = shapes[ i ]; data.shapes.push( shape.uuid ); } } else { data.shapes.push( shapes.uuid ); } data.options = Object.assign( {}, options ); if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); return data; } class IcosahedronGeometry extends PolyhedronGeometry { constructor( radius = 1, detail = 0 ) { const t = ( 1 + Math.sqrt( 5 ) ) / 2; const vertices = [ - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 ]; const indices = [ 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 ]; super( vertices, indices, radius, detail ); this.type = 'IcosahedronGeometry'; this.parameters = { radius: radius, detail: detail }; } static fromJSON( data ) { return new IcosahedronGeometry( data.radius, data.detail ); } } class OctahedronGeometry extends PolyhedronGeometry { constructor( radius = 1, detail = 0 ) { const vertices = [ 1, 0, 0, - 1, 0, 0, 0, 1, 0, 0, - 1, 0, 0, 0, 1, 0, 0, - 1 ]; const indices = [ 0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2 ]; super( vertices, indices, radius, detail ); this.type = 'OctahedronGeometry'; this.parameters = { radius: radius, detail: detail }; } static fromJSON( data ) { return new OctahedronGeometry( data.radius, data.detail ); } } class RingGeometry extends BufferGeometry { constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { super(); this.type = 'RingGeometry'; this.parameters = { innerRadius: innerRadius, outerRadius: outerRadius, thetaSegments: thetaSegments, phiSegments: phiSegments, thetaStart: thetaStart, thetaLength: thetaLength }; thetaSegments = Math.max( 3, thetaSegments ); phiSegments = Math.max( 1, phiSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // some helper variables let radius = innerRadius; const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); const vertex = new Vector3(); const uv = new Vector2(); // generate vertices, normals and uvs for ( let j = 0; j <= phiSegments; j ++ ) { for ( let i = 0; i <= thetaSegments; i ++ ) { // values are generate from the inside of the ring to the outside const segment = thetaStart + i / thetaSegments * thetaLength; // vertex vertex.x = radius * Math.cos( segment ); vertex.y = radius * Math.sin( segment ); vertices.push( vertex.x, vertex.y, vertex.z ); // normal normals.push( 0, 0, 1 ); // uv uv.x = ( vertex.x / outerRadius + 1 ) / 2; uv.y = ( vertex.y / outerRadius + 1 ) / 2; uvs.push( uv.x, uv.y ); } // increase the radius for next row of vertices radius += radiusStep; } // indices for ( let j = 0; j < phiSegments; j ++ ) { const thetaSegmentLevel = j * ( thetaSegments + 1 ); for ( let i = 0; i < thetaSegments; i ++ ) { const segment = i + thetaSegmentLevel; const a = segment; const b = segment + thetaSegments + 1; const c = segment + thetaSegments + 2; const d = segment + 1; // faces indices.push( a, b, d ); indices.push( b, c, d ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); } } class ShapeGeometry extends BufferGeometry { constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) { super(); this.type = 'ShapeGeometry'; this.parameters = { shapes: shapes, curveSegments: curveSegments }; // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables let groupStart = 0; let groupCount = 0; // allow single and array values for "shapes" parameter if ( Array.isArray( shapes ) === false ) { addShape( shapes ); } else { for ( let i = 0; i < shapes.length; i ++ ) { addShape( shapes[ i ] ); this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support groupStart += groupCount; groupCount = 0; } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); // helper functions function addShape( shape ) { const indexOffset = vertices.length / 3; const points = shape.extractPoints( curveSegments ); let shapeVertices = points.shape; const shapeHoles = points.holes; // check direction of vertices if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { shapeVertices = shapeVertices.reverse(); } for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { const shapeHole = shapeHoles[ i ]; if ( ShapeUtils.isClockWise( shapeHole ) === true ) { shapeHoles[ i ] = shapeHole.reverse(); } } const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); // join vertices of inner and outer paths to a single array for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { const shapeHole = shapeHoles[ i ]; shapeVertices = shapeVertices.concat( shapeHole ); } // vertices, normals, uvs for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { const vertex = shapeVertices[ i ]; vertices.push( vertex.x, vertex.y, 0 ); normals.push( 0, 0, 1 ); uvs.push( vertex.x, vertex.y ); // world uvs } // indices for ( let i = 0, l = faces.length; i < l; i ++ ) { const face = faces[ i ]; const a = face[ 0 ] + indexOffset; const b = face[ 1 ] + indexOffset; const c = face[ 2 ] + indexOffset; indices.push( a, b, c ); groupCount += 3; } } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } toJSON() { const data = super.toJSON(); const shapes = this.parameters.shapes; return toJSON( shapes, data ); } static fromJSON( data, shapes ) { const geometryShapes = []; for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { const shape = shapes[ data.shapes[ j ] ]; geometryShapes.push( shape ); } return new ShapeGeometry( geometryShapes, data.curveSegments ); } } function toJSON( shapes, data ) { data.shapes = []; if ( Array.isArray( shapes ) ) { for ( let i = 0, l = shapes.length; i < l; i ++ ) { const shape = shapes[ i ]; data.shapes.push( shape.uuid ); } } else { data.shapes.push( shapes.uuid ); } return data; } class SphereGeometry extends BufferGeometry { constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { super(); this.type = 'SphereGeometry'; this.parameters = { radius: radius, widthSegments: widthSegments, heightSegments: heightSegments, phiStart: phiStart, phiLength: phiLength, thetaStart: thetaStart, thetaLength: thetaLength }; widthSegments = Math.max( 3, Math.floor( widthSegments ) ); heightSegments = Math.max( 2, Math.floor( heightSegments ) ); const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); let index = 0; const grid = []; const vertex = new Vector3(); const normal = new Vector3(); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // generate vertices, normals and uvs for ( let iy = 0; iy <= heightSegments; iy ++ ) { const verticesRow = []; const v = iy / heightSegments; // special case for the poles let uOffset = 0; if ( iy === 0 && thetaStart === 0 ) { uOffset = 0.5 / widthSegments; } else if ( iy === heightSegments && thetaEnd === Math.PI ) { uOffset = - 0.5 / widthSegments; } for ( let ix = 0; ix <= widthSegments; ix ++ ) { const u = ix / widthSegments; // vertex vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); vertices.push( vertex.x, vertex.y, vertex.z ); // normal normal.copy( vertex ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( u + uOffset, 1 - v ); verticesRow.push( index ++ ); } grid.push( verticesRow ); } // indices for ( let iy = 0; iy < heightSegments; iy ++ ) { for ( let ix = 0; ix < widthSegments; ix ++ ) { const a = grid[ iy ][ ix + 1 ]; const b = grid[ iy ][ ix ]; const c = grid[ iy + 1 ][ ix ]; const d = grid[ iy + 1 ][ ix + 1 ]; if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); } } class TetrahedronGeometry extends PolyhedronGeometry { constructor( radius = 1, detail = 0 ) { const vertices = [ 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 ]; const indices = [ 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 ]; super( vertices, indices, radius, detail ); this.type = 'TetrahedronGeometry'; this.parameters = { radius: radius, detail: detail }; } static fromJSON( data ) { return new TetrahedronGeometry( data.radius, data.detail ); } } class TorusGeometry extends BufferGeometry { constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { super(); this.type = 'TorusGeometry'; this.parameters = { radius: radius, tube: tube, radialSegments: radialSegments, tubularSegments: tubularSegments, arc: arc }; radialSegments = Math.floor( radialSegments ); tubularSegments = Math.floor( tubularSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables const center = new Vector3(); const vertex = new Vector3(); const normal = new Vector3(); // generate vertices, normals and uvs for ( let j = 0; j <= radialSegments; j ++ ) { for ( let i = 0; i <= tubularSegments; i ++ ) { const u = i / tubularSegments * arc; const v = j / radialSegments * Math.PI * 2; // vertex vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); vertex.z = tube * Math.sin( v ); vertices.push( vertex.x, vertex.y, vertex.z ); // normal center.x = radius * Math.cos( u ); center.y = radius * Math.sin( u ); normal.subVectors( vertex, center ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( i / tubularSegments ); uvs.push( j / radialSegments ); } } // generate indices for ( let j = 1; j <= radialSegments; j ++ ) { for ( let i = 1; i <= tubularSegments; i ++ ) { // indices const a = ( tubularSegments + 1 ) * j + i - 1; const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; const d = ( tubularSegments + 1 ) * j + i; // faces indices.push( a, b, d ); indices.push( b, c, d ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); } } class TorusKnotGeometry extends BufferGeometry { constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { super(); this.type = 'TorusKnotGeometry'; this.parameters = { radius: radius, tube: tube, tubularSegments: tubularSegments, radialSegments: radialSegments, p: p, q: q }; tubularSegments = Math.floor( tubularSegments ); radialSegments = Math.floor( radialSegments ); // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; // helper variables const vertex = new Vector3(); const normal = new Vector3(); const P1 = new Vector3(); const P2 = new Vector3(); const B = new Vector3(); const T = new Vector3(); const N = new Vector3(); // generate vertices, normals and uvs for ( let i = 0; i <= tubularSegments; ++ i ) { // the radian "u" is used to calculate the position on the torus curve of the current tubular segment const u = i / tubularSegments * p * Math.PI * 2; // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions calculatePositionOnCurve( u, p, q, radius, P1 ); calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); // calculate orthonormal basis T.subVectors( P2, P1 ); N.addVectors( P2, P1 ); B.crossVectors( T, N ); N.crossVectors( B, T ); // normalize B, N. T can be ignored, we don't use it B.normalize(); N.normalize(); for ( let j = 0; j <= radialSegments; ++ j ) { // now calculate the vertices. they are nothing more than an extrusion of the torus curve. // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. const v = j / radialSegments * Math.PI * 2; const cx = - tube * Math.cos( v ); const cy = tube * Math.sin( v ); // now calculate the final vertex position. // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve vertex.x = P1.x + ( cx * N.x + cy * B.x ); vertex.y = P1.y + ( cx * N.y + cy * B.y ); vertex.z = P1.z + ( cx * N.z + cy * B.z ); vertices.push( vertex.x, vertex.y, vertex.z ); // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) normal.subVectors( vertex, P1 ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( i / tubularSegments ); uvs.push( j / radialSegments ); } } // generate indices for ( let j = 1; j <= tubularSegments; j ++ ) { for ( let i = 1; i <= radialSegments; i ++ ) { // indices const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); const b = ( radialSegments + 1 ) * j + ( i - 1 ); const c = ( radialSegments + 1 ) * j + i; const d = ( radialSegments + 1 ) * ( j - 1 ) + i; // faces indices.push( a, b, d ); indices.push( b, c, d ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); // this function calculates the current position on the torus curve function calculatePositionOnCurve( u, p, q, radius, position ) { const cu = Math.cos( u ); const su = Math.sin( u ); const quOverP = q / p * u; const cs = Math.cos( quOverP ); position.x = radius * ( 2 + cs ) * 0.5 * cu; position.y = radius * ( 2 + cs ) * su * 0.5; position.z = radius * Math.sin( quOverP ) * 0.5; } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } static fromJSON( data ) { return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); } } class TubeGeometry extends BufferGeometry { constructor( path = new QuadraticBezierCurve3( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { super(); this.type = 'TubeGeometry'; this.parameters = { path: path, tubularSegments: tubularSegments, radius: radius, radialSegments: radialSegments, closed: closed }; const frames = path.computeFrenetFrames( tubularSegments, closed ); // expose internals this.tangents = frames.tangents; this.normals = frames.normals; this.binormals = frames.binormals; // helper variables const vertex = new Vector3(); const normal = new Vector3(); const uv = new Vector2(); let P = new Vector3(); // buffer const vertices = []; const normals = []; const uvs = []; const indices = []; // create buffer data generateBufferData(); // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); // functions function generateBufferData() { for ( let i = 0; i < tubularSegments; i ++ ) { generateSegment( i ); } // if the geometry is not closed, generate the last row of vertices and normals // at the regular position on the given path // // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) generateSegment( ( closed === false ) ? tubularSegments : 0 ); // uvs are generated in a separate function. // this makes it easy compute correct values for closed geometries generateUVs(); // finally create faces generateIndices(); } function generateSegment( i ) { // we use getPointAt to sample evenly distributed points from the given path P = path.getPointAt( i / tubularSegments, P ); // retrieve corresponding normal and binormal const N = frames.normals[ i ]; const B = frames.binormals[ i ]; // generate normals and vertices for the current segment for ( let j = 0; j <= radialSegments; j ++ ) { const v = j / radialSegments * Math.PI * 2; const sin = Math.sin( v ); const cos = - Math.cos( v ); // normal normal.x = ( cos * N.x + sin * B.x ); normal.y = ( cos * N.y + sin * B.y ); normal.z = ( cos * N.z + sin * B.z ); normal.normalize(); normals.push( normal.x, normal.y, normal.z ); // vertex vertex.x = P.x + radius * normal.x; vertex.y = P.y + radius * normal.y; vertex.z = P.z + radius * normal.z; vertices.push( vertex.x, vertex.y, vertex.z ); } } function generateIndices() { for ( let j = 1; j <= tubularSegments; j ++ ) { for ( let i = 1; i <= radialSegments; i ++ ) { const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); const b = ( radialSegments + 1 ) * j + ( i - 1 ); const c = ( radialSegments + 1 ) * j + i; const d = ( radialSegments + 1 ) * ( j - 1 ) + i; // faces indices.push( a, b, d ); indices.push( b, c, d ); } } } function generateUVs() { for ( let i = 0; i <= tubularSegments; i ++ ) { for ( let j = 0; j <= radialSegments; j ++ ) { uv.x = i / tubularSegments; uv.y = j / radialSegments; uvs.push( uv.x, uv.y ); } } } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } toJSON() { const data = super.toJSON(); data.path = this.parameters.path.toJSON(); return data; } static fromJSON( data ) { // This only works for built-in curves (e.g. CatmullRomCurve3). // User defined curves or instances of CurvePath will not be deserialized. return new TubeGeometry( new Curves[ data.path.type ]().fromJSON( data.path ), data.tubularSegments, data.radius, data.radialSegments, data.closed ); } } class WireframeGeometry extends BufferGeometry { constructor( geometry = null ) { super(); this.type = 'WireframeGeometry'; this.parameters = { geometry: geometry }; if ( geometry !== null ) { // buffer const vertices = []; const edges = new Set(); // helper variables const start = new Vector3(); const end = new Vector3(); if ( geometry.index !== null ) { // indexed BufferGeometry const position = geometry.attributes.position; const indices = geometry.index; let groups = geometry.groups; if ( groups.length === 0 ) { groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; } // create a data structure that contains all edges without duplicates for ( let o = 0, ol = groups.length; o < ol; ++ o ) { const group = groups[ o ]; const groupStart = group.start; const groupCount = group.count; for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { for ( let j = 0; j < 3; j ++ ) { const index1 = indices.getX( i + j ); const index2 = indices.getX( i + ( j + 1 ) % 3 ); start.fromBufferAttribute( position, index1 ); end.fromBufferAttribute( position, index2 ); if ( isUniqueEdge( start, end, edges ) === true ) { vertices.push( start.x, start.y, start.z ); vertices.push( end.x, end.y, end.z ); } } } } } else { // non-indexed BufferGeometry const position = geometry.attributes.position; for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { for ( let j = 0; j < 3; j ++ ) { // three edges per triangle, an edge is represented as (index1, index2) // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) const index1 = 3 * i + j; const index2 = 3 * i + ( ( j + 1 ) % 3 ); start.fromBufferAttribute( position, index1 ); end.fromBufferAttribute( position, index2 ); if ( isUniqueEdge( start, end, edges ) === true ) { vertices.push( start.x, start.y, start.z ); vertices.push( end.x, end.y, end.z ); } } } } // build geometry this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } } function isUniqueEdge( start, end, edges ) { const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { return false; } else { edges.add( hash1 ); edges.add( hash2 ); return true; } } var Geometries = /*#__PURE__*/Object.freeze({ __proto__: null, BoxGeometry: BoxGeometry, CapsuleGeometry: CapsuleGeometry, CircleGeometry: CircleGeometry, ConeGeometry: ConeGeometry, CylinderGeometry: CylinderGeometry, DodecahedronGeometry: DodecahedronGeometry, EdgesGeometry: EdgesGeometry, ExtrudeGeometry: ExtrudeGeometry, IcosahedronGeometry: IcosahedronGeometry, LatheGeometry: LatheGeometry, OctahedronGeometry: OctahedronGeometry, PlaneGeometry: PlaneGeometry, PolyhedronGeometry: PolyhedronGeometry, RingGeometry: RingGeometry, ShapeGeometry: ShapeGeometry, SphereGeometry: SphereGeometry, TetrahedronGeometry: TetrahedronGeometry, TorusGeometry: TorusGeometry, TorusKnotGeometry: TorusKnotGeometry, TubeGeometry: TubeGeometry, WireframeGeometry: WireframeGeometry }); class ShadowMaterial extends Material { constructor( parameters ) { super(); this.isShadowMaterial = true; this.type = 'ShadowMaterial'; this.color = new Color( 0x000000 ); this.transparent = true; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.fog = source.fog; return this; } } class RawShaderMaterial extends ShaderMaterial { constructor( parameters ) { super( parameters ); this.isRawShaderMaterial = true; this.type = 'RawShaderMaterial'; } } class MeshStandardMaterial extends Material { constructor( parameters ) { super(); this.isMeshStandardMaterial = true; this.defines = { 'STANDARD': '' }; this.type = 'MeshStandardMaterial'; this.color = new Color( 0xffffff ); // diffuse this.roughness = 1.0; this.metalness = 0.0; this.map = null; this.lightMap = null; this.lightMapIntensity = 1.0; this.aoMap = null; this.aoMapIntensity = 1.0; this.emissive = new Color( 0x000000 ); this.emissiveIntensity = 1.0; this.emissiveMap = null; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.roughnessMap = null; this.metalnessMap = null; this.alphaMap = null; this.envMap = null; this.envMapIntensity = 1.0; this.wireframe = false; this.wireframeLinewidth = 1; this.wireframeLinecap = 'round'; this.wireframeLinejoin = 'round'; this.flatShading = false; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.defines = { 'STANDARD': '' }; this.color.copy( source.color ); this.roughness = source.roughness; this.metalness = source.metalness; this.map = source.map; this.lightMap = source.lightMap; this.lightMapIntensity = source.lightMapIntensity; this.aoMap = source.aoMap; this.aoMapIntensity = source.aoMapIntensity; this.emissive.copy( source.emissive ); this.emissiveMap = source.emissiveMap; this.emissiveIntensity = source.emissiveIntensity; this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.roughnessMap = source.roughnessMap; this.metalnessMap = source.metalnessMap; this.alphaMap = source.alphaMap; this.envMap = source.envMap; this.envMapIntensity = source.envMapIntensity; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.wireframeLinecap = source.wireframeLinecap; this.wireframeLinejoin = source.wireframeLinejoin; this.flatShading = source.flatShading; this.fog = source.fog; return this; } } class MeshPhysicalMaterial extends MeshStandardMaterial { constructor( parameters ) { super(); this.isMeshPhysicalMaterial = true; this.defines = { 'STANDARD': '', 'PHYSICAL': '' }; this.type = 'MeshPhysicalMaterial'; this.anisotropyRotation = 0; this.anisotropyMap = null; this.clearcoatMap = null; this.clearcoatRoughness = 0.0; this.clearcoatRoughnessMap = null; this.clearcoatNormalScale = new Vector2( 1, 1 ); this.clearcoatNormalMap = null; this.ior = 1.5; Object.defineProperty( this, 'reflectivity', { get: function () { return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); }, set: function ( reflectivity ) { this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); } } ); this.iridescenceMap = null; this.iridescenceIOR = 1.3; this.iridescenceThicknessRange = [ 100, 400 ]; this.iridescenceThicknessMap = null; this.sheenColor = new Color( 0x000000 ); this.sheenColorMap = null; this.sheenRoughness = 1.0; this.sheenRoughnessMap = null; this.transmissionMap = null; this.thickness = 0; this.thicknessMap = null; this.attenuationDistance = Infinity; this.attenuationColor = new Color( 1, 1, 1 ); this.specularIntensity = 1.0; this.specularIntensityMap = null; this.specularColor = new Color( 1, 1, 1 ); this.specularColorMap = null; this._anisotropy = 0; this._clearcoat = 0; this._iridescence = 0; this._sheen = 0.0; this._transmission = 0; this.setValues( parameters ); } get anisotropy() { return this._anisotropy; } set anisotropy( value ) { if ( this._anisotropy > 0 !== value > 0 ) { this.version ++; } this._anisotropy = value; } get clearcoat() { return this._clearcoat; } set clearcoat( value ) { if ( this._clearcoat > 0 !== value > 0 ) { this.version ++; } this._clearcoat = value; } get iridescence() { return this._iridescence; } set iridescence( value ) { if ( this._iridescence > 0 !== value > 0 ) { this.version ++; } this._iridescence = value; } get sheen() { return this._sheen; } set sheen( value ) { if ( this._sheen > 0 !== value > 0 ) { this.version ++; } this._sheen = value; } get transmission() { return this._transmission; } set transmission( value ) { if ( this._transmission > 0 !== value > 0 ) { this.version ++; } this._transmission = value; } copy( source ) { super.copy( source ); this.defines = { 'STANDARD': '', 'PHYSICAL': '' }; this.anisotropy = source.anisotropy; this.anisotropyRotation = source.anisotropyRotation; this.anisotropyMap = source.anisotropyMap; this.clearcoat = source.clearcoat; this.clearcoatMap = source.clearcoatMap; this.clearcoatRoughness = source.clearcoatRoughness; this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; this.clearcoatNormalMap = source.clearcoatNormalMap; this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); this.ior = source.ior; this.iridescence = source.iridescence; this.iridescenceMap = source.iridescenceMap; this.iridescenceIOR = source.iridescenceIOR; this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; this.iridescenceThicknessMap = source.iridescenceThicknessMap; this.sheen = source.sheen; this.sheenColor.copy( source.sheenColor ); this.sheenColorMap = source.sheenColorMap; this.sheenRoughness = source.sheenRoughness; this.sheenRoughnessMap = source.sheenRoughnessMap; this.transmission = source.transmission; this.transmissionMap = source.transmissionMap; this.thickness = source.thickness; this.thicknessMap = source.thicknessMap; this.attenuationDistance = source.attenuationDistance; this.attenuationColor.copy( source.attenuationColor ); this.specularIntensity = source.specularIntensity; this.specularIntensityMap = source.specularIntensityMap; this.specularColor.copy( source.specularColor ); this.specularColorMap = source.specularColorMap; return this; } } class MeshPhongMaterial extends Material { constructor( parameters ) { super(); this.isMeshPhongMaterial = true; this.type = 'MeshPhongMaterial'; this.color = new Color( 0xffffff ); // diffuse this.specular = new Color( 0x111111 ); this.shininess = 30; this.map = null; this.lightMap = null; this.lightMapIntensity = 1.0; this.aoMap = null; this.aoMapIntensity = 1.0; this.emissive = new Color( 0x000000 ); this.emissiveIntensity = 1.0; this.emissiveMap = null; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.specularMap = null; this.alphaMap = null; this.envMap = null; this.combine = MultiplyOperation; this.reflectivity = 1; this.refractionRatio = 0.98; this.wireframe = false; this.wireframeLinewidth = 1; this.wireframeLinecap = 'round'; this.wireframeLinejoin = 'round'; this.flatShading = false; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.specular.copy( source.specular ); this.shininess = source.shininess; this.map = source.map; this.lightMap = source.lightMap; this.lightMapIntensity = source.lightMapIntensity; this.aoMap = source.aoMap; this.aoMapIntensity = source.aoMapIntensity; this.emissive.copy( source.emissive ); this.emissiveMap = source.emissiveMap; this.emissiveIntensity = source.emissiveIntensity; this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.specularMap = source.specularMap; this.alphaMap = source.alphaMap; this.envMap = source.envMap; this.combine = source.combine; this.reflectivity = source.reflectivity; this.refractionRatio = source.refractionRatio; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.wireframeLinecap = source.wireframeLinecap; this.wireframeLinejoin = source.wireframeLinejoin; this.flatShading = source.flatShading; this.fog = source.fog; return this; } } class MeshToonMaterial extends Material { constructor( parameters ) { super(); this.isMeshToonMaterial = true; this.defines = { 'TOON': '' }; this.type = 'MeshToonMaterial'; this.color = new Color( 0xffffff ); this.map = null; this.gradientMap = null; this.lightMap = null; this.lightMapIntensity = 1.0; this.aoMap = null; this.aoMapIntensity = 1.0; this.emissive = new Color( 0x000000 ); this.emissiveIntensity = 1.0; this.emissiveMap = null; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.alphaMap = null; this.wireframe = false; this.wireframeLinewidth = 1; this.wireframeLinecap = 'round'; this.wireframeLinejoin = 'round'; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.gradientMap = source.gradientMap; this.lightMap = source.lightMap; this.lightMapIntensity = source.lightMapIntensity; this.aoMap = source.aoMap; this.aoMapIntensity = source.aoMapIntensity; this.emissive.copy( source.emissive ); this.emissiveMap = source.emissiveMap; this.emissiveIntensity = source.emissiveIntensity; this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.alphaMap = source.alphaMap; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.wireframeLinecap = source.wireframeLinecap; this.wireframeLinejoin = source.wireframeLinejoin; this.fog = source.fog; return this; } } class MeshNormalMaterial extends Material { constructor( parameters ) { super(); this.isMeshNormalMaterial = true; this.type = 'MeshNormalMaterial'; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.wireframe = false; this.wireframeLinewidth = 1; this.flatShading = false; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.flatShading = source.flatShading; return this; } } class MeshLambertMaterial extends Material { constructor( parameters ) { super(); this.isMeshLambertMaterial = true; this.type = 'MeshLambertMaterial'; this.color = new Color( 0xffffff ); // diffuse this.map = null; this.lightMap = null; this.lightMapIntensity = 1.0; this.aoMap = null; this.aoMapIntensity = 1.0; this.emissive = new Color( 0x000000 ); this.emissiveIntensity = 1.0; this.emissiveMap = null; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.specularMap = null; this.alphaMap = null; this.envMap = null; this.combine = MultiplyOperation; this.reflectivity = 1; this.refractionRatio = 0.98; this.wireframe = false; this.wireframeLinewidth = 1; this.wireframeLinecap = 'round'; this.wireframeLinejoin = 'round'; this.flatShading = false; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.color.copy( source.color ); this.map = source.map; this.lightMap = source.lightMap; this.lightMapIntensity = source.lightMapIntensity; this.aoMap = source.aoMap; this.aoMapIntensity = source.aoMapIntensity; this.emissive.copy( source.emissive ); this.emissiveMap = source.emissiveMap; this.emissiveIntensity = source.emissiveIntensity; this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.specularMap = source.specularMap; this.alphaMap = source.alphaMap; this.envMap = source.envMap; this.combine = source.combine; this.reflectivity = source.reflectivity; this.refractionRatio = source.refractionRatio; this.wireframe = source.wireframe; this.wireframeLinewidth = source.wireframeLinewidth; this.wireframeLinecap = source.wireframeLinecap; this.wireframeLinejoin = source.wireframeLinejoin; this.flatShading = source.flatShading; this.fog = source.fog; return this; } } class MeshMatcapMaterial extends Material { constructor( parameters ) { super(); this.isMeshMatcapMaterial = true; this.defines = { 'MATCAP': '' }; this.type = 'MeshMatcapMaterial'; this.color = new Color( 0xffffff ); // diffuse this.matcap = null; this.map = null; this.bumpMap = null; this.bumpScale = 1; this.normalMap = null; this.normalMapType = TangentSpaceNormalMap; this.normalScale = new Vector2( 1, 1 ); this.displacementMap = null; this.displacementScale = 1; this.displacementBias = 0; this.alphaMap = null; this.flatShading = false; this.fog = true; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.defines = { 'MATCAP': '' }; this.color.copy( source.color ); this.matcap = source.matcap; this.map = source.map; this.bumpMap = source.bumpMap; this.bumpScale = source.bumpScale; this.normalMap = source.normalMap; this.normalMapType = source.normalMapType; this.normalScale.copy( source.normalScale ); this.displacementMap = source.displacementMap; this.displacementScale = source.displacementScale; this.displacementBias = source.displacementBias; this.alphaMap = source.alphaMap; this.flatShading = source.flatShading; this.fog = source.fog; return this; } } class LineDashedMaterial extends LineBasicMaterial { constructor( parameters ) { super(); this.isLineDashedMaterial = true; this.type = 'LineDashedMaterial'; this.scale = 1; this.dashSize = 3; this.gapSize = 1; this.setValues( parameters ); } copy( source ) { super.copy( source ); this.scale = source.scale; this.dashSize = source.dashSize; this.gapSize = source.gapSize; return this; } } // converts an array to a specific type function convertArray( array, type, forceClone ) { if ( ! array || // let 'undefined' and 'null' pass ! forceClone && array.constructor === type ) return array; if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { return new type( array ); // create typed array } return Array.prototype.slice.call( array ); // create Array } function isTypedArray( object ) { return ArrayBuffer.isView( object ) && ! ( object instanceof DataView ); } // returns an array by which times and values can be sorted function getKeyframeOrder( times ) { function compareTime( i, j ) { return times[ i ] - times[ j ]; } const n = times.length; const result = new Array( n ); for ( let i = 0; i !== n; ++ i ) result[ i ] = i; result.sort( compareTime ); return result; } // uses the array previously returned by 'getKeyframeOrder' to sort data function sortedArray( values, stride, order ) { const nValues = values.length; const result = new values.constructor( nValues ); for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { const srcOffset = order[ i ] * stride; for ( let j = 0; j !== stride; ++ j ) { result[ dstOffset ++ ] = values[ srcOffset + j ]; } } return result; } // function for parsing AOS keyframe formats function flattenJSON( jsonKeys, times, values, valuePropertyName ) { let i = 1, key = jsonKeys[ 0 ]; while ( key !== undefined && key[ valuePropertyName ] === undefined ) { key = jsonKeys[ i ++ ]; } if ( key === undefined ) return; // no data let value = key[ valuePropertyName ]; if ( value === undefined ) return; // no data if ( Array.isArray( value ) ) { do { value = key[ valuePropertyName ]; if ( value !== undefined ) { times.push( key.time ); values.push.apply( values, value ); // push all elements } key = jsonKeys[ i ++ ]; } while ( key !== undefined ); } else if ( value.toArray !== undefined ) { // ...assume THREE.Math-ish do { value = key[ valuePropertyName ]; if ( value !== undefined ) { times.push( key.time ); value.toArray( values, values.length ); } key = jsonKeys[ i ++ ]; } while ( key !== undefined ); } else { // otherwise push as-is do { value = key[ valuePropertyName ]; if ( value !== undefined ) { times.push( key.time ); values.push( value ); } key = jsonKeys[ i ++ ]; } while ( key !== undefined ); } } function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { const clip = sourceClip.clone(); clip.name = name; const tracks = []; for ( let i = 0; i < clip.tracks.length; ++ i ) { const track = clip.tracks[ i ]; const valueSize = track.getValueSize(); const times = []; const values = []; for ( let j = 0; j < track.times.length; ++ j ) { const frame = track.times[ j ] * fps; if ( frame < startFrame || frame >= endFrame ) continue; times.push( track.times[ j ] ); for ( let k = 0; k < valueSize; ++ k ) { values.push( track.values[ j * valueSize + k ] ); } } if ( times.length === 0 ) continue; track.times = convertArray( times, track.times.constructor ); track.values = convertArray( values, track.values.constructor ); tracks.push( track ); } clip.tracks = tracks; // find minimum .times value across all tracks in the trimmed clip let minStartTime = Infinity; for ( let i = 0; i < clip.tracks.length; ++ i ) { if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { minStartTime = clip.tracks[ i ].times[ 0 ]; } } // shift all tracks such that clip begins at t=0 for ( let i = 0; i < clip.tracks.length; ++ i ) { clip.tracks[ i ].shift( - 1 * minStartTime ); } clip.resetDuration(); return clip; } function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { if ( fps <= 0 ) fps = 30; const numTracks = referenceClip.tracks.length; const referenceTime = referenceFrame / fps; // Make each track's values relative to the values at the reference frame for ( let i = 0; i < numTracks; ++ i ) { const referenceTrack = referenceClip.tracks[ i ]; const referenceTrackType = referenceTrack.ValueTypeName; // Skip this track if it's non-numeric if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; // Find the track in the target clip whose name and type matches the reference track const targetTrack = targetClip.tracks.find( function ( track ) { return track.name === referenceTrack.name && track.ValueTypeName === referenceTrackType; } ); if ( targetTrack === undefined ) continue; let referenceOffset = 0; const referenceValueSize = referenceTrack.getValueSize(); if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { referenceOffset = referenceValueSize / 3; } let targetOffset = 0; const targetValueSize = targetTrack.getValueSize(); if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { targetOffset = targetValueSize / 3; } const lastIndex = referenceTrack.times.length - 1; let referenceValue; // Find the value to subtract out of the track if ( referenceTime <= referenceTrack.times[ 0 ] ) { // Reference frame is earlier than the first keyframe, so just use the first keyframe const startIndex = referenceOffset; const endIndex = referenceValueSize - referenceOffset; referenceValue = referenceTrack.values.slice( startIndex, endIndex ); } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { // Reference frame is after the last keyframe, so just use the last keyframe const startIndex = lastIndex * referenceValueSize + referenceOffset; const endIndex = startIndex + referenceValueSize - referenceOffset; referenceValue = referenceTrack.values.slice( startIndex, endIndex ); } else { // Interpolate to the reference value const interpolant = referenceTrack.createInterpolant(); const startIndex = referenceOffset; const endIndex = referenceValueSize - referenceOffset; interpolant.evaluate( referenceTime ); referenceValue = interpolant.resultBuffer.slice( startIndex, endIndex ); } // Conjugate the quaternion if ( referenceTrackType === 'quaternion' ) { const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); referenceQuat.toArray( referenceValue ); } // Subtract the reference value from all of the track values const numTimes = targetTrack.times.length; for ( let j = 0; j < numTimes; ++ j ) { const valueStart = j * targetValueSize + targetOffset; if ( referenceTrackType === 'quaternion' ) { // Multiply the conjugate for quaternion track types Quaternion.multiplyQuaternionsFlat( targetTrack.values, valueStart, referenceValue, 0, targetTrack.values, valueStart ); } else { const valueEnd = targetValueSize - targetOffset * 2; // Subtract each value for all other numeric track types for ( let k = 0; k < valueEnd; ++ k ) { targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; } } } } targetClip.blendMode = AdditiveAnimationBlendMode; return targetClip; } const AnimationUtils = { convertArray: convertArray, isTypedArray: isTypedArray, getKeyframeOrder: getKeyframeOrder, sortedArray: sortedArray, flattenJSON: flattenJSON, subclip: subclip, makeClipAdditive: makeClipAdditive }; /** * Abstract base class of interpolants over parametric samples. * * The parameter domain is one dimensional, typically the time or a path * along a curve defined by the data. * * The sample values can have any dimensionality and derived classes may * apply special interpretations to the data. * * This class provides the interval seek in a Template Method, deferring * the actual interpolation to derived classes. * * Time complexity is O(1) for linear access crossing at most two points * and O(log N) for random access, where N is the number of positions. * * References: * * http://www.oodesign.com/template-method-pattern.html * */ class Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { this.parameterPositions = parameterPositions; this._cachedIndex = 0; this.resultBuffer = resultBuffer !== undefined ? resultBuffer : new sampleValues.constructor( sampleSize ); this.sampleValues = sampleValues; this.valueSize = sampleSize; this.settings = null; this.DefaultSettings_ = {}; } evaluate( t ) { const pp = this.parameterPositions; let i1 = this._cachedIndex, t1 = pp[ i1 ], t0 = pp[ i1 - 1 ]; validate_interval: { seek: { let right; linear_scan: { //- See http://jsperf.com/comparison-to-undefined/3 //- slower code: //- //- if ( t >= t1 || t1 === undefined ) { forward_scan: if ( ! ( t < t1 ) ) { for ( let giveUpAt = i1 + 2; ; ) { if ( t1 === undefined ) { if ( t < t0 ) break forward_scan; // after end i1 = pp.length; this._cachedIndex = i1; return this.copySampleValue_( i1 - 1 ); } if ( i1 === giveUpAt ) break; // this loop t0 = t1; t1 = pp[ ++ i1 ]; if ( t < t1 ) { // we have arrived at the sought interval break seek; } } // prepare binary search on the right side of the index right = pp.length; break linear_scan; } //- slower code: //- if ( t < t0 || t0 === undefined ) { if ( ! ( t >= t0 ) ) { // looping? const t1global = pp[ 1 ]; if ( t < t1global ) { i1 = 2; // + 1, using the scan for the details t0 = t1global; } // linear reverse scan for ( let giveUpAt = i1 - 2; ; ) { if ( t0 === undefined ) { // before start this._cachedIndex = 0; return this.copySampleValue_( 0 ); } if ( i1 === giveUpAt ) break; // this loop t1 = t0; t0 = pp[ -- i1 - 1 ]; if ( t >= t0 ) { // we have arrived at the sought interval break seek; } } // prepare binary search on the left side of the index right = i1; i1 = 0; break linear_scan; } // the interval is valid break validate_interval; } // linear scan // binary search while ( i1 < right ) { const mid = ( i1 + right ) >>> 1; if ( t < pp[ mid ] ) { right = mid; } else { i1 = mid + 1; } } t1 = pp[ i1 ]; t0 = pp[ i1 - 1 ]; // check boundary cases, again if ( t0 === undefined ) { this._cachedIndex = 0; return this.copySampleValue_( 0 ); } if ( t1 === undefined ) { i1 = pp.length; this._cachedIndex = i1; return this.copySampleValue_( i1 - 1 ); } } // seek this._cachedIndex = i1; this.intervalChanged_( i1, t0, t1 ); } // validate_interval return this.interpolate_( i1, t0, t, t1 ); } getSettings_() { return this.settings || this.DefaultSettings_; } copySampleValue_( index ) { // copies a sample value to the result buffer const result = this.resultBuffer, values = this.sampleValues, stride = this.valueSize, offset = index * stride; for ( let i = 0; i !== stride; ++ i ) { result[ i ] = values[ offset + i ]; } return result; } // Template methods for derived classes: interpolate_( /* i1, t0, t, t1 */ ) { throw new Error( 'call to abstract method' ); // implementations shall return this.resultBuffer } intervalChanged_( /* i1, t0, t1 */ ) { // empty } } /** * Fast and simple cubic spline interpolant. * * It was derived from a Hermitian construction setting the first derivative * at each sample position to the linear slope between neighboring positions * over their parameter interval. */ class CubicInterpolant extends Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); this._weightPrev = - 0; this._offsetPrev = - 0; this._weightNext = - 0; this._offsetNext = - 0; this.DefaultSettings_ = { endingStart: ZeroCurvatureEnding, endingEnd: ZeroCurvatureEnding }; } intervalChanged_( i1, t0, t1 ) { const pp = this.parameterPositions; let iPrev = i1 - 2, iNext = i1 + 1, tPrev = pp[ iPrev ], tNext = pp[ iNext ]; if ( tPrev === undefined ) { switch ( this.getSettings_().endingStart ) { case ZeroSlopeEnding: // f'(t0) = 0 iPrev = i1; tPrev = 2 * t0 - t1; break; case WrapAroundEnding: // use the other end of the curve iPrev = pp.length - 2; tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; break; default: // ZeroCurvatureEnding // f''(t0) = 0 a.k.a. Natural Spline iPrev = i1; tPrev = t1; } } if ( tNext === undefined ) { switch ( this.getSettings_().endingEnd ) { case ZeroSlopeEnding: // f'(tN) = 0 iNext = i1; tNext = 2 * t1 - t0; break; case WrapAroundEnding: // use the other end of the curve iNext = 1; tNext = t1 + pp[ 1 ] - pp[ 0 ]; break; default: // ZeroCurvatureEnding // f''(tN) = 0, a.k.a. Natural Spline iNext = i1 - 1; tNext = t0; } } const halfDt = ( t1 - t0 ) * 0.5, stride = this.valueSize; this._weightPrev = halfDt / ( t0 - tPrev ); this._weightNext = halfDt / ( tNext - t1 ); this._offsetPrev = iPrev * stride; this._offsetNext = iNext * stride; } interpolate_( i1, t0, t, t1 ) { const result = this.resultBuffer, values = this.sampleValues, stride = this.valueSize, o1 = i1 * stride, o0 = o1 - stride, oP = this._offsetPrev, oN = this._offsetNext, wP = this._weightPrev, wN = this._weightNext, p = ( t - t0 ) / ( t1 - t0 ), pp = p * p, ppp = pp * p; // evaluate polynomials const sP = - wP * ppp + 2 * wP * pp - wP * p; const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; const sN = wN * ppp - wN * pp; // combine data linearly for ( let i = 0; i !== stride; ++ i ) { result[ i ] = sP * values[ oP + i ] + s0 * values[ o0 + i ] + s1 * values[ o1 + i ] + sN * values[ oN + i ]; } return result; } } class LinearInterpolant extends Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); } interpolate_( i1, t0, t, t1 ) { const result = this.resultBuffer, values = this.sampleValues, stride = this.valueSize, offset1 = i1 * stride, offset0 = offset1 - stride, weight1 = ( t - t0 ) / ( t1 - t0 ), weight0 = 1 - weight1; for ( let i = 0; i !== stride; ++ i ) { result[ i ] = values[ offset0 + i ] * weight0 + values[ offset1 + i ] * weight1; } return result; } } /** * * Interpolant that evaluates to the sample value at the position preceding * the parameter. */ class DiscreteInterpolant extends Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); } interpolate_( i1 /*, t0, t, t1 */ ) { return this.copySampleValue_( i1 - 1 ); } } class KeyframeTrack { constructor( name, times, values, interpolation ) { if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); this.name = name; this.times = convertArray( times, this.TimeBufferType ); this.values = convertArray( values, this.ValueBufferType ); this.setInterpolation( interpolation || this.DefaultInterpolation ); } // Serialization (in static context, because of constructor invocation // and automatic invocation of .toJSON): static toJSON( track ) { const trackType = track.constructor; let json; // derived classes can define a static toJSON method if ( trackType.toJSON !== this.toJSON ) { json = trackType.toJSON( track ); } else { // by default, we assume the data can be serialized as-is json = { 'name': track.name, 'times': convertArray( track.times, Array ), 'values': convertArray( track.values, Array ) }; const interpolation = track.getInterpolation(); if ( interpolation !== track.DefaultInterpolation ) { json.interpolation = interpolation; } } json.type = track.ValueTypeName; // mandatory return json; } InterpolantFactoryMethodDiscrete( result ) { return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); } InterpolantFactoryMethodLinear( result ) { return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); } InterpolantFactoryMethodSmooth( result ) { return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); } setInterpolation( interpolation ) { let factoryMethod; switch ( interpolation ) { case InterpolateDiscrete: factoryMethod = this.InterpolantFactoryMethodDiscrete; break; case InterpolateLinear: factoryMethod = this.InterpolantFactoryMethodLinear; break; case InterpolateSmooth: factoryMethod = this.InterpolantFactoryMethodSmooth; break; } if ( factoryMethod === undefined ) { const message = 'unsupported interpolation for ' + this.ValueTypeName + ' keyframe track named ' + this.name; if ( this.createInterpolant === undefined ) { // fall back to default, unless the default itself is messed up if ( interpolation !== this.DefaultInterpolation ) { this.setInterpolation( this.DefaultInterpolation ); } else { throw new Error( message ); // fatal, in this case } } console.warn( 'THREE.KeyframeTrack:', message ); return this; } this.createInterpolant = factoryMethod; return this; } getInterpolation() { switch ( this.createInterpolant ) { case this.InterpolantFactoryMethodDiscrete: return InterpolateDiscrete; case this.InterpolantFactoryMethodLinear: return InterpolateLinear; case this.InterpolantFactoryMethodSmooth: return InterpolateSmooth; } } getValueSize() { return this.values.length / this.times.length; } // move all keyframes either forwards or backwards in time shift( timeOffset ) { if ( timeOffset !== 0.0 ) { const times = this.times; for ( let i = 0, n = times.length; i !== n; ++ i ) { times[ i ] += timeOffset; } } return this; } // scale all keyframe times by a factor (useful for frame <-> seconds conversions) scale( timeScale ) { if ( timeScale !== 1.0 ) { const times = this.times; for ( let i = 0, n = times.length; i !== n; ++ i ) { times[ i ] *= timeScale; } } return this; } // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values trim( startTime, endTime ) { const times = this.times, nKeys = times.length; let from = 0, to = nKeys - 1; while ( from !== nKeys && times[ from ] < startTime ) { ++ from; } while ( to !== - 1 && times[ to ] > endTime ) { -- to; } ++ to; // inclusive -> exclusive bound if ( from !== 0 || to !== nKeys ) { // empty tracks are forbidden, so keep at least one keyframe if ( from >= to ) { to = Math.max( to, 1 ); from = to - 1; } const stride = this.getValueSize(); this.times = times.slice( from, to ); this.values = this.values.slice( from * stride, to * stride ); } return this; } // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable validate() { let valid = true; const valueSize = this.getValueSize(); if ( valueSize - Math.floor( valueSize ) !== 0 ) { console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); valid = false; } const times = this.times, values = this.values, nKeys = times.length; if ( nKeys === 0 ) { console.error( 'THREE.KeyframeTrack: Track is empty.', this ); valid = false; } let prevTime = null; for ( let i = 0; i !== nKeys; i ++ ) { const currTime = times[ i ]; if ( typeof currTime === 'number' && isNaN( currTime ) ) { console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); valid = false; break; } if ( prevTime !== null && prevTime > currTime ) { console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); valid = false; break; } prevTime = currTime; } if ( values !== undefined ) { if ( isTypedArray( values ) ) { for ( let i = 0, n = values.length; i !== n; ++ i ) { const value = values[ i ]; if ( isNaN( value ) ) { console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); valid = false; break; } } } } return valid; } // removes equivalent sequential keys as common in morph target sequences // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) optimize() { // times or values may be shared with other tracks, so overwriting is unsafe const times = this.times.slice(), values = this.values.slice(), stride = this.getValueSize(), smoothInterpolation = this.getInterpolation() === InterpolateSmooth, lastIndex = times.length - 1; let writeIndex = 1; for ( let i = 1; i < lastIndex; ++ i ) { let keep = false; const time = times[ i ]; const timeNext = times[ i + 1 ]; // remove adjacent keyframes scheduled at the same time if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { if ( ! smoothInterpolation ) { // remove unnecessary keyframes same as their neighbors const offset = i * stride, offsetP = offset - stride, offsetN = offset + stride; for ( let j = 0; j !== stride; ++ j ) { const value = values[ offset + j ]; if ( value !== values[ offsetP + j ] || value !== values[ offsetN + j ] ) { keep = true; break; } } } else { keep = true; } } // in-place compaction if ( keep ) { if ( i !== writeIndex ) { times[ writeIndex ] = times[ i ]; const readOffset = i * stride, writeOffset = writeIndex * stride; for ( let j = 0; j !== stride; ++ j ) { values[ writeOffset + j ] = values[ readOffset + j ]; } } ++ writeIndex; } } // flush last keyframe (compaction looks ahead) if ( lastIndex > 0 ) { times[ writeIndex ] = times[ lastIndex ]; for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { values[ writeOffset + j ] = values[ readOffset + j ]; } ++ writeIndex; } if ( writeIndex !== times.length ) { this.times = times.slice( 0, writeIndex ); this.values = values.slice( 0, writeIndex * stride ); } else { this.times = times; this.values = values; } return this; } clone() { const times = this.times.slice(); const values = this.values.slice(); const TypedKeyframeTrack = this.constructor; const track = new TypedKeyframeTrack( this.name, times, values ); // Interpolant argument to constructor is not saved, so copy the factory method directly. track.createInterpolant = this.createInterpolant; return track; } } KeyframeTrack.prototype.TimeBufferType = Float32Array; KeyframeTrack.prototype.ValueBufferType = Float32Array; KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; /** * A Track of Boolean keyframe values. */ class BooleanKeyframeTrack extends KeyframeTrack {} BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; BooleanKeyframeTrack.prototype.ValueBufferType = Array; BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** * A Track of keyframe values that represent color. */ class ColorKeyframeTrack extends KeyframeTrack {} ColorKeyframeTrack.prototype.ValueTypeName = 'color'; /** * A Track of numeric keyframe values. */ class NumberKeyframeTrack extends KeyframeTrack {} NumberKeyframeTrack.prototype.ValueTypeName = 'number'; /** * Spherical linear unit quaternion interpolant. */ class QuaternionLinearInterpolant extends Interpolant { constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { super( parameterPositions, sampleValues, sampleSize, resultBuffer ); } interpolate_( i1, t0, t, t1 ) { const result = this.resultBuffer, values = this.sampleValues, stride = this.valueSize, alpha = ( t - t0 ) / ( t1 - t0 ); let offset = i1 * stride; for ( let end = offset + stride; offset !== end; offset += 4 ) { Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); } return result; } } /** * A Track of quaternion keyframe values. */ class QuaternionKeyframeTrack extends KeyframeTrack { InterpolantFactoryMethodLinear( result ) { return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); } } QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; // ValueBufferType is inherited QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** * A Track that interpolates Strings */ class StringKeyframeTrack extends KeyframeTrack {} StringKeyframeTrack.prototype.ValueTypeName = 'string'; StringKeyframeTrack.prototype.ValueBufferType = Array; StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; /** * A Track of vectored keyframe values. */ class VectorKeyframeTrack extends KeyframeTrack {} VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; class AnimationClip { constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) { this.name = name; this.tracks = tracks; this.duration = duration; this.blendMode = blendMode; this.uuid = generateUUID(); // this means it should figure out its duration by scanning the tracks if ( this.duration < 0 ) { this.resetDuration(); } } static parse( json ) { const tracks = [], jsonTracks = json.tracks, frameTime = 1.0 / ( json.fps || 1.0 ); for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); } const clip = new this( json.name, json.duration, tracks, json.blendMode ); clip.uuid = json.uuid; return clip; } static toJSON( clip ) { const tracks = [], clipTracks = clip.tracks; const json = { 'name': clip.name, 'duration': clip.duration, 'tracks': tracks, 'uuid': clip.uuid, 'blendMode': clip.blendMode }; for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); } return json; } static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { const numMorphTargets = morphTargetSequence.length; const tracks = []; for ( let i = 0; i < numMorphTargets; i ++ ) { let times = []; let values = []; times.push( ( i + numMorphTargets - 1 ) % numMorphTargets, i, ( i + 1 ) % numMorphTargets ); values.push( 0, 1, 0 ); const order = getKeyframeOrder( times ); times = sortedArray( times, 1, order ); values = sortedArray( values, 1, order ); // if there is a key at the first frame, duplicate it as the // last frame as well for perfect loop. if ( ! noLoop && times[ 0 ] === 0 ) { times.push( numMorphTargets ); values.push( values[ 0 ] ); } tracks.push( new NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', times, values ).scale( 1.0 / fps ) ); } return new this( name, - 1, tracks ); } static findByName( objectOrClipArray, name ) { let clipArray = objectOrClipArray; if ( ! Array.isArray( objectOrClipArray ) ) { const o = objectOrClipArray; clipArray = o.geometry && o.geometry.animations || o.animations; } for ( let i = 0; i < clipArray.length; i ++ ) { if ( clipArray[ i ].name === name ) { return clipArray[ i ]; } } return null; } static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { const animationToMorphTargets = {}; // tested with https://regex101.com/ on trick sequences // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 const pattern = /^([\w-]*?)([\d]+)$/; // sort morph target names into animation groups based // patterns like Walk_001, Walk_002, Run_001, Run_002 for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { const morphTarget = morphTargets[ i ]; const parts = morphTarget.name.match( pattern ); if ( parts && parts.length > 1 ) { const name = parts[ 1 ]; let animationMorphTargets = animationToMorphTargets[ name ]; if ( ! animationMorphTargets ) { animationToMorphTargets[ name ] = animationMorphTargets = []; } animationMorphTargets.push( morphTarget ); } } const clips = []; for ( const name in animationToMorphTargets ) { clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); } return clips; } // parse the animation.hierarchy format static parseAnimation( animation, bones ) { if ( ! animation ) { console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); return null; } const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { // only return track if there are actually keys. if ( animationKeys.length !== 0 ) { const times = []; const values = []; flattenJSON( animationKeys, times, values, propertyName ); // empty keys are filtered out, so check again if ( times.length !== 0 ) { destTracks.push( new trackType( trackName, times, values ) ); } } }; const tracks = []; const clipName = animation.name || 'default'; const fps = animation.fps || 30; const blendMode = animation.blendMode; // automatic length determination in AnimationClip. let duration = animation.length || - 1; const hierarchyTracks = animation.hierarchy || []; for ( let h = 0; h < hierarchyTracks.length; h ++ ) { const animationKeys = hierarchyTracks[ h ].keys; // skip empty tracks if ( ! animationKeys || animationKeys.length === 0 ) continue; // process morph targets if ( animationKeys[ 0 ].morphTargets ) { // figure out all morph targets used in this track const morphTargetNames = {}; let k; for ( k = 0; k < animationKeys.length; k ++ ) { if ( animationKeys[ k ].morphTargets ) { for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; } } } // create a track for each morph target with all zero // morphTargetInfluences except for the keys in which // the morphTarget is named. for ( const morphTargetName in morphTargetNames ) { const times = []; const values = []; for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { const animationKey = animationKeys[ k ]; times.push( animationKey.time ); values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); } tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); } duration = morphTargetNames.length * fps; } else { // ...assume skeletal animation const boneName = '.bones[' + bones[ h ].name + ']'; addNonemptyTrack( VectorKeyframeTrack, boneName + '.position', animationKeys, 'pos', tracks ); addNonemptyTrack( QuaternionKeyframeTrack, boneName + '.quaternion', animationKeys, 'rot', tracks ); addNonemptyTrack( VectorKeyframeTrack, boneName + '.scale', animationKeys, 'scl', tracks ); } } if ( tracks.length === 0 ) { return null; } const clip = new this( clipName, duration, tracks, blendMode ); return clip; } resetDuration() { const tracks = this.tracks; let duration = 0; for ( let i = 0, n = tracks.length; i !== n; ++ i ) { const track = this.tracks[ i ]; duration = Math.max( duration, track.times[ track.times.length - 1 ] ); } this.duration = duration; return this; } trim() { for ( let i = 0; i < this.tracks.length; i ++ ) { this.tracks[ i ].trim( 0, this.duration ); } return this; } validate() { let valid = true; for ( let i = 0; i < this.tracks.length; i ++ ) { valid = valid && this.tracks[ i ].validate(); } return valid; } optimize() { for ( let i = 0; i < this.tracks.length; i ++ ) { this.tracks[ i ].optimize(); } return this; } clone() { const tracks = []; for ( let i = 0; i < this.tracks.length; i ++ ) { tracks.push( this.tracks[ i ].clone() ); } return new this.constructor( this.name, this.duration, tracks, this.blendMode ); } toJSON() { return this.constructor.toJSON( this ); } } function getTrackTypeForValueTypeName( typeName ) { switch ( typeName.toLowerCase() ) { case 'scalar': case 'double': case 'float': case 'number': case 'integer': return NumberKeyframeTrack; case 'vector': case 'vector2': case 'vector3': case 'vector4': return VectorKeyframeTrack; case 'color': return ColorKeyframeTrack; case 'quaternion': return QuaternionKeyframeTrack; case 'bool': case 'boolean': return BooleanKeyframeTrack; case 'string': return StringKeyframeTrack; } throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); } function parseKeyframeTrack( json ) { if ( json.type === undefined ) { throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); } const trackType = getTrackTypeForValueTypeName( json.type ); if ( json.times === undefined ) { const times = [], values = []; flattenJSON( json.keys, times, values, 'value' ); json.times = times; json.values = values; } // derived classes can define a static parse method if ( trackType.parse !== undefined ) { return trackType.parse( json ); } else { // by default, we assume a constructor compatible with the base return new trackType( json.name, json.times, json.values, json.interpolation ); } } const Cache = { enabled: false, files: {}, add: function ( key, file ) { if ( this.enabled === false ) return; // console.log( 'THREE.Cache', 'Adding key:', key ); this.files[ key ] = file; }, get: function ( key ) { if ( this.enabled === false ) return; // console.log( 'THREE.Cache', 'Checking key:', key ); return this.files[ key ]; }, remove: function ( key ) { delete this.files[ key ]; }, clear: function () { this.files = {}; } }; class LoadingManager { constructor( onLoad, onProgress, onError ) { const scope = this; let isLoading = false; let itemsLoaded = 0; let itemsTotal = 0; let urlModifier = undefined; const handlers = []; // Refer to #5689 for the reason why we don't set .onStart // in the constructor this.onStart = undefined; this.onLoad = onLoad; this.onProgress = onProgress; this.onError = onError; this.itemStart = function ( url ) { itemsTotal ++; if ( isLoading === false ) { if ( scope.onStart !== undefined ) { scope.onStart( url, itemsLoaded, itemsTotal ); } } isLoading = true; }; this.itemEnd = function ( url ) { itemsLoaded ++; if ( scope.onProgress !== undefined ) { scope.onProgress( url, itemsLoaded, itemsTotal ); } if ( itemsLoaded === itemsTotal ) { isLoading = false; if ( scope.onLoad !== undefined ) { scope.onLoad(); } } }; this.itemError = function ( url ) { if ( scope.onError !== undefined ) { scope.onError( url ); } }; this.resolveURL = function ( url ) { if ( urlModifier ) { return urlModifier( url ); } return url; }; this.setURLModifier = function ( transform ) { urlModifier = transform; return this; }; this.addHandler = function ( regex, loader ) { handlers.push( regex, loader ); return this; }; this.removeHandler = function ( regex ) { const index = handlers.indexOf( regex ); if ( index !== - 1 ) { handlers.splice( index, 2 ); } return this; }; this.getHandler = function ( file ) { for ( let i = 0, l = handlers.length; i < l; i += 2 ) { const regex = handlers[ i ]; const loader = handlers[ i + 1 ]; if ( regex.global ) regex.lastIndex = 0; // see #17920 if ( regex.test( file ) ) { return loader; } } return null; }; } } const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); class Loader { constructor( manager ) { this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; this.crossOrigin = 'anonymous'; this.withCredentials = false; this.path = ''; this.resourcePath = ''; this.requestHeader = {}; } load( /* url, onLoad, onProgress, onError */ ) {} loadAsync( url, onProgress ) { const scope = this; return new Promise( function ( resolve, reject ) { scope.load( url, resolve, onProgress, reject ); } ); } parse( /* data */ ) {} setCrossOrigin( crossOrigin ) { this.crossOrigin = crossOrigin; return this; } setWithCredentials( value ) { this.withCredentials = value; return this; } setPath( path ) { this.path = path; return this; } setResourcePath( resourcePath ) { this.resourcePath = resourcePath; return this; } setRequestHeader( requestHeader ) { this.requestHeader = requestHeader; return this; } } Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; const loading = {}; class HttpError extends Error { constructor( message, response ) { super( message ); this.response = response; } } class FileLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { if ( url === undefined ) url = ''; if ( this.path !== undefined ) url = this.path + url; url = this.manager.resolveURL( url ); const cached = Cache.get( url ); if ( cached !== undefined ) { this.manager.itemStart( url ); setTimeout( () => { if ( onLoad ) onLoad( cached ); this.manager.itemEnd( url ); }, 0 ); return cached; } // Check if request is duplicate if ( loading[ url ] !== undefined ) { loading[ url ].push( { onLoad: onLoad, onProgress: onProgress, onError: onError } ); return; } // Initialise array for duplicate requests loading[ url ] = []; loading[ url ].push( { onLoad: onLoad, onProgress: onProgress, onError: onError, } ); // create request const req = new Request( url, { headers: new Headers( this.requestHeader ), credentials: this.withCredentials ? 'include' : 'same-origin', // An abort controller could be added within a future PR } ); // record states ( avoid data race ) const mimeType = this.mimeType; const responseType = this.responseType; // start the fetch fetch( req ) .then( response => { if ( response.status === 200 || response.status === 0 ) { // Some browsers return HTTP Status 0 when using non-http protocol // e.g. 'file://' or 'data://'. Handle as success. if ( response.status === 0 ) { console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); } // Workaround: Checking if response.body === undefined for Alipay browser #23548 if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { return response; } const callbacks = loading[ url ]; const reader = response.body.getReader(); // Nginx needs X-File-Size check // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' ); const total = contentLength ? parseInt( contentLength ) : 0; const lengthComputable = total !== 0; let loaded = 0; // periodically read data into the new stream tracking while download progress const stream = new ReadableStream( { start( controller ) { readData(); function readData() { reader.read().then( ( { done, value } ) => { if ( done ) { controller.close(); } else { loaded += value.byteLength; const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); for ( let i = 0, il = callbacks.length; i < il; i ++ ) { const callback = callbacks[ i ]; if ( callback.onProgress ) callback.onProgress( event ); } controller.enqueue( value ); readData(); } } ); } } } ); return new Response( stream ); } else { throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); } } ) .then( response => { switch ( responseType ) { case 'arraybuffer': return response.arrayBuffer(); case 'blob': return response.blob(); case 'document': return response.text() .then( text => { const parser = new DOMParser(); return parser.parseFromString( text, mimeType ); } ); case 'json': return response.json(); default: if ( mimeType === undefined ) { return response.text(); } else { // sniff encoding const re = /charset="?([^;"\s]*)"?/i; const exec = re.exec( mimeType ); const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; const decoder = new TextDecoder( label ); return response.arrayBuffer().then( ab => decoder.decode( ab ) ); } } } ) .then( data => { // Add to cache only on HTTP success, so that we do not cache // error response bodies as proper responses to requests. Cache.add( url, data ); const callbacks = loading[ url ]; delete loading[ url ]; for ( let i = 0, il = callbacks.length; i < il; i ++ ) { const callback = callbacks[ i ]; if ( callback.onLoad ) callback.onLoad( data ); } } ) .catch( err => { // Abort errors and other errors are handled the same const callbacks = loading[ url ]; if ( callbacks === undefined ) { // When onLoad was called and url was deleted in `loading` this.manager.itemError( url ); throw err; } delete loading[ url ]; for ( let i = 0, il = callbacks.length; i < il; i ++ ) { const callback = callbacks[ i ]; if ( callback.onError ) callback.onError( err ); } this.manager.itemError( url ); } ) .finally( () => { this.manager.itemEnd( url ); } ); this.manager.itemStart( url ); } setResponseType( value ) { this.responseType = value; return this; } setMimeType( value ) { this.mimeType = value; return this; } } class AnimationLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); loader.load( url, function ( text ) { try { onLoad( scope.parse( JSON.parse( text ) ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); } parse( json ) { const animations = []; for ( let i = 0; i < json.length; i ++ ) { const clip = AnimationClip.parse( json[ i ] ); animations.push( clip ); } return animations; } } /** * Abstract Base class to block based textures loader (dds, pvr, ...) * * Sub classes have to implement the parse() method which will be used in load(). */ class CompressedTextureLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const images = []; const texture = new CompressedTexture(); const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setResponseType( 'arraybuffer' ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( scope.withCredentials ); let loaded = 0; function loadTexture( i ) { loader.load( url[ i ], function ( buffer ) { const texDatas = scope.parse( buffer, true ); images[ i ] = { width: texDatas.width, height: texDatas.height, format: texDatas.format, mipmaps: texDatas.mipmaps }; loaded += 1; if ( loaded === 6 ) { if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; texture.image = images; texture.format = texDatas.format; texture.needsUpdate = true; if ( onLoad ) onLoad( texture ); } }, onProgress, onError ); } if ( Array.isArray( url ) ) { for ( let i = 0, il = url.length; i < il; ++ i ) { loadTexture( i ); } } else { // compressed cubemap texture stored in a single DDS file loader.load( url, function ( buffer ) { const texDatas = scope.parse( buffer, true ); if ( texDatas.isCubemap ) { const faces = texDatas.mipmaps.length / texDatas.mipmapCount; for ( let f = 0; f < faces; f ++ ) { images[ f ] = { mipmaps: [] }; for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); images[ f ].format = texDatas.format; images[ f ].width = texDatas.width; images[ f ].height = texDatas.height; } } texture.image = images; } else { texture.image.width = texDatas.width; texture.image.height = texDatas.height; texture.mipmaps = texDatas.mipmaps; } if ( texDatas.mipmapCount === 1 ) { texture.minFilter = LinearFilter; } texture.format = texDatas.format; texture.needsUpdate = true; if ( onLoad ) onLoad( texture ); }, onProgress, onError ); } return texture; } } class ImageLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { if ( this.path !== undefined ) url = this.path + url; url = this.manager.resolveURL( url ); const scope = this; const cached = Cache.get( url ); if ( cached !== undefined ) { scope.manager.itemStart( url ); setTimeout( function () { if ( onLoad ) onLoad( cached ); scope.manager.itemEnd( url ); }, 0 ); return cached; } const image = createElementNS( 'img' ); function onImageLoad() { removeEventListeners(); Cache.add( url, this ); if ( onLoad ) onLoad( this ); scope.manager.itemEnd( url ); } function onImageError( event ) { removeEventListeners(); if ( onError ) onError( event ); scope.manager.itemError( url ); scope.manager.itemEnd( url ); } function removeEventListeners() { image.removeEventListener( 'load', onImageLoad, false ); image.removeEventListener( 'error', onImageError, false ); } image.addEventListener( 'load', onImageLoad, false ); image.addEventListener( 'error', onImageError, false ); if ( url.slice( 0, 5 ) !== 'data:' ) { if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; } scope.manager.itemStart( url ); image.src = url; return image; } } class CubeTextureLoader extends Loader { constructor( manager ) { super( manager ); } load( urls, onLoad, onProgress, onError ) { const texture = new CubeTexture(); texture.colorSpace = SRGBColorSpace; const loader = new ImageLoader( this.manager ); loader.setCrossOrigin( this.crossOrigin ); loader.setPath( this.path ); let loaded = 0; function loadTexture( i ) { loader.load( urls[ i ], function ( image ) { texture.images[ i ] = image; loaded ++; if ( loaded === 6 ) { texture.needsUpdate = true; if ( onLoad ) onLoad( texture ); } }, undefined, onError ); } for ( let i = 0; i < urls.length; ++ i ) { loadTexture( i ); } return texture; } } /** * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) * * Sub classes have to implement the parse() method which will be used in load(). */ class DataTextureLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const texture = new DataTexture(); const loader = new FileLoader( this.manager ); loader.setResponseType( 'arraybuffer' ); loader.setRequestHeader( this.requestHeader ); loader.setPath( this.path ); loader.setWithCredentials( scope.withCredentials ); loader.load( url, function ( buffer ) { let texData; try { texData = scope.parse( buffer ); } catch ( error ) { if ( onError !== undefined ) { onError( error ); } else { console.error( error ); return; } } if ( texData.image !== undefined ) { texture.image = texData.image; } else if ( texData.data !== undefined ) { texture.image.width = texData.width; texture.image.height = texData.height; texture.image.data = texData.data; } texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; if ( texData.colorSpace !== undefined ) { texture.colorSpace = texData.colorSpace; } else if ( texData.encoding !== undefined ) { // @deprecated, r152 texture.encoding = texData.encoding; } if ( texData.flipY !== undefined ) { texture.flipY = texData.flipY; } if ( texData.format !== undefined ) { texture.format = texData.format; } if ( texData.type !== undefined ) { texture.type = texData.type; } if ( texData.mipmaps !== undefined ) { texture.mipmaps = texData.mipmaps; texture.minFilter = LinearMipmapLinearFilter; // presumably... } if ( texData.mipmapCount === 1 ) { texture.minFilter = LinearFilter; } if ( texData.generateMipmaps !== undefined ) { texture.generateMipmaps = texData.generateMipmaps; } texture.needsUpdate = true; if ( onLoad ) onLoad( texture, texData ); }, onProgress, onError ); return texture; } } class TextureLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const texture = new Texture(); const loader = new ImageLoader( this.manager ); loader.setCrossOrigin( this.crossOrigin ); loader.setPath( this.path ); loader.load( url, function ( image ) { texture.image = image; texture.needsUpdate = true; if ( onLoad !== undefined ) { onLoad( texture ); } }, onProgress, onError ); return texture; } } class Light extends Object3D { constructor( color, intensity = 1 ) { super(); this.isLight = true; this.type = 'Light'; this.color = new Color( color ); this.intensity = intensity; } dispose() { // Empty here in base class; some subclasses override. } copy( source, recursive ) { super.copy( source, recursive ); this.color.copy( source.color ); this.intensity = source.intensity; return this; } toJSON( meta ) { const data = super.toJSON( meta ); data.object.color = this.color.getHex(); data.object.intensity = this.intensity; if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); if ( this.distance !== undefined ) data.object.distance = this.distance; if ( this.angle !== undefined ) data.object.angle = this.angle; if ( this.decay !== undefined ) data.object.decay = this.decay; if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); return data; } } class HemisphereLight extends Light { constructor( skyColor, groundColor, intensity ) { super( skyColor, intensity ); this.isHemisphereLight = true; this.type = 'HemisphereLight'; this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); this.groundColor = new Color( groundColor ); } copy( source, recursive ) { super.copy( source, recursive ); this.groundColor.copy( source.groundColor ); return this; } } const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); const _lookTarget$1 = /*@__PURE__*/ new Vector3(); class LightShadow { constructor( camera ) { this.camera = camera; this.bias = 0; this.normalBias = 0; this.radius = 1; this.blurSamples = 8; this.mapSize = new Vector2( 512, 512 ); this.map = null; this.mapPass = null; this.matrix = new Matrix4(); this.autoUpdate = true; this.needsUpdate = false; this._frustum = new Frustum(); this._frameExtents = new Vector2( 1, 1 ); this._viewportCount = 1; this._viewports = [ new Vector4( 0, 0, 1, 1 ) ]; } getViewportCount() { return this._viewportCount; } getFrustum() { return this._frustum; } updateMatrices( light ) { const shadowCamera = this.camera; const shadowMatrix = this.matrix; _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); shadowCamera.position.copy( _lightPositionWorld$1 ); _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); shadowCamera.lookAt( _lookTarget$1 ); shadowCamera.updateMatrixWorld(); _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); shadowMatrix.set( 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0 ); shadowMatrix.multiply( _projScreenMatrix$1 ); } getViewport( viewportIndex ) { return this._viewports[ viewportIndex ]; } getFrameExtents() { return this._frameExtents; } dispose() { if ( this.map ) { this.map.dispose(); } if ( this.mapPass ) { this.mapPass.dispose(); } } copy( source ) { this.camera = source.camera.clone(); this.bias = source.bias; this.radius = source.radius; this.mapSize.copy( source.mapSize ); return this; } clone() { return new this.constructor().copy( this ); } toJSON() { const object = {}; if ( this.bias !== 0 ) object.bias = this.bias; if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; if ( this.radius !== 1 ) object.radius = this.radius; if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); object.camera = this.camera.toJSON( false ).object; delete object.camera.matrix; return object; } } class SpotLightShadow extends LightShadow { constructor() { super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); this.isSpotLightShadow = true; this.focus = 1; } updateMatrices( light ) { const camera = this.camera; const fov = RAD2DEG * 2 * light.angle * this.focus; const aspect = this.mapSize.width / this.mapSize.height; const far = light.distance || camera.far; if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { camera.fov = fov; camera.aspect = aspect; camera.far = far; camera.updateProjectionMatrix(); } super.updateMatrices( light ); } copy( source ) { super.copy( source ); this.focus = source.focus; return this; } } class SpotLight extends Light { constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { super( color, intensity ); this.isSpotLight = true; this.type = 'SpotLight'; this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); this.target = new Object3D(); this.distance = distance; this.angle = angle; this.penumbra = penumbra; this.decay = decay; this.map = null; this.shadow = new SpotLightShadow(); } get power() { // compute the light's luminous power (in lumens) from its intensity (in candela) // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) return this.intensity * Math.PI; } set power( power ) { // set the light's intensity (in candela) from the desired luminous power (in lumens) this.intensity = power / Math.PI; } dispose() { this.shadow.dispose(); } copy( source, recursive ) { super.copy( source, recursive ); this.distance = source.distance; this.angle = source.angle; this.penumbra = source.penumbra; this.decay = source.decay; this.target = source.target.clone(); this.shadow = source.shadow.clone(); return this; } } const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); const _lightPositionWorld = /*@__PURE__*/ new Vector3(); const _lookTarget = /*@__PURE__*/ new Vector3(); class PointLightShadow extends LightShadow { constructor() { super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); this.isPointLightShadow = true; this._frameExtents = new Vector2( 4, 2 ); this._viewportCount = 6; this._viewports = [ // These viewports map a cube-map onto a 2D texture with the // following orientation: // // xzXZ // y Y // // X - Positive x direction // x - Negative x direction // Y - Positive y direction // y - Negative y direction // Z - Positive z direction // z - Negative z direction // positive X new Vector4( 2, 1, 1, 1 ), // negative X new Vector4( 0, 1, 1, 1 ), // positive Z new Vector4( 3, 1, 1, 1 ), // negative Z new Vector4( 1, 1, 1, 1 ), // positive Y new Vector4( 3, 0, 1, 1 ), // negative Y new Vector4( 1, 0, 1, 1 ) ]; this._cubeDirections = [ new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) ]; this._cubeUps = [ new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) ]; } updateMatrices( light, viewportIndex = 0 ) { const camera = this.camera; const shadowMatrix = this.matrix; const far = light.distance || camera.far; if ( far !== camera.far ) { camera.far = far; camera.updateProjectionMatrix(); } _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); camera.position.copy( _lightPositionWorld ); _lookTarget.copy( camera.position ); _lookTarget.add( this._cubeDirections[ viewportIndex ] ); camera.up.copy( this._cubeUps[ viewportIndex ] ); camera.lookAt( _lookTarget ); camera.updateMatrixWorld(); shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); this._frustum.setFromProjectionMatrix( _projScreenMatrix ); } } class PointLight extends Light { constructor( color, intensity, distance = 0, decay = 2 ) { super( color, intensity ); this.isPointLight = true; this.type = 'PointLight'; this.distance = distance; this.decay = decay; this.shadow = new PointLightShadow(); } get power() { // compute the light's luminous power (in lumens) from its intensity (in candela) // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) return this.intensity * 4 * Math.PI; } set power( power ) { // set the light's intensity (in candela) from the desired luminous power (in lumens) this.intensity = power / ( 4 * Math.PI ); } dispose() { this.shadow.dispose(); } copy( source, recursive ) { super.copy( source, recursive ); this.distance = source.distance; this.decay = source.decay; this.shadow = source.shadow.clone(); return this; } } class DirectionalLightShadow extends LightShadow { constructor() { super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); this.isDirectionalLightShadow = true; } } class DirectionalLight extends Light { constructor( color, intensity ) { super( color, intensity ); this.isDirectionalLight = true; this.type = 'DirectionalLight'; this.position.copy( Object3D.DEFAULT_UP ); this.updateMatrix(); this.target = new Object3D(); this.shadow = new DirectionalLightShadow(); } dispose() { this.shadow.dispose(); } copy( source ) { super.copy( source ); this.target = source.target.clone(); this.shadow = source.shadow.clone(); return this; } } class AmbientLight extends Light { constructor( color, intensity ) { super( color, intensity ); this.isAmbientLight = true; this.type = 'AmbientLight'; } } class RectAreaLight extends Light { constructor( color, intensity, width = 10, height = 10 ) { super( color, intensity ); this.isRectAreaLight = true; this.type = 'RectAreaLight'; this.width = width; this.height = height; } get power() { // compute the light's luminous power (in lumens) from its intensity (in nits) return this.intensity * this.width * this.height * Math.PI; } set power( power ) { // set the light's intensity (in nits) from the desired luminous power (in lumens) this.intensity = power / ( this.width * this.height * Math.PI ); } copy( source ) { super.copy( source ); this.width = source.width; this.height = source.height; return this; } toJSON( meta ) { const data = super.toJSON( meta ); data.object.width = this.width; data.object.height = this.height; return data; } } /** * Primary reference: * https://graphics.stanford.edu/papers/envmap/envmap.pdf * * Secondary reference: * https://www.ppsloan.org/publications/StupidSH36.pdf */ // 3-band SH defined by 9 coefficients class SphericalHarmonics3 { constructor() { this.isSphericalHarmonics3 = true; this.coefficients = []; for ( let i = 0; i < 9; i ++ ) { this.coefficients.push( new Vector3() ); } } set( coefficients ) { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].copy( coefficients[ i ] ); } return this; } zero() { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].set( 0, 0, 0 ); } return this; } // get the radiance in the direction of the normal // target is a Vector3 getAt( normal, target ) { // normal is assumed to be unit length const x = normal.x, y = normal.y, z = normal.z; const coeff = this.coefficients; // band 0 target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); // band 1 target.addScaledVector( coeff[ 1 ], 0.488603 * y ); target.addScaledVector( coeff[ 2 ], 0.488603 * z ); target.addScaledVector( coeff[ 3 ], 0.488603 * x ); // band 2 target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); return target; } // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal // target is a Vector3 // https://graphics.stanford.edu/papers/envmap/envmap.pdf getIrradianceAt( normal, target ) { // normal is assumed to be unit length const x = normal.x, y = normal.y, z = normal.z; const coeff = this.coefficients; // band 0 target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 // band 1 target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); // band 2 target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 return target; } add( sh ) { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].add( sh.coefficients[ i ] ); } return this; } addScaledSH( sh, s ) { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); } return this; } scale( s ) { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].multiplyScalar( s ); } return this; } lerp( sh, alpha ) { for ( let i = 0; i < 9; i ++ ) { this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); } return this; } equals( sh ) { for ( let i = 0; i < 9; i ++ ) { if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { return false; } } return true; } copy( sh ) { return this.set( sh.coefficients ); } clone() { return new this.constructor().copy( this ); } fromArray( array, offset = 0 ) { const coefficients = this.coefficients; for ( let i = 0; i < 9; i ++ ) { coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); } return this; } toArray( array = [], offset = 0 ) { const coefficients = this.coefficients; for ( let i = 0; i < 9; i ++ ) { coefficients[ i ].toArray( array, offset + ( i * 3 ) ); } return array; } // evaluate the basis functions // shBasis is an Array[ 9 ] static getBasisAt( normal, shBasis ) { // normal is assumed to be unit length const x = normal.x, y = normal.y, z = normal.z; // band 0 shBasis[ 0 ] = 0.282095; // band 1 shBasis[ 1 ] = 0.488603 * y; shBasis[ 2 ] = 0.488603 * z; shBasis[ 3 ] = 0.488603 * x; // band 2 shBasis[ 4 ] = 1.092548 * x * y; shBasis[ 5 ] = 1.092548 * y * z; shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); shBasis[ 7 ] = 1.092548 * x * z; shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); } } class LightProbe extends Light { constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { super( undefined, intensity ); this.isLightProbe = true; this.sh = sh; } copy( source ) { super.copy( source ); this.sh.copy( source.sh ); return this; } fromJSON( json ) { this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); this.sh.fromArray( json.sh ); return this; } toJSON( meta ) { const data = super.toJSON( meta ); data.object.sh = this.sh.toArray(); return data; } } class MaterialLoader extends Loader { constructor( manager ) { super( manager ); this.textures = {}; } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); loader.load( url, function ( text ) { try { onLoad( scope.parse( JSON.parse( text ) ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); } parse( json ) { const textures = this.textures; function getTexture( name ) { if ( textures[ name ] === undefined ) { console.warn( 'THREE.MaterialLoader: Undefined texture', name ); } return textures[ name ]; } const material = MaterialLoader.createMaterialFromType( json.type ); if ( json.uuid !== undefined ) material.uuid = json.uuid; if ( json.name !== undefined ) material.name = json.name; if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); if ( json.roughness !== undefined ) material.roughness = json.roughness; if ( json.metalness !== undefined ) material.metalness = json.metalness; if ( json.sheen !== undefined ) material.sheen = json.sheen; if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); if ( json.shininess !== undefined ) material.shininess = json.shininess; if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; if ( json.transmission !== undefined ) material.transmission = json.transmission; if ( json.thickness !== undefined ) material.thickness = json.thickness; if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; if ( json.fog !== undefined ) material.fog = json.fog; if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; if ( json.blending !== undefined ) material.blending = json.blending; if ( json.combine !== undefined ) material.combine = json.combine; if ( json.side !== undefined ) material.side = json.side; if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; if ( json.opacity !== undefined ) material.opacity = json.opacity; if ( json.transparent !== undefined ) material.transparent = json.transparent; if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; if ( json.alphaHash !== undefined ) material.alphaHash = json.alphaHash; if ( json.depthFunc !== undefined ) material.depthFunc = json.depthFunc; if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; if ( json.blendSrc !== undefined ) material.blendSrc = json.blendSrc; if ( json.blendDst !== undefined ) material.blendDst = json.blendDst; if ( json.blendEquation !== undefined ) material.blendEquation = json.blendEquation; if ( json.blendSrcAlpha !== undefined ) material.blendSrcAlpha = json.blendSrcAlpha; if ( json.blendDstAlpha !== undefined ) material.blendDstAlpha = json.blendDstAlpha; if ( json.blendEquationAlpha !== undefined ) material.blendEquationAlpha = json.blendEquationAlpha; if ( json.blendColor !== undefined && material.blendColor !== undefined ) material.blendColor.setHex( json.blendColor ); if ( json.blendAlpha !== undefined ) material.blendAlpha = json.blendAlpha; if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; if ( json.rotation !== undefined ) material.rotation = json.rotation; if ( json.linewidth !== undefined ) material.linewidth = json.linewidth; if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; if ( json.scale !== undefined ) material.scale = json.scale; if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; if ( json.dithering !== undefined ) material.dithering = json.dithering; if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; if ( json.visible !== undefined ) material.visible = json.visible; if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; if ( json.userData !== undefined ) material.userData = json.userData; if ( json.vertexColors !== undefined ) { if ( typeof json.vertexColors === 'number' ) { material.vertexColors = ( json.vertexColors > 0 ) ? true : false; } else { material.vertexColors = json.vertexColors; } } // Shader Material if ( json.uniforms !== undefined ) { for ( const name in json.uniforms ) { const uniform = json.uniforms[ name ]; material.uniforms[ name ] = {}; switch ( uniform.type ) { case 't': material.uniforms[ name ].value = getTexture( uniform.value ); break; case 'c': material.uniforms[ name ].value = new Color().setHex( uniform.value ); break; case 'v2': material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); break; case 'v3': material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); break; case 'v4': material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); break; case 'm3': material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); break; case 'm4': material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); break; default: material.uniforms[ name ].value = uniform.value; } } } if ( json.defines !== undefined ) material.defines = json.defines; if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; if ( json.extensions !== undefined ) { for ( const key in json.extensions ) { material.extensions[ key ] = json.extensions[ key ]; } } if ( json.lights !== undefined ) material.lights = json.lights; if ( json.clipping !== undefined ) material.clipping = json.clipping; // for PointsMaterial if ( json.size !== undefined ) material.size = json.size; if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; // maps if ( json.map !== undefined ) material.map = getTexture( json.map ); if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; if ( json.normalScale !== undefined ) { let normalScale = json.normalScale; if ( Array.isArray( normalScale ) === false ) { // Blender exporter used to export a scalar. See #7459 normalScale = [ normalScale, normalScale ]; } material.normalScale = new Vector2().fromArray( normalScale ); } if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); return material; } setTextures( value ) { this.textures = value; return this; } static createMaterialFromType( type ) { const materialLib = { ShadowMaterial, SpriteMaterial, RawShaderMaterial, ShaderMaterial, PointsMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshPhongMaterial, MeshToonMaterial, MeshNormalMaterial, MeshLambertMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshBasicMaterial, MeshMatcapMaterial, LineDashedMaterial, LineBasicMaterial, Material }; return new materialLib[ type ](); } } class LoaderUtils { static decodeText( array ) { if ( typeof TextDecoder !== 'undefined' ) { return new TextDecoder().decode( array ); } // Avoid the String.fromCharCode.apply(null, array) shortcut, which // throws a "maximum call stack size exceeded" error for large arrays. let s = ''; for ( let i = 0, il = array.length; i < il; i ++ ) { // Implicitly assumes little-endian. s += String.fromCharCode( array[ i ] ); } try { // merges multi-byte utf-8 characters. return decodeURIComponent( escape( s ) ); } catch ( e ) { // see #16358 return s; } } static extractUrlBase( url ) { const index = url.lastIndexOf( '/' ); if ( index === - 1 ) return './'; return url.slice( 0, index + 1 ); } static resolveURL( url, path ) { // Invalid URL if ( typeof url !== 'string' || url === '' ) return ''; // Host Relative URL if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); } // Absolute URL http://,https://,// if ( /^(https?:)?\/\//i.test( url ) ) return url; // Data URI if ( /^data:.*,.*$/i.test( url ) ) return url; // Blob URL if ( /^blob:.*$/i.test( url ) ) return url; // Relative URL return path + url; } } class InstancedBufferGeometry extends BufferGeometry { constructor() { super(); this.isInstancedBufferGeometry = true; this.type = 'InstancedBufferGeometry'; this.instanceCount = Infinity; } copy( source ) { super.copy( source ); this.instanceCount = source.instanceCount; return this; } toJSON() { const data = super.toJSON(); data.instanceCount = this.instanceCount; data.isInstancedBufferGeometry = true; return data; } } class BufferGeometryLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); loader.load( url, function ( text ) { try { onLoad( scope.parse( JSON.parse( text ) ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); } parse( json ) { const interleavedBufferMap = {}; const arrayBufferMap = {}; function getInterleavedBuffer( json, uuid ) { if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; const interleavedBuffers = json.interleavedBuffers; const interleavedBuffer = interleavedBuffers[ uuid ]; const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); const array = getTypedArray( interleavedBuffer.type, buffer ); const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); ib.uuid = interleavedBuffer.uuid; interleavedBufferMap[ uuid ] = ib; return ib; } function getArrayBuffer( json, uuid ) { if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; const arrayBuffers = json.arrayBuffers; const arrayBuffer = arrayBuffers[ uuid ]; const ab = new Uint32Array( arrayBuffer ).buffer; arrayBufferMap[ uuid ] = ab; return ab; } const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); const index = json.data.index; if ( index !== undefined ) { const typedArray = getTypedArray( index.type, index.array ); geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); } const attributes = json.data.attributes; for ( const key in attributes ) { const attribute = attributes[ key ]; let bufferAttribute; if ( attribute.isInterleavedBufferAttribute ) { const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); } else { const typedArray = getTypedArray( attribute.type, attribute.array ); const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); } if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); geometry.setAttribute( key, bufferAttribute ); } const morphAttributes = json.data.morphAttributes; if ( morphAttributes ) { for ( const key in morphAttributes ) { const attributeArray = morphAttributes[ key ]; const array = []; for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { const attribute = attributeArray[ i ]; let bufferAttribute; if ( attribute.isInterleavedBufferAttribute ) { const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); } else { const typedArray = getTypedArray( attribute.type, attribute.array ); bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); } if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; array.push( bufferAttribute ); } geometry.morphAttributes[ key ] = array; } } const morphTargetsRelative = json.data.morphTargetsRelative; if ( morphTargetsRelative ) { geometry.morphTargetsRelative = true; } const groups = json.data.groups || json.data.drawcalls || json.data.offsets; if ( groups !== undefined ) { for ( let i = 0, n = groups.length; i !== n; ++ i ) { const group = groups[ i ]; geometry.addGroup( group.start, group.count, group.materialIndex ); } } const boundingSphere = json.data.boundingSphere; if ( boundingSphere !== undefined ) { const center = new Vector3(); if ( boundingSphere.center !== undefined ) { center.fromArray( boundingSphere.center ); } geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); } if ( json.name ) geometry.name = json.name; if ( json.userData ) geometry.userData = json.userData; return geometry; } } class ObjectLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; this.resourcePath = this.resourcePath || path; const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); loader.load( url, function ( text ) { let json = null; try { json = JSON.parse( text ); } catch ( error ) { if ( onError !== undefined ) onError( error ); console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); return; } const metadata = json.metadata; if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); return; } scope.parse( json, onLoad ); }, onProgress, onError ); } async loadAsync( url, onProgress ) { const scope = this; const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; this.resourcePath = this.resourcePath || path; const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); const text = await loader.loadAsync( url, onProgress ); const json = JSON.parse( text ); const metadata = json.metadata; if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); } return await scope.parseAsync( json ); } parse( json, onLoad ) { const animations = this.parseAnimations( json.animations ); const shapes = this.parseShapes( json.shapes ); const geometries = this.parseGeometries( json.geometries, shapes ); const images = this.parseImages( json.images, function () { if ( onLoad !== undefined ) onLoad( object ); } ); const textures = this.parseTextures( json.textures, images ); const materials = this.parseMaterials( json.materials, textures ); const object = this.parseObject( json.object, geometries, materials, textures, animations ); const skeletons = this.parseSkeletons( json.skeletons, object ); this.bindSkeletons( object, skeletons ); // if ( onLoad !== undefined ) { let hasImages = false; for ( const uuid in images ) { if ( images[ uuid ].data instanceof HTMLImageElement ) { hasImages = true; break; } } if ( hasImages === false ) onLoad( object ); } return object; } async parseAsync( json ) { const animations = this.parseAnimations( json.animations ); const shapes = this.parseShapes( json.shapes ); const geometries = this.parseGeometries( json.geometries, shapes ); const images = await this.parseImagesAsync( json.images ); const textures = this.parseTextures( json.textures, images ); const materials = this.parseMaterials( json.materials, textures ); const object = this.parseObject( json.object, geometries, materials, textures, animations ); const skeletons = this.parseSkeletons( json.skeletons, object ); this.bindSkeletons( object, skeletons ); return object; } parseShapes( json ) { const shapes = {}; if ( json !== undefined ) { for ( let i = 0, l = json.length; i < l; i ++ ) { const shape = new Shape().fromJSON( json[ i ] ); shapes[ shape.uuid ] = shape; } } return shapes; } parseSkeletons( json, object ) { const skeletons = {}; const bones = {}; // generate bone lookup table object.traverse( function ( child ) { if ( child.isBone ) bones[ child.uuid ] = child; } ); // create skeletons if ( json !== undefined ) { for ( let i = 0, l = json.length; i < l; i ++ ) { const skeleton = new Skeleton().fromJSON( json[ i ], bones ); skeletons[ skeleton.uuid ] = skeleton; } } return skeletons; } parseGeometries( json, shapes ) { const geometries = {}; if ( json !== undefined ) { const bufferGeometryLoader = new BufferGeometryLoader(); for ( let i = 0, l = json.length; i < l; i ++ ) { let geometry; const data = json[ i ]; switch ( data.type ) { case 'BufferGeometry': case 'InstancedBufferGeometry': geometry = bufferGeometryLoader.parse( data ); break; default: if ( data.type in Geometries ) { geometry = Geometries[ data.type ].fromJSON( data, shapes ); } else { console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); } } geometry.uuid = data.uuid; if ( data.name !== undefined ) geometry.name = data.name; if ( data.userData !== undefined ) geometry.userData = data.userData; geometries[ data.uuid ] = geometry; } } return geometries; } parseMaterials( json, textures ) { const cache = {}; // MultiMaterial const materials = {}; if ( json !== undefined ) { const loader = new MaterialLoader(); loader.setTextures( textures ); for ( let i = 0, l = json.length; i < l; i ++ ) { const data = json[ i ]; if ( cache[ data.uuid ] === undefined ) { cache[ data.uuid ] = loader.parse( data ); } materials[ data.uuid ] = cache[ data.uuid ]; } } return materials; } parseAnimations( json ) { const animations = {}; if ( json !== undefined ) { for ( let i = 0; i < json.length; i ++ ) { const data = json[ i ]; const clip = AnimationClip.parse( data ); animations[ clip.uuid ] = clip; } } return animations; } parseImages( json, onLoad ) { const scope = this; const images = {}; let loader; function loadImage( url ) { scope.manager.itemStart( url ); return loader.load( url, function () { scope.manager.itemEnd( url ); }, undefined, function () { scope.manager.itemError( url ); scope.manager.itemEnd( url ); } ); } function deserializeImage( image ) { if ( typeof image === 'string' ) { const url = image; const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; return loadImage( path ); } else { if ( image.data ) { return { data: getTypedArray( image.type, image.data ), width: image.width, height: image.height }; } else { return null; } } } if ( json !== undefined && json.length > 0 ) { const manager = new LoadingManager( onLoad ); loader = new ImageLoader( manager ); loader.setCrossOrigin( this.crossOrigin ); for ( let i = 0, il = json.length; i < il; i ++ ) { const image = json[ i ]; const url = image.url; if ( Array.isArray( url ) ) { // load array of images e.g CubeTexture const imageArray = []; for ( let j = 0, jl = url.length; j < jl; j ++ ) { const currentUrl = url[ j ]; const deserializedImage = deserializeImage( currentUrl ); if ( deserializedImage !== null ) { if ( deserializedImage instanceof HTMLImageElement ) { imageArray.push( deserializedImage ); } else { // special case: handle array of data textures for cube textures imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); } } } images[ image.uuid ] = new Source( imageArray ); } else { // load single image const deserializedImage = deserializeImage( image.url ); images[ image.uuid ] = new Source( deserializedImage ); } } } return images; } async parseImagesAsync( json ) { const scope = this; const images = {}; let loader; async function deserializeImage( image ) { if ( typeof image === 'string' ) { const url = image; const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; return await loader.loadAsync( path ); } else { if ( image.data ) { return { data: getTypedArray( image.type, image.data ), width: image.width, height: image.height }; } else { return null; } } } if ( json !== undefined && json.length > 0 ) { loader = new ImageLoader( this.manager ); loader.setCrossOrigin( this.crossOrigin ); for ( let i = 0, il = json.length; i < il; i ++ ) { const image = json[ i ]; const url = image.url; if ( Array.isArray( url ) ) { // load array of images e.g CubeTexture const imageArray = []; for ( let j = 0, jl = url.length; j < jl; j ++ ) { const currentUrl = url[ j ]; const deserializedImage = await deserializeImage( currentUrl ); if ( deserializedImage !== null ) { if ( deserializedImage instanceof HTMLImageElement ) { imageArray.push( deserializedImage ); } else { // special case: handle array of data textures for cube textures imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); } } } images[ image.uuid ] = new Source( imageArray ); } else { // load single image const deserializedImage = await deserializeImage( image.url ); images[ image.uuid ] = new Source( deserializedImage ); } } } return images; } parseTextures( json, images ) { function parseConstant( value, type ) { if ( typeof value === 'number' ) return value; console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); return type[ value ]; } const textures = {}; if ( json !== undefined ) { for ( let i = 0, l = json.length; i < l; i ++ ) { const data = json[ i ]; if ( data.image === undefined ) { console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); } if ( images[ data.image ] === undefined ) { console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); } const source = images[ data.image ]; const image = source.data; let texture; if ( Array.isArray( image ) ) { texture = new CubeTexture(); if ( image.length === 6 ) texture.needsUpdate = true; } else { if ( image && image.data ) { texture = new DataTexture(); } else { texture = new Texture(); } if ( image ) texture.needsUpdate = true; // textures can have undefined image data } texture.source = source; texture.uuid = data.uuid; if ( data.name !== undefined ) texture.name = data.name; if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); if ( data.channel !== undefined ) texture.channel = data.channel; if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); if ( data.center !== undefined ) texture.center.fromArray( data.center ); if ( data.rotation !== undefined ) texture.rotation = data.rotation; if ( data.wrap !== undefined ) { texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); } if ( data.format !== undefined ) texture.format = data.format; if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; if ( data.type !== undefined ) texture.type = data.type; if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; if ( data.encoding !== undefined ) texture.encoding = data.encoding; // @deprecated, r152 if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; if ( data.flipY !== undefined ) texture.flipY = data.flipY; if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; if ( data.userData !== undefined ) texture.userData = data.userData; textures[ data.uuid ] = texture; } } return textures; } parseObject( data, geometries, materials, textures, animations ) { let object; function getGeometry( name ) { if ( geometries[ name ] === undefined ) { console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); } return geometries[ name ]; } function getMaterial( name ) { if ( name === undefined ) return undefined; if ( Array.isArray( name ) ) { const array = []; for ( let i = 0, l = name.length; i < l; i ++ ) { const uuid = name[ i ]; if ( materials[ uuid ] === undefined ) { console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); } array.push( materials[ uuid ] ); } return array; } if ( materials[ name ] === undefined ) { console.warn( 'THREE.ObjectLoader: Undefined material', name ); } return materials[ name ]; } function getTexture( uuid ) { if ( textures[ uuid ] === undefined ) { console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); } return textures[ uuid ]; } let geometry, material; switch ( data.type ) { case 'Scene': object = new Scene(); if ( data.background !== undefined ) { if ( Number.isInteger( data.background ) ) { object.background = new Color( data.background ); } else { object.background = getTexture( data.background ); } } if ( data.environment !== undefined ) { object.environment = getTexture( data.environment ); } if ( data.fog !== undefined ) { if ( data.fog.type === 'Fog' ) { object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); } else if ( data.fog.type === 'FogExp2' ) { object.fog = new FogExp2( data.fog.color, data.fog.density ); } if ( data.fog.name !== '' ) { object.fog.name = data.fog.name; } } if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; break; case 'PerspectiveCamera': object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); if ( data.focus !== undefined ) object.focus = data.focus; if ( data.zoom !== undefined ) object.zoom = data.zoom; if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); break; case 'OrthographicCamera': object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); if ( data.zoom !== undefined ) object.zoom = data.zoom; if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); break; case 'AmbientLight': object = new AmbientLight( data.color, data.intensity ); break; case 'DirectionalLight': object = new DirectionalLight( data.color, data.intensity ); break; case 'PointLight': object = new PointLight( data.color, data.intensity, data.distance, data.decay ); break; case 'RectAreaLight': object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); break; case 'SpotLight': object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); break; case 'HemisphereLight': object = new HemisphereLight( data.color, data.groundColor, data.intensity ); break; case 'LightProbe': object = new LightProbe().fromJSON( data ); break; case 'SkinnedMesh': geometry = getGeometry( data.geometry ); material = getMaterial( data.material ); object = new SkinnedMesh( geometry, material ); if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; break; case 'Mesh': geometry = getGeometry( data.geometry ); material = getMaterial( data.material ); object = new Mesh( geometry, material ); break; case 'InstancedMesh': geometry = getGeometry( data.geometry ); material = getMaterial( data.material ); const count = data.count; const instanceMatrix = data.instanceMatrix; const instanceColor = data.instanceColor; object = new InstancedMesh( geometry, material, count ); object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); break; case 'BatchedMesh': geometry = getGeometry( data.geometry ); material = getMaterial( data.material ); object = new BatchedMesh( data.maxGeometryCount, data.maxVertexCount, data.maxIndexCount, material ); object.geometry = geometry; object.perObjectFrustumCulled = data.perObjectFrustumCulled; object.sortObjects = data.sortObjects; object._drawRanges = data.drawRanges; object._reservedRanges = data.reservedRanges; object._visibility = data.visibility; object._active = data.active; object._bounds = data.bounds.map( bound => { const box = new Box3(); box.min.fromArray( bound.boxMin ); box.max.fromArray( bound.boxMax ); const sphere = new Sphere(); sphere.radius = bound.sphereRadius; sphere.center.fromArray( bound.sphereCenter ); return { boxInitialized: bound.boxInitialized, box: box, sphereInitialized: bound.sphereInitialized, sphere: sphere }; } ); object._maxGeometryCount = data.maxGeometryCount; object._maxVertexCount = data.maxVertexCount; object._maxIndexCount = data.maxIndexCount; object._geometryInitialized = data.geometryInitialized; object._geometryCount = data.geometryCount; object._matricesTexture = getTexture( data.matricesTexture.uuid ); break; case 'LOD': object = new LOD(); break; case 'Line': object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); break; case 'LineLoop': object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); break; case 'LineSegments': object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); break; case 'PointCloud': case 'Points': object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); break; case 'Sprite': object = new Sprite( getMaterial( data.material ) ); break; case 'Group': object = new Group(); break; case 'Bone': object = new Bone(); break; default: object = new Object3D(); } object.uuid = data.uuid; if ( data.name !== undefined ) object.name = data.name; if ( data.matrix !== undefined ) { object.matrix.fromArray( data.matrix ); if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); } else { if ( data.position !== undefined ) object.position.fromArray( data.position ); if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); } if ( data.up !== undefined ) object.up.fromArray( data.up ); if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; if ( data.shadow ) { if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); } if ( data.visible !== undefined ) object.visible = data.visible; if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; if ( data.userData !== undefined ) object.userData = data.userData; if ( data.layers !== undefined ) object.layers.mask = data.layers; if ( data.children !== undefined ) { const children = data.children; for ( let i = 0; i < children.length; i ++ ) { object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); } } if ( data.animations !== undefined ) { const objectAnimations = data.animations; for ( let i = 0; i < objectAnimations.length; i ++ ) { const uuid = objectAnimations[ i ]; object.animations.push( animations[ uuid ] ); } } if ( data.type === 'LOD' ) { if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; const levels = data.levels; for ( let l = 0; l < levels.length; l ++ ) { const level = levels[ l ]; const child = object.getObjectByProperty( 'uuid', level.object ); if ( child !== undefined ) { object.addLevel( child, level.distance, level.hysteresis ); } } } return object; } bindSkeletons( object, skeletons ) { if ( Object.keys( skeletons ).length === 0 ) return; object.traverse( function ( child ) { if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { const skeleton = skeletons[ child.skeleton ]; if ( skeleton === undefined ) { console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); } else { child.bind( skeleton, child.bindMatrix ); } } } ); } } const TEXTURE_MAPPING = { UVMapping: UVMapping, CubeReflectionMapping: CubeReflectionMapping, CubeRefractionMapping: CubeRefractionMapping, EquirectangularReflectionMapping: EquirectangularReflectionMapping, EquirectangularRefractionMapping: EquirectangularRefractionMapping, CubeUVReflectionMapping: CubeUVReflectionMapping }; const TEXTURE_WRAPPING = { RepeatWrapping: RepeatWrapping, ClampToEdgeWrapping: ClampToEdgeWrapping, MirroredRepeatWrapping: MirroredRepeatWrapping }; const TEXTURE_FILTER = { NearestFilter: NearestFilter, NearestMipmapNearestFilter: NearestMipmapNearestFilter, NearestMipmapLinearFilter: NearestMipmapLinearFilter, LinearFilter: LinearFilter, LinearMipmapNearestFilter: LinearMipmapNearestFilter, LinearMipmapLinearFilter: LinearMipmapLinearFilter }; class ImageBitmapLoader extends Loader { constructor( manager ) { super( manager ); this.isImageBitmapLoader = true; if ( typeof createImageBitmap === 'undefined' ) { console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); } if ( typeof fetch === 'undefined' ) { console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); } this.options = { premultiplyAlpha: 'none' }; } setOptions( options ) { this.options = options; return this; } load( url, onLoad, onProgress, onError ) { if ( url === undefined ) url = ''; if ( this.path !== undefined ) url = this.path + url; url = this.manager.resolveURL( url ); const scope = this; const cached = Cache.get( url ); if ( cached !== undefined ) { scope.manager.itemStart( url ); // If cached is a promise, wait for it to resolve if ( cached.then ) { cached.then( imageBitmap => { if ( onLoad ) onLoad( imageBitmap ); scope.manager.itemEnd( url ); } ).catch( e => { if ( onError ) onError( e ); } ); return; } // If cached is not a promise (i.e., it's already an imageBitmap) setTimeout( function () { if ( onLoad ) onLoad( cached ); scope.manager.itemEnd( url ); }, 0 ); return cached; } const fetchOptions = {}; fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; fetchOptions.headers = this.requestHeader; const promise = fetch( url, fetchOptions ).then( function ( res ) { return res.blob(); } ).then( function ( blob ) { return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); } ).then( function ( imageBitmap ) { Cache.add( url, imageBitmap ); if ( onLoad ) onLoad( imageBitmap ); scope.manager.itemEnd( url ); return imageBitmap; } ).catch( function ( e ) { if ( onError ) onError( e ); Cache.remove( url ); scope.manager.itemError( url ); scope.manager.itemEnd( url ); } ); Cache.add( url, promise ); scope.manager.itemStart( url ); } } let _context; class AudioContext { static getContext() { if ( _context === undefined ) { _context = new ( window.AudioContext || window.webkitAudioContext )(); } return _context; } static setContext( value ) { _context = value; } } class AudioLoader extends Loader { constructor( manager ) { super( manager ); } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( this.manager ); loader.setResponseType( 'arraybuffer' ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); loader.load( url, function ( buffer ) { try { // Create a copy of the buffer. The `decodeAudioData` method // detaches the buffer when complete, preventing reuse. const bufferCopy = buffer.slice( 0 ); const context = AudioContext.getContext(); context.decodeAudioData( bufferCopy, function ( audioBuffer ) { onLoad( audioBuffer ); } ).catch( handleError ); } catch ( e ) { handleError( e ); } }, onProgress, onError ); function handleError( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } } } const _eyeRight = /*@__PURE__*/ new Matrix4(); const _eyeLeft = /*@__PURE__*/ new Matrix4(); const _projectionMatrix = /*@__PURE__*/ new Matrix4(); class StereoCamera { constructor() { this.type = 'StereoCamera'; this.aspect = 1; this.eyeSep = 0.064; this.cameraL = new PerspectiveCamera(); this.cameraL.layers.enable( 1 ); this.cameraL.matrixAutoUpdate = false; this.cameraR = new PerspectiveCamera(); this.cameraR.layers.enable( 2 ); this.cameraR.matrixAutoUpdate = false; this._cache = { focus: null, fov: null, aspect: null, near: null, far: null, zoom: null, eyeSep: null }; } update( camera ) { const cache = this._cache; const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; if ( needsUpdate ) { cache.focus = camera.focus; cache.fov = camera.fov; cache.aspect = camera.aspect * this.aspect; cache.near = camera.near; cache.far = camera.far; cache.zoom = camera.zoom; cache.eyeSep = this.eyeSep; // Off-axis stereoscopic effect based on // http://paulbourke.net/stereographics/stereorender/ _projectionMatrix.copy( camera.projectionMatrix ); const eyeSepHalf = cache.eyeSep / 2; const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; let xmin, xmax; // translate xOffset _eyeLeft.elements[ 12 ] = - eyeSepHalf; _eyeRight.elements[ 12 ] = eyeSepHalf; // for left eye xmin = - ymax * cache.aspect + eyeSepOnProjection; xmax = ymax * cache.aspect + eyeSepOnProjection; _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); this.cameraL.projectionMatrix.copy( _projectionMatrix ); // for right eye xmin = - ymax * cache.aspect - eyeSepOnProjection; xmax = ymax * cache.aspect - eyeSepOnProjection; _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); this.cameraR.projectionMatrix.copy( _projectionMatrix ); } this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); } } class Clock { constructor( autoStart = true ) { this.autoStart = autoStart; this.startTime = 0; this.oldTime = 0; this.elapsedTime = 0; this.running = false; } start() { this.startTime = now(); this.oldTime = this.startTime; this.elapsedTime = 0; this.running = true; } stop() { this.getElapsedTime(); this.running = false; this.autoStart = false; } getElapsedTime() { this.getDelta(); return this.elapsedTime; } getDelta() { let diff = 0; if ( this.autoStart && ! this.running ) { this.start(); return 0; } if ( this.running ) { const newTime = now(); diff = ( newTime - this.oldTime ) / 1000; this.oldTime = newTime; this.elapsedTime += diff; } return diff; } } function now() { return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 } const _position$1 = /*@__PURE__*/ new Vector3(); const _quaternion$1 = /*@__PURE__*/ new Quaternion(); const _scale$1 = /*@__PURE__*/ new Vector3(); const _orientation$1 = /*@__PURE__*/ new Vector3(); class AudioListener extends Object3D { constructor() { super(); this.type = 'AudioListener'; this.context = AudioContext.getContext(); this.gain = this.context.createGain(); this.gain.connect( this.context.destination ); this.filter = null; this.timeDelta = 0; // private this._clock = new Clock(); } getInput() { return this.gain; } removeFilter() { if ( this.filter !== null ) { this.gain.disconnect( this.filter ); this.filter.disconnect( this.context.destination ); this.gain.connect( this.context.destination ); this.filter = null; } return this; } getFilter() { return this.filter; } setFilter( value ) { if ( this.filter !== null ) { this.gain.disconnect( this.filter ); this.filter.disconnect( this.context.destination ); } else { this.gain.disconnect( this.context.destination ); } this.filter = value; this.gain.connect( this.filter ); this.filter.connect( this.context.destination ); return this; } getMasterVolume() { return this.gain.gain.value; } setMasterVolume( value ) { this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); return this; } updateMatrixWorld( force ) { super.updateMatrixWorld( force ); const listener = this.context.listener; const up = this.up; this.timeDelta = this._clock.getDelta(); this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); _orientation$1.set( 0, 0, - 1 ).applyQuaternion( _quaternion$1 ); if ( listener.positionX ) { // code path for Chrome (see #14393) const endTime = this.context.currentTime + this.timeDelta; listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); listener.forwardX.linearRampToValueAtTime( _orientation$1.x, endTime ); listener.forwardY.linearRampToValueAtTime( _orientation$1.y, endTime ); listener.forwardZ.linearRampToValueAtTime( _orientation$1.z, endTime ); listener.upX.linearRampToValueAtTime( up.x, endTime ); listener.upY.linearRampToValueAtTime( up.y, endTime ); listener.upZ.linearRampToValueAtTime( up.z, endTime ); } else { listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); listener.setOrientation( _orientation$1.x, _orientation$1.y, _orientation$1.z, up.x, up.y, up.z ); } } } class Audio extends Object3D { constructor( listener ) { super(); this.type = 'Audio'; this.listener = listener; this.context = listener.context; this.gain = this.context.createGain(); this.gain.connect( listener.getInput() ); this.autoplay = false; this.buffer = null; this.detune = 0; this.loop = false; this.loopStart = 0; this.loopEnd = 0; this.offset = 0; this.duration = undefined; this.playbackRate = 1; this.isPlaying = false; this.hasPlaybackControl = true; this.source = null; this.sourceType = 'empty'; this._startedAt = 0; this._progress = 0; this._connected = false; this.filters = []; } getOutput() { return this.gain; } setNodeSource( audioNode ) { this.hasPlaybackControl = false; this.sourceType = 'audioNode'; this.source = audioNode; this.connect(); return this; } setMediaElementSource( mediaElement ) { this.hasPlaybackControl = false; this.sourceType = 'mediaNode'; this.source = this.context.createMediaElementSource( mediaElement ); this.connect(); return this; } setMediaStreamSource( mediaStream ) { this.hasPlaybackControl = false; this.sourceType = 'mediaStreamNode'; this.source = this.context.createMediaStreamSource( mediaStream ); this.connect(); return this; } setBuffer( audioBuffer ) { this.buffer = audioBuffer; this.sourceType = 'buffer'; if ( this.autoplay ) this.play(); return this; } play( delay = 0 ) { if ( this.isPlaying === true ) { console.warn( 'THREE.Audio: Audio is already playing.' ); return; } if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return; } this._startedAt = this.context.currentTime + delay; const source = this.context.createBufferSource(); source.buffer = this.buffer; source.loop = this.loop; source.loopStart = this.loopStart; source.loopEnd = this.loopEnd; source.onended = this.onEnded.bind( this ); source.start( this._startedAt, this._progress + this.offset, this.duration ); this.isPlaying = true; this.source = source; this.setDetune( this.detune ); this.setPlaybackRate( this.playbackRate ); return this.connect(); } pause() { if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return; } if ( this.isPlaying === true ) { // update current progress this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; if ( this.loop === true ) { // ensure _progress does not exceed duration with looped audios this._progress = this._progress % ( this.duration || this.buffer.duration ); } this.source.stop(); this.source.onended = null; this.isPlaying = false; } return this; } stop() { if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return; } this._progress = 0; if ( this.source !== null ) { this.source.stop(); this.source.onended = null; } this.isPlaying = false; return this; } connect() { if ( this.filters.length > 0 ) { this.source.connect( this.filters[ 0 ] ); for ( let i = 1, l = this.filters.length; i < l; i ++ ) { this.filters[ i - 1 ].connect( this.filters[ i ] ); } this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); } else { this.source.connect( this.getOutput() ); } this._connected = true; return this; } disconnect() { if ( this._connected === false ) { return; } if ( this.filters.length > 0 ) { this.source.disconnect( this.filters[ 0 ] ); for ( let i = 1, l = this.filters.length; i < l; i ++ ) { this.filters[ i - 1 ].disconnect( this.filters[ i ] ); } this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); } else { this.source.disconnect( this.getOutput() ); } this._connected = false; return this; } getFilters() { return this.filters; } setFilters( value ) { if ( ! value ) value = []; if ( this._connected === true ) { this.disconnect(); this.filters = value.slice(); this.connect(); } else { this.filters = value.slice(); } return this; } setDetune( value ) { this.detune = value; if ( this.source.detune === undefined ) return; // only set detune when available if ( this.isPlaying === true ) { this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); } return this; } getDetune() { return this.detune; } getFilter() { return this.getFilters()[ 0 ]; } setFilter( filter ) { return this.setFilters( filter ? [ filter ] : [] ); } setPlaybackRate( value ) { if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return; } this.playbackRate = value; if ( this.isPlaying === true ) { this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); } return this; } getPlaybackRate() { return this.playbackRate; } onEnded() { this.isPlaying = false; } getLoop() { if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return false; } return this.loop; } setLoop( value ) { if ( this.hasPlaybackControl === false ) { console.warn( 'THREE.Audio: this Audio has no playback control.' ); return; } this.loop = value; if ( this.isPlaying === true ) { this.source.loop = this.loop; } return this; } setLoopStart( value ) { this.loopStart = value; return this; } setLoopEnd( value ) { this.loopEnd = value; return this; } getVolume() { return this.gain.gain.value; } setVolume( value ) { this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); return this; } } const _position = /*@__PURE__*/ new Vector3(); const _quaternion = /*@__PURE__*/ new Quaternion(); const _scale = /*@__PURE__*/ new Vector3(); const _orientation = /*@__PURE__*/ new Vector3(); class PositionalAudio extends Audio { constructor( listener ) { super( listener ); this.panner = this.context.createPanner(); this.panner.panningModel = 'HRTF'; this.panner.connect( this.gain ); } connect() { super.connect(); this.panner.connect( this.gain ); } disconnect() { super.disconnect(); this.panner.disconnect( this.gain ); } getOutput() { return this.panner; } getRefDistance() { return this.panner.refDistance; } setRefDistance( value ) { this.panner.refDistance = value; return this; } getRolloffFactor() { return this.panner.rolloffFactor; } setRolloffFactor( value ) { this.panner.rolloffFactor = value; return this; } getDistanceModel() { return this.panner.distanceModel; } setDistanceModel( value ) { this.panner.distanceModel = value; return this; } getMaxDistance() { return this.panner.maxDistance; } setMaxDistance( value ) { this.panner.maxDistance = value; return this; } setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { this.panner.coneInnerAngle = coneInnerAngle; this.panner.coneOuterAngle = coneOuterAngle; this.panner.coneOuterGain = coneOuterGain; return this; } updateMatrixWorld( force ) { super.updateMatrixWorld( force ); if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; this.matrixWorld.decompose( _position, _quaternion, _scale ); _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); const panner = this.panner; if ( panner.positionX ) { // code path for Chrome and Firefox (see #14393) const endTime = this.context.currentTime + this.listener.timeDelta; panner.positionX.linearRampToValueAtTime( _position.x, endTime ); panner.positionY.linearRampToValueAtTime( _position.y, endTime ); panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); } else { panner.setPosition( _position.x, _position.y, _position.z ); panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); } } } class AudioAnalyser { constructor( audio, fftSize = 2048 ) { this.analyser = audio.context.createAnalyser(); this.analyser.fftSize = fftSize; this.data = new Uint8Array( this.analyser.frequencyBinCount ); audio.getOutput().connect( this.analyser ); } getFrequencyData() { this.analyser.getByteFrequencyData( this.data ); return this.data; } getAverageFrequency() { let value = 0; const data = this.getFrequencyData(); for ( let i = 0; i < data.length; i ++ ) { value += data[ i ]; } return value / data.length; } } class PropertyMixer { constructor( binding, typeName, valueSize ) { this.binding = binding; this.valueSize = valueSize; let mixFunction, mixFunctionAdditive, setIdentity; // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] // // interpolators can use .buffer as their .result // the data then goes to 'incoming' // // 'accu0' and 'accu1' are used frame-interleaved for // the cumulative result and are compared to detect // changes // // 'orig' stores the original state of the property // // 'add' is used for additive cumulative results // // 'work' is optional and is only present for quaternion types. It is used // to store intermediate quaternion multiplication results switch ( typeName ) { case 'quaternion': mixFunction = this._slerp; mixFunctionAdditive = this._slerpAdditive; setIdentity = this._setAdditiveIdentityQuaternion; this.buffer = new Float64Array( valueSize * 6 ); this._workIndex = 5; break; case 'string': case 'bool': mixFunction = this._select; // Use the regular mix function and for additive on these types, // additive is not relevant for non-numeric types mixFunctionAdditive = this._select; setIdentity = this._setAdditiveIdentityOther; this.buffer = new Array( valueSize * 5 ); break; default: mixFunction = this._lerp; mixFunctionAdditive = this._lerpAdditive; setIdentity = this._setAdditiveIdentityNumeric; this.buffer = new Float64Array( valueSize * 5 ); } this._mixBufferRegion = mixFunction; this._mixBufferRegionAdditive = mixFunctionAdditive; this._setIdentity = setIdentity; this._origIndex = 3; this._addIndex = 4; this.cumulativeWeight = 0; this.cumulativeWeightAdditive = 0; this.useCount = 0; this.referenceCount = 0; } // accumulate data in the 'incoming' region into 'accu<i>' accumulate( accuIndex, weight ) { // note: happily accumulating nothing when weight = 0, the caller knows // the weight and shouldn't have made the call in the first place const buffer = this.buffer, stride = this.valueSize, offset = accuIndex * stride + stride; let currentWeight = this.cumulativeWeight; if ( currentWeight === 0 ) { // accuN := incoming * weight for ( let i = 0; i !== stride; ++ i ) { buffer[ offset + i ] = buffer[ i ]; } currentWeight = weight; } else { // accuN := accuN + incoming * weight currentWeight += weight; const mix = weight / currentWeight; this._mixBufferRegion( buffer, offset, 0, mix, stride ); } this.cumulativeWeight = currentWeight; } // accumulate data in the 'incoming' region into 'add' accumulateAdditive( weight ) { const buffer = this.buffer, stride = this.valueSize, offset = stride * this._addIndex; if ( this.cumulativeWeightAdditive === 0 ) { // add = identity this._setIdentity(); } // add := add + incoming * weight this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); this.cumulativeWeightAdditive += weight; } // apply the state of 'accu<i>' to the binding when accus differ apply( accuIndex ) { const stride = this.valueSize, buffer = this.buffer, offset = accuIndex * stride + stride, weight = this.cumulativeWeight, weightAdditive = this.cumulativeWeightAdditive, binding = this.binding; this.cumulativeWeight = 0; this.cumulativeWeightAdditive = 0; if ( weight < 1 ) { // accuN := accuN + original * ( 1 - cumulativeWeight ) const originalValueOffset = stride * this._origIndex; this._mixBufferRegion( buffer, offset, originalValueOffset, 1 - weight, stride ); } if ( weightAdditive > 0 ) { // accuN := accuN + additive accuN this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); } for ( let i = stride, e = stride + stride; i !== e; ++ i ) { if ( buffer[ i ] !== buffer[ i + stride ] ) { // value has changed -> update scene graph binding.setValue( buffer, offset ); break; } } } // remember the state of the bound property and copy it to both accus saveOriginalState() { const binding = this.binding; const buffer = this.buffer, stride = this.valueSize, originalValueOffset = stride * this._origIndex; binding.getValue( buffer, originalValueOffset ); // accu[0..1] := orig -- initially detect changes against the original for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; } // Add to identity for additive this._setIdentity(); this.cumulativeWeight = 0; this.cumulativeWeightAdditive = 0; } // apply the state previously taken via 'saveOriginalState' to the binding restoreOriginalState() { const originalValueOffset = this.valueSize * 3; this.binding.setValue( this.buffer, originalValueOffset ); } _setAdditiveIdentityNumeric() { const startIndex = this._addIndex * this.valueSize; const endIndex = startIndex + this.valueSize; for ( let i = startIndex; i < endIndex; i ++ ) { this.buffer[ i ] = 0; } } _setAdditiveIdentityQuaternion() { this._setAdditiveIdentityNumeric(); this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; } _setAdditiveIdentityOther() { const startIndex = this._origIndex * this.valueSize; const targetIndex = this._addIndex * this.valueSize; for ( let i = 0; i < this.valueSize; i ++ ) { this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; } } // mix functions _select( buffer, dstOffset, srcOffset, t, stride ) { if ( t >= 0.5 ) { for ( let i = 0; i !== stride; ++ i ) { buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; } } } _slerp( buffer, dstOffset, srcOffset, t ) { Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); } _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { const workOffset = this._workIndex * stride; // Store result in intermediate buffer offset Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); // Slerp to the intermediate result Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); } _lerp( buffer, dstOffset, srcOffset, t, stride ) { const s = 1 - t; for ( let i = 0; i !== stride; ++ i ) { const j = dstOffset + i; buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; } } _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { for ( let i = 0; i !== stride; ++ i ) { const j = dstOffset + i; buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; } } } // Characters [].:/ are reserved for track binding syntax. const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); // Attempts to allow node names from any language. ES5's `\w` regexp matches // only latin characters, and the unicode \p{L} is not yet supported. So // instead, we exclude reserved characters and match everything else. const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; // Parent directories, delimited by '/' or ':'. Currently unused, but must // be matched to parse the rest of the track name. const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); // Object on target node, and accessor. May not contain reserved // characters. Accessor may contain any character except closing bracket. const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); // Property and accessor. May not contain reserved characters. Accessor may // contain any non-bracket characters. const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); const _trackRe = new RegExp( '' + '^' + _directoryRe + _nodeRe + _objectRe + _propertyRe + '$' ); const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; class Composite { constructor( targetGroup, path, optionalParsedPath ) { const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); this._targetGroup = targetGroup; this._bindings = targetGroup.subscribe_( path, parsedPath ); } getValue( array, offset ) { this.bind(); // bind all binding const firstValidIndex = this._targetGroup.nCachedObjects_, binding = this._bindings[ firstValidIndex ]; // and only call .getValue on the first if ( binding !== undefined ) binding.getValue( array, offset ); } setValue( array, offset ) { const bindings = this._bindings; for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { bindings[ i ].setValue( array, offset ); } } bind() { const bindings = this._bindings; for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { bindings[ i ].bind(); } } unbind() { const bindings = this._bindings; for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { bindings[ i ].unbind(); } } } // Note: This class uses a State pattern on a per-method basis: // 'bind' sets 'this.getValue' / 'setValue' and shadows the // prototype version of these methods with one that represents // the bound state. When the property is not found, the methods // become no-ops. class PropertyBinding { constructor( rootNode, path, parsedPath ) { this.path = path; this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); this.rootNode = rootNode; // initial state of these methods that calls 'bind' this.getValue = this._getValue_unbound; this.setValue = this._setValue_unbound; } static create( root, path, parsedPath ) { if ( ! ( root && root.isAnimationObjectGroup ) ) { return new PropertyBinding( root, path, parsedPath ); } else { return new PropertyBinding.Composite( root, path, parsedPath ); } } /** * Replaces spaces with underscores and removes unsupported characters from * node names, to ensure compatibility with parseTrackName(). * * @param {string} name Node name to be sanitized. * @return {string} */ static sanitizeNodeName( name ) { return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); } static parseTrackName( trackName ) { const matches = _trackRe.exec( trackName ); if ( matches === null ) { throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); } const results = { // directoryName: matches[ 1 ], // (tschw) currently unused nodeName: matches[ 2 ], objectName: matches[ 3 ], objectIndex: matches[ 4 ], propertyName: matches[ 5 ], // required propertyIndex: matches[ 6 ] }; const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); if ( lastDot !== undefined && lastDot !== - 1 ) { const objectName = results.nodeName.substring( lastDot + 1 ); // Object names must be checked against an allowlist. Otherwise, there // is no way to parse 'foo.bar.baz': 'baz' must be a property, but // 'bar' could be the objectName, or part of a nodeName (which can // include '.' characters). if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) { results.nodeName = results.nodeName.substring( 0, lastDot ); results.objectName = objectName; } } if ( results.propertyName === null || results.propertyName.length === 0 ) { throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); } return results; } static findNode( root, nodeName ) { if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { return root; } // search into skeleton bones. if ( root.skeleton ) { const bone = root.skeleton.getBoneByName( nodeName ); if ( bone !== undefined ) { return bone; } } // search into node subtree. if ( root.children ) { const searchNodeSubtree = function ( children ) { for ( let i = 0; i < children.length; i ++ ) { const childNode = children[ i ]; if ( childNode.name === nodeName || childNode.uuid === nodeName ) { return childNode; } const result = searchNodeSubtree( childNode.children ); if ( result ) return result; } return null; }; const subTreeNode = searchNodeSubtree( root.children ); if ( subTreeNode ) { return subTreeNode; } } return null; } // these are used to "bind" a nonexistent property _getValue_unavailable() {} _setValue_unavailable() {} // Getters _getValue_direct( buffer, offset ) { buffer[ offset ] = this.targetObject[ this.propertyName ]; } _getValue_array( buffer, offset ) { const source = this.resolvedProperty; for ( let i = 0, n = source.length; i !== n; ++ i ) { buffer[ offset ++ ] = source[ i ]; } } _getValue_arrayElement( buffer, offset ) { buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; } _getValue_toArray( buffer, offset ) { this.resolvedProperty.toArray( buffer, offset ); } // Direct _setValue_direct( buffer, offset ) { this.targetObject[ this.propertyName ] = buffer[ offset ]; } _setValue_direct_setNeedsUpdate( buffer, offset ) { this.targetObject[ this.propertyName ] = buffer[ offset ]; this.targetObject.needsUpdate = true; } _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { this.targetObject[ this.propertyName ] = buffer[ offset ]; this.targetObject.matrixWorldNeedsUpdate = true; } // EntireArray _setValue_array( buffer, offset ) { const dest = this.resolvedProperty; for ( let i = 0, n = dest.length; i !== n; ++ i ) { dest[ i ] = buffer[ offset ++ ]; } } _setValue_array_setNeedsUpdate( buffer, offset ) { const dest = this.resolvedProperty; for ( let i = 0, n = dest.length; i !== n; ++ i ) { dest[ i ] = buffer[ offset ++ ]; } this.targetObject.needsUpdate = true; } _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { const dest = this.resolvedProperty; for ( let i = 0, n = dest.length; i !== n; ++ i ) { dest[ i ] = buffer[ offset ++ ]; } this.targetObject.matrixWorldNeedsUpdate = true; } // ArrayElement _setValue_arrayElement( buffer, offset ) { this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; } _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; this.targetObject.needsUpdate = true; } _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; this.targetObject.matrixWorldNeedsUpdate = true; } // HasToFromArray _setValue_fromArray( buffer, offset ) { this.resolvedProperty.fromArray( buffer, offset ); } _setValue_fromArray_setNeedsUpdate( buffer, offset ) { this.resolvedProperty.fromArray( buffer, offset ); this.targetObject.needsUpdate = true; } _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { this.resolvedProperty.fromArray( buffer, offset ); this.targetObject.matrixWorldNeedsUpdate = true; } _getValue_unbound( targetArray, offset ) { this.bind(); this.getValue( targetArray, offset ); } _setValue_unbound( sourceArray, offset ) { this.bind(); this.setValue( sourceArray, offset ); } // create getter / setter pair for a property in the scene graph bind() { let targetObject = this.node; const parsedPath = this.parsedPath; const objectName = parsedPath.objectName; const propertyName = parsedPath.propertyName; let propertyIndex = parsedPath.propertyIndex; if ( ! targetObject ) { targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); this.node = targetObject; } // set fail state so we can just 'return' on error this.getValue = this._getValue_unavailable; this.setValue = this._setValue_unavailable; // ensure there is a value node if ( ! targetObject ) { console.warn( 'THREE.PropertyBinding: No target node found for track: ' + this.path + '.' ); return; } if ( objectName ) { let objectIndex = parsedPath.objectIndex; // special cases were we need to reach deeper into the hierarchy to get the face materials.... switch ( objectName ) { case 'materials': if ( ! targetObject.material ) { console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); return; } if ( ! targetObject.material.materials ) { console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); return; } targetObject = targetObject.material.materials; break; case 'bones': if ( ! targetObject.skeleton ) { console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); return; } // potential future optimization: skip this if propertyIndex is already an integer // and convert the integer string to a true integer. targetObject = targetObject.skeleton.bones; // support resolving morphTarget names into indices. for ( let i = 0; i < targetObject.length; i ++ ) { if ( targetObject[ i ].name === objectIndex ) { objectIndex = i; break; } } break; case 'map': if ( 'map' in targetObject ) { targetObject = targetObject.map; break; } if ( ! targetObject.material ) { console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); return; } if ( ! targetObject.material.map ) { console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); return; } targetObject = targetObject.material.map; break; default: if ( targetObject[ objectName ] === undefined ) { console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); return; } targetObject = targetObject[ objectName ]; } if ( objectIndex !== undefined ) { if ( targetObject[ objectIndex ] === undefined ) { console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); return; } targetObject = targetObject[ objectIndex ]; } } // resolve property const nodeProperty = targetObject[ propertyName ]; if ( nodeProperty === undefined ) { const nodeName = parsedPath.nodeName; console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + '.' + propertyName + ' but it wasn\'t found.', targetObject ); return; } // determine versioning scheme let versioning = this.Versioning.None; this.targetObject = targetObject; if ( targetObject.needsUpdate !== undefined ) { // material versioning = this.Versioning.NeedsUpdate; } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform versioning = this.Versioning.MatrixWorldNeedsUpdate; } // determine how the property gets bound let bindingType = this.BindingType.Direct; if ( propertyIndex !== undefined ) { // access a sub element of the property array (only primitives are supported right now) if ( propertyName === 'morphTargetInfluences' ) { // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. // support resolving morphTarget names into indices. if ( ! targetObject.geometry ) { console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); return; } if ( ! targetObject.geometry.morphAttributes ) { console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); return; } if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; } } bindingType = this.BindingType.ArrayElement; this.resolvedProperty = nodeProperty; this.propertyIndex = propertyIndex; } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { // must use copy for Object3D.Euler/Quaternion bindingType = this.BindingType.HasFromToArray; this.resolvedProperty = nodeProperty; } else if ( Array.isArray( nodeProperty ) ) { bindingType = this.BindingType.EntireArray; this.resolvedProperty = nodeProperty; } else { this.propertyName = propertyName; } // select getter / setter this.getValue = this.GetterByBindingType[ bindingType ]; this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; } unbind() { this.node = null; // back to the prototype version of getValue / setValue // note: avoiding to mutate the shape of 'this' via 'delete' this.getValue = this._getValue_unbound; this.setValue = this._setValue_unbound; } } PropertyBinding.Composite = Composite; PropertyBinding.prototype.BindingType = { Direct: 0, EntireArray: 1, ArrayElement: 2, HasFromToArray: 3 }; PropertyBinding.prototype.Versioning = { None: 0, NeedsUpdate: 1, MatrixWorldNeedsUpdate: 2 }; PropertyBinding.prototype.GetterByBindingType = [ PropertyBinding.prototype._getValue_direct, PropertyBinding.prototype._getValue_array, PropertyBinding.prototype._getValue_arrayElement, PropertyBinding.prototype._getValue_toArray, ]; PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ [ // Direct PropertyBinding.prototype._setValue_direct, PropertyBinding.prototype._setValue_direct_setNeedsUpdate, PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, ], [ // EntireArray PropertyBinding.prototype._setValue_array, PropertyBinding.prototype._setValue_array_setNeedsUpdate, PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, ], [ // ArrayElement PropertyBinding.prototype._setValue_arrayElement, PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, ], [ // HasToFromArray PropertyBinding.prototype._setValue_fromArray, PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, ] ]; /** * * A group of objects that receives a shared animation state. * * Usage: * * - Add objects you would otherwise pass as 'root' to the * constructor or the .clipAction method of AnimationMixer. * * - Instead pass this object as 'root'. * * - You can also add and remove objects later when the mixer * is running. * * Note: * * Objects of this class appear as one object to the mixer, * so cache control of the individual objects must be done * on the group. * * Limitation: * * - The animated properties must be compatible among the * all objects in the group. * * - A single property can either be controlled through a * target group or directly, but not both. */ class AnimationObjectGroup { constructor() { this.isAnimationObjectGroup = true; this.uuid = generateUUID(); // cached objects followed by the active ones this._objects = Array.prototype.slice.call( arguments ); this.nCachedObjects_ = 0; // threshold // note: read by PropertyBinding.Composite const indices = {}; this._indicesByUUID = indices; // for bookkeeping for ( let i = 0, n = arguments.length; i !== n; ++ i ) { indices[ arguments[ i ].uuid ] = i; } this._paths = []; // inside: string this._parsedPaths = []; // inside: { we don't care, here } this._bindings = []; // inside: Array< PropertyBinding > this._bindingsIndicesByPath = {}; // inside: indices in these arrays const scope = this; this.stats = { objects: { get total() { return scope._objects.length; }, get inUse() { return this.total - scope.nCachedObjects_; } }, get bindingsPerObject() { return scope._bindings.length; } }; } add() { const objects = this._objects, indicesByUUID = this._indicesByUUID, paths = this._paths, parsedPaths = this._parsedPaths, bindings = this._bindings, nBindings = bindings.length; let knownObject = undefined, nObjects = objects.length, nCachedObjects = this.nCachedObjects_; for ( let i = 0, n = arguments.length; i !== n; ++ i ) { const object = arguments[ i ], uuid = object.uuid; let index = indicesByUUID[ uuid ]; if ( index === undefined ) { // unknown object -> add it to the ACTIVE region index = nObjects ++; indicesByUUID[ uuid ] = index; objects.push( object ); // accounting is done, now do the same for all bindings for ( let j = 0, m = nBindings; j !== m; ++ j ) { bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); } } else if ( index < nCachedObjects ) { knownObject = objects[ index ]; // move existing object to the ACTIVE region const firstActiveIndex = -- nCachedObjects, lastCachedObject = objects[ firstActiveIndex ]; indicesByUUID[ lastCachedObject.uuid ] = index; objects[ index ] = lastCachedObject; indicesByUUID[ uuid ] = firstActiveIndex; objects[ firstActiveIndex ] = object; // accounting is done, now do the same for all bindings for ( let j = 0, m = nBindings; j !== m; ++ j ) { const bindingsForPath = bindings[ j ], lastCached = bindingsForPath[ firstActiveIndex ]; let binding = bindingsForPath[ index ]; bindingsForPath[ index ] = lastCached; if ( binding === undefined ) { // since we do not bother to create new bindings // for objects that are cached, the binding may // or may not exist binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); } bindingsForPath[ firstActiveIndex ] = binding; } } else if ( objects[ index ] !== knownObject ) { console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); } // else the object is already where we want it to be } // for arguments this.nCachedObjects_ = nCachedObjects; } remove() { const objects = this._objects, indicesByUUID = this._indicesByUUID, bindings = this._bindings, nBindings = bindings.length; let nCachedObjects = this.nCachedObjects_; for ( let i = 0, n = arguments.length; i !== n; ++ i ) { const object = arguments[ i ], uuid = object.uuid, index = indicesByUUID[ uuid ]; if ( index !== undefined && index >= nCachedObjects ) { // move existing object into the CACHED region const lastCachedIndex = nCachedObjects ++, firstActiveObject = objects[ lastCachedIndex ]; indicesByUUID[ firstActiveObject.uuid ] = index; objects[ index ] = firstActiveObject; indicesByUUID[ uuid ] = lastCachedIndex; objects[ lastCachedIndex ] = object; // accounting is done, now do the same for all bindings for ( let j = 0, m = nBindings; j !== m; ++ j ) { const bindingsForPath = bindings[ j ], firstActive = bindingsForPath[ lastCachedIndex ], binding = bindingsForPath[ index ]; bindingsForPath[ index ] = firstActive; bindingsForPath[ lastCachedIndex ] = binding; } } } // for arguments this.nCachedObjects_ = nCachedObjects; } // remove & forget uncache() { const objects = this._objects, indicesByUUID = this._indicesByUUID, bindings = this._bindings, nBindings = bindings.length; let nCachedObjects = this.nCachedObjects_, nObjects = objects.length; for ( let i = 0, n = arguments.length; i !== n; ++ i ) { const object = arguments[ i ], uuid = object.uuid, index = indicesByUUID[ uuid ]; if ( index !== undefined ) { delete indicesByUUID[ uuid ]; if ( index < nCachedObjects ) { // object is cached, shrink the CACHED region const firstActiveIndex = -- nCachedObjects, lastCachedObject = objects[ firstActiveIndex ], lastIndex = -- nObjects, lastObject = objects[ lastIndex ]; // last cached object takes this object's place indicesByUUID[ lastCachedObject.uuid ] = index; objects[ index ] = lastCachedObject; // last object goes to the activated slot and pop indicesByUUID[ lastObject.uuid ] = firstActiveIndex; objects[ firstActiveIndex ] = lastObject; objects.pop(); // accounting is done, now do the same for all bindings for ( let j = 0, m = nBindings; j !== m; ++ j ) { const bindingsForPath = bindings[ j ], lastCached = bindingsForPath[ firstActiveIndex ], last = bindingsForPath[ lastIndex ]; bindingsForPath[ index ] = lastCached; bindingsForPath[ firstActiveIndex ] = last; bindingsForPath.pop(); } } else { // object is active, just swap with the last and pop const lastIndex = -- nObjects, lastObject = objects[ lastIndex ]; if ( lastIndex > 0 ) { indicesByUUID[ lastObject.uuid ] = index; } objects[ index ] = lastObject; objects.pop(); // accounting is done, now do the same for all bindings for ( let j = 0, m = nBindings; j !== m; ++ j ) { const bindingsForPath = bindings[ j ]; bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; bindingsForPath.pop(); } } // cached or active } // if object is known } // for arguments this.nCachedObjects_ = nCachedObjects; } // Internal interface used by befriended PropertyBinding.Composite: subscribe_( path, parsedPath ) { // returns an array of bindings for the given path that is changed // according to the contained objects in the group const indicesByPath = this._bindingsIndicesByPath; let index = indicesByPath[ path ]; const bindings = this._bindings; if ( index !== undefined ) return bindings[ index ]; const paths = this._paths, parsedPaths = this._parsedPaths, objects = this._objects, nObjects = objects.length, nCachedObjects = this.nCachedObjects_, bindingsForPath = new Array( nObjects ); index = bindings.length; indicesByPath[ path ] = index; paths.push( path ); parsedPaths.push( parsedPath ); bindings.push( bindingsForPath ); for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { const object = objects[ i ]; bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); } return bindingsForPath; } unsubscribe_( path ) { // tells the group to forget about a property path and no longer // update the array previously obtained with 'subscribe_' const indicesByPath = this._bindingsIndicesByPath, index = indicesByPath[ path ]; if ( index !== undefined ) { const paths = this._paths, parsedPaths = this._parsedPaths, bindings = this._bindings, lastBindingsIndex = bindings.length - 1, lastBindings = bindings[ lastBindingsIndex ], lastBindingsPath = path[ lastBindingsIndex ]; indicesByPath[ lastBindingsPath ] = index; bindings[ index ] = lastBindings; bindings.pop(); parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; parsedPaths.pop(); paths[ index ] = paths[ lastBindingsIndex ]; paths.pop(); } } } class AnimationAction { constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { this._mixer = mixer; this._clip = clip; this._localRoot = localRoot; this.blendMode = blendMode; const tracks = clip.tracks, nTracks = tracks.length, interpolants = new Array( nTracks ); const interpolantSettings = { endingStart: ZeroCurvatureEnding, endingEnd: ZeroCurvatureEnding }; for ( let i = 0; i !== nTracks; ++ i ) { const interpolant = tracks[ i ].createInterpolant( null ); interpolants[ i ] = interpolant; interpolant.settings = interpolantSettings; } this._interpolantSettings = interpolantSettings; this._interpolants = interpolants; // bound by the mixer // inside: PropertyMixer (managed by the mixer) this._propertyBindings = new Array( nTracks ); this._cacheIndex = null; // for the memory manager this._byClipCacheIndex = null; // for the memory manager this._timeScaleInterpolant = null; this._weightInterpolant = null; this.loop = LoopRepeat; this._loopCount = - 1; // global mixer time when the action is to be started // it's set back to 'null' upon start of the action this._startTime = null; // scaled local time of the action // gets clamped or wrapped to 0..clip.duration according to loop this.time = 0; this.timeScale = 1; this._effectiveTimeScale = 1; this.weight = 1; this._effectiveWeight = 1; this.repetitions = Infinity; // no. of repetitions when looping this.paused = false; // true -> zero effective time scale this.enabled = true; // false -> zero effective weight this.clampWhenFinished = false;// keep feeding the last frame? this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate this.zeroSlopeAtEnd = true;// clips for start, loop and end } // State & Scheduling play() { this._mixer._activateAction( this ); return this; } stop() { this._mixer._deactivateAction( this ); return this.reset(); } reset() { this.paused = false; this.enabled = true; this.time = 0; // restart clip this._loopCount = - 1;// forget previous loops this._startTime = null;// forget scheduling return this.stopFading().stopWarping(); } isRunning() { return this.enabled && ! this.paused && this.timeScale !== 0 && this._startTime === null && this._mixer._isActiveAction( this ); } // return true when play has been called isScheduled() { return this._mixer._isActiveAction( this ); } startAt( time ) { this._startTime = time; return this; } setLoop( mode, repetitions ) { this.loop = mode; this.repetitions = repetitions; return this; } // Weight // set the weight stopping any scheduled fading // although .enabled = false yields an effective weight of zero, this // method does *not* change .enabled, because it would be confusing setEffectiveWeight( weight ) { this.weight = weight; // note: same logic as when updated at runtime this._effectiveWeight = this.enabled ? weight : 0; return this.stopFading(); } // return the weight considering fading and .enabled getEffectiveWeight() { return this._effectiveWeight; } fadeIn( duration ) { return this._scheduleFading( duration, 0, 1 ); } fadeOut( duration ) { return this._scheduleFading( duration, 1, 0 ); } crossFadeFrom( fadeOutAction, duration, warp ) { fadeOutAction.fadeOut( duration ); this.fadeIn( duration ); if ( warp ) { const fadeInDuration = this._clip.duration, fadeOutDuration = fadeOutAction._clip.duration, startEndRatio = fadeOutDuration / fadeInDuration, endStartRatio = fadeInDuration / fadeOutDuration; fadeOutAction.warp( 1.0, startEndRatio, duration ); this.warp( endStartRatio, 1.0, duration ); } return this; } crossFadeTo( fadeInAction, duration, warp ) { return fadeInAction.crossFadeFrom( this, duration, warp ); } stopFading() { const weightInterpolant = this._weightInterpolant; if ( weightInterpolant !== null ) { this._weightInterpolant = null; this._mixer._takeBackControlInterpolant( weightInterpolant ); } return this; } // Time Scale Control // set the time scale stopping any scheduled warping // although .paused = true yields an effective time scale of zero, this // method does *not* change .paused, because it would be confusing setEffectiveTimeScale( timeScale ) { this.timeScale = timeScale; this._effectiveTimeScale = this.paused ? 0 : timeScale; return this.stopWarping(); } // return the time scale considering warping and .paused getEffectiveTimeScale() { return this._effectiveTimeScale; } setDuration( duration ) { this.timeScale = this._clip.duration / duration; return this.stopWarping(); } syncWith( action ) { this.time = action.time; this.timeScale = action.timeScale; return this.stopWarping(); } halt( duration ) { return this.warp( this._effectiveTimeScale, 0, duration ); } warp( startTimeScale, endTimeScale, duration ) { const mixer = this._mixer, now = mixer.time, timeScale = this.timeScale; let interpolant = this._timeScaleInterpolant; if ( interpolant === null ) { interpolant = mixer._lendControlInterpolant(); this._timeScaleInterpolant = interpolant; } const times = interpolant.parameterPositions, values = interpolant.sampleValues; times[ 0 ] = now; times[ 1 ] = now + duration; values[ 0 ] = startTimeScale / timeScale; values[ 1 ] = endTimeScale / timeScale; return this; } stopWarping() { const timeScaleInterpolant = this._timeScaleInterpolant; if ( timeScaleInterpolant !== null ) { this._timeScaleInterpolant = null; this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); } return this; } // Object Accessors getMixer() { return this._mixer; } getClip() { return this._clip; } getRoot() { return this._localRoot || this._mixer._root; } // Interna _update( time, deltaTime, timeDirection, accuIndex ) { // called by the mixer if ( ! this.enabled ) { // call ._updateWeight() to update ._effectiveWeight this._updateWeight( time ); return; } const startTime = this._startTime; if ( startTime !== null ) { // check for scheduled start of action const timeRunning = ( time - startTime ) * timeDirection; if ( timeRunning < 0 || timeDirection === 0 ) { deltaTime = 0; } else { this._startTime = null; // unschedule deltaTime = timeDirection * timeRunning; } } // apply time scale and advance time deltaTime *= this._updateTimeScale( time ); const clipTime = this._updateTime( deltaTime ); // note: _updateTime may disable the action resulting in // an effective weight of 0 const weight = this._updateWeight( time ); if ( weight > 0 ) { const interpolants = this._interpolants; const propertyMixers = this._propertyBindings; switch ( this.blendMode ) { case AdditiveAnimationBlendMode: for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { interpolants[ j ].evaluate( clipTime ); propertyMixers[ j ].accumulateAdditive( weight ); } break; case NormalAnimationBlendMode: default: for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { interpolants[ j ].evaluate( clipTime ); propertyMixers[ j ].accumulate( accuIndex, weight ); } } } } _updateWeight( time ) { let weight = 0; if ( this.enabled ) { weight = this.weight; const interpolant = this._weightInterpolant; if ( interpolant !== null ) { const interpolantValue = interpolant.evaluate( time )[ 0 ]; weight *= interpolantValue; if ( time > interpolant.parameterPositions[ 1 ] ) { this.stopFading(); if ( interpolantValue === 0 ) { // faded out, disable this.enabled = false; } } } } this._effectiveWeight = weight; return weight; } _updateTimeScale( time ) { let timeScale = 0; if ( ! this.paused ) { timeScale = this.timeScale; const interpolant = this._timeScaleInterpolant; if ( interpolant !== null ) { const interpolantValue = interpolant.evaluate( time )[ 0 ]; timeScale *= interpolantValue; if ( time > interpolant.parameterPositions[ 1 ] ) { this.stopWarping(); if ( timeScale === 0 ) { // motion has halted, pause this.paused = true; } else { // warp done - apply final time scale this.timeScale = timeScale; } } } } this._effectiveTimeScale = timeScale; return timeScale; } _updateTime( deltaTime ) { const duration = this._clip.duration; const loop = this.loop; let time = this.time + deltaTime; let loopCount = this._loopCount; const pingPong = ( loop === LoopPingPong ); if ( deltaTime === 0 ) { if ( loopCount === - 1 ) return time; return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; } if ( loop === LoopOnce ) { if ( loopCount === - 1 ) { // just started this._loopCount = 0; this._setEndings( true, true, false ); } handle_stop: { if ( time >= duration ) { time = duration; } else if ( time < 0 ) { time = 0; } else { this.time = time; break handle_stop; } if ( this.clampWhenFinished ) this.paused = true; else this.enabled = false; this.time = time; this._mixer.dispatchEvent( { type: 'finished', action: this, direction: deltaTime < 0 ? - 1 : 1 } ); } } else { // repetitive Repeat or PingPong if ( loopCount === - 1 ) { // just started if ( deltaTime >= 0 ) { loopCount = 0; this._setEndings( true, this.repetitions === 0, pingPong ); } else { // when looping in reverse direction, the initial // transition through zero counts as a repetition, // so leave loopCount at -1 this._setEndings( this.repetitions === 0, true, pingPong ); } } if ( time >= duration || time < 0 ) { // wrap around const loopDelta = Math.floor( time / duration ); // signed time -= duration * loopDelta; loopCount += Math.abs( loopDelta ); const pending = this.repetitions - loopCount; if ( pending <= 0 ) { // have to stop (switch state, clamp time, fire event) if ( this.clampWhenFinished ) this.paused = true; else this.enabled = false; time = deltaTime > 0 ? duration : 0; this.time = time; this._mixer.dispatchEvent( { type: 'finished', action: this, direction: deltaTime > 0 ? 1 : - 1 } ); } else { // keep running if ( pending === 1 ) { // entering the last round const atStart = deltaTime < 0; this._setEndings( atStart, ! atStart, pingPong ); } else { this._setEndings( false, false, pingPong ); } this._loopCount = loopCount; this.time = time; this._mixer.dispatchEvent( { type: 'loop', action: this, loopDelta: loopDelta } ); } } else { this.time = time; } if ( pingPong && ( loopCount & 1 ) === 1 ) { // invert time for the "pong round" return duration - time; } } return time; } _setEndings( atStart, atEnd, pingPong ) { const settings = this._interpolantSettings; if ( pingPong ) { settings.endingStart = ZeroSlopeEnding; settings.endingEnd = ZeroSlopeEnding; } else { // assuming for LoopOnce atStart == atEnd == true if ( atStart ) { settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; } else { settings.endingStart = WrapAroundEnding; } if ( atEnd ) { settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; } else { settings.endingEnd = WrapAroundEnding; } } } _scheduleFading( duration, weightNow, weightThen ) { const mixer = this._mixer, now = mixer.time; let interpolant = this._weightInterpolant; if ( interpolant === null ) { interpolant = mixer._lendControlInterpolant(); this._weightInterpolant = interpolant; } const times = interpolant.parameterPositions, values = interpolant.sampleValues; times[ 0 ] = now; values[ 0 ] = weightNow; times[ 1 ] = now + duration; values[ 1 ] = weightThen; return this; } } const _controlInterpolantsResultBuffer = new Float32Array( 1 ); class AnimationMixer extends EventDispatcher { constructor( root ) { super(); this._root = root; this._initMemoryManager(); this._accuIndex = 0; this.time = 0; this.timeScale = 1.0; } _bindAction( action, prototypeAction ) { const root = action._localRoot || this._root, tracks = action._clip.tracks, nTracks = tracks.length, bindings = action._propertyBindings, interpolants = action._interpolants, rootUuid = root.uuid, bindingsByRoot = this._bindingsByRootAndName; let bindingsByName = bindingsByRoot[ rootUuid ]; if ( bindingsByName === undefined ) { bindingsByName = {}; bindingsByRoot[ rootUuid ] = bindingsByName; } for ( let i = 0; i !== nTracks; ++ i ) { const track = tracks[ i ], trackName = track.name; let binding = bindingsByName[ trackName ]; if ( binding !== undefined ) { ++ binding.referenceCount; bindings[ i ] = binding; } else { binding = bindings[ i ]; if ( binding !== undefined ) { // existing binding, make sure the cache knows if ( binding._cacheIndex === null ) { ++ binding.referenceCount; this._addInactiveBinding( binding, rootUuid, trackName ); } continue; } const path = prototypeAction && prototypeAction. _propertyBindings[ i ].binding.parsedPath; binding = new PropertyMixer( PropertyBinding.create( root, trackName, path ), track.ValueTypeName, track.getValueSize() ); ++ binding.referenceCount; this._addInactiveBinding( binding, rootUuid, trackName ); bindings[ i ] = binding; } interpolants[ i ].resultBuffer = binding.buffer; } } _activateAction( action ) { if ( ! this._isActiveAction( action ) ) { if ( action._cacheIndex === null ) { // this action has been forgotten by the cache, but the user // appears to be still using it -> rebind const rootUuid = ( action._localRoot || this._root ).uuid, clipUuid = action._clip.uuid, actionsForClip = this._actionsByClip[ clipUuid ]; this._bindAction( action, actionsForClip && actionsForClip.knownActions[ 0 ] ); this._addInactiveAction( action, clipUuid, rootUuid ); } const bindings = action._propertyBindings; // increment reference counts / sort out state for ( let i = 0, n = bindings.length; i !== n; ++ i ) { const binding = bindings[ i ]; if ( binding.useCount ++ === 0 ) { this._lendBinding( binding ); binding.saveOriginalState(); } } this._lendAction( action ); } } _deactivateAction( action ) { if ( this._isActiveAction( action ) ) { const bindings = action._propertyBindings; // decrement reference counts / sort out state for ( let i = 0, n = bindings.length; i !== n; ++ i ) { const binding = bindings[ i ]; if ( -- binding.useCount === 0 ) { binding.restoreOriginalState(); this._takeBackBinding( binding ); } } this._takeBackAction( action ); } } // Memory manager _initMemoryManager() { this._actions = []; // 'nActiveActions' followed by inactive ones this._nActiveActions = 0; this._actionsByClip = {}; // inside: // { // knownActions: Array< AnimationAction > - used as prototypes // actionByRoot: AnimationAction - lookup // } this._bindings = []; // 'nActiveBindings' followed by inactive ones this._nActiveBindings = 0; this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > this._controlInterpolants = []; // same game as above this._nActiveControlInterpolants = 0; const scope = this; this.stats = { actions: { get total() { return scope._actions.length; }, get inUse() { return scope._nActiveActions; } }, bindings: { get total() { return scope._bindings.length; }, get inUse() { return scope._nActiveBindings; } }, controlInterpolants: { get total() { return scope._controlInterpolants.length; }, get inUse() { return scope._nActiveControlInterpolants; } } }; } // Memory management for AnimationAction objects _isActiveAction( action ) { const index = action._cacheIndex; return index !== null && index < this._nActiveActions; } _addInactiveAction( action, clipUuid, rootUuid ) { const actions = this._actions, actionsByClip = this._actionsByClip; let actionsForClip = actionsByClip[ clipUuid ]; if ( actionsForClip === undefined ) { actionsForClip = { knownActions: [ action ], actionByRoot: {} }; action._byClipCacheIndex = 0; actionsByClip[ clipUuid ] = actionsForClip; } else { const knownActions = actionsForClip.knownActions; action._byClipCacheIndex = knownActions.length; knownActions.push( action ); } action._cacheIndex = actions.length; actions.push( action ); actionsForClip.actionByRoot[ rootUuid ] = action; } _removeInactiveAction( action ) { const actions = this._actions, lastInactiveAction = actions[ actions.length - 1 ], cacheIndex = action._cacheIndex; lastInactiveAction._cacheIndex = cacheIndex; actions[ cacheIndex ] = lastInactiveAction; actions.pop(); action._cacheIndex = null; const clipUuid = action._clip.uuid, actionsByClip = this._actionsByClip, actionsForClip = actionsByClip[ clipUuid ], knownActionsForClip = actionsForClip.knownActions, lastKnownAction = knownActionsForClip[ knownActionsForClip.length - 1 ], byClipCacheIndex = action._byClipCacheIndex; lastKnownAction._byClipCacheIndex = byClipCacheIndex; knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; knownActionsForClip.pop(); action._byClipCacheIndex = null; const actionByRoot = actionsForClip.actionByRoot, rootUuid = ( action._localRoot || this._root ).uuid; delete actionByRoot[ rootUuid ]; if ( knownActionsForClip.length === 0 ) { delete actionsByClip[ clipUuid ]; } this._removeInactiveBindingsForAction( action ); } _removeInactiveBindingsForAction( action ) { const bindings = action._propertyBindings; for ( let i = 0, n = bindings.length; i !== n; ++ i ) { const binding = bindings[ i ]; if ( -- binding.referenceCount === 0 ) { this._removeInactiveBinding( binding ); } } } _lendAction( action ) { // [ active actions | inactive actions ] // [ active actions >| inactive actions ] // s a // <-swap-> // a s const actions = this._actions, prevIndex = action._cacheIndex, lastActiveIndex = this._nActiveActions ++, firstInactiveAction = actions[ lastActiveIndex ]; action._cacheIndex = lastActiveIndex; actions[ lastActiveIndex ] = action; firstInactiveAction._cacheIndex = prevIndex; actions[ prevIndex ] = firstInactiveAction; } _takeBackAction( action ) { // [ active actions | inactive actions ] // [ active actions |< inactive actions ] // a s // <-swap-> // s a const actions = this._actions, prevIndex = action._cacheIndex, firstInactiveIndex = -- this._nActiveActions, lastActiveAction = actions[ firstInactiveIndex ]; action._cacheIndex = firstInactiveIndex; actions[ firstInactiveIndex ] = action; lastActiveAction._cacheIndex = prevIndex; actions[ prevIndex ] = lastActiveAction; } // Memory management for PropertyMixer objects _addInactiveBinding( binding, rootUuid, trackName ) { const bindingsByRoot = this._bindingsByRootAndName, bindings = this._bindings; let bindingByName = bindingsByRoot[ rootUuid ]; if ( bindingByName === undefined ) { bindingByName = {}; bindingsByRoot[ rootUuid ] = bindingByName; } bindingByName[ trackName ] = binding; binding._cacheIndex = bindings.length; bindings.push( binding ); } _removeInactiveBinding( binding ) { const bindings = this._bindings, propBinding = binding.binding, rootUuid = propBinding.rootNode.uuid, trackName = propBinding.path, bindingsByRoot = this._bindingsByRootAndName, bindingByName = bindingsByRoot[ rootUuid ], lastInactiveBinding = bindings[ bindings.length - 1 ], cacheIndex = binding._cacheIndex; lastInactiveBinding._cacheIndex = cacheIndex; bindings[ cacheIndex ] = lastInactiveBinding; bindings.pop(); delete bindingByName[ trackName ]; if ( Object.keys( bindingByName ).length === 0 ) { delete bindingsByRoot[ rootUuid ]; } } _lendBinding( binding ) { const bindings = this._bindings, prevIndex = binding._cacheIndex, lastActiveIndex = this._nActiveBindings ++, firstInactiveBinding = bindings[ lastActiveIndex ]; binding._cacheIndex = lastActiveIndex; bindings[ lastActiveIndex ] = binding; firstInactiveBinding._cacheIndex = prevIndex; bindings[ prevIndex ] = firstInactiveBinding; } _takeBackBinding( binding ) { const bindings = this._bindings, prevIndex = binding._cacheIndex, firstInactiveIndex = -- this._nActiveBindings, lastActiveBinding = bindings[ firstInactiveIndex ]; binding._cacheIndex = firstInactiveIndex; bindings[ firstInactiveIndex ] = binding; lastActiveBinding._cacheIndex = prevIndex; bindings[ prevIndex ] = lastActiveBinding; } // Memory management of Interpolants for weight and time scale _lendControlInterpolant() { const interpolants = this._controlInterpolants, lastActiveIndex = this._nActiveControlInterpolants ++; let interpolant = interpolants[ lastActiveIndex ]; if ( interpolant === undefined ) { interpolant = new LinearInterpolant( new Float32Array( 2 ), new Float32Array( 2 ), 1, _controlInterpolantsResultBuffer ); interpolant.__cacheIndex = lastActiveIndex; interpolants[ lastActiveIndex ] = interpolant; } return interpolant; } _takeBackControlInterpolant( interpolant ) { const interpolants = this._controlInterpolants, prevIndex = interpolant.__cacheIndex, firstInactiveIndex = -- this._nActiveControlInterpolants, lastActiveInterpolant = interpolants[ firstInactiveIndex ]; interpolant.__cacheIndex = firstInactiveIndex; interpolants[ firstInactiveIndex ] = interpolant; lastActiveInterpolant.__cacheIndex = prevIndex; interpolants[ prevIndex ] = lastActiveInterpolant; } // return an action for a clip optionally using a custom root target // object (this method allocates a lot of dynamic memory in case a // previously unknown clip/root combination is specified) clipAction( clip, optionalRoot, blendMode ) { const root = optionalRoot || this._root, rootUuid = root.uuid; let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; const clipUuid = clipObject !== null ? clipObject.uuid : clip; const actionsForClip = this._actionsByClip[ clipUuid ]; let prototypeAction = null; if ( blendMode === undefined ) { if ( clipObject !== null ) { blendMode = clipObject.blendMode; } else { blendMode = NormalAnimationBlendMode; } } if ( actionsForClip !== undefined ) { const existingAction = actionsForClip.actionByRoot[ rootUuid ]; if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { return existingAction; } // we know the clip, so we don't have to parse all // the bindings again but can just copy prototypeAction = actionsForClip.knownActions[ 0 ]; // also, take the clip from the prototype action if ( clipObject === null ) clipObject = prototypeAction._clip; } // clip must be known when specified via string if ( clipObject === null ) return null; // allocate all resources required to run it const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); this._bindAction( newAction, prototypeAction ); // and make the action known to the memory manager this._addInactiveAction( newAction, clipUuid, rootUuid ); return newAction; } // get an existing action existingAction( clip, optionalRoot ) { const root = optionalRoot || this._root, rootUuid = root.uuid, clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip, clipUuid = clipObject ? clipObject.uuid : clip, actionsForClip = this._actionsByClip[ clipUuid ]; if ( actionsForClip !== undefined ) { return actionsForClip.actionByRoot[ rootUuid ] || null; } return null; } // deactivates all previously scheduled actions stopAllAction() { const actions = this._actions, nActions = this._nActiveActions; for ( let i = nActions - 1; i >= 0; -- i ) { actions[ i ].stop(); } return this; } // advance the time and update apply the animation update( deltaTime ) { deltaTime *= this.timeScale; const actions = this._actions, nActions = this._nActiveActions, time = this.time += deltaTime, timeDirection = Math.sign( deltaTime ), accuIndex = this._accuIndex ^= 1; // run active actions for ( let i = 0; i !== nActions; ++ i ) { const action = actions[ i ]; action._update( time, deltaTime, timeDirection, accuIndex ); } // update scene graph const bindings = this._bindings, nBindings = this._nActiveBindings; for ( let i = 0; i !== nBindings; ++ i ) { bindings[ i ].apply( accuIndex ); } return this; } // Allows you to seek to a specific time in an animation. setTime( timeInSeconds ) { this.time = 0; // Zero out time attribute for AnimationMixer object; for ( let i = 0; i < this._actions.length; i ++ ) { this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. } return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object. } // return this mixer's root target object getRoot() { return this._root; } // free all resources specific to a particular clip uncacheClip( clip ) { const actions = this._actions, clipUuid = clip.uuid, actionsByClip = this._actionsByClip, actionsForClip = actionsByClip[ clipUuid ]; if ( actionsForClip !== undefined ) { // note: just calling _removeInactiveAction would mess up the // iteration state and also require updating the state we can // just throw away const actionsToRemove = actionsForClip.knownActions; for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { const action = actionsToRemove[ i ]; this._deactivateAction( action ); const cacheIndex = action._cacheIndex, lastInactiveAction = actions[ actions.length - 1 ]; action._cacheIndex = null; action._byClipCacheIndex = null; lastInactiveAction._cacheIndex = cacheIndex; actions[ cacheIndex ] = lastInactiveAction; actions.pop(); this._removeInactiveBindingsForAction( action ); } delete actionsByClip[ clipUuid ]; } } // free all resources specific to a particular root target object uncacheRoot( root ) { const rootUuid = root.uuid, actionsByClip = this._actionsByClip; for ( const clipUuid in actionsByClip ) { const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, action = actionByRoot[ rootUuid ]; if ( action !== undefined ) { this._deactivateAction( action ); this._removeInactiveAction( action ); } } const bindingsByRoot = this._bindingsByRootAndName, bindingByName = bindingsByRoot[ rootUuid ]; if ( bindingByName !== undefined ) { for ( const trackName in bindingByName ) { const binding = bindingByName[ trackName ]; binding.restoreOriginalState(); this._removeInactiveBinding( binding ); } } } // remove a targeted clip from the cache uncacheAction( clip, optionalRoot ) { const action = this.existingAction( clip, optionalRoot ); if ( action !== null ) { this._deactivateAction( action ); this._removeInactiveAction( action ); } } } class Uniform { constructor( value ) { this.value = value; } clone() { return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); } } let _id = 0; class UniformsGroup extends EventDispatcher { constructor() { super(); this.isUniformsGroup = true; Object.defineProperty( this, 'id', { value: _id ++ } ); this.name = ''; this.usage = StaticDrawUsage; this.uniforms = []; } add( uniform ) { this.uniforms.push( uniform ); return this; } remove( uniform ) { const index = this.uniforms.indexOf( uniform ); if ( index !== - 1 ) this.uniforms.splice( index, 1 ); return this; } setName( name ) { this.name = name; return this; } setUsage( value ) { this.usage = value; return this; } dispose() { this.dispatchEvent( { type: 'dispose' } ); return this; } copy( source ) { this.name = source.name; this.usage = source.usage; const uniformsSource = source.uniforms; this.uniforms.length = 0; for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { const uniforms = Array.isArray( uniformsSource[ i ] ) ? uniformsSource[ i ] : [ uniformsSource[ i ] ]; for ( let j = 0; j < uniforms.length; j ++ ) { this.uniforms.push( uniforms[ j ].clone() ); } } return this; } clone() { return new this.constructor().copy( this ); } } class InstancedInterleavedBuffer extends InterleavedBuffer { constructor( array, stride, meshPerAttribute = 1 ) { super( array, stride ); this.isInstancedInterleavedBuffer = true; this.meshPerAttribute = meshPerAttribute; } copy( source ) { super.copy( source ); this.meshPerAttribute = source.meshPerAttribute; return this; } clone( data ) { const ib = super.clone( data ); ib.meshPerAttribute = this.meshPerAttribute; return ib; } toJSON( data ) { const json = super.toJSON( data ); json.isInstancedInterleavedBuffer = true; json.meshPerAttribute = this.meshPerAttribute; return json; } } class GLBufferAttribute { constructor( buffer, type, itemSize, elementSize, count ) { this.isGLBufferAttribute = true; this.name = ''; this.buffer = buffer; this.type = type; this.itemSize = itemSize; this.elementSize = elementSize; this.count = count; this.version = 0; } set needsUpdate( value ) { if ( value === true ) this.version ++; } setBuffer( buffer ) { this.buffer = buffer; return this; } setType( type, elementSize ) { this.type = type; this.elementSize = elementSize; return this; } setItemSize( itemSize ) { this.itemSize = itemSize; return this; } setCount( count ) { this.count = count; return this; } } class Raycaster { constructor( origin, direction, near = 0, far = Infinity ) { this.ray = new Ray( origin, direction ); // direction is assumed to be normalized (for accurate distance calculations) this.near = near; this.far = far; this.camera = null; this.layers = new Layers(); this.params = { Mesh: {}, Line: { threshold: 1 }, LOD: {}, Points: { threshold: 1 }, Sprite: {} }; } set( origin, direction ) { // direction is assumed to be normalized (for accurate distance calculations) this.ray.set( origin, direction ); } setFromCamera( coords, camera ) { if ( camera.isPerspectiveCamera ) { this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); this.camera = camera; } else if ( camera.isOrthographicCamera ) { this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); this.camera = camera; } else { console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); } } intersectObject( object, recursive = true, intersects = [] ) { intersectObject( object, this, intersects, recursive ); intersects.sort( ascSort ); return intersects; } intersectObjects( objects, recursive = true, intersects = [] ) { for ( let i = 0, l = objects.length; i < l; i ++ ) { intersectObject( objects[ i ], this, intersects, recursive ); } intersects.sort( ascSort ); return intersects; } } function ascSort( a, b ) { return a.distance - b.distance; } function intersectObject( object, raycaster, intersects, recursive ) { if ( object.layers.test( raycaster.layers ) ) { object.raycast( raycaster, intersects ); } if ( recursive === true ) { const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { intersectObject( children[ i ], raycaster, intersects, true ); } } } /** * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system * * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. * The azimuthal angle (theta) is measured from the positive z-axis. */ class Spherical { constructor( radius = 1, phi = 0, theta = 0 ) { this.radius = radius; this.phi = phi; // polar angle this.theta = theta; // azimuthal angle return this; } set( radius, phi, theta ) { this.radius = radius; this.phi = phi; this.theta = theta; return this; } copy( other ) { this.radius = other.radius; this.phi = other.phi; this.theta = other.theta; return this; } // restrict phi to be between EPS and PI-EPS makeSafe() { const EPS = 0.000001; this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); return this; } setFromVector3( v ) { return this.setFromCartesianCoords( v.x, v.y, v.z ); } setFromCartesianCoords( x, y, z ) { this.radius = Math.sqrt( x * x + y * y + z * z ); if ( this.radius === 0 ) { this.theta = 0; this.phi = 0; } else { this.theta = Math.atan2( x, z ); this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); } return this; } clone() { return new this.constructor().copy( this ); } } /** * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system */ class Cylindrical { constructor( radius = 1, theta = 0, y = 0 ) { this.radius = radius; // distance from the origin to a point in the x-z plane this.theta = theta; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis this.y = y; // height above the x-z plane return this; } set( radius, theta, y ) { this.radius = radius; this.theta = theta; this.y = y; return this; } copy( other ) { this.radius = other.radius; this.theta = other.theta; this.y = other.y; return this; } setFromVector3( v ) { return this.setFromCartesianCoords( v.x, v.y, v.z ); } setFromCartesianCoords( x, y, z ) { this.radius = Math.sqrt( x * x + z * z ); this.theta = Math.atan2( x, z ); this.y = y; return this; } clone() { return new this.constructor().copy( this ); } } const _vector$4 = /*@__PURE__*/ new Vector2(); class Box2 { constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { this.isBox2 = true; this.min = min; this.max = max; } set( min, max ) { this.min.copy( min ); this.max.copy( max ); return this; } setFromPoints( points ) { this.makeEmpty(); for ( let i = 0, il = points.length; i < il; i ++ ) { this.expandByPoint( points[ i ] ); } return this; } setFromCenterAndSize( center, size ) { const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); this.min.copy( center ).sub( halfSize ); this.max.copy( center ).add( halfSize ); return this; } clone() { return new this.constructor().copy( this ); } copy( box ) { this.min.copy( box.min ); this.max.copy( box.max ); return this; } makeEmpty() { this.min.x = this.min.y = + Infinity; this.max.x = this.max.y = - Infinity; return this; } isEmpty() { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); } getCenter( target ) { return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } getSize( target ) { return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); } expandByPoint( point ) { this.min.min( point ); this.max.max( point ); return this; } expandByVector( vector ) { this.min.sub( vector ); this.max.add( vector ); return this; } expandByScalar( scalar ) { this.min.addScalar( - scalar ); this.max.addScalar( scalar ); return this; } containsPoint( point ) { return point.x < this.min.x || point.x > this.max.x || point.y < this.min.y || point.y > this.max.y ? false : true; } containsBox( box ) { return this.min.x <= box.min.x && box.max.x <= this.max.x && this.min.y <= box.min.y && box.max.y <= this.max.y; } getParameter( point, target ) { // This can potentially have a divide by zero if the box // has a size dimension of 0. return target.set( ( point.x - this.min.x ) / ( this.max.x - this.min.x ), ( point.y - this.min.y ) / ( this.max.y - this.min.y ) ); } intersectsBox( box ) { // using 4 splitting planes to rule out intersections return box.max.x < this.min.x || box.min.x > this.max.x || box.max.y < this.min.y || box.min.y > this.max.y ? false : true; } clampPoint( point, target ) { return target.copy( point ).clamp( this.min, this.max ); } distanceToPoint( point ) { return this.clampPoint( point, _vector$4 ).distanceTo( point ); } intersect( box ) { this.min.max( box.min ); this.max.min( box.max ); if ( this.isEmpty() ) this.makeEmpty(); return this; } union( box ) { this.min.min( box.min ); this.max.max( box.max ); return this; } translate( offset ) { this.min.add( offset ); this.max.add( offset ); return this; } equals( box ) { return box.min.equals( this.min ) && box.max.equals( this.max ); } } const _startP = /*@__PURE__*/ new Vector3(); const _startEnd = /*@__PURE__*/ new Vector3(); class Line3 { constructor( start = new Vector3(), end = new Vector3() ) { this.start = start; this.end = end; } set( start, end ) { this.start.copy( start ); this.end.copy( end ); return this; } copy( line ) { this.start.copy( line.start ); this.end.copy( line.end ); return this; } getCenter( target ) { return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); } delta( target ) { return target.subVectors( this.end, this.start ); } distanceSq() { return this.start.distanceToSquared( this.end ); } distance() { return this.start.distanceTo( this.end ); } at( t, target ) { return this.delta( target ).multiplyScalar( t ).add( this.start ); } closestPointToPointParameter( point, clampToLine ) { _startP.subVectors( point, this.start ); _startEnd.subVectors( this.end, this.start ); const startEnd2 = _startEnd.dot( _startEnd ); const startEnd_startP = _startEnd.dot( _startP ); let t = startEnd_startP / startEnd2; if ( clampToLine ) { t = clamp( t, 0, 1 ); } return t; } closestPointToPoint( point, clampToLine, target ) { const t = this.closestPointToPointParameter( point, clampToLine ); return this.delta( target ).multiplyScalar( t ).add( this.start ); } applyMatrix4( matrix ) { this.start.applyMatrix4( matrix ); this.end.applyMatrix4( matrix ); return this; } equals( line ) { return line.start.equals( this.start ) && line.end.equals( this.end ); } clone() { return new this.constructor().copy( this ); } } const _vector$3 = /*@__PURE__*/ new Vector3(); class SpotLightHelper extends Object3D { constructor( light, color ) { super(); this.light = light; this.matrix = light.matrixWorld; this.matrixAutoUpdate = false; this.color = color; this.type = 'SpotLightHelper'; const geometry = new BufferGeometry(); const positions = [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, - 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 1, 1 ]; for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { const p1 = ( i / l ) * Math.PI * 2; const p2 = ( j / l ) * Math.PI * 2; positions.push( Math.cos( p1 ), Math.sin( p1 ), 1, Math.cos( p2 ), Math.sin( p2 ), 1 ); } geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); this.cone = new LineSegments( geometry, material ); this.add( this.cone ); this.update(); } dispose() { this.cone.geometry.dispose(); this.cone.material.dispose(); } update() { this.light.updateWorldMatrix( true, false ); this.light.target.updateWorldMatrix( true, false ); const coneLength = this.light.distance ? this.light.distance : 1000; const coneWidth = coneLength * Math.tan( this.light.angle ); this.cone.scale.set( coneWidth, coneWidth, coneLength ); _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); this.cone.lookAt( _vector$3 ); if ( this.color !== undefined ) { this.cone.material.color.set( this.color ); } else { this.cone.material.color.copy( this.light.color ); } } } const _vector$2 = /*@__PURE__*/ new Vector3(); const _boneMatrix = /*@__PURE__*/ new Matrix4(); const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); class SkeletonHelper extends LineSegments { constructor( object ) { const bones = getBoneList( object ); const geometry = new BufferGeometry(); const vertices = []; const colors = []; const color1 = new Color( 0, 0, 1 ); const color2 = new Color( 0, 1, 0 ); for ( let i = 0; i < bones.length; i ++ ) { const bone = bones[ i ]; if ( bone.parent && bone.parent.isBone ) { vertices.push( 0, 0, 0 ); vertices.push( 0, 0, 0 ); colors.push( color1.r, color1.g, color1.b ); colors.push( color2.r, color2.g, color2.b ); } } geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); super( geometry, material ); this.isSkeletonHelper = true; this.type = 'SkeletonHelper'; this.root = object; this.bones = bones; this.matrix = object.matrixWorld; this.matrixAutoUpdate = false; } updateMatrixWorld( force ) { const bones = this.bones; const geometry = this.geometry; const position = geometry.getAttribute( 'position' ); _matrixWorldInv.copy( this.root.matrixWorld ).invert(); for ( let i = 0, j = 0; i < bones.length; i ++ ) { const bone = bones[ i ]; if ( bone.parent && bone.parent.isBone ) { _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); _vector$2.setFromMatrixPosition( _boneMatrix ); position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); _vector$2.setFromMatrixPosition( _boneMatrix ); position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); j += 2; } } geometry.getAttribute( 'position' ).needsUpdate = true; super.updateMatrixWorld( force ); } dispose() { this.geometry.dispose(); this.material.dispose(); } } function getBoneList( object ) { const boneList = []; if ( object.isBone === true ) { boneList.push( object ); } for ( let i = 0; i < object.children.length; i ++ ) { boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); } return boneList; } class PointLightHelper extends Mesh { constructor( light, sphereSize, color ) { const geometry = new SphereGeometry( sphereSize, 4, 2 ); const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); super( geometry, material ); this.light = light; this.color = color; this.type = 'PointLightHelper'; this.matrix = this.light.matrixWorld; this.matrixAutoUpdate = false; this.update(); /* // TODO: delete this comment? const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); const d = light.distance; if ( d === 0.0 ) { this.lightDistance.visible = false; } else { this.lightDistance.scale.set( d, d, d ); } this.add( this.lightDistance ); */ } dispose() { this.geometry.dispose(); this.material.dispose(); } update() { this.light.updateWorldMatrix( true, false ); if ( this.color !== undefined ) { this.material.color.set( this.color ); } else { this.material.color.copy( this.light.color ); } /* const d = this.light.distance; if ( d === 0.0 ) { this.lightDistance.visible = false; } else { this.lightDistance.visible = true; this.lightDistance.scale.set( d, d, d ); } */ } } const _vector$1 = /*@__PURE__*/ new Vector3(); const _color1 = /*@__PURE__*/ new Color(); const _color2 = /*@__PURE__*/ new Color(); class HemisphereLightHelper extends Object3D { constructor( light, size, color ) { super(); this.light = light; this.matrix = light.matrixWorld; this.matrixAutoUpdate = false; this.color = color; this.type = 'HemisphereLightHelper'; const geometry = new OctahedronGeometry( size ); geometry.rotateY( Math.PI * 0.5 ); this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); if ( this.color === undefined ) this.material.vertexColors = true; const position = geometry.getAttribute( 'position' ); const colors = new Float32Array( position.count * 3 ); geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); this.add( new Mesh( geometry, this.material ) ); this.update(); } dispose() { this.children[ 0 ].geometry.dispose(); this.children[ 0 ].material.dispose(); } update() { const mesh = this.children[ 0 ]; if ( this.color !== undefined ) { this.material.color.set( this.color ); } else { const colors = mesh.geometry.getAttribute( 'color' ); _color1.copy( this.light.color ); _color2.copy( this.light.groundColor ); for ( let i = 0, l = colors.count; i < l; i ++ ) { const color = ( i < ( l / 2 ) ) ? _color1 : _color2; colors.setXYZ( i, color.r, color.g, color.b ); } colors.needsUpdate = true; } this.light.updateWorldMatrix( true, false ); mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); } } class GridHelper extends LineSegments { constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { color1 = new Color( color1 ); color2 = new Color( color2 ); const center = divisions / 2; const step = size / divisions; const halfSize = size / 2; const vertices = [], colors = []; for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { vertices.push( - halfSize, 0, k, halfSize, 0, k ); vertices.push( k, 0, - halfSize, k, 0, halfSize ); const color = i === center ? color1 : color2; color.toArray( colors, j ); j += 3; color.toArray( colors, j ); j += 3; color.toArray( colors, j ); j += 3; color.toArray( colors, j ); j += 3; } const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); super( geometry, material ); this.type = 'GridHelper'; } dispose() { this.geometry.dispose(); this.material.dispose(); } } class PolarGridHelper extends LineSegments { constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { color1 = new Color( color1 ); color2 = new Color( color2 ); const vertices = []; const colors = []; // create the sectors if ( sectors > 1 ) { for ( let i = 0; i < sectors; i ++ ) { const v = ( i / sectors ) * ( Math.PI * 2 ); const x = Math.sin( v ) * radius; const z = Math.cos( v ) * radius; vertices.push( 0, 0, 0 ); vertices.push( x, 0, z ); const color = ( i & 1 ) ? color1 : color2; colors.push( color.r, color.g, color.b ); colors.push( color.r, color.g, color.b ); } } // create the rings for ( let i = 0; i < rings; i ++ ) { const color = ( i & 1 ) ? color1 : color2; const r = radius - ( radius / rings * i ); for ( let j = 0; j < divisions; j ++ ) { // first vertex let v = ( j / divisions ) * ( Math.PI * 2 ); let x = Math.sin( v ) * r; let z = Math.cos( v ) * r; vertices.push( x, 0, z ); colors.push( color.r, color.g, color.b ); // second vertex v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); x = Math.sin( v ) * r; z = Math.cos( v ) * r; vertices.push( x, 0, z ); colors.push( color.r, color.g, color.b ); } } const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); super( geometry, material ); this.type = 'PolarGridHelper'; } dispose() { this.geometry.dispose(); this.material.dispose(); } } const _v1 = /*@__PURE__*/ new Vector3(); const _v2 = /*@__PURE__*/ new Vector3(); const _v3 = /*@__PURE__*/ new Vector3(); class DirectionalLightHelper extends Object3D { constructor( light, size, color ) { super(); this.light = light; this.matrix = light.matrixWorld; this.matrixAutoUpdate = false; this.color = color; this.type = 'DirectionalLightHelper'; if ( size === undefined ) size = 1; let geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( [ - size, size, 0, size, size, 0, size, - size, 0, - size, - size, 0, - size, size, 0 ], 3 ) ); const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); this.lightPlane = new Line( geometry, material ); this.add( this.lightPlane ); geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); this.targetLine = new Line( geometry, material ); this.add( this.targetLine ); this.update(); } dispose() { this.lightPlane.geometry.dispose(); this.lightPlane.material.dispose(); this.targetLine.geometry.dispose(); this.targetLine.material.dispose(); } update() { this.light.updateWorldMatrix( true, false ); this.light.target.updateWorldMatrix( true, false ); _v1.setFromMatrixPosition( this.light.matrixWorld ); _v2.setFromMatrixPosition( this.light.target.matrixWorld ); _v3.subVectors( _v2, _v1 ); this.lightPlane.lookAt( _v2 ); if ( this.color !== undefined ) { this.lightPlane.material.color.set( this.color ); this.targetLine.material.color.set( this.color ); } else { this.lightPlane.material.color.copy( this.light.color ); this.targetLine.material.color.copy( this.light.color ); } this.targetLine.lookAt( _v2 ); this.targetLine.scale.z = _v3.length(); } } const _vector = /*@__PURE__*/ new Vector3(); const _camera = /*@__PURE__*/ new Camera(); /** * - shows frustum, line of sight and up of the camera * - suitable for fast updates * - based on frustum visualization in lightgl.js shadowmap example * https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html */ class CameraHelper extends LineSegments { constructor( camera ) { const geometry = new BufferGeometry(); const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); const vertices = []; const colors = []; const pointMap = {}; // near addLine( 'n1', 'n2' ); addLine( 'n2', 'n4' ); addLine( 'n4', 'n3' ); addLine( 'n3', 'n1' ); // far addLine( 'f1', 'f2' ); addLine( 'f2', 'f4' ); addLine( 'f4', 'f3' ); addLine( 'f3', 'f1' ); // sides addLine( 'n1', 'f1' ); addLine( 'n2', 'f2' ); addLine( 'n3', 'f3' ); addLine( 'n4', 'f4' ); // cone addLine( 'p', 'n1' ); addLine( 'p', 'n2' ); addLine( 'p', 'n3' ); addLine( 'p', 'n4' ); // up addLine( 'u1', 'u2' ); addLine( 'u2', 'u3' ); addLine( 'u3', 'u1' ); // target addLine( 'c', 't' ); addLine( 'p', 'c' ); // cross addLine( 'cn1', 'cn2' ); addLine( 'cn3', 'cn4' ); addLine( 'cf1', 'cf2' ); addLine( 'cf3', 'cf4' ); function addLine( a, b ) { addPoint( a ); addPoint( b ); } function addPoint( id ) { vertices.push( 0, 0, 0 ); colors.push( 0, 0, 0 ); if ( pointMap[ id ] === undefined ) { pointMap[ id ] = []; } pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); } geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); super( geometry, material ); this.type = 'CameraHelper'; this.camera = camera; if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); this.matrix = camera.matrixWorld; this.matrixAutoUpdate = false; this.pointMap = pointMap; this.update(); // colors const colorFrustum = new Color( 0xffaa00 ); const colorCone = new Color( 0xff0000 ); const colorUp = new Color( 0x00aaff ); const colorTarget = new Color( 0xffffff ); const colorCross = new Color( 0x333333 ); this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); } setColors( frustum, cone, up, target, cross ) { const geometry = this.geometry; const colorAttribute = geometry.getAttribute( 'color' ); // near colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 // far colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 // sides colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 // cone colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 // up colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 // target colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c // cross colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 colorAttribute.needsUpdate = true; } update() { const geometry = this.geometry; const pointMap = this.pointMap; const w = 1, h = 1; // we need just camera projection matrix inverse // world matrix must be identity _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); // center / target setPoint( 'c', pointMap, geometry, _camera, 0, 0, - 1 ); setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); // near setPoint( 'n1', pointMap, geometry, _camera, - w, - h, - 1 ); setPoint( 'n2', pointMap, geometry, _camera, w, - h, - 1 ); setPoint( 'n3', pointMap, geometry, _camera, - w, h, - 1 ); setPoint( 'n4', pointMap, geometry, _camera, w, h, - 1 ); // far setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); // up setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, - 1 ); setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, - 1 ); setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, - 1 ); // cross setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, - 1 ); setPoint( 'cn2', pointMap, geometry, _camera, w, 0, - 1 ); setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, - 1 ); setPoint( 'cn4', pointMap, geometry, _camera, 0, h, - 1 ); geometry.getAttribute( 'position' ).needsUpdate = true; } dispose() { this.geometry.dispose(); this.material.dispose(); } } function setPoint( point, pointMap, geometry, camera, x, y, z ) { _vector.set( x, y, z ).unproject( camera ); const points = pointMap[ point ]; if ( points !== undefined ) { const position = geometry.getAttribute( 'position' ); for ( let i = 0, l = points.length; i < l; i ++ ) { position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); } } } const _box = /*@__PURE__*/ new Box3(); class BoxHelper extends LineSegments { constructor( object, color = 0xffff00 ) { const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); const positions = new Float32Array( 8 * 3 ); const geometry = new BufferGeometry(); geometry.setIndex( new BufferAttribute( indices, 1 ) ); geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); this.object = object; this.type = 'BoxHelper'; this.matrixAutoUpdate = false; this.update(); } update( object ) { if ( object !== undefined ) { console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); } if ( this.object !== undefined ) { _box.setFromObject( this.object ); } if ( _box.isEmpty() ) return; const min = _box.min; const max = _box.max; /* 5____4 1/___0/| | 6__|_7 2/___3/ 0: max.x, max.y, max.z 1: min.x, max.y, max.z 2: min.x, min.y, max.z 3: max.x, min.y, max.z 4: max.x, max.y, min.z 5: min.x, max.y, min.z 6: min.x, min.y, min.z 7: max.x, min.y, min.z */ const position = this.geometry.attributes.position; const array = position.array; array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; position.needsUpdate = true; this.geometry.computeBoundingSphere(); } setFromObject( object ) { this.object = object; this.update(); return this; } copy( source, recursive ) { super.copy( source, recursive ); this.object = source.object; return this; } dispose() { this.geometry.dispose(); this.material.dispose(); } } class Box3Helper extends LineSegments { constructor( box, color = 0xffff00 ) { const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; const geometry = new BufferGeometry(); geometry.setIndex( new BufferAttribute( indices, 1 ) ); geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); this.box = box; this.type = 'Box3Helper'; this.geometry.computeBoundingSphere(); } updateMatrixWorld( force ) { const box = this.box; if ( box.isEmpty() ) return; box.getCenter( this.position ); box.getSize( this.scale ); this.scale.multiplyScalar( 0.5 ); super.updateMatrixWorld( force ); } dispose() { this.geometry.dispose(); this.material.dispose(); } } class PlaneHelper extends Line { constructor( plane, size = 1, hex = 0xffff00 ) { const color = hex; const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); geometry.computeBoundingSphere(); super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); this.type = 'PlaneHelper'; this.plane = plane; this.size = size; const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; const geometry2 = new BufferGeometry(); geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); geometry2.computeBoundingSphere(); this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); } updateMatrixWorld( force ) { this.position.set( 0, 0, 0 ); this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); this.lookAt( this.plane.normal ); this.translateZ( - this.plane.constant ); super.updateMatrixWorld( force ); } dispose() { this.geometry.dispose(); this.material.dispose(); this.children[ 0 ].geometry.dispose(); this.children[ 0 ].material.dispose(); } } const _axis = /*@__PURE__*/ new Vector3(); let _lineGeometry, _coneGeometry; class ArrowHelper extends Object3D { // dir is assumed to be normalized constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { super(); this.type = 'ArrowHelper'; if ( _lineGeometry === undefined ) { _lineGeometry = new BufferGeometry(); _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); _coneGeometry = new CylinderGeometry( 0, 0.5, 1, 5, 1 ); _coneGeometry.translate( 0, - 0.5, 0 ); } this.position.copy( origin ); this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); this.line.matrixAutoUpdate = false; this.add( this.line ); this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); this.cone.matrixAutoUpdate = false; this.add( this.cone ); this.setDirection( dir ); this.setLength( length, headLength, headWidth ); } setDirection( dir ) { // dir is assumed to be normalized if ( dir.y > 0.99999 ) { this.quaternion.set( 0, 0, 0, 1 ); } else if ( dir.y < - 0.99999 ) { this.quaternion.set( 1, 0, 0, 0 ); } else { _axis.set( dir.z, 0, - dir.x ).normalize(); const radians = Math.acos( dir.y ); this.quaternion.setFromAxisAngle( _axis, radians ); } } setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 this.line.updateMatrix(); this.cone.scale.set( headWidth, headLength, headWidth ); this.cone.position.y = length; this.cone.updateMatrix(); } setColor( color ) { this.line.material.color.set( color ); this.cone.material.color.set( color ); } copy( source ) { super.copy( source, false ); this.line.copy( source.line ); this.cone.copy( source.cone ); return this; } dispose() { this.line.geometry.dispose(); this.line.material.dispose(); this.cone.geometry.dispose(); this.cone.material.dispose(); } } class AxesHelper extends LineSegments { constructor( size = 1 ) { const vertices = [ 0, 0, 0, size, 0, 0, 0, 0, 0, 0, size, 0, 0, 0, 0, 0, 0, size ]; const colors = [ 1, 0, 0, 1, 0.6, 0, 0, 1, 0, 0.6, 1, 0, 0, 0, 1, 0, 0.6, 1 ]; const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); super( geometry, material ); this.type = 'AxesHelper'; } setColors( xAxisColor, yAxisColor, zAxisColor ) { const color = new Color(); const array = this.geometry.attributes.color.array; color.set( xAxisColor ); color.toArray( array, 0 ); color.toArray( array, 3 ); color.set( yAxisColor ); color.toArray( array, 6 ); color.toArray( array, 9 ); color.set( zAxisColor ); color.toArray( array, 12 ); color.toArray( array, 15 ); this.geometry.attributes.color.needsUpdate = true; return this; } dispose() { this.geometry.dispose(); this.material.dispose(); } } class ShapePath { constructor() { this.type = 'ShapePath'; this.color = new Color(); this.subPaths = []; this.currentPath = null; } moveTo( x, y ) { this.currentPath = new Path(); this.subPaths.push( this.currentPath ); this.currentPath.moveTo( x, y ); return this; } lineTo( x, y ) { this.currentPath.lineTo( x, y ); return this; } quadraticCurveTo( aCPx, aCPy, aX, aY ) { this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); return this; } bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); return this; } splineThru( pts ) { this.currentPath.splineThru( pts ); return this; } toShapes( isCCW ) { function toShapesNoHoles( inSubpaths ) { const shapes = []; for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { const tmpPath = inSubpaths[ i ]; const tmpShape = new Shape(); tmpShape.curves = tmpPath.curves; shapes.push( tmpShape ); } return shapes; } function isPointInsidePolygon( inPt, inPolygon ) { const polyLen = inPolygon.length; // inPt on polygon contour => immediate success or // toggling of inside/outside at every single! intersection point of an edge // with the horizontal line through inPt, left of inPt // not counting lowerY endpoints of edges and whole edges on that line let inside = false; for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { let edgeLowPt = inPolygon[ p ]; let edgeHighPt = inPolygon[ q ]; let edgeDx = edgeHighPt.x - edgeLowPt.x; let edgeDy = edgeHighPt.y - edgeLowPt.y; if ( Math.abs( edgeDy ) > Number.EPSILON ) { // not parallel if ( edgeDy < 0 ) { edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; } if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; if ( inPt.y === edgeLowPt.y ) { if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? // continue; // no intersection or edgeLowPt => doesn't count !!! } else { const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); if ( perpEdge === 0 ) return true; // inPt is on contour ? if ( perpEdge < 0 ) continue; inside = ! inside; // true intersection left of inPt } } else { // parallel or collinear if ( inPt.y !== edgeLowPt.y ) continue; // parallel // edge lies on the same horizontal line as inPt if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! // continue; } } return inside; } const isClockWise = ShapeUtils.isClockWise; const subPaths = this.subPaths; if ( subPaths.length === 0 ) return []; let solid, tmpPath, tmpShape; const shapes = []; if ( subPaths.length === 1 ) { tmpPath = subPaths[ 0 ]; tmpShape = new Shape(); tmpShape.curves = tmpPath.curves; shapes.push( tmpShape ); return shapes; } let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); holesFirst = isCCW ? ! holesFirst : holesFirst; // console.log("Holes first", holesFirst); const betterShapeHoles = []; const newShapes = []; let newShapeHoles = []; let mainIdx = 0; let tmpPoints; newShapes[ mainIdx ] = undefined; newShapeHoles[ mainIdx ] = []; for ( let i = 0, l = subPaths.length; i < l; i ++ ) { tmpPath = subPaths[ i ]; tmpPoints = tmpPath.getPoints(); solid = isClockWise( tmpPoints ); solid = isCCW ? ! solid : solid; if ( solid ) { if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; newShapes[ mainIdx ].s.curves = tmpPath.curves; if ( holesFirst ) mainIdx ++; newShapeHoles[ mainIdx ] = []; //console.log('cw', i); } else { newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); //console.log('ccw', i); } } // only Holes? -> probably all Shapes with wrong orientation if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); if ( newShapes.length > 1 ) { let ambiguous = false; let toChange = 0; for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { betterShapeHoles[ sIdx ] = []; } for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { const sho = newShapeHoles[ sIdx ]; for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { const ho = sho[ hIdx ]; let hole_unassigned = true; for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { if ( sIdx !== s2Idx ) toChange ++; if ( hole_unassigned ) { hole_unassigned = false; betterShapeHoles[ s2Idx ].push( ho ); } else { ambiguous = true; } } } if ( hole_unassigned ) { betterShapeHoles[ sIdx ].push( ho ); } } } if ( toChange > 0 && ambiguous === false ) { newShapeHoles = betterShapeHoles; } } let tmpHoles; for ( let i = 0, il = newShapes.length; i < il; i ++ ) { tmpShape = newShapes[ i ].s; shapes.push( tmpShape ); tmpHoles = newShapeHoles[ i ]; for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { tmpShape.holes.push( tmpHoles[ j ].h ); } } //console.log("shape", shapes); return shapes; } } if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { revision: REVISION, } } ) ); } if ( typeof window !== 'undefined' ) { if ( window.__THREE__ ) { console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); } else { window.__THREE__ = REVISION; } } export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AnimationAction, AnimationClip, AnimationLoader, AnimationMixer, AnimationObjectGroup, AnimationUtils, ArcCurve, ArrayCamera, ArrowHelper, AttachedBindMode, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BackSide, BasicDepthPacking, BasicShadowMap, BatchedMesh, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxGeometry, BoxHelper, BufferAttribute, BufferGeometry, BufferGeometryLoader, ByteType, Cache, Camera, CameraHelper, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CineonToneMapping, CircleGeometry, ClampToEdgeWrapping, Clock, Color, ColorKeyframeTrack, ColorManagement, CompressedArrayTexture, CompressedCubeTexture, CompressedTexture, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureLoader, CubeUVReflectionMapping, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceBack, CullFaceFront, CullFaceFrontBack, CullFaceNone, Curve, CurvePath, CustomBlending, CustomToneMapping, CylinderGeometry, Cylindrical, Data3DTexture, DataArrayTexture, DataTexture, DataTextureLoader, DataUtils, DecrementStencilOp, DecrementWrapStencilOp, DefaultLoadingManager, DepthFormat, DepthStencilFormat, DepthTexture, DetachedBindMode, DirectionalLight, DirectionalLightHelper, DiscreteInterpolant, DisplayP3ColorSpace, DodecahedronGeometry, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicCopyUsage, DynamicDrawUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExtrudeGeometry, FileLoader, Float16BufferAttribute, Float32BufferAttribute, Float64BufferAttribute, FloatType, Fog, FogExp2, FramebufferTexture, FrontSide, Frustum, GLBufferAttribute, GLSL1, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, GridHelper, Group, HalfFloatType, HemisphereLight, HemisphereLightHelper, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, ImageUtils, IncrementStencilOp, IncrementWrapStencilOp, InstancedBufferAttribute, InstancedBufferGeometry, InstancedInterleavedBuffer, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, InterleavedBuffer, InterleavedBufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InvertStencilOp, KeepStencilOp, KeyframeTrack, LOD, LatheGeometry, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, Light, LightProbe, Line, Line3, LineBasicMaterial, LineCurve, LineCurve3, LineDashedMaterial, LineLoop, LineSegments, LinearDisplayP3ColorSpace, LinearEncoding, LinearFilter, LinearInterpolant, LinearMipMapLinearFilter, LinearMipMapNearestFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, LuminanceAlphaFormat, LuminanceFormat, MOUSE, Material, MaterialLoader, MathUtils, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, NormalAnimationBlendMode, NormalBlending, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, NumberKeyframeTrack, Object3D, ObjectLoader, ObjectSpaceNormalMap, OctahedronGeometry, OneFactor, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, P3Primaries, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, Path, PerspectiveCamera, Plane, PlaneGeometry, PlaneHelper, PointLight, PointLightHelper, Points, PointsMaterial, PolarGridHelper, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RawShaderMaterial, Ray, Raycaster, Rec709Primaries, RectAreaLight, RedFormat, RedIntegerFormat, ReinhardToneMapping, RenderTarget, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RingGeometry, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, ShaderChunk, ShaderLib, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, ShortType, Skeleton, SkeletonHelper, SkinnedMesh, Source, Sphere, SphereGeometry, Spherical, SphericalHarmonics3, SplineCurve, SpotLight, SpotLightHelper, Sprite, SpriteMaterial, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StaticCopyUsage, StaticDrawUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, SubtractEquation, SubtractiveBlending, TOUCH, TangentSpaceNormalMap, TetrahedronGeometry, Texture, TextureLoader, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, TwoPassDoubleSide, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, UniformsLib, UniformsUtils, UnsignedByteType, UnsignedInt248Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, VectorKeyframeTrack, VideoTexture, WebGL1Renderer, WebGL3DRenderTarget, WebGLArrayRenderTarget, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGLMultipleRenderTargets, WebGLRenderTarget, WebGLRenderer, WebGLUtils, WebGPUCoordinateSystem, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroFactor, ZeroSlopeEnding, ZeroStencilOp, _SRGBAFormat, createCanvasElement, sRGBEncoding }; ================================================ FILE: public/assets/lib/vendor/three/viewcube.js ================================================ import * as THREE from "./three.module.js"; const DEFAULT_FACENAMES = { top: "TOP", front: "FRONT", right: "RIGHT", back: "BACK", left: "LEFT", bottom: "BOTTOM" }; var ObjectPosition = /* @__PURE__ */ ((ObjectPosition2) => { ObjectPosition2[ObjectPosition2["LEFT_BOTTOM"] = 0] = "LEFT_BOTTOM"; ObjectPosition2[ObjectPosition2["LEFT_TOP"] = 1] = "LEFT_TOP"; ObjectPosition2[ObjectPosition2["RIGHT_TOP"] = 2] = "RIGHT_TOP"; ObjectPosition2[ObjectPosition2["RIGHT_BOTTOM"] = 4] = "RIGHT_BOTTOM"; return ObjectPosition2; })(ObjectPosition || {}); class FixedPosGizmo extends THREE.Object3D { /** * Construct one instance of this gizmo * @param camera Camera used in your canvas * @param renderer Renderer used in your canvas * @param dimension Size of area ocupied by this gizmo. Because width and height of this area is same, * it is single value. The real size of the objet will be calculated automatically considering rotation. * @param pos Position of the gizmo */ constructor(camera, renderer, dimension = 150, pos = 2) { super(); this.camera = camera; this.renderer = renderer; this.gizmoCamera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0, 4); this.gizmoCamera.position.set(0, 0, 2); this.gizmoDim = dimension; this.gizmoPos = pos; this.initialize(); } /** * Function called by constructor to initialize this gizmo. The children class can override this function * to add its own initialization logic. */ initialize() { } /** * Update and rerender this gizmo */ update() { this.updateOrientation(); const autoClear = this.renderer.autoClear; this.renderer.autoClear = false; this.renderer.clearDepth(); const viewport = new THREE.Vector4(); this.renderer.getViewport(viewport); const pos = this.calculateViewportPos(); this.renderer.setViewport(pos.x, pos.y, this.gizmoDim, this.gizmoDim); this.renderer.render(this, this.gizmoCamera); this.renderer.setViewport(viewport.x, viewport.y, viewport.z, viewport.w); this.renderer.autoClear = autoClear; } /** * Free the GPU-related resources allocated by this instance. Call this method whenever this instance * is no longer used in your app. */ dispose() { } updateOrientation() { this.quaternion.copy(this.camera.quaternion).invert(); this.updateMatrixWorld(); } calculatePosInViewport(offsetX, offsetY, bbox) { const x = (offsetX - bbox.min.x) / this.gizmoDim * 2 - 1; const y = -((offsetY - bbox.min.y) / this.gizmoDim) * 2 + 1; return { x, y }; } calculateViewportPos() { const domElement = this.renderer.domElement; const canvasWidth = domElement.offsetWidth; const canvasHeight = domElement.offsetHeight; const pos = this.gizmoPos; const length = this.gizmoDim; let x = canvasWidth - length; let y = canvasHeight - length; switch (pos) { case 0: x = 0; y = 0; break; case 1: x = 0; break; case 4: y = 0; break; } return { x, y }; } calculateViewportBbox() { const domElement = this.renderer.domElement; const canvasWidth = domElement.offsetWidth; const canvasHeight = domElement.offsetHeight; const pos = this.gizmoPos; const length = this.gizmoDim; const bbox = new THREE.Box2( new THREE.Vector2(canvasWidth - length, 0), new THREE.Vector2(canvasWidth, length) ); switch (pos) { case 0: bbox.set( new THREE.Vector2(0, canvasHeight - length), new THREE.Vector2(length, canvasHeight) ); break; case 1: bbox.set(new THREE.Vector2(0, 0), new THREE.Vector2(length, length)); break; case 4: bbox.set( new THREE.Vector2(canvasWidth - length, canvasHeight - length), new THREE.Vector2(canvasWidth, canvasHeight) ); break; } return bbox; } } function createTextTexture(text, props) { const fontface = props.font || "Helvetica"; const fontsize = props.fontSize || 30; const width = props.width || 200; const height = props.height || 200; const bgColor = props.bgColor ? props.bgColor.join(", ") : "255, 255, 255, 1.0"; const fgColor = props.color ? props.color.join(", ") : "0, 0, 0, 1.0"; const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); if (context) { context.font = `bold ${fontsize}px ${fontface}`; context.fillStyle = `rgba(${bgColor})`; context.fillRect(0, 0, width, height); const metrics = context.measureText(text); const textWidth = metrics.width; context.fillStyle = `rgba(${fgColor})`; context.fillText( text, width / 2 - textWidth / 2, height / 2 + fontsize / 2 - 2 ); } const texture = new THREE.Texture(canvas); texture.minFilter = THREE.LinearFilter; texture.needsUpdate = true; return texture; } function createTextSprite(text) { const texture = createTextTexture(text, { fontSize: 100, font: "Arial Narrow, sans-serif", color: [255, 255, 255, 1], bgColor: [0, 0, 0, 0] }); const material = new THREE.SpriteMaterial({ map: texture, transparent: true }); return new THREE.Sprite(material); } function createFaceMaterials(faceNames = DEFAULT_FACENAMES) { const materials = [ { name: FACES.FRONT, map: createTextTexture(faceNames.front, { fontSize: 55, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) }, { name: FACES.RIGHT, map: createTextTexture(faceNames.right, { fontSize: 55, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) }, { name: FACES.BACK, map: createTextTexture(faceNames.back, { fontSize: 55, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) }, { name: FACES.LEFT, map: createTextTexture(faceNames.left, { fontSize: 55, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) }, { name: FACES.TOP, map: createTextTexture(faceNames.top, { fontSize: 60, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) }, { name: FACES.BOTTOM, map: createTextTexture(faceNames.bottom, { fontSize: 48, font: "Arial Narrow, sans-serif", color: ["87", "89", "90", "1"] }) } ]; return materials; } const FACES = { TOP: "1", FRONT: "2", RIGHT: "3", BACK: "4", LEFT: "5", BOTTOM: "6", TOP_FRONT_EDGE: "7", TOP_RIGHT_EDGE: "8", TOP_BACK_EDGE: "9", TOP_LEFT_EDGE: "10", FRONT_RIGHT_EDGE: "11", BACK_RIGHT_EDGE: "12", BACK_LEFT_EDGE: "13", FRONT_LEFT_EDGE: "14", BOTTOM_FRONT_EDGE: "15", BOTTOM_RIGHT_EDGE: "16", BOTTOM_BACK_EDGE: "17", BOTTOM_LEFT_EDGE: "18", TOP_FRONT_RIGHT_CORNER: "19", TOP_BACK_RIGHT_CORNER: "20", TOP_BACK_LEFT_CORNER: "21", TOP_FRONT_LEFT_CORNER: "22", BOTTOM_FRONT_RIGHT_CORNER: "23", BOTTOM_BACK_RIGHT_CORNER: "24", BOTTOM_BACK_LEFT_CORNER: "25", BOTTOM_FRONT_LEFT_CORNER: "26" }; const CORNER_FACES = [ { name: FACES.TOP_FRONT_RIGHT_CORNER }, { name: FACES.TOP_BACK_RIGHT_CORNER }, { name: FACES.TOP_BACK_LEFT_CORNER }, { name: FACES.TOP_FRONT_LEFT_CORNER }, { name: FACES.BOTTOM_BACK_RIGHT_CORNER }, { name: FACES.BOTTOM_FRONT_RIGHT_CORNER }, { name: FACES.BOTTOM_FRONT_LEFT_CORNER }, { name: FACES.BOTTOM_BACK_LEFT_CORNER } ]; const EDGE_FACES = [ { name: FACES.TOP_FRONT_EDGE }, { name: FACES.TOP_RIGHT_EDGE }, { name: FACES.TOP_BACK_EDGE }, { name: FACES.TOP_LEFT_EDGE }, // flip back and front bottom edges { name: FACES.BOTTOM_BACK_EDGE }, { name: FACES.BOTTOM_RIGHT_EDGE }, { name: FACES.BOTTOM_FRONT_EDGE }, { name: FACES.BOTTOM_LEFT_EDGE } ]; const EDGE_FACES_SIDE = [ { name: FACES.FRONT_RIGHT_EDGE }, { name: FACES.BACK_RIGHT_EDGE }, { name: FACES.BACK_LEFT_EDGE }, { name: FACES.FRONT_LEFT_EDGE } ]; class ViewCube extends THREE.Object3D { /** * Construct one instance of view cube 3d object * @param cubeSize Size of area ocupied by view cube * @param borderSize Border size of view cube * @param isShowOutline Flag to decide whether to show edge of view cube * @param faceColor Face color of view cube * @param outlineColor Edge color of view cube * @param faceNames Texts in each face of view cube */ constructor(cubeSize = 60, borderSize = 5, isShowOutline = true, faceColor = 13421772, outlineColor = 10066329, faceNames = DEFAULT_FACENAMES) { super(); this._cubeSize = cubeSize; this._borderSize = borderSize; this._isShowOutline = isShowOutline; this._faceColor = faceColor; this._outlineColor = outlineColor; this.build(faceNames); } /** * Free the GPU-related resources allocated by this instance. Call this method whenever this instance * is no longer used in your app. */ dispose() { this.children.forEach((child) => { var _a, _b, _c, _d; const mesh = child; (_a = mesh.material) == null ? void 0 : _a.dispose(); (_c = (_b = mesh.material) == null ? void 0 : _b.map) == null ? void 0 : _c.dispose(); (_d = mesh.geometry) == null ? void 0 : _d.dispose(); }); } build(faceNames) { const faceSize = this._cubeSize - this._borderSize * 2; const faceOffset = this._cubeSize / 2; const borderSize = this._borderSize; const cubeFaces = this.createCubeFaces(faceSize, faceOffset); const faceMaterials = createFaceMaterials(faceNames); for (const [i, props] of faceMaterials.entries()) { const face = cubeFaces.children[i]; const material = face.material; material.color.setHex(this._faceColor); material.map = props.map; face.name = props.name; } this.add(cubeFaces); const corners = []; for (const [i, props] of CORNER_FACES.entries()) { const corner = this.createCornerFaces( borderSize, faceOffset, props.name, { color: this._faceColor } ); corner.rotateOnAxis( new THREE.Vector3(0, 1, 0), THREE.MathUtils.degToRad(i % 4 * 90) ); corners.push(corner); } const topCorners = new THREE.Group(); const bottomCorners = new THREE.Group(); this.add(topCorners.add(...corners.slice(0, 4))); this.add( bottomCorners.add(...corners.slice(4)).rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI) ); const edges = []; for (const [i, props] of EDGE_FACES.entries()) { const edge = this.createHorzEdgeFaces( faceSize, borderSize, faceOffset, props.name, { color: this._faceColor } ); edge.rotateOnAxis( new THREE.Vector3(0, 1, 0), THREE.MathUtils.degToRad(i % 4 * 90) ); edges.push(edge); } const topEdges = new THREE.Group(); const bottomEdges = new THREE.Group(); this.add(topEdges.add(...edges.slice(0, 4))); this.add( bottomEdges.add(...edges.slice(4)).rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI) ); const sideEdges = new THREE.Group(); for (const [i, props] of EDGE_FACES_SIDE.entries()) { const edge = this.createVertEdgeFaces( borderSize, faceSize, faceOffset, props.name, { color: this._faceColor } ); edge.rotateOnAxis( new THREE.Vector3(0, 1, 0), THREE.MathUtils.degToRad(i * 90) ); sideEdges.add(edge); } this.add(sideEdges); if (this._isShowOutline) { this.add(this.createCubeOutline(this._cubeSize)); } } createFace(size, position, { axis = [0, 1, 0], angle = 0, name = "", matProps = {} } = {}) { if (!Array.isArray(size)) size = [size, size]; const material = new THREE.MeshBasicMaterial(matProps); const geometry = new THREE.PlaneGeometry(size[0], size[1]); const face = new THREE.Mesh(geometry, material); face.name = name; face.rotateOnAxis( new THREE.Vector3(...axis), THREE.MathUtils.degToRad(angle) ); face.position.set(position[0], position[1], position[2]); return face; } createCubeFaces(faceSize, offset) { const faces = new THREE.Object3D(); faces.add( this.createFace(faceSize, [0, 0, offset], { axis: [0, 1, 0], angle: 0 }) ); faces.add( this.createFace(faceSize, [offset, 0, 0], { axis: [0, 1, 0], angle: 90 }) ); faces.add( this.createFace(faceSize, [0, 0, -offset], { axis: [0, 1, 0], angle: 180 }) ); faces.add( this.createFace(faceSize, [-offset, 0, 0], { axis: [0, 1, 0], angle: 270 }) ); faces.add( this.createFace(faceSize, [0, offset, 0], { axis: [1, 0, 0], angle: -90 }) ); faces.add( this.createFace(faceSize, [0, -offset, 0], { axis: [1, 0, 0], angle: 90 }) ); return faces; } createCornerFaces(faceSize, offset, name = "", matProps = {}) { const corner = new THREE.Object3D(); const borderOffset = offset - faceSize / 2; corner.add( this.createFace(faceSize, [borderOffset, borderOffset, offset], { axis: [0, 1, 0], angle: 0, matProps, name }) ); corner.add( this.createFace(faceSize, [offset, borderOffset, borderOffset], { axis: [0, 1, 0], angle: 90, matProps, name }) ); corner.add( this.createFace(faceSize, [borderOffset, offset, borderOffset], { axis: [1, 0, 0], angle: -90, matProps, name }) ); return corner; } createHorzEdgeFaces(w, h, offset, name = "", matProps = {}) { const edge = new THREE.Object3D(); const borderOffset = offset - h / 2; edge.add( this.createFace([w, h], [0, borderOffset, offset], { axis: [0, 1, 0], angle: 0, name, matProps }) ); edge.add( this.createFace([w, h], [0, offset, borderOffset], { axis: [1, 0, 0], angle: -90, name, matProps }) ); return edge; } createVertEdgeFaces(w, h, offset, name = "", matProps = {}) { const edge = new THREE.Object3D(); const borderOffset = offset - w / 2; edge.add( this.createFace([w, h], [borderOffset, 0, offset], { axis: [0, 1, 0], angle: 0, name, matProps }) ); edge.add( this.createFace([w, h], [offset, 0, borderOffset], { axis: [0, 1, 0], angle: 90, name, matProps }) ); return edge; } createCubeOutline(size) { const geometry = new THREE.BoxGeometry(size, size, size); const geo = new THREE.EdgesGeometry(geometry); const mat = new THREE.LineBasicMaterial({ color: this._outlineColor, linewidth: 1 }); const wireframe = new THREE.LineSegments(geo, mat); return wireframe; } } const MAIN_COLOR = 0xf9f9fa; const HOVER_COLOR = 0xececec; const OUTLINE_COLOR = 13421772; const DEFAULT_VIEWCUBE_OPTIONS = { pos: ObjectPosition.RIGHT_TOP, dimension: 150, faceColor: MAIN_COLOR, hoverColor: HOVER_COLOR, outlineColor: OUTLINE_COLOR, faceNames: DEFAULT_FACENAMES }; class ViewCubeGizmo extends FixedPosGizmo { /** * Construct one instance of view cube gizmo * @param camera Camera used in your canvas * @param renderer Renderer used in your canvas * @param options Options to customize view cube gizmo */ constructor(camera, renderer, options = DEFAULT_VIEWCUBE_OPTIONS) { const mergedOptions = { ...DEFAULT_VIEWCUBE_OPTIONS, ...options }; super(camera, renderer, options.dimension, options.pos); this.cube = new ViewCube( 2, 0.2, true, mergedOptions.faceColor, mergedOptions.outlineColor, mergedOptions.faceNames ); this.add(this.cube); this.handleMouseMove = this.handleMouseMove.bind(this); this.handleMouseClick = this.handleMouseClick.bind(this); this.listen(renderer.domElement); } /** * Free the GPU-related resources allocated by this instance. Call this method whenever this instance * is no longer used in your app. */ dispose() { this.cube.dispose(); } listen(domElement) { domElement.addEventListener("mousemove", this.handleMouseMove); domElement.addEventListener("click", this.handleMouseClick); } handleMouseClick(event) { const bbox = this.calculateViewportBbox(); if (bbox.containsPoint(new THREE.Vector2(event.offsetX, event.offsetY))) { const pos = this.calculatePosInViewport( event.offsetX, event.offsetY, bbox ); this.checkSideTouch(pos.x, pos.y); } } handleMouseMove(event) { const bbox = this.calculateViewportBbox(); if (bbox.containsPoint(new THREE.Vector2(event.offsetX, event.offsetY))) { const pos = this.calculatePosInViewport( event.offsetX, event.offsetY, bbox ); this.checkSideOver(pos.x, pos.y); } } checkSideTouch(x, y) { const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(new THREE.Vector2(x, y), this.gizmoCamera); const intersects = raycaster.intersectObjects(this.cube.children, true); if (intersects.length) { for (const { object } of intersects) { if (object.name) { const quaternion = this.getRotation(object.name); this.dispatchEvent({ type: "change", quaternion }); break; } } } } checkSideOver(x, y) { const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(new THREE.Vector2(x, y), this.gizmoCamera); const intersects = raycaster.intersectObjects(this.cube.children, true); this.cube.traverse(function(obj) { if (obj.name) { const mesh = obj; mesh.material.color.setHex(MAIN_COLOR); } }); if (intersects.length) { for (const { object } of intersects) { if (object.name) { object.parent.children.forEach(function(child) { if (child.name === object.name) { const mesh = child; mesh.material.color.setHex( HOVER_COLOR ); } }); break; } } } } getRotation(side) { const targetQuaternion = new THREE.Quaternion(); switch (side) { case FACES.FRONT: targetQuaternion.setFromEuler(new THREE.Euler()); break; case FACES.RIGHT: targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI * 0.5, 0)); break; case FACES.BACK: targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI, 0)); break; case FACES.LEFT: targetQuaternion.setFromEuler(new THREE.Euler(0, -Math.PI * 0.5, 0)); break; case FACES.TOP: targetQuaternion.setFromEuler(new THREE.Euler(-Math.PI * 0.5, 0, 0)); break; case FACES.BOTTOM: targetQuaternion.setFromEuler(new THREE.Euler(Math.PI * 0.5, 0, 0)); break; case FACES.TOP_FRONT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(-Math.PI * 0.25, 0, 0)); break; case FACES.TOP_RIGHT_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, Math.PI * 0.5, 0, "YXZ") ); break; case FACES.TOP_BACK_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, Math.PI, 0, "YXZ") ); break; case FACES.TOP_LEFT_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, -Math.PI * 0.5, 0, "YXZ") ); break; case FACES.BOTTOM_FRONT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(Math.PI * 0.25, 0, 0)); break; case FACES.BOTTOM_RIGHT_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, Math.PI * 0.5, 0, "YXZ") ); break; case FACES.BOTTOM_BACK_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, Math.PI, 0, "YXZ") ); break; case FACES.BOTTOM_LEFT_EDGE: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, -Math.PI * 0.5, 0, "YXZ") ); break; case FACES.FRONT_RIGHT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI * 0.25, 0)); break; case FACES.BACK_RIGHT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(0, Math.PI * 0.75, 0)); break; case FACES.BACK_LEFT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(0, -Math.PI * 0.75, 0)); break; case FACES.FRONT_LEFT_EDGE: targetQuaternion.setFromEuler(new THREE.Euler(0, -Math.PI * 0.25, 0)); break; case FACES.TOP_FRONT_RIGHT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, -Math.PI * 1.75, 0) ); break; case FACES.TOP_BACK_RIGHT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, -Math.PI * 1.25, 0) ); break; case FACES.TOP_BACK_LEFT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, -Math.PI * 0.75, 0) ); break; case FACES.TOP_FRONT_LEFT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, -Math.PI * 0.25, 0) ); break; case FACES.BOTTOM_FRONT_RIGHT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, -Math.PI * 1.75, 0) ); break; case FACES.BOTTOM_BACK_RIGHT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, -Math.PI * 1.25, 0) ); break; case FACES.BOTTOM_BACK_LEFT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(-Math.PI * 0.25, -Math.PI * 0.75, 0) ); break; case FACES.BOTTOM_FRONT_LEFT_CORNER: targetQuaternion.setFromEuler( new THREE.Euler(Math.PI * 0.25, -Math.PI * 0.25, 0) ); break; default: console.error( `[ViewCubeGizmo]: Invalid face, edge, or corner name '${side}'!` ); break; } return targetQuaternion; } } const DEFAULT_AXES_OPTIONS = { pos: ObjectPosition.LEFT_BOTTOM, size: 100, hasZAxis: true }; class AxesGizmo extends FixedPosGizmo { constructor(camera, renderer, options) { const mergedOptions = { ...DEFAULT_AXES_OPTIONS, ...options }; super(camera, renderer, mergedOptions.size, options.pos); this.hasZAxis = mergedOptions.hasZAxis; const vertices = [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0]; const colors = [1, 0, 0, 1, 0.6, 0, 0, 1, 0, 0.6, 1, 0]; if (this.hasZAxis) { vertices.push(0, 0, 0, 0, 0, 2); colors.push(0, 0, 1, 0, 0.6, 1); } const geometry = new THREE.BufferGeometry(); geometry.setAttribute( "position", new THREE.Float32BufferAttribute(vertices, 3) ); geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3)); const material = new THREE.LineBasicMaterial({ vertexColors: true, toneMapped: false }); this.axes = new THREE.LineSegments(geometry, material); this.axes.position.set(-1, -1, -1); this.add(this.axes); this.xText = createTextSprite("X"); this.xText.position.set(1.5, -1, -1); this.add(this.xText); this.yText = createTextSprite("Y"); this.yText.position.set(-1, 1.5, -1); this.add(this.yText); if (this.hasZAxis) { this.zText = createTextSprite("Z"); this.zText.position.set(-1, -1, 1.5); this.add(this.zText); } } /** * Set color of x-axis and y-axis * @param xAxisColor color of x-axis * @param yAxisColor color of y-axis */ setLineColors(xAxisColor, yAxisColor) { const color = new THREE.Color(); const array = this.axes.geometry.attributes.color.array; color.set(xAxisColor); color.toArray(array, 0); color.toArray(array, 3); color.set(yAxisColor); color.toArray(array, 6); color.toArray(array, 9); this.axes.geometry.attributes.color.needsUpdate = true; return this; } /** * Set text color * @param color text color */ setTextColor(color) { this.xText.material.color = color; this.yText.material.color = color; } /** * Free the GPU-related resources allocated by this instance. Call this method whenever this instance * is no longer used in your app. */ dispose() { var _a, _b; this.axes.geometry.dispose(); const material = this.axes.material; material.dispose(); this.xText.geometry.dispose(); this.xText.material.dispose(); this.yText.geometry.dispose(); this.yText.material.dispose(); if (this.hasZAxis) { (_a = this.zText) == null ? void 0 : _a.geometry.dispose(); (_b = this.zText) == null ? void 0 : _b.material.dispose(); } } } class SimpleCameraControls { /** * Construct one instance of view cube helper * @param camera Camera used in your canvas * @param renderer Renderer used in your canvas * @param options Options to customize view cube helper */ constructor(camera) { this.camera = camera; this.animating = false; this.turnRate = 2 * Math.PI; this.target = new THREE.Vector3(); this.q1 = new THREE.Quaternion(); this.q2 = new THREE.Quaternion(); this.radius = 0; this.clock = new THREE.Clock(); } /** * Set associated obit controls * @param controls The associated orbit controls */ setControls(controls) { if (!controls) return; this.controls = controls; } /** * Animation loop */ update() { var _a; if (this.animating === false) return; const delta = this.clock.getDelta(); const step = delta * this.turnRate; this.q1.rotateTowards(this.q2, step); this.camera.position.set(0, 0, 1).applyQuaternion(this.q1).multiplyScalar(this.radius).add(this.target); this.camera.quaternion.rotateTowards(this.q2, step); this.camera.updateProjectionMatrix(); (_a = this.controls) == null ? void 0 : _a.update(); if (this.q1.angleTo(this.q2) <= 1e-5) { this.animating = false; this.clock.stop(); } } /** * Fly with the target quaterion * @param quaternion */ flyTo(quaternion) { const focusPoint = new THREE.Vector3(); const targetPosition = new THREE.Vector3(0, 0, 1); this.radius = this.camera.position.distanceTo(focusPoint); targetPosition.applyQuaternion(quaternion).multiplyScalar(this.radius).add(focusPoint); const dummy = new THREE.Object3D(); dummy.position.copy(focusPoint); dummy.lookAt(this.camera.position); this.q1.copy(dummy.quaternion); dummy.lookAt(targetPosition); this.q2.copy(dummy.quaternion); this.animating = true; this.clock.start(); } } export { AxesGizmo, DEFAULT_AXES_OPTIONS, DEFAULT_FACENAMES, DEFAULT_VIEWCUBE_OPTIONS, FixedPosGizmo, ObjectPosition, SimpleCameraControls, ViewCube, ViewCubeGizmo }; ================================================ FILE: public/assets/lib/vendor/wavesurfer.js ================================================ /*! wavesurfer.js 1.4.0 (Mon, 10 Apr 2017 08:55:35 GMT) * https://github.com/katspaugh/wavesurfer.js * @license BSD-3-Clause */ !function(a,b){"function"==typeof define&&define.amd?define("wavesurfer",[],function(){return a.WaveSurfer=b()}):"object"==typeof exports?module.exports=b():a.WaveSurfer=b()}(this,function(){"use strict";var a={defaultParams:{audioContext:null,audioRate:1,autoCenter:!0,backend:"WebAudio",barHeight:1,closeAudioContext:!1,container:null,cursorColor:"#333",cursorWidth:1,dragSelection:!0,fillParent:!0,forceDecode:!1,height:128,hideScrollbar:!1,interact:!0,loopSelection:!0,mediaContainer:null,mediaControls:!1,mediaType:"audio",minPxPerSec:20,partialRender:!1,pixelRatio:window.devicePixelRatio||screen.deviceXDPI/screen.logicalXDPI,progressColor:"#555",normalize:!1,renderer:"MultiCanvas",scrollParent:!1,skipLength:2,splitChannels:!1,waveColor:"#999"},init:function(b){if(this.params=a.util.extend({},this.defaultParams,b),this.container="string"==typeof b.container?document.querySelector(this.params.container):this.params.container,!this.container)throw new Error("Container element not found");if(null==this.params.mediaContainer?this.mediaContainer=this.container:"string"==typeof this.params.mediaContainer?this.mediaContainer=document.querySelector(this.params.mediaContainer):this.mediaContainer=this.params.mediaContainer,!this.mediaContainer)throw new Error("Media Container element not found");this.savedVolume=0,this.isMuted=!1,this.tmpEvents=[],this.currentAjax=null,this.createDrawer(),this.createBackend(),this.createPeakCache(),this.isDestroyed=!1},createDrawer:function(){var b=this;this.drawer=Object.create(a.Drawer[this.params.renderer]),this.drawer.init(this.container,this.params),this.drawer.on("redraw",function(){b.drawBuffer(),b.drawer.progress(b.backend.getPlayedPercents())}),this.drawer.on("click",function(a,c){setTimeout(function(){b.seekTo(c)},0)}),this.drawer.on("scroll",function(a){b.params.partialRender&&b.drawBuffer(),b.fireEvent("scroll",a)})},createBackend:function(){var b=this;this.backend&&this.backend.destroy(),"AudioElement"==this.params.backend&&(this.params.backend="MediaElement"),"WebAudio"!=this.params.backend||a.WebAudio.supportsWebAudio()||(this.params.backend="MediaElement"),this.backend=Object.create(a[this.params.backend]),this.backend.init(this.params),this.backend.on("finish",function(){b.fireEvent("finish")}),this.backend.on("play",function(){b.fireEvent("play")}),this.backend.on("pause",function(){b.fireEvent("pause")}),this.backend.on("audioprocess",function(a){b.drawer.progress(b.backend.getPlayedPercents()),b.fireEvent("audioprocess",a)})},createPeakCache:function(){this.params.partialRender&&(this.peakCache=Object.create(a.PeakCache),this.peakCache.init())},getDuration:function(){return this.backend.getDuration()},getCurrentTime:function(){return this.backend.getCurrentTime()},play:function(a,b){this.fireEvent("interaction",this.play.bind(this,a,b)),this.backend.play(a,b)},pause:function(){this.backend.isPaused()||this.backend.pause()},playPause:function(){this.backend.isPaused()?this.play():this.pause()},isPlaying:function(){return!this.backend.isPaused()},skipBackward:function(a){this.skip(-a||-this.params.skipLength)},skipForward:function(a){this.skip(a||this.params.skipLength)},skip:function(a){var b=this.getCurrentTime()||0,c=this.getDuration()||1;b=Math.max(0,Math.min(c,b+(a||0))),this.seekAndCenter(b/c)},seekAndCenter:function(a){this.seekTo(a),this.drawer.recenter(a)},seekTo:function(a){this.fireEvent("interaction",this.seekTo.bind(this,a));var b=this.backend.isPaused();b||this.backend.pause();var c=this.params.scrollParent;this.params.scrollParent=!1,this.backend.seekTo(a*this.getDuration()),this.drawer.progress(this.backend.getPlayedPercents()),b||this.backend.play(),this.params.scrollParent=c,this.fireEvent("seek",a)},stop:function(){this.pause(),this.seekTo(0),this.drawer.progress(0)},setVolume:function(a){this.backend.setVolume(a)},getVolume:function(){return this.backend.getVolume()},setPlaybackRate:function(a){this.backend.setPlaybackRate(a)},getPlaybackRate:function(){return this.backend.getPlaybackRate()},toggleMute:function(){this.setMute(!this.isMuted)},setMute:function(a){a!==this.isMuted&&(a?(this.savedVolume=this.backend.getVolume(),this.backend.setVolume(0),this.isMuted=!0):(this.backend.setVolume(this.savedVolume),this.isMuted=!1))},getMute:function(){return this.isMuted},getFilters:function(){return this.backend.filters||[]},toggleScroll:function(){this.params.scrollParent=!this.params.scrollParent,this.drawBuffer()},toggleInteraction:function(){this.params.interact=!this.params.interact},drawBuffer:function(){var a=Math.round(this.getDuration()*this.params.minPxPerSec*this.params.pixelRatio),b=this.drawer.getWidth(),c=a,d=this.drawer.getScrollX(),e=Math.min(d+b,c);if(this.params.fillParent&&(!this.params.scrollParent||a<b)&&(c=b,d=0,e=c),this.params.partialRender)for(var f=this.peakCache.addRangeToPeakCache(c,d,e),g=0;g<f.length;g++){var h=this.backend.getPeaks(c,f[g][0],f[g][1]);this.drawer.drawPeaks(h,c,f[g][0],f[g][1])}else{d=0,e=c;var h=this.backend.getPeaks(c,d,e);this.drawer.drawPeaks(h,c,d,e)}this.fireEvent("redraw",h,c)},zoom:function(a){this.params.minPxPerSec=a,this.params.scrollParent=!0,this.drawBuffer(),this.drawer.progress(this.backend.getPlayedPercents()),this.drawer.recenter(this.getCurrentTime()/this.getDuration()),this.fireEvent("zoom",a)},loadArrayBuffer:function(a){this.decodeArrayBuffer(a,function(a){this.isDestroyed||this.loadDecodedBuffer(a)}.bind(this))},loadDecodedBuffer:function(a){this.backend.load(a),this.drawBuffer(),this.fireEvent("ready")},loadBlob:function(a){var b=this,c=new FileReader;c.addEventListener("progress",function(a){b.onProgress(a)}),c.addEventListener("load",function(a){b.loadArrayBuffer(a.target.result)}),c.addEventListener("error",function(){b.fireEvent("error","Error reading file")}),c.readAsArrayBuffer(a),this.empty()},load:function(a,b,c){switch(this.empty(),this.isMuted=!1,this.params.backend){case"WebAudio":return this.loadBuffer(a,b);case"MediaElement":return this.loadMediaElement(a,b,c)}},loadBuffer:function(a,b){var c=function(b){return b&&this.tmpEvents.push(this.once("ready",b)),this.getArrayBuffer(a,this.loadArrayBuffer.bind(this))}.bind(this);return b?(this.backend.setPeaks(b),this.drawBuffer(),this.tmpEvents.push(this.once("interaction",c)),void 0):c()},loadMediaElement:function(a,b,c){var d=a;if("string"==typeof a)this.backend.load(d,this.mediaContainer,b,c);else{var e=a;this.backend.loadElt(e,b),d=e.src}this.tmpEvents.push(this.backend.once("canplay",function(){this.drawBuffer(),this.fireEvent("ready")}.bind(this)),this.backend.once("error",function(a){this.fireEvent("error",a)}.bind(this))),b&&this.backend.setPeaks(b),b&&!this.params.forceDecode||!this.backend.supportsWebAudio()||this.getArrayBuffer(d,function(a){this.decodeArrayBuffer(a,function(a){this.backend.buffer=a,this.backend.setPeaks(null),this.drawBuffer(),this.fireEvent("waveform-ready")}.bind(this))}.bind(this))},decodeArrayBuffer:function(a,b){this.arraybuffer=a,this.backend.decodeArrayBuffer(a,function(c){this.isDestroyed||this.arraybuffer!=a||(b(c),this.arraybuffer=null)}.bind(this),this.fireEvent.bind(this,"error","Error decoding audiobuffer"))},getArrayBuffer:function(b,c){var d=this,e=a.util.ajax({url:b,responseType:"arraybuffer"});return this.currentAjax=e,this.tmpEvents.push(e.on("progress",function(a){d.onProgress(a)}),e.on("success",function(a,b){c(a),d.currentAjax=null}),e.on("error",function(a){d.fireEvent("error","XHR error: "+a.target.statusText),d.currentAjax=null})),e},onProgress:function(a){if(a.lengthComputable)var b=a.loaded/a.total;else b=a.loaded/(a.loaded+1e6);this.fireEvent("loading",Math.round(100*b),a.target)},exportPCM:function(a,b,c){a=a||1024,b=b||1e4,c=c||!1;var d=this.backend.getPeaks(a,b),e=[].map.call(d,function(a){return Math.round(a*b)/b}),f=JSON.stringify(e);return c||window.open("data:application/json;charset=utf-8,"+encodeURIComponent(f)),f},exportImage:function(a,b){return a||(a="image/png"),b||(b=1),this.drawer.getImage(a,b)},cancelAjax:function(){this.currentAjax&&(this.currentAjax.xhr.abort(),this.currentAjax=null)},clearTmpEvents:function(){this.tmpEvents.forEach(function(a){a.un()})},empty:function(){this.backend.isPaused()||(this.stop(),this.backend.disconnectSource()),this.cancelAjax(),this.clearTmpEvents(),this.drawer.progress(0),this.drawer.setWidth(0),this.drawer.drawPeaks({length:this.drawer.getWidth()},0)},destroy:function(){this.fireEvent("destroy"),this.cancelAjax(),this.clearTmpEvents(),this.unAll(),this.backend.destroy(),this.drawer.destroy(),this.isDestroyed=!0}};return a.create=function(b){var c=Object.create(a);return c.init(b),c},a.util={extend:function(a){var b=Array.prototype.slice.call(arguments,1);return b.forEach(function(b){Object.keys(b).forEach(function(c){a[c]=b[c]})}),a},debounce:function(a,b,c){var d,e,f,g=function(){f=null,c||a.apply(e,d)};return function(){e=this,d=arguments;var h=c&&!f;clearTimeout(f),f=setTimeout(g,b),f||(f=setTimeout(g,b)),h&&a.apply(e,d)}},min:function(a){var b=+(1/0);for(var c in a)a[c]<b&&(b=a[c]);return b},max:function(a){var b=-(1/0);for(var c in a)a[c]>b&&(b=a[c]);return b},getId:function(){return"wavesurfer_"+Math.random().toString(32).substring(2)},ajax:function(b){var c=Object.create(a.Observer),d=new XMLHttpRequest,e=!1;return d.open(b.method||"GET",b.url,!0),d.responseType=b.responseType||"json",d.addEventListener("progress",function(a){c.fireEvent("progress",a),a.lengthComputable&&a.loaded==a.total&&(e=!0)}),d.addEventListener("load",function(a){e||c.fireEvent("progress",a),c.fireEvent("load",a),200==d.status||206==d.status?c.fireEvent("success",d.response,a):c.fireEvent("error",a)}),d.addEventListener("error",function(a){c.fireEvent("error",a)}),d.send(),c.xhr=d,c}},a.Observer={on:function(a,b){this.handlers||(this.handlers={});var c=this.handlers[a];return c||(c=this.handlers[a]=[]),c.push(b),{name:a,callback:b,un:this.un.bind(this,a,b)}},un:function(a,b){if(this.handlers){var c=this.handlers[a];if(c)if(b)for(var d=c.length-1;d>=0;d--)c[d]==b&&c.splice(d,1);else c.length=0}},unAll:function(){this.handlers=null},once:function(a,b){var c=this,d=function(){b.apply(this,arguments),setTimeout(function(){c.un(a,d)},0)};return this.on(a,d)},fireEvent:function(a){if(this.handlers){var b=this.handlers[a],c=Array.prototype.slice.call(arguments,1);b&&b.forEach(function(a){a.apply(null,c)})}}},a.util.extend(a,a.Observer),a.WebAudio={scriptBufferSize:256,PLAYING_STATE:0,PAUSED_STATE:1,FINISHED_STATE:2,supportsWebAudio:function(){return!(!window.AudioContext&&!window.webkitAudioContext)},getAudioContext:function(){return a.WebAudio.audioContext||(a.WebAudio.audioContext=new(window.AudioContext||window.webkitAudioContext)),a.WebAudio.audioContext},getOfflineAudioContext:function(b){return a.WebAudio.offlineAudioContext||(a.WebAudio.offlineAudioContext=new(window.OfflineAudioContext||window.webkitOfflineAudioContext)(1,2,b)),a.WebAudio.offlineAudioContext},init:function(b){this.params=b,this.ac=b.audioContext||this.getAudioContext(),this.lastPlay=this.ac.currentTime,this.startPosition=0,this.scheduledPause=null,this.states=[Object.create(a.WebAudio.state.playing),Object.create(a.WebAudio.state.paused),Object.create(a.WebAudio.state.finished)],this.createVolumeNode(),this.createScriptNode(),this.createAnalyserNode(),this.setState(this.PAUSED_STATE),this.setPlaybackRate(this.params.audioRate),this.setLength(0)},disconnectFilters:function(){this.filters&&(this.filters.forEach(function(a){a&&a.disconnect()}),this.filters=null,this.analyser.connect(this.gainNode))},setState:function(a){this.state!==this.states[a]&&(this.state=this.states[a],this.state.init.call(this))},setFilter:function(){this.setFilters([].slice.call(arguments))},setFilters:function(a){this.disconnectFilters(),a&&a.length&&(this.filters=a,this.analyser.disconnect(),a.reduce(function(a,b){return a.connect(b),b},this.analyser).connect(this.gainNode))},createScriptNode:function(){this.ac.createScriptProcessor?this.scriptNode=this.ac.createScriptProcessor(this.scriptBufferSize):this.scriptNode=this.ac.createJavaScriptNode(this.scriptBufferSize),this.scriptNode.connect(this.ac.destination)},addOnAudioProcess:function(){var a=this;this.scriptNode.onaudioprocess=function(){var b=a.getCurrentTime();b>=a.getDuration()?(a.setState(a.FINISHED_STATE),a.fireEvent("pause")):b>=a.scheduledPause?a.pause():a.state===a.states[a.PLAYING_STATE]&&a.fireEvent("audioprocess",b)}},removeOnAudioProcess:function(){this.scriptNode.onaudioprocess=null},createAnalyserNode:function(){this.analyser=this.ac.createAnalyser(),this.analyser.connect(this.gainNode)},createVolumeNode:function(){this.ac.createGain?this.gainNode=this.ac.createGain():this.gainNode=this.ac.createGainNode(),this.gainNode.connect(this.ac.destination)},setVolume:function(a){this.gainNode.gain.value=a},getVolume:function(){return this.gainNode.gain.value},decodeArrayBuffer:function(a,b,c){this.offlineAc||(this.offlineAc=this.getOfflineAudioContext(this.ac?this.ac.sampleRate:44100)),this.offlineAc.decodeAudioData(a,function(a){b(a)}.bind(this),c)},setPeaks:function(a){this.peaks=a},setLength:function(a){if(!this.mergedPeaks||a!=2*this.mergedPeaks.length-1+2){this.splitPeaks=[],this.mergedPeaks=[];for(var b=this.buffer?this.buffer.numberOfChannels:1,c=0;c<b;c++)this.splitPeaks[c]=[],this.splitPeaks[c][2*(a-1)]=0,this.splitPeaks[c][2*(a-1)+1]=0;this.mergedPeaks[2*(a-1)]=0,this.mergedPeaks[2*(a-1)+1]=0}},getPeaks:function(a,b,c){if(this.peaks)return this.peaks;this.setLength(a);for(var d=this.buffer.length/a,e=~~(d/10)||1,f=this.buffer.numberOfChannels,g=0;g<f;g++)for(var h=this.splitPeaks[g],i=this.buffer.getChannelData(g),j=b;j<=c;j++){for(var k=~~(j*d),l=~~(k+d),m=0,n=0,o=k;o<l;o+=e){var p=i[o];p>n&&(n=p),p<m&&(m=p)}h[2*j]=n,h[2*j+1]=m,(0==g||n>this.mergedPeaks[2*j])&&(this.mergedPeaks[2*j]=n),(0==g||m<this.mergedPeaks[2*j+1])&&(this.mergedPeaks[2*j+1]=m)}return this.params.splitChannels?this.splitPeaks:this.mergedPeaks},getPlayedPercents:function(){return this.state.getPlayedPercents.call(this)},disconnectSource:function(){this.source&&this.source.disconnect()},destroy:function(){this.isPaused()||this.pause(),this.unAll(),this.buffer=null,this.disconnectFilters(),this.disconnectSource(),this.gainNode.disconnect(),this.scriptNode.disconnect(),this.analyser.disconnect(),this.params.closeAudioContext&&("function"==typeof this.ac.close&&"closed"!=this.ac.state&&this.ac.close(),this.ac=null,this.params.audioContext?this.params.audioContext=null:a.WebAudio.audioContext=null,a.WebAudio.offlineAudioContext=null)},load:function(a){this.startPosition=0,this.lastPlay=this.ac.currentTime,this.buffer=a,this.createSource()},createSource:function(){this.disconnectSource(),this.source=this.ac.createBufferSource(),this.source.start=this.source.start||this.source.noteGrainOn,this.source.stop=this.source.stop||this.source.noteOff,this.source.playbackRate.value=this.playbackRate,this.source.buffer=this.buffer,this.source.connect(this.analyser)},isPaused:function(){return this.state!==this.states[this.PLAYING_STATE]},getDuration:function(){return this.buffer?this.buffer.duration:0},seekTo:function(a,b){if(this.buffer)return this.scheduledPause=null,null==a&&(a=this.getCurrentTime(),a>=this.getDuration()&&(a=0)),null==b&&(b=this.getDuration()),this.startPosition=a,this.lastPlay=this.ac.currentTime,this.state===this.states[this.FINISHED_STATE]&&this.setState(this.PAUSED_STATE),{start:a,end:b}},getPlayedTime:function(){return(this.ac.currentTime-this.lastPlay)*this.playbackRate},play:function(a,b){if(this.buffer){this.createSource();var c=this.seekTo(a,b);a=c.start,b=c.end,this.scheduledPause=b,this.source.start(0,a,b-a),"suspended"==this.ac.state&&this.ac.resume&&this.ac.resume(),this.setState(this.PLAYING_STATE),this.fireEvent("play")}},pause:function(){this.scheduledPause=null,this.startPosition+=this.getPlayedTime(),this.source&&this.source.stop(0),this.setState(this.PAUSED_STATE),this.fireEvent("pause")},getCurrentTime:function(){return this.state.getCurrentTime.call(this)},getPlaybackRate:function(){return this.playbackRate},setPlaybackRate:function(a){a=a||1,this.isPaused()?this.playbackRate=a:(this.pause(),this.playbackRate=a,this.play())}},a.WebAudio.state={},a.WebAudio.state.playing={init:function(){this.addOnAudioProcess()},getPlayedPercents:function(){var a=this.getDuration();return this.getCurrentTime()/a||0},getCurrentTime:function(){return this.startPosition+this.getPlayedTime()}},a.WebAudio.state.paused={init:function(){this.removeOnAudioProcess()},getPlayedPercents:function(){var a=this.getDuration();return this.getCurrentTime()/a||0},getCurrentTime:function(){return this.startPosition}},a.WebAudio.state.finished={init:function(){this.removeOnAudioProcess(),this.fireEvent("finish")},getPlayedPercents:function(){return 1},getCurrentTime:function(){return this.getDuration()}},a.util.extend(a.WebAudio,a.Observer),a.MediaElement=Object.create(a.WebAudio),a.util.extend(a.MediaElement,{init:function(a){this.params=a,this.media={currentTime:0,duration:0,paused:!0,playbackRate:1,play:function(){},pause:function(){}},this.mediaType=a.mediaType.toLowerCase(),this.elementPosition=a.elementPosition,this.setPlaybackRate(this.params.audioRate),this.createTimer()},createTimer:function(){var a=this,b=function(){if(!a.isPaused()){a.fireEvent("audioprocess",a.getCurrentTime());var c=window.requestAnimationFrame||window.webkitRequestAnimationFrame;c(b)}};this.on("play",b)},load:function(a,b,c,d){var e=document.createElement(this.mediaType);e.controls=this.params.mediaControls,e.autoplay=this.params.autoplay||!1,e.preload=null==d?"auto":d,e.src=a,e.style.width="100%";var f=b.querySelector(this.mediaType);f&&b.removeChild(f),b.appendChild(e),this._load(e,c)},loadElt:function(a,b){var c=a;c.controls=this.params.mediaControls,c.autoplay=this.params.autoplay||!1,this._load(c,b)},_load:function(a,b){var c=this;"function"==typeof a.load&&a.load(),a.addEventListener("error",function(){c.fireEvent("error","Error loading media element")}),a.addEventListener("canplay",function(){c.fireEvent("canplay")}),a.addEventListener("ended",function(){c.fireEvent("finish")}),this.media=a,this.peaks=b,this.onPlayEnd=null,this.buffer=null,this.setPlaybackRate(this.playbackRate)},isPaused:function(){return!this.media||this.media.paused},getDuration:function(){var a=(this.buffer||this.media).duration;return a>=1/0&&(a=this.media.seekable.end(0)),a},getCurrentTime:function(){return this.media&&this.media.currentTime},getPlayedPercents:function(){return this.getCurrentTime()/this.getDuration()||0},getPlaybackRate:function(){return this.playbackRate||this.media.playbackRate},setPlaybackRate:function(a){this.playbackRate=a||1,this.media.playbackRate=this.playbackRate},seekTo:function(a){null!=a&&(this.media.currentTime=a),this.clearPlayEnd()},play:function(a,b){this.seekTo(a),this.media.play(),b&&this.setPlayEnd(b),this.fireEvent("play")},pause:function(){this.media&&this.media.pause(),this.clearPlayEnd(),this.fireEvent("pause")},setPlayEnd:function(a){var b=this;this.onPlayEnd=function(c){c>=a&&(b.pause(),b.seekTo(a))},this.on("audioprocess",this.onPlayEnd)},clearPlayEnd:function(){this.onPlayEnd&&(this.un("audioprocess",this.onPlayEnd),this.onPlayEnd=null)},getPeaks:function(b,c,d){return this.buffer?a.WebAudio.getPeaks.call(this,b,c,d):this.peaks||[]},getVolume:function(){return this.media.volume},setVolume:function(a){this.media.volume=a},destroy:function(){this.pause(),this.unAll(),this.media&&this.media.parentNode&&this.media.parentNode.removeChild(this.media),this.media=null}}),a.AudioElement=a.MediaElement,a.Drawer={init:function(a,b){this.container=a,this.params=b,this.width=0,this.height=b.height*this.params.pixelRatio,this.lastPos=0,this.initDrawer(b),this.createWrapper(),this.createElements()},createWrapper:function(){this.wrapper=this.container.appendChild(document.createElement("wave")),this.style(this.wrapper,{display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none",height:this.params.height+"px"}),(this.params.fillParent||this.params.scrollParent)&&this.style(this.wrapper,{width:"100%",overflowX:this.params.hideScrollbar?"hidden":"auto",overflowY:"hidden"}),this.setupWrapperEvents()},handleEvent:function(a,b){!b&&a.preventDefault();var c,d=a.targetTouches?a.targetTouches[0].clientX:a.clientX,e=this.wrapper.getBoundingClientRect(),f=this.width,g=this.getWidth();return!this.params.fillParent&&f<g?(c=(d-e.left)*this.params.pixelRatio/f||0,c>1&&(c=1)):c=(d-e.left+this.wrapper.scrollLeft)/this.wrapper.scrollWidth||0,c},setupWrapperEvents:function(){var a=this;this.wrapper.addEventListener("click",function(b){var c=a.wrapper.offsetHeight-a.wrapper.clientHeight;if(0!=c){var d=a.wrapper.getBoundingClientRect();if(b.clientY>=d.bottom-c)return}a.params.interact&&a.fireEvent("click",b,a.handleEvent(b))}),this.wrapper.addEventListener("scroll",function(b){a.fireEvent("scroll",b)})},drawPeaks:function(a,b,c,d){this.setWidth(b),this.params.barWidth?this.drawBars(a,0,c,d):this.drawWave(a,0,c,d)},style:function(a,b){return Object.keys(b).forEach(function(c){a.style[c]!==b[c]&&(a.style[c]=b[c])}),a},resetScroll:function(){null!==this.wrapper&&(this.wrapper.scrollLeft=0)},recenter:function(a){var b=this.wrapper.scrollWidth*a;this.recenterOnPosition(b,!0)},recenterOnPosition:function(a,b){var c=this.wrapper.scrollLeft,d=~~(this.wrapper.clientWidth/2),e=a-d,f=e-c,g=this.wrapper.scrollWidth-this.wrapper.clientWidth;if(0!=g){if(!b&&-d<=f&&f<d){var h=5;f=Math.max(-h,Math.min(h,f)),e=c+f}e=Math.max(0,Math.min(g,e)),e!=c&&(this.wrapper.scrollLeft=e)}},getScrollX:function(){return Math.round(this.wrapper.scrollLeft*this.params.pixelRatio)},getWidth:function(){return Math.round(this.container.clientWidth*this.params.pixelRatio)},setWidth:function(a){this.width!=a&&(this.width=a,this.params.fillParent||this.params.scrollParent?this.style(this.wrapper,{width:""}):this.style(this.wrapper,{width:~~(this.width/this.params.pixelRatio)+"px"}),this.updateSize())},setHeight:function(a){a!=this.height&&(this.height=a,this.style(this.wrapper,{height:~~(this.height/this.params.pixelRatio)+"px"}),this.updateSize())},progress:function(a){var b=1/this.params.pixelRatio,c=Math.round(a*this.width)*b;if(c<this.lastPos||c-this.lastPos>=b){if(this.lastPos=c,this.params.scrollParent&&this.params.autoCenter){var d=~~(this.wrapper.scrollWidth*a);this.recenterOnPosition(d)}this.updateProgress(c)}},destroy:function(){this.unAll(),this.wrapper&&(this.container.removeChild(this.wrapper),this.wrapper=null)},initDrawer:function(){},createElements:function(){},updateSize:function(){},drawWave:function(a,b){},clearWave:function(){},updateProgress:function(a){}},a.util.extend(a.Drawer,a.Observer),a.Drawer.Canvas=Object.create(a.Drawer),a.util.extend(a.Drawer.Canvas,{createElements:function(){var a=this.wrapper.appendChild(this.style(document.createElement("canvas"),{position:"absolute",zIndex:1,left:0,top:0,bottom:0}));if(this.waveCc=a.getContext("2d"),this.progressWave=this.wrapper.appendChild(this.style(document.createElement("wave"),{position:"absolute",zIndex:2,left:0,top:0,bottom:0,overflow:"hidden",width:"0",display:"none",boxSizing:"border-box",borderRightStyle:"solid",borderRightWidth:this.params.cursorWidth+"px",borderRightColor:this.params.cursorColor})),this.params.waveColor!=this.params.progressColor){var b=this.progressWave.appendChild(document.createElement("canvas"));this.progressCc=b.getContext("2d")}},updateSize:function(){var a=Math.round(this.width/this.params.pixelRatio);this.waveCc.canvas.width=this.width,this.waveCc.canvas.height=this.height,this.style(this.waveCc.canvas,{width:a+"px"}),this.style(this.progressWave,{display:"block"}),this.progressCc&&(this.progressCc.canvas.width=this.width,this.progressCc.canvas.height=this.height,this.style(this.progressCc.canvas,{width:a+"px"})),this.clearWave()},clearWave:function(){this.waveCc.clearRect(0,0,this.width,this.height),this.progressCc&&this.progressCc.clearRect(0,0,this.width,this.height)},drawBars:function(b,c,d,e){var f=this;if(b[0]instanceof Array){var g=b;if(this.params.splitChannels)return this.setHeight(g.length*this.params.height*this.params.pixelRatio),void g.forEach(function(a,b){f.drawBars(a,b,d,e)});b=g[0]}var h=[].some.call(b,function(a){return a<0}),i=1;h&&(i=2);var j=.5/this.params.pixelRatio,k=this.width,l=this.params.height*this.params.pixelRatio,m=l*c||0,n=l/2,o=b.length/i,p=this.params.barWidth*this.params.pixelRatio,q=Math.max(this.params.pixelRatio,~~(p/2)),r=p+q,s=1/this.params.barHeight;if(this.params.normalize){var t=a.util.max(b),u=a.util.min(b);s=-u>t?-u:t}var v=o/k;this.waveCc.fillStyle=this.params.waveColor,this.progressCc&&(this.progressCc.fillStyle=this.params.progressColor),[this.waveCc,this.progressCc].forEach(function(a){if(a)for(var c=d/v;c<e/v;c+=r){var f=b[Math.floor(c*v*i)]||0,g=Math.round(f/s*n);a.fillRect(c+j,n-g+m,p+j,2*g)}},this)},drawWave:function(b,c,d,e){var f=this;if(b[0]instanceof Array){var g=b;if(this.params.splitChannels)return this.setHeight(g.length*this.params.height*this.params.pixelRatio),void g.forEach(function(a,b){f.drawWave(a,b,d,e)});b=g[0]}var h=[].some.call(b,function(a){return a<0});if(!h){for(var i=[],j=0,k=b.length;j<k;j++)i[2*j]=b[j],i[2*j+1]=-b[j];b=i}var l=.5/this.params.pixelRatio,m=this.params.height*this.params.pixelRatio,n=m*c||0,o=m/2,p=~~(b.length/2),q=1;this.params.fillParent&&this.width!=p&&(q=this.width/p);var r=1/this.params.barHeight;if(this.params.normalize){var s=a.util.max(b),t=a.util.min(b);r=-t>s?-t:s}this.waveCc.fillStyle=this.params.waveColor,this.progressCc&&(this.progressCc.fillStyle=this.params.progressColor),[this.waveCc,this.progressCc].forEach(function(a){if(a){a.beginPath(),a.moveTo(d*q+l,o+n);for(var c=d;c<e;c++){var f=Math.round(b[2*c]/r*o);a.lineTo(c*q+l,o-f+n)}for(var c=e-1;c>=d;c--){var f=Math.round(b[2*c+1]/r*o);a.lineTo(c*q+l,o-f+n)}a.closePath(),a.fill(),a.fillRect(0,o+n-l,this.width,l)}},this)},updateProgress:function(a){this.style(this.progressWave,{width:a+"px"})},getImage:function(a,b){return this.waveCc.canvas.toDataURL(a,b)}}),a.Drawer.MultiCanvas=Object.create(a.Drawer),a.util.extend(a.Drawer.MultiCanvas,{initDrawer:function(a){if(this.maxCanvasWidth=null!=a.maxCanvasWidth?a.maxCanvasWidth:4e3,this.maxCanvasElementWidth=Math.round(this.maxCanvasWidth/this.params.pixelRatio),this.maxCanvasWidth<=1)throw"maxCanvasWidth must be greater than 1.";if(this.maxCanvasWidth%2==1)throw"maxCanvasWidth must be an even number.";this.hasProgressCanvas=this.params.waveColor!=this.params.progressColor,this.halfPixel=.5/this.params.pixelRatio,this.canvases=[]},createElements:function(){this.progressWave=this.wrapper.appendChild(this.style(document.createElement("wave"),{position:"absolute",zIndex:2,left:0,top:0,bottom:0,overflow:"hidden",width:"0",display:"none",boxSizing:"border-box",borderRightStyle:"solid",borderRightWidth:this.params.cursorWidth+"px",borderRightColor:this.params.cursorColor})),this.addCanvas()},updateSize:function(){for(var a=Math.round(this.width/this.params.pixelRatio),b=Math.ceil(a/this.maxCanvasElementWidth);this.canvases.length<b;)this.addCanvas();for(;this.canvases.length>b;)this.removeCanvas();for(var c in this.canvases){var d=this.maxCanvasWidth+2*Math.ceil(this.params.pixelRatio/2);c==this.canvases.length-1&&(d=this.width-this.maxCanvasWidth*(this.canvases.length-1)),this.updateDimensions(this.canvases[c],d,this.height),this.clearWaveForEntry(this.canvases[c])}},addCanvas:function(){var a={},b=this.maxCanvasElementWidth*this.canvases.length;a.wave=this.wrapper.appendChild(this.style(document.createElement("canvas"),{position:"absolute",zIndex:1,left:b+"px",top:0,bottom:0,height:"100%"})),a.waveCtx=a.wave.getContext("2d"),this.hasProgressCanvas&&(a.progress=this.progressWave.appendChild(this.style(document.createElement("canvas"),{position:"absolute",left:b+"px",top:0,bottom:0,height:"100%"})),a.progressCtx=a.progress.getContext("2d")),this.canvases.push(a)},removeCanvas:function(){var a=this.canvases.pop();a.wave.parentElement.removeChild(a.wave),this.hasProgressCanvas&&a.progress.parentElement.removeChild(a.progress)},updateDimensions:function(a,b,c){var d=Math.round(b/this.params.pixelRatio),e=Math.round(this.width/this.params.pixelRatio);a.start=a.waveCtx.canvas.offsetLeft/e||0,a.end=a.start+d/e,a.waveCtx.canvas.width=b,a.waveCtx.canvas.height=c,this.style(a.waveCtx.canvas,{width:d+"px"}),this.style(this.progressWave,{display:"block"}),this.hasProgressCanvas&&(a.progressCtx.canvas.width=b,a.progressCtx.canvas.height=c,this.style(a.progressCtx.canvas,{width:d+"px"}))},clearWave:function(){for(var a in this.canvases)this.clearWaveForEntry(this.canvases[a])},clearWaveForEntry:function(a){a.waveCtx.clearRect(0,0,a.waveCtx.canvas.width,a.waveCtx.canvas.height),this.hasProgressCanvas&&a.progressCtx.clearRect(0,0,a.progressCtx.canvas.width,a.progressCtx.canvas.height)},drawBars:function(b,c,d,e){var f=this;if(b[0]instanceof Array){var g=b;if(this.params.splitChannels)return this.setHeight(g.length*this.params.height*this.params.pixelRatio),void g.forEach(function(a,b){f.drawBars(a,b,d,e)});b=g[0]}var h=[].some.call(b,function(a){return a<0}),i=1;h&&(i=2);var j=this.width,k=this.params.height*this.params.pixelRatio,l=k*c||0,m=k/2,n=b.length/i,o=this.params.barWidth*this.params.pixelRatio,p=Math.max(this.params.pixelRatio,~~(o/2)),q=o+p,r=1/this.params.barHeight;if(this.params.normalize){var s=a.util.max(b),t=a.util.min(b);r=-t>s?-t:s}for(var u=n/j,v=d/u;v<e/u;v+=q){var w=b[Math.floor(v*u*i)]||0,x=Math.round(w/r*m);this.fillRect(v+this.halfPixel,m-x+l,o+this.halfPixel,2*x)}},drawWave:function(b,c,d,e){var f=this;if(b[0]instanceof Array){var g=b;if(this.params.splitChannels)return this.setHeight(g.length*this.params.height*this.params.pixelRatio),void g.forEach(function(a,b){f.drawWave(a,b,d,e)});b=g[0]}var h=[].some.call(b,function(a){return a<0});if(!h){for(var i=[],j=0,k=b.length;j<k;j++)i[2*j]=b[j],i[2*j+1]=-b[j];b=i}var l=this.params.height*this.params.pixelRatio,m=l*c||0,n=l/2,o=1/this.params.barHeight;if(this.params.normalize){var p=a.util.max(b),q=a.util.min(b);o=-q>p?-q:p}this.drawLine(b,o,n,m,d,e),this.fillRect(0,n+m-this.halfPixel,this.width,this.halfPixel)},drawLine:function(a,b,c,d,e,f){for(var g in this.canvases){var h=this.canvases[g];this.setFillStyles(h),this.drawLineToContext(h,h.waveCtx,a,b,c,d,e,f),this.drawLineToContext(h,h.progressCtx,a,b,c,d,e,f)}},drawLineToContext:function(a,b,c,d,e,f,g,h){if(b){var i=c.length/2,j=1;this.params.fillParent&&this.width!=i&&(j=this.width/i);var k=Math.round(i*a.start),l=Math.round(i*a.end);if(!(k>h||l<g)){var m=Math.max(k,g),n=Math.min(l,h);b.beginPath(),b.moveTo((m-k)*j+this.halfPixel,e+f);for(var o=m;o<n;o++){var p=c[2*o]||0,q=Math.round(p/d*e);b.lineTo((o-k)*j+this.halfPixel,e-q+f)}for(var o=n-1;o>=m;o--){var p=c[2*o+1]||0,q=Math.round(p/d*e);b.lineTo((o-k)*j+this.halfPixel,e-q+f)}b.closePath(),b.fill()}}},fillRect:function(a,b,c,d){for(var e=Math.floor(a/this.maxCanvasWidth),f=Math.min(Math.ceil((a+c)/this.maxCanvasWidth)+1,this.canvases.length),g=e;g<f;g++){var h=this.canvases[g],i=g*this.maxCanvasWidth,j={x1:Math.max(a,g*this.maxCanvasWidth),y1:b,x2:Math.min(a+c,g*this.maxCanvasWidth+h.waveCtx.canvas.width),y2:b+d};j.x1<j.x2&&(this.setFillStyles(h),this.fillRectToContext(h.waveCtx,j.x1-i,j.y1,j.x2-j.x1,j.y2-j.y1),this.fillRectToContext(h.progressCtx,j.x1-i,j.y1,j.x2-j.x1,j.y2-j.y1))}},fillRectToContext:function(a,b,c,d,e){a&&a.fillRect(b,c,d,e)},setFillStyles:function(a){a.waveCtx.fillStyle=this.params.waveColor,this.hasProgressCanvas&&(a.progressCtx.fillStyle=this.params.progressColor)},updateProgress:function(a){this.style(this.progressWave,{width:a+"px"})},getImage:function(a,b){var c=[];return this.canvases.forEach(function(d){c.push(d.wave.toDataURL(a,b))}),c.length>1?c:c[0]}}),a.Drawer.SplitWavePointPlot=Object.create(a.Drawer.Canvas),a.util.extend(a.Drawer.SplitWavePointPlot,{defaultPlotParams:{plotNormalizeTo:"whole",plotTimeStart:0,plotMin:0, plotMax:1,plotColor:"#f63",plotProgressColor:"#F00",plotPointHeight:2,plotPointWidth:2,plotSeparator:!0,plotSeparatorColor:"black",plotRangeDisplay:!1,plotRangeUnits:"",plotRangePrecision:4,plotRangeIgnoreOutliers:!1,plotRangeFontSize:12,plotRangeFontType:"Ariel",waveDrawMedianLine:!0,plotFileDelimiter:"\t"},plotTimeStart:0,plotTimeEnd:-1,plotArrayLoaded:!1,plotArray:[],plotPoints:[],plotMin:0,plotMax:1,initDrawer:function(a){var b=this;for(var c in this.defaultPlotParams)void 0===this.params[c]&&(this.params[c]=this.defaultPlotParams[c]);if(this.plotTimeStart=this.params.plotTimeStart,void 0!==this.params.plotTimeEnd&&(this.plotTimeEnd=this.params.plotTimeEnd),Array.isArray(a.plotArray))this.plotArray=a.plotArray,this.plotArrayLoaded=!0;else{var d=function(a){b.plotArray=a,b.plotArrayLoaded=!0,b.fireEvent("plot_array_loaded")};this.loadPlotArrayFromFile(a.plotFileUrl,d,this.params.plotFileDelimiter)}},drawPeaks:function(a,b,c,d){if(1==this.plotArrayLoaded)this.setWidth(b),this.splitChannels=!0,this.params.height=this.params.height/2,a[0]instanceof Array&&(a=a[0]),this.params.barWidth?this.drawBars(a,1,c,d):this.drawWave(a,1,c,d),this.params.height=2*this.params.height,this.calculatePlots(),this.drawPlots();else{var e=this;e.on("plot-array-loaded",function(){e.drawPeaks(a,b,c,d)})}},drawPlots:function(){var a=this.params.height*this.params.pixelRatio/2,b=.5/this.params.pixelRatio;this.waveCc.fillStyle=this.params.plotColor,this.progressCc&&(this.progressCc.fillStyle=this.params.plotProgressColor);for(var c in this.plotPoints){var d=parseInt(c),e=a-this.params.plotPointHeight-this.plotPoints[c]*(a-this.params.plotPointHeight),f=this.params.plotPointHeight;this.waveCc.fillRect(d,e,this.params.plotPointWidth,f),this.progressCc&&this.progressCc.fillRect(d,e,this.params.plotPointWidth,f)}this.params.plotSeparator&&(this.waveCc.fillStyle=this.params.plotSeparatorColor,this.waveCc.fillRect(0,a,this.width,b)),this.params.plotRangeDisplay&&this.displayPlotRange()},displayPlotRange:function(){var a=this.params.plotRangeFontSize*this.params.pixelRatio,b=this.plotMax.toPrecision(this.params.plotRangePrecision)+" "+this.params.plotRangeUnits,c=this.plotMin.toPrecision(this.params.plotRangePrecision)+" "+this.params.plotRangeUnits;this.waveCc.font=a.toString()+"px "+this.params.plotRangeFontType,this.waveCc.fillText(b,3,a),this.waveCc.fillText(c,3,this.height/2)},calculatePlots:function(){this.plotPoints={},this.calculatePlotTimeEnd();for(var a=[],b=-1,c=0,d=99999999999999,e=0,f=99999999999999,g=this.plotTimeEnd-this.plotTimeStart,h=0;h<this.plotArray.length;h++){var i=this.plotArray[h];if(i.value>c&&(c=i.value),i.value<d&&(d=i.value),i.time>=this.plotTimeStart&&i.time<=this.plotTimeEnd){var j=Math.round(this.width*(i.time-this.plotTimeStart)/g);if(a.push(i.value),j!==b&&a.length>0){var k=this.avg(a);k>e&&(e=k),k<f&&(f=k),this.plotPoints[b]=k,a=[]}b=j}}"whole"==this.params.plotNormalizeTo?(this.plotMin=d,this.plotMax=c):"values"==this.params.plotNormalizeTo?(this.plotMin=this.params.plotMin,this.plotMax=this.params.plotMax):(this.plotMin=f,this.plotMax=e),this.normalizeValues()},normalizeValues:function(){var a={};if("none"!==this.params.plotNormalizeTo){for(var b in this.plotPoints){var c=(this.plotPoints[b]-this.plotMin)/(this.plotMax-this.plotMin);c>1?this.params.plotRangeIgnoreOutliers||(a[b]=1):c<0?this.params.plotRangeIgnoreOutliers||(a[b]=0):a[b]=c}this.plotPoints=a}},loadPlotArrayFromFile:function(b,c,d){void 0===d&&(d="\t");var e=[],f={url:b,responseType:"text"},g=a.util.ajax(f);g.on("load",function(a){if(200==a.currentTarget.status){for(var b=a.currentTarget.responseText.split("\n"),f=0;f<b.length;f++){var g=b[f].split(d);2==g.length&&e.push({time:parseFloat(g[0]),value:parseFloat(g[1])})}c(e)}})},calculatePlotTimeEnd:function(){void 0!==this.params.plotTimeEnd?this.plotTimeEnd=this.params.plotTimeEnd:this.plotTimeEnd=this.plotArray[this.plotArray.length-1].time},avg:function(a){var b=a.reduce(function(a,b){return a+b});return b/a.length}}),a.util.extend(a.Drawer.SplitWavePointPlot,a.Observer),a.PeakCache={init:function(){this.clearPeakCache()},clearPeakCache:function(){this.peakCacheRanges=[],this.peakCacheLength=-1},addRangeToPeakCache:function(a,b,c){a!=this.peakCacheLength&&(this.clearPeakCache(),this.peakCacheLength=a);for(var d=[],e=0;e<this.peakCacheRanges.length&&this.peakCacheRanges[e]<b;)e++;for(e%2==0&&d.push(b);e<this.peakCacheRanges.length&&this.peakCacheRanges[e]<=c;)d.push(this.peakCacheRanges[e]),e++;e%2==0&&d.push(c),d=d.filter(function(a,b,c){return 0==b?a!=c[b+1]:b==c.length-1?a!=c[b-1]:a!=c[b-1]&&a!=c[b+1]}),this.peakCacheRanges=this.peakCacheRanges.concat(d),this.peakCacheRanges=this.peakCacheRanges.sort(function(a,b){return a-b}).filter(function(a,b,c){return 0==b?a!=c[b+1]:b==c.length-1?a!=c[b-1]:a!=c[b-1]&&a!=c[b+1]});var f=[];for(e=0;e<d.length;e+=2)f.push([d[e],d[e+1]]);return f},getCacheRanges:function(){for(var a=[],b=0;b<this.peakCacheRanges.length;b+=2)a.push([this.peakCacheRanges[b],this.peakCacheRanges[b+1]]);return a}},function(){var b=function(){var b=document.querySelectorAll("wavesurfer");Array.prototype.forEach.call(b,function(b){var c=a.util.extend({container:b,backend:"MediaElement",mediaControls:!0},b.dataset);b.style.display="block";var d=a.create(c);if(b.dataset.peaks)var e=JSON.parse(b.dataset.peaks);d.load(b.dataset.url,e)})};"complete"===document.readyState?b():window.addEventListener("load",b)}(),a}); //# sourceMappingURL=wavesurfer.min.js.map ================================================ FILE: public/assets/locales/_.json ================================================ { "ABORTED": "", "ABORT_CURRENT_UPLOADS?": "", "ACTIVITY": "", "ADVANCED": "", "ALL_DONE": "", "ALREADY_EXIST": "", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "", "BEAUTIFUL_URL": "", "BOOKMARK": "", "CAMERA": "", "CANCEL": "", "CANNOT_ESTABLISH_A_CONNECTION": "", "CANT_LOAD_THIS_PICTURE": "", "CANT_USE_FILESYSTEM": "", "CAN_RESHARE": "", "CODE": "", "CONFIGURE": "", "CONFIRM_BY_TYPING": "", "CONNECT": "", "CONNECTION_LOST": "", "COPIED_TO_CLIPBOARD": "", "CREATE_A_NEW_LINK": "", "CREATE_A_TAG": "", "CURRENT": "", "CURRENT_UPLOAD": "", "CUSTOM_LINK_URL": "", "DASHBOARD": "", "DATE": "", "DISPLAY_HIDDEN_FILES": "", "DOESNT_MATCH": "", "DONE": "", "DOWNLOAD": "", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "", "DROP_HERE_TO_UPLOAD": "", "EDITOR": "", "EMBED": "", "EMPTY": "", "ENCRYPTION_KEY": "", "ENDPOINT": "", "ERROR": "", "EXISTING_LINKS": "", "EXPIRATION": "", "EXPORT_AS_{{VALUE}}": "", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "", "HIDE_HIDDEN_FILES": "", "HOME": "", "HOSTNAME*": "", "HOST_KEY": "", "INCORRECT_PASSWORD": "", "INFO": "", "INTERNAL_ERROR": "", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "", "INVALID_ACCOUNT": "", "INVALID_PASSWORD": "", "LAYOUT": "", "LOADING": "", "LOCATION": "", "MISSING_DEPENDENCY": "", "MORE_DETAILS": "", "NAVIGATE": "", "NEW_FILE": "", "NEW_FILE::SHORT": "", "NEW_FOLDER": "", "NEW_FOLDER::SHORT": "", "NO": "", "NOT_ALLOWED": "", "NOT_AUTHORISED": "", "NOT_FOUND": "", "NOT_IMPLEMENTED": "", "NOT_SUPPORTED": "", "NOT_VALID": "", "NUMBER_OF_CONNECTIONS": "", "OK": "", "ONLY_FOR_USERS": "", "OOPS": "", "PASSPHRASE": "", "PASSWORD": "", "PASSWORD_CANT_BE_EMPTY": "", "PATH": "", "PERMISSION_DENIED": "", "PICK_A_MASTER_PASSWORD": "", "PORT": "", "POWERED_BY": "", "PROPERTIES": "", "PROTECT_ACCESS_WITH_A_PASSWORD": "", "QUICK_ACCESS": "", "REGION": "", "REMEMBER_ME": "", "REMOVE": "", "RENAME": "", "RENAME_AS": "", "RESTRICTIONS": "", "RUNNING": "", "SAVE_CURRENT_FILE": "", "SEARCH": "", "SETTINGS": "", "SHARE": "", "SHARED_DRIVE": "", "SKIP_TO_CONTENT": "", "SORT": "", "SORT_BY_DATE": "", "SORT_BY_NAME": "", "SORT_BY_SIZE": "", "SORT_BY_TYPE": "", "STARRED": "", "SUPPORT": "", "TAG": "", "TAGS": "", "THERE_IS_NOTHING_HERE": "", "THE_FILE_{{VALUE}}_WAS_DELETED": "", "THE_FILE_{{VALUE}}_WAS_RENAMED": "", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "", "THE_LINK_WONT_BE_VALID_AFTER": "", "TIMEOUT": "", "TODO": "", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "", "UPLOAD": "", "UPLOADER": "", "USERNAME": "", "VERSION": "", "VIEWER": "", "WAITING": "", "YES": "", "YOUR_EMAIL_ADDRESS": "", "YOUR_FILES": "", "YOUR_MASTER_PASSWORD": "", "YOU_CANT_DO_THAT": "" } ================================================ FILE: public/assets/locales/az.json ================================================ { "ABORTED": "ləğv edildi", "ABORT_CURRENT_UPLOADS?": "cari yükləmə ləğv edilsin?", "ACTIVITY": "Fəaliyyət", "ADVANCED": "qabaqcıl", "ALL_DONE": "hər şey hazırdır", "ALREADY_EXIST": "artıq mövcuddur", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} adlı bir fayl yaradıldı", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} adlı bir qovluq yaradıldı", "BEAUTIFUL_URL": "gözəl_url", "BOOKMARK": "əlfəcin", "CAMERA": "kamera", "CANCEL": "ləğv et", "CANNOT_ESTABLISH_A_CONNECTION": "əlaqə qura bilmir", "CANT_LOAD_THIS_PICTURE": "bu şəkli yükləyə bilməz", "CANT_USE_FILESYSTEM": "fayl sistemindən istifadə edə bilər", "CAN_RESHARE": "yenidən yaşaya bilər", "CODE": "kodu", "CONFIGURE": "konfiqurasiya etmək", "CONFIRM_BY_TYPING": "yazaraq təsdiqləyin", "CONNECT": "qoşulmaq", "CONNECTION_LOST": "bağlantı kəsildi", "COPIED_TO_CLIPBOARD": "panoya kopyalandı", "CREATE_A_NEW_LINK": "yeni bir əlaqə yaradın", "CREATE_A_TAG": "etiket yarat", "CURRENT": "cari", "CURRENT_UPLOAD": "cari yükləmə", "CUSTOM_LINK_URL": "xüsusi link URL", "DASHBOARD": "idarə paneli", "DATE": "Tarix", "DISPLAY_HIDDEN_FILES": "gizli sənədləri göstərin", "DOESNT_MATCH": "uyğun gəlmir", "DONE": "hazır", "DOWNLOAD": "yükləyin", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "dəyişiklikləri saxlamaq istəyirsən", "DROP_HERE_TO_UPLOAD": "yükləmək üçün buraya salın", "EDITOR": "Redaktor", "EMBED": "yerləşdir", "EMPTY": "boş", "ENCRYPTION_KEY": "şifrələmə açarı", "ENDPOINT": "son nöqtə", "ERROR": "səhv", "EXISTING_LINKS": "mövcud bağlantılar", "EXPIRATION": "sona çatması", "EXPORT_AS_{{VALUE}}": "{{VALUE}} olaraq ixrac", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "tez-tez giriş qovluqları burada göstəriləcəkdir", "HIDE_HIDDEN_FILES": "gizli sənədləri gizlət", "HOME": "ana səhifə", "HOSTNAME*": "host adı", "HOST_KEY": "host açarı", "INCORRECT_PASSWORD": "səhv parol", "INFO": "məlumat", "INTERNAL_ERROR": "daxili səhv", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "daxili səhv: {{VALUE}} yarada bilməz", "INVALID_ACCOUNT": "etibarsız hesab", "INVALID_PASSWORD": "etibarsız Şifrə", "LAYOUT": "düzülüş", "LOADING": "yüklənir", "LOCATION": "yeri", "MISSING_DEPENDENCY": "itkin asılılıq", "MORE_DETAILS": "daha çox məlumat", "NAVIGATE": "gedin", "NEW_FILE": "yeni sənəd", "NEW_FILE::SHORT": "yeni sənəd", "NEW_FOLDER": "yeni qovluq", "NEW_FOLDER::SHORT": "yeni qovluq", "NO": "yox", "NOT_ALLOWED": "icazəli deyildir, izinli deyildir, qadağandır", "NOT_AUTHORISED": "səlahiyyətli deyil", "NOT_FOUND": "tapılmadı", "NOT_IMPLEMENTED": "tətbiq olunmayıb", "NOT_SUPPORTED": "dəstəklənmir", "NOT_VALID": "etibarlı deyil", "NUMBER_OF_CONNECTIONS": "əlaqələrin sayı", "OK": "tamam", "ONLY_FOR_USERS": "Yalnız istifadəçilər üçün", "OOPS": "Vay", "PASSPHRASE": "parol", "PASSWORD": "parol", "PASSWORD_CANT_BE_EMPTY": "parol boş ola bilməz", "PATH": "yol", "PERMISSION_DENIED": "icazə rədd edildi", "PICK_A_MASTER_PASSWORD": "usta parol seçin", "PORT": "liman", "POWERED_BY": "ilə təchiz edilmişdir", "PROPERTIES": "xassələri", "PROTECT_ACCESS_WITH_A_PASSWORD": "girişi bir parol ilə qorumaq", "QUICK_ACCESS": "sürətli giriş", "REGION": "bölgə", "REMEMBER_ME": "məni xatırla", "REMOVE": "çıxarın", "RENAME": "adını dəyişdir", "RENAME_AS": "kimi adlandır", "RESTRICTIONS": "məhdudiyyətlər", "RUNNING": "işləyir", "SAVE_CURRENT_FILE": "cari faylı qeyd edin", "SEARCH": "axtarış", "SETTINGS": "parametrləri", "SHARE": "paylaş", "SHARED_DRIVE": "paylaşılmış disk", "SKIP_TO_CONTENT": "Məzmuna keç", "SORT": "sırala", "SORT_BY_DATE": "tarixə görə sırala", "SORT_BY_NAME": "adı ilə sırala", "SORT_BY_SIZE": "Ölçüyə görə sırala", "SORT_BY_TYPE": "növünə görə sırala", "STARRED": "əlfəcin", "SUPPORT": "dəstək", "TAG": "etiket", "TAGS": "etiketlər", "THERE_IS_NOTHING_HERE": "burada heç nə yoxdur", "THE_FILE_{{VALUE}}_WAS_DELETED": "fayl {{VALUE}} silindi", "THE_FILE_{{VALUE}}_WAS_RENAMED": "{{VALUE}} adı dəyişdirildi", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "link panoya kopyalandı", "THE_LINK_WONT_BE_VALID_AFTER": "link sonra etibarlı olmayacaq", "TIMEOUT": "vaxt", "TODO": "etmək", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Trafik sıxlığı, daha sonra yenidən cəhd edin", "UPLOAD": "yüklə", "UPLOADER": "yükləyici", "USERNAME": "istifadəçi adı", "VERSION": "versiya", "VIEWER": "tamaşaçı", "WAITING": "gözləyir", "YES": "bəli", "YOUR_EMAIL_ADDRESS": "e-poçt adresiniz", "YOUR_FILES": "sənədləriniz", "YOUR_MASTER_PASSWORD": "usta parolunuz", "YOU_CANT_DO_THAT": "bunu edə bilməzsən" } ================================================ FILE: public/assets/locales/be.json ================================================ { "ABORTED": "перапынілі", "ABORT_CURRENT_UPLOADS?": "перапыніць бягучую загрузку?", "ACTIVITY": "Дзейнасць", "ADVANCED": "прасунуты", "ALL_DONE": "усё зроблена", "ALREADY_EXIST": "ужо існуюць", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Быў створаны файл з імем {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Была створана папка пад назвай {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "закладка", "CAMERA": "фотаапарат", "CANCEL": "адмяніць", "CANNOT_ESTABLISH_A_CONNECTION": "не ўдаецца ўсталяваць злучэнне", "CANT_LOAD_THIS_PICTURE": "не ўдаецца загрузіць гэтую выяву", "CANT_USE_FILESYSTEM": "можна выкарыстоўваць файлавую сістэму", "CAN_RESHARE": "можа адкрыць агульны доступ", "CODE": "код", "CONFIGURE": "наладзіць", "CONFIRM_BY_TYPING": "пацвердзіце, увёўшы", "CONNECT": "злучыць", "CONNECTION_LOST": "злучэнне страчана", "COPIED_TO_CLIPBOARD": "скапіраваны ў буфер абмену", "CREATE_A_NEW_LINK": "стварыць новую спасылку", "CREATE_A_TAG": "стварыць тэг", "CURRENT": "бягучы", "CURRENT_UPLOAD": "бягучая загрузка", "CUSTOM_LINK_URL": "карыстацкі URL спасылкі", "DASHBOARD": "панэль", "DATE": "даты", "DISPLAY_HIDDEN_FILES": "адлюстроўваць схаваныя файлы", "DOESNT_MATCH": "не супадае", "DONE": "зроблена", "DOWNLOAD": "спампаваць", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Вы хочаце захаваць змены", "DROP_HERE_TO_UPLOAD": "падзенне сюды, каб загрузіць", "EDITOR": "рэдактар", "EMBED": "убудаваць", "EMPTY": "пусты", "ENCRYPTION_KEY": "ключ шыфравання", "ENDPOINT": "канчатковая кропка", "ERROR": "памылка", "EXISTING_LINKS": "існуючыя спасылкі", "EXPIRATION": "тэрмін прыдатнасці", "EXPORT_AS_{{VALUE}}": "экспартаваць як {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Тут будуць паказаны папкі частага доступу", "HIDE_HIDDEN_FILES": "схаваць скрытыя файлы", "HOME": "галоўная старонка", "HOSTNAME*": "імя хаста", "HOST_KEY": "ключ хаста", "INCORRECT_PASSWORD": "няправільны пароль", "INFO": "інфармацыя", "INTERNAL_ERROR": "Унутраная памылка", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "унутраная памылка: немагчыма стварыць {{VALUE}}", "INVALID_ACCOUNT": "несапраўдны ўліковы запіс", "INVALID_PASSWORD": "няправільны пароль", "LAYOUT": "макет", "LOADING": "загрузка", "LOCATION": "месцазнаходжанне", "MISSING_DEPENDENCY": "адсутнічае залежнасць", "MORE_DETAILS": "падрабязней", "NAVIGATE": "арыентавацца", "NEW_FILE": "новы файл", "NEW_FILE::SHORT": "новы файл", "NEW_FOLDER": "новы каталог", "NEW_FOLDER::SHORT": "новы каталог", "NO": "не", "NOT_ALLOWED": "не дазволена", "NOT_AUTHORISED": "не ўпаўнаважаны", "NOT_FOUND": "не знойдзены", "NOT_IMPLEMENTED": "не рэалізаваны", "NOT_SUPPORTED": "не падтрымліваецца", "NOT_VALID": "не дзейнічае", "NUMBER_OF_CONNECTIONS": "колькасць злучэнняў", "OK": "добра", "ONLY_FOR_USERS": "Толькі для карыстальнікаў", "OOPS": "На жаль", "PASSPHRASE": "фразу", "PASSWORD": "пароль", "PASSWORD_CANT_BE_EMPTY": "пусты пароль не можа быць", "PATH": "шлях", "PERMISSION_DENIED": "у доступе адмоўлена", "PICK_A_MASTER_PASSWORD": "выбраць галоўны пароль", "PORT": "порт", "POWERED_BY": "харчаванне ад", "PROPERTIES": "ўласцівасці", "PROTECT_ACCESS_WITH_A_PASSWORD": "абараніць доступ паролем", "QUICK_ACCESS": "хуткі доступ", "REGION": "вобласць", "REMEMBER_ME": "Запомні мяне", "REMOVE": "выдаліць", "RENAME": "пераназваць", "RENAME_AS": "пераназваць як", "RESTRICTIONS": "абмежаванні", "RUNNING": "працуе", "SAVE_CURRENT_FILE": "захаваць бягучы файл", "SEARCH": "пошук", "SETTINGS": "налады", "SHARE": "падзяліцца", "SHARED_DRIVE": "агульны дыск", "SKIP_TO_CONTENT": "Перайсці да змесціва", "SORT": "сартаваць", "SORT_BY_DATE": "Сартаваць па даце", "SORT_BY_NAME": "сартаваць па імені", "SORT_BY_SIZE": "Сартаваць па памеры", "SORT_BY_TYPE": "сартаваць па тыпу", "STARRED": "закладка", "SUPPORT": "падтрымка", "TAG": "тэг", "TAGS": "тэгі", "THERE_IS_NOTHING_HERE": "тут нічога няма", "THE_FILE_{{VALUE}}_WAS_DELETED": "файл {{VALUE}} быў выдалены", "THE_FILE_{{VALUE}}_WAS_RENAMED": "файл {{VALUE}} быў перайменаваны", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "спасылка была скапіявана ў буфер абмену", "THE_LINK_WONT_BE_VALID_AFTER": "пасля гэтага спасылка не будзе сапраўднай", "TIMEOUT": "тайм-аўт", "TODO": "рабіць", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "заторы, паспрабуйце зноў пазней", "UPLOAD": "загрузіць", "UPLOADER": "загрузчык", "USERNAME": "імя карыстальніка", "VERSION": "версія", "VIEWER": "глядач", "WAITING": "чакаюць", "YES": "так", "YOUR_EMAIL_ADDRESS": "Ваш электронны адрас", "YOUR_FILES": "вашы файлы", "YOUR_MASTER_PASSWORD": "ваш галоўны пароль", "YOU_CANT_DO_THAT": "вы не можаце зрабіць гэтага" } ================================================ FILE: public/assets/locales/bg.json ================================================ { "ABORTED": "прекратено", "ABORT_CURRENT_UPLOADS?": "прекратяване на текущото качване?", "ACTIVITY": "Дейност", "ADVANCED": "напреднал", "ALL_DONE": "готово", "ALREADY_EXIST": "вече съществува", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Беше създаден файл с име {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Създадена е папка с име {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "отметка", "CAMERA": "камера", "CANCEL": "отказ", "CANNOT_ESTABLISH_A_CONNECTION": "не може да установи връзка", "CANT_LOAD_THIS_PICTURE": "не може да зареди тази снимка", "CANT_USE_FILESYSTEM": "може да използва файлова система", "CAN_RESHARE": "може да сподели повторно", "CODE": "код", "CONFIGURE": "конфигуриране", "CONFIRM_BY_TYPING": "потвърдете, като напишете", "CONNECT": "Connect", "CONNECTION_LOST": "връзката е загубена", "COPIED_TO_CLIPBOARD": "копиран в клипборда", "CREATE_A_NEW_LINK": "създайте нова връзка", "CREATE_A_TAG": "създайте етикет", "CURRENT": "текущ", "CURRENT_UPLOAD": "текущо качване", "CUSTOM_LINK_URL": "персонализиран URL адрес на връзка", "DASHBOARD": "табло", "DATE": "дата", "DISPLAY_HIDDEN_FILES": "показване на скрити файлове", "DOESNT_MATCH": "не съвпада", "DONE": "Готово", "DOWNLOAD": "Изтегли", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "искате ли да запазите промените", "DROP_HERE_TO_UPLOAD": "пуснете тук, за да качите", "EDITOR": "редактор", "EMBED": "вграждане", "EMPTY": "изпразни", "ENCRYPTION_KEY": "ключ за криптиране", "ENDPOINT": "крайна точка", "ERROR": "грешка", "EXISTING_LINKS": "съществуващи връзки", "EXPIRATION": "изтичане", "EXPORT_AS_{{VALUE}}": "експортиране като {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "тук ще бъдат показани често достъпни папки", "HIDE_HIDDEN_FILES": "скрий скрити файлове", "HOME": "начало", "HOSTNAME*": "име на хост", "HOST_KEY": "хост ключ", "INCORRECT_PASSWORD": "грешна парола", "INFO": "инфо", "INTERNAL_ERROR": "Вътрешна грешка", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "вътрешна грешка: не може да създаде {{VALUE}}", "INVALID_ACCOUNT": "невалиден акаунт", "INVALID_PASSWORD": "невалидна парола", "LAYOUT": "оформление", "LOADING": "зареждане", "LOCATION": "местоположение", "MISSING_DEPENDENCY": "липсваща зависимост", "MORE_DETAILS": "повече подробности", "NAVIGATE": "навигация", "NEW_FILE": "нов файл", "NEW_FILE::SHORT": "нов файл", "NEW_FOLDER": "нова директория", "NEW_FOLDER::SHORT": "нова директория", "NO": "не", "NOT_ALLOWED": "не е позволено", "NOT_AUTHORISED": "не е оторизиран", "NOT_FOUND": "не е намерен", "NOT_IMPLEMENTED": "не се прилага", "NOT_SUPPORTED": "Не се поддържа", "NOT_VALID": "не важи", "NUMBER_OF_CONNECTIONS": "брой връзки", "OK": "Добре", "ONLY_FOR_USERS": "Само за потребители", "OOPS": "Oops", "PASSPHRASE": "фраза за достъп", "PASSWORD": "парола", "PASSWORD_CANT_BE_EMPTY": "паролата не може да бъде празна", "PATH": "път", "PERMISSION_DENIED": "отказано разрешение", "PICK_A_MASTER_PASSWORD": "изберете главна парола", "PORT": "порт", "POWERED_BY": "задвижвани от", "PROPERTIES": "свойства", "PROTECT_ACCESS_WITH_A_PASSWORD": "защити достъпа с парола", "QUICK_ACCESS": "бърз достъп", "REGION": "област", "REMEMBER_ME": "помни ме", "REMOVE": "Премахване", "RENAME": "преименуване", "RENAME_AS": "преименуване като", "RESTRICTIONS": "ограничения", "RUNNING": "работи", "SAVE_CURRENT_FILE": "запишете текущия файл", "SEARCH": "Търсене", "SETTINGS": "настройки", "SHARE": "споделяне", "SHARED_DRIVE": "споделен диск", "SKIP_TO_CONTENT": "Към съдържанието", "SORT": "сортиране", "SORT_BY_DATE": "сортиране по дата", "SORT_BY_NAME": "подредете по име", "SORT_BY_SIZE": "сортиране по размер", "SORT_BY_TYPE": "сортиране по тип", "STARRED": "отметка", "SUPPORT": "поддържа", "TAG": "етикет", "TAGS": "етикети", "THERE_IS_NOTHING_HERE": "тук няма нищо", "THE_FILE_{{VALUE}}_WAS_DELETED": "файлът {{VALUE}} беше изтрит", "THE_FILE_{{VALUE}}_WAS_RENAMED": "файлът {{VALUE}} беше преименуван", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "връзката бе копирана в клипборда", "THE_LINK_WONT_BE_VALID_AFTER": "след това връзката няма да бъде валидна", "TIMEOUT": "изчакване", "TODO": "да направя", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "трафик задръствания, опитайте отново по-късно", "UPLOAD": "качване", "UPLOADER": "качващ", "USERNAME": "потребителско име", "VERSION": "версия", "VIEWER": "зрител", "WAITING": "очакване", "YES": "да", "YOUR_EMAIL_ADDRESS": "Вашата електронна поща", "YOUR_FILES": "вашите файлове", "YOUR_MASTER_PASSWORD": "вашата главна парола", "YOU_CANT_DO_THAT": "не можеш да го направиш" } ================================================ FILE: public/assets/locales/ca.json ================================================ { "ABORTED": "avortat", "ABORT_CURRENT_UPLOADS?": "Avortar la càrrega actual?", "ACTIVITY": "Activitat", "ADVANCED": "avançat", "ALL_DONE": "tot fet", "ALREADY_EXIST": "ja existeix", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Es va crear un fitxer anomenat {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Es va crear una carpeta anomenada {{VALUE}}", "BEAUTIFUL_URL": "bella_url", "BOOKMARK": "marcador", "CAMERA": "càmera", "CANCEL": "cancel·lar", "CANNOT_ESTABLISH_A_CONNECTION": "no pot establir una connexió", "CANT_LOAD_THIS_PICTURE": "No es pot carregar aquesta imatge", "CANT_USE_FILESYSTEM": "pot utilitzar el sistema de fitxers", "CAN_RESHARE": "pot tornar a compartir", "CODE": "codi", "CONFIGURE": "configurar", "CONFIRM_BY_TYPING": "confirmeu escrivint", "CONNECT": "connectar", "CONNECTION_LOST": "connexió perduda", "COPIED_TO_CLIPBOARD": "copiat al porta-retalls", "CREATE_A_NEW_LINK": "crear un enllaç nou", "CREATE_A_TAG": "crear una etiqueta", "CURRENT": "actual", "CURRENT_UPLOAD": "càrrega actual", "CUSTOM_LINK_URL": "URL d'enllaç personalitzat", "DASHBOARD": "panell", "DATE": "data", "DISPLAY_HIDDEN_FILES": "mostrar fitxers ocults", "DOESNT_MATCH": "no coincideix", "DONE": "fet", "DOWNLOAD": "descarregar", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "voleu desar els canvis", "DROP_HERE_TO_UPLOAD": "deixeu-ho aquí per penjar-lo", "EDITOR": "editor", "EMBED": "incrustar", "EMPTY": "buit", "ENCRYPTION_KEY": "clau de xifrat", "ENDPOINT": "punt final", "ERROR": "error", "EXISTING_LINKS": "enllaços existents", "EXPIRATION": "caducitat", "EXPORT_AS_{{VALUE}}": "exporta com a {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "les carpetes d'accés freqüent es mostren aquí", "HIDE_HIDDEN_FILES": "ocultar fitxers ocults", "HOME": "inici", "HOSTNAME*": "nom d'amfitrió", "HOST_KEY": "clau d'amfitrió", "INCORRECT_PASSWORD": "contrasenya incorrecta", "INFO": "info", "INTERNAL_ERROR": "Error intern", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "error intern: no es pot crear un {{VALUE}}", "INVALID_ACCOUNT": "compte no vàlid", "INVALID_PASSWORD": "contrasenya invàlida", "LAYOUT": "disseny", "LOADING": "carregant", "LOCATION": "ubicació", "MISSING_DEPENDENCY": "falta dependència", "MORE_DETAILS": "més detalls", "NAVIGATE": "navegar", "NEW_FILE": "nou fitxer", "NEW_FILE::SHORT": "nou fitxer", "NEW_FOLDER": "nou directori", "NEW_FOLDER::SHORT": "nou directori", "NO": "no", "NOT_ALLOWED": "no permès", "NOT_AUTHORISED": "no autoritzat", "NOT_FOUND": "no trobat", "NOT_IMPLEMENTED": "No implementat", "NOT_SUPPORTED": "no compatible", "NOT_VALID": "no és vàlid", "NUMBER_OF_CONNECTIONS": "nombre de connexions", "OK": "D'acord", "ONLY_FOR_USERS": "Només per als usuaris", "OOPS": "Vaja!", "PASSPHRASE": "frase de contrasenya", "PASSWORD": "contrasenya", "PASSWORD_CANT_BE_EMPTY": "la contrasenya no pot estar buida", "PATH": "Camí", "PERMISSION_DENIED": "permís denegat", "PICK_A_MASTER_PASSWORD": "trieu una contrasenya principal", "PORT": "port", "POWERED_BY": "impulsat per", "PROPERTIES": "propietats", "PROTECT_ACCESS_WITH_A_PASSWORD": "protegir l’accés amb una contrasenya", "QUICK_ACCESS": "accés ràpid", "REGION": "regió", "REMEMBER_ME": "Recorda'm", "REMOVE": "eliminar", "RENAME": "canviar el nom", "RENAME_AS": "canviar el nom com a", "RESTRICTIONS": "restriccions", "RUNNING": "execució", "SAVE_CURRENT_FILE": "desar el fitxer actual", "SEARCH": "cerca", "SETTINGS": "configuració", "SHARE": "compartir", "SHARED_DRIVE": "unitat compartida", "SKIP_TO_CONTENT": "Anar al contingut", "SORT": "ordenar", "SORT_BY_DATE": "ordenar per data", "SORT_BY_NAME": "ordena per nom", "SORT_BY_SIZE": "ordenar per mida", "SORT_BY_TYPE": "ordenar per tipus", "STARRED": "favorit", "SUPPORT": "suport", "TAG": "etiqueta", "TAGS": "etiquetes", "THERE_IS_NOTHING_HERE": "aquí no hi ha res", "THE_FILE_{{VALUE}}_WAS_DELETED": "s'ha suprimit el fitxer {{VALUE}}", "THE_FILE_{{VALUE}}_WAS_RENAMED": "es va canviar el nom del fitxer {{VALUE}}", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "l’enllaç es va copiar al porta-retalls", "THE_LINK_WONT_BE_VALID_AFTER": "l’enllaç no serà vàlid després", "TIMEOUT": "temps d’esperació", "TODO": "fer", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "congestió de trànsit, torna-ho a provar més tard", "UPLOAD": "pujar", "UPLOADER": "carregador", "USERNAME": "nom d'usuari", "VERSION": "versió", "VIEWER": "espectador", "WAITING": "esperant", "YES": "sí", "YOUR_EMAIL_ADDRESS": "la teva adreça de correu electrònic", "YOUR_FILES": "els teus fitxers", "YOUR_MASTER_PASSWORD": "la vostra contrasenya principal", "YOU_CANT_DO_THAT": "No pots fer això" } ================================================ FILE: public/assets/locales/cs.json ================================================ { "ABORTED": "přerušeno", "ABORT_CURRENT_UPLOADS?": "přerušit aktuální nahrávání?", "ACTIVITY": "Aktivita", "ADVANCED": "pokročilý", "ALL_DONE": "vše hotovo", "ALREADY_EXIST": "už existuje", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Byl vytvořen soubor s názvem {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Byla vytvořena složka s názvem {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "záložka", "CAMERA": "Fotoaparát", "CANCEL": "zrušení", "CANNOT_ESTABLISH_A_CONNECTION": "nelze navázat spojení", "CANT_LOAD_THIS_PICTURE": "tento obrázek nelze načíst", "CANT_USE_FILESYSTEM": "může používat souborový systém", "CAN_RESHARE": "může znovu sdílet", "CODE": "kód", "CONFIGURE": "konfigurovat", "CONFIRM_BY_TYPING": "potvrďte zadáním", "CONNECT": "připojit", "CONNECTION_LOST": "připojení ztraceno", "COPIED_TO_CLIPBOARD": "zkopírováno do schránky", "CREATE_A_NEW_LINK": "vytvořit nový odkaz", "CREATE_A_TAG": "vytvořit štítek", "CURRENT": "proud", "CURRENT_UPLOAD": "aktuální nahrávání", "CUSTOM_LINK_URL": "vlastní adresa URL odkazu", "DASHBOARD": "přístrojová deska", "DATE": "datum", "DISPLAY_HIDDEN_FILES": "zobrazit skryté soubory", "DOESNT_MATCH": "neodpovídá", "DONE": "Hotovo", "DOWNLOAD": "stažení", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "chcete uložit změny", "DROP_HERE_TO_UPLOAD": "přetáhněte sem a nahrajte", "EDITOR": "editor", "EMBED": "vložit", "EMPTY": "prázdný", "ENCRYPTION_KEY": "šifrovací klíč", "ENDPOINT": "koncový bod", "ERROR": "chyba", "EXISTING_LINKS": "existující odkazy", "EXPIRATION": "vypršení", "EXPORT_AS_{{VALUE}}": "exportovat jako {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "zde se zobrazí často přístupné složky", "HIDE_HIDDEN_FILES": "skrýt skryté soubory", "HOME": "domů", "HOSTNAME*": "název hostitele", "HOST_KEY": "hostitelský klíč", "INCORRECT_PASSWORD": "nesprávné heslo", "INFO": "informace", "INTERNAL_ERROR": "Vnitřní chyba", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "interní chyba: nelze vytvořit {{VALUE}}", "INVALID_ACCOUNT": "neplatný účet", "INVALID_PASSWORD": "neplatné heslo", "LAYOUT": "rozvržení", "LOADING": "načítání", "LOCATION": "umístění", "MISSING_DEPENDENCY": "chybějící závislost", "MORE_DETAILS": "více podrobností", "NAVIGATE": "navigovat", "NEW_FILE": "nový soubor", "NEW_FILE::SHORT": "nový soubor", "NEW_FOLDER": "nový adresář", "NEW_FOLDER::SHORT": "nový adresář", "NO": "Ne", "NOT_ALLOWED": "nepovoleno", "NOT_AUTHORISED": "není povoleno", "NOT_FOUND": "nenalezeno", "NOT_IMPLEMENTED": "není implementováno", "NOT_SUPPORTED": "není podporováno", "NOT_VALID": "neplatný", "NUMBER_OF_CONNECTIONS": "počet připojení", "OK": "OK", "ONLY_FOR_USERS": "Pouze pro uživatele", "OOPS": "Jejda", "PASSPHRASE": "přístupová fráze", "PASSWORD": "Heslo", "PASSWORD_CANT_BE_EMPTY": "heslo nemůže být prázdné", "PATH": "cesta", "PERMISSION_DENIED": "přístup odepřen", "PICK_A_MASTER_PASSWORD": "vyberte hlavní heslo", "PORT": "port", "POWERED_BY": "poháněno", "PROPERTIES": "vlastnosti", "PROTECT_ACCESS_WITH_A_PASSWORD": "chránit přístup pomocí hesla", "QUICK_ACCESS": "rychlý přístup", "REGION": "oblast", "REMEMBER_ME": "zapamatuj si mě", "REMOVE": "odstranit", "RENAME": "přejmenovat", "RENAME_AS": "přejmenovat jako", "RESTRICTIONS": "omezení", "RUNNING": "běh", "SAVE_CURRENT_FILE": "uložit aktuální soubor", "SEARCH": "Vyhledávání", "SETTINGS": "nastavení", "SHARE": "sdílet", "SHARED_DRIVE": "sdílený disk", "SKIP_TO_CONTENT": "Přejít na obsah", "SORT": "seřadit", "SORT_BY_DATE": "seřadit podle data", "SORT_BY_NAME": "Seřaď dle jména", "SORT_BY_SIZE": "", "SORT_BY_TYPE": "seřadit podle typu", "STARRED": "oblíbené", "SUPPORT": "Podpěra, podpora", "TAG": "štítek", "TAGS": "štítky", "THERE_IS_NOTHING_HERE": "není tu nic", "THE_FILE_{{VALUE}}_WAS_DELETED": "soubor {{VALUE}} byl smazán", "THE_FILE_{{VALUE}}_WAS_RENAMED": "soubor {{VALUE}} byl přejmenován", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "odkaz byl zkopírován do schránky", "THE_LINK_WONT_BE_VALID_AFTER": "odkaz nebude platný po", "TIMEOUT": "Časový limit", "TODO": "dělat", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "dopravní zácpy, zkuste to znovu později", "UPLOAD": "nahrát", "UPLOADER": "uploader", "USERNAME": "uživatelské jméno", "VERSION": "verze", "VIEWER": "divák", "WAITING": "čekání", "YES": "Ano", "YOUR_EMAIL_ADDRESS": "Vaše emailová adresa", "YOUR_FILES": "vaše soubory", "YOUR_MASTER_PASSWORD": "vaše hlavní heslo", "YOU_CANT_DO_THAT": "to nemůžeš udělat" } ================================================ FILE: public/assets/locales/da.json ================================================ { "ABORTED": "aborterede", "ABORT_CURRENT_UPLOADS?": "afbryde den aktuelle upload?", "ACTIVITY": "Aktivitet", "ADVANCED": "fremskreden", "ALL_DONE": "helt færdig", "ALREADY_EXIST": "eksisterer allerede", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "En fil med navnet {{VALUE}} blev oprettet", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "En mappe med navnet {{VALUE}} blev oprettet", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "bogmærke", "CAMERA": "kamera", "CANCEL": "afbestille", "CANNOT_ESTABLISH_A_CONNECTION": "kan ikke oprette en forbindelse", "CANT_LOAD_THIS_PICTURE": "kan ikke indlæse dette billede", "CANT_USE_FILESYSTEM": "kan bruge filsystem", "CAN_RESHARE": "kan videredele", "CODE": "kode", "CONFIGURE": "configure", "CONFIRM_BY_TYPING": "bekræft ved at skrive", "CONNECT": "forbinde", "CONNECTION_LOST": "forbindelsen mistet", "COPIED_TO_CLIPBOARD": "kopieret til udklipsholder", "CREATE_A_NEW_LINK": "oprette et nyt link", "CREATE_A_TAG": "opret et mærke", "CURRENT": "nuværende", "CURRENT_UPLOAD": "nuværende upload", "CUSTOM_LINK_URL": "brugerdefineret link URL", "DASHBOARD": "instrumentbræt", "DATE": "dato", "DISPLAY_HIDDEN_FILES": "vis skjulte filer", "DOESNT_MATCH": "stemmer ikke overens", "DONE": "Færdig", "DOWNLOAD": "Hent", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "vil du gemme ændringerne", "DROP_HERE_TO_UPLOAD": "slip her for at uploade", "EDITOR": "redaktør", "EMBED": "indlejr", "EMPTY": "tom", "ENCRYPTION_KEY": "krypteringsnøgle", "ENDPOINT": "endepunkt", "ERROR": "fejl", "EXISTING_LINKS": "eksisterende links", "EXPIRATION": "udløb", "EXPORT_AS_{{VALUE}}": "eksporter som {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "ofte vises mapper der vises her", "HIDE_HIDDEN_FILES": "skjul skjulte filer", "HOME": "hjem", "HOSTNAME*": "værtsnavn", "HOST_KEY": "værtsnøgle", "INCORRECT_PASSWORD": "forkert kodeord", "INFO": "info", "INTERNAL_ERROR": "Intern fejl", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "intern fejl: kan ikke oprette en {{VALUE}}", "INVALID_ACCOUNT": "ugyldig konto", "INVALID_PASSWORD": "forkert kodeord", "LAYOUT": "layout", "LOADING": "indlæser", "LOCATION": "Beliggenhed", "MISSING_DEPENDENCY": "manglende afhængighed", "MORE_DETAILS": "flere detaljer", "NAVIGATE": "navigere", "NEW_FILE": "ny fil", "NEW_FILE::SHORT": "ny fil", "NEW_FOLDER": "nyt bibliotek", "NEW_FOLDER::SHORT": "nyt bibliotek", "NO": "ingen", "NOT_ALLOWED": "ikke tilladt", "NOT_AUTHORISED": "ikke tilladt", "NOT_FOUND": "ikke fundet", "NOT_IMPLEMENTED": "ikke implementeret", "NOT_SUPPORTED": "ikke understøttet", "NOT_VALID": "ikke gyldig", "NUMBER_OF_CONNECTIONS": "antal forbindelser", "OK": "Okay", "ONLY_FOR_USERS": "Kun til brugere", "OOPS": "Ups", "PASSPHRASE": "løsen", "PASSWORD": "adgangskode", "PASSWORD_CANT_BE_EMPTY": "adgangskode kan ikke være tomt", "PATH": "sti", "PERMISSION_DENIED": "adgang nægtet", "PICK_A_MASTER_PASSWORD": "vælg en hovedadgangskode", "PORT": "Havn", "POWERED_BY": "drevet af", "PROPERTIES": "ejendomme", "PROTECT_ACCESS_WITH_A_PASSWORD": "beskytte adgang med et kodeord", "QUICK_ACCESS": "hurtig adgang", "REGION": "område", "REMEMBER_ME": "Husk mig", "REMOVE": "fjerne", "RENAME": "omdøb", "RENAME_AS": "omdøb som", "RESTRICTIONS": "restriktioner", "RUNNING": "kører", "SAVE_CURRENT_FILE": "gem nuværende fil", "SEARCH": "Søg", "SETTINGS": "indstillinger", "SHARE": "del", "SHARED_DRIVE": "delt drev", "SKIP_TO_CONTENT": "Spring til indhold", "SORT": "sortér", "SORT_BY_DATE": "sorter efter dato", "SORT_BY_NAME": "sorter efter navn", "SORT_BY_SIZE": "sorter efter størrelse", "SORT_BY_TYPE": "sorter efter type", "STARRED": "markeret", "SUPPORT": "support", "TAG": "mærke", "TAGS": "mærker", "THERE_IS_NOTHING_HERE": "der er intet her", "THE_FILE_{{VALUE}}_WAS_DELETED": "filen {{VALUE}} blev slettet", "THE_FILE_{{VALUE}}_WAS_RENAMED": "filen {{VALUE}} blev omdøbt", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "linket blev kopieret på udklipsholderen", "THE_LINK_WONT_BE_VALID_AFTER": "linket er ikke gyldigt efter", "TIMEOUT": "tiden er gået", "TODO": "at gøre", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "trafikpropper, prøv igen senere", "UPLOAD": "upload", "UPLOADER": "uploaderen", "USERNAME": "brugernavn", "VERSION": "version", "VIEWER": "seeren", "WAITING": "venter", "YES": "Ja", "YOUR_EMAIL_ADDRESS": "Din email adresse", "YOUR_FILES": "dine filer", "YOUR_MASTER_PASSWORD": "din hovedadgangskode", "YOU_CANT_DO_THAT": "du kan ikke gøre det" } ================================================ FILE: public/assets/locales/de.json ================================================ { "ABORTED": "abgebrochen", "ABORT_CURRENT_UPLOADS?": "Aktuellen Upload abbrechen?", "ACTIVITY": "Aktivität", "ADVANCED": "Fortgeschritten", "ALL_DONE": "Alles erledigt", "ALREADY_EXIST": "existieren bereits", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Eine Datei mit dem Namen {{VALUE}} wurde erstellt", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Ein Ordner mit dem Namen {{VALUE}} wurde erstellt", "BEAUTIFUL_URL": "schöne URL", "BOOKMARK": "Lesezeichen", "CAMERA": "Kamera", "CANCEL": "Abbrechen", "CANNOT_ESTABLISH_A_CONNECTION": "Kann keine Verbindung herstellen", "CANT_LOAD_THIS_PICTURE": "Kann dieses Bild nicht laden", "CANT_USE_FILESYSTEM": "Kann Dateisystem nicht verwenden", "CAN_RESHARE": "kann neu teilen", "CODE": "Code", "CONFIGURE": "Konfigurieren", "CONFIRM_BY_TYPING": "Bestätige durch eingeben von", "CONNECT": "Verbinden", "CONNECTION_LOST": "Verbindung verloren", "COPIED_TO_CLIPBOARD": "in die Zwischenablage kopiert", "CREATE_A_NEW_LINK": "Erstelle einen neuen Link", "CREATE_A_TAG": "Tag erstellen", "CURRENT": "Aktuell", "CURRENT_UPLOAD": "Aktueller Upload", "CUSTOM_LINK_URL": "benutzerdefinierte Link-URL", "DASHBOARD": "Dashboard", "DATE": "Datum", "DISPLAY_HIDDEN_FILES": "versteckte Dateien anzeigen", "DOESNT_MATCH": "sind nicht gleich", "DONE": "erledigt", "DOWNLOAD": "herunterladen", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Willst du die Änderungen speichern", "DROP_HERE_TO_UPLOAD": "Hierhin ziehen, um hochzuladen", "EDITOR": "Editor", "EMBED": "einbetten", "EMPTY": "leer", "ENCRYPTION_KEY": "Verschlüsselungsschlüssel", "ENDPOINT": "Endpunkt", "ERROR": "Fehler", "EXISTING_LINKS": "vorhandene Links", "EXPIRATION": "Ablauf", "EXPORT_AS_{{VALUE}}": "Export als {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Hier werden Ordner angezeigt, auf die häufig zugegriffen wird", "HIDE_HIDDEN_FILES": "versteckte Dateien verstecken", "HOME": "Startseite", "HOSTNAME*": "Hostname*", "HOST_KEY": "Host-Schlüssel", "INCORRECT_PASSWORD": "falsches Passwort", "INFO": "Info", "INTERNAL_ERROR": "Interner Fehler", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "interner Fehler: {{VALUE}} kann nicht erstellt werden", "INVALID_ACCOUNT": "ungültiger Account", "INVALID_PASSWORD": "Ungültiges Passwort", "LAYOUT": "Layout", "LOADING": "laden", "LOCATION": "Standort", "MISSING_DEPENDENCY": "Fehlende Abhängigkeit", "MORE_DETAILS": "mehr Details", "NAVIGATE": "navigieren", "NEW_FILE": "neue Datei", "NEW_FILE::SHORT": "neue Datei", "NEW_FOLDER": "neues Verzeichnis", "NEW_FOLDER::SHORT": "neues Verz.", "NO": "Nein", "NOT_ALLOWED": "nicht erlaubt", "NOT_AUTHORISED": "nicht autorisiert", "NOT_FOUND": "nicht gefunden", "NOT_IMPLEMENTED": "nicht implementiert", "NOT_SUPPORTED": "nicht unterstützt", "NOT_VALID": "ungültig", "NUMBER_OF_CONNECTIONS": "Anzahl der Verbindungen", "OK": "Ok", "ONLY_FOR_USERS": "Nur für Benutzer", "OOPS": "Hoppla", "PASSPHRASE": "Passphrase", "PASSWORD": "Passwort", "PASSWORD_CANT_BE_EMPTY": "Passwort darf nicht leer sein", "PATH": "Pfad", "PERMISSION_DENIED": "Zugang verweigert", "PICK_A_MASTER_PASSWORD": "Wähle ein Master-Passwort", "PORT": "Port", "POWERED_BY": "unterstützt von", "PROPERTIES": "Eigenschaften", "PROTECT_ACCESS_WITH_A_PASSWORD": "Schütze den Zugriff mit einem Passwort", "QUICK_ACCESS": "schneller Zugriff", "REGION": "Region", "REMEMBER_ME": "angemeldet bleiben", "REMOVE": "entfernen", "RENAME": "umbenennen", "RENAME_AS": "umbenennen als", "RESTRICTIONS": "Beschränkungen", "RUNNING": "läuft", "SAVE_CURRENT_FILE": "Aktuelle Datei speichern", "SEARCH": "Suche", "SETTINGS": "Einstellungen", "SHARE": "teilen", "SHARED_DRIVE": "freigegebenes Laufwerk", "SKIP_TO_CONTENT": "Zum Inhalt springen", "SORT": "sortieren", "SORT_BY_DATE": "nach Datum sortieren", "SORT_BY_NAME": "nach Name sortieren", "SORT_BY_SIZE": "nach Größe sortieren", "SORT_BY_TYPE": "nach Typ sortieren", "STARRED": "markiert", "SUPPORT": "Unterstützung", "TAG": "Tag", "TAGS": "Tags", "THERE_IS_NOTHING_HERE": "Hier ist nichts", "THE_FILE_{{VALUE}}_WAS_DELETED": "Die Datei {{VALUE}} wurde gelöscht", "THE_FILE_{{VALUE}}_WAS_RENAMED": "Die Datei {{VALUE}} wurde umbenannt", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "Der Link wurde in die Zwischenablage kopiert", "THE_LINK_WONT_BE_VALID_AFTER": "Der Link ist danach nicht mehr gültig", "TIMEOUT": "Timeout", "TODO": "To-Do", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Verkehrsstau, versuche es später erneut", "UPLOAD": "hochladen", "UPLOADER": "Hochlader", "USERNAME": "Nutzername", "VERSION": "Version", "VIEWER": "Anzeiger", "WAITING": "warten", "YES": "Ja", "YOUR_EMAIL_ADDRESS": "Deine E-Mail-Adresse", "YOUR_FILES": "Ihre Dateien", "YOUR_MASTER_PASSWORD": "Dein Master-Passwort", "YOU_CANT_DO_THAT": "das kannst du nicht machen" } ================================================ FILE: public/assets/locales/el.json ================================================ { "ABORTED": "ματαιώθηκε", "ABORT_CURRENT_UPLOADS?": "ματαίωση της τρέχουσας μεταφόρτωσης;", "ACTIVITY": "Δραστηριότητα", "ADVANCED": "προχωρημένος", "ALL_DONE": "Όλα τελείωσαν", "ALREADY_EXIST": "υπάρχει ήδη", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Δημιουργήθηκε ένα αρχείο με το όνομα {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Δημιουργήθηκε ένας φάκελος με το όνομα {{VALUE}}", "BEAUTIFUL_URL": "όμορφη_υρ", "BOOKMARK": "σελιδοδείκτης", "CAMERA": "Κάμερα", "CANCEL": "Ματαίωση", "CANNOT_ESTABLISH_A_CONNECTION": "δεν μπορεί να δημιουργήσει σύνδεση", "CANT_LOAD_THIS_PICTURE": "δεν μπορώ να φορτώσω αυτήν την εικόνα", "CANT_USE_FILESYSTEM": "δεν μπορεί να χρησιμοποιήσει το σύστημα αρχείων", "CAN_RESHARE": "μπορεί να αναδημοσιεύσει", "CODE": "κώδικας", "CONFIGURE": "Διαμορφώστε", "CONFIRM_BY_TYPING": "επιβεβαιώστε πληκτρολογώντας", "CONNECT": "συνδέω-συωδεομαι", "CONNECTION_LOST": "η σύνδεση χάθηκε", "COPIED_TO_CLIPBOARD": "αντιγράφηκε στο πρόχειρο", "CREATE_A_NEW_LINK": "δημιουργήστε έναν νέο σύνδεσμο", "CREATE_A_TAG": "δημιουργία ετικέτας", "CURRENT": "Τρέχον", "CURRENT_UPLOAD": "τρέχουσα μεταφόρτωση", "CUSTOM_LINK_URL": "προσαρμοσμένη διεύθυνση URL συνδέσμου", "DASHBOARD": "ταμπλό", "DATE": "ημερομηνία", "DISPLAY_HIDDEN_FILES": "εμφάνιση κρυφών αρχείων", "DOESNT_MATCH": "δεν ταιριάζει", "DONE": "Ολοκληρώθηκε", "DOWNLOAD": "Κατεβάστε", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "θέλετε να αποθηκεύσετε τις αλλαγές", "DROP_HERE_TO_UPLOAD": "ρίξτε εδώ για να ανεβάσετε", "EDITOR": "συντάκτης", "EMBED": "ενσωμάτωση", "EMPTY": "κενό", "ENCRYPTION_KEY": "κλειδί κρυπτογράφησης", "ENDPOINT": "τελικό σημείο", "ERROR": "λάθος", "EXISTING_LINKS": "υπάρχοντες σύνδεσμοι", "EXPIRATION": "λήξη", "EXPORT_AS_{{VALUE}}": "εξαγωγή ως {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "συχνά εμφανίζονται φάκελοι πρόσβασης εδώ", "HIDE_HIDDEN_FILES": "απόκρυψη κρυφών αρχείων", "HOME": "αρχική σελίδα", "HOSTNAME*": "όνομα κεντρικού υπολογιστή", "HOST_KEY": "κλειδί κεντρικού υπολογιστή", "INCORRECT_PASSWORD": "Λάθος κωδικός", "INFO": "πληροφορίες", "INTERNAL_ERROR": "Εσωτερικό σφάλμα", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "εσωτερικό σφάλμα: δεν είναι δυνατή η δημιουργία {{VALUE}}", "INVALID_ACCOUNT": "μη έγκυρος λογαριασμός", "INVALID_PASSWORD": "Λανθασμένος κωδικός", "LAYOUT": "διάταξη", "LOADING": "φόρτωση", "LOCATION": "τοποθεσία", "MISSING_DEPENDENCY": "λείπει εξάρτηση", "MORE_DETAILS": "λεπτομέρειες", "NAVIGATE": "κυβερνώ", "NEW_FILE": "νέο αρχείο", "NEW_FILE::SHORT": "νέο αρχείο", "NEW_FOLDER": "νέος κατάλογος", "NEW_FOLDER::SHORT": "νέος κατάλογος", "NO": "όχι", "NOT_ALLOWED": "δεν επιτρέπεται", "NOT_AUTHORISED": "δεν επιτρέπεται", "NOT_FOUND": "δεν βρέθηκε", "NOT_IMPLEMENTED": "δεν εφαρμόζεται", "NOT_SUPPORTED": "Δεν υποστηρίζεται", "NOT_VALID": "δεν είναι έγκυρη", "NUMBER_OF_CONNECTIONS": "αριθμός συνδέσεων", "OK": "Εντάξει", "ONLY_FOR_USERS": "Μόνο για χρήστες", "OOPS": "Ωχ", "PASSPHRASE": "φράση πρόσβασης", "PASSWORD": "Κωδικός πρόσβασης", "PASSWORD_CANT_BE_EMPTY": "ο κωδικός πρόσβασης δεν μπορεί να είναι κενός", "PATH": "μονοπάτι", "PERMISSION_DENIED": "η άδεια απορρίφθηκε", "PICK_A_MASTER_PASSWORD": "επιλέξτε έναν κύριο κωδικό πρόσβασης", "PORT": "θύρα", "POWERED_BY": "τροφοδοτείται από", "PROPERTIES": "ιδιότητες", "PROTECT_ACCESS_WITH_A_PASSWORD": "προστασία της πρόσβασης με κωδικό πρόσβασης", "QUICK_ACCESS": "άμεση πρόσβαση", "REGION": "περιοχή", "REMEMBER_ME": "Θυμήσου με", "REMOVE": "αφαιρώ", "RENAME": "μετονομασία", "RENAME_AS": "μετονομασία ως", "RESTRICTIONS": "περιορισμοί", "RUNNING": "εκτελείται", "SAVE_CURRENT_FILE": "αποθήκευση τρέχοντος αρχείου", "SEARCH": "Αναζήτηση", "SETTINGS": "Ρυθμίσεις", "SHARE": "κοινοποίηση", "SHARED_DRIVE": "κοινόχρηστος δίσκος", "SKIP_TO_CONTENT": "Μετάβαση στο περιεχόμενο", "SORT": "ταξινόμηση", "SORT_BY_DATE": "ταξινόμηση κατά ημερομηνία", "SORT_BY_NAME": "ταξινόμηση κατά όνομα", "SORT_BY_SIZE": "ταξινόμηση κατά μέγεθος", "SORT_BY_TYPE": "ταξινόμηση ανά τύπο", "STARRED": "αστεράκι", "SUPPORT": "υποστήριξη", "TAG": "ετικέτα", "TAGS": "ετικέτες", "THERE_IS_NOTHING_HERE": "δεν υπάρχει τίποτα εδώ", "THE_FILE_{{VALUE}}_WAS_DELETED": "το αρχείο {{VALUE}} διαγράφηκε", "THE_FILE_{{VALUE}}_WAS_RENAMED": "το αρχείο {{VALUE}} μετονομάστηκε", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "ο σύνδεσμος αντιγράφηκε στο πρόχειρο", "THE_LINK_WONT_BE_VALID_AFTER": "ο σύνδεσμος δεν θα είναι έγκυρος μετά", "TIMEOUT": "τέλος χρόνου", "TODO": "να κάνω", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "κυκλοφοριακή συμφόρηση, δοκιμάστε ξανά αργότερα", "UPLOAD": "μεταφόρτωση", "UPLOADER": "μεταφορτωτής", "USERNAME": "όνομα χρήστη", "VERSION": "έκδοση", "VIEWER": "θεατής", "WAITING": "αναμονή", "YES": "Ναι.", "YOUR_EMAIL_ADDRESS": "η ηλεκτρονική σου διεύθυνση", "YOUR_FILES": "τα αρχεία σας", "YOUR_MASTER_PASSWORD": "τον κύριο κωδικό πρόσβασής σας", "YOU_CANT_DO_THAT": "δεν μπορείς να το κάνεις αυτό" } ================================================ FILE: public/assets/locales/es.json ================================================ { "ABORTED": "abortado", "ABORT_CURRENT_UPLOADS?": "¿cancelar la carga actual?", "ACTIVITY": "Actividad", "ADVANCED": "avanzado", "ALL_DONE": "todo listo", "ALREADY_EXIST": "ya existe", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Se creó un archivo llamado {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Se creó una carpeta llamada {{VALUE}}", "BEAUTIFUL_URL": "URL bonita", "BOOKMARK": "marcador", "CAMERA": "cámara", "CANCEL": "cancelar", "CANNOT_ESTABLISH_A_CONNECTION": "no puedo establecer una conexión", "CANT_LOAD_THIS_PICTURE": "no puedo cargar esta foto", "CANT_USE_FILESYSTEM": "no puede usar el sistema de archivos", "CAN_RESHARE": "puede compartir", "CODE": "código", "CONFIGURE": "configurar", "CONFIRM_BY_TYPING": "confirmar escribiendo", "CONNECT": "conectar", "CONNECTION_LOST": "conexión perdida", "COPIED_TO_CLIPBOARD": "Copiado al portapapeles", "CREATE_A_NEW_LINK": "crear un nuevo enlace", "CREATE_A_TAG": "crear una etiqueta", "CURRENT": "Actual", "CURRENT_UPLOAD": "carga actual", "CUSTOM_LINK_URL": "URL de enlace personalizado", "DASHBOARD": "tablero", "DATE": "fecha", "DISPLAY_HIDDEN_FILES": "mostrar archivos ocultos", "DOESNT_MATCH": "no coincide", "DONE": "hecho", "DOWNLOAD": "descargar", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "quieres guardar los cambios", "DROP_HERE_TO_UPLOAD": "arrastra aquí para subir", "EDITOR": "editor", "EMBED": "incrustar", "EMPTY": "vacío", "ENCRYPTION_KEY": "Clave de encriptación", "ENDPOINT": "punto final", "ERROR": "error", "EXISTING_LINKS": "enlaces existentes", "EXPIRATION": "vencimiento", "EXPORT_AS_{{VALUE}}": "exportar como {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "las carpetas de acceso frecuente se mostrarán aquí", "HIDE_HIDDEN_FILES": "ocultar archivos ocultos", "HOME": "inicio", "HOSTNAME*": "nombre de servidor*", "HOST_KEY": "clave de servidor", "INCORRECT_PASSWORD": "la contraseña no es correcta", "INFO": "información", "INTERNAL_ERROR": "Error interno", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "error interno: no se puede crear un {{VALUE}}", "INVALID_ACCOUNT": "cuenta no válida", "INVALID_PASSWORD": "contraseña inválida", "LAYOUT": "diseño", "LOADING": "cargando", "LOCATION": "ubicación", "MISSING_DEPENDENCY": "falta dependencia", "MORE_DETAILS": "más detalles", "NAVIGATE": "navegar", "NEW_FILE": "nuevo fichero", "NEW_FILE::SHORT": "nuevo fichero", "NEW_FOLDER": "nuevo directorio", "NEW_FOLDER::SHORT": "nuevo direct.", "NO": "No", "NOT_ALLOWED": "No permitido", "NOT_AUTHORISED": "no autorizado", "NOT_FOUND": "no encontrado", "NOT_IMPLEMENTED": "no se ha implementado", "NOT_SUPPORTED": "No soportado", "NOT_VALID": "no es válido", "NUMBER_OF_CONNECTIONS": "cantidad de conexiones", "OK": "Vale", "ONLY_FOR_USERS": "Solo para usuarios", "OOPS": "Ups", "PASSPHRASE": "frase de contraseña", "PASSWORD": "contraseña", "PASSWORD_CANT_BE_EMPTY": "la contraseña no puede estar vacía", "PATH": "camino", "PERMISSION_DENIED": "Permiso denegado", "PICK_A_MASTER_PASSWORD": "elige una contraseña maestra", "PORT": "Puerto", "POWERED_BY": "impulsado por", "PROPERTIES": "propiedades", "PROTECT_ACCESS_WITH_A_PASSWORD": "proteger el acceso con una contraseña", "QUICK_ACCESS": "acceso rápido", "REGION": "región", "REMEMBER_ME": "Recuérdame", "REMOVE": "eliminar", "RENAME": "renombrar", "RENAME_AS": "renombrar como", "RESTRICTIONS": "restricciones", "RUNNING": "ejecutando", "SAVE_CURRENT_FILE": "guardar el archivo actual", "SEARCH": "buscar", "SETTINGS": "ajustes", "SHARE": "compartir", "SHARED_DRIVE": "unidad compartida", "SKIP_TO_CONTENT": "Saltar al contenido", "SORT": "ordenar", "SORT_BY_DATE": "ordenar por fecha", "SORT_BY_NAME": "ordenar por nombre", "SORT_BY_SIZE": "ordenar por tamaño", "SORT_BY_TYPE": "ordenar por tipo", "STARRED": "destacado", "SUPPORT": "apoyo", "TAG": "etiqueta", "TAGS": "etiquetas", "THERE_IS_NOTHING_HERE": "No hay nada aquí", "THE_FILE_{{VALUE}}_WAS_DELETED": "se eliminó el archivo {{VALUE}}", "THE_FILE_{{VALUE}}_WAS_RENAMED": "se cambió el nombre del archivo {{VALUE}}", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "el enlace fue copiado al portapapeles", "THE_LINK_WONT_BE_VALID_AFTER": "el enlace no será válido después", "TIMEOUT": "se acabó el tiempo", "TODO": "todo", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "congestión de tráfico, intenta nuevamente más tarde", "UPLOAD": "subir", "UPLOADER": "cargador", "USERNAME": "nombre de usuario", "VERSION": "versión", "VIEWER": "espectador", "WAITING": "esperando", "YES": "sí", "YOUR_EMAIL_ADDRESS": "tu correo electrónico", "YOUR_FILES": "tus archivos", "YOUR_MASTER_PASSWORD": "tu contraseña maestra", "YOU_CANT_DO_THAT": "no puedes hacer eso" } ================================================ FILE: public/assets/locales/et.json ================================================ { "ABORTED": "katkestatud", "ABORT_CURRENT_UPLOADS?": "kas katkestada praegune üleslaadimine?", "ACTIVITY": "Tegevus", "ADVANCED": "edasijõudnud", "ALL_DONE": "kõik tehtud", "ALREADY_EXIST": "juba olemas", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Loodi fail nimega {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Loodi kaust nimega {{VALUE}}", "BEAUTIFUL_URL": "ilus_url", "BOOKMARK": "järjehoidja", "CAMERA": "kaamera", "CANCEL": "tühista", "CANNOT_ESTABLISH_A_CONNECTION": "ühendust ei saa luua", "CANT_LOAD_THIS_PICTURE": "seda pilti ei saa laadida", "CANT_USE_FILESYSTEM": "ei saa kasutada failisüsteemi", "CAN_RESHARE": "saab edasi jagada", "CODE": "kood", "CONFIGURE": "seadistama", "CONFIRM_BY_TYPING": "kinnitage sisestades", "CONNECT": "ühendada", "CONNECTION_LOST": "ühendus katkes", "COPIED_TO_CLIPBOARD": "kopeeriti lõikelauale", "CREATE_A_NEW_LINK": "loo uus link", "CREATE_A_TAG": "loo silt", "CURRENT": "praegune", "CURRENT_UPLOAD": "praegune üleslaadimine", "CUSTOM_LINK_URL": "kohandatud lingi URL", "DASHBOARD": "armatuurlaud", "DATE": "kuupäev", "DISPLAY_HIDDEN_FILES": "kuva peidetud faile", "DOESNT_MATCH": "ei sobi", "DONE": "tehtud", "DOWNLOAD": "lae alla", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "kas soovite muudatused salvestada?", "DROP_HERE_TO_UPLOAD": "laadimiseks klõpsake siin", "EDITOR": "toimetaja", "EMBED": "manusta", "EMPTY": "tühi", "ENCRYPTION_KEY": "krüptimisvõti", "ENDPOINT": "lõpp-punkt", "ERROR": "viga", "EXISTING_LINKS": "olemasolevad lingid", "EXPIRATION": "aegumine", "EXPORT_AS_{{VALUE}}": "eksportima kui {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "siin kuvatakse sageli juurdepääsuga kaustad", "HIDE_HIDDEN_FILES": "peita peidetud failid", "HOME": "avaleht", "HOSTNAME*": "hostinimi", "HOST_KEY": "host võti", "INCORRECT_PASSWORD": "vale salasõna", "INFO": "info", "INTERNAL_ERROR": "Sisemine viga", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "sisemine viga: {{VALUE}} ei saa luua", "INVALID_ACCOUNT": "kehtetu konto", "INVALID_PASSWORD": "vale parool", "LAYOUT": "paigutus", "LOADING": "laadimine", "LOCATION": "asukoht", "MISSING_DEPENDENCY": "puudub sõltuvus", "MORE_DETAILS": "rohkem üksikasju", "NAVIGATE": "navigeerima", "NEW_FILE": "uus fail", "NEW_FILE::SHORT": "uus fail", "NEW_FOLDER": "uus kataloog", "NEW_FOLDER::SHORT": "uus kataloog", "NO": "ei", "NOT_ALLOWED": "ei ole lubatud", "NOT_AUTHORISED": "pole volitatud", "NOT_FOUND": "ei leitud", "NOT_IMPLEMENTED": "pole rakendatud", "NOT_SUPPORTED": "ei toetata", "NOT_VALID": "ei kehti", "NUMBER_OF_CONNECTIONS": "ühenduste arv", "OK": "Okei", "ONLY_FOR_USERS": "Ainult kasutajatele", "OOPS": "Vabandust", "PASSPHRASE": "parool", "PASSWORD": "parool", "PASSWORD_CANT_BE_EMPTY": "parool ei tohi olla tühi", "PATH": "tee", "PERMISSION_DENIED": "luba on keelatud", "PICK_A_MASTER_PASSWORD": "vali põhiparool", "PORT": "port", "POWERED_BY": "toetatud", "PROPERTIES": "omadused", "PROTECT_ACCESS_WITH_A_PASSWORD": "kaitsta juurdepääsu parooliga", "QUICK_ACCESS": "kiirpääs", "REGION": "piirkonnas", "REMEMBER_ME": "mäleta mind", "REMOVE": "eemalda", "RENAME": "nimeta ümber", "RENAME_AS": "nimeta ümber kui", "RESTRICTIONS": "piirangud", "RUNNING": "töötab", "SAVE_CURRENT_FILE": "salvesta praegune fail", "SEARCH": "otsing", "SETTINGS": "seaded", "SHARE": "jaga", "SHARED_DRIVE": "jagatud ketas", "SKIP_TO_CONTENT": "Sisusse", "SORT": "sorteeri", "SORT_BY_DATE": "järjesta kuupäeva järgi", "SORT_BY_NAME": "järjesta nime järgi", "SORT_BY_SIZE": "sorteeri suuruse järgi", "SORT_BY_TYPE": "sorteeri tüübi järgi", "STARRED": "tärniga märgitud", "SUPPORT": "toetus", "TAG": "silt", "TAGS": "sildid", "THERE_IS_NOTHING_HERE": "siin pole midagi", "THE_FILE_{{VALUE}}_WAS_DELETED": "fail {{VALUE}} kustutati", "THE_FILE_{{VALUE}}_WAS_RENAMED": "fail {{VALUE}} nimetati ümber", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "link kopeeriti lõikelauale", "THE_LINK_WONT_BE_VALID_AFTER": "link ei kehti pärast", "TIMEOUT": "aeg maha", "TODO": "tegema", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "liiklusummikud, proovige hiljem uuesti", "UPLOAD": "laadi üles", "UPLOADER": "üleslaadija", "USERNAME": "kasutajanimi", "VERSION": "versioon", "VIEWER": "vaataja", "WAITING": "ootamine", "YES": "jah", "YOUR_EMAIL_ADDRESS": "sinu emaili aadress", "YOUR_FILES": "sinu failid", "YOUR_MASTER_PASSWORD": "teie põhiparool", "YOU_CANT_DO_THAT": "sa ei saa seda teha" } ================================================ FILE: public/assets/locales/eu.json ================================================ { "ABORTED": "abortatu", "ABORT_CURRENT_UPLOADS?": "Uneko igoera bertan behera utzi?", "ACTIVITY": "Jarduera", "ADVANCED": "aurreratu", "ALL_DONE": "dena eginda", "ALREADY_EXIST": "dagoeneko existitzen dira", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} izeneko fitxategia sortu zen", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} izeneko karpeta sortu da", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "laster-marka", "CAMERA": "kamera", "CANCEL": "bertan behera", "CANNOT_ESTABLISH_A_CONNECTION": "Ezin da konexiorik ezarri", "CANT_LOAD_THIS_PICTURE": "Ezin da irudi hau kargatu", "CANT_USE_FILESYSTEM": "ezin da sistema erabili", "CAN_RESHARE": "birkargatu dezake", "CODE": "kodea", "CONFIGURE": "konfiguratu", "CONFIRM_BY_TYPING": "berretsi idatzita", "CONNECT": "konektatu", "CONNECTION_LOST": "konexioa galdu da", "COPIED_TO_CLIPBOARD": "arbelean kopiatuta", "CREATE_A_NEW_LINK": "lotura berria sortu", "CREATE_A_TAG": "etiketa sortu", "CURRENT": "egungo", "CURRENT_UPLOAD": "Uneko igoera", "CUSTOM_LINK_URL": "esteka URL pertsonalizatua", "DASHBOARD": "Arbel", "DATE": "data", "DISPLAY_HIDDEN_FILES": "erakutsi ezkutuko fitxategiak", "DOESNT_MATCH": "ez dator bat", "DONE": "eginda", "DOWNLOAD": "deskargatu", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "nahi duzu aldaketak gordetzea", "DROP_HERE_TO_UPLOAD": "jaregin hemen kargatzeko", "EDITOR": "editore", "EMBED": "txertatu", "EMPTY": "hutsik", "ENCRYPTION_KEY": "zifratzeko gakoa", "ENDPOINT": "amaiera", "ERROR": "errorea", "EXISTING_LINKS": "dauden loturak", "EXPIRATION": "iraungitze", "EXPORT_AS_{{VALUE}}": "esportatu {{VALUE}} gisa", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "maiz sarbide karpetak hemen erakutsiko dira", "HIDE_HIDDEN_FILES": "ezkutuko fitxategiak ezkutatu", "HOME": "hasiera", "HOSTNAME*": "hostname", "HOST_KEY": "ostalariaren gakoa", "INCORRECT_PASSWORD": "Pasahitz okerra", "INFO": "info", "INTERNAL_ERROR": "Barne-errorea", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "barne errorea: ezin da {{VALUE}} sortu", "INVALID_ACCOUNT": "kontua baliogabea", "INVALID_PASSWORD": "pasahitz okerra", "LAYOUT": "diseinua", "LOADING": "kargatzen", "LOCATION": "kokapena", "MISSING_DEPENDENCY": "mendekotasun falta", "MORE_DETAILS": "xehetasun gehiago", "NAVIGATE": "nabigatu", "NEW_FILE": "fitxategi berria", "NEW_FILE::SHORT": "fitxat. berria", "NEW_FOLDER": "direktorio berria", "NEW_FOLDER::SHORT": "direkt. berria", "NO": "no", "NOT_ALLOWED": "ez da onartzen", "NOT_AUTHORISED": "baimenduta ez", "NOT_FOUND": "ez da aurkitu", "NOT_IMPLEMENTED": "ez da aplikatu", "NOT_SUPPORTED": "ez da onartzen", "NOT_VALID": "ez du balio", "NUMBER_OF_CONNECTIONS": "konexio kopurua", "OK": "Ados", "ONLY_FOR_USERS": "Erabiltzaileentzat bakarrik", "OOPS": "Oops", "PASSPHRASE": "pasaesaldi", "PASSWORD": "pasahitza", "PASSWORD_CANT_BE_EMPTY": "pasahitza ezin da hutsik egon", "PATH": "bidea", "PERMISSION_DENIED": "baimena ukatu", "PICK_A_MASTER_PASSWORD": "aukeratu pasahitz nagusia", "PORT": "portu", "POWERED_BY": "bultzatuta", "PROPERTIES": "propietate", "PROTECT_ACCESS_WITH_A_PASSWORD": "babestu sarbidea pasahitz batekin", "QUICK_ACCESS": "sarrera azkarra", "REGION": "eskualde", "REMEMBER_ME": "Gogora nazazu", "REMOVE": "kendu", "RENAME": "izena aldatu", "RENAME_AS": "izena aldatu honela", "RESTRICTIONS": "murrizketak", "RUNNING": "exekutatzen", "SAVE_CURRENT_FILE": "gorde uneko fitxategia", "SEARCH": "bilatu", "SETTINGS": "ezarpenak", "SHARE": "partekatzea", "SHARED_DRIVE": "partekatutako diskoa", "SKIP_TO_CONTENT": "Joan edukira", "SORT": "ordenatu", "SORT_BY_DATE": "ordenatu dataren arabera", "SORT_BY_NAME": "ordenatu izenaren arabera", "SORT_BY_SIZE": "ordenatu tamainaren arabera", "SORT_BY_TYPE": "ordenatu motaren arabera", "STARRED": "izarduna", "SUPPORT": "laguntza", "TAG": "etiketa", "TAGS": "etiketak", "THERE_IS_NOTHING_HERE": "hemen ez dago ezer", "THE_FILE_{{VALUE}}_WAS_DELETED": "{{VALUE}} fitxategia ezabatu da", "THE_FILE_{{VALUE}}_WAS_RENAMED": "{{VALUE}} fitxategiari berrizendatu zaio", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "esteka arbelean kopiatu zen", "THE_LINK_WONT_BE_VALID_AFTER": "esteka ez da baliozkoa ondoren", "TIMEOUT": "denboraz kanpo", "TODO": "egin", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "trafiko pilaketa, saiatu berriro geroago", "UPLOAD": "igo", "UPLOADER": "igorlea", "USERNAME": "Erabiltzaile izena", "VERSION": "bertsioa", "VIEWER": "ikusle", "WAITING": "itxarote", "YES": "bai", "YOUR_EMAIL_ADDRESS": "Zure helbide elektronikoa", "YOUR_FILES": "zure fitxategiak", "YOUR_MASTER_PASSWORD": "zure pasahitz nagusia", "YOU_CANT_DO_THAT": "ezin duzu hori egin" } ================================================ FILE: public/assets/locales/fi.json ================================================ { "ABORTED": "keskeytetty", "ABORT_CURRENT_UPLOADS?": "keskeytetäänkö nykyiset lähetykset?", "ACTIVITY": "Toiminta", "ADVANCED": "lisäasetukset", "ALL_DONE": "valmista", "ALREADY_EXIST": "on jo olemassa", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Tiedosto {{VALUE}} luotiin", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Kansio {{VALUE}} luotiin", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "kirjanmerkki", "CAMERA": "kamera", "CANCEL": "peruuta", "CANNOT_ESTABLISH_A_CONNECTION": "ei voi muodostaa yhteyttä", "CANT_LOAD_THIS_PICTURE": "tätä kuvaa ei voi ladata", "CANT_USE_FILESYSTEM": "tiedostojärjestelmää ei voi käyttää", "CAN_RESHARE": "voi jakaa uudelleen", "CODE": "koodi", "CONFIGURE": "muokkaa asetuksia", "CONFIRM_BY_TYPING": "Vahvista kirjoittamalla", "CONNECT": "yhdistä", "CONNECTION_LOST": "yhteys katkennut", "COPIED_TO_CLIPBOARD": "kopioitu leikepöydälle", "CREATE_A_NEW_LINK": "luo uusi linkki", "CREATE_A_TAG": "luo tunniste", "CURRENT": "nykyinen", "CURRENT_UPLOAD": "nykyinen lähetys", "CUSTOM_LINK_URL": "mukautetun linkin URL-osoite", "DASHBOARD": "kojelauta", "DATE": "Päivämäärä", "DISPLAY_HIDDEN_FILES": "näytä piilotetut tiedostot", "DOESNT_MATCH": "ei täsmää", "DONE": "valmis", "DOWNLOAD": "lataa", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "haluatko tallentaa muutokset", "DROP_HERE_TO_UPLOAD": "pudota tähän lähettääksesi", "EDITOR": "toimittaja", "EMBED": "upota", "EMPTY": "tyhjä", "ENCRYPTION_KEY": "salausavain", "ENDPOINT": "päätepiste", "ERROR": "virhe", "EXISTING_LINKS": "olemassa olevat linkit", "EXPIRATION": "päättyminen", "EXPORT_AS_{{VALUE}}": "vie nimellä {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "usein näkyvät kansiot näytetään täällä", "HIDE_HIDDEN_FILES": "piilota piilotetut tiedostot", "HOME": "kotisivu", "HOSTNAME*": "isäntänimi", "HOST_KEY": "isäntäavain", "INCORRECT_PASSWORD": "väärä salasana", "INFO": "tiedot", "INTERNAL_ERROR": "sisäinen virhe", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "sisäinen virhe: ei voi luoda {{VALUE}}", "INVALID_ACCOUNT": "virheellinen tili", "INVALID_PASSWORD": "väärä salasana", "LAYOUT": "asettelu", "LOADING": "ladataan", "LOCATION": "sijainti", "MISSING_DEPENDENCY": "puuttuva riippuvuus", "MORE_DETAILS": "lisätietoja", "NAVIGATE": "navigoida", "NEW_FILE": "uusi tiedosto", "NEW_FILE::SHORT": "uusi tiedosto", "NEW_FOLDER": "uusi hakemisto", "NEW_FOLDER::SHORT": "uusi hakemi..", "NO": "ei", "NOT_ALLOWED": "ei sallittu", "NOT_AUTHORISED": "ei valtuutettu", "NOT_FOUND": "ei löydy", "NOT_IMPLEMENTED": "ei toteutettu", "NOT_SUPPORTED": "ei tueta", "NOT_VALID": "ei kelpaa", "NUMBER_OF_CONNECTIONS": "yhteyksien lukumäärä", "OK": "kunnossa", "ONLY_FOR_USERS": "Vain käyttäjille", "OOPS": "Oho", "PASSPHRASE": "tunnuslause", "PASSWORD": "Salasana", "PASSWORD_CANT_BE_EMPTY": "salasana ei voi olla tyhjä", "PATH": "polku", "PERMISSION_DENIED": "pääsy kielletty", "PICK_A_MASTER_PASSWORD": "valitse pääsalasana", "PORT": "portti", "POWERED_BY": "powered by", "PROPERTIES": "ominaisuudet", "PROTECT_ACCESS_WITH_A_PASSWORD": "suojaa pääsy salasanalla", "QUICK_ACCESS": "pikaosoite", "REGION": "alue", "REMEMBER_ME": "muista minut", "REMOVE": "Poisto", "RENAME": "nimeä", "RENAME_AS": "uusi nimi", "RESTRICTIONS": "rajoituksia", "RUNNING": "käynnissä", "SAVE_CURRENT_FILE": "tallenna nykyinen tiedosto", "SEARCH": "Hae", "SETTINGS": "asetukset", "SHARE": "jaa", "SHARED_DRIVE": "jaettu asema", "SKIP_TO_CONTENT": "Siirry sisältöön", "SORT": "lajittele", "SORT_BY_DATE": "lajittele päivämäärän mukaan", "SORT_BY_NAME": "lajittele nimen mukaan", "SORT_BY_SIZE": "lajittele koon mukaan", "SORT_BY_TYPE": "lajittele tyypin mukaan", "STARRED": "tähtimerkintä", "SUPPORT": "tuki", "TAG": "tunniste", "TAGS": "tunnisteet", "THERE_IS_NOTHING_HERE": "täällä ei ole mitään", "THE_FILE_{{VALUE}}_WAS_DELETED": "tiedosto {{VALUE}} poistettiin", "THE_FILE_{{VALUE}}_WAS_RENAMED": "tiedosto {{VALUE}} nimettiin uudelleen", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "linkki kopioitiin leikepöydälle", "THE_LINK_WONT_BE_VALID_AFTER": "linkki ei kelpaa jälkeen", "TIMEOUT": "Aikalisä", "TODO": "tehtävää", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "ruuhkaa, yritä myöhemmin uudelleen", "UPLOAD": "lataa", "UPLOADER": "lataaja", "USERNAME": "käyttäjätunnus", "VERSION": "versio", "VIEWER": "katsoja", "WAITING": "odotetaan", "YES": "Joo", "YOUR_EMAIL_ADDRESS": "Sähköpostiosoitteesi", "YOUR_FILES": "tiedostosi", "YOUR_MASTER_PASSWORD": "pääsalasanasi", "YOU_CANT_DO_THAT": "et voi tehdä sitä" } ================================================ FILE: public/assets/locales/fr.json ================================================ { "ABORTED": "avorté", "ABORT_CURRENT_UPLOADS?": "Annuler les téléchargements en cours?", "ACTIVITY": "activité", "ADVANCED": "avancé", "ALL_DONE": "Tout est bon!", "ALREADY_EXIST": "existe déjà", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Un fichier nommé \"{{VALUE}}\" a été créé", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Un dossier nommé \"{{VALUE}}\" a été créé", "BEAUTIFUL_URL": "id_du_lien", "BOOKMARK": "favoris", "CAMERA": "appareil", "CANCEL": "annuler", "CANNOT_ESTABLISH_A_CONNECTION": "Impossible d'établir une connexion", "CANT_LOAD_THIS_PICTURE": "impossible de charger cette image", "CANT_USE_FILESYSTEM": "Impossible d'utiliser le système de fichiers", "CAN_RESHARE": "peut repartager", "CODE": "code", "CONFIGURE": "configuration", "CONFIRM_BY_TYPING": "confirmez en écrivant", "CONNECT": "connexion", "CONNECTION_LOST": "connexion perdue", "COPIED_TO_CLIPBOARD": "Copié dans le presse-papier", "CREATE_A_NEW_LINK": "créer un lien partagé", "CREATE_A_TAG": "créer un tag", "CURRENT": "en cours", "CURRENT_UPLOAD": "en cours", "CUSTOM_LINK_URL": "URL du lien personnalisé", "DASHBOARD": "dashboard", "DATE": "date", "DISPLAY_HIDDEN_FILES": "afficher les fichiers cachés", "DOESNT_MATCH": "ne correspond pas", "DONE": "terminé", "DOWNLOAD": "Télécharger", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "enregistrer les modifications?", "DROP_HERE_TO_UPLOAD": "glisser-déposer pour charger vos fichiers et dossiers", "EDITOR": "éditeur", "EMBED": "intégrer", "EMPTY": "vide", "ENCRYPTION_KEY": "clé de cryptage", "ENDPOINT": "endpoint", "ERROR": "erreur", "EXISTING_LINKS": "liens existants", "EXPIRATION": "expiration", "EXPORT_AS_{{VALUE}}": "exporter au format {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "les dossiers d'accès fréquents apparaîtront ici", "HIDE_HIDDEN_FILES": "masquer les fichiers cachés", "HOME": "accueil", "HOSTNAME*": "nom d'hôte*", "HOST_KEY": "clé d'hôte", "INCORRECT_PASSWORD": "mot de passe incorrect", "INFO": "Information", "INTERNAL_ERROR": "Erreur interne", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "erreur interne: vous ne pouvez pas créer un {{VALUE}}", "INVALID_ACCOUNT": "compte invalide", "INVALID_PASSWORD": "mot de passe incorrect", "LAYOUT": "disposition", "LOADING": "chargement", "LOCATION": "position", "MISSING_DEPENDENCY": "dépendance manquante", "MORE_DETAILS": "plus de détails", "NAVIGATE": "naviguer", "NEW_FILE": "Nouveau Fichier", "NEW_FILE::SHORT": "Nouv. Fichier", "NEW_FOLDER": "Nouveau Dossier", "NEW_FOLDER::SHORT": "Nouv. Dossier", "NO": "non", "NOT_ALLOWED": "interdit", "NOT_AUTHORISED": "autorisation manquante", "NOT_FOUND": "introuvable", "NOT_IMPLEMENTED": "non implémenté", "NOT_SUPPORTED": "non supporté", "NOT_VALID": "pas valide", "NUMBER_OF_CONNECTIONS": "nombre de connexion", "OK": "ok", "ONLY_FOR_USERS": "uniquement pour certains utilisateurs", "OOPS": "oops!", "PASSPHRASE": "mot de passe de clef", "PASSWORD": "mot de passe", "PASSWORD_CANT_BE_EMPTY": "le mot de passe ne peut pas être vide", "PATH": "chemin", "PERMISSION_DENIED": "autorisation refusée", "PICK_A_MASTER_PASSWORD": "choisissez un mot de passe principal", "PORT": "port", "POWERED_BY": "propulsé par", "PROPERTIES": "propriétés", "PROTECT_ACCESS_WITH_A_PASSWORD": "protéger l'accès avec un mot de passe", "QUICK_ACCESS": "accès rapide", "REGION": "région", "REMEMBER_ME": "se souvenir de moi", "REMOVE": "supprimer", "RENAME": "renommer", "RENAME_AS": "renommer en tant que", "RESTRICTIONS": "restrictions", "RUNNING": "en cours", "SAVE_CURRENT_FILE": "enregistrer le fichier actuel", "SEARCH": "recherche", "SETTINGS": "réglage", "SHARE": "partager", "SHARED_DRIVE": "disque partagé", "SKIP_TO_CONTENT": "Aller au contenu", "SORT": "trier", "SORT_BY_DATE": "trier par date", "SORT_BY_NAME": "trier par nom", "SORT_BY_SIZE": "trier par taille", "SORT_BY_TYPE": "trier par type", "STARRED": "favoris", "SUPPORT": "assistance", "TAG": "tag", "TAGS": "tags", "THERE_IS_NOTHING_HERE": "il n'y a rien ici", "THE_FILE_{{VALUE}}_WAS_DELETED": "le fichier \"{{VALUE}}\" a été supprimé", "THE_FILE_{{VALUE}}_WAS_RENAMED": "le fichier \"{{VALUE}}\" a été renommé", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "le lien a été copié dans le presse-papiers", "THE_LINK_WONT_BE_VALID_AFTER": "le lien ne sera plus valide après", "TIMEOUT": "trop long", "TODO": "todo", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Congestion, réessayez plus tard", "UPLOAD": "importer", "UPLOADER": "uploader", "USERNAME": "nom d'utilisateur", "VERSION": "version", "VIEWER": "viewer", "WAITING": "attente", "YES": "oui", "YOUR_EMAIL_ADDRESS": "votre adresse email", "YOUR_FILES": "vos fichiers", "YOUR_MASTER_PASSWORD": "votre mot de passe principal", "YOU_CANT_DO_THAT": "vous ne pouvez pas faire :)" } ================================================ FILE: public/assets/locales/gl.json ================================================ { "ABORTED": "abortada", "ABORT_CURRENT_UPLOADS?": "cancelar a carga actual?", "ACTIVITY": "Actividade", "ADVANCED": "avanzado", "ALL_DONE": "todo feito", "ALREADY_EXIST": "xa existen", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Creouse un ficheiro chamado {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Creouse un cartafol chamado {{VALUE}}", "BEAUTIFUL_URL": "fermoso_url", "BOOKMARK": "marcador", "CAMERA": "cámara", "CANCEL": "cancelar", "CANNOT_ESTABLISH_A_CONNECTION": "non pode establecer unha conexión", "CANT_LOAD_THIS_PICTURE": "Non se pode cargar esta foto", "CANT_USE_FILESYSTEM": "pode usar o sistema de ficheiros", "CAN_RESHARE": "pode volver compartir", "CODE": "código", "CONFIGURE": "configurar", "CONFIRM_BY_TYPING": "confirma escribindo", "CONNECT": "conectar", "CONNECTION_LOST": "conexión perdida", "COPIED_TO_CLIPBOARD": "copiado ao portapapeis", "CREATE_A_NEW_LINK": "crear unha nova ligazón", "CREATE_A_TAG": "crear unha etiqueta", "CURRENT": "actual", "CURRENT_UPLOAD": "carga actual", "CUSTOM_LINK_URL": "URL de ligazón personalizado", "DASHBOARD": "panel de traballo", "DATE": "data", "DISPLAY_HIDDEN_FILES": "amosar ficheiros ocultos", "DOESNT_MATCH": "non coincide", "DONE": "feito", "DOWNLOAD": "descargar", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "quere gardar os cambios", "DROP_HERE_TO_UPLOAD": "solta aquí para cargar", "EDITOR": "editor", "EMBED": "incrustar", "EMPTY": "baleiro", "ENCRYPTION_KEY": "clave de cifrado", "ENDPOINT": "punto final", "ERROR": "erro", "EXISTING_LINKS": "ligazóns existentes", "EXPIRATION": "caducidade", "EXPORT_AS_{{VALUE}}": "exportar como {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Aquí móstranse os cartafoles de acceso frecuente", "HIDE_HIDDEN_FILES": "ocultar ficheiros ocultos", "HOME": "inicio", "HOSTNAME*": "nome de host", "HOST_KEY": "clave de anfitrión", "INCORRECT_PASSWORD": "Contrasinal incorrecto", "INFO": "información", "INTERNAL_ERROR": "Erro interno", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "erro interno: non se pode crear un {{VALUE}}", "INVALID_ACCOUNT": "conta non válida", "INVALID_PASSWORD": "Contrasinal incorrecta", "LAYOUT": "disposición", "LOADING": "cargando", "LOCATION": "localización", "MISSING_DEPENDENCY": "falta dependencia", "MORE_DETAILS": "máis detalles", "NAVIGATE": "navegar", "NEW_FILE": "novo ficheiro", "NEW_FILE::SHORT": "novo ficheiro", "NEW_FOLDER": "novo directorio", "NEW_FOLDER::SHORT": "novo direct..", "NO": "non", "NOT_ALLOWED": "Prohibido", "NOT_AUTHORISED": "non autorizado", "NOT_FOUND": "non atopado", "NOT_IMPLEMENTED": "non implementado", "NOT_SUPPORTED": "non é compatible", "NOT_VALID": "non é válido", "NUMBER_OF_CONNECTIONS": "número de conexións", "OK": "Ok", "ONLY_FOR_USERS": "Só para usuarios", "OOPS": "Oops", "PASSPHRASE": "frase de contrasinal", "PASSWORD": "contrasinal", "PASSWORD_CANT_BE_EMPTY": "o contrasinal non pode estar baleiro", "PATH": "camiño", "PERMISSION_DENIED": "Permiso denegado", "PICK_A_MASTER_PASSWORD": "escolle un contrasinal principal", "PORT": "Porto", "POWERED_BY": "impulsado por", "PROPERTIES": "propiedades", "PROTECT_ACCESS_WITH_A_PASSWORD": "protexer o acceso cun contrasinal", "QUICK_ACCESS": "acceso rápido", "REGION": "rexión", "REMEMBER_ME": "Lémbrame", "REMOVE": "quitar", "RENAME": "renomear", "RENAME_AS": "renomear como", "RESTRICTIONS": "restricións", "RUNNING": "correndo", "SAVE_CURRENT_FILE": "gardar o ficheiro actual", "SEARCH": "buscar", "SETTINGS": "configuración", "SHARE": "compartir", "SHARED_DRIVE": "unidade compartida", "SKIP_TO_CONTENT": "Ir ao contido", "SORT": "ordenar", "SORT_BY_DATE": "ordenar por data", "SORT_BY_NAME": "ordenar por nome", "SORT_BY_SIZE": "ordenar por tamaño", "SORT_BY_TYPE": "ordenar por tipo", "STARRED": "destacado", "SUPPORT": "apoiar", "TAG": "etiqueta", "TAGS": "etiqueta", "THERE_IS_NOTHING_HERE": "aquí non hai nada", "THE_FILE_{{VALUE}}_WAS_DELETED": "eliminouse o ficheiro {{VALUE}}", "THE_FILE_{{VALUE}}_WAS_RENAMED": "renomeouse o ficheiro {{VALUE}}", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "a ligazón foi copiada no portapapeis", "THE_LINK_WONT_BE_VALID_AFTER": "a ligazón non será válida despois", "TIMEOUT": "tempo de espera", "TODO": "facer", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "conxestión de tráfico, ténteo de novo máis tarde", "UPLOAD": "cargar", "UPLOADER": "cargador", "USERNAME": "nome de usuario", "VERSION": "versión", "VIEWER": "espectador", "WAITING": "á espera", "YES": "si", "YOUR_EMAIL_ADDRESS": "O teu enderezo de email", "YOUR_FILES": "os teus ficheiros", "YOUR_MASTER_PASSWORD": "o seu contrasinal principal", "YOU_CANT_DO_THAT": "Non podes facelo" } ================================================ FILE: public/assets/locales/hr.json ================================================ { "ABORTED": "prekinut", "ABORT_CURRENT_UPLOADS?": "prekinuti trenutačni prijenos?", "ACTIVITY": "Aktivnost", "ADVANCED": "Napredna", "ALL_DONE": "sve učinjeno", "ALREADY_EXIST": "već postoji", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "pod nazivom {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Stvorena je mapa pod nazivom {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "oznaka", "CAMERA": "fotoaparat", "CANCEL": "otkazati", "CANNOT_ESTABLISH_A_CONNECTION": "ne može uspostaviti vezu", "CANT_LOAD_THIS_PICTURE": "ne mogu učitati ovu sliku", "CANT_USE_FILESYSTEM": "mogu koristiti datotečni sustav", "CAN_RESHARE": "može ponovno dijeliti", "CODE": "kodirati", "CONFIGURE": "konfigurirati", "CONFIRM_BY_TYPING": "potvrdite tipkanjem", "CONNECT": "Spojiti", "CONNECTION_LOST": "veza izgubljena", "COPIED_TO_CLIPBOARD": "kopirano u međuspremnik", "CREATE_A_NEW_LINK": "stvori novu vezu", "CREATE_A_TAG": "stvoriti oznaku", "CURRENT": "Trenutno", "CURRENT_UPLOAD": "trenutačni prijenos", "CUSTOM_LINK_URL": "prilagođeni URL veze", "DASHBOARD": "kontrolna ploča", "DATE": "datum", "DISPLAY_HIDDEN_FILES": "prikaz skrivenih datoteka", "DOESNT_MATCH": "ne podudara se", "DONE": "učinio", "DOWNLOAD": "preuzimanje datoteka", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "želite li spremiti promjene", "DROP_HERE_TO_UPLOAD": "ispustite ovdje za učitavanje", "EDITOR": "urednik", "EMBED": "ugradi", "EMPTY": "prazan", "ENCRYPTION_KEY": "ključ za šifriranje", "ENDPOINT": "krajnja točka", "ERROR": "greška", "EXISTING_LINKS": "postojeće poveznice", "EXPIRATION": "izdisanje", "EXPORT_AS_{{VALUE}}": "izvesti kao {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Ovdje će se prikazivati ​​mape s često pristupom", "HIDE_HIDDEN_FILES": "sakriti skrivene datoteke", "HOME": "početna", "HOSTNAME*": "hostname", "HOST_KEY": "ključ računala", "INCORRECT_PASSWORD": "Netočna lozinka", "INFO": "Informacije", "INTERNAL_ERROR": "Interna greška", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "unutarnja pogreška: ne mogu stvoriti {{VALUE}}", "INVALID_ACCOUNT": "nevažeći račun", "INVALID_PASSWORD": "Netočna zaporka", "LAYOUT": "izgled", "LOADING": "učitavanje", "LOCATION": "mjesto", "MISSING_DEPENDENCY": "nedostaje ovisnost", "MORE_DETAILS": "više detalja", "NAVIGATE": "navigaciju", "NEW_FILE": "nova datoteka", "NEW_FILE::SHORT": "nova datoteka", "NEW_FOLDER": "novi direktorij", "NEW_FOLDER::SHORT": "novi direkt..", "NO": "Ne", "NOT_ALLOWED": "nije dozvoljeno", "NOT_AUTHORISED": "nije ovlašten", "NOT_FOUND": "nije pronađeno", "NOT_IMPLEMENTED": "nije implementirano", "NOT_SUPPORTED": "Nije podržano", "NOT_VALID": "Ne vrijedi", "NUMBER_OF_CONNECTIONS": "broj priključaka", "OK": "u redu", "ONLY_FOR_USERS": "Samo za korisnike", "OOPS": "Ups", "PASSPHRASE": "šifra", "PASSWORD": "lozinka", "PASSWORD_CANT_BE_EMPTY": "lozinka ne može biti prazna", "PATH": "staza", "PERMISSION_DENIED": "odobrenje odbijeno", "PICK_A_MASTER_PASSWORD": "odaberite glavnu lozinku", "PORT": "luka", "POWERED_BY": "powered by", "PROPERTIES": "Svojstva", "PROTECT_ACCESS_WITH_A_PASSWORD": "zaštititi pristup lozinkom", "QUICK_ACCESS": "brzi pristup", "REGION": "regija", "REMEMBER_ME": "Zapamti me", "REMOVE": "ukloniti", "RENAME": "preimenuj", "RENAME_AS": "preimenuj kao", "RESTRICTIONS": "ograničenja", "RUNNING": "trčanje", "SAVE_CURRENT_FILE": "spremite trenutnu datoteku", "SEARCH": "traži", "SETTINGS": "postavke", "SHARE": "podijeli", "SHARED_DRIVE": "zajednički disk", "SKIP_TO_CONTENT": "Idi na sadržaj", "SORT": "sortiraj", "SORT_BY_DATE": "poredaj po datumu", "SORT_BY_NAME": "poredaj po imenu", "SORT_BY_SIZE": "poredaj po veličini", "SORT_BY_TYPE": "poredati po vrsti", "STARRED": "favoriti", "SUPPORT": "podrška", "TAG": "oznaka", "TAGS": "oznake", "THERE_IS_NOTHING_HERE": "tu nema ničega", "THE_FILE_{{VALUE}}_WAS_DELETED": "datoteka {{VALUE}} je izbrisana", "THE_FILE_{{VALUE}}_WAS_RENAMED": "datoteka {{VALUE}} preimenovana je", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "veza je kopirana u međuspremnik", "THE_LINK_WONT_BE_VALID_AFTER": "veza poslije neće biti valjana", "TIMEOUT": "pauza", "TODO": "napraviti", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "prometni zastoji, pokušajte ponovo kasnije", "UPLOAD": "učitaj", "UPLOADER": "uploader", "USERNAME": "Korisničko ime", "VERSION": "verzija", "VIEWER": "gledatelj", "WAITING": "čekanja", "YES": "Da", "YOUR_EMAIL_ADDRESS": "Vaša email adresa", "YOUR_FILES": "vaše datoteke", "YOUR_MASTER_PASSWORD": "svoju glavnu lozinku", "YOU_CANT_DO_THAT": "ne možeš to učiniti" } ================================================ FILE: public/assets/locales/hu.json ================================================ { "ABORTED": "Megszakítva", "ABORT_CURRENT_UPLOADS?": "megszakítja az aktuális feltöltést?", "ACTIVITY": "Tevékenység", "ADVANCED": "fejlett", "ALL_DONE": "minden kész", "ALREADY_EXIST": "már létezik", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "A (z) {{VALUE}} nevű fájlt létrehoztuk", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Létrehozott egy {{VALUE}} nevű mappát", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "könyvjelző", "CAMERA": "kamera", "CANCEL": "mégse", "CANNOT_ESTABLISH_A_CONNECTION": "nem tud kapcsolatot létesíteni", "CANT_LOAD_THIS_PICTURE": "nem lehet betölteni ezt a képet", "CANT_USE_FILESYSTEM": "nem használható a fájlrendszer", "CAN_RESHARE": "újra megoszthatja", "CODE": "kód", "CONFIGURE": "Beállítás", "CONFIRM_BY_TYPING": "gépeléssel erősítse meg", "CONNECT": "kapcsolódás", "CONNECTION_LOST": "kapcsolat megszakadt", "COPIED_TO_CLIPBOARD": "a vágólapra másolták", "CREATE_A_NEW_LINK": "Új link létrehozása", "CREATE_A_TAG": "címke létrehozása", "CURRENT": "jelenlegi", "CURRENT_UPLOAD": "aktuális feltöltés", "CUSTOM_LINK_URL": "egyéni link URL", "DASHBOARD": "Irányítópult", "DATE": "dátum", "DISPLAY_HIDDEN_FILES": "rejtett fájlok megjelenítése", "DOESNT_MATCH": "nem egyezik", "DONE": "Kész", "DOWNLOAD": "Letöltés", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "el akarja menteni a változtatásokat", "DROP_HERE_TO_UPLOAD": "ide kattintva feltöltheti", "EDITOR": "szerkesztő", "EMBED": "beágyaz", "EMPTY": "üres", "ENCRYPTION_KEY": "titkosítási kulcs", "ENDPOINT": "végpont", "ERROR": "hiba", "EXISTING_LINKS": "meglévő linkek", "EXPIRATION": "lejárat", "EXPORT_AS_{{VALUE}}": "exportálás mint {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "itt jelennek meg a gyakran hozzáférhető mappák", "HIDE_HIDDEN_FILES": "rejtett fájlok elrejtése", "HOME": "kezdőlap", "HOSTNAME*": "hostname", "HOST_KEY": "host kulcs", "INCORRECT_PASSWORD": "hibás jelszó", "INFO": "info", "INTERNAL_ERROR": "Belső hiba", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "belső hiba: nem lehet létrehozni a (z) {{VALUE}}", "INVALID_ACCOUNT": "Érvénytelen fiók", "INVALID_PASSWORD": "Érvénytelen jelszó", "LAYOUT": "elrendezés", "LOADING": "betöltés", "LOCATION": "elhelyezkedés", "MISSING_DEPENDENCY": "hiányzik a függőség", "MORE_DETAILS": "Részletek", "NAVIGATE": "navigálás", "NEW_FILE": "új fájl", "NEW_FILE::SHORT": "új fájl", "NEW_FOLDER": "új könyvtár", "NEW_FOLDER::SHORT": "új könyvtár", "NO": "nem", "NOT_ALLOWED": "nem megengedett", "NOT_AUTHORISED": "nem engedélyezett", "NOT_FOUND": "nem található", "NOT_IMPLEMENTED": "nincs implementálva", "NOT_SUPPORTED": "Nem támogatott", "NOT_VALID": "nem érvényes", "NUMBER_OF_CONNECTIONS": "csatlakozások száma", "OK": "rendben", "ONLY_FOR_USERS": "Csak a felhasználók számára", "OOPS": "Hoppá", "PASSPHRASE": "jelmondat", "PASSWORD": "Jelszó", "PASSWORD_CANT_BE_EMPTY": "a jelszó nem lehet üres", "PATH": "pálya", "PERMISSION_DENIED": "hozzáférés megtagadva", "PICK_A_MASTER_PASSWORD": "válasszon egy fő jelszót", "PORT": "kikötő", "POWERED_BY": "meghajtó", "PROPERTIES": "tulajdonságok", "PROTECT_ACCESS_WITH_A_PASSWORD": "védje a hozzáférést jelszóval", "QUICK_ACCESS": "gyors hozzáférés", "REGION": "vidék", "REMEMBER_ME": "Emlékezz rám", "REMOVE": "Törlés", "RENAME": "átnevezés", "RENAME_AS": "átnevezés erre", "RESTRICTIONS": "korlátozások", "RUNNING": "futás", "SAVE_CURRENT_FILE": "mentse az aktuális fájlt", "SEARCH": "keresés", "SETTINGS": "beállítások", "SHARE": "megosztás", "SHARED_DRIVE": "meghajtó", "SKIP_TO_CONTENT": "Ugrás a tartalomhoz", "SORT": "rendezés", "SORT_BY_DATE": "rendezés dátum szerint", "SORT_BY_NAME": "név szerinti rendezés", "SORT_BY_SIZE": "méret szerinti rendezés", "SORT_BY_TYPE": "típus szerint rendezni", "STARRED": "csillagozott", "SUPPORT": "támogatás", "TAG": "címke", "TAGS": "címkék", "THERE_IS_NOTHING_HERE": "nincs itt semmi", "THE_FILE_{{VALUE}}_WAS_DELETED": "a (z) {{VALUE}} fájlt törölték", "THE_FILE_{{VALUE}}_WAS_RENAMED": "a (z) {{VALUE}} fájlt átnevezték", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "a linket a vágólapra másolták", "THE_LINK_WONT_BE_VALID_AFTER": "a link nem lesz érvényes utána", "TIMEOUT": "időtúllépés", "TODO": "csinálni", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "forgalmi torlódások, próbálkozzon később újra", "UPLOAD": "feltöltés", "UPLOADER": "feltöltő", "USERNAME": "felhasználónév", "VERSION": "verzió", "VIEWER": "néző", "WAITING": "Várakozik", "YES": "Igen", "YOUR_EMAIL_ADDRESS": "az email címed", "YOUR_FILES": "fájljaid", "YOUR_MASTER_PASSWORD": "a fő jelszavad", "YOU_CANT_DO_THAT": "Nem hajtható végre" } ================================================ FILE: public/assets/locales/id.json ================================================ { "ABORTED": "dibatalkan", "ABORT_CURRENT_UPLOADS?": "batalkan unggahan saat ini?", "ACTIVITY": "Aktivitas", "ADVANCED": "lanjutan", "ALL_DONE": "semua selesai", "ALREADY_EXIST": "sudah ada", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "File bernama {{VALUE}} telah dibuat", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Folder bernama {{VALUE}} telah dibuat", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "penanda", "CAMERA": "kamera", "CANCEL": "batalkan", "CANNOT_ESTABLISH_A_CONNECTION": "tidak dapat membuat koneksi", "CANT_LOAD_THIS_PICTURE": "tidak dapat memuat gambar ini", "CANT_USE_FILESYSTEM": "idak bisa gunakan sistem file", "CAN_RESHARE": "dapat membagikan ulang", "CODE": "kode", "CONFIGURE": "konfigurasikan", "CONFIRM_BY_TYPING": "konfirmasi dengan mengetik", "CONNECT": "hubungkan", "CONNECTION_LOST": "koneksi terputus", "COPIED_TO_CLIPBOARD": "disalin ke papan klip", "CREATE_A_NEW_LINK": "buat tautan baru", "CREATE_A_TAG": "buat tag", "CURRENT": "terkini", "CURRENT_UPLOAD": "unggahan saat ini", "CUSTOM_LINK_URL": "URL tautan khusus", "DASHBOARD": "dasbor", "DATE": "tanggal", "DISPLAY_HIDDEN_FILES": "tampilkan file tersembunyi", "DOESNT_MATCH": "tidak cocok", "DONE": "selesai", "DOWNLOAD": "unduh", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Simpan perubahan?", "DROP_HERE_TO_UPLOAD": "turun di sini untuk mengunggah", "EDITOR": "editor", "EMBED": "sematkan", "EMPTY": "kosong", "ENCRYPTION_KEY": "kunci enkripsi", "ENDPOINT": "titik akhir", "ERROR": "kesalahan", "EXISTING_LINKS": "tautan yang ada", "EXPIRATION": "kedaluwarsa", "EXPORT_AS_{{VALUE}}": "ekspor sebagai {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "folder yang sering diakses akan ditampilkan di sini", "HIDE_HIDDEN_FILES": "sembunyikan file yang tersembunyi", "HOME": "beranda", "HOSTNAME*": "nama host", "HOST_KEY": "kunci host", "INCORRECT_PASSWORD": "kata kunci Salah", "INFO": "info", "INTERNAL_ERROR": "Internal error", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "kesalahan internal: tidak dapat membuat {{VALUE}}", "INVALID_ACCOUNT": "akun tidak berlaku", "INVALID_PASSWORD": "kata sandi salah", "LAYOUT": "tata letak", "LOADING": "memuat", "LOCATION": "lokasi", "MISSING_DEPENDENCY": "hilang ketergantungan", "MORE_DETAILS": "lebih detail", "NAVIGATE": "navigasi", "NEW_FILE": "file baru", "NEW_FILE::SHORT": "file baru", "NEW_FOLDER": "direktori baru", "NEW_FOLDER::SHORT": "direktori baru", "NO": "tidak", "NOT_ALLOWED": "tidak diizinkan", "NOT_AUTHORISED": "tidak resmi", "NOT_FOUND": "tidak ditemukan", "NOT_IMPLEMENTED": "tidak diterapkan", "NOT_SUPPORTED": "tidak didukung", "NOT_VALID": "tidak valid", "NUMBER_OF_CONNECTIONS": "jumlah koneksi", "OK": "baik", "ONLY_FOR_USERS": "Hanya untuk pengguna", "OOPS": "Ups", "PASSPHRASE": "frasa sandi", "PASSWORD": "kata sandi", "PASSWORD_CANT_BE_EMPTY": "kata sandi tidak boleh kosong", "PATH": "jalan", "PERMISSION_DENIED": "izin ditolak", "PICK_A_MASTER_PASSWORD": "pilih kata sandi utama", "PORT": "port", "POWERED_BY": "dipersembahkan oleh", "PROPERTIES": "properti", "PROTECT_ACCESS_WITH_A_PASSWORD": "lindungi akses dengan kata sandi", "QUICK_ACCESS": "akses cepat", "REGION": "wilayah", "REMEMBER_ME": "ingat saya", "REMOVE": "hapus", "RENAME": "ubah nama", "RENAME_AS": "ubah nama sebagai", "RESTRICTIONS": "pembatasan", "RUNNING": "berjalan", "SAVE_CURRENT_FILE": "simpan file saat ini", "SEARCH": "Cari", "SETTINGS": "pengaturan", "SHARE": "bagikan", "SHARED_DRIVE": "drive bersama", "SKIP_TO_CONTENT": "Lewati ke konten", "SORT": "urutkan", "SORT_BY_DATE": "urutkan berdasarkan tanggal", "SORT_BY_NAME": "diurutkan berdasarkan nama", "SORT_BY_SIZE": "urutkan berdasarkan ukuran", "SORT_BY_TYPE": "urutkan berdasarkan jenis", "STARRED": "berbintang", "SUPPORT": "dukung", "TAG": "tag", "TAGS": "tag", "THERE_IS_NOTHING_HERE": "tidak ada apa-apa di sini", "THE_FILE_{{VALUE}}_WAS_DELETED": "file {{VALUE}} telah dihapus", "THE_FILE_{{VALUE}}_WAS_RENAMED": "file {{VALUE}} diganti namanya", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "tautannya disalin di clipboard", "THE_LINK_WONT_BE_VALID_AFTER": "tautan tidak akan valid setelah", "TIMEOUT": "waktu habis", "TODO": "melakukan", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "kemacetan lalu lintas, coba lagi nanti", "UPLOAD": "kirim", "UPLOADER": "pengunggah", "USERNAME": "nama pengguna", "VERSION": "versi", "VIEWER": "penonton", "WAITING": "menunggu", "YES": "Iya", "YOUR_EMAIL_ADDRESS": "Alamat email anda", "YOUR_FILES": "file Anda", "YOUR_MASTER_PASSWORD": "kata sandi utama Anda", "YOU_CANT_DO_THAT": "kamu tidak bisa melakukan itu" } ================================================ FILE: public/assets/locales/index.js ================================================ import rxjs from "../lib/rx.js"; import ajax from "../lib/ajax.js"; import { join } from "../lib/path.js"; let LNG = {}; export default function t(str = "", replacementString, requestedKey) { const calculatedKey = str.toUpperCase() .replace(/ /g, "_") .replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "") .replace(/\_+$/, ""); const value = requestedKey === undefined ? LNG && LNG[calculatedKey] : LNG && LNG[requestedKey]; return reformat( value || str || "", str, ).replace("{{VALUE}}", replacementString); } export async function init() { let selectedLanguage = "en"; switch (navigator.language) { case "zh-TW": selectedLanguage = "zh_tw"; break; default: const userLanguage = navigator.language.split("-")[0] || ""; const idx = [ "az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "et", "eu", "fi", "fr", "gl", "hr", "hu", "id", "is", "it", "ja", "ka", "ko", "lt", "lv", "mn", "nb", "nl", "no", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh", ].indexOf(userLanguage); if (idx !== -1) { selectedLanguage = userLanguage; } } if (selectedLanguage === "en") { return Promise.resolve(); } return ajax({ url: join(import.meta.url, selectedLanguage + ".json"), }).pipe(rxjs.tap(({ responseHeaders, response }) => { const contentType = responseHeaders["content-type"].trim(); if (contentType === "application/json") { LNG = JSON.parse(response); return; } throw new Error(`wrong content type '${contentType}'`); })).toPromise(); } function reformat(translated, initial) { if (initial[0] && initial[0].toLowerCase() === initial[0]) { return translated || ""; } return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || ""; } ================================================ FILE: public/assets/locales/is.json ================================================ { "ABORTED": "Hætt við", "ABORT_CURRENT_UPLOADS?": "hætta við núverandi upphleðslu?", "ACTIVITY": "Virkni", "ADVANCED": "Ítarlegt", "ALL_DONE": "allt búið", "ALREADY_EXIST": "nú þegar til", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Skrá sem heitir {{VALUE}} var búin til", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Mappa sem hét {{VALUE}} var búin til", "BEAUTIFUL_URL": "fallegur_url", "BOOKMARK": "bókamerki", "CAMERA": "myndavél", "CANCEL": "hætta við", "CANNOT_ESTABLISH_A_CONNECTION": "getur ekki komið á tengingu", "CANT_LOAD_THIS_PICTURE": "get ekki hlaðið þessa mynd", "CANT_USE_FILESYSTEM": "ekki hægt að nota skráarkerfi", "CAN_RESHARE": "getur deilt á ný", "CODE": "kóða", "CONFIGURE": "stilla", "CONFIRM_BY_TYPING": "staðfesta með því að slá inn", "CONNECT": "tengja", "CONNECTION_LOST": "tenging rofin", "COPIED_TO_CLIPBOARD": "afritað á klemmuspjald", "CREATE_A_NEW_LINK": "búa til nýjan hlekk", "CREATE_A_TAG": "búa til merki", "CURRENT": "núverandi", "CURRENT_UPLOAD": "núverandi upp", "CUSTOM_LINK_URL": "sérsniðin tengil slóð", "DASHBOARD": "mælaborð", "DATE": "dagsetning", "DISPLAY_HIDDEN_FILES": "birta faldar skrár", "DOESNT_MATCH": "passar ekki", "DONE": "gert", "DOWNLOAD": "hala niður", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "viltu vista breytingarnar", "DROP_HERE_TO_UPLOAD": "slepptu hér til að hlaða inn", "EDITOR": "ritstjóri", "EMBED": "fella inn", "EMPTY": "tómt", "ENCRYPTION_KEY": "dulkóðunarlykill", "ENDPOINT": "endapunktur", "ERROR": "villa", "EXISTING_LINKS": "núverandi tenglar", "EXPIRATION": "gildistími", "EXPORT_AS_{{VALUE}}": "flytja út sem {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "oft birtast möppur hér", "HIDE_HIDDEN_FILES": "fela faldar skrár", "HOME": "heimasíða", "HOSTNAME*": "gestgjafanafn", "HOST_KEY": "hýsillykill", "INCORRECT_PASSWORD": "Rangt lykilorð", "INFO": "upplýsingar", "INTERNAL_ERROR": "Innri villa", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "innri villa: getur ekki búið til {{VALUE}}", "INVALID_ACCOUNT": "ógildur reikningur", "INVALID_PASSWORD": "ógilt lykilorð", "LAYOUT": "útlit", "LOADING": "hleður", "LOCATION": "staðsetningu", "MISSING_DEPENDENCY": "vantar ósjálfstæði", "MORE_DETAILS": "meiri upplýsingar", "NAVIGATE": "sigla", "NEW_FILE": "ný skjal", "NEW_FILE::SHORT": "ný skjal", "NEW_FOLDER": "ný skrá", "NEW_FOLDER::SHORT": "ný skrá", "NO": "nei", "NOT_ALLOWED": "ekki leyft", "NOT_AUTHORISED": "ekki heimild", "NOT_FOUND": "ekki fundið", "NOT_IMPLEMENTED": "ekki hrint í framkvæmd", "NOT_SUPPORTED": "ekki stutt", "NOT_VALID": "ekki gilt", "NUMBER_OF_CONNECTIONS": "fjöldi tenginga", "OK": "allt í lagi", "ONLY_FOR_USERS": "Aðeins fyrir notendur", "OOPS": "Úps", "PASSPHRASE": "aðgangsorð", "PASSWORD": "lykilorð", "PASSWORD_CANT_BE_EMPTY": "lykilorð getur ekki verið tómt", "PATH": "leið", "PERMISSION_DENIED": "leyfi hafnað", "PICK_A_MASTER_PASSWORD": "veldu aðal lykilorð", "PORT": "höfn", "POWERED_BY": "Knúið af", "PROPERTIES": "eiginleikar", "PROTECT_ACCESS_WITH_A_PASSWORD": "vernda aðgang með lykilorði", "QUICK_ACCESS": "hraður aðgangur", "REGION": "svæði", "REMEMBER_ME": "mundu eftir mér", "REMOVE": "eyða", "RENAME": "endurnefna", "RENAME_AS": "endurnefna sem", "RESTRICTIONS": "takmarkanir", "RUNNING": "í gangi", "SAVE_CURRENT_FILE": "vista núverandi skrá", "SEARCH": "leit", "SETTINGS": "stillingar", "SHARE": "deila", "SHARED_DRIVE": "deildrifið", "SKIP_TO_CONTENT": "Fara í efni", "SORT": "raða", "SORT_BY_DATE": "raða eftir dagsetningu", "SORT_BY_NAME": "raða eftir nafni", "SORT_BY_SIZE": "raða eftir stærð", "SORT_BY_TYPE": "raða eftir tegund", "STARRED": "merkt", "SUPPORT": "stuðning", "TAG": "merki", "TAGS": "merki", "THERE_IS_NOTHING_HERE": "það er ekkert hérna", "THE_FILE_{{VALUE}}_WAS_DELETED": "skránni {{VALUE}} var eytt", "THE_FILE_{{VALUE}}_WAS_RENAMED": "var endurnefnt skráin {{VALUE}}", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "hlekkurinn var afritaður á klemmuspjaldið", "THE_LINK_WONT_BE_VALID_AFTER": "hlekkurinn gildir ekki eftir", "TIMEOUT": "Hlé", "TODO": "að gera", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "umferðaröngþveiti, reyndu aftur seinna", "UPLOAD": "hleð upp", "UPLOADER": "upphleðslutæki", "USERNAME": "notandanafn", "VERSION": "útgáfa", "VIEWER": "áhorfandi", "WAITING": "að bíða", "YES": "Já", "YOUR_EMAIL_ADDRESS": "Netfangið þitt", "YOUR_FILES": "skjöl þín", "YOUR_MASTER_PASSWORD": "aðal lykilorð þitt", "YOU_CANT_DO_THAT": "þú getur ekki gert það" } ================================================ FILE: public/assets/locales/it.json ================================================ { "ABORTED": "abortito", "ABORT_CURRENT_UPLOADS?": "interrompere il caricamento corrente?", "ACTIVITY": "Attività", "ADVANCED": "Avanzato", "ALL_DONE": "tutto fatto", "ALREADY_EXIST": "esistono già", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "È stato creato un file chiamato {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "È stata creata una cartella denominata {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "segnalibro", "CAMERA": "telecamera", "CANCEL": "Annulla", "CANNOT_ESTABLISH_A_CONNECTION": "impossibile stabilire una connessione", "CANT_LOAD_THIS_PICTURE": "non riesco a caricare questa immagine", "CANT_USE_FILESYSTEM": "impossibile utilizzare il filesystem", "CAN_RESHARE": "può ricondividere", "CODE": "codice", "CONFIGURE": "configura", "CONFIRM_BY_TYPING": "confermare digitando", "CONNECT": "Collegare", "CONNECTION_LOST": "connessione persa", "COPIED_TO_CLIPBOARD": "copiato negli appunti", "CREATE_A_NEW_LINK": "crea un nuovo link", "CREATE_A_TAG": "crea un tag", "CURRENT": "attuale", "CURRENT_UPLOAD": "caricamento in corso", "CUSTOM_LINK_URL": "URL del link personalizzato", "DASHBOARD": "pannello di controllo", "DATE": "Data", "DISPLAY_HIDDEN_FILES": "mostra file nascosti", "DOESNT_MATCH": "non corrisponde", "DONE": "fatto", "DOWNLOAD": "Scarica", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "vuoi salvare le modifiche", "DROP_HERE_TO_UPLOAD": "rilascia qui per caricare", "EDITOR": "editore", "EMBED": "incorpora", "EMPTY": "vuoto", "ENCRYPTION_KEY": "chiave di crittografia", "ENDPOINT": "endpoint", "ERROR": "errore", "EXISTING_LINKS": "link esistenti", "EXPIRATION": "scadenza", "EXPORT_AS_{{VALUE}}": "esporta come {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "le cartelle di accesso frequente verranno visualizzate qui", "HIDE_HIDDEN_FILES": "nascondi file nascosti", "HOME": "home", "HOSTNAME*": "Nome host*", "HOST_KEY": "chiave host", "INCORRECT_PASSWORD": "password errata", "INFO": "Informazioni", "INTERNAL_ERROR": "Errore interno", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "errore interno: impossibile creare un {{VALUE}}", "INVALID_ACCOUNT": "account non valido", "INVALID_PASSWORD": "password non valida", "LAYOUT": "layout", "LOADING": "caricamento", "LOCATION": "Posizione", "MISSING_DEPENDENCY": "dipendenza mancante", "MORE_DETAILS": "più dettagli", "NAVIGATE": "navigare", "NEW_FILE": "nuovo file", "NEW_FILE::SHORT": "nuovo file", "NEW_FOLDER": "nuova directory", "NEW_FOLDER::SHORT": "nuova directory", "NO": "no", "NOT_ALLOWED": "non autorizzato", "NOT_AUTHORISED": "non autorizzato", "NOT_FOUND": "non trovato", "NOT_IMPLEMENTED": "non implementato", "NOT_SUPPORTED": "non supportato", "NOT_VALID": "non valido", "NUMBER_OF_CONNECTIONS": "numero di connessioni", "OK": "ok", "ONLY_FOR_USERS": "Solo per utenti", "OOPS": "Ops", "PASSPHRASE": "frase d'accesso", "PASSWORD": "parola d'ordine", "PASSWORD_CANT_BE_EMPTY": "la password non può essere vuota", "PATH": "sentiero", "PERMISSION_DENIED": "permesso negato", "PICK_A_MASTER_PASSWORD": "scegli una password principale", "PORT": "porta", "POWERED_BY": "offerto da", "PROPERTIES": "proprietà", "PROTECT_ACCESS_WITH_A_PASSWORD": "proteggere l'accesso con una password", "QUICK_ACCESS": "accesso rapido", "REGION": "regione", "REMEMBER_ME": "Ricordati di me", "REMOVE": "rimuovere", "RENAME": "rinomina", "RENAME_AS": "rinomina come", "RESTRICTIONS": "restrizioni", "RUNNING": "attivo", "SAVE_CURRENT_FILE": "salva il file corrente", "SEARCH": "cerca", "SETTINGS": "impostazioni", "SHARE": "condividi", "SHARED_DRIVE": "unità condivisa", "SKIP_TO_CONTENT": "Vai al contenuto", "SORT": "ordina", "SORT_BY_DATE": "ordinare per data", "SORT_BY_NAME": "ordina per nome", "SORT_BY_SIZE": "ordina per dimensione", "SORT_BY_TYPE": "ordina per tipo", "STARRED": "preferiti", "SUPPORT": "supporto", "TAG": "tag", "TAGS": "tag", "THERE_IS_NOTHING_HERE": "non c'è niente qui", "THE_FILE_{{VALUE}}_WAS_DELETED": "il file {{VALUE}} è stato eliminato", "THE_FILE_{{VALUE}}_WAS_RENAMED": "il file {{VALUE}} è stato rinominato", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "il collegamento è stato copiato negli appunti", "THE_LINK_WONT_BE_VALID_AFTER": "il link non sarà valido dopo", "TIMEOUT": "tempo scaduto", "TODO": "fare", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "congestione del traffico, riprovare più tardi", "UPLOAD": "carica", "UPLOADER": "uploader", "USERNAME": "nome utente", "VERSION": "versione", "VIEWER": "spettatore", "WAITING": "in attesa", "YES": "sì", "YOUR_EMAIL_ADDRESS": "Il tuo indirizzo email", "YOUR_FILES": "i tuoi file", "YOUR_MASTER_PASSWORD": "la tua password principale", "YOU_CANT_DO_THAT": "non puoi farlo" } ================================================ FILE: public/assets/locales/ja.json ================================================ { "ABORTED": "中止", "ABORT_CURRENT_UPLOADS?": "アップロードを中止しますか?", "ACTIVITY": "アクティビティ", "ADVANCED": "詳細", "ALL_DONE": "全て完了", "ALREADY_EXIST": "すでに存在しています", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "ファイル {{VALUE}} が作成されました", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "フォルダ {{VALUE}} が作成されました", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "ブックマーク", "CAMERA": "カメラ", "CANCEL": "キャンセル", "CANNOT_ESTABLISH_A_CONNECTION": "接続を確立できませんでした", "CANT_LOAD_THIS_PICTURE": "この画像を読み込むことができません", "CANT_USE_FILESYSTEM": "ファイルシステムを使用できません", "CAN_RESHARE": "再共有の許可", "CODE": "コード", "CONFIGURE": "設定", "CONFIRM_BY_TYPING": "入力して確認", "CONNECT": "接続", "CONNECTION_LOST": "接続が失われました", "COPIED_TO_CLIPBOARD": "クリップボードにコピーしました", "CREATE_A_NEW_LINK": "新しいリンクを作成", "CREATE_A_TAG": "タグを作成", "CURRENT": "現在", "CURRENT_UPLOAD": "アップロード中", "CUSTOM_LINK_URL": "カスタムリンクURL", "DASHBOARD": "ダッシュボード", "DATE": "日付", "DISPLAY_HIDDEN_FILES": "隠しファイルを表示", "DOESNT_MATCH": "一致しません", "DONE": "完了しました", "DOWNLOAD": "ダウンロード", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "保存しますか?", "DROP_HERE_TO_UPLOAD": "ここにドロップしてアップロード", "EDITOR": "編集", "EMBED": "埋め込む", "EMPTY": "空", "ENCRYPTION_KEY": "暗号化キー", "ENDPOINT": "エンドポイント", "ERROR": "エラー", "EXISTING_LINKS": "既存のリンク", "EXPIRATION": "有効期限", "EXPORT_AS_{{VALUE}}": "{{VALUE}}としてエクスポート", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "頻繁にアクセスするフォルダがここに表示されます", "HIDE_HIDDEN_FILES": "隠しファイルを非表示", "HOME": "ホーム", "HOSTNAME*": "ホスト名", "HOST_KEY": "ホストキー", "INCORRECT_PASSWORD": "パスワードが正しくありません", "INFO": "情報", "INTERNAL_ERROR": "内部エラー", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "内部エラー:{{VALUE}}を作成できません", "INVALID_ACCOUNT": "利用できないアカウント", "INVALID_PASSWORD": "利用できないパスワード", "LAYOUT": "レイアウト", "LOADING": "ローディング", "LOCATION": "場所", "MISSING_DEPENDENCY": "依存関係が不足しています", "MORE_DETAILS": "詳細", "NAVIGATE": "移動", "NEW_FILE": "新しいファイル", "NEW_FILE::SHORT": "新しいファイル", "NEW_FOLDER": "新しいディレクトリ", "NEW_FOLDER::SHORT": "新しいディレクトリ", "NO": "いいえ", "NOT_ALLOWED": "許可されていません", "NOT_AUTHORISED": "認証されていません", "NOT_FOUND": "見つかりません", "NOT_IMPLEMENTED": "実装されていません", "NOT_SUPPORTED": "サポートされていません", "NOT_VALID": "有効ではありません", "NUMBER_OF_CONNECTIONS": "接続数", "OK": "OK", "ONLY_FOR_USERS": "ユーザー限定", "OOPS": "Oops", "PASSPHRASE": "パスフレーズ", "PASSWORD": "パスワード", "PASSWORD_CANT_BE_EMPTY": "パスワードは空にできません", "PATH": "パス", "PERMISSION_DENIED": "アクセスが拒否されました", "PICK_A_MASTER_PASSWORD": "マスターパスワードを設定", "PORT": "ポート", "POWERED_BY": "Powered by", "PROPERTIES": "プロパティ", "PROTECT_ACCESS_WITH_A_PASSWORD": "パスワードでアクセスを保護する", "QUICK_ACCESS": "クイックアクセス", "REGION": "領域", "REMEMBER_ME": "記憶する", "REMOVE": "削除", "RENAME": "名前を変更", "RENAME_AS": "次の名前に変更", "RESTRICTIONS": "制限", "RUNNING": "実行中", "SAVE_CURRENT_FILE": "このファイルを保存", "SEARCH": "検索", "SETTINGS": "設定", "SHARE": "シェア", "SHARED_DRIVE": "共有ドライブ", "SKIP_TO_CONTENT": "コンテンツにスキップ", "SORT": "並べ替え", "SORT_BY_DATE": "日付順", "SORT_BY_NAME": "名前順", "SORT_BY_SIZE": "サイズ順", "SORT_BY_TYPE": "種類順", "STARRED": "スター付き", "SUPPORT": "サポート", "TAG": "タグ", "TAGS": "タグ", "THERE_IS_NOTHING_HERE": "何もありません", "THE_FILE_{{VALUE}}_WAS_DELETED": "ファイル {{VALUE}} が削除されました", "THE_FILE_{{VALUE}}_WAS_RENAMED": "ファイル {{VALUE}} の名前が変更されました", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "リンクがクリップボードにコピーされました", "THE_LINK_WONT_BE_VALID_AFTER": "リンクはその後無効になります", "TIMEOUT": "タイムアウト", "TODO": "Todo", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "しばらくしてからもう一度お試しください", "UPLOAD": "アップロード", "UPLOADER": "アップロー", "USERNAME": "ユーザー名", "VERSION": "バージョン", "VIEWER": "閲覧", "WAITING": "待機中", "YES": "はい", "YOUR_EMAIL_ADDRESS": "メールアドレス", "YOUR_FILES": "あなたのファイル", "YOUR_MASTER_PASSWORD": "マスターパスワード", "YOU_CANT_DO_THAT": "実行できません" } ================================================ FILE: public/assets/locales/ka.json ================================================ { "ABORTED": "შეწყვეტილი", "ABORT_CURRENT_UPLOADS?": "მიმდინარე ატვირთვის შეწყვეტა?", "ACTIVITY": "აქტივობა", "ADVANCED": "მოწინავე", "ALL_DONE": "ყველაფერი შესრულებულია", "ALREADY_EXIST": "უკვე არსებობს", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "შეიქმნა ფაილი სახელწოდებით {{VALUE}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "შეიქმნა საქაღალდე სახელწოდებით {{VALUE}", "BEAUTIFUL_URL": "ლამაზი_ურლი", "BOOKMARK": "სანიშნე", "CAMERA": "კამერა", "CANCEL": "გაუქმება", "CANNOT_ESTABLISH_A_CONNECTION": "არ შეუძლია კავშირის დამყარება", "CANT_LOAD_THIS_PICTURE": "ამ სურათის ჩატვირთვა ვერ მოხერხდა", "CANT_USE_FILESYSTEM": "შეგიძლიათ გამოიყენოთ ფაილების სისტემა", "CAN_RESHARE": "შეუძლია ხელახლა გაზიარება", "CODE": "კოდი", "CONFIGURE": "კონფიგურაცია", "CONFIRM_BY_TYPING": "აკრეფით დაადასტურეთ", "CONNECT": "დაკავშირება", "CONNECTION_LOST": "კავშირი დაკარგულია", "COPIED_TO_CLIPBOARD": "გადაწერა ბუფერში", "CREATE_A_NEW_LINK": "შექმენით ახალი ბმული", "CREATE_A_TAG": "ტეგის შექმნა", "CURRENT": "მიმდინარე", "CURRENT_UPLOAD": "მიმდინარე ატვირთვა", "CUSTOM_LINK_URL": "პირადი ბმული URL", "DASHBOARD": "დაფა", "DATE": "თარიღი", "DISPLAY_HIDDEN_FILES": "ფარული ფაილების ჩვენება", "DOESNT_MATCH": "არ ემთხვევა", "DONE": "შესრულებულია", "DOWNLOAD": "ჩამოტვირთვა", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "გსურთ შეინახოთ ცვლილებები", "DROP_HERE_TO_UPLOAD": "ჩამოაგდეთ აქ ატვირთვისთვის", "EDITOR": "რედაქტორი", "EMBED": "ჩართვა", "EMPTY": "ცარიელი", "ENCRYPTION_KEY": "დაშიფვრის გასაღები", "ENDPOINT": "დასკვნა", "ERROR": "შეცდომა", "EXISTING_LINKS": "არსებული ბმულები", "EXPIRATION": "ვადის გასვლა", "EXPORT_AS_{{VALUE}}": "ექსპორტი, როგორც {{VALUE", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "აქ ხშირად ნახვის საქაღალდეები იქნება ნაჩვენები", "HIDE_HIDDEN_FILES": "დამალული ფაილების დამალვა", "HOME": "მთავარი", "HOSTNAME*": "მასპინძლის სახელი", "HOST_KEY": "მასპინძელი გასაღები", "INCORRECT_PASSWORD": "არასწორი პაროლი", "INFO": "ინფორმაცია", "INTERNAL_ERROR": "შიდა შეცდომა", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "შიდა შეცდომა: ვერ შექმნით {{VALUE}", "INVALID_ACCOUNT": "არასწორი ანგარიში", "INVALID_PASSWORD": "არასწორი პაროლი", "LAYOUT": "განლაგება", "LOADING": "ჩატვირთვა", "LOCATION": "ადგილმდებარეობა", "MISSING_DEPENDENCY": "დაკარგული დამოკიდებულება", "MORE_DETAILS": "მეტი დეტალი", "NAVIGATE": "ნავიგაცია", "NEW_FILE": "ახალი ფაილი", "NEW_FILE::SHORT": "ახალი ფაილი", "NEW_FOLDER": "ახალი დირექტორია", "NEW_FOLDER::SHORT": "ახალი დირექტორია", "NO": "არა", "NOT_ALLOWED": "არაა ნებადართული", "NOT_AUTHORISED": "არ არის უფლებამოსილი", "NOT_FOUND": "ნაპოვნია", "NOT_IMPLEMENTED": "არ განხორციელებულა", "NOT_SUPPORTED": "არ არის მხარდაჭერილი", "NOT_VALID": "მართებული არ არის", "NUMBER_OF_CONNECTIONS": "კავშირების რაოდენობა", "OK": "კარგი", "ONLY_FOR_USERS": "მხოლოდ მომხმარებლებისთვის", "OOPS": "უი", "PASSPHRASE": "ფრაზის ფრაზა", "PASSWORD": "პაროლი", "PASSWORD_CANT_BE_EMPTY": "პაროლი ვერ იქნება ცარიელი", "PATH": "ბილიკი", "PERMISSION_DENIED": "წვდომა აკრძალულია", "PICK_A_MASTER_PASSWORD": "აირჩიე სამაგისტრო პაროლი", "PORT": "პორტი", "POWERED_BY": "იკვებება", "PROPERTIES": "თვისებები", "PROTECT_ACCESS_WITH_A_PASSWORD": "დაიცავით წვდომა პაროლით", "QUICK_ACCESS": "სწრაფი წვდომა", "REGION": "რეგიონი", "REMEMBER_ME": "დამიმახსოვრე", "REMOVE": "ამოიღე", "RENAME": "სახელის შეცვლა", "RENAME_AS": "სახელის შეცვლა როგორც", "RESTRICTIONS": "შეზღუდვები", "RUNNING": "სირბილი", "SAVE_CURRENT_FILE": "მიმდინარე ფაილის შენახვა", "SEARCH": "ძებნა", "SETTINGS": "პარამეტრები", "SHARE": "გაზიარება", "SHARED_DRIVE": "გაზიარებული დისკი", "SKIP_TO_CONTENT": "შინაარსზე გადასვლა", "SORT": "დალაგება", "SORT_BY_DATE": "დაალაგეთ თარიღით", "SORT_BY_NAME": "დაალაგე სახელით", "SORT_BY_SIZE": "დაალაგე ზომით", "SORT_BY_TYPE": "დახარისხება ტიპის მიხედვით", "STARRED": "ჩანიშნული.", "SUPPORT": "მხარდაჭერა", "TAG": "ტეგი", "TAGS": "ტეგები", "THERE_IS_NOTHING_HERE": "აქ არაფერია", "THE_FILE_{{VALUE}}_WAS_DELETED": "ფაილი {{VALUE}} წაიშალა", "THE_FILE_{{VALUE}}_WAS_RENAMED": "ფაილი {{VALUE} დაარქვეს", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "ბმული გადაწერა იქნა ბუფერში", "THE_LINK_WONT_BE_VALID_AFTER": "ამის შემდეგ ბმული ძალაში იქნება", "TIMEOUT": "დროის ამოწურვა", "TODO": "კეთება", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "მოძრაობის შეშუპება, სცადეთ მოგვიანებით", "UPLOAD": "ატვირთვა", "UPLOADER": "ატვირთვის", "USERNAME": "მომხმარებლის სახელი", "VERSION": "ვერსია", "VIEWER": "მნახველი", "WAITING": "ლოდინი", "YES": "დიახ", "YOUR_EMAIL_ADDRESS": "თქვენი ელ - ფოსტის მისამართი", "YOUR_FILES": "ფაილები.", "YOUR_MASTER_PASSWORD": "თქვენი სამაგისტრო პაროლი", "YOU_CANT_DO_THAT": "თქვენ არ შეგიძლიათ ამის გაკეთება" } ================================================ FILE: public/assets/locales/ko.json ================================================ { "ABORTED": "중단됨", "ABORT_CURRENT_UPLOADS?": "현재 업로드를 중단 하시겠습니까?", "ACTIVITY": "활동", "ADVANCED": "고급", "ALL_DONE": "작업 완료", "ALREADY_EXIST": "이미 존재합니다", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "이름이 {{VALUE}} 인 파일이 생성되었습니다", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "이름이 {{VALUE}} 인 폴더가 생성되었습니다", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "서표", "CAMERA": "카메라", "CANCEL": "취소", "CANNOT_ESTABLISH_A_CONNECTION": "연결을 설정할 수 없음", "CANT_LOAD_THIS_PICTURE": "이 사진을 로드 할 수 없음", "CANT_USE_FILESYSTEM": "파일 시스템을 사용할 수 없음", "CAN_RESHARE": "재공유 가능", "CODE": "암호", "CONFIGURE": "구성", "CONFIRM_BY_TYPING": "입력하여 확인", "CONNECT": "연결", "CONNECTION_LOST": "연결 끊김", "COPIED_TO_CLIPBOARD": "클립 보드에 복사", "CREATE_A_NEW_LINK": "새 링크 만들기", "CREATE_A_TAG": "태그 만들기", "CURRENT": "현재", "CURRENT_UPLOAD": "현재 업로드", "CUSTOM_LINK_URL": "맞춤 링크 URL", "DASHBOARD": "제어판", "DATE": "날짜", "DISPLAY_HIDDEN_FILES": "숨겨진 파일 표시", "DOESNT_MATCH": "일치하지 않음", "DONE": "완료", "DOWNLOAD": "다운로드", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "변경 사항을 저장 하시겠습니까?", "DROP_HERE_TO_UPLOAD": "업로드하려면 여기로 드롭하십시오", "EDITOR": "편집자", "EMBED": "비어 있는", "EMPTY": "빈", "ENCRYPTION_KEY": "암호화 키", "ENDPOINT": "종점", "ERROR": "오류", "EXISTING_LINKS": "기존 링크", "EXPIRATION": "링크 만료", "EXPORT_AS_{{VALUE}}": "{{VALUE}} (으)로 내보내기", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "자주 액세스하는 폴더가 여기에 표시됩니다", "HIDE_HIDDEN_FILES": "숨겨진 파일 숨기기", "HOME": "집", "HOSTNAME*": "호스트 이름", "HOST_KEY": "호스트 키", "INCORRECT_PASSWORD": "잘못된 비밀번호", "INFO": "정보", "INTERNAL_ERROR": "내부 에러", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "내부 오류 : {{VALUE}}을 (를) 만들 수 없습니다", "INVALID_ACCOUNT": "잘못된 계정", "INVALID_PASSWORD": "유효하지 않은 비밀번호", "LAYOUT": "공들여 나열한 것", "LOADING": "로딩 중", "LOCATION": "위치", "MISSING_DEPENDENCY": "의존성 누락", "MORE_DETAILS": "자세한 내용", "NAVIGATE": "탐색", "NEW_FILE": "새로운 파일", "NEW_FILE::SHORT": "새 파일", "NEW_FOLDER": "새로운 디렉토리", "NEW_FOLDER::SHORT": "새 디렉토리", "NO": "아니요", "NOT_ALLOWED": "허용되지 않음", "NOT_AUTHORISED": "승인되지 않음", "NOT_FOUND": "찾을 수 없습니다", "NOT_IMPLEMENTED": "구현되지 않음", "NOT_SUPPORTED": "지원되지 않습니다", "NOT_VALID": "유효하지 않습니다", "NUMBER_OF_CONNECTIONS": "연결 수", "OK": "확인", "ONLY_FOR_USERS": "사용자 만", "OOPS": "죄송합니다", "PASSPHRASE": "암호", "PASSWORD": "비밀번호", "PASSWORD_CANT_BE_EMPTY": "비밀번호는 비워 둘 수 없습니다", "PATH": "경로", "PERMISSION_DENIED": "권한 거부됨", "PICK_A_MASTER_PASSWORD": "마스터 비밀번호를 선택하십시오", "PORT": "포트", "POWERED_BY": "에 의해 구동", "PROPERTIES": "속성", "PROTECT_ACCESS_WITH_A_PASSWORD": "비밀번호로 접근 제한", "QUICK_ACCESS": "빠른 액세스", "REGION": "지역", "REMEMBER_ME": "로그인 유지", "REMOVE": "제거", "RENAME": "이름 바꾸기", "RENAME_AS": "다음으로 이름 바꾸기", "RESTRICTIONS": "제한 사항", "RUNNING": "실행중", "SAVE_CURRENT_FILE": "현재 파일 저장", "SEARCH": "검색", "SETTINGS": "설정", "SHARE": "공유하다", "SHARED_DRIVE": "공유 드라이브", "SKIP_TO_CONTENT": "내용으로 건너뛰기", "SORT": "종류", "SORT_BY_DATE": "날짜별로 정렬", "SORT_BY_NAME": "이름으로 정렬", "SORT_BY_SIZE": "크기별로 정렬", "SORT_BY_TYPE": "유형별로 정렬", "STARRED": "별표가 붙은", "SUPPORT": "지원", "TAG": "꼬리표", "TAGS": "태그", "THERE_IS_NOTHING_HERE": "여기에 아무것도 없습니다", "THE_FILE_{{VALUE}}_WAS_DELETED": "{{VALUE}} 파일이 삭제되었습니다", "THE_FILE_{{VALUE}}_WAS_RENAMED": "{{VALUE}} 파일의 이름이 변경되었습니다", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "링크가 클립 보드에 복사되었습니다", "THE_LINK_WONT_BE_VALID_AFTER": "링크는 유효하지 않습니다", "TIMEOUT": "타임 아웃", "TODO": "할일", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "전송량 정체, 나중에 다시 시도해주십시오", "UPLOAD": "업로드", "UPLOADER": "업로더", "USERNAME": "사용자 이름", "VERSION": "버전", "VIEWER": "뷰어", "WAITING": "기다리는 중", "YES": "예", "YOUR_EMAIL_ADDRESS": "귀하의 이메일 주소", "YOUR_FILES": "당신의 파일", "YOUR_MASTER_PASSWORD": "귀하의 마스터 비밀번호", "YOU_CANT_DO_THAT": "당신은 이 작업을 할 수 없습니다" } ================================================ FILE: public/assets/locales/lt.json ================================================ { "ABORTED": "Atšaukta", "ABORT_CURRENT_UPLOADS?": "nutraukti įkėlimą?", "ACTIVITY": "Veikla", "ADVANCED": "išplėstinis", "ALL_DONE": "viskas padaryta", "ALREADY_EXIST": "jau yra", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Buvo sukurtas failas pavadinimu {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Buvo sukurtas aplankas pavadinimu {{VALUE}}", "BEAUTIFUL_URL": "graži_nuoroda", "BOOKMARK": "žymė", "CAMERA": "fotoaparatas", "CANCEL": "atšaukti", "CANNOT_ESTABLISH_A_CONNECTION": "negali užmegzti ryšio", "CANT_LOAD_THIS_PICTURE": "Nepavyksta įkelti nuotraukos", "CANT_USE_FILESYSTEM": "Nepavyksta naudoti failų sistemos", "CAN_RESHARE": "gali bendrinti dar kartą", "CODE": "kodas", "CONFIGURE": "konfigūruoti", "CONFIRM_BY_TYPING": "patvirtinkite įvesdami", "CONNECT": "Prisijungti", "CONNECTION_LOST": "ryšys prarastas", "COPIED_TO_CLIPBOARD": "Nukopijuota į iškarpinę", "CREATE_A_NEW_LINK": "sukurti naują nuorodą", "CREATE_A_TAG": "sukurti žymą", "CURRENT": "esamas", "CURRENT_UPLOAD": "Įkeliama dabar", "CUSTOM_LINK_URL": "tinkintos nuorodos URL", "DASHBOARD": "prietaisų skydelis", "DATE": "data", "DISPLAY_HIDDEN_FILES": "rodyti paslėptus failus", "DOESNT_MATCH": "Neatitinka", "DONE": "padaryta", "DOWNLOAD": "parsisiųsti", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "ar norite išsaugoti pakeitimus", "DROP_HERE_TO_UPLOAD": "numeskite čia norėdami įkelti", "EDITOR": "redaktorius", "EMBED": "įterpti", "EMPTY": "tuščia", "ENCRYPTION_KEY": "šifravimo raktas", "ENDPOINT": "baigtis", "ERROR": "klaida", "EXISTING_LINKS": "esamos nuorodos", "EXPIRATION": "galiojimo laikas", "EXPORT_AS_{{VALUE}}": "eksportuoti kaip {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "čia bus rodomi dažnai prieinami aplankai", "HIDE_HIDDEN_FILES": "paslėpti paslėptus failus", "HOME": "pradžia", "HOSTNAME*": "pagrindinio kompiuterio vardas", "HOST_KEY": "pagrindinio kompiuterio raktas", "INCORRECT_PASSWORD": "Neteisingas slaptažodis", "INFO": "informacija", "INTERNAL_ERROR": "Vidinė klaida", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "vidinė klaida: negalima sukurti {{VALUE}}", "INVALID_ACCOUNT": "neteisinga sąskaita", "INVALID_PASSWORD": "Neteisingas slaptažodis", "LAYOUT": "išdėstymas", "LOADING": "įkeliama", "LOCATION": "vieta", "MISSING_DEPENDENCY": "trūksta priklausomybės", "MORE_DETAILS": "daugiau informacijos", "NAVIGATE": "naviguoti", "NEW_FILE": "naujas failas", "NEW_FILE::SHORT": "naujas failas", "NEW_FOLDER": "naujas katalogas", "NEW_FOLDER::SHORT": "naujas katal..", "NO": "ne", "NOT_ALLOWED": "neleidžiama", "NOT_AUTHORISED": "neleidžiama", "NOT_FOUND": "nerastas", "NOT_IMPLEMENTED": "neįgyvendino", "NOT_SUPPORTED": "nepalaikomas", "NOT_VALID": "negaliojantis", "NUMBER_OF_CONNECTIONS": "jungčių skaičius", "OK": "Gerai", "ONLY_FOR_USERS": "Tik vartotojams", "OOPS": "Oi", "PASSPHRASE": "slaptafrazė", "PASSWORD": "Slaptažodis", "PASSWORD_CANT_BE_EMPTY": "slaptažodis negali būti tuščias", "PATH": "kelias", "PERMISSION_DENIED": "leidimas nesuteiktas", "PICK_A_MASTER_PASSWORD": "pasirinkti pagrindinį slaptažodį", "PORT": "uostas", "POWERED_BY": "maitina", "PROPERTIES": "savybes", "PROTECT_ACCESS_WITH_A_PASSWORD": "apsaugoti prieigą slaptažodžiu", "QUICK_ACCESS": "greita prieiga", "REGION": "regionas", "REMEMBER_ME": "Prisimink mane", "REMOVE": "pašalinti", "RENAME": "pervadinti", "RENAME_AS": "pervadinti kaip", "RESTRICTIONS": "apribojimai", "RUNNING": "vykdomas", "SAVE_CURRENT_FILE": "išsaugoti dabartinį failą", "SEARCH": "Paieška", "SETTINGS": "nustatymai", "SHARE": "bendrinti", "SHARED_DRIVE": "bendrinamas diskas", "SKIP_TO_CONTENT": "Pereiti prie turinio", "SORT": "rūšiuoti", "SORT_BY_DATE": "rūšiuoti pagal datą", "SORT_BY_NAME": "rūšiuoti pagal pavadinimą", "SORT_BY_SIZE": "rūšiuoti pagal dydį", "SORT_BY_TYPE": "rūšiuoti pagal tipą", "STARRED": "pažymėta", "SUPPORT": "palaikymas", "TAG": "žyma", "TAGS": "žymos", "THERE_IS_NOTHING_HERE": "čia nieko nėra", "THE_FILE_{{VALUE}}_WAS_DELETED": "failas {{VALUE}} buvo ištrintas", "THE_FILE_{{VALUE}}_WAS_RENAMED": "failas {{VALUE}} buvo pervadintas", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "nuoroda buvo nukopijuota į mainų sritį", "THE_LINK_WONT_BE_VALID_AFTER": "Nuoroda nebegalios po", "TIMEOUT": "laikas baigėsi", "TODO": "daryti", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "transporto spūstys, bandykite dar kartą vėliau", "UPLOAD": "įkelti", "UPLOADER": "Įkėlėjas", "USERNAME": "Vartotojo vardas", "VERSION": "versija", "VIEWER": "žiūrovas", "WAITING": "laukimas", "YES": "taip", "YOUR_EMAIL_ADDRESS": "Jūsų elektroninio pašto adresas", "YOUR_FILES": "Jūsų failai", "YOUR_MASTER_PASSWORD": "jūsų pagrindinis slaptažodis", "YOU_CANT_DO_THAT": "tu negali to padaryti" } ================================================ FILE: public/assets/locales/lv.json ================================================ { "ABORTED": "pārtraukts", "ABORT_CURRENT_UPLOADS?": "Vai pārtraukt pašreizējo augšupielādi?", "ACTIVITY": "Darbība", "ADVANCED": "pārlabots", "ALL_DONE": "visi uzdevumi izpildīti", "ALREADY_EXIST": "jau pastāv", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Tika izveidots fails ar nosaukumu {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Tika izveidota mape ar nosaukumu {{VALUE}}", "BEAUTIFUL_URL": "skaista_url", "BOOKMARK": "grāmatzīme", "CAMERA": "kamera", "CANCEL": "atcelt", "CANNOT_ESTABLISH_A_CONNECTION": "nevar izveidot savienojumu", "CANT_LOAD_THIS_PICTURE": "nevar ielādēt šo attēlu", "CANT_USE_FILESYSTEM": "Nevar izmantot failu sistēmu.", "CAN_RESHARE": "var atkārtoti koplietot", "CODE": "kods", "CONFIGURE": "konfigurēt", "CONFIRM_BY_TYPING": "apstipriniet, ierakstot", "CONNECT": "savienot", "CONNECTION_LOST": "savienojums pazaudēts", "COPIED_TO_CLIPBOARD": "nokopēts uz starpliktuvi", "CREATE_A_NEW_LINK": "izveidot jaunu saiti", "CREATE_A_TAG": "izveidot tagu", "CURRENT": "pašreizējais", "CURRENT_UPLOAD": "pašreizējā augšupielāde", "CUSTOM_LINK_URL": "pielāgots saites URL", "DASHBOARD": "instruktīva panelis", "DATE": "datums", "DISPLAY_HIDDEN_FILES": "rādīt paslēptos failus", "DOESNT_MATCH": "neatbilst", "DONE": "izdarīts", "DOWNLOAD": "lejupielādēt", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Vai vēlaties saglabāt izmaiņas?", "DROP_HERE_TO_UPLOAD": "Nometiet šeit, lai augšupielādētu.", "EDITOR": "redaktors", "EMBED": "ielikt", "EMPTY": "tukšs", "ENCRYPTION_KEY": "šifrēšanas atslēga", "ENDPOINT": "galapunkts", "ERROR": "kļūda", "EXISTING_LINKS": "esošās saites", "EXPIRATION": "beigu datums", "EXPORT_AS_{{VALUE}}": "eksportēt kā {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Šeit tiks parādītas bieži pieejamās mapes", "HIDE_HIDDEN_FILES": "paslēpt paslēptos failus", "HOME": "mājas", "HOSTNAME*": "hosta nosaukums", "HOST_KEY": "hosta atslēga", "INCORRECT_PASSWORD": "nepareizs parole", "INFO": "informācija", "INTERNAL_ERROR": "iekšēja kļūda", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "iekšēja kļūda: nevar izveidot {{VALUE}}", "INVALID_ACCOUNT": "nederzīgs konts", "INVALID_PASSWORD": "nepareizs parole", "LAYOUT": "izkārtojums", "LOADING": "ielādē", "LOCATION": "atrašanās vieta", "MISSING_DEPENDENCY": "trūkst atkarība", "MORE_DETAILS": "vairāk detaļu", "NAVIGATE": "orientēties", "NEW_FILE": "jauns fails", "NEW_FILE::SHORT": "jauns fails", "NEW_FOLDER": "jauna mape", "NEW_FOLDER::SHORT": "jauna mape", "NO": "nē", "NOT_ALLOWED": "nav atļauts", "NOT_AUTHORISED": "nav autorizēts", "NOT_FOUND": "nav atrasts", "NOT_IMPLEMENTED": "nav ieviests", "NOT_SUPPORTED": "nav atbalstīts", "NOT_VALID": "nav derīgs", "NUMBER_OF_CONNECTIONS": "savienojumu skaits", "OK": "labi", "ONLY_FOR_USERS": "Tikai lietotājiem", "OOPS": "žēl", "PASSPHRASE": "paroles frāze", "PASSWORD": "parole", "PASSWORD_CANT_BE_EMPTY": "parole nevar būt tukša", "PATH": "ceļš", "PERMISSION_DENIED": "pieeja noraidīta", "PICK_A_MASTER_PASSWORD": "izvēlieties galveno paroli", "PORT": "ports", "POWERED_BY": "piedāvāta ar", "PROPERTIES": "īpašības", "PROTECT_ACCESS_WITH_A_PASSWORD": "aizsargāt piekļuvi ar paroli", "QUICK_ACCESS": "ātra piekļuve", "REGION": "reģions", "REMEMBER_ME": "atcerēties mani", "REMOVE": "noņemt", "RENAME": "pārdēvēt", "RENAME_AS": "pārdēvēt kā", "RESTRICTIONS": "ierobežojumi", "RUNNING": "darbojas", "SAVE_CURRENT_FILE": "saglabāt pašreizējo failu", "SEARCH": "meklēt", "SETTINGS": "iestatījumi", "SHARE": "koplietot", "SHARED_DRIVE": "koplietots disks", "SKIP_TO_CONTENT": "Iet uz saturu", "SORT": "kārtot", "SORT_BY_DATE": "sārtot pēc datuma", "SORT_BY_NAME": "sārtot pēc nosaukuma", "SORT_BY_SIZE": "sārtot pēc izmēra", "SORT_BY_TYPE": "sārtot pēc veida", "STARRED": "atzīmēts", "SUPPORT": "atbalsts", "TAG": "tags", "TAGS": "tagi", "THERE_IS_NOTHING_HERE": "šeit nav nekā", "THE_FILE_{{VALUE}}_WAS_DELETED": "fails {{VALUE}} tika izdzēsts", "THE_FILE_{{VALUE}}_WAS_RENAMED": "fails {{VALUE}} tika pārdēvēts", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "saites tika nokopētas starpliktuvē", "THE_LINK_WONT_BE_VALID_AFTER": "Pēc tam saite vairs nebūs derīga", "TIMEOUT": "laika iztrūkums", "TODO": "uzdevums", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "piekļuve aizņemta, mēģiniet vēlāk", "UPLOAD": "augšupielādēt", "UPLOADER": "augšupielādētājs", "USERNAME": "lietotājvārds", "VERSION": "versija", "VIEWER": "skatītājs", "WAITING": "gaida", "YES": "jā", "YOUR_EMAIL_ADDRESS": "jūsu e-pasta adrese", "YOUR_FILES": "jūsu faili", "YOUR_MASTER_PASSWORD": "jūsu galvenā parole", "YOU_CANT_DO_THAT": "nevarat to izdarīt" } ================================================ FILE: public/assets/locales/mn.json ================================================ { "ABORTED": "зогсоосон", "ABORT_CURRENT_UPLOADS?": "одоогийн байршуулалтыг зогсоох уу?", "ACTIVITY": "Үйл ажиллагаа", "ADVANCED": "дэвшилтэт", "ALL_DONE": "бүгд бэлэн", "ALREADY_EXIST": "аль хэдийн байдаг", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} нэртэй файл үүсгэгдсэн", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} нэртэй хавтас үүсгэсэн", "BEAUTIFUL_URL": "үзэсгэлэнтэй_урл", "BOOKMARK": "тэмдэглэл", "CAMERA": "камер", "CANCEL": "цуцлах", "CANNOT_ESTABLISH_A_CONNECTION": "холболт үүсгэж чадахгүй байна", "CANT_LOAD_THIS_PICTURE": "энэ зургийг ачаалах боломжгүй", "CANT_USE_FILESYSTEM": "файлын системийг ашиглах боломжгүй", "CAN_RESHARE": "хуваалцах боломжтой", "CODE": "код", "CONFIGURE": "тохируулах", "CONFIRM_BY_TYPING": "бичиж баталгаажуулна уу", "CONNECT": "холбох", "CONNECTION_LOST": "холболт тасарсан", "COPIED_TO_CLIPBOARD": "санах ой руу хуулсан", "CREATE_A_NEW_LINK": "шинэ холбоос үүсгэх", "CREATE_A_TAG": "шошго үүсгэх", "CURRENT": "Одоогийн", "CURRENT_UPLOAD": "одоогийн байршуулах", "CUSTOM_LINK_URL": "захиалгат холбоосын URL", "DASHBOARD": "хяналтын самбар", "DATE": "огноо", "DISPLAY_HIDDEN_FILES": "далд файлуудыг харуулах", "DOESNT_MATCH": "таарахгүй байна", "DONE": "хийсэн", "DOWNLOAD": "татаж авах", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "та өөрчлөлтийг хадгалахыг хүсэж байна уу", "DROP_HERE_TO_UPLOAD": "энд чирж байршуулна уу", "EDITOR": "редактор", "EMBED": "оруулсан", "EMPTY": "хоосон", "ENCRYPTION_KEY": "шифрлэх түлхүүр", "ENDPOINT": "эцсийн цэг", "ERROR": "алдаа", "EXISTING_LINKS": "байгаа холбоосууд", "EXPIRATION": "дуусах хугацаа", "EXPORT_AS_{{VALUE}}": "{{VALUE}} болгож экспорт хийх", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "эндээс шууд хандалт хийхийг харах болно", "HIDE_HIDDEN_FILES": "далд файлуудыг нуух", "HOME": "гэр", "HOSTNAME*": "хостын нэр", "HOST_KEY": "хост түлхүүр", "INCORRECT_PASSWORD": "нууц үг буруу", "INFO": "мэдээлэл", "INTERNAL_ERROR": "Дотоод алдаа", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "дотоод алдаа: {{VALUE}} үүсгэх боломжгүй", "INVALID_ACCOUNT": "хүчингүй данс", "INVALID_PASSWORD": "Нууц үг буруу байна", "LAYOUT": "төлөвлөлт", "LOADING": "ачаалж байна", "LOCATION": "байршил", "MISSING_DEPENDENCY": "хамаарал алдагдсан", "MORE_DETAILS": "дэлгэрэнгүй мэдээлэл", "NAVIGATE": "залуурдах", "NEW_FILE": "шинэ файл", "NEW_FILE::SHORT": "шинэ файл", "NEW_FOLDER": "шинэ лавлах", "NEW_FOLDER::SHORT": "шинэ лавлах", "NO": "үгүй шүү", "NOT_ALLOWED": "зөвшөөрөгдөөгүй", "NOT_AUTHORISED": "зөвшөөрөлгүй", "NOT_FOUND": "олдсонгүй", "NOT_IMPLEMENTED": "хэрэгжүүлээгүй", "NOT_SUPPORTED": "дэмжихгүй байна", "NOT_VALID": "хүчин төгөлдөр бус", "NUMBER_OF_CONNECTIONS": "холболтын тоо", "OK": "за", "ONLY_FOR_USERS": "Зөвхөн хэрэглэгчид", "OOPS": "Өө,", "PASSPHRASE": "Нэвтрэх үг", "PASSWORD": "нууц үг", "PASSWORD_CANT_BE_EMPTY": "нууц үг хоосон байж болохгүй", "PATH": "зам", "PERMISSION_DENIED": "зөвшөөрөл олгогдоогүй", "PICK_A_MASTER_PASSWORD": "мастер нууц үг сонгох", "PORT": "боомт", "POWERED_BY": "дэмжигдсэн", "PROPERTIES": "шинж чанарууд", "PROTECT_ACCESS_WITH_A_PASSWORD": "нууц үгээр нэвтрэх эрхийг хамгаална", "QUICK_ACCESS": "түргэн нэвтрэх", "REGION": "бүс нутаг", "REMEMBER_ME": "санаж байна уу", "REMOVE": "устгах", "RENAME": "нэрийг өөрчлөх", "RENAME_AS": "дараах нэрээр өөрчлөх", "RESTRICTIONS": "хязгаарлалт", "RUNNING": "ажиллаж байгаа", "SAVE_CURRENT_FILE": "одоогийн файлыг хадгалах", "SEARCH": "хайх", "SETTINGS": "тохиргоо", "SHARE": "хуваалцах", "SHARED_DRIVE": "хуваалцсан хөтөч", "SKIP_TO_CONTENT": "Агуулга руу шилжих", "SORT": "эрэмбэлэх", "SORT_BY_DATE": "огноогоор нь ангилах", "SORT_BY_NAME": "нэрээр нь ангил", "SORT_BY_SIZE": "хэмжийн дагуу эрэмбэлэх", "SORT_BY_TYPE": "төрлөөр нь ангил", "STARRED": "тэмдэглэгдсэн", "SUPPORT": "дэмжлэг", "TAG": "шошго", "TAGS": "шошгонууда", "THERE_IS_NOTHING_HERE": "энд юу ч байхгүй", "THE_FILE_{{VALUE}}_WAS_DELETED": "файлыг {{VALUE}} устгасан байна", "THE_FILE_{{VALUE}}_WAS_RENAMED": "файлын нэр {{VALUE}} өөрчлөгдсөн", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "холбоосыг нь санах ойд хуулав", "THE_LINK_WONT_BE_VALID_AFTER": "холбоос дараа нь хүчингүй болно", "TIMEOUT": "завсарлага", "TODO": "хийх", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "түгжрэл, дараа дахин оролдоно уу", "UPLOAD": "хуулах", "UPLOADER": "байршуулагч", "USERNAME": "хэрэглэгчийн нэр", "VERSION": "хувилбар", "VIEWER": "үзэгч", "WAITING": "хүлээж байна", "YES": "тийм шүү", "YOUR_EMAIL_ADDRESS": "таны имэйл хаяг", "YOUR_FILES": "таны файлууд", "YOUR_MASTER_PASSWORD": "таны мастер нууц үг", "YOU_CANT_DO_THAT": "та үүнийг хийж чадахгүй" } ================================================ FILE: public/assets/locales/nb.json ================================================ { "ABORTED": "abortert", "ABORT_CURRENT_UPLOADS?": "avbryte gjeldende opplasting?", "ACTIVITY": "Aktivitet", "ADVANCED": "avansert", "ALL_DONE": "ferdig", "ALREADY_EXIST": "Finnes allerede", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "En fil med navnet {{VALUE}} ble opprettet", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "En mappe med navnet {{VALUE}} ble opprettet", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "bokmerke", "CAMERA": "kamera", "CANCEL": "Avbryt", "CANNOT_ESTABLISH_A_CONNECTION": "kan ikke opprette en forbindelse", "CANT_LOAD_THIS_PICTURE": "kan ikke laste dette bildet", "CANT_USE_FILESYSTEM": "kan bruke filsystem", "CAN_RESHARE": "kan dele på nytt", "CODE": "kode", "CONFIGURE": "konfigurere", "CONFIRM_BY_TYPING": "bekreft ved å skrive", "CONNECT": "koble", "CONNECTION_LOST": "forbindelsen mistet", "COPIED_TO_CLIPBOARD": "kopiert til utklippstavlen", "CREATE_A_NEW_LINK": "opprette en ny lenke", "CREATE_A_TAG": "opprett en merkelapp", "CURRENT": "strøm", "CURRENT_UPLOAD": "nåværende opplasting", "CUSTOM_LINK_URL": "tilpasset lenke URL", "DASHBOARD": "dashbord", "DATE": "Dato", "DISPLAY_HIDDEN_FILES": "vise skjulte filer", "DOESNT_MATCH": "stemmer ikke", "DONE": "ferdig", "DOWNLOAD": "nedlasting", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "vil du lagre endringene", "DROP_HERE_TO_UPLOAD": "slipp her for å laste opp", "EDITOR": "redaktør", "EMBED": "innebygg", "EMPTY": "tømme", "ENCRYPTION_KEY": "krypteringsnøkkel", "ENDPOINT": "endepunkt", "ERROR": "feil", "EXISTING_LINKS": "eksisterende lenker", "EXPIRATION": "utløp", "EXPORT_AS_{{VALUE}}": "eksportere som {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "ofte vises mapper her", "HIDE_HIDDEN_FILES": "skjul skjulte filer", "HOME": "hjem", "HOSTNAME*": "vertsnavn", "HOST_KEY": "vertsnøkkel", "INCORRECT_PASSWORD": "feil passord", "INFO": "info", "INTERNAL_ERROR": "Intern feil", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "intern feil: kan ikke opprette en {{VALUE}}", "INVALID_ACCOUNT": "ugyldig konto", "INVALID_PASSWORD": "ugyldig passord", "LAYOUT": "oppsett", "LOADING": "laster", "LOCATION": "plassering", "MISSING_DEPENDENCY": "mangler avhengighet", "MORE_DETAILS": "mer informasjon", "NAVIGATE": "navigere", "NEW_FILE": "ny fil", "NEW_FILE::SHORT": "ny fil", "NEW_FOLDER": "ny katalog", "NEW_FOLDER::SHORT": "ny katalog", "NO": "Nei", "NOT_ALLOWED": "ikke tillatt", "NOT_AUTHORISED": "ikke autorisert", "NOT_FOUND": "ikke funnet", "NOT_IMPLEMENTED": "ikke implementert", "NOT_SUPPORTED": "ikke støttet", "NOT_VALID": "ikke gyldig", "NUMBER_OF_CONNECTIONS": "antall tilkoblinger", "OK": "ok", "ONLY_FOR_USERS": "Bare for brukere", "OOPS": "Oops", "PASSPHRASE": "passphrase", "PASSWORD": "passord", "PASSWORD_CANT_BE_EMPTY": "passord kan ikke være tomt", "PATH": "sti", "PERMISSION_DENIED": "tillatelse avslått", "PICK_A_MASTER_PASSWORD": "velg et hovedpassord", "PORT": "havn", "POWERED_BY": "drevet av", "PROPERTIES": "egenskaper", "PROTECT_ACCESS_WITH_A_PASSWORD": "beskytte tilgang med et passord", "QUICK_ACCESS": "hurtigtilgang", "REGION": "region", "REMEMBER_ME": "Husk meg", "REMOVE": "fjern", "RENAME": "gi nytt navn", "RENAME_AS": "gi nytt navn som", "RESTRICTIONS": "begrensninger", "RUNNING": "kjører", "SAVE_CURRENT_FILE": "lagre gjeldende fil", "SEARCH": "Søk", "SETTINGS": "innstillinger", "SHARE": "del", "SHARED_DRIVE": "delt stasjon", "SKIP_TO_CONTENT": "Gå til innhold", "SORT": "sorter", "SORT_BY_DATE": "sorter etter dato", "SORT_BY_NAME": "sorter etter navn", "SORT_BY_SIZE": "sorter etter størrelse", "SORT_BY_TYPE": "sorter etter type", "STARRED": "stjernemerket", "SUPPORT": "Brukerstøtte", "TAG": "merkelapp", "TAGS": "merkelapper", "THERE_IS_NOTHING_HERE": "her er ingenting", "THE_FILE_{{VALUE}}_WAS_DELETED": "filen {{VALUE}} ble slettet", "THE_FILE_{{VALUE}}_WAS_RENAMED": "filen {{VALUE}} ble gitt nytt navn", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "lenken ble kopiert i utklippstavlen", "THE_LINK_WONT_BE_VALID_AFTER": "koblingen vil ikke være gyldig etter", "TIMEOUT": "tidsavbrudd", "TODO": "å gjøre", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "trafikkstopp, prøv igjen senere", "UPLOAD": "last opp", "UPLOADER": "uploader", "USERNAME": "brukernavn", "VERSION": "versjon", "VIEWER": "viewer", "WAITING": "venter", "YES": "ja", "YOUR_EMAIL_ADDRESS": "Din epostadresse", "YOUR_FILES": "dine filer", "YOUR_MASTER_PASSWORD": "ditt hovedpassord", "YOU_CANT_DO_THAT": "du kan ikke gjøre det" } ================================================ FILE: public/assets/locales/nl.json ================================================ { "ABORTED": "afgebroken", "ABORT_CURRENT_UPLOADS?": "huidige upload afbreken?", "ACTIVITY": "Activiteit", "ADVANCED": "Geavanceerd", "ALL_DONE": "helemaal klaar", "ALREADY_EXIST": "bestaat al", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Er is een bestand met de naam {{VALUE}} gemaakt", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Er is een map met de naam {{VALUE}} gemaakt", "BEAUTIFUL_URL": "mooie_url", "BOOKMARK": "bladwijzer", "CAMERA": "camera", "CANCEL": "annuleren", "CANNOT_ESTABLISH_A_CONNECTION": "kan geen verbinding tot stand brengen", "CANT_LOAD_THIS_PICTURE": "kan deze afbeelding niet laden", "CANT_USE_FILESYSTEM": "kan bestandssysteem niet gebruiken", "CAN_RESHARE": "kan opnieuw delen", "CODE": "code", "CONFIGURE": "configureren", "CONFIRM_BY_TYPING": "bevestig door te typen", "CONNECT": "verbinden", "CONNECTION_LOST": "verbinding verloren", "COPIED_TO_CLIPBOARD": "gekopieerd naar het klembord", "CREATE_A_NEW_LINK": "maak een nieuwe link", "CREATE_A_TAG": "maak een tag", "CURRENT": "actueel", "CURRENT_UPLOAD": "huidige upload", "CUSTOM_LINK_URL": "aangepaste link-URL", "DASHBOARD": "dashboard", "DATE": "datum", "DISPLAY_HIDDEN_FILES": "verborgen bestanden weergeven", "DOESNT_MATCH": "komt niet overeen", "DONE": "gedaan", "DOWNLOAD": "downloaden", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "wilt u de wijzigingen opslaan", "DROP_HERE_TO_UPLOAD": "hier neerzetten om te uploaden", "EDITOR": "editor", "EMBED": "insluiten", "EMPTY": "leeg", "ENCRYPTION_KEY": "coderingssleutel", "ENDPOINT": "eindpunt", "ERROR": "fout", "EXISTING_LINKS": "bestaande links", "EXPIRATION": "vervaldatum", "EXPORT_AS_{{VALUE}}": "exporteren als {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "vaak worden toegangsmappen hier weergegeven", "HIDE_HIDDEN_FILES": "verberg verborgen bestanden", "HOME": "home", "HOSTNAME*": "hostnaam", "HOST_KEY": "host sleutel", "INCORRECT_PASSWORD": "Incorrect wachtwoord", "INFO": "info", "INTERNAL_ERROR": "Interne fout", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "interne fout: kan geen {{VALUE}} maken", "INVALID_ACCOUNT": "ongeldig account", "INVALID_PASSWORD": "ongeldig wachtwoord", "LAYOUT": "indeling", "LOADING": "laden", "LOCATION": "plaats", "MISSING_DEPENDENCY": "ontbrekende afhankelijkheid", "MORE_DETAILS": "meer details", "NAVIGATE": "navigeren", "NEW_FILE": "nieuw bestand", "NEW_FILE::SHORT": "nieuw bestand", "NEW_FOLDER": "nieuwe map", "NEW_FOLDER::SHORT": "nieuwe map", "NO": "Nee", "NOT_ALLOWED": "niet toegestaan", "NOT_AUTHORISED": "niet bevoegd", "NOT_FOUND": "niet gevonden", "NOT_IMPLEMENTED": "Niet geïmplementeerd", "NOT_SUPPORTED": "niet ondersteund", "NOT_VALID": "niet geldig", "NUMBER_OF_CONNECTIONS": "aantal verbindingen", "OK": "OK", "ONLY_FOR_USERS": "Alleen voor gebruikers", "OOPS": "Oeps", "PASSPHRASE": "wachtwoordzin", "PASSWORD": "wachtwoord", "PASSWORD_CANT_BE_EMPTY": "wachtwoord mag niet leeg zijn", "PATH": "pad", "PERMISSION_DENIED": "toestemming geweigerd", "PICK_A_MASTER_PASSWORD": "jouw hoofdwachtwoord", "PORT": "poort", "POWERED_BY": "powered by", "PROPERTIES": "eigenschappen", "PROTECT_ACCESS_WITH_A_PASSWORD": "beveilig toegang met wachtwoord", "QUICK_ACCESS": "snelle toegang", "REGION": "regio", "REMEMBER_ME": "Onthoud me", "REMOVE": "verwijderen", "RENAME": "hernoemen", "RENAME_AS": "hernoemen als", "RESTRICTIONS": "beperkingen", "RUNNING": "verwerken", "SAVE_CURRENT_FILE": "sla het huidige bestand op", "SEARCH": "zoeken", "SETTINGS": "instellingen", "SHARE": "delen", "SHARED_DRIVE": "gedeelde schijf", "SKIP_TO_CONTENT": "Ga naar inhoud", "SORT": "sorteren", "SORT_BY_DATE": "sorteren op datum", "SORT_BY_NAME": "sorteren op naam", "SORT_BY_SIZE": "sorteren op grootte", "SORT_BY_TYPE": "sorteer op type", "STARRED": "met ster", "SUPPORT": "ondersteuning", "TAG": "tag", "TAGS": "tags", "THERE_IS_NOTHING_HERE": "er is niks hier", "THE_FILE_{{VALUE}}_WAS_DELETED": "het bestand {{VALUE}} is verwijderd", "THE_FILE_{{VALUE}}_WAS_RENAMED": "het bestand {{VALUE}} is hernoemd", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "de link is gekopieerd naar het klembord", "THE_LINK_WONT_BE_VALID_AFTER": "de link is daarna niet meer geldig", "TIMEOUT": "tijdslimiet", "TODO": "Te doen", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "verkeersopstoppingen, probeer het later opnieuw", "UPLOAD": "uploaden", "UPLOADER": "uploader", "USERNAME": "gebruikersnaam", "VERSION": "versie", "VIEWER": "weergave", "WAITING": "aan het wachten", "YES": "Ja", "YOUR_EMAIL_ADDRESS": "jouw e-mailadres", "YOUR_FILES": "jouw bestanden", "YOUR_MASTER_PASSWORD": "uw hoofdwachtwoord", "YOU_CANT_DO_THAT": "dat kun je niet doen" } ================================================ FILE: public/assets/locales/no.json ================================================ { "ABORTED": "Avbrutt", "ABORT_CURRENT_UPLOADS?": "Avbryt pågående opplastinger?", "ACTIVITY": "Aktivitet", "ADVANCED": "Avansert", "ALL_DONE": "Alt er klart!", "ALREADY_EXIST": "finnes allerede", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "En fil med navn \"{{VALUE}}\" ble opprettet", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "En mappe med navn \"{{VALUE}}\" ble opprettet", "BEAUTIFUL_URL": "pen_url", "BOOKMARK": "Bokmerke", "CAMERA": "Kamera", "CANCEL": "Avbryt", "CANNOT_ESTABLISH_A_CONNECTION": "Kan ikke opprette en tilkobling", "CANT_LOAD_THIS_PICTURE": "Kan ikke laste inn dette bildet", "CANT_USE_FILESYSTEM": "Kan ikke bruke filsystemet", "CAN_RESHARE": "Kan dele videre", "CODE": "Kode", "CONFIGURE": "Konfigurer", "CONFIRM_BY_TYPING": "Bekreft ved å skrive", "CONNECT": "Koble til", "CONNECTION_LOST": "Tilkobling brutt", "COPIED_TO_CLIPBOARD": "Kopiert til utklippstavlen", "CREATE_A_NEW_LINK": "Opprett en ny delingslenke", "CREATE_A_TAG": "Opprett en merkelapp", "CURRENT": "Gjeldende", "CURRENT_UPLOAD": "Pågående opplastning", "CUSTOM_LINK_URL": "Tilpasset lenke-URL", "DASHBOARD": "Oversikt", "DATE": "Dato", "DISPLAY_HIDDEN_FILES": "Vis skjulte filer", "DOESNT_MATCH": "Matcher ikke", "DONE": "Ferdig", "DOWNLOAD": "Last ned", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Vil du lagre endringene?", "DROP_HERE_TO_UPLOAD": "Dra og slipp filer og mapper her for å laste opp", "EDITOR": "Redigerer", "EMBED": "Bygg inn", "EMPTY": "Tom", "ENCRYPTION_KEY": "Krypteringsnøkkel", "ENDPOINT": "Endepunkt", "ERROR": "Feil", "EXISTING_LINKS": "Eksisterende lenker", "EXPIRATION": "Utløpstid", "EXPORT_AS_{{VALUE}}": "Eksporter som {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Ofte brukte mapper vises her", "HIDE_HIDDEN_FILES": "Skjul skjulte filer", "HOME": "Hjem", "HOSTNAME*": "Vertsnavn*", "HOST_KEY": "Verts­nøkkel", "INCORRECT_PASSWORD": "Feil passord", "INFO": "Informasjon", "INTERNAL_ERROR": "Intern feil", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "Intern feil: Kan ikke opprette {{VALUE}}", "INVALID_ACCOUNT": "Ugyldig konto", "INVALID_PASSWORD": "Ugyldig passord", "LAYOUT": "Oppsett", "LOADING": "Laster", "LOCATION": "Plassering", "MISSING_DEPENDENCY": "Mangler avhengighet", "MORE_DETAILS": "Mer informasjon", "NAVIGATE": "Naviger", "NEW_FILE": "Ny fil", "NEW_FILE::SHORT": "Ny fil", "NEW_FOLDER": "Ny mappe", "NEW_FOLDER::SHORT": "Ny mappe", "NO": "Nei", "NOT_ALLOWED": "Ikke tillatt", "NOT_AUTHORISED": "Ikke autorisert", "NOT_FOUND": "Ikke funnet", "NOT_IMPLEMENTED": "Ikke implementert", "NOT_SUPPORTED": "Ikke støttet", "NOT_VALID": "Ikke gyldig", "NUMBER_OF_CONNECTIONS": "Antall tilkoblinger", "OK": "OK", "ONLY_FOR_USERS": "Kun for bestemte brukere", "OOPS": "Oops!", "PASSPHRASE": "Passordfrase", "PASSWORD": "Passord", "PASSWORD_CANT_BE_EMPTY": "Passordet kan ikke være tomt", "PATH": "Sti", "PERMISSION_DENIED": "Ingen tilgang", "PICK_A_MASTER_PASSWORD": "Velg et hovedpassord", "PORT": "Port", "POWERED_BY": "Drevet av", "PROPERTIES": "Egenskaper", "PROTECT_ACCESS_WITH_A_PASSWORD": "Beskytt tilgangen med passord", "QUICK_ACCESS": "Hurtigtilgang", "REGION": "Region", "REMEMBER_ME": "Husk meg", "REMOVE": "Fjern", "RENAME": "Gi nytt navn", "RENAME_AS": "Gi nytt navn som", "RESTRICTIONS": "Begrensninger", "RUNNING": "Kjører", "SAVE_CURRENT_FILE": "Lagre gjeldende fil", "SEARCH": "Søk", "SETTINGS": "Innstillinger", "SHARE": "Del", "SHARED_DRIVE": "Delt stasjon", "SKIP_TO_CONTENT": "Gå til innhold", "SORT": "Sorter", "SORT_BY_DATE": "Sorter etter dato", "SORT_BY_NAME": "Sorter etter navn", "SORT_BY_SIZE": "Sorter etter størrelse", "SORT_BY_TYPE": "Sorter etter type", "STARRED": "Favoritter", "SUPPORT": "Støtte", "TAG": "Tag", "TAGS": "Tagger", "THERE_IS_NOTHING_HERE": "Det er ingenting her", "THE_FILE_{{VALUE}}_WAS_DELETED": "Filen \"{{VALUE}}\" ble slettet", "THE_FILE_{{VALUE}}_WAS_RENAMED": "Filen \"{{VALUE}}\" ble gitt nytt navn", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "Lenken ble kopiert til utklippstavlen", "THE_LINK_WONT_BE_VALID_AFTER": "Lenken blir ugyldig etter", "TIMEOUT": "Tidsavbrudd", "TODO": "Gjøremål", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Høy trafikk, prøv igjen senere", "UPLOAD": "Last opp", "UPLOADER": "Opplaster", "USERNAME": "Brukernavn", "VERSION": "Versjon", "VIEWER": "Viser", "WAITING": "Venter", "YES": "Ja", "YOUR_EMAIL_ADDRESS": "Din e-postadresse", "YOUR_FILES": "Dine filer", "YOUR_MASTER_PASSWORD": "Ditt hovedpassord", "YOU_CANT_DO_THAT": "Du kan ikke gjøre det" } ================================================ FILE: public/assets/locales/pl.json ================================================ { "ABORTED": "anulowano", "ABORT_CURRENT_UPLOADS?": "Przerwać przesyłanie plików?", "ACTIVITY": "Aktywność", "ADVANCED": "Zaawansowane", "ALL_DONE": "Wszystko zrobione", "ALREADY_EXIST": "już istnieje", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Utworzono plik o nazwie {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Utworzono katalog o nazwie {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "zakładka", "CAMERA": "Kamera", "CANCEL": "Anuluj", "CANNOT_ESTABLISH_A_CONNECTION": "Nie można nawiązać połączenia", "CANT_LOAD_THIS_PICTURE": "Nie można załadować tego zdjęcia", "CANT_USE_FILESYSTEM": "Nie można odczytać systemu plików", "CAN_RESHARE": "można udostępnić dalej", "CODE": "Kod", "CONFIGURE": "Skonfiguruj", "CONFIRM_BY_TYPING": "Potwierdź operację, pisząc", "CONNECT": "Połącz", "CONNECTION_LOST": "utracono połączenie", "COPIED_TO_CLIPBOARD": "Skopionowano do schowka", "CREATE_A_NEW_LINK": "Utwórz nowe łącze", "CREATE_A_TAG": "utwórz tag", "CURRENT": "Aktualny", "CURRENT_UPLOAD": "bieżące przesyłanie", "CUSTOM_LINK_URL": "niestandardowy adres łącza", "DASHBOARD": "Kokpit", "DATE": "Data", "DISPLAY_HIDDEN_FILES": "Pokaż ukryte pliki", "DOESNT_MATCH": "nie pasuje", "DONE": "gotowy", "DOWNLOAD": "Pobierz", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Czy chcesz zapisać zmiany", "DROP_HERE_TO_UPLOAD": "upuść tutaj, aby przesłać", "EDITOR": "Edycja", "EMBED": "osadzić", "EMPTY": "pusty", "ENCRYPTION_KEY": "Klucz szyfrujący", "ENDPOINT": "Punkt końcowy", "ERROR": "Błąd", "EXISTING_LINKS": "istniejące linki", "EXPIRATION": "Wygaśnięcie", "EXPORT_AS_{{VALUE}}": "Eksportuj jako {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Często używane katalogi będą widoczne tutaj", "HIDE_HIDDEN_FILES": "Ukryj pliki ukryte", "HOME": "start", "HOSTNAME*": "Nazwa hosta", "HOST_KEY": "Klucz hosta", "INCORRECT_PASSWORD": "niepoprawne hasło", "INFO": "Informacje", "INTERNAL_ERROR": "Błąd wewnętrzny", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "Błąd wewnętrzny: nie można utworzyć {{VALUE}}", "INVALID_ACCOUNT": "nieważne konto", "INVALID_PASSWORD": "Nieprawidłowe hasło", "LAYOUT": "układ", "LOADING": "ładowanie", "LOCATION": "Lokalizacja", "MISSING_DEPENDENCY": "Brakujące zależności", "MORE_DETAILS": "szczegóły", "NAVIGATE": "nawiguj", "NEW_FILE": "nowy plik", "NEW_FILE::SHORT": "nowy plik", "NEW_FOLDER": "nowy folder", "NEW_FOLDER::SHORT": "nowy katalog", "NO": "Nie", "NOT_ALLOWED": "niedozwolony", "NOT_AUTHORISED": "nieautoryzowany", "NOT_FOUND": "nie znaleziono", "NOT_IMPLEMENTED": "nie zaimplementowano", "NOT_SUPPORTED": "niewspierany", "NOT_VALID": "niepoprawny", "NUMBER_OF_CONNECTIONS": "Liczba połączeń", "OK": "Ok", "ONLY_FOR_USERS": "Tylko dla użytkowników", "OOPS": "Ups", "PASSPHRASE": "Hasło", "PASSWORD": "Hasło", "PASSWORD_CANT_BE_EMPTY": "Hasło nie może być puste", "PATH": "ścieżka", "PERMISSION_DENIED": "Odmowa dostępu", "PICK_A_MASTER_PASSWORD": "Wybierz hasło główne", "PORT": "Port", "POWERED_BY": "Działa dzięki", "PROPERTIES": "Właściwości", "PROTECT_ACCESS_WITH_A_PASSWORD": "Chroń dostęp hasłem", "QUICK_ACCESS": "szybki dostęp", "REGION": "Region", "REMEMBER_ME": "Zapamiętaj mnie", "REMOVE": "Usuń", "RENAME": "zmień nazwę", "RENAME_AS": "zmień nazwę na", "RESTRICTIONS": "Ograniczenia", "RUNNING": "Uruchomione", "SAVE_CURRENT_FILE": "Zapisz otwarty plik", "SEARCH": "Szukaj", "SETTINGS": "Ustawienia", "SHARE": "udostępnij", "SHARED_DRIVE": "wspólny dysk", "SKIP_TO_CONTENT": "Przejdź do treści", "SORT": "sortuj", "SORT_BY_DATE": "Sortuj według daty", "SORT_BY_NAME": "sortuj według nazwy", "SORT_BY_SIZE": "sortuj według rozmiaru", "SORT_BY_TYPE": "Sortuj według typu", "STARRED": "oznaczone gwiazdką", "SUPPORT": "Wsparcie", "TAG": "tag", "TAGS": "tagi", "THERE_IS_NOTHING_HERE": "Nic tutaj nie ma", "THE_FILE_{{VALUE}}_WAS_DELETED": "Obiekt {{VALUE}} został usunięty", "THE_FILE_{{VALUE}}_WAS_RENAMED": "Nazwa obiektu {{VALUE}} została zmieniona", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "Łącze zostało skopiowane do schowka", "THE_LINK_WONT_BE_VALID_AFTER": "Łącze wygaśnie po", "TIMEOUT": "Koniec czasu", "TODO": "Do zrobienia", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Spowolnienie sieci, spróbuj ponownie później", "UPLOAD": "prześlij", "UPLOADER": "Przesyłający", "USERNAME": "Nazwa użytkownika", "VERSION": "wersja", "VIEWER": "Odczyt", "WAITING": "Oczekiwanie", "YES": "Tak", "YOUR_EMAIL_ADDRESS": "Twój adres e-mail", "YOUR_FILES": "twoje pliki", "YOUR_MASTER_PASSWORD": "Twoje hasło główne", "YOU_CANT_DO_THAT": "Nie możesz tego zrobić" } ================================================ FILE: public/assets/locales/pt.json ================================================ { "ABORTED": "abortado", "ABORT_CURRENT_UPLOADS?": "abortar upload atual?", "ACTIVITY": "Atividade", "ADVANCED": "avançado", "ALL_DONE": "tudo feito", "ALREADY_EXIST": "já existe", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Um arquivo chamado {{VALUE}} foi criado", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Uma pasta chamada {{VALUE}} foi criada", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "marcador", "CAMERA": "Câmera", "CANCEL": "cancelar", "CANNOT_ESTABLISH_A_CONNECTION": "não pode estabelecer uma conexão", "CANT_LOAD_THIS_PICTURE": "não consigo carregar esta foto", "CANT_USE_FILESYSTEM": "pode usar sistema de arquivos", "CAN_RESHARE": "pode compartilhar de novo", "CODE": "código", "CONFIGURE": "configurar", "CONFIRM_BY_TYPING": "confirme digitando", "CONNECT": "conectar", "CONNECTION_LOST": "conexão perdida", "COPIED_TO_CLIPBOARD": "Copiado para a área de transferência", "CREATE_A_NEW_LINK": "crie um novo link", "CREATE_A_TAG": "criar uma etiqueta", "CURRENT": "atual", "CURRENT_UPLOAD": "upload atual", "CUSTOM_LINK_URL": "URL de link personalizado", "DASHBOARD": "painel de controle", "DATE": "encontro", "DISPLAY_HIDDEN_FILES": "exibir arquivos ocultos", "DOESNT_MATCH": "não corresponde", "DONE": "feito", "DOWNLOAD": "baixar", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "você quer salvar as alterações", "DROP_HERE_TO_UPLOAD": "solte aqui para fazer upload", "EDITOR": "editor", "EMBED": "incorporar", "EMPTY": "esvaziar", "ENCRYPTION_KEY": "Chave de encriptação", "ENDPOINT": "ponto final", "ERROR": "erro", "EXISTING_LINKS": "links existentes", "EXPIRATION": "expiração", "EXPORT_AS_{{VALUE}}": "exportar como {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "As pastas de acesso frequente serão mostradas aqui", "HIDE_HIDDEN_FILES": "ocultar arquivos ocultos", "HOME": "início", "HOSTNAME*": "nome de host*", "HOST_KEY": "chave do host", "INCORRECT_PASSWORD": "senha incorreta", "INFO": "informação", "INTERNAL_ERROR": "Erro interno", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "erro interno: não é possível criar um {{VALUE}}", "INVALID_ACCOUNT": "conta inválida", "INVALID_PASSWORD": "senha inválida", "LAYOUT": "layout", "LOADING": "carregando", "LOCATION": "localização", "MISSING_DEPENDENCY": "falta de dependência", "MORE_DETAILS": "mais detalhes", "NAVIGATE": "navegar", "NEW_FILE": "novo arquivo", "NEW_FILE::SHORT": "novo arquivo", "NEW_FOLDER": "novo diretório", "NEW_FOLDER::SHORT": "novo diret.", "NO": "não", "NOT_ALLOWED": "não permitido", "NOT_AUTHORISED": "não autorizado", "NOT_FOUND": "não encontrado", "NOT_IMPLEMENTED": "não implementado", "NOT_SUPPORTED": "não suportado", "NOT_VALID": "inválido", "NUMBER_OF_CONNECTIONS": "número de conexões", "OK": "Está bem", "ONLY_FOR_USERS": "Somente para usuários", "OOPS": "Opa", "PASSPHRASE": "frase secreta", "PASSWORD": "senha", "PASSWORD_CANT_BE_EMPTY": "a senha não pode estar vazia", "PATH": "caminho", "PERMISSION_DENIED": "permissão negada", "PICK_A_MASTER_PASSWORD": "escolha uma senha mestra", "PORT": "porta", "POWERED_BY": "distribuído por", "PROPERTIES": "propriedades", "PROTECT_ACCESS_WITH_A_PASSWORD": "proteger o acesso com uma senha", "QUICK_ACCESS": "acesso rápido", "REGION": "região", "REMEMBER_ME": "lembre de mim", "REMOVE": "retirar", "RENAME": "renomear", "RENAME_AS": "renomear como", "RESTRICTIONS": "restrições", "RUNNING": "corrida", "SAVE_CURRENT_FILE": "salvar arquivo atual", "SEARCH": "procurar", "SETTINGS": "definições", "SHARE": "compartilhar", "SHARED_DRIVE": "unidade compartilhada", "SKIP_TO_CONTENT": "Pular para o conteúdo", "SORT": "ordenar", "SORT_BY_DATE": "classificar por data", "SORT_BY_NAME": "ordenar por nome", "SORT_BY_SIZE": "ordenar por tamanho", "SORT_BY_TYPE": "ordenar por tipo", "STARRED": "marcado com estrela", "SUPPORT": "Apoio, suporte", "TAG": "etiqueta", "TAGS": "etiquetas", "THERE_IS_NOTHING_HERE": "Não há nada aqui", "THE_FILE_{{VALUE}}_WAS_DELETED": "o arquivo {{VALUE}} foi excluído", "THE_FILE_{{VALUE}}_WAS_RENAMED": "o arquivo {{VALUE}} foi renomeado", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "o link foi copiado na área de transferência", "THE_LINK_WONT_BE_VALID_AFTER": "o link não será válido depois", "TIMEOUT": "tempo esgotado", "TODO": "façam", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "congestionamento de tráfego, tente novamente mais tarde", "UPLOAD": "carregar", "UPLOADER": "remetente", "USERNAME": "nome do usuário", "VERSION": "versão", "VIEWER": "espectador", "WAITING": "esperando", "YES": "sim", "YOUR_EMAIL_ADDRESS": "Seu endereço de email", "YOUR_FILES": "seus arquivos", "YOUR_MASTER_PASSWORD": "sua senha mestra", "YOU_CANT_DO_THAT": "você não pode fazer isso" } ================================================ FILE: public/assets/locales/ro.json ================================================ { "ABORTED": "avortat", "ABORT_CURRENT_UPLOADS?": "anula încărcarea curentă?", "ACTIVITY": "Activitate", "ADVANCED": "avansat", "ALL_DONE": "totul este gata", "ALREADY_EXIST": "Există deja", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "A fost creat un fișier numit {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "A fost creat un folder numit {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "semn de carte", "CAMERA": "aparat foto", "CANCEL": "Anulare", "CANNOT_ESTABLISH_A_CONNECTION": "nu poate stabili o conexiune", "CANT_LOAD_THIS_PICTURE": "nu pot încărca această imagine", "CANT_USE_FILESYSTEM": "poate utiliza sistemul de fișiere", "CAN_RESHARE": "se poate reîncărca", "CODE": "cod", "CONFIGURE": "Configureaza", "CONFIRM_BY_TYPING": "confirmați tastând", "CONNECT": "conectați", "CONNECTION_LOST": "conexiune pierdută", "COPIED_TO_CLIPBOARD": "copiat în clipboard", "CREATE_A_NEW_LINK": "creați o nouă legătură", "CREATE_A_TAG": "creează o etichetă", "CURRENT": "actual", "CURRENT_UPLOAD": "încărcare actuală", "CUSTOM_LINK_URL": "URL-ul linkului personalizat", "DASHBOARD": "tablou de bord", "DATE": "Data", "DISPLAY_HIDDEN_FILES": "afișează fișiere ascunse", "DOESNT_MATCH": "nu se potriveste", "DONE": "Terminat", "DOWNLOAD": "Descarca", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "doriți să salvați modificările", "DROP_HERE_TO_UPLOAD": "picătură aici pentru a încărca", "EDITOR": "editor", "EMBED": "încorporează", "EMPTY": "gol", "ENCRYPTION_KEY": "cheie de criptare", "ENDPOINT": "punct final", "ERROR": "eroare", "EXISTING_LINKS": "legături existente", "EXPIRATION": "expirare", "EXPORT_AS_{{VALUE}}": "export ca {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "frecvent, folderele de acces vor fi afișate aici", "HIDE_HIDDEN_FILES": "ascunde fișiere ascunse", "HOME": "acasă", "HOSTNAME*": "nume de gazdă", "HOST_KEY": "cheia de gazdă", "INCORRECT_PASSWORD": "parola incorecta", "INFO": "info", "INTERNAL_ERROR": "Eroare interna", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "eroare internă: nu se poate crea o {{VALUE}}", "INVALID_ACCOUNT": "cont invalid", "INVALID_PASSWORD": "Parolă Invalidă", "LAYOUT": "aranjament", "LOADING": "încărcare", "LOCATION": "Locație", "MISSING_DEPENDENCY": "lipsă de dependență", "MORE_DETAILS": "mai multe detalii", "NAVIGATE": "Naviga", "NEW_FILE": "nou fișier", "NEW_FILE::SHORT": "nou fișier", "NEW_FOLDER": "director nou", "NEW_FOLDER::SHORT": "director nou", "NO": "Nu", "NOT_ALLOWED": "nepermis", "NOT_AUTHORISED": "neautorizat", "NOT_FOUND": "nu a fost gasit", "NOT_IMPLEMENTED": "neimplementat", "NOT_SUPPORTED": "nu sunt acceptate", "NOT_VALID": "invalid", "NUMBER_OF_CONNECTIONS": "numărul de conexiuni", "OK": "O.K", "ONLY_FOR_USERS": "Numai pentru utilizatori", "OOPS": "Hopa", "PASSPHRASE": "expresia de acces", "PASSWORD": "parola", "PASSWORD_CANT_BE_EMPTY": "parola nu poate fi goală", "PATH": "cale", "PERMISSION_DENIED": "acces refuzat", "PICK_A_MASTER_PASSWORD": "alege o parolă principală", "PORT": "port", "POWERED_BY": "cu sprijinul", "PROPERTIES": "proprietăţi", "PROTECT_ACCESS_WITH_A_PASSWORD": "protejați accesul cu o parolă", "QUICK_ACCESS": "acces rapid", "REGION": "regiune", "REMEMBER_ME": "amintește-ți de mine", "REMOVE": "elimina", "RENAME": "redenumește", "RENAME_AS": "redenumește ca", "RESTRICTIONS": "restricţii", "RUNNING": "rulând", "SAVE_CURRENT_FILE": "salvați fișierul curent", "SEARCH": "căutare", "SETTINGS": "setări", "SHARE": "partajează", "SHARED_DRIVE": "unitate partajată", "SKIP_TO_CONTENT": "Salt la conținut", "SORT": "sortează", "SORT_BY_DATE": "sortati dupa data", "SORT_BY_NAME": "sorteaza dupa nume", "SORT_BY_SIZE": "sortează după dimensiune", "SORT_BY_TYPE": "sortează după tip", "STARRED": "favorite", "SUPPORT": "suport", "TAG": "etichetă", "TAGS": "etichete", "THERE_IS_NOTHING_HERE": "nu este nimic aici", "THE_FILE_{{VALUE}}_WAS_DELETED": "fișierul {{VALUE}} a ​​fost șters", "THE_FILE_{{VALUE}}_WAS_RENAMED": "fișierul {{VALUE}} a ​​fost redenumit", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "linkul a fost copiat în clipboard", "THE_LINK_WONT_BE_VALID_AFTER": "link-ul nu va fi valabil după", "TIMEOUT": "pauză", "TODO": "a face", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "congestionarea traficului, încercați din nou mai târziu", "UPLOAD": "încarcă", "UPLOADER": "uploader", "USERNAME": "nume de utilizator", "VERSION": "versiune", "VIEWER": "vizualizator", "WAITING": "aşteptare", "YES": "da", "YOUR_EMAIL_ADDRESS": "adresa ta de email", "YOUR_FILES": "fișierele tale", "YOUR_MASTER_PASSWORD": "parola de master", "YOU_CANT_DO_THAT": "nu poți face asta" } ================================================ FILE: public/assets/locales/ru.json ================================================ { "ABORTED": "прерванный", "ABORT_CURRENT_UPLOADS?": "прервать текущую загрузку?", "ACTIVITY": "Деятельность", "ADVANCED": "передовой", "ALL_DONE": "все сделано", "ALREADY_EXIST": "уже существует", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Файл с именем {{VALUE}} создан", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Папка с именем {{VALUE}} была создана", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "закладка", "CAMERA": "камера", "CANCEL": "Отмена", "CANNOT_ESTABLISH_A_CONNECTION": "не может установить соединение", "CANT_LOAD_THIS_PICTURE": "не могу загрузить эту картинку", "CANT_USE_FILESYSTEM": "не удается использовать файловую систему", "CAN_RESHARE": "можно поделиться", "CODE": "код", "CONFIGURE": "настроить", "CONFIRM_BY_TYPING": "подтвердить, набрав", "CONNECT": "подключения", "CONNECTION_LOST": "соединение потеряно", "COPIED_TO_CLIPBOARD": "скопировано в буфер обмена", "CREATE_A_NEW_LINK": "создать новую ссылку", "CREATE_A_TAG": "создать тег", "CURRENT": "текущий", "CURRENT_UPLOAD": "текущая загрузка", "CUSTOM_LINK_URL": "URL пользовательской ссылки", "DASHBOARD": "приборная панель", "DATE": "свидание", "DISPLAY_HIDDEN_FILES": "отображать скрытые файлы", "DOESNT_MATCH": "не совпадает", "DONE": "сделанный", "DOWNLOAD": "скачать", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Вы хотите сохранить изменения?", "DROP_HERE_TO_UPLOAD": "сюда, чтобы загрузить", "EDITOR": "редактор", "EMBED": "встроить", "EMPTY": "опорожнить", "ENCRYPTION_KEY": "ключ шифрования", "ENDPOINT": "конечная точка", "ERROR": "ошибка", "EXISTING_LINKS": "существующие ссылки", "EXPIRATION": "истечение", "EXPORT_AS_{{VALUE}}": "экспортировать как {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "папки с часто используемым доступом будут показаны здесь", "HIDE_HIDDEN_FILES": "скрыть скрытые файлы", "HOME": "главная", "HOSTNAME*": "имя хоста", "HOST_KEY": "ключ хоста", "INCORRECT_PASSWORD": "неверный пароль", "INFO": "Информация", "INTERNAL_ERROR": "Внутренняя ошибка", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "внутренняя ошибка: невозможно создать {{VALUE}}", "INVALID_ACCOUNT": "неверный аккаунт", "INVALID_PASSWORD": "Неверный пароль", "LAYOUT": "макет", "LOADING": "загрузка", "LOCATION": "расположение", "MISSING_DEPENDENCY": "отсутствует зависимость", "MORE_DETAILS": "подробности", "NAVIGATE": "навигации", "NEW_FILE": "новый файл", "NEW_FILE::SHORT": "новый файл", "NEW_FOLDER": "новый каталог", "NEW_FOLDER::SHORT": "новый каталог", "NO": "нет", "NOT_ALLOWED": "не положено", "NOT_AUTHORISED": "не авторизован", "NOT_FOUND": "не найдено", "NOT_IMPLEMENTED": "не реализована", "NOT_SUPPORTED": "не поддерживается", "NOT_VALID": "недействительный", "NUMBER_OF_CONNECTIONS": "количество соединений", "OK": "Хорошо", "ONLY_FOR_USERS": "Только для пользователей", "OOPS": "ой", "PASSPHRASE": "ключевая фраза", "PASSWORD": "пароль", "PASSWORD_CANT_BE_EMPTY": "пароль не может быть пустым", "PATH": "дорожка", "PERMISSION_DENIED": "в правах отказано", "PICK_A_MASTER_PASSWORD": "выберите мастер-пароль", "PORT": "порт", "POWERED_BY": "работает на основе", "PROPERTIES": "свойства", "PROTECT_ACCESS_WITH_A_PASSWORD": "защитить доступ паролем", "QUICK_ACCESS": "быстрый доступ", "REGION": "область", "REMEMBER_ME": "Запомни меня", "REMOVE": "Удалить", "RENAME": "переименовать", "RENAME_AS": "переименовать как", "RESTRICTIONS": "ограничения", "RUNNING": "выполняется", "SAVE_CURRENT_FILE": "сохранить текущий файл", "SEARCH": "поиск", "SETTINGS": "настройки", "SHARE": "поделиться", "SHARED_DRIVE": "общий диск", "SKIP_TO_CONTENT": "Перейти к содержимому", "SORT": "сортировать", "SORT_BY_DATE": "сортировать по дате", "SORT_BY_NAME": "сортировать по имени", "SORT_BY_SIZE": "сортировать по размеру", "SORT_BY_TYPE": "сортировать по типу", "STARRED": "избранное", "SUPPORT": "служба поддержки", "TAG": "тег", "TAGS": "теги", "THERE_IS_NOTHING_HERE": "здесь ничего нет", "THE_FILE_{{VALUE}}_WAS_DELETED": "файл {{VALUE}} был удален", "THE_FILE_{{VALUE}}_WAS_RENAMED": "файл {{VALUE}} был переименован", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "ссылка была скопирована в буфер обмена", "THE_LINK_WONT_BE_VALID_AFTER": "ссылка не будет действительной после", "TIMEOUT": "Тайм-аут", "TODO": "список дел", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "пробка, попробуйте позже", "UPLOAD": "загрузить", "UPLOADER": "загрузчик", "USERNAME": "имя пользователя", "VERSION": "версия", "VIEWER": "зритель", "WAITING": "ожидания", "YES": "да", "YOUR_EMAIL_ADDRESS": "Ваш адрес электронной почты", "YOUR_FILES": "ваши файлы", "YOUR_MASTER_PASSWORD": "ваш мастер-пароль", "YOU_CANT_DO_THAT": "ты не можешь сделать это" } ================================================ FILE: public/assets/locales/script.js ================================================ import fs from "fs"; function mainReorderKey(argv) { const filepath = argv[2]; if (!filepath) throw new Error("missing args"); let jsonStr = fs.readFileSync(filepath); const res = {}; const obj = JSON.parse(jsonStr); Object.keys(obj).sort() .map((key) => res[key] = obj[key]); jsonStr = JSON.stringify(res, null, 4); fs.writeFileSync(filepath, jsonStr + "\n"); } function mainAddTranslationKey(argv) { const key = argv[3]; const filepath = argv[2]; if (!filepath) throw new Error("missing args"); else if (!key) return; const json = JSON.parse(fs.readFileSync(filepath)); if (json[key] !== undefined) return; json[key] = ""; fs.writeFileSync(filepath, JSON.stringify(json, null, 4) + "\n"); } // usage: find *.json -type f -exec node script.js {} \; (function() { mainAddTranslationKey(process.argv); mainReorderKey(process.argv); })(); ================================================ FILE: public/assets/locales/sk.json ================================================ { "ABORTED": "chybne", "ABORT_CURRENT_UPLOADS?": "zrušiť aktuálne nahrávania?", "ACTIVITY": "aktivita", "ADVANCED": "pokročilé", "ALL_DONE": "všetko hotové", "ALREADY_EXIST": "už existuje", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Bol vytvorený súbor s názvom {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Bol vytvorený priečinok s názvom {{VALUE}}", "BEAUTIFUL_URL": "pekná URL", "BOOKMARK": "záložka", "CAMERA": "fotoaparát", "CANCEL": "zrušiť", "CANNOT_ESTABLISH_A_CONNECTION": "nie je možné nadviazať spojenie", "CANT_LOAD_THIS_PICTURE": "tento obrázok sa nedá načítať", "CANT_USE_FILESYSTEM": "môže používať súborový systém", "CAN_RESHARE": "môžu zdieľať", "CODE": "kód", "CONFIGURE": "konfigurovať", "CONFIRM_BY_TYPING": "potvrďte zadaním", "CONNECT": "pripojiť", "CONNECTION_LOST": "spojenie stratené", "COPIED_TO_CLIPBOARD": "skopírované do schránky", "CREATE_A_NEW_LINK": "vytvoriť nový odkaz", "CREATE_A_TAG": "vytvoriť značku", "CURRENT": "aktuálne", "CURRENT_UPLOAD": "aktuálne nahrávanie", "CUSTOM_LINK_URL": "vlastný odkaz", "DASHBOARD": "ovládací panel", "DATE": "dátum", "DISPLAY_HIDDEN_FILES": "zobraziť skryté súbory", "DOESNT_MATCH": "nesedí", "DONE": "hotovo", "DOWNLOAD": "stiahnuť", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "chcete uložiť zmeny", "DROP_HERE_TO_UPLOAD": "súbory nahráte presunutím sem", "EDITOR": "upravovať", "EMBED": "vložiť", "EMPTY": "prázdne", "ENCRYPTION_KEY": "šifrovací kľúč", "ENDPOINT": "endpoint", "ERROR": "chyba", "EXISTING_LINKS": "existujúce odkazy", "EXPIRATION": "expirácia", "EXPORT_AS_{{VALUE}}": "exportovať ako {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "tu budú často používané priečinky", "HIDE_HIDDEN_FILES": "skryť skryté súbory", "HOME": "domov", "HOSTNAME*": "hostname", "HOST_KEY": "hostiteľský kľúč", "INCORRECT_PASSWORD": "nesprávne heslo", "INFO": "Info", "INTERNAL_ERROR": "Interná chyba", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "interná chyba: nedá sa vytvoriť {{VALUE}}", "INVALID_ACCOUNT": "nesprávny účet", "INVALID_PASSWORD": "nesprávne heslo", "LAYOUT": "rozloženie", "LOADING": "načítava sa", "LOCATION": "umiestnenie", "MISSING_DEPENDENCY": "chýba závislosť", "MORE_DETAILS": "viac podrobností", "NAVIGATE": "navigácia", "NEW_FILE": "nový súbor", "NEW_FILE::SHORT": "nový súbor", "NEW_FOLDER": "nový priečinok", "NEW_FOLDER::SHORT": "nový priečinok", "NO": "nie", "NOT_ALLOWED": "nepovolené", "NOT_AUTHORISED": "neautorizované", "NOT_FOUND": "nenájdené", "NOT_IMPLEMENTED": "neimplementované", "NOT_SUPPORTED": "nepodporované", "NOT_VALID": "neplatné", "NUMBER_OF_CONNECTIONS": "počet pripojení", "OK": "ok", "ONLY_FOR_USERS": "Len pre prihlásených", "OOPS": "ups", "PASSPHRASE": "passphrase", "PASSWORD": "heslo", "PASSWORD_CANT_BE_EMPTY": "heslo nemôže byť prázdne", "PATH": "cesta", "PERMISSION_DENIED": "prístup zamietnutý", "PICK_A_MASTER_PASSWORD": "zvoľte hlavné heslo", "PORT": "port", "POWERED_BY": "postavené na", "PROPERTIES": "vlastnosti", "PROTECT_ACCESS_WITH_A_PASSWORD": "chrániť prístup heslom", "QUICK_ACCESS": "rýchly prístup", "REGION": "región", "REMEMBER_ME": "Pamätať si ma", "REMOVE": "odstrániť", "RENAME": "premenovať", "RENAME_AS": "premenovať ako", "RESTRICTIONS": "obmedzenia", "RUNNING": "Nahráva", "SAVE_CURRENT_FILE": "uložiť aktuálny súbor", "SEARCH": "Hľadať", "SETTINGS": "Nastavenia", "SHARE": "zdieľať", "SHARED_DRIVE": "zdieľaná jednotka", "SKIP_TO_CONTENT": "Prejsť na obsah", "SORT": "zoradiť", "SORT_BY_DATE": "zoradiť podľa dátumu", "SORT_BY_NAME": "zoradiť podľa názvu", "SORT_BY_SIZE": "zoradiť podľa veľkosti", "SORT_BY_TYPE": "zoradiť podľa typu", "STARRED": "označené hviezdičkou", "SUPPORT": "podpora", "TAG": "značka", "TAGS": "značky", "THERE_IS_NOTHING_HERE": "nič tu nie je", "THE_FILE_{{VALUE}}_WAS_DELETED": "súbor {{VALUE}} bol odstránený", "THE_FILE_{{VALUE}}_WAS_RENAMED": "súbor {{VALUE}} bol premenovaný", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "odkaz bol skopírovaný do schránky", "THE_LINK_WONT_BE_VALID_AFTER": "odkaz bude neplatný po", "TIMEOUT": "čas vypršal", "TODO": "spraviť", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "zahltená linka, skúste neskôr", "UPLOAD": "nahrať", "UPLOADER": "nahrávať", "USERNAME": "používateľské meno", "VERSION": "verzia", "VIEWER": "pozerať", "WAITING": "čaká", "YES": "Áno", "YOUR_EMAIL_ADDRESS": "Vaša emailová adresa", "YOUR_FILES": "vaše súbory", "YOUR_MASTER_PASSWORD": "vaše hlavné heslo", "YOU_CANT_DO_THAT": "to nemôžete urobiť" } ================================================ FILE: public/assets/locales/sl.json ================================================ { "ABORTED": "splavili", "ABORT_CURRENT_UPLOADS?": "prekiniti trenutne nalaganje?", "ACTIVITY": "Dejavnost", "ADVANCED": "napredno", "ALL_DONE": "končano", "ALREADY_EXIST": "že obstaja", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Ustvarjena je bila datoteka z imenom {{VALUE}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Ustvarjena je bila mapa z imenom {{VALUE}}", "BEAUTIFUL_URL": "lepo_url", "BOOKMARK": "zaznamek", "CAMERA": "kamero", "CANCEL": "Prekliči", "CANNOT_ESTABLISH_A_CONNECTION": "ni mogoče vzpostaviti povezave", "CANT_LOAD_THIS_PICTURE": "ni mogoče naložiti te slike", "CANT_USE_FILESYSTEM": "lahko uporablja datotečni sistem", "CAN_RESHARE": "lahko dajo v skupno rabo", "CODE": "Koda", "CONFIGURE": "konfigurirati", "CONFIRM_BY_TYPING": "potrdite s tipkanjem", "CONNECT": "povežite", "CONNECTION_LOST": "povezava izgubljena", "COPIED_TO_CLIPBOARD": "kopirano v odložišče", "CREATE_A_NEW_LINK": "ustvarite novo povezavo", "CREATE_A_TAG": "ustvari oznako", "CURRENT": "trenutno", "CURRENT_UPLOAD": "trenutni prenos", "CUSTOM_LINK_URL": "URL povezave po meri", "DASHBOARD": "armaturna plošča", "DATE": "datum", "DISPLAY_HIDDEN_FILES": "prikaži skrite datoteke", "DOESNT_MATCH": "se ne ujema", "DONE": "Končano", "DOWNLOAD": "Prenesi", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "ali želite shraniti spremembe", "DROP_HERE_TO_UPLOAD": "spusti tukaj za nalaganje", "EDITOR": "urednik", "EMBED": "vgrajeno", "EMPTY": "prazno", "ENCRYPTION_KEY": "šifrirni ključ", "ENDPOINT": "končna točka", "ERROR": "napaka", "EXISTING_LINKS": "obstoječe povezave", "EXPIRATION": "prenehanje veljavnosti", "EXPORT_AS_{{VALUE}}": "izvoziti kot {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Tu so prikazane pogosto dostopne mape", "HIDE_HIDDEN_FILES": "skrije skrite datoteke", "HOME": "domov", "HOSTNAME*": "ime gostitelja", "HOST_KEY": "gostiteljski ključ", "INCORRECT_PASSWORD": "napačno geslo", "INFO": "info", "INTERNAL_ERROR": "Notranja napaka", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "notranja napaka: ni mogoče ustvariti {{VALUE}}", "INVALID_ACCOUNT": "neveljaven račun", "INVALID_PASSWORD": "Neveljavno geslo", "LAYOUT": "postavitev", "LOADING": "nalaganje", "LOCATION": "lokacijo", "MISSING_DEPENDENCY": "manjka odvisnost", "MORE_DETAILS": "več podrobnosti", "NAVIGATE": "krmariti", "NEW_FILE": "nova datoteka", "NEW_FILE::SHORT": "nova datoteka", "NEW_FOLDER": "nov imenik", "NEW_FOLDER::SHORT": "nov imenik", "NO": "št", "NOT_ALLOWED": "ni dovoljeno", "NOT_AUTHORISED": "ni pooblaščen", "NOT_FOUND": "ni najdeno", "NOT_IMPLEMENTED": "ni izveden", "NOT_SUPPORTED": "ne podpira", "NOT_VALID": "ni veljaven", "NUMBER_OF_CONNECTIONS": "število povezav", "OK": "v redu", "ONLY_FOR_USERS": "Samo za uporabnike", "OOPS": "Ups", "PASSPHRASE": "geslo", "PASSWORD": "geslo", "PASSWORD_CANT_BE_EMPTY": "geslo ne more biti prazno", "PATH": "pot", "PERMISSION_DENIED": "dovoljenje zavrnjeno", "PICK_A_MASTER_PASSWORD": "izberite glavno geslo", "PORT": "pristanišče", "POWERED_BY": "poganja ga", "PROPERTIES": "lastnosti", "PROTECT_ACCESS_WITH_A_PASSWORD": "zaščiti dostop z geslom", "QUICK_ACCESS": "hiter dostop", "REGION": "regija", "REMEMBER_ME": "spomni se me", "REMOVE": "Izbriši", "RENAME": "preimenuj", "RENAME_AS": "preimenuj kot", "RESTRICTIONS": "omejitve", "RUNNING": "tek", "SAVE_CURRENT_FILE": "shrani trenutno datoteko", "SEARCH": "Iskanje", "SETTINGS": "nastavitve", "SHARE": "deli", "SHARED_DRIVE": "skupni disk", "SKIP_TO_CONTENT": "Pojdi na vsebino", "SORT": "razvrsti", "SORT_BY_DATE": "razvrsti po datumu", "SORT_BY_NAME": "razvrsti po imenu", "SORT_BY_SIZE": "razvrsti po velikosti", "SORT_BY_TYPE": "razvrsti po vrsti", "STARRED": "označeno", "SUPPORT": "podpora", "TAG": "oznaka", "TAGS": "oznake", "THERE_IS_NOTHING_HERE": "tukaj ni ničesar", "THE_FILE_{{VALUE}}_WAS_DELETED": "datoteka {{VALUE}} je bila izbrisana", "THE_FILE_{{VALUE}}_WAS_RENAMED": "datoteka {{VALUE}} je bila preimenovana", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "povezava je bila kopirana v odložišče", "THE_LINK_WONT_BE_VALID_AFTER": "povezava po tem ne bo veljavna", "TIMEOUT": "odmor", "TODO": "narediti", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "prometne zastoje, poskusite pozneje", "UPLOAD": "naloži", "UPLOADER": "uploader", "USERNAME": "uporabniško ime", "VERSION": "različica", "VIEWER": "gledalca", "WAITING": "čaka", "YES": "da", "YOUR_EMAIL_ADDRESS": "Vaš email naslov", "YOUR_FILES": "vaše datoteke", "YOUR_MASTER_PASSWORD": "svoje glavno geslo", "YOU_CANT_DO_THAT": "tega ne moreš narediti" } ================================================ FILE: public/assets/locales/sr.json ================================================ { "ABORTED": "прекинута", "ABORT_CURRENT_UPLOADS?": "прекинути тренутни уплоад?", "ACTIVITY": "Активност", "ADVANCED": "напредни", "ALL_DONE": "завршено", "ALREADY_EXIST": "већ постоји", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Направљена је датотека под називом {{ВАЛУЕ}}", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Направљена је фасцикла под називом {{ВАЛУЕ}}", "BEAUTIFUL_URL": "беаутифул_урл", "BOOKMARK": "обележивач", "CAMERA": "Камера", "CANCEL": "откажи", "CANNOT_ESTABLISH_A_CONNECTION": "не може успоставити везу", "CANT_LOAD_THIS_PICTURE": "не могу учитати ову слику", "CANT_USE_FILESYSTEM": "може да користи датотечни систем", "CAN_RESHARE": "дели поново", "CODE": "код", "CONFIGURE": "подеси", "CONFIRM_BY_TYPING": "потврдите куцањем", "CONNECT": "повежите се", "CONNECTION_LOST": "веза је изгубљена", "COPIED_TO_CLIPBOARD": "копирано у међуспремник", "CREATE_A_NEW_LINK": "створите нову везу", "CREATE_A_TAG": "креирај ознаку", "CURRENT": "Тренутни", "CURRENT_UPLOAD": "тренутно отпремање", "CUSTOM_LINK_URL": "прилагођена УРЛ адреса везе", "DASHBOARD": "Командна табла", "DATE": "датум", "DISPLAY_HIDDEN_FILES": "приказ скривених датотека", "DOESNT_MATCH": "не одговара", "DONE": "Готово", "DOWNLOAD": "преузимање", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "да ли желите да сачувате измене", "DROP_HERE_TO_UPLOAD": "испустите овде да бисте је послали", "EDITOR": "уредник", "EMBED": "угради", "EMPTY": "празно", "ENCRYPTION_KEY": "шифрирање", "ENDPOINT": "крајња тачка", "ERROR": "грешка", "EXISTING_LINKS": "постојеће везе", "EXPIRATION": "истицање", "EXPORT_AS_{{VALUE}}": "извести као {{ВАЛУЕ}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Овде ће се приказивати директоријуми са често приступом", "HIDE_HIDDEN_FILES": "сакривање скривених датотека", "HOME": "почетна", "HOSTNAME*": "име домаћина", "HOST_KEY": "главни кључ", "INCORRECT_PASSWORD": "Погрешна лозинка", "INFO": "инфо", "INTERNAL_ERROR": "Интeрна грeшка", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "интерна грешка: не може да се креира {{ВАЛУЕ}}", "INVALID_ACCOUNT": "Неважећи рачун", "INVALID_PASSWORD": "неважећа лозинка", "LAYOUT": "распоред", "LOADING": "учитавање", "LOCATION": "локацију", "MISSING_DEPENDENCY": "недостаје зависност", "MORE_DETAILS": "више детаља", "NAVIGATE": "навигација", "NEW_FILE": "нова датотека", "NEW_FILE::SHORT": "нова датотека", "NEW_FOLDER": "нови директориј", "NEW_FOLDER::SHORT": "нови директориј", "NO": "не", "NOT_ALLOWED": "није дозвољено", "NOT_AUTHORISED": "није овлашћен", "NOT_FOUND": "није пронађен", "NOT_IMPLEMENTED": "не спроводи", "NOT_SUPPORTED": "није подржан", "NOT_VALID": "не важи", "NUMBER_OF_CONNECTIONS": "број веза", "OK": "ОК", "ONLY_FOR_USERS": "Само за кориснике", "OOPS": "Упс", "PASSPHRASE": "лозинку", "PASSWORD": "Лозинка", "PASSWORD_CANT_BE_EMPTY": "лозинка не може бити празна", "PATH": "стаза", "PERMISSION_DENIED": "Приступ је одбијен", "PICK_A_MASTER_PASSWORD": "изаберите главну лозинку", "PORT": "Порт", "POWERED_BY": "покреће га", "PROPERTIES": "својства", "PROTECT_ACCESS_WITH_A_PASSWORD": "заштитите приступ лозинком", "QUICK_ACCESS": "брзи приступ", "REGION": "регион", "REMEMBER_ME": "сети ме се", "REMOVE": "уклони", "RENAME": "преименуј", "RENAME_AS": "преименуј као", "RESTRICTIONS": "Ограничења", "RUNNING": "покретање", "SAVE_CURRENT_FILE": "сачувај тренутну датотеку", "SEARCH": "Претрага", "SETTINGS": "подешавања", "SHARE": "подели", "SHARED_DRIVE": "заједнички диск", "SKIP_TO_CONTENT": "Иди на садржај", "SORT": "сортирај", "SORT_BY_DATE": "сортирај по датуму", "SORT_BY_NAME": "сортирај по имену", "SORT_BY_SIZE": "сортирај по величини", "SORT_BY_TYPE": "сортирај по врсти", "STARRED": "означено", "SUPPORT": "подршка", "TAG": "ознака", "TAGS": "означи", "THERE_IS_NOTHING_HERE": "овде нема ничега", "THE_FILE_{{VALUE}}_WAS_DELETED": "датотека {{ВАЛУЕ}} је избрисана", "THE_FILE_{{VALUE}}_WAS_RENAMED": "датотека {{ВАЛУЕ}} је преименована", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "веза је копирана у међуспремник", "THE_LINK_WONT_BE_VALID_AFTER": "веза после тога неће бити валидна", "TIMEOUT": "Истек времена", "TODO": "урадити", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "саобраћајна гужва, покушајте касније", "UPLOAD": "отпреми", "UPLOADER": "отпремач", "USERNAME": "корисничко име", "VERSION": "верзија", "VIEWER": "прегледач", "WAITING": "чека", "YES": "да", "YOUR_EMAIL_ADDRESS": "Ваша имејл адреса", "YOUR_FILES": "ваши фајлови", "YOUR_MASTER_PASSWORD": "ваша главна лозинка", "YOU_CANT_DO_THAT": "не можеш то учинити" } ================================================ FILE: public/assets/locales/sv.json ================================================ { "ABORTED": "avbruten", "ABORT_CURRENT_UPLOADS?": "avbryta aktuell uppladdning?", "ACTIVITY": "Aktivitet", "ADVANCED": "Avancerad", "ALL_DONE": "helt klar", "ALREADY_EXIST": "redan finns", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "En fil med namnet {{VALUE}} skapades", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "En mapp med namnet {{VALUE}} skapades", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "bokmärke", "CAMERA": "kamera", "CANCEL": "Avbryt", "CANNOT_ESTABLISH_A_CONNECTION": "kan inte upprätta en anslutning", "CANT_LOAD_THIS_PICTURE": "kan inte ladda den här bilden", "CANT_USE_FILESYSTEM": "kan använda filsystem", "CAN_RESHARE": "kan dela om", "CODE": "kod", "CONFIGURE": "configure", "CONFIRM_BY_TYPING": "bekräfta genom att skriva", "CONNECT": "ansluta", "CONNECTION_LOST": "anslutning förlorad", "COPIED_TO_CLIPBOARD": "kopierade till urklipp", "CREATE_A_NEW_LINK": "skapa en ny länk", "CREATE_A_TAG": "skapa en etikett", "CURRENT": "nuvarande", "CURRENT_UPLOAD": "nuvarande uppladdning", "CUSTOM_LINK_URL": "anpassad länk URL", "DASHBOARD": "instrumentbräda", "DATE": "datum", "DISPLAY_HIDDEN_FILES": "visa dolda filer", "DOESNT_MATCH": "matchar inte", "DONE": "Gjort", "DOWNLOAD": "ladda ner", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "vill du spara ändringarna", "DROP_HERE_TO_UPLOAD": "släpp här för att ladda upp", "EDITOR": "redaktör", "EMBED": "bädda in", "EMPTY": "tömma", "ENCRYPTION_KEY": "krypteringsnyckel", "ENDPOINT": "slutpunkt", "ERROR": "fel", "EXISTING_LINKS": "befintliga länkar", "EXPIRATION": "utgång", "EXPORT_AS_{{VALUE}}": "exportera som {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "ofta visas mappar här", "HIDE_HIDDEN_FILES": "dölja dolda filer", "HOME": "Hemsida", "HOSTNAME*": "hostname", "HOST_KEY": "värdnyckel", "INCORRECT_PASSWORD": "fel lösenord", "INFO": "info", "INTERNAL_ERROR": "Internt fel", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "internt fel: kan inte skapa ett {{VALUE}}", "INVALID_ACCOUNT": "ogiltigt konto", "INVALID_PASSWORD": "felaktigt lösenord", "LAYOUT": "layout", "LOADING": "laddar", "LOCATION": "lokalisering", "MISSING_DEPENDENCY": "saknas beroende", "MORE_DETAILS": "mer information", "NAVIGATE": "navigera", "NEW_FILE": "ny fil", "NEW_FILE::SHORT": "ny fil", "NEW_FOLDER": "ny katalog", "NEW_FOLDER::SHORT": "ny katalog", "NO": "Nej", "NOT_ALLOWED": "inte tillåtet", "NOT_AUTHORISED": "inte godkänd", "NOT_FOUND": "hittades inte", "NOT_IMPLEMENTED": "ej implementerad", "NOT_SUPPORTED": "stöds inte", "NOT_VALID": "inte giltig", "NUMBER_OF_CONNECTIONS": "antal anslutningar", "OK": "ok", "ONLY_FOR_USERS": "Endast för användare", "OOPS": "Hoppsan", "PASSPHRASE": "lösenfras", "PASSWORD": "Lösenord", "PASSWORD_CANT_BE_EMPTY": "lösenordet kan inte vara tomt", "PATH": "väg", "PERMISSION_DENIED": "åtkomst nekad", "PICK_A_MASTER_PASSWORD": "välj ett huvudlösenord", "PORT": "Port", "POWERED_BY": "drivs av", "PROPERTIES": "attribut", "PROTECT_ACCESS_WITH_A_PASSWORD": "skydda åtkomst med ett lösenord", "QUICK_ACCESS": "snabb åtkomst", "REGION": "område", "REMEMBER_ME": "kom ihåg mig", "REMOVE": "ta bort", "RENAME": "byt namn", "RENAME_AS": "byt namn till", "RESTRICTIONS": "restriktioner", "RUNNING": "pågående", "SAVE_CURRENT_FILE": "spara aktuell fil", "SEARCH": "Sök", "SETTINGS": "inställningar", "SHARE": "dela", "SHARED_DRIVE": "delad enhet", "SKIP_TO_CONTENT": "Gå till innehåll", "SORT": "sortera", "SORT_BY_DATE": "sortera efter datum", "SORT_BY_NAME": "sortera efter namn", "SORT_BY_SIZE": "sortera efter storlek", "SORT_BY_TYPE": "sortera efter typ", "STARRED": "markerad", "SUPPORT": "Stöd", "TAG": "etikett", "TAGS": "etiketter", "THERE_IS_NOTHING_HERE": "det finns ingenting här", "THE_FILE_{{VALUE}}_WAS_DELETED": "filen {{VALUE}} raderades", "THE_FILE_{{VALUE}}_WAS_RENAMED": "filen {{VALUE}} byttes namn", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "länken kopierades i Urklipp", "THE_LINK_WONT_BE_VALID_AFTER": "länken kommer inte att vara giltig efter", "TIMEOUT": "Paus", "TODO": "att göra", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "trafikstockningar, försök igen senare", "UPLOAD": "ladda upp", "UPLOADER": "Uppladdare", "USERNAME": "Användarnamn", "VERSION": "version", "VIEWER": "tittare", "WAITING": "väntar", "YES": "ja", "YOUR_EMAIL_ADDRESS": "din e-postadress", "YOUR_FILES": "dina filer", "YOUR_MASTER_PASSWORD": "ditt huvudlösenord", "YOU_CANT_DO_THAT": "du kan inte göra det" } ================================================ FILE: public/assets/locales/th.json ================================================ { "ABORTED": "ยกเลิก", "ABORT_CURRENT_UPLOADS?": "ยกเลิกการอัปโหลดปัจจุบันหรือไม่", "ACTIVITY": "กิจกรรม", "ADVANCED": "สูง", "ALL_DONE": "ทุกอย่างเสร็จเรียบร้อย", "ALREADY_EXIST": "มีอยู่แล้ว", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "สร้างไฟล์ชื่อ {{VALUE}} แล้ว", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "สร้างโฟลเดอร์ชื่อ {{VALUE}} แล้ว", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "ที่คั่นหน้า", "CAMERA": "กล้อง", "CANCEL": "ยกเลิก", "CANNOT_ESTABLISH_A_CONNECTION": "ไม่สามารถสร้างการเชื่อมต่อ", "CANT_LOAD_THIS_PICTURE": "ไม่สามารถโหลดรูปภาพนี้", "CANT_USE_FILESYSTEM": "สามารถใช้ระบบไฟล์", "CAN_RESHARE": "สามารถแชร์ต่อได้", "CODE": "รหัส", "CONFIGURE": "กำหนดค่า", "CONFIRM_BY_TYPING": "ยืนยันโดยพิมพ์", "CONNECT": "การเชื่อมต่อ", "CONNECTION_LOST": "การเชื่อมต่อขาดหายไป", "COPIED_TO_CLIPBOARD": "คัดลอกไปยังคลิปบอร์ดแล้ว", "CREATE_A_NEW_LINK": "สร้างลิงค์ใหม่", "CREATE_A_TAG": "สร้างแท็ก", "CURRENT": "ปัจจุบัน", "CURRENT_UPLOAD": "อัปโหลดปัจจุบัน", "CUSTOM_LINK_URL": "URL ลิงค์ที่กำหนดเอง", "DASHBOARD": "แผงควบคุม", "DATE": "วันที่", "DISPLAY_HIDDEN_FILES": "แสดงไฟล์ที่ซ่อน", "DOESNT_MATCH": "ไม่ตรง", "DONE": "เสร็จแล้ว", "DOWNLOAD": "ดาวน์โหลด", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "คุณต้องการบันทึกการเปลี่ยนแปลงหรือไม่", "DROP_HERE_TO_UPLOAD": "วางที่นี่เพื่ออัปโหลด", "EDITOR": "บรรณาธิการ", "EMBED": "ฝัง", "EMPTY": "ว่างเปล่า", "ENCRYPTION_KEY": "คีย์การเข้ารหัส", "ENDPOINT": "ปลายทาง", "ERROR": "ความผิดพลาด", "EXISTING_LINKS": "ลิงก์ที่มีอยู่", "EXPIRATION": "การหมดอายุ", "EXPORT_AS_{{VALUE}}": "ส่งออกเป็น {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "โฟลเดอร์ที่เข้าถึงบ่อย ๆ จะปรากฏที่นี่", "HIDE_HIDDEN_FILES": "ซ่อนไฟล์ที่ซ่อน", "HOME": "หน้าหลัก", "HOSTNAME*": "ชื่อโฮสต์", "HOST_KEY": "รหัสโฮสต์", "INCORRECT_PASSWORD": "รหัสผ่านผิดพลาด", "INFO": "ข้อมูล", "INTERNAL_ERROR": "ข้อผิดพลาดภายใน", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "ข้อผิดพลาดภายใน: ไม่สามารถสร้าง {{VALUE}}", "INVALID_ACCOUNT": "บัญชีไม่ถูกต้อง", "INVALID_PASSWORD": "รหัสผ่านไม่ถูกต้อง", "LAYOUT": "รูปแบบ", "LOADING": "กำลังโหลด", "LOCATION": "ที่ตั้ง", "MISSING_DEPENDENCY": "ขาดการพึ่งพา", "MORE_DETAILS": "รายละเอียดเพิ่มเติม", "NAVIGATE": "นำทาง", "NEW_FILE": "ไฟล์ใหม่", "NEW_FILE::SHORT": "ไฟล์ใหม่", "NEW_FOLDER": "ไดเรกทอรีใหม่", "NEW_FOLDER::SHORT": "ไดเรกทอรีใหม่", "NO": "ไม่", "NOT_ALLOWED": "ไม่ได้รับอนุญาต", "NOT_AUTHORISED": "ไม่ได้รับอนุญาต", "NOT_FOUND": "ไม่พบ", "NOT_IMPLEMENTED": "ไม่ได้ดำเนินการ", "NOT_SUPPORTED": "ไม่รองรับ", "NOT_VALID": "ไม่ถูกต้อง", "NUMBER_OF_CONNECTIONS": "จำนวนการเชื่อมต่อ", "OK": "ตกลง", "ONLY_FOR_USERS": "สำหรับผู้ใช้เท่านั้น", "OOPS": "อุ่ย", "PASSPHRASE": "ข้อความรหัสผ่าน", "PASSWORD": "รหัสผ่าน", "PASSWORD_CANT_BE_EMPTY": "รหัสผ่านต้องไม่ว่างเปล่า", "PATH": "เส้นทาง", "PERMISSION_DENIED": "ปฏิเสธการอนุญาต", "PICK_A_MASTER_PASSWORD": "เลือกรหัสผ่านหลัก", "PORT": "ท่าเรือ", "POWERED_BY": "ขับเคลื่อนโดย", "PROPERTIES": "คุณสมบัติ", "PROTECT_ACCESS_WITH_A_PASSWORD": "ป้องกันการเข้าถึงด้วยรหัสผ่าน", "QUICK_ACCESS": "การเข้าถึงอย่างรวดเร็ว", "REGION": "ภูมิภาค", "REMEMBER_ME": "จดจำฉัน", "REMOVE": "ลบ", "RENAME": "เปลี่ยนชื่อ", "RENAME_AS": "เปลี่ยนชื่อเป็น", "RESTRICTIONS": "ข้อ จำกัด", "RUNNING": "วิ่ง", "SAVE_CURRENT_FILE": "บันทึกไฟล์ปัจจุบัน", "SEARCH": "ค้นหา", "SETTINGS": "การตั้งค่า", "SHARE": "แชร์", "SHARED_DRIVE": "ไดรฟ์ที่แชร์", "SKIP_TO_CONTENT": "ข้ามไปยังเนื้อหา", "SORT": "เรียงลำดับ", "SORT_BY_DATE": "จัดเรียงตามวันที่", "SORT_BY_NAME": "เรียงตามชื่อ", "SORT_BY_SIZE": "เรียงตามขนาด", "SORT_BY_TYPE": "จัดเรียงตามประเภท", "STARRED": "ติดดาว", "SUPPORT": "สนับสนุน", "TAG": "แท็ก", "TAGS": "แท็ก", "THERE_IS_NOTHING_HERE": "ไม่มีอะไรที่นี่", "THE_FILE_{{VALUE}}_WAS_DELETED": "ไฟล์ {{VALUE}} ถูกลบแล้ว", "THE_FILE_{{VALUE}}_WAS_RENAMED": "ไฟล์ {{VALUE}} ถูกเปลี่ยนชื่อ", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "ลิงก์ถูกคัดลอกในคลิปบอร์ด", "THE_LINK_WONT_BE_VALID_AFTER": "ลิงก์จะไม่สามารถใช้ได้หลังจากนั้น", "TIMEOUT": "หมดเวลา", "TODO": "ทำ", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "การจราจรติดขัดลองอีกครั้งในภายหลัง", "UPLOAD": "อัปโหลด", "UPLOADER": "อัปโหลด", "USERNAME": "ชื่อผู้ใช้", "VERSION": "เวอร์ชัน", "VIEWER": "ผู้ชม", "WAITING": "ที่รอคอย", "YES": "ใช่", "YOUR_EMAIL_ADDRESS": "ที่อยู่อีเมลของคุณ", "YOUR_FILES": "ไฟล์ของคุณ", "YOUR_MASTER_PASSWORD": "รหัสผ่านหลักของคุณ", "YOU_CANT_DO_THAT": "คุณทำอย่างนั้นไม่ได้" } ================================================ FILE: public/assets/locales/tr.json ================================================ { "ABORTED": "Sonlandırıldı", "ABORT_CURRENT_UPLOADS?": "Mevcut yükleme sonlandırılsın mı?", "ACTIVITY": "Hareketlilik", "ADVANCED": "Gelişmiş", "ALL_DONE": "Tamamlandı", "ALREADY_EXIST": "zaten mevcut", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} adlı bir dosya oluşturuldu", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "{{VALUE}} adlı bir klasör oluşturuldu", "BEAUTIFUL_URL": "özel URL", "BOOKMARK": "yer imi", "CAMERA": "Kamera", "CANCEL": "İptal et", "CANNOT_ESTABLISH_A_CONNECTION": "Bağlantı kurulamıyor", "CANT_LOAD_THIS_PICTURE": "Bu resim görüntülenemiyor", "CANT_USE_FILESYSTEM": "Dosya sistemi kullanılamıyor", "CAN_RESHARE": "yeniden paylaş", "CODE": "Kod", "CONFIGURE": "Yapılandır", "CONFIRM_BY_TYPING": "Yazarak onayla", "CONNECT": "Bağlan", "CONNECTION_LOST": "bağlantı kesildi", "COPIED_TO_CLIPBOARD": "Panoya kopyalandı", "CREATE_A_NEW_LINK": "Yeni bir bağlantı oluştur", "CREATE_A_TAG": "etiket oluştur", "CURRENT": "Mevcut işlem", "CURRENT_UPLOAD": "Mevcut yükleme", "CUSTOM_LINK_URL": "özel bağlantı URL'si", "DASHBOARD": "Panel", "DATE": "Tarih", "DISPLAY_HIDDEN_FILES": "gizli dosyaları göster", "DOESNT_MATCH": "eşleşmiyor", "DONE": "tamam", "DOWNLOAD": "indir", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "Değişiklikleri kaydetmek ister misin?", "DROP_HERE_TO_UPLOAD": "yüklemek için buraya sürükle", "EDITOR": "Metin Düzenleyici", "EMBED": "göm", "EMPTY": "boş", "ENCRYPTION_KEY": "şifreleme anahtarı", "ENDPOINT": "uç nokta", "ERROR": "Hata", "EXISTING_LINKS": "mevcut bağlantılar", "EXPIRATION": "Sona erme tarihi", "EXPORT_AS_{{VALUE}}": "{{VALUE}} olarak dışa aktar", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "Sıkça erişilen klasörler burada gösterilecek", "HIDE_HIDDEN_FILES": "gizli dosyaları gizle", "HOME": "ana sayfa", "HOSTNAME*": "Ana makine adı", "HOST_KEY": "Ana makine anahtarı", "INCORRECT_PASSWORD": "yanlış parola", "INFO": "Bilgi", "INTERNAL_ERROR": "İç hata", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "Dahili hata: {{VALUE}} oluşturulamıyor", "INVALID_ACCOUNT": "Geçersiz hesap", "INVALID_PASSWORD": "Geçersiz şifre", "LAYOUT": "düzen", "LOADING": "yükleniyor", "LOCATION": "Konum", "MISSING_DEPENDENCY": "Eksik bağımlılıklar", "MORE_DETAILS": "daha fazla detay", "NAVIGATE": "Gezinti", "NEW_FILE": "yeni dosya", "NEW_FILE::SHORT": "yeni dosya", "NEW_FOLDER": "yeni dizin", "NEW_FOLDER::SHORT": "yeni dizin", "NO": "Hayır", "NOT_ALLOWED": "izin verilmedi", "NOT_AUTHORISED": "yetkin değil", "NOT_FOUND": "bulunamadı", "NOT_IMPLEMENTED": "uygulanmadı", "NOT_SUPPORTED": "desteklenmiyor", "NOT_VALID": "geçerli değil", "NUMBER_OF_CONNECTIONS": "Bağlantı sayısı", "OK": "Tamam", "ONLY_FOR_USERS": "Yalnızca şu kullanıcılar için", "OOPS": "Amanın!", "PASSPHRASE": "Anahtar Parolası", "PASSWORD": "Parola", "PASSWORD_CANT_BE_EMPTY": "Parola boş olamaz", "PATH": "Yol", "PERMISSION_DENIED": "İzin reddedildi", "PICK_A_MASTER_PASSWORD": "Bir ana parola belirleyin", "PORT": "Port", "POWERED_BY": "tarafından desteklenmektedir", "PROPERTIES": "Özellikleri", "PROTECT_ACCESS_WITH_A_PASSWORD": "Parola ile erişimi koru", "QUICK_ACCESS": "hızlı erişim", "REGION": "Bölge", "REMEMBER_ME": "Beni hatırla", "REMOVE": "Kaldır", "RENAME": "yeniden adlandır", "RENAME_AS": "olarak yeniden adlandır", "RESTRICTIONS": "Kısıtlamalar", "RUNNING": "çalışıyor", "SAVE_CURRENT_FILE": "Mevcut dosyayı kaydet", "SEARCH": "Ara", "SETTINGS": "Ayarlar", "SHARE": "paylaş", "SHARED_DRIVE": "paylaşılan sürücü", "SKIP_TO_CONTENT": "İçeriğe atla", "SORT": "sırala", "SORT_BY_DATE": "tarihe göre sırala", "SORT_BY_NAME": "isme göre sırala", "SORT_BY_SIZE": "boyuta göre sırala", "SORT_BY_TYPE": "türe göre sırala", "STARRED": "yıldızlı", "SUPPORT": "Destek", "TAG": "etiket", "TAGS": "etiketler", "THERE_IS_NOTHING_HERE": "Burada hiçbir şey yok", "THE_FILE_{{VALUE}}_WAS_DELETED": "{{VALUE}} dosyası silindi", "THE_FILE_{{VALUE}}_WAS_RENAMED": "{{VALUE}} dosyası yeniden adlandırıldı", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "Bağlantı panoya kopyalandı", "THE_LINK_WONT_BE_VALID_AFTER": "Bağlantı daha sonra geçerli olmayacaktır", "TIMEOUT": "Zaman aşımı", "TODO": "Yapılacaklar", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "Ağ yoğunluğu, daha sonra tekrar deneyin", "UPLOAD": "yükle", "UPLOADER": "Yükleyici", "USERNAME": "Kullanıcı adı", "VERSION": "sürüm", "VIEWER": "Görüntüleyici", "WAITING": "bekliyor", "YES": "Evet", "YOUR_EMAIL_ADDRESS": "E-posta adresi", "YOUR_FILES": "dosyalarınız", "YOUR_MASTER_PASSWORD": "Ana parolan", "YOU_CANT_DO_THAT": "bunu yapamazsınız" } ================================================ FILE: public/assets/locales/uk.json ================================================ { "ABORTED": "перервано", "ABORT_CURRENT_UPLOADS?": "перервати поточне завантаження?", "ACTIVITY": "Діяльність", "ADVANCED": "розширені", "ALL_DONE": "все зроблено", "ALREADY_EXIST": "вже існує", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Файл під назвою {{VALUE}} створено", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Створена папка під назвою {{VALUE}}", "BEAUTIFUL_URL": "beautiful_url", "BOOKMARK": "закладка", "CAMERA": "камера", "CANCEL": "скасувати", "CANNOT_ESTABLISH_A_CONNECTION": "не вдається встановити з'єднання", "CANT_LOAD_THIS_PICTURE": "не вдається завантажити це зображення", "CANT_USE_FILESYSTEM": "Неможливо використовувати файлову систему", "CAN_RESHARE": "може повторно ділитися", "CODE": "код", "CONFIGURE": "налаштувати", "CONFIRM_BY_TYPING": "підтвердити, ввівши", "CONNECT": "підключити", "CONNECTION_LOST": "зв'язок втрачено", "COPIED_TO_CLIPBOARD": "скопійовано у буфер обміну", "CREATE_A_NEW_LINK": "створити нове посилання", "CREATE_A_TAG": "створити тег", "CURRENT": "струм", "CURRENT_UPLOAD": "поточне завантаження", "CUSTOM_LINK_URL": "спеціальна URL-адреса посилання", "DASHBOARD": "панель приладів", "DATE": "дата", "DISPLAY_HIDDEN_FILES": "відображення прихованих файлів", "DOESNT_MATCH": "не відповідає", "DONE": "зроблено", "DOWNLOAD": "завантажити", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "чи хочете ви зберегти зміни", "DROP_HERE_TO_UPLOAD": "сюди, щоб завантажити", "EDITOR": "редактор", "EMBED": "вбудувати", "EMPTY": "порожній", "ENCRYPTION_KEY": "ключ шифрування", "ENDPOINT": "кінцева точка", "ERROR": "помилка", "EXISTING_LINKS": "існуючі посилання", "EXPIRATION": "термін придатності", "EXPORT_AS_{{VALUE}}": "експортувати як {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "тут будуть відображатися папки, які часто отримують доступ", "HIDE_HIDDEN_FILES": "приховати приховані файли", "HOME": "головна", "HOSTNAME*": "ім'я хоста", "HOST_KEY": "ключ хоста", "INCORRECT_PASSWORD": "Невірний пароль", "INFO": "інформація", "INTERNAL_ERROR": "Внутрішня помилка", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "внутрішня помилка: не вдається створити {{VALUE}}", "INVALID_ACCOUNT": "недійсний рахунок", "INVALID_PASSWORD": "недійсний пароль", "LAYOUT": "макет", "LOADING": "завантаження", "LOCATION": "Розташування", "MISSING_DEPENDENCY": "відсутність залежності", "MORE_DETAILS": "більше деталей", "NAVIGATE": "орієнтуватися", "NEW_FILE": "новий файл", "NEW_FILE::SHORT": "новий файл", "NEW_FOLDER": "новий каталог", "NEW_FOLDER::SHORT": "новий каталог", "NO": "ні", "NOT_ALLOWED": "не дозволено", "NOT_AUTHORISED": "не уповноважений", "NOT_FOUND": "не знайдено", "NOT_IMPLEMENTED": "не впроваджений", "NOT_SUPPORTED": "Не підтримується", "NOT_VALID": "не діє", "NUMBER_OF_CONNECTIONS": "кількість підключень", "OK": "Гаразд", "ONLY_FOR_USERS": "Тільки для користувачів", "OOPS": "На жаль", "PASSPHRASE": "парольна фраза", "PASSWORD": "пароль", "PASSWORD_CANT_BE_EMPTY": "пароль не може бути порожнім", "PATH": "шлях", "PERMISSION_DENIED": "у дозволі відмовлено", "PICK_A_MASTER_PASSWORD": "виберіть головний пароль", "PORT": "порт", "POWERED_BY": "працює на основі", "PROPERTIES": "властивості", "PROTECT_ACCESS_WITH_A_PASSWORD": "захистити доступ паролем", "QUICK_ACCESS": "швидкий доступ", "REGION": "область", "REMEMBER_ME": "Пам'ятай мене", "REMOVE": "видалити", "RENAME": "перейменувати", "RENAME_AS": "перейменувати як", "RESTRICTIONS": "обмеження", "RUNNING": "виконується", "SAVE_CURRENT_FILE": "зберегти поточний файл", "SEARCH": "пошук", "SETTINGS": "налаштування", "SHARE": "поділитися", "SHARED_DRIVE": "спільний диск", "SKIP_TO_CONTENT": "Перейти до контенту", "SORT": "сортувати", "SORT_BY_DATE": "сортувати за датою", "SORT_BY_NAME": "сортувати за назвою", "SORT_BY_SIZE": "сортувати за розміром", "SORT_BY_TYPE": "сортувати за типом", "STARRED": "позначено зіркою", "SUPPORT": "підтримка", "TAG": "тег", "TAGS": "теги", "THERE_IS_NOTHING_HERE": "тут нічого немає", "THE_FILE_{{VALUE}}_WAS_DELETED": "файл {{VALUE}} видалено", "THE_FILE_{{VALUE}}_WAS_RENAMED": "файл {{VALUE}} було перейпрацює на основіменовано", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "посилання було скопійовано у буфер обміну", "THE_LINK_WONT_BE_VALID_AFTER": "після цього посилання не буде дійсним", "TIMEOUT": "час вийшов", "TODO": "зробити", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "затори, спробуйте пізніше", "UPLOAD": "завантажити", "UPLOADER": "завантажувач", "USERNAME": "ім'я користувача", "VERSION": "версія", "VIEWER": "глядач", "WAITING": "очікування", "YES": "так", "YOUR_EMAIL_ADDRESS": "Ваша електронна адреса", "YOUR_FILES": "ваші файли", "YOUR_MASTER_PASSWORD": "ваш головний пароль", "YOU_CANT_DO_THAT": "ти не можеш цього зробити" } ================================================ FILE: public/assets/locales/vi.json ================================================ { "ABORTED": "đã hủy bỏ", "ABORT_CURRENT_UPLOADS?": "hủy bỏ tải lên hiện tại?", "ACTIVITY": "Hoạt động", "ADVANCED": "nâng cao", "ALL_DONE": "Hoàn tất", "ALREADY_EXIST": "đã tồn tại", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "Một tệp có tên {{VALUE}} đã được tạo", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "Một thư mục có tên {{VALUE}} đã được tạo", "BEAUTIFUL_URL": "đẹp_url", "BOOKMARK": "dấu trang", "CAMERA": "Máy ảnh", "CANCEL": "hủy bỏ", "CANNOT_ESTABLISH_A_CONNECTION": "không thể thiết lập kết nối", "CANT_LOAD_THIS_PICTURE": "không thể tải hình ảnh này", "CANT_USE_FILESYSTEM": "Không thể sử dụng hệ thống tập tin", "CAN_RESHARE": "có thể chia sẻ lại", "CODE": "mã", "CONFIGURE": "cấu hình", "CONFIRM_BY_TYPING": "xác nhận bằng cách gõ", "CONNECT": "kết nối", "CONNECTION_LOST": "mất liên lạc", "COPIED_TO_CLIPBOARD": "sao chép vào clipboard", "CREATE_A_NEW_LINK": "tạo một liên kết mới", "CREATE_A_TAG": "tạo thẻ", "CURRENT": "hiện hành", "CURRENT_UPLOAD": "tải lên hiện tại", "CUSTOM_LINK_URL": "URL liên kết tùy chỉnh", "DASHBOARD": "bảng điều khiển", "DATE": "ngày", "DISPLAY_HIDDEN_FILES": "hiển thị các tập tin ẩn", "DOESNT_MATCH": "không phù hợp", "DONE": "Xong", "DOWNLOAD": "Tải xuống", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "bạn có muốn lưu các thay đổi", "DROP_HERE_TO_UPLOAD": "thả vào đây để tải lên", "EDITOR": "biên tập viên", "EMBED": "nhúng", "EMPTY": "Trống", "ENCRYPTION_KEY": "khóa mã hóa", "ENDPOINT": "điểm cuối", "ERROR": "lỗi", "EXISTING_LINKS": "liên kết hiện có", "EXPIRATION": "hết hạn", "EXPORT_AS_{{VALUE}}": "xuất dưới dạng {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "thư mục truy cập thường xuyên sẽ được hiển thị ở đây", "HIDE_HIDDEN_FILES": "ẩn các tập tin ẩn", "HOME": "trang chủ", "HOSTNAME*": "tên máy chủ", "HOST_KEY": "khóa máy chủ", "INCORRECT_PASSWORD": "mật khẩu không đúng", "INFO": "thông tin", "INTERNAL_ERROR": "Lỗi cục bộ", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "lỗi nội bộ: không thể tạo {{VALUE}}", "INVALID_ACCOUNT": "tài khoản không hợp lệ", "INVALID_PASSWORD": "mật khẩu không hợp lệ", "LAYOUT": "bố cục", "LOADING": "đang tải", "LOCATION": "vị trí", "MISSING_DEPENDENCY": "thiếu phụ thuộc", "MORE_DETAILS": "thêm chi tiết", "NAVIGATE": "điều hướng", "NEW_FILE": "tập tin mới", "NEW_FILE::SHORT": "tập tin mới", "NEW_FOLDER": "từ điển mới", "NEW_FOLDER::SHORT": "từ điển mới", "NO": "Không", "NOT_ALLOWED": "không cho phép", "NOT_AUTHORISED": "không được uỷ quyền", "NOT_FOUND": "không tìm thấy", "NOT_IMPLEMENTED": "không được thực hiện", "NOT_SUPPORTED": "không được hỗ trợ", "NOT_VALID": "không hợp lệ", "NUMBER_OF_CONNECTIONS": "số lượng kết nối", "OK": "đồng ý", "ONLY_FOR_USERS": "Chỉ dành cho người dùng", "OOPS": "Rất tiếc!", "PASSPHRASE": "cụm mật khẩu", "PASSWORD": "mật khẩu", "PASSWORD_CANT_BE_EMPTY": "mật khẩu không thể để trống", "PATH": "Đường dẫn", "PERMISSION_DENIED": "Quyền truy cập bị từ chối", "PICK_A_MASTER_PASSWORD": "Chọn mật khẩu chính", "PORT": "Cổng", "POWERED_BY": "cung cấp bởi", "PROPERTIES": "Thuộc tính", "PROTECT_ACCESS_WITH_A_PASSWORD": "bảo vệ quyền truy cập bằng mật khẩu", "QUICK_ACCESS": "truy cập nhanh", "REGION": "khu vực", "REMEMBER_ME": "nhớ tôi", "REMOVE": "Xóa", "RENAME": "đổi tên", "RENAME_AS": "đổi tên thành", "RESTRICTIONS": "những hạn chế", "RUNNING": "đang chạy", "SAVE_CURRENT_FILE": "lưu tập tin hiện tại", "SEARCH": "Tìm kiếm", "SETTINGS": "cài đặt", "SHARE": "chia sẻ", "SHARED_DRIVE": "ổ chung", "SKIP_TO_CONTENT": "Chuyển đến nội dung", "SORT": "sắp xếp", "SORT_BY_DATE": "lọc theo ngày", "SORT_BY_NAME": "sắp xếp theo tên", "SORT_BY_SIZE": "sắp xếp theo kích thước", "SORT_BY_TYPE": "sắp xếp theo loại", "STARRED": "được gắn sao", "SUPPORT": "Hỗ trợ", "TAG": "thẻ", "TAGS": "các thẻ", "THERE_IS_NOTHING_HERE": "Không có cái gì ở đây cả", "THE_FILE_{{VALUE}}_WAS_DELETED": "tệp {{VALUE}} đã bị xóa", "THE_FILE_{{VALUE}}_WAS_RENAMED": "tệp {{VALUE}} đã được đổi tên", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "liên kết đã được sao chép trong clipboard", "THE_LINK_WONT_BE_VALID_AFTER": "liên kết sẽ không có hiệu lực sau", "TIMEOUT": "hết giờ", "TODO": "làm", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "tắc nghẽn giao thông, thử lại sau", "UPLOAD": "tải lên", "UPLOADER": "người tải lên", "USERNAME": "tên tài khoản", "VERSION": "phiên bản", "VIEWER": "người xem", "WAITING": "đang chờ đợi", "YES": "Đúng", "YOUR_EMAIL_ADDRESS": "địa chỉ email của bạn", "YOUR_FILES": "các tệp của bạn", "YOUR_MASTER_PASSWORD": "mật khẩu chủ của bạn", "YOU_CANT_DO_THAT": "bạn không thể làm điều đó" } ================================================ FILE: public/assets/locales/zh.json ================================================ { "ABORTED": "已中止", "ABORT_CURRENT_UPLOADS?": "中止当前上传?", "ACTIVITY": "活动", "ADVANCED": "高级", "ALL_DONE": "全部完成", "ALREADY_EXIST": "已经存在", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "已创建名为 {{VALUE}} 的文件", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "已创建名为 {{VALUE}} 的文件夹", "BEAUTIFUL_URL": "好看的 URL", "BOOKMARK": "书签", "CAMERA": "相机", "CANCEL": "取消", "CANNOT_ESTABLISH_A_CONNECTION": "无法建立连接", "CANT_LOAD_THIS_PICTURE": "无法加载这张图片", "CANT_USE_FILESYSTEM": "无法访问文件系统", "CAN_RESHARE": "可以转发", "CODE": "代码", "CONFIGURE": "配置", "CONFIRM_BY_TYPING": "输入以确认", "CONNECT": "连接", "CONNECTION_LOST": "连接已断开", "COPIED_TO_CLIPBOARD": "复制到剪贴板", "CREATE_A_NEW_LINK": "创建一个新链接", "CREATE_A_TAG": "创建标签", "CURRENT": "目前", "CURRENT_UPLOAD": "当前上传", "CUSTOM_LINK_URL": "自定义链接网址", "DASHBOARD": "仪表板", "DATE": "日期", "DISPLAY_HIDDEN_FILES": "显示隐藏文件", "DOESNT_MATCH": "不匹配", "DONE": "完成", "DOWNLOAD": "下载", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "您要保存更改吗", "DROP_HERE_TO_UPLOAD": "拖拽到这里上传", "EDITOR": "编辑", "EMBED": "嵌入", "EMPTY": "空的", "ENCRYPTION_KEY": "加密密钥", "ENDPOINT": "端点", "ERROR": "错误", "EXISTING_LINKS": "现有链接", "EXPIRATION": "到期", "EXPORT_AS_{{VALUE}}": "导出为 {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "经常访问的文件夹将显示在这里", "HIDE_HIDDEN_FILES": "不显示隐藏文件", "HOME": "首页", "HOSTNAME*": "主机名", "HOST_KEY": "主机密钥", "INCORRECT_PASSWORD": "密码错误", "INFO": "信息", "INTERNAL_ERROR": "内部错误", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "内部错误:无法创建 {{VALUE}}", "INVALID_ACCOUNT": "无效账户", "INVALID_PASSWORD": "无效密码", "LAYOUT": "布局", "LOADING": "加载中", "LOCATION": "位置", "MISSING_DEPENDENCY": "缺少依赖", "MORE_DETAILS": "更多详情", "NAVIGATE": "导航", "NEW_FILE": "新文件", "NEW_FILE::SHORT": "新文件", "NEW_FOLDER": "新目录", "NEW_FOLDER::SHORT": "新目录", "NO": "否", "NOT_ALLOWED": "不允许", "NOT_AUTHORISED": "未经授权", "NOT_FOUND": "未找到", "NOT_IMPLEMENTED": "未实现", "NOT_SUPPORTED": "不支持", "NOT_VALID": "无效", "NUMBER_OF_CONNECTIONS": "连接数", "OK": "好", "ONLY_FOR_USERS": "仅供用户", "OOPS": "哎呀", "PASSPHRASE": "密码短语", "PASSWORD": "密码", "PASSWORD_CANT_BE_EMPTY": "密码不能为空", "PATH": "路径", "PERMISSION_DENIED": "拒绝访问", "PICK_A_MASTER_PASSWORD": "选择一个主密码", "PORT": "端口", "POWERED_BY": "技术支持来自", "PROPERTIES": "属性", "PROTECT_ACCESS_WITH_A_PASSWORD": "用密码保护访问", "QUICK_ACCESS": "快速访问", "REGION": "区域", "REMEMBER_ME": "记住账号", "REMOVE": "移除", "RENAME": "重命名", "RENAME_AS": "重命名为", "RESTRICTIONS": "限制条件", "RUNNING": "运行中", "SAVE_CURRENT_FILE": "保存当前文件", "SEARCH": "搜索", "SETTINGS": "参数", "SHARE": "分享", "SHARED_DRIVE": "共享驱动器", "SKIP_TO_CONTENT": "跳转到内容", "SORT": "排序", "SORT_BY_DATE": "按日期排序", "SORT_BY_NAME": "按名称分类", "SORT_BY_SIZE": "按大小排序", "SORT_BY_TYPE": "按类型排序", "STARRED": "标星", "SUPPORT": "支持", "TAG": "标签", "TAGS": "标签", "THERE_IS_NOTHING_HERE": "这里什么都没有", "THE_FILE_{{VALUE}}_WAS_DELETED": "文件 {{VALUE}} 已删除", "THE_FILE_{{VALUE}}_WAS_RENAMED": "文件 {{VALUE}} 已重命名", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "链接已复制到剪贴板", "THE_LINK_WONT_BE_VALID_AFTER": "链接在之后将无效", "TIMEOUT": "超时", "TODO": "待办", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "网络拥堵,请稍后再试", "UPLOAD": "上传", "UPLOADER": "上传者", "USERNAME": "用户名", "VERSION": "版本", "VIEWER": "查看者", "WAITING": "等候", "YES": "是", "YOUR_EMAIL_ADDRESS": "您的电子邮件地址", "YOUR_FILES": "您的文件", "YOUR_MASTER_PASSWORD": "您的主密码", "YOU_CANT_DO_THAT": "您无法那样做" } ================================================ FILE: public/assets/locales/zh_tw.json ================================================ { "ABORTED": "已中止", "ABORT_CURRENT_UPLOADS?": "中止目前上傳?", "ACTIVITY": "活動", "ADVANCED": "進階", "ALL_DONE": "全部完成", "ALREADY_EXIST": "已經存在", "A_FILE_NAMED_{{VALUE}}_WAS_CREATED": "已建立名為 {{VALUE}} 的檔案", "A_FOLDER_NAMED_{{VALUE}}_WAS_CREATED": "已建立名為 {{VALUE}} 的資料夾", "BEAUTIFUL_URL": "美觀的 URL", "BOOKMARK": "書籤", "CAMERA": "相機", "CANCEL": "取消", "CANNOT_ESTABLISH_A_CONNECTION": "無法建立連線", "CANT_LOAD_THIS_PICTURE": "無法載入這張圖片", "CANT_USE_FILESYSTEM": "無法使用檔案系統", "CAN_RESHARE": "可再次分享", "CODE": "代碼", "CONFIGURE": "設定", "CONFIRM_BY_TYPING": "輸入以確認", "CONNECT": "連線", "CONNECTION_LOST": "連線已中斷", "COPIED_TO_CLIPBOARD": "已複製到剪貼簿", "CREATE_A_NEW_LINK": "建立一個新連結", "CREATE_A_TAG": "建立標籤", "CURRENT": "目前", "CURRENT_UPLOAD": "目前上傳", "CUSTOM_LINK_URL": "自訂連結網址", "DASHBOARD": "儀表板", "DATE": "日期", "DISPLAY_HIDDEN_FILES": "顯示隱藏檔案", "DOESNT_MATCH": "不符合", "DONE": "完成", "DOWNLOAD": "下載", "DO_YOU_WANT_TO_SAVE_THE_CHANGES_?": "您要儲存變更嗎", "DROP_HERE_TO_UPLOAD": "將檔案拖曳到此處上傳", "EDITOR": "編輯", "EMBED": "嵌入", "EMPTY": "空的", "ENCRYPTION_KEY": "加密金鑰", "ENDPOINT": "端點", "ERROR": "錯誤", "EXISTING_LINKS": "現有連結", "EXPIRATION": "到期", "EXPORT_AS_{{VALUE}}": "匯出為 {{VALUE}}", "FREQUENTLY_ACCESS_FOLDERS_WILL_BE_SHOWN_HERE": "經常存取的資料夾將顯示在這裡", "HIDE_HIDDEN_FILES": "不顯示隱藏檔案", "HOME": "首頁", "HOSTNAME*": "主機名稱", "HOST_KEY": "主機金鑰", "INCORRECT_PASSWORD": "密碼錯誤", "INFO": "資訊", "INTERNAL_ERROR": "內部錯誤", "INTERNAL_ERROR_CANT_CREATE_A_{{VALUE}}": "內部錯誤:無法建立 {{VALUE}}", "INVALID_ACCOUNT": "無效帳戶", "INVALID_PASSWORD": "無效的密碼", "LAYOUT": "配置", "LOADING": "載入中", "LOCATION": "位置", "MISSING_DEPENDENCY": "缺少相依性", "MORE_DETAILS": "更多細節", "NAVIGATE": "導覽", "NEW_FILE": "新增檔案", "NEW_FILE::SHORT": "新增檔案", "NEW_FOLDER": "新資料夾", "NEW_FOLDER::SHORT": "新資料夾", "NO": "否", "NOT_ALLOWED": "不允許", "NOT_AUTHORISED": "未經授權", "NOT_FOUND": "未找到", "NOT_IMPLEMENTED": "未實作", "NOT_SUPPORTED": "不支援", "NOT_VALID": "無效", "NUMBER_OF_CONNECTIONS": "連線數", "OK": "確定", "ONLY_FOR_USERS": "僅供使用者", "OOPS": "哎呀", "PASSPHRASE": "密碼短語", "PASSWORD": "密碼", "PASSWORD_CANT_BE_EMPTY": "密碼不能為空", "PATH": "路徑", "PERMISSION_DENIED": "權限不足", "PICK_A_MASTER_PASSWORD": "選擇一個主密碼", "PORT": "連接埠", "POWERED_BY": "Powered by", "PROPERTIES": "屬性", "PROTECT_ACCESS_WITH_A_PASSWORD": "用密碼保護存取", "QUICK_ACCESS": "快速存取", "REGION": "區域", "REMEMBER_ME": "記住我", "REMOVE": "移除", "RENAME": "重新命名", "RENAME_AS": "重新命名為", "RESTRICTIONS": "限制", "RUNNING": "運作中", "SAVE_CURRENT_FILE": "儲存目前檔案", "SEARCH": "搜尋", "SETTINGS": "設定", "SHARE": "分享", "SHARED_DRIVE": "共用磁碟", "SKIP_TO_CONTENT": "跳轉到內容", "SORT": "排序", "SORT_BY_DATE": "依日期排序", "SORT_BY_NAME": "依名稱排序", "SORT_BY_SIZE": "按大小排序", "SORT_BY_TYPE": "依類型排序", "STARRED": "加星標", "SUPPORT": "支援", "TAG": "標籤", "TAGS": "標籤", "THERE_IS_NOTHING_HERE": "這裡什麼都沒有", "THE_FILE_{{VALUE}}_WAS_DELETED": "檔案 {{VALUE}} 已刪除", "THE_FILE_{{VALUE}}_WAS_RENAMED": "檔案 {{VALUE}} 已重新命名", "THE_LINK_WAS_COPIED_IN_THE_CLIPBOARD": "連結已複製到剪貼簿", "THE_LINK_WONT_BE_VALID_AFTER": "連結在此後將失效", "TIMEOUT": "逾時", "TODO": "待辦事項", "TRAFFIC_CONGESTION_TRY_AGAIN_LATER": "網路壅塞,請稍後再試", "UPLOAD": "上傳", "UPLOADER": "上傳者", "USERNAME": "使用者名稱", "VERSION": "版本", "VIEWER": "檢視者", "WAITING": "等待中", "YES": "是", "YOUR_EMAIL_ADDRESS": "您的電子郵件地址", "YOUR_FILES": "您的檔案", "YOUR_MASTER_PASSWORD": "您的主密碼", "YOU_CANT_DO_THAT": "您不能這麼做" } ================================================ FILE: public/assets/model/backend.js ================================================ import rxjs from "../lib/rx.js"; import ajax from "../lib/ajax.js"; const backend$ = ajax({ url: "api/backend", method: "GET", responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), ); export function getBackends() { return backend$; } ================================================ FILE: public/assets/model/chromecast.js ================================================ import { get as getConfig } from "./config.js"; export const Chromecast = new class ChromecastManager { init() { if (navigator.onLine === false) return Promise.resolve(); if (!getConfig("enable_chromecast", false)) { return Promise.resolve(); } else if (!("chrome" in window)) { return Promise.resolve(); } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { return Promise.resolve(); } return new Promise((resolve) => { if (document.head.querySelector("script#chromecast")) return resolve(null); const script = document.createElement("script"); script.id = "chromecast"; script.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"; script.onerror = () => resolve(null); window["__onGCastApiAvailable"] = function(isAvailable) { if (isAvailable) window.cast.framework.CastContext.getInstance().setOptions({ receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, }); resolve(null); }; document.head.appendChild(script); }); } createLink(apiPath) { const target = new URL(location.origin + apiPath); const shareID = new URLSearchParams(location.search).get("search"); if (shareID) target.searchParams.append("share", shareID); return target.toString(); } createRequest(mediaInfo) { if (!window.BEARER_TOKEN) throw new Error("Invalid account"); // TODO: it would be much much nicer to set the authorization from an HTTP header // but this would require to create a custom web receiver app, setup accounts on // google, etc,... Until that happens, we're setting the authorization within the // url. Once we have that app, the authorisation will come from a customData field // of a chrome.cast.media.LoadRequest const target = new URL(mediaInfo.contentId); target.searchParams.append("authorization", window.BEARER_TOKEN); mediaInfo.contentId = target.toString(); return new window.chrome.cast.media.LoadRequest(mediaInfo); } context() { if (!window.chrome?.cast?.isAvailable) return; return window.cast.framework.CastContext.getInstance(); } session() { const context = this.context(); if (!context) return; return context.getCurrentSession(); } media() { const session = this.session(); if (!session) return; return session.getMediaSession(); } }(); ================================================ FILE: public/assets/model/config.d.ts ================================================ interface Config { [key: string]: any; thumbnailer: string[]; } export function init(): Promise<Config>; export function get(): Config; export function get<T>(key: string, defaultValue?: T): T; export function getVersion(): string; export function query(): any; ================================================ FILE: public/assets/model/config.js ================================================ import rxjs from "../lib/rx.js"; import ajax from "../lib/ajax.js"; const config$ = ajax({ url: "api/config", method: "GET", responseType: "json", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), ); let CONFIG = {}; export async function init() { const config = await config$.toPromise(); CONFIG = config; return config; } export function get(key, defaultValue) { if (key) return CONFIG[key] || defaultValue; return CONFIG; } export function getVersion() { return get("version", "na"); } export function query() { return config$; } ================================================ FILE: public/assets/model/plugin.js ================================================ import rxjs from "../lib/rx.js"; import ajax from "../lib/ajax.js"; const plugin$ = ajax({ url: "api/plugin", method: "GET", responseType: "json", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), ); let plugins = {}; export async function init() { plugins = await plugin$.toPromise(); } export function get(mime) { return plugins[mime]; } export async function load(mime) { const specs = plugins[mime]; if (!specs) return null; const [, url] = specs; const module = await import(new URL(url, import.meta.url).href); return module.default; } ================================================ FILE: public/assets/model/session.js ================================================ import rxjs from "../lib/rx.js"; import ajax from "../lib/ajax.js"; import { forwardURLParams } from "../lib/path.js"; export function getSession() { return ajax({ url: withShare("api/session"), method: "GET", responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), rxjs.tap(({ authorization }) => { if (authorization) window.BEARER_TOKEN = authorization; }), ); } export function createSession(authenticationRequest) { return ajax({ method: "POST", url: withShare("api/session"), body: authenticationRequest, responseType: "json", }).pipe( rxjs.tap(({ responseHeaders }) => { if (responseHeaders.bearer) window.BEARER_TOKEN = responseHeaders.bearer; // see ctrl_boot_frontoffice.js -> setup_iframe }), rxjs.map(({ responseJSON }) => responseJSON.result), ); } export function deleteSession() { return ajax({ url: withShare("api/session"), method: "DELETE" }).pipe(rxjs.tap(() => { delete window.BEARER_TOKEN; })); } window.addEventListener("pagechange", async() => { if (location.hash === "") return; // happy path const token = new URLSearchParams(location.hash.replace(new RegExp("^#"), "?")).get("bearer"); if (token) window.BEARER_TOKEN = token; }); const withShare = (url) => forwardURLParams(url, ["share"]); ================================================ FILE: public/assets/pages/adminpage/animate.js ================================================ import { transition, slideYIn } from "../../lib/animate.js"; export default function($node) { return transition($node, { timeEnter: 100, enter: slideYIn(3) }); } export const cssHideMenu = ".component_menu_sidebar{transform: translateX(-300px)}"; ================================================ FILE: public/assets/pages/adminpage/component_box-item.js ================================================ import { ApplicationError } from "../../lib/error.js"; class BoxItem extends HTMLElement { constructor() { super(); this.attributeChangedCallback(); } static get observedAttributes() { return ["data-selected"]; } attributeChangedCallback() { this.innerHTML = this.render({ label: this.getAttribute("data-label"), }); this.classList.add("box-item", "pointer", "no-select"); } render({ label }) { return ` <div> <strong>${label}</strong> <span class="no-select"> <span class="icon">+</span> </span> </div> `; } toggleSelection(opt = {}) { const { tmpl, isSelected = !this.classList.contains("active") } = opt; const $icon = this.querySelector(".icon"); if (!$icon) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: no icon"); if (isSelected) { this.classList.add("active"); if (tmpl) $icon.innerHTML = tmpl; } else { this.classList.remove("active"); $icon.innerHTML = "+"; } } } customElements.define("box-item", BoxItem); ================================================ FILE: public/assets/pages/adminpage/ctrl_about.css ================================================ .component_page_about { padding-top: 50px; overflow-x: auto; } .component_page_about tr { display: block; background: white; border: 2px solid #ebebec; border-style: solid; border-radius: 5px; margin: 15px 0 0 0; padding: 10px 15px; color: var(--light); font-size: 0.9rem; } .component_page_about td { display: block; min-width: 100%; } .component_page_about td:first-of-type { font-weight: 600; color: var(--color); text-transform: capitalize; font-size: 1.05rem; border-bottom: 2px solid var(--border); margin-bottom: 5px; padding-bottom: 5px; } .component_page_about .small { display: block; margin-left: 20px; font-size: 0.9rem; padding: 0px 2px; } .component_page_admin .page_container .component_page_about a { color: var(--ligh); } ================================================ FILE: public/assets/pages/adminpage/ctrl_about.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, stateMutation } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { CSS } from "../../helpers/loader.js"; import AdminHOC from "./decorator.js"; import { get as getRelease } from "./model_release.js"; import transition from "./animate.js"; export default AdminHOC(async function(render) { const $page = createElement(` <div class="component_page_about"> <style>${await CSS(import.meta.url, "ctrl_about.css")}</style> <div data-bind="about"><Loader /></div> </div> `); render(transition($page)); effect(getRelease().pipe( rxjs.map(({ html }) => html), stateMutation(qs($page, "[data-bind=\"about\"]"), "innerHTML"), )); }); ================================================ FILE: public/assets/pages/adminpage/ctrl_activity.js ================================================ import { createElement, createRender } from "../../lib/skeleton/index.js"; import { initConfig } from "./model_config.js"; import componentLogForm from "./ctrl_activity_form.js"; import componentLogViewer from "./ctrl_activity_viewer.js"; import componentLogGraph from "./ctrl_activity_graph.js"; import componentAuditor from "./ctrl_activity_audit.js"; import transition from "./animate.js"; import AdminHOC from "./decorator.js"; export default AdminHOC(async function(render) { const $page = createElement(` <div class="component_logpage sticky"> <h2>System Logs</h2> <div class="component_logviewer"></div> <div class="component_stats"></div> <div class="component_logger"></div> <h2>Audit Report</h2> <div class="component_audit"></div> <div> `); render(transition($page)); await initConfig(); componentLogViewer(createRender($page.querySelector(".component_logviewer"))); componentLogForm(createRender($page.querySelector(".component_logger"))); componentLogGraph(createRender($page.querySelector(".component_stats"))); componentAuditor(createRender($page.querySelector(".component_audit"))); }); ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_audit.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, stateMutation, applyMutation } from "../../lib/rx.js"; import { qs, qsa } from "../../lib/dom.js"; import { createForm } from "../../lib/form.js"; import { formTmpl } from "../../components/form.js"; import { generateSkeleton } from "../../components/skeleton.js"; import { useForm$ } from "./helper_form.js"; import { get as getAudit, setLoader } from "./model_audit.js"; export default function(render) { const $page = createElement(` <div> <form> ${generateSkeleton(10)} </form> <div data-bind="auditor"></div> </div> `); render($page); const audit$ = getAudit().pipe(rxjs.share()); // create the form on the dom const setup$ = audit$.pipe( rxjs.first(), rxjs.map(({ form }) => form), rxjs.mergeMap((formSpec) => createForm(formSpec, formTmpl())), rxjs.map(($form) => [$form]), applyMutation(qs($page, "form"), "replaceChildren"), ); effect(setup$); // setup the form handler effect(setup$.pipe( rxjs.first(), rxjs.tap(() => updateLoop($page, audit$)), )); } function updateLoop($page, audit$) { // feature1: query result effect(audit$.pipe( rxjs.map(({ render }) => render), stateMutation(qs($page, "[data-bind=\"auditor\"]"), "innerHTML"), rxjs.tap(() => setLoader(false)), )); // feature2: update to the query form effect(rxjs.of(null).pipe( useForm$(() => qsa($page, "form [name]")), rxjs.tap(() => setLoader(true)), rxjs.debounceTime(1000), rxjs.first(), rxjs.map(() => qs($page, "form")), rxjs.map(($form) => { const formData = new FormData($form); const p = new URLSearchParams(); for (const [key, value] of formData.entries()) { if (!value) continue; p.set(key.replace(new RegExp("^search\."), ""), `${value}`); } return p; }), rxjs.tap((p) => updateLoop($page, getAudit(p).pipe(rxjs.share()))), )); } ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_form.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutation } from "../../lib/rx.js"; import { qsa } from "../../lib/dom.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { generateSkeleton } from "../../components/skeleton.js"; import notification from "../../components/notification.js"; import { formTmpl } from "../../components/form.js"; import { query as getConfig } from "../../model/config.js"; import { get as getAdminConfig, save as saveConfig } from "./model_config.js"; import { renderLeaf, useForm$, formObjToJSON$ } from "./helper_form.js"; export default function(render) { const $form = createElement(` <form style="min-height: 240px; margin-top:20px;"> ${generateSkeleton(4)} </form> `); render($form); // feature1: render the form const setup$ = getAdminConfig().pipe( rxjs.map(({ log }) => ({ params: log })), rxjs.map((formSpec) => createForm(formSpec, formTmpl({ renderLeaf }))), rxjs.mergeMap((promise) => rxjs.from(promise)), rxjs.map(($form) => [$form]), applyMutation($form, "replaceChildren"), rxjs.share(), ); effect(setup$); // feature2: form change effect(setup$.pipe( useForm$(() => qsa($form, "[name]")), rxjs.mergeMap((formState) => getAdminConfig().pipe( rxjs.first(), rxjs.map((formSpec) => { const fstate = Object.fromEntries(Object.entries(formState).map(([key, value]) => ([ key.replace(new RegExp("^params\."), "log."), value, ]))); return mutateForm(formSpec, fstate); }), formObjToJSON$(), )), rxjs.mergeMap((adminConfig) => getConfig().pipe( rxjs.first(), rxjs.map((publicConfig) => { adminConfig["connections"] = publicConfig["connections"]; return adminConfig; }), )), saveConfig(), rxjs.catchError((err) => { notification.error((err && err.message) || "Oops"); return rxjs.EMPTY; }), )); } ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_graph.css ================================================ .component_page_admin .component_stats { clear: both; height: 100px; z-index: 1; margin-bottom: 30px; } .component_page_admin .component_logviewer { z-index: 0; } .component_stats .chart { display: flex; align-items: flex-end; justify-content: end; height: 100%; } .component_stats .chart .bar { max-width: 45px; cursor: pointer; display: flex; flex: 1; border-top-left-radius: 3px; border-top-right-radius: 3px; background: #ebebec; border: 2px solid var(--border); } .component_stats .chart .bar[title="0"], .component_stats .chart .bar[title="1"] { border-top-left-radius: 0px; border-top-right-radius: 0px; } .component_stats .legend { display: flex; justify-content: space-between; color: var(--light); font-size: 0.8rem; } .component_stats .legend .title { font-style: italic; } .component_stats .legend.invisible { opacity: 0; } .component_stats .component_skeleton { margin-bottom: 5px; } ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_graph.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { generateSkeleton } from "../../components/skeleton.js"; import { loadCSS } from "../../helpers/loader.js"; import { get as getLogs } from "./model_log.js"; const NUMBER_BUCKETS = 30; const MIN_TIME_WIDTH = 5000; export default async function(render) { render(createElement(`<div>${generateSkeleton(3)}</div>`)); await loadCSS(import.meta.url, "./ctrl_activity_graph.css"); effect(getLogs(300).pipe( rxjs.first(), rxjs.repeat({ delay: 2500 }), rxjs.scan(({ start, end, width, max, init = true, buckets = Array(NUMBER_BUCKETS).fill(0) }, logfile) => { const times = []; let i = 0; while (i < logfile.length) { const t = new Date(logfile.substring(i, i + 19)).getTime(); if (!isNaN(t)) times.push(t); const end = logfile.indexOf("\n", i); i = end === -1 ? logfile.length : end + 1; } if (init === true) { start = times[0]; end = times[times.length - 1]; width = Math.max((end - start) / NUMBER_BUCKETS, MIN_TIME_WIDTH); for (let i=times.length-1; i>=0; i--) { let idx = Math.floor((times[i] - start) / width); if (idx === NUMBER_BUCKETS) idx -= 1; buckets[idx] += 1; } for (let i=buckets.length-1; i>=0; i--) { if (buckets[i] === 0) buckets[i] = -1; else break; } max = Math.max(1, ...buckets); init = false; } else { for (let i=times.length-1; i>=0; i--) { const current = times[i]; // start end current // | | | // |=============|<-- new --> if (current <= end) { break; } const idx = Math.floor((times[i] - start) / width); if (!buckets[idx]) buckets[idx] = 0; buckets[idx] += 1; } const shift = buckets.length - NUMBER_BUCKETS; for (let i=0; i<shift; i++) { buckets.shift(); start += width; } end = times[times.length - 1]; } return { start, end, width, buckets, max, init }; }, {}), rxjs.tap(({ buckets, start, end, max }) => { const $root = document.createDocumentFragment(); const $chart = createElement(`<div class="chart"></div>`); let display = true; for (let i = 0; i < buckets.length; i++) { if (buckets[i] < 0) { display = false; continue; } const $bar = createElement(`<div class="bar" title="${buckets[i]}"></div>`); const height = Math.sqrt(buckets[i]) / Math.sqrt(max) * 100; $bar.style.height = Math.min(height, 120) + "%"; $chart.appendChild($bar); } $root.appendChild($chart); $root.appendChild(createElement(` <div class="legend"> <span>${new Date(start).toLocaleTimeString()}</span> <span class="title">Log Events</span> <span>${new Date(end).toLocaleTimeString()}</span> </div> `)); if (display) render($root); }), rxjs.catchError((err) => rxjs.EMPTY), )); } ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_viewer.css ================================================ .component_logpage button{ width: inherit; float: right; margin-top: 5px; padding-left: 20px; padding-right: 20px; } ================================================ FILE: public/assets/pages/adminpage/ctrl_activity_viewer.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, stateMutation } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { CSS } from "../../helpers/loader.js"; import { get as getLogs, url as getLogUrl } from "./model_log.js"; export default async function(render) { const $page = createElement(` <div> <style>${await CSS(import.meta.url, "ctrl_activity_viewer.css")}</style> <pre style="height:350px; max-height: 350px">…</pre> <a href="${getLogUrl()}" download="${logname()}"> <button class="component_button primary">Download</button> </a> <br><br> </div> `); const $log = qs($page, "pre"); render($page); effect(rxjs.of(null).pipe( rxjs.mergeMap(() => $log.matches(":hover") ? rxjs.EMPTY : getLogs()), rxjs.map((logData) => logData + "\n\n\n\n\n"), stateMutation($log, "textContent"), rxjs.tap(() => { if ($log?.scrollTop !== 0) return; $log.scrollTop = $log.scrollHeight; }), rxjs.repeat({ delay: 2500 }), rxjs.catchError(() => rxjs.EMPTY), )); } function logname() { const t = new Date().toISOString().substring(0, 10).replace(/-/g, ""); return `access_${t}.log`; }; ================================================ FILE: public/assets/pages/adminpage/ctrl_login.css ================================================ .component_page_adminlogin { max-width: 300px; } ================================================ FILE: public/assets/pages/adminpage/ctrl_login.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, stateMutation, applyMutation, preventDefault } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { transition, zoomIn } from "../../lib/animate.js"; import { AjaxError } from "../../lib/error.js"; import ctrlError from "../ctrl_error.js"; import { CSS } from "../../helpers/loader.js"; import notification from "../../components/notification.js"; import "../../components/icon.js"; import { authenticate$ } from "./model_admin_session.js"; export default async function(render) { const $form = createElement(` <div class="component_container component_page_adminlogin"> <style>${await CSS(import.meta.url, "ctrl_login.css")}</style> <form> <div class="input_group"> <input type="password" name="password" placeholder="Password" class="component_input" autocomplete> <button class="transparent"> <component-icon name="arrow_right"></component-icon> </button> </div> </form> </div> `); // feature: nice transition render(transition($form, { timeEnter: 250, enter: zoomIn(1.2), timeLeave: 0 })); // feature: form interactions effect(rxjs.fromEvent(qs($form, "form"), "submit").pipe( preventDefault(), // STEP1: loading spinner rxjs.mapTo(["name", "loading"]), applyMutation(qs($form, "component-icon"), "setAttribute"), // STEP2: attempt to login rxjs.map(() => ({ password: qs($form, "[name=\"password\"]").value })), rxjs.switchMap((creds) => authenticate$(creds).pipe( rxjs.catchError((err) => { if (err instanceof AjaxError) { switch (err.code()) { case "INTERNAL_SERVER_ERROR": return rxjs.throwError(err); case "FORBIDDEN": return rxjs.of(false); } } notification.error(err && err.message); return rxjs.of(false); }), )), // STEP3: update the UI when authentication fails, happy path is handle at the middleware // level one layer above as the login ctrl has no idea what to show after login rxjs.filter((ok) => !ok), rxjs.mapTo(["name", "arrow_right"]), applyMutation(qs($form, "component-icon"), "setAttribute"), rxjs.mapTo(""), stateMutation(qs($form, "[name=\"password\"]"), "value"), rxjs.mapTo(["error"]), applyMutation(qs($form, ".input_group"), "classList", "add"), rxjs.delay(300), applyMutation(qs($form, ".input_group"), "classList", "remove"), rxjs.catchError(ctrlError(render)), )); // feature: autofocus effect(rxjs.of(null).pipe( applyMutation(qs($form, "input"), "focus") )); // feature: vertically center the form effect(rxjs.fromEvent(window, "resize").pipe( rxjs.startWith(null), rxjs.map(() => ["margin-top", `${Math.floor(window.innerHeight / 3)}px`]), applyMutation($form, "style", "setProperty") )); } ================================================ FILE: public/assets/pages/adminpage/ctrl_settings.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutation } from "../../lib/rx.js"; import { qs, qsa } from "../../lib/dom.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { formTmpl } from "../../components/form.js"; import { generateSkeleton } from "../../components/skeleton.js"; import { query as getConfig } from "../../model/config.js"; import ctrlError from "../ctrl_error.js"; import { get as getAdminConfig, save as saveConfig, initConfig } from "./model_config.js"; import { renderLeaf, useForm$, formObjToJSON$ } from "./helper_form.js"; import transition from "./animate.js"; import AdminHOC from "./decorator.js"; export default AdminHOC(async function(render) { const $container = createElement(` <div class="component_settingspage sticky"> <form data-bind="form" class="formbuilder"> <h2>…</h2> ${generateSkeleton(10)} </form> </div> `); render(transition($container)); await initConfig(); const config$ = getAdminConfig().pipe( rxjs.first(), reshapeConfigBeforeDisplay, ); const tmpl = formTmpl({ renderNode: ({ level, format, label }) => { if (level !== 0) return null; return createElement(` <div> <h2>${format(label)}</h2> <div data-bind="children"></div> </div> `); }, renderLeaf, autocomplete: false, }); // feature: setup the form const init$ = config$.pipe( rxjs.mergeMap((formSpec) => createForm(formSpec, tmpl)), rxjs.map(($form) => [$form]), applyMutation(qs($container, `[data-bind="form"]`), "replaceChildren"), rxjs.share(), ); effect(init$); // feature: handle form change effect(init$.pipe( useForm$(() => qsa($container, "[data-bind=\"form\"] [name]")), rxjs.debounceTime(250), rxjs.mergeMap((formState) => config$.pipe( rxjs.first(), rxjs.map((formSpec) => mutateForm(formSpec, formState)), )), reshapeConfigBeforeSave, saveConfig(), rxjs.catchError(ctrlError()), )); }); // the config contains stuff wich we don't want to show in this page such as: // - the middleware info which is set in the backend page // - the connections info which is set in the backend page // - the constant info which is for the setup page const reshapeConfigBeforeDisplay = rxjs.map((cfg) => { const { constant, middleware, connections, ...other } = cfg; return other; }); // before saving things back to the server, we want to hydrate the config and insert back: // - the middleware info // - the connections info const reshapeConfigBeforeSave = rxjs.pipe( rxjs.mergeMap((configWithMissingKeys) => getAdminConfig().pipe( rxjs.first(), rxjs.map((config) => { configWithMissingKeys["middleware"] = config["middleware"]; return configWithMissingKeys; }), formObjToJSON$(), )), rxjs.mergeMap((adminConfig) => getConfig().pipe( rxjs.first(), rxjs.map((publicConfig) => { adminConfig["connections"] = publicConfig["connections"]; return adminConfig; }), )), ); ================================================ FILE: public/assets/pages/adminpage/ctrl_setup.css ================================================ .component_setup { transform: none !important; } .component_setup h4 { user-select: none; text-align: center; font-size: 1.4em; font-weight: 500; padding: 20px 0 0px 0; } .component_setup h4 .component_icon { width: 1.3em; cursor: pointer; } .component_setup h4 .component_icon[alt="loading"] { opacity: 0; } .component_setup #step1 p { font-size: 1.05em; margin-bottom: 5px; } .component_setup #step1 form { max-width: 400px; } .component_setup #step2 .component_dependency_installed { margin: 10px 0; padding: 10px 10px; border-radius: 3px; color: rgba(0, 0, 0, 0.6); } .component_setup #step2 .component_dependency_installed.yes { background: var(--success); } .component_setup #step2 .component_dependency_installed.no { background: var(--primary); } .component_setup #step2 .component_dependency_installed.no.severe { background: var(--error); } .component_setup #step2 .component_dependency_installed strong { font-weight: bold; font-style: italic; display: block; } .component_setup .stepper-form-appear, .component_setup .stepper-form-enter { transition-delay: 0.3s; transform: scale(1.02); opacity: 0; transition: all 0.3s ease; } .component_setup .stepper-form-appear.stepper-form-appear-active, .component_setup .stepper-form-appear.stepper-form-enter-active, .component_setup .stepper-form-enter.stepper-form-appear-active, .component_setup .stepper-form-enter.stepper-form-enter-active { opacity: 1; transform: scale(1); } .component_setup .component_icon { width: 30px; } .pulse { animation: zoomPulse 1.2s ease-in-out infinite; transform-origin: center left; } @keyframes zoomPulse { 0%, 100% { transform: scale(1.03); } 50% { transform: scale(1.07); } } ================================================ FILE: public/assets/pages/adminpage/ctrl_setup.js ================================================ import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutation, preventDefault } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { ApplicationError } from "../../lib/error.js"; import { transition, animate, zoomIn, slideXOut, slideXIn } from "../../lib/animate.js"; import bcrypt from "../../lib/vendor/bcrypt.js"; import { CSS } from "../../helpers/loader.js"; import { createModal, MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import { query as getConfig } from "../../model/config.js"; import ctrlError from "../ctrl_error.js"; import { get as getAdminConfig, save as saveConfig } from "./model_config.js"; import WithShell from "./decorator_sidemenu.js"; import { cssHideMenu } from "./animate.js"; import { formObjToJSON$ } from "./helper_form.js"; import { getDeps } from "./model_setup.js"; import { authenticate$, isAdmin$ } from "./model_admin_session.js"; import "../../components/icon.js"; const stepper$ = new rxjs.BehaviorSubject( parseInt(new URLSearchParams(location.search).get("step")) || 1 ); export default setupHOC(async function(render) { const $page = createElement(` <div class="component_setup"> <div data-bind="multistep-form"></div> <style>${await CSS(import.meta.url, "ctrl_setup.css")}</style> </div> `); render($page); let pwd = ""; effect(stepper$.pipe( rxjs.map((step) => { if (step === 1) return WithShell(componentStep1, { setPassword: (p) => pwd = p }); else if (step === 2) return WithShell(componentStep2, { getPassword: () => pwd }); throw new ApplicationError("INTERNAL_ERROR", "Assumption failed"); }), rxjs.tap((ctrl) => ctrl(createRender(qs($page, "[data-bind=\"multistep-form\"]")))), rxjs.catchError(ctrlError(render)), )); }); function setupHOC(ctrlWrapped) { const ctrlGoAdmin = () => location.href = "/admin/"; return (render) => { effect(isAdmin$().pipe( rxjs.map((isAdmin) => isAdmin ? ctrlWrapped : ctrlGoAdmin), rxjs.tap((ctrl) => ctrl(render)), rxjs.catchError(ctrlError(render)), )); }; } function componentStep1(render, { setPassword }) { const $page = createElement(` <div id="step1"> <h4>Welcome Aboard!</h4> <div> <p>First thing first, setup your password: </p> <form> <div class="input_group"> <input type="password" name="password" placeholder="Password" class="component_input" autocomplete autofocus> <button class="transparent"> <component-icon name="arrow_right"></component-icon> </button> </div> </form> </div> <style>${cssHideMenu}</style> </div> `); render(transition($page, { timeEnter: 250, enter: zoomIn(1.2), timeLeave: 0 })); qs($page, "input").focus(); // feature: form handling effect(rxjs.fromEvent(qs($page, "form"), "submit").pipe( preventDefault(), rxjs.mapTo(["name", "loading"]), applyMutation(qs($page, "component-icon"), "setAttribute"), rxjs.map(() => qs($page, "input").value), rxjs.mergeMap((pwd) => getAdminConfig().pipe( rxjs.first(), rxjs.map((config) => { config["auth"]["admin"]["value"] = bcrypt.hashSync(pwd); return config; }), reshapeConfigBeforeSave, saveConfig(), rxjs.mergeMap(() => authenticate$({ password: pwd })), rxjs.tap(() => setPassword(pwd)), )), rxjs.tap(() => animate($page, { time: 200, keyframes: slideXOut(-30) })), rxjs.delay(200), rxjs.tap(() => stepper$.next(2)) )); } const reshapeConfigBeforeSave = rxjs.pipe( formObjToJSON$(), rxjs.mergeMap((config) => getConfig().pipe( rxjs.first(), rxjs.map((publicConfig) => { config["connections"] = publicConfig["connections"]; return config; }), )), ); function componentStep2(render, { getPassword }) { const $page = createElement(` <div id="step2"> <h4> <component-icon name="arrow_left" data-bind="previous"></component-icon> You're at the Helm now </h4> <div data-bind="dependencies"></div> <div data-bind="onboarding"></div> <style id="cssHideMenu">${cssHideMenu}</style> </div> `); render($page); // feature: show state of dependencies effect(getDeps({ getPassword }).pipe( rxjs.first(), rxjs.mergeMap((deps) => deps), rxjs.map(({ name_success, name_failure, pass, severe, message }) => ({ className: (severe ? "severe" : "") + " " + (pass ? "yes" : "no"), label: pass ? name_success : name_failure, $extraLabel: pass ? null : message, })), rxjs.mergeMap(({ label, className, $extraLabel }) => rxjs.of(createElement(` <div class="component_dependency_installed ${className}"> <strong>${label}</strong> </div> `)).pipe(rxjs.tap(($node) => $extraLabel && $node.appendChild($extraLabel)))), applyMutation(qs($page, "[data-bind=\"dependencies\"]"), "appendChild"), )); // feature: navigate previous step effect(rxjs.fromEvent(qs($page, "[data-bind=\"previous\"]"), "click").pipe( rxjs.tap(() => stepper$.next(1)) )); // feature: reveal animation effect(rxjs.of(null).pipe( rxjs.tap(() => animate(qs($page, "h4"), { time: 200, keyframes: slideXIn(30) })), rxjs.delay(200), rxjs.mapTo([]), applyMutation(qs($page, "style#cssHideMenu"), "remove") )); // feature: telemetry popup const componentTelemetryPopup = (render) => { const $modal = createElement(` <div> <p style="text-align: justify;"> Help making this software better by sending crash reports and anonymous usage statistics </p> <form style="font-size: 0.9em; margin-top: 10px; line-height: 1rem;"> <label> <div class="component_checkbox"> <input type="checkbox"> <span class="indicator"></span> </div> The data is never shared with a third party. </label> </form> </div> `); const ret = new rxjs.Subject(); const $checkbox = qs($modal, `[type="checkbox"]`); const close = render($modal, (id) => { if (id !== MODAL_RIGHT_BUTTON) { ret.next(false); ret.complete(); return ret.toPromise(); } ret.next($checkbox.checked); ret.complete(); return ret.toPromise(); }); $checkbox.oninput = (e) => { if (!e.target.checked) return; close(MODAL_RIGHT_BUTTON); }; return ret.toPromise(); }; // feature: telemetry modal onDestroy(() => requestAnimationFrame(() => getAdminConfig().pipe( reshapeConfigBeforeSave, rxjs.first(), rxjs.delay(300), rxjs.filter((config) => config["log"]["telemetry"] !== true), rxjs.mergeMap(async(config) => { const enabled = await componentTelemetryPopup(createModal({ withButtonsRight: "OK" })); if (enabled === false) return null; config["log"]["telemetry"] = enabled; return config; }), rxjs.mergeMap((config) => { if (config) return rxjs.of(config).pipe(saveConfig()); return rxjs.of(null); }), ).toPromise())); } ================================================ FILE: public/assets/pages/adminpage/ctrl_storage.css ================================================ .component_dashboard .box-container { display: flex; flex-wrap: wrap; margin-left: -3px; margin-right: -3px; } .component_dashboard .box-container .box-item { position: relative; width: 20%; } .component_dashboard .box-container .box-item strong { font-weight: 400; } @media (max-width: 1350px) { .component_dashboard .box-container .box-item { width: 25%; } } @media (max-width: 900px) { .component_dashboard .box-container .box-item { width: 33.33%; } } @media (max-width: 750px) { .component_dashboard .box-container .box-item { width: 50%; } } .component_dashboard .box-container .box-item > div { margin: 3px; padding: 30px 0; text-align: center; color: var(--light); text-shadow: 0px 0px 1px rgba(0, 0, 0, 0.5); font-size: 1.1em; text-transform: uppercase; border-radius: 2px; background: var(--border); border: 2px solid var(--border); } @media (max-width: 900px) { .component_dashboard .box-container .box-item > div { padding: 25px 0; } } @media (max-width: 750px) { .component_dashboard .box-container .box-item > div { padding: 20px 0; } } .component_dashboard .box-container .box-item > div > span { display: none; } .component_dashboard .box-container .box-item > div:hover > span { display: block; cursor: pointer; position: absolute; top: 0px; right: 0px; left: 0; bottom: 0; font-size: 2.3rem; font-family: monospace; line-height: 25px; color: var(--color); text-shadow: none; background: var(--emphasis-primary); padding: 18px 0; margin: 6px; opacity: 0.95; } @media (max-width: 900px) { .component_dashboard .box-container .box-item > div:hover > span { padding: 12px 0; } } @media (max-width: 750px) { .component_dashboard .box-container .box-item > div:hover > span { padding: 7px 0; } } .component_dashboard .box-container .box-item > div:hover > span .icon { background: var(--primary); border-radius: 50%; width: 40px; height: 40px; display: inline-block; line-height: 40px; opacity: 0.6; color: white; } .component_dashboard .box-container .box-item > div:hover > span .icon .component_icon { padding: 7px; width: 25px; height: 25px; } .component_dashboard .box-container .box-item.active > div { background: var(--primary); transition: background 0.1s; } .component_dashboard .box-container .box-item.pointer { cursor: pointer; } .component_dashboard .box-container .box-item.no-select { user-select: none; } .component_dashboard form fieldset { position: relative; } .component_dashboard form fieldset .icons { position: absolute; border-radius: 50%; padding: 10px; border: 3px solid var(--bg-color); background: var(--primary); cursor: pointer; right: -20px; top: -30px; } .component_dashboard form fieldset .icons .component_icon { height: 20px; } .component_dashboard form fieldset label > img { height: 200px; display: flex; margin: 5px auto 0 auto; } .component_dashboard .component_storagebackend form { width: calc(100% - 15px); } .component_dashboard [data-bind="authentication_middleware"] .component_checkbox { position: relative; top: 5px; } .component_dashboard [data-bind="idp"] > .formbuilder > fieldset > legend { display: none; } .component_dashboard [data-bind="attribute-mapping"] > .formbuilder > fieldset > legend { color: var(--color); font-weight: 600; font-size: 1.15rem; display: contents; text-transform: none; } .component_dashboard [data-bind="attribute-mapping"] > .formbuilder > fieldset > legend:before { content: " "; display: inline-block; height: 20px; width: 20px; background: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLWljb24iIHN0eWxlPSJmaWxsOiAjNTc1OTVBIiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTc2LjgxODM0NyA5ODEuMzMzMzMzYTE4OC4zMzA2NjcgMTg4LjMzMDY2NyAwIDAgMS01Ny42LTEyLjhBNTcuMzQ0IDU3LjM0NCAwIDAgMSAwLjAxODM0NyA5MjQuMjQ1MzMzYTU0LjYxMzMzMyA1NC42MTMzMzMgMCAwIDEgMTkuMi0zOC4wNTg2NjYgOTEuNTIgOTEuNTIgMCAwIDEgMzguNC0xMi44aDMyYzE5LjIgMCAzOC40IDYuMzU3MzMzIDY0IDYuMzU3MzMzYTY5LjQxODY2NyA2OS40MTg2NjcgMCAwIDAgNTEuMi0xMi44IDI5Ny40NzIgMjk3LjQ3MiAwIDAgMCAzOC40LTk1LjE0NjY2N2w4OS42LTM2MS41MTQ2NjZIMjI0LjAxODM0N2wxOS4yLTY5Ljc2aDExNS4ydi0xMi44YTQyOC45NzA2NjcgNDI4Ljk3MDY2NyAwIDAgMSA1MS4yLTE1Mi4yMzQ2NjcgMjQzLjIgMjQzLjIgMCAwIDEgMTAyLjQtOTUuMTQ2NjY3IDM4OC42MDggMzg4LjYwOCAwIDAgMSAxMjEuNi0zOC4wNTg2NjYgMTIxLjQyOTMzMyAxMjEuNDI5MzMzIDAgMCAxIDUxLjIgMTIuOCA1Ny4zNDQgNTcuMzQ0IDAgMCAxIDE5LjIgNDQuNDE2IDg5LjYgODkuNiAwIDAgMS0xMi44IDM4LjA1ODY2NiA5MS41MiA5MS41MiAwIDAgMS0zOC40IDEyLjggMjI5LjIwNTMzMyAyMjkuMjA1MzMzIDAgMCAxLTY0LTEyLjhjLTEyLjggMC0xOS4yLTYuMzU3MzMzLTMyLTYuMzU3MzMzYTY2LjI2MTMzMyA2Ni4yNjEzMzMgMCAwIDAtNDQuOCAzMS43MDEzMzMgNTU0LjY2NjY2NyA1NTQuNjY2NjY3IDAgMCAwLTMyIDk1LjE0NjY2N2wtMjUuNiA4Mi40MzIgMjc1LjItNi4zNTczMzNoNi40YTU1LjQ2NjY2NyA1NS40NjY2NjcgMCAwIDEgMzguNCAxOS4wMjkzMzMgNDM0LjUxNzMzMyA0MzQuNTE3MzMzIDAgMCAxIDE5LjIgNTAuNzMwNjY3bDYuNCAyNS4zODY2NjYgMTkuMi0yNS4zODY2NjZhMzAyLjAzNzMzMyAzMDIuMDM3MzMzIDAgMCAxIDY0LTYzLjQwMjY2NyAxOTkuMTY4IDE5OS4xNjggMCAwIDEgODMuMi0zOC4wNTg2NjcgOTEuNTIgOTEuNTIgMCAwIDEgMzguNCAxMi44IDQyLjExMiA0Mi4xMTIgMCAwIDEgMCA2My40MDI2NjdsLTYuNCA2LjM1NzMzM2MtNi40IDYuMzU3MzMzLTE5LjIgMTIuOC01Ny42IDEyLjhhNzMuMDQ1MzMzIDczLjA0NTMzMyAwIDAgMC01MS4yIDE5LjAyOTMzNEE0MzcuNzE3MzMzIDQzNy43MTczMzMgMCAwIDAgODE5LjIxODM0NyA0OTkuMnYxMi44bDI1LjYgMTMzLjIwNTMzM2MxMi44IDUwLjczMDY2NyA0NC44IDEwMS40NjEzMzMgNjQgMTAxLjQ2MTMzNHMxOS4yLTEyLjggMjUuNi0xOS4wMjkzMzR2LTYuMzU3MzMzYTY3LjQ5ODY2NyA2Ny40OTg2NjcgMCAwIDEgNDQuOC0zOC4wNTg2NjdjMTIuOCAwIDE5LjIgNi4zNTczMzMgMzIgMTIuOGE0OC4yMTMzMzMgNDguMjEzMzMzIDAgMCAxIDEyLjggMzEuNzAxMzM0IDY2LjQ3NDY2NyA2Ni40NzQ2NjcgMCAwIDEtMzIgNTcuMDg4IDk2Ljc2OCA5Ni43NjggMCAwIDEtNzAuNCAyNS4zODY2NjYgMTU3Ljg2NjY2NyAxNTcuODY2NjY3IDAgMCAxLTEwMi40LTM4LjA1ODY2NiAzNTYuNzM2IDM1Ni43MzYgMCAwIDEtNzAuNC0xMjYuODQ4bC02LjQtMjUuMzg2NjY3LTE5LjIgMTkuMDI5MzMzQTY0MS41Nzg2NjcgNjQxLjU3ODY2NyAwIDAgMSA2NDAuMDE4MzQ3IDc0MC4zMDkzMzNhOTYuNzY4IDk2Ljc2OCAwIDAgMS03MC40IDI1LjM4NjY2NyA1OC4zNjggNTguMzY4IDAgMCAxLTQ0LjgtMTkuMDI5MzMzIDU0LjYxMzMzMyA1NC42MTMzMzMgMCAwIDEtMTkuMi0zOC4wNTg2NjcgNDYuMzM2IDQ2LjMzNiAwIDAgMSAxMi44LTM4LjA1ODY2N2M2LjQtMTIuOCAxOS4yLTEyLjggMzItMTIuOGE0NS43Mzg2NjcgNDUuNzM4NjY3IDAgMCAxIDM4LjQgMTkuMDI5MzM0bDYuNCAxMi44IDEyLjgtNi4zNTczMzQgMTkuMi0xOS4wMjkzMzNhMzE4LjU0OTMzMyAzMTguNTQ5MzMzIDAgMCAwIDQ0LjgtNTAuNzMwNjY3bDU3LjYtODIuNDMyLTI1LjYtMTIwLjQ5MDY2Ni0yNTYtNi4zNTczMzQtNzAuNCAyODUuMzk3MzM0YTUzMC41MTczMzMgNTMwLjUxNzMzMyAwIDAgMS0xMDguOCAyMTUuNjM3MzMzQTMxNy43Mzg2NjcgMzE3LjczODY2NyAwIDAgMSA3Ni44MTgzNDcgOTgxLjMzMzMzM3oiICAvPjwvc3ZnPgo=); background-repeat: no-repeat; margin-right: 10px; position: relative; top: 3px; } .component_dashboard [data-bind="attribute-mapping"] > .formbuilder > fieldset { padding: 15px; } .component_dashboard [data-bind="attribute-mapping"] > .formbuilder > fieldset > legend:after { content: " "; display: block; border-bottom: 2px solid rgba(0, 0, 0, 0.1); margin: 10px 0 15px 0; } /* .component_dashboard [data-bind="authentication_middleware"] form fieldset legend { font-weight: bold; } .component_dashboard [data-bind="authentication_middleware"] form fieldset fieldset legend { background: var(--border); color: var(--light); font-weight: normal; border-radius: 5px; border: 2px solid var(--border); font-size: 1.05rem; } */ ================================================ FILE: public/assets/pages/adminpage/ctrl_storage.js ================================================ import { createElement, createRender } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { CSS } from "../../helpers/loader.js"; import transition from "./animate.js"; import AdminHOC from "./decorator.js"; import { initConfig } from "./model_config.js"; import { initStorage, initMiddleware } from "./ctrl_storage_state.js"; import componentBanner from "./ctrl_storage_component_banner.js"; import componentBackend from "./ctrl_storage_component_backend.js"; import componentAuthenticationMiddleware from "./ctrl_storage_component_authentication.js"; export default AdminHOC(async function(render) { const $page = createElement(` <div class="component_dashboard sticky"> <style>${await CSS(import.meta.url, "ctrl_storage.css")}</style> <div data-bind="banner"></div> <div data-bind="backend"></div> <div data-bind="authentication_middleware"></div> </div> `); await initConfig(); await initStorage(); await initMiddleware(); render(transition($page)); componentBanner(createRender(qs($page, "[data-bind=\"banner\"]"))); componentBackend(createRender(qs($page, "[data-bind=\"backend\"]"))); componentAuthenticationMiddleware(createRender(qs($page, "[data-bind=\"authentication_middleware\"]"))); // feature: request to reload page effect(rxjs.fromEvent(new BroadcastChannel("admin"), "message").pipe( rxjs.filter(({ data }) => data === "reload"), rxjs.mergeMap(() => rxjs.fromEvent(document, "visibilitychange")), rxjs.filter(() => document.visibilityState === "visible"), rxjs.tap(() => location.reload()), )); }); ================================================ FILE: public/assets/pages/adminpage/ctrl_storage_component_authentication.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutation, applyMutations, onClick } from "../../lib/rx.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { qs, qsa } from "../../lib/dom.js"; import { ApplicationError } from "../../lib/error.js"; import { formTmpl } from "../../components/form.js"; import { generateSkeleton } from "../../components/skeleton.js"; import ctrlError from "../ctrl_error.js"; import { getState, getMiddlewareAvailable, getMiddlewareEnabled, toggleMiddleware, getBackendAvailable, getBackendEnabled, } from "./ctrl_storage_state.js"; import { renderLeaf } from "./helper_form.js"; import { get as getAdminConfig, save as saveConfig } from "./model_config.js"; import "./component_box-item.js"; export default async function(render) { const $page = createElement(` <div> <h2 class="hidden">Authentication Middleware</h2> <div class="box-container"> ${generateSkeleton(5)} </div> <div style="min-height: 300px"> <form data-bind="idp"></form> <form data-bind="attribute-mapping"></div> </div> </div> `); render($page); // feature: setup the buttons const init$ = getMiddlewareAvailable().pipe( rxjs.first(), rxjs.map((specs) => Object.keys(specs).map((label) => createElement(` <box-item data-label="${label}"></box-item> `))), rxjs.tap(() => { qs($page, "h2").classList.remove("hidden"); qs($page, ".box-container").innerHTML = ""; }), applyMutations(qs($page, ".box-container"), "appendChild"), rxjs.share(), ); effect(init$); // feature: state of buttons effect(init$.pipe( rxjs.concatMap(() => getMiddlewareEnabled()), rxjs.filter((backend) => !!backend), rxjs.tap((backend) => qsa($page, "box-item").forEach(($button) => { $button.getAttribute("data-label") === backend ? $button.classList.add("active") : $button.classList.remove("active"); })), )); // feature: click to select a middleware effect(init$.pipe( rxjs.mergeMap(($nodes) => $nodes), rxjs.mergeMap(($node) => onClick($node)), rxjs.mergeMap(($node) => toggleMiddleware($node.getAttribute("data-label"))), saveMiddleware(), )); // feature: setup forms - we insert everything in the DOM so we don't lose // transient state when clicking around const setupIDPForm$ = getMiddlewareAvailable().pipe( rxjs.mergeMap((availableSpecs) => getAdminConfig().pipe( rxjs.first(), rxjs.map((cfg) => ({ type: cfg?.middleware?.identity_provider?.type?.value, params: JSON.parse(cfg?.middleware?.identity_provider?.params?.value || "{}"), })), rxjs.catchError(() => rxjs.of({})), rxjs.map((idpState) => [availableSpecs, idpState]), )), rxjs.concatMap(async([ availableSpecs, idpState = { type: null, params: null }, ]) => { const { type, params } = idpState; const idps = []; for (const key in availableSpecs) { let idpSpec = availableSpecs[key]; delete idpSpec.type; if (key === type) idpSpec = mutateForm(idpSpec, params || {}); const $idp = await createForm({ [key]: idpSpec }, formTmpl({ renderLeaf, autocomplete: false, })); $idp.classList.add("hidden"); $idp.setAttribute("id", key); if (Object.keys(idpSpec).length === 0) $idp.style.display = "none"; idps.push($idp); } return idps; }), applyMutations(qs($page, `[data-bind="idp"]`), "appendChild"), rxjs.share(), ); effect(setupIDPForm$); // feature: handle visibility of the identity_provider form to match the selected midleware effect(setupIDPForm$.pipe( rxjs.concatMap(() => getMiddlewareEnabled()), rxjs.tap((currentMiddleware) => { qsa($page, `[data-bind="idp"] .formbuilder`).forEach(($node) => { $node.getAttribute("id") === currentMiddleware ? $node.classList.remove("hidden") : $node.classList.add("hidden"); }); const $attrMap = qs($page, `[data-bind="attribute-mapping"]`); currentMiddleware ? $attrMap.classList.remove("hidden") : $attrMap.classList.add("hidden"); qsa($page, ".box-item").forEach(($button) => { const $icon = qs($button, ".icon"); $icon.style.transition = "transform 0.2s ease"; if (qs($button, "strong").textContent === currentMiddleware) { $button.classList.add("active"); $icon.style.transform = "rotate(45deg)"; } else { $button.classList.remove("active"); $icon.style.transform = ""; } }); }), )); // feature: setup the attribute mapping form const setupAMForm$ = init$.pipe( rxjs.mapTo({ attribute_mapping: { related_backend: { type: "text", datalist: [], multi: true, autocomplete: false, value: "", }, // dynamic form here is generated reactively from the value of the "related_backend" field } }), // related_backend value rxjs.mergeMap((spec) => getAdminConfig().pipe( rxjs.first(), rxjs.map((cfg) => { spec.attribute_mapping.related_backend.value = cfg?.middleware?.attribute_mapping?.related_backend?.value; return spec; }), )), rxjs.concatMap(async(specs) => await createForm(specs, formTmpl({}))), applyMutation(qs($page, `[data-bind="attribute-mapping"]`), "replaceChildren"), rxjs.share(), ); effect(setupAMForm$); // feature: setup autocompletion of related backend field effect(setupAMForm$.pipe( rxjs.switchMap(() => rxjs.merge( getBackendEnabled(), rxjs.fromEvent(qs(document.body, `[data-bind="backend-enabled"]`), "input").pipe( rxjs.debounceTime(500), rxjs.mergeMap(() => getState().pipe(rxjs.map(({ connections }) => connections))), ), )), rxjs.map((connections) => connections.map(({ label }) => label)), rxjs.tap((datalist) => { const $input = $page.querySelector(`[name="attribute_mapping.related_backend"]`); if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing related backend"); $input.setAttribute("datalist", datalist.join(",")); // @ts-ignore $input.refresh(); }), )); // feature: related backend values triggers creation/deletion of related backends effect(setupAMForm$.pipe( rxjs.switchMap(() => rxjs.merge( // case 1: user is typing in the related backend field rxjs.fromEvent(qs($page, `[name="attribute_mapping.related_backend"]`), "input").pipe( rxjs.map((e) => e.target.value), ), // case 2: user is adding / removing a storage backend getBackendEnabled().pipe( rxjs.map(() => qs($page, `[name=\"attribute_mapping.related_backend"]`).value) ), // case 3: user is changing the storage backend label rxjs.fromEvent(qs(document.body, `[data-bind="backend-enabled"]`), "input").pipe( rxjs.map(() => qs($page, `[name="attribute_mapping.related_backend"]`).value), ), )), rxjs.map((value) => value.split(",").map((val) => (val || "").trim()).filter((t) => !!t)), rxjs.mergeMap((inputBackends) => getState().pipe( rxjs.map(({ connections }) => connections), rxjs.first(), rxjs.map((enabledBackends) => inputBackends .map((label) => enabledBackends.find((b) => b.label === label)) .filter((label) => !!label)), )), rxjs.mergeMap((backends) => getBackendAvailable().pipe(rxjs.first(), rxjs.map((specs) => { // we don't want to show the "normal" form but a flat version of it // so we're getting rid of anything that could make some magic happen like toggle and // ids which enable those interactions for (const key in specs) { for (const input in specs[key]) { if (specs[key][input]["type"] === "enable") { delete specs[key][input]; } else if ("id" in specs[key][input]) { delete specs[key][input]["id"]; } } } return [backends, specs]; }))), rxjs.map(([backends, formSpec]) => { const spec = {}; backends.forEach(({ label, type }) => { if (formSpec[type]) spec[label] = JSON.parse(JSON.stringify(formSpec[type])); }); return spec; }), rxjs.mergeMap((spec) => getAdminConfig().pipe( rxjs.first(), rxjs.map((cfg) => JSON.parse(cfg?.middleware?.attribute_mapping?.params?.value || "{}")), rxjs.catchError(() => rxjs.of({})), rxjs.map((cfg) => { // transform the form state from legacy format (= an object struct which was replicating the spec object) // to the new format which leverage the dom (= or the input name attribute to be precise) to store the entire schema const state = {}; for (const key1 in cfg) { for (const key2 in cfg[key1]) { state[`${key1}.${key2}`] = cfg[key1][key2]; } } return [spec, state]; }), )), rxjs.map(([formSpec, formState]) => mutateForm(formSpec, formState)), rxjs.mergeMap(async(formSpec) => await createForm(formSpec, formTmpl({ renderLeaf: () => createElement("<label></label>"), }))), rxjs.tap(($node) => { /** @type { Element | undefined} */ let $relatedBackendField; qsa($page, `[data-bind="attribute-mapping"] fieldset`).forEach(($el, i) => { if (i === 0) $relatedBackendField = $el; else $el.remove(); }); $relatedBackendField?.appendChild($node); }), )); // feature: form input change handler effect(setupAMForm$.pipe( rxjs.mapTo(new Date()), rxjs.switchMap((d) => rxjs.fromEvent($page, "input").pipe( rxjs.filter(() => new Date() - d > 200), // to prevent password manager from auto triggerring events ... )), rxjs.mergeMap(() => getMiddlewareEnabled().pipe(rxjs.first())), saveMiddleware(), )); } const saveMiddleware = () => rxjs.pipe( rxjs.mergeMap(() => getState()), saveConfig(), rxjs.catchError(ctrlError()), ); ================================================ FILE: public/assets/pages/adminpage/ctrl_storage_component_backend.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutations, applyMutation, onClick } from "../../lib/rx.js"; import { createForm } from "../../lib/form.js"; import { qs, qsa } from "../../lib/dom.js"; import { formTmpl } from "../../components/form.js"; import { generateSkeleton } from "../../components/skeleton.js"; import ctrlError from "../ctrl_error.js"; import { getState, getBackendAvailable, getBackendEnabled, addBackendEnabled, removeBackendEnabled } from "./ctrl_storage_state.js"; import { save as saveConfig } from "./model_config.js"; import "./component_box-item.js"; export default async function(render) { const $page = createElement(` <div class="component_storagebackend"> <h2>Storage Backend</h2> <div class="box-container" data-bind="backend-available"> ${generateSkeleton(10)} </div> <form data-bind="backend-enabled"></form> </div> `); render($page); const $available = qs($page, `[data-bind="backend-available"]`); const $enabled = qs($page, `[data-bind="backend-enabled"]`); // feature: setup the buttons const init$ = getBackendAvailable().pipe( rxjs.tap(() => $available.innerHTML = ""), rxjs.mergeMap((specs) => Promise.all(Object.keys(specs).map((label) => createElement(` <box-item data-label="${label}"></box-item> `)))), applyMutations($available, "appendChild"), rxjs.share(), ); effect(init$); // feature: state of buttons effect(init$.pipe( rxjs.mergeMap(() => getBackendEnabled()), rxjs.map((enabled) => { const enabledSet = new Set(); enabled.forEach(({ type }) => { enabledSet.add(type); }); return enabledSet; }), rxjs.tap((backends) => qsa($page, "box-item").forEach(($button) => { backends.has($button.getAttribute("data-label")) ? $button.classList.add("active") : $button.classList.remove("active"); })), )); // feature: click to select a backend effect(init$.pipe( rxjs.mergeMap(($nodes) => $nodes), rxjs.mergeMap(($node) => onClick($node)), rxjs.mergeMap(($node) => addBackendEnabled($node.getAttribute("data-label"))), saveConnections(), )); // feature: setup form const setupForm$ = getBackendEnabled().pipe( // initialise the forms rxjs.mergeMap((enabled) => Promise.all(enabled.map(({ type, label }) => createForm({ [type]: { "": { type: "text", placeholder: "Label", value: label }, } }, formTmpl({ renderLeaf: () => createElement("<label></label>"), renderNode: ({ label, format }) => { const $fieldset = createElement(` <fieldset> <legend class="no-select"> ${format(label)} </legend> <div data-bind="children"></div> </fieldset> `); const $remove = createElement(` <div class="icons no-select"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close"> </div> `); $fieldset.appendChild($remove); return $fieldset; }, }))))), rxjs.tap(() => $enabled.innerHTML = ""), rxjs.mergeMap((nodeList) => { if (nodeList.length === 0) return rxjs.of(createElement(` <div class="alert"> You need to select at least 1 storage backend </div> `)).pipe( applyMutation($enabled, "appendChild"), rxjs.mergeMap(() => rxjs.EMPTY), ); return rxjs.of(nodeList).pipe( applyMutations($enabled, "appendChild"), ); }), rxjs.share(), ); effect(setupForm$); // feature: remove an existing backend effect(setupForm$.pipe( rxjs.mergeMap(($nodes) => $nodes), rxjs.mergeMap(($node) => onClick($node.querySelector(".icons"))), rxjs.map(($node) => qs($node.parentElement, "input").value), rxjs.mergeMap((label) => removeBackendEnabled(label)), saveConnections(), )); // feature: form input change handler effect(setupForm$.pipe( rxjs.mergeMap((forms) => forms), rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")), saveConnections(), )); } const saveConnections = () => rxjs.pipe( rxjs.mergeMap((connections) => getState().pipe(rxjs.map((config) => { if (Array.isArray(connections)) config.connections = connections; return config; }))), saveConfig(), rxjs.catchError(ctrlError()), ); ================================================ FILE: public/assets/pages/adminpage/ctrl_storage_component_banner.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { settingsGet, settingsSave } from "../../lib/store.js"; import { getBackendEnabled } from "./ctrl_storage_state.js"; export default async function(render) { const { storage_banner } = settingsGet({ storage_banner: true }, "admin"); if (!storage_banner) return; const connections = await getBackendEnabled().pipe(rxjs.first()).toPromise(); if (connections.length > 0) return render(withClose(createElement(` <div id="storage-banner"> <span class="no-select pointer" id="close">X</span> Want to apply configuration preset? Check out the <a class="wavy" href="admin/setup?step=2">configuration wizard</a> <style>${CSS}</style> </div> `))); render(withClose(createElement(` <div id="storage-banner"> <span class="no-select pointer" id="close">X</span> First time here? You have 2 options for the initial setup:<br> 1. Pick a storage below and connect directly with your storage credentials<br> 2. OR Connect your storage with a separate authentication method. Use the <a class="wavy" href="admin/setup?step=2">configuration wizard</a> presets to get started quickly <style>${CSS}</style> </div> `))); } function withClose($el) { qs($el, "#close").onclick = () => { settingsSave({ storage_banner: false }, "admin"); $el.remove(); }; return $el; } const CSS = ` #storage-banner { background: var(--surface); margin: 20px 0 0 0; padding: 15px 20px; border-radius: 3px; color: rgba(255, 255, 255, 0.8); text-align: justify; } #storage-banner a { color: inherit; } #storage-banner #close { font-family: monospace; cursor: pointer; text-shadow: 0 0 black; float: right; padding: 5px; margin-right: -5px; }`; ================================================ FILE: public/assets/pages/adminpage/ctrl_storage_state.js ================================================ import rxjs from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { ApplicationError } from "../../lib/error.js"; import { query as getConfig } from "../../model/config.js"; import { get as getAdminConfig } from "./model_config.js"; import { formObjToJSON$ } from "./helper_form.js"; const backendsEnabled$ = new rxjs.BehaviorSubject(); export async function initStorage() { return await getConfig().pipe( rxjs.map(({ connections }) => connections), rxjs.tap((connections) => { if (backendsEnabled$.value !== undefined) return; backendsEnabled$.next(Array.isArray(connections) ? connections : []); }), ).toPromise(); } export { getBackends as getBackendAvailable } from "./model_backend.js"; export function getBackendEnabled() { return backendsEnabled$.asObservable(); } export function addBackendEnabled(type) { return getState().pipe(rxjs.map(({ connections }) => { let label = type; let i = 0; while (true) { if (!connections.find((obj) => obj.label === label)) { break; } i += 1; label = `${type} ${i}`; } const newConnections = connections.concat({ type, label }); backendsEnabled$.next(newConnections); return newConnections; })); } export function removeBackendEnabled(labelToRemove) { return getState().pipe(rxjs.map(({ connections }) => { const newConnections = connections.filter(({ label }) => label !== labelToRemove); backendsEnabled$.next(newConnections); return newConnections; })); } const middlewareEnabled$ = new rxjs.ReplaySubject(1); export async function initMiddleware() { return await getAdminConfig().pipe( rxjs.map(({ middleware }) => middleware), formObjToJSON$(), rxjs.tap(({ identity_provider }) => middlewareEnabled$.next(identity_provider.type)), rxjs.first(), ).toPromise(); } export { getAuthMiddleware as getMiddlewareAvailable } from "./model_auth_middleware.js"; export function getMiddlewareEnabled() { return middlewareEnabled$.asObservable(); } export function toggleMiddleware(type) { return middlewareEnabled$.pipe( rxjs.first(), rxjs.map((oldValue) => { const newValue = oldValue === type ? null : type; middlewareEnabled$.next(newValue); return newValue; }), ); } export function getState() { return getAdminConfig().pipe( rxjs.first(), formObjToJSON$(), rxjs.map((config) => { // connections const connections = []; const formData = new FormData(qs(document.body, `[data-bind="backend-enabled"]`)); for (const [type, label] of formData.entries()) { connections.push({ type, label }); } config.connections = connections; return config; }), rxjs.map((config) => { // middleware const authType = document .querySelector(`[data-bind="authentication_middleware"] box-item.active`) ?.getAttribute("data-label"); config.middleware = { identity_provider: { type: null }, attribute_mapping: null, }; if (!authType) return config; const $formIDP = document.querySelector(`[data-bind="idp"]`); if (!($formIDP instanceof HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: idp isn't a form"); let formValues = [...new FormData($formIDP)]; config.middleware.identity_provider = { type: authType, params: JSON.stringify( formValues .filter(([key]) => key.startsWith(`${authType}.`)) // remove elements that aren't in scope .map(([key, value]) => [key.replace(new RegExp(`^${authType}\.`), ""), value]) // format the relevant keys .reduce((acc, [key, value]) => { // transform onto something ready to be saved if (key === "type") return acc; else if (key === "banner") return acc; else if (typeof key !== "string") return acc; return { ...acc, [key]: value, }; }, {}), ), }; const $formAM = document.querySelector(`[data-bind="attribute-mapping"]`); if (!($formAM instanceof HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: attribute mapping isn't a form"); formValues = [...new FormData($formAM)]; config.middleware.attribute_mapping = { related_backend: (formValues.shift() || [])[1], params: JSON.stringify(formValues.reduce((acc, [key, value]) => { const k = key.split("."); if (k.length !== 2) return acc; if (!acc[`${k[0]}`]) acc[`${k[0]}`] = {}; if (value !== "") acc[`${k[0]}`][`${k[1]}`] = value; return acc; }, {})), }; return config; }), ); } ================================================ FILE: public/assets/pages/adminpage/ctrl_workflow.css ================================================ .component_page_workflow .pull-right { float: right; } .component_page_workflow button { font-size: 0.9rem; padding: 3px 5px; margin: 0 2px; } .component_page_workflow h2 input { padding: 0 0 0 20px; border: none; font-family: inherit; color: inherit; font-size: inherit; width: 100%; position: relative; bottom: 2px; } .component_page_workflow .box { display: block; background: white; border: 2px solid #ebebec; border-style: solid; border-radius: 5px; margin: 15px 0 0 0; padding: 15px; overflow: visible; } .component_page_workflow .box.disabled { background: var(--border); } .component_page_workflow .box h3 { font-weight: 600; margin: 0; } .component_page_workflow .box h3 span { font-weight: 300; font-size: 0.8rem; font-style: italic; color: var(--light); } .component_page_workflow .box svg { float: left; width: 25px; fill: var(--light); opacity: 0.8; } .component_page_workflow .box .workflow-summary button { color: var(--light); } .component_page_workflow hr { border: none; height: 30px; margin: 0 0 -15px 0; position: relative; } .component_page_workflow hr:after { content: " "; position: absolute; left: 26px; top: 0; bottom: 0; border-right: 4px dashed rgba(0, 0, 0, 0.2); border-right-style: dotted; } .component_page_workflow .box [data-bind="form"] { overflow: hidden; } .component_page_workflow .box [data-bind="form"] .formbuilder { padding: 10px 10px 0px 10px; border-radius: 5px; margin-top: 10px; border-top: 2px solid #ebebec; background: transparent; } .component_page_workflow .box.disabled [data-bind="form"] .formbuilder { border-color: var(--border); } .component_page_workflow .box [data-bind="form"] .formbuilder:empty { display: none; } .component_page_workflow .box h3 button.pull-right { padding: 0; margin: 0; } /* details page buttons */ .component_page_workflow .box button[alt="delete"] { position: absolute; top: -13px; right: -13px; } .component_page_workflow .box button[alt="delete"] svg { width: 10px; height: 10px; border: 2px solid var(--border); background: #ebebec; fill: var(--light); padding: 5px; border-radius: 15px; } /* list page */ .component_page_workflow h3.empty { padding: 30px; background: white; border: 2px dashed var(--color); text-shadow: 0px 0px 1px rgba(0, 0, 0, 0.5); border-radius: 5px; font-weight: 600; color: var(--color); text-transform: uppercase; font-size: 1.05rem; letter-spacing: -0.5px; opacity: 0.4; cursor: pointer; } /* Workflow creation modal */ .component_workflow_create { font-size: 1rem; } .component_workflow_create h2 { margin: 0 0 5px 0; font-size: 1.1rem; color: var(--light); } .component_workflow_create form { margin-bottom: 15px; } .component_workflow_create form input { font-size: 1rem; border: 2px solid var(--border); border-radius: 5px; padding: 5px 10px; color: rgba(0,0,0,0.75); width: 100%; display: block; box-sizing: border-box; margin-bottom: 5px; } .component_workflow_create form input::placeholder { color: rgba(0,0,0,0.4); } .component_workflow_create [data-bind="list"] { overflow: hidden; } .component_workflow_create [data-bind="list"] .item { color: var(--color); background: #f2f2f2; transition: all 0.1s ease; padding: 5px 5px 5px 12px; border-radius: 5px; cursor: pointer; letter-spacing: -0.5px; margin-top: 2px; align-items: center; } .component_workflow_create [data-bind="list"] .item svg { height: 20px; margin-right: 5px; fill: var(--light); } /* save button */ .workflow-fab { position: fixed; bottom: 20px; right: 20px; background: var(--emphasis); border-radius: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); cursor: pointer; } .workflow-fab select { background: var(--emphasis); color: var(--bg-color); font-family: monospace; border: none; padding-left: 10px; padding-right: 5px; text-align: right; border-top-left-radius: 2px; border-bottom-left-radius: 10px; font-weight: 600; } .workflow-fab button > * { height: 30px; fill: var(--bg-color); padding: 7px; } .workflow-fab button > component-icon img { filter: brightness(0) invert(1); } /* ADD BUTTON */ .component_page_workflow [data-bind="add"] { display: inline-block; } .component_page_workflow [data-bind="add"] .box { align-items: center; background: var(--emphasis); display: flex; padding: 4px 0px 2px 0px; position: relative; left: 5px; overflow-x: scroll; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); border-width: 0; } .component_page_workflow [data-bind="add"] .box button { color: var(--bg-color); font-family: monospace; text-transform: uppercase; background:var(--border); font-size: 0.85rem; letter-spacing: -0.5px; margin-right: 5px; } .component_page_workflow [data-bind="add"] .box svg { margin-right: 0; fill: var(--bg-color); transition: transform 0.2s ease; } .component_page_workflow [data-bind="add"] .box > .item { position: sticky; left: 0; z-index: 1; background: transparent; backdrop-filter: blur(2px); padding: 3px 8px; } .component_page_workflow [data-bind="add"] .box > .flex { padding: 0 5px 0 0; margin-left: -5px; } ================================================ FILE: public/assets/pages/adminpage/ctrl_workflow.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import { loadCSS } from "../../helpers/loader.js"; import AdminHOC from "./decorator.js"; import { workflowAll } from "./model_workflow.js"; import ctrlList from "./ctrl_workflow_list.js"; import ctrlDetails from "./ctrl_workflow_details.js"; export default AdminHOC(async function(render) { await loadCSS(import.meta.url, "./ctrl_workflow.css"); render(createElement("<component-loader inlined></component-loader>")); const { workflows, triggers, actions } = await workflowAll().toPromise(); const specs = getSpecs(); if (specs) ctrlDetails(render, { workflow: specs, triggers, actions }); else ctrlList(render, { workflows, triggers, actions }); }); function getSpecs() { const GET = new URLSearchParams(location.search); try { return JSON.parse(atob(GET.get("specs"))); } catch (err) { return null; } } ================================================ FILE: public/assets/pages/adminpage/ctrl_workflow_details.js ================================================ import { createElement, createFragment, navigate } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import { animate, slideXIn, slideXOut } from "../../lib/animate.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { formTmpl } from "../../components/form.js"; import { workflowUpsert, workflowDelete, workflowGet } from "./model_workflow.js"; import transition from "./animate.js"; export default async function(render, { workflow, triggers, actions }) { const { id } = workflow; const $page = createElement(` <div class="component_page_workflow"> <h2 class="ellipsis flex"> <a href="./admin/workflow" data-link> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="width:20px;fill:var(--color);"> <path d="M169.4 297.4C156.9 309.9 156.9 330.2 169.4 342.7L361.4 534.7C373.9 547.2 394.2 547.2 406.7 534.7C419.2 522.2 419.2 501.9 406.7 489.4L237.3 320L406.6 150.6C419.1 138.1 419.1 117.8 406.6 105.3C394.1 92.8 373.8 92.8 361.3 105.3L169.3 297.3z"/> </svg> </a> <form class="full-width"><input type="text" value="${safe(workflow.name)}" name="name" /></form> </h2> <div data-bind="trigger"></div> <div data-bind="actions"></div> <div data-bind="add"></div> <h2 class="ellipsis">Activity</h2> <pre data-bind="history" class="scroll-y" style="max-height:300px">...</pre> <style>.component_page_admin .page_container h2:after { display: none; }</style> </div> `); render(transition($page)); // feature1: setup trigger const $trigger = qs($page, `[data-bind="trigger"]`); $trigger.appendChild(await createTrigger({ trigger: workflow.trigger, triggers })); // feature2: setup actions const $actions = qs($page, `[data-bind="actions"]`); for (let i=0; i<(workflow.actions || []).length; i++) { $actions.appendChild(await createAction({ action: workflow.actions[i], actions })); } // feature3: add a step const $add = qs($page, `[data-bind="add"]`); $add.appendChild(await createAdd({ workflow, actions, createAction: async({ action, actions }) => { const $action = await createAction({ action, actions }); qs($action, `button[alt="delete"]`).onclick = (e) => removeAction(e.target); $actions.appendChild($action); withToggle(qs($action, `[data-bind="form"]`)); }, })); // feature4: save button effect(rxjs.of(createSave({ withDelete: !!id })).pipe( rxjs.tap(($fab) => $page.parentElement.appendChild($fab)), rxjs.mergeMap(($fab) => onClick(qs($fab, "button"))), rxjs.tap(($button) => { $button.setAttribute("disabled", "true"); $button.firstElementChild.classList.add("hidden"); $button.firstElementChild.nextElementSibling.classList.remove("hidden"); }), rxjs.mergeMap(($button) => { let action = rxjs.of(null); let redirect = !id; const workflow = formData($page, { id }); if (workflow.published === "delete") { if (window.confirm("delete this workflow?")) { action = workflowDelete(id); redirect = true; } } else { workflow.published = workflow.published === "publish"; action = workflowUpsert(workflow); } const start = new Date(); return action.pipe( rxjs.switchMap((result) => { const elapsed = new Date() - start; return rxjs.of(result).pipe(rxjs.delay(Math.max(0, 400 - elapsed))); }), rxjs.finalize(() => { $button.removeAttribute("disabled"); $button.firstElementChild.classList.remove("hidden"); $button.firstElementChild.nextElementSibling.classList.add("hidden"); if (redirect) navigate(window.location.pathname); else history.replaceState(null, "", window.location.pathname + "?specs=" + encodeURIComponent(btoa(JSON.stringify(workflow)))); }), ); }), )); // feature5: toggle form visibility qsa($page, `[data-bind="form"]`).forEach(($form) => { withToggle($form); if (workflow.id) $form.classList.add("hidden"); }); // feature6: remove button qsa($page, `button[alt="delete"]`).forEach(($delete) => $delete.onclick = (e) => { removeAction(e.target); }); // feature7: history const $history = qs($page, `[data-bind="history"]`); if (!workflow.id) { $history.previousElementSibling.remove(); $history.remove(); } else effect(rxjs.of(null).pipe( rxjs.mergeMap(() => workflowGet(workflow.id)), rxjs.tap(({ history }) => { if (history.length === 0) $history.innerText = "Ø"; else $history.innerText = history.map(({ id, created_at, status, steps }) => { const [date, time] = created_at.split(" "); let msg = `date=${safe(date)} time=${safe(time)} id=${safe(String(id))} status=${safe(status)}`; try { steps = JSON.parse(steps); } catch (err) { steps = []; } if (steps.length > 0) { const s = new Array(steps.length); for (let i = 0; i < steps.length; i++) { const { name, done = false } = steps[i]; const out = name.split("/")[1] || "na"; if (status === "FAILURE" && !done) s[i] = out + "[✗]"; else if (status === "RUNNING" && !done && (steps[i-1] ? steps[i-1].done : true)) s[i] = out + "[○]"; else if (["RUNNING", "PENDING"].indexOf(status) !== -1 && done) s[i] = out + "[✓]"; else if (["READY", "CLAIMED"].indexOf(status) !== -1) s[i] = out + "[○]"; else s[i] = out; } msg += "[" + s.join(" ➜ ") + "]"; } return msg; }).join("\n"); }), rxjs.catchError(() => rxjs.of(null)), rxjs.delay(5000), rxjs.repeat(), )); } async function createTrigger({ trigger, triggers }) { const currentTrigger = triggers.find(({ name }) => name === trigger.name); if (!currentTrigger) return createElement(`<div class="box disabled">Trigger not found "${safe(trigger.name)}"</div>`); const { title, icon } = currentTrigger; const $trigger = createFragment(` <div class="box disabled"> ${icon} <h3 class="ellipsis no-select">  ${safe(title)} <button alt="configure" class="pull-right"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg></button> </h3> <form data-bind="form" data-key="trigger" data-step="${safe(trigger.name)}"></form> </div><hr> `); const $form = await createForm( mutateForm(currentTrigger.specs, trigger.params || {}), formTmpl(), ); qs($trigger, `[data-bind="form"]`).appendChild($form); return $trigger; } async function createAction({ action, actions }) { const selected = actions.find((_action) => _action.name === action.name); if (!selected) return createElement(` <div> <div class="box disabled">Action not found "${safe(action.name)}"</div> <hr> </div> `); const subtitle = selected.subtitle && action.params && action.params[selected.subtitle] ? `(${safe(action.params[selected.subtitle])})` : ""; const $action = createElement(` <div> <div class="box"> ${selected.icon} <h3 class="ellipsis no-select">  ${safe(selected.title)} <span>${safe(subtitle)}</span> <button alt="delete" class="pull-right"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"/></svg></button> <button alt="configure" class="pull-right ${Object.keys(selected.specs).length === 0 ? "hidden": ""}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg></button> </h3> <form data-bind="form" data-key="actions" data-step="${safe(action.name)}" data-array></form> </div> <hr> </div> `); const $form = await createForm( mutateForm(selected.specs, action.params || {}), formTmpl(), ); qs($action, `[data-bind="form"]`).appendChild($form); return $action; } async function removeAction($target) { const $box = $target.closest(".box"); await animate($box, { time: 150, keyframes: slideXOut(10), }); $box.parentElement.remove(); } async function createAdd({ actions, createAction }) { const $el = createElement(` <div class="box"> <button class="item" title="add"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"> <path d="M352 128C352 110.3 337.7 96 320 96C302.3 96 288 110.3 288 128L288 288L128 288C110.3 288 96 302.3 96 320C96 337.7 110.3 352 128 352L288 352L288 512C288 529.7 302.3 544 320 544C337.7 544 352 529.7 352 512L352 352L512 352C529.7 352 544 337.7 544 320C544 302.3 529.7 288 512 288L352 288L352 128z"/> </svg> </button> </div> `); const $categories = createElement(` <div class="hidden flex no-select"> `+actions.map(({ name }) => `<button data-name="${safe(name)}">${safe(name)}</button>`).join("")+` </div> `); const $item = qs($el, ".item"); const width = 45; let rotate = 0; $el.appendChild($categories); $item.onclick = async(e) => { rotate += 45; $item.firstElementChild.style.transform = `rotate(${rotate}deg)`; if (rotate % 90 === 0) { $categories.classList.add("hidden"); await animate($el, { time: 150, keyframes: [ { width: "300px" }, { width: `${width}px` }, ], }); } else { await animate($el, { time: 150, keyframes: [ { width: `${width}px` }, { width: "300px" }, ], }); $categories.classList.remove("hidden"); animate($categories, { time: 80, keyframes: slideXIn(-20) }); } }; qsa($el, "[data-name]").forEach(($action) => $action.onclick = () => { const action = actions.find(({ name }) => name === $action.getAttribute("data-name")); if (action) createAction({ action, actions }); $item.onclick(); }); return $el; } function createSave({ withDelete = true }) { const $fab = createElement(` <form class="workflow-fab flex no-select"> <select name="published"> <option>publish</option> <option>unpublish</option> ${withDelete ? "<option>delete</option>" : ""} </select> <button style="display:contents;" type="button"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"> <path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/> </svg> <component-icon class="hidden" name="loading"></component-icon> </button> </form> `); animate($fab, { time: 100, keyframes: slideXIn(5) }); return $fab; } function withToggle($form) { const height = $form.clientHeight; const $box = $form.closest(".box"); qs($box, `h3 button[alt="configure"]`).onclick = () => { const shouldOpen = $form.classList.contains("hidden"); if (shouldOpen) { animate($form, { time: Math.max(120, height/2), keyframes: [{ height: "0px" }, { height: `${height}px` }], }); $form.classList.remove("hidden"); } else { animate($form, { time: 80, keyframes: [{ height: `${height}px` }, { height: "0px" }], onExit: () => $form.classList.add("hidden"), }); } }; } function formData($page, { id }) { const result = { id: id || Date.now().toString(36) + Math.random().toString(36).slice(2, 6), }; qsa($page.parentElement, "form").forEach(($form) => { const params = [...new FormData($form)].reduce((acc, [key, value]) => { if (value) acc[key] = value; return acc; }, {}); const key = $form.getAttribute("data-key"); if (key) { const name = $form.getAttribute("data-step"); if ($form.hasAttribute("data-array")) { if (!result[key]) result[key] = []; const step = { name }; if (Object.keys(params).length > 0) step.params = params; result[key].push(step); } else { const step = { name }; if (Object.keys(params).length > 0) step.params = params; result[key] = step; } } else { Object.keys(params).forEach((key) => { result[key] = params[key]; }); } }); return result; } ================================================ FILE: public/assets/pages/adminpage/ctrl_workflow_list.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { animate } from "../../lib/animate.js"; import { createModal } from "../../components/modal.js"; import { generateSkeleton } from "../../components/skeleton.js"; import t from "../../locales/index.js"; import transition from "./animate.js"; export default async function(render, { workflows, triggers }) { const $page = createElement(` <div class="component_page_workflow"> <h2> Workflows <a class="pull-right pointer no-select">+</a> </h2> <div data-bind="workflows"></div> </div> `); render(transition($page)); const $workflows = qs($page, `[data-bind="workflows"]`); if (workflows.length === 0) $workflows.appendChild(createEmptyWorkflow({ triggers })); else workflows .sort((wa, wb) => wa.published === wb.published ? 0 : wa.published ? -1 : 1) .forEach((workflow) => $workflows.appendChild(createWorkflow({ workflow }))); effect(onClick(qs($page, "h2 > a")).pipe( rxjs.tap(($a) => animate($a, { time: 300, keyframes: [{ transform: "rotate(0)" }, { transform: `rotate(90deg)` }], })), rxjs.tap(() => ctrlModal(createModal(), { triggers })), )); } function createWorkflow({ workflow }) { const { name, published, actions, trigger, updated_at } = workflow; const summaryHTML = { trigger: `<button class="light">${safe(trigger.name)}</button>`, actions: (actions || []).map(({ name }) => `<button class="light">${safe(name.split("/")[0])}</button>`).join(""), }; const $workflow = createElement(` <a href="./admin/workflow?specs=${encodeURIComponent(btoa(JSON.stringify(workflow)))}" class="box ${published ? "" : "disabled"}" data-link> <h3 class="ellipsis"> ${safe(name)} <span>(${Intl.DateTimeFormat(navigator.language).format(new Date(safe(updated_at)))})</span> </h3> <div class="workflow-summary"> ${summaryHTML.trigger} ${summaryHTML.actions ? "→" + summaryHTML.actions : ""} </div> </a> `); return $workflow; } function createEmptyWorkflow({ triggers }) { const $empty = createElement(` <h3 class="center empty no-select">Create your first workflow</h3> `); effect(onClick($empty).pipe( rxjs.tap(() => ctrlModal(createModal(), { triggers })), )); return $empty; } function ctrlModal(render, { triggers }) { const $page = createElement(` <div class="component_workflow_create"> <form> <h2>Step1: Name the Workflow</h2> <input name="tag" type="text" placeholder="${t("Workflow Name")}" value=""> <div data-bind="list"> ${generateSkeleton(1)} </div> </form> </div> `); render($page); const $list = qs($page, `[data-bind="list"]`); const $input = qs($page, "input"); effect(rxjs.of(triggers).pipe( rxjs.map((arr) => arr.map(({ name, title, icon }) => createElement(` <a class="item flex no-select ellipsis" data-name="${safe(name)}">${icon} ${safe(title)}</a> `))), rxjs.map(($els) => { $list.innerHTML = ""; $input.focus(); const $fragment = document.createDocumentFragment(); $fragment.appendChild(createElement(`<h2>Step2: Select a Trigger</h2>`)); $els.forEach(($el) => $fragment.appendChild($el)); $list.appendChild($fragment); const height = $list.clientHeight; $list.style.height = "0"; return { height, $els }; }), rxjs.mergeMap(({ height, $els }) => rxjs.fromEvent($input, "keydown").pipe( rxjs.debounceTime(200), rxjs.tap((e) => { const shouldOpen = e.target.value.length > 0; if ($list.clientHeight === 0 && shouldOpen) animate($list, { time: 300, keyframes: [{ height: "0" }, { height: `${height}px` }], onExit: () => $list.style.height = "", }); else if ($list.clientHeight > 0 && !shouldOpen) animate($list, { time: 100, keyframes: [{ height: `${height}px` }, { height: "0" }], onExit: () => $list.style.height = "0", }); $els.forEach(($el) => $el.setAttribute("href", "./admin/workflow?specs="+encodeURIComponent(btoa(JSON.stringify({ name: e.target.value, published: false, trigger: { name: $el.getAttribute("data-name") }, actions: [{ name: "tools/debug" }] }))))); }), )), )); } ================================================ FILE: public/assets/pages/adminpage/decorator.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import AdminOnly from "./decorator_admin_only.js"; import WithShell from "./decorator_sidemenu.js"; import "../../components/loader.js"; export default function HOC(ctrlPage) { const ctrlLoading = (render) => { render(createElement("<component-loader inlined></component-loader>")); }; return (render) => { AdminOnly(WithShell(ctrlPage))(render); return (render) => { WithShell(ctrlLoading)(render); }; }; } ================================================ FILE: public/assets/pages/adminpage/decorator_admin_only.js ================================================ import rxjs, { effect } from "../../lib/rx.js"; import ctrlError from "../ctrl_error.js"; import ctrlLogin from "./ctrl_login.js"; import { isAdmin$ } from "./model_admin_session.js"; export default function AdminOnly(ctrlWrapped) { return (render) => { effect(isAdmin$().pipe( rxjs.map((isAdmin) => isAdmin ? ctrlWrapped : ctrlLogin), rxjs.tap((ctrl) => ctrl(render)), rxjs.catchError(ctrlError(render)), )); }; } ================================================ FILE: public/assets/pages/adminpage/decorator_sidemenu.css ================================================ .component_menu_sidebar { height: 100vh; background: linear-gradient(#232628, #2c3032); width: 250px; border-right: 2px solid var(--color); padding: 50px 0px 0px 40px; transition: transform 0.3s ease; transition-delay: 0s; flex-shrink: 0; } .component_menu_sidebar h2 { font-family: "Source Code Pro", monospace; color: white; font-weight: normal; font-size: 1.5rem; line-height: 1.5rem; margin: 25px 0 40px 0; } .component_menu_sidebar ul { color: rgba(255, 255, 255, 0.5); list-style-type: none; padding: 0; } .component_menu_sidebar ul li { margin: 15px 0; } .component_menu_sidebar ul li a.active, .component_menu_sidebar ul li a:hover { color: white; } .component_menu_sidebar ul li.version { position: absolute; bottom: 0; opacity: 0.25; } .component_menu_sidebar ul li a { display: inline-block; } .component_menu_sidebar .header { display: block; height: 40px; } .component_menu_sidebar .header svg { width: 40px; height: 40px; } .component_menu_sidebar .header svg.arrow_left { position: absolute; margin-left: -35px; opacity: 0.7; padding: 8px; box-sizing: border-box; fill: #6f6f6f; } .component_menu_sidebar .header svg.logo { transform: scale(1.4); color: #9AD1ED; } @media screen and (max-width: 760px) { .component_menu_sidebar .header svg.arrow_left { display: none; } } @media screen and (max-width: 1000px) { .component_menu_sidebar { padding-left: 30px; width: 200px; } .component_menu_sidebar h2 { font-size: 1.35em; } } @media screen and (max-width: 760px) { .component_menu_sidebar { padding: 25px 10px; } .component_menu_sidebar h2 { margin: 15px 0 25px 0; font-size: 1.25em; padding: 0; } .component_menu_sidebar .version { display: none; } } @media screen and (max-width: 650px) { .component_menu_sidebar { width: 100px; } .component_menu_sidebar h2 { display: none; } } @media screen and (max-width: 500px) { .component_page_admin { flex-wrap: wrap; } .component_menu_sidebar { width: 100%; height: initial; display: flex; padding: 10px; border-bottom: 2px solid var(--color); } .component_menu_sidebar ul { margin: 0; display: flex; align-items: center; padding-left: 10px; } .component_menu_sidebar ul li { margin: 0 10px; } } ================================================ FILE: public/assets/pages/adminpage/decorator_sidemenu.js ================================================ import { createElement, createRender } from "../../lib/skeleton/index.js"; import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, stateMutation } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { CSS } from "../../helpers/loader.js"; import { get as getRelease } from "./model_release.js"; import { isSaving } from "./model_config.js"; import { isLoading } from "./model_audit.js"; import "../../components/icon.js"; export default function(ctrl, opts = {}) { return async function(render) { const $page = createElement(` <div class="component_page_admin"> <style>${await CSS(import.meta.url, "decorator_sidemenu.css", "index.css")}</style> <div class="component_menu_sidebar no-select"> <a class="header" href=""> <svg class="arrow_left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="m 16,7.16 -4.58,4.59 4.58,4.59 -1.41,1.41 -6,-6 6,-6 z"/> </svg> <span data-bind="logo"></span> </a> <h2>Admin console</h2> <ul> <li> <a href="${toHref("/admin/storage")}" data-link> Storage </a> </li> <li> <a href="${toHref("/admin/workflow")}" data-link> Workflow </a> </li> <li> <a href="${toHref("/admin/activity")}" data-link> Activity </a> </li> <li> <a href="${toHref("/admin/settings")}" data-link> Settings </a> </li> <li class="version"> <a href="${toHref("/admin/about")}" data-link data-bind="version">   </a> </li> </ul> </div> <div class="page_container scroll-y" data-bind="admin"></div> </div> `); render($page); // feature: setup the childrens ctrl(createRender(qs($page, "[data-bind=\"admin\"]")), opts); // feature: display the release version effect(getRelease().pipe( rxjs.map(({ version }) => version), stateMutation(qs($page, "[data-bind=\"version\"]"), "textContent"), rxjs.catchError(() => rxjs.EMPTY), )); // feature: logo serving as loading indicator effect(rxjs.combineLatest([ isSaving().pipe(rxjs.startWith(false)), isLoading().pipe(rxjs.startWith(false)), ]).pipe( rxjs.map(([a, b]) => a || b), rxjs.map((isLoading) => isLoading ? "<component-icon name=\"loading\"></component-icon>" : `<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path d="M330 202a81 79 0 00-162 0 81 79 0 000 158 81 79 0 000-158m81 79a81 79 0 1181 79H168" fill="none" stroke="currentColor" stroke-width="35px"/> </svg>`), stateMutation(qs($page, "[data-bind=\"logo\"]"), "innerHTML") )); // feature: currently active menu link effect(rxjs.of($page.querySelectorAll(".component_menu_sidebar li a")).pipe( rxjs.mergeMap(($els) => $els), rxjs.filter(($el) => location.pathname.endsWith($el.getAttribute("href"))), rxjs.tap(($el) => $el.classList.add("active")), rxjs.tap(($el) => $el.removeAttribute("href")) )); }; } ================================================ FILE: public/assets/pages/adminpage/helper_form.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; export function renderLeaf({ format, label, description, type }) { if (label === "banner") return createElement(` <div class="banner"> ${fromMarkdown(description)} </div> `); const $el = createElement(` <label class="no-select"> <div class="flex"> <span class="ellipsis"> ${format(label)}: </span> <div style="width:100%;" data-bind="children"></div> </div> </label> `); if (type === "hidden") $el.classList.add("hidden"); if (description) $el.appendChild(createElement(` <div class="flex"> <span class="nothing"></span> <div style="width:100%;"> <div class="description">${description}</div> </div> </div> `)); return $el; } function fromMarkdown(str = "") { str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>"); str = str.replaceAll("\n", "<br>"); return str; } export function useForm$($inputNodeList) { return rxjs.pipe( rxjs.mergeMap(() => $inputNodeList()), rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")), rxjs.mergeMap(($el) => { if ($el.target.checkValidity() === false) { $el.target.reportValidity(); return rxjs.EMPTY; } return rxjs.of($el); }), rxjs.mergeMap(async(e) => ({ name: e.target.getAttribute("name"), value: await (async function() { switch (e.target.getAttribute("type")) { case "checkbox": return e.target.checked; case "file": if (e.target.files.length === 0) return null; return await new Promise((done) => { const reader = new window.FileReader(); reader.readAsDataURL(e.target.files[0]); reader.onload = () => done(reader.result); }); default: return e.target.value; } }()), })), rxjs.scan((store, keyValue) => { store[keyValue.name] = keyValue.value; return store; }, {}) ); } export function formObjToJSON$() { const formObjToJSON = (o, level = 0) => { const obj = Object.assign({}, o); Object.keys(obj).forEach((key) => { const t = obj[key]; if ("label" in t && "type" in t && "default" in t && "value" in t) { let value = obj[key].value; if (t.type === "number") value = parseInt(value); obj[key] = value; } else { obj[key] = formObjToJSON(obj[key], level + 1); } }); return obj; }; return rxjs.map(formObjToJSON); } ================================================ FILE: public/assets/pages/adminpage/index.css ================================================ .component_page_admin { display: flex; } .component_page_admin .adminpage { max-width: 1400px; } .component_page_admin .page_container { width: 100%; background: #fdfdfc; padding-bottom: 150px; padding-left: 60px; padding-right: 60px; box-sizing: border-box; max-height: 100vh; } .component_page_admin .page_container.scroll-y > div { max-width: 1300px; } @media screen and (max-width: 1000px) { .component_page_admin .page_container { padding-left: 30px; padding-right: 30px; } } @media screen and (max-width: 500px) { .component_page_admin .page_container { padding-left: 10px; padding-right: 10px; } } .component_page_admin .page_container h2 { font-family: 'Source Code Pro', monospace; text-shadow: 0 0 2px var(--bg-color); font-size: 2em; padding: 50px 0 0 0; margin-bottom: 30px; margin-top: 0; font-weight: normal; line-height: 1em; margin-left: -60px; margin-right: -60px; padding-left: 60px; padding-right: 60px; } @media screen and (max-width: 1000px) { .component_page_admin .page_container h2 { padding-top: 25px; } } @media screen and (max-width: 500px) { .component_page_admin .page_container h2 { padding-top: 10px; } } .component_page_admin .page_container h2:after { content: "_"; display: block; font-size: 0; border-bottom: 3px solid var(--color); width: 90px; margin-top: 5px; opacity: 0.9; line-height: 0; } .component_page_admin .page_container .sticky h2 { position: sticky; background: rgba(253, 253, 252, 0.9); backdrop-filter: saturate(180%) blur(20px); box-shadow: 0 2px 10px rgba(0,0,0,0.02); z-index: 2; top: 0; } @media screen and (max-width: 550px) { .component_page_admin .page_container label > div { display: block; } .component_page_admin .page_container label > div .nothing { display: none; } } .component_page_admin .page_container label > div > span { display: inline-block; line-height: 30px; min-width: 150px; padding-right: 20px; margin-top: auto; margin-bottom: auto; } @media screen and (max-width: 760px) { .component_page_admin .page_container label > div > span { min-width: 115px; } } /* form builder */ .component_page_admin .page_container label > div .component_checkbox .indicator { top: 6px; } .component_page_admin .page_container a { color: var(--dark); } .component_page_admin .page_container a:hover { opacity: 0.8; transition: opacity 0.2s; text-shadow: 0 0 #000000aa; } .component_page_admin .page_container pre { font-family: 'Source Code Pro', monospace; background: var(--dark); font-size: 0.9em; padding: 10px; margin-bottom: 0; border-radius: 3px; color: white; max-width: 100%; overflow-x: auto; overflow-y: auto; } .component_page_admin .page_container .component_loader > svg { height: 50px; } .component_page_admin .page_container fieldset { background: white; border-color: #ebebec; border-style: solid; border-radius: 5px; padding: 10px 15px; margin: 15px 0 0 0; } .component_page_admin .formbuilder h2 ~ div > div > label, .component_page_admin .formbuilder h2 ~ div > div > div > label { display: block; border-bottom: 2px dashed var(--border); margin-bottom: 15px; padding-bottom: 5px; margin-top: 15px; } .component_page_admin .formbuilder .component_input, .component_page_admin .formbuilder .component_textarea, .component_page_admin .formbuilder .component_select { background: var(--border); border: 2px solid var(--border); border-radius: 3px; padding-left: 5px; } .component_page_admin .formbuilder .component_input, .component_page_admin .formbuilder .component_select{ height: 33px; } .component_page_admin .formbuilder .component_input:not([disabled]):hover, .component_page_admin .formbuilder .component_input:not([disabled]):focus, .component_page_admin .formbuilder .component_textarea:not([disabled]):hover, .component_page_admin .formbuilder .component_textarea:not([disabled]):focus, .component_page_admin .formbuilder .component_select:not([disabled]):hover, .component_page_admin .formbuilder .component_select:not([disabled]):focus { background: rgba(0, 0, 0, 0.08); } .component_page_admin form .formbuilder_password:focus-within img.component_icon, .component_page_admin form .formbuilder_password:hover img.component_icon{ border-color: rgba(0, 0, 0, 0.05); background: rgba(0, 0, 0, 0.08); } .component_page_admin .formbuilder .formbuilder_password .component_input { border-right: none; border-top-right-radius: 0; border-bottom-right-radius: 0; } .component_page_admin .formbuilder_password img.component_icon { border: 2px solid rgba(0, 0, 0, 0.05); height: 19px; border-left: none; background: rgba(0, 0, 0, 0.07); border-top-right-radius: 3px; border-bottom-right-radius: 3px; padding-right: 5px; } .component_page_admin .formbuilder input::placeholder, .component_page_admin .formbuilder textarea::placeholder { opacity: 0.6; } .component_page_admin .formbuilder .banner pre { background: inherit; color: inherit; padding: 0; margin: 0; } ================================================ FILE: public/assets/pages/adminpage/model_admin_session.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; const sessionSubject$ = new rxjs.Subject(); const adminSession$ = rxjs.merge( sessionSubject$, rxjs.merge( rxjs.interval(30000), rxjs.fromEvent(document, "visibilitychange").pipe(rxjs.filter(() => !document.hidden)), ).pipe( rxjs.startWith(null), rxjs.mergeMap(() => ajax({ url: "admin/api/session", responseType: "json" })), rxjs.map(({ responseJSON }) => responseJSON.result), ) ).pipe( rxjs.distinctUntilChanged(), rxjs.shareReplay(1) ); export function isAdmin$() { return adminSession$; } export function authenticate$(body) { return ajax({ url: "admin/api/session", method: "POST", body, responseType: "json" }).pipe( rxjs.mapTo(true), rxjs.tap((ok) => ok && sessionSubject$.next(ok)), ); } ================================================ FILE: public/assets/pages/adminpage/model_audit.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; const isLoading$ = new rxjs.BehaviorSubject(false); export function get(searchParams = new URLSearchParams()) { return ajax({ url: "admin/api/audit?" + searchParams.toString(), responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result) ); } export function setLoader(value) { return isLoading$.next(!!value); } export function isLoading() { return isLoading$.asObservable(); } ================================================ FILE: public/assets/pages/adminpage/model_auth_middleware.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; const model$ = ajax({ url: "admin/api/middlewares/authentication", method: "GET", responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), rxjs.share(), ); export function getAuthMiddleware() { return model$; } ================================================ FILE: public/assets/pages/adminpage/model_backend.js ================================================ import rxjs from "../../lib/rx.js"; import { getBackends as _getBackends } from "../../model/backend.js"; import { isSaving } from "./model_config.js"; const backend$ = _getBackends().pipe(rxjs.shareReplay(1)); export function getBackends() { return isSaving().pipe( rxjs.filter((loading) => !loading), rxjs.mergeMap(() => backend$), ); } ================================================ FILE: public/assets/pages/adminpage/model_config.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; const isSaving$ = new rxjs.BehaviorSubject(false); const config$ = isSaving$.pipe( rxjs.filter((loading) => !loading), rxjs.switchMapTo(ajax({ url: "admin/api/config", method: "GET", responseType: "json" })), rxjs.map((res) => res.responseJSON.result), rxjs.shareReplay(1), ); export async function initConfig() { if (isSaving$.value === true) isSaving$.next(false); } export function isSaving() { return isSaving$.asObservable(); } export function get() { return config$; } export function save() { return rxjs.pipe( rxjs.tap(() => isSaving$.next(true)), rxjs.debounceTime(800), rxjs.mergeMap((formData) => ajax({ url: "admin/api/config", method: "POST", responseType: "json", body: formData, })), rxjs.tap(() => isSaving$.next(false)), rxjs.catchError((err) => { isSaving$.next(false); return rxjs.throwError(err); }), ); } ================================================ FILE: public/assets/pages/adminpage/model_log.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; export function url(logSize = 0) { return "admin/api/logs" + (logSize ? `?maxSize=${logSize}` : ""); } export function get(t = 100) { return ajax({ url: url(1024 * t), // fetch the last 100KB by default responseType: "text", }).pipe( rxjs.map(({ response }) => response), ); } ================================================ FILE: public/assets/pages/adminpage/model_release.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; const release$ = ajax({ url: "about", responseType: "text", }).pipe(rxjs.shareReplay(1)); export function get() { return release$.pipe( rxjs.map(({ response, responseHeaders }) => { const a = document.createElement("html"); a.innerHTML = response; return { html: a.querySelector("table")?.outerHTML, version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1") }; }), ); } ================================================ FILE: public/assets/pages/adminpage/model_setup.js ================================================ import { createElement, nop } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; import { qs, qsa } from "../../lib/dom.js"; import { createModal, MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import notification from "../../components/notification.js"; import { query as getConfig } from "../../model/config.js"; import { formObjToJSON$ } from "./helper_form.js"; import { getBackendAvailable, getMiddlewareAvailable } from "./ctrl_storage_state.js"; import { get as getAdminConfig, save as saveConfig } from "./model_config.js"; export function getDeps({ getPassword = nop }) { return rxjs.combineLatest([ getAdminConfig().pipe(formObjToJSON$()), getBackendAvailable(), getMiddlewareAvailable(), getConfig(), ]).pipe( rxjs.map(([{ constant, middleware }, backendSpecs, authSpecs, { connections }]) => ([ { name_success: "You're using HTTPS", name_failure: "Your connection isn't encrypted", pass: window.location.protocol !== "http:", severe: true, message: createElement("<i>Set up HTTPS to secure your connection and protect your data in transit</i>"), }, { name_success: "Running as non root user: '" + constant.user + "'", name_failure: "Running as root", pass: constant.user !== "root", severe: true, message: createElement("<i>You should run as a non-privileged user, running as root is risky.</i>"), }, { pass: false, severe: connections.length === 0, name_failure: connections.length === 0 ? "Almost there!" : "Want to try something different?", message: withPreset(createElement(` <div id="configuration-preset"> <i>Pick a preset that best fits your needs:</i><br> <div data-bind="templates"></div> <div class="note"> Notes: Templates get you started quickly, but they're just scratching the surface. Want more? Head to <a href="admin/storage">/admin/storage</a> to wire up your own storage, authentication via SSO, and RBAC authorization. </div> <style>${CSS}</style> </div> `), { password: getPassword(), hasStorage: (name, field = null) => !!backendSpecs[name] && (field === null ? true : !!backendSpecs[name][field]), hasAuth: (name) => !!authSpecs[name] }), }, { name_success: "Enterprise Support: active", name_failure: "Enterprise Support: no", pass: constant.license !== "agpl", message: createElement("<i>Need guaranteed response times? Upgrade to a support plan with 9x5 or 24x7 coverage and SLAs</i>"), }, { name_success: "Enterprise License: active", name_failure: "You're on the AGPL version", pass: constant.license !== "agpl", message: createElement("<i>Upgrade to Filestash enterprise for production usage in a commercial environment</i>"), } ])), ); } function withPreset($preset, { password, hasStorage, hasAuth }) { const TEMPLATES = [ { name: "Local files - just for me", success: createElement("<span>Ready! Visit <a class=\"wavy\" href=\"login\">/login</a> with your admin password to start browsing files.</span>"), output_connections: [{ "type": "local", "label": "local" }], output_identity_provider: { "type": "passthrough", "params": "{\"strategy\":\"password_only\"}" }, output_attribute_mapping: { "related_backend": "local", "params": "{\"local\":{\"type\":\"local\",\"password\":\"{{ .password }}\"}}" }, isOK: hasStorage("local") && hasAuth("passthrough"), }, { name: "Local Storage - Local Users", success: createElement("<span>Applied! Create your first users from <a class=\"wavy\" href=\"admin/simple-user-management\">/admin/simple-user-management</a></span>"), input_storage: password ? null : ["admin_password"], output_connections: [{ "type": "local", "label": "local" }], output_identity_provider: { "type": "local", "params": "{}" }, output_attribute_mapping: { "related_backend": "local", "params": "{\"local\":{\"type\":\"local\",\"password\":\""+(password || "[[admin_password]]") +"\"}}", }, isOK: hasStorage("local") && hasAuth("local"), }, { name: "SFTP Storage - Use existing credentials", success: createElement("<span>Done! Visit <a class=\"wavy\" href=\"login\">/login</a> and authenticate with your SFTP username and password or private key.</span>"), input_storage: ["sftp_hostname"], output_connections: [{ "type": "sftp", "label": "sftp" }], output_identity_provider: { "type": "passthrough", "params": "{\"strategy\":\"username_and_password\"}" }, output_attribute_mapping: { "related_backend": "sftp", "params": "{\"sftp\":{\"type\":\"sftp\",\"hostname\":\"[[sftp_hostname]]\",\"username\":\"{{ .user }}\",\"password\":\"{{ .password }}\"}}" }, isOK: hasStorage("sftp") && hasAuth("passthrough"), }, { name: "S3 Storage - Use your AWS credentials", success: createElement("<span>All set! Visit <a class=\"wavy\" href=\"login\">/login</a> - use your access_key_id as username, secret_access_key as password.</span>"), output_connections: [{ "type": "s3", "label": "s3" }], output_identity_provider: { "type": "passthrough", "params": "{\"strategy\":\"username_and_password\"}" }, output_attribute_mapping: { "related_backend": "s3", "params": "{\"s3\":{\"type\":\"s3\",\"access_key_id\":\"{{ .user }}\",\"secret_access_key\":\"{{ .password }}\"}}" }, isOK: hasStorage("s3") && hasAuth("passthrough"), }, { name: "S3 Storage - Multiple users", success: createElement("<span>All set! Create your first users from <a class=\"wavy\" href=\"admin/simple-user-management\">/admin/simple-user-management</a></span>"), input_storage: ["access_key_id", "secret_access_key"], output_connections: [{ "type": "s3", "label": "s3" }], output_identity_provider: { "type": "local", "params": "{}" }, output_attribute_mapping: { "related_backend": "s3", "params": "{\"s3\":{\"type\":\"s3\",\"access_key_id\":\"[[access_key_id]]\",\"secret_access_key\":\"[[secret_access_key]]\"}}" }, isOK: hasStorage("s3") && hasAuth("local"), }, { name: "FTP Storage - WordPress Users", success: createElement("<span>Done! Visit <a class=\"wavy\" href=\"login\">/login</a> - use your wordpress credentials to access your FTP.</span>"), input_storage: ["ftp_hostname", "ftp_username", "ftp_password"], input_auth: ["wordpress_url"], output_connections: [{ "type": "ftp", "label": "ftp" }], output_identity_provider: { "type": "wordpress", "params": "{\"url\":\"[[wordpress_url]]\"}" }, output_attribute_mapping: { "related_backend": "ftp", "params": "{\"ftp\":{\"type\":\"ftp\",\"hostname\":\"[[ftp_hostname]]\",\"username\":\"[[ftp_username]]\",\"password\":\"[[ftp_password]]\"}}" }, isOK: hasStorage("ftp") && hasAuth("wordpress"), }, ]; const $templates = qs($preset, `[data-bind="templates"]`); const onClick = (specs) => { return async() => { if (!specs.isOK) return createModal({ withButtonsRight: "OK" })(createElement(` <div> Your build is missing some plugins. Contact Support </div> `)); const output = { connections: specs.output_connections, identity_provider: specs.output_identity_provider, attribute_mapping: specs.output_attribute_mapping, }; const $form = createElement("<form id=\"configuration-preset-modal\"></form>"); $form.onsubmit = (e) => e.preventDefault(); const save = await new Promise((done) => { const fields = (specs.input_storage || []).concat(specs.input_auth || []); if (fields.length === 0) return done(true); fields.forEach((fieldName) => $form.appendChild(createElement(` <label class="no-select"> ${fieldName} <input class="component_input" type="text" name="${fieldName}" /> </label> `))); createModal({ withButtonsRight: "Apply" })($form, (choice, $modal) => { if (choice !== MODAL_RIGHT_BUTTON) return done(false); let ok = false; qsa($modal, "input").forEach(($input) => { if ($input.value) ok = true; output.identity_provider.params = output.identity_provider.params.replaceAll("[["+$input.getAttribute("name")+"]]", $input.value.replaceAll(`"`, `\"`)); output.attribute_mapping.params = output.attribute_mapping.params.replaceAll("[["+$input.getAttribute("name")+"]]", $input.value.replaceAll(`"`, `\"`)); }); done(ok); }); }); if (save === false) return; await getAdminConfig().pipe( rxjs.first(), formObjToJSON$(), rxjs.map((config) => { config.connections = output.connections; config.middleware.attribute_mapping = output.attribute_mapping; config.middleware.identity_provider = output.identity_provider; return config; }), saveConfig(), rxjs.tap(() => notification.success(specs.success)), rxjs.catchError((err) => { notification.error(err.message); return rxjs.throwError(err); }), ).toPromise(); }; }; TEMPLATES.map((specs) => { const $div = createElement(`<div></div>`); const $button = document.createElement("button"); $button.className = "no-select"; $button.innerText = `APPLY PRESET`; $button.onclick = onClick(specs); $div.appendChild($button); $div.appendChild(createElement(`<span> ➡ ${specs.name || "N/A"}</span>`)); return $div; }).forEach(($button) => $templates.appendChild($button)); return $preset; } const CSS = ` #configuration-preset .note { font-style: italic; text-align: justify; padding: 5px 10px; margin-top: 10px; border: 2px dashed rgba(255,255,255,0.2); font-size: 0.95rem; } #configuration-preset a { text-decoration: underline; text-decoration-style: wavy; text-decoration-color: rgba(0, 0, 0, 0.3); } #configuration-preset button { background: rgba(0, 0, 0, 0.3); margin: 2px 0; padding: 5px 9px; text-transform: uppercase; box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); font-size: 0.92rem; } .touch-no #configuration-preset button:hover { background: rgba(0, 0, 0, 0.45); } #configuration-preset-modal label { font-size: 0.9rem; } `; ================================================ FILE: public/assets/pages/adminpage/model_workflow.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; export function workflowAll() { return ajax({ url: "admin/api/workflow", responseType: "json", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), rxjs.map(({ workflows, triggers, actions }) => ({ workflows, triggers, actions, })), ); } export function workflowUpsert(body) { return ajax({ url: "admin/api/workflow", responseType: "json", method: "POST", body, }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result) ); } export function workflowDelete(id) { return ajax({ url: "admin/api/workflow?id=" + id, responseType: "json", method: "DELETE", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result) ); } export function workflowGet(id) { return ajax({ url: "admin/api/workflow/" + id, responseType: "json", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result) ); } ================================================ FILE: public/assets/pages/connectpage/ctrl_forkme.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; import config$ from "./model_config.js"; export default async function(render) { const hasFork = await config$.pipe(rxjs.filter(({ license }) => license === "agpl")).toPromise(); if (!hasFork) return; render(createElement(` <a href="https://github.com/mickael-kerjean/skeleton" class="component_forkme" aria-label="View source on GitHub"> <svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--emphasis); color:var(--primary); position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"> <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path> <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path> </svg> <style> .component_forkme:hover .octo-arm{animation:octocat-wave 560ms ease-in-out} .dark-mode .component_forkme .octo-arm, .dark-mode .component_forkme .octo-body { fill: var(--bg-color); } @media (max-width:500px){ .component_forkme:hover .octo-arm{animation:none} .component_forkme .octo-arm{animation:octocat-wave 560ms ease-in-out} } @keyframes octocat-wave{ 0%,100%{transform:rotate(0)} 20%,60%{transform:rotate(-25deg)} 40%,80%{transform:rotate(10deg)} } </style> </a> `)); } ================================================ FILE: public/assets/pages/connectpage/ctrl_form.css ================================================ .component_page_connection_form .box { margin-bottom: 7px; border-radius: 3px; box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); background: white; } .component_page_connection_form .box button { padding: 7px 5px; } .component_page_connection_form .box form { margin-top: 5px; } .component_page_connection_form div.buttons { display: flex; margin: 0px 0px 15px 0px; padding: 5px; overflow-x: auto; } .component_page_connection_form div.buttons button { width: 100%; min-width: 110px; box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.2); border: 1px solid transparent; } .component_page_connection_form div.buttons button:not(.active):hover { transition: background 0.2s ease; background: rgba(0, 0, 0, 0.03); } .component_page_connection_form div.buttons button.active { transition: box-shadow 0.2s; box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.2); } .component_page_connection_form form label { font-style: italic; font-size: 0.9em; display: block; text-transform: capitalize; } .component_page_connection_form form .advanced { color: rgba(0, 0, 0, 0.4); font-style: italic; } .component_page_connection_form form .advanced_form { max-height: 156px; margin-top: 5px; margin-bottom: 5px; padding-right: 10px; } .component_page_connection_form form button.emphasis { color: white; text-transform: uppercase; margin-top: 5px; } .component_page_connection_form form .third-party { text-align: center; } .component_page_connection_form form .third-party img { height: 115px; margin: 0; } .component_page_connection_form form .component_checkbox .indicator { top: 2px; } .component_page_connection_form form input.component_input[name="oauth2"] { display: none; } .component_page_connection_form .component_loader { margin: 45px 0; } .scroll-x { overflow-x: auto !important; overflow-y: hidden !important; -webkit-overflow-scrolling: touch; } .scroll-x::-webkit-scrollbar { height: 4px; } .scroll-x::-webkit-scrollbar-track { background: white; } .scroll-x::-webkit-scrollbar-thumb { -webkit-border-radius: 2px; -moz-border-radius: 2px; -ms-border-radius: 2px; -o-border-radius: 2px; border-radius: 2px; background: rgba(0, 0, 0, 0.1); } .dark-mode div.buttons button.active { background: var(--bg-color); color: var(--super-light); } ================================================ FILE: public/assets/pages/connectpage/ctrl_form.js ================================================ import { createElement, navigate } from "../../lib/skeleton/index.js"; import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, applyMutation, applyMutations, preventDefault, onClick } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { animate, slideYIn, transition, opacityIn } from "../../lib/animate.js"; import assert from "../../lib/assert.js"; import { forwardURLParams } from "../../lib/path.js"; import { createForm } from "../../lib/form.js"; import { settings_get, settings_put } from "../../lib/settings.js"; import t from "../../locales/index.js"; import { formTmpl } from "../../components/form.js"; import notification from "../../components/notification.js"; import { CSS } from "../../helpers/loader.js"; import { createSession } from "../../model/session.js"; import ctrlError from "../ctrl_error.js"; import config$ from "./model_config.js"; import backend$ from "./model_backend.js"; import { setCurrentBackend, getCurrentBackend, getURLParams } from "./ctrl_form_state.js"; import { updateBackend } from "../filespage/cache.js"; const connections$ = config$.pipe( rxjs.map(({ connections, auth }) => (connections || []).map((conn) => { conn.middleware = (auth || []).indexOf(conn.label) >= 0; return conn; })), rxjs.shareReplay(1), ); export default async function(render) { const $page = createElement(` <div class="no-select component_page_connection_form"> <style>${await CSS(import.meta.url, "ctrl_form.css")}</style> <div role="navigation" class="buttons scroll-x box hidden"></div> <div data-bind="form" class="box hidden"><form></form></div> </div> `); render(transition($page, { enter: [ { transform: "scale(0.97)", opacity: 0 }, { transform: "scale(1)", opacity: 1 }, ], timeEnter: 100, })); // feature1: create navigation buttons to select storage const $nav = qs($page, "[role=\"navigation\"]"); effect(connections$.pipe( rxjs.map((conns) => conns.map((conn, i) => ({ ...conn, n: i }))), rxjs.map((conns) => conns.map(({ label, n }) => createElement(`<button data-current="${n}">${safe(label)}</button>`))), applyMutations($nav, "appendChild"), rxjs.tap((conns = []) => { if (conns.length > 1) $nav.classList.remove("hidden"); }), rxjs.tap(() => animate($nav, { time: 250, keyframes: opacityIn() })), )); // feature2: select a default storage among all the available ones effect(connections$.pipe( rxjs.map((conns) => { let n = parseInt(settings_get("login_tab")); if (Number.isNaN(n)) n = (conns.length || 0) / 2 - 1; if (n < 0 || n >= conns.length) n = 0; return n; }), rxjs.tap((current) => setCurrentBackend(Math.round(current))), )); // feature3: create the storage forms const formSpecs$ = connections$.pipe(rxjs.mergeMap((conns) => backend$.pipe( rxjs.map((backendSpecs) => conns.map(({ type, middleware, label }) => { if (middleware) return { // admin has set this storage as auth middleware middleware: { type: "hidden" }, label: { type: "hidden", value: label }, }; return backendSpecs[type] || {}; })), ))); effect(getCurrentBackend().pipe( rxjs.mergeMap((n) => formSpecs$.pipe( rxjs.map((specs) => specs[n]), )), rxjs.mergeMap((formSpec) => createForm(formSpec, formTmpl({ renderNode: () => createElement("<div></div>"), renderLeaf: ({ label, type }) => { if (type === "enable") return createElement(` <label class="advanced"> <span data-bind="children"></span> ${label} </label> `); return createElement("<label></label>"); } }))), applyMutation(qs($page, "[data-bind=\"form\"] form"), "replaceChildren"), rxjs.tap(($innerForm) => $innerForm.parentElement.appendChild(createElement(`<button class="emphasis full-width">${t("CONNECT")}</button>`))), rxjs.tap(($innerForm) => { const $box = $innerForm.parentElement.parentElement; let $animationTarget = $innerForm; if ($box.classList.contains("hidden")) { // first load $box.classList.remove("hidden"); $animationTarget = $box; } animate($animationTarget, { time: 200, keyframes: slideYIn(2) }); }), )); // feature4: interaction with the nav buttons effect(getCurrentBackend().pipe( rxjs.first(), rxjs.mergeMap(() => qsa($page, "[role=\"navigation\"] button")), rxjs.mergeMap(($button) => onClick($button)), rxjs.map(($button) => parseInt($button.getAttribute("data-current"))), rxjs.distinctUntilChanged(), rxjs.tap((current) => { settings_put("login_tab", current); setCurrentBackend(current); }), )); // feature5: highlight the currently selected storage effect(getCurrentBackend().pipe( rxjs.map((n) => [qsa($page, "[role=\"navigation\"] button"), n]), rxjs.tap(([$buttons, n]) => $buttons.forEach(($button, i) => { if (i !== n) $button.classList.remove("active", "primary"); else $button.classList.add("active", "primary"); })), )); // feature6: form submission const $loader = createElement(`<component-loader></component-loader>`); const toggleLoader = (hide) => { if (hide) { $page.classList.add("hidden"); assert.truthy($page.parentElement).appendChild($loader); } else { $loader.remove(); $page.classList.remove("hidden"); } }; effect(rxjs.merge( // 6.a form submission event handler rxjs.fromEvent(qs($page, "form"), "submit").pipe( preventDefault(), rxjs.map((e) => new FormData(e.target)), rxjs.map((formData) => { const json = {}; for (const pair of formData.entries()) { json[pair[0]] = pair[1] === "" ? null : pair[1]; } return json; }), ), // 6.b formatted URL in the like of type=xxx&etc=etc rxjs.of(getURLParams()).pipe( rxjs.filter(({ type }) => !!type), rxjs.mergeMap((urlParams) => connections$.pipe( rxjs.map((conns) => conns.filter(({ middleware, type }) => middleware !== true && type === urlParams["type"])), rxjs.mergeMap((conns) => { if (conns.length === 0) return rxjs.EMPTY; return rxjs.of(urlParams); }), )), ), // 6.c auto submit when it's the only choice available connections$.pipe( rxjs.filter((conns) => conns.length === 1), rxjs.map((conns) => conns[0]), rxjs.filter(({ middleware }) => middleware), ), ).pipe( rxjs.mergeMap((formData) => { // CASE 1: authentication middleware flow if (!("middleware" in formData)) return rxjs.of(formData); let url = "api/session/auth/?action=redirect"; url += "&label=" + formData["label"]; const p = getURLParams(); if (Object.keys(p).length > 0) { url += "&state=" + btoa(JSON.stringify(p)); } toggleLoader(true); location.href = url; return rxjs.EMPTY; }), rxjs.mergeMap((formData) => { // CASE 2: oauth2 related backends like dropbox and gdrive if (!("oauth2" in formData)) return rxjs.of(formData); return new rxjs.Observable((subscriber) => { const u = new URL(location.toString()); u.pathname = formData["oauth2"]; const _next = getURLParams()["next"]; if (_next) u.searchParams.set("next", _next); subscriber.next(u.toString()); }).pipe( rxjs.tap(() => toggleLoader(true)), rxjs.mergeMap((url) => ajax({ url, responseType: "json" })), rxjs.tap(({ responseJSON }) => location.href = responseJSON.result), rxjs.catchError(ctrlError()), ); }), rxjs.mergeMap((formData) => { // CASE 3: regular login delete formData["label"]; delete formData["middleware"]; return rxjs.of(null).pipe( rxjs.tap(() => toggleLoader(true)), rxjs.mergeMap(() => createSession(formData)), rxjs.tap(({ home, backendID }) => { updateBackend(backendID); let redirectURL = toHref("/files/"); const GET = getURLParams(); if (GET["next"]) redirectURL = GET["next"]; else if (home) redirectURL = toHref("/files" + home); if (redirectURL.startsWith("/api/")) return location.replace(redirectURL); navigate(forwardURLParams(redirectURL, ["nav"])); }), rxjs.catchError((err) => { toggleLoader(false); notification.error(t(err && err.message)); return rxjs.EMPTY; }) ); }), )); // feature7: empty connection handling effect(connections$.pipe( rxjs.filter((conns) => conns.length === 0), rxjs.mergeMap(() => Promise.reject(new Error("there is nothing here"))), // TODO: check translation? rxjs.catchError(ctrlError()), )); // feature8: bug on back navigation where loader get stuck effect(rxjs.fromEvent(window, "pageshow").pipe( rxjs.tap((event) => event.persisted && toggleLoader(false)), )); } ================================================ FILE: public/assets/pages/connectpage/ctrl_form_state.js ================================================ import rxjs from "../../lib/rx.js"; const currentBackend$ = new rxjs.ReplaySubject(1); export function setCurrentBackend(n) { currentBackend$.next(n); } export function getCurrentBackend() { return currentBackend$.asObservable(); } export function getURLParams() { return [...new URLSearchParams(location.search)].reduce((acc, [key, value]) => { acc[key] = value; return acc; }, {}); } ================================================ FILE: public/assets/pages/connectpage/ctrl_poweredby.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; import { transition } from "../../lib/animate.js"; import t from "../../locales/index.js"; import config$ from "./model_config.js"; export default async function(render) { const hasFork = await config$.pipe(rxjs.filter(({ license }) => license === "agpl")).toPromise(); if (!hasFork) return; await new Promise((done) => setTimeout(done, 1000)); render(transition(createElement(` <div class="component_poweredbyfilestash"> ${t("Powered by")} <strong><a href="https://www.filestash.app">Filestash</a></strong> <style> .component_poweredbyfilestash{ display: inline-block; color: rgba(0, 0, 0, 0.4); font-size: 0.9em; line-height: 20px; position: fixed; bottom: 10px; right: 20px; } .component_poweredbyfilestash strong{ font-weight: normal; } .component_poweredbyfilestash strong a{ text-decoration: underline; } .dark-mode .component_poweredbyfilestash { color: var(--light); } </style> </div> `))); } ================================================ FILE: public/assets/pages/connectpage/model_backend.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; export default ajax({ url: "api/backend", responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), rxjs.shareReplay(1), ); ================================================ FILE: public/assets/pages/connectpage/model_config.js ================================================ import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; export default ajax({ url: "api/config", responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result), rxjs.shareReplay(1), ); ================================================ FILE: public/assets/pages/ctrl_adminpage.js ================================================ import { navigate } from "../lib/skeleton/index.js"; import { toHref } from "../lib/skeleton/router.js"; import AdminOnly from "./adminpage/decorator_admin_only.js"; export default AdminOnly(function() { navigate(toHref("/admin/storage")); }); ================================================ FILE: public/assets/pages/ctrl_connectpage.css ================================================ .component_page_connect{ background: var(--primary); height: 100%; padding: 15px 0 0 0; } .dark-mode .component_page_connect{ background: var(--bg-color); } ================================================ FILE: public/assets/pages/ctrl_connectpage.js ================================================ import { createElement, createRender } from "../lib/skeleton/index.js"; import rxjs, { effect, applyMutation } from "../lib/rx.js"; import { qs } from "../lib/dom.js"; import { CSS } from "../helpers/loader.js"; import ctrlForm from "./connectpage/ctrl_form.js"; import ctrlForkme from "./connectpage/ctrl_forkme.js"; import ctrlPoweredby from "./connectpage/ctrl_poweredby.js"; export default async function(render) { const $page = createElement(` <div class="component_page_connect"> <style>${await CSS(import.meta.url, "ctrl_connectpage.css")}</style> <div data-bind="component_forkme"></div> <div data-bind="centerthis" class="component_page_connection_form component_container" style="max-width:565px;"> <div data-bind="component_form"></div> </div> <div data-bind="component_poweredby"></div> </div> `); render($page); // feature1: forkme & poweredby button ctrlForkme(createRender(qs($page, `[data-bind="component_forkme"]`))); ctrlPoweredby(createRender(qs($page, `[data-bind="component_poweredby"]`))); await new Promise((done) => setTimeout(done, 250)); // feature2: connection form ctrlForm(createRender(qs($page, `[data-bind="component_form"]`))); // feature3: center the form effect(rxjs.fromEvent(window, "resize").pipe( rxjs.startWith(null), rxjs.map(() => { const h = 400; const size = Math.round((document.body.offsetHeight - h) / 2); if (size < 0) return 0; if (size > 150) return 150; return size; }), rxjs.map((size) => ["padding-top", `${size}px`]), applyMutation(qs($page, `[data-bind="centerthis"]`), "style", "setProperty") )); } ================================================ FILE: public/assets/pages/ctrl_error.js ================================================ import { createElement, createRender } from "../lib/skeleton/index.js"; import { toHref, fromHref, navigate } from "../lib/skeleton/router.js"; import { forwardURLParams } from "../lib/path.js"; import rxjs, { effect, applyMutation } from "../lib/rx.js"; import { qs, safe } from "../lib/dom.js"; import t from "../locales/index.js"; import { AjaxError, ApplicationError } from "../lib/error.js"; import "../components/icon.js"; export default function(render) { let hasBack = window.self === window.top; if (!render) { render = createRender(document.body); try { render = createRender(qs(document.body, "#app")); } catch (err) { hasBack = false; } } return function(err) { const [msg, trace] = processError(err); const shouldRedirectLogin = err instanceof AjaxError && err.err().status === 401; let link = ""; if (hasBack) { link = forwardURLParams(calculateBacklink(fromHref(window.location.pathname)), ["share"]); if (shouldRedirectLogin) { link = fromHref("/login?next=" + encodeURIComponent(forwardURLParams(fromHref(window.location.pathname), ["share"]))); } } const $page = createElement(` <div> <style>${css}</style> <a href="${link}" class="backnav ${!hasBack && "hidden"}"> <component-icon name="arrow_left"></component-icon> ${t("home")} </a> <div class="component_container"> <div class="error-page"> <h1>${t("Oops!")}</h1> <h2>${t(safe(msg))}</h2> <p> <button class="light" data-bind="details">${t("More details")}</button> <button class="primary" data-bind="refresh">${t("Reload")}</button> <pre class="hidden"><code>${formatTrace(trace)}</code></pre> </p> </div> </div> </div> `); render($page); // feature: show error details effect(rxjs.fromEvent(qs($page, "button[data-bind=\"details\"]"), "click").pipe( rxjs.mapTo(["hidden"]), applyMutation(qs($page, "pre"), "classList", "toggle") )); // feature: refresh button const shouldHideRefreshButton = location.pathname === "/"; const $refresh = qs($page, "button[data-bind=\"refresh\"]"); if (shouldHideRefreshButton) $refresh.remove(); else effect(rxjs.fromEvent($refresh, "click").pipe( rxjs.tap(() => shouldRedirectLogin ? navigate(link) : location.reload()), )); return rxjs.EMPTY; }; } function processError(err) { let msg, trace; if (err instanceof AjaxError) { msg = t(err.message); trace = ` type: ${err.type()} code: ${err.code()} message: ${err.message} trace: ${err.stack}`; } else if (err instanceof ApplicationError) { msg = t(err.message); trace = ` type: ${err.type()} debug: ${err.debug()} trace: ${err.stack}`; } else { msg = t("Internal Error"); trace = ` type: Error message: ${err.message} trace: ${err.stack || "N/A"}`; } return [msg, trace.trim()]; } function formatTrace(str) { return str .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll(" ", " ") .split("\n") .map((line) => line.indexOf("/lib/vendor/") === -1 ? line : `<span style="opacity:0.25">${line}</span>`) .join("\n"); } const css = ` .error-page { width: 80%; max-width: 600px; margin: 50px auto 0 auto; flex-direction: column; } .error-page h1 { margin: 5px 0; font-size: 3.1em; } .error-page h2 { margin: 10px 0; font-weight: normal; font-weight: 100; } .error-page button { padding-left: 10px; padding-right: 10px; line-height: 1.2rem; text-transform: capitalize; } .error-page code { margin-top: 5px; display: block; padding: 10px; overflow-x: auto; overflow-y: auto; background: #e2e2e2; color: var(--dark); border-radius: 3px; max-height: 350px; } .error-page pre { margin: 0; } .error-page p { font-style: italic; margin-bottom: 5px; } .error-page a { border-bottom: 1px dashed; } .backnav { display: inline-block; padding: 10px 5px; } .backnav .component_icon { height: 23px; margin-right: -3px; vertical-align: middle; } `; function calculateBacklink(pathname = "") { let url = "/"; const listPath = pathname.replace(new RegExp("/$"), "").split("/"); switch (listPath[1]) { case "view": // in view mode, navigate to current folder listPath[1] = "files"; listPath.pop(); url = listPath.join("/") + "/"; break; case "files": // in file browser mode, navigate to parent folder listPath.pop(); url = listPath.join("/") + "/"; break; } return toHref(url === "/files/" ? "/" : url); } ================================================ FILE: public/assets/pages/ctrl_filespage.css ================================================ .component_page_filespage { display: flex; flex-direction: column; height: auto; min-height: 100%; width: 100%; background: var(--bg-color); box-sizing: border-box; } body:not(.dark-mode) .component_page_filespage { background: linear-gradient(177deg, rgba(250,250,250,0.3) 0%, var(--bg-color) 30%); } .scroll-y { flex: 1; overflow-y: scroll !important; overflow-x: hidden !important; -webkit-overflow-scrolling: touch; } ================================================ FILE: public/assets/pages/ctrl_filespage.js ================================================ import { createElement, createRender, onDestroy } from "../lib/skeleton/index.js"; import { navigate } from "../lib/skeleton/router.js"; import { qs } from "../lib/dom.js"; import assert from "../lib/assert.js"; import { loadCSS } from "../helpers/loader.js"; import WithShell, { init as initShell } from "../components/decorator_shell_filemanager.js"; import t from "../locales/index.js"; import componentFilesystem, { init as initFilesystem } from "./filespage/ctrl_filesystem.js"; import componentSubmenu, { init as initSubmenu } from "./filespage/ctrl_submenu.js"; import componentNewItem, { init as initNewItem } from "./filespage/ctrl_newitem.js"; import componentUpload, { init as initUpload } from "./filespage/ctrl_upload.js"; import { init as initState } from "./filespage/state_config.js"; import { init as initThing } from "./filespage/thing.js"; import "../components/breadcrumb.js"; export default WithShell(function(render) { if (new RegExp("/$").test(location.pathname) === false) { navigate(location.pathname + "/"); return; } const $page = createElement(` <div class="component_page_filespage scroll-y"> <div is="component_upload"></div> <div is="component_submenu"></div> <div is="component_newitem"></div> <div is="component_filesystem"></div> </div> `); render($page); // feature1: render the filesystem componentFilesystem(createRender(qs($page, "[is=\"component_filesystem\"]"))); // feature2: render the menubar componentSubmenu(createRender(qs($page, "[is=\"component_submenu\"]"))); // feature3: render the creation menu componentNewItem(createRender(qs($page, "[is=\"component_newitem\"]"))); // feature4: render the upload button componentUpload(createRender(qs($page, "[is=\"component_upload\"]"))); // feature5: accessibility / skip links const $skip = createElement(`<a aria-role="navigation" href="#main">${t("Skip to content")}</a>`); $skip.onclick = (e) => { e.preventDefault(); const $content = document.querySelector("main a"); if ($content) assert.type($content, HTMLElement).focus(); }; document.body.prepend($skip); onDestroy(() => $skip.remove()); }); export function init() { return Promise.all([ loadCSS(import.meta.url, "ctrl_filespage.css"), initShell(), initFilesystem(), initSubmenu(), initNewItem(), initUpload(), initState(), initThing(), ]); } ================================================ FILE: public/assets/pages/ctrl_homepage.js ================================================ import { createElement, navigate } from "../lib/skeleton/index.js"; import { toHref } from "../lib/skeleton/router.js"; import rxjs, { effect } from "../lib/rx.js"; import { ApplicationError, AjaxError } from "../lib/error.js"; import { forwardURLParams } from "../lib/path.js"; import ctrlError from "./ctrl_error.js"; import { getSession } from "../model/session.js"; import "../components/loader.js"; export default function(render) { render(createElement("<component-loader></component-loader>")); // feature1: trigger error page via url params const GET = new URLSearchParams(location.search); const err = GET.get("error"); if (err) { ctrlError(render)(new ApplicationError( err, GET.get("trace") || "server error from URL", )); return; } // feature2: redirect user where it makes most sense effect(getSession().pipe( rxjs.catchError((err) => { if (err instanceof AjaxError && err.err().status === 401) { return rxjs.of({ is_authenticated: false }); } return rxjs.throwError(err); }), rxjs.tap(({ is_authenticated, home = "/" }) => { if (is_authenticated !== true) return navigate(forwardURLParams(toHref("/login"), ["share", "next"])); return navigate(forwardURLParams(toHref(`/files${home}`), ["share"])); }), rxjs.catchError(ctrlError(render)), )); }; ================================================ FILE: public/assets/pages/ctrl_logout.js ================================================ import { navigate } from "../lib/skeleton/index.js"; import { toHref } from "../lib/skeleton/router.js"; import rxjs, { effect } from "../lib/rx.js"; import { deleteSession } from "../model/session.js"; import { init as setup_config, get as getConfig } from "../model/config.js"; import ctrlError from "./ctrl_error.js"; import $loader from "../components/loader.js"; export default function(render) { render($loader); effect(deleteSession().pipe( rxjs.mergeMap(setup_config), rxjs.tap(() => { while (hooks.length) hooks.pop()(); }), rxjs.tap(() => getConfig("logout") ? location.href = getConfig("logout") : navigate(toHref("/"))), rxjs.catchError(ctrlError(render)), )); } const hooks = []; export function onLogout(fn) { hooks.push(fn); } ================================================ FILE: public/assets/pages/ctrl_notfound.js ================================================ import ctrlError from "./ctrl_error.js"; import { ApplicationError } from "../lib/error.js"; export default function(render) { ctrlError(render)(new ApplicationError("Not Found", "404 - Not Found")); } ================================================ FILE: public/assets/pages/ctrl_sharepage.css ================================================ .component_page_sharelogin { max-width: 300px; } ================================================ FILE: public/assets/pages/ctrl_sharepage.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import rxjs, { effect, stateMutation, applyMutation, preventDefault } from "../lib/rx.js"; import { qs } from "../lib/dom.js"; import { navigate, toHref } from "../lib/skeleton/router.js"; import { transition, zoomIn } from "../lib/animate.js"; import ajax from "../lib/ajax.js"; import { AjaxError } from "../lib/error.js"; import { basename } from "../lib/path.js"; import { loadCSS } from "../helpers/loader.js"; import { isDir } from "../pages/filespage/helper.js"; import assert from "../lib/assert.js"; import t from "../locales/index.js"; import notification from "../components/notification.js"; import ctrlError from "./ctrl_error.js"; export default function(render) { const shareID = location.pathname.replace(toHref("/s/"), ""); const state$ = new rxjs.BehaviorSubject({ step: null }); const setState = (newState) => state$.next(newState); effect(state$.asObservable().pipe(rxjs.mergeMap(({ step, ...state }) => { if (step === null) return verify(render, { shareID, setState, body: null }); else if (step === "password") return ctrlPassword(render, { shareID, setState }); else if (step === "email") return ctrlEmail(render, { shareID, setState }); else if (step === "code") return ctrlEmailCodeVerification(render, { shareID, setState }); else if (step === "done") { if (isDir(state["path"])) navigate(toHref(`/files/?share=${shareID}`)); else navigate(toHref(`/view/${encodeURIComponent(basename(state["path"]))}?share=${shareID}&nav=false`)); return rxjs.EMPTY; } else assert.fail(`unknown step: "${step}"`); }), rxjs.catchError(ctrlError()))); } function ctrlPassword(render, { shareID, setState }) { const $page = createElement(` <div class="component_container component_page_sharelogin"> <form> <div class="input_group"> <input type="password" name="password" placeholder="${t("Password")}" class="component_input" autocomplete> <button class="transparent"> <component-icon name="arrow_right"></component-icon> </button> </div> </form> </div> `); return ctrlAbstract(render, { shareID, setState, $page }); } function ctrlEmail(render, { shareID, setState }) { const $page = createElement(` <div class="component_container component_page_sharelogin"> <form> <div class="input_group"> <input type="email" name="email" placeholder="${t("Your email address")}" class="component_input" autocomplete> <button class="transparent"> <component-icon name="arrow_right"></component-icon> </button> </div> </form> </div> `); return ctrlAbstract(render, { shareID, setState, $page }); } function ctrlEmailCodeVerification(render, { shareID, setState }) { const $page = createElement(` <div class="component_container component_page_sharelogin"> <form> <div class="input_group"> <input type="text" name="code" placeholder="${t("Code")}" class="component_input" autocomplete> <button class="transparent"> <component-icon name="arrow_right"></component-icon> </button> </div> </form> </div> `); return ctrlAbstract(render, { shareID, setState, $page }); } function verify(_, { shareID, setState, body }) { return ajax({ method: "POST", url: `api/share/${shareID}/proof`, responseType: "json", body, }).pipe(rxjs.mergeMap(({ responseJSON }) => { const { key = "", path } = responseJSON.result; if (key === "") setState({ step: "done", path }); else setState({ step: key }); return rxjs.of(!("error" in responseJSON.result)); })); } export async function init() { return loadCSS(import.meta.url, "./ctrl_sharepage.css"); } function ctrlAbstract(render, { shareID, setState, $page }) { // feature: nice transition render(transition($page, { timeEnter: 250, enter: zoomIn(1.2), timeLeave: 0, })); effect(rxjs.fromEvent(qs($page, "form"), "submit").pipe( preventDefault(), // STEP1: loading spinner rxjs.mapTo(["name", "loading"]), applyMutation(qs($page, "component-icon"), "setAttribute"), // STEP2: attempt to login rxjs.map(() => ({ type: qs($page, "input").name, value: qs($page, "input").value })), rxjs.switchMap((creds) => verify(render, { shareID, body: creds, setState }).pipe(rxjs.catchError((err) => { if (err instanceof AjaxError) { switch (err.code()) { case "INTERNAL_SERVER_ERROR": return rxjs.throwError(err); case "FORBIDDEN": return rxjs.of(false); } } notification.error(err && err.message); return rxjs.of(false); }))), // STEP3: update the UI when authentication fails, happy path is handle at the middleware // level one layer above as the login ctrl has no idea what to show after login rxjs.filter((ok) => !ok), rxjs.mapTo(["name", "arrow_right"]), applyMutation(qs($page, "component-icon"), "setAttribute"), rxjs.mapTo(""), stateMutation(qs($page, "input"), "value"), rxjs.mapTo(["error"]), applyMutation(qs($page, ".input_group"), "classList", "add"), rxjs.delay(300), applyMutation(qs($page, ".input_group"), "classList", "remove"), rxjs.catchError(ctrlError(render)), )); // feature: autofocus effect(rxjs.of(null).pipe( applyMutation(qs($page, "input"), "focus") )); // feature: vertically center the form effect(rxjs.fromEvent(window, "resize").pipe( rxjs.startWith(null), rxjs.map(() => ["margin-top", `${Math.floor(window.innerHeight / 3)}px`]), applyMutation($page, "style", "setProperty") )); return rxjs.EMPTY; } ================================================ FILE: public/assets/pages/ctrl_viewerpage.css ================================================ .component_page_viewerpage { width: 100%; display: flex; } .component_page_viewerpage > div { width: 100%; display: flex; flex-direction: column; } ================================================ FILE: public/assets/pages/ctrl_viewerpage.js ================================================ import { createElement, createRender } from "../lib/skeleton/index.js"; import rxjs, { effect } from "../lib/rx.js"; import { ApplicationError } from "../lib/error.js"; import { basename } from "../lib/path.js"; import assert from "../lib/assert.js"; import { get as getConfig } from "../model/config.js"; import { loadCSS } from "../helpers/loader.js"; import WithShell, { init as initShell } from "../components/decorator_shell_filemanager.js"; import { init as initMenubar } from "./viewerpage/component_menubar.js"; import { init as initCache } from "./filespage/cache.js"; import ctrlError from "./ctrl_error.js"; import { getFilename, getDownloadUrl, getCurrentPath } from "./viewerpage/common.js"; import { opener } from "./viewerpage/mimetype.js"; import { options } from "./viewerpage/model_files.js"; import "../components/breadcrumb.js"; function loadModule(appName) { switch (appName) { case "editor": return import("./viewerpage/application_editor.js"); case "pdf": return import("./viewerpage/application_pdf.js"); case "image": return import("./viewerpage/application_image.js"); case "download": return import("./viewerpage/application_downloader.js"); case "form": return import("./viewerpage/application_form.js"); case "audio": return import("./viewerpage/application_audio.js"); case "video": return import("./viewerpage/application_video.js"); case "ebook": return import("./viewerpage/application_ebook.js"); case "3d": return import("./viewerpage/application_3d.js"); case "appframe": return import("./viewerpage/application_iframe.js"); case "map": return import("./viewerpage/application_map.js"); case "url": return import("./viewerpage/application_url.js"); case "table": return import("./viewerpage/application_table.js"); case "skeleton": return import("./viewerpage/application_skeleton.js"); default: throw new ApplicationError("Internal Error", `Unknown opener app "${appName}" at "${getCurrentPath()}"`); } }; const loadModuleWithMemory = (function() { const memory = {}; return function(appName) { if (memory[appName]) return Promise.resolve(memory[appName]); return loadModule(appName).then((module) => { memory[appName] = module; return module; }); }; })(); export default WithShell(async function(render) { const $page = createElement(`<div class="component_page_viewerpage"></div>`); render($page); // feature: render viewer application effect(rxjs.of(getConfig("mime", {})).pipe( rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)), rxjs.mergeMap(([opener, opts]) => rxjs.from(loadModuleWithMemory(opener)).pipe(rxjs.switchMap(async(module) => { module.default(createRender($page), { ...opts, acl$: options(), getFilename, getDownloadUrl }); }))), rxjs.catchError(ctrlError()), )); // feature: cleanup up the design when navbar is not there effect(rxjs.of(new URL(location.toString()).searchParams.get("nav")).pipe( rxjs.filter((value) => value === "false"), rxjs.tap(() => { const $parent = assert.truthy($page.parentElement); $parent.style.border = "none"; $parent.style.borderRadius = "0"; }), )); }); export async function init() { return Promise.all([ loadCSS(import.meta.url, "./ctrl_viewerpage.css"), initShell(), initMenubar(), initCache(), rxjs.of(getConfig("mime", {})).pipe( rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)), rxjs.mergeMap(([opener]) => loadModule(opener)), rxjs.mergeMap((module) => typeof module.init === "function"? module.init() : rxjs.EMPTY), rxjs.catchError(() => rxjs.EMPTY), ).toPromise(), ]); } ================================================ FILE: public/assets/pages/filespage/cache.js ================================================ import assert from "../../lib/assert.js"; import { getSession } from "../../model/session.js"; import { onLogout } from "../ctrl_logout.js"; class ICache { /** * @param {string} _path * @return {Promise<any>} */ async get(_path) { throw new Error("NOT_IMPLEMENTED"); } /** * @param {string} _path * @param {any} _data * @return {Promise<void>} */ async store(_path, _data) { throw new Error("NOT_IMPLEMENTED"); } /** * @return {Promise<void>} */ async remove() { throw new Error("NOT_IMPLEMENTED"); } /** * @param {string} path * @param {function(any): any} fn * @return {Promise<void>} */ async update(path, fn) { const data = await this.get(path); return this.store(path, fn(data || {})); } /** * @return {Promise<void>} */ async destroy() { throw new Error("NOT_IMPLEMENTED"); } } class InMemoryCache extends ICache { constructor() { super(); this.data = {}; } /** * @override */ async get(path) { return this.data[this._key(path)] || null; } /** * @override */ async store(path, obj) { this.data[this._key(path)] = obj; } /** * @override */ async remove(path, exact = true) { if (!path) { this.data = {}; return; } const key = this._key(path); if (exact) { delete this.data[key]; return; } for (const k in this.data) { if (k.indexOf(key) === 0) { delete this.data[k]; } } } /** * @override */ async destroy() { this.data = {}; } _key(path) { return currentBackend() + "::" + currentShare() + "::" + path; } }; class IndexDBCache extends ICache { DB_VERSION = 5; FILE_PATH = "file_path"; /** @type {Promise<IDBDatabase> | null} */ db = null; constructor() { super(); const request = indexedDB.open("filestash", this.DB_VERSION); request.onupgradeneeded = this._migration.bind(this); this.db = new Promise((done, err) => { request.onsuccess = (e) => { done(assert.truthy(e.target).result); }; request.onerror = () => err(new Error("INDEXEDDB_NOT_SUPPORTED")); }); } /** * @override */ async get(path) { const db = assert.truthy(await this.db); const tx = db.transaction(this.FILE_PATH, "readonly"); const store = tx.objectStore(this.FILE_PATH); const query = store.get(this._key(path)); return await new Promise((done) => { query.onsuccess = () => done(query.result || null); query.onerror = () => done(null); }); } /** * @override */ async store(path, value = {}) { const db = assert.truthy(await this.db); const tx = db.transaction(this.FILE_PATH, "readwrite"); const store = tx.objectStore(this.FILE_PATH); const request = store.put({ ...value, backend: currentBackend(), share: currentShare(), path, }); return await new Promise((done, error) => { done(value); request.onsuccess = () => done(value); request.onerror = error; }); } /** * @override */ async remove(path, exact = true) { const db = assert.truthy(await this.db); const tx = db.transaction(this.FILE_PATH, "readwrite"); const store = tx.objectStore(this.FILE_PATH); const key = this._key(path); if (exact !== true) { const request = store.openCursor(IDBKeyRange.bound( [key[0], key[1], key[2]], [key[0], key[1], key[2]+"\u{FFFF}".repeat(5000)], true, true, )); await new Promise((done, err) => { request.onsuccess = function(event) { const cursor = event.target.result; if (cursor) { cursor.delete([key[0], key[1], cursor.value.path]); cursor.continue(); return; } done(null); }; request.onerror = err; }); } const req = store.delete(key); return await new Promise((done, err) => { req.onsuccess = () => done(null); req.onerror = err; }); } _key(path) { return [currentBackend(), currentShare(), path]; } _migration(event) { const db = event.target.result; if (event.oldVersion === 1) { // we've change the schema on v2 adding an index, let's flush // to make sure everything will be fine db.deleteObjectStore("file_path"); db.deleteObjectStore("file_content"); } else if (event.oldVersion === 2) { // we've change the primary key to be a (path,share) db.deleteObjectStore("file_path"); db.deleteObjectStore("file_content"); } else if (event.oldVersion === 3) { // we've added a FILE_TAG to store tag related data and update // keyPath to have "backend" db.deleteObjectStore("file_path"); db.deleteObjectStore("file_content"); } else if (event.oldVersion === 4) { // we got rid of the idea of offline first file manager so let's get rid of // FILE_CONTENT and FILE_TAG db.deleteObjectStore("file_path"); db.deleteObjectStore("file_content"); db.deleteObjectStore("file_tag"); } const store = db.createObjectStore(this.FILE_PATH, { keyPath: ["backend", "share", "path"] }); store.createIndex("idx_path", ["backend", "share", "path"], { unique: true }); } } let cache = null; export async function clearCache(path) { // TODO: remove useless function await cache.remove(path, false); } export async function init() { const setup_cache = () => { cache = new InMemoryCache(); if (!("indexedDB" in window)) return; else if (window.self !== window.top) return; cache = assert.truthy(new IndexDBCache()); return cache.db.catch((err) => { if (err.message === "INDEXEDDB_NOT_SUPPORTED") { // Firefox in private mode act like if it supports indexedDB but // is throwing that string as an error if you try to use it ... // so we fallback with our basic ram cache cache = new InMemoryCache(); return; } throw err; }); }; const setup_session = async() => { if (!backendID) { try { const session = await getSession().toPromise(); backendID = session.backendID; onLogout(() => backendID = ""); } catch (err) {} } }; return Promise.all([setup_cache(), setup_session()]); } export default function() { return cache; }; let backendID = ""; export function currentBackend() { return backendID; } export function updateBackend(id) { backendID = id; } export function currentShare() { return new window.URL(location.href).searchParams.get("share") || ""; } ================================================ FILE: public/assets/pages/filespage/ctrl_filesystem.css ================================================ [is="component_filesystem"] .component_filesystem { padding-top: 3px; } [is="component_filesystem"] .component_filesystem [data-target="dragselect"] { position: fixed; border-radius: 2px; z-index: 3; background: var(--primary); border: 2px solid rgba(0, 0, 0, 0.1); opacity: 0.25; width: 500px; height: 100px; } [is="component_filesystem"] .component_filesystem [data-type="list"] { grid-gap: 2px; } [is="component_filesystem"] .component_filesystem [data-type="grid"] { grid-gap: 5px; } @media screen and (min-width: 1100px) { .component_filemanager_shell [data-bind="sidebar"]:not(.hidden) ~ div [is="component_filesystem"] .component_filesystem [data-type="grid"] { grid-gap: 8px; } } [is="component_filesystem"] .empty { text-align: center; font-weight: 100; margin: 0 auto; opacity: 0.9; font-size: 1.2rem; color: var(--light); height: 100%; opacity: 0.8; font-family: sans-serif; padding: 50px 0; @media screen and (max-width: 420px) { padding: 30px; } } [is="component_filesystem"] .empty .component_icon { height: 200px; max-width: 100%; } [is="component_filesystem"] .empty .label { margin-top: -40px; } ================================================ FILE: public/assets/pages/filespage/ctrl_filesystem.js ================================================ import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js"; import { animate, slideYIn } from "../../lib/animate.js"; import rxjs, { effect, preventDefault } from "../../lib/rx.js"; import assert from "../../lib/assert.js"; import { loadCSS } from "../../helpers/loader.js"; import { qs } from "../../lib/dom.js"; import { ApplicationError } from "../../lib/error.js"; import { createLoader } from "../../components/loader.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import { currentPath, sort, isMobile, isAlreadyFocused } from "./helper.js"; import { createThing } from "./thing.js"; import { clearSelection, addSelection, getSelection$, isSelected } from "./state_selection.js"; import { getState$ } from "./state_config.js"; import { ls, search } from "./model_files.js"; import { getPermission } from "./model_acl.js"; const ICONS = { EMPTY_FILES: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjE3MCIgdmlld0JveD0iMCAwIDMwMCAxNzAiIGZpbGw9Im5vbmUiPgogIDxwYXRoCiAgICAgZD0ibSA1Mi42Mjk5MDUsMTYwLjE2Nzg1IGMgMS41NDYsMCAyLjgsLTEuMjUzNiAyLjgsLTIuOCAwLC0xLjU0NjQgLTEuMjU0LC0yLjggLTIuOCwtMi44IC0xLjU0NywwIC0yLjgsMS4yNTM2IC0yLjgsMi44IDAsMS41NDY0IDEuMjUzLDIuOCAyLjgsMi44IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjciCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMiIC8+CiAgPHBhdGgKICAgICBkPSJtIDExMy4wMzAxOCwyMi4zOTM2NDkgYyAxLjU0NjQsMCAyLjgsLTEuMjUzNiAyLjgsLTIuOCAwLC0xLjU0NjQgLTEuMjUzNiwtMi44IC0yLjgsLTIuOCAtMS41NDY0LDAgLTIuOCwxLjI1MzYgLTIuOCwyLjggMCwxLjU0NjQgMS4yNTM2LDIuOCAyLjgsMi44IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjkiCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMiIC8+CiAgPHBhdGgKICAgICBkPSJtIDczLjI5MzU2NSwxMTIuNDYyMTYgYyAyLjg3MTg5LDAgNS4yMDAwMywtMi4zMjgxIDUuMjAwMDMsLTUuMiAwLC0yLjg3MTkgLTIuMzI4MTQsLTUuMiAtNS4yMDAwMywtNS4yIC0yLjg3MTg4LDAgLTUuMTk5OTk1LDIuMzI4MSAtNS4xOTk5OTUsNS4yIDAsMi44NzE5IDIuMzI4MTE1LDUuMiA1LjE5OTk5NSw1LjIgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoNTA3MSIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMzM0IiAvPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM1MTE3Ij4KICAgIDxmaWx0ZXIKICAgICAgIGlkPSJmaWx0ZXIwX2QiCiAgICAgICB4PSIxNi4wNzYyIgogICAgICAgeT0iMTIuOTU3NSIKICAgICAgIHdpZHRoPSIxMTQuOCIKICAgICAgIGhlaWdodD0iMTM0LjYwMDAxIgogICAgICAgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgICAgPGZlRmxvb2QKICAgICAgICAgZmxvb2Qtb3BhY2l0eT0iMCIKICAgICAgICAgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiCiAgICAgICAgIGlkPSJmZUZsb29kNTA5NyIgLz4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICAgaW49IlNvdXJjZUFscGhhIgogICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiCiAgICAgICAgIGlkPSJmZUNvbG9yTWF0cml4NTA5OSIgLz4KICAgICAgPGZlT2Zmc2V0CiAgICAgICAgIGR5PSIxMSIKICAgICAgICAgaWQ9ImZlT2Zmc2V0NTEwMSIgLz4KICAgICAgPGZlR2F1c3NpYW5CbHVyCiAgICAgICAgIHN0ZERldmlhdGlvbj0iMTEiCiAgICAgICAgIGlkPSJmZUdhdXNzaWFuQmx1cjUxMDMiIC8+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICAgdmFsdWVzPSIwIDAgMCAwIDAuMzk3NzA4IDAgMCAwIDAgMC40Nzc0OSAwIDAgMCAwIDAuNTc1IDAgMCAwIDAuMjcgMCIKICAgICAgICAgaWQ9ImZlQ29sb3JNYXRyaXg1MTA1IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluMj0iQmFja2dyb3VuZEltYWdlRml4IgogICAgICAgICByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA3IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgICBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgcmVzdWx0PSJzaGFwZSIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA5IiAvPgogICAgPC9maWx0ZXI+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJwYWludDBfbGluZWFyIgogICAgICAgeDE9IjczLjQ1MzEwMiIKICAgICAgIHkxPSIyMS44NjE5IgogICAgICAgeDI9IjczLjQ1MzEwMiIKICAgICAgIHkyPSIxMTUuNTM0IgogICAgICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcAogICAgICAgICBzdG9wLWNvbG9yPSIjRkRGRUZGIgogICAgICAgICBpZD0ic3RvcDUxMTIiIC8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC45OTY0IgogICAgICAgICBzdG9wLWNvbG9yPSIjRUNGMEY1IgogICAgICAgICBpZD0ic3RvcDUxMTQiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGZpbHRlcgogICAgICAgaWQ9ImZpbHRlcjBfZC03IgogICAgICAgeD0iMC4zOTExMTMwMSIKICAgICAgIHk9IjM1LjM5NDc5OCIKICAgICAgIHdpZHRoPSIxNDUuNTk1IgogICAgICAgaGVpZ2h0PSIxMDIuOCIKICAgICAgIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgICAgIDxmZUZsb29kCiAgICAgICAgIGZsb29kLW9wYWNpdHk9IjAiCiAgICAgICAgIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4IgogICAgICAgICBpZD0iZmVGbG9vZDEyMjciIC8+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgIGluPSJTb3VyY2VBbHBoYSIKICAgICAgICAgdHlwZT0ibWF0cml4IgogICAgICAgICB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIgogICAgICAgICBpZD0iZmVDb2xvck1hdHJpeDEyMjkiIC8+CiAgICAgIDxmZU9mZnNldAogICAgICAgICBkeT0iMTEiCiAgICAgICAgIGlkPSJmZU9mZnNldDEyMzEiIC8+CiAgICAgIDxmZUdhdXNzaWFuQmx1cgogICAgICAgICBzdGREZXZpYXRpb249IjExIgogICAgICAgICBpZD0iZmVHYXVzc2lhbkJsdXIxMjMzIiAvPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgIHZhbHVlcz0iMCAwIDAgMCAwLjM5NzcwOCAwIDAgMCAwIDAuNDc3NDkgMCAwIDAgMCAwLjU3NSAwIDAgMCAwLjI3IDAiCiAgICAgICAgIGlkPSJmZUNvbG9yTWF0cml4MTIzNSIgLz4KICAgICAgPGZlQmxlbmQKICAgICAgICAgbW9kZT0ibm9ybWFsIgogICAgICAgICBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIKICAgICAgICAgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3ciCiAgICAgICAgIGlkPSJmZUJsZW5kMTIzNyIgLz4KICAgICAgPGZlQmxlbmQKICAgICAgICAgbW9kZT0ibm9ybWFsIgogICAgICAgICBpbj0iU291cmNlR3JhcGhpYyIKICAgICAgICAgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3ciCiAgICAgICAgIHJlc3VsdD0ic2hhcGUiCiAgICAgICAgIGlkPSJmZUJsZW5kMTIzOSIgLz4KICAgIDwvZmlsdGVyPgogIDwvZGVmcz4KICA8cGF0aAogICAgIGQ9Im0gMjEuMjIwODMyLDkyLjI2NDAxNiBoIC00LjkgYyAtMC4zLDAgLTAuNiwwLjEgLTAuNywwLjQgbCAtMi40LDQuMyBjIC0wLjEsMC4zIC0wLjEsMC42IDAsMC45IGwgMi40LDQuMzAwMDA0IGMgMC4xLDAuMyAwLjQsMC40IDAuNywwLjQgaCA0LjkgYyAwLjMsMCAwLjYsLTAuMSAwLjcsLTAuNCBsIDIuNCwtNC4zMDAwMDQgYyAwLjEsLTAuMyAwLjEsLTAuNiAwLC0wLjkgbCAtMi40LC00LjMgYyAtMC4xLC0wLjMgLTAuNCwtMC40IC0wLjcsLTAuNCB6IgogICAgIGlkPSJwYXRoNCIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMyIgLz4KICA8cGF0aAogICAgIGNsYXNzPSJmaWxlIgogICAgIGQ9Im0gMjcxLjA5MjU5LDExLjY1MjMxMiAtNy4zNzAxOSwzLjU4MzkxOCBjIC0wLjc2MTk0LDAuNDMxMTE2IC0xLjcwNDkxLDAuNTgzNTAxIC0yLjU3MjQ2LDAuNjA1MjIyIC0wLjg2NzU0LDAuMDIxNzIgLTEuNzg3OTEsLTAuMTYxMjQ0IC0yLjYzMjg1LC0wLjQ3NDg2NyAtMS42MTQ0NCwtMC43NTc4OTggLTMuMDU1NCwtMi4xMTI0NDIgLTMuOTM4MTUsLTMuODQxNTA5IDAsMCAtMS41MDg5NiwtMy4zMTAwMzcyIC0zLjA3MDc1LC02LjgyNDc3NDUgbCAtMC4yMzM4OCwtMC40ODM0MzY4IC0xNS45NjI1Myw3LjUwNzQ2OTMgYyAtMi41NDIyMywxLjE0NTI3MSAtMy42MTMyOSw0LjE4NTE0NCAtMi41MTkyOCw2LjczMjk5OCBsIDExLjUyODU1LDI1LjY0NDA3NCBjIDEuMDk0MDIsMi41NDc4NTUgNC4wODksMy41ODAyMDUgNi42MzEyNSwyLjQzNDkzMSBsIDI1LjU5NTg0LC0xMi4wNDk0MDIgYyAyLjU0MjI0LC0xLjE0NTI3NSAzLjYxMzMxLC00LjE4NTE0MyAyLjUxOTMsLTYuNzMyOTk4IEwgMjcxLjcwMzY1LDExLjQ4MjQ5IFoiCiAgICAgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNDtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6MS40OTQ3MSIKICAgICBpZD0icGF0aDYiIC8+CiAgPHBhdGgKICAgICBjbGFzcz0iZmlsZSIKICAgICBkPSJtIDI1OS40MTQ2OCwxMi45MjMzNjggYyAtMS4xMDE0NCwtMC40NjE3MTMgLTEuOTc2NTksLTEuMzE1MzggLTIuNjI1NDMsLTIuNTYxMDA1IGwgLTAuMDUyOCwtMC4yMDQ3IC0wLjI4NjY4LC0wLjY4ODE0NzIgLTEuMDQxMTksLTIuMzQzMTU4IC0wLjU3MzQxLC0xLjM3NjI3NiAxMy4wMDU5OSw0LjcyMTc3MDIgLTUuMTU5OTMsMi40MjEyMDIgYyAtMC40ODI3NywwLjI0Mzg2NiAtMS4wMTg0MSwwLjI4MzAyOCAtMS41NTQwMSwwLjMyMjE5NiAtMC41MzU2NCwwLjAzOTE2IC0xLjEyNDA2LC0wLjEyNjM2NCAtMS43MTI0OSwtMC4yOTE4OTkgeiIKICAgICBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMzM0O2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDoxLjQ5NDcxIgogICAgIGlkPSJwYXRoOCIgLz4KICA8cGF0aAogICAgIGQ9Im0gMjMuOTQ0NDAzLDE3LjYzNTU2MyBjIC0yLjA3NzQxOSwxLjE5OTQxNSAtMi40NzQ2NjksMy44MzI3OTggLTEuMzAzNTc5LDUuODYxMjAyIGwgMTEuMTU3OTY5LDE5LjMyNjEzMyBjIDEuMjY4NjksMi4xOTc0MzUgMy41MzE5NiwyLjkxOTEzNSA1LjY2MjY0LDEuNjg4OTc1IGwgMjQuMzQzMTIsLTE0LjA1NDQ5MSBjIDEuMzg0OTYsLTAuNzk5NjEgMS45MjQxMSwtMy4wNjQxNjkgMC42MjI4OCwtNS4zMTc5MzkgTCA1NC45Mjg1NDMsOC42ODY4NjI3IGMgLTEuMDQwOTksLTEuODAzMDExIC0zLjA3NjU0LC0yLjEzMDMxMyAtNC40NjE0NiwtMS4zMzA3MzQgbCAtMTIuNTE3ODIsNy4yMjcxNjkzIC00Ljk0OTQ0LC0yLjE3NTg2OSB6IgogICAgIGlkPSJwYXRoMTYiCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNDtzdHJva2Utd2lkdGg6MC42MzI1OTQiIC8+CiAgPGcKICAgICBpZD0iZzIwIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4yODEwNCwxMzEuMDczNDYpIgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMTMzMzMzMzQiPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNDIuNCwyMy4yIDguNiw5LjkgYyAwLjMsMC4zIDAuMSwwLjggLTAuMywwLjkgTCAzOCwzNi4yIGMgLTAuNCwwLjEgLTAuNywtMC4zIC0wLjYsLTAuNyBsIDQuMSwtMTIuMSBjIDAuMSwtMC40IDAuNywtMC41IDAuOSwtMC4yIHoiCiAgICAgICBpZD0icGF0aDE4IgogICAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNCIgLz4KICA8L2c+CiAgPHBhdGgKICAgICBkPSJtIDI4Ni4wODgzMiw4NS43MTYxIC0yLDEgYyAtMC4zLDAuMSAtMC42LDAgLTAuOCwtMC4zIGwgLTEsLTIgYyAtMC4xLC0wLjMgMCwtMC42IDAuMywtMC44IGwgMiwtMSBjIDAuMywtMC4xIDAuNiwwIDAuOCwwLjMgbCAxLDIgYyAwLjEsMC4zIDAsMC43IC0wLjMsMC44IHoiCiAgICAgaWQ9InBhdGgxMCIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMyIgLz4KICA8cGF0aAogICAgIGQ9Im0gMTExLjM1ODEsNjcuODkzNDgzIGggNzIuMDk5NyBjIDIuOSwwIDUuMSwyLjIgNS4xLDUuMSB2IDQ1LjY5OTk5NyBjIDAsMi45IC0yLjIsNS4xIC01LjEsNS4xIGggLTcyLjA5OTcgYyAtMi45LDAgLTUuMSwtMi4yIC01LjEsLTUuMSBWIDcyLjk5MzQ4MyBjIDAsLTIuOSAyLjQsLTUuMSA1LjEsLTUuMSB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMTk5IiAvPgogIDxnCiAgICAgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZCkiCiAgICAgaWQ9ImcxMjAzIgogICAgIHN0eWxlPSJmaWx0ZXI6dXJsKCNmaWx0ZXIwX2QtNykiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzYuNDY2OCwzMS4xOTg2ODMpIj4KICAgIDxwYXRoCiAgICAgICBkPSJNIDExNy42OTEsNDYuNDk0OCBIIDg3LjI5MTEgYyAtMywwIC01LjgsMSAtOC4xLDIuOSBsIC04LDYuNSBjIC0yLjIsMS44IC01LjEsMi45IC04LjEsMi45IGggLTM0LjQgYyAtMy41LDAgLTYuMywyLjkgLTYuMyw2LjMgMCwwLjMgMCwwLjYgMC4xLDAuOSBsIDYuMywzMy43IGMgMC41LDMuMjAwMiAzLjIsNS41MDAyIDYuMyw1LjUwMDIgaCA3My41OTk5IGMgMy4yLDAgNS44LC0yLjIgNi4zLC01LjQwMDIgbCA4LjksLTQ2LjEgYyAwLjYsLTMuNSAtMS43LC02LjYgLTUuMiwtNy4zIC0wLjMsMC4xIC0wLjcsMC4xIC0xLDAuMSB6IgogICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgIGlkPSJwYXRoMTIwMSIgLz4KICA8L2c+CiAgPHBhdGgKICAgICBkPSJtIDEzNS4zNTgxLDExMi41OTM1OCBjIDEuOCwwIDMuMywtMS41IDMuMywtMy4zIDAsLTEuOCAtMS41LC0zLjMgLTMuMywtMy4zIC0xLjgsMCAtMy4zLDEuNSAtMy4zLDMuMyAwLDEuOCAxLjUsMy4zIDMuMywzLjMgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIwNSIgLz4KICA8cGF0aAogICAgIGQ9Im0gMTYxLjA1ODEsMTEyLjQ5MzQ4IGMgMS44LDAgMy4zLC0xLjUgMy4zLC0zLjMgMCwtMS44IC0xLjUsLTMuMyAtMy4zLC0zLjMgLTEuOCwwIC0zLjMsMS41IC0zLjMsMy4zIDAsMS45IDEuNSwzLjMgMy4zLDMuMyB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjA3IiAvPgogIDxwYXRoCiAgICAgZD0ibSAxNDQuMTU4LDg4Ljg5MzQ4MSBjIC0zLjYsLTcgLTQuNCwtMTUuMzk5OTk4IC0yLC0yMi45OTk5OTggMi4zLC03LjYgNy44LC0xNC4xIDE0LjYsLTE3LjggMi4xLC0xLjEgNC41LC0yIDYuOSwtMi4xIDIuNCwtMC4xIDUsMC43IDYuNiwyLjcgMS42LDEuOCAxLjksNC44IDAuNiw2LjggLTEuNCwxLjkgLTQuMiwyLjcgLTYuNSwyLjEgLTMuNywtMC43IC02LjcsLTMuNiAtNy42LC03LjEgLTAuOSwtMy41IDAuMywtNy42IDMuMSwtOS45IDEuOCwtMS42IDQuMywtMi41IDYuNiwtMy4yIDExLjE5OTgsLTMuMyAyMy4zOTk4LC0zLjcgMzQuNzk5OCwtMS4yIgogICAgIHN0cm9rZT0iIzU3NTk1YSIKICAgICBzdHJva2Utd2lkdGg9IjIiCiAgICAgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIgogICAgIHN0cm9rZS1kYXNoYXJyYXk9IjQsIDQiCiAgICAgaWQ9InBhdGgxMjA5IiAvPgogIDxwYXRoCiAgICAgZD0ibSAxNTEuMzU4LDExNi4wOTM0OCBoIC02LjIgdiAxLjUgaCA2LjIgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIxMSIgLz4KICA8cGF0aAogICAgIGQ9Im0gMjA3LjI1NzgsMzMuNTkzNDMzIGMgLTAuMSwxLjUgLTAuMiwyLjkgLTEuMywzLjIgLTEuMSwwLjMgLTEuNiwtMC43IC0yLjMsLTIuMSAtMC43LC0xLjMgLTAuMywtMi42OTk5OTYgMC45LC0yLjk5OTk5NiAxLjEsLTAuMyAyLjksMC4xIDIuNywxLjg5OTk5NiB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjEzIiAvPgogIDxwYXRoCiAgICAgZD0ibSAyMDYuMDU3OCw0MC43OTM0NTMgYyAwLjMsLTEuOCAwLjYsLTIuOCAtMC40LC0zLjMgLTEuMSwtMC41IC0xLjgsMC40IC0zLDEuNiAtMSwxLjEgLTAuNCwyLjcwMDAzIDAuNiwzLjIwMDAzIDEuMiwwLjYgMi41LDAgMi44LC0xLjUwMDAzIHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDEyMTUiIC8+CiAgPHBhdGgKICAgICBkPSJtIDIwNy40NTc4LDM3LjQ5MzUzMyBjIC0wLjEsMC43IC0wLjYsMS4yIC0xLjMsMS4zIC0wLjMsMCAtMC42LDAgLTEsMCAtMS40LC0wLjIgLTIuNSwtMS4xIC0yLjQsLTIgMC4xLC0wLjkgMS40LC0xLjQgMywtMS4yIDAuMywwIDAuNiwwLjEgMC44LDAuMiAwLjYsMC4yIDEsMC45IDAuOSwxLjcgMCwwIDAsLTAuMSAwLDAgeiIKICAgICBmaWxsPSIjNTc1OTVhIgogICAgIGlkPSJwYXRoMTIxNyIgLz4KICA8cGF0aAogICAgIGQ9Im0gOTUuODU3OSw2NS41OTM0ODMgYyAwLC0xLjcgMCwtMy40IDEuMiwtMy45IDEuMywtMC41IDIsMC43IDMsMi40IDAuOSwxLjUgMC41LDMuMSAtMC44LDMuNiAtMS4xLDAuNSAtMy40LDAuMiAtMy40LC0yLjEgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIxOSIgLz4KICA8cGF0aAogICAgIGQ9Im0gOTYuNTU3OSw1Ny4xOTM1ODMgYyAtMC4yLDIuMSAtMC41LDMuMyAwLjgsMy44IDEuMywwLjUgMiwtMC42IDMuMywtMi4yIDEsLTEuNCAwLjMsLTMuMiAtMSwtMy43IC0xLjMsLTAuNSAtMi45LDAuNCAtMy4xLDIuMSB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjIxIiAvPgogIDxwYXRoCiAgICAgZD0ibSA5NS40NTgsNjEuMTkzNTgzIGMgMCwtMC44IDAuNiwtMS40IDEuMywtMS41IDAuMywtMC4xIDAuNywtMC4xIDEuMSwwIDEuNiwwLjEgMywxIDIuOSwyIC0wLjEsMSAtMS40LDEuNyAtMy4xLDEuNSAtMC4zLDAgLTAuNiwtMC4xIC0wLjksLTAuMiAtMC44LC0wLjEgLTEuMywtMC45IC0xLjMsLTEuOCB6IgogICAgIGZpbGw9IiM1NzU5NWEiCiAgICAgaWQ9InBhdGgxMjIzIiAvPgogIDxwYXRoCiAgICAgZD0ibSAxMDIuMjU4MSw2MS40OTM1ODMgYyAxMC41LDAgMjkuOSw2LjEgMzAuMiwyOC40OTk5OTgiCiAgICAgc3Ryb2tlPSIjNTc1OTVhIgogICAgIHN0cm9rZS13aWR0aD0iMiIKICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgc3Ryb2tlLWRhc2hhcnJheT0iNCwgNCIKICAgICBpZD0icGF0aDEyMjUiIC8+CiAgPHBhdGgKICAgICBkPSJtIDE3NS4wNzg0MSw0LjE1ODgzOCBoIC0yLjk4ODA4IGMgLTAuMTgyOTQsMCAtMC4zNjU4OCwwLjA2NTAwMyAtMC40MjY4NywwLjI2MDAxMDIgbCAtMS40NjM1NCwyLjc5NTExMDEgYyAtMC4wNjEsMC4xOTUwMDcgLTAuMDYxLDAuMzkwMDE1IDAsMC41ODUwMjMgbCAxLjQ2MzU0LDIuNzk1MTEwNyBjIDAuMDYxLDAuMTk1MDA4IDAuMjQzOTMsMC4yNjAwMTEgMC40MjY4NywwLjI2MDAxMSBoIDIuOTg4MDggYyAwLjE4Mjk0LDAgMC4zNjU4OSwtMC4wNjUgMC40MjY4NywtMC4yNjAwMTEgbCAxLjQ2MzU1LC0yLjc5NTExMDcgYyAwLjA2MSwtMC4xOTUwMDggMC4wNjEsLTAuMzkwMDE2IDAsLTAuNTg1MDIzIGwgLTEuNDYzNTUsLTIuNzk1MTEwMSBjIC0wLjA2MSwtMC4xOTUwMDc2IC0wLjI0MzkzLC0wLjI2MDAxMDIgLTAuNDI2ODcsLTAuMjYwMDEwMiB6IgogICAgIGlkPSJwYXRoNC01IgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMTMzMzMzO3N0cm9rZS13aWR0aDowLjYyOTU5OCIgLz4KPC9zdmc+Cg==", EMPTY_SEARCH: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjE3MCIgdmlld0JveD0iMCAwIDMwMCAxNzAiIGZpbGw9Im5vbmUiPgogIDxwYXRoCiAgICAgZD0ibSAxODUuNzkzNjksNTguNTYxOTY0IGMgMi4yNjQsMCA0LjEsLTEuODM1NiA0LjEsLTQuMSAwLC0yLjI2NDQgLTEuODM2LC00LjEgLTQuMSwtNC4xIC0yLjI2NSwwIC00LjEsMS44MzU2IC00LjEsNC4xIDAsMi4yNjQ0IDEuODM1LDQuMSA0LjEsNC4xIHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjUiCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4yIiAvPgogIDxwYXRoCiAgICAgZD0ibSAxOTEuNzkzNjksNDIuNTYyMDY0IGMgMS41NDYsMCAyLjgsLTEuMjUzNiAyLjgsLTIuOCAwLC0xLjU0NjQgLTEuMjU0LC0yLjggLTIuOCwtMi44IC0xLjU0NywwIC0yLjgsMS4yNTM2IC0yLjgsMi44IDAsMS41NDY0IDEuMjUzLDIuOCAyLjgsMi44IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjciCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4yIiAvPgogIDxwYXRoCiAgICAgZD0ibSA5MC4zOTM1OTUsNTguNDYxOTY0IGMgMS41NDY0LDAgMi44LC0xLjI1MzYgMi44LC0yLjggMCwtMS41NDY0IC0xLjI1MzYsLTIuOCAtMi44LC0yLjggLTEuNTQ2NCwwIC0yLjgsMS4yNTM2IC0yLjgsMi44IDAsMS41NDY0IDEuMjUzNiwyLjggMi44LDIuOCB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGg1MDY5IgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMiIgLz4KICA8cGF0aAogICAgIGQ9Im0gNzMuMjkzNTY1LDExMi40NjIxNiBjIDIuODcxODksMCA1LjIwMDAzLC0yLjMyODEgNS4yMDAwMywtNS4yIDAsLTIuODcxOSAtMi4zMjgxNCwtNS4yIC01LjIwMDAzLC01LjIgLTIuODcxODgsMCAtNS4xOTk5OTUsMi4zMjgxIC01LjE5OTk5NSw1LjIgMCwyLjg3MTkgMi4zMjgxMTUsNS4yIDUuMTk5OTk1LDUuMiB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGg1MDcxIgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMiIgLz4KICA8ZwogICAgIGZpbHRlcj0idXJsKCNmaWx0ZXIwX2QpIgogICAgIGlkPSJnNTA3NSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2Ny41NDY2OTUsMjIuMzE1NTY0KSI+CiAgICA8cGF0aAogICAgICAgZD0ibSAxMDguMDc2LDQ2LjI1NzUgMC44LDY0LjQwMDUgYyAwLDIuMiAtMS44LDMuOSAtNCwzLjkgSCA0Mi4wNzYyIGMgLTIuMiwwIC00LC0xLjggLTQsLTMuOSBWIDI3Ljg1NzUgYyAwLC0yLjIgMS44LC0zLjkgNCwtMy45IGggNDUuNSB6IgogICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgIGlkPSJwYXRoNTA3MyIgLz4KICA8L2c+CiAgPHBhdGgKICAgICBkPSJtIDEzOC4zMjI3OSw1OS43NzI5NjQgaCAtMjIuMSBjIC0wLjcsMCAtMS4zLC0wLjYgLTEuMywtMS4zIDAsLTAuNyAwLjYsLTEuMyAxLjMsLTEuMyBoIDIyLjEgYyAwLjcsMCAxLjMsMC42IDEuMywxLjMgMCwwLjcgLTAuNiwxLjMgLTEuMywxLjMgeiIKICAgICBmaWxsPSIjZjJmM2Y1IgogICAgIGlkPSJwYXRoNTA3NyIKICAgICBzdHlsZT0iZmlsbDojZjJmM2Y1O2ZpbGwtb3BhY2l0eToxIiAvPgogIDxwYXRoCiAgICAgZD0ibSAxMjcuODIyNzksNjYuOTczMTY0IGggLTExLjYgYyAtMC43LDAgLTEuMywtMC42IC0xLjMsLTEuMyAwLC0wLjcgMC42LC0xLjMgMS4zLC0xLjMgaCAxMS41IGMgMC43LDAgMS4zLDAuNiAxLjMsMS4zIDAsMC43IC0wLjYsMS4zIC0xLjIsMS4zIHoiCiAgICAgZmlsbD0iI2YyZjNmNSIKICAgICBpZD0icGF0aDUwNzkiIC8+CiAgPHBhdGgKICAgICBkPSJtIDE1NS4xMjI4OSw0Ni4yNzMwNjQgdiAxNy44IGMgMCwyLjUgMi4yLDQuNSA0LjcsNC41IGggMTUuNzk5OCIKICAgICBmaWxsPSIjZjJmM2Y1IgogICAgIGlkPSJwYXRoNTA4MSIgLz4KICA8cGF0aAogICAgIGQ9Im0gMTI0Ljg1NjA5LDU5LjUzODI2NCA0Ljc1MTYsLTE2Ljc2IDE5LjUxNjYsMi4yNDggLTYuODEzOSwxMC45MjI0IDUuMjc3OCw0LjIxNjcgLTExLjM2MjgsMjQuODUzNyAwLjYzMzEsLTE5LjI0MTYgeiIKICAgICBmaWxsPSJ2YXIoLS1iZy1jb2xvcl8iCiAgICAgaWQ9InBhdGg1MDgzIiAvPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM1MTE3Ij4KICAgIDxmaWx0ZXIKICAgICAgIGlkPSJmaWx0ZXIwX2QiCiAgICAgICB4PSIxNi4wNzYyIgogICAgICAgeT0iMTIuOTU3NSIKICAgICAgIHdpZHRoPSIxMTQuOCIKICAgICAgIGhlaWdodD0iMTM0LjYwMDAxIgogICAgICAgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgICAgPGZlRmxvb2QKICAgICAgICAgZmxvb2Qtb3BhY2l0eT0iMCIKICAgICAgICAgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiCiAgICAgICAgIGlkPSJmZUZsb29kNTA5NyIgLz4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICAgaW49IlNvdXJjZUFscGhhIgogICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiCiAgICAgICAgIGlkPSJmZUNvbG9yTWF0cml4NTA5OSIgLz4KICAgICAgPGZlT2Zmc2V0CiAgICAgICAgIGR5PSIxMSIKICAgICAgICAgaWQ9ImZlT2Zmc2V0NTEwMSIgLz4KICAgICAgPGZlR2F1c3NpYW5CbHVyCiAgICAgICAgIHN0ZERldmlhdGlvbj0iMTEiCiAgICAgICAgIGlkPSJmZUdhdXNzaWFuQmx1cjUxMDMiIC8+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICAgdmFsdWVzPSIwIDAgMCAwIDAuMzk3NzA4IDAgMCAwIDAgMC40Nzc0OSAwIDAgMCAwIDAuNTc1IDAgMCAwIDAuMjcgMCIKICAgICAgICAgaWQ9ImZlQ29sb3JNYXRyaXg1MTA1IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluMj0iQmFja2dyb3VuZEltYWdlRml4IgogICAgICAgICByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA3IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgICBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgcmVzdWx0PSJzaGFwZSIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA5IiAvPgogICAgPC9maWx0ZXI+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJwYWludDBfbGluZWFyIgogICAgICAgeDE9IjczLjQ1MzEwMiIKICAgICAgIHkxPSIyMS44NjE5IgogICAgICAgeDI9IjczLjQ1MzEwMiIKICAgICAgIHkyPSIxMTUuNTM0IgogICAgICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcAogICAgICAgICBzdG9wLWNvbG9yPSIjRkRGRUZGIgogICAgICAgICBpZD0ic3RvcDUxMTIiIC8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC45OTY0IgogICAgICAgICBzdG9wLWNvbG9yPSIjRUNGMEY1IgogICAgICAgICBpZD0ic3RvcDUxMTQiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8cGF0aAogICAgIGQ9Im0gMTgwLjE4MzM3LDE0My4xNTA0NiBjIC0xLDAgLTIsLTAuNCAtMi44LC0xLjMgbCAtMTYuNjk5NiwtMTYuNzAwMyAtMC42LDAuNCBjIC01LjQsNCAtMTEuNyw2LjEgLTE4LjEsNi4xIC03LjcsMCAtMTUuNCwtMy4xIC0yMS4xLC04LjUgLTUuOTk5OTksLTUuNjk5OTkgLTkuMjk5OTksLTEzLjM5OTk5IC05LjI5OTk5LC0yMS43OTk5OSAwLC0xNi43MDAwMDMgMTMuNTk5OTksLTMwLjMwMDAwMyAzMC4yOTk5OSwtMzAuMzAwMDAzIDExLjQsMCAyMS40LDYgMjYuOCwxNi4yIDUuMjk5NiwxMC4xIDQuNTk5NiwyMS45MDAwMDMgLTEuOSwzMS40MDAwMDMgbCAtMC40LDAuNiAxNi43OTk2LDE2Ljc5OTk5IGMgMS43LDEuNyAxLjMsMy40MDAzIDEsNC4zMDAzIC0wLjgsMS42IC0yLjQsMi44IC00LDIuOCB6IG0gLTM4LjI5OTYsLTYzLjgwMDI5MyBjIC0xMi4yLDAgLTIxLjk5OTk5LDkuOSAtMjEuOTk5OTksMjIuMDAwMDAzIDAsMTMuOCAxMS4yOTk5OSwyMi4wOTk5OSAyMi4yOTk5OSwyMi4wOTk5OSA2LjcsMCAxMi44LC0yLjk5OTk5IDE3LjEsLTguMzk5OTkgNS4zLC02LjYgNi4yLC0xNS41MDAwMDMgMi41LC0yMy4yMDAwMDMgLTMuOCwtNy43IC0xMS40LC0xMi41IC0xOS45LC0xMi41IHoiCiAgICAgZmlsbD0iIzU3NTk1YSIKICAgICBpZD0icGF0aDQ1MjMiIC8+CiAgPHBhdGgKICAgICBkPSJtIDEzMi4zODM3NywxMDQuOTUwMjcgYyAxLjMsMCAyLjQsLTEuMSAyLjQsLTIuNCAwLC0xLjMgLTEuMSwtMi40IC0yLjQsLTIuNCAtMS4zLDAgLTIuNCwxLjEgLTIuNCwyLjQgMCwxLjMgMS4xLDIuNCAyLjQsMi40IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDQ1MjUiIC8+CiAgPHBhdGgKICAgICBkPSJtIDE1MC43ODM3NywxMDQuOTUwMjcgYyAxLjMsMCAyLjQsLTEuMSAyLjQsLTIuNCAwLC0xLjMgLTEuMSwtMi40IC0yLjQsLTIuNCAtMS4zLDAgLTIuNCwxLjEgLTIuNCwyLjQgMCwxLjQgMS4xLDIuNCAyLjQsMi40IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDQ1MjciIC8+CiAgPHBhdGgKICAgICBkPSJtIDEzMi4yODk5Nyw5NC42MTAwNjcgLTUuMjc5NSwyLjg1MDUgMC43MTI3LDEuMzE5OSA1LjI3OTQsLTIuODUwNiB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGg0NTI5IiAvPgogIDxwYXRoCiAgICAgZD0ibSAxNTAuNDU3MzcsOTQuNTcyMjY3IC0wLjcxMjUsMS4zMiA1LjI4LDIuODUgMC43MTI1LC0xLjMyIHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDQ1MzEiIC8+CiAgPHBhdGgKICAgICBkPSJtIDE0MS41ODM2NywxMTAuNTUwMjcgYyAxLjU0NjQsMCAyLjgsLTAuOTQwMiAyLjgsLTIuMSAwLC0xLjE1OTggLTEuMjUzNiwtMi4xIC0yLjgsLTIuMSAtMS41NDY0LDAgLTIuOCwwLjk0MDIgLTIuOCwyLjEgMCwxLjE1OTggMS4yNTM2LDIuMSAyLjgsMi4xIHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDQ1MzMiIC8+CiAgPHBhdGgKICAgICBkPSJNIDEyLjA4MjE1NCwxNDEuNTQ0MDggSCA3LjE4MjE1NDQgYyAtMC4zLDAgLTAuNiwwLjEgLTAuNywwLjQgbCAtMi40LDQuMyBjIC0wLjEsMC4zIC0wLjEsMC42IDAsMC45IGwgMi40LDQuMyBjIDAuMSwwLjMgMC40LDAuNCAwLjcsMC40IGggNC44OTk5OTk2IGMgMC4zLDAgMC42LC0wLjEgMC43LC0wLjQgbCAyLjQsLTQuMyBjIDAuMSwtMC4zIDAuMSwtMC42IDAsLTAuOSBsIC0yLjQsLTQuMyBjIC0wLjEsLTAuMyAtMC40LC0wLjQgLTAuNywtMC40IHoiCiAgICAgaWQ9InBhdGg0IgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMiIgLz4KICA8cGF0aAogICAgIGNsYXNzPSJmaWxlIgogICAgIGQ9Im0gMjcxLjA5MjU5LDExLjY1MjMxMiAtNy4zNzAxOSwzLjU4MzkxOCBjIC0wLjc2MTk0LDAuNDMxMTE2IC0xLjcwNDkxLDAuNTgzNTAxIC0yLjU3MjQ2LDAuNjA1MjIyIC0wLjg2NzU0LDAuMDIxNzIgLTEuNzg3OTEsLTAuMTYxMjQ0IC0yLjYzMjg1LC0wLjQ3NDg2NyAtMS42MTQ0NCwtMC43NTc4OTggLTMuMDU1NCwtMi4xMTI0NDIgLTMuOTM4MTUsLTMuODQxNTA5IDAsMCAtMS41MDg5NiwtMy4zMTAwMzcyIC0zLjA3MDc1LC02LjgyNDc3NDUgbCAtMC4yMzM4OCwtMC40ODM0MzY4IC0xNS45NjI1Myw3LjUwNzQ2OTMgYyAtMi41NDIyMywxLjE0NTI3MSAtMy42MTMyOSw0LjE4NTE0NCAtMi41MTkyOCw2LjczMjk5OCBsIDExLjUyODU1LDI1LjY0NDA3NCBjIDEuMDk0MDIsMi41NDc4NTUgNC4wODksMy41ODAyMDUgNi42MzEyNSwyLjQzNDkzMSBsIDI1LjU5NTg0LC0xMi4wNDk0MDIgYyAyLjU0MjI0LC0xLjE0NTI3NSAzLjYxMzMxLC00LjE4NTE0MyAyLjUxOTMsLTYuNzMyOTk4IEwgMjcxLjcwMzY1LDExLjQ4MjQ5IFoiCiAgICAgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4yO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDoxLjQ5NDcxIgogICAgIGlkPSJwYXRoNiIgLz4KICA8cGF0aAogICAgIGNsYXNzPSJmaWxlIgogICAgIGQ9Im0gMjU5LjQxNDY4LDEyLjkyMzM2OCBjIC0xLjEwMTQ0LC0wLjQ2MTcxMyAtMS45NzY1OSwtMS4zMTUzOCAtMi42MjU0MywtMi41NjEwMDUgbCAtMC4wNTI4LC0wLjIwNDcgLTAuMjg2NjgsLTAuNjg4MTQ3MiAtMS4wNDExOSwtMi4zNDMxNTggLTAuNTczNDEsLTEuMzc2Mjc2IDEzLjAwNTk5LDQuNzIxNzcwMiAtNS4xNTk5MywyLjQyMTIwMiBjIC0wLjQ4Mjc3LDAuMjQzODY2IC0xLjAxODQxLDAuMjgzMDI4IC0xLjU1NDAxLDAuMzIyMTk2IC0wLjUzNTY0LDAuMDM5MTYgLTEuMTI0MDYsLTAuMTI2MzY0IC0xLjcxMjQ5LC0wLjI5MTg5OSB6IgogICAgIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMjtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6MS40OTQ3MSIKICAgICBpZD0icGF0aDgiIC8+CiAgPHBhdGgKICAgICBkPSJtIDIzLjk0NDQwMywxNy42MzU1NjMgYyAtMi4wNzc0MTksMS4xOTk0MTUgLTIuNDc0NjY5LDMuODMyNzk4IC0xLjMwMzU3OSw1Ljg2MTIwMiBsIDExLjE1Nzk2OSwxOS4zMjYxMzMgYyAxLjI2ODY5LDIuMTk3NDM1IDMuNTMxOTYsMi45MTkxMzUgNS42NjI2NCwxLjY4ODk3NSBsIDI0LjM0MzEyLC0xNC4wNTQ0OTEgYyAxLjM4NDk2LC0wLjc5OTYxIDEuOTI0MTEsLTMuMDY0MTY5IDAuNjIyODgsLTUuMzE3OTM5IEwgNTQuOTI4NTQzLDguNjg2ODYyNyBjIC0xLjA0MDk5LC0xLjgwMzAxMSAtMy4wNzY1NCwtMi4xMzAzMTMgLTQuNDYxNDYsLTEuMzMwNzM0IGwgLTEyLjUxNzgyLDcuMjI3MTY5MyAtNC45NDk0NCwtMi4xNzU4NjkgeiIKICAgICBpZD0icGF0aDE2IgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMjtzdHJva2Utd2lkdGg6MC42MzI1OTQiIC8+CiAgPGcKICAgICBpZD0iZzIwIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4yODEwNCwxMzEuMDczNDYpIgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMiI+CiAgICA8cGF0aAogICAgICAgZD0ibSA0Mi40LDIzLjIgOC42LDkuOSBjIDAuMywwLjMgMC4xLDAuOCAtMC4zLDAuOSBMIDM4LDM2LjIgYyAtMC40LDAuMSAtMC43LC0wLjMgLTAuNiwtMC43IGwgNC4xLC0xMi4xIGMgMC4xLC0wLjQgMC43LC0wLjUgMC45LC0wLjIgeiIKICAgICAgIGlkPSJwYXRoMTgiCiAgICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjIiIC8+CiAgPC9nPgogIDxwYXRoCiAgICAgZD0ibSAyMjMuMDczMyw5Mi4zNzMwMzEgLTIsMSBjIC0wLjMsMC4xIC0wLjYsMCAtMC44LC0wLjMgbCAtMSwtMiBjIC0wLjEsLTAuMyAwLC0wLjYgMC4zLC0wLjggbCAyLC0xIGMgMC4zLC0wLjEgMC42LDAgMC44LDAuMyBsIDEsMiBjIDAuMSwwLjMgMCwwLjcgLTAuMywwLjggeiIKICAgICBpZD0icGF0aDEwIgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMiIgLz4KPC9zdmc+Cg==", }; const VIRTUAL_SCROLL_MINIMUM_TRIGGER = 100; export const files$ = new rxjs.BehaviorSubject(null); export default async function(render) { const $page = createElement(` <div class="component_filesystem container"> <div data-target="dragselect" style="display:none;"></div> <div data-target="header" style="text-align:center;"></div> <div class="ifscroll-before"></div> <div data-target="list" class="list"></div> <div class="ifscroll-after"></div> <br> </div> `); render($page); onDestroy(() => files$.next(null)); // feature: virtual scrolling const path = currentPath(); const $header = qs($page, `[data-target="header"]`); const $list = qs($page, `[data-target="list"]`); const removeLoader = createLoader($header); const $listBefore = qs($page, ".ifscroll-before"); const $listAfter = qs($page, ".ifscroll-after"); const refreshScreen$ = rxjs.merge( // case1: trigger the first display rxjs.of(null), // case2: height change => redraw screen rxjs.fromEvent(window, "resize").pipe( // height change = always redraw rxjs.startWith(null), rxjs.map(() => document.body.clientHeight), rxjs.distinctUntilChanged(), rxjs.skip(1), ), // case3: width change => redraw if grid size change rxjs.fromEvent(window, "resize").pipe( rxjs.startWith(null), rxjs.map(() => { if ($list.getAttribute("data-type") === "grid") { return gridSize($list.clientWidth, document.body.clientWidth); } return 0; }), rxjs.distinctUntilChanged(), rxjs.skip(1), ), ); let count = 0; effect(ls(path).pipe( rxjs.switchMap(({ files, ...rest }) => getState$().pipe(rxjs.switchMap((state) => { $header.innerHTML = ""; $list.innerHTML = ""; if (state.search) { const removeLoader = createLoader($header); $listBefore.setAttribute("style", ""); $listAfter.setAttribute("style", ""); return rxjs.timer(state.search ? 450 : 0).pipe( rxjs.switchMap(() => search(state.search).pipe( rxjs.map(({ files }) => ({ files, ...state, ...rest, })), )), removeLoader, ); } return rxjs.of({ files, ...state, ...rest }); }))), rxjs.mergeMap((obj) => getPermission(path).pipe( rxjs.map((permissions) => ({ ...obj, permissions })), )), rxjs.mergeMap(({ show_hidden, files, search, ...rest }) => { if (show_hidden === false) files = files.filter(({ name }) => name[0] !== "."); if (!search) files = sort(files, rest["sort"], rest["order"]); return rxjs.of({ ...rest, files, search }); }), rxjs.map((data) => ({ ...data, count: count++ })), removeLoader, rxjs.switchMap((obj) => refreshScreen$.pipe(rxjs.mapTo(obj))), rxjs.mergeMap(({ files, search, ...rest }) => { files$.next(files); if (files.length === 0) { renderEmpty(createRender(qs($page, `[data-target="header"]`)), search ? ICONS.EMPTY_SEARCH : ICONS.EMPTY_FILES); return rxjs.EMPTY; } return rxjs.of({ ...rest, files, search }); }), rxjs.mergeMap(({ files, view, search, count, permissions }) => { // STEP1: setup the list of files $list.closest(".scroll-y").scrollTop = 0; let FILE_HEIGHT, COLUMN_PER_ROW; switch (view) { case "grid": FILE_HEIGHT = 160; COLUMN_PER_ROW = gridSize($list.clientWidth, document.body.clientWidth); $list.style.gridTemplateColumns = `repeat(${COLUMN_PER_ROW}, 1fr)`; $list.setAttribute("data-type", "grid"); break; case "list": FILE_HEIGHT = 47; COLUMN_PER_ROW = 1; $list.style.gridTemplateColumns = `repeat(1, 1fr)`; $list.setAttribute("data-type", "list"); break; default: throw new Error("Not Implemented"); } const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1; let size = files.length; if (size > VIRTUAL_SCROLL_MINIMUM_TRIGGER) { size = Math.min(files.length, BLOCK_SIZE * COLUMN_PER_ROW); } const $fs = document.createDocumentFragment(); for (let i = 0; i < size; i++) { const file = files[i]; $fs.appendChild(createThing({ ...file, ...createLink(file, currentPath()), view, search, n: i, permissions, })); } if (count === 0) animate( $list, { time: 200, keyframes: slideYIn(isMobile ? 10 : 5) }, ); $list.replaceChildren($fs); /// /////////////////////////////////// // CASE 1: virtual scroll isn't enabled if (files.length <= VIRTUAL_SCROLL_MINIMUM_TRIGGER) { return rxjs.of({ virtual: false }); } /// /////////////////////////////////// // CASE 2: with virtual scroll const height = (Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE) * FILE_HEIGHT; if (height > 33554400) { console.log(`maximum CSS height reached, requested height ${height} is too large`); } const setHeight = (size) => { if (size < 0 || size > height) throw new ApplicationError( "INTERNAL ERROR", `assertion on size failed: size[${size}] height[${height}]` ); $listBefore.style.height = `${size}px`; $listAfter.style.height = `${height - size}px`; }; setHeight(0); const top = ($node) => $node.getBoundingClientRect().top; return rxjs.of({ virtual: true, files, search, path, view, permissions, currentState: 0, $list, setHeight, FILE_HEIGHT, BLOCK_SIZE, COLUMN_PER_ROW, MARGIN: top($list) - top($list.closest(".scroll-y")), }); }), rxjs.switchMap(({ files, path, view, search, permissions, BLOCK_SIZE, COLUMN_PER_ROW, FILE_HEIGHT, MARGIN, currentState, setHeight, $list, virtual, }) => ( virtual ? rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }) : rxjs.EMPTY ).pipe( rxjs.map((e) => { // 0-------------0-----------1-----------2-----------3 .... // [padding] $block1 $block2 $block3 .... const nextState = Math.floor((e.target.scrollTop - MARGIN) / FILE_HEIGHT); return Math.max(nextState, 0); }), rxjs.distinctUntilChanged(), rxjs.debounce(() => new rxjs.Observable((observer) => { const id = requestAnimationFrame(() => observer.next()); return () => cancelAnimationFrame(id); })), rxjs.tap((nextState) => { // STEP1: calculate the virtual scroll paramameters let diff = nextState - currentState; const diffSgn = Math.sign(diff); if (Math.abs(diff) > BLOCK_SIZE) { // diff is bound by BLOCK_SIZE // we can't be moving more than what is on the screen diff = diffSgn * BLOCK_SIZE; } let fileStart = nextState * COLUMN_PER_ROW; if (diffSgn > 0) { // => scroll down fileStart += BLOCK_SIZE * COLUMN_PER_ROW; fileStart -= Math.min(diff, BLOCK_SIZE) * COLUMN_PER_ROW; } let fileEnd = fileStart + diffSgn * diff * COLUMN_PER_ROW; if (fileStart >= files.length) { // occur when BLOCK_SIZE is larger than its absolute minimum return; } else if (fileEnd > files.length) { // occur when files.length isn't a multiple of COLUMN_PER_ROW and // we've scrolled to the bottom of the list already nextState = Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE; fileEnd = files.length - 1; for (let i=0; i<COLUMN_PER_ROW; i++) { // add some padding to fileEnd to balance the list to the // nearest COLUMN_PER_ROW fileEnd += 1; if (fileEnd % COLUMN_PER_ROW === 0) { break; } } } // STEP2: create the new elements const $fs = document.createDocumentFragment(); let n = 0; for (let i = fileStart; i < fileEnd; i++) { const file = files[i]; if (file === undefined) $fs.appendChild(createThing({ type: "hidden", })); else $fs.appendChild(createThing({ ...file, ...createLink(file, path), search, view, permissions, n: i, })); n += 1; } // STEP3: update the DOM if (diffSgn > 0) { // scroll down $list.appendChild($fs); for (let i = 0; i < n; i++) $list.firstChild.remove(); } else { // scroll up $list.insertBefore($fs, $list.firstChild); for (let i = 0; i < n; i++) $list.lastChild.remove(); } setHeight(nextState * FILE_HEIGHT); currentState = nextState; }), )), rxjs.catchError(ctrlError()), )); // feature: keyboard selection effect(rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => e.keyCode === 27), rxjs.tap(() => clearSelection()), )); effect(rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => e.key === "a" && (e.ctrlKey || e.metaKey) && (files$.value || []).length > 0 && !isAlreadyFocused()), preventDefault(), rxjs.tap(() => { clearSelection(); if (!Array.isArray(files$.value) || files$.value.length === 0) return; const path = currentPath(); const el0 = files$.value[0]; const elm1 = files$.value.slice(-1)[0]; addSelection({ n: 0, path: path + el0.name + (el0.type === "directory" ? "/" : ""), shift: false, files: [], }); if (elm1) addSelection({ n: (files$.value || []).length - 1, path: path + elm1.name + (elm1.type === "directory" ? "/" : ""), shift: true, files: (files$.value || []), }); }), )); effect(getSelection$().pipe(rxjs.tap(() => { for (const $thing of $page.querySelectorAll(".component_thing")) { let checked = isSelected(parseInt(assert.truthy($thing.getAttribute("data-n")))); if ($thing.getAttribute("data-selectable") === "false") checked = false; $thing.classList.add(checked ? "selected" : "not-selected"); $thing.classList.remove(checked ? "not-selected" : "selected"); qs(assert.type($thing, HTMLElement), `input[type="checkbox"]`).checked = checked; }; }))); // feature: mouse drag selection const $dragContainer = assert.type($page.closest(".component_page_filespage"), HTMLElement); const $dragselect = qs($page, `[data-target="dragselect"]`); const dragmove = (x, y, w, h) => { $dragselect.style.left = `${x}px`; $dragselect.style.top = `${y}px`; $dragselect.style.width = `${w}px`; $dragselect.style.height = `${h}px`; if ((w+1) * (h+1) < 50) { $dragselect.style.display = "none"; return false; } $dragselect.style.display = "block"; return true; }; if (isMobile === false) effect(rxjs.fromEvent($dragContainer, "mousedown").pipe( rxjs.filter((e) => { if (e.target.nodeName === "INPUT") return false; else if (e.target.closest("button")) return false; else if (e.buttons !== 1) return false; return !e.target.closest(`[draggable="true"]`); }), rxjs.tap((e) => e.preventDefault()), rxjs.map((e) => ({ start: [e.clientX, e.clientY], state: [...$page.querySelectorAll(".component_thing")].map(($file) => { const bounds = $file.getBoundingClientRect(); const $checkbox = qs(assert.type($file, HTMLElement), ".component_checkbox"); return { $checkbox, checked: () => $checkbox.firstElementChild.checked, bounds: { x: bounds.x, y: bounds.y, w: bounds.width, h: bounds.height, }, }; }), })), rxjs.mergeMap(({ start, state }) => rxjs.fromEvent(document, "mousemove").pipe( rxjs.takeUntil(rxjs.merge( rxjs.fromEvent(window, "mouseup"), rxjs.fromEvent(window, "keydown").pipe(rxjs.filter(({ key }) => key === "Escape")), )), rxjs.finalize(() => dragmove(0, 0, 0, 0)), rxjs.map((e) => ({ start, end: [e.clientX, e.clientY], state })), )), rxjs.map(({ start, end, state }) => ({ state, obj: { x: Math.min(start[0], end[0]), y: Math.min(start[1], end[1]), w: Math.abs(start[0] - end[0]), h: Math.abs(start[1] - end[1]), }, })), rxjs.filter(({ obj }) => dragmove(obj.x, obj.y, obj.w, obj.h)), rxjs.tap(({ obj, state }) => { for (let i=0; i<state.length; i++) { const { bounds, $checkbox, checked } = state[i]; const collision = !( obj.x + obj.w < bounds.x || obj.x > bounds.x + bounds.w || obj.y + obj.h < bounds.y || obj.y > bounds.y + bounds.h ); if ((collision && !checked()) || (!collision && checked())) { $checkbox.click(); } } }), )); // feature: remove long touch popup on mobile const disableLongTouch = (e) => { if (isMobile === false) return; e.preventDefault(); }; document.addEventListener("contextmenu", disableLongTouch); onDestroy(() => document.removeEventListener("contextmenu", disableLongTouch)); } function renderEmpty(render, base64Icon) { const $page = createElement(` <div class="empty no-select"> <p class="empty_image"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${base64Icon}" alt="empty_folder"> </p> <p class="label">${t("There is nothing here")}</p> </div> `); animate(render($page), { time: 250, keyframes: slideYIn(5) }); } export function init() { return Promise.all([ loadCSS(import.meta.url, "./ctrl_filesystem.css"), loadCSS(import.meta.url, "./thing.css"), loadCSS(import.meta.url, "./modal.css"), ]); } export function createLink(file, currentPath) { let path = file.path; if (!path) path = currentPath + file.name + (file.type === "directory" ? "/" : ""); let link = file.type === "directory" ? "files" + path : "view" + path; link = encodeURIComponent(link).replaceAll("%2F", "/"); return { path, link, }; } function gridSize(size, windowSize) { const DESIRED_FILE_WIDTH_ON_LARGE_SCREEN = 210; if (windowSize > 1100) return Math.max( 4, Math.floor(size / DESIRED_FILE_WIDTH_ON_LARGE_SCREEN), ); else if (size > 750) return 4; else if (size > 520) return 3; else if (size > 300) return 2; return 1; } ================================================ FILE: public/assets/pages/filespage/ctrl_frequentlyaccess.css ================================================ [is="component_frequently_access"] .component_container { padding: 0; min-height: 145px; padding: 0 0 7px 0; } [is="component_frequently_access"] .component_icon { height: 25px; } [is="component_frequently_access"] .caption { margin-top: 15px; letter-spacing: 0.06em; display: inline-block; text-transform: uppercase; font-size: 12px; padding-bottom: 5px; color: var(--light); } [is="component_frequently_access"] .frequent_wrapper { display: flex; } [is="component_frequently_access"] .frequent_wrapper a { width: 33.33%; background: rgba(0, 0, 0, 0.05); transition: background 0.05s linear 0s; border-radius: 2px; overflow: hidden; margin-right: 5px; padding: 5px; cursor: pointer; line-height: 25px; display: block; } [is="component_frequently_access"] .frequent_wrapper a img { float: left; height: 25px; margin-right: 2px; } [is="component_frequently_access"] .frequent_wrapper a div { width: calc(100% - 30px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } @media (max-width: 800px) { [is="component_frequently_access"] .frequent_wrapper a:nth-child(6) { display: none; } } @media (max-width: 750px) { [is="component_frequently_access"] .frequent_wrapper a:nth-child(5) { display: none; } } @media (max-width: 650px) { [is="component_frequently_access"] .frequent_wrapper a:nth-child(4) { display: none; } } @media (max-width: 450px) { [is="component_frequently_access"] .frequent_wrapper a:nth-child(3) { display: none; } } [is="component_frequently_access"] .nothing_placeholder { padding: 20px; text-align: center; background: var(--emphasis-primary); color: rgba(0, 0, 0, 0.5); border-radius: 5px; position: relative; top: 25px; } [is="component_frequently_access"] .nothing_placeholder svg { display: block; width: 45px; margin: 0 auto; padding-bottom: 5px; } .touch-no [is="component_frequently_access"] .frequent_wrapper a:hover { transition: background 0.1s linear 0s; background: var(--emphasis-primary); } .frequent-access-appear { opacity: 0; transition: opacity 0.3s ease-out; } .frequent-access-appear .frequent_wrapper a { transition: all 0.3s ease-out; opacity: 0; transform: translateX(5px); } .frequent-access-appear.frequent-access-appear-active { opacity: 1; } .frequent-access-appear.frequent-access-appear-active .frequent_wrapper a { transform: translateX(0px); opacity: 1; } .frequent-access-enter { opacity: 0; transition: opacity 0.5s ease-out; } .frequent-access-enter .frequent_wrapper a { transition: all 0.3s ease-out; transition-delay: 0.2s; opacity: 0; transform: translateY(-5px); } .frequent-access-enter.frequent-access-enter-active { opacity: 1; transition: opacity 0.5s ease-out; } .frequent-access-enter.frequent-access-enter-active .frequent_wrapper a { transform: translateY(0px); opacity: 1; } .dark-mode [is="component_frequently_access"] .frequent_wrapper a { color: var(--light); background: var(--dark); } .dark-mode [is="component_frequently_access"] .frequent_wrapper a:hover { background: rgba(255, 255, 255, 0.05); } .dark-mode [is="component_frequently_access"] .nothing_placeholder { background: var(--dark); color: rgba(255, 255, 255, 0.5); } ================================================ FILE: public/assets/pages/filespage/ctrl_frequentlyaccess.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import { loadCSS } from "../../helpers/loader.js"; import { transition, opacityIn } from "../../lib/animate.js"; import t from "../../locales/index.js"; export default function(render) { const $page = createElement(` <div class="component_container"> <div class="nothing_placeholder no-select"> <svg aria-hidden="true" focusable="false" data-icon="layer-group" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="currentColor" d="M12.41 148.02l232.94 105.67c6.8 3.09 14.49 3.09 21.29 0l232.94-105.67c16.55-7.51 16.55-32.52 0-40.03L266.65 2.31a25.607 25.607 0 0 0-21.29 0L12.41 107.98c-16.55 7.51-16.55 32.53 0 40.04zm487.18 88.28l-58.09-26.33-161.64 73.27c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.51 209.97l-58.1 26.33c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 276.3c16.55-7.5 16.55-32.5 0-40zm0 127.8l-57.87-26.23-161.86 73.37c-7.56 3.43-15.59 5.17-23.86 5.17s-16.29-1.74-23.86-5.17L70.29 337.87 12.41 364.1c-16.55 7.5-16.55 32.5 0 40l232.94 105.59c6.8 3.08 14.49 3.08 21.29 0L499.59 404.1c16.55-7.5 16.55-32.5 0-40z"></path> </svg> ${t("Frequently access folders will be shown here")} </div> </div> `); if (location.pathname === "/files/") render(transition($page, { enter: opacityIn(), timeEnter: 800, })); } export function init() { return loadCSS(import.meta.url, "./ctrl_frequentlyaccess.css"); } ================================================ FILE: public/assets/pages/filespage/ctrl_newitem.css ================================================ .component_newitem { overflow: hidden; } .component_newitem .component_thing { margin: 2px 0; } .component_newitem .component_thing .box { margin: 0; } .component_newitem .component_thing .box .file-details { flex-grow: 1; } .component_newitem .component_thing .box .file-details input { border-color: var(--border); padding: 0; } .component_newitem .component_thing .box .component_action { display: block; opacity: 1; } ================================================ FILE: public/assets/pages/filespage/ctrl_newitem.js ================================================ import { createElement, nop } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick, preventDefault } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { animate } from "../../lib/animate.js"; import { loadCSS } from "../../helpers/loader.js"; import { getAction$, setAction } from "./state_newthing.js"; import { currentPath } from "./helper.js"; import { mkdir as mkdir$, touch as touch$ } from "./model_files.js"; import { mkdir as mkdirVL, touch as touchVL, withVirtualLayer } from "./model_virtual_layer.js"; const touch = (path) => withVirtualLayer( touch$(path), touchVL(path), ); const mkdir = (path) => withVirtualLayer( mkdir$(path), mkdirVL(path), ); const IMAGE = { FILE: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiI+CiAgPHBhdGggc3R5bGU9ImNvbG9yOiMwMDAwMDA7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MC45ODQ4MTA0MSIgZD0ibSAyLDEzLjA4MjQxMiAwLjAxOTQ2MiwxLjQ5MjM0NyBjIDVlLTYsMC4yMjIxNDUgMC4yMDU1OTAyLDAuNDI0MjYyIDAuNDMxMTUwMiwwLjQyNDI3MiBMIDEzLjU4OTYxMiwxNSBDIDEzLjgxNTE3MywxNC45OTk5OTUgMTMuOTk5OTksMTQuNzk3ODc0IDE0LDE0LjU3NTcyOSB2IC0xLjQ5MzMxNyBjIC00LjE3MTg2OTIsMC42NjIwMjMgLTcuNjUxNjkyOCwwLjM5ODY5NiAtMTIsMCB6IiAvPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtkaXNwbGF5OmlubGluZTtmaWxsOiNhYWFhYWE7c3Ryb2tlLXdpZHRoOjAuOTg0MDgxMjciIGQ9Ik0gMi4zNTAxLDEuMDAxMzMxMiBDIDIuMTUyNTksMS4wMzgzMjQ3IDEuOTk2NTksMS4yMjcyNzIzIDIuMDAwMDksMS40MjQ5MzU2IFYgMTQuMTMzNDU3IGMgNWUtNiwwLjIyMTgxNiAwLjIwNTIzLDAuNDIzNjM0IDAuNDMwNzksMC40MjM2NDQgbCAxMS4xMzksLTEuMDFlLTQgYyAwLjIyNTU2LC02ZS02IDAuNDMwMTEsLTAuMjAwNzU4IDAuNDMwMTIsLTAuNDIyNTc0IGwgNi43ZS00LC05LjgyMjY0MjYgYyAtMi40ODQwNDYsLTEuMzU1MDA2IC0yLjQzNTIzNCwtMi4wMzEyMjU0IC0zLjUwMDEsLTMuMzA5NzA3IC0wLjA0MywtMC4wMTU4ODIgMC4wNDYsMC4wMDE3NCAwLDAgTCAyLjQzMDY3LDEuMDAxMTA4IEMgMi40MDM4MywwLjk5ODU5IDIuMzc2NzQsMC45OTg1OSAyLjM0OTksMS4wMDExMDggWiIgLz4KICA8cGF0aCBzdHlsZT0iZGlzcGxheTppbmxpbmU7ZmlsbDojOGM4YzhjO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojOWU3NTc1O3N0cm9rZS13aWR0aDowO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Im0gMTAuNTAwNTcsMS4wMDIwNzY0IGMgMCwzLjI3NjgwMjggLTAuMDA1MiwzLjE3MzkxNjEgMC4zNjI5MjEsMy4yNjk4MjAyIDAuMjgwMTA5LDAuMDcyOTg0IDMuMTM3MTgsMC4wMzk4ODcgMy4xMzcxOCwwLjAzOTg4NyAtMS4xMjAwNjcsLTEuMDU1NjY5MiAtMi4zMzM0LC0yLjIwNjQ3MTMgLTMuNTAwMSwtMy4zMDk3MDc0IHoiIC8+Cjwvc3ZnPg==", FOLDER: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuODY2NjY0MzEsMCwwLDAuODY2NjcsLTE3Mi4wNDU3OCwtODY0LjMyNzU5KSIgc3R5bGU9ImZpbGw6Izc1YmJkOTtmaWxsLW9wYWNpdHk6MC45NDExNzY0NztmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojNzViYmQ5O2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg2NjY3LDAsMCwwLjg2NjY3LC0xNzIuMDQ2OTIsLTg2NC43ODM0KSIgc3R5bGU9ImZpbGw6IzlhZDFlZDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojOWFkMWVkO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KPC9zdmc+Cg==", TRASH: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODIuNDI4IDQ4Mi40MjkiPgogIDxwYXRoIHN0eWxlPSJmaWxsOiM2ZjZmNmY7c3Ryb2tlLXdpZHRoOjAuOTQ3MjAwNTQiIGQ9Im0gMjM5LjcxMDM4LDEwLjg1ODU2NyBjIC0yOS4yODYzMywwLjE0ODk1OSAtNTYuMjI4MjEsMjMuMTU4MTY3IC02MS4xMzg4Myw1MS45OTYxMjkgLTAuMDM1OSw1LjUyMjQ0IC04LjExOTM2LDEuNTIzODUyIC0xMS44MTQxMSwyLjczNDMwMSAtMjEuNjU5MywwLjM1NzE4IC00My4zODAyLC0wLjY3Njg3NSAtNjUuMDA3MTksMC40Mzg0NTIgLTI1Ljc0Mzk2MSwyLjgxNDg5NiAtNDcuMDQxMDg0LDI2LjM4MTc2IC00Ny4xNzMxNzIsNTIuMjkyMTMxIC0xLjcyMjExOCwyMi4zMjI3NyAxMS42Nzg0MSw0NC43NzgwOSAzMi4zMjg3NjgsNTMuNTM1MzIgMS41MDI3NjcsNy4xMzU1IDAuMjE0MTksMTYuMTEyMjggMC42NDM4LDIzLjk1NTY4IDAuMTEwMTQ1LDc1LjI4MzExIC0wLjIxODQzMywxNTAuNTc3MzcgMC4xNjA5NSwyMjUuODUzNjcgMS40ODk4MDUsMjUuODUxOTIgMjMuOTUyNDE0LDQ4LjI5NzYgNDkuODA1NzI0LDQ5Ljc2Njg3IDY4Ljk5NTMyLDAuMjc5OTggMTM4LjAxNjU0LDAuMjI5NjYgMjA3LjAxMzE3LDAuMDI0NyAyNi4wMTg1MiwtMS4yNzY5MSA0OC43MjA1LC0yMy44MzQ0MyA1MC4xOTI0OSwtNDkuODM3NjcgMC4zNjUyOCwtODMuMTUzOTggMC4wNDk3LC0xNjYuMzI1MDggMC4xNTUzOCwtMjQ5LjQ4NTU4IDIwLjg0ODU5LC04LjUyMTk5IDM0LjU5NTY3LC0zMC45NzQ5OSAzMi45NzkzNiwtNTMuNDExMzEgMC4wNzUyLC0yNi4wNzE2MTEgLTIxLjMyNDY5LC00OS45MDA0NDIgLTQ3LjIyOTkxLC01Mi42OTkyMDYgLTI0LjY2MTA5LC0xLjA5MzY1MSAtNDkuNDEyODgsLTAuMDg0ODcgLTc0LjEwNTUsLTAuNDMyOSAtMy45NDgzNywwLjYxMjkxMSAtMi4zMDc4NywtNS4zNzQ4NTkgLTMuODc5MTQsLTcuOTc4OTIzIC03LjI2MTcsLTI3LjU4NzA0MiAtMzQuMzcxMDIsLTQ3Ljg1NDgyMzggLTYyLjkzMTc5LC00Ni43NTE2NDMgeiBtIDEuNTA0MDQsMjguNTMwNzE3IGMgMTUuNDcwMDYsLTAuMzA1NjE5IDMwLjI2NjY3LDExLjA4NDk0OCAzNC4wMzQ0NywyNi4xOTk3MTMgLTIyLjY4MzQsLTAuMDA1OSAtNDUuMzk2OTIsMC4wMTIzMyAtNjguMDYxNTQsLTAuMDA5MiAzLjgwNzAyLC0xNS4wNzAyMDQgMTguMzQxMTcsLTI2LjQxOTgyMyAzNC4wMjcwNywtMjYuMTkwNDYzIHogTSAxMDguNjc4NTEsOTQuMTM2MzYzIGMgODkuNDUyNTcsMC4xMzkxNjYgMTc4LjkyODgzLC0wLjI3NzY1MSAyNjguMzY2NjksMC4yMDcyIDEzLjc0MTMxLDEuNDE4NTc4IDIzLjkyNjY0LDE1LjE3NjA3NyAyMi4yNjY2MiwyOC43MDgzMTcgMC4wMzI1LDE0LjU1MDU0IC0xNC4wNzUxNCwyNi41NjAyNiAtMjguNDA0OTIsMjQuODcxNDIgLTg4LjUwNjYsLTAuMTQwMzcgLTE3Ny4wMzY5MSwwLjI4MDA2IC0yNjUuNTI4OCwtMC4yMDkwNiBDIDkxLjEyNTU5LDE0Ni4yNDIyMyA4MC45ODkyODUsMTMxLjcwMTYzIDgzLjE4MTc5NCwxMTcuNzAxNjggODMuOTcwODg3LDEwNC43NDEzIDk1LjU3NzEzMSw5My45MjA1OTYgMTA4LjY3ODUxLDk0LjEzNjM2MyBaIE0gMzY2LjMzLDE3Ni40NzA2NiBjIC0wLjE0MDc3LDgxLjQyODQ4IDAuMjgwNjIsMTYyLjg4MDcgLTAuMjA5MDUsMjQ0LjI5NDQ3IC0xLjQzODYyLDEzLjkxMTUxIC0xNS40NzY4NSwyNC4xMDU4OCAtMjkuMTUyMzIsMjIuMjc1ODggLTY2LjE5Njc4LC0wLjE0MTYyIC0xMzIuNDE3NDMsMC4yODE3NSAtMTk4LjU5OTQ1LC0wLjIwOTA2IC0xMy44OTE2OSwtMS40NDg4IC0yNC4xMTkwOSwtMTUuNDc1NzUgLTIyLjI3MjE2LC0yOS4xNTc4NiAwLC03OS4wNjc4MSAwLC0xNTguMTM1NjEgMCwtMjM3LjIwMzQzIDgzLjQxMDk4LDAgMTY2LjgyMTk4LDAgMjUwLjIzMjk4LDAgeiIvPgogIDxwYXRoIHN0eWxlPSJmaWxsOiM2ZjZmNmY7c3Ryb2tlLXdpZHRoOjAuOTgyMDgxIiBkPSJtIDE3MS42ODY0NCwyNDcuNDczNzkgYyAtOS4zNDY3NiwwLjE1NjQ0IC0xNS43NDAzMiw5Ljg4ODA1IC0xNC4wODY3MywxOC43MTEzMyAwLjEyMzUxLDQ3LjYyNzAxIC0wLjI0NDAxLDk1LjI3OTAzIDAuMTc4MzksMTQyLjg5MDg3IDEuMjA3NjQsMTAuOTcxMzYgMTUuOTE4MDMsMTYuNTI3OTQgMjQuMDcyNDksOS4wODQyNSA4LjQxNzU5LC02LjgxODg3IDQuNDc0NjksLTE4Ljg4MzkyIDUuMzQ3NzQsLTI4LjA4MTM4IC0wLjEyNDM5LC00My4zNzEyNyAwLjI0NTIsLTg2Ljc2Nzg0IC0wLjE3ODM5LC0xMzAuMTIzODEgLTEuMDM3OTUsLTcuMzE0MzkgLTcuOTUwNTQsLTEyLjk1NzA1IC0xNS4zMzM1LC0xMi40ODEyNiB6Ii8+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzZmNmY2ZjtzdHJva2Utd2lkdGg6MC45ODIwODEiIGQ9Im0gMjQwLjUwMTE2LDI0Ny40NzM3OSBjIC05LjM0NjQ5LDAuMTU2MTYgLTE1Ljc0MDY3LDkuODg4MTcgLTE0LjA4NjczLDE4LjcxMTMzIDAuMTIzNTIsNDcuNjI3MDEgLTAuMjQ0MDEsOTUuMjc5MDMgMC4xNzgzOSwxNDIuODkwODcgMS44MDUwNCwxNy41NjQ4OSAzMC4zNzQxMiwxNS4zNDIyNyAyOS40MjAyMywtMi4zMDE3NiAtMC4xMjMzMywtNDguOTM2NDIgMC4yNDM3NywtOTcuODk4MiAtMC4xNzgzOCwtMTQ2LjgxOTE4IC0xLjAzNzM1LC03LjMxNDEgLTcuOTUxMDEsLTEyLjk1Njk0IC0xNS4zMzM1MSwtMTIuNDgxMjYgeiIvPgogIDxwYXRoIHN0eWxlPSJmaWxsOiM2ZjZmNmY7c3Ryb2tlLXdpZHRoOjAuOTgyMDgxIiBkPSJtIDMwOS4zMTU4OCwyNDcuNDczNzkgYyAtOS4zNDcxMSwwLjE1NTMgLTE1Ljc0MzE0LDkuODg3MTMgLTE0LjA4NjcyLDE4LjcxMTMzIDAuMTIzNTQsNDcuNjI0OTkgLTAuMjQ0MDYsOTUuMjc1ODEgMC4xNzgzOCwxNDIuODg1MTEgMS4xOTg1NiwxMC45NzMyMSAxNS45MTY2NCwxNi41MzYwNiAyNC4wNzA1OCw5LjA5MDAxIDguNDE5MzMsLTYuODE3ODcgNC40NzM2NiwtMTguODg0MiA1LjM0Nzc0LC0yOC4wODEzOCAtMC4xMjQ0MiwtNDMuMzcxMjQgMC4yNDUyNCwtODYuNzY4MDQgLTAuMTc4MzksLTEzMC4xMjM4MSAtMS4wMzY3NCwtNy4zMTMyMSAtNy45NTAxMiwtMTIuOTU2NTggLTE1LjMzMTU5LC0xMi40ODEyNiB6Ii8+Cjwvc3ZnPg==", }; export default async function(render) { const $node = createElement(` <div class="component_newitem container" id="newthing"> <div class="component_thing"> <div class="mouse-is-hover highlight box flex"> <img class="component_icon" draggable="false" alt="directory" aria-hidden="true"> <span class="file-details"> <form class="full-width"> <input type="text" name="name" value="" class="full-width"> <input type="hidden" name="type" value=""> </form> </span> <span class="component_action"> <div class="action"> <div role="button"> <img class="component_icon" draggable="false" src="${IMAGE.TRASH}" alt="trash"> </div> </div> </span> </div> </div> </div> `); $node.classList.add("hidden"); const $input = qs($node, "input[type=\"text\"]"); const $icon = qs($node, ".component_icon"); const $remove = qs($node, ".action"); // feature1: setup the dom effect(getAction$().pipe( rxjs.map((targetName) => { if (targetName === "NEW_FILE") return { targetName, alt: "file", img: IMAGE.FILE, }; if (targetName === "NEW_FOLDER") return { targetName, alt: "directory", img: IMAGE.FOLDER, }; return null; }), rxjs.filter((val) => val), rxjs.tap(async({ img, alt }) => { $icon.setAttribute("src", `${img}`); $icon.setAttribute("alt", alt); $input.value = ""; $input.nextElementSibling.setAttribute("name", alt); let done = Promise.resolve(nop); if ($node.classList.contains("hidden")) done = animate($node, { keyframes: [{ height: `0px` }, { height: "50px" }], time: 100, fill: "forwards", }); $node.classList.remove("hidden"); render($node); await done; await new Promise(requestAnimationFrame); $input.focus(); }), )); // feature2: remove the component effect(rxjs.merge( rxjs.merge( onClick($remove), rxjs.fromEvent(window, "keydown").pipe(rxjs.filter((e) => e.keyCode === 27)), ).pipe(rxjs.tap(() => setAction(null))), getAction$().pipe( rxjs.filter((actionName) => !actionName), ), ).pipe(rxjs.tap(async() => { await animate($node, { keyframes: [{ height: "50px" }, { height: `0px` }], time: 50, fill: "backwards", }); $node.classList.add("hidden"); }))); // feature3: submit form effect(rxjs.fromEvent($node, "submit").pipe( preventDefault(), rxjs.filter(() => $input.value), rxjs.mergeMap(() => { window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 27 })); // close const type = $input.nextElementSibling.getAttribute("name"); if (type === "file") return touch(currentPath() + $input.value); return mkdir(currentPath() + $input.value + "/"); }), rxjs.catchError((err) => console.log("ERR", err)), )); } export function init() { return loadCSS(import.meta.url, "./ctrl_newitem.css"); } ================================================ FILE: public/assets/pages/filespage/ctrl_submenu.css ================================================ /* General design */ [is="component_submenu"] { margin-top: 10px; position: sticky; top: 0; z-index: 3; padding: 4px 0; transition: 0.2s ease box-shadow; } [is="component_submenu"]::before { content: ""; backdrop-filter: blur(15px) saturate(2); position: absolute; inset: 0; z-index: -1; } .scrolling [is="component_submenu"] { box-shadow: 0 5px 10px rgba(100,100,100,.05); background: rgba(245,245,245,0.9); border-bottom: 1px solid rgba(0,0,0,0.02); } [is="component_submenu"] .component_submenu { display: flex; justify-content: space-between; padding: 0 15px; } [is="component_submenu"] .component_submenu::-webkit-scrollbar { display: none; } [is="component_submenu"] .component_submenu .action button { padding: 6px 10px; background: transparent; text-transform: uppercase; font-size: 0.8rem; color: var(--light); letter-spacing: 0; border-radius: 5px; line-height: 15px; } [is="component_submenu"] .component_submenu .action button img { width: 15px; height: 15px; margin-left: -3px; margin-right: -3px; } .touch-yes [is="component_submenu"] .component_submenu .action button img { width: 16px; height: 16px; } [is="component_submenu"] .component_submenu .action.left { margin-right: 2px; display: flex; } [is="component_submenu"] .component_submenu .action.left button { min-width: 50px; } [is="component_submenu"] .component_submenu .action.right.hover, .touch-no [is="component_submenu"] .component_submenu .action.right:hover, .touch-yes [is="component_submenu"] .component_submenu .action.right, .touch-yes [is="component_submenu"] .component_submenu .action.left { background: var(--border); } .touch-no [is="component_submenu"] .component_submenu .action button:hover { filter: brightness(0.9); background: var(--border); } .touch-no [is="component_submenu"] .component_submenu .action button:active, [is="component_submenu"] .component_submenu .action button:active { filter: brightness(0.75); background: var(--border); } [is="component_submenu"] .component_submenu .action.left, [is="component_submenu"] .component_submenu .action.right { border-radius: 5px; white-space: nowrap; } [is="component_submenu"] .component_submenu .action.right button[data-bind="clear"] { display: flex; align-items: center; background: var(--border); font-weight: bold; } [is="component_submenu"] .component_submenu .action.right button[data-bind="clear"] img { padding-left: 2px; } [is="component_submenu"] .component_submenu .action form input[name="q"] { background: transparent; border: none; padding-left: 5px; color: var(--color); font-size: 0.95rem; } /* dark mode */ .dark-mode [is="component_submenu"] .component_submenu .action button img { filter: brightness(70%) invert(1); } .dark-mode [is="component_submenu"] .component_submenu .action button { color: var(--light); font-weight: bold; } .dark-mode .scrolling [is="component_submenu"] { background: rgba(43, 45, 48, 0.99); } /* ripple effect */ .touch-no [is="component_submenu"] .component_submenu .action button { position: relative; overflow: hidden; } .touch-no [is="component_submenu"] .component_submenu .action button::after { content: ""; position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; background: var(--color); border-radius: 50%; transform: translate(-50%, -50%) scale(0); opacity: 0; pointer-events: none; } .touch-no [is="component_submenu"] .component_submenu .action button:active::after { animation: ripple 0.2s ease-out; } @keyframes ripple { 0% { transform: translate(-50%, -50%) scale(0); opacity: 0.2; } 100% { transform: translate(-50%, -50%) scale(20); opacity: 0; } } ================================================ FILE: public/assets/pages/filespage/ctrl_submenu.js ================================================ import { createElement, createRender, createFragment, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick, preventDefault } from "../../lib/rx.js"; import { animate, slideXIn, slideYIn } from "../../lib/animate.js"; import { basename, join, forwardURLParams } from "../../lib/path.js"; import assert from "../../lib/assert.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { get as getConfig } from "../../model/config.js"; import { loadCSS } from "../../helpers/loader.js"; import t from "../../locales/index.js"; import "../../components/dropdown.js"; import "../../components/icon.js"; import { createModal } from "../../components/modal.js"; import componentShare from "./modal_share.js"; import componentTag from "./modal_tag.js"; import componentRename from "./modal_rename.js"; import componentDelete from "./modal_delete.js"; import { getSelection$, clearSelection, lengthSelection, expandSelection } from "./state_selection.js"; import { getAction$, setAction } from "./state_newthing.js"; import { setState, getState$ } from "./state_config.js"; import { clearCache } from "./cache.js"; import { getPermission, calculatePermission } from "./model_acl.js"; import { currentPath, extractPath } from "./helper.js"; import { rm as rm$, mv as mv$ } from "./model_files.js"; import { rm as rmVL, mv as mvVL, withVirtualLayer } from "./model_virtual_layer.js"; const modalOpt = { withButtonsRight: t("OK"), withButtonsLeft: t("CANCEL"), }; const rm = (...paths) => withVirtualLayer( rm$(...paths), rmVL(...paths), ); const mv = (from, to) => withVirtualLayer( mv$(from, to), mvVL(from, to), ); export default async function(render) { const $page = createElement(` <div class="component_submenu container" role="toolbar"> <div class="action left no-select"></div> <div class="action right no-select"></div> </div> `); render($page); onDestroy(() => clearSelection()); const getSelectionLength$ = getSelection$().pipe( rxjs.map(() => lengthSelection()), // <- potentially expensive, hence the share rxjs.shareReplay(), ); const $scroll = assert.type($page.closest(".scroll-y"), HTMLElement); componentLeft(createRender(qs($page, ".action.left")), { $scroll, getSelectionLength$ }); componentRight(createRender(qs($page, ".action.right")), { getSelectionLength$ }); effect(rxjs.fromEvent($scroll, "scroll", { passive: true }).pipe( rxjs.map((e) => e.target.scrollTop > 12), rxjs.distinctUntilChanged(), rxjs.startWith(false), rxjs.tap((scrolling) => scrolling ? $scroll.classList.add("scrolling") : $scroll.classList.remove("scrolling")), )); } function componentLeft(render, { $scroll, getSelectionLength$ }) { effect(getSelectionLength$.pipe( rxjs.filter((l) => l === 0), rxjs.mergeMap(() => getPermission()), rxjs.map(() => render(createFragment(` <button data-action="new-file" title="${t("New File")}"${toggleDependingOnPermission(currentPath(), "new-file")} aria-controls="newthing"> ${window.innerWidth < 410 && t("New File").length > 10 ? t("New File", null, "NEW_FILE::SHORT") : t("New File")} </button> <button data-action="new-folder" title="${t("New Folder")}"${toggleDependingOnPermission(currentPath(), "new-folder")} aria-controls="newthing"> ${window.innerWidth < 410 && t("New Folder").length > 10 ? t("New Folder", null, "NEW_FOLDER::SHORT") : t("New Folder")} </button> `))), rxjs.mergeMap(($page) => rxjs.merge( onClick(qs($page, `[data-action="new-file"]`)).pipe(rxjs.mapTo("NEW_FILE")), onClick(qs($page, `[data-action="new-folder"]`)).pipe(rxjs.mapTo("NEW_FOLDER")), )), rxjs.mergeMap((actionName) => getAction$().pipe( rxjs.first(), rxjs.map((currentAction) => actionName === currentAction ? null : actionName), )), rxjs.tap((actionName) => { $scroll.scrollTo({ top: 0, behavior: window.chrome ? "smooth" : "instant", // prevent firefox bug }); setAction(actionName); }), )); onDestroy(() => setAction(null)); effect(getSelectionLength$.pipe( rxjs.filter((l) => l === 1), rxjs.map(() => render(createFragment(` <a target="_blank" ${generateLinkAttributes(expandSelection())}><button data-action="download" title="${t("Download")}"> ${t("Download")} </button></a> <button data-action="delete"${toggleDependingOnPermission(currentPath(), "delete")} title="${t("Remove")}"> ${t("Remove")} </button> <button data-action="rename" title="${t("Rename")}"${toggleDependingOnPermission(currentPath(), "rename")}> ${t("Rename")} </button> <button data-action="share" title="${t("Share")}" class="${(getConfig("enable_share") && !new URLSearchParams(location.search).has("share")) ? "" : "hidden"}"> ${t("Share")} </button> <button data-action="tag" title="${t("Tag")}" class="${getConfig("enable_tags", false) ? "" : "hidden"}"> ${t("Tag")} </button> `))), rxjs.tap(($buttons) => animate($buttons, { time: 100, keyframes: slideYIn(5) })), rxjs.switchMap(($page) => rxjs.merge( onClick(qs($page, `[data-action="download"]`), { preventDefault: true }).pipe(rxjs.tap(($button) => { let url = $button.parentElement.getAttribute("href"); url += "&name=" + $button.parentElement.getAttribute("download"); window.open(url); })), onClick(qs($page, `[data-action="share"]`)).pipe(rxjs.tap(() => { componentShare(createModal({ withButtonsRight: null, withButtonsLeft: null, }), { path: expandSelection()[0].path }); })), onClick(qs($page, `[data-action="tag"]`)).pipe(rxjs.tap(() => { componentTag(createModal({ ...modalOpt, withButtonsLeft: null, withButtonsRight: null, }), { path: expandSelection()[0].path }); })), onClick(qs($page, `[data-action="rename"]`)).pipe(rxjs.mergeMap(() => { const path = expandSelection()[0].path; return rxjs.from(componentRename( createModal(modalOpt), basename(path.replace(new RegExp("/$"), "")), )).pipe(rxjs.mergeMap((val) => { const [basepath] = extractPath(path); let newpath = join(location.origin + basepath, val); if (path.slice(-1) === "/" && newpath.slice(-1) !== "/") newpath += "/"; clearSelection(); clearCache(path); clearCache(newpath); return mv(path, newpath); })); })), onClick(qs($page, `[data-action="delete"]`)).pipe(rxjs.mergeMap(() => { const path = expandSelection()[0].path; return rxjs.from(componentDelete( createModal(modalOpt), basename(path.replace(new RegExp("/$"), "")).substr(0, 15), )).pipe(rxjs.mergeMap(() => { const selection = expandSelection()[0].path; clearSelection(); clearCache(path); return rm(selection); })); })), )), )); effect(getSelectionLength$.pipe( rxjs.filter((l) => l > 1), rxjs.map(() => render(createFragment(` <a target="_blank" ${generateLinkAttributes(expandSelection())}><button data-action="download"> ${t("Download")} </button></a> <button data-action="delete"${toggleDependingOnPermission(currentPath(), "delete")}> ${t("Remove")} </button> `))), rxjs.mergeMap(($page) => rxjs.merge( onClick(qs($page, `[data-action="download"]`), { preventDefault: true }).pipe(rxjs.tap(($button) => { window.open($button.parentElement.getAttribute("href")); })), onClick(qs($page, `[data-action="delete"]`)).pipe(rxjs.mergeMap(() => { const paths = expandSelection().map(({ path }) => path); return rxjs.from(componentDelete( createModal(modalOpt), "remove", )).pipe(rxjs.mergeMap(() => { clearSelection(); return rm(...paths); })); })), )), )); } function componentRight(render, { getSelectionLength$ }) { const ICONS = { LIST_VIEW: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojNjI2NDY5O2ZpbGwtb3BhY2l0eToxIiBkPSJtIDEzMy4zMzMsNTYgdiA2NCBjIDAsMTMuMjU1IC0xMC43NDUsMjQgLTI0LDI0IEggMjQgQyAxMC43NDUsMTQ0IDAsMTMzLjI1NSAwLDEyMCBWIDU2IEMgMCw0Mi43NDUgMTAuNzQ1LDMyIDI0LDMyIGggODUuMzMzIGMgMTMuMjU1LDAgMjQsMTAuNzQ1IDI0LDI0IHogbSAzNzkuMzM0LDIzMiB2IC02NCBjIDAsLTEzLjI1NSAtMTAuNzQ1LC0yNCAtMjQsLTI0IEggMjEzLjMzMyBjIC0xMy4yNTUsMCAtMjQsMTAuNzQ1IC0yNCwyNCB2IDY0IGMgMCwxMy4yNTUgMTAuNzQ1LDI0IDI0LDI0IGggMjc1LjMzMyBjIDEzLjI1NiwwIDI0LjAwMSwtMTAuNzQ1IDI0LjAwMSwtMjQgeiBtIDAsLTE2OCBWIDU2IGMgMCwtMTMuMjU1IC0xMC43NDUsLTI0IC0yNCwtMjQgSCAyMTMuMzMzIGMgLTEzLjI1NSwwIC0yNCwxMC43NDUgLTI0LDI0IHYgNjQgYyAwLDEzLjI1NSAxMC43NDUsMjQgMjQsMjQgaCAyNzUuMzMzIGMgMTMuMjU2LDAgMjQuMDAxLC0xMC43NDUgMjQuMDAxLC0yNCB6IE0gMTA5LjMzMywyMDAgSCAyNCBDIDEwLjc0NSwyMDAgMCwyMTAuNzQ1IDAsMjI0IHYgNjQgYyAwLDEzLjI1NSAxMC43NDUsMjQgMjQsMjQgaCA4NS4zMzMgYyAxMy4yNTUsMCAyNCwtMTAuNzQ1IDI0LC0yNCB2IC02NCBjIDAsLTEzLjI1NSAtMTAuNzQ1LC0yNCAtMjQsLTI0IHogTSAwLDM5MiB2IDY0IGMgMCwxMy4yNTUgMTAuNzQ1LDI0IDI0LDI0IGggODUuMzMzIGMgMTMuMjU1LDAgMjQsLTEwLjc0NSAyNCwtMjQgdiAtNjQgYyAwLC0xMy4yNTUgLTEwLjc0NSwtMjQgLTI0LC0yNCBIIDI0IEMgMTAuNzQ1LDM2OCAwLDM3OC43NDUgMCwzOTIgWiBtIDE4OS4zMzMsMCB2IDY0IGMgMCwxMy4yNTUgMTAuNzQ1LDI0IDI0LDI0IGggMjc1LjMzMyBjIDEzLjI1NSwwIDI0LC0xMC43NDUgMjQsLTI0IHYgLTY0IGMgMCwtMTMuMjU1IC0xMC43NDUsLTI0IC0yNCwtMjQgSCAyMTMuMzMzIGMgLTEzLjI1NSwwIC0yNCwxMC43NDUgLTI0LDI0IHoiIC8+Cjwvc3ZnPgo=", GRID_VIEW: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQgNDQiPgogIDxwYXRoIGZpbGw9IiM2MjY0NjkiIGQ9Ik0gMTgsNCBIIDYgQyA0LjksNCA0LDQuOSA0LDYgdiAxMiBjIDAsMS4xIDAuOSwyIDIsMiBoIDEyIGMgMS4xLDAgMiwtMC45IDIsLTIgViA2IEMgMjAsNC45IDE5LjEsNCAxOCw0IFoiIC8+CiAgPHBhdGggZmlsbD0iIzYyNjQ2OSIgZD0iTSAzOCw0IEggMjYgYyAtMS4xLDAgLTIsMC45IC0yLDIgdiAxMiBjIDAsMS4xIDAuOSwyIDIsMiBoIDEyIGMgMS4xLDAgMiwtMC45IDIsLTIgViA2IEMgNDAsNC45IDM5LjEsNCAzOCw0IFoiIC8+CiAgPHBhdGggZmlsbD0iIzYyNjQ2OSIgZD0iTSAxOCwyNCBIIDYgYyAtMS4xLDAgLTIsMC45IC0yLDIgdiAxMiBjIDAsMS4xIDAuOSwyIDIsMiBoIDEyIGMgMS4xLDAgMiwtMC45IDIsLTIgViAyNiBjIDAsLTEuMSAtMC45LC0yIC0yLC0yIHoiIC8+CiAgPHBhdGggZmlsbD0iIzYyNjQ2OSIgZD0iTSAzOCwyNCBIIDI2IGMgLTEuMSwwIC0yLDAuOSAtMiwyIHYgMTIgYyAwLDEuMSAwLjksMiAyLDIgaCAxMiBjIDEuMSwwIDIsLTAuOSAyLC0yIFYgMjYgYyAwLC0xLjEgLTAuOSwtMiAtMiwtMiB6IiAvPgo8L3N2Zz4K", CROSS: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=", MAGNIFYING_GLASS: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojNjI2NDY5O2ZpbGwtb3BhY2l0eToxIiBkPSJNNTA1IDQ0Mi43TDQwNS4zIDM0M2MtNC41LTQuNS0xMC42LTctMTctN0gzNzJjMjcuNi0zNS4zIDQ0LTc5LjcgNDQtMTI4QzQxNiA5My4xIDMyMi45IDAgMjA4IDBTMCA5My4xIDAgMjA4czkzLjEgMjA4IDIwOCAyMDhjNDguMyAwIDkyLjctMTYuNCAxMjgtNDR2MTYuM2MwIDYuNCAyLjUgMTIuNSA3IDE3bDk5LjcgOTkuN2M5LjQgOS40IDI0LjYgOS40IDMzLjkgMGwyOC4zLTI4LjNjOS40LTkuNCA5LjQtMjQuNi4xLTM0ek0yMDggMzM2Yy03MC43IDAtMTI4LTU3LjItMTI4LTEyOCAwLTcwLjcgNTcuMi0xMjggMTI4LTEyOCA3MC43IDAgMTI4IDU3LjIgMTI4IDEyOCAwIDcwLjctNTcuMiAxMjgtMTI4IDEyOHoiIC8+Cjwvc3ZnPgo=", SORT: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMjAgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojNjI2NDY5O2ZpbGwtb3BhY2l0eToxIiBkPSJNNDEgMjg4aDIzOGMyMS40IDAgMzIuMSAyNS45IDE3IDQxTDE3NyA0NDhjLTkuNCA5LjQtMjQuNiA5LjQtMzMuOSAwTDI0IDMyOWMtMTUuMS0xNS4xLTQuNC00MSAxNy00MXptMjU1LTEwNUwxNzcgNjRjLTkuNC05LjQtMjQuNi05LjQtMzMuOSAwTDI0IDE4M2MtMTUuMSAxNS4xLTQuNCA0MSAxNyA0MWgyMzhjMjEuNCAwIDMyLjEtMjUuOSAxNy00MXoiIC8+Cjwvc3ZnPgo=", CHECK: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eToxIiBkPSJNMTczLjg5OCA0MzkuNDA0bC0xNjYuNC0xNjYuNGMtOS45OTctOS45OTctOS45OTctMjYuMjA2IDAtMzYuMjA0bDM2LjIwMy0zNi4yMDRjOS45OTctOS45OTggMjYuMjA3LTkuOTk4IDM2LjIwNCAwTDE5MiAzMTIuNjkgNDMyLjA5NSA3Mi41OTZjOS45OTctOS45OTcgMjYuMjA3LTkuOTk3IDM2LjIwNCAwbDM2LjIwMyAzNi4yMDRjOS45OTcgOS45OTcgOS45OTcgMjYuMjA2IDAgMzYuMjA0bC0yOTQuNCAyOTQuNDAxYy05Ljk5OCA5Ljk5Ny0yNi4yMDcgOS45OTctMzYuMjA0LS4wMDF6IiAvPgo8L3N2Zz4K", }; const escape$ = rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((event) => event.keyCode === 27), rxjs.share(), ); const defaultLayout = (view) => { switch (view) { case "grid": return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.LIST_VIEW}" alt="grid" />`; case "list": return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.GRID_VIEW}" alt="list" />`; default: throw new Error("NOT_IMPLEMENTED"); } }; const defaultSort = (sort) => { return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.SORT}" alt="${sort}" />`; }; effect(getSelectionLength$.pipe( rxjs.filter((l) => l === 0), rxjs.mergeMap(() => getState$().pipe(rxjs.first())), rxjs.map(({ view, sort, search }) => ({ search, $page: render(createFragment(` <form style="display: inline-block;" onsubmit="event.preventDefault()"> <input value="${safe(search)}" class="hidden" placeholder="${t("search")}" name="q" id="searchInput" aria-label="${t("search")}" tabindex="-1" autocapitalize="none" /> </form> <button data-action="search" title="${t("Search")}" aria-controls="searchInput" class=${getConfig("enable_search") ? "" : "hidden"}> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.MAGNIFYING_GLASS}" alt="search" /> </button> <button data-action="view" title="${t("Layout")}"> ${defaultLayout(view)} </button> <button data-action="sort" title="${t("Sort")}" aria-haspopup="listbox" id="sortToggle"> ${defaultSort(sort)} </button> <div class="component_dropdown view sort" data-target="sort" role="listbox" aria-labelledby="sortToggle"> <div class="dropdown_container"> <ul> <li data-target="type" role="option"> ${t("Sort By Type")} <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.CHECK}" alt="check" /> </li> <li data-target="date" role="option"> ${t("Sort By Date")} </li> <li data-target="name" role="option"> ${t("Sort By Name")} </li> <li data-target="size" role="option"> ${t("Sort By Size")} </li> </ul> </div> </div> `)) })), rxjs.mergeMap(({ $page, search }) => rxjs.merge( // feature: view button onClick(qs($page, `[data-action="view"]`)).pipe(rxjs.tap(($button) => { const $img = $button.querySelector("img"); if ($img.getAttribute("alt") === "list") { setState("view", "grid"); $button.setAttribute("aria-pressed", "true"); $img.setAttribute("alt", "grid"); $img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.LIST_VIEW); } else { setState("view", "list"); $button.setAttribute("aria-pressed", "false"); $img.setAttribute("alt", "list"); $img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.GRID_VIEW); } })), // feature: sort button rxjs.merge( onClick(qs($page, `[data-action="sort"]`)).pipe(rxjs.map(($el) => { // toggle the dropdown return !$el.nextElementSibling.classList.contains("active"); })), escape$.pipe(rxjs.mapTo(false)), // quit the dropdown on esc rxjs.fromEvent(window, "click").pipe( // quit when clicking outside the dropdown rxjs.filter((e) => !e.target.closest(`[data-action="sort"]`) && !e.target.closest(".dropdown_container")), rxjs.mapTo(false), ), ).pipe( rxjs.takeUntil(getSelection$().pipe(rxjs.skip(1))), rxjs.mergeMap((targetStateIsOpen) => { const $sort = qs($page, `[data-target="sort"]`); const $lis = qsa($page, `.dropdown_container li`); targetStateIsOpen ? $sort.classList.add("active") : $sort.classList.remove("active"); $sort.setAttribute("aria-expanded", targetStateIsOpen); return onClick($lis).pipe( rxjs.first(), rxjs.mergeMap(($el) => getState$().pipe(rxjs.first(), rxjs.map((state) => ({ order: state.order, $el, })))), rxjs.tap(({ $el, order }) => { setState( "sort", $el.getAttribute("data-target"), "order", order === "asc" ? "des" : "asc", ); [...$lis].forEach(($li) => { const $img = $li.querySelector("img"); if ($img) $img.remove(); }); const $img = createElement(`<img class="component_icon" src="data:image/svg+xml;base64,${ICONS.CHECK}" alt="check" />`); if (order !== "asc") $img.classList.add("inverted"); $el.appendChild($img); $sort.classList.remove("active"); }), ); }), ), // feature: search box rxjs.merge( rxjs.merge( onClick(qs($page, `[data-action="search"]`)), rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => (e.ctrlKey || e.metaKey) && e.key === "f"), preventDefault(), ), ).pipe(rxjs.map(() => qs($page, "input").classList.contains("hidden"))), escape$.pipe(rxjs.mapTo(false)), search ? rxjs.of(true) : rxjs.EMPTY, ).pipe( rxjs.takeUntil(getSelection$().pipe(rxjs.skip(1))), rxjs.mergeMap(async(show) => { const $input = qs($page, "input"); const $searchImg = qs($page, "img"); if (show) { $page.classList.add("hover"); $input.classList.remove("hidden"); $searchImg.setAttribute("src", "data:image/svg+xml;base64," + ICONS.CROSS); $searchImg.setAttribute("alt", "close"); const $listOfButtons = $page.parentElement.firstElementChild.children; for (const $item of $listOfButtons) { $item.classList.add("hidden"); } setAction(null); // reset new file, new folder await animate($input, { keyframes: [{ width: "0px" }, { width: "180px" }], time: 200, }); $input.select(); $input.focus(); } else { $page.classList.remove("hover"); $searchImg.setAttribute("src", "data:image/svg+xml;base64," + ICONS.MAGNIFYING_GLASS); $searchImg.setAttribute("alt", "search"); await animate($input, { keyframes: [{ width: "180px" }, { width: "0px" }], time: 100, }); $input.classList.add("hidden"); $input.value = ""; const $listOfButtons = $page.parentElement.firstElementChild.children; for (const $item of $listOfButtons) { $item.classList.remove("hidden"); animate($item, { time: 100, keyframes: slideXIn(5) }); } setState("search", ""); } return $input; }), rxjs.mergeMap(($input) => rxjs.merge( rxjs.fromEvent($input, "input"), rxjs.fromEvent($input, "change"), ).pipe( rxjs.map(() => $input.value), rxjs.distinctUntilChanged(), rxjs.tap((val) => setState("search", val)), )), ), )), )); onDestroy(() => setState("search", "")); effect(getSelectionLength$.pipe( rxjs.filter((l) => l >= 1), rxjs.map((size) => render(createFragment(` <button data-bind="clear"> ${size} <component-icon name="close"></component-icon> </button> `))), rxjs.mergeMap(($page) => onClick(qs($page, `[data-bind="clear"]`)).pipe( rxjs.tap(() => clearSelection()), rxjs.takeUntil(getSelection$().pipe(rxjs.skip(1))), )), )); } export function init() { return Promise.all([ loadCSS(import.meta.url, "../../css/designsystem_dropdown.css"), loadCSS(import.meta.url, "./ctrl_submenu.css"), loadCSS(import.meta.url, "./modal_share.css"), loadCSS(import.meta.url, "./modal_tag.css"), ]); } function generateLinkAttributes(selections) { let filename = "archive.zip"; let href = "api/files/zip?"; if (selections.length === 1) { const path = selections[0].path; const regDir = new RegExp("/$"); const isDir = regDir.test(path); if (isDir) { filename = basename(path.replace(regDir, "")) + ".zip"; } else { filename = basename(path); href = "api/files/cat?"; } } href += selections.map(({ path }) => "path=" + encodeURIComponent(path)).join("&"); href = forwardURLParams(href, ["share"]); return `href="${href}" download="${safe(filename)}"`; } function toggleDependingOnPermission(path, action) { return calculatePermission(path, action) === false ? ` style="display:none"` : ""; } ================================================ FILE: public/assets/pages/filespage/ctrl_upload.css ================================================ /***************************/ /* COMPONENT: UPLOAD QUEUE */ .component_upload { position: fixed; bottom: 0; right: 00px; z-index: 999; width: 100%; max-width: 550px; box-shadow: 1px 2px 20px rgba(0, 0, 0, 0.1); background: white; box-sizing: border-box; border-top-left-radius: 15px; border-top-right-radius: 15px; } .dark-mode .component_upload { color: var(--bg-color); } .component_upload h2 { padding: 20px 20px 5px 20px; margin: 0 0 5px 0; font-size: 1.2em; font-weight: normal; } .component_upload h2 .percent { color: var(--emphasis-primary); } .component_upload h2 .count_block { display: inline; margin-left: 10px; } .component_upload h2 .count_block span.grandTotal { font-size: 0.8em; opacity: 0.8; } .component_upload h2 .count_block span.grandTotal:before { content: "/"; } .component_upload h2 .count_block span.completed { opacity: 0.8; } .component_upload h2 .component_icon { cursor: pointer; margin-left: 10px; width: 20px; float: right; } .component_upload h3 { padding: 3px 20px 3px 20px; margin: 0; font-size: 0.95rem; font-weight: normal; background: var(--dark); color: var(--super-light); } .component_upload h3 span { float: right; } .component_upload .stats_content { padding: 5px 10px 5px 20px; clear: both; max-height: 200px; overflow-y: auto; overflow-x: hidden; font-size: 0.85em; } .component_upload .stats_content:empty { display: none; } .component_upload .stats_content .error_color { color: var(--error); } .component_upload .stats_content .todo_color { opacity: 0.7; } .component_upload .stats_content .file_row { display: flex; flex-direction: row; flex-wrap: wrap; width: 100%; } .component_upload .stats_content .file_row .file_path { flex: 1; padding: 0px 3px 0px 0; line-height: 25px; } .component_upload .stats_content .file_row .file_path .speed { font-size: 0.7rem; opacity: 0.7; padding-left: 5px; line-height: 0.7rem; } .component_upload .stats_content .file_row .file_path .speed:not(:empty):before { content: "("; } .component_upload .stats_content .file_row .file_path .speed:not(:empty):after { content: ")"; } .component_upload .stats_content .file_row .file_state { width: 50px; line-height: 25px; text-align: right; padding-right: 3px; } .component_upload .stats_content .file_row .file_control { width: 25px; display: flex; justify-content: end; align-items: center; margin-left: -2px; margin-right: -2px; } .component_upload .stats_content .file_row .file_control img { display: none; cursor: pointer; height: 22px; width: 22px; } .component_upload .stats_content .file_row:hover .file_control img { display: inherit; padding-top: 3px; } /***********************/ /* COMPONENT: FILEZONE */ [data-bind="filemanager-children"].dropzone { border: 2px dashed!important; } /**************************/ /* COMPONENT: FILE UPLOAD */ .component_mobilefileupload { display: inline; position: fixed; bottom: 25px; right: 25px; z-index: 1; } .component_mobilefileupload input[type="file"] { position: absolute; inset: 0; border-radius: 50%; appearance: none; z-index: -1; } .component_mobilefileupload input[type="file"]:focus-visible { background: rgba(0, 0, 0, 0.8); outline: 2px solid; } .component_mobilefileupload input[type="file"]::file-selector-button { display: none; } .component_mobilefileupload input[type="file"]:focus + label, .component_mobilefileupload input[type="file"] + label:hover { opacity: 0.95; } .component_mobilefileupload input[type="file"] + label { display: flex; align-items: center; justify-content: center; cursor: pointer; background: #57595A; border-radius: 50%; background-position: center; box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; width: 50px; height: 50px; } .component_mobilefileupload input[type="file"] + label:hover { transition: background 0.4s; filter: brightness(0.95); } .component_mobilefileupload input[type="file"] + label:hover::before { content: ""; width: inherit; height: inherit; position: absolute; border-radius: 100%; animation: halofab 0.8s ease-in-out; z-index: -1; } @keyframes halofab { 0% { border: 1px solid transparent; } 100% { border: 25px solid rgba(0,0,0,0.1); opacity: 0 } } .component_mobilefileupload input[type="file"] + label:active { filter: brightness(0.8); } .component_mobilefileupload input[type="file"] + label .component_icon { width: 24px; height: 24px; padding: 13px; } .component_mobilefileupload button { padding: 0; border-radius: inherit; } .mobilefileupload-appear { transform: translateX(75px); transition: transform 0.25s ease; } .mobilefileupload-appear-active { transition-delay: 0.3s; transform: translateX(0px); } ================================================ FILE: public/assets/pages/filespage/ctrl_upload.d.ts ================================================ interface HttpContext { xhr: XMLHttpRequest; } declare function executeHttp( this: HttpContext, url: string, options: { method: string; headers: Record<string, string>; body: any; progress: (event: ProgressEvent) => void; speed: (event: ProgressEvent) => void; } ): Promise<any>; export function init(); export default function(any): void; ================================================ FILE: public/assets/pages/filespage/ctrl_upload.js ================================================ import { createElement, createFragment, createRender } from "../../lib/skeleton/index.js"; import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import { forwardURLParams } from "../../lib/path.js"; import { animate, slideYOut } from "../../lib/animate.js"; import { qs, qsa } from "../../lib/dom.js"; import { AjaxError } from "../../lib/error.js"; import assert from "../../lib/assert.js"; import { get as getConfig } from "../../model/config.js"; import { loadCSS } from "../../helpers/loader.js"; import { currentPath, isNativeFileUpload } from "./helper.js"; import { getPermission, calculatePermission } from "./model_acl.js"; import { mkdir, save } from "./model_virtual_layer.js"; import t from "../../locales/index.js"; const workers$ = new rxjs.BehaviorSubject({ tasks: [], size: null }); const ABORT_ERROR = new AjaxError("aborted", null, "ABORTED"); const TUS_CHECKSUM = false; export default async function(render) { if (!document.querySelector(`[is="component_upload_queue"]`)) { const $queue = createElement(`<div is="component_upload_queue"></div>`); document.body.appendChild($queue); componentUploadQueue(createRender($queue), { workers$ }); } effect(getPermission().pipe( rxjs.filter(() => calculatePermission(currentPath(), "upload")), rxjs.tap(() => { const $page = createFragment(` <div is="component_filezone"></div> <div is="component_upload_fab"></div> `); componentFilezone(createRender(assert.type($page.children[0], HTMLElement)), { workers$ }); componentUploadFAB(createRender(assert.type($page.children[1], HTMLElement)), { workers$ }); render($page); }), )); } export function init() { return loadCSS(import.meta.url, "./ctrl_upload.css"); } function componentUploadFAB(render, { workers$ }) { const $page = createElement(` <div class="component_mobilefileupload no-select"> <form> <input type="file" name="file" id="mobilefileupload" multiple tabindex="0" /> <label for="mobilefileupload" title="${t("Upload")}" role="button"> <img class="component_icon" draggable="false" alt="upload" aria-hidden="true" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMzg0IDUxMiI+CiAgPHBhdGggZmlsbD0iI2YyZjJmMiIgZD0iTSAzNjAsNDYwIEggMjQgQyAxMC43LDQ2MCAwLDQ1My4zIDAsNDQwIHYgLTEyIGMgMCwtMTMuMyAxMC43LC0yMCAyNCwtMjAgaCAzMzYgYyAxMy4zLDAgMjQsNi43IDI0LDIwIHYgMTIgYyAwLDEzLjMgLTEwLjcsMjAgLTI0LDIwIHoiIC8+CiAgPHBhdGggZmlsbD0iI2YyZjJmMiIgZD0ibSAyMjYuNTUzOSwxNDkuMDAzMDMgdiAxNjEuOTQxIGMgMCw2LjYyNyAtNS4zNzMsMTIgLTEyLDEyIGggLTQ0IGMgLTYuNjI3LDAgLTEyLC01LjM3MyAtMTIsLTEyIHYgLTE2MS45NDEgaCAtNTIuMDU5IGMgLTIxLjM4MiwwIC0zMi4wOSwtMjUuODUxIC0xNi45NzEsLTQwLjk3MSBsIDg2LjA1OSwtODYuMDU4OTk3IGMgOS4zNzMsLTkuMzczIDI0LjU2OSwtOS4zNzMgMzMuOTQxLDAgbCA4Ni4wNTksODYuMDU4OTk3IGMgMTUuMTE5LDE1LjExOSA0LjQxMSw0MC45NzEgLTE2Ljk3MSw0MC45NzEgeiIgLz4KPC9zdmc+Cg==" /> </label> </form> </div> `); effect(rxjs.fromEvent(qs($page, `input[type="file"]`), "change").pipe( rxjs.tap(async(e) => { workers$.next({ loading: true }); workers$.next(await processFiles(e.target.files)); }), )); render($page); } function componentFilezone(render, { workers$ }) { const selector = `[data-bind="filemanager-children"]`; const $target = assert.type(qs(document.body, selector), HTMLElement); $target.ondragenter = (e) => { if (!isNativeFileUpload(e)) return; $target.classList.add("dropzone"); }; $target.ondrop = async(e) => { if (!isNativeFileUpload(e)) return; $target.classList.remove("dropzone"); e.preventDefault(); workers$.next({ loading: true }); if (e.dataTransfer.items instanceof window.DataTransferItemList) { let i = 0; const tickID = setInterval(() => workers$.next({ loading: true, size: ++i }), 1000); workers$.next(await processItems(e.dataTransfer.items)); clearInterval(tickID); } else if (e.dataTransfer.files instanceof window.FileList) { workers$.next(await processFiles(e.dataTransfer.files)); } else { assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js"); } }; $target.ondragleave = (e) => { if (!isNativeFileUpload(e)) return; if (!(e.relatedTarget === null || // eg: drag outside the window !e.relatedTarget.closest(selector) // eg: drag on the breadcrumb, ... )) return; $target.classList.remove("dropzone"); }; $target.ondragover = (e) => e.preventDefault(); } const MAX_WORKERS = 4; function componentUploadQueue(render, { workers$ }) { const $page = createElement(` <div class="component_upload hidden"> <h2 class="no-select">${t("Current Upload")} <div class="count_block"> <span class="completed">0</span> <span class="grandTotal">0</span> </div> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close"> </h2> <h3 class="no-select"> <span></span></h3> <div class="stats_content"></div> </div> `); render($page); const $content = qs($page, ".stats_content"); const $file = createElement(` <div class="file_row todo_color"> <div class="file_path ellipsis"><span class="path"></span><span class="speed no-select"></span></div> <div class="file_state no-select"></div> <div class="file_control no-select"></div> </div> `); const updateTotal = { reset: () => { qs($page, ".grandTotal").innerText = 0; qs($page, ".completed").innerText = 0; }, addToTotal: (n) => { const $total = qs($page, ".grandTotal"); $total.innerText = parseInt($total.innerText) + n; }, incrementCompleted: () => { const $completed = qs($page, ".completed"); $completed.innerText = parseInt($completed.innerText) + 1; }, }; // feature1: close the queue onClick(qs($page, `img[alt="close"]`)).pipe(rxjs.tap(async() => { const cleanup = await animate($page, { time: 200, keyframes: slideYOut(50) }); $content.innerHTML = ""; $page.classList.add("hidden"); updateTotal.reset(); cleanup(); })).subscribe(); // feature2: setup the task queue in the dom workers$.subscribe(({ tasks, loading = false, size = 0 }) => { if (loading) { $page.classList.remove("hidden"); updateDOMGlobalTitle($page, t("Loading")+ ".".repeat((size+2)%3+1)); return; } if (tasks.length === 0) return; updateTotal.addToTotal(tasks.length); const $fragment = document.createDocumentFragment(); for (let i = 0; i<tasks.length; i++) { const $task = assert.type($file.cloneNode(true), HTMLElement); $fragment.appendChild($task); $task.setAttribute("data-path", tasks[i]["path"]); $task.firstElementChild.firstElementChild.textContent = tasks[i]["path"]; // qs($todo, ".file_path span.path") $task.firstElementChild.firstElementChild.setAttribute("title", tasks[i]["path"]); $task.firstElementChild.nextElementSibling.classList.add("file_state_todo"); // qs($todo, ".file_state") $task.firstElementChild.nextElementSibling.textContent = t("Waiting"); } $content.appendChild($fragment); $content.classList.remove("hidden"); }); // feature3: process tasks const ICON = { STOP: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgdmlld0JveD0iMCAwIDM4NCA1MTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggc3R5bGU9ImZpbGw6ICM2MjY0Njk7IiBkPSJNMCAxMjhDMCA5Mi43IDI4LjcgNjQgNjQgNjRIMzIwYzM1LjMgMCA2NCAyOC43IDY0IDY0VjM4NGMwIDM1LjMtMjguNyA2NC02NCA2NEg2NGMtMzUuMyAwLTY0LTI4LjctNjQtNjRWMTI4eiIgLz4KPC9zdmc+Cg==", RETRY: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBzdHlsZT0iZmlsbDogIzYyNjQ2OTsiIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNGMtNC40MiAwLTcuOTkgMy41OC03Ljk5IDhzMy41NyA4IDcuOTkgOGMzLjczIDAgNi44NC0yLjU1IDcuNzMtNmgtMi4wOGMtLjgyIDIuMzMtMy4wNCA0LTUuNjUgNC0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02YzEuNjYgMCAzLjE0LjY5IDQuMjIgMS43OEwxMyAxMWg3VjRsLTIuMzUgMi4zNXoiLz48L3N2Zz4K", }; const $iconStop = createElement(`<img class="component_icon" draggable="false" src="${ICON.STOP}" alt="stop" title="${t("Aborted")}">`); const $iconRetry = createElement(`<img class="component_icon" draggable="false" src="${ICON.RETRY}" alt="retry">`); const $close = qs($page, `img[alt="close"]`); const updateDOMTaskProgress = ($task, text) => $task.firstElementChild.nextElementSibling.textContent = text; const updateDOMTaskSpeed = ($task, text) => $task.firstElementChild.firstElementChild.nextElementSibling.textContent = formatSpeed(text); const updateDOMGlobalSpeed = (function(workersSpeed) { let last = 0; return (nworker, currentWorkerSpeed) => { workersSpeed[nworker] = currentWorkerSpeed; if (new Date().getTime() - last <= 500) return; last = new Date().getTime(); const speed = workersSpeed.reduce((acc, el) => acc + el, 0); const $speed = assert.type($page.firstElementChild?.nextElementSibling?.firstElementChild, HTMLElement); $speed.textContent = formatSpeed(speed); }; }(new Array(MAX_WORKERS).fill(0))); const updateDOMGlobalTitle = ($page, text) => $page.firstElementChild.nextElementSibling.firstChild.textContent = text; const updateDOMWithStatus = ($task, { status, exec, nworker }) => { const cancel = () => exec.cancel(); const executeMutation = (status) => { switch (status) { case "todo": updateDOMGlobalTitle($page, t("Running") + "..."); break; case "doing": const $stop = assert.type($iconStop.cloneNode(true), HTMLElement); updateDOMTaskProgress($task, formatPercent(0)); $task.classList.remove("error_color"); $task.classList.add("todo_color"); $task.setAttribute("data-status", "running"); $task.firstElementChild.nextElementSibling.nextElementSibling.replaceChildren($stop); $stop.onclick = () => { cancel(); $task.removeAttribute("data-status"); $task.firstElementChild.nextElementSibling.nextElementSibling.classList.add("hidden"); }; $close.addEventListener("click", cancel, { once: true }); break; case "done": updateDOMGlobalTitle($page, t("Done")); updateDOMTaskProgress($task, t("Done")); updateDOMGlobalSpeed(nworker, 0); updateDOMTaskSpeed($task, 0); $task.removeAttribute("data-path"); $task.removeAttribute("data-status"); $task.classList.remove("todo_color"); $task.firstElementChild.nextElementSibling.nextElementSibling.classList.add("hidden"); $close.removeEventListener("click", cancel); break; case "error": const $retry = assert.type($iconRetry.cloneNode(true), HTMLElement); updateDOMGlobalTitle($page, t("Error")); updateDOMTaskProgress($task, t("Error")); updateDOMGlobalSpeed(nworker, 0); updateDOMTaskSpeed($task, 0); $task.removeAttribute("data-path"); $task.removeAttribute("data-status"); $task.classList.remove("todo_color"); $task.classList.add("error_color"); $task.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.remove(); $task.firstElementChild.nextElementSibling.nextElementSibling.appendChild($retry); $retry.onclick = async() => { executeMutation("todo"); executeMutation("doing"); try { await exec.retry(); executeMutation("done"); } catch (err) { executeMutation("error"); } }; $close.removeEventListener("click", cancel); break; default: assert.fail(`UNEXPECTED_STATUS status="${status}" path="${$task.getAttribute("path")}"`); } }; executeMutation(status); }; let tasks = []; const reservations = new Array(MAX_WORKERS).fill(false); const processWorkerQueue = async(nworker) => { while (tasks.length > 0) { updateDOMGlobalTitle($page, t("Running")+"..."); // step1: retrieve next task const task = nextTask(tasks); if (!task) { await new Promise((resolve) => setTimeout(resolve, 1000)); continue; } // step2: validate the task is ready to run now const $tasks = qsa($page, `[data-path="${task.path}"][data-status="running"]`); if ($tasks.length > 0) { await new Promise((resolve) => setTimeout(resolve, 1000)); tasks.unshift(task); continue; } // step3: process the task through its entire lifecycle const $task = qs($page, `[data-path="${task.path}"]`); const exec = task.exec({ progress: (progress) => updateDOMTaskProgress($task, formatPercent(progress)), speed: (speed) => { updateDOMTaskSpeed($task, speed); updateDOMGlobalSpeed(nworker, speed); }, }); updateDOMWithStatus($task, { exec, status: "doing", nworker }); try { await exec.run(task); updateDOMWithStatus($task, { exec, status: "done", nworker }); } catch (err) { updateDOMWithStatus($task, { exec, status: "error", nworker }); } updateTotal.incrementCompleted(); task.done = true; // step4: execute only at task completion if (tasks.length === 0 && // no remaining tasks reservations.filter((t) => t === true).length === 1 // only for the last remaining job ) updateDOMGlobalTitle($page, t("Done")); } }; const nextTask = (tasks) => { for (let i=0; i<tasks.length; i++) { const possibleTask = tasks[i]; if (!possibleTask.ready()) continue; tasks.splice(i, 1); return possibleTask; } return null; }; const noFailureAllowed = (fn) => fn().catch(() => noFailureAllowed(fn)); workers$.subscribe(async({ tasks: newTasks, loading = false }) => { if (loading) return; tasks = tasks.concat(newTasks); // add new tasks to the pool while (!$page.classList.contains("hidden")) { const nworker = reservations.indexOf(false); if (nworker === -1) break; // the pool of workers is already to its max reservations[nworker] = true; noFailureAllowed(processWorkerQueue.bind(null, nworker)).then(() => reservations[nworker] = false); } reservations.fill(false); }); } class IExecutor { contructor() {} cancel() { throw new Error("NOT_IMPLEMENTED"); } retry() { throw new Error("NOT_IMPLEMENTED"); } run() { throw new Error("NOT_IMPLEMENTED"); } } function workerImplFile({ progress, speed }) { return new class Worker extends IExecutor { constructor() { super(); this.xhr = null; } /** * @override */ cancel() { if (this.xhr) assert.type(this.xhr, XMLHttpRequest).abort(); this.xhr = null; } /** * @override */ async run({ file, path, virtual }) { const _file = await file(); const executeJob = () => this.prepareJob({ file: _file, path, virtual }); this.retry = () => { virtual.before(); return executeJob(); }; return executeJob(); } async prepareJob({ file, path, virtual }) { const chunkSize = getConfig("upload_chunk_size", 0) *1024*1024; const numberOfChunks = Math.ceil(file.size / chunkSize); const cacheHeaders = { "Cache-Control": "no-store", "Pragma": "no-cache", }; const tusHeaders = { "Tus-Resumable": "1.0.0", ...cacheHeaders, }; // Case1: basic upload if (chunkSize === 0 || numberOfChunks === 0 || numberOfChunks === 1) { try { await executeHttp.call(this, toHref(`/api/files/save?path=${encodeURIComponent(path)}`), { method: "POST", headers: { ...cacheHeaders }, body: file, progress, speed, }); virtual.afterSuccess(); } catch (err) { virtual.afterError(); if (err === ABORT_ERROR) return; throw err; } return; } // Case2: chunked upload => TUS: https://www.ietf.org/archive/id/draft-tus-httpbis-resumable-uploads-protocol-00.html const apiURL = toHref(`/api/files/save?path=${encodeURIComponent(path)}`); let uploadURL = ""; let offset = 0; try { // tus: retry mechanism const resp = await executeHttp.call(this, apiURL, { method: "HEAD", headers: { ...tusHeaders }, body: null, progress: () => {}, speed, }); if (file.size === parseInt(resp.headers["upload-length"])) { const tmp = parseInt(resp.headers["upload-offset"]); if (tmp > 0) { offset = tmp; uploadURL = apiURL; } } } catch (err) {} if (offset === 0) { // tus: upload creation try { const resp = await executeHttp.call(this, apiURL, { method: "POST", headers: { ...tusHeaders, "Upload-Length": file.size, }, body: null, progress: () => {}, speed, }); uploadURL = resp.headers.location; if (!uploadURL.startsWith(toHref("/api/files/save?"))) throw new Error("Internal Error"); } catch (err) { virtual.afterError(); if (err === ABORT_ERROR) return; throw err; } } try { for (let i=Math.ceil(offset/chunkSize); i<numberOfChunks; i++) { if (this.xhr === null) break; const chunk = file.slice(offset, offset + chunkSize); const headers = { ...tusHeaders, "Content-Type": "application/offset+octet-stream", "Upload-Offset": offset, }; if (TUS_CHECKSUM && crypto.subtle.digest) { const hash = await crypto.subtle.digest("SHA-1", await chunk.arrayBuffer()); const checksum = [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join(""); headers["Upload-Checksum"] = `sha1 ${checksum}`; } await executeHttp.call(this, uploadURL, { method: "PATCH", headers, body: chunk, progress: (p) => { const start = Math.ceil(100 * offset / file.size); const end = Math.ceil(100 * Math.min(file.size, offset + chunkSize) / file.size); progress(Math.floor(start + (end - start) * p / 100)); }, speed, }); offset += chunkSize; } virtual.afterSuccess(); } catch (err) { virtual.afterError(); if (err === ABORT_ERROR) return; throw err; } } }(); } function workerImplDirectory({ progress }) { return new class Worker extends IExecutor { constructor() { super(); this.xhr = null; } /** * @override */ cancel() { assert.type(this.xhr, XMLHttpRequest).abort(); } /** * @override */ async run({ virtual, path }) { const executeJob = () => this.prepareJob({ virtual, path }); this.retry = () => { virtual.before(); return executeJob(); }; return executeJob(); } async prepareJob({ virtual, path }) { let percent = 0; const id = setInterval(() => { percent += 10; if (percent >= 100) { clearInterval(id); return; } progress(percent); }, 100); try { await executeHttp.call(this, toHref(`/api/files/mkdir?path=${encodeURIComponent(path)}`), { method: "POST", headers: {}, body: null, progress, speed: () => {}, }); clearInterval(id); progress(100); virtual.afterSuccess(); } catch (err) { clearInterval(id); virtual.afterError(); if (err === ABORT_ERROR) return; throw err; } } }(); } function executeHttp(url, { method, headers, body, progress, speed }) { const xhr = new XMLHttpRequest(); const prevProgress = []; this.xhr = xhr; return new Promise((resolve, reject) => { xhr.open(method, forwardURLParams(url, ["share"])); xhr.setRequestHeader("X-Requested-With", "XmlHttpRequest"); xhr.withCredentials = true; for (const key in headers) { xhr.setRequestHeader(key, headers[key]); } xhr.upload.onprogress = (e) => { if (!e.lengthComputable) return; const percent = Math.floor(100 * e.loaded / e.total); progress(percent); if (prevProgress.length === 0) { prevProgress.push(e); return; } prevProgress.push(e); const calculateTime = (p1, pm1) => (p1.timeStamp - pm1.timeStamp)/1000; const calculateBytes = (p1, pm1) => p1.loaded - pm1.loaded; let avgSpeed = 0; for (let i=1; i<prevProgress.length; i++) { const p1 = prevProgress[i]; const pm1 = prevProgress[i-1]; avgSpeed += calculateBytes(p1, pm1) / calculateTime(p1, pm1); } avgSpeed = avgSpeed / (prevProgress.length - 1); speed(avgSpeed); if (e.timeStamp - prevProgress[0].timeStamp > 5000) { prevProgress.shift(); } }; xhr.upload.onabort = () => reject(ABORT_ERROR); xhr.onerror = (e) => reject(new AjaxError("failed", e, "FAILED")); xhr.onload = () => { if ([200, 201, 204].indexOf(xhr.status) === -1) { reject(new Error(xhr.statusText)); return; } progress(100); resolve({ status: xhr.status, headers: xhr.getAllResponseHeaders() .split("\r\n") .reduce((acc, el) => { const tmp = el.split(": "); if (typeof tmp[0] === "string" && typeof tmp[1] === "string") { acc[tmp[0]] = tmp[1]; } return acc; }, {}) }); }; xhr.send(body); }); } async function processFiles(filelist) { const tasks = []; let size = 0; const detectFiletype = (file) => { // the 4096 is an heuristic observed and taken from: // https://stackoverflow.com/questions/25016442 // however the proposed answer is just wrong as it doesn't consider folder with // name such as: test.png and as Stackoverflow favor consanguinity with their // point system, I couldn't rectify the proposed answer. The following code is // actually working as expected if (file.size % 4096 !== 0) { return Promise.resolve("file"); } return new Promise((resolve) => { const reader = new window.FileReader(); const tid = setTimeout(() => reader.abort(), 1000); reader.onload = () => resolve("file"); reader.onabort = () => resolve("file"); reader.onerror = () => { resolve("directory"); clearTimeout(tid); }; reader.readAsArrayBuffer(file); }); }; for (const currentFile of filelist) { const type = await detectFiletype(currentFile); let path = currentPath() + currentFile.name; let task = null; switch (type) { case "file": task = { type: "file", file: () => new Promise((resolve) => resolve(currentFile)), path, date: currentFile.lastModified, exec: workerImplFile, virtual: save(path, currentFile.size), done: false, ready: () => true, }; size += currentFile.size; break; case "directory": path += "/"; task = { type: "directory", path, date: currentFile.lastModified, exec: workerImplDirectory, virtual: mkdir(path), done: false, ready: () => true, }; size += 4096; break; default: assert.fail(`NOT_SUPPORTED type="${type}"`); } task.virtual.before(); tasks.push(task); } return { tasks, size }; } async function processItems(itemList) { const bfs = async(queue) => { const tasks = []; let size = 0; const basepath = currentPath(); while (queue.length > 0) { const entry = queue.shift(); const path = basepath + entry.fullPath.substring(1); let task = {}; if (entry === null) continue; else if (entry.isFile) { const entrySize = await new Promise((resolve) => { if (typeof entry.getMetadata !== "function") return resolve(-1); // eg: firefox entry.getMetadata( ({ size }) => resolve(size), (err) => resolve(-1), ); }); task = { type: "file", file: () => new Promise((resolve, reject) => entry.file( (file) => resolve(file), (error) => reject(error), )), path, exec: workerImplFile, virtual: save(path, entrySize), done: false, ready: () => false, }; size += entrySize; } else if (entry.isDirectory) { task = { type: "directory", path: path + "/", exec: workerImplDirectory, virtual: mkdir(path), done: false, ready: () => false, }; const reader = entry.createReader(); let q = []; do { q = await new Promise((resolve) => reader.readEntries(resolve)); queue = queue.concat(q); } while (q.length > 0); } else { assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js"); } task.ready = () => { const isInDirectory = (filepath, folder) => folder.indexOf(filepath) === 0; for (let i=0; i<tasks.length; i++) { // filter out tasks that are NOT dependencies of the current task if (tasks[i].path === task.path) break; else if (tasks[i].type === "file") continue; else if (isInDirectory(tasks[i].path, task.path) === false) continue; // block execution unless dependent task has completed if (tasks[i].done === false) return false; } return true; }; task.virtual.before(); tasks.push(task); } return { tasks, size }; }; const entries = []; for (const item of itemList) entries.push(item.webkitGetAsEntry()); return await bfs(entries); } function formatPercent(number) { return `${number}%`; } function formatSize(bytes, si = true) { const thresh = si ? 1000 : 1024; if (Math.abs(bytes) < thresh) { return bytes.toFixed(1) + "B"; } const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; let u = -1; do { bytes /= thresh; ++u; } while (Math.abs(bytes) >= thresh && u < units.length - 1); return bytes.toFixed(1) + units[u]; } function formatSpeed(bytes, si = true) { return formatSize(bytes, si)+ "/s"; } ================================================ FILE: public/assets/pages/filespage/helper.js ================================================ import { extname } from "../../lib/path.js"; import { fromHref } from "../../lib/skeleton/router.js"; import assert from "../../lib/assert.js"; const regexCurrentPath = new RegExp("^/files"); export function currentPath() { return decodeURIComponent(fromHref(location.pathname).replace(regexCurrentPath, "")); } const regexDir = new RegExp("/$"); export function isDir(path) { return regexDir.test(path); } export function extractPath(path) { path = path.replace(regexDir, ""); const p = path.split("/"); const filename = p.pop(); return [p.join("/") + "/", filename]; } export function sort(files, type, order) { switch (type) { case "name": return sortByName(files, order); case "date": return sortByDate(files, order); case "size": return sortBySize(files, order); default: return sortByType(files, order); } } export const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); function sortByType(files, order) { let tmp; return files.sort(function(fileA, fileB) { tmp = _moveDownward(fileA, fileB); if (tmp !== 0) return tmp; tmp = _moveFolderUpward(fileA, fileB); if (tmp !== 0) return tmp; tmp = _moveHiddenFilesDownward(fileA, fileB); if (tmp !== 0) return tmp; const faname = fileA.name.toLowerCase(); const fbname = fileB.name.toLowerCase(); const aExt = extname(faname); const bExt = extname(fbname); if (aExt === bExt) return sortString(faname, fbname, order); return sortString(aExt, bExt, order); }); } function sortByName(files, order) { let tmp; return files.sort(function(fileA, fileB) { tmp = _moveDownward(fileA, fileB); if (tmp !== 0) return tmp; tmp = _moveFolderUpward(fileA, fileB); if (tmp !== 0) return tmp; tmp = _moveHiddenFilesDownward(fileA, fileB); if (tmp !== 0) return tmp; return sortString(fileA.name.toLowerCase(), fileB.name.toLowerCase(), order); }); } function sortByDate(files, order) { return files.sort(function(fileA, fileB) { if (fileB.time === fileA.time) { return sortString(fileA.name, fileB.name, order); } return sortNumber(fileA.time, fileB.time, order); }); } function sortBySize(files, order) { let tmp; return files.sort(function(fileA, fileB) { tmp = _moveFolderUpward(fileA, fileB); if (tmp !== 0) return tmp; tmp = _moveHiddenFilesDownward(fileA, fileB); if (tmp !== 0) return tmp; if (fileB.size === fileA.size) { return sortString(fileA.name, fileB.name, order); } return sortNumber(fileA.size, fileB.size, order); }); } function sortString(a, b, order) { if (order === "asc") return a > b ? +1 : -1; return a < b ? +1 : -1; } function sortNumber(a, b, order) { if (order === "asc") return b - a; return a - b; } function _moveDownward(fileA, fileB) { const aIsLast = fileA.last; const bIsLast = fileB.last; if (aIsLast && !bIsLast) return +1; else if (!aIsLast && bIsLast) return -1; return 0; } function _moveFolderUpward(fileA, fileB) { const aIsDirectory = ["directory", "link"].indexOf(fileA.type) !== -1; const bIsDirectory = ["directory", "link"].indexOf(fileB.type) !== -1; if (!aIsDirectory && bIsDirectory) return +1; else if (aIsDirectory && !bIsDirectory) return -1; return 0; } function _moveHiddenFilesDownward(fileA, fileB) { const aIsHidden = fileA.name[0] === "."; const bIsHidden = fileB.name[0] === "."; if (aIsHidden && !bIsHidden) return +1; if (!aIsHidden && bIsHidden) return -1; return 0; } export const isNativeFileUpload = (e) => JSON.stringify(e.dataTransfer.types.slice(-1)) === "[\"Files\"]"; export const isAlreadyFocused = () => { const tagName = assert.type(document.activeElement, HTMLElement).tagName; return ["INPUT", "TEXTAREA"].indexOf(tagName) !== -1; }; ================================================ FILE: public/assets/pages/filespage/modal.css ================================================ .component_modal .modal-error-message { height: 1rem; margin-top: -5px; } .component_modal .modal-error-message:not(:empty) { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; transform: translate3d(0, 0, 0); backface-visibility: hidden; perspective: 1000px; } ================================================ FILE: public/assets/pages/filespage/modal_delete.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, preventDefault } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import t from "../../locales/index.js"; export default function(render, removeLabel) { return document.body.classList.contains("touch-yes") ? renderMobile(render, removeLabel) : renderDesktop(render, removeLabel); } function renderDesktop(render, removeLabel) { const $modal = createElement(` <div> <span style="white-space: nowrap;"> <span class="no-select">${t("Confirm by typing")} "</span>${removeLabel}<span class="no-select">"</span> </span> <form style="margin-top: 10px;"> <input class="component_input" type="text" autocomplete="new-password" value=""> <div class="modal-error-message"></div> </form> </div> `); const ret = new rxjs.Subject(); const isValid = () => $input.value === removeLabel; const $input = qs($modal, "input"); const pressOK = render($modal, (id) => { if (id !== MODAL_RIGHT_BUTTON) { return; } else if (!isValid()) { qs($modal, ".modal-error-message").textContent = t("Doesn't match"); return ret.toPromise(); } ret.next(); ret.complete(); return ret.toPromise(); }).bind(null, MODAL_RIGHT_BUTTON); $input.focus(); effect(rxjs.fromEvent(qs($modal, "form"), "submit").pipe( preventDefault(), rxjs.tap(pressOK), )); return ret.toPromise(); } function renderMobile(_, removeLabel) { return new Promise((done) => { const value = window.prompt(t("Confirm by typing") + ": " + removeLabel, ""); if (value !== removeLabel) { return; } done(null); }); } ================================================ FILE: public/assets/pages/filespage/modal_rename.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import { extname } from "../../lib/path.js"; import rxjs, { effect, preventDefault } from "../../lib/rx.js"; import { qs } from "../../lib/dom.js"; import { MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import t from "../../locales/index.js"; export default function(render, filename) { return document.body.classList.contains("touch-yes") ? renderMobile(render, filename) : renderDesktop(render, filename); } function renderDesktop(render, filename) { const $modal = createElement(` <div> ${t("Rename as")}: <form style="margin-top: 10px;"> <input class="component_input" type="text" autocomplete="new-password" value=""> <div class="modal-error-message"></div> </form> </div> `); const ret = new rxjs.Subject(); const $input = qs($modal, "input"); const pressOK = render($modal, function(id) { const value = $input.value.trim(); if (id !== MODAL_RIGHT_BUTTON) { return; } else if (!value || value === filename) { qs($modal, ".modal-error-message").textContent = t("Not Valid"); return ret.toPromise(); } ret.next(value); ret.complete(); }).bind(null, MODAL_RIGHT_BUTTON); const ext = extname(filename); $input.value = filename; $input.focus(); if (ext === filename) $input.select(); else $input.setSelectionRange(0, filename.length - ext.length - 1); effect(rxjs.fromEvent(qs($modal, "form"), "submit").pipe( preventDefault(), rxjs.tap(pressOK), )); return ret.toPromise(); } function renderMobile(_, filename) { return new Promise((done) => { const value = window.prompt(t("Rename as"), filename); if (!value || value === filename) { return; } done(value); }); } ================================================ FILE: public/assets/pages/filespage/modal_share.css ================================================ .component_share h2 { margin: 0 0 5px 0; font-size: 1.2em; font-weight: 100; display: flex; justify-content: space-between; } .component_share h2 .component_icon { width: 24px; } .component_share component-icon { display: block; text-align: center; margin: 20px 0 30px 0; } .component_share component-icon .component_icon[alt="loading"] { width: 24px; } .component_share .copy { cursor: copy; } .component_share .share--content { margin-bottom: 10px; } .component_share .share--content.link-type { display: flex; flex-direction: row; } .component_share .share--content.link-type > button { cursor: pointer; text-align: center; margin-right: 4px; padding: 15px 0; width: 100%; border-radius: 3px; color: rgba(0, 0, 0, 0.3); text-shadow: 0px 0px 1px var(--color); background: var(--bg-color); background-position: center; line-height: 1rem; align-items: center; justify-content: center; display: flex; } .dark-mode .component_share .share--content.link-type > button { background: var(--color); color: rgba(0,0,0,0.6); } .component_share .share--content.link-type > button.active { background: var(--primary); } .component_share .share--content.link-type > button:hover { transition: background 0.4s; background: var(--primary) radial-gradient(circle, transparent 1%, var(--emphasis-primary) 1%) center/15000%; } .component_share .share--content.link-type > button:active { background-color: #00000033; background-size: 100%; transition: background 0s; } .component_share .share--content.existing-links { overflow-y: auto; -webkit-overflow-scrolling: touch; } .component_share .share--content.existing-links .link-details { display: flex; justify-content: space-between; padding: 3px 5px; border: 2px solid var(--super-light); border-left: none; border-right: none; margin-top: -2px; line-height: 20px; } .touch-yes .component_share .share--content.existing-links .link-details { padding-top: 5px; padding-bottom: 5px; } .component_share .share--content.existing-links .link-details .role { margin-right: 5px; padding-right: 5px; font-size: 0.8em; opacity: 0.6; min-width: 75px; text-transform: uppercase; } .component_share .share--content.existing-links .link-details .path { font-size: 0.9em; flex-grow: 2; width: 100%; overflow-x: hidden; overflow-y: hidden; } .component_share .share--content.existing-links .link-details .link-details--icons { display: flex; flex-direction: row-reverse; flex-wrap: nowrap; } .component_share .share--content.existing-links .link-details .component_icon { width: 20px; cursor: pointer; } .touch-yes .component_share .share--content.existing-links .link-details .component_icon { padding: 0 3px; } .component_share .share--content.advanced-settings, .component_share .share--content.restrictions { max-height: 115px; overflow-y: auto; -webkit-overflow-scrolling: touch; padding-right: 5px; } .component_share .share--content.existing-links { min-height: 80px; } .component_share .shared-link { display: flex; justify-content: space-between; font-size: .9em; background: var(--bg-color); border: 2px solid var(--primary); color: var(--dark); border-radius: 4px; padding: 0px 5px; margin-top: 10px; margin-bottom: 20px; } .dark-mode .component_share .shared-link { background: var(--color); } .component_share .shared-link input { flex-grow: 1; border: none; height: 25px; font-family: arial; font-size: 1rem; box-sizing: border-box; background: inherit; color: inherit; } .touch-yes .component_share .shared-link input { height: 30px; } .component_share .shared-link button { padding: 0 2px; } .component_share .shared-link img { width: 16px; padding-bottom: 1px; } .component_share .formbuilder label { display: block; } .component_share .formbuilder input { background: var(--bg-color); color: var(--color); padding: 5px; border-radius: 4px; border: none; } .component_share .formbuilder input:focus { border: none; } .component_supercheckbox > label { font-size: 0.95em; font-style: italic; } .component_supercheckbox > label .label { margin-left: -5px; } .component_supercheckbox > label input { margin-right: 5px; margin-top: 2px; } .component_supercheckbox > div > input { width: calc(100% - 10px); box-sizing: border-box; margin-bottom: 5px; border: none; border-radius: 3px; font-size: 0.9em; padding: 3px 5px; color: var(--color); background: var(--bg-color); border-bottom: 0; } .dark-mode .component_share .component_checkbox span { background-color: var(--color); border: 2px solid var(--color); } ================================================ FILE: public/assets/pages/filespage/modal_share.js ================================================ import { createElement, createRender } from "../../lib/skeleton/index.js"; import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import assert from "../../lib/assert.js"; import ajax from "../../lib/ajax.js"; import { forwardURLParams, join } from "../../lib/path.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { randomString } from "../../lib/random.js"; import { animate } from "../../lib/animate.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { formTmpl } from "../../components/form.js"; import notification from "../../components/notification.js"; import t from "../../locales/index.js"; import { currentPath, isDir } from "./helper.js"; const IMAGE = { COPY: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMzM2O3N0cm9rZS13aWR0aDowLjYzOTk5OTk5IiBkPSJNNDY0IDBIMTQ0Yy0yNi41MSAwLTQ4IDIxLjQ5LTQ4IDQ4djQ4SDQ4Yy0yNi41MSAwLTQ4IDIxLjQ5LTQ4IDQ4djMyMGMwIDI2LjUxIDIxLjQ5IDQ4IDQ4IDQ4aDMyMGMyNi41MSAwIDQ4LTIxLjQ5IDQ4LTQ4di00OGg0OGMyNi41MSAwIDQ4LTIxLjQ5IDQ4LTQ4VjQ4YzAtMjYuNTEtMjEuNDktNDgtNDgtNDh6TTM2MiA0NjRINTRhNiA2IDAgMCAxLTYtNlYxNTBhNiA2IDAgMCAxIDYtNmg0MnYyMjRjMCAyNi41MSAyMS40OSA0OCA0OCA0OGgyMjR2NDJhNiA2IDAgMCAxLTYgNnptOTYtOTZIMTUwYTYgNiAwIDAgMS02LTZWNTRhNiA2IDAgMCAxIDYtNmgzMDhhNiA2IDAgMCAxIDYgNnYzMDhhNiA2IDAgMCAxLTYgNnoiLz4KPC9zdmc+Cg==", LOADING: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=", DELETE: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODIuNDI4IDQ4Mi40MjkiPgogIDxwYXRoIHN0eWxlPSJmaWxsOiM2ZjZmNmY7c3Ryb2tlLXdpZHRoOjAuOTQ3MjAwNTQiIGQ9Im0gMjM5LjcxMDM4LDEwLjg1ODU2NyBjIC0yOS4yODYzMywwLjE0ODk1OSAtNTYuMjI4MjEsMjMuMTU4MTY3IC02MS4xMzg4Myw1MS45OTYxMjkgLTAuMDM1OSw1LjUyMjQ0IC04LjExOTM2LDEuNTIzODUyIC0xMS44MTQxMSwyLjczNDMwMSAtMjEuNjU5MywwLjM1NzE4IC00My4zODAyLC0wLjY3Njg3NSAtNjUuMDA3MTksMC40Mzg0NTIgLTI1Ljc0Mzk2MSwyLjgxNDg5NiAtNDcuMDQxMDg0LDI2LjM4MTc2IC00Ny4xNzMxNzIsNTIuMjkyMTMxIC0xLjcyMjExOCwyMi4zMjI3NyAxMS42Nzg0MSw0NC43NzgwOSAzMi4zMjg3NjgsNTMuNTM1MzIgMS41MDI3NjcsNy4xMzU1IDAuMjE0MTksMTYuMTEyMjggMC42NDM4LDIzLjk1NTY4IDAuMTEwMTQ1LDc1LjI4MzExIC0wLjIxODQzMywxNTAuNTc3MzcgMC4xNjA5NSwyMjUuODUzNjcgMS40ODk4MDUsMjUuODUxOTIgMjMuOTUyNDE0LDQ4LjI5NzYgNDkuODA1NzI0LDQ5Ljc2Njg3IDY4Ljk5NTMyLDAuMjc5OTggMTM4LjAxNjU0LDAuMjI5NjYgMjA3LjAxMzE3LDAuMDI0NyAyNi4wMTg1MiwtMS4yNzY5MSA0OC43MjA1LC0yMy44MzQ0MyA1MC4xOTI0OSwtNDkuODM3NjcgMC4zNjUyOCwtODMuMTUzOTggMC4wNDk3LC0xNjYuMzI1MDggMC4xNTUzOCwtMjQ5LjQ4NTU4IDIwLjg0ODU5LC04LjUyMTk5IDM0LjU5NTY3LC0zMC45NzQ5OSAzMi45NzkzNiwtNTMuNDExMzEgMC4wNzUyLC0yNi4wNzE2MTEgLTIxLjMyNDY5LC00OS45MDA0NDIgLTQ3LjIyOTkxLC01Mi42OTkyMDYgLTI0LjY2MTA5LC0xLjA5MzY1MSAtNDkuNDEyODgsLTAuMDg0ODcgLTc0LjEwNTUsLTAuNDMyOSAtMy45NDgzNywwLjYxMjkxMSAtMi4zMDc4NywtNS4zNzQ4NTkgLTMuODc5MTQsLTcuOTc4OTIzIC03LjI2MTcsLTI3LjU4NzA0MiAtMzQuMzcxMDIsLTQ3Ljg1NDgyMzggLTYyLjkzMTc5LC00Ni43NTE2NDMgeiBtIDEuNTA0MDQsMjguNTMwNzE3IGMgMTUuNDcwMDYsLTAuMzA1NjE5IDMwLjI2NjY3LDExLjA4NDk0OCAzNC4wMzQ0NywyNi4xOTk3MTMgLTIyLjY4MzQsLTAuMDA1OSAtNDUuMzk2OTIsMC4wMTIzMyAtNjguMDYxNTQsLTAuMDA5MiAzLjgwNzAyLC0xNS4wNzAyMDQgMTguMzQxMTcsLTI2LjQxOTgyMyAzNC4wMjcwNywtMjYuMTkwNDYzIHogTSAxMDguNjc4NTEsOTQuMTM2MzYzIGMgODkuNDUyNTcsMC4xMzkxNjYgMTc4LjkyODgzLC0wLjI3NzY1MSAyNjguMzY2NjksMC4yMDcyIDEzLjc0MTMxLDEuNDE4NTc4IDIzLjkyNjY0LDE1LjE3NjA3NyAyMi4yNjY2MiwyOC43MDgzMTcgMC4wMzI1LDE0LjU1MDU0IC0xNC4wNzUxNCwyNi41NjAyNiAtMjguNDA0OTIsMjQuODcxNDIgLTg4LjUwNjYsLTAuMTQwMzcgLTE3Ny4wMzY5MSwwLjI4MDA2IC0yNjUuNTI4OCwtMC4yMDkwNiBDIDkxLjEyNTU5LDE0Ni4yNDIyMyA4MC45ODkyODUsMTMxLjcwMTYzIDgzLjE4MTc5NCwxMTcuNzAxNjggODMuOTcwODg3LDEwNC43NDEzIDk1LjU3NzEzMSw5My45MjA1OTYgMTA4LjY3ODUxLDk0LjEzNjM2MyBaIE0gMzY2LjMzLDE3Ni40NzA2NiBjIC0wLjE0MDc3LDgxLjQyODQ4IDAuMjgwNjIsMTYyLjg4MDcgLTAuMjA5MDUsMjQ0LjI5NDQ3IC0xLjQzODYyLDEzLjkxMTUxIC0xNS40NzY4NSwyNC4xMDU4OCAtMjkuMTUyMzIsMjIuMjc1ODggLTY2LjE5Njc4LC0wLjE0MTYyIC0xMzIuNDE3NDMsMC4yODE3NSAtMTk4LjU5OTQ1LC0wLjIwOTA2IC0xMy44OTE2OSwtMS40NDg4IC0yNC4xMTkwOSwtMTUuNDc1NzUgLTIyLjI3MjE2LC0yOS4xNTc4NiAwLC03OS4wNjc4MSAwLC0xNTguMTM1NjEgMCwtMjM3LjIwMzQzIDgzLjQxMDk4LDAgMTY2LjgyMTk4LDAgMjUwLjIzMjk4LDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iZmlsbDojNmY2ZjZmO3N0cm9rZS13aWR0aDowLjk4MjA4MSIgZD0ibSAxNzEuNjg2NDQsMjQ3LjQ3Mzc5IGMgLTkuMzQ2NzYsMC4xNTY0NCAtMTUuNzQwMzIsOS44ODgwNSAtMTQuMDg2NzMsMTguNzExMzMgMC4xMjM1MSw0Ny42MjcwMSAtMC4yNDQwMSw5NS4yNzkwMyAwLjE3ODM5LDE0Mi44OTA4NyAxLjIwNzY0LDEwLjk3MTM2IDE1LjkxODAzLDE2LjUyNzk0IDI0LjA3MjQ5LDkuMDg0MjUgOC40MTc1OSwtNi44MTg4NyA0LjQ3NDY5LC0xOC44ODM5MiA1LjM0Nzc0LC0yOC4wODEzOCAtMC4xMjQzOSwtNDMuMzcxMjcgMC4yNDUyLC04Ni43Njc4NCAtMC4xNzgzOSwtMTMwLjEyMzgxIC0xLjAzNzk1LC03LjMxNDM5IC03Ljk1MDU0LC0xMi45NTcwNSAtMTUuMzMzNSwtMTIuNDgxMjYgeiIgLz4KICA8cGF0aCBzdHlsZT0iZmlsbDojNmY2ZjZmO3N0cm9rZS13aWR0aDowLjk4MjA4MSIgZD0ibSAyNDAuNTAxMTYsMjQ3LjQ3Mzc5IGMgLTkuMzQ2NDksMC4xNTYxNiAtMTUuNzQwNjcsOS44ODgxNyAtMTQuMDg2NzMsMTguNzExMzMgMC4xMjM1Miw0Ny42MjcwMSAtMC4yNDQwMSw5NS4yNzkwMyAwLjE3ODM5LDE0Mi44OTA4NyAxLjgwNTA0LDE3LjU2NDg5IDMwLjM3NDEyLDE1LjM0MjI3IDI5LjQyMDIzLC0yLjMwMTc2IC0wLjEyMzMzLC00OC45MzY0MiAwLjI0Mzc3LC05Ny44OTgyIC0wLjE3ODM4LC0xNDYuODE5MTggLTEuMDM3MzUsLTcuMzE0MSAtNy45NTEwMSwtMTIuOTU2OTQgLTE1LjMzMzUxLC0xMi40ODEyNiB6IiAvPgogIDxwYXRoIHN0eWxlPSJmaWxsOiM2ZjZmNmY7c3Ryb2tlLXdpZHRoOjAuOTgyMDgxIiBkPSJtIDMwOS4zMTU4OCwyNDcuNDczNzkgYyAtOS4zNDcxMSwwLjE1NTMgLTE1Ljc0MzE0LDkuODg3MTMgLTE0LjA4NjcyLDE4LjcxMTMzIDAuMTIzNTQsNDcuNjI0OTkgLTAuMjQ0MDYsOTUuMjc1ODEgMC4xNzgzOCwxNDIuODg1MTEgMS4xOTg1NiwxMC45NzMyMSAxNS45MTY2NCwxNi41MzYwNiAyNC4wNzA1OCw5LjA5MDAxIDguNDE5MzMsLTYuODE3ODcgNC40NzM2NiwtMTguODg0MiA1LjM0Nzc0LC0yOC4wODEzOCAtMC4xMjQ0MiwtNDMuMzcxMjQgMC4yNDUyNCwtODYuNzY4MDQgLTAuMTc4MzksLTEzMC4xMjM4MSAtMS4wMzY3NCwtNy4zMTMyMSAtNy45NTAxMiwtMTIuOTU2NTggLTE1LjMzMTU5LC0xMi40ODEyNiB6IiAvPgo8L3N2Zz4K", EDIT: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjkgMTI5Ij4KICA8cGF0aCBmaWxsPSIjNkY2RjZGIiBkPSJtMTE5LjIsMTE0LjNoLTEwOS40Yy0yLjMsMC00LjEsMS45LTQuMSw0LjFzMS45LDQuMSA0LjEsNC4xaDEwOS41YzIuMywwIDQuMS0xLjkgNC4xLTQuMXMtMS45LTQuMS00LjItNC4xeiIgLz4KICA8cGF0aCBmaWxsPSIjNkY2RjZGIiBkPSJtNS43LDc4bC0uMSwxOS41YzAsMS4xIDAuNCwyLjIgMS4yLDMgMC44LDAuOCAxLjgsMS4yIDIuOSwxLjJsMTkuNC0uMWMxLjEsMCAyLjEtMC40IDIuOS0xLjJsNjctNjdjMS42LTEuNiAxLjYtNC4yIDAtNS45bC0xOS4yLTE5LjRjLTEuNi0xLjYtNC4yLTEuNi01LjktMS43NzYzNmUtMTVsLTEzLjQsMTMuNS01My42LDUzLjVjLTAuNywwLjgtMS4yLDEuOC0xLjIsMi45em03MS4yLTYxLjFsMTMuNSwxMy41LTcuNiw3LjYtMTMuNS0xMy41IDcuNi03LjZ6bS02Mi45LDYyLjlsNDkuNC00OS40IDEzLjUsMTMuNS00OS40LDQ5LjMtMTMuNiwuMSAuMS0xMy41eiIvPgo8L3N2Zz4K", }; export default function(render, { path }) { const $modal = createElement(` <div class="component_share"> <h2>${t("Create a New Link")}</h2> <div class="share--content link-type no-select"> <button data-role="viewer">${t("Viewer")}</button> <button data-role="editor">${t("Editor")}</button> <button data-role="uploader" class="${isDir(path) ? "" : "hidden"}">${t("Uploader")}</button> </div> <div data-bind="share-body"></div> </div> `); render($modal); const ret = new rxjs.Subject(); const role$ = new rxjs.BehaviorSubject(null); const state = { /** @type {object} */ form: {}, /** @type {any[] | null} */ links: null, }; // feature: select const toggle = (val) => rxjs.mergeMap(() => { state.form = {}; role$.next(role$.value === val ? null : val); return rxjs.EMPTY; }); effect(rxjs.merge( onClick(qs($modal, `[data-role="viewer"]`)).pipe(toggle("viewer")), onClick(qs($modal, `[data-role="editor"]`)).pipe(toggle("editor")), onClick(qs($modal, `[data-role="uploader"]`)).pipe(toggle("uploader")), role$.asObservable(), ).pipe(rxjs.tap(() => { const ctrl = role$.value === null ? ctrlListShares : ctrlCreateShare; // feature: set active button for (const $button of qs($modal, ".share--content").children) { $button.getAttribute("data-role") === role$.value ? $button.classList.add("active") : $button.classList.remove("active"); } // feature: render body and associated events ctrl(createRender(qs($modal, `[data-bind="share-body"]`)), { formState: { path, ...state.form, }, formLinks: state.links, load: (data) => { const role = shareObjToRole(data); state.form = { ...data, url_enable: !!data.url, password_enable: !!data.password, expire_enable: !!data.expire, users_enable: !!data.users, }; role$.next(role); }, save: async({ id, ...data }) => { const body = { id, path, ...data, ...roleToShareObj(role$.value) }; await ajax({ method: "POST", body, url: `api/share/${id}`, }).toPromise(); assert.truthy(state.links).push({ ...body, path: body.path.substring(currentPath().length - 1), }); role$.next(null); }, remove: async({ id }) => { await ajax({ method: "DELETE", url: `api/share/${id}`, }).toPromise(); state.links = (state.links || []).filter((link) => link && link.id !== id); role$.next(null); }, all: async() => { const { responseJSON } = await ajax({ url: `api/share?path=` + encodeURIComponent(path), method: "GET", responseType: "json", }).toPromise(); const currentFolder = path.replace(new RegExp("/$"), "").split("/").pop(); const sharedLinkIsFolder = new RegExp("/$").test(path); state.links = responseJSON.results.map((obj) => { obj.path = sharedLinkIsFolder ? `./${currentFolder}${obj.path}` : `./${currentFolder}`; return obj; }).sort((a, b) => { if (a.path === b.path) return a.id > b.id ? 1 : -1; return a.path > b.path ? 1 : -1; }); return state.links; }, }); }))); return ret.toPromise(); } async function ctrlListShares(render, { load, remove, all, formLinks }) { const $page = createElement(` <div class="hidden"> <h2>${t("Existing Links")}</h2> <div class="share--content existing-links" style="max-height: 90px;"> <component-icon name="loading"></component-icon> </div> </div> `); render($page); effect(rxjs.merge( rxjs.of(formLinks).pipe(rxjs.filter((val) => val !== null)), rxjs.from(all()), ).pipe(rxjs.tap((links) => { if (links.length === 0) { $page.classList.add("hidden"); return; } $page.classList.remove("hidden"); const $fragment = document.createDocumentFragment(); const $content = qs($page, ".share--content"); let length = links.length; links.forEach((shareObj) => { const $share = createElement(` <div class="link-details no-select"> <div class="copy role ellipsis">${t(shareObjToRole(shareObj))}</div> <div class="copy path ellipsis" title="${safe(shareObj.path)}">${safe(shareObj.path)}</div> <div class="link-details--icons"> <img class="component_icon" draggable="false" src="${IMAGE.DELETE}" alt="delete"> <img class="component_icon" draggable="false" src="${IMAGE.EDIT}" alt="edit"> </div> </div> `); qsa($share, ".copy").forEach(($el) => $el.onclick = () => { const link = location.origin + forwardURLParams(toHref(`/s/${shareObj.id}`), ["share"]); copyToClipboard(link); notification.info(t("The link was copied in the clipboard")); }); qs($share, `[alt="delete"]`).onclick = async() => { $share.remove(); length -= 1; if (length === 0) $content.replaceChildren(createElement(` <component-icon name="loading"></component-icon> `)); await remove(shareObj); }; qs($share, `[alt="edit"]`).onclick = () => load(shareObj); $fragment.appendChild($share); }); $content.replaceChildren($fragment); }))); } async function ctrlCreateShare(render, { save, formState }) { const enc = (p) => encodeURIComponent(p).replaceAll("%2F", "/"); if (formState.path) formState.path = join( location.origin + enc(currentPath()), enc(formState.path), ); let id = formState.id || randomString(7); const $page = createElement(` <div> <h2 class="no-select pointer">${t("Advanced")}<span><img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMyMSIgZD0ibSA3LjcwNSw4LjA0NSA0LjU5LDQuNTggNC41OSwtNC41OCAxLjQxLDEuNDEgLTYsNiAtNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_bottom"></span></h2> <form class="share--content restrictions no-select"></form> <div class="shared-link"> <input name="create" class="copy" type="text" readonly="" value="${location.origin}${toHref("/s/" + id)}"> <button title="Copy URL"> <img class="component_icon" draggable="false" src="${IMAGE.COPY}" alt="copy"> </button> </div> </div> `); render($page); const $body = qs($page, ".restrictions"); // feature1: setup the shared link form const formSpec = { users_enable: { type: "enable", label: t("Only for users"), target: ["users"], default: false, }, users: { id: "users", type: "text", placeholder: "name0@email.com,name1@email.com", }, password_enable: { label: t("Password"), type: "enable", target: ["password"], default: false, }, password: { id: "password", type: "text", placeholder: t("Password"), }, expire_enable: { label: t("Expiration"), type: "enable", target: ["expire"], default: false, }, expire: { id: "expire", type: "date", }, url_enable: { label: "link", type: "enable", target: ["link"], default: false, }, url: { id: "link", type: "text", }, path: { type: "hidden", }, }; const tmpl = formTmpl({ renderNode: () => createElement("<div></div>"), renderLeaf: ({ label, type }) => { if (type !== "enable") return createElement("<label></label>"); const title = label === "users_enable" ? t("Only for users") : label === "expire_enable" ? t("Expiration") : label === "password_enable" ? t("Password") : label === "url_enable" ? t("Custom Link url") : assert.fail("unknown label"); return createElement(` <div class="component_supercheckbox"> <label class="ellipsis"> <span data-bind="children"></span> <span class="label">${title}</span> </label> </div> `); }, }); const $form = await createForm(mutateForm(formSpec, formState), tmpl); $body.replaceChildren($form); const clientHeight = $body.offsetHeight; $body.classList.add("hidden"); qs($page, "h2").onclick = async() => { // toggle advanced button if ($body.classList.contains("hidden")) { $body.classList.remove("hidden"); await animate($body, { time: 200, keyframes: [{ height: "0" }, { height: `${clientHeight}px` }], }); return; } await animate($body, { time: 100, keyframes: [{ height: `${clientHeight}px` }, { height: "0" }], }); $body.classList.add("hidden"); }; // sync editable custom link input with link id effect(rxjs.fromEvent(qs($form, `[name="url"]`), "keyup").pipe(rxjs.tap((e) => { id = e.target.value.replaceAll(new RegExp("[^0-9A-Za-z\-]", "g"), ""); qs(assert.type($form.closest(".component_share"), HTMLElement), `input[name="create"]`).value = `${location.origin}${toHref("/s/" + id)}`; }))); // feature: create a shared link const $copy = qs($page, `[alt="copy"]`); effect(onClick(qs($page, ".shared-link")).pipe( rxjs.first(), rxjs.switchMap(async() => { const form = new FormData(assert.type(qs(document.body, ".component_share form"), HTMLFormElement)); const body = [...form].reduce((acc, [key, value]) => { if (form.has(`${key}_enable`)) acc[key] = value; return acc; }, { id, path: form.get("path") }); $copy.setAttribute("src", IMAGE.LOADING); const link = location.origin + forwardURLParams(toHref(`/s/${id}`), ["share"]); await save(body); copyToClipboard(link); notification.info(t("The link was copied in the clipboard")); }), rxjs.catchError((err) => { $copy.setAttribute("src", IMAGE.COPY); notification.error(t(err.message)); throw err; }), rxjs.retry(), )); } function roleToShareObj(role) { return { can_read: (function(r) { if (r === "viewer") return true; else if (r === "editor") return true; return false; }(role)), can_write: (function(r) { if (r === "editor") return true; return false; }(role)), can_upload: (function(r) { if (r === "uploader") return true; else if (r === "editor") return true; return false; }(role)), }; } function shareObjToRole({ can_read, can_write, can_upload }) { if (can_read === true && can_write === false && can_upload === false) { return "viewer"; } else if (can_read === false && can_write === false && can_upload === true) { return "uploader"; } else if (can_read === true && can_write === true && can_upload === true) { return "editor"; } return undefined; } export function copyToClipboard(str) { if (!str) return; const $input = document.createElement("input"); $input.setAttribute("type", "text"); $input.setAttribute("style", "position: absolute; top:0;left:0;background:red"); $input.setAttribute("display", "none"); document.body.appendChild($input); $input.value = str; $input.select(); document.execCommand("copy"); $input.remove(); } ================================================ FILE: public/assets/pages/filespage/modal_tag.css ================================================ .component_tag { font-size: 1rem; } .component_tag form { margin-bottom: 15px; } .component_tag form input { font-size: 1rem; border: 2px solid var(--border); border-radius: 5px; padding: 5px 10px; color: rgba(0,0,0,0.75); width: 100%; display: block; box-sizing: border-box; } .component_tag form input::placeholder { color: rgba(0,0,0,0.2); } .component_tag [data-bind="taglist"] { display: flex; gap: 5px; flex-wrap: wrap; margin: -3px 0 0px 0px; padding: 0; } .component_tag [data-bind="taglist"] .component_skeleton { margin: 0; height: 29px; } .component_tag .tag { display: flex; background: #f2f2f2; transition: all 0.1s ease; padding: 3px 5px 3px 12px; border-radius: 5px; margin: 0; font-size: 0.95rem; cursor: pointer; letter-spacing: -0.5px; text-transform: lowercase; } .component_tag .tag.active { background: var(--dark); color: #f2f2f2; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); } .component_tag .tag svg { width: 15px; height: 15px; border-radius: 50%; margin-left: 5px; padding: 4px; } .touch-no .component_tag .tag svg:hover { background: rgba(0, 0, 0, 0.05); } .touch-no .component_tag .tag.active svg:hover { background: rgba(255, 255, 255, 0.15); } .component_tag .tag img[alt="close"] { width: 14px; padding: 5px; } .component_tag .scroll-y { overflow-y: auto !important; max-height: 200px; } ================================================ FILE: public/assets/pages/filespage/modal_tag.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import { forwardURLParams } from "../../lib/path.js"; import ajax from "../../lib/ajax.js"; import { qs } from "../../lib/dom.js"; import assert from "../../lib/assert.js"; import { generateSkeleton } from "../../components/skeleton.js"; import t from "../../locales/index.js"; const shareID = new URLSearchParams(location.search).get("share"); const $tmpl = createElement(` <div class="tag no-select"> <div class="ellipsis">Projects</div> <svg style="${shareID ? "opacity:0.2" : ""}" class="component_icon" draggable="false" alt="close" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </div> `); export default async function(render, { path }) { const $modal = createElement(` <div class="component_tag"> <form class="${shareID ? "hidden" : ""}"> <input name="tag" type="text" placeholder="${t("Add a Tag")}" value=""> </form> <div class="scroll-y" data-bind="taglist"> ${generateSkeleton(1)} </div> </div> `); render($modal); const tags$ = new rxjs.BehaviorSubject(await rxjs.zip( ajax({ url: forwardURLParams(`api/metadata?path=${path}`, ["share"]), method: "GET", responseType: "json", }).pipe( rxjs.map(({ responseJSON }) => responseJSON.results .reduce((acc, { id, value }) => { if (id !== "tags") return acc; acc = acc.concat(value.split(", ")); return acc; }, []) ), ), shareID ? rxjs.of([]) : ajax({ url: forwardURLParams("api/metadata/search", ["share"]), method: "POST", responseType: "json", body: { path: "/", tags: [] }, }).pipe( rxjs.map(({ responseJSON }) => Object .values(responseJSON.results) .reduce((acc, forms) => forms.reduce((facc, { id, value }) => { if (id !== "tags") return facc; const vals = value.split(", "); for (let i=0; i<vals.length; i++) { if (facc.indexOf(vals[i]) !== -1) continue; facc.push(vals[i]); } return facc; }, acc), []), ), ), ).pipe(rxjs.map(([currentTags, allTags]) => { const out = currentTags.map((name) => ({ name, active: true })); for (let i=0; i<allTags.length; i++) { if (currentTags.indexOf(allTags[i]) === -1) { out.push({ name: allTags[i], active: false }); } } if (out.length === 0 && !shareID) { return [{ name: t("bookmark"), active: false }]; } return out; })).toPromise()); const save = (tags) => ajax({ url: forwardURLParams(`api/metadata?path=${path}`, ["share"]), method: "POST", body: tags.length === 0 ? [] : [{ id: "tags", type: "hidden", value: tags.join(", ") }], }).pipe(rxjs.tap(() => window.dispatchEvent(new Event("filestash::tag")))); // feature: create DOM const dom$ = tags$.pipe( rxjs.map((tags) => tags.sort((a, b) => a.name > b.name ? 1 : -1)), rxjs.map((tags) => tags.map(({ name, active }) => { const $el = assert.type($tmpl, HTMLElement).cloneNode(true); $el.firstElementChild.innerText = name; if (active) $el.classList.add("active"); qs($el, "svg").onclick = (e) => { if (shareID) return; e.preventDefault(); e.stopPropagation(); $el.classList.remove("active"); tags$.next(tags$.value.filter((tag) => { return tag.name !== $el.innerText.trim(); })); save(tags$.value .filter(({ active }) => active) .map(({ name }) => name)).toPromise(); }; return $el; })), rxjs.tap(($nodes) => { const $container = qs($modal, `[data-bind="taglist"]`); if ($nodes.length === 0) $container.replaceChildren(createElement(`<div class="center full-width">∅</div>`)); else $container.replaceChildren(...$nodes); }), ); // feature: tag creation effect(rxjs.fromEvent(qs($modal, "form"), "submit").pipe( rxjs.filter(() => !shareID), rxjs.tap((e) => { e.preventDefault(); const tagname = assert.typeof(new FormData(e.target).get("tag"), "string").toLowerCase().trim(); if (!tagname) return; else if (tags$.value.find(({ name }) => name === tagname)) return; qs($modal, `input[name="tag"]`).value = ""; tags$.next(tags$.value.concat({ name: tagname, active: true, })); }), rxjs.mergeMap(() => save( tags$.value .filter(({ active }) => !!active) .map(({ name }) => name) )), )); // feature: toggle tags effect(dom$.pipe( rxjs.mergeMap(($nodes) => rxjs.merge( ...$nodes.map(($node) => onClick($node)), ).pipe( rxjs.filter(() => !shareID), rxjs.tap(($node) => $node.classList.toggle("active")), rxjs.debounceTime(800), rxjs.map(() => $nodes .filter(($node) => $node.classList.contains("active")) .map(($node) => $node.innerText.trim()) .filter((text) => !!text) ), )), rxjs.mergeMap((tags) => save(tags)), )); } ================================================ FILE: public/assets/pages/filespage/model_acl.js ================================================ import rxjs from "../../lib/rx.js"; const perms$ = new rxjs.BehaviorSubject({}); export function setPermissions(path, value = {}) { if (JSON.stringify(value) === JSON.stringify(perms$.value[path])) return; perms$.next({ ...perms$.value, [path]: value, }); } export function calculatePermission(path, action) { const toBool = (n) => n === undefined ? true : n; if (!perms$.value[path]) return false; switch (action) { case "new-file": return toBool(perms$.value[path]["can_create_file"]); case "new-folder": return toBool(perms$.value[path]["can_create_directory"]); case "delete": return toBool(perms$.value[path]["can_delete"]); case "rename": return toBool(perms$.value[path]["can_move"]); case "upload": return toBool(perms$.value[path]["can_upload"]); default: return false; } } export function getPermission(path) { return perms$.asObservable().pipe( rxjs.map((perms) => perms[path]), ); } ================================================ FILE: public/assets/pages/filespage/model_files.js ================================================ import { toHref, navigate } from "../../lib/skeleton/router.js"; import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { basename, forwardURLParams } from "../../lib/path.js"; import notification from "../../components/notification.js"; import assert from "../../lib/assert.js"; import { AjaxError } from "../../lib/error.js"; import t from "../../locales/index.js"; import { currentPath, isAlreadyFocused } from "./helper.js"; import { setPermissions } from "./model_acl.js"; import fscache from "./cache.js"; import { ls as middlewareLs } from "./model_virtual_layer.js"; import { tagFilter } from "./model_tag.js"; /* * The naive approach would be to make an API call and refresh the screen after an action * is made but that give a poor UX. Instead, we rely on 2 layers of caching: * - the indexedDB cache that stores the part of the filesystem we've already visited. That way * we can make navigation feel instant by first returning what's in the cache first and only * refresh the screen if our cache is out of date. * - the transcient cache which is used whenever the user do something. For example, when creating * a file we have 3 actions being done: * 1. a new file is shown in the UI but with a loading spinner * 2. the api call is made * 3. the new file is being persisted in the screen if the API call is a success */ const handleSuccess = (text) => rxjs.tap(() => notification.info(text)); const handleError = rxjs.catchError((err) => { notification.error(err.message); throw err; }); const handleErrorRedirectLogin = rxjs.catchError((err) => { if (err instanceof AjaxError && err.err().status === 401) { navigate(toHref("/login?next=" + location.pathname + location.hash + location.search)); return rxjs.EMPTY; } throw err; }); const trimDirectorySuffix = (name) => name.replace(new RegExp("/$"), ""); export const touch = (path) => ajax({ url: withURLParams(`api/files/touch?path=${encodeURIComponent(path)}`), method: "POST", responseType: "json", }).pipe( handleSuccess(t("A file named '{{VALUE}}' was created", basename(path))), handleError, ); export const mkdir = (path) => ajax({ url: withURLParams(`api/files/mkdir?path=${encodeURIComponent(path)}`), method: "POST", responseType: "json", }).pipe( handleSuccess(t("A folder named '{{VALUE}}' was created", basename(trimDirectorySuffix(path)))), handleError, ); export const rm = (...paths) => rxjs.forkJoin(paths.map((path) => ajax({ url: withURLParams(`api/files/rm?path=${encodeURIComponent(path)}`), method: "POST", responseType: "json", }))).pipe( handleSuccess(paths.length > 1 ? t("All Done!") : t("The file '{{VALUE}}' was deleted", basename(trimDirectorySuffix(paths[0])))), handleError, ); export const mv = (from, to) => ajax({ url: withURLParams(`api/files/mv?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`), method: "POST", responseType: "json", }).pipe( handleSuccess(t("The file '{{VALUE}}' was renamed", basename(trimDirectorySuffix(from)))), handleError, ); export const save = () => rxjs.of(null).pipe(rxjs.delay(1000)); export const ls = (path) => { const lsFromCache = (path) => rxjs.from(fscache().get(path)); const lsFromHttp = (path) => ajax({ url: withURLParams(`api/files/ls?path=${encodeURIComponent(path)}`), method: "GET", responseType: "json", }).pipe( handleErrorRedirectLogin, rxjs.map(({ responseJSON }) => ({ files: responseJSON.results, permissions: responseJSON.permissions, })), rxjs.tap(({ files, permissions }) => { fscache().store(path, { files, permissions }); hooks.ls.emit({ path, files, permissions }); }), ); return rxjs.combineLatest( lsFromCache(path), rxjs.merge( rxjs.of(null), rxjs.merge(rxjs.of(null), rxjs.fromEvent(window, "keydown").pipe( // "r" shorcut rxjs.filter((e) => e.keyCode === 82 && !isAlreadyFocused()), )).pipe( rxjs.switchMap(() => lsFromHttp(path)), rxjs.catchError((err) => navigator.onLine ? rxjs.throwError(err) : rxjs.EMPTY), ), ), rxjs.merge( rxjs.of(navigator.onLine), rxjs.fromEvent(window, "online"), rxjs.fromEvent(window, "offline"), ) ).pipe( rxjs.mergeMap(([cache, http]) => { if (http) return rxjs.of(http); if (cache) return rxjs.of(cache); return rxjs.EMPTY; }), rxjs.map(({ files, ...res }) => { if (navigator.onLine) res["files"] = files; else res["files"] = files.map((file) => file.type === "file" ? { ...file, offline: true } : file); return res; }), rxjs.distinctUntilChanged((prev, curr) => { let refresh = false; if (prev.files.length !== curr.files.length) refresh = true; else if (JSON.stringify(prev.permissions) !== JSON.stringify(curr.permissions)) refresh = true; else { for (let i=0; i<curr.files.length; i++) { if (curr.files[i].type !== prev.files[i].type || curr.files[i].size !== prev.files[i].size || curr.files[i].name !== prev.files[i].name || curr.files[i].offline !== prev.files[i].offline) { refresh = true; break; } } } return !refresh; }), rxjs.tap(({ permissions }) => setPermissions(path, permissions)), middlewareLs(path), tagFilter(path), ); }; export const search = (term) => ajax({ url: withURLParams(`api/files/search?path=${encodeURIComponent(currentPath())}&q=${encodeURIComponent(term)}`), responseType: "json" }).pipe(rxjs.map(({ responseJSON }) => ({ files: responseJSON.results, }))); class Hook { constructor() { this.list = []; this.id = 0; } listen(fn) { if (typeof fn !== "function") assert.fail("hook must be a function"); const id = this.id; this.list.push({ id, fn }); this.id += 1; return () => { this.list = this.list.filter((obj) => obj.id !== id); }; } emit(data) { this.list.map(({ fn }) => fn(data)); } } export const hooks = { ls: new Hook(), mutation: new Hook(), // ... // add hooks on a needed basis }; const withURLParams = (url) => forwardURLParams(url, ["share"]); ================================================ FILE: public/assets/pages/filespage/model_tag.js ================================================ import rxjs from "../../lib/rx.js"; import { basename, forwardURLParams } from "../../lib/path.js"; import ajax from "../../lib/ajax.js"; export const tagFilter = (path) => rxjs.mergeMap((resp) => { const tags = new URLSearchParams(location.search).getAll("tag"); if (tags.length === 0) return rxjs.of(resp); return ajax({ url: forwardURLParams(`api/metadata/search`, ["share"]), body: JSON.stringify({ "tags": new URLSearchParams(location.search).getAll("tag"), path, }), method: "POST", responseType: "json", }).pipe( rxjs.mergeMap((tags) => rxjs.of(Object.keys(tags.responseJSON.results).map((fullpath) => ({ name: basename(fullpath.replace(new RegExp("/$"), "")), type: fullpath.slice(-1) === "/" ? "directory" : "file", size: -1, path: fullpath, })))), rxjs.map((files) => { resp.files = files; return resp; }), ); }); ================================================ FILE: public/assets/pages/filespage/model_virtual_layer.js ================================================ import { onDestroy } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.js"; import fscache from "./cache.js"; import { hooks } from "./model_files.js"; import { extractPath, isDir, currentPath } from "./helper.js"; /* * The virtual files is used to rerender the list of files in a particular location. That's used * when we want to update the dom when doing either of a touch, mkdir, rm, mv, ... * * |---------------| |---------------| * | LS | ---> | Virtual Layer | ---> Observable * |---------------| |---------------| * * It is split onto 2 parts: * - the virtualFiles$: which are things we want to display in addition to what is currently * visible on the screen * - the mutationFiles$: which are things already on the screen which we need to mutate. For * example when we want a particular file to show a loading spinner, ... */ const virtualFiles$ = new rxjs.BehaviorSubject({ // "/tmp/": [], // "/home/": [{ name: "test", type: "directory" }] }); const mutationFiles$ = new rxjs.BehaviorSubject({ // "/home/": [{ name: "test", fn: (file) => file, ...] }); class IVirtualLayer { before() { throw new Error("NOT_IMPLEMENTED"); } async afterSuccess() { throw new Error("NOT_IMPLEMENTED"); } async afterError() { return rxjs.EMPTY; } } export function withVirtualLayer(ajax$, mutate) { mutate.before(); return ajax$.pipe( rxjs.tap((resp) => mutate.afterSuccess(resp)), rxjs.catchError(mutate.afterError), ); }; export function touch(path) { const [basepath, filename] = extractPath(path); const file = { name: filename, type: "file", size: 0, time: new Date().getTime(), }; return new class TouchVL extends IVirtualLayer { /** * @override */ before() { stateAdd(virtualFiles$, basepath, { ...file, loading: true, }); } /** * @override */ async afterSuccess() { removeLoading(virtualFiles$, basepath, filename); onDestroy(() => statePop(virtualFiles$, basepath, filename)); await fscache().update(basepath, ({ files = [], ...rest }) => ({ files: files.concat([file]), ...rest, })); hooks.mutation.emit({ op: "touch", path: basepath }); } /** * @override */ async afterError() { statePop(virtualFiles$, basepath, filename); return rxjs.of(fscache().remove(basepath)).pipe( rxjs.mergeMap(() => rxjs.EMPTY), ); } }(); } export function mkdir(path) { const [basepath, dirname] = extractPath(path); const file = { name: dirname, type: "directory", size: 0, time: new Date().getTime(), }; return new class MkdirVL extends IVirtualLayer { /** * @override */ before() { stateAdd(virtualFiles$, basepath, { ...file, loading: true, }); statePop(mutationFiles$, basepath, dirname); // case: rm followed by mkdir } /** * @override */ async afterSuccess() { if (basepath === currentPath()) removeLoading(virtualFiles$, basepath, dirname); else onDestroy(() => removeLoading(virtualFiles$, basepath, dirname)); onDestroy(() => statePop(virtualFiles$, basepath, dirname)); await fscache().update(basepath, ({ files = [], ...rest }) => ({ files: files.concat([file]), ...rest, })); hooks.mutation.emit({ op: "mkdir", path: basepath }); } /** * @override */ async afterError() { statePop(virtualFiles$, basepath, dirname); return rxjs.of(fscache().remove(basepath)).pipe( rxjs.mergeMap(() => rxjs.EMPTY), ); } }(); } export function save(path, size) { const [basepath, filename] = extractPath(path); const file = { name: filename, type: "file", size, time: new Date().getTime(), }; return new class SaveVL extends IVirtualLayer { /** * @override */ before() { stateAdd(virtualFiles$, basepath, { ...file, loading: true, }); statePop(mutationFiles$, basepath, filename); // eg: rm followed by save } /** * @override */ async afterSuccess() { if (basepath === currentPath()) removeLoading(virtualFiles$, basepath, filename); else onDestroy(() => removeLoading(virtualFiles$, basepath, filename)); onDestroy(() => statePop(virtualFiles$, basepath, filename)); await fscache().update(basepath, ({ files = [], ...rest }) => ({ files: files.concat([file]), ...rest, })); hooks.mutation.emit({ op: "save", path: basepath }); } /** * @override */ async afterError() { statePop(virtualFiles$, basepath, filename); return rxjs.EMPTY; } }(); } export function rm(...paths) { if (paths.length === 0) return rxjs.of(null); const arr = new Array(paths.length * 2); let basepath = null; for (let i=0; i<paths.length; i++) { [arr[2*i], arr[2*i+1]] = extractPath(paths[i]); if (i === 0) basepath = arr[2*i]; else if (basepath !== arr[2*i]) throw new Error("NOT_IMPLEMENTED"); } return new class RmVL extends IVirtualLayer { /** * @override */ before() { for (let i=0; i<arr.length; i+=2) { stateAdd(mutationFiles$, arr[i], { name: arr[i+1], fn: (file) => { if (file.name === arr[i+1]) { file.loading = true; file.last = true; } return file; }, }); statePop(virtualFiles$, arr[i], arr[i+1]); // eg: touch followed by rm } } /** * @override */ async afterSuccess() { for (let i=0; i<arr.length; i+=2) { stateAdd(mutationFiles$, arr[i], { name: arr[i+1], fn: (file) => { for (let i=0; i<arr.length; i+=2) { if (file.name === arr[i+1]) return null; } return file; }, }); } onDestroy(() => { for (let i=0; i<arr.length; i+=2) { statePop(mutationFiles$, arr[i], arr[i+1]); } }); await Promise.all(paths.map((path) => fscache().remove(path, false))); await fscache().update(basepath, ({ files = [], ...rest }) => ({ files: files.filter(({ name }) => { for (let i=0; i<arr.length; i+=2) { if (name === arr[i+1]) { return false; } } return true; }), ...rest, })); if (arr.length > 0) hooks.mutation.emit({ op: "rm", path: arr[0] }); } /** * @override */ async afterError() { for (let i=0; i<arr.length; i+=2) { stateAdd(mutationFiles$, arr[i], { name: arr[i+1], fn: (file) => { if (file.name === arr[i+1]) { file = { ...file, loading: false, last: false }; } return file; }, }); } return rxjs.EMPTY; } }(); } export function mv(fromPath, toPath) { const [fromBasepath, fromName] = extractPath(fromPath); const [toBasepath, toName] = extractPath(toPath); let type = null; return new class MvVL extends IVirtualLayer { /** * @override */ before() { if (fromBasepath === toBasepath) this._beforeSamePath(); else this._beforeDifferentPath(); } _beforeSamePath() { stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === fromName) { type = file.type; return { ...file, name: toName, loading: true, }; } return file; }, }); } _beforeDifferentPath() { stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === fromName) { type = file.type; return { ...file, loading: true, last: true, }; } return file; }, }); stateAdd(virtualFiles$, toBasepath, { name: toName, loading: true, type, }); } /** * @override */ async afterSuccess() { fscache().remove(fromPath, false); if (fromBasepath === toBasepath) await this._afterSuccessSamePath(); else await this._afterSuccessDifferentPath(); } async _afterSuccessSamePath() { stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === toName) { file = { ...file, loading: false }; } return file; }, }); await fscache().update(fromBasepath, ({ files = [], ...rest }) => { return { files: files.map((file) => { if (file.name === fromName) { file.name = toName; } return file; }), ...rest, }; }); hooks.mutation.emit({ op: "mv", path: fromBasepath }); } async _afterSuccessDifferentPath() { stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === fromName) return null; return file; }, }); onDestroy(() => statePop(mutationFiles$, fromBasepath, fromName)); statePop(virtualFiles$, toBasepath, toName); await fscache().update(fromBasepath, ({ files = [], ...rest }) => ({ files: files.filter((file) => file.name !== fromName), ...rest, })); await fscache().update(toBasepath, ({ files = [], ...rest }) => ({ files: files.concat([{ name: fromName, time: new Date().getTime(), type, }]), ...rest, })); if (isDir(fromPath)) await fscache().remove(fromPath); hooks.mutation.emit({ op: "mv", path: fromBasepath }); hooks.mutation.emit({ op: "mv", path: toBasepath }); } /** * @override */ async afterError() { if (fromBasepath === toBasepath) stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === toName) return { ...file, name: fromName, loading: false, }; return file; }, }); else { stateAdd(mutationFiles$, fromBasepath, { name: fromName, fn: (file) => { if (file.name === fromName) return { ...file, loading: false, last: false, }; return file; }, }); statePop(virtualFiles$, toBasepath, toName); } statePop(mutationFiles$, fromBasepath, fromName); return rxjs.EMPTY; } }(); } export function ls(path) { return rxjs.pipe( // case1: file mutation = update a file state, typically to add a loading state to an // file or remove it entirely rxjs.switchMap(({ files, ...res }) => mutationFiles$.pipe( rxjs.map((all) => all[path]), rxjs.mergeMap((fns) => { const shouldContinue = !!(fns && fns.length > 0); if (!shouldContinue) return rxjs.of({ ...res, files }); for (let i=files.length-1; i>=0; i--) { for (let j=0; j<fns.length; j++) { files[i] = fns[j].fn(files[i]); if (!files[i]) { files.splice(i, 1); break; } } } return rxjs.of({ ...res, files }); }), )), // case2: virtual files = additional files we want to see displayed in the UI rxjs.switchMap(({ files, ...res }) => virtualFiles$.pipe( rxjs.map((all) => all[path] || []), rxjs.distinctUntilChanged((prev, curr) => { // we only want to get notified of changes within "path" if (prev.length !== curr.length) return false; for (let i=0; i<prev.length; i++) { if (prev[i].name !== curr[i].name) return false; else if (prev[i].type !== curr[i].type) return false; else if (prev[i].loading !== curr[i].loading) return false; } return true; }), rxjs.mergeMap((virtualFiles) => { if (virtualFiles.length === 0) return rxjs.of({ ...res, files }); return rxjs.of({ ...res, files: files.concat(virtualFiles), }); }), )), ); } function stateAdd(behavior, path, obj) { let arr = behavior.value[path]; if (!arr) arr = []; let alreadyKnown = false; for (let i=0; i<arr.length; i++) { if (arr[i].name === obj.name) { alreadyKnown = true; arr[i] = obj; break; } } if (!alreadyKnown) { arr = arr.concat([obj]); } behavior.next({ ...behavior.value, [path]: arr, }); } function statePop(behavior, path, filename) { const arr = behavior.value[path]; if (!arr) return; const newArr = arr.filter(({ name }) => name !== filename); if (newArr.length === 0) { const newState = { ...behavior.value }; delete newState[path]; behavior.next(newState); return; } behavior.next({ ...behavior.value, [path]: newArr, }); } function removeLoading(behavior, path, filename) { const arr = behavior.value[path]; if (!arr) return; virtualFiles$.next({ ...virtualFiles$.value, [path]: arr.map((file) => { if (file.name === filename) { return { ...file, loading: false }; } return file; }), }); } ================================================ FILE: public/assets/pages/filespage/state_config.js ================================================ import rxjs, { effect, preventDefault } from "../../lib/rx.js"; import { get as getConfig } from "../../model/config.js"; import { settingsGet, settingsSave } from "../../lib/store.js"; let state$ = null; export function init() { state$ = new rxjs.BehaviorSubject(settingsGet({ view: getConfig("default_view", "grid"), show_hidden: getConfig("display_hidden", false), sort: getConfig("default_sort", "type"), order: null, search: new URLSearchParams(location.search).get("q"), }, "filespage")); } export const getState$ = () => state$.asObservable(); export const setState = (...args) => { const obj = { ...state$.value }; let hasChange = false; for (let i=0; i<args.length; i+=2) { if (obj[args[i]] === args[i+1]) continue; obj[args[i]] = args[i+1]; hasChange = true; } if (!hasChange) return; state$.next(obj); settingsSave({ view: state$.value.view, show_hidden: state$.value.show_hidden, sort: state$.value.sort, order: state$.value.order, }, "filespage"); }; effect(rxjs.fromEvent(window, "keydown").pipe( rxjs.filter((e) => e.ctrlKey && e.key === "h"), preventDefault(), rxjs.tap(() => setState("show_hidden", !state$.value.show_hidden)), )); ================================================ FILE: public/assets/pages/filespage/state_newthing.js ================================================ import rxjs from "../../lib/rx.js"; const action$ = new rxjs.ReplaySubject(1); action$.next(null); export function getAction$() { return action$.asObservable(); } export function setAction(actionTarget) { action$.next(actionTarget); } ================================================ FILE: public/assets/pages/filespage/state_selection.js ================================================ import rxjs from "../../lib/rx.js"; import { onDestroy } from "../../lib/skeleton/index.js"; import { extractPath } from "./helper.js"; /* * CAUTION: Use a lot of caution if you change this file as it's easy for a bug to slip in and be responsible * for someone using selection to delete something and lose some data. We really don't want this to happen! * * BACKGROUND: There's many way we could have work on the selection. In fact I've made a couple prior attempts * with very simple implementations but none of them felt "right" from a user point of view. The approach * taken here mimicks the behavior of osx finder. To understand what is going on, I strongly advise you * open up osx finder and play with it first. * * DATA STRUCTURE: The supporting data structure is a list containing 2 kind of elements: * - "anchors" which is created when clicking on something with the cmd key * - "range" which is when you click somewhere with the expectation it will expand your range * using the shift key */ const selection$ = new rxjs.BehaviorSubject([ // { "type": "anchor", ... } => user click somewhere and the shift key is NOT pressed // { "type": "range", ... } => ----------------------------------------- pressed ]); onDestroy(clearSelection); export function addSelection({ shift = false, n = 0, ...rest }) { const selections = selection$.value; const selection = { type: shift ? "range" : "anchor", n, ...rest }; // case1: select a single file/folder if (selection.type === "anchor") { selection$.next(selections.concat([selection])); return; } // case2: range selection const last = selection$.value[selections.length - 1] || { n: 0, type: "anchor" }; if (last.type === "anchor") { selection$.next(selections.concat([selection])); return; } // clear out previous range selector. That's the behavior on apple finder when we do: // [A,1] [R,3] = [A,1] [R,3] => expands to [1,2,3] // [A,1] [R,3] [R,8] = [A,1] [R,8] => expands to [1,2,3,4,5,6,7,8] // [A,1] [R,3] [R,8] [R,6] = [A,1] [R,6] => expands to [1,2,3,4,5,6] selections[selections.length - 1] = selection; selection$.next(selections); } export function clearSelection() { if (selection$.value.length > 0) selection$.next([]); } export function getSelection$() { return selection$.asObservable(); } export function isSelected(n) { let isChecked = false; const selections = selection$.value; for (let i=0; i<selections.length; i++) { if (selections[i].type === "anchor" && selections[i].n === n) { isChecked = !isChecked; } else if (selections[i].type === "range" && isBetween( n, selections[i-1]?.n || 0, selections[i]?.n, )) { isChecked = true; // WARNING: change with caution } } return isChecked; } export function lengthSelection() { return _selectionHelper((set) => set.size); } export function expandSelection() { return _selectionHelper((set) => { const arr = new Array(set.size); let i = 0; for (const path of set) { arr[i] = { path }; i += 1; } return arr; }); } // This is the core function used to not only calculate the selection length but also expand the // selection. We're quite slow with an algo running in 3N selection size with room for improvement // but be very cautious, a bug in here could cause terrible consequence function _selectionHelper(fn) { const set = new Set(); const selections = selection$.value; for (let i=0; i<selections.length; i++) { const curr = selections[i]; if (selections[i].type === "anchor") { if (isSelected(selections[i].n)) { set.add(curr.path); } continue; } const [basepath] = extractPath(curr.path); const min = i === 0 ? 0 : Math.min(selections[i].n, selections[i-1].n); const max = i === 0 ? selections[i].n : Math.max(selections[i].n, selections[i-1].n); for (let j=min; j<=max; j++) { if (isSelected(j) === false) continue; const { offline, name, type } = selections[i].files[j]; if (offline === true) continue; set.add(basepath + name + (type === "directory" ? "/" : "")); } } return fn(set); } function isBetween(n, lowerBound, higherBound) { return n <= Math.max(higherBound, lowerBound) && n >= Math.min(lowerBound, higherBound); } ================================================ FILE: public/assets/pages/filespage/thing.css ================================================ .touch-no .component_thing:hover, .component_thing .highlight, .component_thing.hover, .component_thing .highlight { transition: 0.1s ease-out background; background: var(--border); border-color: var(--super-light); border-radius: 5px; } .component_thing:not(.loading):hover .component_datetime { display: none; } .component_thing:not([href]) { cursor: not-allowed; } .component_thing .file-is-hover { background: var(--emphasis-primary); } .component_thing .file-is-dragging { opacity: 0.15; } .component_thing .file-details { padding: 0 5px; line-height: 25px; white-space: nowrap; } .component_thing .file-details > span { display: inline-block; width: calc(100% - 130px); max-width: 750px; vertical-align: bottom; color: inherit; } .component_thing form { display: inline-block; } .component_thing form input { font-size: 1em; border-width: 0px; padding: 0 2px 0 2px; background: inherit; border-bottom: 2px solid var(--emphasis-primary); color: var(--color); } .component_thing.view-grid .component_action { float: right; color: #6f6f6f; line-height: 25px; margin: 0 -10px; padding: 0 10px; position: relative; } .component_thing .component_icon { width: 25px; height: 25px; } .component_thing .component_filesize { color: var(--light); font-size: 0.85em; padding-left: 3px; } .component_thing .component_datetime { float: right; color: var(--light); line-height: 25px; margin: 0 -10px; padding: 0 10px; position: relative; } .component_thing .component_checkbox { opacity: 0; z-index: 2; position: absolute; transition: 0.15s ease-out all; transform: translateX(0px); padding: 2.5px; border-radius: 50px; } .component_thing .component_checkbox:has(input:focus-visible) { opacity: 1; } .component_thing .component_action{ display: none; float: right; color: #6f6f6f; line-height: 25px; margin: 0 -10px; padding: 0 10px; position: relative; } .component_thing .component_action .component_icon{ padding: 1px 0; box-sizing: border-box; } .list > .component_thing.view-grid .component_action { transform: translateX(5px); transition: 0.15s ease-out all; z-index: 2; display: block; position: absolute; top: 5px; right: 5px; border-radius: 5px; margin-right: 0px; padding: 0px; } .list > .component_thing.view-grid:hover .component_action { transition-delay: 0.1s; transform: translateX(0); } .component_thing:hover .component_action, .component_thing:focus-visible .component_action { opacity: 1; display: block; } .component_thing .component_action { opacity: 0; } .component_thing.selected .component_action { opacity: 0; transition-delay: 0s; } .component_thing:has(.component_icon[alt="loading"]) .component_action { display: none; } .touch-yes .component_thing .component_checkbox { opacity: 1; } .component_thing .component_checkbox .indicator { top: 7px; left: 7px; } .touch-no .component_thing:hover .component_checkbox, .touch-no .component_thing:focus-visible .component_checkbox, .component_thing.selected .component_checkbox { transition-delay: 0.1s; opacity: 1; } .component_thing .selectionOverlay { display: none; border-radius: 3px; } .component_thing.selected .selectionOverlay { display: block; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: var(--primary); box-shadow: 0 0 30px rgba(0, 0, 0, 0.1); z-index: 1; opacity: 0.2; } /* GRID & LIST VIEW */ .list { display: grid; } .list > .component_thing { width: 100%; position: relative; box-sizing: border-box; min-width: 0; } .list > .component_thing.view-grid { border: 2px solid var(--border); border-radius: 5px; text-align: center; height: 160px; box-sizing: border-box; } .list > .component_thing.view-grid > img { padding: 0; margin: 0; display: block; } .list > .component_thing.view-grid > img.component_icon.thumbnail { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; background: var(--dark); padding: 0; margin: 0; display: block; border-radius: 3px; } .list > .component_thing.view-grid > img.component_icon.thumbnail.placeholder { position: absolute; top: 0; } .list > .component_thing.view-grid > img.component_icon { padding: 30px; box-sizing: border-box; width: 100%; height: 100%; object-fit: contain; margin: 0 auto; z-index: 0; } .list > .component_thing.view-grid .info_extension { position: absolute; top: 45%; text-align: right; left: 0; right: 20%; right: calc(50% - 50px); margin: 0 auto; text-transform: uppercase; font-size: 0.95em; text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); } .list > .component_thing.view-grid .info_extension span { background: var(--color); color: var(--bg-color); font-weight: bold; border-radius: 4px; padding: 2px 10px; display: inline-block; text-align: center; min-width: 20px; max-width: 65px; font-size: 0.95em; } .list > .component_thing .info_extension span:empty { display: none; } .list > .component_thing.view-grid .component_filename { letter-spacing: -0.5px; position: absolute; bottom: 2px; left: 2px; right: 2px; border-radius: 2px; padding: 3px 0px; } .list > .component_thing.view-grid .component_filename .file-details { width: calc(100% - 10px); display: block; } .list > .component_thing.view-grid .component_filename .file-details > span { width: 100%; } .list > .component_thing.view-grid .component_filename .file-details > span form input { letter-spacing: -0.5px; text-align: center; width: 100%; padding: 0; } .list > .component_thing.view-grid .image_layer { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 1; background: rgba(0, 0, 0, 0); transition: 0.2s ease-out background; } .list > .component_thing.view-grid .component_filesize, .list > .component_thing.view-grid .component_datetime { display: none; } .list > .component_thing.view-grid img.thumbnail { transition: 0.2s ease-out transform; } .list > .component_thing.view-grid.not-selected:hover img.thumbnail, .list > .component_thing.view-grid.not-selected.hover img.thumbnail { transform: scale(0.6); } .list > .component_thing.view-grid.not-selected:hover .image_layer { background: var(--border); } .list > .component_thing.view-grid.not-selected:hover .thumbnail ~ .component_filename { opacity: 1; } .list > .component_thing.view-grid.not-selected .thumbnail ~ .component_filename { transition: 0.2s ease-out opacity; opacity: 0; } .list > .component_thing.view-grid.selected img.thumbnail { transform: scale(0.6); } .list > .component_thing.view-grid .component_checkbox { display: block; top: 3px; left: 3px; transform: translateX(-5px); } .touch-yes .list > .component_thing.view-grid .component_checkbox { transform: translateX(0px); } .touch-yes .list > .component_thing.view-grid .component_checkbox input[type="checkbox"]:not(:checked) ~ span { background-color: inherit; } .list > .component_thing.view-grid:hover .component_checkbox, .list > .component_thing.view-grid:focus-within .component_checkbox, .list > .component_thing.view-grid.selected .component_checkbox { transform: translateX(0px); } .list > .component_thing.view-list { padding: 10px; } .list > .component_thing.view-list .info_extension { display: none; } .list > .component_thing.view-list .component_checkbox { top: 8px; left: 8px; transition: 0.5s ease all; background: rgba(0, 0, 0, 0); } .list > .component_thing.view-list .component_checkbox:hover { background: rgba(0, 0, 0, 0.1); } .list > .component_thing.view-list:hover .component_checkbox, .list > .component_thing.view-list.selected .component_checkbox { display: block; } .list > .component_thing.view-list:hover .component_checkbox ~ .component_icon, .list > .component_thing.view-list.selected .component_checkbox ~ .component_icon { visibility: hidden; } .list > .component_thing.view-list:hover .component_checkbox .indicator, .list > .component_thing.view-list.selected .component_checkbox .indicator { border-color: #57595A; } .list > .component_thing.view-list:hover .component_checkbox input ~ span, .list > .component_thing.view-list.selected .component_checkbox input ~ span { background-color: rgba(255, 255, 255, 0.4); } .list > .component_thing.view-list:hover .component_checkbox input:checked ~ span, .list > .component_thing.view-list.selected .component_checkbox input:checked ~ span { background-color: #57595A; } /* Dark Mode */ .dark-mode .component_thing:hover { background: rgba(255, 255, 255, 0.05); border-radius: 3px; } .dark-mode .component_thing { background: inherit; } .dark-mode .component_thing .component_filename { color: var(--light); } .dark-mode .component_thing.highlight { background: rgba(255, 255, 255, 0.05); } .dark-mode .component_thing form input { border-color: var(--light); color: rgba(255, 255, 255, 0.8); } ================================================ FILE: public/assets/pages/filespage/thing.d.ts ================================================ export function init(); export function createThing(any): HTMLElement; ================================================ FILE: public/assets/pages/filespage/thing.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import { animate, opacityOut, opacityIn } from "../../lib/animate.js"; import assert from "../../lib/assert.js"; import { forwardURLParams } from "../../lib/path.js"; import { get as getConfig } from "../../model/config.js"; import { extractPath, isDir, isNativeFileUpload } from "./helper.js"; import { files$ } from "./ctrl_filesystem.js"; import { addSelection, isSelected, clearSelection, expandSelection } from "./state_selection.js"; import { mv as mv$ } from "./model_files.js"; import { mv as mvVL, withVirtualLayer } from "./model_virtual_layer.js"; const mv = (from, to) => withVirtualLayer( mv$(from, to), mvVL(from, to), ); const IMAGE = { FILE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K", FOLDER: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuODY2NjY0MzEsMCwwLDAuODY2NjcsLTE3Mi4wNDU3OCwtODY0LjMyNzU5KSIgc3R5bGU9ImZpbGw6Izc1YmJkOTtmaWxsLW9wYWNpdHk6MC45NDExNzY0NztmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojNzViYmQ5O2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg2NjY3LDAsMCwwLjg2NjY3LC0xNzIuMDQ2OTIsLTg2NC43ODM0KSIgc3R5bGU9ImZpbGw6IzlhZDFlZDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojOWFkMWVkO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KPC9zdmc+Cg==", LOADING: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=", THUMBNAIL_PLACEHOLDER: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAJhHpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHja7ZhZkuS4EUT/cQodARFAYDkOVjPdQMfXC2ZWdU9vmjbNj2STaUWySBBLeLiHI8P51z9v+AefpFZCttpKLyXyyT13HVy0+PqM5ygxP8ePj77v/uF++Hyg3Eqc0+tBK+/XDvdpIfq+v15nGdy3rzrq5/1g/vHBeHek7T3Ax4zeAyV5DRDfHYfx7ijpe+T8+n++lhVLb/XrJax3+/t+/oSh+dI45FS1WJGaOWaNtZbOddOYK3HbPtG7tPt7Nt8dffN/+GiqzElPkhQ5PjNMTD+1NDgXjpqyt0mJ65z6c7+9ZgpaGmLlun/E9defX808fEz9Dfm3UMuPoP68+grp8AT040H6BqHyef7hfbEv98PXkD64fTVyKZ8j/+F+XnH+Yc0fqPnfvbvde16rG7mw5PJe1MdSnivaTY/W81bhW/mz2DzY/u18G5RY5NGOPuLkuosC45UsW4ZcOc95yWKKWY9WzqpLk6zAzQYYXdcDd/avXK0AvAFf0yIdEnf1cy7yDNuf4ZY0Bt7SAtkjdCa88l99w59pdK8TSiS2z1gxL3WmMQtHTiREoRmIyH0H1Z4Af3y//TiuCQTtCXNjgSNO7wH4p8mX5EoP0ImGxvlFYKn73QEhYgbGZCSBQCySTIrEqhqqCIFsADSYuhNqAouY6WaSmlMqgAMLGJt3qjxN1fR1GyEECEslpAo2UBCwcjbyp+ZGDg1Lls2sWLVm3UZJxRlWSi2uqKOmmqvVUmtttdcRWmq5WSutttZ6G117QnGtw8feeu9jMOig58HbgwZjTJ1p5mmzzDrb7HMsDSutvGyVVVdbfY2tO214vMuuu+2+x5FDKp187JRTTzv9jEuq3XTztVtuve32cMcnam9Yv/v+BmryRk0fpLxh/USNu7V+dCEuJ+aYgZhmAfDqCEgKqo5ZbJKzOnKOWewuhKZM0hycLY4YCOYjalc+sfuCnAVk9C/BLQCE/hXIBYfuTyD3PW4/Qm2Pp9ClByGnoQc1Jth3qRnamE8DHr9AAn98DvE/NPiz5787+p/vqF9ZpnlAwpGS3CJ99HmWVLs3LdSdNCPnxoY7/c5ou951IweE7fnfm7zvtKjoAf1RtCYyoRmGaR0FHlbkwCSbfH8OP3vwu2c6sr3TWPXsqlJgW0OB77wT/R+np7ypHIdVd9mrnMS7p49T9kBvlNp6h2o7oQzIVrYHIK2R76aKnIpq1N5PLolaXmA+zuRUb5c+27V79mz10jr3GwYObDM1KI2GoGbTrlna8VYEqRHnMtVGniheFZzjRg8URdhz+12ExtJ1PdoFA1tyKwyQXbqklSzneK1bc4vR80bJmAkv33P7SAC0E1OZs959WW1bwYaiTd8+YRUyHNpa7p2nNJSltLF7wXowMV2npnMmgnpmvv3swFWR2RlkzYxGP0k13Kj/3jl892AOguJ51WPNds+xSiFAMjF0q7ac/FZMCUmmbb1aFguuoYMX6pdWe79+MHAnk+pfmurT9LNl/66l1hwOvgzwRddwgT2NRZLou6Dc88yTp7blVghjnY7ZxWDOtbMCb5tlOVlQeg1NdjQP3Nmaeq1QQ04mD6MUH3dauQ7DaM9/hzRG42d9UMwOjeroJgHo5vzukSebzXXBA7CBXCwPDyGQjGl79NXJsFuU1VtZ44aT5gaqRSnNq+rcrY1TdVCwsJvMVea4FrGsEcxHLUV3qXERAlLjwWCYyQinUS2xxJjlRkoXthUXjrGU31Ok8MMHsNKBcntd7+y21q55F1RJgYNILMhAVjjvfWPlwvZ6s8KjubBCCtvWqtN3kNITUMCgRH02QJhln9vibQvmnQ2lPINx2XicIHA8dZJsOCpto3yHnCJee5c7uL/uJIwDH9Eo7xUCXDunjF43fgSuLMQmhTNn2ySqlfr03Ju+Op6vjoFgSlpJJXfJY8aDrICy5xmOAQVTT/saZvPMWz71w9TXbLrBMLGHc99y8r0k3iUYDT+c8b0xo3j1jmXXMwL9TWhauABZ8kWw+oM7EdlWPdBT60GAPAV6dvwfMmZ5RTh+cw7xJw++nJm59eoDjodjDIZj+gZYPOQ6pXwOVuBf1L5lFULQBprpICLxNUuZJcXdMy4JX5YnykcO7HmYsIS+C4Y+9dkbFGP3NQUjqOaxE3I+9WOzvnx/xGqBO8zKm9qmjQKIPQOZfKgi7H8QvdhXZhNmckZEEaW3BanbosjkQQFpXCrzWlf2pByQkA217xBDrC122VzbqbuTOBWFXXVstoW4xF7YH5IM/SwgkK2dGJCEI69mLkW2oFk6qZU90SP2j8IWfkyITm1axJHd7NGElW11kN3QwQorIRupdqw/s6TSdjuVFL3Y8TwGm2O2qMRjtGk4xdbw4WX1c+JxzULWNU5qfY/46Gp4ZcSknDGddxutj/cUcAwQzFmP8iDR/VLwktg6X/DXPycC4ffI7zuNH9A/2Q05s1HFMwPMI8nUKoFM1Xf+JCFVVBVXU5PVmRGJ7UUaXlCFOynvQdsdwgbk1Cs7nuXVua7dPZfHOmw4WPCNCwUG+N0jRfBSEc1hyxR+5o7th4gRrmU3CLIv9di2LXIThWb34vuQd8LbEOQUjBNpr4U6M9m/kCgCaGgZ0pNCT0L+oM6QKMt4VMlplaH2C48XHLx+/Zc2tMfrQd/fUDn8J8r+nMpwpbJnLtgk8gL4+1M1cCt2+syQbvUGEz1B3A3witeGW+MVZI0d1K1i0XFMEJmJZ0VygoBCpAbDGCSI3TkhKmzsrmde0dNcuKF7zBd9KuI1GDJDlUVECi7oamKTFAqb9mVUdLwn5V9tEaNRvTwPL3iaGwRs/suCp8hwT8DecVYtm11ldYfA3vOERcI8AhDxinefvHiTQHgES7W+Z3IwccUM3Q7Fb2CP+2YrKVDEfxEi+VDItMfAGZJihRAxo/hgYnGOnbTg7RATUs09QsTuRbc0EbEmbGel+vIlZ4ZHPNcWt3f32eptXLgbPGYELzB9xqGghzoj2TaECuT7Szag0GLnU2A0Hc3IMiZaQuwMHzX9N61F5+gBiQKoex3yEFt8NZ8DNzZmjz1zx8h2ukVuSMi8fl4ZfuccnguvfRhMggcEBdWjCBJ9CuqKeNFR0R985+NrfAt80dJ97OWYinsgCqQ73qfM4GYt668a/6pt+J3Gv2obPhuTYcOwfv6rYUZAzlNC2CV1qhOOEEnK/eeBCv9VhP/u6P+3IxTi7h7+DQ9kU6FC0KmCAAAABmJLR0QANwBRAGDKjvo2AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4gYYDTEITeuGKgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAACAASURBVHja7d3XcmNXtqXhsbY38ASp09HnJfqiI/rx+nFbJDywvesLEFSqSlJxg2AmzP9FZJSqojKV2AQwx3Jzmf/1v/9PJwAA8FAsHgEAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAgADAIwAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAEAAAAAABAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAACAAAAAAAgAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAAAIAAAA4ME4PALcRFK1LLmOI8dx5DiufN+X53lyXVeObctYlozMdb8Io+Pf8a/+ml2npmmU54X2h72SNFFVVfzgARAA8IBF3xh5nq8gCBQEgcIglO/7cl1XlmX9UVDvSBTFGo1G2u22WqyWyvNcXdfxZgBAAMD9sy1bYRhqMBgoiiKFQSjHdWRkZIy569dujJHneZpOZ7JtR6+LV2VZRggAQADAnRf+KNRkNFEcx/J9X7ZtP+azsG2NRiMZI72+vSolBAAgAODeWJYl3/M1nUw0Go3led7DFv5/DQHD4UiSeQ8BKSEAAAEAd/IGdByNhiPNZjNFYUTh/6sQMBpKRnp9JQQAIADgxhljFPi+ZtOZxpOJPNe7+/X9s0OAZWs0HMnouByQpIQAAAQA3CDLGMVxrPnTXMPhiFH/Z56ZZR2XA8xxOSBJEkIAAAIAbquQjYZDzefPiqP4eJwPnw8Bg+FxJkBvOiQHQgAAAgBuo4CNR2M9z58VRRFT/mc+w8FgqFM3IUIAAAIAbmDkP9Lz87OikOL/9RAw0PtUACEAAAEA18kYo0EcH0f+Fy7+Xdf98UuddEt10EiWsc5aBrEsS4N4IPMimTfpcDioJQQAIADgmop/GISaP80vMu3fvffMr+tadVOrripVda22adS+B4Ebqv/yfF+j4Uiu654VAuJ4oBcZSUb7w56ZAAAEAFzJG8xxNJvNNBgMv7Thr+1aVWWlLMuUZanSLFOe56qbWnovet2NPp+6qjSbPX0hBMT6zbzIGGm/3zMTAIAAgF/LsixNxmONR+Ozj/p1XaeyKnXY77U/7HVIEtV1fTcj3aqqtFguJWM0m87ODgFRFOvl+bfjTMB+RwgAQADAr2GMURSGmk7OK2qS1DSNkuSg9Wat/eFwt9fjllWpxXIhI2n6lRAQR3p5nwnY7fdq25Y3IgACAH4u27Y1nc4UBMFZ6/5VVWmz3Wi1Wip7gCtxy7LU23IhY4wm06lc54wQYCzFUSzz/JskQgAAAgB+weh/EA80HAx6T/13XaeqKrVcrbRcLe9quv9TIWDxJhmj6WQqx3HOevZRFH0sB+z2O0IAAAIAftKbynE0Ho/lut5ZI//lcqnFe/F/NEVZ6u3tTUZGk8nkSyHgt5cXGWO03W0JAQD+DX1YcfHRfxzFisKo967/uq61Xq+0XK0esvj/EQIKvS5etdluzn4OxhiFYaSX5xeNR2NaLgMgAOB72batwWAgz+s3+m/bVvv9Tsv1SlVdPfxzPC0HbLdbNU3zhRAQ6uX5RZMxIQDAn7EEgIsKfL/36L/rOuV5puVqpbIseYjvz6QojjMBxkjj8eSso5Q/hgBJ2u52ZwcKAMwAAH/9ZnovNr7v9/p9TdNos90qzbjn/i9DwNurdruvzQQEQaiX59+OPRksrl8GQADABdmOozCMeo1Uu65TlqXa7/eMTP/m+eRFod/fXrX7wuj9GAICvby8aDKZnN2YCQABAPg3nusq8Pud+2+aRofDQXmR8wD/KQTkuV7fftduv1PTfiEE+IGen581JQQAD489ALgY1/V6b/4ry1JJmnJM7RMhIMtzvb69yshoNBqdtanvIwTMXyQZbbYbnv0/P/iPOyZYngIBAPgbQRDIsntu/ityZXnGw/tsCMgyvb69SkYaDc8PAb7v6+X5RcPB8OwZhXt/1l3XqW3b482TdaWyrFRWpdq2Vdu2H/8fgAAAAoDv957+z/Octf+ehSnNUr29vcoYo+GZtyyeQoDneRSxTwSB06+maVSWpfI8U5qlyotCdV2raRqeIwgAeFye58moXwAoioIvzjOKUpKmen09LgcMBoOzz/gbY866q+Ghg24QaDgcqm1blWWpNEuVJImyPFNZlMyogACAB3wz9bzA5jSawrkhINHr2++SkQbxgEY/P8kpNFmWJcdxFIahJuOJ8jzT4XDQIUmUZikzWyAA4HHYjv3p0eRpOpWuf18LAYckkV5fZV6keDCQZQgBvyIQOI6jwWCoKIo1ynMlyUHb3U5JmrDJEgQA3L++I9DTZip8cSYgOej1TXoxRnEcEwJ+8WcgiiIFQaA4Hmi/32m92Sgvcpa6QADAHX/59Sw8bUfxv4S263Q4HGSMJduyFIYR6/pXEgQ8z1MUxVqtV9rtdw99yRUIAMBx5KpO6jqJQdGFnucxUDHKvLIvWMfRYDCQ7/sKw1Cr1VI5G19xLUGVR4BfVrFwEcYYDeJYL/NnRv9X+vPxPE9Psyf913/9Dw0HbNgEMwAALlBc4jjWy/OLBmf2BMDPYdu2RsORHMeRvVgc2zpzUgAEAADnFv/fKP43w7IsxVEs+8WWbdvabNaqCQH4Ve9HHgFwy8X/N4r/Df7sgiDQ8/xZ0+lUDpcygRkAAP1H/uevJ7ddq7bhJMY/P2zJyHw0/7nU/opTK+b5/Fld12m92bAcAAIAgM8W//NH/m3bfpxRr2nG9JeV37Is2bYt13XleZ58z5PrerKd4/T9V/stGGPke8cQ0DSttrstfTFAAADw98X/5QLFf7ff6e3tVUma8mA/8dyNjIxl5Lmu4ihWHMcKglCe58n+whT+H9czz9U0jfaHPUcEQQAA8NfFf3iB4v/69qo0TSk2n/DxjFqprmtlea7VZq0wCDUcDjWIBwrD8OwgYIxRGEZ6enpSVVfKMq7HBgEAgC477U/xv0wgaJpGh+SgNEu1DbYaD0cajccK/OCsn49lWRoMBpqVM73Wr6oqlmVAAAAo/tEFi//rq9KM4n8pbdsqTVMVRaEkTTSbzjR8P+vf+8vYdjQejZVlmTbbDfsBQAAAHr34v7xccORP8f8Wx/X7g4qiVF4Umk1n8jyv96kBz/M0m06V55lSlgLwzTg8DFxr8Y+PxX94gd3+TPt/v67rVJSFFsuFXl9/V573vwHQGKMoijUeT760uRAgAAC3PPJ/pvjforqutd5s9Pr2u7Is6/3cLcvSeDRWxL0OIAAAD1j8LzjyTyj+P13TNtpst3pbvPWeCThdHjQZj2VbzAKAAABQ/HsW/98p/r9U2x6b+yxXS1VV2ev32ratOB4oikIeJAgAAMW/38ifaf8rmAloGm02G222W9V13ev3ep6n4XDELAAIAADF//PFn5H/9ajqSsvVUkma9PqZWJalKIoUBD4PEQQA4G6L/zPF/56VRaH1eq2yLHu9N3zPVxTFbAbEt6APAH5R5ZOMseR6rqzmMXPosQ/88TIYiv99a7tOh+Sg/X4n13369M/acRxFYSTHcegOCAIA7qX+G0VRpP/+n/8tPWzBMnIcW/6Z7WMp/relrmvt9jsNBgMFwec29xljFASBfN8nAIAAgPsZ/XqeJ8/zeBjnjio/iv8bxf8GdF2nNMt0SBJ5nv/p0Od5ngLPV2ISfsa4KPYAADdb/PfvxZ/CcEuzAGma9DoRYNmW/CCgMyAIAADF/1T8Xyn+tzgLkGYqis83BzI6bgZ0CAAgAAAUf4r/7SrKold3wNNy2Tk3DAIEAIDijyuaBciLXHXz+WUAx3EIACAAABR/iv/tB4BCTd18/ovasuQ6Lg8PBACA4o9bVpalmqbutQzgcmIGBACA4o/b1jSN6h4zAJLYBAgCAPBQxf9A8b9HXdepaZpeMwAcAwQBAHik4v9K8b/nn3Gnz/9cjcXXNS6LbaXAFY4M94e9Fgs6/N31z1qdetR/LgQCAQD3o65rlVXZ60vwvot/q6qqlaSJdvudiqKg+N/3D7zX/53yDwIA7makmySJfn/9XW3X8kC644iwbVvVda225ZkAIADgTgNA2zbK81xN2/BAAOAnY1cJAAAEAAAA8AhYAgBwUX+3W50NjQABAMAdFHljjCzL+vjn03+3bVuWZcl6DwJddzzz3jTN8ex716rtuvd9IK26938GQAAAcIUsyzreSmc7cl1Hvu/L93y5nifXcWTbjizLkszxDvs/jf7fz7x33TEIVHWtqqpUFoXyolBVVaqbWnVdq2nYFAoQAAD88qLveZ58z1cYBArCUIEfyHXdfyv2fRrVBO8j/tPov64rFUWhLM+VZZmKolBRFoQBgAAA4GcxxsixHUVRdPwVhgqCUI7jfEz1X+Lf8eN/2rYtz/M1HI5U1/V7GMiUpomSJFVZlSwTAAQAAN9V+F3X1XAw1HAwUBiG8jz/ONL/Sf9+SXJdV67rKo5jVaOR8jzXIfmjOyJNkgACAIALFv7RcKjxaKwgCOW67i/vPW+Mkef5cl1PURRrPBppv99rs9sSBAACAICvcJ3jSHs2nSqK4o9p/msLKI7jyLZj+UGg4XCozWaj7X6nsmRpACAAAPg0y7IUhqGmk6nGo5Fc17v62+ZOexPsKJbvB4oHA63XKx2SRHVd80MFCAAA/nHU77oaj8aaTqeKwuinrfFfekZgPBor8ANtthutN2tuTwQIAAD+rnAGQaCn2ZPGo/FVrPNf4vXMnbkCP9BytdQhObA3ACAAADixLEuDeKD5fK5BPJBt2/fzJeY4Go1G8jxXy9VSm81GNf0DAAIA8Ohsy9Z4PNb8aa4wDG9uyv+zAScMI708O3KcYxCoqoofPkAAAB70A27bmkymmj/NFQTBt0z5/2tP/+M6fKfTcvzxX2n+dGeAsYwsc9kgYoyR7/uaP81lW5YWy4WKsuRNABAAgAf7cDuOZpOpnuZz+Z5/seJ/KvjHvv21yvee/mVVqapKVdXxf2/fE4AxRrZly3Ecea4rz/Pkeb48zzveLeA4H5cKXYLruprOZjKWpbfFm4qi4M0AEACAx2DbtqaTqebzubwLFf+2bVWWpYqiUJ5nSrNUWZ6rrus/3ej3dzvxP9r/vk8LOI6jMAgVRZHCIJDvB/I87yJLFK7jajqZSp30tnhlJgAgAAD3z7IsTcYTzZ++XvxPo/0sz5QmiZIkUZKlH0W/758lSd3xv6gsS5Vlqd1+J8dxFEeR4ihWHA8UBMGXNyo6jqPJdKKua/W6eGNPAEAAAO6XMUaj4VDzp7l8/2vFv2kaZXmm3W6nw+GgPM/VtJffXd91naqq0ma71X5/UBhuNRwMNRqNFQTBl2YEXMfVZDpV3TRaLhecDgAIAMB9Fv8oio4b/sLzN/y1bauiLLTdbLTd7ZQX+U87W9+0jQ5JoizPtT8cNJlMvtyzwHM9Pc1mquta682aPgEAAQC4L8dC96Qojs/aYd91neq61uFw0Gq9VJKman7RiLlpGiVpoqIolKaJnmZPCsPo7GUBz/P1NHtSVZXaHw50DAQIAMB9sG1bk8lEo+FIttW/SHZdp7zItV6vtd6sVVXVLy+SXdepqittNhsVRaGnp7lGw5Fc1+39ZxljFIahZrMnlWWlvMh504AAwCMAbpsxRnEcazKZnFUcu65TkiZaLBba7Xbfss7/FW3XKUlT1fX/U1VVmk2nZ11eZFmWhoOh8kmuxeKN/QB4eBaPALhtnudpNpkp8IMzimurQ3LQ6+vv2m43V1f8f1SUpRaLhd4Wi7OvAXYcR5PxRHE8uOl7EAACAPDoH2DL0mg40mAw6L1bvus6pUmi19dX7ff7j8Y916yqK63WK70t3s4OAb7vazKZyDtjtgQgAAC4CoEfaDKeyHH6reZ1Xacsy/S2WOhwuI3if3Lazb9cLc46239aChgMhnd5LwJAAADu3OmSn3OO/BVlocVyod1+d1PF/8cQsHrfsFjXde/f7zjOcRbA83gjgQAA4MZG/2Gg4XDYe9f/aWf9dru56TPxVVVptVppt++/cdEYoyiMNGQWAAQAADf1wX1f++97yU/bttrv91qtVnexCz4vci2XS2Vp1ns/gG3bGo3OO1YIEAAA/JrRv+9rEMe9GuN0Xac8z7VaLVVW93M5TpqlWm9WvV+TMUaBH2jIiQAQAADcguO5/4F8v9/af90cN8+lZ4yWr1nbttpudzrs972XAhzX0WAw6L2JEiAAAPjpXNdVHEW9ilbXdUoOiba77VWf9T/XKdyURdEr3FjGUhCECoOANxYIAACuW+AHCsKw1+i/qkptdxuVZXmXz6TrOqVZpu1u13tjo+u6iqKYzYAgAAC44g+sZSkMAnnu54+vdV2nQ5LokCR3fQlO0zTa7XcqiqLX77NtW2EQymUZAAQAANfKcRyFYdhrtFpVlQ6Hw1lNc25NURTaH/a9ZgGMMfJ9T57n8wYDAQDAdXJdV0Hw+c1/XdcpyzMldz76/3EWIEmS3mHHdT35vs9pABAAAFwfY4w815PjfP7cetM0StNURVk8xDM6XWucZf1OOti2Ld/3ZbMPAAQAAFf3YTXWsUj1OPtfVdXDjP5/fM1ZlvbeDOh5nmz2AYAAAODqPqyWpaDHNHXXdSrLUnmRP9RzattWeVGorj+/DGCMkeu6cmybNxoIAACu7MNqW3J77P5v21ZFkatpmod7VkVZqCyrXjMfruPIcRz2AYAAAOB6GGPkOI4cx/50gTqNhB9p+v+kKitVPVsD27Yjx3EJAHgYLHgBV1rwLcuSZVmybVue6723rHV7/Rn++50BZVWpbVs1TaOu6+42FBhjjr8so6Zp1XatbPO5aX3LsjQcDCR1KopSdV2paVu1778eMUiBAADgJ47yXdeV7/mKwlBBEMrzPTm28xEKPj+itTV/mms2namuaxXvu+PTPFNZlqqr6i5uBPx4bu/PLggC+X6gKIpkGavXnzMeTzQcjdS1neqmVlmU76cKUhVFqaquVNc1YQAEAAAX+BDatvwgUBiEiqJIURjJ87w/RrNnTkkbY2Tbtmzb/iiMo9FYTdOoKHKlaao0S5XlucqiUHtDRc0YI8d2FAS+giBQGEQKw0Ce58uyrI9n1vfZWZYlS5Zkv/dc8AMNu6HarlVZlscAlabKskz5g+6vAAEAwAVGrYM4VhwPFEXR+zl0+9vWoE9hwrKs44VC8UBVVSl/bxR0SBJlWXbVlwVZ78sap2cWheG/Ff1veWay5ISOwiDUeDRWURRK0+MzS9KEWQEQAAD854LiOq6Gw6FGw6HCMJLrub2mqi/5d/E8T57nKY4HGhe5kiTRdrdTmqVXNbo93YEwGo0Vx7ECP/glO/b/2Ix5bMk8Go2Uppl2+532hz1BAAQAAH9ROOzj3fOTyURRGMl1r2fHuW3biqJYvh9oMBjqcNhrvVkry/PeDXUuXfgDP9BkMtFwMPzYD3EtocT3j8sOcRxrnI603mx0SA4fmy0BAgDwwGzLVhiGmk2nGg5Hclznl4z4PxsEwjD8mBXYbNfabLcqy/KnFjRjjHzP03g80WQ86d0B8WeHO8/zjrMCUaT9fq/1eqUsy696OQUgAADfWRhcT+PxWNPJVEEQ3Myd838KAlGs1Xqlw+HwU04NHPdGDDSbzRRHsWzbvomz+ZZlyfd8uVNXURhpvVlru92qrEpmA0AAAB6FZVmKo1iz2UzD4fDjGN+tBRjHcTQajeX7gTabtVab9bfNBhhjFPi+ptOZJuOxPO82b+ezLEthGMr1XIVhqNVqpSRNfulSCkAAAH7Gh8p2NB6PNZvNFIXRzYz6/7EwB4Hm82f5vq/FcqkkvewFQ5ZlaTAY6Gn2dGx4ZDs3/8xcxz0uX3i+VuulNtut6rrmAwICAHCPXNfVfPak6XR6syPYv/2ycByNxxM5rqvFYqHdfneRUa1lWZqMJ5o/zRVG4dXujzj3tUVRJMdx5Hm+lsuFyqrigwICAHAvTpvW5vNnTcYTua777f/OvxqBf3fgsCxLg3ggx7blOLbWm82Xjgvatq3JeKzn+bOCIPz2v/+veGanlsxPsyc5jqO3xZvKouRDAwIAcA/FP/ADPT8/azway7nwnfJd1330ov/xP9u2OXbv6/74e1i29dFM6NQc59JNco5LAqGe5y+yLFur9eqsqW3P9TSdTjWbzuT7l50tOd13cHpeXdeqbTs1baOufb8LwUhG73cu2JYs88PzsszFZyIcx9FkPJFt21osFhKXDoEAANx48Q8CvcxfNB6PL3ZU7XRxT1VXqspSRVGoKItjP/qqVNO2Uid1+mNEa44VTZZlyXu/T8D3/fez6t5HA5tLFNrTqHb+NJekXiHgdGxu/jTXdDK92N+p644Fvq5r1VWl4vTciuJ490Hz3qTnh+f28cyMJcd15Hve+3N7f2auI8d2LvZztW1bo+HouC+k67h5EAQA4Fb5vq/n+fNFin/Xde99+gtleaYsTZVk6XHXfdt+lPrPbL4ry1KHJJGRZCzr2D43jBRGkcL3y3K++vf9UwjoOi3Xq08tB/iep+fnF03Gk4vMlrRtq6oqlee50lOv/jz7UzOezzyzoiyUpqnM8cXJdV1FYagojBSGoXw/uEjzpuOtg0O1bUsAAAEAuEWnUex49LXi33Wd6rr+o7d8kijPc7Xd+VfQfhQ+SWqajwtsrM1aYRBqEMcaDAYKw+jL5+w9z9PT01xN22q9Wf/jxkDf8y9W/Nu2VZ7nSpKDDkmiNE1V1dWXTid03fvcQNd9zB5stlv5742R4jjWIB58XNb0lfB0rY2NQAAA8A9cx9X8af6lQnYa8Sdpou12qyQ5qPjGjnunf98hOSjNUm33Ow0HA41H448g8NWZgLqutdvv/vI1uK6r+Xz+5eLftq3KstB2t9N+v1OWZd/aoKjrOuVFoaIstdvvFEeRRqOxhoPhVbVzBggAwDezbVvT6VTTyfTs3f6n0et6s9Zut1NRFj+1W1zbtsqyTEVR6HBINJlMvtR8xxijIAw0f5qrqitlWfan1+PYtmbT2ZcDU13Xx1a7m5XSLPup5+q7rlNVVdrudkrSVPt4/6duhQABALhjljEaDUeaTWdnF/9jEdtpuVoqSdNf2iGubVulWaqyPF5v+zSbK47PK2iWsRTHseazJ/2/199VlsejbpZlaTQaf+mZfQSm9Uqb7fbLU/0XCQLbjbI8+wg2X10WAAgAwBULw1Cz2UxBEPT+su+6TmVZar1eabVeX1WP+LpptN3tVJalnmZzjcfjs4q1bdsaDkfK81zL1Upt1yoKI81m5x/1a5pGh8NBi+VCh+RwNS11265Tnud6fXtVURR6mj0pDMOb7/wIAgCAf+G6rqaT45TvOcU/yzMtl0utN+svNc/5zpFtmmWqX39XVVeaTWdnjWpd19VkMlWW5yrLQrPp9Kxndpot2e62WizelOX5VV6qU9e11uuVqrrS89OzBoMBIQAEAOBenI5ujUaj3tPjx8Ka6u3tTdvd9uovhSmrUovlQm3TaD6f994XYIxRGIaaTqcqy1Kj0fisgngsrGu9Ld9++nXE58wG7Pd7tU2jtn3RcDSUbbEvAAQA4Ob5nqfp5LjO27f453l+M8X/x+K7Wq/Udd3HJUB9QoBlWRqPxuq67qxNfz8W/6IobuKZdV2nJE3Vvf2uTt0xLBICQAAAbpdt2RqNxorOmMYuy1LL5UK73e7mroOtm0arzVoy0vP8pfdywLm7/ZumOU7731Dx/zEEpFmmt7dXGRmNRiOWA3C1eGcC/2n07/tnNfup61qb7Ubr7UZN29zka2+aRuv1WsvVUtVPuMWubduPDX9FeZsX5nyEgMWbkuSgtmv5EIEAANzcB8SyNBqN5Af9psCPhWyv1Wp183fA102j1XqlzXbzra/ltFyyWC3+rY/ALYaAJE20WC6UX+nmRYAAAPyH0f9w0G9D17Fz3PEYXFEWd/EcqqrScrX81mN4VV1ptV7pcDjcRcHsuk67/V7r9VpVXfFhAgEAuJkPhzEaDoa9N8A1TaPtZqMkSe5q5FcUhVarpYri8l0L27bVfrfXdru9ub0S/+l1bbYbHfaHu3pdIAAAd81xXcVx3GszW9d1StNEm+32Ztf9/+m1HZJEm+3mon0MTlP/6819jpSr6jizURQsBYAAANyEKAwV+P06/lVVpfVmo/JOpv7/1WmHfpalFytmTXv8M9ML/pnXFpzSLL272Q0QAIC7ZFu24CGyTQAAEYFJREFUojDq1Q73NPpPkkTtHY/0iqLQdre9yIbAruuUZZn2+91Vdke8bHDaKcszZgFAAACumes6CsKw19G/41W4e1VVedfP5njCIbnITv2maXTY75Xl+d2/p4qyOHYLZBYABADgevl+IN/ze49k0/S+R/8/FrNDcvjyqL0oiqu64Oe7ZwEOyeHmmhuBAAA8zofCsuT7fq/p/7ZtlaTJzTavOWcWIE2Tjyt/zyqIbaM0TR9i9H+S57nSX3wFNEAAAP6Gbdnyfb9XC9eyKpVmj/XFnueF8jw/u9NdXdXHTnkP9MyaplGapTffHAoEAOA+A4Bjy+9xA17XdSqKYzF8JHVTK8uzs5YBuq5TWRZKs8faFHe6Frp8kJkiEACAm2GMkeu4vaf/izz/Kb3yr66YZZnqqv9otm1bZXn+kB3yiqJUURbcEQACAHBtHMfp1fynaRrlD9rkpSgLVXXV+7Uf9xCkD/nM2rZRURRqGwIACADAdc0AuG6v9f+6rlUUjzmlW9e1qqp/ADg+s+IhA8Bpyeie+x6AAADcZgBwHOmTzf+6rlNd1yqrxwwAx7X8std0dtd1qqrqoS/IKauSAAACAHBtAcBxHBl9fgNgXdcP/WVelqW69vMj+WMAKB/6KFxVVWqahq6AIAAA1xQA+nT/67pOdVNLD/xFXje1uj4zAOrOWja4J23bqmk4CggCAHA9HwhjZKx+H4umrvWopazrOrVNq7bHDIA6qarrhw4AXdepaVpmAEAAAK5pBsDqcftf13UP39Wt6/oXMta/j7MAnQgAIAAA15IAjr96juYeOwD0H/0y8j0uhVD/QQAArudbmS/lnxIaeMjfHbQAAgDwzQnAWOahn5gx5tPHJin+PDcQAICrHWX1+aI1xsiy7Id+ZsYyn7434VTIKGaSZaxPHzeVROtgEACA79R2be9NfbZl6VHnAIwxsi1blvn8V0nXdQ9fzI7HTa1eF06xcRIEAOBbpwCkpkcAODUO6rtx8J44jtNrBqBtW3XtYx+BsyyrV78JiZMTIAAA31v/u/b9XP/nitMpANgPvAzged6n7044HZtsHvwiHNdxZdt2rxmAR7ttEgQA3KsrHTC3Xaeqrj+9D9AYI9txel0ffFdfIMaS53q9ZgDqun743gme58m2nR7vy5YAAAIA7iwFXFkQOI20+kxPO7Yjz/Me8ifovIefPgGgqqqH3gNgjJHve72WAJq6ObacBggAuEa9ds/LXO3xubqpe33Z2ratIAh6FcF74fueHNfpNZVdVsVDzwBYliXf83stm1RVpaZmDwAIALhSfb/ULWNdXdHsuk51VauuegYA35fTY0r3XkayQRDKdT6//NE0jcrysS8C8jxPnv/5ACAdb1xkBgAEAFytPruUjTGy7P47oX/WDEBZlp8uUsYYeb4v3/cf6udt27bCIJBlf/5rpK7rh74J0BijMAjle59/r7Rdq6IsOAUAAgCuOQD0u+HNtmx5rneFr6NRURa9XovneQrD8KGWAQI/UBAEn+4B0HWdyrJQVT/uZjbbthWF4fHo6GdDU1WrKAqaJ4EAgOvVd5eybdvyPO/qimbbtiqKQnX9+SlXx3YUR9HDnAawLEtxFMnrMZLtuk55nvd6rvfG931FUdxv+r8qVZYEABAAcMWKHtPmpwDg+/7VnQjsuk5FUaisyk//HmOMwjBSGDzGLIDneorjuNcSTl3XyovH3QB4DE2x/KDH9H/bKs9zlRwBBAEAVx0Aek5TWpZ13Dx3haPmsix7vx7P8zQcDK5yX8NFvzSMURRFCsOoV9gpy0J5nj/sSNb3fI2Go15No+q6VpZlrP+DAIDrDwB9diofz0MHCoPw6l5L3Ry/ePtMV1uWpcFwqOjO9wK4rqfxaNRruaNtW2VZrrIsHvKzYVu2RqNhr30ipz0TaZYy/Q8CAK5bVVe9ds8fi4mrKIp6rYn+DF3XKU1TlWXZ6/f5nq/xaHK3RwIty9JgMFAcD3o3/0mzpNc9C/fieFwy0Hg86TU71Lat0ixTWZR8uYAAgCsPAFWlPM97F5Q4jhX4wRXOaOTKslRN2/R6PcPhUIPB4C5nAXzP13Qy6bWL/binIleSPuZI1rEdTadTBX7QOzQdDgeuAQYBANevaRrleda7H0AYhBoM4qubBWjaVofDoVdTIElyPVfT6fTu+gLYtq3JZNJ77b9pGh2SRFX5eCNZyxiNRqPj2n/P0X+WpUpTpv9BAMAN6LpOWZ6rKPqt89q2rdFofHWzAF3X6ZAmyrK01851yxx3e8+ms14j5WtmjNFoONSk5zR213XKi1z7/V7tgxUyY4zi+Pg+6HtXRF1X2u52dP8DAQC3oyiK3gXTGKMojDQZj+Vc2Q76uq6PX8Q9z647jqPxaKzRcCTrDpYCojDUbPokz/d6j/73+73yIn+4z4Lv+3qazRVF/WZM2rZVkqRKkgOjfxAAcDuaplGSJmc1BRqPJxoMhle1dt51nQ7JQWma9l6LPRaAp94b5q6ukHm+ZrMnxXH86a5/H6P/PNd2t324s/+e52k+e9JwOOy9tFVVlTbbzfFaaoAAgFvRdZ2SJFGWZ71HL77v6+np6eqa6Zy+kOueoca8n5efP81vtkGQ67qazWYaj8a9+xvUda3NdtN7SejWua6rp9mTJpNp7yWgtm212++UpAmjfxAAcHvKqtJ+v+/d890YoziKNZ/Pe6+Z/oxZgN1+3//Gw/dTAc/PzzfXH8BzPT1NZ5pNZ71bHLddqyQ5aLffPdTo/1T8Z7P+z+y4hybTdrt96HbJIADgxmcB9vu9srT/LIBt2xqPxnp+uq4QUNe1NpvNWTMbx+WNsX777b80HAyv7rTDXwWxIAj0/Pys+fz5rJ9DURRab9a9+yjcMt/39Tx/1vxpftYlV3Vda7vdsPMfP439P/7nf/9fHgMurW3bjynwvlPHlmXJ8zxZlqWyuJ5rUJumkWUshWHY/zUZS57rHU86dJ3KqrrKkbFlWRoOBnp+ftFkMpHr9G/TXNe11quVNtvHWPs/bWJ9mT9resZsyenzstvvtFguH/q2RBAAcCfqppbneb0boJxGzZ7vyXEcVWV5FSGg6zrVdS3PdeX5fq8NcadC4bquguB4HWxd12qbRtcw1jv93WbTmZ6fnzWIz7vToG1b7XY7LVaLhxj927at0XCkl+cXjUajs459dl2nLMv09vamLM/44gABALev6zq1TaswDOQ4bv8QYB1vC/Q9X01Tq66bXz412rat6rpR4Pty3f5XGRtj5DiOgiD42BNQ180v7fbm2LYGcazfnl80m87k+/5ZyxRd1ynN0mMhy+67kFnGyA8CzZ/mep4/f6mddVmWelu8abffMfUPAgDuR1M30vsU6TkjytNywEexbOpfPq1c15XatlMQBHJs56yNfZZlyXU9RafrYbtObdv91CDg2LbCMNLT05Oe5y+K41iOc97rOTX8eXt70/6wv9tCZoyR73kajyf67flF49FYnuedvbmzqiqt1iut1itu/AMBAPc3C1DXtRzbOXtkeRo1R1Ek3/dljI7F8hcFge79i9t0UhAEss+8+McYI9u2FfiB4nig4P35dDq+tu8ooqep/kEcazqd6eX5RcPRUJ7rfemEQlmWWiwX2mw3d7nuf7q6ejQaaz5/1mw6UxCEX7r6uW5qbTcbvS0XvftmABcZBPAI8N2qqtJyvZTruWfvgj+FgPForCiMlKaJDoeDkjRVXuQ/veg0TaPVZi3LtvX09HTWru8fX5vneXLdqQaDobIsU5omSrNMWZ6pqqovhQFjjGzLOl69HIaKo0hRFMnz/IucSCjKQsvVQuvN+q5GsaewFAahojBUFMcKw/DsWZ9/ff/stju9LRcPdVICBAA84CxAlmVaLBaybVtxFJ/9BfpHsXQVxwMVZaE8z5W9F8uyKI+3970XzO+ciG6bRuv1SpYxH7u/v1IYTgXHdV0NBgOVZamiLFQUhYoiV14c//mjyHbdv70+c/yD/thw6PsK/EB+EMj3fHmed/Y0/98V/9VyqdX6WPxvsdnR6ZlJx2URz/MVBL58P1Dg+/Len9uljm82TaPdbqu3xevDNUkCAQAPGgIOyUH2wpb1Yn25M96PxTIKIzWjRnVTq6kb1XWluq7VNI3arvv+9WhjVBSFbNv+0pTwjyzLUhAECoJAbduqad5fX9OoqWtVp9f3wymC05KCYztynOOv09/Jtu2LF+e2bVVVlSzb1mw6u8kRvjFGlmX98NxOz+v47C7ds6Fuau12O729vSrLczb9gQCAx9C2rba7rYyRXp5/UxAEFylKlmW9b6pzP8LGxy91+hnn7E6F5Dt89vWdnuWpsH33aNwYozAMr+4Wxz5Df6Of98xOrZHfFm8qioLiDwIAHjAEbHfqOunl+UXhN7TH/RnF7xpGrtfw97CNTT/R/6DrOlVVqfV6reVqqYI1fxAA8KiattF2t1XXtZrPnxVH8dW3xwXODbx5nmu5Wh4vlKLHPwgA4Iux1Xa3U900ms/mGg6HZ3VRA65VXdc6HPZarlY6JIeHuxIZBADgb52uDq7rWmVZaDKZfqmpCnAt4bYoCm22G202GxUl6/0gAAB/GQLyPNfr4k1Znms2m30sCRAEcGvv5ao+XoW92ayVpCnd/UAAAP6T01WoeZ5rMh5rPJ6c3TkQ+NmFv65rpWmq9WatQ3JQXdeM+kEAAD6r7bpjM5+y1D45aDKeaDAYyHO9i52vBy72fm1bVXWlPMu13W21P+y/3LURIADgoTVtoyRJlGWZ4ijSaDg6Xprj+9/S0AboM9pvmubYgTLLtD/sdXjfx0LhBwEAuOAX7W5//IINg+OFOVEYKgjCj9ashAF89/tQ0kfRL97bTidpqjzP1XYthR8EAOC7tG2rJE2VZpkcxzn2tvf9Y6tc/9Tf/r0P/w/d3YCvFPyqqlSWxccdDGVZqChK1Q2jfRAAgJ/+5VxVlaqqOt4r8EP/dsdxPi5tcR1Htu18zBCcggHwx5vpj5bKbdeqbRrVda2qqlSUpaqqOt670NSq6+Z4wRRAAACuIwzUdX3srMaFagDQG2esAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAAAEAAAAQAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAACAAAAIAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAABAAAAAAAQAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAAAIAAAAgAAAAgB/9f6C2m89BbnloAAAAAElFTkSuQmCC", }; let TYPES = null; export function init() { TYPES = { MIME: getConfig("mime", {}), THUMBNAILER: (function() { const set = new Set(); const thumbnailers = getConfig("thumbnailer"); for (let i=0; i<thumbnailers.length; i++) { set.add(thumbnailers[i]); } return set; })(), }; } const $tmpl = createElement(` <a href="__TEMPLATE__" class="component_thing no-select" data-selectable="true" draggable="false" data-link> <div class="component_checkbox"><input name="select" type="checkbox"><span class="indicator"></span></div> <img class="component_icon" loading="lazy" draggable="false" src="__TEMPLATE__" alt="directory"> <div class="info_extension"><span class="ellipsis"></span></div> <span class="component_filename"> <span class="file-details"><span class="ellipsis"> <span class="component_filesize">(281B)</span> </span></span> </span> <span class="component_datetime"></span> <div class="component_action"></div> <div class="selectionOverlay"></div> </a> `); // a filesystem "thing" is typically either a file or folder which have a lot of behavior builtin. // Probably one day we can rename that to something more clear but the gist is a thing can be // displayed in list mode / grid mode, have some substate to enable loading state for upload, // can toggle links, potentially includes a thumbnail, can be used as a source and target for // drag and drop on other folders and many other non obvious stuff export function createThing({ name = "", type = "N/A", time = 0, path = "", size = 0, loading = false, offline = false, link = "", view = "", search = "", n = 0, permissions = {}, }) { const [, ext] = formatFile(name); const mime = TYPES.MIME[ext.toLowerCase()]; const $thing = assert.type($tmpl.cloneNode(true), HTMLElement); // you might wonder why don't we use querySelector to nicely get the dom nodes? Well, // we're in the hot path, better performance here is critical to get 60FPS. const $link = $thing; const $checkbox = $thing.children[0]; // = qs($thing, ".component_checkbox"); const $img = $thing.children[1]; // = qs($thing, "img") const $extension = $thing.children[2].firstElementChild; // = qs($thing, ".info_extension > span"); const $label = $thing.children[3].firstElementChild.firstElementChild; // = qs($thing, ".component_filename .file-details > span"); const $time = $thing.children[4]; // = qs($thing, ".component_datetime"); $link.setAttribute("href", link); if (location.search) $link.setAttribute("href", forwardURLParams(link, ["share", "canary"])); $thing.setAttribute("data-droptarget", type === "directory"); $thing.setAttribute("data-n", n); $thing.setAttribute("data-path", path); $thing.classList.add("view-" + view); $time.textContent = formatTime(time); $img.setAttribute("src", (type === "file" ? IMAGE.FILE : IMAGE.FOLDER)); $img.setAttribute("alt", type); $label.textContent = name; if (type === "file") { $extension.textContent = ext; const $filesize = document.createElement("span"); $filesize.classList.add("component_filesize"); $filesize.textContent = formatSize(size); $label.appendChild($filesize); } if (mime && view === "grid" && TYPES.THUMBNAILER.has(mime) && offline === false) { $extension.style.display = "none"; $img.classList.add("thumbnail"); const $placeholder = $img.cloneNode(true); $placeholder.classList.add("placeholder"); $placeholder.setAttribute("src", IMAGE.THUMBNAIL_PLACEHOLDER); $img.parentElement.appendChild($placeholder); $img.src = "api/files/cat?path=" + encodeURIComponent(path) + "&thumbnail=true" + location.search.replace("?", "&"); $img.loaded = false; const t = new Date().getTime(); $img.onload = async() => { const duration = new Date().getTime() - t; $img.loaded = true; await Promise.all([ animate($img, { keyframes: opacityIn(), time: duration > 1500 ? 300 : duration > 50 ? 200 : 0, }), animate($placeholder, { keyframes: opacityOut(), time: duration > 1500 ? 300 : duration > 50 ? 200 : 0, }), ]); $placeholder.remove(); }; const id = setInterval(() => { // cancellation when image is outside the viewport if ($img.loaded === true) return clearInterval(id); else if (typeof $thing.checkVisibility !== "function") return clearInterval(id); else if ($thing.checkVisibility() === false) { $img.src = ""; clearInterval(id); } }, 250); } if (loading) { $img.setAttribute("src", IMAGE.LOADING); $img.setAttribute("alt", "loading"); $link.removeAttribute("href"); $extension.innerHTML = ""; return $thing; } else if (type === "hidden") { $thing.classList.add("hidden"); return $thing; } else if (offline) { $thing.setAttribute("data-selectable", "false"); $link.removeAttribute("href"); $checkbox.classList.add("hidden"); return $thing; } const checked = isSelected(n); if (permissions && permissions.can_move !== false) $thing.setAttribute("draggable", "true"); $thing.classList.add(checked ? "selected" : "not-selected"); $checkbox.firstElementChild.checked = checked; $checkbox.onclick = (e) => { if (e.target.nodeName !== "INPUT") e.preventDefault(); // eg: keyboard navigation e.stopPropagation(); addSelection({ n, path: $thing.getAttribute("data-path"), shift: (e.shiftKey || e.metaKey), files: (files$.value || []), }); }; if (getConfig("open_mode") === "double_click") $thing.onclick = (e) => { if (isSelected(n) && expandSelection().length === 1) return; e.preventDefault(); e.stopPropagation(); clearSelection(); $checkbox.onclick(e); }; $thing.ondragstart = (e) => { if (expandSelection().length > 0) return e.preventDefault(); clearSelection(); $thing.classList.add("hover"); $checkbox.style.display = "none"; e.dataTransfer.setData("path", path); }; $thing.ondrop = async(e) => { $thing.classList.remove("hover"); $checkbox.style.display = ""; const from = e.dataTransfer.getData("path"); let to = path; if ($thing.getAttribute("data-droptarget") !== "true") return; else if (from === to) return; if (isDir(to)) { const [, fromName] = extractPath(from); to += fromName; if (isDir(from)) to += "/"; } await mv(from, to).toPromise(); }; $thing.ondragover = (e) => { if (isNativeFileUpload(e)) return; else if ($thing.getAttribute("data-droptarget") !== "true") return; e.preventDefault(); $thing.classList.add("hover"); }; $thing.ondragleave = () => { $thing.classList.remove("hover"); }; return $thing; } function formatTime(unixTime) { if (unixTime <= 0) return ""; const date = new Date(unixTime); if (!date) return ""; // Intl.DateTimeFormat is slow and in the hot path, so // let's render date manually if possible if (navigator.language.substr(0, 2) === "en") { return date.getFullYear() + "/" + (date.getMonth() + 1).toString().padStart(2, "0") + "/" + date.getDate().toString().padStart(2, "0"); } return new Intl.DateTimeFormat(navigator.language).format(date); } function formatFile(filename) { const fname = filename.split("."); if (fname.length < 2) { return [filename, ""]; } const ext = fname.pop(); return [fname.join("."), ext]; } function formatSize(bytes) { if (Number.isNaN(bytes) || bytes < 0 || bytes === undefined) { return ""; } else if (bytes < 1024) { return "("+bytes+"B)"; } else if (bytes < 1048576) { return "("+Math.round(bytes/1024*10)/10+"KB)"; } else if (bytes < 1073741824) { return "("+Math.round(bytes/(1024*1024)*10)/10+"MB)"; } else if (bytes < 1099511627776) { return "("+Math.round(bytes/(1024*1024*1024)*10)/10+"GB)"; } else { return "("+Math.round(bytes/(1024*1024*1024*1024))+"TB)"; } } ================================================ FILE: public/assets/pages/viewerpage/application_3d/init.js ================================================ import { createElement, onDestroy } from "../../../lib/skeleton/index.js"; import { OrbitControls } from "../../../lib/vendor/three/OrbitControls.js"; export default function({ THREE, $page, $menubar, mesh, refresh, is2D, background }) { // setup the dom const renderer = new THREE.WebGLRenderer({ antialias: true, shadowMapEnabled: true }); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.setClearColor(0xf5f5f5); renderer.setSize($page.clientWidth, $page.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); $page.appendChild(renderer.domElement); // center everything const box = new THREE.Box3().setFromObject(mesh); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); // setup the scene, camera and controls const scene = new THREE.Scene(); scene.background = new THREE.Color(background); const camera = new THREE.PerspectiveCamera( 45, $page.clientWidth / $page.clientHeight, Math.max(0.1, maxDim / 100), maxDim * 1000, ); const controls = new OrbitControls(camera, renderer.domElement); if (is2D()) { controls.zoomToCursor = true; controls.enableRotate = false; controls.mouseButtons = { LEFT: THREE.MOUSE.PAN, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.ORBIT }; } scene.add(mesh); mesh.castShadow = true; mesh.receiveShadow = true; camera.position.set(center.x, center.y, center.z + maxDim * (is2D() ? 1.3 : 1.8)); controls.target.copy(center); const mixer = new THREE.AnimationMixer(mesh); if (mesh.animations.length > 0) { const ICON = { PLAY: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iMCAwIDU4Ljc1MiA1OC43NTIiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzE1OCIKICAgc29kaXBvZGk6ZG9jbmFtZT0icGxheS5zdmciCiAgIGlua3NjYXBlOnZlcnNpb249IjEuMi4yIChiMGE4NDg2NTQxLCAyMDIyLTEyLTAxKSIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzMTYyIiAvPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MTYwIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzAwMDAwMCIKICAgICBib3JkZXJvcGFjaXR5PSIwLjI1IgogICAgIGlua3NjYXBlOnNob3dwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMCIKICAgICBpbmtzY2FwZTpkZXNrY29sb3I9IiNkMWQxZDEiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjE1LjQyMDc1MiIKICAgICBpbmtzY2FwZTpjeD0iMjkuMzQzNTc2IgogICAgIGlua3NjYXBlOmN5PSIyNS45MzkwNzMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTA0IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjExNTciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjciCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjM0IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnMTU4IiAvPgogIDxwYXRoCiAgICAgZD0iTSA0NS42MDY5MTMsMjUuMTc0NzEyIDIyLjY5MTQwMSw2LjEwODk4MjUgYyAtMS40Njk2MjUsLTAuOTA3ODUyNSAtMy4zNzIzNTQsLTAuOTA1Mzc2NiAtNC44MzY1ODQsMCAtMS40OTM1MTUsMC45MjEwNTc3IC0yLjQyMjE0NSwyLjY0MTg1MSAtMi40MjIxNDUsNC40ODk3NDM1IFYgNDguNzMyNjYgYyAwLDEuODQ4NzE4IDAuOTI3ODYsMy41Njk1MTEgMi40MTI4OTcsNC40ODU2MTcgMC43MzQ0MjcsMC40NTgwNTMgMS41NzM2NjMsMC42OTk4NzIgMi40MjY3NjksMC42OTk4NzIgMC44NTA3OTUsMCAxLjY4OTI2LC0wLjI0MDk5NCAyLjQyMDYwNCwtMC42OTU3NDUgbCAyMi45MTU1MTIsLTE5LjA2NzM4IGMgMS40OTE5NzQsLTAuOTIzNTMzIDIuNDE4MjkyLC0yLjY0MzUwMSAyLjQxODI5MiwtNC40ODg5MTggLTcuN2UtNCwtMS44NDI5NDEgLTAuOTI2MzE4LC0zLjU2MjkwOSAtMi40MTk4MzMsLTQuNDkxMzk0IHogbSAtMi42MDU3MDIsNC42OTM1OTggYyAtMjguNjY3NDc0LC0xOS45MTIyMDY3IC0xNC4zMzM3MzcsLTkuOTU2MTAzIDAsMCB6IgogICAgIHN0eWxlPSJmaWxsOiNmMmYyZjIiCiAgICAgaWQ9InBhdGgxNTYiCiAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2Nzc2NzY2NjY2NjIiAvPgo8L3N2Zz4K", PAUSE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgdmlld0JveD0iMCAwIDU4Ljc1MiA1OC43NTIiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzE1OCIKICAgc29kaXBvZGk6ZG9jbmFtZT0icGF1c2Uuc3ZnIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjIuMiAoYjBhODQ4NjU0MSwgMjAyMi0xMi0wMSkiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnMKICAgICBpZD0iZGVmczE2MiIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9Im5hbWVkdmlldzE2MCIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiMwMDAwMDAiCiAgICAgYm9yZGVyb3BhY2l0eT0iMC4yNSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VjaGVja2VyYm9hcmQ9IjAiCiAgICAgaW5rc2NhcGU6ZGVza2NvbG9yPSIjZDFkMWQxIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTp6b29tPSIxNS40MjA3NTIiCiAgICAgaW5rc2NhcGU6Y3g9IjI4LjQzNTcwOSIKICAgICBpbmtzY2FwZTpjeT0iMjUuOTM5MDczIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkwNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMTU3IgogICAgIGlua3NjYXBlOndpbmRvdy14PSI3IgogICAgIGlua3NjYXBlOndpbmRvdy15PSIzNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzE1OCIgLz4KICA8cmVjdAogICAgIHN0eWxlPSJmaWxsOiNmMmYyZjI7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuOTM2NjQxIgogICAgIGlkPSJyZWN0NTA3IgogICAgIHdpZHRoPSIzOS42NTU5MTQiCiAgICAgaGVpZ2h0PSI0NS4xMjEzNTciCiAgICAgeD0iMTAuMzExNDcyIgogICAgIHk9IjcuMTUyMjY4OSIKICAgICByeD0iNS43NSIgLz4KPC9zdmc+Cg==", }; const $button = createElement(`<img class="component_icon" draggable="false" src="${ICON.PLAY}" alt="play">`); const action = mixer.clipAction(mesh.animations[0]); let isPlaying = false; $button.onclick = () => { if (isPlaying === false) action.play(); else action.stop(); isPlaying = !isPlaying; $button.setAttribute("src", isPlaying ? ICON.PAUSE : ICON.PLAY); }; $menubar.add($button); } const onResize = () => { camera.aspect = $page.clientWidth / $page.clientHeight; camera.updateProjectionMatrix(); renderer.setSize($page.clientWidth, $page.clientHeight); }; window.addEventListener("resize", onResize); onDestroy(() => window.removeEventListener("resize", onResize)); const clock = new THREE.Clock(); refresh.push(() => { controls.update(); renderer.render(scene, camera); mixer.update(clock.getDelta()); }); return { renderer, scene, camera, controls, box }; } ================================================ FILE: public/assets/pages/viewerpage/application_3d/scene_cube.js ================================================ import { onDestroy } from "../../../lib/skeleton/index.js"; import { ViewCubeGizmo, SimpleCameraControls, ObjectPosition } from "../../../lib/vendor/three/viewcube.js"; export default function({ camera, renderer, refresh, controls }) { const viewCubeGizmo = new ViewCubeGizmo(camera, renderer, { pos: ObjectPosition.RIGHT_BOTTOM, dimension: 130, faceColor: 0xf9f9fa, outlineColor: 0xe2e2e2, }); const simpleCameraControls = new SimpleCameraControls(camera); simpleCameraControls.setControls(controls); refresh.push(() => { viewCubeGizmo.update(); simpleCameraControls.update(); }); const onCubeClick = (event) => simpleCameraControls.flyTo(event.quaternion); viewCubeGizmo.addEventListener("change", onCubeClick); onDestroy(() => viewCubeGizmo.removeEventListener("change", onCubeClick)); } ================================================ FILE: public/assets/pages/viewerpage/application_3d/scene_light.js ================================================ import { settings_get } from "../../../lib/settings.js"; import * as THREE from "../../../lib/vendor/three/three.module.js"; const LIGHT_COLOR = 0xf5f5f5; export default function({ scene, box, camera }) { addLight( scene, new THREE.AmbientLight(LIGHT_COLOR), settings_get("viewerpage_3d_light", 2), ); // to make things "look nice", a good setup is to get lights positioned // in a 3D cube with a couple "twist" in term of position & intensity const l = addLight.bind(this, scene, new THREE.DirectionalLight(LIGHT_COLOR)); l(0.25, [plus(box.max.x*3), 0, 20]); // right l(0.25, [minus(box.min.x*3), 0, -20]); // left l(0.35, [0, plus(box.max.y*4), 20]); // top l(0.35, [0, minus(box.min.y*4), -20]); // bottom l(0.2, [0, 0, plus(7*box.max.z)]); // front l(0.2, [0, 0, minus(15*box.min.z)]); // back l(0.4, [camera.position.x, camera.position.y, camera.position.z]); // camera } function addLight(scene, light, intensity, pos = []) { light = light.clone(); light.intensity = intensity; light.position.set(...pos); if (light.type !== "AmbientLight") light.castShadow = true; scene.add(light); } const plus = notZero.bind(null, 1); const minus = notZero.bind(null, -1); function notZero(sgn, n) { if (n === 0) return sgn; return n; } ================================================ FILE: public/assets/pages/viewerpage/application_3d/toolbar.js ================================================ import { createElement } from "../../../lib/skeleton/index.js"; import { qs } from "../../../lib/dom.js"; import * as THREE from "../../../lib/vendor/three/three.module.js"; export default function(render, { camera, controls, mesh, $menubar, $toolbar, is2D }) { if (mesh.children.length <= 1) return; $menubar.add(buttonLayers({ $toolbar })); render(createChild( document.createDocumentFragment(), mesh, 0, { camera, controls, is2D } )); } function buttonLayers({ $toolbar }) { const $button = createElement(`<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNmMmYyZjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJtIDEuODI0MDExMiw2LjUzMDQxMTMgOS44OTUxMjU4LDUuMzc3MjgwNyBhIDAuNTkyMzE0MDQsMC41OTIzMTQwNCAwIDAgMCAwLjU1NzQ3NCwwIGwgOS44OTUxMywtNS4zNzcyODA3IGEgMC41MTEwMTYwMiwwLjUxMTAxNjAyIDAgMCAwIC0wLjA1ODA4LC0wLjk0MDczMzkgbCAtOS44OTUxMiwtNC4wNDE2NzI1IGEgMC41ODA3MDAwNSwwLjU4MDcwMDA1IDAgMCAwIC0wLjQ0MTMzNCwwIEwgMS44ODIwODI1LDUuNTg5Njc3NCBhIDAuNTExMDE2MDIsMC41MTEwMTYwMiAwIDAgMCAtMC4wNTgwNzEsMC45NDA3MzM5IHoiIC8+CiAgPHBhdGggZD0iTSAyMi4xMTM2NywxMC40NDQzMzIgMTkuOTg4MzA2LDkuNTM4NDM3NiAxMi4yNzY2MTEsMTMuNzMxMDkxIGEgMC41OTIzMTQwNCwwLjU5MjMxNDA0IDAgMCAxIC0wLjU1NzQ3NCwwIEwgNC4wMDc0NDM1LDkuNTM4NDM3NiAxLjg4MjA4MTIsMTAuNDQ0MzMyIGEgMC41NTc0NzIwNCwwLjU1NzQ3MjA0IDAgMCAwIDAsMC45ODcxODcgbCA5Ljg5NTEyOTgsNS42OTA4NTkgYSAwLjUzNDI0NDA3LDAuNTM0MjQ0MDcgMCAwIDAgMC41NTc0NzEsMCBsIDkuODk1MTMsLTUuNjkwODU5IEEgMC41NTc0NzIwNCwwLjU1NzQ3MjA0IDAgMCAwIDIyLjExMzY3LDEwLjQ0NDMzMiBaIiAvPgogIDxwYXRoIGQ9Im0gMjIuMTEzNjcsMTUuNjAwOTQzIC0xLjgxMTc4NCwtMC43ODk3NSAtOC4wMjUyNzUsNC4zNjY4NjYgYSAwLjU5MjMxNDA0LDAuNTkyMzE0MDQgMCAwIDEgLTAuNTU3NDc0LDAgbCAtOC4wMjUyNzE1LC00LjM2Njg2NiAtMS44MTE3ODQzLDAuNzg5NzUgYSAwLjU2OTA4NjAzLDAuNTY5MDg2MDMgMCAwIDAgMCwxLjAxMDQyMiBsIDkuODk1MTI5OCw1LjgwNjk5OCBhIDAuNTkyMzE0MDQsMC41OTIzMTQwNCAwIDAgMCAwLjU1NzQ3MSwwIGwgOS44OTUxMywtNS44MDY5OTggQSAwLjU2OTA4NjAzLDAuNTY5MDg2MDMgMCAwIDAgMjIuMTEzNjcsMTUuNjAwOTQzIFoiIC8+Cjwvc3ZnPgo=" alt="layers">`); $button.onclick = () => $toolbar.classList.toggle("open"); return $button; } function createChild($fragment, mesh, child = 0, opts) { if (["Bone"].indexOf(mesh.type) >= 0) return; buildDOM($fragment, mesh, child, opts); if (mesh.children.length > 0 && child < 4) { for (let i=0; i<mesh.children.length; i++) { if (mesh.children[i].type === "Group" || !!mesh.children[i].name) { createChild($fragment, mesh.children[i], child + 1, opts); } } } return $fragment; } function buildDOM($fragment, child, left, { camera, controls, is2D }) { const $label = createElement(` <label class="no-select" style="padding-left: ${left*20}px"> <div class="component_checkbox"> <input type="checkbox" ${child.visible ? "checked" : ""} /> <span class="indicator"></span> </div> <span class="text">${name(child)}</span> </label> `); qs($label, "input").onchange = () => child.visible = !child.visible; $label.onclick = async(e) => { if (is2D()) return; else if (e.target.nodeName === "INPUT" || e.target.classList.contains("component_checkbox")) return; e.preventDefault(); e.stopPropagation(); getRootObject(child).traverse((c) => { if (!c.material) return; c.material.opacity = c.id === child.id || c.parent.id === child.id ? 1 : 0.2; c.material.depthWrite = c.material.opacity === 1; }); await flyTo({ mesh: child, camera, controls }); }; $fragment.appendChild($label); } async function flyTo({ mesh, camera, controls }) { const box = new THREE.Box3().setFromObject(mesh); const size = box.getSize(new THREE.Vector3()); const targetLookAt = box.getCenter(new THREE.Vector3()); const targetDistance = Math.max(size.x, size.y, size.z) * 1.1; const targetPosition = targetLookAt.clone().add(new THREE.Vector3(targetDistance, targetDistance, targetDistance)); const [startPosition, startLookAt] = [camera.position.clone(), controls.target.clone()]; const startTime = performance.now(); return new Promise((resolve) => (function animate() { const t = Math.min((performance.now() - startTime) / 500, 1); camera.position.lerpVectors(startPosition, targetPosition, t); controls.target.lerpVectors(startLookAt, targetLookAt, t); controls.update(); t < 1 ? requestAnimationFrame(animate) : resolve(); })()); } function getRootObject(mesh) { if (mesh.type === "Scene" || mesh.parent.type === "Scene") return mesh; return getRootObject(mesh.parent); } function name(mesh) { if (mesh.name) return mesh.name; else if (mesh.isGroup && mesh.uuid) return `group: ${mesh.uuid}`; else if (mesh.uuid) return mesh.uuid; return "N/A"; } ================================================ FILE: public/assets/pages/viewerpage/application_3d.css ================================================ .component_3dviewer .threeviewer_container { position: relative; overflow: hidden; } .component_3dviewer .threeviewer_container, .component_3dviewer .threeviewer_container .drawarea { height: 100%; } .component_3dviewer .toolbar.open { transition: 0.3s ease transform; transform: translateX(0px); height: 300px; } .component_3dviewer .toolbar { position: absolute; top: 0; right: 0; transform: translateX(250px); transition: 0.1s ease transform; border-left: 1px solid #e2e2e205; background: #e2e2e205; color: var(--dark); width: 250px; z-index: 1; padding-top: 20px; } .component_3dviewer .toolbar label { display: block; text-transform: capitalize; margin-bottom: -5px; } .component_3dviewer .toolbar label .component_checkbox { margin-top: -10px; } .component_3dviewer .toolbar label .text { margin-left: -10px; display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: calc(100% - 25px); cursor: pointer; } ================================================ FILE: public/assets/pages/viewerpage/application_3d.d.ts ================================================ export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_3d.js ================================================ import { createElement, createRender, nop } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { AjaxError } from "../../lib/error.js"; import { load as loadPlugin } from "../../model/plugin.js"; import { loadCSS } from "../../helpers/loader.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import componentDownloader, { init as initDownloader } from "./application_downloader.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; import * as THREE from "../../lib/vendor/three/three.module.js"; import setup3D from "./application_3d/init.js"; import withLight from "./application_3d/scene_light.js"; import withCube from "./application_3d/scene_cube.js"; import ctrlToolbar from "./application_3d/toolbar.js"; class I3DLoader { load() { throw new Error("NOT_IMPLEMENTED"); } transform() { throw new Error("NOT_IMPLEMENTED"); } background() { return 0xf2f2f4; } is2D() { return false; } } export default async function(render, { mime, acl$, getDownloadUrl = nop, getFilename = nop, hasCube = true, hasMenubar = true }) { const $page = createElement(` <div class="component_3dviewer"> <component-menubar filename="${safe(getFilename())}" class="${!hasMenubar && "hidden"}"></component-menubar> <div class="threeviewer_container"> <div class="drawarea"></div> <div class="toolbar scroll-y"></div> </div> </div> `); render($page); const $menubar = renderMenubar( qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl()), ); const $draw = qs($page, ".drawarea"); const $toolbar = qs($page, ".toolbar"); const removeLoader = createLoader($draw); await effect(rxjs.from(loadPlugin(mime)).pipe( rxjs.mergeMap(async(loader) => { if (!loader) { componentDownloader(render, { mime, acl$, getFilename, getDownloadUrl }); return rxjs.EMPTY; } return new (await loader(I3DLoader, { THREE }))(); }), rxjs.mergeMap((loader) => new rxjs.Observable((observer) => loader.load( getDownloadUrl(), (object) => observer.next(loader.transform(object)), null, (err) => observer.error(err), )).pipe( removeLoader, rxjs.mergeMap((mesh) => create3DScene({ mime, mesh, is2D: loader.is2D, background: loader.background(), $draw, $toolbar, $menubar, hasCube, })), )), rxjs.catchError((err) => { let _err = err; console.log("ERR", err); if (err.response && err.response.status === 401) { _err = new Error(err.message); _err.status = err.response.status; _err = new AjaxError(err.message, _err, "Not Authorised"); } return ctrlError()(_err); }), )); } function create3DScene({ mesh, $draw, $toolbar, $menubar, hasCube, is2D, background }) { const refresh = []; const { renderer, camera, scene, controls, box } = setup3D({ THREE, $page: $draw, mesh, refresh, $menubar, is2D, background, }); withLight({ scene, box, camera }); if (hasCube && !is2D()) withCube({ camera, renderer, refresh, controls }); ctrlToolbar(createRender($toolbar), { mesh, controls, camera, refresh, $menubar, $toolbar, is2D, }); return rxjs.animationFrames().pipe(rxjs.tap(() => { refresh.forEach((fn) => fn()); })); } export function init($root) { const priors = ($root && [ $root.classList.add("component_page_viewerpage"), loadCSS(import.meta.url, "./component_menubar.css"), loadCSS(import.meta.url, "../ctrl_viewerpage.css"), ]); return Promise.all([ loadCSS(import.meta.url, "./application_3d.css"), initDownloader(), ...priors, ]); } ================================================ FILE: public/assets/pages/viewerpage/application_audio.css ================================================ body:not(.dark-mode) .component_audioplayer .audioplayer_container { background: var(--surface); } .component_audioplayer { display: flex; flex-direction: column; flex: 1; width: 100%; } .component_audioplayer .audioplayer_container { display: flex; flex-direction: column; flex: 1; width: 100%; text-align: center; height: 100%; overflow: hidden; padding: 20px; box-sizing: border-box; } .component_audioplayer .audioplayer_container .audioplayer_error { color: white; margin-top: 30px; } .component_audioplayer .audioplayer_container .audioplayer_box { background: white; box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px; position: relative; border-radius: 3px; padding: 10px 0 15px 0; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control { padding-top: 20px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control.hidden { position: unset !important; width: inherit !important; height: inherit !important; opacity: 0; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control img { height: 25px; width: 25px; cursor: pointer; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons { float: left; padding-left: 15px; margin-top: -10px; display: flex; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons > span { margin-right: 10px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons input[type="range"] { margin-left: -5px; width: 60px; cursor: pointer; outline: none; -webkit-appearance: none; background: transparent; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 12px; width: 12px; border: none; border-radius: 50%; background: #6f6f6f; margin-top: -5px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons input[type="range"]::-moz-range-thumb { -webkit-appearance: none; height: 12px; width: 12px; border: none; border-radius: 50%; background: #6f6f6f; margin-top: -5px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons input[type="range"]::-webkit-slider-runnable-track { width: 100%; height: 2px; background-color: #6f6f6f; border-radius: 2px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .buttons input[type="range"]::-moz-range-track { width: 100%; height: 2px; background-color: #6f6f6f; border-radius: 2px; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .timecode { float: right; padding-right: 25px; margin-top: -5px; line-height: 1rem; } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .timecode #separator { padding: 0 5px; } @media screen and (max-width: 450px) { .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_control .timecode #separator, .component_audioplayer > .audioplayer_container .audioplayer_box .audioplayer_control .timecode #currentTime { display: none; } } .component_audioplayer .audioplayer_container .audioplayer_box .audioplayer_loader { height: 260px; background: var(--color); position: absolute; opacity: 0.1; top: 0; left: 0; } .component_audioplayer .audioplayer_container .audioplayer_box .component_icon[alt="loading"] { position: absolute; margin: 50px -60px; } .component_audioplayer .audioplayer_container .audioplayer_box .chromecast_loader .component_icon[alt="loading"] { margin: 0; right: 20px; top: 20px; width: 30px; } .component_audioplayer .audioplayer_container .audioplayer_box .percent { position: absolute; margin: 100px -60px; width: 120px; font-size: 1.4rem; } ================================================ FILE: public/assets/pages/viewerpage/application_audio.d.ts ================================================ interface Window { WaveSurfer: { create: (options: any) => any; }; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_audio.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { onDestroy } from "../../lib/skeleton/lifecycle.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import { settings_get, settings_put } from "../../lib/settings.js"; import Chromecast from "../../lib/chromecast.js"; import assert from "../../lib/assert.js"; import ctrlError from "../ctrl_error.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; import { ICON } from "./common_icon.js"; import { formatTimecode } from "./common_player.js"; import { transition } from "./common.js"; const STATUS_PLAYING = "PLAYING"; const STATUS_PAUSED = "PAUSED"; const STATUS_BUFFERING = "BUFFERING"; export default function(render, { getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_audioplayer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div class="audioplayer_container"> <div class="audioplayer_box"> <div data-bind="loader"> <div class="audioplayer_loader"></div> <span class="percent"></span> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjNmY2ZjZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=" alt="loading"> </div> <div id="waveform"></div> <div class="audioplayer_control hidden"> <div class="buttons no-select"> <span> <img class="component_icon" draggable="false" src="${ICON.PLAY}" alt="play"> <img class="component_icon hidden" draggable="false" src="${ICON.PAUSE}" alt="pause"> <component-icon name="loading" class="hidden"></component-icon> </span> <span> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_MUTE}" alt="volume_mute"> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_LOW}" alt="volume_low"> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_NORMAL}" alt="volume"> </span> <input type="range" min="0" max="100" value="52"> </div> <div class="timecode"> <span id="currentTime">00:00</span> <span id="separator" class="no-select">/</span> <span id="totalDuration">02:13</span> </div> </div> </div> </div> </div> `); render($page); renderMenubar(qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl())); transition(qs($page, ".audioplayer_box")); const $control = { main: qs($page, `.audioplayer_control`), play: qs($page, `.audioplayer_control [alt="play"]`), pause: qs($page, `.audioplayer_control [alt="pause"]`), loading: qs($page, `.audioplayer_control component-icon[name="loading"]`), }; const $volume = { range: qs($page, `input[type="range"]`), icon_mute: qs($page, `img[alt="volume_mute"]`), icon_low: qs($page, `img[alt="volume_low"]`), icon_normal: qs($page, `img[alt="volume"]`), }; const currentTime$ = new rxjs.BehaviorSubject([ 0, // starting time - does change when seeking to another point 0, // offset to align the audio context currentTime. otherwise when seeking, the // currentTime keep growing and progress bar goes haywire ]); const currentTime = (wavesurfer) => { return currentTime$.value[0] + (wavesurfer.backend.ac.currentTime - currentTime$.value[1]); }; const setVolume = (volume, wavesurfer) => { settings_put("volume", volume); wavesurfer.setVolume(volume / 100); $volume.range.value = volume; if (volume === 0) { $volume.icon_mute.classList.remove("hidden"); $volume.icon_low.classList.add("hidden"); $volume.icon_normal.classList.add("hidden"); } else if (volume < 50) { $volume.icon_mute.classList.add("hidden"); $volume.icon_low.classList.remove("hidden"); $volume.icon_normal.classList.add("hidden"); } else { $volume.icon_mute.classList.add("hidden"); $volume.icon_low.classList.add("hidden"); $volume.icon_normal.classList.remove("hidden"); } }; const setStatus = (status, wavesurfer) => { switch (status) { case STATUS_PLAYING: $control.play.classList.add("hidden"); $control.pause.classList.remove("hidden"); $control.loading.classList.add("hidden"); wavesurfer.backend.ac.resume(); break; case STATUS_PAUSED: $control.play.classList.remove("hidden"); $control.pause.classList.add("hidden"); $control.loading.classList.add("hidden"); wavesurfer.backend.ac.suspend(); break; case STATUS_BUFFERING: $control.play.classList.add("hidden"); $control.pause.classList.add("hidden"); $control.loading.classList.remove("hidden"); break; default: assert.fail(status); } }; const setSeek = (newTime, wavesurfer) => { currentTime$.next([newTime, wavesurfer.backend.ac.currentTime]); wavesurfer.backend.source.stop(0); wavesurfer.backend.disconnectSource(); wavesurfer.backend.createSource(); wavesurfer.backend.source.start(0, newTime); }; // feature1: setup the dom const setup$ = rxjs.of(qs($page, "#waveform")).pipe( rxjs.mergeMap(($node) => Promise.resolve(window.WaveSurfer.create({ container: $node, interact: false, waveColor: "#323639", progressColor: "#808080", cursorColor: "#6f6f6f", cursorWidth: 3, height: 200, barWidth: 1, }))), rxjs.tap((wavesurfer) => { wavesurfer.load(getDownloadUrl()); wavesurfer.on("error", (err) => { throw new Error(err); }); wavesurfer.on("ready", () => { $control.main.classList.remove("hidden"); qs($control.main, "#totalDuration").textContent = formatTimecode(wavesurfer.getDuration()); }); onDestroy(() => wavesurfer.destroy()); }), rxjs.catchError(ctrlError()), rxjs.shareReplay(1), ); effect(setup$); // feature2: loading animation effect(setup$.pipe( rxjs.mergeMap((wavesurfer) => new rxjs.Observable((observer) => { wavesurfer.on("loading", (n) => observer.next(n)); wavesurfer.on("ready", () => observer.next(100)); })), rxjs.mergeMap((n) => { const $loader = qs($page, `[data-bind="loader"]`); $loader.querySelector(".audioplayer_loader").style.width = `${n}%`; $loader.querySelector(".percent").textContent = `${n}%`; if (n !== 100) return rxjs.EMPTY; return rxjs.of(null).pipe( rxjs.delay(200), rxjs.tap(() => $loader.classList.add("hidden")), ); }), )); // feature3: connect the audio const ready$ = setup$.pipe( rxjs.mergeMap((wavesurfer) => new rxjs.Observable((observer) => { wavesurfer.on("ready", () => observer.next(wavesurfer)); })), rxjs.share(), ); effect(ready$.pipe(rxjs.tap((wavesurfer) => { wavesurfer.backend.createSource(); wavesurfer.backend.source.start(0, 0); wavesurfer.backend.ac.suspend(); currentTime$.next([0, wavesurfer.backend.ac.currentTime]); }))); // feature4: hint of song progress effect(setup$.pipe( rxjs.mergeMap(() => rxjs.merge( onClick($control.play).pipe(rxjs.mapTo(STATUS_PLAYING)), rxjs.fromEvent(document, "keydown").pipe(rxjs.mapTo(STATUS_PLAYING)), )), rxjs.first(), rxjs.mergeMap((status) => setup$.pipe(rxjs.tap((wavesurfer) => setStatus(status, wavesurfer)))), rxjs.switchMap((wavesurfer) => rxjs.animationFrames().pipe(rxjs.tap(() => { const _currentTime = currentTime(wavesurfer); const percent = _currentTime / wavesurfer.getDuration(); if (percent > 1) return; wavesurfer.drawer.progress(percent); qs($control.main, "#currentTime").textContent = formatTimecode(_currentTime); }))), )); // feature5: player control - play / pause effect(setup$.pipe( rxjs.mergeMap((wavesurfer) => rxjs.merge( onClick($control.play).pipe(rxjs.mapTo(STATUS_PLAYING)), onClick($control.pause).pipe(rxjs.mapTo(STATUS_PAUSED)), ).pipe( rxjs.tap((status) => setStatus(status, wavesurfer)), )), )); // feature6: player control - volume effect(setup$.pipe( rxjs.switchMap(() => rxjs.fromEvent($volume.range, "input").pipe(rxjs.map((e) => e.target.value))), rxjs.startWith(settings_get("volume") === null ? 80 : settings_get("volume")), rxjs.mergeMap((volume) => setup$.pipe(rxjs.tap((wavesurfer) => setVolume(parseInt(volume), wavesurfer)))), )); // feature7: player control - seek effect(ready$.pipe( rxjs.mergeMap((wavesurfer) => rxjs.fromEvent(qs($page, "#waveform"), "click").pipe( rxjs.map((e) => ({ e, wavesurfer })), )), rxjs.map(({ e, wavesurfer }) => { const rec = e.target.closest("#waveform").getBoundingClientRect(); return { wavesurfer, progress: (e.clientX - rec.x) / rec.width }; }), rxjs.tap(({ progress, wavesurfer }) => { wavesurfer.drawer.progress(progress); const newTime = wavesurfer.getDuration() * progress; setSeek(newTime, wavesurfer); }), )); // feature8: player control - keyboard shortcut effect(ready$.pipe( rxjs.switchMap((wavesurfer) => rxjs.fromEvent(document, "keydown").pipe( rxjs.map((e) => e.code), rxjs.tap((code) => { switch (code) { case "Space": case "KeyK": setStatus( wavesurfer.backend.ac.state === "suspended" ? STATUS_PLAYING : STATUS_PAUSED, wavesurfer, ); break; case "KeyM": setVolume(wavesurfer.getVolume() > 0 ? 0 : settings_get("volume"), wavesurfer); break; case "ArrowUp": setVolume(Math.min(wavesurfer.getVolume()*100 + 10, 100), wavesurfer); break; case "ArrowDown": setVolume(Math.max(wavesurfer.getVolume()*100 - 10, 0), wavesurfer); break; case "KeyL": setSeek(Math.min(wavesurfer.getDuration(), currentTime(wavesurfer) + 10), wavesurfer); break; case "KeyF": // chromecastLoader(); break; case "KeyJ": setSeek(Math.max(0, currentTime(wavesurfer) - 10), wavesurfer); break; case "Digit0": setSeek(0, wavesurfer); break; case "Digit1": setSeek(wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit2": setSeek(2 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit3": setSeek(3 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit4": setSeek(4 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit5": setSeek(5 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit6": setSeek(6 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit7": setSeek(7 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit8": setSeek(8 * wavesurfer.getDuration() / 10, wavesurfer); break; case "Digit9": setSeek(9 * wavesurfer.getDuration() / 10, wavesurfer); break; } }), )), )); // // feature9: setup chromecast // effect(ready$.pipe( // rxjs.tap(() => renderMenubar(buildMenubar( // menubarChromecast(), // menubarDownload(), // ))), // )); // effect(rxjs.combineLatest( // setup$, // getSession(), // getConfig(), // ).pipe( // rxjs.mergeMap(async ([wavesurfer, user, config]) => { // if (!Chromecast.isAvailable()) return; // const filename = basename(decodeURIComponent(location.pathname)); // // const link = Chromecast.createLink(getDownloadUrl()); // const media = new chrome.cast.media.MediaInfo( // getDownloadUrl(), // mime, // ); // media.metadata = new chrome.cast.media.MusicTrackMediaMetadata() // media.metadata.title = "test"; // media.metadata.title = filename.substr(0, filename.lastIndexOf(extname(filename))); // media.metadata.subtitle = config.name; // media.metadata.albumName = config.name; // media.metadata.images = [ // new chrome.cast.Image(origin + "/assets/icons/music.png"), // ]; // wavesurfer.setMute(true); // wavesurfer.pause(); // const session = Chromecast.session(); // if (!session) return // setVolume(session.getVolume() * 100); // const req = await Chromecast.createRequest(media, user.authorization); // return session.loadMedia(req); // // .catch((err) => { // // console.error(err); // // notify.send(t("Cannot establish a connection"), "error"); // // setIsChromecast(false); // // setIsLoading(false); // // }); // }), // )); } export function init() { return Promise.all([ setup_chromecast(), loadJS(import.meta.url, "../../lib/vendor/wavesurfer.js"), loadCSS(import.meta.url, "./application_audio.css"), ]); } function setup_chromecast() { if (!("chrome" in window)) { return Promise.resolve(); } else if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { return Promise.resolve(); } // if (!CONFIG.enable_chromecast) { // return Promise.resolve(); // } else return Chromecast.init(); } ================================================ FILE: public/assets/pages/viewerpage/application_downloader.css ================================================ body:not(.dark-mode) .component_filedownloader { background: var(--surface); } body.dark-mode .component_filedownloader { background: #232426; } .component_filedownloader { display: block!important; text-align: center; } .component_filedownloader .download_button { background: rgba(0, 0, 0, 0.3); border-radius: 3px; display: inline-block; color: rgba(255,255,255,0.6); margin-top: 50px; text-transform: uppercase; font-weight: bold; box-shadow: rgba(0,0,0,.14) 0px 4px 5px 0px, rgba(0,0,0,.12) 0px 1px 10px 0px, rgba(0,0,0,.2) 0px 2px 4px -1px; } .component_filedownloader .download_button > * { display: inline-block; padding: 15px 20px; } .component_filedownloader component-icon img { height: 20px; float: right; } ================================================ FILE: public/assets/pages/viewerpage/application_downloader.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { loadCSS } from "../../helpers/loader.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import { transition } from "./common.js"; import { renderMenubar } from "./component_menubar.js"; import "../../components/icon.js"; export default async function(render, { acl$, getFilename, getDownloadUrl, hasMenubar = true }) { const $page = createElement(` <div class="component_filedownloader"> <component-menubar filename="${safe(getFilename())}" class="${!hasMenubar && "hidden"}"></component-menubar> <div class="download_button no-select"> <a download="${safe(getFilename())}" href="${safe(getDownloadUrl())}">${t("DOWNLOAD")}</a> <component-icon name="loading" class="hidden"></component-icon> </div> </div> `); render(transition($page)); renderMenubar(qs($page, "component-menubar")); const $link = qs($page, "a"); const $loading = qs($page, "component-icon"); const setLoading = (isLoading) => { isLoading ? $loading.classList.remove("hidden") : $loading.classList.add("hidden"); isLoading ? $link.classList.add("hidden") : $link.classList.remove("hidden"); }; effect(rxjs.fromEvent($link, "click").pipe( rxjs.tap(() => { setLoading(true); document.cookie = "download=yes; path=/; max-age=10;"; }), rxjs.mergeMap(() => new Promise((done) => { const id = setInterval(() => { if (/download=yes/.test(document.cookie)) return; clearInterval(id); done(null); }, 200); })), rxjs.tap(() => setLoading(false)), )); effect(acl$.pipe( rxjs.catchError(ctrlError()), )); } export function init() { return loadCSS(import.meta.url, "./application_downloader.css"); } ================================================ FILE: public/assets/pages/viewerpage/application_ebook.css ================================================ .component_ebookviewer .ebookviewer_container { display: flex; flex-grow: 1; min-height: 0; } .component_ebookviewer .ebookviewer_container .epub-container { padding-top: 20px; padding-bottom: 50px; box-sizing: border-box; overflow-x: hidden !important; } ================================================ FILE: public/assets/pages/viewerpage/application_ebook.d.ts ================================================ interface Window { ePub: { Book: new (options: any) => any; }; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_ebook.js ================================================ import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import assert from "../../lib/assert.js"; import { loadJS, loadCSS } from "../../helpers/loader.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; export default function(render, { getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_ebookviewer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div class="ebookviewer_container" data-bind="epub"></div> </div> `); render($page); renderMenubar(qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl())); const rendition$ = new rxjs.ReplaySubject(1); // feature1: setup the dom const $epub = qs($page, `[data-bind="epub"]`); const removeLoader = createLoader($page, { append: ($loader) => $page.insertBefore($loader, qs($page, ".ebookviewer_container")), }); const setup$ = rxjs.of($epub).pipe( rxjs.mergeMap(async($epub) => { const book = new window.ePub.Book({ replacements: "blobUrl", }); const rendition = book.renderTo($epub, { height: "100%", width: "100%", flow: "scrolled-doc", method: "continuous", allowScriptedContent: false, }); rendition.display(); onDestroy(() => { book.destroy(); rendition.destroy(); }); book.open(getDownloadUrl()); await new Promise((done) => rendition.hooks.render.register(() => { rendition$.next(rendition); done(null); })); }), removeLoader, rxjs.catchError(ctrlError()), rxjs.share(), ); effect(setup$); // feature2: navigation effect(setup$.pipe( rxjs.mergeMap(() => rxjs.merge( rxjs.fromEvent(document, "keydown"), rendition$.pipe(rxjs.mergeMap(() => rxjs.fromEvent(assert.type(qs(document.body, "iframe"), HTMLElement).contentDocument.body, "keydown"))), )), rxjs.map((e) => { switch (e.code) { case "Space": return (r) => r.next(); case "ArrowRight": return (r) => r.next(); case "PageDown": return (r) => r.next(); case "ArrowLeft": return (r) => r.prev(); case "PageUp": return (r) => r.prev(); } return null; }), rxjs.mergeMap((fn) => fn === null ? rxjs.EMPTY : rendition$.asObservable().pipe( rxjs.first(), rxjs.tap((rendition) => fn(rendition)) )), rxjs.catchError(ctrlError()), )); } export function init() { return Promise.all([ loadJS(import.meta.url, "../../lib/vendor/epub/zip.min.js"), loadJS(import.meta.url, "../../lib/vendor/epub/epub.min.js"), loadCSS(import.meta.url, "./application_ebook.css"), ]); } ================================================ FILE: public/assets/pages/viewerpage/application_editor/clike.js ================================================ import "../../../lib/vendor/codemirror/mode/clike/clike.js"; window.CodeMirror.__mode = "text/x-c++src"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/clojure.js ================================================ import "../../../lib/vendor/codemirror/mode/clojure/clojure.js"; window.CodeMirror.__mode = "clojure"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/cmake.js ================================================ import "../../../lib/vendor/codemirror/mode/cmake/cmake.js"; window.CodeMirror.__mode = "cmake"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/commonlisp.js ================================================ import "../../../lib/vendor/codemirror/mode/commonlisp/commonlisp.js"; window.CodeMirror.__mode = "commonlisp"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/css.js ================================================ import "../../../lib/vendor/codemirror/mode/css/css.js"; window.CodeMirror.__mode = "css"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/diff.js ================================================ import "../../../lib/vendor/codemirror/mode/diff/diff.js"; window.CodeMirror.__mode = "diff"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/dockerfile.js ================================================ import "../../../lib/vendor/codemirror/mode/dockerfile/dockerfile.js"; window.CodeMirror.__mode = "dockerfile"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/elm.js ================================================ import "../../../lib/vendor/codemirror/mode/elm/elm.js"; window.CodeMirror.__mode = "elm"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/emacs-org.js ================================================ export const org_cycle = (cm) => { const pos = cm.getCursor(); isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos); }; const state = { stab: "CONTENT", }; export const org_set_fold = (cm) => { const cursor = cm.getCursor(); set_folding_mode(cm, state.stab); cm.setCursor(cursor); return state.stab; }; /* * DONE: Global visibility cycling * TODO: or move to previous table field. */ export const org_shifttab = (cm) => { if (state.stab === "SHOW_ALL") { state.stab = "OVERVIEW"; } else if (state.stab === "OVERVIEW") { state.stab = "CONTENT"; } else if (state.stab === "CONTENT") { state.stab = "SHOW_ALL"; } set_folding_mode(cm, state.stab); return state.stab; }; function set_folding_mode(cm, mode) { if (mode === "OVERVIEW") { folding_mode_overview(cm); } else if (mode === "SHOW_ALL") { folding_mode_all(cm); } else if (mode === "CONTENT") { folding_mode_content(cm); } cm.refresh(); function folding_mode_overview(cm) { cm.operation(function() { for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { fold(cm, window.CodeMirror.Pos(i, 0)); } }); } function folding_mode_content(cm) { cm.operation(function() { let previous_header = null; for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { fold(cm, window.CodeMirror.Pos(i, 0)); if (/header/.test(cm.getTokenTypeAt(window.CodeMirror.Pos(i, 0))) === true) { const level = cm.getLine(i).replace(/^(\*+).*/, "$1").length; if (previous_header && level > previous_header.level) { unfold(cm, window.CodeMirror.Pos(previous_header.line, 0)); } previous_header = { line: i, level, }; } } }); } function folding_mode_all(cm) { cm.operation(function() { for (let i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) { if (/header/.test(cm.getTokenTypeAt(window.CodeMirror.Pos(i, 0))) === true) { unfold(cm, window.CodeMirror.Pos(i, 0)); } } }); } } /* * Promote heading or move table column to left. */ export const org_metaleft = (cm) => { const line = cm.getCursor().line; _metaleft(cm, line); }; function _metaleft(cm, line) { let p = null; if (p = isTitle(cm, line)) { if (p["level"] > 1) { cm.replaceRange( "", { line: p.start, ch: 0 }, { line: p.start, ch: 1 }, ); } } else if (p = isItemList(cm, line)) { for (let i=p.start; i<=p.end; i++) { if (p["level"] > 0) { cm.replaceRange( "", { line: i, ch: 0 }, { line: i, ch: 2 }, ); } } } else if (p = isNumberedList(cm, line)) { for (let i=p.start; i<=p.end; i++) { if (p["level"] > 0) { cm.replaceRange( "", { line: i, ch: 0 }, { line: i, ch: 3 }, ); } } rearrange_list(cm, line); } } /* * Demote a subtree, a list item or move table column to right. * In front of a drawer or a block keyword, indent it correctly. */ export const org_metaright = (cm) => { const line = cm.getCursor().line; _metaright(cm, line); }; function _metaright(cm, line) { let p = null; let tmp = null; if (p = isTitle(cm, line)) { cm.replaceRange("*", { line: p.start, ch: 0 }); } else if (p = isItemList(cm, line)) { if (tmp = isItemList(cm, p.start - 1)) { if (p.level < tmp.level + 1) { for (let i=p.start; i<=p.end; i++) { cm.replaceRange(" ", { line: i, ch: 0 }); } } } } else if (p = isNumberedList(cm, line)) { if (tmp = isNumberedList(cm, p.start - 1)) { if (p.level < tmp.level + 1) { for (let i=p.start; i<=p.end; i++) { cm.replaceRange(" ", { line: i, ch: 0 }); } rearrange_list(cm, p.start); } } } } /* * Insert a new heading or wrap a region in a table */ export const org_meta_return = (cm) => { const line = cm.getCursor().line; const content = cm.getLine(line); let p = null; if (p = isItemList(cm, line)) { const level = p.level; cm.replaceRange( "\n"+" ".repeat(level*2)+"- ", { line: p.end, ch: cm.getLine(p.end).length }, ); cm.setCursor({ line: p.end+1, ch: level*2+2 }); } else if (p = isNumberedList(cm, line)) { const level = p.level; cm.replaceRange( "\n"+" ".repeat(level*3)+(p.n+1)+". ", { line: p.end, ch: cm.getLine(p.end).length }, ); cm.setCursor({ line: p.end+1, ch: level*3+3 }); rearrange_list(cm, line); } else if (p = isTitle(cm, line)) { const tmp = previousOfType(cm, "title", line); const level = (tmp && tmp.level) || 1; cm.replaceRange("\n"+"*".repeat(level)+" ", { line, ch: content.length }); cm.setCursor({ line: line+1, ch: level+1 }); } else if (content.trim() === "") { cm.replaceRange("* ", { line, ch: 0 }); cm.setCursor({ line, ch: 2 }); } else { cm.replaceRange("\n\n* ", { line, ch: content.length }); cm.setCursor({ line: line + 2, ch: 2 }); } }; const TODO_CYCLES = ["TODO", "DONE", ""]; /* * Cycle the thing at point or in the current line, depending on context. * Depending on context, this does one of the following: * - TODO: switch a timestamp at point one day into the past * - DONE: on a headline, switch to the previous TODO keyword. * - TODO: on an item, switch entire list to the previous bullet type * - TODO: on a property line, switch to the previous allowed value * - TODO: on a clocktable definition line, move time block into the past */ export const org_shiftleft = (cm) => { const cycles = [].concat(TODO_CYCLES.slice(0).reverse(), TODO_CYCLES.slice(-1)); const line = cm.getCursor().line; const content = cm.getLine(line); const params = isTitle(cm, line); if (params === null) return; params["status"] = cycles[cycles.indexOf(params["status"]) + 1]; cm.replaceRange( makeTitle(params), { line, ch: 0 }, { line, ch: content.length }, ); }; /* * Cycle the thing at point or in the current line, depending on context. * Depending on context, this does one of the following: * - TODO: switch a timestamp at point one day into the future * - DONE: on a headline, switch to the next TODO keyword. * - TODO: on an item, switch entire list to the next bullet type * - TODO: on a property line, switch to the next allowed value * - TODO: on a clocktable definition line, move time block into the future */ export const org_shiftright = (cm) => { cm.operation(() => { const cycles = [].concat(TODO_CYCLES, [TODO_CYCLES[0]]); const line = cm.getCursor().line; const content = cm.getLine(line); const params = isTitle(cm, line); if (params === null) return; params["status"] = cycles[cycles.indexOf(params["status"]) + 1]; cm.replaceRange( makeTitle(params), { line, ch: 0 }, { line, ch: content.length }, ); }); }; export const org_insert_todo_heading = (cm) => { cm.operation(() => { const line = cm.getCursor().line; const content = cm.getLine(line); let p = null; if (p = isItemList(cm, line)) { const level = p.level; cm.replaceRange( "\n"+" ".repeat(level*2)+"- [ ] ", { line: p.end, ch: cm.getLine(p.end).length }, ); cm.setCursor({ line: line+1, ch: 6+level*2 }); } else if (p = isNumberedList(cm, line)) { const level = p.level; cm.replaceRange( "\n"+" ".repeat(level*3)+(p.n+1)+". [ ] ", { line: p.end, ch: cm.getLine(p.end).length }, ); cm.setCursor({ line: p.end+1, ch: level*3+7 }); rearrange_list(cm, line); } else if (p = isTitle(cm, line)) { const level = (p && p.level) || 1; cm.replaceRange("\n"+"*".repeat(level)+" TODO ", { line, ch: content.length }); cm.setCursor({ line: line+1, ch: level+6 }); } else if (content.trim() === "") { cm.replaceRange("* TODO ", { line, ch: 0 }); cm.setCursor({ line, ch: 7 }); } else { cm.replaceRange("\n\n* TODO ", { line, ch: content.length }); cm.setCursor({ line: line + 2, ch: 7 }); } }); }; /* * Move subtree up or move table row up. * Calls ‘org-move-subtree-up’ or ‘org-table-move-row’ or * ‘org-move-item-up’, depending on context */ export const org_metaup = (cm) => { cm.operation(() => { const line = cm.getCursor().line; let p = null; if (p = isItemList(cm, line)) { const a = isItemList(cm, p.start - 1); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); rearrange_list(cm, line); } } else if (p = isNumberedList(cm, line)) { const a = isNumberedList(cm, p.start - 1); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); rearrange_list(cm, line); } } else if (p = isTitle(cm, line)) { let _line = line; let a; do { _line -= 1; if (a = isTitle(cm, _line, p.level)) { break; } } while (_line > 0); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); org_set_fold(cm); } } }); }; /* * Move subtree down or move table row down. * Calls ‘org-move-subtree-down’ or ‘org-table-move-row’ or * ‘org-move-item-down’, depending on context */ export const org_metadown = (cm) => { cm.operation(() => { const line = cm.getCursor().line; let p = null; if (p = isItemList(cm, line)) { const a = isItemList(cm, p.end + 1); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); } } else if (p = isNumberedList(cm, line)) { const a = isNumberedList(cm, p.end + 1); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); } rearrange_list(cm, line); } else if (p = isTitle(cm, line)) { const a = isTitle(cm, p.end + 1, p.level); if (a) { swap(cm, [p.start, p.end], [a.start, a.end]); org_set_fold(cm); } } }); }; export const org_shiftmetaright = function(cm) { cm.operation(() => { const line = cm.getCursor().line; let p = null; if (p = isTitle(cm, line)) { _metaright(cm, line); for (let i=p.start + 1; i<=p.end; i++) { if (isTitle(cm, i)) { _metaright(cm, i); } } } }); }; export const org_shiftmetaleft = function(cm) { cm.operation(() => { const line = cm.getCursor().line; let p = null; if (p = isTitle(cm, line)) { if (p.level === 1) return; _metaleft(cm, line); for (let i=p.start + 1; i<=p.end; i++) { if (isTitle(cm, i)) { _metaleft(cm, i); } } } }); }; function makeTitle(p) { let content = "*".repeat(p["level"])+" "; if (p["status"]) { content += p["status"]+" "; } content += p["content"]; return content; } function previousOfType(cm, type, line) { let tmp; let i; for (i=line - 1; i>0; i--) { if (type === "list" || type === null) { tmp = isItemList(cm, line); } else if (type === "numbered" || type === null) { tmp = isNumberedList(cm, line); } else if (type === "title" || type === null) { tmp = isTitle(cm, line); } if (tmp !== null) { return tmp; } } return null; } function isItemList(cm, line) { const rootLineItem = findRootLine(cm, line); if (rootLineItem === null) return null; line = rootLineItem; const content = cm.getLine(line); if (content && (content.trimLeft()[0] !== "-" || content.trimLeft()[1] !== " ")) return null; const padding = content.replace(/^(\s*).*$/, "$1").length; if (padding % 2 !== 0) return null; return { type: "list", level: padding / 2, content: content.trimLeft().replace(/^\s*\-\s(.*)$/, "$1"), start: line, end: (function(_cm, _line) { let line_candidate = _line; let content = null; do { _line += 1; content = _cm.getLine(_line); if (content === undefined || content.trimLeft()[0] === "-") { break; } else if (/^\s+/.test(content)) { line_candidate = _line; continue; } else { break; } } while (_line <= _cm.lineCount()); return line_candidate; }(cm, line)), }; function findRootLine(_cm, _line) { let content; do { content = _cm.getLine(_line); if (/^\s*\-/.test(content)) return _line; else if (/^\s+/.test(content) === false) { break; } _line -= 1; } while (_line >= 0); return null; } } function isNumberedList(cm, line) { const rootLineItem = findRootLine(cm, line); if (rootLineItem === null) return null; line = rootLineItem; const content = cm.getLine(line); if (/^[0-9]+[\.\)]\s.*$/.test(content && content.trimLeft()) === false) return null; const padding = content.replace(/^(\s*)[0-9]+.*$/, "$1").length; if (padding % 3 !== 0) return null; return { type: "numbered", level: padding / 3, content: content.trimLeft().replace(/^[0-9]+[\.\)]\s(.*)$/, "$1"), start: line, end: (function(_cm, _line) { let line_candidate = _line; let content = null; do { _line += 1; content = _cm.getLine(_line); if (content === undefined || /^[0-9]+[\.\)]/.test(content.trimLeft())) { break; } else if (/^\s+/.test(content)) { line_candidate = _line; continue; } else { break; } } while (_line <= _cm.lineCount()); return line_candidate; }(cm, line)), // specific n: parseInt(content.trimLeft().replace(/^([0-9]+).*$/, "$1")), separator: content.trimLeft().replace(/^[0-9]+([\.\)]).*$/, "$1"), }; function findRootLine(_cm, _line) { let content; do { content = _cm.getLine(_line); if (/^\s*[0-9]+[\.\)]\s/.test(content)) return _line; else if (/^\s+/.test(content) === false) { break; } _line -= 1; } while (_line >= 0); return null; } } function isTitle(cm, line, level) { const content = cm.getLine(line); if (/^\*+\s/.test(content) === false) return null; const match = content.match(/^(\*+)([\sA-Z]*)\s(.*)$/); const reference_level = match[1].length; if (level !== undefined && level !== reference_level) return null; if (match === null) return null; return { type: "title", level: reference_level, content: match[3], start: line, end: (function(_cm, _line) { let line_candidate = _line; let content = null; do { _line += 1; content = _cm.getLine(_line); if (content === undefined) break; const match = content.match(/^(\*+)\s.*/); if ( match && match[1] && (match[1].length === reference_level || match[1].length < reference_level) ) { break; } else { line_candidate = _line; continue; } } while (_line <= _cm.lineCount()); return line_candidate; }(cm, line)), // specific status: match[2].trim(), }; } function rearrange_list(cm, line) { const line_inferior = find_limit_inferior(cm, line); const line_superior = find_limit_superior(cm, line); let last_p = null; let p; for (let i=line_inferior; i<=line_superior; i++) { if (p = isNumberedList(cm, i)) { // rearrange numbers on the numbered list if (last_p) { if (p.level === last_p.level) { const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); if (tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); } else if (p.level > last_p.level) { if (p.n !== 1) { setNumber(cm, p.start, 1); } } else if (p.level < last_p.level) { const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level); if (tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1); } } else { if (p.n !== 1) setNumber(cm, p.start, 1); } } if (p = (isNumberedList(cm, i) || isItemList(cm, i))) { // rearrange spacing levels in list if (last_p) { if (p.level > last_p.level) { if (p.level !== last_p.level + 1) { setLevel(cm, [p.start, p.end], last_p.level + 1, p.type); } } } else { if (p.level !== 0) { setLevel(cm, [p.start, p.end], 0, p.type); } } } last_p = p; // we can process content block instead of line if (p) { i += (p.end - p.start); } } function findLastAtLevel(_cm, line, line_limit_inf, level) { let p; do { line -= 1; if ((p = isNumberedList(_cm, line)) && p.level === level) { return p; } } while (line > line_limit_inf); return null; } function setLevel(_cm, range, level, type) { let content; let i; for (i=range[0]; i<=range[1]; i++) { content = cm.getLine(i).trimLeft(); const n_spaces = (function(_level, _line, _type) { let spaces = _level * 3; if (_line > 0) { spaces += _type === "numbered" ? 3 : 2; } return spaces; }(level, i - range[0], type)); content = " ".repeat(n_spaces) + content; cm.replaceRange( content, { line: i, ch: 0 }, { line: i, ch: _cm.getLine(i).length }, ); } } function setNumber(_cm, line, level) { const content = _cm.getLine(line); const new_content = content.replace(/[0-9]+\./, level+"."); cm.replaceRange( new_content, { line, ch: 0 }, { line, ch: content.length }, ); } function find_limit_inferior(_cm, _line) { let content; let p; let match; let line_candidate = _line; do { content = _cm.getLine(_line); p = isNumberedList(_cm, _line); match = /(\s+).*$/.exec(content); if (p) line_candidate = _line; if (!p || !match) break; _line -= 1; } while (_line >= 0); return line_candidate; } function find_limit_superior(_cm, _line) { let content; let p; let match; let line_candidate = _line; do { content = _cm.getLine(_line); p = isNumberedList(_cm, _line); match = /(\s+).*$/.exec(content); if (p) line_candidate = _line; if (!p || !match) break; _line += 1; } while (_line < _cm.lineCount()); return line_candidate; } } function swap(cm, from, to) { const from_content = cm.getRange( { line: from[0], ch: 0 }, { line: from[1], ch: cm.getLine(from[1]).length }, ); const to_content = cm.getRange( { line: to[0], ch: 0 }, { line: to[1], ch: cm.getLine(to[1]).length }, ); const cursor = cm.getCursor(); if (to[0] > from[0]) { // moving down cm.replaceRange( from_content, { line: to[0], ch: 0 }, { line: to[1], ch: cm.getLine(to[1]).length }, ); cm.replaceRange( to_content, { line: from[0], ch: 0 }, { line: from[1], ch: cm.getLine(from[1]).length }, ); cm.setCursor({ line: cursor.line + (to[1] - to[0] + 1), ch: cursor.ch, }); } else { // moving up cm.replaceRange( to_content, { line: from[0], ch: 0 }, { line: from[1], ch: cm.getLine(from[1]).length }, ); cm.replaceRange( from_content, { line: to[0], ch: 0 }, { line: to[1], ch: cm.getLine(to[1]).length }, ); cm.setCursor({ line: cursor.line - (to[1] - to[0] + 1), ch: cursor.ch, }); } } export function fold(cm, start) { cm.foldCode(start, null, "fold"); } export function unfold(cm, start) { cm.foldCode(start, null, "unfold"); } export function isFold(cm, start) { const line = start.line; const marks = cm.findMarks(window.CodeMirror.Pos(line, 0), window.CodeMirror.Pos(line + 1, 0)); for (let i = 0; i < marks.length; ++i) { if (marks[i].__isFold && marks[i].find().from.line === line) return marks[i]; } return false; } ================================================ FILE: public/assets/pages/viewerpage/application_editor/erlang.js ================================================ import "../../../lib/vendor/codemirror/mode/erlang/erlang.js"; window.CodeMirror.__mode = "erlang"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/go.js ================================================ import "../../../lib/vendor/codemirror/mode/go/go.js"; window.CodeMirror.__mode = "go"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/groovy.js ================================================ import "../../../lib/vendor/codemirror/mode/groovy/groovy.js"; window.CodeMirror.__mode = "text/x-groovy"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/htmlmixed.js ================================================ import "../../../lib/vendor/codemirror/mode/htmlmixed/htmlmixed.js"; window.CodeMirror.__mode = "htmlmixed"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/java.js ================================================ import "./clike.js"; window.CodeMirror.__mode = "text/x-java"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/javascript.js ================================================ import "../../../lib/vendor/codemirror/mode/javascript/javascript.js"; window.CodeMirror.__mode = "javascript"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/jsx.js ================================================ import "../../../lib/vendor/codemirror/mode/jsx/jsx.js"; window.CodeMirror.__mode = "jsx"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/keymap_base.js ================================================ import "../../../lib/vendor/codemirror/keymap/sublime.js"; ================================================ FILE: public/assets/pages/viewerpage/application_editor/keymap_vim.js ================================================ import "../../../lib/vendor/codemirror/keymap/vim.js"; ================================================ FILE: public/assets/pages/viewerpage/application_editor/lua.js ================================================ import "../../../lib/vendor/codemirror/mode/lua/lua.js"; window.CodeMirror.__mode = "lua"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/orgmode.js ================================================ import "../../../lib/vendor/codemirror/addon/mode/simple.js"; import { org_cycle, org_shifttab, org_metaleft, org_metaright, org_meta_return, org_metaup, org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold, isFold, org_shiftmetaleft, org_shiftmetaright, } from "./emacs-org.js"; import { join } from "../../../lib/path.js"; import { currentShare } from "../../filespage/cache.js"; window.CodeMirror.__mode = "orgmode"; window.CodeMirror.defineSimpleMode("orgmode", { start: [ { regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star", "header level1 org-todo", "header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"] }, { regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star", "header org-todo", "header org-done", "header org-priority", "header", "header void", "header comment"] }, { regex: /(\+[^\+]+\+)/, token: ["strikethrough"] }, { regex: /(\*[^\*]+\*)/, token: ["strong"] }, { regex: /(\/[^\/]+\/)/, token: ["em"] }, { regex: /(\_[^\_]+\_)/, token: ["link"] }, { regex: /(\~[^\~]+\~)/, token: ["comment"] }, { regex: /(\=[^\=]+\=)/, token: ["comment"] }, { regex: /\[\[[^\[\]]+\]\[[^\[\]]+\]\]/, token: "org-url" }, // links { regex: /\[\[[^\[\]]+\]\]/, token: "org-image" }, // image { regex: /\[[xX\s\-\_]\]/, token: "qualifier org-toggle" }, // checkbox { regex: /\#\+(?:(BEGIN|begin))_[a-zA-Z]*/, token: "comment", next: "env", sol: true }, // comments { regex: /:?[A-Z_]+\:.*/, token: "comment", sol: true }, // property drawers { regex: /(\#\+[a-zA-Z_]*)(\:.*)/, token: ["keyword", "qualifier"], sol: true }, // environments { regex: /(CLOCK\:|SHEDULED\:|DEADLINE\:)(\s.+)/, token: ["comment", "keyword"] }, ], env: [ { regex: /\#\+(?:(END|end))_[a-zA-Z]*/, token: "comment", next: "start", sol: true }, { regex: /.*/, token: "comment" }, ], }); window.CodeMirror.registerHelper("fold", "orgmode", function(cm, start) { // init const levelToMatch = headerLevel(start.line); // no folding needed if (levelToMatch === null) return; // find folding limits const lastLine = cm.lastLine(); let end = start.line; while (end < lastLine) { end += 1; const level = headerLevel(end); if (level && level <= levelToMatch) { end = end - 1; break; }; } return { from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length), to: window.CodeMirror.Pos(end, cm.getLine(end).length), }; function headerLevel(lineNo) { const line = cm.getLine(lineNo); const match = /^\*+/.exec(line); if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(window.CodeMirror.Pos(lineNo, 0)))) { return match[0].length; } return null; } }); window.CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) { return mode.name === "orgmode"; }, function(cm, start) { const drawer = isBeginningOfADrawer(start.line); if (drawer === false) return; // find folding limits const lastLine = cm.lastLine(); let end = start.line; while (end < lastLine) { end += 1; if (isEndOfADrawer(end)) { break; } } return { from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length), to: window.CodeMirror.Pos(end, cm.getLine(end).length), }; function isBeginningOfADrawer(lineNo) { const line = cm.getLine(lineNo); const match = /^\:.*\:$/.exec(line); if (match && match.length === 1 && match[0] !== ":END:") { return true; } return false; } function isEndOfADrawer(lineNo) { const line = cm.getLine(lineNo); return line.trim() === ":END:"; } }); window.CodeMirror.registerHelper("orgmode", "init", (editor) => { editor.setOption("extraKeys", { "Tab": (cm) => org_cycle(cm), "Shift-Tab": (cm) => org_shifttab(cm), "Alt-Left": (cm) => org_metaleft(cm), "Alt-Right": (cm) => org_metaright(cm), "Alt-Enter": (cm) => org_meta_return(cm), "Alt-Up": (cm) => org_metaup(cm), "Alt-Down": (cm) => org_metadown(cm), "Shift-Alt-Left": (cm) => org_shiftmetaleft(cm), "Shift-Alt-Right": (cm) => org_shiftmetaright(cm), "Shift-Alt-Enter": (cm) => org_insert_todo_heading(cm), "Shift-Left": (cm) => org_shiftleft(cm), "Shift-Right": (cm) => org_shiftright(cm), }); editor.on("mousedown", toggleHandler); editor.on("touchstart", toggleHandler); editor.on("gutterClick", foldLine); // fold everything except headers by default editor.operation(function() { for (let i = 0; i < editor.lineCount(); i++) { if (/header/.test(editor.getTokenTypeAt(window.CodeMirror.Pos(i, 0))) === false) { fold(editor, window.CodeMirror.Pos(i, 0)); } } }); return window.CodeMirror.orgmode.destroy.bind(this, editor); }); window.CodeMirror.registerHelper("orgmode", "destroy", (editor) => { editor.off("mousedown", toggleHandler); editor.off("touchstart", toggleHandler); editor.off("gutterClick", foldLine); }); function foldLine(cm, line) { const cursor = { line, ch: 0 }; isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); } let widgets = []; function toggleHandler(cm, e) { const position = cm.coordsChar({ left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX), top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY), }, "page"); const token = cm.getTokenAt(position); _disableSelection(); if (/org-level-star/.test(token.type)) { _preventIfShould(); _foldHeadline(); _disableSelection(); } else if (/org-toggle/.test(token.type)) { _preventIfShould(); _toggleCheckbox(); _disableSelection(); } else if (/org-todo/.test(token.type)) { _preventIfShould(); _toggleTodo(); _disableSelection(); } else if (/org-done/.test(token.type)) { _preventIfShould(); _toggleDone(); _disableSelection(); } else if (/org-priority/.test(token.type)) { _preventIfShould(); _togglePriority(); _disableSelection(); } else if (/org-url/.test(token.type)) { _disableSelection(); _navigateLink(); } else if (/org-image/.test(token.type)) { _disableSelection(); _toggleImageWidget(); } function _preventIfShould() { if ("ontouchstart" in window) e.preventDefault(); } function _disableSelection() { cm.on("beforeSelectionChange", _onSelectionChangeHandler); function _onSelectionChangeHandler(cm, obj) { obj.update([{ anchor: position, head: position, }]); cm.off("beforeSelectionChange", _onSelectionChangeHandler); } } function _foldHeadline() { const line = position.line; if (line >= 0) { const cursor = { line, ch: 0 }; isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor); } } function _toggleCheckbox() { const line = position.line; const content = cm.getRange( { line, ch: token.start }, { line, ch: token.end }, ); const new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]"; cm.replaceRange( new_content, { line, ch: token.start }, { line, ch: token.end }, ); } function _toggleTodo() { const line = position.line; cm.replaceRange( "DONE", { line, ch: token.start }, { line, ch: token.end }, ); } function _toggleDone() { const line = position.line; cm.replaceRange( "TODO", { line, ch: token.start }, { line, ch: token.end }, ); } function _togglePriority() { const PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "]; const line = position.line; const content = cm.getRange({ line, ch: token.start }, { line, ch: token.end }); const new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1]; cm.replaceRange( new_content, { line, ch: token.start }, { line, ch: token.end }, ); } function _toggleImageWidget() { const exist = !!widgets .filter((line) => line === position.line)[0]; if (exist === false) { if (!token.string.match(/\[\[(.*)\]\]/)) return null; const $node = _buildImage(RegExp.$1); const widget = cm.addLineWidget(position.line, $node, { coverGutter: false }); widgets.push(position.line); $node.addEventListener("click", closeWidget); function closeWidget() { widget.clear(); $node.removeEventListener("click", closeWidget); widgets = widgets.filter((line) => line !== position.line); } } function _buildImage(src) { const $el = document.createElement("div"); const $img = document.createElement("img"); if (/^https?\:\/\//.test(src)) { $img.src = src; } else { const path = join(location, src).replace(/^\/view/, ""); $img.src = "/api/files/cat?path=" + path; const share = currentShare(); if (share) $img.src += "&share=" + share; } $el.appendChild($img); return $el; } return null; } function _navigateLink() { token.string.match(/\[\[(.*?)\]\[/); const link = RegExp.$1; if (!link) return; let open = "_blank"; const isMobile = screen.availWidth < screen.availHeight; if (!document.querySelector(".component_fab img.component_icon[alt=\"save\"]")) { open = "_self"; } else if (isMobile) { open = "_self"; } if (/^https?\:\/\//.test(link)) { window.open(link, open); } else { let url = join(location, link); const share = currentShare(); if (share) url += "?share=" + share; window.open(url, open); } } } export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/perl.js ================================================ import "../../../lib/vendor/codemirror/mode/perl/perl.js"; window.CodeMirror.__mode = "perl"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/php.js ================================================ import "./clike.js"; import "../../../lib/vendor/codemirror/mode/php/php.js"; window.CodeMirror.__mode = "php"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/properties.js ================================================ import "../../../lib/vendor/codemirror/mode/properties/properties.js"; window.CodeMirror.__mode = "properties"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/python.js ================================================ import "../../../lib/vendor/codemirror/mode/python/python.js"; window.CodeMirror.__mode = "python"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/r.js ================================================ import "../../../lib/vendor/codemirror/mode/r/r.js"; window.CodeMirror.__mode = "r"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/ruby.js ================================================ import "../../../lib/vendor/codemirror/mode/ruby/ruby.js"; window.CodeMirror.__mode = "ruby"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/rust.js ================================================ import "../../../lib/vendor/codemirror/mode/rust/rust.js"; window.CodeMirror.__mode = "rust"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/sass.js ================================================ import "../../../lib/vendor/codemirror/mode/sass/sass.js"; window.CodeMirror.__mode = "sass"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/shell.js ================================================ import "../../../lib/vendor/codemirror/mode/shell/shell.js"; window.CodeMirror.__mode = "shell"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/sparql.js ================================================ import "../../../lib/vendor/codemirror/mode/sparql/sparql.js"; window.CodeMirror.__mode = "sparql"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/spreadsheet.js ================================================ import "../../../lib/vendor/codemirror/mode/spreadsheet/spreadsheet.js"; window.CodeMirror.__mode = "spreadsheet"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/sql.js ================================================ import "../../../lib/vendor/codemirror/mode/sql/sql.js"; window.CodeMirror.__mode = "sql"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/stex.js ================================================ import "../../../lib/vendor/codemirror/mode/stex/stex.js"; window.CodeMirror.__mode = "stex"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/text.js ================================================ export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/xml.js ================================================ import "../../../lib/vendor/codemirror/mode/xml/xml.js"; window.CodeMirror.__mode = "xml"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/yaml-frontmatter.js ================================================ import "../../../lib/vendor/codemirror/mode/gfm/gfm.js"; import "../../../lib/vendor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js"; window.CodeMirror.__mode = "yaml-frontmatter"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor/yaml.js ================================================ import "../../../lib/vendor/codemirror/mode/yaml/yaml.js"; window.CodeMirror.__mode = "yaml"; export default window.CodeMirror; ================================================ FILE: public/assets/pages/viewerpage/application_editor.css ================================================ /* https://github.com/codemirror/codemirror5/issues/4895 */ .component_editor { position: relative; flex-grow: 1; } .component_editor .CodeMirror { position: absolute; top: 0; bottom: 0; left: 0; right: 0; height: 100%; } /* UX Integration */ .CodeMirror-scroll { -webkit-overflow-scrolling: touch; } .CodeMirror { color: #3b4045; background: var(--bg-color); } .CodeMirror-sizer > div { padding-top: 4px; padding-bottom: 5px; } .CodeMirror-foldmarker { padding: 5px; } .CodeMirror .CodeMirror-cursor{ background-color: rgba(198,200,204,0.65); } .CodeMirror .cm-vim-message { color: var(--error)!important; } /* HIDE LINE NUMBERS ON MOBILE */ @media screen and (max-width: 400px) { .CodeMirror-sizer { margin-left: 0 !important; } .CodeMirror-gutters { display: none; } .CodeMirror-gutter-wrapper { display: none; } } .CodeMirror-linenumber { cursor: pointer; } /* SEARCH */ .CodeMirror-dialog { position: sticky; left: 0; right: 0; background: var(--color); z-index: 15; padding: 5px .8em; overflow: hidden; color: #e2e2e2; box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5); } .CodeMirror-dialog-top { border-bottom: 1px solid #eee; bottom: 0; } .CodeMirror-dialog-bottom { border-top: 1px solid #eee; bottom: 0; } .CodeMirror-dialog input { border: none; outline: none; background: transparent; width: 20em; color: white; font-family: inherit; padding-left: 5px; font-size: inherit; } .CodeMirror-dialog button { font-size: 70%; } /* Font stuff */ .CodeMirror { font-size: 16px; font-family: "Source Code Pro", monospace; } @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 400; src: local("Source Code Pro"), local("SourceCodePro-Regular"), url(../../fonts/SourceCodePro-Regular-400-latin-ext.woff2) format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 400; src: local("Source Code Pro"), local("SourceCodePro-Regular"), url(../../fonts/SourceCodePro-Regular-400-latin.woff2) format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 600; src: local('Source Code Pro Semibold'), local('SourceCodePro-Semibold'), url(../../fonts/SourceCodePro-Semibold-600-latin-ext.woff2) format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 600; src: local("Source Code Pro Semibold"), local("SourceCodePro-Semibold"), url(../../fonts/SourceCodePro-Semibold-600-latin.woff2) format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } .cm-s-default .cm-header { font-size: 18px; } .cm-s-default .cm-header.cm-level1 { font-size: 19px; } @media only screen and (max-width: 600px) { .CodeMirror { font-size: 14px; } .cm-s-default .cm-header { font-size: 15px; } .cm-s-default .cm-header.cm-level1 { font-size: 16px; } } /* Make things more confy */ .CodeMirror .CodeMirror-code { line-height: 1.3em; } .CodeMirror .CodeMirror-code > div { clear: both; } .CodeMirror[mode="orgmode"] .CodeMirror-code { line-height: 1.5em; } .CodeMirror .CodeMirror-line { padding-left: 10%; padding-right: 0; max-width: 950px; } @media screen and (max-width: 1150px) { .CodeMirror .CodeMirror-line { padding-left: 8%; padding-right: 0; } } @media screen and (max-width: 900px) { .CodeMirror .CodeMirror-line { padding-left: 5%; padding-right: 0; } } @media screen and (max-width: 800px) { .CodeMirror .CodeMirror-line { padding-left: 2%; padding-right: 0; } } .CodeMirror .CodeMirror-linenumber { color: var(--color); opacity: 0.6; } .CodeMirror .CodeMirror-gutters { box-shadow: none; background-color: inherit; border-right: none; } /* Widget stuff */ .CodeMirror-linewidget img { cursor: pointer; margin: 10px; height: 300px; max-width: 80%; text-align: center; box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5); background: var(--dark); } /* Code Highlight Theme */ .cm-s-default .cm-header { color: #3E7AA6; line-height: 1em; font-weight: 600; } .cm-header.cm-level1 { color: #376e95; } .cm-s-default .cm-keyword { color: var(--emphasis-secondary); } .cm-s-default .cm-header.cm-org-level-star { color: #6f6f6f; vertical-align: baseline; display: inline-block; padding-left: 5px; margin-left: -5px; cursor: pointer; } .cm-s-default .cm-header.cm-org-todo { color: #FF8355; font-weight: normal; cursor: pointer; } .cm-s-default .cm-header.cm-org-done { color: #3BB27C; font-weight: normal; cursor: pointer; } .cm-s-default .cm-header.cm-org-priority { cursor: pointer; font-weight: normal; } .cm-s-default .cm-org-toggle { cursor: pointer; background: var(--light); color: var(--super-light); border-radius: 3px; font-weight: bold; padding-bottom: 2px; vertical-align: text-bottom; opacity: 0.7; } .cm-s-default .cm-void { display: inline-block; max-width: 10px; overflow: hidden; white-space: nowrap; } .cm-s-default .cm-header.cm-comment { font-weight: normal; font-size: 0.9em !important; float: right; display: inline-block; color: var(--emphasis); line-height: 23px; } pre.CodeMirror-line { clear: right; } .cm-s-default .cm-link { color: var(--emphasis); } .cm-s-default .cm-strong { color: var(--emphasis); } .cm-s-default .cm-org-url, .cm-s-default .cm-org-image { color: var(--emphasis) !important; border-bottom: 1px dashed var(--light); cursor: pointer; } .cm-s-default .cm-variable-3 { color: #085; } .cm-s-default .cm-comment { color: var(--light); } .cm-s-default .cm-string, .cm-s-default .cm-string-2 { color: #c41a16; } .cm-s-default .cm-def { color: #445588; } .cm-s-default .cm-quote { color: var(--dark); } .cm-s-default .cm-invalidchar { color: var(--error); } .CodeMirror-gutters { box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.1); } .CodeMirror-foldmarker { padding-left: 5px; color: var(--color)!important; text-shadow: 1px 1px 10px var(--color)!important; } span.CodeMirror-matchingbracket, span.CodeMirror-matchingtag { background: rgba(0, 0, 0, 0.1); color: inherit !important; } /* BUGFIX */ .CodeMirror-cursor { min-width: 1px !important; } /* DAR MODE THEME */ .dark-mode .CodeMirror { background: #232426; } .dark-mode .CodeMirror, .dark-mode .CodeMirror .CodeMirror-linenumber, .dark-mode .CodeMirror .cm-variable-2, .dark-mode .CodeMirror .cm-variable-3, .dark-mode .CodeMirror .cm-number, .dark-mode .CodeMirror .cm-quote { color: #f6f3e8; } .dark-mode .CodeMirror .cm-string, .dark-mode .CodeMirror .cm-string-2 { color: #95e454; } .dark-mode .CodeMirror .cm-atom { color: #e5786d; } .dark-mode .CodeMirror .cm-keyword, .dark-mode .CodeMirror .cm-meta, .dark-mode .CodeMirror .cm-header, .dark-mode .CodeMirror .cm-property { color: #8ac6f2; } .dark-mode .CodeMirror .cm-def, .dark-mode .CodeMirror .cm-tag, .dark-mode .CodeMirror .cm-attribute, .dark-mode .CodeMirror .cm-builtin, .dark-mode .CodeMirror .cm-qualifier { color: #cae682; } .dark-mode .CodeMirror .cm-comment { color: #99968b; } .dark-mode .CodeMirror .CodeMirror-cursor { background-color: #99968b; } .dark-mode .CodeMirror .CodeMirror-selected { background-color: rgba(0, 0, 0, 0.3); } .dark-mode .CodeMirror .cm-error { color: var(--error); } .dark-mode .CodeMirror .cm-org-url, .dark-mode .CodeMirror .cm-link, .dark-mode .CodeMirror .cm-org-image, .dark-mode .CodeMirror .cm-url { color: #ccaa8f !important; } ================================================ FILE: public/assets/pages/viewerpage/application_editor.d.ts ================================================ interface Window { CodeMirror: { (element: HTMLElement, options: any): any; __mode: string; commands: { save: (editor: any) => void; }; }; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_editor.js ================================================ import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { animate, slideXIn, opacityOut } from "../../lib/animate.js"; import { qs, safe } from "../../lib/dom.js"; import { get as getConfig } from "../../model/config.js"; import { load as loadPlugin } from "../../model/plugin.js"; import { createLoader } from "../../components/loader.js"; import { createModal, MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import ajax from "../../lib/ajax.js"; import { extname } from "../../lib/path.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import ctrlDownloader, { init as initDownloader } from "./application_downloader.js"; import { $ICON } from "./common_fab.js"; import { cat, save } from "./model_files.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; import "../../components/fab.js"; import "../../components/icon.js"; const TIME_BEFORE_ABORT_EDIT = 5000; class IEditor {} export default async function(render, { acl$, getFilename, getDownloadUrl, mime }) { const $page = createElement(` <div class="component_ide"> <component-menubar filename="${safe(getFilename())}" class="hidden"></component-menubar> <div class="component_editor hidden"></div> <button is="component-fab" class="hidden"></button> </div> `); const $dom = { editor: () => qs($page, ".component_editor"), menubar: () => qs($page, "component-menubar"), fab: () => qs($page, `[is="component-fab"]`), }; render($page); renderMenubar($dom.menubar(), buttonDownload(getFilename(), getDownloadUrl())); const content$ = new rxjs.ReplaySubject(1); // feature1: setup the dom const removeLoader = createLoader($page); const setup$ = rxjs.zip( rxjs.race( // when a download takes too long, abort, we don't want to spin for 2 hours // only to find out the user tried to open something that don't even fit in // memory. This also account for terrible network conditions when out in // the bush; we abort after: // TIME_BEFORE_ABORT_EDIT + NETWORK LATENCY seconds cat(getDownloadUrl()), rxjs.of(null).pipe( rxjs.delay(TIME_BEFORE_ABORT_EDIT), rxjs.mergeMap(() => ajax("about")), rxjs.mapTo(null), ), ), acl$, ).pipe( rxjs.mergeMap(([content, acl]) => { if (content === null || has_binary(content)) return rxjs.from(initDownloader()).pipe( removeLoader, rxjs.mergeMap(() => { ctrlDownloader(render, { acl$, getFilename, getDownloadUrl }); return rxjs.EMPTY; }), ); return rxjs.of(content).pipe( rxjs.mergeMap((content) => rxjs.of(getConfig()).pipe( rxjs.mergeMap((config) => rxjs.from(loadKeybinding(config.editor)).pipe(rxjs.mapTo(config))), rxjs.map((config) => [content, config]), rxjs.mergeMap((arr) => rxjs.from(loadMode(extname(getFilename()))).pipe( rxjs.map((mode) => arr.concat([mode])), )), )), removeLoader, rxjs.map(([content, config, mode]) => { const $editor = $dom.editor(); content$.next(content); $editor.classList.remove("hidden"); const editor = window.CodeMirror($editor, { value: content, lineNumbers: true, mode: window.CodeMirror.__mode, keyMap: ["emacs", "vim"].indexOf(config["editor"]) === -1 ? "sublime" : config["editor"], lineWrapping: true, readOnly: acl.indexOf("PUT") === -1 && acl.indexOf("POST") === -1, foldOptions: { widget: "..." }, matchBrackets: {}, autoCloseBrackets: true, matchTags: { bothTags: true }, autoCloseTags: true, }); editor.getWrapperElement().setAttribute("mode", mode); if (!("ontouchstart" in window)) editor.focus(); if (config["editor"] === "emacs") editor.addKeyMap({ "Ctrl-X Ctrl-C": () => window.history.back(), }); if (mode === "orgmode") { const cleanup = window.CodeMirror.orgmode.init(editor); onDestroy(cleanup); } onDestroy(() => editor.clearHistory()); $dom.menubar().classList.remove("hidden"); return editor; }), rxjs.tap((editor) => requestAnimationFrame(() => editor.refresh())), ); }), rxjs.mergeMap(async(editor) => { const loader = await loadPlugin(mime); if (loader) new (await loader(IEditor, { mime, $menubar: $dom.menubar(), getFilename, getDownloadUrl }))(editor); return editor; }), rxjs.catchError(ctrlError()), rxjs.share(), ); effect(setup$); // feature2: handle resize effect(setup$.pipe( rxjs.mergeMap((editor) => rxjs.fromEvent(window, "resize").pipe( rxjs.tap(() => editor.refresh()), )), )); // feature3: handle UI for edit effect(setup$.pipe( rxjs.switchMap((editor) => new rxjs.Observable((observer) => editor.on("change", (cm) => observer.next(cm)))), rxjs.mergeMap((editor) => content$.pipe(rxjs.map((oldContent) => [editor, editor.getValue(), oldContent]))), rxjs.tap(async([editor, newContent = "", oldContent = ""]) => { const $fab = $dom.fab(); if ($fab.disabled) return; const $breadcrumb = qs(document.body, "component-breadcrumb"); if (newContent === oldContent) { await animate($fab, { time: 100, keyframes: opacityOut() }); $fab.classList.add("hidden"); $breadcrumb.removeAttribute("indicator"); return; } $breadcrumb.setAttribute("indicator", "true"); const shouldAnimate = $fab.classList.contains("hidden"); $fab.classList.remove("hidden"); $fab.render($ICON.SAVING); $fab.onclick = () => window.CodeMirror.commands.save(editor); if (shouldAnimate) await animate($fab, { time: 100, keyframes: slideXIn(40) }); }), )); // feature4: save effect(setup$.pipe( rxjs.mergeMap(() => new rxjs.Observable((observer) => { window.CodeMirror.commands.save = (cm) => observer.next(cm); })), rxjs.mergeMap((cm) => { const $fab = $dom.fab(); $fab.classList.remove("hidden"); $fab.render($ICON.LOADING); $fab.disabled = true; const content = cm.getValue(); return save(content).pipe(rxjs.tap(() => { $fab.removeAttribute("disabled"); content$.next(content); })); }), rxjs.catchError(ctrlError()), )); // feature5: save on exit effect(setup$.pipe( rxjs.mergeMap((cm) => new Promise((resolve, reject) => window.history.block = async() => { const block = qs(document.body, "component-breadcrumb").hasAttribute("indicator"); if (block === false) return false; const userAction = await new Promise((done) => { createModal({ withButtonsRight: t("Yes"), withButtonsLeft: t("No"), })( createElement(` <div style="text-align:center;padding-bottom:5px;"> ${t("Do you want to save the changes ?")} </div> `), (val) => done(val), ); }); if (userAction === MODAL_RIGHT_BUTTON) { const $fab = $dom.fab(); $fab.render($ICON.LOADING); $fab.disabled = true; try { await save(cm.getValue()).toPromise(); resolve(); } catch (err) { reject(err); return true; } } return false; })), rxjs.catchError(ctrlError()), )); } function has_binary(str) { let countUnrepresentableChar = 0; for (let i=0; i<str.length; i++) { if (countUnrepresentableChar > 2) return true; else if (str[i] === "\ufffd") countUnrepresentableChar += 1; } return false; } export function init() { return Promise.all([ loadCSS(import.meta.url, "../../lib/vendor/codemirror/lib/codemirror.css"), loadJS(import.meta.url, "../../lib/vendor/codemirror/lib/codemirror.js"), loadCSS(import.meta.url, "./application_editor.css"), ]).then(() => Promise.all([ loadJS(import.meta.url, "../../lib/vendor/codemirror/keymap/emacs.js"), // search loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/search/searchcursor.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/search/search.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/comment/comment.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/dialog/dialog.js"), // folding loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldcode.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldgutter.js"), loadCSS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/foldgutter.css"), // editing feature loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchbrackets.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/closebrackets.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/closetag.js"), ])); } function loadMode(ext) { let mode = "text"; let before = Promise.resolve(null); if (ext === "org" || ext === "org_archive") { mode = "orgmode"; before = Promise.all([ loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/simple.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/fold/xml-fold.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/edit/matchtags.js"), ]).then(() => Promise.resolve(null)); } else if (ext === "sh") mode = "shell"; else if (ext === "py") mode = "python"; else if (ext === "html" || ext === "htm") { mode = "htmlmixed"; before = Promise.all([ loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/xml/xml.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/javascript/javascript.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/css/css.js"), ]).then(() => Promise.resolve(null)); } else if (ext === "css") mode = "css"; else if (ext === "less" || ext === "scss" || ext === "sass") mode = "sass"; else if (ext === "js" || ext === "json") mode = "javascript"; else if (ext === "jsx") mode = "jsx"; else if (ext === "php" || ext === "php5" || ext === "php4") { mode = "php"; before = Promise.all([ loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/xml/xml.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/javascript/javascript.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/css/css.js"), ]).then(() => Promise.resolve(null)); } else if (ext === "elm") mode = "elm"; else if (ext === "erl") mode = "erlang"; else if (ext === "go") mode = "go"; else if (ext === "groovy") mode = "groovy"; else if (ext === "markdown" || ext === "md") { mode = "yaml-frontmatter"; before = Promise.all([ loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/markdown/markdown.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/gfm/gfm.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/mode/yaml/yaml.js"), loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/overlay.js"), ]).then(() => Promise.resolve(null)); } else if (ext === "pl" || ext === "pm") mode = "perl"; else if (ext === "clj") mode = "clojure"; else if (ext === "el" || ext === "lisp" || ext === "cl" || ext === "emacs") mode = "commonlisp"; else if (ext === "dockerfile") { mode = "dockerfile"; before = loadJS(import.meta.url, "../../lib/vendor/codemirror/addon/mode/simple.js").then(() => Promise.resolve(null)); } else if (ext === "R") mode = "r"; else if (ext === "makefile") mode = "cmake"; else if (ext === "rb") mode = "ruby"; else if (ext === "sql") mode = "sql"; else if (ext === "xml" || ext === "rss" || ext === "svg" || ext === "atom") mode = "xml"; else if (ext === "yml" || ext === "yaml") mode = "yaml"; else if (ext === "lua") mode = "lua"; else if (ext === "csv") mode = "spreadsheet"; else if (ext === "rs" || ext === "rlib") mode = "rust"; else if (ext === "latex" || ext === "tex") mode = "stex"; else if (ext === "diff" || ext === "patch") mode = "diff"; else if (ext === "sparql") mode = "sparql"; else if (ext === "properties") mode = "properties"; else if (ext === "c" || ext === "cpp" || ext === "h") mode = "clike"; else if (ext === "java") mode = "java"; return before.then(() => loadJS(import.meta.url, `./application_editor/${mode}.js`, { type: "module" })) .catch(() => loadJS(import.meta.url, "./application_editor/text.js", { type: "module" })) .then(() => Promise.resolve(mode)); } function loadKeybinding(editor) { if (editor === "emacs" || !editor) { return Promise.resolve(); } return loadJS(import.meta.url, `./application_editor/keymap_${editor}.js`, { type: "module" }); } ================================================ FILE: public/assets/pages/viewerpage/application_editor_orgmode.js ================================================ export default function(editor) { window.CodeMirror.orgmode.init(editor, (key, value) => { if (key === "shifttab") { // org_shifttab(this.state.editor) // this.props.onFoldChange(value); } }); } ================================================ FILE: public/assets/pages/viewerpage/application_form.css ================================================ .component_formviewer { background: var(--surface); } .dark-mode .component_formviewer { color: rgba(0,0,0,0.5); } .component_formviewer > .formviewer_container { padding-top: 20px; padding-bottom: 50px; overflow-y: auto; flex-direction: column; flex: 1; width: 100%; } .component_formviewer > .formviewer_container .box { padding: 25px 20px; box-sizing: border-box; border-radius: 5px; background: #fff; box-shadow: rgba(0,0,0,.1) 0px 4px 5px 0px; width: 98%; max-width: 800px; margin: 0 auto; } .component_formviewer > .formviewer_container .box .formbuilder { margin-bottom: 10px; } .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div { display: flex; line-height: 30px; } .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > div { width: 100%; } @media screen and (max-width: 470px) { .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div { display: block; line-height: inherit; } .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div span.nothing { position: absolute; } } .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span { display: inline-block; width: 160px; max-width: 160px; text-align: right; padding-right: 15px; color: var(--dark); } @media screen and (max-width: 470px) { .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span { text-align: left; font-weight: bold; } } .component_formviewer > .formviewer_container .box .formbuilder label.no-select > div > span span.mandatory { opacity: 0.6; } .component_formviewer .formbuilder .component_input[disabled], .component_formviewer .formbuilder .component_input[readonly], .component_formviewer .formbuilder .component_textarea[disabled], .component_formviewer .formbuilder .component_textarea[readonly], .component_formviewer .formbuilder .component_select[disabled], .component_formviewer .formbuilder .component_select[readonly] { background: rgba(0, 0, 0, 0.05); border-radius: 2px; padding-left: 7px; border-color: transparent; } .component_formviewer fieldset { margin-bottom: 10px; border: 2px solid var(--border); border-radius: 5px; color: var(--light); } ================================================ FILE: public/assets/pages/viewerpage/application_form.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, applyMutation, onClick } from "../../lib/rx.js"; import { animate, slideXIn, opacityOut } from "../../lib/animate.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { loadCSS } from "../../helpers/loader.js"; import { createForm, mutateForm } from "../../lib/form.js"; import { formTmpl, $renderInput } from "../../components/form.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import { transition } from "./common.js"; import { $ICON } from "./common_fab.js"; import { cat, save } from "./model_files.js"; import "./component_menubar.js"; import "../../components/icon.js"; import "../../components/fab.js"; export default function(render, { acl$, getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_formviewer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div class="formviewer_container hidden"> <form class="sticky box"></form> </div> <button is="component-fab" data-options="download"></button> </div> `); render($page); const $container = qs($page, ".formviewer_container"); const $fab = qs($page, `[is="component-fab"]`); const formState = () => { const $form = qs($page, "form"); const fd = new FormData($form); const json = [...fd].reduce((acc, el) => { acc[el[0]] = el[1]; return acc; }, {}); $form.querySelectorAll("input[type=\"checkbox\"][name]").forEach((cb) => { if (fd.has(cb.name)) json[cb.name] = true; else json[cb.name] = false; }); return json; }; const file$ = new rxjs.ReplaySubject(1); // feature1: setup the dom const removeLoader = createLoader($page); effect(cat(getDownloadUrl()).pipe( rxjs.map((content) => JSON.parse(content)), rxjs.mergeMap((formSpec) => acl$.pipe(rxjs.map((acl) => { if (acl.indexOf("POST") === -1) { return readOnlyForm(formSpec); } return formSpec; }))), rxjs.mergeMap((formSpec) => rxjs.from(createForm(formSpec, formTmpl({ renderInput: (opts) => { const $el = $renderInput({ autocomplete: true })(opts); if ($el.hasAttribute("disabled")) { $el.removeAttribute("disabled"); $el.setAttribute("readonly", "true"); } return $el; }, renderLeaf: ({ label, format, description, required }) => label === "banner" ? createElement(` <div class="banner"> ${fromMarkdown(safe(description))} </div> `) : createElement(` <label class="no-select"> <div> <span class="ellipsis"> ${safe(format(label))} ` + (required === true ? `<span class="mandatory">*</span>`: "") +` </span> <div data-bind="children"></div> </div> <div> <span class="nothing"></span> <div class="description">${fromMarkdown(safe(description))}</div> </div> </label> `), }))).pipe( removeLoader, applyMutation(qs($page, "form"), "replaceChildren"), rxjs.tap(() => { $container.classList.remove("hidden"); transition($container); }), rxjs.mapTo(formSpec), )), rxjs.tap((formSpec) => file$.next(formSpec)), rxjs.catchError(ctrlError()), )); // feature2: display/hide save button effect(rxjs.merge( file$.asObservable(), file$.pipe( rxjs.first(), rxjs.mergeMap((formSpec) => rxjs.from(qsa($page, "form [name]")).pipe( rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")), rxjs.mapTo(formSpec), )), ), ).pipe( rxjs.map((originalState) => { const smod = (_, value) => value || undefined; return JSON.stringify(formObjToJSON(originalState), smod) !== JSON.stringify(formState(), smod); }), rxjs.mergeMap(async(isSaveButtonVisible) => { if (isSaveButtonVisible) { if ($fab.classList.contains("hidden")) await animate($fab, { time: 100, keyframes: slideXIn(40) }); $fab.render($ICON.SAVING); $fab.classList.remove("hidden"); } else if (!isSaveButtonVisible) { if (!$fab.classList.contains("hidden")) await animate($fab, { time: 100, keyframes: opacityOut() }); $fab.classList.add("hidden"); } }), rxjs.catchError(ctrlError()), )); // feature3: submit the form effect(onClick($fab).pipe( rxjs.tap(() => { $fab.render($ICON.LOADING); $fab.disabled = true; }), rxjs.mergeMap(() => file$.pipe( rxjs.first(), rxjs.map((formSpec) => mutateForm(formSpec, formState())), rxjs.mergeMap((formSpec) => save(formSpec).pipe( rxjs.tap(() => file$.next(formSpec)), )), )), rxjs.tap(() => { $fab.render($ICON.SAVING); $fab.removeAttribute("disabled"); $fab.classList.add("hidden"); }), rxjs.catchError(ctrlError()), )); } export function init() { return loadCSS(import.meta.url, "./application_form.css"); } const formObjToJSON = (o, level = 0) => { const obj = Object.assign({}, o); Object.keys(obj).forEach((key) => { const t = obj[key]; if (typeof t !== "object") throw new Error("MALFORMED FORM"); if ("label" in t && "type" in t && "default" in t && "value" in t) { obj[key] = obj[key].value; } else { obj[key] = formObjToJSON(obj[key], level + 1); } }); return obj; }; function readOnlyForm(formSpec) { if ("type" in formSpec) { formSpec["readonly"] = true; return formSpec; } for (const key in formSpec) { formSpec[key] = readOnlyForm(formSpec[key]); } return formSpec; } function fromMarkdown(str = "") { str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>"); str = str.replaceAll("\n", "<br>"); return str; } ================================================ FILE: public/assets/pages/viewerpage/application_iframe.css ================================================ body:not(.dark-mode) .component_appframe { background: var(--surface); } .component_appframe { text-align: center; width: 100%; } .component_appframe iframe { width: 100%; height: 100%; border: none; } .component_appframe .error { color: white; font-size: 17px; margin-top: 10px; font-family: monospace; } ================================================ FILE: public/assets/pages/viewerpage/application_iframe.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { forwardURLParams } from "../../lib/path.js"; import { loadCSS } from "../../helpers/loader.js"; import notification from "../../components/notification.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import { getCurrentPath } from "./common.js"; export default function(render, { endpoint = "" }) { const url = forwardURLParams(`${endpoint}?path=${encodeURIComponent(getCurrentPath())}`, ["share"]); const $page = createElement(` <div class="component_appframe"> <iframe style="width:100%;height:100%" src="${url}" scrolling="no"></iframe> </div> `); render($page); effect(rxjs.fromEvent(window, "message").pipe( rxjs.filter((event) => event.origin === location.origin), rxjs.map((event) => JSON.parse(event.data)), rxjs.tap(({ type, msg }) => { switch (type) { case "error": throw new Error(msg); case "notify::error": notification.error(t(msg)); break; case "notify::info": notification.info(t(msg)); break; case "notify::success": notification.success(t(msg)); break; default: break; } }), rxjs.catchError((err) => { notification.error(t(err.message)); return ctrlError()(err); }), )); } export function init() { return loadCSS(import.meta.url, "./application_iframe.css"); } ================================================ FILE: public/assets/pages/viewerpage/application_image/information.css ================================================ .component_imageviewer .images_aside button { padding: 0; } .component_imageviewer .images_aside component-icon[name="loading"] { display: block; text-align: center; padding-top: 25px; } .component_imageviewer .images_aside component-icon[name="loading"] img { height: 30px; } .component_mapshot { overflow: hidden; width: 100%; max-width: 100%; background: rgba(255, 255, 255, 0.3); border-radius: 3px; } .component_mapshot .wrapper { position: relative; } .component_mapshot .wrapper .marker { position: absolute; z-index: 1; } .component_mapshot .wrapper .marker img { width: 30px; height: 30px; } .component_mapshot .wrapper:hover .bigpicture { transition: 0.4s ease transform; transform: scale(3); } .component_mapshot .wrapper .bigpicture { transition: 0.2s ease transform; } .component_mapshot .wrapper .bigpicture .line { display: flex; width: 33.33%; } .component_mapshot .wrapper .bigpicture .line img { filter: grayscale(90%); } .component_mapshot .wrapper .bigpicture .line .btl { border-top-left-radius: 2px; } .component_mapshot .wrapper .bigpicture .line .btr { border-top-right-radius: 2px; } .component_mapshot .wrapper .bigpicture .line .bbl { border-bottom-left-radius: 2px; } .component_mapshot .wrapper .bigpicture .line .bbr { border-bottom-right-radius: 2px; } .component_mapshot .wrapper .mapshot_placeholder { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 2; border-radius: 2px; background: var(--color); } .component_mapshot .wrapper .mapshot_placeholder span { display: flex; height: 100%; width: 100%; } .component_mapshot .wrapper .mapshot_placeholder span .component_loader, .component_mapshot .wrapper .mapshot_placeholder span div { margin: auto; color: var(--light); } .component_mapshot .wrapper .mapshot_placeholder span .component_loader svg, .component_mapshot .wrapper .mapshot_placeholder span div svg { height: 50px; } .component_mapshot.loaded .mapshot_placeholder { display: none; } .component_mapshot.error .mapshot_placeholder.loading { display: none; } ================================================ FILE: public/assets/pages/viewerpage/application_image/information.js ================================================ import { createElement, createRender } from "../../../lib/skeleton/index.js"; import rxjs, { effect, onClick } from "../../../lib/rx.js"; import { qs } from "../../../lib/dom.js"; import t from "../../../locales/index.js"; import { loadJS, loadCSS } from "../../../helpers/loader.js"; export default async function(render, { toggle, load$ }) { const $page = createElement(` <div id="pane-info"> <div data-bind="header"></div> <div data-bind="body"> <component-icon name="loading"></component-icon> </div> </div> `); render($page); componentHeader(createRender(qs($page, `[data-bind="header"]`)), { toggle }); componentBody(createRender(qs($page, `[data-bind="body"]`)), { load$ }); } function componentHeader(render, { toggle }) { const $header = createElement(` <div class="header"> <div>${t("Info")}</div> <button style="flex: 1 1 0%;" aria-label="${t("close")}"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MS45NzYgNTEuOTc2Ij4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjUzMzMzMjg1O3N0cm9rZS13aWR0aDoxLjQ1NjgxMTE5IiBkPSJtIDQxLjAwNTMxLDQwLjg0NDA2MiBjIC0xLjEzNzc2OCwxLjEzNzc2NSAtMi45ODIwODgsMS4xMzc3NjUgLTQuMTE5ODYxLDAgTCAyNi4wNjg2MjgsMzAuMDI3MjM0IDE0LjczNzU1MSw0MS4zNTgzMSBjIC0xLjEzNzc3MSwxLjEzNzc3MSAtMi45ODIwOTMsMS4xMzc3NzEgLTQuMTE5ODYxLDAgLTEuMTM3NzcyMiwtMS4xMzc3NjggLTEuMTM3NzcyMiwtMi45ODIwODggMCwtNC4xMTk4NjEgTCAyMS45NDg3NjYsMjUuOTA3MzcyIDExLjEzMTkzOCwxNS4wOTA1NTEgYyAtMS4xMzc3NjQ3LC0xLjEzNzc3MSAtMS4xMzc3NjQ3LC0yLjk4MzU1MyAwLC00LjExOTg2MSAxLjEzNzc3NCwtMS4xMzc3NzIxIDIuOTgyMDk4LC0xLjEzNzc3MjEgNC4xMTk4NjUsMCBMIDI2LjA2ODYyOCwyMS43ODc1MTIgMzYuMzY5NzM5LDExLjQ4NjM5OSBjIDEuMTM3NzY4LC0xLjEzNzc2OCAyLjk4MjA5MywtMS4xMzc3NjggNC4xMTk4NjIsMCAxLjEzNzc2NywxLjEzNzc2OSAxLjEzNzc2NywyLjk4MjA5NCAwLDQuMTE5ODYyIEwgMzAuMTg4NDg5LDI1LjkwNzM3MiA0MS4wMDUzMSwzNi43MjQxOTcgYyAxLjEzNzc3MSwxLjEzNzc2NyAxLjEzNzc3MSwyLjk4MjA5MSAwLDQuMTE5ODY1IHoiIC8+Cjwvc3ZnPgo=" alt="close"> </button> </div> `); render($header); effect(onClick(qs($header, `[alt="close"]`)).pipe(rxjs.tap(toggle))); } function componentBody(render, { load$ }) { const $page = createElement(` <div> <div class="content_box"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjIiIGQ9Ik0gNDAwLDY0IEggMzUyIFYgMTIgQyAzNTIsNS40IDM0Ni42LDAgMzQwLDAgaCAtNDAgYyAtNi42LDAgLTEyLDUuNCAtMTIsMTIgViA2NCBIIDE2MCBWIDEyIEMgMTYwLDUuNCAxNTQuNiwwIDE0OCwwIEggMTA4IEMgMTAxLjQsMCA5Niw1LjQgOTYsMTIgViA2NCBIIDQ4IEMgMjEuNSw2NCAwLDg1LjUgMCwxMTIgdiAzNTIgYyAwLDI2LjUgMjEuNSw0OCA0OCw0OCBoIDM1MiBjIDI2LjUsMCA0OCwtMjEuNSA0OCwtNDggViAxMTIgQyA0NDgsODUuNSA0MjYuNSw2NCA0MDAsNjQgWiBtIC0yLDQwNCBIIDUwIGMgLTMuMywwIC02LjAyMjE0NywtMi43MDAwNyAtNiwtNiBWIDE1NCBoIDM2MCB2IDMwOCBjIDAsMy4zIC0yLjcsNiAtNiw2IHoiIC8+Cjwvc3ZnPgo=" alt="schedule"> <div class="headline ellipsis" data-bind="date">-</div> <div class="description ellipsis" data-bind="time">-</div> </div> <div class="content_box"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eTowLjIiIGQ9Ik01MTIgMTQ0djI4OGMwIDI2LjUtMjEuNSA0OC00OCA0OEg0OGMtMjYuNSAwLTQ4LTIxLjUtNDgtNDhWMTQ0YzAtMjYuNSAyMS41LTQ4IDQ4LTQ4aDg4bDEyLjMtMzIuOWM3LTE4LjcgMjQuOS0zMS4xIDQ0LjktMzEuMWgxMjUuNWMyMCAwIDM3LjkgMTIuNCA0NC45IDMxLjFMMzc2IDk2aDg4YzI2LjUgMCA0OCAyMS41IDQ4IDQ4ek0zNzYgMjg4YzAtNjYuMi01My44LTEyMC0xMjAtMTIwcy0xMjAgNTMuOC0xMjAgMTIwIDUzLjggMTIwIDEyMCAxMjAgMTIwLTUzLjggMTIwLTEyMHptLTMyIDBjMCA0OC41LTM5LjUgODgtODggODhzLTg4LTM5LjUtODgtODggMzkuNS04OCA4OC04OCA4OCAzOS41IDg4IDg4eiIgLz4KPC9zdmc+Cg==" alt="camera"> <div class="headline ellipsis" data-bind="camera-setting">-</div> <div class="description ellipsis" data-bind="camera-name">-</div> </div> <div data-bind="map"></div> <div data-bind="all"> <component-icon name="loading"></component-icon> </div> </div> `); render($page); effect(load$.pipe(rxjs.tap(async($img) => { if (!$img) return; const metadata = await extractExif($img); qs($page, `[data-bind="date"]`).innerText = formatDate(metadata.date) || "-"; qs($page, `[data-bind="time"]`).innerText = formatTime(metadata.date) || "-"; qs($page, `[data-bind="camera-setting"]`).innerText = formatCameraSettings(metadata) || "-"; qs($page, `[data-bind="camera-name"]`).innerText = formatCameraName(metadata) || "-"; if (metadata.location) await componentMap(createRender(qs($page, `[data-bind="map"]`)), { metadata }); componentMore(createRender(qs($page, `[data-bind="all"]`)), { metadata }); }), rxjs.catchError((err) => { qs($page, `[data-bind="all"]`).remove(); return rxjs.EMPTY; }))); } async function componentMap(render, { metadata }) { const DMSToDD = (d) => { if (!d || d.length !== 4) return null; const [degrees, minutes, seconds, direction] = d; const dd = degrees + minutes/60 + seconds/(60*60); return direction === "S" || direction === "W" ? -dd : dd; }; const lat = DMSToDD(metadata.location[0]); const lng = DMSToDD(metadata.location[1]); const $page = createElement(` <div class="component_mapshot error"> <div class="wrapper"> <div class="mapshot_placeholder error hidden"> <span><div>Erreur</div></span> </div> <div class="mapshot_placeholder loading hidden"> <div class="loader"> <div class="component_loader"> <svg width="120px" height="120px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><rect x="0" y="0" width="100" height="100" fill="none"></rect><circle cx="50" cy="50" r="40" stroke="rgba(100%,100%,100%,0.679)" fill="none" stroke-width="10" stroke-linecap="round"></circle><circle cx="50" cy="50" r="40" stroke="#6f6f6f" fill="none" stroke-width="6" stroke-linecap="round"><animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="502"></animate><animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4"></animate></circle></svg> </div> </div> </div> <a href="https://www.google.com/maps/search/?api=1&query=${lat},${lng}"> <div class="marker"><img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzMxMzUzODtmaWxsLW9wYWNpdHk6MSIgZD0iTTgsMEM0LjY4NywwLDIsMi42ODcsMiw2YzAsMy44NTQsNC4zMjEsOC42NjMsNSw5LjM5OEM3LjI4MSwxNS43MDMsNy41MTYsMTYsOCwxNnMwLjcxOS0wLjI5NywxLTAuNjAyICBDOS42NzksMTQuNjYzLDE0LDkuODU0LDE0LDZDMTQsMi42ODcsMTEuMzEzLDAsOCwweiBNOCwxMGMtMi4yMDksMC00LTEuNzkxLTQtNHMxLjc5MS00LDQtNHM0LDEuNzkxLDQsNFMxMC4yMDksMTAsOCwxMHogTTgsNCAgQzYuODk2LDQsNiw0Ljg5Niw2LDZzMC44OTYsMiwyLDJzMi0wLjg5NiwyLTJTOS4xMDQsNCw4LDR6IiAvPgo8L3N2Zz4K" alt="location"></div> <div data-bind="maptile"></div> </a> </div> </div> `); render($page); await new Promise((resolve) => setTimeout(resolve, 500)); const TILE_SERVER = "https://tile.openstreetmap.org/${z}/${x}/${y}.png"; const TILE_SIZE = Math.floor($page.clientWidth / 3 * 100) / 100; if (TILE_SIZE === 0) return; $page.style.height = "${TILE_SIZE*3}px;"; const mapper = (function map_url(lat, lng, zoom) { // https://wiki.openstreetmap.org/wiki/Slippy_map_tilenamse const n = Math.pow(2, zoom); const tile_numbers = [ (lng+180)/360*n, (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2*n, zoom, ]; return { tile: function(tile_server, x = 0, y = 0) { return tile_server .replace("${x}", Math.floor(tile_numbers[0] || 0)+x) .replace("${y}", Math.floor(tile_numbers[1] || 0)+y) .replace("${z}", Math.floor(zoom)); }, position: function() { return [ tile_numbers[0] - Math.floor(tile_numbers[0]), tile_numbers[1] - Math.floor(tile_numbers[1]), ]; }, }; }(lat, lng, 11)); const $tiles = createElement(` <div class="bigpicture"> <div class="line"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, -1, -1)}" class="btl" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 0, -1)}" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 1, -1)}" class="btr" style="height: ${TILE_SIZE}px;"> </div> <div class="line"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, -1, 0)}" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 0, 0)}" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 1, 0)}" style="height: ${TILE_SIZE}px;"> </div> <div class="line"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, -1, 1)}" class="bbl" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 0, 1)}" style="height: ${TILE_SIZE}px;"> <img crossorigin="anonymous" src="${mapper.tile(TILE_SERVER, 1, 1)}" class="bbr" style="height: ${TILE_SIZE}px;"> </div> </div> `); qs($page, `[data-bind="maptile"]`).appendChild($tiles); const pos = mapper.position(); qs($page, ".marker").setAttribute("style", ` left: ${TILE_SIZE * (1 + pos[0]) - 15}px; top: ${TILE_SIZE * (1 + pos[1]) - 30}px; `); const center = (position, i) => Math.floor(TILE_SIZE * (1 + position[i]) * 1000)/1000; $tiles.setAttribute("style", `transform-origin: ${center(mapper.position(), 0)}px ${center(mapper.position(), 1)}px;`); } function componentMore(render, { metadata }) { const $all = document.createDocumentFragment(); const formatKey = (str) => str.replace(/([A-Z][a-z])/g, " $1"); const formatValue = (str) => { if (!metadata.all || metadata.all[str] === undefined) return "-"; if (typeof metadata.all[str] === "number") { return Math.floor(metadata.all[str]*100)/100; } else if (metadata.all[str].denominator !== undefined && metadata.all[str].numerator !== undefined) { if (metadata.all[str].denominator === 1) { return metadata.all[str].numerator; } else if (metadata.all[str].numerator > metadata.all[str].denominator) { return Math.floor( metadata.all[str].numerator * 10 / metadata.all[str].denominator, ) / 10; } else { return metadata.all[str].numerator+"/"+metadata.all[str].denominator; } } else if (typeof metadata.all[str] === "string") { return metadata.all[str]; } else if (Array.isArray(metadata.all[str])) { let arr = metadata.all[str]; if (arr.length > 15) { arr = arr.slice(0, 3); arr.push("..."); } return arr.toString().split(",").join(", "); } else { return JSON.stringify(metadata.all[str], null, 2); } }; Object.keys(metadata.all || {}).sort((a, b) => { if (a.toLowerCase().trim() < b.toLowerCase().trim()) return -1; else if (a.toLowerCase().trim() > b.toLowerCase().trim()) return +1; return 0; }).forEach((key) => { switch (key) { case "undefined": case "thumbnail": break; default: $all.appendChild(createElement(` <div class="meta_key"> <div class="title ellipsis">${formatKey(key)}: </div> <div class="value ellipsis" title="${formatValue(key)}">${formatValue(key)}</div> </div> `)); } }); render($all); } export function init() { return Promise.all([ loadJS(import.meta.url, "../../../lib/vendor/exif-js.js"), loadCSS(import.meta.url, "./information.css"), ]); } const extractExif = ($img) => new Promise((resolve) => window.EXIF.getData($img, function() { const metadata = window.EXIF.getAllTags($img); const to_date = (str = "") => { if (str === "") return null; const digits = str.split(/[ :]/).map((digit) => parseInt(digit)); return new Date( digits[0] || 0, digits[1] || 0, digits[2] || 0, digits[3] || 0, digits[4] || 0, digits[5] || 0, ); }; resolve({ date: to_date( metadata["DateTime"] || metadata["DateTimeDigitized"] || metadata["DateTimeOriginal"] || metadata["GPSDateStamp"], ), location: (metadata["GPSLatitude"] && metadata["GPSLongitude"] && [ [ metadata["GPSLatitude"][0], metadata["GPSLatitude"][1], metadata["GPSLatitude"][2], metadata["GPSLatitudeRef"], ], [ metadata["GPSLongitude"][0], metadata["GPSLongitude"][1], metadata["GPSLongitude"][2], metadata["GPSLongitudeRef"], ], ]) || null, maker: metadata["Make"] || null, model: metadata["Model"] || null, focal: metadata["FocalLength"] || null, aperture: metadata["FNumber"] || null, shutter: metadata["ExposureTime"] || null, iso: metadata["ISOSpeedRatings"] || null, dimension: (metadata["PixelXDimension"] && metadata["PixelYDimension"] && [ metadata["PixelXDimension"], metadata["PixelYDimension"], ]) || null, all: Object.keys(metadata).length === 0 ? null : metadata, }); })); const formatTime = (t) => t?.toLocaleTimeString( "en-us", { weekday: "short", hour: "2-digit", minute: "2-digit" }, ); const formatDate = (t) => t?.toLocaleDateString( navigator.language, { year: "numeric", month: "short", day: "numeric" }, ); const formatCameraSettings = (metadata) => { const str = format("model", metadata); const f = format("focal", metadata); if (!f) return str; return `${str} (${f})`; }; const formatCameraName = (metadata) => { return [ format("shutter", metadata), format("aperture", metadata), format("iso", metadata), ].join(" ").trim() || "-"; }; const format = (key, metadata) => { if (!metadata[key]) return ""; switch (key) { case "focal": return `${metadata.focal}mm`; case "iso": return `ISO${metadata.iso}`; case "aperture": return `ƒ${Math.floor(metadata.aperture*10)/10}`; case "shutter": if (metadata.shutter > 60) return metadata.shutter+"m"; else if (metadata.shutter > 1) return metadata.shutter+"s"; return `1/${Math.floor(metadata.shutter.denominator / metadata.shutter.numerator)}s`; case "dimension": if (metadata.dimension.length !== 2 || !metadata.dimension[0] || !metadata.dimension[1]) return "-"; return metadata.dimension[0]+"x"+metadata.dimension[1]; default: return metadata[key]; } }; ================================================ FILE: public/assets/pages/viewerpage/application_image/pagination.css ================================================ /* PAGINATION */ .component_pager { position: absolute; top: 0; bottom: 0; display: flex; opacity: 0.2; margin: auto; padding: 15px; color: #f2f2f2; z-index: 1; } .component_pager.left { left: 0; padding-right: 50px; } .component_pager.right { right: 0; padding-left: 50px; } .touch-yes .component_pager.left { display: none; } .touch-yes .component_pager.right { display: none; } .component_pager:hover { opacity: 0.9; transition: opacity 0.5s ease; } .component_pager a { margin: auto; } .component_pager a svg { transition: opacity 0.2s ease; width: 37px; padding: 8px; background: var(--dark); border-radius: 50%; margin: auto; opacity: 0.75; } .touch-no .component_pager a svg:hover { opacity: 1; box-shadow: 0px 0px 5px rgba(255, 255, 255, 0.05); } ================================================ FILE: public/assets/pages/viewerpage/application_image/pagination.js ================================================ import { createFragment } from "../../../lib/skeleton/index.js"; import rxjs, { effect } from "../../../lib/rx.js"; import { qs } from "../../../lib/dom.js"; import { join, forwardURLParams } from "../../../lib/path.js"; import { animate, slideXOut } from "../../../lib/animate.js"; import { loadCSS } from "../../../helpers/loader.js"; import { get as getConfig } from "../../../model/config.js"; import { getCurrentPath, getFilename } from "../common.js"; import { getMimeType } from "../mimetype.js"; import { createLink } from "../../filespage/ctrl_filesystem.js"; import fscache from "../../filespage/cache.js"; import { sort } from "../../filespage/helper.js"; import { getState$ as getParams$, init as initParams } from "../../filespage/state_config.js"; export default async function(render, { $img }) { if (window.self !== window.top) return; const lsCache = await fscache().get(join(location, getCurrentPath() + "/../")); if (!lsCache) return; const params = await getParams$().pipe(rxjs.first()).toPromise(); const state = { prev: null, curr: null, next: null, length: 0, }; const files = filterImages( sort(lsCache.files, params["sort"], params["order"]), state, ); if (state.length <= 1) return; const $page = createFragment(` <div class="component_pager left hidden"> <a data-link> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <polyline points="15 18 9 12 15 6"/> </svg> </a> </div> <div class="component_pager right hidden"> <a> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <polyline points="9 18 15 12 9 6"></polyline> </svg> </a> </div> `); if (state.prev !== null) updateDOM({ $el: $page.children[0], name: files[state.prev].name, $img, }); if (state.next !== null) updateDOM({ $el: $page.children[1], name: files[state.next].name, $img, }); const $navigation = render($page); initMobileNavigation({ $img, $navigation }); initKeyboardNavigation({ $img, $navigation }); } function filterImages(files, state) { const currentFilename = getFilename(); const mimeTypes = getConfig("mime", {}); for (let i=0; i<files.length; i++) { const filename = files[i].name; if (!getMimeType(filename, mimeTypes).startsWith("image/")) { continue; } state.length += 1; if (currentFilename === filename) { state.curr = i; } else if (state.curr === null) { state.prev = i; } else { state.next = i; break; } } return files; } function updateDOM({ $el, name, $img }) { const $link = qs($el, "a"); $link.onclick = async(e) => { if (e.target.hasAttribute("data-link")) return; e.preventDefault(); e.stopPropagation(); const sgn = $el.classList.contains("left") ? +1 : -1; await animate($img, { keyframes: slideXOut(sgn * 25), time: 100, easing: "ease-in", }); $link.setAttribute("data-link", "true"); $link.click(); }; const { link } = createLink({ name }, join(location, getCurrentPath() + "/../")); $link.setAttribute("href", forwardURLParams(link, ["share", "canary"])); $el.classList.remove("hidden"); } function initMobileNavigation({ $img, $navigation }) { const state = { active: false, originX: null, originT: null, dist: null, }; effect(rxjs.fromEvent($img, "touchstart", { passive: true }).pipe(rxjs.debounceTime(10), rxjs.tap((event) => { if (event.touches.length !== 1) return; $img.style.transition = "0s ease transform"; state.active = true; state.originT = performance.now(); state.originX = event.touches[0].pageX; }))); effect(rxjs.fromEvent($img, "touchmove", { passive: true }).pipe(rxjs.tap((event) => { if (event.touches.length !== 1 || state.active === false) return; state.dist = event.touches[0].pageX - state.originX; $img.style.transform = `translateX(${state.dist}px)`; }))); effect(rxjs.fromEvent($img, "touchend").pipe(rxjs.tap(async(event) => { if (state.active === false) return; state.active = false; const shouldTurnPage = ((distPx, elapsedMs, widthPx) => { const velocity = Math.abs(distPx) / elapsedMs; const fastEnough = velocity > 1; const farEnough = Math.abs(distPx) > widthPx * 0.5; return farEnough || fastEnough; })(state.dist, performance.now() - state.originT, $img.clientWidth); if (!shouldTurnPage) { $img.style.transition = "0.2s ease transform"; $img.style.transform = ""; return; } let $navlink = null; if (state.dist > 0) $navlink = qs($navigation, ".left a"); else $navlink = qs($navigation, ".right a"); if (!$navlink.hasAttribute("href")) { $img.style.transition = "0.5s ease transform"; $img.style.transform = ""; return; } $navlink.click(); await animate($img, { time: 200, keyframes: [ { transform: `translateX(${state.dist}px)`, opacity: 1 }, { transform: `translateX(${$img.clientWidth*Math.sign(state.dist)}px)`, opacity: 0 }, ] }); $img.classList.add("hidden"); }))); } function initKeyboardNavigation({ $img, $navigation }) { effect(rxjs.fromEvent(window, "keydown").pipe(rxjs.tap(({ key }) => { let $navlink = null; switch (key) { case "ArrowLeft": $navlink = qs($navigation, ".left a"); break; case "ArrowRight": $navlink = qs($navigation, ".right a"); break; } if (!$navlink || !$navlink.hasAttribute("href")) return; $navlink.click(); }))); } export function init() { return Promise.all([ loadCSS(import.meta.url, "./pagination.css"), initParams(), ]); } ================================================ FILE: public/assets/pages/viewerpage/application_image/zoom.js ================================================ import rxjs, { effect } from "../../../lib/rx.js"; import { qs } from "../../../lib/dom.js"; export default function({ $img, $page }) { const $navigation = qs($page, `[data-bind="component_navigation"]`); effect(rxjs.merge(...builder({ $img })).pipe( rxjs.tap(() => $navigation.classList.add("hidden")), rxjs.scan((state, { clientX, clientY, moveX, moveY, scale, duration }) => { state.x += moveX ?? 0; state.y += moveY ?? 0; const next = Math.min(20, Math.max(1, state.scale * (scale ?? 1))); if (next > 1) { const rect = $img.getBoundingClientRect(); const ox = (clientX ?? rect.left + rect.width / 2) - rect.left; const oy = (clientY ?? rect.top + rect.height / 2) - rect.top; const f = next / state.scale; state.x += (1 - f) * ox; state.y += (1 - f) * oy; } else { state.x = 0; state.y = 0; } state.scale = next; state.duration = duration ?? (next === 1 ? 500 : 0); return state; }, { scale: 1, x: 0, y: 0, duration: 0 }), rxjs.tap(({ scale, x, y, duration }) => { $img.style.transition = `transform ${duration}ms ease`; $img.style.transform = `translate(${x}px,${y}px) scale(${scale})`; if (scale === 1) $navigation.classList.remove("hidden"); }), )); } function builder({ $img }) { $img.style.transformOrigin = "0 0"; $img.style.transition = ""; return [ // zoom via double click rxjs.fromEvent($img.parentElement, "dblclick").pipe( rxjs.filter((e) => e.target === $img), rxjs.map((e) => ({ scale: 2, clientX: e.clientX, clientY: e.clientY })), ), // zoom via scroll wheel rxjs.fromEvent($img.parentElement, "wheel").pipe( rxjs.tap((e) => e.preventDefault()), rxjs.map((event) => { let scale = Math.exp(-event.deltaY / 300); if (scale > 1.07) scale = 1.07; else if (scale < 0.93) scale = 0.93; return { scale, clientX: event.clientX, clientY: event.clientY, }; }), ), // zoom via keyboard shortcut rxjs.fromEvent(window, "keydown").pipe( rxjs.filter(({ key }) => ["Escape", "+", "-", "ArrowUp", "ArrowDown"].indexOf(key) !== -1), rxjs.withLatestFrom(rxjs.fromEvent(window, "mousemove").pipe( rxjs.startWith({ clientX: null, clientY: null }), )), rxjs.map(([{ key }, { clientX, clientY }]) => { let scale = 0; if (["+", "ArrowUp"].indexOf(key) !== -1) scale = 3/2; else if (["-", "ArrowDown"].indexOf(key) !== -1) scale = 2/3; return { clientX, clientY, scale, duration: 100 }; }), ), // pinch zoom rxjs.fromEvent($img.parentElement, "touchstart", { passive: false }).pipe( rxjs.filter((e) => e.touches.length === 2), rxjs.switchMap((event) => rxjs.fromEvent($img.parentElement, "touchmove", { passive: false }).pipe( rxjs.filter((event) => event.touches.length >= 2), rxjs.tap((event) => event.preventDefault()), rxjs.takeUntil(rxjs.fromEvent(window, "touchend")), rxjs.map((event) => ({ clientX: (event.touches[0].pageX + event.touches[1].pageX) / 2, clientY: (event.touches[0].pageY + event.touches[1].pageY) / 2, distance: Math.hypot( event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY, ), })), rxjs.pairwise(), rxjs.map(([prev, curr]) => ({ clientX: curr.clientX, clientY: curr.clientY, scale: Math.min(1.15, Math.max(0.85, curr.distance / prev.distance)), moveX: curr.clientX - prev.clientX, moveY: curr.clientY - prev.clientY, duration: 0, })), )), ), // grab and drag rxjs.fromEvent($img.parentElement, "mousedown", { passive: false }).pipe( rxjs.filter((e) => e.target === $img && e.button === 0), rxjs.switchMap((down) => { let prev = { x: down.clientX, y: down.clientY, t: down.timeStamp }; const move$ = rxjs.fromEvent(window, "mousemove").pipe( rxjs.takeUntil(rxjs.fromEvent(window, "mouseup")), rxjs.map((m) => { const dx = m.clientX - prev.x; const dy = m.clientY - prev.y; const dt = m.timeStamp - prev.t || 1; prev = { x: m.clientX, y: m.clientY, t: m.timeStamp }; return { moveX: dx, moveY: dy, dt, duration: 0 }; }), rxjs.tap(() => ($img.style.cursor = "move")), rxjs.share(), ); const $inertia = move$.pipe( rxjs.startWith({ moveX: 0, moveY: 0, dt: 1 }), rxjs.last(), rxjs.tap(() => ($img.style.cursor = "default")), rxjs.switchMap(({ moveX, moveY, dt }) => { const DECAY = 0.8; const FRAME = 16; const STOPV = 0.05; const vx = moveX / dt; const vy = moveY / dt; const speed = Math.hypot(vx, vy); if (speed < STOPV) return rxjs.EMPTY; const nFrames = Math.ceil(Math.log(STOPV / speed) / Math.log(DECAY)); const gsum = (DECAY * (1 - Math.pow(DECAY, nFrames))) / (1 - DECAY); return rxjs.of({ moveX: vx * gsum * FRAME, moveY: vy * gsum * FRAME, duration: nFrames * FRAME }); }), ); return rxjs.merge(move$, $inertia); }), ), ]; } ================================================ FILE: public/assets/pages/viewerpage/application_image.css ================================================ .component_imageviewer .component_image_container, body:not(.dark-mode) .component_imageviewer .component_image_container .fullscreen .component_pager .wrapper > span { background: var(--surface); } .dark-mode .component_imageviewer .component_image_container { background: #2d2f31; } .component_imageviewer .component_image_container { display: flex; flex-grow: 1; height: 100%; width: 100%; text-align: center; overflow: hidden; height: 100%; box-sizing: border-box; } .component_imageviewer, .component_imageviewer .images_wrapper { flex: 1; display: flex; overflow: hidden; width: 100%; height: 100%; flex-direction: column; position: relative; justify-content: center; } .component_imageviewer img.photo { margin: 10px; width: fit-content; max-width: 98%;; min-height: 100px; max-height: 100%; background: var(--dark); box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; border-radius: 2px; align-self: center; } .component_imageviewer img.photo.idle { transition: 0.2s ease transform; } @media screen and (max-width: 500px) { .component_imageviewer img.photo { margin: 7px; } } /* fullscreen mode */ .component_imageviewer .component_image_container.fullscreen { background: var(--dark); } .component_imageviewer .component_image_container.fullscreen .component_pager .wrapper > span { background: var(--dark); } .component_imageviewer .component_image_container.fullscreen img.photo { background: var(--color); } /* image loading spinner */ .component_imageviewer .component_loader { margin-top: 0; } .component_imageviewer .component_loader img { width: 40px; } /* error loading image */ .component_imageviewer .component_filedownloader { margin: 0 auto; } /* information menu */ .component_imageviewer .images_aside { flex: 0; text-align: left; width: 0; z-index: 1; min-width: 0px; transition: 0.15s ease min-width; background: #f2f2f2; color: var(--dark); } .component_imageviewer .images_aside.open { min-width: 300px; transition: 0.5s ease min-width; transition: 0.3s ease min-width; } @media screen and (max-width: 850px) { .component_imageviewer .images_aside.open { min-width: 250px; font-size: 0.94em; } .component_imageviewer .images_aside.open [data-bind="header"], .component_imageviewer .images_aside.open [data-bind="body"] { padding: 15px 15px 0px 15px; } .component_imageviewer .images_aside [data-bind="header"] .header { padding: 10px 0; } } @media screen and (max-width: 650px) { .component_imageviewer .images_aside.open { min-width: 200px; } } @media screen and (max-width: 580px) { .component_imageviewer component-menubar button .component_icon[alt="info"] { display: none; } .component_imageviewer .images_aside.open { width: 0px; min-width: 0; } } .component_imageviewer .images_aside.open [data-bind="body"] { transform: translateX(0px); opacity: 1; } .component_imageviewer .images_aside [data-bind="body"] { transition: 0.2s ease opacity, 0.3s ease transform; opacity: 0; transform: translateX(10px); transition-delay: 0.2s; } .component_imageviewer .images_aside .header { display: flex; line-height: 25px; white-space: nowrap; padding: 20px 0px; font-size: 1.25em; } .component_imageviewer .images_aside .header .component_icon { height: 18px; float: right; cursor: pointer; padding: 5px; margin: -5px -5px 0 0; } .component_imageviewer .images_aside .header .component_icon:hover { background: #0000000a; border-radius: 50%; } .component_imageviewer .images_aside.open [data-bind="header"], .component_imageviewer .images_aside [data-bind="body"] { padding: 10px 20px 0px 20px; } .component_imageviewer .images_aside [data-bind="body"] .content_box { clear: both; opacity: 0.85; margin-bottom: 20px; } .component_imageviewer .images_aside [data-bind="body"] .content_box > div { width: calc(100% - 40px); } .component_imageviewer .images_aside [data-bind="body"] .content_box .component_icon { height: 30px; width: 30px; float: left; padding: 5px 10px 5px 0; } .component_imageviewer .images_aside [data-bind="body"] .component_mapshot { margin-bottom: 10px; } .component_imageviewer .images_aside .meta_key { display: flex; justify-content: space-between; margin: 5px 0; border-top: 1px solid #0000000a; padding-top: 5px; text-align: right; font-size: 0.85em; } .component_imageviewer .images_aside .meta_key .title { margin-right: 5px; } .component_imageviewer .images_aside .meta_key .value { color: var(--light); } ================================================ FILE: public/assets/pages/viewerpage/application_image.d.ts ================================================ interface Window { EXIF: { getAllTags: (any) => object; getData: (HTMLElement, any) => void; }; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_image.js ================================================ import { createElement, createRender, onDestroy } from "../../lib/skeleton/index.js"; import { toHref } from "../../lib/skeleton/router.js"; import rxjs, { effect, onLoad, onClick } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { animate } from "../../lib/animate.js"; import { extname } from "../../lib/path.js"; import { qs, safe } from "../../lib/dom.js"; import { get as getConfig } from "../../model/config.js"; import { load as loadPlugin } from "../../model/plugin.js"; import { Chromecast } from "../../model/chromecast.js"; import { loadCSS } from "../../helpers/loader.js"; import { createLoader } from "../../components/loader.js"; import notification from "../../components/notification.js"; import t from "../../locales/index.js"; import ctrlError from "../ctrl_error.js"; import componentInformation, { init as initInformation } from "./application_image/information.js"; import componentPagination, { init as initPagination } from "./application_image/pagination.js"; import componentZoom from "./application_image/zoom.js"; import ctrlDownloader, { init as initDownloader } from "./application_downloader.js"; import { renderMenubar, buttonDownload, buttonFullscreen } from "./component_menubar.js"; class IImage { getSRC() { throw new Error("NOT_IMPLEMENTED"); } } export default function(render, { getFilename, getDownloadUrl, mime, hasMenubar = true, acl$ }) { const $page = createElement(` <div class="component_imageviewer"> <component-menubar filename="${safe(getFilename())}" class="${!hasMenubar && "hidden"}"></component-menubar> <div class="component_image_container"> <div class="images_wrapper no-select"> <img class="photo idle hidden" draggable="false" /> <div data-bind="component_navigation"></div> </div> <div class="images_aside scroll-y"></div> </div> </div> `); render($page); const $imgContainer = qs($page, ".images_wrapper"); const $photo = qs($page, "img.photo"); const $menubar = qs($page, "component-menubar"); const removeLoader = createLoader($imgContainer); const load$ = new rxjs.BehaviorSubject(null); const toggleInfo = ($button) => { const $aside = qs($page, ".images_aside"); $aside.classList.toggle("open"); $button.setAttribute("aria-expanded", $aside.classList.contains("open") ? "true" : "false"); componentInformation(createRender(qs($page, ".images_aside")), { toggle: toggleInfo, load$ }); }; renderMenubar( $menubar, buttonDownload(getFilename(), getDownloadUrl()), buttonFullscreen(qs($page, ".component_image_container")), mime === "image/jpeg" && buttonInfo({ toggle: toggleInfo }), ["image/jpeg", "image/png"].indexOf(mime) !== -1 && buttonChromecast(getFilename(), getDownloadUrl()), ); effect(rxjs.from(loadPlugin(mime)).pipe( rxjs.mergeMap(async(loader) => { let src = `${getDownloadUrl()}&size=${window.innerWidth}`; if (loader) { const { response } = await ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).toPromise(); const img = new (await loader(IImage, { mime, $menubar, getFilename, getDownloadUrl }))({ $photo }); src = await img.getSRC(response); } $photo.setAttribute("src", src); await onLoad($photo).toPromise(); }), rxjs.tap(() => load$.next($photo)), removeLoader, rxjs.tap(() => { const cancel = animate($photo, { onEnter: () => $photo.classList.remove("hidden"), onExit: async() => (await cancel)(), time: 300, easing: "cubic-bezier(.51,.92,.24,1.15)", keyframes: [ { opacity: 0, transform: "scale(.97)" }, { opacity: 1 }, { opacity: 1, transform: "scale(1)" }, ], }); }), rxjs.catchError((err) => { if (err.target instanceof HTMLElement && err.type === "error") { return rxjs.of(initDownloader()).pipe( removeLoader, rxjs.mergeMap(() => { load$.error(err); ctrlDownloader(createRender(qs($page, ".images_wrapper")), { acl$, getFilename, getDownloadUrl, hasMenubar: false }); return rxjs.EMPTY; }), ); } return ctrlError()(err); }), )); effect(load$.pipe( rxjs.first(), rxjs.tap(() => { componentZoom({ $img: $photo, $page, $menubar, load$ }); componentPagination(createRender(qs($page, "[data-bind=\"component_navigation\"]")), { $img: $photo }); }), )); } export function init() { return Promise.all([ loadCSS(import.meta.url, "./application_image.css"), loadCSS(import.meta.url, "./component_menubar.css"), initPagination(), initInformation(), Chromecast.init(), ]); } function buttonInfo({ toggle }) { const $el = createElement(` <button aria-controls="pane-info" aria-expanded="false"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg4MiwwLDAsMC44ODIsNS45LDUuOSkiPgogICAgPHBhdGggc3R5bGU9ImZpbGw6I2YyZjJmMjtmaWxsLW9wYWNpdHk6MSIgZD0ibSA2Mi4xNjIsMCBjIDYuNjk2LDAgMTAuMDQzLDQuNTY3IDEwLjA0Myw5Ljc4OSAwLDYuNTIyIC01LjgxNCwxMi41NTUgLTEzLjM5MSwxMi41NTUgLTYuMzQ0LDAgLTEwLjA0NSwtMy43NTIgLTkuODY5LC05Ljk0NyBDIDQ4Ljk0NSw3LjE3NiA1My4zNSwwIDYyLjE2MiwwIFogTSA0MS41NDMsMTAwIGMgLTUuMjg3LDAgLTkuMTY0LC0zLjI2MiAtNS40NjMsLTE3LjYxNSBsIDYuMDcsLTI1LjQ1NyBjIDEuMDU3LC00LjA3NyAxLjIzLC01LjcwNyAwLC01LjcwNyAtMS41ODgsMCAtOC40NTEsMi44MTYgLTEyLjUxLDUuNTkgTCAyNyw1Mi40MDYgQyAzOS44NjMsNDEuNDggNTQuNjYyLDM1LjA3MiA2MS4wMDQsMzUuMDcyIGMgNS4yODUsMCA2LjE2OCw2LjM2MSAzLjUyNSwxNi4xNDggTCA1Ny41OCw3Ny45OCBjIC0xLjIzNCw0LjcyOSAtMC43MDMsNi4zNTkgMC41MjcsNi4zNTkgMS41ODYsMCA2Ljc4NywtMS45NjMgMTEuODk2LC02LjA0MSBMIDczLDgyLjM3NyBDIDYwLjQ4OCw5NS4xIDQ2LjgzLDEwMCA0MS41NDMsMTAwIFoiIC8+CiAgPC9nPgo8L3N2Zz4K" alt="info"> </button> `); effect(rxjs.merge( onClick($el), rxjs.fromEvent(window, "keydown").pipe(rxjs.filter((e) => e.key === "i")), ).pipe(rxjs.mapTo($el), rxjs.tap(toggle))); return $el; } function buttonChromecast(filename, downloadURL) { const context = Chromecast.context(); if (!context) return; const chromecastSetup = (event) => { switch (event.sessionState) { case window.cast.framework.SessionState.SESSION_STARTED: chromecastLoader(); break; } }; const chromecastLoader = () => { const session = Chromecast.session(); if (!session) return; const link = Chromecast.createLink("/" + toHref(downloadURL)); const media = new window.chrome.cast.media.MediaInfo( link, getConfig("mime", {})[extname(filename)], ); media.metadata = new window.chrome.cast.media.PhotoMediaMetadata(); media.metadata.title = filename; media.metadata.images = [ new window.chrome.cast.Image(location.origin + "/" + toHref("/assets/icons/photo.png")), ]; try { const req = Chromecast.createRequest(media); session.loadMedia(req); } catch (err) { console.error(err); notification.error(t("Cannot establish a connection")); } }; context.addEventListener( window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED, chromecastSetup, ); onDestroy(() => context.removeEventListener( window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED, chromecastSetup, )); const media = Chromecast.media(); if (media && media.media && media.media.mediaCategory === "IMAGE") chromecastLoader(); return document.createElement("google-cast-launcher"); } ================================================ FILE: public/assets/pages/viewerpage/application_map.css ================================================ #map .leaflet-control-attribution { display: none; } #map { height: 100%; z-index: 2; } #map .leaflet-popup-content-wrapper { border-radius: 2px; } #map .leaflet-popup-content-wrapper, #map .leaflet-popup-tip { box-shadow: 0 3px 14px rgba(0,0,0,0.15); } #map .leaflet-control-layers-list { user-select: none; } #map .leaflet-control-measure .leaflet-control-measure-toggle { opacity: 0.6; } #map .leaflet-control-scale { margin-left: 10px; margin-bottom: 10px; } #map .leaflet-popup-content { margin: 10px 20px 10px 15px; } ================================================ FILE: public/assets/pages/viewerpage/application_map.d.ts ================================================ interface Window { L: any; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_map.js ================================================ import { createElement, nop } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import ajax from "../../lib/ajax.js"; import { load as loadPlugin } from "../../model/plugin.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import componentDownloader, { init as initDownloader } from "./application_downloader.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; class IMap { toGeoJSON() { throw new Error("NOT_IMPLEMENTED"); } } export default async function(render, { mime, getDownloadUrl = nop, getFilename = nop, acl$ = rxjs.EMPTY }) { const $page = createElement(` <div class="component_map"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div id="map"></div> </div> `); render($page); const $menubar = renderMenubar( qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl()), ); const map = window.L.map("map"); const removeLoader = createLoader(qs($page, "#map")); await effect(ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe( rxjs.mergeMap(async({ response }) => { const loader = await loadPlugin(mime); if (!loader) { try { loadGeoJSON(map, JSON.parse(new TextDecoder().decode(response))); window.L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 21 }).addTo(map); } catch (err) { componentDownloader(render, { mime, acl$, getFilename, getDownloadUrl }); } return rxjs.EMPTY; } const mapImpl = new (await loader(IMap, { mime, getDownloadUrl, getFilename, $menubar }))(response, { map, $page, L: window.L, }); loadGeoJSON(map, await mapImpl.toGeoJSON()); }), removeLoader, rxjs.catchError(ctrlError()), )); } export async function init($root) { const priors = $root ? [ $root.classList.add("component_page_viewerpage"), loadCSS(import.meta.url, "./component_menubar.css"), loadCSS(import.meta.url, "../ctrl_viewerpage.css"), ] : []; await Promise.all([ loadJS(import.meta.url, "../../lib/vendor/leaflet/leaflet.js"), loadCSS(import.meta.url, "../../lib/vendor/leaflet/leaflet.css"), loadCSS(import.meta.url, "./application_map.css"), initDownloader(), ...priors, ]); } function loadGeoJSON(map, content) { const overlay = { global: window.L.layerGroup([]) }; let n = 0; const geojson = window.L.geoJSON(content, { style: (feature) => { const style = { color: "#3388ff", weight: 3 }; if (feature.properties.color) style.color = feature.properties.color; if (feature.properties.weight) style.weight = feature.properties.weight; return style; }, pointToLayer: (feature, latlng) => { return window.L.circleMarker(latlng, { radius: 6, fillColor: "#e2e2e2", color: "#000000", opacity: 0.5, weight: 1, fillOpacity: 0.3 }); }, onEachFeature: (feature, shape) => { n += 1; if (n > 10000) return; const { group = "global", ...props } = feature.properties || {}; const featureObject = (function() { if (props["name"]) shape = shape.bindPopup(props["name"]); switch (props["popup::type"]) { case "text": shape = shape.bindPopup(props["popup::content"]); break; case "html": shape = shape.bindPopup((function() { const $richLabel = window.L.DomUtil.create("div", ""); $richLabel.innerHTML = props["popup::content"]; if ($richLabel.querySelector("script")) $richLabel.innerHTML = `__BLOCKED_CONTENT__`; return $richLabel; })(), { className: "leaflet-measure-resultpopup", autoPanPadding: [10, 10], }); break; } return shape; })(feature.geometry || {}); if (!featureObject) return; if (!overlay[group]) overlay[group] = window.L.layerGroup([]); overlay[group].addLayer(featureObject); } }); // center map around bounds try { const center = geojson.getBounds().getCenter(); const zoom = (function(p1, p2) { const distance = Math.log10(1 + Math.abs(p1.lat - p2.lat) + Math.abs(p1.lng - p2.lng)); const [a, b] = distance > 0.5 ? [-4, 11] : [-15, 15]; return Math.floor(a * distance + b); })(geojson.getBounds().getNorthEast(), geojson.getBounds().getSouthWest()); map.setView([center.lat, center.lng], zoom); } catch (err) { map.setView([0, 0], 2); } // display everything Object.keys(overlay).forEach((key) => overlay[key].addTo(map)); delete overlay["global"]; if (Object.keys(overlay).length > 0) window.L.control.layers({}, overlay).addTo(map); } ================================================ FILE: public/assets/pages/viewerpage/application_pdf.css ================================================ .component_page_viewerpage .component_pdfviewer { background: var(--surface); text-align:center; } .component_page_viewerpage .component_pdfviewer [data-bind="pdf"] { overflow-y: scroll; flex: 1 1 auto; } .component_page_viewerpage .component_pdfviewer [data-bind="pdf"] embed { width:100%; height:100%; } .component_page_viewerpage .component_pdfviewer [data-bind="pdf"] component-icon[name="loading"] { padding-top: 75px; display: block; } ================================================ FILE: public/assets/pages/viewerpage/application_pdf.d.ts ================================================ interface Window { pdfjsLib: { getDocument: (url: string) => { promise: Promise<any> }; GlobalWorkerOptions: { workerSrc: string; }; // Add other properties and methods of pdfjsLib as needed }; env?: string; chrome: object; } export default function(any): void; ================================================ FILE: public/assets/pages/viewerpage/application_pdf.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect, onLoad } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { createLoader } from "../../components/loader.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import { join } from "../../lib/path.js"; import ctrlError from "../ctrl_error.js"; import { transition } from "./common.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; import "../../components/icon.js"; const hasNativePDF = "application/pdf" in window.navigator.mimeTypes && !!window.chrome; export default async function(render, opts) { const ctrl = hasNativePDF ? ctrlPDFNative : ctrlPDFJs; ctrl(render, opts); } function ctrlPDFNative(render, { getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_pdfviewer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div data-bind="pdf"> <embed class="hidden" src="${safe(getDownloadUrl())}#toolbar=0" type="application/pdf" /> </div> </div> `); render($page); renderMenubar(qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl())); const removeLoader = createLoader(qs($page, `[data-bind="pdf"]`)); effect(onLoad(qs($page, "embed")).pipe( removeLoader, rxjs.tap(($embed) => $embed.classList.remove("hidden")), rxjs.tap(($embed) => transition($embed)), rxjs.catchError(ctrlError()), )); } async function ctrlPDFJs(render, { getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_pdfviewer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div data-bind="pdf"></div> </div> `); render($page); const $container = qs($page, `[data-bind="pdf"]`); const createBr = () => $container.appendChild(createElement(`<div style="height:${document.body.clientWidth > 600 ? 20 : 5}px"> </div>`)); const removeLoader = createLoader($container); const base = qs(document.head, "base").getAttribute("href"); effect(rxjs.from(window.pdfjsLib.getDocument(base + getDownloadUrl()).promise).pipe( removeLoader, rxjs.mergeMap(async(pdf) => { createBr(); for (let i=0; i<pdf.numPages; i++) { const page = await pdf.getPage(i + 1); const marginLeftRight = (document.body.clientWidth > 600 ? 50 : 15); const ratio = window.devicePixelRatio || 1; const viewport = page.getViewport({ scale: Math.min( Math.max( document.body.clientWidth - marginLeftRight, 0, ), 800, ) / page.getViewport({ scale: 1 / ratio }).width, }); const $canvas = document.createElement("canvas"); $canvas.height = viewport.height; $canvas.width = viewport.width; $canvas.style.width = Math.floor(viewport.width / ratio) + "px"; $canvas.style.height = Math.floor(viewport.height / ratio) + "px"; $container.appendChild($canvas); if (window.env === "test") $canvas.getContext = () => null; await page.render({ canvasContext: $canvas.getContext("2d"), viewport, }); await new Promise((done) => window.requestAnimationFrame(done)); } createBr(); }), rxjs.catchError(ctrlError()), )); } export function init() { const deps = [ loadCSS(import.meta.url, "./application_pdf.css"), ]; if (!hasNativePDF) { deps.push(loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.js", { type: "module" })); deps.push(loadJS(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js", { type: "module" }).then(() => { window.pdfjsLib.GlobalWorkerOptions.workerSrc = join(import.meta.url, "../../lib/vendor/pdfjs/pdf.worker.js"); })); } return Promise.all(deps); } ================================================ FILE: public/assets/pages/viewerpage/application_skeleton.css ================================================ .component_skeletonviewer { background: var(--bg-color); } .component_skeleton_container { height: 100%; width: 100%; position: relative; } .component_skeleton_container > .component_loader { width: 100%; } ================================================ FILE: public/assets/pages/viewerpage/application_skeleton.js ================================================ import { createElement, createRender } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, safe } from "../../lib/dom.js"; import { load as loadPlugin } from "../../model/plugin.js"; import { loadCSS } from "../../helpers/loader.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import componentDownloader, { init as initDownloader } from "./application_downloader.js"; import { renderMenubar } from "./component_menubar.js"; export default function(render, { mime, getFilename, getDownloadUrl, acl$, hasMenubar = true }) { const $page = createElement(` <div class="component_skeletonviewer"> <component-menubar filename="${safe(getFilename())}" class="${!hasMenubar && "hidden"}"></component-menubar> <div class="component_skeleton_container flex"></div> </div> `); render($page); const $menubar = renderMenubar(qs($page, "component-menubar")); const $container = qs($page, ".component_skeleton_container"); const removeLoader = createLoader($container); effect(rxjs.from(loadPlugin(mime)).pipe( rxjs.mergeMap((loader) => { const opts = { mime, acl$, getFilename, getDownloadUrl }; if (!loader) { componentDownloader(render, opts); return rxjs.EMPTY; } return rxjs.from(loader(createRender($container), { $menubar, ...opts })); }), removeLoader, rxjs.catchError(ctrlError()), )); } export function init() { return Promise.all([ loadCSS(import.meta.url, "./application_skeleton.css"), initDownloader(), ]); } ================================================ FILE: public/assets/pages/viewerpage/application_table.css ================================================ .component_tableviewer { height: 100%; background: var(--bg-color); } .component_table_container:has(.component_loader) .table { display: none; } .component_tableviewer component-menubar input[type="search"] { background: rgba(255, 255, 255, 0.05); border: none; color: inherit; border-radius: 3px; margin-right: 5px; margin-top: 2px; padding-left: 3px; } .component_tableviewer input[type="search"]::-webkit-search-cancel-button { appearance: none; } .component_tableviewer .component_table_container { height: 100%; position: relative; } .component_tableviewer .table { font-family: monospace; position: absolute; top: 0; bottom: 0; left: 0; right: 0; white-space: nowrap; display: block; font-size: 13px; display: flex; flex-direction: column; } .component_tableviewer .table .thead { overflow-x: scroll; scrollbar-width: none; border-bottom: 1px solid var(--border); } .component_tableviewer .table .tbody { flex: 1; overflow-y: auto; overflow-x: auto; } .component_tableviewer .thead > div, .component_tableviewer .tbody > div { display: block; } .component_tableviewer .table .thead .th { padding: 5px 10px; font-weight: normal; display: inline-block; position: sticky; top: 0; opacity: 0.8; text-transform: capitalize; } .component_tableviewer .table .thead .th img { height: 13px; border-radius: 5px; position: absolute; margin-top: 4px; right: 7px; cursor: pointer; transition: ease transform 0.2s; filter: contrast(1) invert(0); } .dark-mode .component_tableviewer .table .thead .th img { filter: contrast(1) invert(1); } .component_tableviewer .table .tbody .td { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); margin-top: -1px; display: inline-block; padding: 5px 10px; line-height: 17px; height: 17px; vertical-align: middle; cursor: pointer; } .component_tableviewer .table .tbody .td .empty { opacity: 0.4; } .component_tableviewer .table .tbody .tr:hover .td { background: var(--border); } ================================================ FILE: public/assets/pages/viewerpage/application_table.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import ajax from "../../lib/ajax.js"; import { loadCSS } from "../../helpers/loader.js"; import t from "../../locales/index.js"; import { createLoader } from "../../components/loader.js"; import { get as getPlugin } from "../../model/plugin.js"; import ctrlDownloader, { init as initDownloader } from "./application_downloader.js"; import { renderMenubar, buttonDownload } from "./component_menubar.js"; import { transition } from "./common.js"; const MAX_ROWS = 200; class ITable { contructor() {} getHeader() { throw new Error("NOT_IMPLEMENTED"); } getBody() { throw new Error("NOT_IMPLEMENTED"); } } export default async function(render, { mime, getDownloadUrl, getFilename, hasMenubar = true, acl$ = rxjs.EMPTY, module = null }) { const $page = createElement(` <div class="component_tableviewer"> <component-menubar filename="${safe(getFilename())}" class="${!hasMenubar && "hidden"}"></component-menubar> <div class="component_table_container"> <table class="table"> <thead class="thead"></thead> <tbody class="tbody"></tbody> </div> </div> </div> `); render($page); const $menubar = renderMenubar( qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl()), ); const $dom = { thead: qs($page, ".thead"), tbody: qs($page, ".tbody"), }; const removeLoader = createLoader(qs($page, ".component_table_container")); const padding = 10; const STATE = { header: {}, body: [], rows: [], }; // feature: initial render const init$ = ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe( rxjs.mergeMap(async({ response }) => { const loader = getPlugin(mime); if (!loader) throw new TypeError(`unsupported mimetype "${mime}"`); const [, url] = loader; const m = module || (await import(url)).default; let table = new (await m(ITable, { $menubar }))(response); if (typeof table.then === "function") table = await table; STATE.header = table.getHeader(); STATE.body = table.getBody(); STATE.rows = STATE.body; }), removeLoader, rxjs.tap(() => { buildHead(STATE, $dom, padding); buildRows(STATE.rows.slice(0, MAX_ROWS), STATE.header, $dom.tbody, padding, true, false); }), rxjs.catchError((err) => rxjs.from(initDownloader()).pipe( rxjs.tap(() => ctrlDownloader(render, { acl$, getFilename, getDownloadUrl })), rxjs.tap(() => console.log("cannot open file", err)), rxjs.mergeMap(() => rxjs.EMPTY), )), rxjs.share(), ); effect(init$); // feature: search const $search = createElement(`<input type="search" placeholder="search">`); effect(init$.pipe( rxjs.tap(() => $menubar.add($search)), rxjs.mergeMap(() => rxjs.fromEvent($search, "keyup").pipe(rxjs.debounce((e) => { if (!e.target.value) return rxjs.of(null); return rxjs.timer(300); }))), rxjs.tap((e) => { const terms = e.target.value.toLowerCase().trim().split(" "); $dom.tbody.scrollTo(0, 0); if (terms === "") STATE.rows = STATE.body; else STATE.rows = STATE.body.filter((row) => { const line = Object.values(row).join("").toLowerCase(); for (let i=0; i<terms.length; i++) { if (line.indexOf(terms[i]) === -1) { return false; } } return true; }); buildRows(STATE.rows.slice(0, MAX_ROWS), STATE.header, $dom.tbody, padding, false, true); }), )); // feature: fixed header scroll along effect(rxjs.fromEvent($dom.tbody, "scroll").pipe( rxjs.tap(() => $dom.thead.scrollTo($dom.tbody.scrollLeft, 0)) )); effect(rxjs.fromEvent($dom.thead, "scroll").pipe( rxjs.tap(() => $dom.tbody.scrollTo($dom.thead.scrollLeft, $dom.tbody.scrollTop)) )); // feature: infinite scroll effect(rxjs.fromEvent($dom.tbody, "scroll").pipe( rxjs.mergeMap(async(e) => { const scrollBottom = e.target.scrollHeight - (e.target.scrollTop + e.target.clientHeight); if (scrollBottom > 0) return; else if (STATE.rows.length <= MAX_ROWS) return; else if (STATE.rows.length <= $dom.tbody.children.length) return; const current = $dom.tbody.children.length; const newRows = STATE.rows.slice(current, current + 10); buildRows(newRows, STATE.header, $dom.tbody, padding, false, false); }), )); // feature: make the last column to always fit the viewport effect(rxjs.merge( init$, init$.pipe( rxjs.mergeMap(() => rxjs.fromEvent(window, "resize")), rxjs.debounce((e) => e["debounce"] === false ? rxjs.of(null) : rxjs.timer(100)), ), ).pipe( rxjs.tap(() => resizeLastColumnIfNeeded({ $target: $dom.thead, $childs: qs($dom.thead, ".tr"), padding, })), rxjs.tap(() => qsa($dom.tbody, ".tr").forEach(($tr) => resizeLastColumnIfNeeded({ $target: $dom.tbody, $childs: $tr, padding, }))), )); } export function init($root) { const priors = ($root && [ $root.classList.add("component_page_viewerpage"), loadCSS(import.meta.url, "./component_menubar.css"), loadCSS(import.meta.url, "../ctrl_viewerpage.css"), ]); return Promise.all([ loadCSS(import.meta.url, "./application_table.css"), ...priors, ]); } async function buildRows(rows, legends, $tbody, padding, isInit, withClear) { if (withClear) $tbody.innerHTML = ""; for (let i=0; i<rows.length; i++) { const obj = rows[i]; if (!obj) break; const $tr = createElement(`<div class="tr"></div>`); legends.forEach(({ name, size }, i) => { const $col = createElement(`<div class="${withCenter("td ellipsis", size, i === legends.length -1)}" style="${styleCell(size, name, padding)}"></div>`); $col.setAttribute("data-column", name); $col.setAttribute("title", obj[name]); if (obj[name]) $col.textContent = obj[name]; else $col.appendChild(createElement(`<span class=\"empty\">-</span>`)); $tr.appendChild($col); }); $tbody.appendChild($tr); } $tbody.style.opacity = "0"; if (rows.length === 0) $tbody.appendChild(createElement(` <h3 class="center no-select" style="opacity:0.2; margin-top:30px"> ${t("Empty")} </h3> `)); if (!isInit) { const e = new Event("resize"); e["debounce"] = false; window.dispatchEvent(e); await new Promise(requestAnimationFrame); } $tbody.style.opacity = "1"; if (isInit) transition($tbody.parentElement); } function buildHead(STATE, $dom, padding) { const $tr = createElement(`<div class="tr"></div>`); STATE.header.forEach(({ name, size }, i) => { const $th = createElement(` <div class="${withCenter("th ellipsis", size, i === STATE.header.length - 1)}" style="${styleCell(size, name, padding)}"></div> `); $th.setAttribute("title", name); $th.textContent = name; $th.appendChild(createElement(`<img class="no-select" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MC41MzMzMzMyMSIgZD0ibSA3LjcwNSw4LjA0NSA0LjU5LDQuNTggNC41OSwtNC41OCAxLjQxLDEuNDEgLTYsNiAtNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" />`)); let ascending = null; qs($th, "img").onclick = (e) => { ascending = !ascending; STATE.rows = sortBy(STATE.rows, ascending, name); qsa(e.target.closest(".tr"), "img").forEach(($img) => { $img.style.transform = "rotate(0deg)"; }); if (ascending) e.target.style.transform = "rotate(180deg)"; $dom.tbody.scrollTo($dom.tbody.scrollLeft, 0); buildRows(STATE.rows.slice(0, MAX_ROWS), STATE.header, $dom.tbody, padding, false, true); }; $tr.appendChild($th); }); $dom.thead.appendChild($tr); } function styleCell(l, name, padding) { const maxSize = 40; const charSize = 7; let sizeInChar = Math.min(l, maxSize); if (name.length >= sizeInChar) sizeInChar = Math.min(name.length + 1, maxSize); return `width: ${sizeInChar*charSize+padding*2}px;`; } function withCenter(className, fieldLength, isLast) { if (fieldLength > 4 || isLast) return className; return `${className} center`; } function resizeLastColumnIfNeeded({ $target, $childs, padding = 0 }) { const fullWidth = $target.clientWidth; let currWidth = 0; $childs.childNodes.forEach(($node) => currWidth += $node.clientWidth); if (currWidth < fullWidth && $childs.lastChild !== null) { const lastWidth = ($childs.lastChild.clientWidth - padding * 2) + fullWidth - currWidth; $childs.lastChild.setAttribute("style", `width: ${lastWidth}px`); } } function sortBy(rows, ascending, key) { const o = ascending ? 1 : -1; return rows.sort((a, b) => { let diff = a[key] - b[key]; if (isNaN(diff)) { if (a[key] === b[key]) diff = 0; else if (a[key] < b[key]) diff = -1; else diff = 1; } return o*diff; }); } ================================================ FILE: public/assets/pages/viewerpage/application_url.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { ApplicationError } from "../../lib/error.js"; import assert from "../../lib/assert.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import { cat } from "./model_files.js"; export default function(render, { getDownloadUrl }) { const $page = createElement(` <div class="component_urlopener" style="background: var(--surface);"></div> `); render($page); createLoader($page); effect(cat(getDownloadUrl()).pipe( rxjs.tap((content) => { const url = content.replace(/[\s\S]*(https?:\/\/\S+)[\s\S]*/, "$1"); try { new URL(url); // throws on invalid URL location.replace(url); } catch (err) { const message = assert.type(err, window.Error).message; throw new ApplicationError("Not Valid", message); } }), rxjs.catchError(ctrlError()), )); } ================================================ FILE: public/assets/pages/viewerpage/application_video.css ================================================ body:not(.dark-mode) .component_videoplayer { background: var(--surface); } .component_videoplayer { display: flex; flex: 1; flex-direction: column; width: 100%; } .component_videoplayer .video_container { display: flex; flex: 1; text-align: center; overflow: hidden; padding: 15px 10px 65px 10px; height: 100%; box-sizing: border-box; } .component_videoplayer .video_container > span { display: flex; flex: 1; text-align: center; overflow: hidden; height: 100%; box-sizing: border-box; } .component_videoplayer .video_container .video_screen { background: black; box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px; position: relative; border-radius: 3px; margin: auto; position: relative; } .component_videoplayer .video_container .video_screen .video_wrapper { width: 800px; height: 450px; } @media (max-width: 850px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 600px; }} @media (max-width: 650px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 500px; max-height: 400px; }} @media (max-width: 550px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 450px; max-height: 350px; }} @media (max-width: 500px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 350px; max-height: 300px; }} @media (max-width: 370px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 250px; max-height: 200px; }} @media (max-height: 650px) { .component_videoplayer .video_container .video_screen .video_wrapper { max-width: 600px; height: 420px; } .component_videoplayer .video_container { padding: 10px 10px 10px 10px; } } @media (max-height: 550px) { .component_videoplayer .video_container .video_screen .video_wrapper { height: 330px; }} @media (max-height: 445px) { .component_videoplayer .video_container .video_screen .video_wrapper { height: 250px; }} @media (max-height: 370px) { .component_videoplayer .video_container .video_screen .video_wrapper { height: 200px; }} @media (max-height: 310px) { .component_videoplayer .video_container .video_screen .video_wrapper { height: 100px; }} .component_videoplayer .video_container .video_screen .loader { position: absolute; top: calc(50% - 85px); width: 100%; } .component_videoplayer .video_container .video_screen .loader > img { height: 170px; cursor: pointer; filter: brightness(0.5) invert(1); } @media (max-width: 700px) or (max-height: 650px) { .component_videoplayer .video_container .video_screen .loader > img { height: 100px; } .component_videoplayer .video_container .video_screen .loader { top: calc(50% - 60px); } } .component_videoplayer .video_container .video_screen.is-casting-yes .videoplayer_control, .component_videoplayer .video_container .video_screen.video-state-pause .videoplayer_control, .component_videoplayer .video_container .video_screen.video-state-buffer .videoplayer_control, .component_videoplayer .video_container .video_screen.video-state-play:hover .videoplayer_control { opacity: 1; transition: 0.1s opacity ease; } .component_videoplayer .video_container .video_screen .videoplayer_control { transition: 0.5s opacity ease; opacity: 0; display: flex; text-align: left; position: absolute; bottom: 0; width: 100%; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADCCAYAAACIaaiTAAAAAXNSR0IArs4c6QAAARJJREFUOE9lyNdHBQAAhfHb3nvvuu2997jNe29TJJEkkkgSSSSJJJJEEkkiifRH5jsP56Xz8PM5gcC/xfCIWBNHiXiTQIlEk0SJZJNCiVRIM+mUyDCZlMgy2ZTIMbmUyDP5lCgwhZQoMsWUKDGllCgz5ZSogEpTRYlqU0OJoKmlRJ2pp0SDaaREk2mmRItppUSbaadEh+mkRBd0mx5K9Jo+SvSbAUoMmiFKDJsRSoyaMUqMmwlKhMwkJabMNCVmYNbMUSJsIpSImnlKLJhFSiyZZWoFVmEN1mEDNmELtmEHdmEP9uEADuEIjuEETuEMzuECLuEKruEGbuEO7uEBHuEJnuEFXuEN3uEDPuELvuEHfv8AoRErEi7Uc8UAAAAASUVORK5CYII=); background-repeat: repeat-x; background-size: contain; padding: 0 0 7px 0; } .component_videoplayer .video_container .video_screen .videoplayer_control img { cursor: pointer; width: 25px; filter: brightness(0) invert(1); padding: 5px 5px 5px 10px; } .component_videoplayer .video_container .video_screen .videoplayer_control input[type="range"] { width: 60px; cursor: pointer; outline: none; -webkit-appearance: none; background: transparent; } .component_videoplayer .video_container .video_screen .videoplayer_control input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 12px; width: 12px; border: none; border-radius: 50%; background: white; margin-top: -5px; } .component_videoplayer .video_container .video_screen .videoplayer_control input[type="range"]::-moz-range-thumb { -webkit-appearance: none; height: 12px; width: 12px; border: none; border-radius: 50%; background: white; margin-top: -5px; } .component_videoplayer .video_container .video_screen .videoplayer_control input[type="range"]::-webkit-slider-runnable-track { width: 100%; height: 2px; background-color: rgba(255, 255, 255, 0.7); border-radius: 2px; } .component_videoplayer .video_container .video_screen .videoplayer_control input[type="range"]::-moz-range-track { width: 100%; height: 2px; background-color: rgba(255, 255, 255, 0.7); border-radius: 2px; } .component_videoplayer .video_container .video_screen .videoplayer_control .timecode { color: white; margin: auto 0; padding-left: 10px; } .component_videoplayer .video_container .video_screen .videoplayer_control .timecode .hint { position: absolute; top: -40px; margin-left: -23px; font-size: 0.9rem; background: #f1f1f155; border-radius: 3px; padding: 2px 5px; background: var(--dark); } .component_videoplayer .video_container .video_screen .videoplayer_control .progress { position: absolute; width: 100%; top: -20px; height: 20px; cursor: pointer; } .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-active, .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-buffer, .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-placeholder { top: 10px; position: absolute; height: 4px; } .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-active { background: var(--primary); border-top-right-radius: 2px; border-bottom-right-radius: 2px; } .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-active .thumb { display: none; float: right; width: 4px; height: 4px; background: rgba(255, 255, 255, 0.1); border-radius: 50%; margin-top: 0px; position: relative; left: 3px; } .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-buffer { background: #e2e2e244; } .component_videoplayer .video_container .video_screen .videoplayer_control .progress .progress-placeholder { background: #e2e2e222; width: 100%; } .component_videoplayer .video_container video { width: 100%; height: 100%; } .component_videoplayer .video_container .video_screen .videoplayer_control:hover .progress .progress-active .thumb { display: block; } .video-enter, .video-appear { opacity: 0; } .video-enter.video-enter-active, .video-appear.video-appear-active { transition: top .3s,right .3s,bottom .3s,left .3s,max-width .3s,max-height .3s; -webkit-animation-name: zoomIn; animation-name: zoomIn; animation-duration: .3s; -webkit-animation-timing-function: cubic-bezier(0.51, 0.92, 0.24, 1.15); animation-timing-function: cubic-bezier(0.51, 0.92, 0.24, 1.15); opacity: 1; } @keyframes zoomIn { 0% { opacity: 0; transform: scale(0.85); } 100% { opacity: 1; transform: scale(1); } } ================================================ FILE: public/assets/pages/viewerpage/application_video.js ================================================ import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import { animate, slideYIn } from "../../lib/animate.js"; import { loadCSS, loadJS } from "../../helpers/loader.js"; import { qs, qsa, safe } from "../../lib/dom.js"; import { settings_get, settings_put } from "../../lib/settings.js"; import { ApplicationError } from "../../lib/error.js"; import assert from "../../lib/assert.js"; import Hls from "../../lib/vendor/hlsjs/hls.js"; import ctrlError from "../ctrl_error.js"; import { transition } from "./common.js"; import { formatTimecode } from "./common_player.js"; import { ICON } from "./common_icon.js"; import { renderMenubar, buttonDownload, buttonFullscreen } from "./component_menubar.js"; import "../../components/icon.js"; const STATUS_PLAYING = "PLAYING"; const STATUS_PAUSED = "PAUSED"; const STATUS_BUFFERING = "BUFFERING"; export default function(render, { mime, getFilename, getDownloadUrl }) { const $page = createElement(` <div class="component_videoplayer"> <component-menubar filename="${safe(getFilename())}"></component-menubar> <div class="video_container"> <span> <div class="video_screen video-state-pause is-casting-no"> <div class="video_wrapper"> <video></video> </div> <div class="loader no-select"> <component-icon name="loading"></component-icon> </div> <div class="videoplayer_control no-select hidden"> <div class="progress"> <div data-bind="progress-buffer"> <div class="progress-buffer" style="left: 0%; width: 0%;"></div> </div> <div class="progress-active" style="width: 0%;"> <div class="thumb"></div> </div> <div class="progress-placeholder"></div> </div> <img class="component_icon" draggable="false" src="${ICON.PLAY}" alt="play"> <img class="component_icon hidden" draggable="false" src="${ICON.PAUSE}" alt="pause"> <component-icon name="loading" class="hidden"></component-icon> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_MUTE}" alt="volume_mute"> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_LOW}" alt="volume_low"> <img class="component_icon hidden" draggable="false" src="${ICON.VOLUME_NORMAL}" alt="volume"> <input type="range" min="0" max="100" value="13"> <span class="timecode"> <div class="current"></div> <div class="hint hidden"></div> </span> </div> </div> </span> </div> </div> `); render($page); renderMenubar( qs($page, "component-menubar"), buttonDownload(getFilename(), getDownloadUrl()), buttonFullscreen(qs($page, "video")), ); transition(qs($page, ".video_container")); const $video = qs($page, "video"); const $control = { play: qs($page, `.videoplayer_control [alt="play"]`), pause: qs($page, `.videoplayer_control [alt="pause"]`), loading: qs($page, `.videoplayer_control component-icon[name="loading"]`), }; const $volume = { range: qs($page, `input[type="range"]`), icon_mute: qs($page, `img[alt="volume_mute"]`), icon_low: qs($page, `img[alt="volume_low"]`), icon_normal: qs($page, `img[alt="volume"]`), }; const setVolume = (volume) => { settings_put("volume", volume); $video.volume = volume / 100; $volume.range.value = volume; if (volume === 0) { $volume.icon_mute.classList.remove("hidden"); $volume.icon_low.classList.add("hidden"); $volume.icon_normal.classList.add("hidden"); } else if (volume < 50) { $volume.icon_mute.classList.add("hidden"); $volume.icon_low.classList.remove("hidden"); $volume.icon_normal.classList.add("hidden"); } else { $volume.icon_mute.classList.add("hidden"); $volume.icon_low.classList.add("hidden"); $volume.icon_normal.classList.remove("hidden"); } }; const setStatus = (status) => { switch (status) { case "PLAYING": $control.play.classList.add("hidden"); $control.pause.classList.remove("hidden"); $control.loading.classList.add("hidden"); $video.play(); break; case "PAUSED": $control.play.classList.remove("hidden"); $control.pause.classList.add("hidden"); $control.loading.classList.add("hidden"); $video.pause(); break; case "BUFFERING": $control.play.classList.add("hidden"); $control.pause.classList.add("hidden"); $control.loading.classList.remove("hidden"); break; default: assert.fail(status); } }; const setSeek = (newTime, shouldSet = false) => { if (shouldSet) $video.currentTime = newTime; const width = 100 * (newTime / $video.duration); qs($page, ".progress .progress-active").style.width = `${width}%`; if (!isNaN($video.duration)) { qs($page, ".timecode .current").textContent = formatTimecode($video.currentTime) + " / " + formatTimecode($video.duration); } }; // feature1: setup the dom const setup$ = rxjs.of(null).pipe( rxjs.map(() => { const loadPolicy = { default: { maxLoadTimeMs: 3600000, maxTimeToFirstByteMs: Infinity, timeoutRetry: { maxNumRetry: 0 } } }; const hls = new Hls({ debug: !!new URLSearchParams(location.search).get("debug"), manifestLoadPolicy: loadPolicy, }); const sources = window.overrides["video-map-sources"]([{ src: getDownloadUrl(), type: mime, }]); for (let i=0; i<sources.length; i++) { if (sources[i].type !== "application/x-mpegURL") { const $source = document.createElement("source"); $source.setAttribute("type", "video/mp4"); $source.setAttribute("src", sources[i].src); $video.appendChild($source); return [{ ...sources[i], type: "video/mp4" }]; } hls.loadSource(sources[i].src); } hls.attachMedia($video); onDestroy(() => { $video.pause(); $video.remove(); }); return sources; }), rxjs.mergeMap((sources) => rxjs.merge( rxjs.fromEvent($video, "loadeddata"), ...[...qsa($page, "source")].map(($source) => rxjs.fromEvent($source, "error").pipe(rxjs.tap(() => { throw new ApplicationError("NOT_SUPPORTED", JSON.stringify({ mime, sources }, null, 2)); }))), )), rxjs.mergeMap(() => { const $loader = qs($page, ".loader"); $loader.replaceChildren(createElement(`<img src="${ICON.PLAY}" />`)); animate($loader, { time: 150, keyframes: [ { transform: "scale(0.7)" }, { transform: "scale(1)" }, ], }); setSeek(0); return rxjs.race( rxjs.fromEvent($loader, "click").pipe(rxjs.mapTo($loader)), rxjs.fromEvent(document, "keydown").pipe(rxjs.filter((e) => e.code === "Space"), rxjs.first()), ).pipe(rxjs.mapTo($loader)); }), rxjs.tap(($loader) => { $loader.classList.add("hidden"); const $control = qs($page, ".videoplayer_control"); $control.classList.remove("hidden"); animate($control, { time: 300, keyframes: slideYIn(5) }); setStatus(STATUS_PLAYING); }), rxjs.catchError(ctrlError()), rxjs.share(), ); effect(setup$); effect(setup$.pipe(rxjs.mergeMap(() => rxjs.fromEvent($video, "error").pipe(rxjs.tap(() => { // console.error(err); // notify.send(t("Not supported"), "error"); // setIsPlaying(false); // setIsLoading(false); }))))); // feature2: player control - volume effect(setup$.pipe( rxjs.switchMap(() => rxjs.fromEvent($volume.range, "input").pipe(rxjs.map((e) => e.target.value))), rxjs.startWith(settings_get("volume") === null ? 80 : settings_get("volume")), rxjs.tap((volume) => setVolume(parseInt(volume))), )); // feature3: player control - play/pause effect(setup$.pipe( rxjs.mergeMap(() => rxjs.merge( rxjs.fromEvent($control.play, "click").pipe(rxjs.mapTo(STATUS_PLAYING)), rxjs.fromEvent($control.pause, "click").pipe(rxjs.mapTo(STATUS_PAUSED)), rxjs.fromEvent($video, "ended").pipe(rxjs.mapTo(STATUS_PAUSED)), rxjs.fromEvent($video, "waiting").pipe(rxjs.mapTo(STATUS_BUFFERING)), rxjs.fromEvent($video, "playing").pipe(rxjs.mapTo(STATUS_PLAYING)), )), rxjs.debounceTime(50), rxjs.tap((status) => setStatus(status)), )); // feature4: hint const $hint = qs($page, `.hint`); effect(setup$.pipe( rxjs.switchMap(() => rxjs.fromEvent(qs($page, ".progress"), "mousemove")), rxjs.map((e) => { const rec = e.target.getBoundingClientRect(); const width = e.clientX - rec.x; const time = $video.duration * width / rec.width; let posX = width; posX = Math.max(posX, 30); posX = Math.min(posX, e.target.clientWidth - 30); return { x: `${posX}px`, time }; }), rxjs.tap(({ x, time }) => { $hint.classList.remove("hidden"); $hint.style.left = x; $hint.textContent = formatTimecode(time); }), )); effect(setup$.pipe( rxjs.switchMap(() => rxjs.fromEvent(qs($page, ".progress"), "mouseleave")), rxjs.tap(() => $hint.classList.add("hidden")), )); // feature5: player control - seek effect(setup$.pipe( rxjs.switchMap(() => rxjs.fromEvent(qs($page, ".progress"), "click").pipe( rxjs.map((e) => { // TODO: use onClick instead? let $progress = e.target; if (e.target.classList.contains("progress") === false) { $progress = e.target.parentElement; } const rec = $progress.getBoundingClientRect(); return (e.clientX - rec.x) / rec.width; }), rxjs.tap((n) => { if (n < 2/100) { setStatus(STATUS_PAUSED); n = 0; } setSeek(n * $video.duration, true); }), )), )); // feature6: player control - keyboard shortcut effect(setup$.pipe( rxjs.switchMap(() => rxjs.merge( rxjs.fromEvent(document, "keydown").pipe(rxjs.map((e) => e.code)), rxjs.fromEvent($video, "click").pipe(rxjs.mapTo("Space")), )), rxjs.tap((code) => { switch (code) { case "Space": case "KeyK": setStatus($video.paused ? STATUS_PLAYING : STATUS_PAUSED); break; case "KeyM": setVolume($video.volume > 0 ? 0 : settings_get("volume")); break; case "ArrowUp": setVolume(Math.min($video.volume*100 + 10, 100)); break; case "ArrowDown": setVolume(Math.max($video.volume*100 - 10, 0)); break; case "KeyL": setSeek(Math.min($video.duration, $video.currentTime + 10), true); break; case "KeyJ": setSeek(Math.max(0, $video.currentTime - 10), true); break; case "KeyF": // TODO break; case "Digit0": setSeek(0, true); break; case "Digit1": setSeek($video.duration / 10, true); break; case "Digit2": setSeek($video.duration * 2 / 10, true); break; case "Digit3": setSeek($video.duration * 3 / 10, true); break; case "Digit4": setSeek($video.duration * 4 / 10, true); break; case "Digit5": setSeek($video.duration * 5 / 10, true); break; case "Digit6": setSeek($video.duration * 6 / 10, true); break; case "Digit7": setSeek($video.duration * 7 / 10, true); break; case "Digit8": setSeek($video.duration * 8 / 10, true); break; case "Digit9": setSeek($video.duration * 9 / 10, true); break; } }), )); // feature7: render the progress bar effect(setup$.pipe( rxjs.mergeMap(() => rxjs.fromEvent($video, "timeupdate")), rxjs.tap(() => setSeek($video.currentTime)), )); // feature8: render loading buffer effect(setup$.pipe( rxjs.mergeMap(() => rxjs.fromEvent($video, "timeupdate")), rxjs.tap(() => { const calcWidth = (i) => { return ($video.buffered.end(i) - $video.buffered.start(i)) / $video.duration * 100; }; const calcLeft = (i) => { return $video.buffered.start(i) / $video.duration * 100; }; const $container = qs($page, `[data-bind="progress-buffer"]`); if ($video.buffered.length !== $container.children.length) { $container.innerHTML = ""; const $fragment = document.createDocumentFragment(); Array.from({ length: $video.buffered.length }) .map(() => $fragment.appendChild(createElement(` <div className="progress-buffer" style=""></div> `))); $container.appendChild($fragment); } for (let i=0; i<$video.buffered.length; i++) { $container.children[i].style.left = calcLeft(i) + "%"; $container.children[i].style.width = calcWidth(i) + "%"; } }), )); } export function init() { if (!window.overrides) window.overrides = {}; return Promise.all([ loadCSS(import.meta.url, "./application_video.css"), loadJS(import.meta.url, "/overrides/video-transcoder.js"), ]).then(async() => { if (typeof window.overrides["video-map-sources"] !== "function") window.overrides["video-map-sources"] = (s) => (s); }); } ================================================ FILE: public/assets/pages/viewerpage/common.js ================================================ import { fromHref } from "../../lib/skeleton/router.js"; import { transition as transitionLib, slideYIn } from "../../lib/animate.js"; import { basename, forwardURLParams } from "../../lib/path.js"; export function transition($node) { return transitionLib($node, { timeEnter: 150, enter: slideYIn(2) }); } export function getFilename() { return basename(getCurrentPath()) || " "; } export function getDownloadUrl() { return forwardURLParams("api/files/cat?path=" + encodeURIComponent(getCurrentPath()), ["share"]); } export function getCurrentPath(start = "/view/") { const fullpath = fromHref(location.pathname + location.hash); return decodeURIComponent(fullpath.replace(new RegExp("^" + start), "/")); } ================================================ FILE: public/assets/pages/viewerpage/common_fab.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; export const $ICON = { SAVING: createElement(`<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MzguNTMzIDQzOC41MzMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQzOC41MzMgNDM4LjUzMzsiPgogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik00MzIuODIzLDEyMS4wNDljLTMuODA2LTkuMTMyLTguMzc3LTE2LjM2Ny0xMy43MDktMjEuNjk1bC03OS45NDEtNzkuOTQyYy01LjMyNS01LjMyNS0xMi41Ni05Ljg5NS0yMS42OTYtMTMuNzA0ICAgQzMwOC4zNDYsMS45MDMsMjk5Ljk2OSwwLDI5Mi4zNTcsMEgyNy40MDlDMTkuNzk4LDAsMTMuMzI1LDIuNjYzLDcuOTk1LDcuOTkzYy01LjMzLDUuMzI3LTcuOTkyLDExLjc5OS03Ljk5MiwxOS40MTR2MzgzLjcxOSAgIGMwLDcuNjE3LDIuNjYyLDE0LjA4OSw3Ljk5MiwxOS40MTdjNS4zMyw1LjMyNSwxMS44MDMsNy45OTEsMTkuNDE0LDcuOTkxaDM4My43MThjNy42MTgsMCwxNC4wODktMi42NjYsMTkuNDE3LTcuOTkxICAgYzUuMzI1LTUuMzI4LDcuOTg3LTExLjgsNy45ODctMTkuNDE3VjE0Ni4xNzhDNDM4LjUzMSwxMzguNTYyLDQzNi42MjksMTMwLjE4OCw0MzIuODIzLDEyMS4wNDl6IE0xODIuNzI1LDQ1LjY3NyAgIGMwLTIuNDc0LDAuOTA1LTQuNjExLDIuNzE0LTYuNDIzYzEuODA3LTEuODA0LDMuOTQ5LTIuNzA4LDYuNDIzLTIuNzA4aDU0LjgxOWMyLjQ2OCwwLDQuNjA5LDAuOTAyLDYuNDE3LDIuNzA4ICAgYzEuODEzLDEuODEyLDIuNzE3LDMuOTQ5LDIuNzE3LDYuNDIzdjkxLjM2MmMwLDIuNDc4LTAuOTEsNC42MTgtMi43MTcsNi40MjdjLTEuODA4LDEuODAzLTMuOTQ5LDIuNzA4LTYuNDE3LDIuNzA4aC01NC44MTkgICBjLTIuNDc0LDAtNC42MTctMC45MDItNi40MjMtMi43MDhjLTEuODA5LTEuODEyLTIuNzE0LTMuOTQ5LTIuNzE0LTYuNDI3VjQ1LjY3N3ogTTMyOC45MDYsNDAxLjk5MUgxMDkuNjMzVjI5Mi4zNTVoMjE5LjI3MyAgIFY0MDEuOTkxeiBNNDAyLDQwMS45OTFoLTM2LjU1MmgtMC4wMDdWMjgzLjIxOGMwLTcuNjE3LTIuNjYzLTE0LjA4NS03Ljk5MS0xOS40MTdjLTUuMzI4LTUuMzI4LTExLjgtNy45OTQtMTkuNDEtNy45OTRIMTAwLjQ5OCAgIGMtNy42MTQsMC0xNC4wODcsMi42NjYtMTkuNDE3LDcuOTk0Yy01LjMyNyw1LjMyOC03Ljk5MiwxMS44LTcuOTkyLDE5LjQxN3YxMTguNzczSDM2LjU0NFYzNi41NDJoMzYuNTQ0djExOC43NzEgICBjMCw3LjYxNSwyLjY2MiwxNC4wODQsNy45OTIsMTkuNDE0YzUuMzMsNS4zMjcsMTEuODAzLDcuOTkzLDE5LjQxNyw3Ljk5M2gxNjQuNDU2YzcuNjEsMCwxNC4wODktMi42NjYsMTkuNDEtNy45OTMgICBjNS4zMjUtNS4zMjcsNy45OTQtMTEuNzk5LDcuOTk0LTE5LjQxNFYzNi41NDJjMi44NTQsMCw2LjU2MywwLjk1LDExLjEzNiwyLjg1M2M0LjU3MiwxLjkwMiw3LjgwNiwzLjgwNSw5LjcwOSw1LjcwOGw4MC4yMzIsODAuMjMgICBjMS45MDIsMS45MDMsMy44MDYsNS4xOSw1LjcwOCw5Ljg1MWMxLjkwOSw0LjY2NSwyLjg1Nyw4LjMzLDIuODU3LDEwLjk5NFY0MDEuOTkxeiIvPgo8L3N2Zz4K" alt="save" style="height: 100%; width: 100%;">`), LOADING: createElement(`<component-icon name="loading"></component-icon>`), }; ================================================ FILE: public/assets/pages/viewerpage/common_icon.js ================================================ export const ICON = { PLAY: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgdmlld0JveD0iMCAwIDU4Ljc1MiA1OC43NTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+Cgk8cGF0aCBkPSJNIDQ1LjYwNjkxMywyNS4xNzQ3MTIgMjIuNjkxNDAxLDYuMTA4OTgyNSBjIC0xLjQ2OTYyNSwtMC45MDc4NTI1IC0zLjM3MjM1NCwtMC45MDUzNzY2IC00LjgzNjU4NCwwIC0xLjQ5MzUxNSwwLjkyMTA1NzcgLTIuNDIyMTQ1LDIuNjQxODUxIC0yLjQyMjE0NSw0LjQ4OTc0MzUgViA0OC43MzI2NiBjIDAsMS44NDg3MTggMC45Mjc4NiwzLjU2OTUxMSAyLjQxMjg5Nyw0LjQ4NTYxNyAwLjczNDQyNywwLjQ1ODA1MyAxLjU3MzY2MywwLjY5OTg3MiAyLjQyNjc2OSwwLjY5OTg3MiAwLjg1MDc5NSwwIDEuNjg5MjYsLTAuMjQwOTk0IDIuNDIwNjA0LC0wLjY5NTc0NSBsIDIyLjkxNTUxMiwtMTkuMDY3MzggYyAxLjQ5MTk3NCwtMC45MjM1MzMgMi40MTgyOTIsLTIuNjQzNTAxIDIuNDE4MjkyLC00LjQ4ODkxOCAtNy43ZS00LC0xLjg0Mjk0MSAtMC45MjYzMTgsLTMuNTYyOTA5IC0yLjQxOTgzMywtNC40OTEzOTQgeiBNIDQzLjAwMTIxMSwyOS44NjgzMSAyMC42NzA5MDYsNDguNjQyNzU0IGMgLTAuMDYzMTksMC4wMzg3OSAtMC4xMzg3MTYsMC4wNDI5MiAtMC4yMTUwMSwtMC4wMDQxIC0wLjA2NDc0LC0wLjA0MDQ0IC0wLjEwNTU3OSwtMC4xMTcxOTYgLTAuMTA1NTc5LC0wLjE5OTcyOCBWIDEwLjg5MTY2MSBjIDAsLTAuMDgyNTMgMC4wNDAwNywtMC4xNTg0NjIgMC4xMDc4OTEsLTAuMjAwNTUzIDAuMDMyMzcsLTAuMDIwNjMgMC4wNjkzNiwtMC4wMzEzNiAwLjEwNzEyLC0wLjAzMTM2IDAuMDM5MywwIDAuMDc2MjksMC4wMTA3MyAwLjEwOTQzMiwwLjAzMTM2IGwgMjIuMzIyNTk3LDE4Ljc2OTQ5MyBjIDAuMDY4NTksMC4wNDI5MiAwLjExMTc0NCwwLjEyMTMyMiAwLjExMTc0NCwwLjIwNTUwNSAtNy43ZS00LDAuMDg1MDEgLTAuMDQwODQsMC4xNjAxMTIgLTAuMTA3ODksMC4yMDIyMDQgeiIgc3R5bGU9ImZpbGw6IzZmNmY2ZiIgLz4KPC9zdmc+Cg==", PAUSE: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgZmlsbD0iIzZmNmY2ZiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNIDMyMCw4MS45ODc4NjggViA0MjcuNzY5MzEgYyAwLDkuMzQzMzYgNy42NDQ2LDE2Ljk4Nzg4IDE2Ljk4NzksMTYuOTg3ODggaCAyNS45NzU3IGMgOS4zNDMzLDAgMTYuOTg3OCwtNy42NDQ1MiAxNi45ODc4LC0xNi45ODc4OCBWIDgxLjk4Nzg2OCBDIDM3OS45NTE0LDcyLjY0NDUzNiAzNzIuMzA2OSw2NSAzNjIuOTYzNiw2NSBIIDMzNi45ODc5IEMgMzI3LjY0NDYsNjUgMzIwLDcyLjY0NDUzNiAzMjAsODEuOTg3ODY4IFoiIC8+CiAgPHBhdGggZD0iTSAxNTAsODEuOTg3ODY4IFYgNDI3Ljc2OTMxIGMgMCw5LjM0MzM2IDcuNjQ0NTUsMTYuOTg3ODggMTYuOTg3NzksMTYuOTg3ODggaCAyNS45NzU1MiBjIDkuMzQzMjQsMCAxNi45ODc2OSwtNy42NDQ1MiAxNi45ODc2OSwtMTYuOTg3ODggViA4MS45ODc4NjggQyAyMDkuOTUxLDcyLjY0NDUzNiAyMDIuMzA2NTUsNjUgMTkyLjk2MzMxLDY1IEggMTY2Ljk4Nzc5IEMgMTU3LjY0NDU1LDY1IDE1MCw3Mi42NDQ1MzYgMTUwLDgxLjk4Nzg2OCBaIiAvPgo8L3N2Zz4K", VOLUME_MUTE: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzZmNmY2ZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Im0gMjEuOTgzODgzLDE0Ljg4Mzg4MyAtNiwtNS45OTk5OTk1IG0gNiwwIC02LDUuOTk5OTk5NSIgLz4KICA8cGF0aCBkPSJNIDEuMTI1MzM1NiwxNS4yMTc4OTcgViA4Ljc4MTAzMjggYyAwLC0wLjYyNDIyMDUgMC40ODcxOTY3LC0xLjEzMDk5MTcgMS4wODc0OTIzLC0xLjEzMDk5MTcgSCA2LjExMjU3NDggQSAxLjA2NTc0MjIsMS4wNjU3NDIyIDAgMCAwIDYuODgxNDMxOCw3LjMxODM1NjIgTCAxMC4xNDM5MDksMy42MzM5MzI4IGMgMC42ODUxMiwtMC43MTMzOTUgMS44NTYzNDUsLTAuMjA3NzExMSAxLjg1NjM0NSwwLjgwMDM5NDIgdiAxNS4xMzEzNjggYyAwLDEuMDE1NzE1IC0xLjE4NTM2MiwxLjUxNzA0NSAtMS44NjYxMzEsMC43ODk1MTMgTCA2Ljg4MjUxOSwxNi42OTE0NSBBIDEuMDY1NzQyMiwxLjA2NTc0MjIgMCAwIDAgNi4xMDM4NzQ4LDE2LjM0OTk3NSBIIDIuMjEyODI3OSBjIC0wLjYwMDI5NTYsMCAtMS4wODc0OTIzLC0wLjUwNjc2OCAtMS4wODc0OTIzLC0xLjEzMjA3OCB6IiAvPgo8L3N2Zz4K", VOLUME_LOW: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzZmNmY2ZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Im0gMTYuMzUwMjI1LDguMTkzNzg3IGMgMS40NDk2MjYsMS45MzM1NTkgMS40NDk2MjYsNS42Nzg4ODQgMCw3LjYxMjQ0NiIgLz4KICA8cGF0aCBkPSJNIDEuMTI1MzM1NiwxNS4yMTc4OTcgViA4Ljc4MTAzMjggYyAwLC0wLjYyNDIyMDUgMC40ODcxOTY3LC0xLjEzMDk5MTcgMS4wODc0OTIzLC0xLjEzMDk5MTcgSCA2LjExMjU3NDggQSAxLjA2NTc0MjIsMS4wNjU3NDIyIDAgMCAwIDYuODgxNDMxOCw3LjMxODM1NjIgTCAxMC4xNDM5MDksMy42MzM5MzI4IGMgMC42ODUxMiwtMC43MTMzOTUgMS44NTYzNDUsLTAuMjA3NzExMSAxLjg1NjM0NSwwLjgwMDM5NDIgdiAxNS4xMzEzNjggYyAwLDEuMDE1NzE1IC0xLjE4NTM2MiwxLjUxNzA0NSAtMS44NjYxMzEsMC43ODk1MTMgTCA2Ljg4MjUxOSwxNi42OTE0NSBBIDEuMDY1NzQyMiwxLjA2NTc0MjIgMCAwIDAgNi4xMDM4NzQ4LDE2LjM0OTk3NSBIIDIuMjEyODI3OSBjIC0wLjYwMDI5NTYsMCAtMS4wODc0OTIzLC0wLjUwNjc2OCAtMS4wODc0OTIzLC0xLjEzMjA3OCB6IiAvPgo8L3N2Zz4K", VOLUME_NORMAL: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzZmNmY2ZiIgc3Ryb2tlLXdpZHRoPSIyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Im0gMTYuMzUwMjI1LDguMTkzNzg3IGMgMS40NDk2MjYsMS45MzM1NTkgMS40NDk2MjYsNS42Nzg4ODQgMCw3LjYxMjQ0NiIgLz4KICA8cGF0aCBkPSJtIDE5LjYxMjcwMyw0LjM4NzU2NDQgYyA0LjMzNjkxOSw0LjE0MTE3MDQgNC4zNjMwMTQsMTEuMTEwOTA4NiAwLDE1LjIyNDg4NzYiIC8+CiAgPHBhdGggZD0iTSAxLjEyNTMzNTYsMTUuMjE3ODk3IFYgOC43ODEwMzI4IGMgMCwtMC42MjQyMjA1IDAuNDg3MTk2NywtMS4xMzA5OTE3IDEuMDg3NDkyMywtMS4xMzA5OTE3IEggNi4xMTI1NzQ4IEEgMS4wNjU3NDIyLDEuMDY1NzQyMiAwIDAgMCA2Ljg4MTQzMTgsNy4zMTgzNTYyIEwgMTAuMTQzOTA5LDMuNjMzOTMyOCBjIDAuNjg1MTIsLTAuNzEzMzk1IDEuODU2MzQ1LC0wLjIwNzcxMTEgMS44NTYzNDUsMC44MDAzOTQyIHYgMTUuMTMxMzY4IGMgMCwxLjAxNTcxNSAtMS4xODUzNjIsMS41MTcwNDUgLTEuODY2MTMxLDAuNzg5NTEzIEwgNi44ODI1MTksMTYuNjkxNDUgQSAxLjA2NTc0MjIsMS4wNjU3NDIyIDAgMCAwIDYuMTAzODc0OCwxNi4zNDk5NzUgSCAyLjIxMjgyNzkgYyAtMC42MDAyOTU2LDAgLTEuMDg3NDkyMywtMC41MDY3NjggLTEuMDg3NDkyMywtMS4xMzIwNzggeiIgLz4KPC9zdmc+Cg==", }; ================================================ FILE: public/assets/pages/viewerpage/common_player.js ================================================ export function formatTimecode(seconds) { return String(Math.floor(seconds / 60)).padStart(2, "0") + ":" + String(Math.floor(seconds % 60)).padStart(2, "0"); } // TODO: abstract setVolume, setSeek and setStatus ================================================ FILE: public/assets/pages/viewerpage/component_menubar.css ================================================ .component_menubar { background: var(--dark); color: #f1f1f1; position: relative; display: block; box-shadow: 0 0px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2); z-index: 4; text-align: left; } .component_menubar .container { padding: 0; font-size: 0.9em; } .component_menubar .container.inherit-width { max-width: inherit!important; } .component_menubar .container > span { display: flex; } .component_menubar .titlebar { flex: 1; padding: 8px 0; width: 0; letter-spacing: 0.3px; padding-left: 5px; line-height: 19px; } .component_menubar .action-item { padding-right: 10px; display: flex; align-items: center; } .component_menubar .action-item button { padding: 0; } .component_menubar .action-item .component_icon { height: 19px; width: 19px; cursor: pointer; padding: 0 3px; } .component_menubar .action-item .download-button .component_icon { padding-right: 1px; } .component_menubar .action-item .btn { background: #e2e2e2; color: rgba(0,0,0,0.6); padding: 0 5px; border-radius: 2px; cursor: pointer; text-transform: uppercase; font-weight: bold; } .component_menubar select { background: #f2f2f2; color: var(--color); border: 2px solid #f2f2f2; border-radius: 2px; padding: 0 3px 0 3px; margin: 0 3px; } .dark-mode .component_menubar { background: var(--bg-color); } .dark-mode .component_menubar .container { color: inherit; } .touch-yes .component_menubar .action-item .component_icon { height: 21px; width: 21px; } /* icons in additional menu */ .component_menubar .action-item [is="component-dropdown"] .dropdown_button { border: none; padding: 2px 0px; margin: 0; } /* chromecast */ :root { --disconnected-color: #F2F2F2; --connected-color: var(--primary); } google-cast-launcher { display: inline-block; height: 23px; width: 23px; cursor: pointer; padding: 0 5px; position: relative; top: 1px; cursor: pointer; } ================================================ FILE: public/assets/pages/viewerpage/component_menubar.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import { qs, safe } from "../../lib/dom.js"; import { animate, slideYIn } from "../../lib/animate.js"; import { loadCSS } from "../../helpers/loader.js"; import { isSDK } from "../../helpers/sdk.js"; import assert from "../../lib/assert.js"; import "../../components/dropdown.js"; export default class ComponentMenubar extends HTMLElement { constructor() { super(); this.classList.add("component_menubar"); this.innerHTML = ` <div class="container"> <span> <div class="titlebar ellipsis" style="opacity:0">${safe(this.getAttribute("filename")) || " "}</div> <div class="action-item no-select"></div> </span> </div> `; if (new URLSearchParams(location.search).get("nav") === "false") { const $container = assert.type(this.firstElementChild, HTMLElement); $container.classList.add("inherit-width"); } } async connectedCallback() { const $title = assert.type(this.querySelector(".titlebar"), HTMLElement); this.timeoutID = setTimeout(() => animate($title, { time: 250, keyframes: slideYIn(2), onExit: () => $title.style.opacity = 1, }), 100); } disconnectedCallback() { clearTimeout(this.timeoutID); } render(buttons) { const $item = assert.type(this.querySelector(".action-item"), HTMLElement); for (let i=buttons.length-1; i>=0; i--) { $item.appendChild(buttons[i]); } animate($item, { time: 250, keyframes: slideYIn(2) }); } add($button) { const $item = assert.type(this.querySelector(".action-item"), HTMLElement); $item.prepend($button); animate($button, { time: 250, keyframes: slideYIn(2) }); return $button; } } export function buttonDownload(name, link) { const ICON = { DOWNLOAD: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzODQgNTEyIj4KICA8cGF0aCBmaWxsPSIjZjJmMmYyIiBkPSJNIDM2MCw0NjAgSCAyNCBDIDEwLjcsNDYwIDAsNDUzLjMgMCw0NDAgdiAtMTIgYyAwLC0xMy4zIDEwLjcsLTIwIDI0LC0yMCBoIDMzNiBjIDEzLjMsMCAyNCw2LjcgMjQsMjAgdiAxMiBjIDAsMTMuMyAtMTAuNywyMCAtMjQsMjAgeiIgLz4KICA8cGF0aCBmaWxsPSIjZjJmMmYyIiBkPSJNIDIyNi41NTM5LDIzNC44ODQyOCBWIDUyLjk0MzI4MyBjIDAsLTYuNjI3IC01LjM3MywtMTIgLTEyLC0xMiBoIC00NCBjIC02LjYyNywwIC0xMiw1LjM3MyAtMTIsMTIgViAyMzQuODg0MjggaCAtNTIuMDU5IGMgLTIxLjM4MiwwIC0zMi4wOSwyNS44NTEgLTE2Ljk3MSw0MC45NzEgbCA4Ni4wNTksODYuMDU5IGMgOS4zNzMsOS4zNzMgMjQuNTY5LDkuMzczIDMzLjk0MSwwIGwgODYuMDU5LC04Ni4wNTkgYyAxNS4xMTksLTE1LjExOSA0LjQxMSwtNDAuOTcxIC0xNi45NzEsLTQwLjk3MSB6IiAvPgo8L3N2Zz4K", LOADING: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0nMTIwcHgnIGhlaWdodD0nMTIwcHgnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIiBjbGFzcz0idWlsLXJpbmctYWx0Ij4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImJrIj48L3JlY3Q+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjZmZmZmZmIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2UtZGFzaG9mZnNldCIgZHVyPSIycyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGZyb209IjAiIHRvPSI1MDIiPjwvYW5pbWF0ZT4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9InN0cm9rZS1kYXNoYXJyYXkiIGR1cj0iMnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiB2YWx1ZXM9IjE1MC42IDEwMC40OzEgMjUwOzE1MC42IDEwMC40Ij48L2FuaW1hdGU+CiAgPC9jaXJjbGU+Cjwvc3ZnPgo=", }; const $el = createElement(` <span class="download-button"> <a href="${link}" download="${safe(name)}"> <img class="component_icon" draggable="false" src="${ICON.DOWNLOAD}" alt="download"> </a> </span> `); const $img = qs($el, "img"); qs($el, "a").onclick = () => { if (isSDK()) return; document.cookie = "download=yes; path=/; max-age=120;"; $img.setAttribute("src", ICON.LOADING); const id = setInterval(() => { if (/download=yes/.test(document.cookie) !== false) return; clearInterval(id); $img.setAttribute("src", ICON.DOWNLOAD); }, 500); }; return $el; } export function buttonFullscreen($screen, fullscreen) { let fullscreenHandler = fullscreen; if (!fullscreen) { if ("webkitRequestFullscreen" in document.body) { fullscreenHandler = () => $screen.webkitRequestFullscreen(); } else if ("mozRequestFullScreen" in document.body) { fullscreenHandler = () => $screen.mozRequestFullScreen(); } } if (!fullscreenHandler) return document.createDocumentFragment(); const $el = createElement(` <button role="button"> <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MzguNTQzIDQzOC41NDMiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuNzI5LDAsMCwwLjcyOSw1OS40MjI1NzYsNTkuNDIyNDQxKSI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojZjJmMmYyO2ZpbGwtb3BhY2l0eToxIiBkPSJtIDQwNy40MiwxNTkuMDI5IGMgMy42MiwzLjYxNiA3Ljg5OCw1LjQyOCAxMi44NDcsNS40MjggMi4yODIsMCA0LjY2OCwtMC40NzYgNy4xMzksLTEuNDI5IDcuNDI2LC0zLjIzNSAxMS4xMzYsLTguODUzIDExLjEzNiwtMTYuODQ2IFYgMTguMjc2IGMgMCwtNC45NDkgLTEuODA3LC05LjIzMSAtNS40MjgsLTEyLjg0NyAtMy42MSwtMy42MTcgLTcuODk4LC01LjQyNCAtMTIuODQ3LC01LjQyNCBIIDI5Mi4zNiBjIC03Ljk5MSwwIC0xMy42MDcsMy44MDUgLTE2Ljg0OCwxMS40MTkgLTMuMjMsNy40MjMgLTEuOTAyLDEzLjk5IDQsMTkuNjk4IEwgMzIwLjYyMyw3Mi4yMzQgMjE5LjI3MSwxNzMuNTg5IDExNy45MTcsNzIuMjMxIDE1OS4wMjksMzEuMTE5IGMgNS45MDEsLTUuNzA4IDcuMjMyLC0xMi4yNzUgMy45OTksLTE5LjY5OCBDIDE1OS43ODksMy44MDcgMTU0LjE3NSwwIDE0Ni4xODIsMCBIIDE4LjI3NiBDIDEzLjMyNCwwIDkuMDQxLDEuODA5IDUuNDI1LDUuNDI2IDEuODA4LDkuMDQyIDAuMDAxLDEzLjMyNCAwLjAwMSwxOC4yNzMgViAxNDYuMTggYyAwLDcuOTk2IDMuODA5LDEzLjYxIDExLjQxOSwxNi44NDYgMi4yODUsMC45NDggNC41NywxLjQyOSA2Ljg1NSwxLjQyOSA0Ljk0OCwwIDkuMjI5LC0xLjgxMiAxMi44NDcsLTUuNDI3IEwgNzIuMjM0LDExNy45MTkgMTczLjU4OCwyMTkuMjczIDcyLjIzNCwzMjAuNjIyIDMxLjEyMiwyNzkuNTA5IGMgLTUuNzExLC01LjkwMyAtMTIuMjc1LC03LjIzMSAtMTkuNzAyLC00LjAwMSAtNy42MTQsMy4yNDEgLTExLjQxOSw4Ljg1NiAtMTEuNDE5LDE2Ljg1NCB2IDEyNy45MDYgYyAwLDQuOTQ4IDEuODA3LDkuMjI5IDUuNDI0LDEyLjg0NyAzLjYxOSwzLjYxNCA3LjkwMiw1LjQyMSAxMi44NTEsNS40MjEgaCAxMjcuOTA2IGMgNy45OTYsMCAxMy42MSwtMy44MDYgMTYuODQ2LC0xMS40MTYgMy4yMzQsLTcuNDI3IDEuOTAzLC0xMy45OSAtMy45OTksLTE5LjcwNSBMIDExNy45MTcsMzY2LjMwOSAyMTkuMjcxLDI2NC45NSAzMjAuNjI0LDM2Ni4zMTEgMjc5LjUxLDQwNy40MjEgYyAtNS44OTksNS43MDggLTcuMjI4LDEyLjI3OSAtMy45OTcsMTkuNjk4IDMuMjM3LDcuNjE3IDguODU2LDExLjQyMyAxNi44NTEsMTEuNDIzIGggMTI3LjkwNyBjIDQuOTQ4LDAgOS4yMzIsLTEuODEzIDEyLjg0NywtNS40MjggMy42MTMsLTMuNjEzIDUuNDIsLTcuODk4IDUuNDIsLTEyLjg0NyBWIDI5Mi4zNjIgYyAwLC03Ljk5NCAtMy43MDksLTEzLjYxMyAtMTEuMTM2LC0xNi44NTEgLTcuODAyLC0zLjIzIC0xNC40NjIsLTEuOTAzIC0xOS45ODUsNC4wMDQgTCAzNjYuMzExLDMyMC42MjEgMjY0Ljk1MiwyMTkuMjcxIDM2Ni4zMSwxMTcuOTE3IFoiIC8+CiAgPC9nPgo8L3N2Zz4K" alt="fullscreen"> </button> `); $el.onclick = fullscreenHandler; return $el; } export function renderMenubar($menubar, ...buttons) { assert.type($menubar, ComponentMenubar); $menubar.render(buttons.filter(($button) => $button)); return $menubar; } export async function init() { return loadCSS(import.meta.url, "./component_menubar.css"); } if (!customElements.get("component-menubar")) customElements.define("component-menubar", ComponentMenubar); ================================================ FILE: public/assets/pages/viewerpage/mimetype.js ================================================ import { get as getPlugin } from "../../model/plugin.js"; export function opener(file = "", mimes) { const mime = getMimeType(file, mimes); const type = mime.split("/")[0]; if (window.overrides && typeof window.overrides["xdg-open"] === "function") { const openerFromPlugin = window.overrides["xdg-open"](mime); if (openerFromPlugin !== null) { return openerFromPlugin; } } const p = getPlugin(mime); if (p) return [p[0], { mime, ...p[1] }]; if (type === "text") { return ["editor", { mime }]; } else if (mime === "application/pdf") { return ["pdf", { mime }]; } else if (type === "image") { return ["image", { mime }]; } else if (["application/javascript", "application/xml", "application/json", "application/x-perl"].indexOf(mime) !== -1) { return ["editor", { mime }]; } else if (["audio/wave", "audio/mp3", "audio/flac", "audio/ogg", "audio/mpeg"].indexOf(mime) !== -1) { return ["audio", { mime }]; } else if (mime === "application/x-form") { return ["form", { mime }]; } else if (mime === "application/geo+json" || mime === "application/vnd.ogc.wms_xml" || mime === "application/vnd.shp") { return ["map", { mime }]; } else if (type === "video" || mime === "application/ogg") { return ["video", { mime }]; } else if (["application/epub+zip"].indexOf(mime) !== -1) { return ["ebook", { mime }]; } else if (mime === "application/x-url") { return ["url", { mime }]; } else if (type === "application" && mime !== "application/text") { return ["download", { mime }]; } return ["editor", { mime }]; } export function getMimeType(file, mimes = {}) { return mimes[file.split(".").slice(-1)[0].toLowerCase()] || "text/plain"; } ================================================ FILE: public/assets/pages/viewerpage/model_files.js ================================================ import { toHref, navigate } from "../../lib/skeleton/router.js"; import rxjs from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { AjaxError } from "../../lib/error.js"; import { forwardURLParams } from "../../lib/path.js"; import { getCurrentPath } from "./common.js"; export const options = () => ajax({ url: forwardURLParams(`api/files/cat?path=${encodeURIComponent(getCurrentPath())}`, ["share"]), method: "OPTIONS", }).pipe( rxjs.catchError((err) => { if (err instanceof AjaxError && err.err().status === 401) { navigate(toHref("/login?next=" + location.pathname + location.hash + location.search)); return rxjs.EMPTY; } throw err; }), rxjs.map((res) => res.responseHeaders.allow.replace(/\r/, "").split(", ")), ); export const cat = (url) => ajax({ url: forwardURLParams(url, ["share"]), method: "GET", }).pipe( rxjs.map(({ response }) => response), ); export const save = (content) => ajax({ url: forwardURLParams("api/files/cat?path=" + encodeURIComponent(getCurrentPath()), ["share"]), method: "POST", body: content, }); ================================================ FILE: public/global.d.ts ================================================ interface Window { chrome: any; cast: any; overrides: { [key: string]: any; "xdg-open"?: (mime: string) => void; }; VERSION: string; bundler: any; BEARER_TOKEN?: string; } ================================================ FILE: public/index.backoffice.html ================================================ <!DOCTYPE html> <html lang="en"> <head> <base href="{{ .base }}"> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./admin/assets/css/designsystem.css"> <link rel="icon" href="favicon.ico"> <title>Admin Console
    ================================================ FILE: public/index.frontoffice.html ================================================ {{ if .appname | eq "Filestash" }} {{ end -}} {{- range .bundle }} {{- end }}
    ================================================ FILE: public/tsconfig.json ================================================ { "compilerOptions": { "allowJs": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "alwaysStrict": true, "checkJs": true, "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "noEmit": true, "noErrorTruncation": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitAny": false, "noUnusedLocals": true, "noUnusedParameters": true, "noUncheckedIndexedAccess": true, "strictNullChecks": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strict": true, "lib": [ "dom", "dom.iterable", "es2022" ], "module": "es2022", "target": "es2022", "typeRoots": [] }, "include": [ "assets/boot/*.js", "assets/pages/*.js", "global.d.ts" ], "exclude": [ "**/*.test.js", "assets/worker/sw_cache.js", "coverage", "vite.config.js", "jest.setup.js" ] } ================================================ FILE: public/vite.config.js ================================================ import { defineConfig } from "vitest/config"; export default defineConfig(({ comand, mode }) => { return { plugins: [], test: { global: true, environment: "jsdom", setupFiles: ["./vite.setup.js"], } }; }); ================================================ FILE: public/vite.setup.js ================================================ import { describe, it, test, expect, vi, afterEach, afterAll, beforeEach, beforeAll, } from "vitest"; global.nextTick = () => new Promise((done) => setTimeout(done, 0)); global.requestAnimationFrame = (callback) => setTimeout(callback, 0); global.describe = describe; global.it = it; global.test = test; global.expect = expect; global.vi = vi; global.beforeEach = beforeEach; global.beforeAll = beforeAll; global.afterEach = afterEach; global.afterAll = afterAll; ================================================ FILE: server/common/app.go ================================================ package common import ( "context" ) type App struct { Backend IBackend Body map[string]interface{} Session map[string]string Share Share Context context.Context Authorization string Languages []string } ================================================ FILE: server/common/backend.go ================================================ package common import ( "io" "os" "strings" ) const BACKEND_NIL = "_nothing_" var Backend = NewDriver() func NewDriver() Driver { return Driver{make(map[string]IBackend)} } type Driver struct { ds map[string]IBackend } func (d *Driver) Register(name string, driver IBackend) { if driver == nil { panic("backend: register invalid nil backend") } d.ds[name] = driver } func (d *Driver) Get(name string) IBackend { b := d.ds[name] if b == nil || name == BACKEND_NIL { return Nothing{} } return b } func (d *Driver) Drivers() map[string]IBackend { return d.ds } type Nothing struct{} func (b Nothing) Init(params map[string]string, app *App) (IBackend, error) { return &b, nil } func (b Nothing) Ls(path string) ([]os.FileInfo, error) { return []os.FileInfo{}, nil } func (b Nothing) Stat(path string) (os.FileInfo, error) { return nil, ErrNotFound } func (b Nothing) Cat(path string) (io.ReadCloser, error) { return NewReadCloserFromReader(strings.NewReader("")), ErrNotImplemented } func (b Nothing) Mkdir(path string) error { return ErrNotImplemented } func (b Nothing) Rm(path string) error { return ErrNotImplemented } func (b Nothing) Mv(from string, to string) error { return ErrNotImplemented } func (b Nothing) Touch(path string) error { return ErrNotImplemented } func (b Nothing) Save(path string, file io.Reader) error { return ErrNotImplemented } func (b Nothing) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "nothing", }, }, } } ================================================ FILE: server/common/cache.go ================================================ package common import ( "fmt" "github.com/mitchellh/hashstructure" "github.com/patrickmn/go-cache" "sync" "time" ) type AppCache struct { Cache *cache.Cache sync.Mutex } func (a *AppCache) Get(key interface{}) interface{} { hash, err := hashstructure.Hash(key, nil) if err != nil { return nil } a.Lock() defer a.Unlock() value, found := a.Cache.Get(fmt.Sprintf("%d", hash)) if found == false { return nil } return value } func (a *AppCache) Set(key map[string]string, value interface{}) { hash, err := hashstructure.Hash(key, nil) if err != nil { return } a.Cache.Set(fmt.Sprint(hash), value, cache.DefaultExpiration) } func (a *AppCache) SetKey(key string, value interface{}) { a.Cache.Set(key, value, cache.DefaultExpiration) } func (a *AppCache) Del(key map[string]string) { hash, _ := hashstructure.Hash(key, nil) a.Cache.Delete(fmt.Sprint(hash)) } func (a *AppCache) OnEvict(fn func(string, interface{})) { a.Cache.OnEvicted(fn) } func NewAppCache(arg ...time.Duration) AppCache { var retention time.Duration = 5 var cleanup time.Duration = 10 if len(arg) > 0 { retention = arg[0] if len(arg) > 1 { cleanup = arg[1] } } c := AppCache{} c.Cache = cache.New(retention*time.Minute, cleanup*time.Minute) return c } func NewQuickCache(arg ...time.Duration) AppCache { var retention time.Duration = 5 var cleanup time.Duration = 10 if len(arg) > 0 { retention = arg[0] if len(arg) > 1 { cleanup = arg[1] } } c := AppCache{} c.Cache = cache.New(retention*time.Second, cleanup*time.Second) return c } // ============================================================================ type KeyValueStore struct { cache map[string]interface{} sync.RWMutex } func NewKeyValueStore() KeyValueStore { return KeyValueStore{cache: make(map[string]interface{})} } func (this *KeyValueStore) Get(key string) interface{} { var val interface{} this.RLock() val = this.cache[key] this.RUnlock() return val } func (this *KeyValueStore) Set(key string, value interface{}) { this.Lock() this.cache[key] = value this.Unlock() } func (this *KeyValueStore) Clear() { this.Lock() this.cache = make(map[string]interface{}) this.Unlock() } ================================================ FILE: server/common/config.go ================================================ package common import ( "bytes" "encoding/json" "os" "os/user" "regexp" "strings" "sync" ) var Config Configuration type Configuration struct { mu sync.RWMutex cache sync.Map Form []Form Conn []map[string]any } type ConfigElement struct { currentElement *FormElement cfg *Configuration } type Form struct { Title string Form []Form Elmnts []FormElement } type FormElement struct { Id string `json:"id,omitempty"` Name string `json:"label"` Type string `json:"type"` Description string `json:"description,omitempty"` Placeholder string `json:"placeholder,omitempty"` Pattern string `json:"pattern,omitempty"` Opts []string `json:"options,omitempty"` Target []string `json:"target,omitempty"` ReadOnly bool `json:"readonly"` Default interface{} `json:"default"` Value interface{} `json:"value"` MultiValue bool `json:"multi,omitempty"` Datalist []string `json:"datalist,omitempty"` Order int `json:"-"` Required bool `json:"required"` } func InitConfig() error { Config = NewConfiguration() if err := Config.Load(); err != nil { return err } Config.Initialise() return nil } func NewConfiguration() Configuration { return Configuration{ Form: []Form{ Form{ Title: "general", Elmnts: []FormElement{ FormElement{Name: "name", Type: "text", Default: APPNAME, Description: "Name as shown in the UI", Placeholder: "Default: \"" + APPNAME + "\""}, FormElement{Name: "port", Type: "number", Default: 8334, Description: "Port on which the application is available.", Placeholder: "Default: 8334"}, FormElement{Name: "host", Type: "text", Description: "The host people need to use to access this server", Placeholder: WhiteLabelText("Eg: \"demo.filestash.app\"", "Eg: \"files.yourcompany.com\"")}, FormElement{Name: "secret_key", Type: "password", Required: true, Pattern: "[a-zA-Z0-9]{16}", Description: "The key that's used to encrypt and decrypt content. Update this settings will invalidate existing user sessions and shared links, use with caution!"}, FormElement{Name: "force_ssl", Type: "boolean", Description: "Enable the web security mechanism called 'Strict Transport Security'"}, FormElement{Name: "editor", Type: "select", Default: "emacs", Opts: []string{"base", "emacs", "vim"}, Description: "Keybinding to be use in the editor. Default: \"emacs\""}, FormElement{Name: "logout", Type: "text", Default: "", Description: "Redirection URL whenever user click on the logout button"}, FormElement{Name: "display_hidden", Type: "boolean", Default: false, Description: "Should files starting with a dot be visible by default?"}, FormElement{Name: "refresh_after_upload", Type: "boolean", Default: false, Description: "Refresh directory listing after upload"}, FormElement{Name: "open_mode", Type: "select", Default: "single_click", Opts: []string{"single_click", "double_click"}, Description: "How files and folders are opened in the file browser"}, FormElement{Name: "upload_button", Type: "boolean", Default: false, Description: "Display the upload button on any device"}, FormElement{Name: "upload_pool_size", Type: "number", Default: 15, Description: "Maximum number of files upload in parallel. Default: 15"}, FormElement{Name: "upload_chunk_size", Type: "number", Default: 0, Description: "Size of Chunks for Uploads in MB."}, FormElement{Name: "buffer_size", Type: "select", Default: "medium", Opts: []string{"small", "medium", "large"}, Description: "I/O buffer size for transfers. Larger buffers boost throughput on 20 GbE+ networks but use more memory."}, FormElement{Name: "filepage_default_view", Type: "select", Default: "grid", Opts: []string{"list", "grid"}, Description: "Default layout for files and folder on the file page"}, FormElement{Name: "filepage_default_sort", Type: "select", Default: "type", Opts: []string{"type", "date", "name"}, Description: "Default order for files and folder on the file page"}, FormElement{Name: "cookie_timeout", Type: "number", Default: 60 * 24 * 7, Description: "Authentication Cookie expiration in minutes. Default: 60 * 24 * 7 = 1 week"}, FormElement{Name: "extended_session", Type: "boolean", Default: false, Description: "Store extra auth data in session"}, FormElement{Name: "custom_css", Type: "long_text", Default: "", Description: "Setcustom css code for your instance"}, }, }, Form{ Title: "features", Form: []Form{ Form{ Title: "api", Elmnts: []FormElement{ FormElement{Name: "enable", Type: "boolean", Default: true, Description: "Enable/Disable the API"}, }, }, Form{ Title: "share", Elmnts: []FormElement{ FormElement{Name: "enable", Type: "boolean", Default: true, Description: "Enable/Disable the share feature"}, FormElement{Name: "default_access", Type: "select", Default: "editor", Opts: []string{"editor", "viewer"}, Description: "Default access for shared links"}, FormElement{Name: "redirect", Type: "text", Placeholder: "redirection URL", Description: "When set, shared links will perform a redirection to another link. Example: https://example.com?full_path={{path}}"}, }, }, Form{ Title: "protection", Elmnts: []FormElement{ FormElement{Name: "iframe", Type: "text", Default: "", Description: "list of domains who can use the application from an iframe. eg: https://example.com"}, FormElement{Name: "enable_chromecast", Type: "boolean", Default: true, Description: "Enable users to stream content on a chromecast device. This feature requires the browser to access google's server to download the chromecast SDK."}, FormElement{Name: "signature", Type: "text", Default: "", Description: "Enforce signature when using URL parameters in the authentication process"}, }, }, }, }, Form{ Title: "log", Elmnts: []FormElement{ FormElement{Name: "enable", Type: "enable", Target: []string{"log_level"}, Default: true}, FormElement{Name: "level", Type: "select", Default: defaultValue("INFO", "LOG_LEVEL"), Opts: []string{"DEBUG", "INFO", "WARNING", "ERROR"}, Id: "log_level", Description: "Default: \"INFO\". This setting determines the level of detail at which log events are written to the log file"}, FormElement{Name: "telemetry", Type: "boolean", Default: false, Description: "We won't share anything with any third party. This will only to be used to improve our software"}, }, }, Form{ Title: "email", Elmnts: []FormElement{ FormElement{Name: "server", Type: "text", Default: "smtp.gmail.com", Description: "Address of the SMTP email server.", Placeholder: "Default: smtp.gmail.com"}, FormElement{Name: "port", Type: "number", Default: 587, Description: "Port of the SMTP email server. Eg: 587", Placeholder: "Default: 587"}, FormElement{Name: "username", Type: "text", Description: "The username for authenticating to the SMTP server.", Placeholder: "Eg: username@gmail.com"}, FormElement{Name: "password", Type: "password", Description: "The password associated with the SMTP username.", Placeholder: "Eg: Your google password"}, FormElement{Name: "from", Type: "text", Description: "Email address visible on sent messages.", Placeholder: "Eg: username@gmail.com"}, }, }, Form{ Title: "auth", Elmnts: []FormElement{ FormElement{Name: "admin", Type: "bcrypt", Default: "", Description: "Password of the admin section."}, }, }, }, Conn: []map[string]any{}, } } func (this Form) MarshalJSON() ([]byte, error) { return formToJSON(this, func(el FormElement) any { return el }) } func formToJSON(f Form, fn func(FormElement) any) ([]byte, error) { var buf bytes.Buffer buf.WriteByte('{') first := true for _, el := range f.Elmnts { v := fn(el) if v == nil { continue } if !first { buf.WriteByte(',') } first = false key, _ := json.Marshal(strings.ReplaceAll(el.Name, " ", "_")) val, _ := json.Marshal(v) buf.Write(key) buf.WriteByte(':') buf.Write(val) } for _, sub := range f.Form { subBytes, _ := formToJSON(sub, fn) if bytes.Equal(subBytes, []byte("{}")) { continue } if !first { buf.WriteByte(',') } first = false key, _ := json.Marshal(strings.ReplaceAll(sub.Title, " ", "_")) buf.Write(key) buf.WriteByte(':') buf.Write(subBytes) } buf.WriteByte('}') return buf.Bytes(), nil } func (this *Configuration) Load() error { cFile, err := LoadConfig() if err != nil { Log.Error("config::load %s", err) return err } // Extract enabled backends var d struct { Connections []map[string]any `json:"connections"` } json.Unmarshal(cFile, &d) this.Conn = []map[string]any{} if d.Connections != nil { this.Conn = d.Connections } // Hydrate Config with data coming from the config file var raw map[string]any json.Unmarshal(cFile, &raw) for path, value := range flattenJSON("", raw) { el := this.Get(path) if el.currentElement != nil && el.currentElement.Value != value { el.currentElement.Value = value } } this.cache.Clear() Log.SetVisibility(this.Get("log.level").String()) for _, fn := range Hooks.Get.OnConfig() { fn() } return nil } func flattenJSON(prefix string, m map[string]any) map[string]any { out := map[string]any{} for k, v := range m { key := k if prefix != "" { key = prefix + "." + k } switch val := v.(type) { case map[string]any: for nk, nv := range flattenJSON(key, val) { out[nk] = nv } case []any: default: out[key] = val } } return out } func (this *Configuration) Initialise() { shouldSave := false if env := os.Getenv("ADMIN_PASSWORD"); env != "" { shouldSave = true this.Get("auth.admin").Set(env) } if env := os.Getenv("APPLICATION_URL"); env != "" { shouldSave = true _ = this.Get("general.host").Set(env).String() } if this.Get("general.secret_key").String() == "" { shouldSave = true key := RandomString(16) this.Get("general.secret_key").Set(key) } if shouldSave { this.Save() } InitSecretDerivate(this.Get("general.secret_key").String()) } func (this *Configuration) Save() { this.mu.RLock() formBytes, err := formToJSON(Form{Form: this.Form}, func(el FormElement) any { return el.Value }) conn, _ := json.Marshal(this.Conn) this.mu.RUnlock() if err != nil { Log.Error("config::save marshal %s", err.Error()) return } var buf bytes.Buffer buf.WriteByte('{') inner := formBytes[1 : len(formBytes)-1] if len(inner) > 0 { buf.Write(inner) buf.WriteByte(',') } buf.WriteString(`"connections":`) buf.Write(conn) buf.WriteByte('}') if err := SaveConfig(PrettyPrint(buf.Bytes())); err != nil { Log.Error("config::save %s", err.Error()) } } func (this *Configuration) Export() interface{} { return struct { Editor string `json:"editor"` License string `json:"license"` DisplayHidden bool `json:"display_hidden"` Name string `json:"name"` UploadButton bool `json:"upload_button"` Connections interface{} `json:"connections"` SharedLinkDefaultAccess string `json:"share_default_access"` SharedLinkRedirect string `json:"share_redirect"` Logout string `json:"logout"` MimeTypes map[string]string `json:"mime"` UploadPoolSize int `json:"upload_pool_size"` UploadChunkSize int `json:"upload_chunk_size"` RefreshAfterUpload bool `json:"refresh_after_upload"` FilePageDefaultSort string `json:"default_sort"` FilePageDefaultView string `json:"default_view"` AuthMiddleware []string `json:"auth"` Thumbnailer []string `json:"thumbnailer"` Origin string `json:"origin"` Version string `json:"version"` EnableChromecast bool `json:"enable_chromecast"` OpenMode string `json:"open_mode"` EnableSearch bool `json:"enable_search"` EnableShare bool `json:"enable_share"` EnableTags bool `json:"enable_tags"` }{ Editor: this.Get("general.editor").String(), License: LICENSE, DisplayHidden: this.Get("general.display_hidden").Bool(), Name: this.Get("general.name").String(), UploadButton: this.Get("general.upload_button").Bool(), Connections: this.Conn, SharedLinkDefaultAccess: this.Get("features.share.default_access").String(), SharedLinkRedirect: this.Get("features.share.redirect").String(), Logout: this.Get("general.logout").String(), MimeTypes: AllMimeTypes(), UploadPoolSize: this.Get("general.upload_pool_size").Int(), UploadChunkSize: this.Get("general.upload_chunk_size").Int(), RefreshAfterUpload: this.Get("general.refresh_after_upload").Bool(), FilePageDefaultSort: this.Get("general.filepage_default_sort").String(), FilePageDefaultView: this.Get("general.filepage_default_view").String(), AuthMiddleware: func() []string { if this.Get("middleware.identity_provider.type").String() == "" { return []string{} } return regexp.MustCompile("\\s*,\\s*").Split( this.Get("middleware.attribute_mapping.related_backend").String(), -1, ) }(), Thumbnailer: func() []string { tMap := Hooks.Get.Thumbnailer() out := make([]string, 0, len(tMap)) for k := range tMap { out = append(out, k) } return out }(), Origin: func() string { host := this.Get("general.host").String() if host == "" { return "" } scheme := "http://" if this.Get("general.force_ssl").Bool() { scheme = "https://" } return scheme + host }(), OpenMode: this.Get("general.open_mode").String(), Version: BUILD_REF, EnableChromecast: this.Get("features.protection.enable_chromecast").Bool(), EnableSearch: Hooks.Get.SearchEngine() != nil, EnableShare: this.Get("features.share.enable").Bool(), EnableTags: Hooks.Get.Metadata() != nil, } } func (this *Configuration) Get(key string) *ConfigElement { if tmp, ok := this.cache.Load(key); ok { return &ConfigElement{currentElement: tmp.(*FormElement), cfg: this} } var traverse func(forms *[]Form, path []string) *FormElement traverse = func(forms *[]Form, path []string) *FormElement { if len(path) == 0 { return nil } for i := range *forms { currentForm := (*forms)[i] if currentForm.Title == path[0] { if len(path) == 2 { // we are on a leaf // 1) attempt to get a `formElement` for j, el := range currentForm.Elmnts { if el.Name == path[1] { return &(*forms)[i].Elmnts[j] } } // 2) `formElement` does not exist, let's create it. (*forms)[i].Elmnts = append(currentForm.Elmnts, FormElement{Name: path[1], Type: "hidden"}) return &(*forms)[i].Elmnts[len(currentForm.Elmnts)] } else { // we are NOT on a leaf, let's continue our tree transversal return traverse(&(*forms)[i].Form, path[1:]) } } } // append a new `form` if the current key doesn't exist *forms = append(*forms, Form{Title: path[0]}) return traverse(forms, path) } this.mu.Lock() currentElement := traverse(&this.Form, strings.Split(key, ".")) this.cache.Store(key, currentElement) this.mu.Unlock() return &ConfigElement{currentElement: currentElement, cfg: this} } func (this *ConfigElement) Schema(fn func(*FormElement) *FormElement) *ConfigElement { fn(this.currentElement) this.cfg.cache.Clear() return this } func (this *ConfigElement) Default(value interface{}) *ConfigElement { if this.currentElement == nil { return this } this.cfg.mu.Lock() shouldSave := this.currentElement.Default == nil if shouldSave { this.currentElement.Default = value } else if this.currentElement.Default != value { Log.Debug("Attempt to set multiple default config value => %+v", this.currentElement) } this.cfg.mu.Unlock() if shouldSave { this.cfg.Save() } return this } func (this *ConfigElement) Set(value interface{}) *ConfigElement { if this.currentElement == nil { return this } this.cfg.mu.Lock() changed := this.currentElement.Value != value if changed { this.currentElement.Value = value this.cfg.cache.Clear() } this.cfg.mu.Unlock() if changed { this.cfg.Save() } return this } func (this *ConfigElement) String() string { switch v := this.Interface().(type) { case string: return v case []byte: return string(v) } return "" } func (this *ConfigElement) Int() int { switch v := this.Interface().(type) { case float64: return int(v) case int64: return int(v) case int: return v } return 0 } func (this *ConfigElement) Bool() bool { if v, ok := this.Interface().(bool); ok { return v } return false } func (this *ConfigElement) Interface() interface{} { if this.currentElement == nil { return nil } this.cfg.mu.RLock() el := *this.currentElement this.cfg.mu.RUnlock() if el.Value == nil { return el.Default } return el.Value } func (this *Configuration) MarshalJSON() ([]byte, error) { username := "n/a" if u, err := user.Current(); err == nil { if u.Username != "" { username = u.Username } else { username = u.Name } } return Form{Form: append(this.Form, Form{ Title: "constant", Elmnts: []FormElement{ {Name: "user", Type: "boolean", ReadOnly: true, Value: username}, {Name: "license", Type: "text", ReadOnly: true, Value: LICENSE}, }, })}.MarshalJSON() } func defaultValue(dval string, envName string) string { if val := os.Getenv(envName); val != "" { return val } return dval } ================================================ FILE: server/common/config_state.go ================================================ package common /* * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNI * WARNING - CHANGE IN THIS FILE CAN SILENTLY BREAK OTHER INSTALLATION - WARNING * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARN * * Some contributors wanted to be able to load and persist config in other system * like S3 and provide custom encryption layer on top of it. Those contributors have * custom plugins which run generators that override this file before the build is * generated. Indeed for that specific use case we couldn't extend the runtime plugin * mechanism so had to fallback to this approach which would set the config loader at * build time, hence this warning. */ import ( "fmt" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "io" "os" ) var ( configKeysToEncrypt []string = []string{ "middleware.identity_provider.params", "middleware.attribute_mapping.params", } config_path func() string ) func init() { config_path = func() string { return GetAbsolutePath(CONFIG_PATH, "config.json") } } func LoadConfig() ([]byte, error) { file, err := os.OpenFile(config_path(), os.O_RDONLY, os.ModePerm) if err != nil { if os.IsNotExist(err) { os.MkdirAll(GetAbsolutePath(CONFIG_PATH), os.ModePerm) return []byte(""), nil } return nil, err } cFile, err := io.ReadAll(file) file.Close() if err != nil { return nil, err } configStr := string(cFile) for _, jsonPathWithEncryptedData := range configKeysToEncrypt { p := gjson.Get(configStr, jsonPathWithEncryptedData).String() if p == "" { continue } key := os.Getenv("CONFIG_SECRET") if key == "" { InitSecretDerivate(gjson.Get(configStr, "general.secret_key").String()) key = SECRET_KEY_DERIVATE_FOR_PROOF } t, err := DecryptString(Hash(key, 16), p) if err != nil { Log.Warning("common::config_state::load cannot decrypt config path '%s': %s", jsonPathWithEncryptedData, err.Error()) continue } val, err := sjson.Set(configStr, jsonPathWithEncryptedData, t) if err != nil { Log.Warning("common::config_state::load cannot put json value in config '%s': %s", jsonPathWithEncryptedData, err.Error()) continue } configStr = val } return []byte(configStr), nil } func SaveConfig(v []byte) error { file, err := os.Create(config_path()) if err != nil { return fmt.Errorf( APPNAME+" needs to be able to create and edit its configuration, but it currently cannot. "+ "Change the permissions to allow writing to `%s`", config_path(), ) } configStr := string(v) for _, jsonPathWithEncryptedData := range configKeysToEncrypt { key := os.Getenv("CONFIG_SECRET") if key == "" { key = SECRET_KEY_DERIVATE_FOR_PROOF } p := gjson.Get(configStr, jsonPathWithEncryptedData).String() if p == "" { continue } t, err := EncryptString(Hash(key, 16), p) if err != nil { Log.Warning("common::config_state::save cannot encrypt config path '%s': %s", jsonPathWithEncryptedData, err.Error()) continue } val, err := sjson.Set(configStr, jsonPathWithEncryptedData, t) if err != nil { Log.Warning("common::config_state::save cannot put json value in config '%s': %s", jsonPathWithEncryptedData, err.Error()) continue } configStr = val } file.Write(PrettyPrint([]byte(configStr))) if err = file.Sync(); err != nil { file.Close() return err } return file.Close() } ================================================ FILE: server/common/constants.go ================================================ package common import ( "os" "path/filepath" "strings" ) //go:generate go run ../generator/constants.go var ( APP_VERSION = "v0.6" COOKIE_NAME_AUTH = "auth" COOKIE_NAME_PROOF = "proof" COOKIE_NAME_ADMIN = "admin" COOKIE_PATH_ADMIN = "/admin/api/" COOKIE_PATH = "/api/" URL_SETUP = "/admin/setup" ) var ( CONFIG_PATH = "state/config/" CERT_PATH = "state/certs/" PLUGIN_PATH = "state/plugins/" DB_PATH = "state/db/" FTS_PATH = "state/search/" LOG_PATH = "state/log/" TMP_PATH = "cache/" ) func init() { // STEP1: setup app rootPath := "data/" if p := os.Getenv("FILESTASH_PATH"); p != "" { rootPath = p } LOG_PATH = filepath.Join(rootPath, LOG_PATH) CONFIG_PATH = filepath.Join(rootPath, CONFIG_PATH) DB_PATH = filepath.Join(rootPath, DB_PATH) FTS_PATH = filepath.Join(rootPath, FTS_PATH) CERT_PATH = filepath.Join(rootPath, CERT_PATH) TMP_PATH = filepath.Join(rootPath, TMP_PATH) PLUGIN_PATH = filepath.Join(rootPath, PLUGIN_PATH) BASE = strings.TrimSuffix(os.Getenv("FILESTASH_BASE"), "/") COOKIE_PATH_ADMIN = WithBase(COOKIE_PATH_ADMIN) COOKIE_PATH = WithBase(COOKIE_PATH) URL_SETUP = WithBase(URL_SETUP) // STEP2: initialise the config os.MkdirAll(GetAbsolutePath(CERT_PATH), os.ModePerm) os.MkdirAll(GetAbsolutePath(DB_PATH), os.ModePerm) os.MkdirAll(GetAbsolutePath(FTS_PATH), os.ModePerm) os.MkdirAll(GetAbsolutePath(LOG_PATH), os.ModePerm) os.MkdirAll(GetAbsolutePath(PLUGIN_PATH), os.ModePerm) os.RemoveAll(GetAbsolutePath(TMP_PATH)) os.MkdirAll(GetAbsolutePath(TMP_PATH), os.ModePerm) } var ( APPNAME string = "Filestash" BASE string BUILD_REF string BUILD_DATE string LICENSE string = "agpl" SECRET_KEY string SECRET_KEY_DERIVATE_FOR_PROOF string SECRET_KEY_DERIVATE_FOR_ADMIN string SECRET_KEY_DERIVATE_FOR_USER string SECRET_KEY_DERIVATE_FOR_HASH string SECRET_KEY_DERIVATE_FOR_SIGNATURE string ) func InitSecretDerivate(secret string) { SECRET_KEY = secret SECRET_KEY_DERIVATE_FOR_PROOF = Hash("PROOF_"+SECRET_KEY, len(SECRET_KEY)) SECRET_KEY_DERIVATE_FOR_ADMIN = Hash("ADMIN_"+SECRET_KEY, len(SECRET_KEY)) SECRET_KEY_DERIVATE_FOR_USER = Hash("USER_"+SECRET_KEY, len(SECRET_KEY)) SECRET_KEY_DERIVATE_FOR_HASH = Hash("HASH_"+SECRET_KEY, len(SECRET_KEY)) SECRET_KEY_DERIVATE_FOR_SIGNATURE = Hash("SGN_"+SECRET_KEY, len(SECRET_KEY)) } func WithBase(href string) string { if BASE == "" { return href } return BASE + href } func TrimBase(href string) string { if BASE == "" { return href } return strings.TrimPrefix(href, BASE) } func IsWhiteLabel() bool { return APPNAME != "Filestash" } func WhiteLabelText(a, b string) string { if IsWhiteLabel() { return b } return a } ================================================ FILE: server/common/crypto.go ================================================ package common import ( "bytes" "compress/zlib" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "hash/fnv" "io" "math/big" mathrand "math/rand" "os" "runtime" "sort" "sync" ) var ( Letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") GCMNonce NonceGenerator = NewNonceGenerator(12) ) func EncryptString(secret string, data string) (string, error) { d, err := compress([]byte(data)) if err != nil { return "", err } d, err = EncryptAESGCM([]byte(secret), d) if err != nil { return "", err } return base64.URLEncoding.EncodeToString(d), nil } func DecryptString(secret string, data string) (string, error) { d, err := base64.URLEncoding.DecodeString(data) if err != nil { return "", err } d, err = DecryptAESGCM([]byte(secret), d) if err != nil { return "", err } d, err = decompress(d) if err != nil { return "", err } return string(d), nil } func Hash(str string, n int) string { hasher := sha256.New() hasher.Write([]byte(str)) return hashSize(hasher.Sum(nil), n) } func QuickHash(str string, n int) string { hasher := fnv.New64() hasher.Write([]byte(str)) return hashSize(hasher.Sum(nil), n) } func HashStream(r io.Reader, n int) string { hasher := sha256.New() io.Copy(hasher, r) h := hex.EncodeToString(hasher.Sum(nil)) if n == 0 { return h } else if n >= len(h) { return h } return h[0:n] } func hashSize(b []byte, n int) string { h := "" for i := 0; i < len(b); i++ { if n > 0 && len(h) >= n { break } h += ReversedBaseChange(Letters, int(b[i])) } if len(h) > n { return h[0 : len(h)-1] } return h } func ReversedBaseChange(alphabet []rune, i int) string { str := "" for { str += string(alphabet[i%len(alphabet)]) i = i / len(alphabet) if i == 0 { break } } return str } func RandomString(n int) string { b := make([]rune, n) for i := range b { max := *big.NewInt(int64(len(Letters))) r, err := rand.Int(rand.Reader, &max) if err != nil { b[i] = Letters[0] } else { b[i] = Letters[r.Int64()] } } return string(b) } func QuickString(n int) string { b := make([]rune, n) for i := range b { b[i] = Letters[mathrand.Intn(len(Letters))] } return string(b) } func EncryptAESGCM(key []byte, plaintext []byte) ([]byte, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(c) if err != nil { return nil, err } nonce := GCMNonce.Next() if gcm.NonceSize() != len(nonce) { Log.Error("common::crypto nonce size isn't '12' but '%d'", gcm.NonceSize()) return nil, ErrNotValid } return gcm.Seal(nonce, nonce, plaintext, nil), nil } func DecryptAESGCM(key []byte, ciphertext []byte) ([]byte, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(c) if err != nil { return nil, err } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, NewError("ciphertext too short", 500) } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] return gcm.Open(nil, nonce, ciphertext, nil) } func compress(something []byte) ([]byte, error) { var b bytes.Buffer w := zlib.NewWriter(&b) w.Write(something) w.Close() return b.Bytes(), nil } func decompress(something []byte) ([]byte, error) { b := bytes.NewBuffer(something) r, err := zlib.NewReader(b) if err != nil { return []byte(""), nil } r.Close() return io.ReadAll(r) } func sign(something []byte) ([]byte, error) { return something, nil } func verify(something []byte) ([]byte, error) { return something, nil } // Create a unique ID that can be use to identify different session func GenerateID(params map[string]string) string { p := "" orderedKeys := make([]string, len(params)) for key, _ := range params { orderedKeys = append(orderedKeys, key) } sort.Strings(orderedKeys) for _, key := range orderedKeys { switch key { case "password": case "path": case "session": case "timestamp": default: if val := params[key]; val != "" { p += key + "=>" + params[key] + ", " } } } if p == "" { return "na" } p += "salt=>" + SECRET_KEY return Hash(p, 20) } // Create an ID that identify a machine func GenerateMachineID() string { if runtime.GOOS == "linux" { if f, err := os.OpenFile("/etc/machine-id", os.O_RDONLY, os.ModePerm); err == nil { defer f.Close() b := make([]byte, 32) if _, err = f.Read(b); err == nil { return string(b) } } else if f, err := os.OpenFile("/var/lib/dbus/machine-id", os.O_RDONLY, os.ModePerm); err == nil { defer f.Close() b := make([]byte, 32) if _, err = f.Read(b); err == nil { return string(b) } } } return "na" } type NonceGenerator struct { current []byte count int *sync.Mutex } func NewNonceGenerator(size int) NonceGenerator { firstNonce := make([]byte, size) io.ReadFull(rand.Reader, firstNonce) var m sync.Mutex return NonceGenerator{firstNonce, size, &m} } func (this *NonceGenerator) Next() []byte { this.Lock() newNonce := make([]byte, this.count) for i := len(this.current) - 1; i >= 0; i-- { if this.current[i] < 255 { this.current[i] += 1 break } this.current[i] = 0 } newNonce = this.current this.Unlock() return newNonce } ================================================ FILE: server/common/debug.go ================================================ package common import ( "fmt" "runtime" ) func PrintMemUsage() { var m runtime.MemStats runtime.ReadMemStats(&m) // For info on each, see: https://golang.org/pkg/runtime/#MemStats fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc)) fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) fmt.Printf("\tObjects = %d", m.HeapObjects) fmt.Printf("\tNumGC = %v\n", m.NumGC) } func bToMb(b uint64) uint64 { return b / 1024 / 1024 } ================================================ FILE: server/common/default.go ================================================ package common import ( "crypto/tls" "fmt" "net" "net/http" "time" ) var USER_AGENT = fmt.Sprintf("Filestash/%s.%s (http://filestash.app)", APP_VERSION, BUILD_DATE) func init() { if IsWhiteLabel() { USER_AGENT = APPNAME } } var HTTPClient = http.Client{ Timeout: 5 * time.Hour, Transport: NewTransformedTransport(&http.Transport{ Dial: (&net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 10 * time.Second, }).Dial, TLSHandshakeTimeout: 5 * time.Second, IdleConnTimeout: 60 * time.Second, ResponseHeaderTimeout: 60 * time.Second, }), } var HTTP = http.Client{ Timeout: 10000 * time.Millisecond, Transport: NewTransformedTransport(&http.Transport{ Dial: (&net.Dialer{ Timeout: 5000 * time.Millisecond, KeepAlive: 5000 * time.Millisecond, }).Dial, TLSHandshakeTimeout: 5000 * time.Millisecond, IdleConnTimeout: 5000 * time.Millisecond, ResponseHeaderTimeout: 5000 * time.Millisecond, }), } var DefaultTLSConfig = tls.Config{ MinVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, }, PreferServerCipherSuites: true, CurvePreferences: []tls.CurveID{ tls.CurveP256, tls.X25519, }, } func NewTransformedTransport(transport *http.Transport) http.RoundTripper { return &TransformedTransport{transport} } type TransformedTransport struct { Orig http.RoundTripper } func (this *TransformedTransport) RoundTrip(req *http.Request) (*http.Response, error) { req.Header.Add("User-Agent", USER_AGENT) return this.Orig.RoundTrip(req) } ================================================ FILE: server/common/dummy.go ================================================ package common import ( "io" slog "log" ) func NewNilLogger() *slog.Logger { return slog.New(dummyWriter{}, "", slog.LstdFlags) } type dummyWriter struct { io.Writer } func (this dummyWriter) Write(p []byte) (n int, err error) { return len(p), nil } ================================================ FILE: server/common/error.go ================================================ package common import ( "fmt" "net/http" ) func NewError(message string, status int) AppError { if status == 0 { status = 500 } return AppError{message, status} } var ( ErrNotFound = NewError("Not Found", 404) ErrNotAllowed = NewError("Not Allowed", 403) ErrPermissionDenied = NewError("Permission Denied", 403) ErrNotValid = NewError("Not Valid", 405) ErrConflict = NewError("Already exist", 409) ErrNotReachable = NewError("Cannot establish a connection", 502) ErrInvalidPassword = NewError("Invalid Password", 403) ErrNotImplemented = NewError("Not Implemented", 501) ErrNotSupported = NewError("Not supported", 501) ErrFilesystemError = NewError("Can't use filesystem", 503) ErrMissingDependency = NewError("Missing dependency", 424) ErrNotAuthorized = NewError("Not authorised", 401) ErrAuthenticationFailed = NewError("Invalid account", 400) ErrCongestion = NewError("Traffic congestion, try again later", 500) ErrTimeout = NewError("Timeout", 500) ErrInternal = NewError("Internal Error", 500) ) func IsATranslatedError(err error) bool { if err == ErrNotFound || err == ErrNotAllowed || err == ErrPermissionDenied || err == ErrNotValid || err == ErrInvalidPassword || err == ErrNotImplemented || err == ErrNotSupported || err == ErrFilesystemError || err == ErrMissingDependency || err == ErrNotAuthorized || err == ErrAuthenticationFailed || err == ErrCongestion || err == ErrTimeout || err == ErrInternal { return true } return false } type AppError struct { message string status int } func (e AppError) Error() string { return fmt.Sprintf("%s", e.message) } func (e AppError) Status() int { return e.status } func HTTPError(err error) AppError { switch err.Error() { case "Not Found": return ErrNotFound case "Not Allowed": return ErrNotAllowed case "Permission Denied": return ErrPermissionDenied case "Not Valid": return ErrNotValid case "Already exist": return ErrConflict case "Cannot establish a connection": return ErrNotReachable case "Invalid Password": return ErrInvalidPassword case "Not Implemented": return ErrNotImplemented case "Not supported": return ErrNotSupported case "Can't use filesystem": return ErrFilesystemError case "Missing dependency": return ErrMissingDependency case "Not authorised": return ErrNotAuthorized case "Invalid account": return ErrAuthenticationFailed case "Traffic congestion, try again later": return ErrCongestion case "Timeout": return ErrTimeout case "Internal Error": return ErrInternal default: return NewError(err.Error(), http.StatusBadRequest) } } func HTTPFriendlyStatus(n int) string { if n < 400 && n > 600 { return "Humm" } switch n { case 400: return "Bad Request" case 401: return "Unauthorized" case 402: return "Payment Required" case 403: return "Forbidden" case 404: return "Not Found" case 405: return "Not Allowed" case 406: return "Not Acceptable" case 407: return "Authentication Required" case 408: return "Timeout" case 409: return "Conflict" case 410: return "Gone" case 411: return "Length Required" case 412: return "Failed" case 413: return "Too Large" case 414: return "URI Too Long" case 415: return "Unsupported Media" case 416: return "Not Like This" case 417: return "Unexpected" case 418: return "I'm a teapot" case 421: return "Redirection Problem" case 422: return "Unprocessable" case 423: return "Locked" case 424: return "Failed Dependency" case 426: return "Upgrade Required" case 428: return "Need Something" case 429: return "Too Many Requests" case 431: return "Request Too Large" case 451: return "Not Available" case 500: return "Internal Server Error" case 501: return "Not Implemented" case 502: return "Bad Gateway" case 503: return "Service Unavailable" case 504: return "Gateway Timeout" case 505: return "Unsupported HTTP Version" case 506: return "Need To Negotiate" case 507: return "Insufficient Storage" case 508: return "Loop Detected" case 510: return "Not Extended" case 511: return "Authentication Required" default: return "Oops" } } ================================================ FILE: server/common/files.go ================================================ package common import ( "errors" "os" "path" "path/filepath" "strings" "github.com/bmatcuk/doublestar/v4" ) var MOCK_CURRENT_DIR string func GetCurrentDir() string { if MOCK_CURRENT_DIR != "" { return MOCK_CURRENT_DIR } ex, _ := os.Executable() return filepath.Dir(ex) } func GetAbsolutePath(base string, opts ...string) string { fullPath := base if strings.HasPrefix(base, "/") == false { // relative filepath are relative to the binary fullPath = filepath.Join(GetCurrentDir(), base) } if len(opts) == 0 { return fullPath } return filepath.Join(append([]string{fullPath}, opts...)...) } func IsDirectory(path string) bool { if path == "" { return false } if path[len(path)-1:] != "/" { return false } return true } /* * Join 2 path together, result has a file */ func JoinPath(base, file string) string { filePath := path.Join(base, file) if strings.HasPrefix(filePath, base) == false { return base } return filePath } func EnforceDirectory(path string) string { if path == "" { return "/" } else if path[len(path)-1:] == "/" { return path } return path + "/" } func SplitPath(path string) (root string, filename string) { if path == "" { path = "/" } if IsDirectory(path) == false { filename = filepath.Base(path) } if root = strings.TrimSuffix(path, filename); root == "" { root = "/" } return root, filename } func SafeOsMkdir(path string, mode os.FileMode) error { if err := safePath(path); err != nil { Log.Debug("common::files safeOsMkdir err[%s] path[%s]", err.Error(), path) return ErrFilesystemError } return processError(os.Mkdir(path, mode)) } func SafeOsRemove(path string) error { if err := safePath(path); err != nil { Log.Debug("common::files safeOsRemove err[%s] path[%s]", err.Error(), path) return ErrFilesystemError } return processError(os.Remove(path)) } func SafeOsRemoveAll(path string) error { if err := safePath(path); err != nil { Log.Debug("common::files safeOsRemoveAll err[%s] path[%s]", err.Error(), path) return ErrFilesystemError } return processError(os.RemoveAll(path)) } func SafeOsRename(from string, to string) error { if err := safePath(from); err != nil { Log.Debug("common::files safeOsRename err[%s] from[%s]", err.Error(), from) return ErrFilesystemError } else if err := safePath(to); err != nil { Log.Debug("common::files safeOsRemove err[%s] to[%s]", err.Error(), to) return ErrFilesystemError } return processError(os.Rename(from, to)) } func GlobMatch(pattern, name string) bool { m, _ := doublestar.Match(pattern, name) return m } func safePath(path string) error { p, err := filepath.EvalSymlinks(path) if err != nil { if errors.Is(err, os.ErrNotExist) == false { return err } parentPath := filepath.Join(path, "../") return safePath(parentPath) } if p != filepath.Clean(path) { Log.Debug("common::files safePath path[%s] p[%s]", path, p) return ErrFilesystemError } return nil } func processError(err error) error { if err == nil { return nil } if pe, ok := err.(*os.PathError); ok { return pe.Err } if le, ok := err.(*os.LinkError); ok { return le.Err } return err } ================================================ FILE: server/common/files_all.go ================================================ // +build !linux package common import ( "os" ) func SafeOsOpenFile(path string, flag int, perm os.FileMode) (*os.File, error) { if err := safePath(path); err != nil { Log.Debug("common::files safeOsOpenFile err[%s] path[%s]", err.Error(), path) return nil, ErrFilesystemError } return os.OpenFile(path, flag, perm) } ================================================ FILE: server/common/files_linux.go ================================================ package common import ( "errors" "io/fs" "os" "syscall" ) func SafeOsOpenFile(path string, flag int, perm os.FileMode) (*os.File, error) { if err := safePath(path); err != nil { Log.Debug("common::files safeOsOpenFile err[%s] path[%s]", err.Error(), path) return nil, ErrFilesystemError } f, err := os.OpenFile(path, flag|syscall.O_NOFOLLOW, perm) if err != nil { if errors.Is(err, fs.ErrNotExist) { return nil, ErrNotFound } return nil, processError(err) } return f, err } ================================================ FILE: server/common/log.go ================================================ package common import ( "fmt" slog "log" "os" "strings" "time" ) var ( Log = &log{enable: true} logfile *os.File ) func InitLogger() (err error) { logfile, err = os.OpenFile(GetAbsolutePath(LOG_PATH, "access.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { slog.Printf("ERROR log file: %+v", err) return err } logfile.WriteString("") return nil } type log struct { enable bool debug bool info bool warn bool error bool } func (l *log) Info(format string, v ...interface{}) { if l.info && l.enable { message := fmt.Sprintf("%s SYST INFO ", l.now()) message = fmt.Sprintf(message+format+"\n", v...) logfile.WriteString(message) fmt.Print(strings.Replace(message, "%", "%%", -1)) } } func (l *log) Warning(format string, v ...interface{}) { if l.warn && l.enable { message := fmt.Sprintf("%s SYST WARN ", l.now()) message = fmt.Sprintf(message+format+"\n", v...) logfile.WriteString(message) fmt.Print(strings.Replace(message, "%", "%%", -1)) } } func (l *log) Error(format string, v ...interface{}) { if l.error && l.enable { message := fmt.Sprintf("%s SYST ERROR ", l.now()) message = fmt.Sprintf(message+format+"\n", v...) logfile.WriteString(message) fmt.Print(strings.Replace(message, "%", "%%", -1)) } } func (l *log) Debug(format string, v ...interface{}) { if l.debug && l.enable { message := fmt.Sprintf("%s SYST DEBUG ", l.now()) message = fmt.Sprintf(message+format+"\n", v...) logfile.WriteString(message) fmt.Print(strings.Replace(message, "%", "%%", -1)) } } func (l *log) Stdout(format string, v ...interface{}) { message := fmt.Sprintf("%s ", l.now()) message = fmt.Sprintf(message+format+"\n", v...) logfile.WriteString(message) fmt.Print(strings.Replace(message, "%", "%%", -1)) } func (l *log) now() string { return time.Now().Format("2006/01/02 15:04:05") } func (l *log) Close() { logfile.Close() } func (l *log) SetVisibility(str string) { switch str { case "WARNING": l.debug = false l.info = false l.warn = true l.error = true case "ERROR": l.debug = false l.info = false l.warn = false l.error = true case "DEBUG": l.debug = true l.info = true l.warn = true l.error = true case "INFO": l.debug = false l.info = true l.warn = true l.error = true default: l.debug = false l.info = true l.warn = true l.error = true } } func (l *log) Enable(val bool) { l.enable = val } ================================================ FILE: server/common/mime.go ================================================ package common import ( "path/filepath" "strings" ) //go:generate go run ../generator/mime.go var MimeTypes map[string]string = make(map[string]string, 0) func GetMimeType(p string) string { ext := filepath.Ext(p) if ext != "" { ext = ext[1:] } ext = strings.ToLower(ext) mType := MimeTypes[ext] if mType == "" { return "application/octet-stream" } return mType } func AllMimeTypes() map[string]string { return MimeTypes } ================================================ FILE: server/common/plugin.go ================================================ package common import ( "bytes" "context" "io" "io/fs" "net/http" "path/filepath" "sort" "strings" "github.com/gorilla/mux" ) type Register struct{} type Get struct{} var Hooks = struct { Get Get Register Register }{ Get: Get{}, Register: Register{}, } type Options struct { ID string } type Option func(*Options) func WithID(id string) Option { return func(o *Options) { o.ID = id } } /* * ProcessFileContentBeforeSend is a processing hooks used in plugins like: * 1. pluggable image transcoding service: plg_image_light, plg_image_bimg, plg_image_golang * 2. video transcoding service: plg_video_transcode * 3. disallow certain type of file: plg_security_svg */ var process_file_content_before_send []func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, bool, error) func (this Register) ProcessFileContentBeforeSend(fn func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, bool, error)) { process_file_content_before_send = append(process_file_content_before_send, fn) } func (this Get) ProcessFileContentBeforeSend() []func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, bool, error) { return process_file_content_before_send } /* * HttpEndpoint is a hook that makes it possible to register new endpoint in the application. * It is used in plugin like: * 1. plg_video_transcoder to server the transcoded video segment via hls * 2. plg_editor_onlyoffice to server the content for a custom type in an iframe * 3. plg_handler_syncthing to create better integration with syncthing * 4. plg_handler_console to server a full blown console for debugging the application */ var http_endpoint []func(*mux.Router) error func (this Register) HttpEndpoint(fn func(*mux.Router) error) { http_endpoint = append(http_endpoint, fn) } func (this Get) HttpEndpoint() []func(*mux.Router) error { return http_endpoint } /* * Override some urls with static content. The main use case for this is to enable * plugins to change the frontend code and overwrite some core components */ func (this Register) Static(www fs.FS, chroot string) { fs.WalkDir(www, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } else if d.IsDir() { return nil } this.HttpEndpoint(func(r *mux.Router) error { r.PathPrefix("/" + strings.TrimPrefix(path, chroot)).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { f, err := www.Open(path) if err != nil { w.Header().Set("Content-Type", "text/plain") w.Write([]byte("plugin.go::static::err " + err.Error())) return } w.Header().Set("Content-Type", GetMimeType(filepath.Ext(path))) io.Copy(w, f) f.Close() }) return nil }) return nil }) } /* * Starter is the meat that let us connect to a wide variety of server like: * - plg_starter_http which is the default that server the application under 8334 * - plg_starter_tor to serve the application via tor * - plg_starter_web that create ssl certificate via letsencrypt * - plg_started_http2 to create an HTTP2 server * - ... */ var starter_process func(context.Context, *mux.Router) func (this Register) Starter(fn func(context.Context, *mux.Router)) { starter_process = fn } func (this Get) Starter() func(context.Context, *mux.Router) { return starter_process } /* * AuthenticationMiddleware is what enabled us to authenticate user via different means: * - plg_authentication_admin to enable connection to an admin * - plg_authentication_saml * - plg_authentication_openid * - plg_authentication_ldap * - ... */ var authentication_middleware map[string]IAuthentication = make(map[string]IAuthentication, 0) func (this Register) AuthenticationMiddleware(id string, am IAuthentication) { authentication_middleware[id] = am } func (this Get) AuthenticationMiddleware() map[string]IAuthentication { return authentication_middleware } /* * AuthorisationMiddleware is to enable custom rule for authorisation. eg: anonymous can see, registered * user can see/edit some files but not some others, admin can do everything */ var authorisation_middleware []IAuthorisation func (this Register) AuthorisationMiddleware(a IAuthorisation) { authorisation_middleware = append(authorisation_middleware, a) } func (this Get) AuthorisationMiddleware() []IAuthorisation { return authorisation_middleware } /* * Search is the pluggable search mechanism. By default, there's 2 options: * - plg_search_stateless which does stateless search based on filename only * - plg_search_statefull which does full text search with a sqlite data store * The idea here is to enable different type of usage like leveraging elastic search or solr * with custom stuff around it */ var search ISearch func (this Register) SearchEngine(s ISearch) { search = s } func (this Get) SearchEngine() ISearch { return search } /* * The idea here is to enable plugin to register their own thumbnailing process, typically * images but could also be videos, pdf, excel documents, ... */ var thumbnailer map[string]IThumbnailer = make(map[string]IThumbnailer) func (this Register) Thumbnailer(mimeType string, fn IThumbnailer) { thumbnailer[mimeType] = fn } func (this Get) Thumbnailer() map[string]IThumbnailer { return thumbnailer } /* * Pluggable Audit interface */ var audit IAuditPlugin func (this Register) AuditEngine(a IAuditPlugin) { audit = a } func (this Get) AuditEngine() IAuditPlugin { return audit } /* * UI Overrides * They are the means by which server plugin change the frontend behaviors. */ var overrides []string func (this Register) FrontendOverrides(url string) { overrides = append(overrides, url) } func (this Get) FrontendOverrides() []string { return overrides } var xdg_open []string func (this Register) XDGOpen(jsString string) { xdg_open = append(xdg_open, jsString) } func (this Get) XDGOpen() []string { return xdg_open } var cssOverride = map[string]string{} func (this Register) CSS(stylesheet string, opts ...Option) { // idempotent options := Options{} for _, opt := range opts { opt(&options) } if options.ID == "" { options.ID = QuickHash(stylesheet, 10) } cssOverride[options.ID] = stylesheet } func (this Get) CSS() string { s := "" for _, v := range cssOverride { s += v + "\n" } return s } var favicon struct { binary []byte mime string } func (this Register) Favicon(binary []byte) { favicon.binary = binary favicon.mime = "image/svg+xml" if bytes.HasPrefix(binary, []byte{0x00, 0x00, 0x01, 0x00}) { favicon.mime = "image/x-icon" } else if bytes.HasPrefix(binary, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}) { favicon.mime = "image/png" } else if bytes.HasPrefix(binary, []byte{0x47, 0x49, 0x46, 0x38}) { favicon.mime = "image/vnd.microsoft.icon" } } func (this Get) Favicon() ([]byte, string) { return favicon.binary, favicon.mime } const OverrideVideoSourceMapper = "/overrides/video-transcoder.js" var afterload []func() func (this Register) Onload(fn func()) { afterload = append(afterload, fn) } func (this Get) Onload() []func() { return afterload } var onquit []func() func (this Register) OnQuit(fn func()) { onquit = append(onquit, fn) } func (this Get) OnQuit() []func() { return onquit } var configChange []func() func (this Register) OnConfig(fn func()) { configChange = append(configChange, fn) } func (this Get) OnConfig() []func() { return configChange } var middlewares []func(HandlerFunc) HandlerFunc func (this Register) Middleware(m func(HandlerFunc) HandlerFunc) { middlewares = append(middlewares, m) } func (this Get) Middleware() []func(HandlerFunc) HandlerFunc { return middlewares } var staticOverrides = map[string][]byte{} func (this Register) StaticPatch(patchFile []byte, opts ...Option) { // idempotent options := Options{} for _, opt := range opts { opt(&options) } if options.ID == "" { options.ID = QuickHash(string(patchFile), 10) } staticOverrides[options.ID] = patchFile } func (this Get) StaticPatch() [][]byte { s := [][]byte{} for _, v := range staticOverrides { s = append(s, v) } return s } var meta IMetadata func (this Register) Metadata(m IMetadata) { meta = m } func (this Get) Metadata() IMetadata { return meta } var workflow_triggers []ITrigger func (this Register) WorkflowTrigger(t ITrigger) { workflow_triggers = append(workflow_triggers, t) sort.Slice(workflow_triggers, func(i, j int) bool { return workflow_triggers[i].Manifest().Order < workflow_triggers[j].Manifest().Order }) } func (this Get) WorkflowTriggers() []ITrigger { return workflow_triggers } var workflow_actions []IAction func (this Register) WorkflowAction(a IAction) { workflow_actions = append(workflow_actions, a) sort.Slice(workflow_actions, func(i, j int) bool { return workflow_actions[i].Manifest().Order < workflow_actions[j].Manifest().Order }) } func (this Get) WorkflowActions() []IAction { return workflow_actions } var directory IDirectoryService func (this Register) DirectoryService(d IDirectoryService) { directory = d } func (this Get) DirectoryService() IDirectoryService { return directory } func init() { Hooks.Register.FrontendOverrides(OverrideVideoSourceMapper) } ================================================ FILE: server/common/recovery.go ================================================ package common import ( "net/http" ) // previous cookie configuration in canary release of 2024/10 break existing cookie and // can introduce weird error when a user has things in cache. // this code will deprecate early 2025 func RecoverFromBadCookie(res http.ResponseWriter) { Log.Debug("common::recovery exec=RecoverFromBadCookie") http.SetCookie(res, &http.Cookie{ Name: "auth", Value: "", MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteStrictMode, Path: WithBase("/api/"), Secure: false, }) } ================================================ FILE: server/common/response.go ================================================ package common import ( "bytes" "compress/gzip" "encoding/json" "net/http" "strings" ) const IndentSize = " " type APISuccessResult struct { Status string `json:"status"` Result interface{} `json:"result,omitempty"` } type APISuccessResults struct { Status string `json:"status"` Results interface{} `json:"results"` } type APISuccessResultsWithMetadata struct { Status string `json:"status"` Results interface{} `json:"results"` Metadata interface{} `json:"permissions,omitempty"` } type APIErrorMessage struct { Status string `json:"status"` Message string `json:"message,omitempty"` } func SendSuccessResult(res http.ResponseWriter, data interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.SetIndent("", IndentSize) encoder.Encode(APISuccessResult{"ok", data}) } func SendSuccessResultWithEtagAndGzip(res http.ResponseWriter, req *http.Request, data interface{}) { var dataToSend []byte var err error if dataToSend, err = json.MarshalIndent(APISuccessResult{"ok", data}, "", IndentSize); err != nil { Log.Warning("common::response Marshal %s", err.Error()) dataToSend = []byte("{}") } mode := "normal" if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") == true { mode = "gzip" } hash := QuickHash(mode+string(dataToSend), 20) if req.Header.Get("If-None-Match") == hash { res.WriteHeader(http.StatusNotModified) return } head := res.Header() head.Set("Etag", hash) if mode == "gzip" { head.Set("Content-Encoding", "gzip") var b bytes.Buffer w, _ := gzip.NewWriterLevel(&b, 1) w.Write(dataToSend) w.Close() dataToSend = b.Bytes() } res.Write(dataToSend) } func SendSuccessResults(res http.ResponseWriter, data interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.SetIndent("", IndentSize) encoder.Encode(APISuccessResults{"ok", data}) } func SendSuccessResultsWithMetadata(res http.ResponseWriter, data interface{}, p interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.SetIndent("", IndentSize) encoder.Encode(APISuccessResultsWithMetadata{"ok", data, p}) } func SendErrorResult(res http.ResponseWriter, err error) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.SetIndent("", IndentSize) obj, ok := err.(interface{ Status() int }) if ok == true { res.WriteHeader(obj.Status()) } else { res.WriteHeader(http.StatusInternalServerError) } m := func(r string) string { if r == "" { return r } return strings.ToUpper(string(r[0])) + string(r[1:]) }(err.Error()) encoder.Encode(APIErrorMessage{"error", m}) } func SendRaw(res http.ResponseWriter, data interface{}) { encoder := json.NewEncoder(res) encoder.SetEscapeHTML(false) encoder.SetIndent("", IndentSize) encoder.Encode(data) } func Page(stuff string) string { if strings.Contains(stuff, "") { stuff += "" } return ` ` + Config.Get("general.name").String() + ` ` + stuff + ` ` } func RedirectPage(url string) string { return ` ` } ================================================ FILE: server/common/ssl/cert.go ================================================ package ssl import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "io" "os" ) func GetCertificate(key *rsa.PrivateKey, root *x509.Certificate) (*x509.Certificate, []byte, error) { if cert, certPEM, err := pullCertificateFromFS(); err == nil { return cert, certPEM, nil } cert, certPEM, err := generateNewCertificate(root, key) if err != nil { Clear() return nil, nil, err } if err = saveCertificateToFS(certPEM); err != nil { Clear() return nil, nil, err } return cert, certPEM, nil } func generateNewCertificate(root *x509.Certificate, key *rsa.PrivateKey) (*x509.Certificate, []byte, error) { certDER, err := x509.CreateCertificate(rand.Reader, root, root, &key.PublicKey, key) if err != nil { return nil, nil, err } cert, err := x509.ParseCertificate(certDER) if err != nil { return nil, nil, err } return cert, pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certDER, }), nil } func pullCertificateFromFS() (*x509.Certificate, []byte, error) { file, err := os.OpenFile(certPEMPath(), os.O_RDONLY, os.ModePerm) if err != nil { return nil, nil, err } defer file.Close() certPEM, err := io.ReadAll(file) if err != nil { return nil, nil, err } block, _ := pem.Decode(certPEM) cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, nil, err } return cert, certPEM, nil } func saveCertificateToFS(certPEM []byte) error { file, err := os.OpenFile(certPEMPath(), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } _, err = file.Write(certPEM) file.Close() return err } func clearCert() { os.Remove(certPEMPath()) } ================================================ FILE: server/common/ssl/generate.go ================================================ package ssl import ( "crypto/rsa" "crypto/tls" "crypto/x509" . "github.com/mickael-kerjean/filestash/server/common" ) func GenerateSelfSigned() (tls.Certificate, *x509.CertPool, error) { var err error var key *rsa.PrivateKey var root *x509.Certificate var keyPEM []byte var certPEM []byte var TLSCert tls.Certificate if key, keyPEM, err = GetPrivateKey(); err != nil { Log.Error("[https] key_generation %v", err) Clear() return TLSCert, nil, err } if root, err = GetRoot(); err != nil { Log.Error("[https] root_certificate %v", err) Clear() return TLSCert, nil, err } if _, certPEM, err = GetCertificate(key, root); err != nil { Log.Error("[https] x509_certificate %v", err) Clear() return TLSCert, nil, err } if TLSCert, err = tls.X509KeyPair(certPEM, keyPEM); err != nil { Log.Error("[https] tls_certificate %v", err) Clear() return TLSCert, nil, err } rootCAs, _ := x509.SystemCertPool() if rootCAs == nil { rootCAs = x509.NewCertPool() } if ok := rootCAs.AppendCertsFromPEM([]byte(certPEM)); ok == false { Log.Error("[https] tls_client") Clear() return TLSCert, rootCAs, err } return TLSCert, rootCAs, nil } ================================================ FILE: server/common/ssl/index.go ================================================ package ssl import ( . "github.com/mickael-kerjean/filestash/server/common" ) var ( keyPEMPath func() string certPEMPath func() string ) func init() { keyPEMPath = func() string { return GetAbsolutePath(CERT_PATH, "key.pem") } certPEMPath = func() string { return GetAbsolutePath(CERT_PATH, "cert.pem") } } func Clear() { clearPrivateKey() clearCert() } ================================================ FILE: server/common/ssl/private.go ================================================ package ssl import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "io" "os" ) func GetPrivateKey() (*rsa.PrivateKey, []byte, error) { if private, privatePEM, err := pullPrivateKeyFromFS(); err == nil { return private, privatePEM, nil } key, keyPEM, err := generateNewPrivateKey() if err != nil { Clear() return nil, nil, err } if err = savePrivateKeyToFS(keyPEM); err != nil { Clear() return nil, nil, err } return key, keyPEM, nil } func generateNewPrivateKey() (*rsa.PrivateKey, []byte, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } return key, pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key), }), nil } func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) { file, err := os.OpenFile(keyPEMPath(), os.O_RDONLY, os.ModePerm) if err != nil { return nil, nil, err } defer file.Close() keyPEM, err := io.ReadAll(file) if err != nil { return nil, nil, err } block, _ := pem.Decode(keyPEM) priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, nil, err } return priv, keyPEM, nil } func savePrivateKeyToFS(privatePEM []byte) error { file, err := os.OpenFile(keyPEMPath(), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } _, err = file.Write(privatePEM) file.Close() if err != nil { return err } return nil } func clearPrivateKey() { os.Remove(keyPEMPath()) } ================================================ FILE: server/common/ssl/root.go ================================================ package ssl import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" "math/big" "net" "time" ) func GetRoot() (*x509.Certificate, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return nil, err } return &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Filestash"}, }, NotBefore: time.Now().Add(-24 * time.Hour), NotAfter: time.Now().Add(24 * 365 * 100 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, IsCA: false, IPAddresses: func() []net.IP { ips := []net.IP{} ifaces, err := net.Interfaces() if err != nil { return []net.IP{net.ParseIP("127.0.0.1")} } for _, i := range ifaces { addrs, err := i.Addrs() if err != nil { return []net.IP{net.ParseIP("127.0.0.1")} } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.IP } ips = append(ips, ip) } } return ips }(), }, nil } ================================================ FILE: server/common/token.go ================================================ package common import ( "time" ) const ( ADMIN_CLAIM = "ADMIN" ) type AdminToken struct { Claim string `json:"token"` Expire time.Time `json:"time"` } func NewAdminToken() AdminToken { return AdminToken{ Claim: ADMIN_CLAIM, Expire: time.Now().Add(time.Hour * 24), } } func (this AdminToken) IsAdmin() bool { if this.Claim != ADMIN_CLAIM { return false } return true } func (this AdminToken) IsValid() bool { if this.Expire.Sub(time.Now()) <= 0 { return false } return true } ================================================ FILE: server/common/types.go ================================================ package common import ( "encoding/json" "io" "net/http" "os" "time" ) type IBackend interface { Init(params map[string]string, app *App) (IBackend, error) Ls(path string) ([]os.FileInfo, error) Stat(path string) (os.FileInfo, error) Cat(path string) (io.ReadCloser, error) Mkdir(path string) error Rm(path string) error Mv(from string, to string) error Save(path string, file io.Reader) error Touch(path string) error LoginForm() Form } type IAuthentication interface { Setup() Form EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) } type IAuthorisation interface { Ls(ctx *App, path string) error Cat(ctx *App, path string) error Stat(ctx *App, path string) error Mkdir(ctx *App, path string) error Rm(ctx *App, path string) error Mv(ctx *App, from string, to string) error Save(ctx *App, path string) error Touch(ctx *App, path string) error } type IFile interface { os.FileInfo Path() string } type ISearch interface { Query(ctx App, basePath string, term string) ([]IFile, error) } type ILogger interface { Debug(format string, v ...interface{}) Info(format string, v ...interface{}) Warning(format string, v ...interface{}) Error(format string, v ...interface{}) Stdout(format string, v ...interface{}) SetVisibility(str string) } type IThumbnailer interface { Generate(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, error) } type IAuditPlugin interface { Query(ctx *App, searchParams map[string]string) (AuditQueryResult, error) } type AuditQueryResult struct { Form *Form `json:"form"` RenderHTML string `json:"render"` } const ( MetaModeTag = 1 << iota MetaModeBookmark MetaModeForm ) type IMetadata interface { Get(ctx *App, path string) ([]FormElement, error) Set(ctx *App, path string, value []FormElement) error Search(ctx *App, path string, facets map[string]any) (map[string][]FormElement, error) } type ITrigger interface { Manifest() WorkflowSpecs Init() (chan ITriggerEvent, error) } type IAction interface { Manifest() WorkflowSpecs Execute(params map[string]string, input map[string]string) (map[string]string, error) } type IDirectoryService interface { Search(query string) ([]DirectoryUser, error) } type DirectoryUser struct { Id string `json:"id"` Name string `json:"name"` Email string `json:"email"` } type ITriggerEvent interface { WorkflowID() string Input() map[string]string } type WorkflowSpecs struct { Name string `json:"name"` Title string `json:"title"` Subtitle string `json:"subtitle"` Icon string `json:"icon"` Specs Form `json:"specs"` Order int `json:"-"` } type File struct { FName string `json:"name"` FType string `json:"type"` FTime int64 `json:"time"` FSize int64 `json:"size"` FPath string `json:"path,omitempty"` Offline bool `json:"offline,omitempty"` } func (f File) Name() string { return f.FName } func (f File) Size() int64 { return f.FSize } func (f File) Mode() os.FileMode { if f.IsDir() { return os.ModeDir } return os.FileMode(0664) } func (f File) ModTime() time.Time { if f.FTime == 0 { t := new(time.Time) return *t } return time.Unix(f.FTime, 0) } func (f File) IsDir() bool { if f.FType != "directory" { return false } return true } func (f File) Sys() interface{} { return f } func (f File) Path() string { return f.FPath } type Metadata struct { CanSee *bool `json:"can_read,omitempty"` CanCreateFile *bool `json:"can_create_file,omitempty"` CanCreateDirectory *bool `json:"can_create_directory,omitempty"` CanRename *bool `json:"can_rename,omitempty"` CanMove *bool `json:"can_move,omitempty"` CanUpload *bool `json:"can_upload,omitempty"` CanDelete *bool `json:"can_delete,omitempty"` CanShare *bool `json:"can_share,omitempty"` HideExtension *bool `json:"hide_extension,omitempty"` RefreshOnCreate *bool `json:"refresh_on_create,omitempty"` Expire *time.Time `json:"-"` } const PASSWORD_DUMMY = "{{PASSWORD}}" type Share struct { Id string `json:"id"` Backend string `json:"-"` Auth string `json:"auth,omitempty"` Path string `json:"path"` Password *string `json:"password,omitempty"` Users *string `json:"users,omitempty"` Expire *int64 `json:"expire,omitempty"` Url *string `json:"url,omitempty"` CanShare bool `json:"can_share"` CanManageOwn bool `json:"can_manage_own"` CanRead bool `json:"can_read"` CanWrite bool `json:"can_write"` CanUpload bool `json:"can_upload"` } func (s Share) IsValid() error { if s.Expire != nil { now := time.Now().UnixNano() / 1000000 if now > *s.Expire { return NewError("Link has expired", 410) } } return nil } func (s *Share) MarshalJSON() ([]byte, error) { p := Share{ s.Id, s.Backend, "", s.Path, func(pass *string) *string { if pass != nil { return NewString(PASSWORD_DUMMY) } return nil }(s.Password), s.Users, s.Expire, s.Url, s.CanShare, s.CanManageOwn, s.CanRead, s.CanWrite, s.CanUpload, } return json.Marshal(p) } func (s *Share) UnmarshallJSON(b []byte) error { var tmp map[string]interface{} if err := json.Unmarshal(b, &tmp); err != nil { return err } for key, value := range tmp { switch key { case "password": s.Password = NewStringpFromInterface(value) case "users": s.Users = NewStringpFromInterface(value) case "expire": s.Expire = NewInt64pFromInterface(value) case "url": s.Url = NewStringpFromInterface(value) case "can_share": s.CanShare = NewBoolFromInterface(value) case "can_manage_own": s.CanManageOwn = NewBoolFromInterface(value) case "can_read": s.CanRead = NewBoolFromInterface(value) case "can_write": s.CanWrite = NewBoolFromInterface(value) case "can_upload": s.CanUpload = NewBoolFromInterface(value) } } return nil } type HandlerFunc func(*App, http.ResponseWriter, *http.Request) type Middleware func(HandlerFunc) HandlerFunc ================================================ FILE: server/common/utils.go ================================================ package common import ( "bytes" "encoding/json" "fmt" "io" "strconv" "strings" ) func NewBool(t bool) *bool { return &t } func NewString(t string) *string { if t == "" { return nil } return &t } func NewInt(t int) *int { return &t } func NewBoolFromInterface(val interface{}) bool { switch val.(type) { case bool: return val.(bool) default: return false } } func NewInt64pFromInterface(val interface{}) *int64 { switch val.(type) { case int64: v := val.(int64) return &v case float64: v := int64(val.(float64)) return &v default: return nil } } func NewStringpFromInterface(val interface{}) *string { switch val.(type) { case string: v := val.(string) return &v default: return nil } } func NewStringFromInterface(val interface{}) string { switch val.(type) { case string: return val.(string) case float64: return fmt.Sprintf("%d", int64(val.(float64))) case bool: return fmt.Sprintf("%t", val.(bool)) case []interface{}: list := val.([]interface{}) strSlice := make([]string, len(list)) for i, el := range list { strSlice[i] = NewStringFromInterface(el) } return strings.Join(strSlice, ",") default: return "" } } func NewReadCloserFromBytes(t []byte) io.ReadCloser { return io.NopCloser(bytes.NewReader(t)) } func NewReadCloserFromReader(r io.Reader) io.ReadCloser { return io.NopCloser(r) } func PrettyPrint(json_dirty []byte) []byte { var json_pretty bytes.Buffer error := json.Indent(&json_pretty, json_dirty, "", " ") if error != nil { return json_dirty } json_pretty.Write([]byte("\n")) return json_pretty.Bytes() } func CookieName(idx int) string { if idx == 0 { return COOKIE_NAME_AUTH } return COOKIE_NAME_AUTH + strconv.Itoa(idx) } ================================================ FILE: server/ctrl/about.go ================================================ package ctrl import ( "fmt" "html" "net/http" "os" "path/filepath" "regexp" "strings" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" ) var listOfPlugins = struct { oss []string enterprise []string custom []string apps []string }{} func InitPluginList(code []byte, plgs map[string]model.PluginImpl) error { listOfPackages := regexp.MustCompile(`\t_?\s*\"(github.com/[^\"]+)`).FindAllStringSubmatch(string(code), -1) for _, packageNameMatch := range listOfPackages { if len(packageNameMatch) != 2 { Log.Error("ctrl::static error=assertion_failed msg=invalid_match_size arg=%d", len(packageNameMatch)) return ErrNotValid } packageName := packageNameMatch[1] packageShortName := filepath.Base(packageName) if strings.HasPrefix(packageName, "github.com/mickael-kerjean/filestash/server/plugin/") { listOfPlugins.oss = append(listOfPlugins.oss, packageShortName) } else if strings.HasPrefix(packageName, "github.com/mickael-kerjean/filestash/filestash-enterprise/plugins/") { listOfPlugins.enterprise = append(listOfPlugins.enterprise, packageShortName) } else if strings.HasPrefix(packageName, "github.com/mickael-kerjean/filestash/filestash-enterprise/customers/") { listOfPlugins.custom = append(listOfPlugins.custom, packageShortName) } else { listOfPlugins.custom = append(listOfPlugins.custom, packageShortName) } } for name, _ := range plgs { listOfPlugins.apps = append(listOfPlugins.apps, name) } return nil } func AboutHandler(ctx *App, res http.ResponseWriter, req *http.Request) { t, _ := template. New("about"). Funcs(map[string]interface{}{ "renderPlugin": func(lstr string, commit string) string { if len(lstr) == 0 { return "N/A" } else if commit == "" { return html.EscapeString(lstr) } list := strings.Split(lstr, " ") for i, _ := range list { list[i] = `` + html.EscapeString(list[i]) + `` } return strings.Join(list, " ") }, }). Parse(Page(`

    {{ .Version }}

    Commit hash {{ .CommitHash }}
    Binary hash {{ index .Checksum 0}}
    Config hash {{ index .Checksum 1}}
    License {{ .License }}
    Plugins STANDARD [{{ renderPlugin (index .Plugins 0) .CommitHash }}]
    ENTERPRISE [{{ renderPlugin (index .Plugins 1) "" }}]
    CUSTOM [{{ renderPlugin (index .Plugins 2) "" }}]
    RUNTIME [{{ renderPlugin (index .Plugins 3) "" }}]
    `)) hashFileContent := func(path string, n int) string { f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return "" } defer f.Close() return HashStream(f, n) } t.Execute(res, struct { Version string CommitHash string Checksum []string License string Plugins []string }{ Version: fmt.Sprintf("Filestash %s.%s", APP_VERSION, BUILD_DATE), CommitHash: BUILD_REF, Checksum: []string{ hashFileContent(GetAbsolutePath("filestash"), 0), hashFileContent(GetAbsolutePath(CONFIG_PATH, "config.json"), 0), }, License: strings.ToUpper(LICENSE), Plugins: []string{ strings.Join(listOfPlugins.oss, " "), strings.Join(listOfPlugins.enterprise, " "), strings.Join(listOfPlugins.custom, " "), strings.Join(listOfPlugins.apps, " "), }, }) } ================================================ FILE: server/ctrl/admin.go ================================================ package ctrl import ( "encoding/json" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" "io" "net/http" "os" "strconv" "time" ) var logpath = GetAbsolutePath(LOG_PATH, "access.log") func AdminSessionGet(ctx *App, res http.ResponseWriter, req *http.Request) { if admin := Config.Get("auth.admin").String(); admin == "" { SendSuccessResult(res, true) return } obfuscate := func() string { c, err := req.Cookie(COOKIE_NAME_ADMIN) if err != nil { return "" } return c.Value }() str, err := DecryptString(SECRET_KEY_DERIVATE_FOR_ADMIN, obfuscate) if err != nil { SendSuccessResult(res, false) return } token := AdminToken{} json.Unmarshal([]byte(str), &token) if token.IsValid() == false { SendSuccessResult(res, false) return } else if token.IsAdmin() == false { SendSuccessResult(res, false) return } SendSuccessResult(res, true) } func AdminSessionAuthenticate(ctx *App, res http.ResponseWriter, req *http.Request) { // Step 1: Deliberatly make the request slower to make hacking attempt harder for the attacker time.Sleep(1500 * time.Millisecond) // Step 2: Make sure current user has appropriate access admin := Config.Get("auth.admin").String() if admin == "" { SendErrorResult(res, NewError("Missing admin account, please contact your administrator", 500)) return } var params map[string]string b, _ := io.ReadAll(req.Body) json.Unmarshal(b, ¶ms) if err := bcrypt.CompareHashAndPassword([]byte(admin), []byte(params["password"])); err != nil { SendErrorResult(res, ErrInvalidPassword) return } // Step 3: Send response to the client body, _ := json.Marshal(NewAdminToken()) obfuscate, err := EncryptString(SECRET_KEY_DERIVATE_FOR_ADMIN, string(body)) if err != nil { SendErrorResult(res, err) return } http.SetCookie(res, &http.Cookie{ Name: COOKIE_NAME_ADMIN, Value: obfuscate, Path: COOKIE_PATH_ADMIN, MaxAge: 60 * 60, // valid for 1 hour SameSite: http.SameSiteStrictMode, }) if req.Header.Get("Accept") == "application/json" { SendSuccessResult(res, map[string]string{"access_token": obfuscate}) return } SendSuccessResult(res, true) } func AdminBackend(ctx *App, res http.ResponseWriter, req *http.Request) { drivers := Backend.Drivers() backends := make(map[string]Form, len(drivers)) for key := range drivers { backends[key] = drivers[key].LoginForm() } SendSuccessResultWithEtagAndGzip(res, req, backends) return } func AdminAuthenticationMiddleware(ctx *App, res http.ResponseWriter, req *http.Request) { drivers := Hooks.Get.AuthenticationMiddleware() middlewares := make(map[string]Form, len(drivers)) for id, driver := range drivers { middlewares[id] = driver.Setup() } SendSuccessResultWithEtagAndGzip(res, req, middlewares) return } func FetchLogHandler(ctx *App, res http.ResponseWriter, req *http.Request) { file, err := os.OpenFile(logpath, os.O_RDONLY, os.ModePerm) if err != nil { SendErrorResult(res, err) return } defer file.Close() maxSize := req.URL.Query().Get("maxSize") if maxSize != "" { cursor := func() int64 { tmp, err := strconv.Atoi(maxSize) if err != nil { return 0 } return int64(tmp) }() for cursor >= 0 { if _, err := file.Seek(-cursor, io.SeekEnd); err != nil { break } char := make([]byte, 1) file.Read(char) if char[0] == 10 || char[0] == 13 { // stop if we find a line break } cursor += 1 } } res.Header().Set("Content-Type", "text/plain") io.Copy(res, file) } func FetchAuditHandler(ctx *App, res http.ResponseWriter, req *http.Request) { plg := Hooks.Get.AuditEngine() if plg == nil { SendErrorResult(res, ErrNotImplemented) return } searchParams := map[string]string{} _get := req.URL.Query() for key, element := range _get { if len(element) == 0 { continue } searchParams[key] = element[0] } result, err := plg.Query(ctx, searchParams) if err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, result) } ================================================ FILE: server/ctrl/config.go ================================================ package ctrl import ( . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" ) var configpath = GetAbsolutePath(CONFIG_PATH, "config.json") func PrivateConfigHandler(ctx *App, res http.ResponseWriter, req *http.Request) { SendSuccessResult(res, &Config) } func PrivateConfigUpdateHandler(ctx *App, res http.ResponseWriter, req *http.Request) { b, _ := io.ReadAll(req.Body) if err := SaveConfig(b); err != nil { SendErrorResult(res, err) return } Config.Load() SendSuccessResult(res, nil) } func PublicConfigHandler(ctx *App, res http.ResponseWriter, req *http.Request) { cfg := Config.Export() SendSuccessResultWithEtagAndGzip(res, req, cfg) } ================================================ FILE: server/ctrl/files.go ================================================ package ctrl import ( "archive/zip" "context" "crypto/sha1" "encoding/base64" "encoding/hex" "fmt" "hash" "hash/crc32" "hash/fnv" "io" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "sync" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" ) type FileInfo struct { Name string `json:"name"` Type string `json:"type"` Size int64 `json:"size"` Time int64 `json:"time"` Mode uint32 `json:"mode,omitempty"` Offline bool `json:"offline,omitempty"` } var ( file_cache AppCache zip_timeout func() int disable_csp func() bool ) func init() { zip_timeout = func() int { return Config.Get("features.protection.zip_timeout").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Default = 60 f.Name = "zip_timeout" f.Type = "number" f.Description = "Timeout when user wants to download or extract a zip" f.Placeholder = "Default: 60seconds" return f }).Int() } disable_csp = func() bool { return Config.Get("features.protection.disable_csp").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Default = false f.Name = "disable_csp" f.Type = "boolean" f.Description = "Disable the content security policy. Unless you 100% trust the content in your storage and want to execute code running from that storage, you shouldn't have this option checked" return f }).Bool() } file_cache = NewAppCache() file_cache.OnEvict(func(key string, value interface{}) { os.RemoveAll(filepath.Join(GetAbsolutePath(TMP_PATH), key)) }) Hooks.Register.Onload(func() { zip_timeout() disable_csp() }) initChunkedUploader() } func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanRead(ctx) == false { if model.CanUpload(ctx) == false { Log.Debug("ls::permission 'permission denied'") SendErrorResult(res, ErrPermissionDenied) return } SendSuccessResults(res, make([]FileInfo, 0)) return } path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("ls::path '%s'", err.Error()) SendErrorResult(res, err) return } var perms Metadata = Metadata{} if obj, ok := ctx.Backend.(interface{ Meta(path string) Metadata }); ok { perms = obj.Meta(path) } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Ls(ctx, path); err != nil { Log.Info("ls::auth '%s'", err.Error()) SendErrorResult(res, err) return } ctx.Context = context.WithValue(ctx.Context, "AUDIT", false) if err = auth.Mkdir(ctx, path); err != nil { perms.CanCreateDirectory = NewBool(false) } if err = auth.Touch(ctx, path); err != nil { perms.CanCreateFile = NewBool(false) } if err = auth.Mv(ctx, path, path); err != nil { perms.CanRename = NewBool(false) perms.CanMove = NewBool(false) } if err = auth.Save(ctx, path); err != nil { perms.CanUpload = NewBool(false) } if err = auth.Rm(ctx, path); err != nil { perms.CanDelete = NewBool(false) } if err = auth.Cat(ctx, path); err != nil { perms.CanSee = NewBool(false) } ctx.Context = context.WithValue(ctx.Context, "AUDIT", nil) } if model.CanEdit(ctx) == false { perms.CanCreateFile = NewBool(false) perms.CanCreateDirectory = NewBool(false) perms.CanRename = NewBool(false) perms.CanMove = NewBool(false) perms.CanDelete = NewBool(false) perms.CanUpload = NewBool(false) } if model.CanUpload(ctx) == false { perms.CanCreateDirectory = NewBool(false) perms.CanRename = NewBool(false) perms.CanMove = NewBool(false) perms.CanDelete = NewBool(false) perms.CanUpload = NewBool(false) } if model.CanShare(ctx) == false { perms.CanShare = NewBool(false) } entries, err := ctx.Backend.Ls(path) if err != nil { Log.Debug("ls::backend '%s'", err.Error()) SendErrorResult(res, err) return } files := make([]FileInfo, len(entries)) etagger := fnv.New32() etagger.Write([]byte(path + strconv.Itoa(len(entries)))) for i := 0; i < len(entries); i++ { name := entries[i].Name() files[i] = FileInfo{ Name: name, Size: entries[i].Size(), Time: func(mt time.Time) (modTime int64) { if mt.IsZero() == false { modTime = mt.UnixNano() / int64(time.Millisecond) } if i < 200 { // etag is generated from a few values to avoid large memory usage etagger.Write([]byte(name + strconv.Itoa(int(modTime)))) } return modTime }(entries[i].ModTime()), Type: func(mode os.FileMode) string { if mode.IsRegular() { return "file" } return "directory" }(entries[i].Mode()), Mode: func(mode os.FileMode) uint32 { return uint32(mode) }(entries[i].Mode()), } if f, ok := entries[i].Sys().(File); ok && f.Offline == true { files[i].Offline = true } } etagValue := base64.StdEncoding.EncodeToString(etagger.Sum(nil)) res.Header().Set("Etag", etagValue) if etagValue != "" && req.Header.Get("If-None-Match") == etagValue { res.WriteHeader(http.StatusNotModified) return } SendSuccessResultsWithMetadata(res, files, perms) } func FileCat(ctx *App, res http.ResponseWriter, req *http.Request) { var ( file io.ReadCloser fileMutation bool = false contentLength int64 = -1 needToCreateCache bool = false query url.Values = req.URL.Query() header http.Header = res.Header() ) http.SetCookie(res, &http.Cookie{ Name: "download", Value: "", MaxAge: -1, Path: "/", }) if model.CanRead(ctx) == false { Log.Debug("cat::permission 'permission denied'") SendErrorResult(res, ErrPermissionDenied) return } path, err := PathBuilder(ctx, query.Get("path")) if err != nil { Log.Debug("cat::path '%s'", err.Error()) SendErrorResult(res, err) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if req.Method == http.MethodHead { if err = auth.Stat(ctx, path); err != nil { SendErrorResult(res, ErrNotAuthorized) return } } else if err = auth.Cat(ctx, path); err != nil { SendErrorResult(res, ErrNotAuthorized) return } } // use our cache if necessary (range request) when possible if req.Header.Get("range") != "" { ctx.Session["fullpath"] = path if p := file_cache.Get(ctx.Session); p != nil { f, err := os.OpenFile(p.(string), os.O_RDONLY, os.ModePerm) if err == nil { file = f if fi, err := f.Stat(); err == nil { contentLength = fi.Size() } } } } // perform the actual `cat` if needed mType := GetMimeType(query.Get("path")) if file == nil { if file, err = ctx.Backend.Cat(path); err != nil { if req.Method == http.MethodHead { if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.IsDir() { if finfo.ModTime().Unix() > 0 { header.Set("Last-Modified", finfo.ModTime().UTC().Format(http.TimeFormat)) } header.Set("Content-Type", "inode/directory") res.WriteHeader(http.StatusNoContent) return } } Log.Debug("cat::backend '%s'", err.Error()) SendErrorResult(res, err) return } if mType == "application/javascript" { mType = "text/plain" } header.Set("Content-Type", mType) if req.Header.Get("range") != "" { needToCreateCache = true } } // plugin hooks thumb := query.Get("thumbnail") if thumb == "true" { fileMutation = true if finfo, err := ctx.Backend.Stat(path); err == nil && finfo.ModTime().Unix() > 0 { lm := finfo.ModTime().UTC().Format(http.TimeFormat) if lm == req.Header.Get("If-Modified-Since") { res.WriteHeader(http.StatusNotModified) return } header.Set("Last-Modified", lm) } for plgMType, plgHandler := range Hooks.Get.Thumbnailer() { if plgMType != mType { continue } file, err = plgHandler.Generate(file, ctx, &res, req) if err != nil { if req.Context().Err() == nil { Log.Debug("cat::thumbnailer '%s'", err.Error()) } SendErrorResult(res, err) return } break } } for _, obj := range Hooks.Get.ProcessFileContentBeforeSend() { f, changed, err := obj(file, ctx, &res, req) if err != nil { Log.Debug("cat::hooks '%s'", err.Error()) SendErrorResult(res, err) return } else if changed { file = f fileMutation = true } } // The extra complexity is to support: https://en.wikipedia.org/wiki/Progressive_download // => range request requires a seeker to work, some backend support it, some don't. 2 strategies: // 1. backend support Seek: use what the current backend gives us // 2. backend doesn't support Seek: build up a cache so that subsequent call don't trigger multiple downloads if req.Header.Get("range") != "" && needToCreateCache == true { if obj, ok := file.(io.Seeker); ok == true { if size, err := obj.Seek(0, io.SeekEnd); err == nil { if _, err = obj.Seek(0, io.SeekStart); err == nil { contentLength = size } } } else { tmpPath := GetAbsolutePath(TMP_PATH, "file_"+QuickString(20)+".dat") f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { Log.Debug("cat::range0 '%s'", err.Error()) SendErrorResult(res, err) return } file_cache.Set(ctx.Session, tmpPath) if _, err = io.Copy(f, file); err != nil { f.Close() file.Close() Log.Debug("cat::range1 '%s'", err.Error()) SendErrorResult(res, err) return } if err = f.Sync(); err != nil { f.Close() file.Close() Log.Debug("cat::range2 '%s'", err.Error()) SendErrorResult(res, err) return } f.Close() file.Close() if f, err = os.OpenFile(tmpPath, os.O_RDONLY, os.ModePerm); err != nil { Log.Debug("cat::range3 '%s'", err.Error()) SendErrorResult(res, err) return } if fi, err := f.Stat(); err == nil { contentLength = fi.Size() } file = f } } // Range request: find how much data we need to send var ranges [][]int64 if req.Header.Get("range") != "" { ranges = make([][]int64, 0) for _, r := range strings.Split(strings.TrimPrefix(req.Header.Get("range"), "bytes="), ",") { r = strings.TrimSpace(r) if r == "" { continue } var start int64 = -1 var end int64 = -1 sides := strings.Split(r, "-") if len(sides) == 2 { if start, err = strconv.ParseInt(sides[0], 10, 64); err != nil || start < 0 { start = 0 } if end, err = strconv.ParseInt(sides[1], 10, 64); err != nil || end < start { end = contentLength - 1 } } if start != -1 && end != -1 && end-start >= 0 { ranges = append(ranges, []int64{start, end}) } } } else if fileMutation == false && contentLength < 0 { if finfo, err := ctx.Backend.Stat(path); err == nil { if finfo.ModTime().Unix() > 0 { header.Set("Last-Modified", finfo.ModTime().UTC().Format(http.TimeFormat)) } if finfo.IsDir() { header.Set("Content-Type", "inode/directory") if req.Method == http.MethodHead { res.WriteHeader(http.StatusNoContent) return } SendErrorResult(res, ErrNotFound) return } contentLength = finfo.Size() } } // publish headers if contentLength >= 0 { header.Set("Content-Length", fmt.Sprintf("%d", contentLength)) } if disable_csp() == false { header.Set("Content-Security-Policy", "default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'; font-src data:; script-src-elem 'self'") } if fname := query.Get("name"); fname != "" { header.Set("Content-Disposition", "attachment; filename=\""+fname+"\"") } header.Set("Accept-Ranges", "bytes") if req.Method != http.MethodHead { size := 32 if thumb != "true" { switch Config.Get("general.buffer_size").String() { case "small": size = 32 case "medium": size = 128 case "large": size = 2 * 1024 } } buf := make([]byte, size*1024) if f, ok := file.(io.ReadSeeker); ok && len(ranges) > 0 { if _, err = f.Seek(ranges[0][0], io.SeekStart); err == nil { header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ranges[0][0], ranges[0][1], contentLength)) header.Set("Content-Length", fmt.Sprintf("%d", ranges[0][1]-ranges[0][0]+1)) res.WriteHeader(http.StatusPartialContent) io.CopyBuffer(res, io.LimitReader(f, ranges[0][1]-ranges[0][0]+1), buf) } else { res.WriteHeader(http.StatusRequestedRangeNotSatisfiable) } } else { io.CopyBuffer(res, file, buf) } } file.Close() } func FileAccess(ctx *App, res http.ResponseWriter, req *http.Request) { path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("access::path '%s'", err.Error()) SendErrorResult(res, err) return } var perms Metadata = Metadata{} if obj, ok := ctx.Backend.(interface{ Meta(path string) Metadata }); ok { perms = obj.Meta(path) } allowed := []string{} if model.CanRead(ctx) { if perms.CanSee == nil || *perms.CanSee == true { allowed = append(allowed, "GET") } } if model.CanEdit(ctx) { if (perms.CanCreateFile == nil || *perms.CanCreateFile == true) && (perms.CanCreateDirectory == nil || *perms.CanCreateDirectory == true) { allowed = append(allowed, "PUT") } } if model.CanUpload(ctx) { if perms.CanUpload == nil || *perms.CanUpload == true { allowed = append(allowed, "POST") } } header := res.Header() header.Set("Allow", strings.Join(allowed, ", ")) SendSuccessResult(res, nil) } var chunkedUploadCache AppCache func FileSave(ctx *App, res http.ResponseWriter, req *http.Request) { h := res.Header() h.Set("Cache-Control", "no-store") h.Set("Pragma", "no-cache") h.Set("Connection", "Close") path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("files::save action=path_builder err=%s", err.Error()) SendErrorResult(res, err) return } if model.CanEdit(ctx) == false { if model.CanUpload(ctx) == false { Log.Debug("files::save action=permission_upload err=permission_denied") SendErrorResult(res, ErrPermissionDenied) return } // for user who cannot edit but can upload => we want to ensure there // won't be any overwritten data root, filename := SplitPath(path) entries, err := ctx.Backend.Ls(root) if err != nil { Log.Debug("files::save action=permission_ls err=%s", err.Error()) SendErrorResult(res, ErrPermissionDenied) return } for i := 0; i < len(entries); i++ { if entries[i].Name() == filename { Log.Debug("files::save action=permission_ls err=already_exist") SendErrorResult(res, ErrConflict) return } } } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Save(ctx, path); err != nil { Log.Info("files::save action=middleware err=%s", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } // There is 2 ways to save something: // - case1: regular upload, we just insert the file in the pipe proto := "" if _, ok := req.Header["Tus-Resumable"]; ok { proto = "tus" } if proto == "" && req.Method == http.MethodPost { err = ctx.Backend.Save(path, req.Body) req.Body.Close() if err != nil { Log.Debug("files::save action=backend_save err=%s", err.Error()) SendErrorResult(res, NewError(err.Error(), 403)) return } SendSuccessResult(res, nil) return } // - case2: chunked upload using the TUS protocol: https://tus.io/protocols/resumable-upload cacheKey := map[string]string{ "path": path, "session": GenerateID(ctx.Session), } if proto == "tus" && req.Method == http.MethodOptions { h.Set("Tus-Resumable", "1.0.0") h.Set("Tus-Version", "1.0.0") h.Set("Tus-Extension", "creation,checksum") h.Set("Tus-Checksum-Algorithm", "sha1,crc32") return } if proto == "tus" && req.Method == http.MethodHead { c := chunkedUploadCache.Get(cacheKey) if c == nil { SendErrorResult(res, ErrNotFound) return } offset, length := c.(*chunkedUpload).Meta() h.Set("Tus-Resumable", "1.0.0") h.Set("Upload-Offset", fmt.Sprintf("%d", offset)) h.Set("Upload-Length", fmt.Sprintf("%d", length)) h.Set("Cache-Control", "no-store") res.WriteHeader(http.StatusNoContent) return } if proto == "tus" && req.Method == http.MethodPost { if c := chunkedUploadCache.Get(cacheKey); c != nil { chunkedUploadCache.Del(cacheKey) } size, err := strconv.ParseUint(req.Header.Get("Upload-Length"), 10, 0) if err != nil { Log.Debug("files::save::tus action=backend_save step=header_check_post err=%s", err.Error()) SendErrorResult(res, ErrNotValid) return } ctx.Context = context.Background() b, err := ctx.Backend.Init(ctx.Session, ctx) if err != nil { Log.Debug("files::save::tus action=backend_save step=backend_init err=%s", err.Error()) SendErrorResult(res, ErrNotValid) return } uploader := createChunkedUploader(b.Save, path, size) chunkedUploadCache.Set(cacheKey, uploader) h.Set("Tus-Resumable", "1.0.0") h.Set("Content-Length", "0") h.Set("Location", req.URL.String()) res.WriteHeader(http.StatusCreated) return } if proto == "tus" && req.Method == http.MethodPatch { if req.Header.Get("Content-Type") != "application/offset+octet-stream" { SendErrorResult(res, NewError("Unsupported Media Type", 415)) return } var ( hash hash.Hash expectedChecksum string ) if checksumHeader := req.Header.Get("upload-checksum"); checksumHeader != "" { parts := strings.SplitN(checksumHeader, " ", 2) if len(parts) != 2 { SendErrorResult(res, NewError("Bad Request", 400)) return } else if parts[0] == "sha1" { hash = sha1.New() } else if parts[1] == "crc32" { hash = crc32.NewIEEE() } else { SendErrorResult(res, NewError("Bad Request", 400)) return } expectedChecksum = parts[1] } requestOffset, err := strconv.ParseUint(req.Header.Get("Upload-Offset"), 10, 0) if err != nil { Log.Debug("files::save::tus action=backend_save step=header_check_patch err=%s", err.Error()) SendErrorResult(res, ErrNotValid) return } c := chunkedUploadCache.Get(cacheKey) if c == nil { Log.Debug("files::save::tus action=backend_save step=cache_fetch_patch") SendErrorResult(res, NewError("Conflict", 409)) return } uploader := c.(*chunkedUpload) initialOffset, totalSize := uploader.Meta() if initialOffset != requestOffset { Log.Debug("files::save::tus action=uploader.next path=%s err=offset_missmatch", path) SendErrorResult(res, ErrNotValid) return } reader := req.Body if hash != nil { reader = io.NopCloser(io.TeeReader(req.Body, hash)) } if err := uploader.Next(reader); err != nil { Log.Debug("files::save::tus action=uploader.next path=%s err=%s", path, err.Error()) SendErrorResult(res, NewError(err.Error(), 403)) return } if hash != nil && expectedChecksum != hex.EncodeToString(hash.Sum(nil)) { SendErrorResult(res, NewError("Checksum Mismatch", 460)) return } newOffset, _ := uploader.Meta() if newOffset > totalSize { uploader.Close() chunkedUploadCache.Del(cacheKey) Log.Warning("files::save::tus path=%s err=assert_offset size=%d old_offset=%d new_offset=%d", path, totalSize, initialOffset, newOffset) SendErrorResult(res, NewError("aborted - offset larger than total size", 403)) return } else if newOffset == totalSize { if err := uploader.Close(); err != nil { Log.Debug("files::save::tus action=uploader.close err=%s", err.Error()) SendErrorResult(res, ErrNotValid) return } chunkedUploadCache.Del(cacheKey) } h.Set("Tus-Resumable", "1.0.0") h.Set("Upload-Offset", fmt.Sprintf("%d", newOffset)) res.WriteHeader(http.StatusNoContent) return } SendErrorResult(res, ErrNotImplemented) } func createChunkedUploader(save func(path string, file io.Reader) error, path string, size uint64) *chunkedUpload { r, w := io.Pipe() done := make(chan error, 1) go func() { done <- save(path, r) }() return &chunkedUpload{ fn: save, stream: w, done: done, offset: 0, size: size, } } func initChunkedUploader() { chunkedUploadCache = NewAppCache(60*24, 1) chunkedUploadCache.OnEvict(func(key string, value interface{}) { c := value.(*chunkedUpload) if c == nil { Log.Warning("ctrl::files::chunked::cleanup nil on close") return } if err := c.Close(); err != nil { Log.Warning("ctrl::files::chunked::cleanup action=close err=%s", err.Error()) return } }) } type chunkedUpload struct { fn func(path string, file io.Reader) error stream *io.PipeWriter offset uint64 size uint64 done chan error once sync.Once mu sync.Mutex } func (this *chunkedUpload) Next(body io.ReadCloser) error { n, err := io.Copy(this.stream, body) body.Close() this.mu.Lock() this.offset += uint64(n) this.mu.Unlock() return err } func (this *chunkedUpload) Close() error { this.stream.Close() err := <-this.done this.once.Do(func() { close(this.done) }) return err } func (this *chunkedUpload) Meta() (uint64, uint64) { this.mu.Lock() defer this.mu.Unlock() return this.offset, this.size } func FileMv(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanEdit(ctx) == false { Log.Debug("mv::permission 'permission denied'") SendErrorResult(res, NewError("Permission denied", 403)) return } from, err := PathBuilder(ctx, req.URL.Query().Get("from")) if err != nil { Log.Debug("mv::path::from '%s'", err.Error()) SendErrorResult(res, err) return } to, err := PathBuilder(ctx, req.URL.Query().Get("to")) if err != nil { Log.Debug("mv::path::to '%s'", err.Error()) SendErrorResult(res, err) return } if from == "" || to == "" { Log.Debug("mv::params 'missing path parameter'") SendErrorResult(res, NewError("missing path parameter", 400)) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Mv(ctx, from, to); err != nil { Log.Info("mv::auth '%s'", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } err = ctx.Backend.Mv(from, to) if err != nil { Log.Debug("mv::backend '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func FileRm(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanEdit(ctx) == false { Log.Debug("rm::permission 'permission denied'") SendErrorResult(res, NewError("Permission denied", 403)) return } path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("rm::path '%s'", err.Error()) SendErrorResult(res, err) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Rm(ctx, path); err != nil { Log.Info("rm::auth '%s'", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } err = ctx.Backend.Rm(path) if err != nil { Log.Debug("rm::backend '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func FileMkdir(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanUpload(ctx) == false { Log.Debug("mkdir::permission 'permission denied'") SendErrorResult(res, NewError("Permission denied", 403)) return } path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("mkdir::path '%s'", err.Error()) SendErrorResult(res, err) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Mkdir(ctx, path); err != nil { Log.Info("mkdir::auth '%s'", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } err = ctx.Backend.Mkdir(path) if err != nil { Log.Debug("mkdir::backend '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func FileTouch(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanUpload(ctx) == false { Log.Debug("touch::permission 'permission denied'") SendErrorResult(res, NewError("Permission denied", 403)) return } path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { Log.Debug("touch::path '%s'", err.Error()) SendErrorResult(res, err) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Touch(ctx, path); err != nil { Log.Info("touch::auth '%s'", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } err = ctx.Backend.Touch(path) if err != nil { Log.Debug("touch::backend '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func FileDownloader(ctx *App, res http.ResponseWriter, req *http.Request) { var err error if model.CanRead(ctx) == false { Log.Debug("downloader::permission 'permission denied'") SendErrorResult(res, ErrPermissionDenied) return } paths := req.URL.Query()["path"] for i := 0; i < len(paths); i++ { if paths[i], err = PathBuilder(ctx, paths[i]); err != nil { Log.Debug("downloader::path '%s'", err.Error()) SendErrorResult(res, err) return } } resHeader := res.Header() resHeader.Set("Content-Type", "application/zip") filename := "download" if len(paths) == 1 { filename = filepath.Base(paths[0]) } resHeader.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename)) start := time.Now() var addToZipRecursive func(*App, *zip.Writer, string, string, *[]string) error addToZipRecursive = func(c *App, zw *zip.Writer, backendPath string, zipRoot string, errList *[]string) (err error) { if time.Now().Sub(start) > time.Duration(zip_timeout())*time.Second { Log.Debug("downloader::timeout zip not completed due to timeout") return ErrTimeout } if strings.HasSuffix(backendPath, "/") == false { // Process File zipPath := strings.TrimPrefix(backendPath, zipRoot) file, err := ctx.Backend.Cat(backendPath) if err != nil { *errList = append(*errList, fmt.Sprintf("downloader::cat %s %s\n", zipPath, err.Error())) if err == ErrNotReachable { return nil } Log.Debug("downloader::cat backendPath['%s'] zipPath['%s'] error['%s']", backendPath, zipPath, err.Error()) return err } zipFile, err := zw.Create(zipPath) if err != nil { *errList = append(*errList, fmt.Sprintf("downloader::create %s %s\n", zipPath, err.Error())) Log.Debug("downloader::create backendPath['%s'] zipPath['%s'] error['%s']", backendPath, zipPath, err.Error()) return err } if _, err = io.Copy(zipFile, file); err != nil { *errList = append(*errList, fmt.Sprintf("downloader::copy %s %s\n", zipPath, err.Error())) Log.Debug("downloader::copy backendPath['%s'] zipPath['%s'] error['%s']", backendPath, zipPath, err.Error()) io.Copy(zipFile, strings.NewReader("")) return err } file.Close() return nil } // Process Folder entries, err := c.Backend.Ls(backendPath) if err != nil { *errList = append(*errList, fmt.Sprintf("downloader::ls %s\n", err.Error())) Log.Debug("downloader::ls path['%s'] error['%s']", backendPath, err.Error()) return err } for i := 0; i < len(entries); i++ { newBackendPath := backendPath + entries[i].Name() if entries[i].IsDir() { newBackendPath += "/" } if err = addToZipRecursive(ctx, zw, newBackendPath, zipRoot, errList); err != nil { *errList = append(*errList, fmt.Sprintf("downloader::recursive %s\n", err.Error())) Log.Debug("downloader::recursive path['%s'] error['%s']", newBackendPath, err.Error()) return err } } return nil } zipWriter := zip.NewWriter(res) defer zipWriter.Close() errList := []string{} for i := 0; i < len(paths); i++ { zipRoot := "" if strings.HasSuffix(paths[i], "/") { zipRoot = strings.TrimSuffix(paths[i], filepath.Base(paths[i])+"/") } else { zipRoot = strings.TrimSuffix(paths[i], filepath.Base(paths[i])) } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Ls(ctx, paths[i]); err != nil { Log.Info("downloader::ls::auth path['%s'] => '%s'", paths[i], err.Error()) SendErrorResult(res, ErrNotAuthorized) return } if err = auth.Cat(ctx, paths[i]); err != nil { Log.Info("downloader::cat::auth path['%s'] => '%s'", paths[i], err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } addToZipRecursive(ctx, zipWriter, paths[i], zipRoot, &errList) } if len(errList) > 0 { if errorWriter, err := zipWriter.Create("error.log"); err == nil { for _, e := range errList { io.Copy(errorWriter, strings.NewReader(e)) } } } } func FileExtract(ctx *App, res http.ResponseWriter, req *http.Request) { if model.CanRead(ctx) == false { Log.Debug("extract::permission 'permission denied'") SendErrorResult(res, ErrPermissionDenied) return } paths := req.URL.Query()["path"] for _, auth := range Hooks.Get.AuthorisationMiddleware() { for i := 0; i < len(paths); i++ { if err := auth.Mkdir(ctx, paths[i]); err != nil { Log.Debug("extract::permission::mkdir %s", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } else if err := auth.Save(ctx, paths[i]); err != nil { Log.Debug("extract::permission::Save %s", err.Error()) SendErrorResult(res, ErrNotAuthorized) return } } } c, cancel := context.WithTimeout(ctx.Context, time.Duration(zip_timeout())*time.Second) extractPath := func(base string, path string) (string, error) { base = filepath.Dir(base) path = filepath.Join(base, path) if strings.HasPrefix(path, base) == false { return "", ErrFilesystemError } return path, nil } extractZip := func(path string) (err error) { if err = c.Err(); err != nil { cancel() return ErrTimeout } zipFile, err := ctx.Backend.Cat(path) if err != nil { return err } defer zipFile.Close() f, err := os.CreateTemp("", "tmpzip.*.zip") if err != nil { Log.Debug("extract::create_temp '%s'", err.Error()) return nil } defer os.Remove(f.Name()) io.Copy(f, zipFile) s, err := f.Stat() if err != nil { return err } r, err := zip.NewReader(f, s.Size()) if err != nil { return err } isFolderAlreadyCreated := map[string]bool{ fmt.Sprintf("%s/", filepath.Dir(path)): true, } for _, f := range r.File { time.Sleep(2 * time.Millisecond) if err = c.Err(); err != nil { cancel() return ErrTimeout } // STEP1: ensure the underlying folders exists spl := strings.Split(f.Name, "/") for i, p := range spl { if p == "" { continue } p = strings.Join(spl[0:i], "/") p, err = extractPath(path, p) if strings.HasSuffix(p, "/") == false { p += "/" } if isFolderAlreadyCreated[p] { continue } isFolderAlreadyCreated[p] = true if err := ctx.Backend.Mkdir(p); err != nil { Log.Debug("extract::mkdir err %s", err.Error()) } } // STEP2: create the file if f.FileInfo().IsDir() == false { p, err := extractPath(path, f.Name) if err != nil { Log.Debug("extract::chroot %s", err.Error()) return err } rc, err := f.Open() if err != nil { Log.Debug("extract::fopen %s", err.Error()) return err } err = ctx.Backend.Save(p, rc) rc.Close() if err != nil { Log.Debug("extract::save err %s", err.Error()) } } } return nil } var err error for i := 0; i < len(paths); i++ { if paths[i], err = PathBuilder(ctx, paths[i]); err != nil { Log.Debug("extract::path '%s'", err.Error()) SendErrorResult(res, err) return } if err = extractZip(paths[i]); err != nil { SendErrorResult(res, err) return } } SendSuccessResult(res, nil) } func PathBuilder(ctx *App, path string) (string, error) { if path == "" { return "", NewError("No path available", 400) } sessionPath := ctx.Session["path"] basePath := filepath.ToSlash(filepath.Join(sessionPath, path)) if path[len(path)-1:] == "/" && basePath != "/" { basePath += "/" } if strings.HasPrefix(basePath, ctx.Session["path"]) == false { return "", ErrFilesystemError } return basePath, nil } ================================================ FILE: server/ctrl/metadata.go ================================================ package ctrl import ( "encoding/json" "fmt" "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) func MetaGet(ctx *App, w http.ResponseWriter, r *http.Request) { m := Hooks.Get.Metadata() if m == nil { SendErrorResult(w, ErrNotImplemented) return } path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } out, err := m.Get(ctx, path) if err != nil { SendErrorResult(w, err) return } SendSuccessResults(w, out) } func MetaUpsert(ctx *App, w http.ResponseWriter, r *http.Request) { m := Hooks.Get.Metadata() if m == nil { SendErrorResult(w, ErrNotImplemented) return } path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } forms := []FormElement{} if err := json.NewDecoder(r.Body).Decode(&forms); err != nil { SendErrorResult(w, ErrNotImplemented) return } if err := m.Set(ctx, path, forms); err != nil { SendErrorResult(w, err) return } SendSuccessResult(w, nil) } func MetaSearch(ctx *App, w http.ResponseWriter, r *http.Request) { m := Hooks.Get.Metadata() if m == nil { SendErrorResult(w, ErrNotImplemented) return } facets := map[string]any{} if err := json.NewDecoder(r.Body).Decode(&facets); err != nil { SendErrorResult(w, ErrNotImplemented) return } path, err := PathBuilder(ctx, fmt.Sprintf("%s", facets["path"])) if err != nil { SendErrorResult(w, err) return } delete(facets, "path") out, err := m.Search(ctx, path, facets) if err != nil { SendErrorResult(w, err) return } SendSuccessResults(w, out) } ================================================ FILE: server/ctrl/plugin.go ================================================ package ctrl import ( "io" "net/http" "os" "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" ) func PluginExportHandler(ctx *App, res http.ResponseWriter, req *http.Request) { plgExports := map[string][]string{} for name, plg := range model.PLUGINS { for _, module := range plg.Modules { if module["type"] == "xdg-open" { index := module["entrypoint"] if index == "" { index = "/index.js" } plgExports[module["mime"]] = []string{ module["application"], WithBase(JoinPath("/assets/"+BUILD_REF+"/plugin/", filepath.Join(name+".zip", index))), } } } } SendSuccessResultWithEtagAndGzip(res, req, plgExports) } func PluginStaticHandler(ctx *App, res http.ResponseWriter, req *http.Request) { path := mux.Vars(req)["path"] mtype := GetMimeType(path) staticConfig := []struct { ContentType string FileExt string }{ {"br", ".br"}, {"gzip", ".gz"}, {"", ""}, } var ( b []byte err error ) head := res.Header() acceptEncoding := req.Header.Get("Accept-Encoding") for _, cfg := range staticConfig { if strings.Contains(acceptEncoding, cfg.ContentType) == false { continue } b, err = model.GetPluginFile(mux.Vars(req)["name"], path+cfg.FileExt) if err != nil { continue } head.Set("Content-Type", mtype) if cfg.ContentType != "" { head.Set("Content-Encoding", cfg.ContentType) } res.Write(b) return } SendErrorResult(res, err) return } func PluginDownloadHandler(ctx *App, res http.ResponseWriter, req *http.Request) { f, err := os.Open(JoinPath( GetAbsolutePath(PLUGIN_PATH), mux.Vars(req)["name"]+".zip", )) if err != nil { SendErrorResult(res, err) return } io.Copy(res, f) f.Close() } ================================================ FILE: server/ctrl/report.go ================================================ package ctrl import ( "encoding/json" "fmt" "net/http" "os" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func ReportHandler(ctx *App, res http.ResponseWriter, req *http.Request) { // This function is quite dumb indeed, the goal is to show a report trace in the logs SendSuccessResult(res, nil) } func WellKnownSecurityHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if IsWhiteLabel() { NotFoundHandler(ctx, res, req) return } res.WriteHeader(http.StatusOK) res.Write([]byte("# If you would like to report a security issue\n")) res.Write([]byte("# you may report it to me via email\n")) res.Write([]byte("Contact: support@filestash.app\n")) } func HealthHandler(ctx *App, res http.ResponseWriter, req *http.Request) { res.Header().Set("Access-Control-Allow-Origin", "*") res.Header().Set("Content-Type", "application/json") // CHECK 1: open the config file file, err := os.OpenFile( GetAbsolutePath(CONFIG_PATH, "config.json"), os.O_RDONLY, os.ModePerm, ) if err != nil { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"status": "error", "reason": "fopen_error"}`)) return } if _, err := file.Read(make([]byte, 10)); err != nil { file.Close() res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"status": "error", "reason": "fread_error"}`)) return } file.Close() // CHECK2: about page r, err := http.Get(fmt.Sprintf( "%s://127.0.0.1:%d/about", func() string { if req.TLS == nil { return "http" } return "https" }(), Config.Get("general.port").Int(), )) if err == nil { r.Body.Close() if r.StatusCode != http.StatusOK { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(fmt.Sprintf(`{"status": "error", "reason": "endpoint_error", "debug": "status=%d"}`, r.StatusCode))) return } } // CHECK3: config sanity check cgsk := Config.Get("general.secret_key").String() caa := Config.Get("auth.admin").String() if len(cgsk) != 16 || len(caa) != 60 { m, _ := json.MarshalIndent( []string{ fmt.Sprintf( "general.secret_key[size=%d]", len(cgsk), ), fmt.Sprintf( "admin.auth[size=%d]", len(caa), ), fmt.Sprintf( "log[level=%s]", Config.Get("log.level").String(), ), fmt.Sprintf( "connections[size=%d]", len(Config.Conn), ), fmt.Sprintf( "middleware.identity_provider[type=%s][params=%d]", Config.Get("middleware.identity_provider.type").String(), len(Config.Get("middleware.identity_provider.params").String()), ), fmt.Sprintf( "middleware.attribute_mapping[type=%s][params=%d]", strings.ReplaceAll(Config.Get("middleware.attribute_mapping.related_backend").String(), " ", ""), len(Config.Get("middleware.attribute_mapping.params").String()), ), }, "", " ", ) status := "error" if os.Getenv("ADMIN_PASSWORD") != "" && len(caa) == 0 { res.WriteHeader(http.StatusServiceUnavailable) Log.Error("ctrl::report::healthz message=corrupted_config check=3A config=%s", m) } else if len(cgsk) != 16 { res.WriteHeader(http.StatusServiceUnavailable) Log.Error("ctrl::report::healthz message=corrupted_config check=3B config=%s", m) } else { res.WriteHeader(http.StatusOK) status = "transcient" } res.Write([]byte(fmt.Sprintf( `{"status": "%s", "reason": "configuration_error", "debug": %s}`, status, m, ))) return } // SUCCESS!! res.WriteHeader(http.StatusOK) if req.Method != "HEAD" { res.Write([]byte(`{"status": "pass"}`)) } } ================================================ FILE: server/ctrl/search.go ================================================ package ctrl import ( . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" "net/http" "strings" ) func FileSearch(ctx *App, res http.ResponseWriter, req *http.Request) { path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { path = "/" } q := req.URL.Query().Get("q") if model.CanRead(ctx) == false { Log.Debug("ctrl::search 'can not read \"%s\"'", path) SendErrorResult(res, ErrPermissionDenied) return } var searchResults []IFile searchEngine := Hooks.Get.SearchEngine() if searchEngine == nil { SendErrorResult(res, ErrMissingDependency) return } searchResults, err = searchEngine.Query(*ctx, path, q) if err != nil { SendErrorResult(res, err) return } // overwrite the path of a file according to chroot if ctx.Session["path"] != "" { for i := 0; i < len(searchResults); i++ { searchResults[i] = File{ FName: searchResults[i].Name(), FSize: searchResults[i].Size(), FType: func() string { if searchResults[i].IsDir() { return "directory" } return "file" }(), FPath: "/" + strings.TrimPrefix( searchResults[i].Path(), ctx.Session["path"], ), } } } SendSuccessResults(res, searchResults) } ================================================ FILE: server/ctrl/session.go ================================================ package ctrl import ( "encoding/base64" "encoding/json" "fmt" "net" "net/http" "net/url" "slices" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/middleware" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" ) type Session struct { Home *string `json:"home,omitempty"` IsAuth bool `json:"is_authenticated"` Backend string `json:"backendID"` Authorization string `json:"authorization,omitempty"` } func SessionGet(ctx *App, res http.ResponseWriter, req *http.Request) { r := Session{ IsAuth: false, } if ctx.Backend == nil { SendSuccessResult(res, r) return } home, err := model.GetHome(ctx.Backend, ctx.Session["path"]) if err != nil { SendSuccessResult(res, r) return } else if ctx.Share.Id != "" { home = "/" } r.IsAuth = true r.Home = NewString(home) r.Backend = backendID(ctx.Session) if ctx.Share.Id == "" && Config.Get("features.protection.enable_chromecast").Bool() { r.Authorization = ctx.Authorization } SendSuccessResult(res, r) } func SessionAuthenticate(ctx *App, res http.ResponseWriter, req *http.Request) { ctx.Body["timestamp"] = time.Now().Format(time.RFC3339) session := model.MapStringInterfaceToMapStringString(ctx.Body) session["path"] = EnforceDirectory(session["path"]) backend, err := model.NewBackend(ctx, session) if err != nil { Log.Debug("[auth] action=authenticate::newBackend err=%s", ferror(err)) Log.Stdout("AUDIT action[fail] backend[%s] user[%s] target[%s]", session["type"], backendID(session), ip(req)) SendErrorResult(res, err) return } if obj, ok := backend.(interface { OAuthToken(*map[string]interface{}) error }); ok { err := obj.OAuthToken(&ctx.Body) if err != nil { Log.Debug("[auth] action=authenticate::oauthtoken err=%s", ferror(err)) SendErrorResult(res, NewError("Can't authenticate (OAuth error)", 401)) return } session = model.MapStringInterfaceToMapStringString(ctx.Body) backend, err = model.NewBackend(ctx, session) if err != nil { Log.Debug("[auth] action=authenticate::oauth::newBackend err=%s", ferror(err)) Log.Stdout("AUDIT action[fail] backend[%s] user[%s] target[%s]", session["type"], username(session), ip(req)) SendErrorResult(res, NewError("Can't authenticate", 401)) return } } home, err := model.GetHome(backend, session["path"]) if err != nil { Log.Debug("[auth] action=authenticate::getHome err=%s", ferror(err)) SendErrorResult(res, ErrAuthenticationFailed) return } s, err := json.Marshal(session) if err != nil { Log.Debug("[auth] action=authenticate::marshall err=%s", ferror(err)) SendErrorResult(res, NewError(err.Error(), 500)) return } obfuscate, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(s)) if err != nil { Log.Debug("[auth] action=authenticate::encrypt err=%s", ferror(err)) SendErrorResult(res, NewError(err.Error(), 500)) return } // split session cookie if greater than 3800 bytes value_limit := 3800 index := 0 end := 0 for { if len(obfuscate) >= (index+1)*value_limit { end = (index + 1) * value_limit } else { end = len(obfuscate) } http.SetCookie(res, applyCookieRules(&http.Cookie{ Name: CookieName(index), Value: obfuscate[index*value_limit : end], MaxAge: 60 * Config.Get("general.cookie_timeout").Int(), Path: COOKIE_PATH, }, req)) if end == len(obfuscate) { break } else { Log.Debug("[auth] action=authenticate::obfuscate index=%d length=%d total=%d", index, len(obfuscate[index*value_limit:end]), len(obfuscate)) index++ } } if Config.Get("features.protection.iframe").String() != "" { res.Header().Set("bearer", obfuscate) } Log.Stdout("AUDIT action[login] backend[%s] user[%s] target[%s]", session["type"], username(session), ip(req)) SendSuccessResult(res, Session{ IsAuth: true, Home: NewString(home), Backend: backendID(session), Authorization: obfuscate, }) } func SessionLogout(ctx *App, res http.ResponseWriter, req *http.Request) { go func() { // user typically expect the logout to feel instant but in our case we still need to make sure // the connection is closed as lot of backend requires to hold an active session which we cache. // Whenever somebody logout after say 30 minutes idle, the logout would first create a connection // then close which can take a few seconds and make for a bad user experience. // By pushing that connection close in a goroutine, we make sure the logout is much faster for // the user while still retaining that functionality. middleware.SessionTry(func(c *App, _res http.ResponseWriter, _req *http.Request) { if c.Backend != nil { if obj, ok := c.Backend.(interface{ Close() error }); ok { obj.Close() } } })(ctx, res, req) }() index := 0 for { _, err := req.Cookie(CookieName(index)) if err != nil { break } http.SetCookie(res, applyCookieRules(&http.Cookie{ Name: CookieName(index), Value: "", MaxAge: -1, Path: COOKIE_PATH, }, req)) index++ } http.SetCookie(res, &http.Cookie{ Name: COOKIE_NAME_ADMIN, Value: "", MaxAge: -1, Path: COOKIE_PATH_ADMIN, }) http.SetCookie(res, &http.Cookie{ Name: COOKIE_NAME_PROOF, Value: "", MaxAge: -1, Path: COOKIE_PATH, }) Log.Stdout("AUDIT action[logout] backend[%s] user[%s] target[%s]", ctx.Session["type"], username(ctx.Session), ip(req)) SendSuccessResult(res, nil) } func SessionOAuthBackend(ctx *App, res http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) a := map[string]string{ "type": vars["service"], } b, err := model.NewBackend(ctx, a) if err != nil { Log.Debug("session::oauth 'NewBackend' %+v", err) SendErrorResult(res, err) return } obj, ok := b.(interface{ OAuthURL() string }) if ok == false { Log.Debug("session::oauth 'Backend does not support oauth - \"%s\"'", a["type"]) SendErrorResult(res, ErrNotSupported) return } redirectUrl, err := url.Parse(obj.OAuthURL()) if err != nil { Log.Debug("session::oauth 'Parse URL - \"%s\"'", a["type"]) SendErrorResult(res, ErrNotValid) return } stateValue := vars["service"] if req.URL.Query().Get("next") != "" { stateValue += "::" + req.URL.Query().Get("next") } q := redirectUrl.Query() q.Set("state", stateValue) redirectUrl.RawQuery = q.Encode() if strings.Contains(req.Header.Get("Accept"), "text/html") { http.Redirect(res, req, redirectUrl.String(), http.StatusSeeOther) return } SendSuccessResult(res, redirectUrl.String()) } func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request) { SSOCookieName := "ssoref" // Step0: Initialisation _get := req.URL.Query() plugin := func() IAuthentication { selectedPluginId := Config.Get("middleware.identity_provider.type").String() if selectedPluginId == "" { return nil } for key, plugin := range Hooks.Get.AuthenticationMiddleware() { if key == selectedPluginId { return plugin } } return nil }() if plugin == nil { http.Redirect( res, req, "/?error=Not%20Found&trace=middleware not found", http.StatusTemporaryRedirect, ) return } formData := map[string]string{} for key, element := range _get { if len(element) == 0 { continue } formData[key] = element[0] } if req.Method == http.MethodPost { if err := req.ParseForm(); err != nil { http.Redirect( res, req, "/?error=Not%20Valid&trace=parsing body - "+err.Error(), http.StatusTemporaryRedirect, ) return } for key, values := range req.Form { if len(values) == 0 { continue } formData[key] = values[0] } } idpParams := TmplParams(map[string]string{}) if err := json.Unmarshal( []byte(Config.Get("middleware.identity_provider.params").String()), &idpParams, ); err != nil { http.Redirect( res, req, "/?error=Not%20Valid&trace=unpacking idp - "+err.Error(), http.StatusTemporaryRedirect, ) return } for k, v := range idpParams { out, err := TmplExec(NewStringFromInterface(v), idpParams) if err != nil { http.Redirect( res, req, "/?error=Not%20Valid&trace=idp - "+err.Error(), http.StatusTemporaryRedirect, ) return } idpParams[k] = out } // Step1: Entrypoint of the authentication process is handled by the plugin if req.Method == "GET" && _get.Get("action") == "redirect" { if label := _get.Get("label"); label != "" { http.SetCookie( res, applyCookieSameSiteRule( applyCookieRules(&http.Cookie{ Name: SSOCookieName, Value: label + "::" + _get.Get("state"), MaxAge: 60 * 10, Path: COOKIE_PATH, }, req), http.SameSiteDefaultMode, ), ) } if err := plugin.EntryPoint(idpParams, req, res); err != nil { Log.Error("entrypoint - %s", err.Error()) res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) res.Write([]byte(Page(err.Error()))) } return } // Step2: End of the authentication process. Could come from: // - target of a html form. eg: ldap, mysql, ... // - identity provider redirection uri. eg: oauth2, openid, ... pluginCallback, err := plugin.Callback(formData, idpParams, res) if err == ErrAuthenticationFailed { Log.Warning("failed authentication - %s", err.Error()) http.Redirect( res, req, req.URL.Path+"?action=redirect", http.StatusSeeOther, ) return } else if err != nil && strings.HasPrefix(res.Header().Get("Content-Type"), "text/html") == false { Log.Error("session::authMiddleware 'callback error - %s'", err.Error()) http.Redirect( res, req, "/?error="+ErrNotAllowed.Error()+"&trace=redirect request failed - "+err.Error(), http.StatusSeeOther, ) return } else if err != nil { // response handled directly within a plugin return } templateBind := TmplParams(pluginCallback) var ( label = "" state = "" ) if refCookie, err := req.Cookie(SSOCookieName); err == nil { // TODO: deprecate SSOCookieName s := strings.SplitN(refCookie.Value, "::", 2) switch len(s) { case 1: label = s[0] case 2: label = s[0] state = s[1] } } else if l := req.URL.Query().Get("label"); l != "" { label = l state = req.URL.Query().Get("state") } else { Log.Warning("session::authMiddleware action=callback_error err=missing_label url=%s", req.URL.String()) } if decodedState, err := base64.StdEncoding.DecodeString(state); err == nil { stateStruct := map[string]string{} json.Unmarshal(decodedState, &stateStruct) // check variables are "legit" attributes := "" signature := "" fields := strings.Split(Config.Get("features.protection.signature").String(), ",") for k, v := range stateStruct { if k == "signature" { signature = v } if slices.Contains(fields, k) { attributes += fmt.Sprintf("%s[%s] ", k, v) } } if attributes = strings.TrimSpace(attributes); attributes != "" { v, err := DecryptString(SECRET_KEY_DERIVATE_FOR_SIGNATURE, signature) if err != nil || attributes != v { v, _ = EncryptString(SECRET_KEY_DERIVATE_FOR_SIGNATURE, attributes) Log.Debug("callback signature is required, signature=%s", v) http.Redirect( res, req, WithBase("/?error=Invalid%20Signature&trace=signature is not correct"), http.StatusTemporaryRedirect, ) return } } // populate variable for key, value := range stateStruct { if templateBind[key] != "" { continue } templateBind[key] = value } } redirectURI := templateBind["next"] if redirectURI == "" { redirectURI = WithBase("/") } if templateBind["nav"] != "" { redirectURI += "?nav=" + templateBind["nav"] } // Step3: create a backend connection object session, err := func(tb map[string]string) (map[string]string, error) { globalMapping := map[string]map[string]interface{}{} if err = json.Unmarshal( []byte(Config.Get("middleware.attribute_mapping.params").String()), &globalMapping, ); err != nil { Log.Warning("session::authMiddlware 'attribute mapping error' %s", err.Error()) return map[string]string{}, err } mappingToUse := map[string]string{} for k, v := range globalMapping[label] { out, err := TmplExec(NewStringFromInterface(v), tb) if err != nil { Log.Debug("session::authMiddleware action=tmplExec err=%s", err.Error()) } mappingToUse[k] = out } mappingToUse["timestamp"] = time.Now().Format(time.RFC3339) if label != "" && Config.Get("general.extended_session").Bool() { pluginCallback["label"] = label if jsonStr, err := json.Marshal(pluginCallback); err == nil { mappingToUse["session"] = string(jsonStr) } } return mappingToUse, nil }(templateBind) if err != nil { Log.Debug("session::authMiddleware 'auth mapping failed %s'", err.Error()) http.Redirect( res, req, WithBase("/?error=Not%20Valid&trace=mapping_error - "+err.Error()), http.StatusTemporaryRedirect, ) return } if _, err := model.NewBackend(ctx, session); err != nil { Log.Debug("session::authMiddleware 'backend connection failed %s'", err.Error()) Log.Info("[auth] status=failed user=%s backend=%s::%s ip=%s err=%s", username(session), session["type"], backendID(session), ip(req), ferror(err)) url := "/?error=" + ErrNotValid.Error() + "&trace=backend error - " + err.Error() if IsATranslatedError(err) { url = "/?error=" + err.Error() + "&trace=backend error - " + err.Error() } http.Redirect(res, req, WithBase(url), http.StatusTemporaryRedirect) return } // Step4: persist connection with a cookie s, err := json.Marshal(session) if err != nil { Log.Debug("session::authMiddleware 'session marshal error %+v'", session) SendErrorResult(res, ErrNotValid) return } obfuscate, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(s)) if err != nil { Log.Debug("session::authMiddleware 'encryption error - %s", err.Error()) SendErrorResult(res, ErrNotValid) return } http.SetCookie(res, applyCookieRules(&http.Cookie{ // TODO: deprecate SSOCookieName Name: SSOCookieName, Value: "", MaxAge: -1, Path: COOKIE_PATH, }, req)) http.SetCookie(res, applyCookieRules(&http.Cookie{ Name: COOKIE_NAME_AUTH, Value: obfuscate, MaxAge: 60 * Config.Get("general.cookie_timeout").Int(), Path: COOKIE_PATH, }, req)) if Config.Get("features.protection.iframe").String() != "" { redirectURI += "#bearer=" + obfuscate } Log.Info("[auth] status=success user=%s backend=%s::%s ip=%s", username(session), session["type"], backendID(session), ip(req)) http.Redirect(res, req, redirectURI, http.StatusSeeOther) } func applyCookieRules(cookie *http.Cookie, req *http.Request) *http.Cookie { cookie.HttpOnly = true cookie.SameSite = http.SameSiteStrictMode if Config.Get("features.protection.iframe").String() != "" { if f := req.Header.Get("Referer"); strings.HasPrefix(f, "https://") { cookie.Secure = true cookie.SameSite = http.SameSiteNoneMode cookie.Partitioned = true } else { Log.Warning("you are trying to access Filestash from a non secure origin ('%s') and with iframe enabled. Either use SSL or disable iframe from the admin console.", f) } } return cookie } func applyCookieSameSiteRule(cookie *http.Cookie, sameSiteValue http.SameSite) *http.Cookie { cookie.SameSite = sameSiteValue return cookie } func backendID(session map[string]string) string { return Hash(GenerateID(session)+session["path"], 20) } func username(session map[string]string) string { if session["username"] != "" { return strings.ReplaceAll(session["username"], " ", "+") } else if session["user"] != "" { return strings.ReplaceAll(session["user"], " ", "+") } return GenerateID(session) } func ip(req *http.Request) string { if xff := req.Header.Get("X-Forwarded-For"); xff != "" { if parts := strings.Split(xff, ","); len(parts) > 0 { return strings.TrimSpace(parts[0]) } } if xrip := req.Header.Get("X-Real-Ip"); xrip != "" { return xrip } host, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { return req.RemoteAddr } return host } func ferror(err error) string { return strings.ReplaceAll(err.Error(), " ", "+") } ================================================ FILE: server/ctrl/share.go ================================================ package ctrl import ( "encoding/json" "fmt" "github.com/gorilla/mux" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" "net/http" "strings" ) func ShareList(ctx *App, res http.ResponseWriter, req *http.Request) { path, err := PathBuilder(ctx, req.URL.Query().Get("path")) if err != nil { SendErrorResult(res, err) return } listOfSharedLinks, err := model.ShareList( GenerateID(ctx.Session), path, ) if err != nil { Log.Debug("share::list '%s'", err.Error()) SendErrorResult(res, err) return } for i := 0; i < len(listOfSharedLinks); i++ { listOfSharedLinks[i].Path = "/" + strings.TrimPrefix(listOfSharedLinks[i].Path, path) } SendSuccessResults(res, listOfSharedLinks) } func ShareUpsert(ctx *App, res http.ResponseWriter, req *http.Request) { share_id := mux.Vars(req)["share"] if share_id == "private" { Log.Debug("share::upsert 'private'") SendErrorResult(res, ErrNotValid) return } s := Share{ Id: share_id, Auth: func() string { if ctx.Share.Id == "" { str := "" index := 0 for { cookie, err := req.Cookie(CookieName(index)) if err != nil { break } index++ str += cookie.Value } return str } return ctx.Share.Auth }(), Backend: func() string { if ctx.Share.Id == "" { return GenerateID(ctx.Session) } return ctx.Share.Backend }(), Path: func() string { leftPath := "/" rightPath := strings.TrimPrefix(NewStringFromInterface(ctx.Body["path"]), "/") if ctx.Share.Id != "" { leftPath = ctx.Share.Path } else { if ctx.Session["path"] != "" { leftPath = EnforceDirectory(ctx.Session["path"]) } } return leftPath + rightPath }(), Password: NewStringpFromInterface(ctx.Body["password"]), Users: NewStringpFromInterface(ctx.Body["users"]), Expire: NewInt64pFromInterface(ctx.Body["expire"]), Url: NewStringpFromInterface(ctx.Body["url"]), CanManageOwn: NewBoolFromInterface(ctx.Body["can_manage_own"]), CanShare: NewBoolFromInterface(ctx.Body["can_share"]), CanRead: NewBoolFromInterface(ctx.Body["can_read"]), CanWrite: NewBoolFromInterface(ctx.Body["can_write"]), CanUpload: NewBoolFromInterface(ctx.Body["can_upload"]), } if err := model.ShareUpsert(&s); err != nil { Log.Debug("share::upsert '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func ShareDelete(ctx *App, res http.ResponseWriter, req *http.Request) { share_target := mux.Vars(req)["share"] if err := model.ShareDelete(share_target); err != nil { Log.Debug("share::delete '%s'", err.Error()) SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func ShareVerifyProof(ctx *App, res http.ResponseWriter, req *http.Request) { var submittedProof model.Proof var verifiedProof []model.Proof var requiredProof []model.Proof var remainingProof []model.Proof var s Share var err error // 1) initialise the current context share_id := mux.Vars(req)["share"] s, err = model.ShareGet(share_id) if err != nil { Log.Debug("share::verify::init '%s'", err.Error()) SendErrorResult(res, err) return } submittedProof = model.Proof{ Key: fmt.Sprint(ctx.Body["type"]), Value: fmt.Sprint(ctx.Body["value"]), } verifiedProof = model.ShareProofGetAlreadyVerified(req) requiredProof = model.ShareProofGetRequired(s) // 2) validate the current context if len(verifiedProof) > 20 || len(requiredProof) > 20 { http.SetCookie(res, &http.Cookie{ Name: COOKIE_NAME_PROOF, Value: "", MaxAge: -1, Path: COOKIE_PATH, }) Log.Debug("share::verify::validate 'proof issue' len(verifiedProof)[%d] len(requiredProof)[%d]", len(verifiedProof), len(requiredProof)) SendErrorResult(res, ErrNotValid) return } if err := s.IsValid(); err != nil { Log.Debug("share::verify::validate '%s'", err.Error()) SendErrorResult(res, err) return } // 3) process the proof sent by the user submittedProof, err = model.ShareProofVerifier(s, submittedProof) if err != nil { Log.Debug("share::verify::process '%s'", err.Error()) submittedProof.Error = NewString(err.Error()) SendSuccessResult(res, submittedProof) return } if submittedProof.Key == "code" { submittedProof.Value = "" submittedProof.Message = NewString("We've sent you a message with a verification code") SendSuccessResult(res, submittedProof) return } if submittedProof.Key != "" { submittedProof.Id = Hash(submittedProof.Key+"::"+submittedProof.Value, 20) alreadyExist := false for i := 0; i < len(verifiedProof); i++ { if verifiedProof[i].Id == submittedProof.Id { alreadyExist = true break } } if alreadyExist == false { verifiedProof = append(verifiedProof, submittedProof) } } // 4) Find remaining proofs: requiredProof - verifiedProof remainingProof = model.ShareProofCalculateRemainings(requiredProof, verifiedProof) // 5) persist proofs in client cookie cookie := http.Cookie{ Name: COOKIE_NAME_PROOF, Value: func(p []model.Proof) string { j, _ := json.Marshal(p) str, _ := EncryptString(SECRET_KEY_DERIVATE_FOR_PROOF, string(j)) return str }(verifiedProof), Path: COOKIE_PATH, MaxAge: 60 * 60 * 24 * 30, HttpOnly: true, SameSite: http.SameSiteNoneMode, Secure: true, } http.SetCookie(res, &cookie) if len(remainingProof) > 0 { SendSuccessResult(res, remainingProof[0]) return } SendSuccessResult(res, struct { Id string `json:"id"` Path string `json:"path"` CanRead bool `json:"can_read"` CanWrite bool `json:"can_write"` CanUpload bool `json:"can_upload"` }{ Id: s.Id, Path: s.Path, CanRead: s.CanRead, CanWrite: s.CanWrite, CanUpload: s.CanUpload, }) } ================================================ FILE: server/ctrl/static/404.html ================================================ 404

    404

    Page Not Found

    ================================================ FILE: server/ctrl/static/loader.html ================================================ {{ define "loader-cat" }}customElements.define("component-bootscreen", class ComponentBootScreen extends HTMLElement { connectedCallback() { this.innerHTML = this.render(); this.timeout = setTimeout(function(){ const $rbw = document.querySelector("#rbw .w"); $rbw.innerHTML = $rbw.innerHTML.repeat(10); const $loader = document.querySelector("#n-lder"); $loader.classList.add("loading"); }, 500); } disconnectedCallback() { clearTimeout(this.timeout); } render() { return `
    `; }});{{ end }} {{ define "loader-basic" }}customElements.define("component-bootscreen", class ComponentBootScreen extends HTMLElement { connectedCallback() { this.innerHTML = `
    `;}});{{ end }} ================================================ FILE: server/ctrl/static.go ================================================ package ctrl import ( "bytes" "compress/gzip" _ "embed" "encoding/base64" "encoding/json" "fmt" "io" "io/fs" "net/http" "os" "path/filepath" "regexp" "strconv" "strings" "text/template" . "github.com/mickael-kerjean/filestash" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/pkg/compress" "github.com/bluekeyes/go-gitdiff/gitdiff" ) var ( WWWDir fs.FS //go:embed static/404.html HtmlPage404 []byte //go:embed static/loader.html TmplLoader []byte ) func init() { WWWDir = os.DirFS(GetAbsolutePath("../")) } func ServeBackofficeHandler(ctx *App, res http.ResponseWriter, req *http.Request) { url := req.URL.Path if filepath.Ext(filepath.Base(url)) != "" { req.URL.Path = strings.TrimPrefix(TrimBase(req.URL.Path), "/admin/") ServeFile("/")(ctx, res, req) return } if url != URL_SETUP && Config.Get("auth.admin").String() == "" { http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect) return } ServeIndex("index.backoffice.html")(ctx, res, req) return } func ServeFrontofficeHandler(ctx *App, res http.ResponseWriter, req *http.Request) { ua := req.Header.Get("User-Agent") if strings.Contains(ua, "MSIE ") || strings.Contains(ua, "Trident/") || strings.Contains(ua, "Edge/") { // Microsoft is behaving on many occasion differently than Firefox / Chrome. // I have neither the time / motivation for it to work properly res.WriteHeader(http.StatusBadRequest) res.Write([]byte(Page(`

    Internet explorer is not supported

    We don't support IE / Edge at this time
    Please use either Chromium, Firefox or Chrome

    `))) return } url := TrimBase(req.URL.Path) if url != "/" && strings.HasPrefix(url, "/s/") == false && strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false && url != "/login" && url != "/logout" && strings.HasPrefix(url, "/tags") == false { NotFoundHandler(ctx, res, req) return } if url != URL_SETUP && Config.Get("auth.admin").String() == "" { http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect) return } ServeIndex("index.frontoffice.html")(ctx, res, req) } func ServeFavicon(ctx *App, res http.ResponseWriter, req *http.Request) { if binary, mime := Hooks.Get.Favicon(); len(binary) > 0 { res.Header().Set("Content-Type", mime) res.Write(binary) return } r, _ := http.NewRequest(http.MethodGet, "/favicon.svg", nil) ServeFile("/assets/logo/")(ctx, res, r) } func NotFoundHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if strings.Contains(req.Header.Get("accept"), "text/html") { res.WriteHeader(http.StatusNotFound) res.Write(HtmlPage404) return } SendErrorResult(res, ErrNotFound) } func ManifestHandler(ctx *App, res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) res.Write([]byte(fmt.Sprintf(`{ "name": "%s", "short_name": "%s", "icons": [ { "src": "/assets/logo/android-chrome-192x192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/assets/logo/android-chrome-512x512.png", "type": "image/png", "sizes": "512x512" } ], "theme_color": "#f2f3f5", "background_color": "#f2f3f5", "orientation": "any", "display": "standalone", "start_url": "/" }`, Config.Get("general.name"), Config.Get("general.name")))) } func RobotsHandler(ctx *App, res http.ResponseWriter, req *http.Request) { res.Write([]byte("")) } func CustomCssHandler(ctx *App, res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "text/css") io.WriteString(res, Hooks.Get.CSS()) io.WriteString(res, Config.Get("general.custom_css").String()) } func ServeFile(chroot string) func(*App, http.ResponseWriter, *http.Request) { return func(ctx *App, res http.ResponseWriter, req *http.Request) { filePath := JoinPath( chroot, strings.Replace( TrimBase(req.URL.Path), "assets/"+BUILD_REF+"/", "assets/", 1, ), ) head := res.Header() head.Set("Cache-Control", "no-cache") if f := applyPatch(filePath); f != nil { head.Set("Content-Type", GetMimeType(filepath.Ext(filePath))) res.WriteHeader(http.StatusOK) res.Write(f.Bytes()) return } // case: main path acceptEncoding := req.Header.Get("Accept-Encoding") staticConfig := []struct { ContentType string FileExt string }{ {"br", ".br"}, {"gzip", ".gz"}, {"", ""}, } for _, cfg := range staticConfig { if strings.Contains(acceptEncoding, cfg.ContentType) == false { continue } curPath := filePath + cfg.FileExt file, err := WWWPublic.Open(curPath) if err != nil { continue } else if stat, err := file.Stat(); err == nil { etag := QuickHash(fmt.Sprintf( "%s %d %d %s", curPath, stat.Size(), stat.Mode(), stat.ModTime()), 10, ) if etag == req.Header.Get("If-None-Match") { res.WriteHeader(http.StatusNotModified) return } head.Set("Etag", etag) } head.Set("Content-Type", GetMimeType(filepath.Ext(filePath))) if cfg.ContentType != "" { head.Set("Content-Encoding", cfg.ContentType) } res.WriteHeader(http.StatusOK) io.Copy(res, file) file.Close() return } http.NotFound(res, req) } } func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request) { // STEP1: pull the data from the embed file, err := WWWPublic.Open(indexPath) if err != nil { return func(ctx *App, res http.ResponseWriter, req *http.Request) { http.NotFound(res, req) } } defer file.Close() // STEP2: compile the template b, err := io.ReadAll(file) if err != nil { return func(ctx *App, res http.ResponseWriter, req *http.Request) { SendErrorResult(res, err) } } tmpl := template.Must(template.New(indexPath).Funcs(template.FuncMap{ "load_asset": func(path string) (string, error) { file, err := WWWPublic.Open(path) if err != nil { return "", err } out := "/* LOAD " + path + " */ " f, err := io.ReadAll(file) file.Close() out += regexp.MustCompile(`\s+`).ReplaceAllString( strings.ReplaceAll(string(f), "\n", ""), " ", ) return out, err }, }).Parse(string(b))) tmpl = template.Must(tmpl.Parse(string(TmplLoader))) return func(ctx *App, res http.ResponseWriter, req *http.Request) { head := res.Header() sign := signature() base := WithBase("/") templateData := map[string]any{ "appname": APPNAME, "base": base, "version": BUILD_REF, "license": LICENSE, "hash": sign, "favicon": favicon(), "bundle": func() []string { b := make([]string, len(preload)) v := BUILD_REF[0:7] + "::" + sign for i := 0; i < len(preload); i++ { b[i] = fmt.Sprintf("./assets/bundle.js?version=%s&chunk=%d", v, i+1) } return b }(), } calculatedEtag := QuickHash(base+BUILD_REF+LICENSE+sign, 10) head.Set("ETag", calculatedEtag) if etag := req.Header.Get("If-None-Match"); etag == calculatedEtag { res.WriteHeader(http.StatusNotModified) return } var out io.Writer = res if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { head.Set("Content-Encoding", "gzip") gz := gzip.NewWriter(res) defer gz.Close() out = gz } head.Set("Content-Type", "text/html") head.Set("Cache-Control", "no-cache") tmpl.Execute(out, templateData) } } var preload = [][]string{ { "/assets/" + BUILD_REF + "/boot/ctrl_boot_frontoffice.js", "/assets/" + BUILD_REF + "/boot/router_frontoffice.js", "/assets/" + BUILD_REF + "/boot/common.js", "/assets/" + BUILD_REF + "/css/designsystem_input.css", "/assets/" + BUILD_REF + "/css/designsystem_textarea.css", "/assets/" + BUILD_REF + "/css/designsystem_inputgroup.css", "/assets/" + BUILD_REF + "/css/designsystem_checkbox.css", "/assets/" + BUILD_REF + "/css/designsystem_formbuilder.css", "/assets/" + BUILD_REF + "/css/designsystem_button.css", "/assets/" + BUILD_REF + "/css/designsystem_icon.css", "/assets/" + BUILD_REF + "/css/designsystem_dropdown.css", "/assets/" + BUILD_REF + "/css/designsystem_container.css", "/assets/" + BUILD_REF + "/css/designsystem_box.css", "/assets/" + BUILD_REF + "/css/designsystem_darkmode.css", "/assets/" + BUILD_REF + "/css/designsystem_skeleton.css", "/assets/" + BUILD_REF + "/css/designsystem_utils.css", "/assets/" + BUILD_REF + "/css/designsystem_alert.css", "/assets/" + BUILD_REF + "/css/designsystem.css", "/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.css", "/assets/" + BUILD_REF + "/components/loader.js", "/assets/" + BUILD_REF + "/components/modal.js", "/assets/" + BUILD_REF + "/components/modal.css", "/assets/" + BUILD_REF + "/components/notification.js", "/assets/" + BUILD_REF + "/components/notification.css", "/assets/" + BUILD_REF + "/components/sidebar.js", "/assets/" + BUILD_REF + "/components/sidebar_files.js", "/assets/" + BUILD_REF + "/components/sidebar_tags.js", "/assets/" + BUILD_REF + "/components/sidebar.css", "/assets/" + BUILD_REF + "/components/dropdown.js", "/assets/" + BUILD_REF + "/components/decorator_shell_filemanager.js", "/assets/" + BUILD_REF + "/components/form.js", "/assets/" + BUILD_REF + "/components/icon.js", "/assets/" + BUILD_REF + "/components/breadcrumb.js", "/assets/" + BUILD_REF + "/components/breadcrumb.css", "/assets/" + BUILD_REF + "/components/skeleton.js", "/assets/" + BUILD_REF + "/helpers/loader.js", "/assets/" + BUILD_REF + "/helpers/log.js", "/assets/" + BUILD_REF + "/helpers/sdk.js", "/assets/" + BUILD_REF + "/lib/rx.js", "/assets/" + BUILD_REF + "/lib/ajax.js", }, { "/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs.min.js", "/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-ajax.min.js", "/assets/" + BUILD_REF + "/lib/vendor/rxjs/rxjs-shared.min.js", "/assets/" + BUILD_REF + "/lib/store.js", "/assets/" + BUILD_REF + "/lib/form.js", "/assets/" + BUILD_REF + "/lib/path.js", "/assets/" + BUILD_REF + "/lib/random.js", "/assets/" + BUILD_REF + "/lib/settings.js", "/assets/" + BUILD_REF + "/lib/animate.js", "/assets/" + BUILD_REF + "/lib/assert.js", "/assets/" + BUILD_REF + "/lib/dom.js", "/assets/" + BUILD_REF + "/lib/skeleton/index.js", "/assets/" + BUILD_REF + "/lib/skeleton/router.js", "/assets/" + BUILD_REF + "/lib/skeleton/lifecycle.js", "/assets/" + BUILD_REF + "/lib/error.js", "/assets/" + BUILD_REF + "/locales/index.js", "/assets/" + BUILD_REF + "/model/config.js", "/assets/" + BUILD_REF + "/model/chromecast.js", "/assets/" + BUILD_REF + "/model/session.js", "/assets/" + BUILD_REF + "/model/plugin.js", "/assets/" + BUILD_REF + "/pages/ctrl_logout.js", "/assets/" + BUILD_REF + "/pages/ctrl_error.js", }, { "/assets/" + BUILD_REF + "/pages/ctrl_homepage.js", "/assets/" + BUILD_REF + "/pages/ctrl_connectpage.js", "/assets/" + BUILD_REF + "/pages/ctrl_connectpage.css", "/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.css", "/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form.js", "/assets/" + BUILD_REF + "/pages/connectpage/ctrl_forkme.js", "/assets/" + BUILD_REF + "/pages/connectpage/ctrl_poweredby.js", "/assets/" + BUILD_REF + "/pages/connectpage/model_backend.js", "/assets/" + BUILD_REF + "/pages/connectpage/model_config.js", "/assets/" + BUILD_REF + "/pages/connectpage/ctrl_form_state.js", "/assets/" + BUILD_REF + "/pages/filespage/thing.js", "/assets/" + BUILD_REF + "/pages/filespage/thing.css", }, { "/assets/" + BUILD_REF + "/pages/ctrl_filespage.js", "/assets/" + BUILD_REF + "/pages/ctrl_filespage.css", "/assets/" + BUILD_REF + "/pages/filespage/model_acl.js", "/assets/" + BUILD_REF + "/pages/filespage/cache.js", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.js", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_filesystem.css", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.js", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_upload.css", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.js", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_newitem.css", }, { "/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.js", "/assets/" + BUILD_REF + "/pages/filespage/ctrl_submenu.css", "/assets/" + BUILD_REF + "/pages/filespage/state_config.js", "/assets/" + BUILD_REF + "/pages/filespage/helper.js", "/assets/" + BUILD_REF + "/pages/filespage/model_files.js", "/assets/" + BUILD_REF + "/pages/filespage/model_tag.js", "/assets/" + BUILD_REF + "/pages/filespage/model_virtual_layer.js", "/assets/" + BUILD_REF + "/pages/filespage/modal.css", "/assets/" + BUILD_REF + "/pages/filespage/modal_tag.js", "/assets/" + BUILD_REF + "/pages/filespage/modal_tag.css", "/assets/" + BUILD_REF + "/pages/filespage/modal_share.js", "/assets/" + BUILD_REF + "/pages/filespage/modal_share.css", "/assets/" + BUILD_REF + "/pages/filespage/modal_rename.js", "/assets/" + BUILD_REF + "/pages/filespage/modal_delete.js", "/assets/" + BUILD_REF + "/pages/filespage/state_selection.js", "/assets/" + BUILD_REF + "/pages/filespage/state_newthing.js", // "/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.js", // TODO: dynamic imports "/assets/" + BUILD_REF + "/pages/ctrl_viewerpage.css", "/assets/" + BUILD_REF + "/pages/viewerpage/mimetype.js", "/assets/" + BUILD_REF + "/pages/viewerpage/model_files.js", "/assets/" + BUILD_REF + "/pages/viewerpage/common.js", "/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.js", "/assets/" + BUILD_REF + "/pages/viewerpage/application_downloader.css", "/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.js", "/assets/" + BUILD_REF + "/pages/viewerpage/component_menubar.css", }, } func ServeBundle() func(*App, http.ResponseWriter, *http.Request) { isDebug := os.Getenv("DEBUG") == "true" buildChunks := func(quality int) (chunks [][]byte, chunksBr [][]byte, chunksGzip [][]byte, etags []string) { numChunks := len(preload) chunks = make([][]byte, numChunks+1) chunksBr = make([][]byte, numChunks+1) chunksGzip = make([][]byte, numChunks+1) etags = make([]string, numChunks+1) var fullBuf bytes.Buffer for i := 0; i < numChunks; i++ { var chunkBuf bytes.Buffer for _, path := range preload[i] { curPath := "/assets/" + strings.TrimPrefix(path, "/assets/"+BUILD_REF+"/") f := applyPatch(curPath) if f == nil { file, err := WWWPublic.Open(curPath) if err != nil { Log.Warning("static::bundler failed to find file %s", err.Error()) continue } f = new(bytes.Buffer) if _, err := io.Copy(f, file); err != nil { Log.Warning("static::bundler msg=copy_error err=%s", err.Error()) continue } file.Close() } code, err := json.Marshal(f.String()) if err != nil { Log.Warning("static::bundle msg=marshal_failed path=%s err=%s", path, err.Error()) continue } line := fmt.Sprintf("bundler.register(%q, %s);\n", WithBase(path), code) chunkBuf.WriteString(line) fullBuf.WriteString(line) } chunks[i+1] = chunkBuf.Bytes() chunksGzip[i+1] = compress.Gzip(chunks[i+1], quality) chunksBr[i+1] = compress.Br(chunks[i+1], quality) etags[i+1] = QuickHash(string(chunks[i+1]), 10) } chunks[0] = fullBuf.Bytes() chunksGzip[0] = compress.Gzip(chunks[0], quality) chunksBr[0] = compress.Br(chunks[0], quality) etags[0] = QuickHash(string(chunks[0]), 10) return chunks, chunksBr, chunksGzip, etags } quality := 11 if isDebug { quality = 8 } chunks, chunksBr, chunksGzip, etags := buildChunks(quality) return func(ctx *App, res http.ResponseWriter, req *http.Request) { if isDebug { chunks, chunksBr, chunksGzip, etags = buildChunks(quality) } chunkIndex := 0 if parsed, err := strconv.Atoi(req.URL.Query().Get("chunk")); err == nil { chunkIndex = parsed } if chunkIndex >= len(chunks) { http.NotFound(res, req) return } head := res.Header() head.Set("Content-Type", "application/javascript") head.Set("Cache-Control", "no-cache") head.Set("Etag", etags[chunkIndex]) if req.Header.Get("If-None-Match") == etags[chunkIndex] && etags[chunkIndex] != "" { res.WriteHeader(http.StatusNotModified) return } else if strings.Contains(req.Header.Get("Accept-Encoding"), "br") && len(chunksBr[chunkIndex]) > 0 { head.Set("Content-Encoding", "br") res.Write(chunksBr[chunkIndex]) return } else if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") && len(chunksGzip[chunkIndex]) > 0 { head.Set("Content-Encoding", "gzip") res.Write(chunksGzip[chunkIndex]) return } res.Write(chunks[chunkIndex]) } } func applyPatch(filePath string) (file *bytes.Buffer) { var ( outputBuffer bytes.Buffer wasPatched bool ) for i, patch := range Hooks.Get.StaticPatch() { if i == 0 { origFile, err := WWWPublic.Open(filePath) if err != nil { Log.Debug("ctrl::static cannot open public file - %+v", err.Error()) return nil } _, err = outputBuffer.ReadFrom(origFile) origFile.Close() if err != nil { Log.Debug("ctrl::static cannot read from origFile - %s", err.Error()) return nil } } patchFiles, _, err := gitdiff.Parse(NewReadCloserFromBytes(patch)) if err != nil { Log.Debug("ctrl::static cannot parse patch file - %s", err.Error()) return nil } for i := 0; i < len(patchFiles); i++ { if patchFiles[i].NewName != patchFiles[i].OldName { continue } else if filePath != strings.TrimPrefix(patchFiles[i].NewName, "public") { continue } var patched bytes.Buffer if err := gitdiff.Apply( &patched, bytes.NewReader(outputBuffer.Bytes()), patchFiles[i], ); err != nil { Log.Debug("ctrl::static err=cannot_apply_patch path=%s err=%s", patchFiles[i].NewName, err.Error()) return nil } outputBuffer = patched wasPatched = true } } if wasPatched { return &outputBuffer } return nil } func signature() string { text := BUILD_REF patches := Hooks.Get.StaticPatch() for i := 0; i < len(patches); i++ { text += string(patches[i]) } entries, _ := os.ReadDir(GetAbsolutePath(PLUGIN_PATH)) for _, e := range entries { stat, _ := e.Info() text += fmt.Sprintf("[%s][%d][%s]", stat.Name(), stat.Size(), stat.ModTime().String()) } return strings.ToLower(QuickHash(text, 3)) } func favicon() string { f, mime := Hooks.Get.Favicon() if len(f) == 0 { file, err := WWWPublic.Open("/assets/logo/favicon.svg") mime = "image/svg+xml" if err != nil { return "favicon.ico" } f, err = io.ReadAll(file) if err != nil { return "favicon.ico" } } return "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(f) } ================================================ FILE: server/ctrl/tmpl.go ================================================ package ctrl import ( "bytes" "crypto/rsa" "encoding/base64" "encoding/json" "fmt" "math/big" "os" "regexp" "strings" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "github.com/golang-jwt/jwt/v5" ) func TmplExec(params string, input map[string]string) (string, error) { if params == "" { return "", nil } tmpl, err := template. New("ctrl::session::auth_middleware"). Funcs(tmplFuncs). Parse(params) if err != nil { Log.Debug("tmpl::execute action=parse err=%s", err.Error()) return params, err } var b bytes.Buffer if err = tmpl.Execute(&b, input); err != nil { Log.Debug("tmpl::execute action=execute err%s", err.Error()) return params, err } return b.String(), nil } func TmplParams(data map[string]string) map[string]string { out := map[string]string{} for key, value := range data { out[key] = value } out["machine_id"] = GenerateMachineID() for _, value := range os.Environ() { pair := strings.SplitN(value, "=", 2) if len(pair) == 2 { out[fmt.Sprintf("ENV_%s", pair[0])] = pair[1] } } return out } var tmplFuncs = template.FuncMap{ "split": func(s, sep string) []string { return strings.Split(sep, s) }, "get": func(i int, arr any) (string, error) { switch v := arr.(type) { case string: splits := strings.Split(v, ",") if i < len(splits) && i >= 0 { return strings.TrimSpace(splits[i]), nil } return "", nil case []string: if i < len(v) && i >= 0 { return v[i], nil } return "", nil default: return "", ErrNotImplemented } }, "contains": func(match string, opts ...any) (bool, error) { exact := true var input any if len(opts) == 0 { return false, ErrNotValid } else if len(opts) == 1 { input = opts[0] } else if len(opts) == 2 { exact = opts[0].(bool) input = opts[1] } switch in := input.(type) { case string: splits := strings.Split(in, ",") for _, split := range splits { split = strings.TrimSpace(split) if exact && split == match { return true, nil } else if !exact && strings.Contains(split, match) { return true, nil } } return false, nil case []string: for _, split := range in { split = strings.TrimSpace(split) if exact && split == match { return true, nil } else if !exact && strings.Contains(split, match) { return true, nil } } return false, nil default: return false, ErrNotImplemented } }, "filter": func(arg string, stdin string) (string, error) { out := []string{} r, regErr := regexp.Compile(arg) if regErr != nil { return "", regErr } for _, chunk := range strings.Split(stdin, ",") { c := strings.TrimSpace(chunk) if r.Match([]byte(c)) { out = append(out, c) } } return strings.Join(out, ", "), nil }, "replace": func(arguments ...string) (string, error) { var ( arg string stdin string replace string ) if len(arguments) == 2 { arg = arguments[0] stdin = arguments[1] } else if len(arguments) == 3 { arg = arguments[0] replace = arguments[1] stdin = arguments[2] } else { return "", ErrNotImplemented } chunks := strings.Split(stdin, ",") r, regErr := regexp.Compile(arg) if regErr != nil { return "", regErr } for i := range chunks { c := strings.TrimSpace(chunks[i]) chunks[i] = r.ReplaceAllString(c, replace) } return strings.Join(chunks, ", "), nil }, "debug": func(data string) (string, error) { Log.Debug("ctrl/tmpl data=%s", data) return data, nil }, "decryptGCM": func(key string, str string) (string, error) { t, err := base64.StdEncoding.DecodeString(str) if err != nil { return "", err } d, err := DecryptAESGCM([]byte(key), t) return string(d), err }, "encryptGCM": func(key string, str string) (string, error) { data, err := EncryptAESGCM([]byte(key), []byte(str)) return base64.StdEncoding.EncodeToString(data), err }, "jwt": func(args ...string) (string, error) { if len(args) == 0 { return "", ErrNotValid } var stdin = args[len(args)-1] var token *jwt.Token var err error claims := jwt.MapClaims{} if len(args) == 1 { token, _, err = jwt.NewParser(jwt.WithPaddingAllowed()).ParseUnverified(stdin, claims) token.Valid = true } else if len(args) == 2 { token, err = jwt.NewParser(jwt.WithPaddingAllowed()).ParseWithClaims(stdin, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { return []byte(args[0]), nil } else if _, ok := token.Method.(*jwt.SigningMethodRSA); ok { modBytes, err := base64.RawURLEncoding.DecodeString(args[0]) if err != nil { return "", err } return &rsa.PublicKey{ N: new(big.Int).SetBytes(modBytes), E: 65537, }, nil } Log.Debug("ctrl::tmpl invalid jwt signing method: %v", token.Header["alg"]) return nil, ErrNotImplemented }) } if err != nil { return "", err } else if !token.Valid { return "", ErrNotValid } b, err := json.Marshal(claims) return string(b), err }, "jq": func(args ...string) (out string, err error) { if len(args) == 0 || len(args) > 2 { return "", ErrNotValid } stdin := args[len(args)-1] data := map[string]any{} if err = json.Unmarshal([]byte(stdin), &data); err != nil { return "", err } if len(args) == 1 { return stdin, nil } filters := strings.Split(args[0], ".") for i := 0; i < len(filters)-1; i++ { if filters[i] == "" { continue } if obj, ok := data[filters[i]].(map[string]any); ok { data = obj } else { return "", nil } } switch val := data[filters[len(filters)-1]].(type) { case []any: valStr := make([]string, len(val)) for i := range val { valStr[i] = fmt.Sprintf("%v", val[i]) } return strings.Join(valStr, ", "), nil default: return fmt.Sprintf("%v", val), nil } }, "toLower": func(data string) (string, error) { return strings.ToLower(data), nil }, "toUpper": func(data string) (string, error) { return strings.ToUpper(data), nil }, } ================================================ FILE: server/ctrl/webdav.go ================================================ package ctrl import ( "net/http" "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" "github.com/mickael-kerjean/net/webdav" ) func WebdavHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if ctx.Share.Id == "" { http.NotFound(res, req) return } // https://github.com/golang/net/blob/master/webdav/webdav.go#L49-L68 canRead := model.CanRead(ctx) canWrite := model.CanEdit(ctx) canUpload := model.CanUpload(ctx) switch req.Method { case "OPTIONS", "HEAD", "GET": if canRead == false { SendErrorResult(res, ErrPermissionDenied) return } case "MKCOL", "DELETE", "COPY", "MOVE", "PROPPATCH": if canWrite == false { SendErrorResult(res, ErrPermissionDenied) return } case "PROPFIND": if canRead == false { SendErrorResult(res, ErrPermissionDenied) return } case "PUT": if canWrite == false || canUpload == false { SendErrorResult(res, ErrPermissionDenied) return } case "LOCK", "UNLOCK": if canWrite == false || canUpload == false { SendErrorResult(res, ErrPermissionDenied) return } default: SendErrorResult(res, ErrNotImplemented) return } h := &webdav.Handler{ Prefix: "/s/" + ctx.Share.Id, FileSystem: model.NewWebdavFs(ctx.Backend, ctx.Share.Backend, ctx.Share.Path, req), LockSystem: model.NewWebdavLock(), } h.ServeHTTP(res, req) } /* * OSX ask for a lot of crap while mounting as a network drive. To avoid wasting resources with such * an imbecile and considering we can't even see the source code they are running, the best approach we * could go on is: "crap in, crap out" where useless request coming in are identified and answer appropriatly */ func WebdavBlacklist(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { base := filepath.Base(req.URL.String()) if req.Method == "PUT" || req.Method == "MKCOL" { if strings.HasPrefix(base, "._") { res.WriteHeader(http.StatusMethodNotAllowed) res.Write([]byte("")) return } else if base == ".DS_Store" { res.WriteHeader(http.StatusMethodNotAllowed) res.Write([]byte("")) return } else if base == ".localized" { res.WriteHeader(http.StatusMethodNotAllowed) res.Write([]byte("")) return } } else if req.Method == "PROPFIND" { if strings.HasPrefix(base, "._") { res.WriteHeader(http.StatusForbidden) return } else if base == ".DS_Store" { res.WriteHeader(http.StatusForbidden) res.Write([]byte("")) return } else if base == ".localized" { res.WriteHeader(http.StatusForbidden) return } else if base == ".ql_disablethumbnails" { res.WriteHeader(http.StatusForbidden) res.Write([]byte("")) return } else if base == ".ql_disablecache" { res.WriteHeader(http.StatusForbidden) return } else if base == ".hidden" { res.WriteHeader(http.StatusForbidden) return } else if base == ".Spotlight-V100" { res.WriteHeader(http.StatusForbidden) return } else if base == ".metadata_never_index" { res.WriteHeader(http.StatusForbidden) return } else if base == "Contents" { res.WriteHeader(http.StatusForbidden) return } else if base == ".metadata_never_index_unless_rootfs" { res.WriteHeader(http.StatusForbidden) return } } else if req.Method == "GET" { if base == ".DS_Store" { res.WriteHeader(http.StatusForbidden) res.Write([]byte("")) return } } else if req.Method == "DELETE" { if base == ".DS_Store" { res.WriteHeader(http.StatusForbidden) res.Write([]byte("")) return } } else if req.Method == "LOCK" || req.Method == "UNLOCK" { if base == ".DS_Store" { res.WriteHeader(http.StatusMethodNotAllowed) res.Write([]byte("")) return } } fn(ctx, res, req) }) } ================================================ FILE: server/generator/constants.go ================================================ package main import ( "fmt" "os" "os/exec" "strings" "time" ) func main() { cmd, b := exec.Command("git", "rev-parse", "HEAD"), new(strings.Builder) cmd.Stdout = b cmd.Run() f, err := os.OpenFile("../common/constants_generated.go", os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) return } f.Write([]byte(fmt.Sprintf(` package common func init() { BUILD_REF = "%s" BUILD_DATE = "%s" } `, strings.TrimSpace(b.String()), time.Now().Format("20060102")))) f.Close() } ================================================ FILE: server/generator/emacs-el.go ================================================ package main import ( "fmt" "io" "os" ) func main() { f, err := os.OpenFile("../../config/emacs.el", os.O_RDONLY, os.ModePerm) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) return } defer f.Close() j, err := io.ReadAll(f) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } f, err = os.OpenFile("./export_generated.go", os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) return } f.Write([]byte(fmt.Sprintf(`package ctrl func init() { EmacsElConfig = `+"`"+` %s `+"`"+` } `, j))) f.Close() } ================================================ FILE: server/generator/mime.go ================================================ package main import ( "bytes" "encoding/json" "fmt" "go/format" "io" "os" ) func main() { f, err := os.OpenFile("../../config/mime.json", os.O_RDONLY, os.ModePerm) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) return } defer f.Close() j, err := io.ReadAll(f) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } mTypes := make(map[string]string, 0) json.Unmarshal(j, &mTypes) var buf bytes.Buffer buf.WriteString("package common\n") buf.WriteString("func init() {\n") for key, value := range mTypes { fmt.Fprintf(&buf, "MimeTypes[\"%s\"] = \"%s\"\n", key, value) } buf.WriteString("}\n") formatted, err := format.Source(buf.Bytes()) if err != nil { fmt.Fprintf(os.Stderr, "error formatting: %v\n", err) os.Exit(1) } if err = os.WriteFile("../common/mime_generated.go", formatted, 0644); err != nil { fmt.Fprintf(os.Stderr, "error writing file: %v\n", err) os.Exit(1) } } ================================================ FILE: server/middleware/context.go ================================================ package middleware import ( "encoding/json" "io" "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) func BodyParser(fn HandlerFunc) HandlerFunc { extractBody := func(req *http.Request) (map[string]interface{}, error) { body := map[string]interface{}{} byt, err := io.ReadAll(req.Body) if err != nil { return body, err } if err := json.Unmarshal(byt, &body); err != nil { if len(byt) == 0 { err = nil } return body, err } return body, nil } return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { var err error if ctx.Body, err = extractBody(req); err != nil { SendErrorResult(res, ErrNotValid) return } fn(ctx, res, req) }) } ================================================ FILE: server/middleware/http.go ================================================ package middleware import ( "fmt" "net/http" "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/time/rate" ) func ApiHeaders(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { header := res.Header() header.Set("Content-Type", "application/json") header.Set("Cache-Control", "no-cache") fn(ctx, res, req) }) } func StaticHeaders(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { header := res.Header() header.Set("Content-Type", GetMimeType(filepath.Ext(req.URL.Path))) header.Set("Cache-Control", "max-age=2592000") fn(ctx, res, req) }) } func PublicCORS(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { header := res.Header() header.Set("Access-Control-Allow-Origin", "*") header.Set("Access-Control-Allow-Headers", "x-requested-with") if req.Method == http.MethodOptions { header.Set("Access-Control-Allow-Methods", "GET, OPTIONS") res.WriteHeader(http.StatusNoContent) return } fn(ctx, res, req) }) } func IndexHeaders(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { header := res.Header() header.Set("Content-Type", "text/html") header.Set("Cache-Control", "no-cache") header.Set("Referrer-Policy", "same-origin") header.Set("X-Content-Type-Options", "nosniff") header.Set("X-XSS-Protection", "1; mode=block") if !IsWhiteLabel() { header.Set("X-Powered-By", fmt.Sprintf("Filestash/%s.%s ", APP_VERSION, BUILD_DATE)) } if ori := Config.Get("features.protection.iframe").String(); ori == "" { header.Set("X-Frame-Options", "DENY") } fn(ctx, res, req) }) } func SecureHeaders(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { header := res.Header() if Config.Get("general.force_ssl").Bool() { header.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } header.Set("X-Content-Type-Options", "nosniff") header.Set("X-XSS-Protection", "1; mode=block") fn(ctx, res, req) }) } func SecureOrigin(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { if host := Config.Get("general.host").String(); host != "" { host = strings.TrimPrefix(host, "http://") host = strings.TrimPrefix(host, "https://") if req.Host != host && req.Host != fmt.Sprintf("%s:443", host) { if strings.HasPrefix(req.URL.Path, "/admin/") == false { Log.Error("Request coming from \"%s\" was blocked, only traffic from \"%s\" is allowed. You can change this from the admin console under configure -> host", req.Host, host) SendErrorResult(res, ErrNotAllowed) return } else { Log.Warning("Access from incorrect hostname. From the admin console under configure -> host, you need to use the following hostname: '%s' current value is '%s'", req.Host, host) } } } if req.Header.Get("X-Requested-With") == "XmlHttpRequest" { // Browser XHR Access fn(ctx, res, req) return } else if Config.Get("features.api.enable").Bool() && len(req.Cookies()) == 0 { // API Access fn(ctx, res, req) return } Log.Warning("Intrusion detection: %s - %s", RetrievePublicIp(req), req.URL.String()) SendErrorResult(res, ErrNotAllowed) }) } var limiter = rate.NewLimiter(10, 1000) func RateLimiter(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { if limiter.Allow() == false { Log.Warning("middleware::http::ratelimit too many requests") SendErrorResult( res, NewError(http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests), ) return } fn(ctx, res, req) }) } func RetrievePublicIp(req *http.Request) string { if req.Header.Get("X-Forwarded-For") != "" { return req.Header.Get("X-Forwarded-For") } else { return req.RemoteAddr } } ================================================ FILE: server/middleware/index.go ================================================ package middleware import ( "net/http" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { go func() { for { time.Sleep(10 * time.Second) telemetry.Flush() } }() }) } func NewMiddlewareChain(fn HandlerFunc, m []Middleware) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { var resw ResponseWriter = NewResponseWriter(res) var f func(*App, http.ResponseWriter, *http.Request) = fn for i := len(m) - 1; i >= 0; i-- { f = m[i](f) } app := App{ Context: req.Context(), } f(&app, &resw, req) if req.Body != nil { req.Body.Close() } go logger(&app, &resw, req) } } type ResponseWriter struct { http.ResponseWriter status int start time.Time } func NewResponseWriter(res http.ResponseWriter) ResponseWriter { return ResponseWriter{ ResponseWriter: res, start: time.Now(), } } func (w *ResponseWriter) WriteHeader(status int) { w.status = status w.ResponseWriter.WriteHeader(status) } func (w *ResponseWriter) Write(b []byte) (int, error) { if w.status == 0 { w.status = 200 } return w.ResponseWriter.Write(b) } func (w *ResponseWriter) Status() int { return w.status } func (w *ResponseWriter) Flush() { w.ResponseWriter.(http.Flusher).Flush() } func PluginInjector(fn HandlerFunc) HandlerFunc { for _, middleware := range Hooks.Get.Middleware() { fn = middleware(fn) } return fn } ================================================ FILE: server/middleware/session.go ================================================ package middleware import ( "bytes" "encoding/base64" "encoding/json" "net/http" "regexp" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" ) func LoggedInOnly(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { if ctx.Backend == nil || ctx.Session == nil { SendErrorResult(res, ErrPermissionDenied) return } fn(ctx, res, req) }) } func AdminOnly(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { if admin := Config.Get("auth.admin").String(); admin != "" { authStr := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ") if authStr == "" { c, err := req.Cookie(COOKIE_NAME_ADMIN) if err != nil { SendErrorResult(res, ErrPermissionDenied) return } authStr = c.Value } str, err := DecryptString(SECRET_KEY_DERIVATE_FOR_ADMIN, authStr) if err != nil { SendErrorResult(res, ErrPermissionDenied) return } token := AdminToken{} json.Unmarshal([]byte(str), &token) if token.IsValid() == false || token.IsAdmin() == false { SendErrorResult(res, ErrPermissionDenied) return } } fn(ctx, res, req) }) } func SessionStart(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { var err error if ctx.Share, err = _extractShare(req); err != nil { SendErrorResult(res, err) return } ctx.Authorization = _extractAuthorization(req) if ctx.Session, err = _extractSession(req, ctx); err != nil { RecoverFromBadCookie(res) SendErrorResult(res, err) return } if ctx.Backend, err = _extractBackend(req, ctx); err != nil { if len(ctx.Session) == 0 { SendErrorResult(res, ErrNotAuthorized) return } SendErrorResult(res, err) return } ctx.Languages = _extractLanguages(req) fn(ctx, res, req) }) } func SessionTry(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { ctx.Share, _ = _extractShare(req) ctx.Authorization = _extractAuthorization(req) ctx.Session, _ = _extractSession(req, ctx) ctx.Backend, _ = _extractBackend(req, ctx) fn(ctx, res, req) }) } func CanManageShare(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { share_id := mux.Vars(req)["share"] if share_id == "" { Log.Debug("middleware::session::share 'invalid share id'") SendErrorResult(res, ErrNotValid) return } // anyone can manage a share_id that's not been attributed yet s, err := model.ShareGet(share_id) if err != nil { if err == ErrNotFound { SessionStart(fn)(ctx, res, req) return } Log.Debug("middleware::session::share 'cannot get share - %s'", err.Error()) SendErrorResult(res, err) return } // In a scenario where the shared link has already been atributed, we need to make sure // the user that's currently logged in can manage the link. 2 scenarios here: // 1) scenario 1: the user is the very same one that generated the shared link in the first place ctx.Share = Share{} ctx.Authorization = _extractAuthorization(req) if ctx.Session, err = _extractSession(req, ctx); err != nil { Log.Debug("middleware::session::share 'cannot extract session - %s'", err.Error()) SendErrorResult(res, err) return } if s.Backend == GenerateID(ctx.Session) { fn(ctx, res, req) return } // 2) scenario 2: the user is different than the one that has generated the shared link // in this scenario, the link owner might have granted for user the right to reshare links if ctx.Share, err = _extractShare(req); err != nil { Log.Debug("middleware::session::share 'cannot extract share - %s'", err.Error()) SendErrorResult(res, err) return } ctx.Authorization = _extractAuthorization(req) if ctx.Session, err = _extractSession(req, ctx); err != nil { Log.Debug("middleware::session::share 'cannot extract session 2 - %s'", err.Error()) SendErrorResult(res, err) return } id := GenerateID(ctx.Session) if s.Backend == id { if s.CanShare == true { fn(ctx, res, req) return } Log.Debug("middleware::session::share 'permission denied - s.CanShare[%+v] s.Backend[%s]'", s.CanShare, s.Backend) } else { Log.Debug("middleware::session::share 'permission denied - s.CanShare[%+v] s.Backend[%s] GenerateID[%s]'", s.CanShare, s.Backend, id) } SendErrorResult(res, ErrPermissionDenied) return }) } func _extractAuthorization(req *http.Request) (token string) { // strategy 1: split cookie index := 0 for { cookie, err := req.Cookie(CookieName(index)) if err != nil { break } index++ token += cookie.Value } // strategy 2: Authorization header if token == "" { authHeader := req.Header.Get("Authorization") if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") { token = strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ") } } // strategy 3: Authorization query param if token == "" { if auth := req.URL.Query().Get("authorization"); auth != "" { token = auth } } return token } func _extractShareId(req *http.Request) string { share := req.URL.Query().Get("share") if share != "" { return share } m := mux.Vars(req)["share"] if m == "private" { return "" } return m } func _extractShare(req *http.Request) (Share, error) { var err error share_id := _extractShareId(req) if share_id == "" { return Share{}, nil } if Config.Get("features.share.enable").Bool() == false { Log.Debug("Share feature isn't enabled, contact your administrator") return Share{}, NewError("Feature isn't enabled, contact your administrator", 405) } s, err := model.ShareGet(share_id) if err != nil { return Share{}, nil } if err = s.IsValid(); err != nil { return Share{}, err } var verifiedProof []model.Proof = model.ShareProofGetAlreadyVerified(req) username, password := func(authHeader string) (string, string) { decoded, err := base64.StdEncoding.DecodeString( strings.TrimPrefix(authHeader, "Basic "), ) if err != nil { return "", "" } s := bytes.Split(decoded, []byte(":")) if len(s) < 2 { return "", "" } p := string(bytes.Join(s[1:], []byte(":"))) usr := regexp.MustCompile(`^(.*)\[([0-9a-zA-Z]+)\]$`).FindStringSubmatch(string(s[0])) if len(usr) != 3 { return "", p } if Hash(usr[1]+SECRET_KEY_DERIVATE_FOR_HASH, 10) != usr[2] { return "", p } return usr[1], p }(req.Header.Get("Authorization")) if s.Users != nil && username != "" { if v, ok := model.ShareProofVerifierEmail(*s.Users, username); ok { verifiedProof = append(verifiedProof, model.Proof{Key: "email", Value: v}) } } if s.Password != nil && password != "" { if v, ok := model.ShareProofVerifierPassword(*s.Password, password); ok { verifiedProof = append(verifiedProof, model.Proof{Key: "password", Value: v}) } } var requiredProof []model.Proof = model.ShareProofGetRequired(s) var remainingProof []model.Proof = model.ShareProofCalculateRemainings(requiredProof, verifiedProof) if len(remainingProof) != 0 { return Share{}, NewError("Unauthorized Shared space", 400) } return s, nil } func _extractSession(req *http.Request, ctx *App) (map[string]string, error) { var ( str string err error session map[string]string = make(map[string]string) ) if ctx.Share.Id != "" { // Shared link str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, ctx.Share.Auth) if err != nil { // This typically happen when changing the secret key return session, ErrNotAuthorized } err = json.Unmarshal([]byte(str), &session) if IsDirectory(ctx.Share.Path) { session["path"] = ctx.Share.Path } else { // when the shared link is pointing to a file, we mustn't have access to the surroundings // => we need to take extra care of which path to use as a chroot var path string = req.URL.Query().Get("path") if strings.HasPrefix(req.URL.Path, "/api/export/") == true { var re = regexp.MustCompile(`^/api/export/[^\/]+/[^\/]+/[^\/]+(\/.+)$`) path = re.ReplaceAllString(req.URL.Path, `$1`) } if strings.HasSuffix(ctx.Share.Path, path) == false { return make(map[string]string), ErrPermissionDenied } session["path"] = strings.TrimSuffix(ctx.Share.Path, path) + "/" } return session, err } if ctx.Authorization == "" { return session, nil } str, err = DecryptString(SECRET_KEY_DERIVATE_FOR_USER, ctx.Authorization) if err != nil { // This typically happen when changing the secret key Log.Debug("middleware::session decrypt error '%s'", err.Error()) return session, ErrNotAuthorized } if err = json.Unmarshal([]byte(str), &session); err != nil { return session, err } t, err := time.Parse(time.RFC3339, session["timestamp"]) if err != nil { Log.Warning("middleware::session 'cannot parse time - %s'", err.Error()) return session, ErrNotAuthorized } else if t.Add(24 * 365 * time.Hour).Before(time.Now()) { Log.Warning("middleware::session 'cookie too old - %s'", t.Format(time.RFC3339)) return session, ErrNotAuthorized } return session, err } func _extractBackend(req *http.Request, ctx *App) (IBackend, error) { return model.NewBackend(ctx, ctx.Session) } func _extractLanguages(req *http.Request) []string { var lng = []string{} for _, lngs := range strings.Split(req.Header.Get("Accept-Language"), ",") { chunks := strings.Split(lngs, ";") if len(chunks) == 0 { continue } lng = append(lng, chunks[0]) } return lng } ================================================ FILE: server/middleware/telemetry.go ================================================ package middleware import ( "bytes" "encoding/json" "net/http" "sync" "time" . "github.com/mickael-kerjean/filestash/server/common" ) var telemetry = Telemetry{Data: make([]LogEntry, 0)} type Telemetry struct { Data []LogEntry mu sync.Mutex } type LogEntry struct { Host string `json:"host"` Method string `json:"method"` RequestURI string `json:"pathname"` Proto string `json:"proto"` Status int `json:"status"` Scheme string `json:"scheme"` UserAgent string `json:"userAgent"` Ip string `json:"ip"` Referer string `json:"referer"` Duration float64 `json:"responseTime"` Version string `json:"version"` Backend string `json:"backend"` Share string `json:"share"` License string `json:"license"` Session string `json:"session"` RequestID string `json:"requestID"` } func logger(ctx *App, res http.ResponseWriter, req *http.Request) { if obj, ok := res.(*ResponseWriter); ok && req.RequestURI != "/about" { point := LogEntry{ Version: APP_VERSION + "." + BUILD_DATE, License: LICENSE, Scheme: req.URL.Scheme, Host: req.Host, Method: req.Method, RequestURI: req.RequestURI, Proto: req.Proto, Status: obj.status, UserAgent: req.Header.Get("User-Agent"), Ip: req.RemoteAddr, Referer: req.Referer(), Duration: float64(time.Now().Sub(obj.start)) / (1000 * 1000), Backend: func() string { if ctx.Session["type"] == "" { return "null" } return ctx.Session["type"] }(), Share: func() string { if ctx.Share.Id == "" { return "null" } return ctx.Share.Id }(), Session: func() string { if ctx.Session["type"] == "" { return "null" } return GenerateID(ctx.Session) }(), RequestID: func() string { defer func() string { if r := recover(); r != nil { return "oops" } return "null" }() return res.Header().Get("X-Request-ID") }(), } if Config.Get("log.telemetry").Bool() { telemetry.Record(point) } if Config.Get("log.enable").Bool() { Log.Stdout("HTTP %3d %3s %6.1fms %s", point.Status, point.Method, point.Duration, limit(point.RequestURI, 200)) } } } func limit(input string, maxLength int) string { if len(input) > maxLength { return input[:maxLength] + "..." } return input } func (this *Telemetry) Record(point LogEntry) { this.mu.Lock() this.Data = append(this.Data, point) this.mu.Unlock() } func (this *Telemetry) Flush() { if len(this.Data) == 0 { return } this.mu.Lock() pts := this.Data this.Data = make([]LogEntry, 0) this.mu.Unlock() body, err := json.Marshal(pts) if err != nil { return } r, err := http.NewRequest("POST", "https://downloads.filestash.app/event", bytes.NewReader(body)) r.Header.Set("Connection", "Close") r.Header.Set("Content-Type", "application/json") r.Close = true if err != nil { return } resp, err := HTTP.Do(r) if err != nil { return } resp.Body.Close() } ================================================ FILE: server/model/audit.go ================================================ package model import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.AuditEngine(SimpleAudit{}) } var AuditForm Form = Form{ Form: []Form{ Form{ Title: "search", Elmnts: []FormElement{ FormElement{ Name: "date from", Type: "datetime", }, FormElement{ Name: "date to", Type: "datetime", }, FormElement{ Name: "action", Type: "select", Opts: []string{"", "rename", "list", "download", "create_folder", "remove", "move", "save_file", "create_file"}, }, FormElement{ Name: "path", Type: "text", }, FormElement{ Name: "backend", Type: "text", }, FormElement{ Name: "session", Type: "text", }, FormElement{ Name: "share", Type: "text", }, FormElement{ Name: "user", Type: "text", }, FormElement{ Name: "target", Type: "text", }, }, }, }, } type SimpleAudit struct{} func (this SimpleAudit) Query(ctx *App, searchParams map[string]string) (AuditQueryResult, error) { return AuditQueryResult{ Form: &AuditForm, RenderHTML: `
    You need to install an audit plugin to use this
    `, }, nil } ================================================ FILE: server/model/files.go ================================================ package model import ( "fmt" . "github.com/mickael-kerjean/filestash/server/common" "strings" ) func NewBackend(ctx *App, conn map[string]string) (IBackend, error) { isAllowed := func() bool { // by default, a hacker could use filestash to establish connections outside of what's // define in the config file. We need to prevent this possibilities := make([]map[string]interface{}, 0) for i := 0; i < len(Config.Conn); i++ { d := Config.Conn[i] if d["type"] != conn["type"] { continue } if val, ok := d["hostname"]; ok == true { if val != conn["hostname"] { continue } } if val, ok := d["path"]; ok == true { if val == nil { val = "/" } if configPath, ok := val.(string); ok == false { continue } else if strings.HasPrefix(conn["path"], configPath) == false { continue } } if val, ok := d["url"]; ok == true { if val != conn["url"] { continue } } possibilities = append(possibilities, Config.Conn[i]) } if len(possibilities) > 0 { return true } return false } if isAllowed() == false { return Backend.Get(BACKEND_NIL), ErrNotAllowed } return Backend.Get(conn["type"]).Init(conn, ctx) } func GetHome(b IBackend, base string) (string, error) { if strings.TrimSpace(base) == "" { base = "/" } home := "/" if obj, ok := b.(interface{ Home() (string, error) }); ok { tmp, err := obj.Home() if err != nil { return base, err } home = EnforceDirectory(tmp) } else if _, err := b.Ls(base); err != nil { return base, err } base = EnforceDirectory(base) if strings.HasPrefix(home, base) { return "/" + home[len(base):], nil } return "/", nil } func MapStringInterfaceToMapStringString(m map[string]interface{}) map[string]string { res := make(map[string]string) for key, value := range m { res[key] = fmt.Sprintf("%v", value) if res[key] == "" { res[key] = "" } } return res } ================================================ FILE: server/model/formater/README.md ================================================ This is a bare bone utilities to convert a stream onto text for full text search purpose. There's some other alternative but none of them run with a small footprint. At the moment it supports: - office documents - pdf (TODO: remove dependency on pdftotext) - text base files ================================================ FILE: server/model/formater/office.go ================================================ package formater import ( "archive/zip" "bytes" "encoding/xml" "fmt" . "github.com/mickael-kerjean/filestash/server/common" "io" "math/rand" "os" "regexp" "strings" ) func OfficeFormater(r io.ReadCloser) (io.ReadCloser, error) { tmpName := fmt.Sprintf("/tmp/docx_%d.docx", rand.Intn(1000000)) defer os.Remove(tmpName) f, err := os.OpenFile(tmpName, os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { return nil, err } _, err = io.Copy(f, r) if err != nil { return nil, err } z, err := zip.OpenReader(tmpName) if err != nil { return nil, err } defer z.Close() hasData := false content := bytes.NewBuffer([]byte{}) for _, f := range z.File { shouldExtract := false if f.Name == "word/document.xml" { shouldExtract = true } else if strings.HasPrefix(f.Name, "ppt/slides/slide") { shouldExtract = true } else if f.Name == "xl/sharedStrings.xml" { shouldExtract = true } if shouldExtract == false { continue } hasData = true o, err := f.Open() if err != nil { return nil, err } dec := xml.NewDecoder(o) for { t, err := dec.Token() if err != nil { break } if t == nil { break } switch el := t.(type) { case xml.StartElement: if el.Name.Local == "t" { w := WordDoc{} dec.DecodeElement(&w, &el) if len(w.Text) > 0 { w.Text = regexp.MustCompile("\\s+\\.\\s+").ReplaceAll(w.Text, []byte(". ")) w.Text = regexp.MustCompile("\\s{2,}").ReplaceAll(w.Text, []byte(" ")) content.Write(w.Text) content.Write([]byte(" ")) } } } } } if hasData == false { return nil, ErrNotFound } return NewReadCloserFromReader(content), nil } type WordDoc struct { Text []byte `xml:",innerxml"` } ================================================ FILE: server/model/formater/pdf.go ================================================ package formater import ( "bytes" "fmt" . "github.com/mickael-kerjean/filestash/server/common" "io" "math/rand" "os" "os/exec" ) func PdfFormater(r io.ReadCloser) (io.ReadCloser, error) { tmpName := fmt.Sprintf("/tmp/pdf_%d.docx", rand.Intn(1000000)) defer os.Remove(tmpName) f, err := os.OpenFile(tmpName, os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { return nil, err } _, err = io.Copy(f, r) if err != nil { f.Close() return nil, err } f.Close() cmd := exec.Command("pdftotext", tmpName, "-") out := bytes.NewBuffer([]byte{}) cmd.Stdout = out err = cmd.Run() if err != nil { return nil, err } return NewReadCloserFromReader(out), nil } ================================================ FILE: server/model/formater/txt.go ================================================ package formater import ( "io" ) func TxtFormater(rc io.ReadCloser) (io.ReadCloser, error) { return rc, nil } ================================================ FILE: server/model/index.go ================================================ package model import ( "database/sql" "time" . "github.com/mickael-kerjean/filestash/server/common" ) var DB *sql.DB func init() { Hooks.Register.Onload(func() { var err error if DB, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH)+"/share.sql?_fk=true"); err != nil { Log.Error("model::index sqlite open error '%s'", err.Error()) return } if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil { stmt.Exec() } if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil { stmt.Exec() } if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil { stmt.Exec() if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil { stmt.Exec() } } go func() { autovacuum() }() }) } func autovacuum() { if stmt, err := DB.Prepare("DELETE FROM Verification WHERE expire < datetime('now')"); err == nil { stmt.Exec() } time.Sleep(6 * time.Hour) } ================================================ FILE: server/model/permissions.go ================================================ package model import ( . "github.com/mickael-kerjean/filestash/server/common" ) func CanRead(ctx *App) bool { if ctx.Share.Id != "" { return ctx.Share.CanRead } return true } func CanEdit(ctx *App) bool { if ctx.Share.Id != "" { return ctx.Share.CanWrite } return true } func CanUpload(ctx *App) bool { if ctx.Share.Id != "" { return ctx.Share.CanUpload } return true } func CanShare(ctx *App) bool { if ctx.Share.Id != "" { return ctx.Share.CanShare } return true } ================================================ FILE: server/model/plugin.go ================================================ package model import ( "archive/zip" "encoding/json" "io" "os" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) var PLUGINS = map[string]PluginImpl{} type PluginImpl struct { Author string `json:"author"` Version string `json:"version"` Modules []map[string]string `json:"modules"` } func PluginDiscovery() error { entries, err := os.ReadDir(GetAbsolutePath(PLUGIN_PATH)) if err != nil { return err } for _, entry := range entries { if entry.IsDir() { continue } fname := entry.Name() if strings.HasSuffix(fname, ".zip") == false { continue } name, impl, err := InitModule(fname) if err != nil { Log.Error("could not initialise module name=%s err=%s", entry.Name(), err.Error()) continue } for i := 0; i < len(impl.Modules); i++ { switch impl.Modules[i]["type"] { case "css": b, err := GetPluginFile(name, impl.Modules[i]["entrypoint"]) if err != nil { return err } Hooks.Register.CSS(string(b)) case "patch": b, err := GetPluginFile(name, impl.Modules[i]["entrypoint"]) if err != nil { return err } Hooks.Register.StaticPatch(b) case "favicon": b, err := GetPluginFile(name, impl.Modules[i]["entrypoint"]) if err != nil { return err } Hooks.Register.Favicon(b) case "middleware": b, err := GetPluginFile(name, impl.Modules[i]["entrypoint"]) if err != nil { return err } m, err := WasmAdapterForMiddleware(b) if err != nil { return err } Hooks.Register.Middleware(m) case "http": // TODO return ErrNotImplemented } } PLUGINS[name] = impl } return nil } func GetPluginFile(pluginName string, path string) ([]byte, error) { zipReader, err := zip.OpenReader(JoinPath( GetAbsolutePath(PLUGIN_PATH), pluginName+".zip", )) if err != nil { return nil, err } for _, zipFile := range zipReader.File { if zipFile.Name != path { continue } f, err := zipFile.Open() if err != nil { zipReader.Close() return nil, err } data, err := io.ReadAll(f) f.Close() zipReader.Close() if err != nil { return nil, err } return data, nil } zipReader.Close() return nil, ErrNotFound } func InitModule(plgName string) (string, PluginImpl, error) { var plgImpl = PluginImpl{} r, err := zip.OpenReader(JoinPath(GetAbsolutePath(PLUGIN_PATH), plgName)) plgName = strings.TrimSuffix(plgName, ".zip") if err != nil { return plgName, plgImpl, err } defer r.Close() var manifestFile io.ReadCloser for _, f := range r.File { if f.Name != "manifest.json" { continue } rc, err := f.Open() if err != nil { return plgName, plgImpl, err } manifestFile = rc break } if manifestFile == nil { return plgName, plgImpl, ErrNotFound } defer manifestFile.Close() if err = json.NewDecoder(manifestFile).Decode(&plgImpl); err != nil { return plgName, plgImpl, err } return plgName, plgImpl, nil } ================================================ FILE: server/model/plugin_adapter.go ================================================ package model import ( "bufio" "bytes" "context" "io" "net/http" . "github.com/mickael-kerjean/filestash/server/common" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" ) func WasmAdapterForMiddleware(wasmBytes []byte) (Middleware, error) { return func(next HandlerFunc) HandlerFunc { return func(app *App, w http.ResponseWriter, r *http.Request) { module, err := wasmPrepare(wasmBytes) if err != nil { SendErrorResult(w, NewError("plugin::adapter action=prepare error="+err.Error(), http.StatusInternalServerError)) return } defer module.Close(app.Context) wasmFunc := module.ExportedFunction("middleware") if wasmFunc == nil { SendErrorResult(w, NewError("plugin::adapter action=export error=missing+middleware+function", http.StatusInternalServerError)) return } response, err := wasmFunc.Call(app.Context) if err != nil { SendErrorResult(w, NewError(err.Error(), http.StatusInternalServerError)) return } responseBytes, err := wasmOutput(module.Memory(), response) if err != nil { SendErrorResult(w, err) return } resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(responseBytes)), nil) if err != nil { SendErrorResult(w, err) return } defer resp.Body.Close() for head, value := range resp.Header { w.Header()[head] = value } if resp.StatusCode != http.StatusNoContent { w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) return } next(app, w, r) return } }, nil } func wasmPrepare(wasmBytes []byte) (api.Module, error) { ctx := context.Background() runtime := wazero.NewRuntime(ctx) compiledModule, err := runtime.CompileModule(ctx, wasmBytes) if err != nil { return nil, err } return runtime.InstantiateModule(ctx, compiledModule, wazero.NewModuleConfig()) } func wasmOutput(memory api.Memory, response []uint64) ([]byte, error) { if len(response) != 1 { return nil, NewError("Invalid WASM response", http.StatusInternalServerError) } ptr := uint32(response[0]) var responseLength uint32 for offset := uint32(0); ; offset += 8192 { chunk, ok := memory.Read(ptr+offset, 8192) if !ok { responseLength = offset break } for i, b := range chunk { if b == 0 { responseLength = offset + uint32(i) goto found } } } found: responseBytes, _ := memory.Read(ptr, responseLength) return responseBytes, nil } ================================================ FILE: server/model/share.go ================================================ package model import ( "bytes" "crypto/tls" "database/sql" "encoding/json" "net/http" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/pkg/sqlite" "golang.org/x/crypto/bcrypt" "gopkg.in/gomail.v2" "html/template" ) type Proof struct { Id string `json:"id"` Key string `json:"key"` Value string `json:"-"` Message *string `json:"message,omitempty"` Error *string `json:"error,omitempty"` } func ShareList(backend string, path string) ([]Share, error) { stmt, err := DB.Prepare("SELECT id, related_path, params FROM Share WHERE related_backend = ? AND related_path LIKE ? || '%' ") if err != nil { return nil, err } rows, err := stmt.Query(backend, path) if err != nil { return nil, err } sharedFiles := []Share{} for rows.Next() { var a Share var params []byte rows.Scan(&a.Id, &a.Path, ¶ms) json.Unmarshal(params, &a) sharedFiles = append(sharedFiles, a) } rows.Close() return sharedFiles, nil } func ShareAll() ([]Share, error) { rows, err := DB.Query("SELECT id, related_path, params FROM Share") if err != nil { return nil, err } sharedFiles := []Share{} for rows.Next() { var a Share var params []byte rows.Scan(&a.Id, &a.Path, ¶ms) json.Unmarshal(params, &a) sharedFiles = append(sharedFiles, a) } rows.Close() return sharedFiles, nil } func ShareGet(id string) (Share, error) { var p Share stmt, err := DB.Prepare("SELECT id, related_backend, related_path, auth, params FROM share WHERE id = ?") if err != nil { return p, err } defer stmt.Close() row := stmt.QueryRow(id) var str []byte if err = row.Scan(&p.Id, &p.Backend, &p.Path, &p.Auth, &str); err != nil { if err == sql.ErrNoRows { return p, ErrNotFound } return p, err } json.Unmarshal(str, &p) return p, nil } func ShareUpsert(p *Share) error { if p.Password != nil { if *p.Password == PASSWORD_DUMMY { if s, err := ShareGet(p.Id); err != nil { p.Password = s.Password } } else { hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(*p.Password), bcrypt.DefaultCost) p.Password = NewString(string(hashedPassword)) } } stmt, err := DB.Prepare("INSERT INTO Location(backend, path) VALUES($1, $2)") if err != nil { return err } _, err = stmt.Exec(p.Backend, p.Path) if err != nil { throw := true if sqlite.IsConstraint(err) { throw = false } if throw == true { return err } } stmt, err = DB.Prepare("INSERT INTO Share(id, related_backend, related_path, params, auth) VALUES($1, $2, $3, $4, $5) ON CONFLICT(id) DO UPDATE SET related_backend = $2, related_path = $3, params = $4") if err != nil { return err } j, _ := json.Marshal(&struct { Password *string `json:"password,omitempty"` Users *string `json:"users,omitempty"` Expire *int64 `json:"expire,omitempty"` Url *string `json:"url,omitempty"` CanShare bool `json:"can_share"` CanManageOwn bool `json:"can_manage_own"` CanRead bool `json:"can_read"` CanWrite bool `json:"can_write"` CanUpload bool `json:"can_upload"` }{ Password: p.Password, Users: p.Users, Expire: p.Expire, Url: p.Url, CanShare: p.CanShare, CanManageOwn: p.CanManageOwn, CanRead: p.CanRead, CanWrite: p.CanWrite, CanUpload: p.CanUpload, }) _, err = stmt.Exec(p.Id, p.Backend, p.Path, j, p.Auth) return err } func ShareDelete(id string) error { stmt, err := DB.Prepare("DELETE FROM Share WHERE id = ?") if err != nil { return err } _, err = stmt.Exec(id) return err } func ShareProofVerifier(s Share, proof Proof) (Proof, error) { p := proof if proof.Key == "password" { if s.Password == nil { return p, NewError("No password required", 400) } v, ok := ShareProofVerifierPassword(*s.Password, proof.Value) if ok == false { time.Sleep(1000 * time.Millisecond) return p, ErrInvalidPassword } p.Value = v } if proof.Key == "email" { // find out if user is authorized if s.Users == nil { return p, NewError("Authentication not required", 400) } v, ok := ShareProofVerifierEmail(*s.Users, proof.Value) if ok == false { time.Sleep(1000 * time.Millisecond) return p, ErrNotAuthorized } user := v // prepare the verification code stmt, err := DB.Prepare("INSERT INTO Verification(key, code) VALUES(?, ?)") if err != nil { return p, err } code := RandomString(4) if _, err := stmt.Exec("email::"+user, code); err != nil { return p, err } // Prepare message var b bytes.Buffer t := template.New("email") t.Parse(TmplEmailVerification()) t.Execute(&b, struct { Code string Username string }{code, networkDriveUsernameEnc(user)}) p.Key = "code" p.Value = "" p.Message = NewString("We've sent you a message with a verification code") // Send email email := struct { Hostname string `json:"server"` Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` From string `json:"from"` }{ Hostname: Config.Get("email.server").String(), Port: Config.Get("email.port").Int(), Username: Config.Get("email.username").String(), Password: Config.Get("email.password").String(), From: Config.Get("email.from").String(), } m := gomail.NewMessage() m.SetHeader("From", email.From) m.SetHeader("To", proof.Value) m.SetHeader("Subject", "Your verification code") m.SetBody("text/html", b.String()) d := gomail.NewDialer(email.Hostname, email.Port, email.Username, email.Password) d.TLSConfig = &tls.Config{InsecureSkipVerify: true} if err := d.DialAndSend(m); err != nil { Log.Error("Sendmail error: %v", err) Log.Error("Verification code '%s'", code) return p, NewError("Couldn't send email", 500) } return p, nil } if proof.Key == "code" { // find key for given code stmt, err := DB.Prepare("SELECT key FROM Verification WHERE code = ? AND expire > datetime('now')") if err != nil { return p, NewError("Not found", 404) } row := stmt.QueryRow(proof.Value) var key string if err = row.Scan(&key); err != nil { if err == sql.ErrNoRows { stmt.Close() p.Key = "email" p.Value = "" return p, NewError("Not found", 404) } stmt.Close() return p, err } stmt.Close() // cleanup current attempt so that it isn't used for malicious purpose if stmt, err = DB.Prepare("DELETE FROM Verification WHERE code = ?"); err == nil { stmt.Exec(proof.Value) stmt.Close() } p.Key = "email" p.Value = strings.TrimPrefix(key, "email::") } return p, nil } func ShareProofVerifierPassword(hashed string, given string) (string, bool) { if err := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(given)); err != nil { return "", false } return hashed, true } func ShareProofVerifierEmail(users string, wanted string) (string, bool) { s := strings.Split(users, ",") user := "" for _, possibleUser := range s { possibleUser := strings.Trim(possibleUser, " ") if wanted == possibleUser { user = possibleUser break } else if possibleUser[0:1] == "*" { if strings.HasSuffix(wanted, strings.TrimPrefix(possibleUser, "*")) { user = possibleUser break } } } if user == "" { return "", false } return user, true } func ShareProofGetAlreadyVerified(req *http.Request) []Proof { var p []Proof var cookieValue string c, _ := req.Cookie(COOKIE_NAME_PROOF) if c == nil { return p } cookieValue = c.Value if len(cookieValue) > 500 { return p } j, err := DecryptString(SECRET_KEY_DERIVATE_FOR_PROOF, cookieValue) if err != nil { return p } _ = json.Unmarshal([]byte(j), &p) return p } func ShareProofGetRequired(s Share) []Proof { var p []Proof if s.Password != nil { p = append(p, Proof{Key: "password", Value: *s.Password}) } if s.Users != nil { p = append(p, Proof{Key: "email", Value: *s.Users}) } return p } func ShareProofCalculateRemainings(ref []Proof, mem []Proof) []Proof { var remainingProof []Proof for i := 0; i < len(ref); i++ { keep := true for j := 0; j < len(mem); j++ { if shareProofAreEquivalent(ref[i], mem[j]) { keep = false break } } if keep { remainingProof = append(remainingProof, ref[i]) } } return remainingProof } func shareProofAreEquivalent(ref Proof, p Proof) bool { if ref.Key != p.Key { return false } else if ref.Value != "" && ref.Value == p.Value { return true } for _, chunk := range strings.Split(ref.Value, ",") { chunk = strings.Trim(chunk, " ") if p.Id == Hash(ref.Key+"::"+chunk, 20) { return true } } return false } func TmplEmailVerification() string { return ` Verification code
     
    Your code to login

    Your verification code is: {{.Code}}

    When mounted as a network drive, you can authenticate as: {{.Username}}
    ` + WhiteLabelText(`Powered by Filestash.`, APPNAME) + `
     
    ` } func networkDriveUsernameEnc(email string) string { return email + "[" + Hash(email+SECRET_KEY_DERIVATE_FOR_HASH, 10) + "]" } ================================================ FILE: server/model/webdav.go ================================================ package model /* * Implementation of a webdav.FileSystem: https://godoc.org/golang.org/x/net/webdav#FileSystem that is used * to generate our webdav server. * A lot of memoization is happening so that we don't DDOS the underlying storage which was important * considering most webdav client within OS are extremely greedy in HTTP request */ import ( "context" "fmt" "io" "net/http" "os" "path/filepath" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/net/webdav" ) var webdav_cache AppCache func init() { webdav_cache = NewQuickCache(20, 10) webdav_cache.OnEvict(func(filename string, _ interface{}) { os.Remove(filename) }) } type WebdavFs struct { req *http.Request backend IBackend path string id string chroot string webdavFile *WebdavFile } func NewWebdavFs(b IBackend, primaryKey string, chroot string, req *http.Request) *WebdavFs { return &WebdavFs{ backend: b, id: primaryKey, chroot: chroot, req: req, } } func (this WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) error { if name = this.fullpath(name); name == "" { return os.ErrNotExist } return this.backend.Mkdir(name) } func (this *WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { cachePath := filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20)) fwriteFile := func() *os.File { if this.req.Method == "PUT" { f, err := os.OpenFile(cachePath+"_writer", os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm) if err != nil { return nil } return f } return nil } if this.webdavFile != nil { this.webdavFile.fwrite = fwriteFile() return this.webdavFile, nil } if name = this.fullpath(name); name == "" { return nil, os.ErrNotExist } this.webdavFile = &WebdavFile{ path: name, backend: this.backend, cache: cachePath, fwrite: fwriteFile(), } return this.webdavFile, nil } func (this WebdavFs) RemoveAll(ctx context.Context, name string) error { if name = this.fullpath(name); name == "" { return os.ErrNotExist } return this.backend.Rm(name) } func (this WebdavFs) Rename(ctx context.Context, oldName, newName string) error { if oldName = this.fullpath(oldName); oldName == "" { return os.ErrNotExist } else if newName = this.fullpath(newName); newName == "" { return os.ErrNotExist } return this.backend.Mv(oldName, newName) } func (this *WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error) { if this.webdavFile != nil { this.webdavFile.push_to_remote_if_needed() return this.webdavFile.Stat() } fullname := this.fullpath(name) if isMicrosoftWebDAVClient(this.req) && this.req.Method == "PROPFIND" { if name == "" { fullname = this.chroot } fullname = EnforceDirectory(fullname) } if fullname == "" { return nil, os.ErrNotExist } this.webdavFile = &WebdavFile{ path: fullname, backend: this.backend, cache: filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20)), } return this.webdavFile.Stat() } func (this WebdavFs) fullpath(path string) string { p := filepath.Join(this.chroot, path) if strings.HasSuffix(path, "/") == true && strings.HasSuffix(p, "/") == false { p += "/" } if strings.HasPrefix(p, this.chroot) == false { return "" } return p } /* * Implement a webdav.File and os.Stat : https://godoc.org/golang.org/x/net/webdav#File */ type WebdavFile struct { path string backend IBackend cache string fread *os.File fwrite *os.File files []os.FileInfo } func (this *WebdavFile) Read(p []byte) (n int, err error) { if strings.HasPrefix(filepath.Base(this.path), ".") { return 0, os.ErrNotExist } if this.fread == nil { if this.fread = this.pull_remote_file(); this.fread == nil { return -1, os.ErrInvalid } } return this.fread.Read(p) } func (this *WebdavFile) Close() error { if this.fread != nil { if this.fread.Close() == nil { this.fread = nil } } if this.fwrite != nil { if err := this.push_to_remote_if_needed(); err == nil { if this.fwrite.Close() == nil { this.fwrite = nil } } } return nil } func (this *WebdavFile) Seek(offset int64, whence int) (int64, error) { if this.fread == nil { this.fread = this.pull_remote_file() if this.fread == nil { return offset, ErrNotFound } } a, err := this.fread.Seek(offset, whence) if err != nil { return a, ErrNotFound } return a, nil } func (this *WebdavFile) Readdir(count int) ([]os.FileInfo, error) { if this.files != nil { return this.files, nil } if strings.HasPrefix(filepath.Base(this.path), ".") { return nil, os.ErrNotExist } f, err := this.backend.Ls(this.path) this.files = f return f, err } func (this *WebdavFile) Stat() (os.FileInfo, error) { this.push_to_remote_if_needed() if strings.HasSuffix(this.path, "/") { _, err := this.Readdir(0) if err != nil { return nil, os.ErrNotExist } return this, nil } baseDir := filepath.Base(this.path) files, err := this.backend.Ls(strings.TrimSuffix(this.path, baseDir)) if err != nil { return nil, os.ErrNotExist } found := false for i := range files { if files[i].Name() == baseDir { found = true break } } if found == false { return nil, os.ErrNotExist } return this, nil } func (this *WebdavFile) Write(p []byte) (int, error) { if this.fwrite == nil { return 0, os.ErrNotExist } if strings.HasPrefix(filepath.Base(this.path), ".") { return 0, os.ErrNotExist } return this.fwrite.Write(p) } func (this WebdavFile) pull_remote_file() *os.File { filename := this.cache + "_reader" if f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm); err == nil { return f } if f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm); err == nil { if reader, err := this.backend.Cat(this.path); err == nil { io.Copy(f, reader) f.Close() webdav_cache.SetKey(this.cache+"_reader", nil) reader.Close() if f, err = os.OpenFile(filename, os.O_RDONLY, os.ModePerm); err == nil { return f } return nil } f.Close() } return nil } func (this *WebdavFile) push_to_remote_if_needed() error { if this.fwrite == nil { return nil } this.fwrite.Close() f, err := os.OpenFile(this.cache+"_writer", os.O_RDONLY, os.ModePerm) if err != nil { return err } err = this.backend.Save(this.path, f) if err == nil { if err = os.Rename(this.cache+"_writer", this.cache+"_reader"); err == nil { this.fwrite = nil webdav_cache.SetKey(this.cache+"_reader", nil) } } f.Close() return err } func (this WebdavFile) Name() string { return filepath.Base(this.path) } func (this *WebdavFile) Size() int64 { if this.fread == nil { if this.fread = this.pull_remote_file(); this.fread == nil { return 0 } } if info, err := this.fread.Stat(); err == nil { return info.Size() } return 0 } func (this WebdavFile) Mode() os.FileMode { return 0 } func (this WebdavFile) ModTime() time.Time { return time.Now() } func (this WebdavFile) IsDir() bool { if strings.HasSuffix(this.path, "/") { return true } return false } func (this WebdavFile) Sys() interface{} { return nil } func (this WebdavFile) ETag(ctx context.Context) (string, error) { // Building an etag can be an expensive call if the data isn't available locally. // => 2 etags strategies: // - use a legit etag value when the data is already in our cache // - use a dummy value that's changing all the time when we don't have much info etag := Hash(fmt.Sprintf("%d%s", this.ModTime().UnixNano(), this.path), 20) if this.fread != nil { if s, err := this.fread.Stat(); err == nil { etag = Hash(fmt.Sprintf(`"%x%x"`, this.path, s.Size()), 20) } } return etag, nil } var lock webdav.LockSystem func NewWebdavLock() webdav.LockSystem { if lock == nil { lock = webdav.NewMemLS() } return lock } func isMicrosoftWebDAVClient(req *http.Request) bool { return strings.HasPrefix(req.Header.Get("User-Agent"), "Microsoft-WebDAV-MiniRedir/") } ================================================ FILE: server/pkg/compress/cgo.go ================================================ //go:build cgo && linux package compress import ( "github.com/google/brotli/go/cbrotli" ) func Gzip(content []byte, quality int) []byte { return []byte{} } func Br(content []byte, quality int) []byte { out, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: quality}) return out } ================================================ FILE: server/pkg/compress/nocgo.go ================================================ //go:build !cgo || windows package compress import ( "bytes" "compress/gzip" ) func Gzip(content []byte, quality int) []byte { var buf bytes.Buffer gz, err := gzip.NewWriterLevel(&buf, quality) if err != nil { gz = gzip.NewWriter(&buf) } _, _ = gz.Write(content) gz.Close() return buf.Bytes() } func Br(content []byte, quality int) []byte { return []byte("") } ================================================ FILE: server/pkg/index.go ================================================ package core import ( _ "github.com/mickael-kerjean/filestash/server/pkg/sqlite" ) ================================================ FILE: server/pkg/sqlite/cgo.go ================================================ //go:build cgo package sqlite import ( "errors" "github.com/mattn/go-sqlite3" ) func IsConstraint(err error) bool { if err == nil { return false } var sqliteErr sqlite3.Error if !errors.As(err, &sqliteErr) { return false } if sqliteErr.Code == sqlite3.ErrConstraint { return true } switch sqliteErr.ExtendedCode { case sqlite3.ErrConstraintPrimaryKey, sqlite3.ErrConstraintUnique, sqlite3.ErrConstraintForeignKey, sqlite3.ErrConstraintCheck, sqlite3.ErrConstraintNotNull: return true } return false } ================================================ FILE: server/pkg/sqlite/nocgo.go ================================================ //go:build !cgo package sqlite import ( "errors" "database/sql" modernc "modernc.org/sqlite" ) func init() { sql.Register("sqlite3", &modernc.Driver{}) } func IsConstraint(err error) bool { if err == nil { return false } var sqliteErr *modernc.Error if !errors.As(err, &sqliteErr) { return false } code := sqliteErr.Code() return code == 19 || (code >= 1550 && code <= 1599) } ================================================ FILE: server/pkg/workflow/action.go ================================================ package workflow import ( . "github.com/mickael-kerjean/filestash/server/common" _ "github.com/mickael-kerjean/filestash/server/pkg/workflow/actions" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) func ExecuteAction(action Step, input map[string]string) (map[string]string, error) { currAction, err := findAction(action.Name) if err != nil { return nil, err } return currAction.Execute(action.Params, input) } func findAction(action string) (IAction, error) { for _, currAction := range Hooks.Get.WorkflowActions() { if currAction.Manifest().Name == action { return currAction, nil } } return nil, ErrNotFound } ================================================ FILE: server/pkg/workflow/actions/notify_email.go ================================================ package actions import ( "strings" . "github.com/mickael-kerjean/filestash/server/common" "gopkg.in/gomail.v2" ) func init() { Hooks.Register.WorkflowAction(&ActionNotifyEmail{}) } type ActionNotifyEmail struct{} func (this *ActionNotifyEmail) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: "notify/email", Title: "Notify", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "email", Type: "text", }, { Name: "subject", Type: "text", }, { Name: "message", Type: "long_text", }, }, }, } } func (this *ActionNotifyEmail) Execute(params map[string]string, input map[string]string) (map[string]string, error) { email := struct { Hostname string Port int Username string Password string From string To string Subject string Message string }{ Hostname: Config.Get("email.server").String(), Port: Config.Get("email.port").Int(), Username: Config.Get("email.username").String(), Password: Config.Get("email.password").String(), From: Config.Get("email.from").String(), To: Render(params["email"], input), Subject: Render(params["subject"], input), Message: Render(params["message"], input), } m := gomail.NewMessage() m.SetHeader("From", email.From) m.SetHeader("To", email.To) m.SetHeader("Subject", email.Subject) m.SetBody("text/html", strings.ReplaceAll(email.Message, "\n", "
    ")) mail := gomail.NewDialer(email.Hostname, email.Port, email.Username, email.Password) return input, mail.DialAndSend(m) } ================================================ FILE: server/pkg/workflow/actions/run_api.go ================================================ package actions import ( "bytes" "fmt" "io" "net/http" "slices" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.WorkflowAction(&RunApi{}) } type RunApi struct{} func (this *RunApi) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: "run/api", Title: "Make API Call", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "url", Type: "text", }, { Name: "method", Type: "select", Opts: []string{"POST", "PUT", "GET", "PATCH"}, }, { Name: "headers", Type: "long_text", }, { Name: "body", Type: "long_text", }, }, }, } } func (this *RunApi) Execute(params map[string]string, input map[string]string) (map[string]string, error) { req, err := http.NewRequest(params["method"], Render(params["url"], input), bytes.NewBufferString(Render(params["body"], input))) if err != nil { return input, err } else if params["headers"] != "" { for _, header := range strings.Split(Render(params["headers"], input), "\n") { if parts := strings.SplitN(strings.TrimSpace(header), ":", 2); len(parts) == 2 { req.Header.Add( strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), ) } } } if params["body"] != "" && slices.Contains([]string{"POST", "PUT", "PATCH"}, params["method"]) && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "application/json") } resp, err := HTTP.Do(req) if err != nil { return input, err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return input, NewError(fmt.Sprintf("received status code is %d", resp.StatusCode), resp.StatusCode) } responseBody, err := io.ReadAll(resp.Body) if err != nil { return input, err } output := make(map[string]string) for k, v := range input { output[k] = v } output["http::status"] = string(resp.StatusCode) output["http::response"] = string(responseBody) return output, nil } ================================================ FILE: server/pkg/workflow/actions/tools_debug.go ================================================ package actions import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.WorkflowAction(&ToolsDebug{}) } type ToolsDebug struct{} func (this *ToolsDebug) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: "tools/debug", Title: "Debug", Icon: ``, Specs: Form{}, } } func (this *ToolsDebug) Execute(params map[string]string, input map[string]string) (map[string]string, error) { Log.Info("[workflow] action=tools/debug input=%v", input) return input, nil } ================================================ FILE: server/pkg/workflow/actions/utils.go ================================================ package actions import ( . "github.com/mickael-kerjean/filestash/server/ctrl" ) func Render(templateText string, variables map[string]string) string { rendered, err := TmplExec(templateText, TmplParams(variables)) if err != nil { return templateText } return rendered } ================================================ FILE: server/pkg/workflow/config.go ================================================ package workflow import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() PluginNumberWorker() }) } var PluginEnable = func() bool { return Config.Get("features.workflow.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{"workflow_workers"} f.Description = "Enable/Disable workflows" f.Default = true return f }).Bool() } var PluginNumberWorker = func() int { return Config.Get("features.workflow.workers").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "workflow_workers" f.Name = "workers" f.Type = "number" f.Description = "Number of workers running in parallel. Default: 1" f.Default = 1 return f }).Int() } ================================================ FILE: server/pkg/workflow/handler.go ================================================ package workflow import ( "encoding/json" "net/http" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" "github.com/gorilla/mux" ) func WorkflowAll(ctx *App, res http.ResponseWriter, req *http.Request) { workflows, err := AllWorkflows() if err != nil { SendErrorResult(res, err) return } triggers := Hooks.Get.WorkflowTriggers() tm := make([]WorkflowSpecs, len(triggers)) for i, t := range triggers { tm[i] = t.Manifest() } actions := Hooks.Get.WorkflowActions() am := make([]WorkflowSpecs, len(actions)) for i, a := range actions { am[i] = a.Manifest() } SendSuccessResult(res, map[string]any{ "workflows": workflows, "triggers": tm, "actions": am, }) } func WorkflowUpsert(ctx *App, res http.ResponseWriter, req *http.Request) { var workflow Workflow if err := json.NewDecoder(req.Body).Decode(&workflow); err != nil { SendErrorResult(res, ErrInternal) return } if err := UpsertWorkflow(workflow); err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, nil) } func WorkflowGet(ctx *App, res http.ResponseWriter, req *http.Request) { workflowID := mux.Vars(req)["workflowID"] if workflowID == "" { SendErrorResult(res, ErrNotValid) return } workflow, err := GetWorkflow(workflowID) if err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, workflow) } func WorkflowDelete(ctx *App, res http.ResponseWriter, req *http.Request) { id := req.URL.Query().Get("id") if id == "" { SendErrorResult(res, ErrNotValid) return } else if err := DeleteWorkflow(id); err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, nil) } ================================================ FILE: server/pkg/workflow/index.go ================================================ package workflow import ( "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) var job_event = make(chan interface{}, 100) func Init() error { if err := InitState(); err != nil { return err } else if PluginEnable() == false { Log.Debug("[workflow] state=disabled") return nil } Log.Debug("[workflow] state=enabled worker=%d", PluginNumberWorker()) triggers := Hooks.Get.WorkflowTriggers() for i := 0; i < len(triggers); i++ { t, err := triggers[i].Init() if err != nil { return err } go func(t chan ITriggerEvent) { for trigger := range t { if err := CreateJob(trigger.WorkflowID(), trigger.Input()); err != nil { Log.Error("[workflow] action=createJob err=%s", err.Error()) } select { case job_event <- nil: default: } } }(t) } for i := 0; i < PluginNumberWorker(); i++ { go func(i int) { time.Sleep(time.Duration((i+1)*100) * time.Millisecond) for { select { case <-job_event: case <-time.After(60 * time.Second): } jobID, workflow, input, err := NextJob() if err == ErrNotFound { continue } else if err != nil { Log.Error("[workflow] type=worker err=%s", err.Error()) time.Sleep(10 * time.Second) continue } ExecuteJob(jobID, workflow, input) } }(i) } return nil } ================================================ FILE: server/pkg/workflow/job.go ================================================ package workflow import ( . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) func ExecuteJob(jobID string, workflow Workflow, input map[string]string) { var err error UpdateJob(jobID, "RUNNING", workflow.Actions, input) for i := 0; i < len(workflow.Actions); i++ { if workflow.Actions[i].Done { continue } input, err = ExecuteAction(workflow.Actions[i], input) workflow.Actions[i].Done = true if err != nil { status := "FAILURE" workflow.Actions[i].Done = false if input["status"] == "PENDING" { status = "PENDING" } UpdateJob(jobID, status, workflow.Actions, input) return } UpdateJob(jobID, "RUNNING", workflow.Actions, input) } UpdateJob(jobID, "SUCCESS", workflow.Actions, map[string]string{}) return } ================================================ FILE: server/pkg/workflow/model/block.go ================================================ package model import ( . "github.com/mickael-kerjean/filestash/server/common" ) type StepDefinition struct { Name string `json:"name"` Title string `json:"title"` Subtitle string `json:"subtitle"` Icon string `json:"icon"` Specs map[string]FormElement `json:"specs"` } ================================================ FILE: server/pkg/workflow/model/index.go ================================================ package model import ( "database/sql" "github.com/mickael-kerjean/filestash/server/common" ) var db *sql.DB func InitState() (err error) { db, err = sql.Open("sqlite3", common.GetAbsolutePath(common.DB_PATH, "workflow.sql")) if err != nil { return err } db.Exec(` CREATE TABLE IF NOT EXISTS workflows ( id TEXT PRIMARY KEY, name TEXT NOT NULL, published BOOLEAN DEFAULT 0, trigger TEXT NOT NULL, -- JSON encoded Step actions TEXT NOT NULL, -- JSON encoded []Step created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_workflows_trigger_name ON workflows(json_extract(trigger, '$.name'));`) db.Exec(` CREATE TABLE IF NOT EXISTS jobs ( id INTEGER PRIMARY KEY AUTOINCREMENT, related_workflow TEXT NOT NULL, status TEXT CHECK(status IN ('READY', 'PENDING', 'CLAIMED', 'RUNNING', 'SUCCESS', 'FAILURE')) DEFAULT 'READY', steps TEXT NOT NULL, input TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (related_workflow) REFERENCES workflows(id) ); CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status); CREATE INDEX IF NOT EXISTS idx_jobs_workflow ON jobs(related_workflow, created_at DESC);`) db.Exec(` UPDATE jobs SET status = 'READY', updated_at = CURRENT_TIMESTAMP WHERE status IN ('RUNNING', 'CLAIMED')`) return nil } ================================================ FILE: server/pkg/workflow/model/job.go ================================================ package model import ( "database/sql" "encoding/json" . "github.com/mickael-kerjean/filestash/server/common" ) type Job struct { ID int `json:"id"` RelatedWorkflow string `json:"related_workflow"` Status string `json:"status"` Steps []Step `json:"steps"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` } func CreateJob(workflowID string, input map[string]string) error { workflow, err := GetWorkflow(workflowID) if err != nil { return err } stepsJSON, err := json.Marshal(workflow.Actions) if err != nil { return err } inputJSON, err := json.Marshal(input) if err != nil { return err } tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() if _, err = tx.Exec(` INSERT INTO jobs (related_workflow, status, steps, input) VALUES (?, 'READY', ?, ?) `, workflowID, string(stepsJSON), string(inputJSON)); err != nil { return err } if _, err = tx.Exec(`DELETE FROM jobs WHERE related_workflow = ? AND id NOT IN ( SELECT id FROM jobs WHERE related_workflow = ? ORDER BY created_at DESC LIMIT 1000 )`, workflowID, workflowID); err != nil { return err } return tx.Commit() } func NextJob() (string, Workflow, map[string]string, error) { tx, err := db.Begin() if err != nil { return "", Workflow{}, nil, err } defer tx.Rollback() query := ` SELECT id, related_workflow, steps, input FROM jobs WHERE status = 'READY' ORDER BY updated_at ASC LIMIT 1` var ( jobID string workflowID string stepsJSON string inputJSON string ) if err = tx.QueryRow(query).Scan(&jobID, &workflowID, &stepsJSON, &inputJSON); err != nil { if err == sql.ErrNoRows { return "", Workflow{}, nil, ErrNotFound } return "", Workflow{}, nil, err } if _, err = tx.Exec(`UPDATE jobs SET status = 'CLAIMED', updated_at = CURRENT_TIMESTAMP WHERE id = ?`, jobID); err != nil { return "", Workflow{}, nil, err } else if err = tx.Commit(); err != nil { return "", Workflow{}, nil, err } workflow, err := GetWorkflow(workflowID) if err != nil { return "", Workflow{}, nil, err } var input map[string]string if err = json.Unmarshal([]byte(inputJSON), &input); err != nil { return "", Workflow{}, nil, err } input["jobID"] = jobID input["workflowID"] = workflow.ID input["trigger"] = workflow.Trigger.Name return jobID, workflow, input, nil } func UpdateJob(jobID string, status string, steps []Step, input map[string]string) { stepsJSON, err := json.Marshal(steps) if err != nil { Log.Error("[workflow] from=job on=updateJob step=marshal err=%s", err.Error()) return } inputJSON, err := json.Marshal(input) if err != nil { Log.Error("[workflow] from=job on=updateJob step=marshal err=%s", err.Error()) return } query := ` UPDATE jobs SET status = ?, steps = ?, input = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? ` if _, err := db.Exec(query, status, string(stepsJSON), string(inputJSON), jobID); err != nil { Log.Error("[workflow] from=job on=updateJob err=%s", err.Error()) } } ================================================ FILE: server/pkg/workflow/model/workflow.go ================================================ package model import ( "database/sql" "encoding/json" . "github.com/mickael-kerjean/filestash/server/common" ) type Workflow struct { ID string `json:"id"` Name string `json:"name"` Published bool `json:"published"` Trigger Step `json:"trigger"` Actions []Step `json:"actions"` UpdatedAt string `json:"updated_at"` CreatedAt string `json:"created_at"` History []any `json:"history"` } type Step struct { Name string `json:"name"` Params map[string]string `json:"params",omitzero` Done bool `json:"done,omitempty"` } func FindWorkflows(triggerName string) ([]Workflow, error) { rows, err := db.Query(` SELECT w.id, w.name, w.published, w.trigger, w.actions, w.created_at, w.updated_at FROM workflows w WHERE json_extract(w.trigger, '$.name') = ? ORDER BY w.created_at DESC `, triggerName) if err != nil { return nil, err } defer rows.Close() var workflows = []Workflow{} for rows.Next() { var w Workflow var triggerJSON, actionsJSON string if err := rows.Scan(&w.ID, &w.Name, &w.Published, &triggerJSON, &actionsJSON, &w.CreatedAt, &w.UpdatedAt); err != nil { return nil, err } if err := json.Unmarshal([]byte(triggerJSON), &w.Trigger); err != nil { return nil, err } if err := json.Unmarshal([]byte(actionsJSON), &w.Actions); err != nil { return nil, err } workflows = append(workflows, w) } return workflows, rows.Err() } func AllWorkflows() ([]Workflow, error) { rows, err := db.Query(` SELECT id, name, published, trigger, actions, created_at, updated_at FROM workflows ORDER BY created_at DESC `) if err != nil { return nil, err } defer rows.Close() var workflows = []Workflow{} for rows.Next() { var w Workflow var triggerJSON, actionsJSON string err := rows.Scan(&w.ID, &w.Name, &w.Published, &triggerJSON, &actionsJSON, &w.CreatedAt, &w.UpdatedAt) if err != nil { return nil, err } if err := json.Unmarshal([]byte(triggerJSON), &w.Trigger); err != nil { return nil, err } if err := json.Unmarshal([]byte(actionsJSON), &w.Actions); err != nil { return nil, err } workflows = append(workflows, w) } return workflows, rows.Err() } func UpsertWorkflow(workflow Workflow) error { triggerJSON, err := json.Marshal(workflow.Trigger) if err != nil { return err } actionsJSON, err := json.Marshal(workflow.Actions) if err != nil { return err } query := ` INSERT OR REPLACE INTO workflows (id, name, published, trigger, actions, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)` _, err = db.Exec(query, workflow.ID, workflow.Name, workflow.Published, string(triggerJSON), string(actionsJSON)) return err } func GetWorkflow(id string) (Workflow, error) { query := ` SELECT w.id, w.name, w.published, w.trigger, w.actions, w.created_at, w.updated_at, (SELECT COALESCE(JSON_GROUP_ARRAY(JSON_OBJECT( 'id', j.id, 'status', j.status, 'created_at', j.created_at, 'steps', j.steps )), JSON_ARRAY()) FROM ( SELECT * FROM jobs j WHERE j.related_workflow = w.id ORDER BY j.created_at DESC LIMIT 3000 ) j) as history FROM workflows w WHERE w.id = ?` row := db.QueryRow(query, id) var w Workflow var triggerJSON, actionsJSON, historyJSON string if err := row.Scan(&w.ID, &w.Name, &w.Published, &triggerJSON, &actionsJSON, &w.CreatedAt, &w.UpdatedAt, &historyJSON); err != nil { if err == sql.ErrNoRows { return Workflow{}, ErrNotFound } return Workflow{}, err } if err := json.Unmarshal([]byte(triggerJSON), &w.Trigger); err != nil { return Workflow{}, err } if err := json.Unmarshal([]byte(actionsJSON), &w.Actions); err != nil { return Workflow{}, err } if err := json.Unmarshal([]byte(historyJSON), &w.History); err != nil { return Workflow{}, err } return w, nil } func DeleteWorkflow(id string) error { result, err := db.Exec(`DELETE FROM workflows WHERE id = ?`, id) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } else if rowsAffected == 0 { return ErrNotFound } return nil } ================================================ FILE: server/pkg/workflow/trigger/fileaction.go ================================================ package trigger import ( "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) var ( fileaction_event = make(chan ITriggerEvent, 1) fileaction_name = "event" ) func init() { Hooks.Register.WorkflowTrigger(&FileEventTrigger{}) } type hookAuthorisation struct{} func (this hookAuthorisation) Ls(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "ls", "path": path}) return nil } func (this hookAuthorisation) Cat(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "cat", "path": path}) return nil } func (this hookAuthorisation) Mkdir(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "mkdir", "path": path}) return nil } func (this hookAuthorisation) Rm(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "rm", "path": path}) return nil } func (this hookAuthorisation) Mv(ctx *App, from string, to string) error { processFileAction(ctx, map[string]string{"event": "mv", "path": from + ", " + to}) return nil } func (this hookAuthorisation) Save(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "stat", "path": path}) return nil } func (this hookAuthorisation) Stat(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "save", "path": path}) return nil } func (this hookAuthorisation) Touch(ctx *App, path string) error { processFileAction(ctx, map[string]string{"event": "touch", "path": path}) return nil } type FileEventTrigger struct{} func (this *FileEventTrigger) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: fileaction_name, Title: "When Something Happen", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "event", Type: "text", Datalist: []string{"ls", "cat", "mkdir", "mv", "rm", "touch", "save", "stat"}, MultiValue: true, }, { Name: "path", Type: "text", }, }, }, Order: 3, } } func (this *FileEventTrigger) Init() (chan ITriggerEvent, error) { Hooks.Register.AuthorisationMiddleware(hookAuthorisation{}) return fileaction_event, nil } func processFileAction(ctx *App, params map[string]string) { if ctx.Context.Value("AUDIT") == false { return } if err := TriggerEvents(fileaction_event, fileaction_name, fileactionCallback(params)); err != nil { Log.Error("[workflow] trigger=event step=triggerEvents err=%s", err.Error()) } } func fileactionCallback(out map[string]string) func(Workflow) (map[string]string, bool) { return func(w Workflow) (map[string]string, bool) { if !matchEvent(w.Trigger.Params["event"], out["event"]) { return out, false } else if !matchPath(w.Trigger.Params["path"], out["path"]) { return out, false } return out, true } } func matchEvent(paramValue string, eventValue string) bool { if paramValue == "" { return true } for _, pvalue := range strings.Split(paramValue, ",") { if strings.TrimSpace(pvalue) == eventValue { return true } } return false } func matchPath(paramValue string, eventValue string) bool { if paramValue == "" { return true } for _, epath := range strings.Split(eventValue, ",") { if GlobMatch(paramValue, strings.TrimSpace(epath)) { return true } } return false } ================================================ FILE: server/pkg/workflow/trigger/filewatch.go ================================================ package trigger import ( "context" "encoding/json" "os" "sync" "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" "github.com/mickael-kerjean/filestash/server/model" ) var ( filewatch_event = make(chan ITriggerEvent, 1) filewatch_name = "watch" filewatch_state sync.Map ) func init() { Hooks.Register.WorkflowTrigger(&WatchTrigger{}) } type WatchTrigger struct{} func (this *WatchTrigger) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: "watch", Title: "When the Filesystem Changes", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "token", Type: "text", }, { Name: "path", Type: "text", }, }, }, Order: 4, } } func (this *WatchTrigger) Init() (chan ITriggerEvent, error) { go func() { for { if err := TriggerEvents(filewatch_event, filewatch_name, filewatchCallback); err != nil { Log.Error("[workflow] trigger=watch step=triggerEvents err=%s", err.Error()) } time.Sleep(10 * time.Second) } }() return filewatch_event, nil } func filewatchCallback(workflow Workflow) (map[string]string, bool) { path := workflow.Trigger.Params["path"] out := map[string]string{"path": path} backend, session, err := createBackend(workflow.Trigger.Params["token"]) if err != nil { Log.Error("[workflow] trigger=filewatch step=callback::init err=%s", err.Error()) return out, false } files, err := backend.Ls(path) if err != nil { Log.Error("[workflow] trigger=filewatch step=callback::ls err=%s", err.Error()) return out, false } key := GenerateID(session) + path fincache, exists := filewatch_state.Load(key) if !exists { filewatch_state.Store(key, files) return out, false } prevFiles := fincache.([]os.FileInfo) if len(files) != len(prevFiles) { filewatch_state.Store(key, files) return out, true } changes := []string{} for i := 0; i < len(files); i++ { hasChange := false if files[i].Name() != prevFiles[i].Name() { hasChange = true } else if files[i].Size() != prevFiles[i].Size() { hasChange = true } else if files[i].ModTime() != prevFiles[i].ModTime() { hasChange = true } if hasChange { p := JoinPath(path, files[i].Name()) if files[i].IsDir() { p = EnforceDirectory(p) } changes = append(changes, p) } } if len(changes) > 0 { filewatch_state.Store(key, files) return out, true } return out, false } func createBackend(token string) (IBackend, map[string]string, error) { session := map[string]string{} str, err := DecryptString(SECRET_KEY_DERIVATE_FOR_USER, token) if err != nil { return nil, session, err } if err = json.Unmarshal([]byte(str), &session); err != nil { return nil, session, err } backend, err := model.NewBackend( &App{Context: context.Background()}, session, ) return backend, session, err } ================================================ FILE: server/pkg/workflow/trigger/index.go ================================================ package trigger import ( "encoding/json" "net/http" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) type TriggerEvent struct { ID string Params map[string]string } func (this TriggerEvent) Input() map[string]string { return this.Params } func (this *TriggerEvent) WorkflowID() string { return this.ID } func TriggerEvents(event chan ITriggerEvent, triggerID string, callback func(Workflow) (map[string]string, bool)) error { workflows, err := FindWorkflows(triggerID) if err != nil { return err } for _, workflow := range workflows { if !workflow.Published { continue } params, emit := callback(workflow) if !emit { continue } select { case event <- &TriggerEvent{ ID: workflow.ID, Params: params, }: default: return NewError("Workflow is busy", http.StatusServiceUnavailable) } } return nil } func toJSON(val any) string { b, err := json.Marshal(val) if err != nil { return "{}" } return string(b) } ================================================ FILE: server/pkg/workflow/trigger/schedule.go ================================================ package trigger import ( "strconv" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) var ( cron_name = "schedule" cron_event = make(chan ITriggerEvent, 10) ) func init() { Hooks.Register.WorkflowTrigger(&ScheduleTrigger{}) } type ScheduleTrigger struct{} func (this *ScheduleTrigger) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: cron_name, Title: "On a Schedule", Subtitle: "cron", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "cron", Type: "text", Placeholder: "Default: @daily", Default: "@daily", Datalist: []string{"@always", "@hourly", "@daily", "@weekly", "@monthly", "@yearly"}, }, }, }, Order: 1, } } func (this *ScheduleTrigger) Init() (chan ITriggerEvent, error) { go func() { for { if err := TriggerEvents(cron_event, cron_name, scheduleCallback); err != nil { Log.Error("[workflow] trigger=schedule step=triggerEvents err=%s", err.Error()) } time.Sleep(time.Until(time.Now().Truncate(time.Minute).Add(time.Minute))) } }() return cron_event, nil } func scheduleCallback(workflow Workflow) (map[string]string, bool) { expr := workflow.Trigger.Params["cron"] if v, ok := map[string]string{ "@yearly": "0 0 1 1 *", "@monthly": "0 0 1 * *", "@weekly": "0 0 * * 0", "@daily": "0 0 * * *", "@hourly": "0 * * * *", "@always": "* * * * *", }[expr]; ok { expr = v } fields := strings.Fields(expr) if len(fields) != 5 { return map[string]string{"cron": expr}, false } t := time.Now() return map[string]string{"cron": expr}, cronFieldMatch(fields[0], t.Minute(), 0) && cronFieldMatch(fields[1], t.Hour(), 0) && cronFieldMatch(fields[2], t.Day(), 1) && cronFieldMatch(fields[3], int(t.Month()), 1) && cronFieldMatch(fields[4], int(t.Weekday()), 0) } func cronFieldMatch(field string, value, min int) bool { for _, part := range strings.Split(field, ",") { step := 1 if i := strings.Index(part, "/"); i != -1 { step, _ = strconv.Atoi(part[i+1:]) part = part[:i] } var lo, hi int switch { case part == "*": lo, hi = min, value case strings.Contains(part, "-"): lo, _ = strconv.Atoi(part[:strings.Index(part, "-")]) hi, _ = strconv.Atoi(part[strings.Index(part, "-")+1:]) default: lo, _ = strconv.Atoi(part) hi = lo } if value >= lo && value <= hi && (value-lo)%step == 0 && step > 0 { return true } } return false } ================================================ FILE: server/pkg/workflow/trigger/webhook.go ================================================ package trigger import ( "bytes" "html/template" "net/http" "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" "github.com/gorilla/mux" ) var ( webhook_event = make(chan ITriggerEvent, 5) webhook_name = "webhook" webhookTmpl = template.Must(template.New("webhook").Parse(`

    Triggers

      {{range .}}
    • {{.Name}}
    • {{end}}
    `)) ) func init() { Hooks.Register.WorkflowTrigger(&WebhookTrigger{}) } func webhookCallback(r *http.Request, id string) func(w Workflow) (map[string]string, bool) { return func(w Workflow) (map[string]string, bool) { headers := map[string]any{} for k, v := range r.Header { headers[k] = strings.Join(v, ", ") } query := map[string]any{} for k, v := range r.URL.Query() { query[k] = strings.Join(v, ", ") } out := map[string]string{ "method": r.Method, "headers": toJSON(headers), "query": toJSON(query), } if id == "" { return out, true } return out, id == w.ID } } type WebhookTrigger struct{} func (this *WebhookTrigger) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: webhook_name, Title: "From a WebHook", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "url", Type: "text", ReadOnly: true, Value: "/api/workflow/webhook?web", }, }, }, Order: 5, } } func (this *WebhookTrigger) Init() (chan ITriggerEvent, error) { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc(WithBase("/api/workflow/webhook"), func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Has("web") && strings.Contains(r.Header.Get("Accept"), "text/html") { workflows, err := FindWorkflows(webhook_name) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var buf bytes.Buffer if err := webhookTmpl.Execute(&buf, workflows); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html") w.Write([]byte(Page(buf.String()))) return } if err := TriggerEvents(webhook_event, webhook_name, webhookCallback(r, r.URL.Query().Get("id"))); err != nil { SendErrorResult(w, err) return } SendSuccessResult(w, nil) }).Methods("GET", "POST") return nil }) return webhook_event, nil } ================================================ FILE: server/pkg/workflow/trigger.go ================================================ package workflow import ( _ "github.com/mickael-kerjean/filestash/server/pkg/workflow/trigger" ) ================================================ FILE: server/plugin/index.go ================================================ package plugin import ( . "github.com/mickael-kerjean/filestash/server/common" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_ldap" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_local" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_passthrough" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_wordpress" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_artifactory" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dropbox" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ftp" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_gdrive" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_git" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ldap" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_local" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nop" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_perkeep" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_psql" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_s3" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_samba" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_sftp" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_storj" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_tmp" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_url" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_webdav" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_wopi" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_site" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_ascii" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_c" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_license" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_metadata_sqlite" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_search_stateless" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_scanner" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder" ) func init() { Hooks.Register.Onload(func() { Log.Debug("plugins loaded") }) } ================================================ FILE: server/plugin/plg_application_docxjs/Makefile ================================================ all: make setup make build make install setup: [ -d lib/vendor ] || mkdir -p lib/vendor curl -L https://raw.githubusercontent.com/VolodymyrBaydalka/docxjs/refs/heads/master/dist/docx-preview.js > lib/vendor/docx-preview.js curl -L https://unpkg.com/jszip/dist/jszip.min.js > lib/vendor/jszip.min.js build: zip -r application_docx.zip . install: mv application_docx.zip ../../../dist/data/state/plugins/ ================================================ FILE: server/plugin/plg_application_docxjs/loader_docx.css ================================================ .component_docx { width: 100%; } .component_docx .docx-wrapper { background: var(--surface); } .component_docx .docx-wrapper > section.docx { box-shadow: 0 0 3px rgba(0, 0, 0, 0.1); border-radius: 4px; } .component_filedownloader { width: 100%; margin: 0 auto; } ================================================ FILE: server/plugin/plg_application_docxjs/loader_docx.js ================================================ import { createElement } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { loadJS, loadCSS } from "../../helpers/loader.js"; import ctrlError from "../../pages/ctrl_error.js"; import { transition } from "../../pages/viewerpage/common.js"; import ctrlDownloader, { init as initDownloader } from "../../pages/viewerpage/application_downloader.js"; import { createLoader } from "../../components/loader.js"; await init(); /* * This viewer application is for rendering a docx onto an HTML document. To get a smooth UI, the whole * flow is broken down to these steps: * 1) show a loading spinner => `... = createLoader($page);` * 2) fetch the docx file => effect(ajax({ xxxx })).pipe( * 3) remove the spinner => cancelLoader, * 4) render + transition => ... renderDocx ... * 5) fallback on error => ... catchError ... ctrlDownloader(...) * * note: you don't have to use rxjs if you don't like it, the same code could be written with plain * promises instead. We just happen to be rxjs fanboys who enjoy the automatic resource cleanup it * enforces. In this example, if the user navigates away before the docx is loaded, the ajax call * is cancelled automatically. Additionally, we prefer thinking in terms of streams rather than * the classical state management approach used by most frameworks. */ export default async function(render, { getDownloadUrl, getFilename, $menubar, acl$ }) { const $page = createElement(`
    `); render($page); const cancelLoader = createLoader($page); effect(ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe( cancelLoader, rxjs.mergeMap(async ({ response }) => renderDocx(response, $page)), rxjs.catchError(() => ctrlDownloader(render, { acl$, getFilename, getDownloadUrl, hasMenubar: false })), )); } async function renderDocx(response, $page) { $page.classList.add("hidden"); await window.docx.renderAsync(response, $page) $page.classList.remove("hidden"); $page.parentElement.classList.add("scroll-y"); transition($page); } function init() { return Promise.all([ loadCSS(import.meta.url, "./loader_docx.css"), initDownloader(), loadJS(import.meta.url, "./lib/vendor/jszip.min.js").then(() => loadJS(import.meta.url, "./lib/vendor/docx-preview.js")), ]); } ================================================ FILE: server/plugin/plg_application_docxjs/manifest.json ================================================ { "author": "Filestash Pty Ltd", "version": "v0.0", "modules": [ { "type": "xdg-open", "mime": "application/word", "entrypoint": "loader_docx.js", "application": "skeleton" } ] } ================================================ FILE: server/plugin/plg_application_office/Makefile ================================================ all: make build make install install: zip -r application_office.zip . -x "lib/vendor/*" mv application_office.zip ../../../dist/data/state/plugins/ build: clang --target=wasm32 -O3 -nostdlib -Wl,--no-entry -Wl,--export=middleware -o middleware.wasm middleware.c make deps_zeta deps_zeta: [ -d lib/lowa ] || mkdir lib/lowa curl https://cdn.zetaoffice.net/zetaoffice_latest/soffice.js > lib/lowa/soffice.js curl https://cdn.zetaoffice.net/zetaoffice_latest/soffice.wasm > lib/lowa/soffice.wasm.br curl https://cdn.zetaoffice.net/zetaoffice_latest/soffice.data.js.metadata > lib/lowa/soffice.data.js.metadata curl https://cdn.zetaoffice.net/zetaoffice_latest/soffice.data > lib/lowa/soffice.data.br curl https://zetaoffice.net/demos/standalone/assets/vendor/zetajs/zeta.js > lib/lowa/zeta.js ================================================ FILE: server/plugin/plg_application_office/README.md ================================================
    Screenshot
    ================================================ FILE: server/plugin/plg_application_office/loader_lowa.css ================================================ @import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/fontawesome.min.css"); .component_word { display: flex; flex: 1 1 auto; } .component_word canvas { border: none; outline: none; height: 100%; width: 100%; } .component_menubar .action-item .texteditor svg.component_icon { width: 16px; margin: 1px 0 0 2px; } .component_menubar .action-item .texteditor.active svg.component_icon { background: rgba(255, 255, 255, 0.2); border-radius: 5px; } .component_menubar .action-item .texteditor input[type="number"] { width: 15px; color: var(--color); border: 2px solid #f2f2f2; background: #f2f2f2; border-radius: 2px; padding: 0 3px 0 3px; margin: 0 3px; -moz-appearance: textfield; } .component_menubar .action-item .texteditor input[type="number"]::-webkit-outer-spin-button, .component_menubar .action-item .texteditor input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; } .component_menubar .action-item .texteditor .fontawesome { font-family: "FontAwesome"; color: var(--light); background: inherit; color: inherit; border: none; } .component_menubar .action-item .texteditor select.fontawesome option { background: var(--dark); } .component_menubar .action-item .texteditor.picker { display: flex; align-items: center; } .component_menubar .action-item .texteditor.picker input[type="color"] { padding: 0; width: 0; border: none; } .component_word + .component_loader { position: absolute; inset: 0; margin-top: 150px; } ================================================ FILE: server/plugin/plg_application_office/loader_lowa.go ================================================ package plg_application_office import ( "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Middleware(func(h HandlerFunc) HandlerFunc { return func(app *App, res http.ResponseWriter, req *http.Request) { head := res.Header() head.Set("Cross-Origin-Opener-Policy", "same-origin") head.Set("Cross-Origin-Embedder-Policy", "require-corp") h(app, res, req) } }) } ================================================ FILE: server/plugin/plg_application_office/loader_lowa.js ================================================ import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import rxjs, { effect } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { qs } from "../../lib/dom.js"; import { join } from "../../lib/path.js"; import { loadJS, loadCSS } from "../../helpers/loader.js"; import { buttonDownload } from "../../pages/viewerpage/component_menubar.js"; import { $ICON } from "../../pages/viewerpage/common_fab.js"; import { save } from "../../pages/viewerpage/model_files.js"; import "../../components/fab.js"; import { $toolbar } from "./lib/dom.js"; await loadCSS(import.meta.url, "./loader_lowa.css"); let $canvas = null; window.Module = { uno_scripts: [join(import.meta.url, "./lib/lowa/zeta.js"), join(import.meta.url, "./loader_lowa.uno.js")], locateFile: (path, prefix) => (prefix || join(import.meta.url, "./lib/lowa/")) + path, }; export default async function(render, { mime, getDownloadUrl, getFilename, $menubar, acl$ }) { const canWrite = (await acl$.toPromise()).indexOf("POST") >= 0; const $page = createElement(`
    `); render($page); // feature1: init const filename = getFilename(); const $fab = qs($page, `[is="component-fab"]`); const $qcanvas = qs($page, "canvas"); if ($canvas) { $qcanvas.remove(); $page.appendChild($canvas); } else { $canvas = $qcanvas; } Object.assign($canvas.style, { width: "100%", height: "100%", }); // feature2: toolbar init if (canWrite) { $menubar.add(buttonDownload(filename, getDownloadUrl())); if (isWriter(mime)) { $menubar.add($toolbar.bullet); $menubar.add($toolbar.alignment); $menubar.add($toolbar.title); } $menubar.add($toolbar.size); $menubar.add($toolbar.strike); $menubar.add($toolbar.underline); $menubar.add($toolbar.italic); $menubar.add($toolbar.bold); $menubar.add($toolbar.color); } // feature3: setup lowa window.Module.canvas = $canvas; await loadJS(import.meta.url, "./lib/lowa/soffice.js"); let port = await Module.uno_main; onDestroy(() => { $canvas.style.visibility = "hidden"; port.postMessage({ cmd: "destroy", mime }); }); // feature4: display rule for save button const action$ = new rxjs.Subject(); if (canWrite) effect(rxjs.merge(rxjs.fromEvent($canvas, "keyup"), action$).pipe(rxjs.tap(() => { $fab.classList.remove("hidden"); $fab.render($ICON.SAVING); $fab.onclick = () => { $fab.render($ICON.LOADING); $fab.disabled = true; port.postMessage({ cmd: "save" }); }; }))); // feature5: load file await effect(ajax({ url: getDownloadUrl(), responseType: "arraybuffer" }).pipe( rxjs.mergeMap(async ({ response }) => { try { FS.mkdir("/tmp/office/"); } catch {} await FS.writeFile("/tmp/office/" + filename , new Uint8Array(response)); await port.postMessage({ cmd: "load", filename, mime }); onDestroy(() => FS.unlink("/tmp/office/" + filename)); $canvas.focus(); }), )); await new Promise((resolve) => { port.onmessage = function(e) { switch (e.data.cmd) { case "loaded": window.dispatchEvent(new Event("resize")); setTimeout(() => { resolve(); $canvas.style.visibility = "visible"; }, 250); break; case "save": const bytes = FS.readFile("/tmp/office/" + filename); effect(save(new Blob([bytes], {})).pipe(rxjs.tap(() => { $fab.classList.add("hidden"); $fab.render($ICON.SAVING); $fab.disabled = false; }))); break; case "setFormat": switch(e.data.id) { case "Bold": e.data.state ? $toolbar.bold.classList.add("active") : $toolbar.bold.classList.remove("active"); break; case "Italic": e.data.state ? $toolbar.italic.classList.add("active") : $toolbar.italic.classList.remove("active"); break; case "Underline": e.data.state ? $toolbar.underline.classList.add("active") : $toolbar.underline.classList.remove("active"); break; case "Strikeout": e.data.state ? $toolbar.strike.classList.add("active") : $toolbar.strike.classList.remove("active"); break; case "LeftPara": if (e.data.state) qs($toolbar.alignment, "select").value = "left"; break; case "RightPara": if (e.data.state) qs($toolbar.alignment, "select").value = "right"; break; case "CenterPara": if (e.data.state) qs($toolbar.alignment, "select").value = "center"; break; case "JustifyPara": if (e.data.state) qs($toolbar.alignment, "select").value = "justify"; break; case "DefaultBullet": qs($toolbar.bullet, "select").value = e.data.state ? "ul" : "normal"; break; case "DefaultNumbering": qs($toolbar.bullet, "select").value = e.data.state ? "ol" : "normal"; break; case "StyleApply": let value = "normal"; if (e.data.state === "Title") value = "title"; else if (e.data.state === "Heading 1") value = "head1"; else if (e.data.state === "Heading 2") value = "head2"; else if (e.data.state === "Heading 3") value = "head3"; qs($toolbar.title, "select").value = value; break; case "Color": const hex = e.data.state && e.data.state > 0 ? "#" + e.data.state.toString(16).padStart(6, "0") : "#000000"; $toolbar.color.children[0].style.fill = hex; $toolbar.color.children[1].value = hex; break; case "FontHeight": const fontSize = e.data.state; qs($toolbar.size, "input").value = fontSize; break; default: console.log("format", e); throw new Error("Unknown format"); } $canvas.focus(); break; default: console.log("message", e); throw new Error("Unknown message"); } }; }); // feature6: toolbar events $toolbar.bold.onclick = () => { $toolbar.bold.classList.toggle("active"); action$.next(); port.postMessage({ cmd: "toggleFormatting", id: "Bold" }); }; $toolbar.italic.onclick = () => { $toolbar.italic.classList.toggle("active"); action$.next(); port.postMessage({ cmd: "toggleFormatting", id: "Italic" }); }; $toolbar.underline.onclick = () => { $toolbar.underline.classList.toggle("active"); action$.next(); port.postMessage({ cmd: "toggleFormatting", id: "Underline" }); }; $toolbar.bullet.onchange = (e) => { switch(e.target.value) { case "normal": port.postMessage({ cmd: "toggleFormatting", id: "RemoveBullets" }); break; case "ul": port.postMessage({ cmd: "toggleFormatting", id: "DefaultBullet" }); break; case "ol": port.postMessage({ cmd: "toggleFormatting", id: "DefaultNumbering" }); break; } action$.next(); }; $toolbar.strike.onclick = () => { $toolbar.strike.classList.toggle("active"); action$.next(); port.postMessage({ cmd: "toggleFormatting", id: "Strikeout" }); }; $toolbar.alignment.onchange = (e) => { switch(e.target.value) { case "left": port.postMessage({ cmd: "toggleFormatting", id: "LeftPara" }); break; case "right": port.postMessage({ cmd: "toggleFormatting", id: "RightPara" }); break; case "center": port.postMessage({ cmd: "toggleFormatting", id: "CenterPara" }); break; case "justify": port.postMessage({ cmd: "toggleFormatting", id: "JustifyPara" }); break; default: throw new Error("Unknown tool alignment"); } action$.next(); }; $toolbar.title.onchange = (e) => { switch(e.target.value) { case "normal": port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Standard&FamilyName:string=ParagraphStyles" }); break; case "title": port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Title&FamilyName:string=ParagraphStyles" }); break; case "head1": port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 1&FamilyName:string=ParagraphStyles" }); break; case "head2": port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 2&FamilyName:string=ParagraphStyles" }); break; case "head3": port.postMessage({ cmd: "toggleFormatting", id: "StyleApply?Style:string=Heading 3&FamilyName:string=ParagraphStyles" }); break; default: throw new Error("Unknown text style"); } action$.next(); }; $toolbar.color.onclick = (e) => { if (e.target.tagName === "INPUT") return; const $svg = e.target.closest("svg") const $input = $svg.nextElementSibling; $input.onchange = (e) => { $svg.style.fill = e.target.value; const color = parseInt(e.target.value.slice(1), 16); port.postMessage({ cmd: "toggleFormatting", id: `Color?Color:long=${color}` }) }; $input.click(); action$.next(); }; effect(rxjs.fromEvent(qs($toolbar.size, "input"), "keyup").pipe( rxjs.debounceTime(250), rxjs.tap((e) => { const fontSize = parseInt(e.target.value); port.postMessage({ cmd: "toggleFormatting", id: `FontHeight?FontHeight.Height:float=${fontSize}` }); action$.next(); }), )); // feature7: workaround known lowa bug // - when pressing escape, lowa goes out of fullscreen and show some unwanted stuff // - context menu functions like "replace" image which does crash everything with errors generated from soffice.js // - ctrl + s is broken effect(rxjs.fromEvent($page, "keydown", { capture: true }).pipe(rxjs.tap((e) => { if (e.key === "Escape") e.stopPropagation(); if (e.key === "s" && e.ctrlKey) e.stopPropagation(); }))); effect(rxjs.fromEvent($page, "mousedown", { capture: true }).pipe(rxjs.tap((e) => { if (e.which === 3) e.stopPropagation(); }))); } function isWriter(mime) { return ["application/word", "application/msword", "application/rtf", "application/vnd.oasis.opendocument.text"].indexOf(mime) >= 0; } ================================================ FILE: server/plugin/plg_application_office/loader_lowa.uno.js ================================================ // reference: // - uno programming: https://www.youtube.com/watch?v=CzxLKG9CUvo // - dispatch commands: https://wiki.documentfoundation.org/Development/DispatchCommands Module.zetajs.then(function(zetajs) { init({ css: zetajs.uno.com.sun.star, zetajs, }); }); function init({ zetajs, css }) { const context = zetajs.getUnoComponentContext(); const desktop = css.frame.Desktop.create(context); let ctrl, xModel; // UI Element: remove toolbar in writer const config = css.configuration.ReadWriteAccess.create(context, "en-US"); ["Writer", "Calc", "Impress"].forEach((app) => { const uielems = config.getByHierarchicalName(`/org.openoffice.Office.UI.${app}WindowState/UIElements/States`); for (const i of uielems.getElementNames()) { const uielem = uielems.getByName(i); if (uielem.getByName("Visible")) uielem.setPropertyValue("Visible", false); } }); // Theme & Colors const elmnts = config.getByHierarchicalName("/org.openoffice.Office.UI/ColorScheme/ColorSchemes"); for (const i of elmnts.getElementNames()) { const colorScheme = elmnts.getByName(i); // console.log(colorScheme.getElementNames()); colorScheme.getByName("AppBackground").setPropertyValue("Color", 16119285); // #f5f5f5 colorScheme.getByName("WriterPageBreaks").setPropertyValue("Color", 16119285); // #f5f5f5 colorScheme.getByName("WriterSectionBoundaries").setPropertyValue("Color", 16119285); // #f5f5f5 colorScheme.getByName("Shadow").setPropertyValue("Color", 16119285); // #f5f5f5 colorScheme.getByName("FontColor").setPropertyValue("Color", 2368548); // #242424 colorScheme.getByName("WriterHeaderFooterMark").setPropertyValue("Color", 16777215); // #ffffff } config.commitChanges(); zetajs.mainPort.onmessage = function(e) { switch (e.data.cmd) { case "destroy": toggleTools({ mime: e.data.mime, css, ctrl, context }); xModel = null; ctrl = null; break; case "load": const { filename, mime } = e.data; const in_path = `file:///tmp/office/${filename}`; xModel = desktop.loadComponentFromURL(in_path, "_default", 0, []); ctrl = xModel.getCurrentController(); ctrl.getFrame().LayoutManager.hideElement("private:resource/menubar/menubar"); ctrl.getFrame().LayoutManager.hideElement("private:resource/statusbar/statusbar"); ctrl.getFrame().getContainerWindow().FullScreen = true; toggleTools({ mime, css, ctrl, context }); const commands = [ // ref: https://wiki.documentfoundation.org/Development/DispatchCommands "Bold", "Italic", "Underline", "Strikeout", "LeftPara", "RightPara", "CenterPara", "JustifyPara", "Color", "FontHeight", ...(isWriter(mime) ? ["StyleApply", "DefaultBullet", "DefaultNumbering"] : []), ]; for (const id of commands) { const urlObj = transformUrl(".uno:" + id, { css, context }); const listener = zetajs.unoObject([css.frame.XStatusListener], { disposing: function(source) {}, statusChanged: function(state) { state = zetajs.fromAny(state.State); if (id === "StyleApply") state = state && state.StyleName || null; else if (id === "Color") state = typeof state === "number" ? state : null; else if (id === "FontHeight") state = state && state.Height || null; else if (typeof state !== "boolean") state = false; if (state === null) return; zetajs.mainPort.postMessage({ cmd: "setFormat", id, state }); }, }); queryDispatch(urlObj, { ctrl }).addStatusListener(listener, urlObj); } zetajs.mainPort.postMessage({ cmd: "loaded" }); break; case "save": xModel.store(); zetajs.mainPort.postMessage({ cmd: "save" }); break; case "toggleFormatting": dispatch(".uno:" + e.data.id, { css, ctrl, context }); break; default: throw Error("Unknown message command: " + e.data.cmd); } } } function transformUrl(unoUrl, { css, context }) { const ioparam = { val: new css.util.URL({ Complete: unoUrl }), }; css.util.URLTransformer.create(context).parseStrict(ioparam); return ioparam.val; } function queryDispatch(urlObj, { ctrl }) { return ctrl.queryDispatch(urlObj, "_self", 0); } function dispatch(unoUrl, { css, ctrl, context }) { const urlObj = transformUrl(unoUrl, { css, context }); queryDispatch(urlObj, { ctrl }).dispatch(urlObj, []); } function toggleTools({ css, ctrl, context, mime }) { dispatch(".uno:Sidebar", { css, ctrl, context }); if (isCalc(mime)) dispatch(".uno:InputLineVisible", { css, ctrl, context }); if (isWriter(mime)) dispatch(".uno:Ruler", { css, ctrl, context }); } function isWriter(mime) { return ["application/word", "application/msword", "application/rtf", "application/vnd.oasis.opendocument.text"].indexOf(mime) >= 0; } function isCalc(mime) { return ["application/excel", "application/vnd.ms-excel", "application/vnd.oasis.opendocument.spreadsheet"].indexOf(mime) >= 0; } ================================================ FILE: server/plugin/plg_application_office/manifest.json ================================================ { "author": "Filestash Pty Ltd", "version": "v0.0", "modules": [ { "type": "middleware", "entrypoint": "middleware.wasm" }, { "type": "xdg-open", "mime": "application/word", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/msword", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/rtf", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/vnd.oasis.opendocument.text", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/excel", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/vnd.ms-excel", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/vnd.oasis.opendocument.spreadsheet", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/powerpoint", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/vnd.ms-powerpoint", "entrypoint": "loader_lowa.js", "application": "skeleton" }, { "type": "xdg-open", "mime": "application/vnd.oasis.opendocument.presentation", "entrypoint": "loader_lowa.js", "application": "skeleton" } ] } ================================================ FILE: server/plugin/plg_application_office/middleware.c ================================================ //go:build ignore __attribute__((export_name("middleware"))) const unsigned char* middleware(void) { return (const unsigned char*) "HTTP/1.0 204 OK\r\n" "Cross-Origin-Opener-Policy: same-origin\r\n" "Cross-Origin-Embedder-Policy: require-corp\r\n" "\r\n" "\0"; } ================================================ FILE: server/plugin/plg_authenticate_admin/index.go ================================================ package plg_authenticate_admin import ( "fmt" "html" "net/http" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" ) func init() { Hooks.Register.AuthenticationMiddleware("admin", Admin{}) } type Admin struct{} func (this Admin) Setup() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "admin", }, { Name: "password", Type: "text", ReadOnly: true, Value: "__YOUR_ADMIN_PASSWORD__", Description: `This plugin will redirect the user to a page asking for a password. Only the admin password will be considered valid. This plugin exposes {{ .user }} (which is 'admin') and {{ .password }} for the attribute mapping section `, }, }, } } func (this Admin) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { getFlash := func() string { c, err := req.Cookie("flash") if err != nil { return "" } http.SetCookie(res, &http.Cookie{ Name: "flash", MaxAge: -1, Path: "/", }) return fmt.Sprintf(`

    %s

    `, html.EscapeString(c.Value)) } res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    ` + getFlash() + `
    `))) return nil } func (this Admin) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { if err := bcrypt.CompareHashAndPassword( []byte(Config.Get("auth.admin").String()), []byte(formData["password"]), ); err != nil { http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Invalid password", MaxAge: 1, Path: "/", }) return nil, ErrAuthenticationFailed } return map[string]string{ "user": "admin", "password": formData["password"], }, nil } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/AUTHORS.md ================================================ ### Initial author [Jeramey Crawford](https://github.com/jeramey) ### Other authors [Jonas mg](https://github.com/tredoe) ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/LICENSE ================================================ Copyright (c) 2012, Jeramey Crawford Copyright (c) 2013, Jonas mg All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/README.md ================================================ crypt ===== A password hashing library. The goal of crypt is to bring a library of many common and popular password hashing algorithms to Go and to provide a simple and consistent interface to each of them. As every hashing method is implemented in pure Go, this library should be as portable as Go itself. All hashing methods come with a test suite which verifies their operation against itself as well as the output of other password hashing implementations to ensure compatibility with them. I hope you find this library to be useful and easy to use! Note: forked from ## Installation go get github.com/tredoe/osutil/user/crypt ## License The source files are distributed under a BSD-style license that can be found in the LICENSE file. ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/apr1_crypt/apr1_crypt.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package apr1_crypt implements the standard Unix MD5-crypt algorithm created // by Poul-Henning Kamp for FreeBSD, and modified by the Apache project. // // The only change from MD5-crypt is the use of the magic constant "$apr1$" // instead of "$1$". The algorithms are otherwise identical. package apr1_crypt import ( "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/common" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/md5_crypt" ) func init() { crypt.RegisterCrypt(crypt.APR1, New, MagicPrefix) } const ( MagicPrefix = "$apr1$" SaltLenMin = 1 SaltLenMax = 8 RoundsDefault = 1000 ) var md5Crypt = md5_crypt.New() func init() { md5Crypt.SetSalt(GetSalt()) } type crypter struct{ Salt common.Salt } // New returns a new crypt.Crypter computing the variant "apr1" of MD5-crypt func New() crypt.Crypter { return &crypter{common.Salt{}} } func (c *crypter) Generate(key, salt []byte) (string, error) { return md5Crypt.Generate(key, salt) } func (c *crypter) Verify(hashedKey string, key []byte) error { return md5Crypt.Verify(hashedKey, key) } func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil } func (c *crypter) SetSalt(salt common.Salt) {} func GetSalt() common.Salt { return common.Salt{ MagicPrefix: []byte(MagicPrefix), SaltLenMin: SaltLenMin, SaltLenMax: SaltLenMax, RoundsDefault: RoundsDefault, } } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/common/base64.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. package common const alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // Base64_24Bit is a variant of Base64 encoding, commonly used with password // hashing algorithms to encode the result of their checksum output. // // The algorithm operates on up to 3 bytes at a time, encoding the following // 6-bit sequences into up to 4 hash64 ASCII bytes. // // 1. Bottom 6 bits of the first byte // 2. Top 2 bits of the first byte, and bottom 4 bits of the second byte. // 3. Top 4 bits of the second byte, and bottom 2 bits of the third byte. // 4. Top 6 bits of the third byte. // // This encoding method does not emit padding bytes as Base64 does. func Base64_24Bit(src []byte) (hash []byte) { if len(src) == 0 { return []byte{} // TODO: return nil } hashSize := (len(src) * 8) / 6 if (len(src) % 6) != 0 { hashSize += 1 } hash = make([]byte, hashSize) dst := hash for len(src) > 0 { switch len(src) { default: dst[0] = alphabet[src[0]&0x3f] dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] dst[2] = alphabet[((src[1]>>4)|(src[2]<<4))&0x3f] dst[3] = alphabet[(src[2]>>2)&0x3f] src = src[3:] dst = dst[4:] case 2: dst[0] = alphabet[src[0]&0x3f] dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] dst[2] = alphabet[(src[1]>>4)&0x3f] src = src[2:] dst = dst[3:] case 1: dst[0] = alphabet[src[0]&0x3f] dst[1] = alphabet[(src[0]>>6)&0x3f] src = src[1:] dst = dst[2:] } } return } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/common/doc.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package common contains routines used by multiple password hashing // algorithms. // // Generally, you will never import this package directly. Many of the // *_crypt packages will import this package if they require it. package common ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/common/salt.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. package common import ( "crypto/rand" "errors" "strconv" ) var ( ErrSaltPrefix = errors.New("invalid magic prefix") ErrSaltFormat = errors.New("invalid salt format") ErrSaltRounds = errors.New("invalid rounds") ) // Salt represents a salt. type Salt struct { MagicPrefix []byte SaltLenMin int SaltLenMax int RoundsMin int RoundsMax int RoundsDefault int } // Generate generates a random salt of a given length. // // The length is set thus: // // length > SaltLenMax: length = SaltLenMax // length < SaltLenMin: length = SaltLenMin func (s *Salt) Generate(length int) []byte { if length > s.SaltLenMax { length = s.SaltLenMax } else if length < s.SaltLenMin { length = s.SaltLenMin } saltLen := (length * 6 / 8) if (length*6)%8 != 0 { saltLen++ } salt := make([]byte, saltLen) rand.Read(salt) out := make([]byte, len(s.MagicPrefix)+length) copy(out, s.MagicPrefix) copy(out[len(s.MagicPrefix):], Base64_24Bit(salt)) return out } // GenerateWRounds creates a random salt with the random bytes being of the // length provided, and the rounds parameter set as specified. // // The parameters are set thus: // // length > SaltLenMax: length = SaltLenMax // length < SaltLenMin: length = SaltLenMin // // rounds < 0: rounds = RoundsDefault // rounds < RoundsMin: rounds = RoundsMin // rounds > RoundsMax: rounds = RoundsMax // // If rounds is equal to RoundsDefault, then the "rounds=" part of the salt is // removed. func (s *Salt) GenerateWRounds(length, rounds int) []byte { if length > s.SaltLenMax { length = s.SaltLenMax } else if length < s.SaltLenMin { length = s.SaltLenMin } if rounds < 0 { rounds = s.RoundsDefault } else if rounds < s.RoundsMin { rounds = s.RoundsMin } else if rounds > s.RoundsMax { rounds = s.RoundsMax } saltLen := (length * 6 / 8) if (length*6)%8 != 0 { saltLen++ } salt := make([]byte, saltLen) rand.Read(salt) roundsText := "" if rounds != s.RoundsDefault { roundsText = "rounds=" + strconv.Itoa(rounds) + "$" } out := make([]byte, len(s.MagicPrefix)+len(roundsText)+length) copy(out, s.MagicPrefix) copy(out[len(s.MagicPrefix):], []byte(roundsText)) copy(out[len(s.MagicPrefix)+len(roundsText):], Base64_24Bit(salt)) return out } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/crypt.go ================================================ // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package crypt provides interface for password crypt functions and collects // common constants. package crypt import ( "errors" "strings" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/common" ) var ErrKeyMismatch = errors.New("hashed value is not the hash of the given password") // Crypter is the common interface implemented by all crypt functions. type Crypter interface { // Generate performs the hashing algorithm, returning a full hash suitable // for storage and later password verification. // // If the salt is empty, a randomly-generated salt will be generated with a // length of SaltLenMax and number RoundsDefault of rounds. // // Any error only can be got when the salt argument is not empty. Generate(key, salt []byte) (string, error) // Verify compares a hashed key with its possible key equivalent. // Returns nil on success, or an error on failure; if the hashed key is // diffrent, the error is "ErrKeyMismatch". Verify(hashedKey string, key []byte) error // Cost returns the hashing cost (in rounds) used to create the given hashed // key. // // When, in the future, the hashing cost of a key needs to be increased in // order to adjust for greater computational power, this function allows one // to establish which keys need to be updated. // // The algorithms based in MD5-crypt use a fixed value of rounds. Cost(hashedKey string) (int, error) // SetSalt sets a different salt. It is used to easily create derivated // algorithms, i.e. "apr1_crypt" from "md5_crypt". SetSalt(salt common.Salt) } // Crypt identifies a crypt function that is implemented in another package. type Crypt uint const ( APR1 Crypt = iota + 1 // import "github.com/mickael-kerjean/filestash/server/plg_authenticate_htpasswd/deps/crypt/apr1_crypt" MD5 // import "github.com/mickael-kerjean/filestash/server/plg_authenticate_htpasswd/deps/crypt/md5_crypt" SHA256 // import "github.com/mickael-kerjean/filestash/server/plg_authenticate_htpasswd/deps/crypt/sha256_crypt" SHA512 // import "github.com/mickael-kerjean/filestash/server/plg_authenticate_htpasswd/deps/crypt/sha512_crypt" maxCrypt ) var cryptPrefixes = make([]string, maxCrypt) var crypts = make([]func() Crypter, maxCrypt) // RegisterCrypt registers a function that returns a new instance of the given // crypt function. This is intended to be called from the init function in // packages that implement crypt functions. func RegisterCrypt(c Crypt, f func() Crypter, prefix string) { if c >= maxCrypt { panic("crypt: RegisterHash of unknown crypt function") } crypts[c] = f cryptPrefixes[c] = prefix } // New returns a new crypter. func New(c Crypt) Crypter { f := crypts[c] if f != nil { return f() } panic("crypt: requested crypt function is unavailable") } // NewFromHash returns a new Crypter using the prefix in the given hashed key. func NewFromHash(hashedKey string) Crypter { var f func() Crypter if strings.HasPrefix(hashedKey, cryptPrefixes[SHA512]) { f = crypts[SHA512] } else if strings.HasPrefix(hashedKey, cryptPrefixes[SHA256]) { f = crypts[SHA256] } else if strings.HasPrefix(hashedKey, cryptPrefixes[MD5]) { f = crypts[MD5] } else if strings.HasPrefix(hashedKey, cryptPrefixes[APR1]) { f = crypts[APR1] } else { toks := strings.SplitN(hashedKey, "$", 3) prefix := "$" + toks[1] + "$" panic("crypt: unknown cryp function from prefix: " + prefix) } if f != nil { return f() } panic("crypt: requested cryp function is unavailable") } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/md5_crypt/md5_crypt.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package md5_crypt implements the standard Unix MD5-crypt algorithm created by // Poul-Henning Kamp for FreeBSD. package md5_crypt import ( "bytes" "crypto/md5" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/common" ) func init() { crypt.RegisterCrypt(crypt.MD5, New, MagicPrefix) } // NOTE: Cisco IOS only allows salts of length 4. const ( MagicPrefix = "$1$" SaltLenMin = 1 // Real minimum is 0, but that isn't useful. SaltLenMax = 8 RoundsDefault = 1000 ) type crypter struct{ Salt common.Salt } // New returns a new crypt.Crypter computing the MD5-crypt password hashing. func New() crypt.Crypter { return &crypter{GetSalt()} } func (c *crypter) Generate(key, salt []byte) (string, error) { if len(salt) == 0 { salt = c.Salt.Generate(SaltLenMax) } if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) { return "", common.ErrSaltPrefix } saltToks := bytes.Split(salt, []byte{'$'}) if len(saltToks) < 3 { return "", common.ErrSaltFormat } else { salt = saltToks[2] } if len(salt) > 8 { salt = salt[0:8] } // Compute alternate MD5 sum with input KEY, SALT, and KEY. Alternate := md5.New() Alternate.Write(key) Alternate.Write(salt) Alternate.Write(key) AlternateSum := Alternate.Sum(nil) // 16 bytes A := md5.New() A.Write(key) A.Write(c.Salt.MagicPrefix) A.Write(salt) // Add for any character in the key one byte of the alternate sum. i := len(key) for ; i > 16; i -= 16 { A.Write(AlternateSum) } A.Write(AlternateSum[0:i]) // The original implementation now does something weird: // For every 1 bit in the key, the first 0 is added to the buffer // For every 0 bit, the first character of the key // This does not seem to be what was intended but we have to follow this to // be compatible. for i = len(key); i > 0; i >>= 1 { if (i & 1) == 0 { A.Write(key[0:1]) } else { A.Write([]byte{0}) } } Csum := A.Sum(nil) // In fear of password crackers here comes a quite long loop which just // processes the output of the previous round again. // We cannot ignore this here. for i = 0; i < RoundsDefault; i++ { C := md5.New() // Add key or last result. if (i & 1) != 0 { C.Write(key) } else { C.Write(Csum) } // Add salt for numbers not divisible by 3. if (i % 3) != 0 { C.Write(salt) } // Add key for numbers not divisible by 7. if (i % 7) != 0 { C.Write(key) } // Add key or last result. if (i & 1) == 0 { C.Write(key) } else { C.Write(Csum) } Csum = C.Sum(nil) } out := make([]byte, 0, 23+len(c.Salt.MagicPrefix)+len(salt)) out = append(out, c.Salt.MagicPrefix...) out = append(out, salt...) out = append(out, '$') out = append(out, common.Base64_24Bit([]byte{ Csum[12], Csum[6], Csum[0], Csum[13], Csum[7], Csum[1], Csum[14], Csum[8], Csum[2], Csum[15], Csum[9], Csum[3], Csum[5], Csum[10], Csum[4], Csum[11], })...) // Clean sensitive data. A.Reset() Alternate.Reset() for i = 0; i < len(AlternateSum); i++ { AlternateSum[i] = 0 } return string(out), nil } func (c *crypter) Verify(hashedKey string, key []byte) error { newHash, err := c.Generate(key, []byte(hashedKey)) if err != nil { return err } if newHash != hashedKey { return crypt.ErrKeyMismatch } return nil } func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil } func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt } func GetSalt() common.Salt { return common.Salt{ MagicPrefix: []byte(MagicPrefix), SaltLenMin: SaltLenMin, SaltLenMax: SaltLenMax, RoundsDefault: RoundsDefault, } } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/sha256_crypt/sha256_crypt.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package sha256_crypt implements Ulrich Drepper's SHA256-crypt password // hashing algorithm. // // The specification for this algorithm can be found here: // http://www.akkadia.org/drepper/SHA-crypt.txt package sha256_crypt import ( "bytes" "crypto/sha256" "strconv" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/common" ) func init() { crypt.RegisterCrypt(crypt.SHA256, New, MagicPrefix) } const ( MagicPrefix = "$5$" SaltLenMin = 1 SaltLenMax = 16 RoundsMin = 1000 RoundsMax = 999999999 RoundsDefault = 5000 ) var _rounds = []byte("rounds=") type crypter struct{ Salt common.Salt } // New returns a new crypt.Crypter computing the SHA256-crypt password hashing. func New() crypt.Crypter { return &crypter{GetSalt()} } func (c *crypter) Generate(key, salt []byte) (string, error) { var rounds int var isRoundsDef bool if len(salt) == 0 { salt = c.Salt.GenerateWRounds(SaltLenMax, RoundsDefault) } if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) { return "", common.ErrSaltPrefix } saltToks := bytes.Split(salt, []byte{'$'}) if len(saltToks) < 3 { return "", common.ErrSaltFormat } if bytes.HasPrefix(saltToks[2], _rounds) { isRoundsDef = true pr, err := strconv.ParseInt(string(saltToks[2][7:]), 10, 32) if err != nil { return "", common.ErrSaltRounds } rounds = int(pr) if rounds < RoundsMin { rounds = RoundsMin } else if rounds > RoundsMax { rounds = RoundsMax } salt = saltToks[3] } else { rounds = RoundsDefault salt = saltToks[2] } if len(salt) > 16 { salt = salt[0:16] } // Compute alternate SHA256 sum with input KEY, SALT, and KEY. Alternate := sha256.New() Alternate.Write(key) Alternate.Write(salt) Alternate.Write(key) AlternateSum := Alternate.Sum(nil) // 32 bytes A := sha256.New() A.Write(key) A.Write(salt) // Add for any character in the key one byte of the alternate sum. i := len(key) for ; i > 32; i -= 32 { A.Write(AlternateSum) } A.Write(AlternateSum[0:i]) // Take the binary representation of the length of the key and for every add // the alternate sum, for every 0 the key. for i = len(key); i > 0; i >>= 1 { if (i & 1) != 0 { A.Write(AlternateSum) } else { A.Write(key) } } Asum := A.Sum(nil) // Start computation of P byte sequence. P := sha256.New() // For every character in the password add the entire password. for i = 0; i < len(key); i++ { P.Write(key) } Psum := P.Sum(nil) // Create byte sequence P. Pseq := make([]byte, 0, len(key)) for i = len(key); i > 32; i -= 32 { Pseq = append(Pseq, Psum...) } Pseq = append(Pseq, Psum[0:i]...) // Start computation of S byte sequence. S := sha256.New() for i = 0; i < (16 + int(Asum[0])); i++ { S.Write(salt) } Ssum := S.Sum(nil) // Create byte sequence S. Sseq := make([]byte, 0, len(salt)) for i = len(salt); i > 32; i -= 32 { Sseq = append(Sseq, Ssum...) } Sseq = append(Sseq, Ssum[0:i]...) Csum := Asum // Repeatedly run the collected hash value through SHA256 to burn CPU cycles. for i = 0; i < rounds; i++ { C := sha256.New() // Add key or last result. if (i & 1) != 0 { C.Write(Pseq) } else { C.Write(Csum) } // Add salt for numbers not divisible by 3. if (i % 3) != 0 { C.Write(Sseq) } // Add key for numbers not divisible by 7. if (i % 7) != 0 { C.Write(Pseq) } // Add key or last result. if (i & 1) != 0 { C.Write(Csum) } else { C.Write(Pseq) } Csum = C.Sum(nil) } out := make([]byte, 0, 80) out = append(out, c.Salt.MagicPrefix...) if isRoundsDef { out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...) } out = append(out, salt...) out = append(out, '$') out = append(out, common.Base64_24Bit([]byte{ Csum[20], Csum[10], Csum[0], Csum[11], Csum[1], Csum[21], Csum[2], Csum[22], Csum[12], Csum[23], Csum[13], Csum[3], Csum[14], Csum[4], Csum[24], Csum[5], Csum[25], Csum[15], Csum[26], Csum[16], Csum[6], Csum[17], Csum[7], Csum[27], Csum[8], Csum[28], Csum[18], Csum[29], Csum[19], Csum[9], Csum[30], Csum[31], })...) // Clean sensitive data. A.Reset() Alternate.Reset() P.Reset() for i = 0; i < len(Asum); i++ { Asum[i] = 0 } for i = 0; i < len(AlternateSum); i++ { AlternateSum[i] = 0 } for i = 0; i < len(Pseq); i++ { Pseq[i] = 0 } return string(out), nil } func (c *crypter) Verify(hashedKey string, key []byte) error { newHash, err := c.Generate(key, []byte(hashedKey)) if err != nil { return err } if newHash != hashedKey { return crypt.ErrKeyMismatch } return nil } func (c *crypter) Cost(hashedKey string) (int, error) { saltToks := bytes.Split([]byte(hashedKey), []byte{'$'}) if len(saltToks) < 3 { return 0, common.ErrSaltFormat } if !bytes.HasPrefix(saltToks[2], _rounds) { return RoundsDefault, nil } roundToks := bytes.Split(saltToks[2], []byte{'='}) cost, err := strconv.ParseInt(string(roundToks[1]), 10, 0) return int(cost), err } func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt } func GetSalt() common.Salt { return common.Salt{ MagicPrefix: []byte(MagicPrefix), SaltLenMin: SaltLenMin, SaltLenMax: SaltLenMax, RoundsDefault: RoundsDefault, RoundsMin: RoundsMin, RoundsMax: RoundsMax, } } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/deps/crypt/sha512_crypt/sha512_crypt.go ================================================ // Copyright 2012, Jeramey Crawford // Copyright 2013, Jonas mg // All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. // Package sha512_crypt implements Ulrich Drepper's SHA512-crypt password // hashing algorithm. // // The specification for this algorithm can be found here: // http://www.akkadia.org/drepper/SHA-crypt.txt package sha512_crypt import ( "bytes" "crypto/sha512" "strconv" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/common" ) func init() { crypt.RegisterCrypt(crypt.SHA512, New, MagicPrefix) } const ( MagicPrefix = "$6$" SaltLenMin = 1 SaltLenMax = 16 RoundsMin = 1000 RoundsMax = 999999999 RoundsDefault = 5000 ) var _rounds = []byte("rounds=") type crypter struct{ Salt common.Salt } // New returns a new crypt.Crypter computing the SHA512-crypt password hashing. func New() crypt.Crypter { return &crypter{GetSalt()} } func (c *crypter) Generate(key, salt []byte) (string, error) { var rounds int var isRoundsDef bool if len(salt) == 0 { salt = c.Salt.GenerateWRounds(SaltLenMax, RoundsDefault) } if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) { return "", common.ErrSaltPrefix } saltToks := bytes.Split(salt, []byte{'$'}) if len(saltToks) < 3 { return "", common.ErrSaltFormat } if bytes.HasPrefix(saltToks[2], _rounds) { isRoundsDef = true pr, err := strconv.ParseInt(string(saltToks[2][7:]), 10, 32) if err != nil { return "", common.ErrSaltRounds } rounds = int(pr) if rounds < RoundsMin { rounds = RoundsMin } else if rounds > RoundsMax { rounds = RoundsMax } salt = saltToks[3] } else { rounds = RoundsDefault salt = saltToks[2] } if len(salt) > SaltLenMax { salt = salt[0:SaltLenMax] } // Compute alternate SHA512 sum with input KEY, SALT, and KEY. Alternate := sha512.New() Alternate.Write(key) Alternate.Write(salt) Alternate.Write(key) AlternateSum := Alternate.Sum(nil) // 64 bytes A := sha512.New() A.Write(key) A.Write(salt) // Add for any character in the key one byte of the alternate sum. i := len(key) for ; i > 64; i -= 64 { A.Write(AlternateSum) } A.Write(AlternateSum[0:i]) // Take the binary representation of the length of the key and for every add // the alternate sum, for every 0 the key. for i = len(key); i > 0; i >>= 1 { if (i & 1) != 0 { A.Write(AlternateSum) } else { A.Write(key) } } Asum := A.Sum(nil) // Start computation of P byte sequence. P := sha512.New() // For every character in the password add the entire password. for i = 0; i < len(key); i++ { P.Write(key) } Psum := P.Sum(nil) // Create byte sequence P. Pseq := make([]byte, 0, len(key)) for i = len(key); i > 64; i -= 64 { Pseq = append(Pseq, Psum...) } Pseq = append(Pseq, Psum[0:i]...) // Start computation of S byte sequence. S := sha512.New() for i = 0; i < (16 + int(Asum[0])); i++ { S.Write(salt) } Ssum := S.Sum(nil) // Create byte sequence S. Sseq := make([]byte, 0, len(salt)) for i = len(salt); i > 64; i -= 64 { Sseq = append(Sseq, Ssum...) } Sseq = append(Sseq, Ssum[0:i]...) Csum := Asum // Repeatedly run the collected hash value through SHA512 to burn CPU cycles. for i = 0; i < rounds; i++ { C := sha512.New() // Add key or last result. if (i & 1) != 0 { C.Write(Pseq) } else { C.Write(Csum) } // Add salt for numbers not divisible by 3. if (i % 3) != 0 { C.Write(Sseq) } // Add key for numbers not divisible by 7. if (i % 7) != 0 { C.Write(Pseq) } // Add key or last result. if (i & 1) != 0 { C.Write(Csum) } else { C.Write(Pseq) } Csum = C.Sum(nil) } out := make([]byte, 0, 123) out = append(out, c.Salt.MagicPrefix...) if isRoundsDef { out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...) } out = append(out, salt...) out = append(out, '$') out = append(out, common.Base64_24Bit([]byte{ Csum[42], Csum[21], Csum[0], Csum[1], Csum[43], Csum[22], Csum[23], Csum[2], Csum[44], Csum[45], Csum[24], Csum[3], Csum[4], Csum[46], Csum[25], Csum[26], Csum[5], Csum[47], Csum[48], Csum[27], Csum[6], Csum[7], Csum[49], Csum[28], Csum[29], Csum[8], Csum[50], Csum[51], Csum[30], Csum[9], Csum[10], Csum[52], Csum[31], Csum[32], Csum[11], Csum[53], Csum[54], Csum[33], Csum[12], Csum[13], Csum[55], Csum[34], Csum[35], Csum[14], Csum[56], Csum[57], Csum[36], Csum[15], Csum[16], Csum[58], Csum[37], Csum[38], Csum[17], Csum[59], Csum[60], Csum[39], Csum[18], Csum[19], Csum[61], Csum[40], Csum[41], Csum[20], Csum[62], Csum[63], })...) // Clean sensitive data. A.Reset() Alternate.Reset() P.Reset() for i = 0; i < len(Asum); i++ { Asum[i] = 0 } for i = 0; i < len(AlternateSum); i++ { AlternateSum[i] = 0 } for i = 0; i < len(Pseq); i++ { Pseq[i] = 0 } return string(out), nil } func (c *crypter) Verify(hashedKey string, key []byte) error { newHash, err := c.Generate(key, []byte(hashedKey)) if err != nil { return err } if newHash != hashedKey { return crypt.ErrKeyMismatch } return nil } func (c *crypter) Cost(hashedKey string) (int, error) { saltToks := bytes.Split([]byte(hashedKey), []byte{'$'}) if len(saltToks) < 3 { return 0, common.ErrSaltFormat } if !bytes.HasPrefix(saltToks[2], _rounds) { return RoundsDefault, nil } roundToks := bytes.Split(saltToks[2], []byte{'='}) cost, err := strconv.ParseInt(string(roundToks[1]), 10, 0) return int(cost), err } func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt } func GetSalt() common.Salt { return common.Salt{ MagicPrefix: []byte(MagicPrefix), SaltLenMin: SaltLenMin, SaltLenMax: SaltLenMax, RoundsDefault: RoundsDefault, RoundsMin: RoundsMin, RoundsMax: RoundsMax, } } ================================================ FILE: server/plugin/plg_authenticate_htpasswd/index.go ================================================ package plg_authenticate_htpasswd import ( "crypto/sha1" "crypto/subtle" "encoding/base64" "fmt" "html" "net/http" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/apr1_crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/md5_crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/sha256_crypt" "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_htpasswd/deps/crypt/sha512_crypt" "golang.org/x/crypto/bcrypt" ) func init() { Hooks.Register.AuthenticationMiddleware("htpasswd", Htpasswd{}) } type Htpasswd struct{} func (this Htpasswd) Setup() Form { return Form{ Elmnts: []FormElement{ { Name: "banner", Type: "hidden", Description: `Inspired by Apache, the htpasswd plugin uses the content of a .htpasswd file to authenticate users. It displays a username and password login page, verifying credentials against the provided htpasswd data. The plugin exposes 2 variables: {{ .user }} and {{ .password }} which can be used in the attribute mapping section to create rules tailored to your specific use case. Examples of this can be found [in the documentation](https://www.filestash.app/docs/install-and-upgrade/#advanced-authentication---facade-pattern)`, }, { Name: "type", Type: "hidden", Value: "htpasswd", }, { Name: "users", Type: "long_text", Placeholder: `test1:$apr1$ZiAIyyhS$ovyMo9eJRgDF/luvmAigP0 test2:{SHA}EJ9LPFDXsN9ynSmbxvjp75Bmlx8= test3:$6$ME6DxvSEUjW4Kx/j$vQ5Yh1utmNEr4EZnWH0ZQa6hrG5yu2siybFW10aAax4u611W9awI5V90YWqGs4NjTSHkCrhpdbJoNErW9/Pbh1:19306:0:99999:7::: test4:$6$wTy86P73X/DsCiQy$El3JVUjepBUO.e.1OTuDt4yL9w2CnzY4jHaIbg1P7p508n8vjzCC8ZNsWa1IlbhciBM8.0LqqXWi3OuhGfPmP. test5:$5$RkdUxGLHGhmrO0yj$K6bCqmB.OPR7KM4i5eiAG.mxFyhElLNdthSL.dreqN5 test6:$1$vuUKD.37$R6eCPFBa6lKIVfnkABveB1`, Default: "", Description: `The list of users who are granted access using either or both the htpasswd file format or the /etc/shadow file format. To generate a password: 'openssl passwd -6' or 'mkpasswd -m SHA-512' or the htpasswd cli tool.`, }, }, } } func (this Htpasswd) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { getFlash := func() string { c, err := req.Cookie("flash") if err != nil { return "" } http.SetCookie(res, &http.Cookie{ Name: "flash", MaxAge: -1, Path: "/", }) return fmt.Sprintf(`

    %s

    `, html.EscapeString(c.Value)) } res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    ` + getFlash() + `
    `))) return nil } func (this Htpasswd) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { if idpParams["users"] == "" { Log.Error("plg_authenticate_htpasswd::callback there is no user configured") return nil, NewError("You haven't configured any users", 500) } lines := strings.Split(idpParams["users"], "\n") for n, line := range lines { pair := strings.SplitN(line, ":", 2) if len(pair) != 2 { continue } else if formData["user"] != pair[0] { continue } else if verifyPassword( formData["password"], strings.SplitN(pair[1], ":", 2)[0], // filter out unwanted fields from hash formData["user"], ) == false { continue } return map[string]string{ "user": formData["user"], "password": formData["password"], "n": fmt.Sprintf("%d", n), }, nil } http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Invalid username or password", MaxAge: 1, Path: "/", }) return nil, ErrAuthenticationFailed } func verifyPassword(password string, hash string, _user string) bool { if strings.HasPrefix(hash, "{SHA}") { d := sha1.New() d.Write([]byte(password)) return subtle.ConstantTimeCompare( []byte(strings.TrimPrefix(hash, "{SHA}")), []byte(base64.StdEncoding.EncodeToString(d.Sum(nil))), ) == 1 } var c crypt.Crypter parts := strings.SplitN(hash, "$", 4) if len(parts) != 4 { if password == hash { Log.Warning("plg_authenticate_htpasswd password for user '%s' isn't stored in a secure way, you should hash your password using something like 'openssl passwd -6'", _user) return true } else { return false } } if strings.HasPrefix(hash, "$apr1$") { c = apr1_crypt.New() parts[2] = "$apr1$" + parts[2] } else if strings.HasPrefix(hash, "$6$") { c = sha512_crypt.New() parts[2] = "$6$" + parts[2] } else if strings.HasPrefix(hash, "$5$") { c = sha256_crypt.New() parts[2] = "$5$" + parts[2] } else if strings.HasPrefix(hash, "$1$") { c = md5_crypt.New() parts[2] = "$1$" + parts[2] } else if strings.HasPrefix(hash, "$2a$") { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } else { return false } shadow, err := c.Generate( []byte(password), []byte(parts[2]), ) if err != nil { return false } else if shadow != hash { return false } return true } ================================================ FILE: server/plugin/plg_authenticate_ldap/index.go ================================================ package plg_authenticate_ldap import ( . "github.com/mickael-kerjean/filestash/server/common" "net/http" ) func init() { Hooks.Register.AuthenticationMiddleware("ldap", Ldap{}) } type Ldap struct{} func (this Ldap) Setup() Form { return Form{ Elmnts: []FormElement{ { Name: "banner", Type: "hidden", Description: `This enterprise SSO plugin delegates authentication to an LDAP server, presenting users with a username and password login page. Their credentials are then verified against your LDAP directory. The plugin exposes the LDAP attribute of the authenticated users which can be used in the attribute mapping section to create rules tailored to your specific use case, see the documentation [on the website](https://www.filestash.app/setup-ldap.html). `, }, { Name: "type", Type: "hidden", Value: "ldap", }, { Name: "Hostname", Type: "text", Value: "", Placeholder: "eg: ldap.example.com", }, { Name: "Port", Type: "text", Value: "", Placeholder: "eg: 389", }, { Name: "Bind DN", Type: "text", Value: "", Placeholder: "Bind DN", }, { Name: "Bind DN Password", Type: "password", Value: "", Placeholder: "Bind CN Password", }, { Name: "Base DN", Type: "text", Value: "", Placeholder: "Base DN", }, { Name: "Search Filter", Type: "text", Value: "", Placeholder: "default: (&(objectclass=person)(|(uid={{.username}})(mail={{.username}})(sAMAccountName={{.username}})))", }, }, } } func (this Ldap) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { http.Redirect( res, req, "https://www.filestash.app/purchase-enterprise-selfhosted.html", http.StatusTemporaryRedirect, ) return nil } func (this Ldap) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { return nil, ErrNotImplemented } ================================================ FILE: server/plugin/plg_authenticate_local/README.md ================================================ ``` export TOKEN=xxxx # list users curl -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" "http://localhost:8334/admin/api/simple-user-management" # upsert user curl -X POST -d 'email=test@example.com&password=password&role=user' -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" "http://localhost:8334/admin/api/simple-user-management" # delete user curl -X DELETE -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" "http://localhost:8334/admin/api/simple-user-management?email=test@example.com" ``` ================================================ FILE: server/plugin/plg_authenticate_local/auth.go ================================================ package plg_authenticate_local import ( "bytes" "encoding/base64" "encoding/json" "fmt" "html" "image/png" "net/http" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "github.com/pquerna/otp/totp" "golang.org/x/crypto/bcrypt" ) type SimpleAuth struct{} func (this SimpleAuth) Setup() Form { nUsers := 0 aUsers := 0 if users, err := getUsers(); err == nil { nUsers = len(users) for i := range users { if users[i].Disabled == false { aUsers += 1 } } } return Form{ Elmnts: []FormElement{ { Name: "banner", Type: "hidden", Description: fmt.Sprintf(`
    MANAGEMENT GUI: /admin/simple-user-management
    STATS:
    ┌─────────────┐   ┌──────────────┐
    │ TOTAL USERS │   │ ACTIVE USERS │
    |    %.4d     │   |     %.4d     │
    └─────────────┘   └──────────────┘
    EMAIL SERVER: %t
    
    `, nUsers, aUsers, isEmailSetup()), }, { Name: "type", Type: "hidden", Value: "local", }, { Name: "mfa", Type: "select", Default: "", Opts: []string{"", "TOTP"}, }, { Name: "notification_subject", Type: "text", }, { Name: "notification_body", Type: "long_text", Placeholder: `Hello, Your account was created by an administrator. You can access it via http://{{ .instance_url }}. Your password is: {{ .password }} The roles assigned to you: {{ .role }} Cheers!`, }, { Name: "db", Type: "hidden", }, }, } } func (this SimpleAuth) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { getFlash := func() string { c, err := req.Cookie("flash") if err != nil { return "" } http.SetCookie(res, &http.Cookie{ Name: "flash", MaxAge: -1, Path: "/", }) return fmt.Sprintf(`

    %s

    `, html.EscapeString(c.Value)) } res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) if c, err := req.Cookie("mfa"); err == nil && c.Value != "" { user := withMFA(User{}, c.Value) key, err := totp.Generate(totp.GenerateOpts{ Issuer: Config.Get("general.name").String(), AccountName: user.Email, }) if err != nil { return err } var buf bytes.Buffer img, err := key.Image(200, 200) if err != nil { return err } if err = png.Encode(&buf, img); err != nil { return err } template.Must(template.New("app").Parse(Page(`
    {{ if eq .User.MFA "" }}
    {{ end }} `+getFlash()+`
    `))).Execute(res, struct { User User Session string MFASecret string QRCode string }{ User: user, Session: c.Value, MFASecret: key.Secret(), QRCode: base64.StdEncoding.EncodeToString(buf.Bytes()), }) return nil } res.Write([]byte(Page(`
    ` + getFlash() + `
    `))) return nil } func (this SimpleAuth) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { users, err := getUsers() if err != nil { return nil, err } requestedUser := withMFA(User{ Email: formData["user"], Password: formData["password"], }, formData["session"]) requestedUser.Code = formData["code"] for i := range users { if users[i].Email != requestedUser.Email { continue } if err = bcrypt.CompareHashAndPassword([]byte(users[i].Password), []byte(requestedUser.Password)); err != nil { break } if users[i].Disabled == true { http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Account is disabled", MaxAge: 1, Path: "/", }) Log.Warning("plg_authentication_simple::auth action=authenticate email=%s err=disabled", users[i].Email) return nil, ErrAuthenticationFailed } if idpParams["mfa"] == "TOTP" { shouldSaveMFAKey := false if users[i].MFA == "" { users[i].MFA = formData["mfa"] shouldSaveMFAKey = true } if totp.Validate(requestedUser.Code, users[i].MFA) == false { requestedUser.MFA = users[i].MFA http.SetCookie(res, &http.Cookie{ Name: "mfa", Value: requestedUser.EncryptedString(), MaxAge: 1, }) return nil, ErrAuthenticationFailed } if shouldSaveMFAKey { saveUsers(users) } } session := map[string]string{ "user": requestedUser.Email, "password": requestedUser.Password, "bcrypt": users[i].Password, "role": users[i].Role, } s := "" for k, v := range session { if k == "password" || k == "bcrypt" { v = "*****" } s += fmt.Sprintf("%s[%s] ", k, v) } Log.Debug("IDP Attributes => %s", s) return session, nil } http.SetCookie(res, &http.Cookie{ Name: "flash", Value: "Invalid username or password", MaxAge: 1, Path: "/", }) return nil, ErrAuthenticationFailed } func withMFA(user User, session string) User { if session == "" { return user } data, err := DecryptString(SECRET_KEY_DERIVATE_FOR_USER, session) if err != nil { return User{} } var u User if err = json.Unmarshal([]byte(data), &u); err != nil { return User{} } user.Email = u.Email user.Password = u.Password return u } func (user User) EncryptedString() string { b, err := json.Marshal(user) if err != nil { return "" } d, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(b)) if err != nil { return "" } return d } ================================================ FILE: server/plugin/plg_authenticate_local/config.go ================================================ package plg_authenticate_local import ( "encoding/json" ) type pluginConfig map[string]any func (this pluginConfig) GetUsers() ([]User, error) { db, ok := this["db"].(string) if !ok || db == "" { return []User{}, nil } var users []User if err := json.Unmarshal([]byte(db), &users); err != nil { return nil, err } return users, nil } func (this pluginConfig) GetMailSubject() string { if subject, ok := this["notification_subject"].(string); ok { return subject } return "" } func (this pluginConfig) GetMailBody() string { if body, ok := this["notification_body"].(string); ok { return body } return "" } func (this pluginConfig) SetUsers(users []User) { if usersData, err := json.Marshal(users); err == nil { this["db"] = string(usersData) } } ================================================ FILE: server/plugin/plg_authenticate_local/data.go ================================================ package plg_authenticate_local import ( "encoding/json" "os" . "github.com/mickael-kerjean/filestash/server/common" ) var force bool func init() { Hooks.Register.Onload(func() { force = os.Getenv("PLG_AUTHENTICATE_LOCAL_ENABLED") == "true" }) } func getPluginData() (pluginConfig, error) { cfg := make(pluginConfig) if !isEnabled() { Log.Warning("plg_authenticate_simple::disable msg=middleware_is_not_enabled") return cfg, ErrMissingDependency } err := json.Unmarshal( []byte(Config.Get("middleware.identity_provider.params").String()), &cfg, ) return cfg, err } func savePluginData(cfg pluginConfig) error { if !isEnabled() { Log.Warning("plg_authenticate_simple::disable msg=middleware_is_not_enabled") return ErrMissingDependency } b, err := json.Marshal(cfg) if err != nil { return err } Config.Get("middleware.identity_provider.params").Set(string(b)) return nil } func isEnabled() bool { if Config.Get("middleware.identity_provider.type").String() == "local" { return true } else if force { return true } Log.Warning("plg_authenticate_simple::disable msg=middleware_is_not_enabled") return false } ================================================ FILE: server/plugin/plg_authenticate_local/handler.go ================================================ package plg_authenticate_local import ( _ "embed" "html/template" "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) //go:embed handler.html var PAGE string func UserManagementHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if req.Method == http.MethodDelete { email := req.FormValue("email") if email == "" { email = req.URL.Query().Get("email") } if err := removeUser(req.FormValue("email")); err != nil { SendErrorResult(res, err) return } SendSuccessResult(res, nil) return } currentUser := User{} users, err := getUsers() if err != nil { SendErrorResult(res, err) return } email := formatEmail(req.URL.Query().Get("email")) if email == "" { email = formatEmail(req.FormValue("email")) } if email != "" { for i := range users { if users[i].Email == email { currentUser = users[i] break } } } if req.Method == http.MethodPost { user := User{ Email: email, Password: formatPassword(req.FormValue("password")), Role: formatRole(req.FormValue("role")), Disabled: req.FormValue("disabled") == "on", } redirectURI := req.URL.String() fn := createUser if currentUser.Email != "" { fn = updateUser redirectURI = req.URL.Path } if err := fn(user); err != nil { SendErrorResult(res, err) return } if currentUser.Email == "" { go sendInvitateMail(user) } if isAPI(req) { SendSuccessResult(res, nil) return } http.Redirect(res, req, redirectURI, http.StatusSeeOther) return } if isAPI(req) { SendSuccessResults(res, users) return } template. Must(template.New("app").Parse(Page(PAGE))). Execute(res, struct { Users []User CurrentUser User BackURL string }{ Users: users, CurrentUser: currentUser, BackURL: WithBase("/admin/storage"), }) } func isAPI(r *http.Request) bool { return r.Header.Get("Accept") == "application/json" } ================================================ FILE: server/plugin/plg_authenticate_local/handler.html ================================================

    {{ if eq .CurrentUser.Email "" }}User Creation{{ else }}User Update{{ end }} +

    User Management +

    Manage your team members and their account permissions

    {{ $length := len .Users }} {{ if eq $length 0 }}

    There is not user yet, create one!

    {{ else }} {{range $user := .Users }} {{end}}
    Email Role
    {{ $user.Email }} {{ $user.Role }} DEL EDIT
    {{ end }}
    < back ================================================ FILE: server/plugin/plg_authenticate_local/index.go ================================================ package plg_authenticate_local import ( "net/http" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/middleware" "github.com/gorilla/mux" ) func init() { Hooks.Register.AuthenticationMiddleware("local", SimpleAuth{}) Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.Handle(WithBase("/admin/simple-user-management"), http.RedirectHandler(WithBase("/admin/api/simple-user-management"), http.StatusSeeOther)).Methods("GET") r.HandleFunc(WithBase("/admin/api/simple-user-management"), middleware.NewMiddlewareChain( UserManagementHandler, []Middleware{middleware.AdminOnly}, )).Methods("GET", "POST", "DELETE", "PATCH") return nil }) } type User struct { Email string `json:"email"` Password string `json:"password"` Role string `json:"role,omitempty"` Disabled bool `json:"disabled,omitempty"` Code string `json:"-"` MFA string `json:"mfa,omitempty"` } ================================================ FILE: server/plugin/plg_authenticate_local/notify.go ================================================ package plg_authenticate_local import ( "bytes" "fmt" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "gopkg.in/gomail.v2" ) func isEmailSetup() bool { if Config.Get("email.from").String() == "" { return false } if Config.Get("email.server").String() == "" { return false } if Config.Get("email.port").Int() == 0 { return false } if Config.Get("email.username").String() == "" { return false } if Config.Get("email.password").String() == "" { return false } return true } func sendInvitateMail(user User) error { cfg, err := getPluginData() if err != nil { return err } else if cfg.GetMailSubject() == "" || cfg.GetMailBody() == "" { return nil } m := gomail.NewMessage() m.SetHeader("From", Config.Get("email.from").String()) m.SetHeader("To", user.Email) m.SetHeader("Subject", withTemplate(cfg.GetMailSubject(), user)) m.SetBody("text/plain", withTemplate(cfg.GetMailBody(), user)) d := gomail.NewDialer( Config.Get("email.server").String(), Config.Get("email.port").Int(), Config.Get("email.username").String(), Config.Get("email.password").String(), ) if err := d.DialAndSend(m); err != nil { return fmt.Errorf("cannot send mail - reason=%s", err.Error()) } Log.Info("plg_authenticate_simple::notification action=sent email=%s", user.Email) return nil } func withTemplate(in string, user User) string { var b bytes.Buffer tmpl, err := template.New("app").Parse(in) if err != nil { Log.Warning("plg_authenticate_simple::mail err=cannot_compile_template") return in } tmpl.Execute(&b, map[string]string{ "instance_url": Config.Get("general.host").String(), "user": user.Email, "password": user.Password, "role": user.Role, }) return b.String() } ================================================ FILE: server/plugin/plg_authenticate_local/service.go ================================================ package plg_authenticate_local import ( "sort" "strings" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" ) func removeUser(email string) error { users, err := getUsers() if err != nil { return err } for i := range users { if users[i].Email == email { users[i] = users[len(users)-1] return saveUsers(users[:len(users)-1]) } } return ErrNotFound } func createUser(user User) error { if user.Password == "" { return ErrNotValid } pwd := user.Password if len(pwd) > 72 { pwd = pwd[0:72] } p, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) if err != nil { return err } user.Password = string(p) users, err := getUsers() if err != nil { return err } return saveUsers(append(users, user)) } func updateUser(user User) error { users, err := getUsers() if err != nil { return err } for i := range users { if users[i].Email == user.Email { if strings.HasPrefix(user.Password, "$2a$") == false { p, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) if err != nil { return err } user.Password = string(p) users[i].Password = user.Password } users[i].Disabled = user.Disabled users[i].Role = user.Role return saveUsers(users) } } return ErrNotFound } func getUsers() ([]User, error) { cfg, err := getPluginData() if err != nil { return nil, err } return cfg.GetUsers() } func saveUsers(users []User) error { cfg, err := getPluginData() if err != nil { return err } sort.Slice(users, func(i, j int) bool { userI := []byte(users[i].Email) userJ := []byte(users[j].Email) n := len(userI) if len(userJ) < len(userI) { n = len(userJ) } for i := 0; i < n; i++ { if userI[i] == userJ[i] { continue } return userI[i] < userJ[i] } return false }) cfg.SetUsers(users) return savePluginData(cfg) } ================================================ FILE: server/plugin/plg_authenticate_local/utils.go ================================================ package plg_authenticate_local import ( "strings" ) func formatRole(s string) string { arr := strings.Split(s, ",") for i := range arr { arr[i] = strings.TrimSpace(arr[i]) } return strings.Join(arr, ", ") } func formatEmail(s string) string { return strings.TrimSpace(strings.ToLower(s)) } func formatPassword(s string) string { return strings.TrimSpace(s) } ================================================ FILE: server/plugin/plg_authenticate_passthrough/index.go ================================================ package plg_authenticate_passthrough import ( "fmt" "html" "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.AuthenticationMiddleware("passthrough", Passthrough{}) } type Passthrough struct{} func (this Passthrough) Setup() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "passthrough", }, { Name: "strategy", Type: "select", Value: "direct", Opts: []string{"direct", "password_only", "username_and_password"}, Description: `This plugin has 3 base strategies: 1. The 'direct' strategy will redirect the user to your storage without asking for anything and use whatever is configured in the attribute mapping section. 2. The 'password_only' strategy will redirect the user to a page asking for a password which you can map to a field in the attribute mapping section like this: {{ .password }} 3. The 'username_and_password' strategy is similar to the 'password_only' strategy but you will see in the login page both a username and password field which can be used fom the attribute mapping section like this: {{ .user }} {{ .password }}`, }, }, } } func (this Passthrough) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { res.Header().Set("Content-Type", "text/html; charset=utf-8") getParams := "?label=" + html.EscapeString(req.URL.Query().Get("label")) + "&state=" + html.EscapeString(req.URL.Query().Get("state")) switch idpParams["strategy"] { case "direct": res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    `))) case "password_only": res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    `))) case "username_and_password": res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    `))) default: res.WriteHeader(http.StatusNotFound) res.Write([]byte(Page(fmt.Sprintf("Unknown strategy: '%s'", idpParams["strategy"])))) } return nil } func (this Passthrough) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { return map[string]string{ "user": formData["user"], "password": formData["password"], }, nil } ================================================ FILE: server/plugin/plg_authenticate_wordpress/README.md ================================================ # What is this? This plugin lets you use a WordPress site as an identity provider. It surely is unconventional, but it enables access patterns such as read and write access to /var/www for editors, read-only access for subscribers, or secure links to S3 buckets, ... With this plugin, users are authenticated against WordPress, and RBAC rules control who can do what and where. ================================================ FILE: server/plugin/plg_authenticate_wordpress/helper.go ================================================ package plg_authenticate_wordpress import ( "strings" ) func extractXMLValue(xml, key string) string { start := strings.Index(xml, ""+key+"") if start == -1 { return "" } start = strings.Index(xml[start:], "") if start == -1 { return "" } start += len("") end := strings.Index(xml[start:], "") if end == -1 { return "" } return xml[start : start+end] } func extractXMLArray(xml, key string) []string { start := strings.Index(xml, ""+key+"") if start == -1 { return []string{} } start = strings.Index(xml[start:], "") if start == -1 { return []string{} } end := strings.Index(xml[start:], "") if end == -1 { return []string{} } arrayXML := xml[start : start+end] var values []string for { valStart := strings.Index(arrayXML, "") if valStart == -1 { break } valStart += len("") valEnd := strings.Index(arrayXML[valStart:], "") if valEnd == -1 { break } values = append(values, arrayXML[valStart:valStart+valEnd]) arrayXML = arrayXML[valStart+valEnd:] } return values } ================================================ FILE: server/plugin/plg_authenticate_wordpress/index.go ================================================ package plg_authenticate_wordpress import ( "io" "net/http" "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/ctrl" ) func init() { Hooks.Register.AuthenticationMiddleware("wordpress", Wordpress{}) } type Wordpress struct{} func (this Wordpress) Setup() Form { return Form{ Elmnts: []FormElement{ { Name: "banner", Type: "hidden", Description: `Make it possible for your Wordpress users to authenticate to your storage. If valid, you will have the following attributes available about the user in the attribute mapping section: {{ .user }}, {{ .email }}, {{ .roles }}, {{ .id }}, and {{ .password }}`, }, { Name: "type", Type: "hidden", Value: "wordpress", }, { Name: "url", Type: "text", Description: `The URL of your wordpress instance. Eg: http://localhost`, }, }, } } func (this Wordpress) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error { res.Header().Set("Content-Type", "text/html; charset=utf-8") res.WriteHeader(http.StatusOK) res.Write([]byte(Page(`
    `))) return nil } func (this Wordpress) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) { username := formData["user"] password := formData["password"] wpURL := strings.TrimRight(idpParams["url"], "/") if username == "" || password == "" || wpURL == "" { return nil, ErrAuthenticationFailed } xmlBody, err := TmplExec( ` wp.getProfile 1 {{ .user }} {{ .password }} `, map[string]string{ "user": username, "password": password, }, ) if err != nil { return nil, err } req, err := http.NewRequest("POST", wpURL+"/xmlrpc.php", strings.NewReader(xmlBody)) if err != nil { return nil, NewError("Failed to create request: "+err.Error(), 500) } req.Header.Set("Content-Type", "text/xml") resp, err := HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } bodyS := string(body) if strings.Contains(bodyS, "") { return nil, ErrAuthenticationFailed } user := extractXMLValue(bodyS, "username") if user == "" { return nil, ErrAuthenticationFailed } out := map[string]string{ "user": user, "password": password, } if email := extractXMLValue(bodyS, "email"); email != "" { out["email"] = email } if userID := extractXMLValue(bodyS, "user_id"); userID != "" { out["id"] = userID } out["role"] = strings.Join(extractXMLArray(bodyS, "roles"), ", ") return out, nil } ================================================ FILE: server/plugin/plg_authorisation_example/index.go ================================================ package plg_authorisation_example import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.AuthorisationMiddleware(AuthM{}) } type AuthM struct{} func (this AuthM) Ls(ctx *App, path string) error { Log.Stdout("LS %+v", ctx.Session) return nil } func (this AuthM) Cat(ctx *App, path string) error { Log.Stdout("CAT %+v", ctx.Session) return nil } func (this AuthM) Mkdir(ctx *App, path string) error { Log.Stdout("MKDIR %+v", ctx.Session) return ErrNotAllowed } func (this AuthM) Rm(ctx *App, path string) error { Log.Stdout("RM %+v", ctx.Session) return ErrNotAllowed } func (this AuthM) Mv(ctx *App, from string, to string) error { Log.Stdout("MV %+v", ctx.Session) return ErrNotAllowed } func (this AuthM) Save(ctx *App, path string) error { Log.Stdout("SAVE %+v", ctx.Session) return ErrNotAllowed } func (this AuthM) Touch(ctx *App, path string) error { Log.Stdout("TOUCH %+v", ctx.Session) return ErrNotAllowed } ================================================ FILE: server/plugin/plg_backend_artifactory/index.go ================================================ package plg_backend_artifactory import ( "bytes" "encoding/json" "fmt" . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "net/url" "os" "path/filepath" "strings" "time" ) func init() { Backend.Register("artifactory", ArtifactoryStorage{}) } type ArtifactoryStorage struct { instance string token string } func (this ArtifactoryStorage) Init(params map[string]string, app *App) (IBackend, error) { if strings.HasPrefix(params["instance"], "https://") == false && strings.HasPrefix(params["instance"], "http://") == false { return this, ErrNotValid } this.token = params["token"] this.instance = strings.TrimSuffix(strings.TrimSuffix(params["instance"], "/"), "/artifactory") return this, nil } func (this ArtifactoryStorage) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "artifactory", }, { Name: "instance", Type: "text", Placeholder: "Instance URL", }, { Name: "token", Type: "text", Placeholder: "Access Token", }, { Name: "path", Type: "text", Placeholder: "Path", }, }, } } func (this ArtifactoryStorage) Meta(path string) Metadata { if path == "/" { return Metadata{ CanCreateFile: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), } } return Metadata{} } func (this ArtifactoryStorage) Ls(path string) ([]os.FileInfo, error) { p := this.artifactoryPath(path) var ( req *http.Request err error parse func([]byte) ([]os.FileInfo, error) ) if p.repository == "" { req, err = http.NewRequest( "GET", fmt.Sprintf("%s/artifactory/api/repositories", this.instance), nil, ) parse = func(jsonStr []byte) ([]os.FileInfo, error) { artifactoryResponse := []struct { Key string `json:"key"` Description string `json:"description"` Type string `json:"type"` Url string `json:"url"` PackageType string `json:"packageType"` }{} if err := json.Unmarshal(jsonStr, &artifactoryResponse); err != nil { Log.Warning("plg_backend_artifactory::ls unmarshall %s", err.Error()) return nil, ErrNotValid } files := make([]os.FileInfo, len(artifactoryResponse)) for i, artifactoryFile := range artifactoryResponse { files[i] = File{ FName: artifactoryFile.Key, FType: "directory", FTime: 0, FSize: 0, } } return files, nil } } else { req, err = http.NewRequest( "GET", fmt.Sprintf("%s/artifactory/api/storage%s?list&deep=0&listFolders=1", this.instance, path), nil, ) parse = func(jsonStr []byte) ([]os.FileInfo, error) { artifactoryResponse := struct { Files []struct { Uri string `json:"uri"` Size int64 `json:"size"` LastModified string `json:"lastModified"` Folder bool `json:"folder"` Sha1 string `json:"sha1"` Sha2 string `json:"sha2"` } `json:"files"` CreationTime string `json:"created"` Uri string `json:"uri"` }{} if err := json.Unmarshal(jsonStr, &artifactoryResponse); err != nil { Log.Warning("plg_backend_artifactory::ls unmarshall %s", err.Error()) return nil, ErrNotValid } files := make([]os.FileInfo, len(artifactoryResponse.Files)) for i, artifactoryFile := range artifactoryResponse.Files { files[i] = File{ FName: filepath.Base(artifactoryFile.Uri), FType: func() string { if artifactoryFile.Folder { return "directory" } return "file" }(), FTime: func() int64 { t, err := time.Parse("2006-01-02T15:04:05.000Z", artifactoryFile.LastModified) if err != nil { return 0 } return t.Unix() }(), FSize: artifactoryFile.Size, } } return files, nil } } if err != nil { return nil, err } req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return nil, err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::ls readall data[%s] status[%d]", string(jsonStr), res.StatusCode) return []os.FileInfo{}, ErrNotValid } else if res.StatusCode != 200 { Log.Debug("plg_backend_artifactory::ls nok status[%d] data[%s]", res.StatusCode, string(jsonStr)) return []os.FileInfo{}, ErrNotValid } return parse(jsonStr) } func (this ArtifactoryStorage) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this ArtifactoryStorage) Cat(path string) (io.ReadCloser, error) { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-FileInfo if p := this.artifactoryPath(path); p.path == "" { return nil, ErrNotValid } req, err := http.NewRequest( "GET", fmt.Sprintf("%s/artifactory/api/storage%s", this.instance, path), nil, ) if err != nil { return nil, err } req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return nil, err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::cat readall data[%s] status[%d]", string(jsonStr), res.StatusCode) return nil, ErrNotValid } else if res.StatusCode != 200 { Log.Debug("plg_backend_artifactory::cat nok status[%d] data[%s]", res.StatusCode, string(jsonStr)) return nil, ErrNotValid } artifactoryResponse := struct { Repo string `json:"repo"` Path string `json:"path"` Created string `json:"created"` CreatedBy string `json:"createdBy"` LastModified string `json:"lastModified"` ModifiedBy string `json:"modifiedBy"` LastUpdated string `json:"lastUpdated"` DownloadLink string `json:"downloadUri"` MimeType string `json:"mimeType"` Size string `json:"size"` Checksums struct { Sha1 string `json:"sha1"` Md5 string `json:"md5"` Sha256 string `json:"sha256"` } `json:"checksums"` OriginalChecksums struct { Sha1 string `json:"sha1"` Md5 string `json:"md5"` Sha256 string `json:"sha256"` } `json:"originalChecksums"` Uri string `json:"uri"` }{} if err := json.Unmarshal(jsonStr, &artifactoryResponse); err != nil { Log.Warning("plg_backend_artifactory::ls unmarshall %s", err.Error()) return nil, ErrNotValid } req, err = http.NewRequest("GET", artifactoryResponse.DownloadLink, nil) req.Header.Add("Authorization", "Bearer "+this.token) res, err = HTTPClient.Do(req) return res.Body, err } func (this ArtifactoryStorage) Mkdir(path string) error { p := this.artifactoryPath(path) var ( req *http.Request err error validStatus int ) if p.path == "" { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-CreateRepository req, err = http.NewRequest( "PUT", fmt.Sprintf("%s/artifactory/api/repositories/%s", this.instance, p.repository), bytes.NewReader([]byte(`{"rclass" : "local"}`)), ) validStatus = 200 } else { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-CreateDirectory req, err = http.NewRequest( "PUT", fmt.Sprintf("%s/artifactory%s", this.instance, path), bytes.NewReader([]byte(fmt.Sprintf(`{ "key": "%s%s", "repo": "%s", "path": "%s", "created": "%s" }`, this.instance, path, p.repository, path, ))), ) validStatus = 201 } if err != nil { return err } req.Header.Add("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::mkdir readall status[%d] data[%s]", res.StatusCode, string(jsonStr)) return ErrNotValid } else if res.StatusCode != validStatus { Log.Debug("plg_backend_artifactory::mkdir nok url[%s] status[%d] data[%s]", req.URL, res.StatusCode, string(jsonStr)) return ErrNotValid } return nil } func (this ArtifactoryStorage) Rm(path string) error { var ( req *http.Request err error validStatus int ) p := this.artifactoryPath(path) if p.path == "" { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-DeleteRepository req, err = http.NewRequest( "DELETE", fmt.Sprintf("%s/artifactory/api/repositories/%s", this.instance, p.repository), nil, ) validStatus = 200 } else { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-DeleteItem req, err = http.NewRequest( "DELETE", fmt.Sprintf("%s/artifactory%s", this.instance, path), nil, ) validStatus = 204 } if err != nil { return err } req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::rm readall status[%d] data[%s]", res.StatusCode, string(jsonStr)) return ErrNotValid } else if res.StatusCode != validStatus { Log.Debug("plg_backend_artifactory::rm nok url[%s] status[%d] data[%s]", req.URL, res.StatusCode, string(jsonStr)) return ErrNotValid } return nil } func (this ArtifactoryStorage) Mv(from, to string) error { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-MoveItem req, err := http.NewRequest( "POST", fmt.Sprintf( "%s/artifactory/api/move%s?to=%s", this.instance, from, url.QueryEscape(to), ), nil, ) if err != nil { return err } req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::mv readall status[%d] data[%s]", res.StatusCode, string(jsonStr)) return ErrNotValid } else if res.StatusCode != 200 { Log.Debug("plg_backend_artifactory::mv nok url[%s] status[%d] data[%s]", req.URL, res.StatusCode, string(jsonStr)) return ErrNotValid } return nil } func (this ArtifactoryStorage) Save(path string, content io.Reader) error { // https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-Example-DeployinganArtifact p := this.artifactoryPath(path) if p.path == "" { return ErrNotValid } req, err := http.NewRequest( "PUT", fmt.Sprintf("%s/artifactory%s", this.instance, path), content, ) if err != nil { return err } req.Header.Add("Authorization", "Bearer "+this.token) res, err := HTTPClient.Do(req) if err != nil { return err } jsonStr, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { Log.Debug("plg_backend_artifactory::save readall status[%d] data[%s]", res.StatusCode, string(jsonStr)) return ErrNotValid } else if res.StatusCode != 201 { Log.Debug("plg_backend_artifactory::save nok url[%s] status[%d] data[%s]", req.URL, res.StatusCode, string(jsonStr)) return ErrNotValid } return nil } func (this ArtifactoryStorage) Touch(path string) error { return this.Save(path, bytes.NewReader([]byte(""))) } func (this ArtifactoryStorage) artifactoryPath(_path string) (p struct { repository string path string }) { sp := strings.Split(_path, "/") if len(sp) > 1 { p.repository = sp[1] } if len(sp) > 2 { p.path = strings.Join(sp[2:], "/") } return p } ================================================ FILE: server/plugin/plg_backend_azure/index.go ================================================ package plg_backend_azure import ( "context" "fmt" "io" "os" "path/filepath" "strings" "sync" . "github.com/mickael-kerjean/filestash/server/common" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" ) type AzureBlob struct { client *azblob.Client ctx context.Context } func init() { Backend.Register("azure", &AzureBlob{}) } func (this *AzureBlob) Init(params map[string]string, app *App) (IBackend, error) { cred, err := container.NewSharedKeyCredential(params["account_name"], params["account_key"]) if err != nil { Log.Debug("plg_backend_azure::new_cred_error %s", err.Error()) return nil, ErrAuthenticationFailed } serviceURL := fmt.Sprintf("https://%s.blob.core.windows.net/", params["account_name"]) client, err := azblob.NewClientWithSharedKeyCredential(serviceURL, cred, nil) if err != nil { Log.Debug("plg_backend_azure::new_client_error %s", err.Error()) return nil, ErrAuthenticationFailed } return &AzureBlob{client, app.Context}, nil } func (this *AzureBlob) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "azure", }, FormElement{ Name: "account_name", Type: "text", Placeholder: "Account Name", }, FormElement{ Name: "account_key", Type: "password", Placeholder: "Account Key", }, FormElement{ Name: "path", Type: "text", Placeholder: "Path", }, }, } } func (this *AzureBlob) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) ap := this.path(path) if ap.containerName == "" { pager := this.client.NewListContainersPager(nil) for pager.More() { resp, err := pager.NextPage(this.ctx) if err != nil { return files, err } for _, blob := range resp.ListContainersSegmentResponse.ContainerItems { files = append(files, File{ FName: *blob.Name, FType: "directory", FTime: blob.Properties.LastModified.Unix(), FSize: -1, }) } } return files, nil } client := this.client.ServiceClient().NewContainerClient(ap.containerName) pager := client.NewListBlobsHierarchyPager("/", &container.ListBlobsHierarchyOptions{ Prefix: &ap.blobName, }) for pager.More() { resp, err := pager.NextPage(this.ctx) if err != nil { return files, err } for _, blob := range resp.ListBlobsHierarchySegmentResponse.Segment.BlobPrefixes { if *blob.Name == "/" { continue } files = append(files, File{ FName: filepath.Base(*blob.Name), FType: "directory", FTime: -1, FSize: -1, }) } for _, blob := range resp.ListBlobsHierarchySegmentResponse.Segment.BlobItems { files = append(files, File{ FName: filepath.Base(*blob.Name), FType: "file", FTime: blob.Properties.LastModified.Unix(), FSize: *blob.Properties.ContentLength, }) } } return files, nil } func (this AzureBlob) Cat(path string) (io.ReadCloser, error) { ap := this.path(path) return &azureFilecat{ offset: 0, ctx: this.ctx, ap: ap, client: this.client, reader: nil, }, nil } type azureFilecat struct { offset int64 ctx context.Context ap azurePath reader io.ReadCloser client *azblob.Client mu sync.Mutex } func (this *azureFilecat) Read(p []byte) (n int, err error) { this.mu.Lock() defer this.mu.Unlock() if this.reader == nil { resp, err := this.client.DownloadStream( this.ctx, this.ap.containerName, this.ap.blobName, &azblob.DownloadStreamOptions{ Range: azblob.HTTPRange{ Offset: this.offset, }, }, ) if err != nil { return 0, err } this.reader = resp.Body } return this.reader.Read(p) } func (this *azureFilecat) Seek(offset int64, whence int) (int64, error) { this.mu.Lock() defer this.mu.Unlock() if offset < 0 { return this.offset, os.ErrInvalid } switch whence { case io.SeekStart: case io.SeekCurrent: offset += this.offset case io.SeekEnd: props, err := this.client.ServiceClient().NewContainerClient(this.ap.containerName).NewBlockBlobClient(this.ap.blobName).GetProperties(this.ctx, nil) if err != nil { return this.offset, err } offset += *props.ContentLength default: return this.offset, ErrNotImplemented } this.offset = offset return this.offset, nil } func (this *azureFilecat) Close() error { this.mu.Lock() defer this.mu.Unlock() if this.reader == nil { return nil } return this.reader.Close() } func (this AzureBlob) Stat(path string) (os.FileInfo, error) { ap := this.path(path) props, err := this.client.ServiceClient().NewContainerClient(ap.containerName).NewBlockBlobClient(ap.blobName).GetProperties(this.ctx, nil) if err == nil { if props.ContentLength == nil || props.LastModified == nil { return nil, ErrNotValid } return File{ FName: filepath.Base(path), FSize: *props.ContentLength, FTime: (*props.LastModified).Unix(), }, nil return nil, err } return File{ FName: filepath.Base(path), FType: "directory", FTime: -1, }, nil } func (this *AzureBlob) Mkdir(path string) error { ap := this.path(path) if ap.blobName == "" { _, err := this.client.CreateContainer(this.ctx, ap.containerName, nil) return err } _, err := this.client.UploadBuffer(this.ctx, ap.containerName, EnforceDirectory(ap.blobName)+".keep", []byte(""), nil) return err } func (this *AzureBlob) Rm(path string) error { ap := this.path(path) if ap.blobName == "" { _, err := this.client.DeleteContainer(this.ctx, ap.containerName, nil) return err } finfo, err := this.Stat(path) if err != nil { return err } if finfo.IsDir() == false { _, err := this.client.DeleteBlob(this.ctx, ap.containerName, ap.blobName, nil) return err } pager := this.client.NewListBlobsFlatPager(ap.containerName, &container.ListBlobsFlatOptions{ Include: container.ListBlobsInclude{Snapshots: true, Versions: true}, Prefix: &ap.blobName, }) for pager.More() { resp, err := pager.NextPage(this.ctx) if err != nil { return err } for _, blob := range resp.Segment.BlobItems { _, err := this.client.DeleteBlob(context.Background(), ap.containerName, *blob.Name, nil) if err != nil { return err } } } return nil } func (this *AzureBlob) Mv(from string, to string) error { apFrom := this.path(from) apTo := this.path(to) if apFrom.containerName != apTo.containerName { return ErrNotSupported } finfo, err := this.Stat(from) if err != nil { return err } // CASE 1: Move a file if finfo.IsDir() == false { sourceClient := this.client.ServiceClient().NewContainerClient(apFrom.containerName).NewBlockBlobClient(apFrom.blobName) destClient := this.client.ServiceClient().NewContainerClient(apTo.containerName).NewBlockBlobClient(apTo.blobName) if _, err := destClient.StartCopyFromURL( this.ctx, sourceClient.URL(), nil, ); err != nil { return err } _, err = this.client.DeleteBlob(this.ctx, apFrom.containerName, apFrom.blobName, nil) return err } // CASE 2: Move a directory pager := this.client.NewListBlobsFlatPager(apFrom.containerName, &container.ListBlobsFlatOptions{ Include: container.ListBlobsInclude{Snapshots: true, Versions: true}, Prefix: &apFrom.blobName, }) for pager.More() { resp, err := pager.NextPage(this.ctx) if err != nil { return err } for _, blob := range resp.Segment.BlobItems { if err := this.ctx.Err(); err != nil { return err } else if blob.Name == nil { return ErrNotValid } oldName := *blob.Name newName := strings.Replace(oldName, apFrom.blobName, apTo.blobName, 1) sourceClient := this.client.ServiceClient().NewContainerClient(apFrom.containerName).NewBlockBlobClient(oldName) destClient := this.client.ServiceClient().NewContainerClient(apTo.containerName).NewBlockBlobClient(newName) if _, err := destClient.StartCopyFromURL( context.Background(), sourceClient.URL(), nil, ); err != nil { return err } _, err = this.client.DeleteBlob(context.Background(), apFrom.containerName, oldName, nil) if err != nil { return err } } } return nil } func (this *AzureBlob) Touch(path string) error { return this.Save(path, strings.NewReader("")) } func (this *AzureBlob) Save(path string, file io.Reader) error { ap := this.path(path) _, err := this.client.UploadStream( this.ctx, ap.containerName, ap.blobName, file, nil, ) return err } func (this *AzureBlob) Meta(path string) Metadata { if path == "/" { return Metadata{ CanCreateFile: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), } } return Metadata{} } type azurePath struct { containerName string blobName string } func (this AzureBlob) path(path string) azurePath { ap := azurePath{} path = strings.TrimLeft(path, "/") sp := strings.SplitN(path, "/", 2) if len(sp) != 2 { return ap } ap.containerName = sp[0] ap.blobName = sp[1] return ap } ================================================ FILE: server/plugin/plg_backend_backblaze/index.go ================================================ package plg_backend_backblaze import ( "bytes" "crypto/sha1" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) var backblaze_cache AppCache type Backblaze struct { params map[string]string Buckets map[string]string ApiUrl string `json:"apiUrl"` DownloadUrl string `json:"downloadUrl"` AccountId string `json:"accountId"` Token string `json:"authorizationToken"` Status int `json:"status"` } type BackblazeError struct { Code string `json:"code"` Message string `json:"message"` Status int `json:"status"` } func init() { Backend.Register("backblaze", Backblaze{}) backblaze_cache = NewAppCache() } func (this Backblaze) Init(params map[string]string, app *App) (IBackend, error) { this.params = params // By default backblaze required quite a few API calls to just find the data that's under a given bucket // This would result in a slow application hence we are caching everyting that's in the hot path if obj := backblaze_cache.Get(params); obj != nil { return obj.(*Backblaze), nil } // To perform some query, we need to first know things like where we will have to query, get a token, ... res, err := this.request("GET", "https://api.backblazeb2.com/b2api/v2/b2_authorize_account", nil, nil) if err != nil { return nil, err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return nil, err } if err := json.Unmarshal(body, &this); err != nil { return nil, err } // Extract bucket related information as backblaze use bucketId as an identifer // BucketId is just some internal ref as people expect to see the bucketName res, err = this.request( "POST", this.ApiUrl+"/b2api/v2/b2_list_buckets", strings.NewReader(fmt.Sprintf( `{"accountId":"%s"}`, this.AccountId, )), nil, ) if err != nil { return nil, err } body, err = io.ReadAll(res.Body) res.Body.Close() if err != nil { return nil, err } var buckets struct { Buckets []struct { BucketId string `json:"bucketId"` BucketName string `json:"bucketName"` } `json:"buckets"` } if err = json.Unmarshal(body, &buckets); err != nil { return nil, err } this.Buckets = make(map[string]string, len(buckets.Buckets)) for i := range buckets.Buckets { this.Buckets[buckets.Buckets[i].BucketName] = buckets.Buckets[i].BucketId } delete(params, "password") backblaze_cache.Set(params, &this) return this, nil } func (this Backblaze) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "backblaze", }, FormElement{ Name: "username", Type: "text", Placeholder: "KeyID", }, FormElement{ Name: "password", Type: "password", Placeholder: "applicationKey", }, }, } } func (this Backblaze) Ls(path string) ([]os.FileInfo, error) { if path == "/" { files := make([]os.FileInfo, 0, len(this.Buckets)) for key := range this.Buckets { files = append(files, File{ FName: key, FType: "directory", }) } return files, nil } // prepare the query p := this.path(path) reqJSON, _ := json.Marshal(struct { BucketId string `json:"bucketId"` Delimiter string `json:"delimiter"` MaxFileCount int `json:"maxFileCount"` Prefix string `json:"prefix"` }{p.BucketId, "/", 10000, p.Prefix}) res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_list_file_names", bytes.NewReader(reqJSON), nil, ) if err != nil { return nil, err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return nil, err } var resBody struct { Files []struct { FType string `json:"action"` Size int64 `json:"contentLength"` Name string `json:"fileName"` Time int64 `json:"uploadTimestamp"` } `json:"files"` } if err = json.Unmarshal(body, &resBody); err != nil { return nil, err } files := make([]os.FileInfo, len(resBody.Files)) for i := range resBody.Files { files[i] = File{ FName: strings.TrimSuffix(strings.TrimPrefix(resBody.Files[i].Name, p.Prefix), "/"), FType: func() string { if resBody.Files[i].FType == "folder" { return "directory" } return "file" }(), FSize: resBody.Files[i].Size, FTime: resBody.Files[i].Time / 1000, } } return files, nil } func (this Backblaze) Cat(path string) (io.ReadCloser, error) { res, err := this.request( "GET", this.DownloadUrl+"/file"+path+"?Authorization="+this.Token, nil, nil, ) if err != nil { return nil, err } return res.Body, nil } func (this Backblaze) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this Backblaze) Mkdir(path string) error { p := this.path(path) if p.BucketId == "" { bucketName := "" if bp := strings.Split(path, "/"); len(bp) > 1 { bucketName = bp[1] } if bucketName == "" { return ErrNotValid } res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_create_bucket", strings.NewReader(fmt.Sprintf( `{"accountId": "%s", "bucketName": "%s", "bucketType": "allPrivate"}`, this.AccountId, bucketName, )), nil, ) if err != nil { return err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resError BackblazeError if err := json.Unmarshal(body, &resError); err != nil { return err } if resError.Message != "" { return NewError(resError.Message, resError.Status) } return nil } return this.Touch(path + ".bzEmpty") } func (this Backblaze) Rm(path string) error { p := this.path(path) if p.BucketId == "" { return ErrNotValid } if p.Prefix == "" { backblaze_cache.Del(this.params) // cache invalidation res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_delete_bucket", strings.NewReader(fmt.Sprintf( `{"accountId": "%s", "bucketId": "%s"}`, this.AccountId, p.BucketId, )), nil, ) if err != nil { return err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resError BackblazeError if err := json.Unmarshal(body, &resError); err != nil { return err } if resError.Message != "" { return NewError(resError.Message, resError.Status) } return nil } // Backblaze doesn't provide a recursive API to delete => requires multiple steps // Step 1: find every files in a folder: b2_list_file_names res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_list_file_names", strings.NewReader(fmt.Sprintf( `{"bucketId": "%s", "maxFileCount": 10000, "delimiter": "/", "prefix": "%s"}`, p.BucketId, p.Prefix, )), nil, ) if err != nil { return err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } bRes := struct { Files []struct { FileId string `json:"fileId"` FileName string `json:"fileName"` } `json:"files"` }{} if err = json.Unmarshal(body, &bRes); err != nil { return err } // Step 2: delete files 1 by 1: b2_delete_file_version for i := range bRes.Files { res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_delete_file_version", strings.NewReader(fmt.Sprintf( `{"fileName": "%s", "fileId": "%s"}`, bRes.Files[i].FileName, bRes.Files[i].FileId, )), nil, ) if err != nil { return err } if body, err = io.ReadAll(res.Body); err != nil { return err } res.Body.Close() var resError BackblazeError if err := json.Unmarshal(body, &resError); err != nil { return err } if resError.Message != "" { return NewError(resError.Message, resError.Status) } } return nil } func (this Backblaze) Mv(from string, to string) error { return ErrNotSupported } func (this Backblaze) Touch(path string) error { p := this.path(path) // Step 1: get the URL we will proceed to the upload res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_get_upload_url", strings.NewReader(fmt.Sprintf(`{"bucketId": "%s"}`, p.BucketId)), nil, ) if err != nil { return err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resBody struct { UploadUrl string `json:"uploadUrl"` Token string `json:"authorizationToken"` } if err := json.Unmarshal(body, &resBody); err != nil { return err } // Step 2: perform the upload of the empty file res, err = this.request( "POST", resBody.UploadUrl, nil, func(r *http.Request) { r.Header.Set("Authorization", resBody.Token) r.Header.Set("X-Bz-File-Name", url.QueryEscape(p.Prefix)) r.Header.Set("Content-Type", "application/octet-stream") r.Header.Set("Content-Length", "0") r.Header.Set("X-Bz-Content-Sha1", "da39a3ee5e6b4b0d3255bfef95601890afd80709") }, ) if err != nil { return err } body, err = io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resError BackblazeError if err := json.Unmarshal(body, &resError); err != nil { return err } if resError.Message != "" { return NewError(resError.Message, resError.Status) } return nil } func (this Backblaze) Save(path string, file io.Reader) error { p := this.path(path) // Step 1: get the URL we will proceed to the upload res, err := this.request( "POST", this.ApiUrl+"/b2api/v2/b2_get_upload_url", strings.NewReader(fmt.Sprintf(`{"bucketId": "%s"}`, p.BucketId)), nil, ) if err != nil { return err } body, err := io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resBody struct { UploadUrl string `json:"uploadUrl"` Token string `json:"authorizationToken"` } if err := json.Unmarshal(body, &resBody); err != nil { return err } // Step 2: get details backblaze requires to perform the upload backblazeFileDetail := struct { path string ContentLength int64 Sha1 []byte }{} backblazeFileDetail.path = filepath.Join( GetAbsolutePath(TMP_PATH), "data_"+QuickString(20)+".dat", ) f, err := os.OpenFile(backblazeFileDetail.path, os.O_CREATE|os.O_RDWR, os.ModePerm) if err != nil { return err } defer f.Close() defer os.Remove(backblazeFileDetail.path) io.Copy(f, file) if obj, ok := file.(io.Closer); ok { obj.Close() } s, err := f.Stat() if err != nil { return err } backblazeFileDetail.ContentLength = s.Size() f.Seek(0, io.SeekStart) h := sha1.New() if _, err := io.Copy(h, f); err != nil { return err } backblazeFileDetail.Sha1 = h.Sum(nil) // Step 3: perform the upload f.Seek(0, io.SeekStart) res, err = this.request( "POST", resBody.UploadUrl, f, func(r *http.Request) { r.ContentLength = backblazeFileDetail.ContentLength r.Header.Set("Authorization", resBody.Token) r.Header.Set("X-Bz-File-Name", url.QueryEscape(p.Prefix)) r.Header.Set("Content-Type", "application/octet-stream") r.Header.Set("X-Bz-Content-Sha1", fmt.Sprintf("%x", backblazeFileDetail.Sha1)) }, ) if err != nil { return err } body, err = io.ReadAll(res.Body) res.Body.Close() if err != nil { return err } var resError BackblazeError if err := json.Unmarshal(body, &resError); err != nil { return err } if resError.Message != "" { return NewError(resError.Message, resError.Status) } return nil } func (this Backblaze) Meta(path string) Metadata { m := Metadata{ CanRename: NewBool(false), CanMove: NewBool(false), } if path == "/" { m.CanCreateFile = NewBool(false) m.CanUpload = NewBool(false) } return m } func (this Backblaze) request(method string, url string, body io.Reader, fn func(req *http.Request)) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } if this.Token == "" { req.Header.Set( "Authorization", fmt.Sprintf( "Basic %s", base64.StdEncoding.EncodeToString( []byte(fmt.Sprintf("%s:%s", this.params["username"], this.params["password"])), ), ), ) } else { req.Header.Set("Authorization", this.Token) } req.Header.Set("User-Agent", "Filestash "+APP_VERSION+"."+BUILD_DATE) req.Header.Set("Accept", "application/json") if fn != nil { fn(req) } if req.Body != nil { defer req.Body.Close() } res, err := HTTPClient.Do(req) if err != nil { return res, err } if res.StatusCode == 401 { res.Body.Close() return res, ErrAuthenticationFailed } else if res.StatusCode == 403 { res.Body.Close() return res, ErrNotAllowed } else if res.StatusCode == 429 { res.Body.Close() retryAfter, err := strconv.Atoi(res.Header.Get("Retry-After")) if err == nil && retryAfter < 10 && retryAfter >= 0 { time.Sleep(time.Duration(retryAfter) * time.Second) return this.request(method, url, body, fn) } return res, ErrCongestion } else if res.StatusCode == 503 { res.Body.Close() retryAfter, err := strconv.Atoi(res.Header.Get("Retry-After")) if err == nil && retryAfter < 10 && retryAfter >= 0 { time.Sleep(time.Duration(retryAfter) * time.Second) return this.request(method, url, body, fn) } return res, ErrCongestion } return res, nil } type BackblazePath struct { BucketId string Prefix string } func (this Backblaze) path(path string) BackblazePath { bp := strings.Split(path, "/") bucket := "" if len(bp) > 1 { bucket = bp[1] } prefix := "" if len(bp) > 2 { prefix = strings.Join(bp[2:], "/") } return BackblazePath{ this.Buckets[bucket], prefix, } } ================================================ FILE: server/plugin/plg_backend_dav/index.go ================================================ package plg_backend_dav import ( "encoding/xml" "fmt" . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "time" ) var DavCache AppCache const ( CARDDAV string = "carddav" CALDAV string = "caldav" ) func init() { DavCache = NewAppCache(2, 1) Backend.Register(CARDDAV, Dav{}) Backend.Register(CALDAV, Dav{}) } type Dav struct { which string url string params map[string]string cache map[string]interface{} } func (this Dav) Init(params map[string]string, app *App) (IBackend, error) { if b := DavCache.Get(params); b != nil { backend := b.(*Dav) return backend, nil } backend := Dav{ url: strings.ReplaceAll(params["url"], "%{username}", url.PathEscape(params["username"])), which: params["type"], params: params, } DavCache.Set(params, &backend) return backend, nil } func (this Dav) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: this.which, }, FormElement{ Name: "url", Type: "text", Placeholder: "URL", }, FormElement{ Name: "username", Type: "text", Placeholder: "username", }, FormElement{ Name: "password", Type: "password", Placeholder: "password", }, }, } } func (this Dav) Ls(path string) ([]os.FileInfo, error) { var files []os.FileInfo var err error if path == "/" { var collections []DavCollection if collections, err = this.getCollections(); err != nil { return files, err } files = make([]os.FileInfo, 0, len(collections)) for _, collection := range collections { files = append(files, File{ FName: collection.Name, FType: "directory", FSize: -1, }) } return files, nil } var resources []DavResource if resources, err = this.getResources(path); err != nil { return files, err } files = make([]os.FileInfo, 0, len(resources)) for _, card := range resources { files = append(files, File{ FName: card.Name, FType: "file", FSize: -1, FTime: card.Time, }) } return files, nil } func (this Dav) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this Dav) Cat(path string) (io.ReadCloser, error) { var uri string var err error var res *http.Response if uri, err = this.getResourceURI(path); err != nil { return nil, err } if res, err = this.request("GET", uri, nil, nil); err != nil { return nil, err } return res.Body, nil } func (this Dav) Mkdir(path string) error { var uri string var err error var res *http.Response if uri, err = this.getUserURI(); err != nil { return err } if len(strings.Split(strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/"), "/")) != 1 { return ErrNotValid } else if _, err = this.getCollectionURI(path); err == nil { return ErrConflict } name := filepath.Base(path) if strings.HasSuffix(uri, "/") { uri = uri + name } else { uri = uri + "/" + name } if this.which == CARDDAV { if res, err = this.request("MKCOL", uri, queryNewAddressBook(name), nil); err != nil { return err } res.Body.Close() DavCache.Del(this.params) return nil } else if this.which == CALDAV { if res, err = this.request("MKCOL", uri, queryNewCalendar(name), nil); err != nil { return err } res.Body.Close() DavCache.Del(this.params) return nil } return ErrNotValid } func (this Dav) Rm(path string) error { var uri string var err error var res *http.Response p := strings.Split(strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/"), "/") if len(p) == 1 { if uri, err = this.getCollectionURI(path); err != nil { return err } if res, err = this.request("DELETE", uri, nil, nil); err != nil { return err } DavCache.Del(this.params) res.Body.Close() return nil } else if len(p) == 2 { if uri, err = this.getResourceURI(path); err != nil { return err } if res, err = this.request("DELETE", uri, nil, nil); err != nil { return err } res.Body.Close() return nil } return ErrNotValid } func (this Dav) Mv(from string, to string) error { if filepath.Dir(from) != filepath.Dir(to) { return ErrNotValid } reader, err := this.Cat(from) if err != nil { return err } d, err := io.ReadAll(reader) if err != nil { return ErrNotValid } content := strings.Split(string(d), "\n") for line := range content { if this.which == CARDDAV { if strings.HasPrefix(content[line], "FN:") { content[line] = fmt.Sprintf("FN:%s\n", strings.TrimSuffix(filepath.Base(to), filepath.Ext(to))) break } } else if this.which == CALDAV { if strings.HasPrefix(content[line], "SUMMARY:") { content[line] = fmt.Sprintf("SUMMARY:%s\n", strings.TrimSuffix(filepath.Base(to), filepath.Ext(to))) break } } } if err = this.Save(to, strings.NewReader(strings.Join(content, "\n"))); err != nil { return err } return this.Rm(from) } func (this Dav) Touch(path string) error { var uri string var uid string var err error var res *http.Response var content string = "" if uri, err = this.getCollectionURI(path); err != nil { return err } else if strings.HasSuffix(uri, "/") == false { uri += "/" } uid = RandomString(20) uri += uid if this.which == CARDDAV { uri += ".vcf" name := strings.Split(strings.TrimSuffix(filepath.Base(path), ".vcf"), " ") content += "BEGIN:VCARD\n" content += "PRODID:-//Filestash//Filestash Carddav client//EN" content += "VERSION:3.0\n" if len(name) == 1 { content += fmt.Sprintf("FN:%s\n", name[0]) content += fmt.Sprintf("N:;%s;;;\n", name[0]) } else if len(name) >= 2 { content += fmt.Sprintf("FN:%s\n", strings.Join(name, " ")) if len(name) == 2 { content += fmt.Sprintf("N:%s;%s;;;\n", name[0], strings.Join(name[1:], " ")) } else { content += fmt.Sprintf("N:%s\n", name[0]) } } content += "END:VCARD" } else if this.which == CALDAV { now := time.Now() uri += ".ics" name := strings.TrimSuffix(filepath.Base(path), ".ics") content += "BEGIN:VCALENDAR\n" content += "VERSION:2.0\n" content += "PRODID:-//Filestash//Filestash Caldav Client//EN\n" content += "BEGIN:VEVENT\n" content += fmt.Sprintf("UID:%s\n", uid) content += fmt.Sprintf("DTSTART:%04d%02d%02dT080000Z\n", now.Year(), now.Month(), now.Day()) content += fmt.Sprintf("DTEND:%04d%02d%02dT090000Z\n", now.Year(), now.Month(), now.Day()) content += fmt.Sprintf("DTSTAMP:%04d%02d%02dT%02d%02d00Z\n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()) content += fmt.Sprintf("SUMMARY:%s\n", name) content += "END:VEVENT\n" content += "END:VCALENDAR" } if content == "" { return ErrNotValid } if res, err = this.request("PUT", uri, strings.NewReader(content), func(req *http.Request) { if this.which == CALDAV { req.Header.Add("Content-Type", "text/calendar") } else if this.which == CARDDAV { req.Header.Add("Content-Type", "text/vcard") } req.Header.Add("If-None-Match", "*") }); err != nil { return err } defer res.Body.Close() return nil } func (this Dav) Save(path string, file io.Reader) error { var uriInit string var uri string var err error var res *http.Response if uriInit, err = this.getResourceURI(path); err != nil { uriInit = "" } if uri, err = this.getCollectionURI(path); err != nil { return err } else if strings.HasSuffix(uri, "/") == false { uri += "/" } uri += RandomString(15) if this.which == CARDDAV && strings.HasSuffix(uri, ".vcf") == false { uri += ".vcf" } else if this.which == CALDAV && strings.HasSuffix(uri, ".ics") == false { uri += ".ics" } if res, err = this.request("PUT", uri, file, func(req *http.Request) { if this.which == CALDAV { req.Header.Add("Content-Type", "text/calendar") } else if this.which == CARDDAV { req.Header.Add("Content-Type", "text/vcard") } req.Header.Add("If-None-Match", "*") }); err != nil { return err } res.Body.Close() if uriInit != "" { if res, err = this.request("DELETE", uriInit, nil, nil); err != nil { return err } res.Body.Close() } return nil } func (this Dav) Meta(path string) Metadata { m := Metadata{ CanMove: NewBool(false), HideExtension: NewBool(true), } if path == "/" { m.CanCreateFile = NewBool(false) m.CanCreateDirectory = NewBool(true) m.CanRename = NewBool(false) m.CanUpload = NewBool(false) m.RefreshOnCreate = NewBool(false) } else { m.CanCreateFile = NewBool(true) m.CanCreateDirectory = NewBool(false) m.CanRename = NewBool(true) m.CanUpload = NewBool(true) m.RefreshOnCreate = NewBool(true) } return m } func (this Dav) request(method string, url string, body io.Reader, fn func(req *http.Request)) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } if this.params["username"] != "" { req.SetBasicAuth(this.params["username"], this.params["password"]) } if req.Body != nil { defer req.Body.Close() } if fn != nil { fn(req) } res, err := HTTPClient.Do(req) if err != nil { return nil, err } else if res.StatusCode > 400 { res.Body.Close() return nil, NewError(HTTPFriendlyStatus(res.StatusCode), res.StatusCode) } return res, nil } func (this Dav) getUserURI() (string, error) { var res *http.Response var err error if this.cache["getUserURI"] != nil { return this.cache["getUserURI"].(string), nil } if res, err = this.request( "PROPFIND", this.url, strings.NewReader(` `), nil, ); err != nil { return "", err } var t struct { Responses []DavCollection `xml:"response"` } decoder := xml.NewDecoder(res.Body) defer res.Body.Close() if err := decoder.Decode(&t); err != nil { return "", err } if len(t.Responses) == 0 { return "", ErrNotReachable } url, err := this.parseURL(t.Responses[0].User) if err != nil { return "", err } if this.cache == nil { this.cache = make(map[string]interface{}) } this.cache["getUserURI"] = url DavCache.Set(this.params, &this) return url, nil } func (this Dav) getCollections() ([]DavCollection, error) { var uri string var res *http.Response var err error if this.cache["getCollections"] != nil { return this.cache["getCollections"].([]DavCollection), nil } if uri, err = this.getUserURI(); err != nil { return nil, err } if res, err = this.request("PROPFIND", uri, strings.NewReader(` `), func(req *http.Request) { req.Header.Add("Depth", "1") req.Header.Add("Content-Type", "application/xml") }); err != nil { return nil, err } var t struct { Responses []DavCollection `xml:"response"` } decoder := xml.NewDecoder(res.Body) defer res.Body.Close() if err := decoder.Decode(&t); err != nil { return nil, err } var collections []DavCollection = make([]DavCollection, 0, len(t.Responses)) for i := range t.Responses { if t.Responses[i].Name == "" { continue } else if this.which == CARDDAV { if strings.Contains(t.Responses[i].Type.Inner, "addressbook") { collections = append(collections, t.Responses[i]) } } else if this.which == CALDAV { if strings.Contains(t.Responses[i].Type.Inner, "calendar") { collections = append(collections, t.Responses[i]) } } } if this.cache == nil { this.cache = make(map[string]interface{}) } this.cache["getCollections"] = collections DavCache.Set(this.params, &this) return collections, nil } func (this Dav) getCollectionURI(path string) (string, error) { path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/") p := strings.Split(path, "/") if len(p) == 0 { return "", ErrNotFound } coll, err := this.getCollections() if err != nil { return "", err } for i := 0; i < len(coll); i++ { if coll[i].Name == string(p[0]) { return this.parseURL(coll[i].Url) } } return "", ErrNotFound } func (this Dav) getResources(path string) ([]DavResource, error) { var uri string var res *http.Response var err error if uri, err = this.getCollectionURI(path); err != nil { return nil, err } if res, err = this.request( "REPORT", uri, func() io.Reader { var query string = "" if this.which == CARDDAV { query = ` ` } else if this.which == CALDAV { query = ` ` } return strings.NewReader(query) }(), func(req *http.Request) { req.Header.Add("Depth", "1") req.Header.Add("Content-Type", "application/xml") }, ); err != nil { return nil, err } decoder := xml.NewDecoder(res.Body) defer res.Body.Close() var r struct { Responses []DavResource `xml:"response"` } if err = decoder.Decode(&r); err != nil { return nil, err } for i := range r.Responses { r.Responses[i].Name, r.Responses[i].Time = func() (string, int64) { var t int64 = 0 name := "unknown" if this.which == CARDDAV { for _, line := range strings.Split(r.Responses[i].Vcard, "\n") { if strings.HasPrefix(line, "FN:") && this.which == CARDDAV { name = strings.TrimPrefix(line, "FN:") break } } name += ".vcf" } else if this.which == CALDAV { strToInt := func(chunk string) int { ret, _ := strconv.Atoi(chunk) return ret } for _, line := range strings.Split(r.Responses[i].Ical, "\n") { if strings.HasPrefix(line, "SUMMARY:") { name = strings.TrimPrefix(line, "SUMMARY:") } else if strings.HasPrefix(line, "DTSTART:") { // https://tools.ietf.org/html/rfc2445#section-4.3.5 // quick and dirty parser for form 1 & 2 c := strings.TrimSuffix(strings.TrimSpace(strings.TrimPrefix(line, "DTSTART:")), "Z") if len(c) == 15 && t == 0 { t = time.Date( strToInt(c[0:4]), time.Month(strToInt(c[4:6])+1), strToInt(c[6:8]), // date strToInt(c[9:11]), strToInt(c[11:13]), strToInt(c[13:15]), // time 0, time.UTC, ).Unix() } } else if strings.HasPrefix(line, "DTSTART;VALUE=DATE:") { c := strings.TrimSpace(strings.TrimPrefix(line, "DTSTART;VALUE=DATE:")) if len(c) == 8 && t == 0 { t = time.Date( strToInt(c[0:4]), time.Month(strToInt(c[4:6])+1), strToInt(c[6:8]), // date 0, 0, 0, // time 0, time.UTC, ).Unix() } } } name += ".ics" } return name, t }() } return r.Responses, nil } func (this Dav) getResourceURI(path string) (string, error) { path = strings.TrimSuffix(strings.TrimPrefix(path, "/"), "/") p := strings.Split(path, "/") if len(p) != 2 { return "", ErrNotValid } var resources []DavResource var err error if resources, err = this.getResources(path); err != nil { return "", ErrNotFound } filename := filepath.Base(path) for i := range resources { if resources[i].Name == filename { return this.parseURL(resources[i].Url) } } return "", ErrNotFound } func (this Dav) parseURL(link string) (string, error) { var origin *url.URL var destination *url.URL var err error if destination, _ = url.Parse(link); err != nil { return "", err } if origin, err = url.Parse(this.url); err != nil { return "", err } if destination.Host == "" || destination.Scheme == "" { destination.Host = origin.Host destination.Scheme = origin.Scheme } return destination.String(), nil } func joinURL(base string, bit string) string { if strings.HasSuffix(base, "/") == false { base += "/" } return base + bit } type DavResource struct { Url string `xml:"href"` Name string `xml:"-"` Time int64 `xml:"-"` Vcard string `xml:"propstat>prop>address-data,omitempty"` Ical string `xml:"propstat>prop>calendar-data,omitempty"` } type DavCollection struct { Url string `xml:"href"` Name string `xml:"propstat>prop>displayname,omitempty"` User string `xml:"propstat>prop>current-user-principal>href,omitempty"` Type struct { Inner string `xml:",innerxml"` } `xml:"propstat>prop>resourcetype,omitempty"` } func queryNewCalendar(name string) io.Reader { query := ` {{NAME}} #9AD1ED ` query = strings.Replace(query, "{{NAME}}", name, -1) return strings.NewReader(query) } func queryNewAddressBook(name string) io.Reader { query := ` {{NAME}} ` query = strings.Replace(query, "{{NAME}}", name, -1) return strings.NewReader(query) } ================================================ FILE: server/plugin/plg_backend_dropbox/index.go ================================================ package plg_backend_dropbox import ( "encoding/json" . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "os" "path/filepath" "regexp" "strings" "time" ) func init() { Backend.Register("dropbox", Dropbox{}) } type Dropbox struct { ClientId string Hostname string Bearer string } func (d Dropbox) Init(params map[string]string, app *App) (IBackend, error) { backend := &Dropbox{} if env := os.Getenv("DROPBOX_CLIENT_ID"); env != "" { backend.ClientId = env } else { backend.ClientId = Config.Get("auth.dropbox.client_id").Default("").String() } backend.Hostname = Config.Get("general.host").String() backend.Bearer = params["access_token"] if backend.ClientId == "" { return backend, NewError("Missing ClientID: Contact your admin", 502) } else if backend.Hostname == "" { return backend, NewError("Missing Hostname: Contact your admin", 502) } return backend, nil } func (d Dropbox) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "dropbox", }, FormElement{ ReadOnly: true, Name: "oauth2", Type: "text", Value: "/api/session/auth/dropbox", }, FormElement{ ReadOnly: true, Name: "image", Type: "image", Value: "data:image/svg+xml;utf8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDIuNCAzOS41IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICA8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjEyLjUsMCAwLDguMSA4LjcsMTUuMSAyMS4yLDcuMyIvPgo8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjAsMjEuOSAxMi41LDMwLjEgMjEuMiwyMi44IDguNywxNS4xIi8+Cjxwb2x5Z29uIGZpbGw9IiMwMDdFRTUiIHBvaW50cz0iMjEuMiwyMi44IDMwLDMwLjEgNDIuNCwyMiAzMy44LDE1LjEiLz4KPHBvbHlnb24gZmlsbD0iIzAwN0VFNSIgcG9pbnRzPSI0Mi40LDguMSAzMCwwIDIxLjIsNy4zIDMzLjgsMTUuMSIvPgo8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjIxLjMsMjQuNCAxMi41LDMxLjcgOC44LDI5LjIgOC44LDMyIDIxLjMsMzkuNSAzMy44LDMyIDMzLjgsMjkuMiAzMCwzMS43Ii8+Cjwvc3ZnPgo=", }, }, } } func (d Dropbox) OAuthURL() string { url := "https://www.dropbox.com/oauth2/authorize?" url += "client_id=" + d.ClientId url += "&redirect_uri=https://" + d.Hostname + "/login" url += "&response_type=token" return url } func (d Dropbox) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) args := struct { Path string `json:"path"` Recursive bool `json:"recursive"` IncludeDeleted bool `json:"include_deleted"` IncludeMediaInfo bool `json:"include_media_info"` }{d.path(path), false, false, true} res, err := d.request("POST", "https://api.dropboxapi.com/2/files/list_folder", d.toReader(args), nil) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 400 { return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't get things in"+filepath.Base(path), res.StatusCode) } var r struct { Files []struct { Type string `json:".tag"` Name string `json:"name"` Time time.Time `json:"client_modified"` Size uint `json:"size"` } `json:"entries"` } decoder := json.NewDecoder(res.Body) decoder.Decode(&r) for _, obj := range r.Files { files = append(files, File{ FName: obj.Name, FType: func(p string) string { if p == "folder" { return "directory" } return "file" }(obj.Type), FTime: obj.Time.UnixNano() / 1000, FSize: int64(obj.Size), }) } return files, nil } func (d Dropbox) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (d Dropbox) Cat(path string) (io.ReadCloser, error) { res, err := d.request("POST", "https://content.dropboxapi.com/2/files/download", nil, func(req *http.Request) { arg := struct { Path string `json:"path"` }{d.path(path)} json, _ := io.ReadAll(d.toReader(arg)) req.Header.Set("Dropbox-API-Arg", string(json)) }) if err != nil { return nil, err } return res.Body, nil } func (d Dropbox) Mkdir(path string) error { args := struct { Path string `json:"path"` Autorename bool `json:"autorename"` }{d.path(path), false} res, err := d.request("POST", "https://api.dropboxapi.com/2/files/create_folder_v2", d.toReader(args), nil) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't create "+filepath.Base(path), res.StatusCode) } return nil } func (d Dropbox) Rm(path string) error { args := struct { Path string `json:"path"` }{d.path(path)} res, err := d.request("POST", "https://api.dropboxapi.com/2/files/delete_v2", d.toReader(args), nil) if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't remove "+filepath.Base(path), res.StatusCode) } res.Body.Close() return err } func (d Dropbox) Mv(from string, to string) error { args := struct { FromPath string `json:"from_path"` ToPath string `json:"to_path"` }{d.path(from), d.path(to)} res, err := d.request("POST", "https://api.dropboxapi.com/2/files/move_v2", d.toReader(args), nil) if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode) } res.Body.Close() return err } func (d Dropbox) Touch(path string) error { return d.Save(path, strings.NewReader("")) } func (d Dropbox) Save(path string, file io.Reader) error { res, err := d.request("POST", "https://content.dropboxapi.com/2/files/upload", file, func(req *http.Request) { arg := struct { Path string `json:"path"` AutoRename bool `json:"autorename"` Mode string `json:"mode"` }{d.path(path), false, "overwrite"} json, _ := io.ReadAll(d.toReader(arg)) req.Header.Set("Dropbox-API-Arg", string(json)) req.Header.Set("Content-Type", "application/octet-stream") }) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode) } return err } func (d Dropbox) request(method string, url string, body io.Reader, fn func(*http.Request)) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+d.Bearer) if fn == nil { req.Header.Set("Content-Type", "application/json") } else { fn(req) } if req.Body != nil { defer req.Body.Close() } return HTTPClient.Do(req) } func (d Dropbox) toReader(a interface{}) io.Reader { j, err := json.Marshal(a) if err != nil { return nil } return strings.NewReader(string(j)) } func (d Dropbox) path(path string) string { return regexp.MustCompile(`\/$`).ReplaceAllString(path, "") } ================================================ FILE: server/plugin/plg_backend_ftp/index.go ================================================ package plg_backend_ftp import ( "context" "crypto/tls" "fmt" . "github.com/mickael-kerjean/filestash/server/common" //"github.com/secsy/goftp" <- FTP issue with microsoft FTP "github.com/prasad83/goftp" "io" "os" "regexp" "strconv" "strings" "sync" "time" ) var FtpCache AppCache type Ftp struct { client *goftp.Client p map[string]string wg *sync.WaitGroup ctx context.Context } func init() { Backend.Register("ftp", Ftp{}) FtpCache = NewAppCache(2, 1) FtpCache.OnEvict(func(key string, value interface{}) { c := value.(*Ftp) if c == nil { Log.Warning("plg_backend_ftp::ftp is nil on close") return } else if c.wg == nil { Log.Warning("plg_backend_ftp::wg is nil on close") c.Close() return } c.wg.Wait() Log.Debug("plg_backend_ftp::vacuum") c.Close() }) } func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) { if c := FtpCache.Get(params); c != nil { d := c.(*Ftp) if d == nil { Log.Warning("plg_backend_ftp::ftp is nil on get") return nil, ErrInternal } else if d.wg == nil { Log.Warning("plg_backend_ftp::wg is nil on get") return nil, ErrInternal } d.wg.Add(1) d.ctx = app.Context go func() { <-d.ctx.Done() d.wg.Done() }() return d, nil } if params["hostname"] == "" { params["hostname"] = "localhost" } if params["port"] == "" { params["port"] = "21" } if params["username"] == "" { params["acl"] = "r" params["username"] = "anonymous" } if params["username"] == "anonymous" && params["password"] == "" { params["password"] = "anonymous" } conn := 5 if params["conn"] != "" { if i, err := strconv.Atoi(params["conn"]); err == nil && i > 0 { conn = i } } connectStrategy := []string{"ftp", "ftps::implicit", "ftps::explicit"} if strings.HasPrefix(params["hostname"], "ftp://") { connectStrategy = []string{"ftp"} params["hostname"] = strings.TrimPrefix(params["hostname"], "ftp://") } else if strings.HasPrefix(params["hostname"], "ftps://") { connectStrategy = []string{"ftps::implicit", "ftps::explicit"} params["hostname"] = strings.TrimPrefix(params["hostname"], "ftps://") } var backend *Ftp = nil hostname := fmt.Sprintf("%s:%s", params["hostname"], params["port"]) cfgBuilder := func(timeout time.Duration, withTLS bool) goftp.Config { cfg := goftp.Config{ User: params["username"], Password: params["password"], ConnectionsPerHost: conn, Timeout: timeout * time.Second, } cfg.Timeout = timeout if withTLS { cfg.TLSConfig = &tls.Config{ InsecureSkipVerify: true, ClientSessionCache: tls.NewLRUClientSessionCache(0), SessionTicketsDisabled: false, ServerName: hostname, } } return cfg } for i := 0; i < len(connectStrategy); i++ { if connectStrategy[i] == "ftp" { client, err := goftp.DialConfig(cfgBuilder(5*time.Second, false), hostname) if err != nil { Log.Debug("plg_backend_ftp::ftp dial %s", err.Error()) continue } else if _, err := client.ReadDir("/"); err != nil { client.Close() Log.Debug("plg_backend_ftp::ftp verify %s", err.Error()) continue } client.Close() client, err = goftp.DialConfig(cfgBuilder(60*time.Second, false), hostname) if err != nil { continue } params["mode"] = connectStrategy[i] backend = &Ftp{client, params, nil, app.Context} break } else if connectStrategy[i] == "ftps::implicit" { cfg := cfgBuilder(60*time.Second, true) cfg.TLSMode = goftp.TLSImplicit client, err := goftp.DialConfig(cfg, hostname) if err != nil { Log.Debug("plg_backend_ftp::ftps::implicit dial %s", err.Error()) continue } else if _, err := client.ReadDir("/"); err != nil { Log.Debug("plg_backend_ftp::ftps::implicit verify %s", err.Error()) client.Close() continue } params["mode"] = connectStrategy[i] backend = &Ftp{client, params, nil, app.Context} break } else if connectStrategy[i] == "ftps::explicit" { cfg := cfgBuilder(5*time.Second, true) cfg.TLSMode = goftp.TLSExplicit client, err := goftp.DialConfig(cfg, hostname) if err != nil { Log.Debug("plg_backend_ftp::ftps::explicit dial '%s'", err.Error()) continue } else if _, err := client.ReadDir("/"); err != nil { Log.Debug("plg_backend_ftp::ftps::explicit verify %s", err.Error()) client.Close() continue } client.Close() cfg = cfgBuilder(60*time.Second, true) cfg.TLSMode = goftp.TLSExplicit client, err = goftp.DialConfig(cfg, hostname) if err != nil { continue } params["mode"] = connectStrategy[i] backend = &Ftp{client, params, nil, app.Context} break } } if backend == nil { return nil, ErrAuthenticationFailed } backend.wg = new(sync.WaitGroup) backend.wg.Add(1) backend.ctx = app.Context go func() { <-backend.ctx.Done() backend.wg.Done() }() FtpCache.Set(params, backend) return backend, nil } func (f Ftp) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "ftp", }, { Name: "hostname", Type: "text", Placeholder: "Hostname*", }, { Name: "username", Type: "text", Placeholder: "Username", }, { Name: "password", Type: "password", Placeholder: "Password", }, { Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"ftp_path", "ftp_port", "ftp_conn"}, }, { Id: "ftp_path", Name: "path", Type: "text", Placeholder: "Path", }, { Id: "ftp_port", Name: "port", Type: "number", Placeholder: "Port", }, { Id: "ftp_conn", Name: "conn", Type: "number", Placeholder: "Number of connections", }, }, } } func (f Ftp) Meta(path string) Metadata { if f.p["acl"] == "r" { return Metadata{ CanCreateFile: NewBool(false), CanCreateDirectory: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), CanDelete: NewBool(false), } } return Metadata{} } func (f Ftp) Home() (home string, err error) { f.Execute(func(client *goftp.Client) error { home, err = f.client.Getwd() return err }) return home, err } func (f Ftp) Ls(path string) (files []os.FileInfo, err error) { f.Execute(func(client *goftp.Client) error { files, err = client.ReadDir(path) return err }) return files, err } func (f Ftp) Cat(path string) (reader io.ReadCloser, err error) { f.Execute(func(client *goftp.Client) error { if _, err = client.Stat(path); err != nil { return err } pr, pw := io.Pipe() go func() { err = client.Retrieve(path, pw) if err != nil { pr.CloseWithError(NewError("Problem", 409)) } pw.Close() }() reader = pr return nil }) return reader, err } func (f Ftp) Stat(path string) (finfo os.FileInfo, err error) { f.Execute(func(client *goftp.Client) error { finfo, err = client.Stat(path) return err }) if err == nil { return finfo, err } if ftpErr, ok := err.(goftp.Error); ok && ftpErr.Code() == 550 { return nil, ErrNotFound } return nil, ErrNotImplemented } func (f Ftp) Mkdir(path string) (err error) { f.Execute(func(client *goftp.Client) error { _, err = client.Mkdir(path) return err }) return err } func (f Ftp) Rm(path string) (err error) { isDirectory := func(p string) bool { return regexp.MustCompile(`\/$`).MatchString(p) } transformError := func(e error) error { // For some reasons bsftp is struggling with the library // sometimes returning a 200 OK if e == nil { return nil } if obj, ok := e.(goftp.Error); ok { if obj.Code() < 300 && obj.Code() > 0 { return nil } } return e } var recursiveDelete func(client *goftp.Client, _path string) error recursiveDelete = func(client *goftp.Client, _path string) error { if isDirectory(_path) { entries, err := client.ReadDir(_path) if transformError(err) != nil { return err } for _, entry := range entries { if entry.IsDir() { err = recursiveDelete(client, _path+entry.Name()+"/") if transformError(err) != nil { return err } } else { err = recursiveDelete(client, _path+entry.Name()) if transformError(err) != nil { return err } } } err = client.Rmdir(_path) return transformError(err) } err = client.Delete(_path) return transformError(err) } f.Execute(func(client *goftp.Client) error { err = recursiveDelete(client, path) return err }) return err } func (f Ftp) Mv(from string, to string) (err error) { f.Execute(func(client *goftp.Client) error { err = client.Rename(from, to) return err }) return err } func (f Ftp) Touch(path string) (err error) { f.Execute(func(client *goftp.Client) error { err = client.Store(path, strings.NewReader("")) return err }) return err } func (f Ftp) Save(path string, file io.Reader) (err error) { f.Execute(func(client *goftp.Client) error { err = client.Store(path, file) return err }) return err } func (f Ftp) Close() error { return f.client.Close() } func (f Ftp) Execute(fn func(*goftp.Client) error) { err := fn(f.client) if ftpErr, ok := err.(goftp.Error); ok { code := ftpErr.Code() if code == 421 || (code == 0 && err.Error() == "error reading response: EOF") { f.Close() FtpCache.Set(f.p, nil) if b, err := f.Init(f.p, &App{Context: f.ctx}); err == nil { fn(b.(*Ftp).client) } } } } ================================================ FILE: server/plugin/plg_backend_ftp_only/index.go ================================================ package plg_backend_ftp_only import ( "fmt" "io" "os" "regexp" "strconv" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" //"github.com/secsy/goftp" <- FTP issue with microsoft FTP "github.com/prasad83/goftp" ) var FtpCache AppCache type Ftp struct { client *goftp.Client } func init() { Backend.Register("ftp", Ftp{}) FtpCache = NewAppCache(2, 1) FtpCache.OnEvict(func(key string, value interface{}) { c := value.(*Ftp) c.Close() }) } func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) { if c := FtpCache.Get(params); c != nil { d := c.(*Ftp) return d, nil } if params["hostname"] == "" { params["hostname"] = "localhost" } if params["port"] == "" { params["port"] = "21" } if params["username"] == "" { params["username"] = "anonymous" } if params["username"] == "anonymous" && params["password"] == "" { params["password"] = "anonymous" } conn := 5 if params["conn"] != "" { if i, err := strconv.Atoi(params["conn"]); err == nil && i > 0 { conn = i } } configWithoutTLS := goftp.Config{ User: params["username"], Password: params["password"], ConnectionsPerHost: conn, Timeout: 10 * time.Second, } var backend *Ftp = nil client, err := goftp.DialConfig(configWithoutTLS, fmt.Sprintf("%s:%s", strings.TrimPrefix(params["hostname"], "ftp://"), params["port"])) if err != nil { return backend, err } if _, err := client.ReadDir("/"); err != nil { client.Close() return backend, ErrAuthenticationFailed } backend = &Ftp{client} FtpCache.Set(params, backend) return backend, nil } func (f Ftp) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "ftp", }, FormElement{ Name: "hostname", Type: "text", Placeholder: "Hostname*", }, FormElement{ Name: "username", Type: "text", Placeholder: "Username", }, FormElement{ Name: "password", Type: "password", Placeholder: "Password", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"ftp_path", "ftp_port", "ftp_conn", "ftp_disable_ftps"}, }, FormElement{ Id: "ftp_path", Name: "path", Type: "text", Placeholder: "Path", }, FormElement{ Id: "ftp_port", Name: "port", Type: "number", Placeholder: "Port", }, FormElement{ Id: "ftp_conn", Name: "conn", Type: "number", Placeholder: "Number of connections", }, FormElement{ Id: "ftp_conn", Name: "conn", Type: "number", Placeholder: "Number of connections", }, FormElement{ Id: "ftp_disable_ftps", Name: "Disable FTPS", Type: "select", Opts: []string{"DEBUG", "INFO", "WARNING", "ERROR"}, }, }, } } func (f Ftp) Home() (string, error) { return f.client.Getwd() } func (f Ftp) Ls(path string) ([]os.FileInfo, error) { return f.client.ReadDir(path) } func (f Ftp) Cat(path string) (io.ReadCloser, error) { pr, pw := io.Pipe() go func() { // TODO: prevent closing if err := f.client.Retrieve(path, pw); err != nil { pr.CloseWithError(NewError("Problem", 409)) } pw.Close() }() return pr, nil } func (f Ftp) Mkdir(path string) error { _, err := f.client.Mkdir(path) return err } func (f Ftp) Rm(path string) error { isDirectory := func(p string) bool { return regexp.MustCompile(`\/$`).MatchString(p) } transformError := func(e error) error { // For some reasons bsftp is struggling with the library // sometimes returning a 200 OK if e == nil { return nil } if obj, ok := e.(goftp.Error); ok { if obj.Code() < 300 && obj.Code() > 0 { return nil } } return e } if isDirectory(path) { entries, err := f.Ls(path) if transformError(err) != nil { return err } for _, entry := range entries { if entry.IsDir() { err = f.Rm(path + entry.Name() + "/") if transformError(err) != nil { return err } } else { err = f.Rm(path + entry.Name()) if transformError(err) != nil { return err } } } err = f.client.Rmdir(path) return transformError(err) } err := f.client.Delete(path) return transformError(err) } func (f Ftp) Mv(from string, to string) error { return f.client.Rename(from, to) } func (f Ftp) Touch(path string) error { return f.client.Store(path, strings.NewReader("")) } func (f Ftp) Save(path string, file io.Reader) error { // TODO: prevent closing return f.client.Store(path, file) } func (f Ftp) Close() error { return f.client.Close() } ================================================ FILE: server/plugin/plg_backend_gdrive/index.go ================================================ package plg_backend_gdrive import ( . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/drive/v3" "io" "os" "path/filepath" "regexp" "strconv" "strings" "time" ) const gdriveFolderMarker = "application/vnd.google-apps.folder" type GDrive struct { Client *drive.Service Config *oauth2.Config } func init() { Backend.Register("gdrive", GDrive{}) } func (g GDrive) Init(params map[string]string, app *App) (IBackend, error) { backend := GDrive{} config := &oauth2.Config{ Endpoint: google.Endpoint, ClientID: Config.Get("auth.gdrive.client_id").Default(os.Getenv("GDRIVE_CLIENT_ID")).String(), ClientSecret: Config.Get("auth.gdrive.client_secret").Default(os.Getenv("GDRIVE_CLIENT_SECRET")).String(), RedirectURL: "https://" + Config.Get("general.host").String() + "/login", Scopes: []string{"https://www.googleapis.com/auth/drive"}, } if config.ClientID == "" { return backend, NewError("Missing Client ID: Contact your admin", 502) } else if config.ClientSecret == "" { return backend, NewError("Missing Client Secret: Contact your admin", 502) } else if config.RedirectURL == "/login" { return backend, NewError("Missing Hostname: Contact your admin", 502) } token := &oauth2.Token{ AccessToken: params["token"], RefreshToken: params["refresh"], Expiry: func(t string) time.Time { expiry, err := strconv.ParseInt(t, 10, 64) if err != nil { return time.Now() } return time.Unix(expiry, 0) }(params["expiry"]), TokenType: "bearer", } client := config.Client(context.Background(), token) srv, err := drive.New(client) if err != nil { return nil, NewError(err.Error(), 400) } backend.Client = srv backend.Config = config return backend, nil } func (g GDrive) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "gdrive", }, FormElement{ ReadOnly: true, Name: "oauth2", Type: "text", Value: "/api/session/auth/gdrive", }, FormElement{ ReadOnly: true, Name: "image", Type: "image", Value: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTM5IDEyMC40IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICA8cGF0aCBkPSJtMjQuMiAxMjAuNC0yNC4yLTQxLjkgNDUuMy03OC41IDI0LjIgNDEuOXoiIGZpbGw9IiMwZGE5NjAiLz4KICA8cGF0aCBkPSJtNTguOSA2MC4yIDEwLjYtMTguMy0yNC4yLTQxLjl6IiBmaWxsPSIjMGRhOTYwIi8+CiAgPHBhdGggZD0ibTI0LjIgMTIwLjQgMjQuMi00MS45aDkwLjZsLTI0LjIgNDEuOXoiIGZpbGw9IiMyZDZmZGQiLz4KICA8cGF0aCBkPSJtNjkuNSA3OC41aC0yMS4xbDEwLjUtMTguMy0zNC43IDYwLjJ6IiBmaWxsPSIjMmQ2ZmRkIi8+ICAKICA8cGF0aCBkPSJtMTM5IDc4LjVoLTQ4LjRsLTQ1LjMtNzguNWg0OC40eiIgZmlsbD0iI2ZmZDI0ZCIvPgogIDxwYXRoIGQ9Im05MC42IDc4LjVoNDguNGwtNTguOS0xOC4zeiIgZmlsbD0iI2ZmZDI0ZCIvPgo8L3N2Zz4K", }, }, } } func (g GDrive) OAuthURL() string { return g.Config.AuthCodeURL("gdrive", oauth2.AccessTypeOnline) } func (g GDrive) OAuthToken(ctx *map[string]interface{}) error { code := "" if str, ok := (*ctx)["code"].(string); ok { code = str } token, err := g.Config.Exchange(oauth2.NoContext, code) if err != nil { return err } (*ctx)["token"] = token.AccessToken (*ctx)["refresh"] = token.RefreshToken (*ctx)["expiry"] = strconv.FormatInt(token.Expiry.UnixNano()/1000, 10) delete(*ctx, "code") return nil } func (g GDrive) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) file, err := g.infoPath(path) if err != nil { return nil, err } res, err := g.Client.Files.List().Q("'" + file.id + "' in parents AND trashed = false").Fields("nextPageToken, files(name, size, modifiedTime, mimeType)").PageSize(500).Do() if err != nil { return nil, NewError(err.Error(), 404) } for _, obj := range res.Files { files = append(files, File{ FName: obj.Name, FType: func(mType string) string { if mType == gdriveFolderMarker { return "directory" } return "file" }(obj.MimeType), FTime: func(t string) int64 { a, err := time.Parse(time.RFC3339, t) if err != nil { return 0 } return a.UnixNano() / 1000 }(obj.ModifiedTime), FSize: obj.Size, }) } return files, nil } func (g GDrive) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (g GDrive) Cat(path string) (io.ReadCloser, error) { file, err := g.infoPath(path) if err != nil { return nil, err } if strings.HasPrefix(file.mType, "application/vnd.google-apps") { mType := "text/plain" if file.mType == "application/vnd.google-apps.spreadsheet" { mType = "text/csv" } data, err := g.Client.Files.Export(file.id, mType).Download() if err != nil { return nil, err } return data.Body, nil } data, err := g.Client.Files.Get(file.id).Download() if err != nil { return nil, err } return data.Body, nil } func (g GDrive) Mkdir(path string) error { parent, err := g.infoPath(getParentPath(path)) if err != nil { return NewError("Directory already exists", 409) } _, err = g.Client.Files.Create(&drive.File{ Name: filepath.Base(path), Parents: []string{parent.id}, MimeType: gdriveFolderMarker, }).Do() return err } func (g GDrive) Rm(path string) error { file, err := g.infoPath(path) if err != nil { return err } if err = g.Client.Files.Delete(file.id).Do(); err != nil { return err } return nil } func (g GDrive) Mv(from string, to string) error { ffile, err := g.infoPath(from) if err != nil { return err } tfile, err := g.infoPath(getParentPath(to)) if err != nil { return err } _, err = g.Client.Files.Update(ffile.id, &drive.File{ Name: filepath.Base(to), }).RemoveParents(ffile.parent).AddParents(tfile.id).Do() return err } func (g GDrive) Touch(path string) error { file, err := g.infoPath(getParentPath(path)) if err != nil { return NewError("Base folder not found", 404) } _, err = g.Client.Files.Create(&drive.File{ Name: filepath.Base(path), Parents: []string{file.id}, }).Media(strings.NewReader("")).Do() return err } func (g GDrive) Save(path string, reader io.Reader) error { if file, err := g.infoPath(path); err == nil { _, err = g.Client.Files.Update(file.id, &drive.File{}).Media(reader).Do() return err } file, err := g.infoPath(getParentPath(path)) if err != nil { return err } _, err = g.Client.Files.Create(&drive.File{ Name: filepath.Base(path), Parents: []string{file.id}, }).Media(reader).Do() return err } func (g GDrive) infoPath(p string) (*GDriveMarker, error) { FindSolutions := func(level int, folder string) ([]GDriveMarker, error) { res, err := g.Client.Files.List().Q("name = '" + folder + "' AND trashed = false").Fields("files(parents, id, name, mimeType)").PageSize(500).Do() if err != nil { return nil, err } solutions := make([]GDriveMarker, 0) for _, file := range res.Files { if len(file.Parents) == 0 { continue } solutions = append(solutions, GDriveMarker{ file.Id, file.Parents[0], file.Name, level, file.MimeType, }) } return solutions, nil } FindRoot := func(level int) ([]GDriveMarker, error) { root := make([]GDriveMarker, 0) res, err := g.Client.Files.List().Q("'root' in parents").Fields("files(parents, id, name, mimeType)").PageSize(1).Do() if err != nil { return nil, err } if len(res.Files) == 0 || len(res.Files[0].Parents) == 0 { root = append(root, GDriveMarker{ "root", "root", "root", level, gdriveFolderMarker, }) return root, nil } root = append(root, GDriveMarker{ res.Files[0].Parents[0], "root", "root", level, gdriveFolderMarker, }) return root, nil } MergeSolutions := func(solutions_bag []GDriveMarker, solutions_new []GDriveMarker) []GDriveMarker { if len(solutions_bag) == 0 { return solutions_new } solutions := make([]GDriveMarker, 0) for _, new := range solutions_new { for _, old := range solutions_bag { if new.id == old.parent && new.level+1 == old.level { old.level = new.level old.parent = new.id solutions = append(solutions, old) } } } return solutions } var FindId func(folders []string, solutions_bag []GDriveMarker) (*GDriveMarker, error) FindId = func(folders []string, solutions_bag []GDriveMarker) (*GDriveMarker, error) { var solutions_new []GDriveMarker var err error if len(folders) == 0 { solutions_new, err = FindRoot(0) } else { solutions_new, err = FindSolutions(len(folders), folders[len(folders)-1]) } if err != nil { return nil, NewError("Can't get data", 500) } solutions_bag = MergeSolutions(solutions_bag, solutions_new) if len(solutions_bag) == 0 { return nil, NewError("Doesn't exist", 404) } else if len(solutions_bag) == 1 { return &solutions_bag[0], nil } else { return FindId(folders[:len(folders)-1], solutions_bag) } } path := make([]string, 0) for _, chunk := range strings.Split(p, "/") { if chunk == "" { continue } path = append(path, chunk) } if len(path) == 0 { return &GDriveMarker{ "root", "", "root", 0, gdriveFolderMarker, }, nil } return FindId(path, make([]GDriveMarker, 0)) } type GDriveMarker struct { id string parent string name string level int mType string } func getParentPath(path string) string { re := regexp.MustCompile("/$") path = re.ReplaceAllString(path, "") return filepath.Dir(path) + "/" } ================================================ FILE: server/plugin/plg_backend_git/index.go ================================================ package plg_backend_git import ( "fmt" "io" "os" "path/filepath" "strings" "time" "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" "github.com/go-git/go-git/v6/plumbing/transport" "github.com/go-git/go-git/v6/plumbing/transport/http" sshgit "github.com/go-git/go-git/v6/plumbing/transport/ssh" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/ssh" ) var git_cache AppCache type Git struct { git *GitLib } func init() { Backend.Register("git", Git{}) git_cache = NewAppCache() git_cache.OnEvict(func(key string, value interface{}) { g := value.(*Git) g.Close() }) } type GitParams struct { repo string username string password string passphrase string commit string branch string authorName string authorEmail string committerName string committerEmail string basePath string } func (git Git) Init(params map[string]string, app *App) (IBackend, error) { if obj := git_cache.Get(params); obj != nil { return obj.(*Git), nil } g := &Git{ git: &GitLib{ params: &GitParams{ params["repo"], params["username"], params["password"], params["passphrase"], params["commit"], params["branch"], params["authorName"], params["authorEmail"], params["committerName"], params["committerEmail"], "", }, }, } p := g.git.params if p.branch == "" { p.branch = "master" } if p.commit == "" { p.commit = "{action} ({filename}): {path}" } if p.authorName == "" { p.authorName = APPNAME } if p.authorEmail == "" && !IsWhiteLabel() { p.authorEmail = "https://filestash.app" } if p.committerName == "" { p.committerName = APPNAME } if p.committerEmail == "" && !IsWhiteLabel() { p.committerEmail = "https://filestash.app" } hash := GenerateID(params) p.basePath = GetAbsolutePath( TMP_PATH, "git_"+hash, ) + "/" repo, err := g.git.open(p, p.basePath) g.git.repo = repo if err != nil { return g, err } git_cache.Set(params, g) return g, nil } func (g Git) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Value: "git", Type: "hidden", }, { Name: "repo", Type: "text", Placeholder: "Repository*", }, { Name: "username", Type: "text", Placeholder: "Username", }, { Name: "password", Type: "long_password", Placeholder: "Password", }, { Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{ "git_path", "git_passphrase", "git_commit", "git_branch", "git_author_email", "git_author_name", "git_committer_email", "git_committer_name", }, }, { Id: "git_path", Name: "path", Type: "text", Placeholder: "Path", }, { Id: "git_passphrase", Name: "passphrase", Type: "text", Placeholder: "Passphrase", }, { Id: "git_commit", Name: "commit", Type: "text", Placeholder: "Commit Format: default to \"{action}({filename}): {path}\"", }, { Id: "git_branch", Name: "branch", Type: "text", Placeholder: "Branch: default to \"master\"", }, { Id: "git_author_email", Name: "author_email", Type: "text", Placeholder: "Author email", }, { Id: "git_author_name", Name: "author_name", Type: "text", Placeholder: "Author name", }, { Id: "git_committer_email", Name: "committer_email", Type: "text", Placeholder: "Committer email", }, { Id: "git_committer_name", Name: "committer_name", Type: "text", Placeholder: "Committer name", }, }, } } func (g Git) Ls(path string) ([]os.FileInfo, error) { g.git.refresh() p, err := g.path(path) if err != nil { return nil, NewError(err.Error(), 403) } f, err := SafeOsOpenFile(p, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } files, err := f.Readdir(-1) if err != nil { f.Close() return nil, err } return files, f.Close() } func (g Git) Stat(path string) (os.FileInfo, error) { g.git.refresh() p, err := g.path(path) if err != nil { return nil, NewError(err.Error(), 403) } f, err := SafeOsOpenFile(p, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } finfo, err := f.Stat() if err != nil { f.Close() return nil, err } return finfo, f.Close() } func (g Git) Cat(path string) (io.ReadCloser, error) { p, err := g.path(path) if err != nil { return nil, NewError(err.Error(), 403) } return SafeOsOpenFile(p, os.O_RDONLY, os.ModePerm) } func (g Git) Mkdir(path string) error { p, err := g.path(path) if err != nil { return NewError(err.Error(), 403) } return SafeOsMkdir(p, os.ModePerm) } func (g Git) Rm(path string) error { p, err := g.path(path) if err != nil { return NewError(err.Error(), 403) } if err = SafeOsRemoveAll(p); err != nil { return NewError(err.Error(), 403) } message := g.git.message("delete", path) if err = g.git.save(message); err != nil { return NewError(err.Error(), 403) } return nil } func (g Git) Mv(from string, to string) error { fpath, err := g.path(from) if err != nil { return NewError(err.Error(), 403) } tpath, err := g.path(to) if err != nil { return NewError(err.Error(), 403) } if err = SafeOsRename(fpath, tpath); err != nil { return NewError(err.Error(), 403) } message := g.git.message("move", from) if err = g.git.save(message); err != nil { return NewError(err.Error(), 403) } return nil } func (g Git) Touch(path string) error { p, err := g.path(path) if err != nil { return NewError(err.Error(), 403) } file, err := SafeOsOpenFile(p, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return NewError(err.Error(), 403) } file.Close() message := g.git.message("create", path) if err = g.git.save(message); err != nil { return NewError(err.Error(), 403) } return nil } func (g Git) Save(path string, file io.Reader) error { p, err := g.path(path) if err != nil { return NewError(err.Error(), 403) } fo, err := SafeOsOpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { return err } io.Copy(fo, file) fo.Close() message := g.git.message("save", path) if err = g.git.save(message); err != nil { return NewError(err.Error(), 403) } return nil } func (g Git) Close() error { return os.RemoveAll(g.git.params.basePath) } func (g Git) path(path string) (string, error) { if path == "" { return "", ErrNotValid } basePath := filepath.Join(g.git.params.basePath, path) if string(path[len(path)-1]) == "/" { basePath += "/" } if strings.HasPrefix(basePath, g.git.params.basePath) == false { return "", ErrNotFound } return basePath, nil } type GitLib struct { repo *git.Repository params *GitParams } func (g *GitLib) open(params *GitParams, path string) (*git.Repository, error) { g.params = params if _, err := os.Stat(g.params.basePath); os.IsNotExist(err) { auth, err := g.auth() if err != nil { return nil, err } g, err := git.PlainClone(path, &git.CloneOptions{ URL: g.params.repo, Depth: 1, ReferenceName: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", g.params.branch)), SingleBranch: true, Auth: auth, }) if err == transport.ErrEmptyRemoteRepository { return g, nil } return g, err } return git.PlainOpen(g.params.basePath) } func (g *GitLib) save(message string) error { w, err := g.repo.Worktree() if err != nil { return NewError(err.Error(), 500) } _, err = w.Add(".") if err != nil { return NewError(err.Error(), 500) } _, err = w.Commit(message, &git.CommitOptions{ All: true, Author: &object.Signature{ Name: g.params.authorName, Email: g.params.authorEmail, When: time.Now(), }, Committer: &object.Signature{ Name: g.params.committerName, Email: g.params.committerEmail, When: time.Now(), }, }) if err != nil { return err } auth, err := g.auth() if err != nil { return err } return g.repo.Push(&git.PushOptions{ Auth: auth, }) } func (g *GitLib) refresh() error { w, err := g.repo.Worktree() if err != nil { return err } return w.Pull(&git.PullOptions{RemoteName: "origin"}) } func (g *GitLib) auth() (transport.AuthMethod, error) { if strings.HasPrefix(g.params.repo, "http") { return &http.BasicAuth{ Username: g.params.username, Password: g.params.password, }, nil } isPrivateKey := func(pass string) bool { if len(pass) > 1000 && strings.HasPrefix(pass, "-----") { return true } return false } if isPrivateKey(g.params.password) { signer, err := ssh.ParsePrivateKeyWithPassphrase([]byte(g.params.password), []byte(g.params.passphrase)) if err != nil { return nil, err } return &sshgit.PublicKeys{ User: "git", Signer: signer, HostKeyCallbackHelper: sshgit.HostKeyCallbackHelper{ HostKeyCallback: ssh.InsecureIgnoreHostKey(), }, }, nil } return &sshgit.Password{ User: g.params.username, Password: g.params.password, HostKeyCallbackHelper: sshgit.HostKeyCallbackHelper{ HostKeyCallback: ssh.InsecureIgnoreHostKey(), }, }, nil } func (g *GitLib) message(action string, path string) string { message := strings.Replace(g.params.commit, "{action}", "save", -1) message = strings.Replace(message, "{filename}", filepath.Base(path), -1) message = strings.Replace(message, "{path}", strings.Replace(path, g.params.basePath, "", -1), -1) return message } ================================================ FILE: server/plugin/plg_backend_ipfs/README.md ================================================ ``` # run a IPFS server node: docker run --rm --name ipfs_host -p 4001:4001 -p 4001:4001/udp -p 127.0.0.1:8080:8080 -p 127.0.0.1:5001:5001 ipfs/kubo:v0.39.0 ``` api doc: - https://github.com/ipfs/kubo/blob/c1e1cfebbbc946f957e39345b9224a05704f701d/client/rpc/api.go#L60C6-L69C1 - https://github.com/ipfs/kubo/tree/master/client/rpc - https://github.com/ipfs/go-ipfs-api ================================================ FILE: server/plugin/plg_backend_ipfs/index.go ================================================ package plg_backend_ipfs import ( "encoding/json" "io" "mime/multipart" "net/http" "net/url" "os" "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Backend.Register("ipfs", Ipfs{}) } type Ipfs struct { url string } func (this Ipfs) Init(params map[string]string, app *App) (IBackend, error) { url := strings.TrimSuffix(params["url"], "/") if url == "" { url = "http://127.0.0.1:5001" } this.url = url if _, err := this.Stat("/"); err != nil { return nil, err } return &this, nil } func (this Ipfs) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "ipfs", }, { Name: "url", Type: "text", Placeholder: "Address (default: http://127.0.0.1:5001)", }, }, } } func (this Ipfs) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) file, err := this.Stat(path) if err != nil || !file.IsDir() { return nil, ErrNotValid } resp := struct { Entries []struct { Name string Type int Size int64 Hash string } }{} if err := this.query("/api/v0/files/ls?long=true&arg="+url.QueryEscape(path), &resp); err != nil { return nil, ErrNotValid } for _, entry := range resp.Entries { t := "" if entry.Type == 1 { t = "directory" } else if entry.Type == 0 { t = "file" } if t == "" { Log.Debug("plg_backend_ipfs::ls msg=unknown_entry_type entry=%v", entry) continue } files = append(files, File{ FName: entry.Name, FSize: entry.Size, FType: t, }) } return files, nil } func (this Ipfs) Stat(path string) (os.FileInfo, error) { out := struct { Hash string Type string Size int64 CumulativeSize int }{} if err := this.query("/api/v0/files/stat?arg="+url.QueryEscape(path), &out); err != nil { return nil, err } return File{ FName: filepath.Base(path), FSize: out.Size, FType: out.Type, }, nil } func (this Ipfs) Cat(path string) (io.ReadCloser, error) { req, err := http.NewRequest(http.MethodPost, this.url+"/api/v0/files/read?arg="+url.QueryEscape(path), nil) if err != nil { return nil, err } resp, err := HTTPClient.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, ErrNotValid } return resp.Body, nil } func (this Ipfs) Mkdir(path string) error { return this.query("/api/v0/files/mkdir?arg="+url.QueryEscape(path), nil) } func (this Ipfs) Rm(path string) error { return this.query("/api/v0/files/rm?recursive=true&arg="+url.QueryEscape(path), nil) } func (this Ipfs) Mv(from, to string) error { from = strings.TrimSuffix(from, "/") to = strings.TrimSuffix(to, "/") return this.query("/api/v0/files/mv?arg="+url.QueryEscape(from)+"&arg="+url.QueryEscape(to), nil) } func (this Ipfs) Save(path string, content io.Reader) error { pipeReader, pipeWriter := io.Pipe() writer := multipart.NewWriter(pipeWriter) go func() { defer pipeWriter.Close() defer writer.Close() part, err := writer.CreateFormFile("data", "") if err != nil { pipeWriter.CloseWithError(err) return } if _, err := io.Copy(part, content); err != nil { pipeWriter.CloseWithError(err) return } }() req, err := http.NewRequest( http.MethodPost, this.url+"/api/v0/files/write?create=true&truncate=true&arg="+url.QueryEscape(path), pipeReader, ) if err != nil { return err } req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := HTTPClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return ErrNotValid } return nil } func (this Ipfs) Touch(path string) error { if _, err := this.Stat(path); err == nil { return nil } return this.Save(path, strings.NewReader("")) } func (this Ipfs) query(cmd string, response any) error { req, err := http.NewRequest(http.MethodPost, this.url+cmd, nil) if err != nil { return err } req.Header.Set("Accept", "application/json") resp, err := HTTPClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return ErrNotValid } if response != nil { return json.NewDecoder(resp.Body).Decode(&response) } return nil } ================================================ FILE: server/plugin/plg_backend_ldap/index.go ================================================ package plg_backend_ldap /* * Introduction * ============ * To get a sample of what this backend can do: * - example.com: http://127.0.0.1:8334/login#type=ldap&hostname=ldap://ldap.forumsys.com&bind_dn=uid%3Dtesla,dc%3Dexample,dc%3Dcom&bind_password=password&base_dn=dc%3Dexample,dc%3Dcom * - freeipa: http://127.0.0.1:8334/login#type=ldap&hostname=ldap://ipa.demo1.freeipa.org&bind_dn=uid%3Dadmin,cn%3Dusers,cn%3Daccounts,dc%3Ddemo1,dc%3Dfreeipa,dc%3Dorg&bind_password=Secret123&base_dn=dc%3Ddemo1,dc%3Dfreeipa,dc%3Dorg */ import ( "encoding/json" "fmt" "io" "os" "path/filepath" "sort" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/go-ldap/ldap/v3" ) var LDAPCache AppCache func init() { Backend.Register("ldap", LDAP{}) LDAPCache = NewAppCache(2, 1) LDAPCache.OnEvict(func(key string, value interface{}) { c := value.(*LDAP) c.dial.Close() }) } type LDAP struct { dial *ldap.Conn baseDN string } func (this LDAP) Init(params map[string]string, app *App) (IBackend, error) { if obj := LDAPCache.Get(params); obj != nil { return obj.(*LDAP), nil } dialURL := func() string { if params["port"] == "" { // default port will be set by the LDAP library return params["hostname"] } return fmt.Sprintf("%s:%s", params["hostname"], params["port"]) }() l, err := ldap.DialURL(dialURL) if err != nil { return nil, err } if err = l.Bind(params["bind_dn"], params["bind_password"]); err != nil { return nil, err } b := &LDAP{baseDN: params["base_dn"], dial: l} LDAPCache.Set(params, b) return b, nil } func (this LDAP) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "ldap", }, FormElement{ Name: "hostname", Type: "text", Placeholder: "Hostname", }, FormElement{ Name: "bind_dn", Type: "text", Placeholder: "bind DN", }, FormElement{ Name: "bind_password", Type: "password", Placeholder: "Bind DN password", }, FormElement{ Name: "base_dn", Type: "text", Placeholder: "Base DN", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"ldap_path", "ldap_port"}, }, FormElement{ Id: "ldap_path", Name: "path", Type: "text", Placeholder: "Path", }, FormElement{ Id: "ldap_port", Name: "port", Type: "number", Placeholder: "Port", }, }, } } func (this LDAP) Ls(path string) ([]os.FileInfo, error) { baseDN := this.pathToBase(path) files := make([]os.FileInfo, 0) // explore the current folder sr, err := this.dial.Search(ldap.NewSearchRequest( baseDN, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{"objectClass"}, nil, )) if err != nil { return files, err } for i := 0; i < len(sr.Entries); i++ { entry := sr.Entries[i] // filename as will appear in the UI: filename := strings.TrimSuffix(entry.DN, ","+baseDN) // data type as will appear in the UI t := "file" if len(entry.Attributes) != 1 { continue } objectClasses := entry.Attributes[0].Values for j := 0; j < len(objectClasses); j++ { if s := Schema[objectClasses[j]]; s != nil { if s.IsContainer { t = "directory" break } } } if t == "file" { filename += ".form" } files = append(files, File{ FName: filename, FType: t, FTime: 1497276000000, FSize: -1, }) } return files, nil } func (this LDAP) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this LDAP) Cat(path string) (io.ReadCloser, error) { /////////////////////////////////////////////// // STEP1: search for the requested entry baseDN := this.pathToBase(path) sr, err := this.dial.Search(ldap.NewSearchRequest( baseDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 2, 0, false, "(objectClass=*)", []string{}, nil, )) if err != nil { return nil, err } if len(sr.Entries) != 1 { return nil, ErrNotValid } entry := sr.Entries[0] /////////////////////////////////////////////// // STEP2: create the form that fits in the entry schema var forms []FormElement = []FormElement{ NewFormElementFromAttributeWithValue("dn", baseDN), NewFormElementFromAttributeWithValue("objectClass", strings.Join(entry.GetAttributeValues("objectClass"), ", ")), } forms[0].ReadOnly = true forms[0].Required = true required := make([]FormElement, 0) optional := make([]FormElement, 0) for _, value := range entry.GetAttributeValues("objectClass") { required = append(required, FindRequiredAttributesForObject(value)...) optional = append(optional, FindOptionalAttributesForObject(value)...) } sort.SliceStable(required, sortFormElement(required)) sort.SliceStable(optional, sortFormElement(optional)) forms = append(forms, required...) forms = append(forms, optional...) /////////////////////////////////////////////// // STEP3: fillup the form with the entry values for i := 0; i < len(entry.Attributes); i++ { data := struct { key string value string }{ key: entry.Attributes[i].Name, value: strings.Join(entry.Attributes[i].Values, ", "), } var i int for i, _ = range forms { if forms[i].Name == data.key { forms[i].Value = data.value } } if i == len(forms) { forms = append(forms, NewFormElementFromAttributeWithValue(data.key, data.value)) } if forms[i].Name == "gidNumber" { for _, value := range entry.GetAttributeValues("objectClass") { if value == "posixAccount" { forms[i].Datalist = this.autocompleteLDAP("(objectclass=posixGroup)", "gidNumber") break } } } else if forms[i].Name == "memberUid" { for _, value := range entry.GetAttributeValues("objectClass") { if value == "posixGroup" { forms[i].Datalist = this.autocompleteLDAP("(objectclass=posixAccount)", "cn") forms[i].MultiValue = true break } } } } /////////////////////////////////////////////// // STEP4: Send the form data over b, err := Form{Elmnts: forms}.MarshalJSON() if err != nil { return nil, err } return NewReadCloserFromBytes(b), nil } func (this LDAP) Mkdir(path string) error { ldapNode := strings.Split(filepath.Base(path), "=") if len(ldapNode) != 2 { return ErrNotValid } var objectClass string switch ldapNode[0] { case "ou": objectClass = "organizationalUnit" case "o": objectClass = "organization" case "c": objectClass = "country" default: return ErrNotValid } forms := FindRequiredAttributesForObject(objectClass) for i := range forms { if forms[i].Name == "objectClass" { forms[i].Value = strings.Join(FindDerivatedClasses(objectClass), ", ") } else { forms[i].Value = ldapNode[1] } } if err := this.dial.Add(&ldap.AddRequest{ DN: this.pathToBase(path), Attributes: func() []ldap.Attribute { attributes := make([]ldap.Attribute, 0, len(forms)) for i := 0; i < len(forms); i++ { attributes = append(attributes, ldap.Attribute{ Type: forms[i].Name, Vals: strings.Split(fmt.Sprintf("%s", forms[i].Value), ", "), }) } return attributes }(), }); err != nil { return ErrPermissionDenied } return nil } func (this LDAP) Rm(path string) error { var err error sr, err := this.dial.Search(ldap.NewSearchRequest( this.pathToBase(path), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{}, nil, )) if err != nil { return err } for i := len(sr.Entries) - 1; i >= 0; i-- { if err == nil { err = this.dial.Del(&ldap.DelRequest{ DN: sr.Entries[i].DN, }) } } return err } func (this LDAP) Mv(from string, to string) error { toBase := this.pathToBase(to) fromBase := this.pathToBase(from) return this.dial.ModifyDN(&ldap.ModifyDNRequest{ DN: fromBase, NewRDN: func(t string) string { a := strings.Split(t, ",") if len(a) == 0 { return t } return a[0] }(toBase), DeleteOldRDN: true, NewSuperior: func(t string) string { a := strings.Split(t, ",") if len(a) == 0 { return t } return strings.Join(a[1:], ",") }(toBase), }) } func (this LDAP) Touch(path string) error { ldapNode := strings.Split(filepath.Base(path), "=") if len(ldapNode) != 2 { return ErrNotValid } var objectClass []string switch ldapNode[0] { case "cn": objectClass = []string{"inetOrgPerson", "posixAccount"} default: return ErrNotValid } ldapNode[1] = strings.TrimSuffix(ldapNode[1], ".form") var uniqueForms map[string]FormElement = make(map[string]FormElement) for i := 0; i < len(objectClass); i++ { for _, obj := range FindRequiredAttributesForObject(objectClass[i]) { uniqueForms[obj.Name] = obj } } var forms []FormElement = make([]FormElement, 0) for _, element := range uniqueForms { if element.Name == "objectClass" { element.Value = strings.Join(objectClass, ", ") } else { element.Value = this.generateLDAP(element.Name, ldapNode[1]) } forms = append(forms, element) } if err := this.dial.Add(&ldap.AddRequest{ DN: this.pathToBase(path), Attributes: func() []ldap.Attribute { attributes := make([]ldap.Attribute, 0, len(forms)) for i := 0; i < len(forms); i++ { attributes = append(attributes, ldap.Attribute{ Type: forms[i].Name, Vals: strings.Split(fmt.Sprintf("%s", forms[i].Value), ", "), }) } return attributes }(), }); err != nil { return ErrPermissionDenied } return nil } func (this LDAP) Save(path string, file io.Reader) error { var data map[string]FormElement if err := json.NewDecoder(file).Decode(&data); err != nil { return err } else if data["dn"].Value == nil { return ErrNotValid } if this.pathToBase(path) != data["dn"].Value { // change in the path can only be perform via `MV` return ErrNotAllowed } sr, err := this.dial.Search(ldap.NewSearchRequest( fmt.Sprintf("%s", data["dn"].Value), ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", []string{}, nil, )) if err != nil { return err } if len(sr.Entries) != 1 { return ErrNotValid } var attributes map[string]*[]string = make(map[string]*[]string) for i := 0; i < len(sr.Entries[0].Attributes); i++ { attributes[sr.Entries[0].Attributes[i].Name] = &sr.Entries[0].Attributes[i].Values } modifyRequest := ldap.NewModifyRequest(fmt.Sprintf("%s", data["dn"].Value), nil) for key := range data { if data[key].Value == nil || key == "dn" { continue } if attributes[key] == nil { modifyRequest.Add(key, strings.Split(fmt.Sprintf("%s", data[key].Value), ", ")) } else if data[key].Value != strings.Join(*attributes[key], ", ") { modifyRequest.Replace(key, strings.Split(fmt.Sprintf("%s", data[key].Value), ", ")) } } for key := range attributes { if data[key].Value == nil && attributes[key] != nil { modifyRequest.Delete(key, *attributes[key]) } } if err := this.dial.Modify(modifyRequest); err != nil { return ErrPermissionDenied } return nil } func (this LDAP) Meta(path string) Metadata { return Metadata{ CanUpload: NewBool(false), HideExtension: NewBool(true), RefreshOnCreate: NewBool(true), } } func (this LDAP) pathToBase(path string) string { path = strings.TrimSuffix(path, ".form") if path = strings.Trim(path, "/"); path == "" { return this.baseDN } pathArray := strings.Split(path, "/") baseArray := strings.Split(this.baseDN, ",") reversedPath := []string{} for i := len(pathArray) - 1; i >= 0; i-- { reversedPath = append(reversedPath, pathArray[i]) } return strings.Join(append(reversedPath, baseArray...), ",") } func (this LDAP) autocompleteLDAP(filter string, value string) []string { val := []string{} sr, err := this.dial.Search(ldap.NewSearchRequest( this.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, []string{value}, nil, )) if err != nil { return val } for i := 0; i < len(sr.Entries); i++ { val = append(val, sr.Entries[i].GetAttributeValue(value)) } sort.Strings(val) return val } func (this LDAP) generateLDAP(name string, deflts string) string { d := strings.Split(deflts, "-") switch name { case "cn": return strings.ToLower(deflts) case "uid": return strings.ToLower(deflts) case "uidNumber": return "65534" case "homeDirectory": return "/home/" + strings.ToLower(deflts) case "loginShell": return "/bin/false" case "aliasedObjectName": return strings.ToLower(deflts) case "c": return strings.ToLower(deflts) case "o": return strings.ToLower(deflts) case "userPassword": return "welcome" case "gidNumber": return "65534" case "sn": if len(d) == 2 { return strings.Title(d[1]) } return strings.Title(strings.Join(d, " ")) case "givenName": if len(d) == 2 { return strings.Title(d[0]) } return strings.Title(strings.Join(d, " ")) default: return deflts } } type LDAPSchema struct { IsContainer bool // can be used as a folder to store more entry? Description string // doc string coming from the schema Type string // AUXILIARY / STRUCTURAL or ABSTRACT Silent bool // show up (or not) as part of the client autocomplete Required []string // required attributes Optional []string // optional attributes Inherit []string // class this schema inherits } func FindRequiredAttributesForObject(objectClass string) []FormElement { if Schema[objectClass] == nil { return make([]FormElement, 0) } elements := make([]FormElement, 0, len(Schema[objectClass].Required)) for i := 0; i < len(Schema[objectClass].Inherit); i++ { els := FindRequiredAttributesForObject(Schema[objectClass].Inherit[i]) elements = append(elements, els...) } for i := 0; i < len(Schema[objectClass].Required); i++ { elements = append( elements, func() FormElement { f := NewFormElementFromAttribute(Schema[objectClass].Required[i]) f.Required = true return f }(), ) } return elements } func FindOptionalAttributesForObject(objectClass string) []FormElement { if Schema[objectClass] == nil { return make([]FormElement, 0) } elements := make([]FormElement, 0, len(Schema[objectClass].Optional)) for i := 0; i < len(Schema[objectClass].Inherit); i++ { els := FindOptionalAttributesForObject(Schema[objectClass].Inherit[i]) elements = append(elements, els...) } for i := 0; i < len(Schema[objectClass].Optional); i++ { elements = append( elements, NewFormElementFromAttribute(Schema[objectClass].Optional[i]), ) } return elements } func NewFormElementFromAttribute(attr string) FormElement { var form FormElement = FormElement{} if LDAPAttribute[attr] != nil { form = *LDAPAttribute[attr] } if form.Name == "" { form.Name = attr } if form.Type == "" { form.Type = "text" } return form } func NewFormElementFromAttributeWithValue(attr string, value string) FormElement { f := NewFormElementFromAttribute(attr) f.Value = value return f } func FindDerivatedClasses(objectClass string) []string { classes := []string{objectClass} if Schema[objectClass] == nil { return classes } for i := 0; i < len(Schema[objectClass].Inherit); i++ { classes = append(classes, FindDerivatedClasses(Schema[objectClass].Inherit[i])...) } return classes } func sortFormElement(e []FormElement) func(i, j int) bool { return func(i, j int) bool { l := LDAPAttribute[e[i].Name] r := LDAPAttribute[e[j].Name] if l == nil && r == nil { // tie return false } else if r == nil { return true } else if l == nil { return false } if l.Order == 0 && r.Order == 0 { return false } else if l.Order == 0 { return false } else if r.Order == 0 { return true } return l.Order < r.Order } } /* * The following is loading LDAP schema that was found on the openLDAP directory: * https://github.com/openldap/openldap/tree/master/servers/slapd/schema * As such, the source code in OpenLDAP says: * "Redistribution and use in source and binary forms, with or without modification, * are permitted only as authorized by the OpenLDAP Public License." * This license can be found: http://www.openldap.org/software/release/license.html * * It includes: core.schema, inetorgperson.schema, collective.schema, corba.schema, cosine.schema * duaconf.schema, dyngroup.schema, java.schema, misc.schema, msuser.schema, nis.schema, openldap.schema * pmi.schema, ppolicy.schema. */ var Schema map[string]*LDAPSchema = map[string]*LDAPSchema{ // SCHEMA: core.schema "top": &LDAPSchema{ Description: "Top of the superclass chain - RFC2256", Type: "ABSTRACT", IsContainer: false, Silent: true, Inherit: []string{}, Required: []string{"objectClass"}, Optional: []string{}, }, "alias": &LDAPSchema{ Description: "An alias - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"aliasedObjectName"}, Optional: []string{}, }, "country": &LDAPSchema{ Description: "A country - RFC2256", Type: "STRUCTURAL", IsContainer: true, Silent: true, Inherit: []string{"top"}, Required: []string{"c"}, Optional: []string{"searchGuide", "description"}, }, "locality": &LDAPSchema{ Description: "A locality - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"street", "seeAlso", "searchGuide", "st", "l", "description"}, }, "organization": &LDAPSchema{ Description: "An organization - RFC2256", Type: "STRUCTURAL", IsContainer: true, Silent: false, Inherit: []string{"top"}, Required: []string{"o"}, Optional: []string{"userPassword", "searchGuide", "seeAlso", "businessCategory", "x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "st", "l", "description"}, }, "organizationalUnit": &LDAPSchema{ Description: "An organizational unit - RFC2256", Type: "STRUCTURAL", IsContainer: true, Silent: false, Inherit: []string{"top"}, Required: []string{"ou"}, Optional: []string{"userPassword", "searchGuide", "seeAlso", "businessCategory", "x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "st", "l", "description"}, }, "person": &LDAPSchema{ Description: "A person - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: false, Inherit: []string{"top"}, Required: []string{"sn", "cn"}, Optional: []string{"userPassword", "telephoneNumber", "seeAlso", "description"}, }, "organizationalPerson": &LDAPSchema{ Description: "An organizational person - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: false, Inherit: []string{"person"}, Required: []string{}, Optional: []string{"title", "x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "ou", "st", "l"}, }, "organizationalRole": &LDAPSchema{ Description: "An organizational role - RFC2256", Type: "STRUCTURAL", IsContainer: false, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "seeAlso", "roleOccupant", "preferredDeliveryMethod", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "ou", "st", "l", "description"}, }, "groupOfNames": &LDAPSchema{ Description: "A group of names (DNs) - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"member", "cn"}, Optional: []string{"businessCategory", "seeAlso", "owner", "ou", "o", "description"}, }, "residentialPerson": &LDAPSchema{ Description: "An residential person - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"person"}, Required: []string{"l"}, Optional: []string{"businessCategory", "x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "preferredDeliveryMethod", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "st", "l"}, }, "applicationProcess": &LDAPSchema{ Description: "An application process - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"seeAlso", "ou", "l", "description"}, }, "applicationEntity": &LDAPSchema{ Description: "An application entity - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"presentationAddress", "cn"}, Optional: []string{"supportedApplicationContext", "seeAlso", "ou", "o", "l", "description"}, }, "dSA": &LDAPSchema{ Description: "A directory system agent (a server) - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"applicationEntity STRUCTURAL"}, Required: []string{}, Optional: []string{"knowledgeInformation"}, }, "device": &LDAPSchema{ Description: "A device - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"serialNumber", "seeAlso", "owner", "ou", "o", "l", "description"}, }, "strongAuthenticationUser": &LDAPSchema{ Description: "A strong authentication user - RFC2256", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"userCertificate"}, Optional: []string{}, }, "certificationAuthority": &LDAPSchema{ Description: "A certificate authority - RFC2256", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"authorityRevocationList", "certificateRevocationList", ""}, Optional: []string{"crossCertificatePair"}, }, "groupOfUniqueNames": &LDAPSchema{ Description: "A group of unique names (DN and Unique Identifier) - RFC2256", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"uniqueMember", "cn"}, Optional: []string{"businessCategory", "seeAlso", "owner", "ou", "o", "description"}, }, "userSecurityInformation": &LDAPSchema{ Description: "A user security information - RFC2256", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"supportedAlgorithms"}, }, "certificationAuthority-V2": &LDAPSchema{ Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"certificationAuthority"}, Required: []string{}, Optional: []string{"deltaRevocationList"}, }, "cRLDistributionPoint": &LDAPSchema{ Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"certificateRevocationList", "authorityRevocationList", "deltaRevocationList"}, }, "dmd": &LDAPSchema{ Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"dmdName"}, Optional: []string{"userPassword", "searchGuide", "seeAlso", "businessCategory", "x121Address", "registeredAddress", "destinationIndicator", "preferredDeliveryMethod", "telexNumber", "teletexTerminalIdentifier", "telephoneNumber", "internationaliSDNNumber", "facsimileTelephoneNumber", "street", "postOfficeBox", "postalCode", "postalAddress", "physicalDeliveryOfficeName", "st", "l", "description"}, }, "pkiUser": &LDAPSchema{ Description: "A PKI user - RFC2587", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"userCertificate"}, }, "pkiCA": &LDAPSchema{ Description: "PKI certificate authority - RFC2587", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"authorityRevocationList", "certificateRevocationList", "cACertificate", "crossCertificatePair"}, }, "deltaCRL": &LDAPSchema{ Description: "PKI user - RFC2587", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"deltaRevocationList"}, }, "labeledURIObject": &LDAPSchema{ Description: "Object that contains the URI attribute type - RFC2079", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"labeledURI"}, }, "simpleSecurityObject": &LDAPSchema{ Description: "Simple security object - RFC1274", Type: "AUXILIARY", IsContainer: false, Silent: false, Inherit: []string{"top"}, Required: []string{"userPassword"}, Optional: []string{}, }, "dcObject": &LDAPSchema{ Description: "Domain component object - RFC2247", Type: "AUXILIARY", IsContainer: true, Silent: true, Inherit: []string{"top"}, Required: []string{"dc"}, Optional: []string{}, }, "uidObject": &LDAPSchema{ Description: "Uid object - RFC2377", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"uid"}, Optional: []string{}, }, // SCHEMA: inetorgperson.schema "inetOrgPerson": &LDAPSchema{ Description: "Internet Organizational Person - RFC2798", Type: "STRUCTURAL", IsContainer: false, Silent: false, Inherit: []string{"organizationalPerson"}, Required: []string{}, Optional: []string{"audio", "businessCategory", "carLicense", "departmentNumber", "displayName", "employeeNumber", "employeeType", "givenName", "homePhone", "homePostalAddress", "initials", "jpegPhoto", "labeledURI", "mail", "manager", "mobile", "o", "pager", "photo", "roomNumber", "secretary", "uid", "userCertificate", "x500uniqueIdentifier", "preferredLanguage", "userSMIMECertificate", "userPKCS12"}, }, // SCHEMA: collective.schema // SCHEMA: corba.schema "corbaContainer": &LDAPSchema{ Description: "Container for a CORBA object", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{}, }, "corbaObject": &LDAPSchema{ Description: "CORBA object representation", Type: "ABSTRACT", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"corbaRepositoryId", "description"}, }, "corbaObjectReference": &LDAPSchema{ Description: "CORBA interoperable object reference", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"corbaObject"}, Required: []string{"corbaIor"}, Optional: []string{}, }, // SCHEMA: cosine.schema "pilotObject": &LDAPSchema{ Description: "Pilot object - RFC1274", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"info", "photo", "manager", "uniqueIdentifier", "lastModifiedTime", "lastModifiedBy", "dITRedirect", "audio"}, }, "pilotPerson": &LDAPSchema{ Description: "The PilotPerson object class is used as a sub-class of person, to allow the use of a number of additional attributes to be assigned to entries of object class person", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"person"}, Required: []string{}, Optional: []string{"userid", "textEncodedORAddress", "rfc822Mailbox", "favouriteDrink", "roomNumber", "userClass", "homeTelephoneNumber", "homePostalAddress", "secretary", "personalTitle", "preferredDeliveryMethod", "businessCategory", "janetMailbox", "otherMailbox", "mobileTelephoneNumber", "pagerTelephoneNumber", "organizationalStatus", "mailPreferenceOption", "personalSignature"}, }, "newPilotPerson": &LDAPSchema{ Description: "The PilotPerson object class is used as a sub-class of person, to allow the use of a number of additional attributes to be assigned to entries of object class person", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"person"}, Required: []string{}, Optional: []string{"userid", "textEncodedORAddress", "rfc822Mailbox", "favouriteDrink", "roomNumber", "userClass", "homeTelephoneNumber", "homePostalAddress", "secretary", "personalTitle", "preferredDeliveryMethod", "businessCategory", "janetMailbox", "otherMailbox", "mobileTelephoneNumber", "pagerTelephoneNumber", "organizationalStatus", "mailPreferenceOption", "personalSignature"}, }, "account": &LDAPSchema{ Description: "The Account object class is used to define entries representing computer accounts. The userid attribute should be used for naming entries of this object class.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"userid"}, Optional: []string{"description", "seeAlso", "localityName", "organizationName", "organizationalUnitName", "host"}, }, "document": &LDAPSchema{ Description: "The Document object class is used to define entries which represent documents.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"documentIdentifier"}, Optional: []string{"commonName", "description", "seeAlso", "localityName", "organizationName", "organizationalUnitName", "documentTitle", "documentVersion", "documentAuthor", "documentLocation", "documentPublisher"}, }, "room": &LDAPSchema{ Description: "The Room object class is used to define entries representing rooms. The commonName attribute should be used for naming pentries of this object class.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"commonName"}, Optional: []string{"roomNumber", "description", "seeAlso", "telephoneNumber"}, }, "documentSeries": &LDAPSchema{ Description: "The Document Series object class is used to define an entry which represents a series of documents (e.g., The Request For Comments papers).", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"commonName"}, Optional: []string{"description", "seeAlso", "telephonenumber", "localityName", "organizationName", "organizationalUnitName"}, }, "domain": &LDAPSchema{ Description: "The Domain object class is used to define entries which represent DNS or NRS domains. The domainComponent attribute should be used for naming entries of this object class.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"domainComponent"}, Optional: []string{"associatedName", "organizationName", "description", "businessCategory", "seeAlso", "searchGuide", "userPassword", "localityName", "stateOrProvinceName", "streetAddress", "physicalDeliveryOfficeName", "postalAddress", "postalCode", "postOfficeBox", "streetAddress", "facsimileTelephoneNumber", "internationalISDNNumber", "telephoneNumber", "teletexTerminalIdentifier", "telexNumber", "preferredDeliveryMethod", "destinationIndicator", "registeredAddress", "x121Address"}, }, "RFC822localPart": &LDAPSchema{ Description: "The RFC822 Local Part object class is used to define entries which represent the local part of RFC822 mail addresses. This treats this part of an RFC822 address as a domain.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"domain"}, Required: []string{}, Optional: []string{"commonName", "surname", "description", "seeAlso", "telephoneNumber", "physicalDeliveryOfficeName", "postalAddress", "postalCode", "postOfficeBox", "streetAddress", "facsimileTelephoneNumber", "internationalISDNNumber", "telephoneNumber", "teletexTerminalIdentifier", "telexNumber", "preferredDeliveryMethod", "destinationIndicator", "registeredAddress", "x121Address"}, }, "dNSDomain": &LDAPSchema{ Description: "The DNS Domain (Domain NameServer) object class is used to define entries for DNS domains.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"domain"}, Required: []string{}, Optional: []string{"ARecord", "MDRecord", "MXRecord", "NSRecord", "SOARecord", "CNAMERecord"}, }, "domainRelatedObject": &LDAPSchema{ Description: "An object related to an domain - RFC1274. The Domain Related Object object class is used to define entries which represent DNS/NRS domains which are \"equivalent\" to an X.500 domain: e.g., an organisation or organisational unit", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"associatedDomain"}, Optional: []string{}, }, "friendlyCountry": &LDAPSchema{ Description: "The Friendly Country object class is used to define country entries in the DIT. The object class is used to allow friendlier naming of countries than that allowed by the object class country. The naming attribute of object class country, countryName, has to be a 2 letter string defined in ISO 3166.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"country"}, Required: []string{"friendlyCountryName"}, Optional: []string{}, }, "pilotOrganization": &LDAPSchema{ Description: "The PilotOrganization object class is used as a sub-class of organization and organizationalUnit to allow a number of additional attributes to be assigned to entries of object classes organization and organizationalUnit.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"organization", "organizationalUnit"}, Required: []string{}, Optional: []string{"buildingName"}, }, "pilotDSA": &LDAPSchema{ Description: "The PilotDSA object class is used as a sub-class of the dsa object class to allow additional attributes to be assigned to entries for DSAs.", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"dsa"}, Required: []string{}, Optional: []string{"dSAQuality"}, }, "qualityLabelledData": &LDAPSchema{ Description: "The Quality Labelled Data object class is used to allow the ssignment of the data quality attributes to subtrees in the DIT", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"dsaQuality"}, Optional: []string{"subtreeMinimumQuality", "subtreeMaximumQuality"}, }, // SCHEMA: duaconf.schema "DUAConfigProfile": &LDAPSchema{ Description: "Abstraction of a base configuration for a DUA", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"defaultServerList", "preferredServerList", "defaultSearchBase", "defaultSearchScope", "searchTimeLimit", "bindTimeLimit", "credentialLevel", "authenticationMethod", "followReferrals", "dereferenceAliases", "serviceSearchDescriptor", "serviceCredentialLevel", "serviceAuthenticationMethod", "objectclassMap", "attributeMap", "profileTTL"}, }, // SCHEMA: dyngroup.schema "groupOfURLs": &LDAPSchema{ Description: "undefined", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"memberURL", "businessCategory", "description", "o", "ou", "owner", "seeAlso"}, }, "dgIdentityAux": &LDAPSchema{ Description: "undefined", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"dgIdentity", "dgAuthz"}, }, // SCHEMA: java.schema "javaContainer": &LDAPSchema{ Description: "Container for a Java object", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{}, }, "javaObject": &LDAPSchema{ Description: "Java object representation", Type: "ABSTRACT", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"javaClassName"}, Optional: []string{"javaClassNames", "javaCodebase", "javaDoc", "description"}, }, "javaSerializedObject": &LDAPSchema{ Description: "Java serialized object", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"javaObject"}, Required: []string{"javaSerializedData"}, Optional: []string{}, }, "javaMarshalledObject": &LDAPSchema{ Description: "Java marshalled object", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"javaObject"}, Required: []string{"javaSerializedData"}, Optional: []string{}, }, "javaNamingReference": &LDAPSchema{ Description: "JNDI reference", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"javaObject"}, Required: []string{}, Optional: []string{"javaReferenceAddress", "javaFactory"}, }, // SCHEMA: misc.schema "inetLocalMailRecipient": &LDAPSchema{ Description: "Internet local mail recipient", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{}, }, "nisMailAlias": &LDAPSchema{ Description: "NIS mail alias", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, }, // SCHEMA: msuser.schema "mstop": &LDAPSchema{ Type: "ABSTRACT", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"objectClass", "instanceType", "nTSecurityDescriptor", "objectCategory"}, Optional: []string{"cn", "description", "distinguishedName", "whenCreated", "whenChanged", "subRefs", "displayName", "uSNCreated", "isDeleted", "dSASignature", "objectVersion", "repsTo", "repsFrom", "memberOf", "ownerBL", "uSNChanged", "uSNLastObjRem", "showInAdvancedViewOnly", "adminDisplayName", "proxyAddresses", "adminDescription", "extensionName", "uSNDSALastObjRemoved", "displayNamePrintable", "directReports", "wWWHomePage", "USNIntersite", "name", "objectGUID", "replPropertyMetaData", "replUpToDateVector", "flags", "revision", "wbemPath", "fSMORoleOwner", "systemFlags", "siteObjectBL", "serverReferenceBL", "nonSecurityMemberBL", "queryPolicyBL", "wellKnownObjects", "isPrivilegeHolder", "partialAttributeSet", "managedObjects", "partialAttributeDeletionList", "url", "lastKnownParent", "bridgeheadServerListBL", "netbootSCPBL", "isCriticalSystemObject", "frsComputerReferenceBL", "fRSMemberReferenceBL", "uSNSource", "fromEntry", "allowedChildClasses", "allowedChildClassesEffective", "allowedAttributes", "allowedAttributesEffective", "possibleInferiors", "canonicalName", "proxiedObjectName", "sDRightsEffective", "dSCorePropagationData", "otherWellKnownObjects", "mS-DS-ConsistencyGuid", "mS-DS-ConsistencyChildCount", "masteredBy", "msCOM-PartitionSetLink", "msCOM-UserLink", "msDS-Approx-Immed-Subordinates", "msDS-NCReplCursors", "msDS-NCReplInboundNeighbors", "msDS-NCReplOutboundNeighbors", "msDS-ReplAttributeMetaData", "msDS-ReplValueMetaData", "msDS-NonMembersBL", "msDS-MembersForAzRoleBL", "msDS-OperationsForAzTaskBL", "msDS-TasksForAzTaskBL", "msDS-OperationsForAzRoleBL", "msDS-TasksForAzRoleBL", "msDs-masteredBy", "msDS-ObjectReferenceBL", "msDS-PrincipalName", "msDS-RevealedDSAs", "msDS-KrbTgtLinkBl", "msDS-IsFullReplicaFor", "msDS-IsDomainFor", "msDS-IsPartialReplicaFor", "msDS-AuthenticatedToAccountlist", "msDS-NC-RO-Replica-Locations-BL", "msDS-RevealedListBL", "msDS-PSOApplied", "msDS-NcType", "msDS-OIDToGroupLinkBl", "msDS-HostServiceAccountBL", "isRecycled", "msDS-LocalEffectiveDeletionTime", "msDS-LocalEffectiveRecycleTime", "msDS-LastKnownRDN", "msDS-EnabledFeatureBL", "msDS-ClaimSharesPossibleValuesWithBL", "msDS-MembersOfResourcePropertyListBL", "msDS-IsPrimaryComputerFor", "msDS-ValueTypeReferenceBL", "msDS-TDOIngressBL", "msDS-TDOEgressBL", "msDS-parentdistname", "msDS-ReplValueMetaDataExt", "msds-memberOfTransitive", "msds-memberTransitive", "msSFU30PosixMemberOf", "msDFSR-MemberReferenceBL", "msDFSR-ComputerReferenceBL"}, }, "group": &LDAPSchema{ Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"mstop"}, Required: []string{"groupType"}, Optional: []string{"member", "nTGroupMembers", "operatorCount", "adminCount", "groupAttributes", "groupMembershipSAM", "controlAccessRights", "desktopProfile", "nonSecurityMember", "managedBy", "primaryGroupToken", "msDS-AzLDAPQuery", "msDS-NonMembers", "msDS-AzBizRule", "msDS-AzBizRuleLanguage", "msDS-AzLastImportedBizRulePath", "msDS-AzApplicationData", "msDS-AzObjectGuid", "msDS-AzGenericData", "msDS-PrimaryComputer", "mail", "msSFU30Name", "msSFU30NisDomain", "msSFU30PosixMember"}, }, "user": &LDAPSchema{ Description: "undefined", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"mstop", "organizationalPerson"}, Required: []string{}, Optional: []string{"o", "businessCategory", "userCertificate", "givenName", "initials", "x500uniqueIdentifier", "displayName", "networkAddress", "employeeNumber", "employeeType", "homePostalAddress", "userAccountControl", "badPwdCount", "codePage", "homeDirectory", "homeDrive", "badPasswordTime", "lastLogoff", "lastLogon", "dBCSPwd", "localeID", "scriptPath", "logonHours", "logonWorkstation", "maxStorage", "userWorkstations", "unicodePwd", "otherLoginWorkstations", "ntPwdHistory", "pwdLastSet", "preferredOU", "primaryGroupID", "userParameters", "profilePath", "operatorCount", "adminCount", "accountExpires", "lmPwdHistory", "groupMembershipSAM", "logonCount", "controlAccessRights", "defaultClassStore", "groupsToIgnore", "groupPriority", "desktopProfile", "dynamicLDAPServer", "userPrincipalName", "lockoutTime", "userSharedFolder", "userSharedFolderOther", "servicePrincipalName", "aCSPolicyName", "terminalServer", "mSMQSignCertificates", "mSMQDigests", "mSMQDigestsMig", "mSMQSignCertificatesMig", "msNPAllowDialin", "msNPCallingStationID", "msNPSavedCallingStationID", "msRADIUSCallbackNumber", "msRADIUSFramedIPAddress", "msRADIUSFramedRoute", "msRADIUSServiceType", "msRASSavedCallbackNumber", "msRASSavedFramedIPAddress", "msRASSavedFramedRoute", "mS-DS-CreatorSID", "msCOM-UserPartitionSetLink", "msDS-Cached-Membership", "msDS-Cached-Membership-Time-Stamp", "msDS-Site-Affinity", "msDS-User-Account-Control-Computed", "lastLogonTimestamp", "msIIS-FTPRoot", "msIIS-FTPDir", "msDRM-IdentityCertificate", "msDS-SourceObjectDN", "msPKIRoamingTimeStamp", "msPKIDPAPIMasterKeys", "msPKIAccountCredentials", "msRADIUS-FramedInterfaceId", "msRADIUS-SavedFramedInterfaceId", "msRADIUS-FramedIpv6Prefix", "msRADIUS-SavedFramedIpv6Prefix", "msRADIUS-FramedIpv6Route", "msRADIUS-SavedFramedIpv6Route", "msDS-SecondaryKrbTgtNumber", "msDS-AuthenticatedAtDC", "msDS-SupportedEncryptionTypes", "msDS-LastSuccessfulInteractiveLogonTime", "msDS-LastFailedInteractiveLogonTime", "msDS-FailedInteractiveLogonCount", "msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon", "msTSProfilePath", "msTSHomeDirectory", "msTSHomeDrive", "msTSAllowLogon", "msTSRemoteControl", "msTSMaxDisconnectionTime", "msTSMaxConnectionTime", "msTSMaxIdleTime", "msTSReconnectionAction", "msTSBrokenConnectionAction", "msTSConnectClientDrives", "msTSConnectPrinterDrives", "msTSDefaultToMainPrinter", "msTSWorkDirectory", "msTSInitialProgram", "msTSProperty01", "msTSProperty02", "msTSExpireDate", "msTSLicenseVersion", "msTSManagingLS", "msDS-UserPasswordExpiryTimeComputed", "msTSExpireDate2", "msTSLicenseVersion2", "msTSManagingLS2", "msTSExpireDate3", "msTSLicenseVersion3", "msTSManagingLS3", "msTSExpireDate4", "msTSLicenseVersion4", "msTSManagingLS4", "msTSLSProperty01", "msTSLSProperty02", "msDS-ResultantPSO", "msPKI-CredentialRoamingTokens", "msTSPrimaryDesktop", "msTSSecondaryDesktops", "msDS-PrimaryComputer", "msDS-SyncServerUrl", "msDS-AssignedAuthNPolicySilo", "msDS-AuthNPolicySiloMembersBL", "msDS-AssignedAuthNPolicy", "userSMIMECertificate", "uid", "mail", "roomNumber", "photo", "manager", "homePhone", "secretary", "mobile", "pager", "audio", "jpegPhoto", "carLicense", "departmentNumber", "preferredLanguage", "userPKCS12", "labeledURI", "msSFU30Name", "msSFU30NisDomain"}, }, "container": &LDAPSchema{ Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"mstop"}, Required: []string{"cn"}, Optional: []string{"schemaVersion", "defaultClassStore", "msDS-ObjectReference"}, }, "computer": &LDAPSchema{ Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"user"}, Required: []string{}, Optional: []string{"cn", "networkAddress", "localPolicyFlags", "defaultLocalPolicyObject", "machineRole", "location", "netbootInitialization", "netbootGUID", "netbootMachineFilePath", "siteGUID", "operatingSystem", "operatingSystemVersion", "operatingSystemServicePack", "operatingSystemHotfix", "volumeCount", "physicalLocationObject", "dNSHostName", "policyReplicationFlags", "managedBy", "rIDSetReferences", "catalogs", "netbootSIFFile", "netbootMirrorDataFile", "msDS-AdditionalDnsHostName", "msDS-AdditionalSamAccountName", "msDS-ExecuteScriptPassword", "msDS-KrbTgtLink", "msDS-RevealedUsers", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "msDS-RevealedList", "msDS-AuthenticatedAtDC", "msDS-isGC", "msDS-isRODC", "msDS-SiteName", "msDS-PromotionSettings", "msTPM-OwnerInformation", "msTSProperty01", "msTSProperty02", "msDS-IsUserCachableAtRodc", "msDS-HostServiceAccount", "msTSEndpointData", "msTSEndpointType", "msTSEndpointPlugin", "msTSPrimaryDesktopBL", "msTSSecondaryDesktopBL", "msTPM-TpmInformationForComputer", "msDS-GenerationId", "msImaging-ThumbprintHash", "msImaging-HashAlgorithm", "netbootDUID", "msSFU30Name", "msSFU30Aliases", "msSFU30NisDomain", "nisMapName"}, }, // SCHEMA: nis.schema "posixAccount": &LDAPSchema{ Description: "Abstraction of an account with POSIX attributes", Type: "AUXILIARY", IsContainer: false, Silent: false, Inherit: []string{"top"}, Required: []string{"cn", "uid", "uidNumber", "gidNumber", "homeDirectory"}, Optional: []string{"userPassword", "loginShell", "gecos", "description"}, }, "shadowAccount": &LDAPSchema{ Description: "Additional attributes for shadow passwords", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"uid"}, Optional: []string{"userPassword", "shadowLastChange", "shadowMin", "shadowMax", "shadowWarning", "shadowInactive", "shadowExpire", "shadowFlag", "description"}, }, "posixGroup": &LDAPSchema{ Description: "Abstraction of a group of accounts", Type: "STRUCTURAL", IsContainer: false, Silent: false, Inherit: []string{"top"}, Required: []string{"cn", "gidNumber"}, Optional: []string{"userPassword", "memberUid", "description"}, }, "ipService": &LDAPSchema{ Description: "Abstraction an Internet Protocol service", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "ipServicePort", "ipServiceProtocol"}, Optional: []string{"description"}, }, "ipProtocol": &LDAPSchema{ Description: "Abstraction of an IP protocol", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "ipProtocolNumber", "description"}, Optional: []string{"description"}, }, "oncRpc": &LDAPSchema{ Description: "Abstraction of an ONC/RPC binding", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "oncRpcNumber", "description"}, Optional: []string{"description"}, }, "ipHost": &LDAPSchema{ Description: "Abstraction of a host, an IP device", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "ipHostNumber"}, Optional: []string{"l", "description", "manager"}, }, "ipNetwork": &LDAPSchema{ Description: "Abstraction of an IP network", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "ipNetworkNumber"}, Optional: []string{"ipNetmaskNumber", "l", "description", "manager"}, }, "nisNetgroup": &LDAPSchema{ Description: "Abstraction of a netgroup", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{"nisNetgroupTriple", "memberNisNetgroup", "description"}, }, "nisMap": &LDAPSchema{ Description: "A generic abstraction of a NIS map", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"nisMapName"}, Optional: []string{"description"}, }, "nisObject": &LDAPSchema{ Description: "An entry in a NIS map", Type: "STRUCTURAL", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{"cn", "nisMapEntry", "nisMapName"}, Optional: []string{"description"}, }, "ieee802Device": &LDAPSchema{ Description: "A device with a MAC address", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"macAddress"}, }, "bootableDevice": &LDAPSchema{ Description: "A device with boot parameters", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"bootFile", "bootParameter"}, }, // SCHEMA: openldap.schema "OpenLDAPorg": &LDAPSchema{ Description: "OpenLDAP Organizational Object", Type: "UNSPECIFIED", IsContainer: false, Silent: true, Inherit: []string{"organization"}, Required: []string{}, Optional: []string{"buildingName", "displayName", "labeledURI"}, }, "OpenLDAPou": &LDAPSchema{ Description: "OpenLDAP Organizational Unit Object", Type: "UNSPECIFIED", IsContainer: false, Silent: true, Inherit: []string{"organizationalUnit"}, Required: []string{}, Optional: []string{"buildingName", "displayName", "labeledURI", "o"}, }, "OpenLDAPperson": &LDAPSchema{ Description: "OpenLDAP Person", Type: "UNSPECIFIED", IsContainer: false, Silent: true, Inherit: []string{"pilotPerson", "inetOrgPerson"}, Required: []string{"uid", "cn"}, Optional: []string{"givenName", "labeledURI", "o"}, }, "OpenLDAPdisplayableObject": &LDAPSchema{ Description: "OpenLDAP Displayable Object", Type: "AUXILIARY", IsContainer: false, Silent: true, Inherit: []string{}, Required: []string{}, Optional: []string{"displayName"}, }, // SCHEMA: extra "nsContainer": &LDAPSchema{ // https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.1/html/Configuration_and_Command_Reference/config-object-classes.html Description: "Container Entry", Type: "UNSPECIFIED", IsContainer: true, Silent: true, Inherit: []string{"top"}, Required: []string{"cn"}, Optional: []string{}, }, "aeZone": &LDAPSchema{ // https://docs.ldap.com/specs/draft-howard-namedobject-01.txt Type: "STRUCTURAL", IsContainer: true, Silent: true, Inherit: []string{"top"}, Required: []string{}, Optional: []string{"cn"}, }, } var LDAPAttribute map[string]*FormElement = map[string]*FormElement{ ////////////////////////// // SCHEMA: core.schema: "objectClass": &FormElement{ Description: "Object classes of the entity - RFC2256", Order: 1, Datalist: func() []string { list := make([]string, 0) for key, _ := range Schema { if Schema[key].Silent == false { list = append(list, key) } } sort.Strings(list) return list }(), MultiValue: true, }, "aliasedObjectName": &FormElement{ Description: "Name of aliased object - RFC2256", }, "aliasedEntryName": &FormElement{ Description: "Name of aliased object - RFC2256", }, "knowledgeInformation": &FormElement{ Description: "Knowledge information - RFC2256", }, "cn": &FormElement{ Description: "Common name(s) for which the entity is known by - RFC2256", Order: 1, }, "commonName": &FormElement{ Description: "Common name(s) for which the entity is known by - RFC2256", Order: 2, }, "sn": &FormElement{ Description: "Last (family) name(s) for which the entity is known by - RFC2256", Order: 4, }, "surname": &FormElement{ Description: "Last (family) name(s) for which the entity is known by - RFC2256", Order: 4, }, "serialNumber": &FormElement{ Description: "Serial number of the entity - RFC2256", }, "c": &FormElement{ Description: "Two-letter ISO-3166 country code - RFC4519", }, "countryName": &FormElement{ Description: "Two-letter ISO-3166 country code - RFC4519", Order: 15, }, "l": &FormElement{ Description: "Locality which this object resides in - RFC2256", Order: 15, }, "localityName": &FormElement{ Description: "Locality which this object resides in - RFC2256", Order: 15, }, "st": &FormElement{ Description: "State or province which this object resides in - RFC2256", Order: 15, }, "stateOrProvinceName": &FormElement{ Description: "State or province which this object resides in - RFC2256", Order: 15, }, "street": &FormElement{ Description: "Street address of this object - RFC2256", Order: 15, }, "streetAddress": &FormElement{ Description: "Street address of this object - RFC2256", Order: 15, }, "o": &FormElement{ Description: "Organization this object belongs to - RFC2256", Order: 10, }, "organizationName": &FormElement{ Description: "Organization this object belongs to - RFC2256", Order: 10, }, "ou": &FormElement{ Description: "Organizational unit this object belongs to - RFC2256", Order: 10, }, "organizationalUnitName": &FormElement{ Description: "Organizational unit this object belongs to - RFC2256", Order: 10, }, "title": &FormElement{ Description: "Title associated with the entity - RFC2256", Order: 20, }, "description": &FormElement{ Description: "Descriptive information - RFC2256", Order: 5, }, "searchGuide": &FormElement{ Description: "Search guide, deprecated by enhancedSearchGuide - RFC2256", }, "businessCategory": &FormElement{ Description: "Business category - RFC2256", }, "postalAddress": &FormElement{ Description: "Postal address - RFC2256", Order: 15, }, "postalCode": &FormElement{ Description: "Postal code - RFC2256", Order: 15, }, "postOfficeBox": &FormElement{ Description: "Post Office Box - RFC2256", Order: 15, }, "physicalDeliveryOfficeName": &FormElement{ Description: "Physical Delivery Office Name - RFC2256", Order: 20, }, "telephoneNumber": &FormElement{ Description: "Telephone Number - RFC2256", Order: 8, }, "telexNumber": &FormElement{ Description: "Telex Number - RFC2256", Order: 25, }, "teletexTerminalIdentifier": &FormElement{ Description: "Teletex Terminal Identifier - RFC2256", Order: 25, }, "facsimileTelephoneNumber": &FormElement{ Description: "Facsimile (Fax) Telephone Number - RFC2256", Order: 25, }, "fax": &FormElement{ Description: "Facsimile (Fax) Telephone Number - RFC2256", Order: 25, }, "x121Address": &FormElement{ Description: "X.121 Address - RFC2256", }, "internationaliSDNNumber": &FormElement{ Description: "International ISDN number - RFC2256", }, "registeredAddress": &FormElement{ Description: "Registered postal address - RFC2256", Order: 15, }, "destinationIndicator": &FormElement{ Description: "Destination indicator - RFC2256", }, "preferredDeliveryMethod": &FormElement{ Description: "Preferred delivery method - RFC2256", }, "presentationAddress": &FormElement{ Description: "Presentation address - RFC2256", }, "supportedApplicationContext": &FormElement{ Description: "Supported application context - RFC2256", }, "member": &FormElement{ Description: "Member of a group - RFC2256", }, "owner": &FormElement{ Description: "Owner (of the object) - RFC2256", }, "roleOccupant": &FormElement{ Description: "Occupant of role - RFC2256", }, "seeAlso": &FormElement{ Description: "DN of related object - RFC2256", Order: 20, }, "userPassword": &FormElement{ Description: "Password of user - RFC2256/2307", Order: 6, }, "userCertificate": &FormElement{ Description: "X.509 user certificate, use ;binary - RFC2256", }, "cACertificate": &FormElement{ Description: "X.509 CA certificate, use ;binary - RFC2256", }, "authorityRevocationList": &FormElement{ Description: "X.509 authority revocation list, use ;binary - RFC2256", }, "certificateRevocationList": &FormElement{ Description: "X.509 certificate revocation list, use ;binary - RFC2256", }, "crossCertificatePair": &FormElement{ Description: "X.509 cross certificate pair, use ;binary - RFC2256", }, "name": &FormElement{ Order: 4, }, "givenName": &FormElement{ Description: "First name(s) for which the entity is known by - RFC2256", Order: 4, }, "gn": &FormElement{ Description: "First name(s) for which the entity is known by - RFC2256", Order: 4, }, "initials": &FormElement{ Description: "Initials of some or all of names, but not the surname(s). - RFC2256", Order: 20, }, "generationQualifier": &FormElement{ Description: "Name qualifier indicating a generation - RFC2256", }, "x500UniqueIdentifier": &FormElement{ Description: "X.500 unique identifier - RFC2256", }, "dnQualifier": &FormElement{ Description: "DN qualifier - RFC2256", }, "enhancedSearchGuide": &FormElement{ Description: "Enhanced search guide - RFC2256", }, "protocolInformation": &FormElement{ Description: "Protocol information - RFC2256", }, "distinguishedName": &FormElement{}, "uniqueMember": &FormElement{ Description: "Unique member of a group - RFC2256", Order: 9, }, "houseIdentifier": &FormElement{ Description: "House identifier - RFC2256", }, "supportedAlgorithms": &FormElement{ Description: "Supported algorithms - RFC2256", }, "deltaRevocationList": &FormElement{ Description: "Delta revocation list; use ;binary - RFC2256", }, "dmdName": &FormElement{ Description: "Name of DMD - RFC2256", }, "pseudonym": &FormElement{ Description: "Pseudonym for the object - X.520(4th)", }, "labeledURI": &FormElement{ Description: "Uniform Resource Identifier with optional label - RFC2079", }, "uid": &FormElement{ Description: "User identifier - RFC1274", Order: 7, }, "userid": &FormElement{ Description: "User identifier - RFC1274", }, "mail": &FormElement{ Description: "RFC822 Mailbox - RFC1274", Order: 7, }, "rfc822Mailbox": &FormElement{ Description: "RFC822 Mailbox - RFC1274", }, "dc": &FormElement{ Description: "Domain component - RFC1274/2247", Order: 10, }, "domainComponent": &FormElement{ Description: "Domain component - RFC1274/2247", Order: 10, }, "associatedDomain": &FormElement{ Description: "Domain associated with object - RFC1274", }, "email": &FormElement{ Description: "Legacy attribute for email addresses in DNs - RFC3280", }, "emailAddress": &FormElement{ Description: "Legacy attribute for email addresses in DNs - RFC3280", }, "pkcs9email": &FormElement{ Description: "Legacy attribute for email addresses in DNs - RFC3280", }, ////////////////////////// // SCHEMA: inetorgperson.schema "carLicense": &FormElement{ Description: "Vehicle license or registration plate - RFC2798", Order: 20, }, "departmentNumber": &FormElement{ Description: "Identifies a department within an organization - RFC2798", Order: 20, }, "displayName": &FormElement{ Description: "Preferred name to be used when displaying entries - RFC2798", Order: 5, }, "employeeNumber": &FormElement{ Description: "Numerically identifies an employee within an organization - RFC2798", Order: 20, }, "employeeType": &FormElement{ Description: "Type of employment for a person - RFC2798", Order: 20, }, "jpegPhoto": &FormElement{ Description: "A JPEG image - RFC2798", Order: 17, }, "preferredLanguage": &FormElement{ Description: "Preferred written or spoken language for a person - RFC2798", Order: 19, }, "userSMIMECertificate": &FormElement{ Description: "PKCS7 SignedData used to support S/MIME - RFC2798", }, "userPKCS12": &FormElement{ Description: "Personal identity information, a PKCS 12 PFX - RFC2798", }, ////////////////////////// // SCHEMA: collective.schema "c-l": &FormElement{ Description: "Locality name for the collection of entries", }, "c-st": &FormElement{ Description: "State or province name for the collection of entries", }, "c-street": &FormElement{ Description: "Street address for the collection of entries", }, "c-o": &FormElement{ Description: "Organization name for the collection of entries", Order: 10, }, "c-ou": &FormElement{ Description: "Organizational unit name for the collection of entries", Order: 10, }, "c-PostalAddress": &FormElement{ Description: "Postal address for the collection of entries", Order: 15, }, "c-PostalCode": &FormElement{ Order: 15, Description: "Postal code for the collection of entries", }, "c-PostOfficeBox": &FormElement{ Order: 15, Description: "Post office box for the collection of entries", }, "c-PhysicalDeliveryOfficeName": &FormElement{ Order: 20, Description: "Physical dlivery office name for a collection of entries.", }, "c-TelephoneNumber": &FormElement{ Order: 8, Description: "telephone number for the collection of entries", }, "c-TelexNumber": &FormElement{ Order: 25, Description: "Telex number for the collection of entries", }, "c-FacsimileTelephoneNumber": &FormElement{ Description: "Facsimile telephone number for a collection of entries.", }, "c-InternationalISDNNumber": &FormElement{ Description: "International ISDN number for the collection of entries", }, ////////////////////////// // SCHEMA: corba.schema "corbaIor": &FormElement{ Description: "Stringified interoperable object reference of a CORBA object", }, "corbaRepositoryId": &FormElement{ Description: "Repository ids of interfaces implemented by a CORBA object", }, ////////////////////////// // SCHEMA: cosine.schema "textEncodedORAddress": &FormElement{ Description: "Text encoding of an X.400 O/R address, as specified in RFC 987", }, "info": &FormElement{ Description: "General information - RFC1274", }, "drink": &FormElement{ Description: "Favorite drink - RFC1274", }, "favouriteDrink": &FormElement{ Description: "Favorite drink - RFC1274", }, "roomNumber": &FormElement{ Description: "Room number - RFC1274", Order: 20, }, "photo": &FormElement{ Description: "Photo (G3 fax) - RFC1274", Order: 17, }, "userClass": &FormElement{ Description: "Category of user - RFC1274", }, "host": &FormElement{ Description: "Host computer - RFC1274", }, "manager": &FormElement{ Description: "DN of manager - RFC1274", Order: 20, }, "documentIdentifier": &FormElement{ Description: "Unique identifier of document - RFC1274", }, "documentTitle": &FormElement{ Description: "Title of document - RFC1274", }, "documentVersion": &FormElement{ Description: "Version of document - RFC1274", }, "documentAuthor": &FormElement{ Description: "DN of author of document - RFC1274", }, "documentLocation": &FormElement{ Description: "Location of document original - RFC1274", }, "homePhone": &FormElement{ Description: "Home telephone number - RFC1274", Order: 8, }, "homeTelephoneNumber": &FormElement{ Description: "Home telephone number - RFC1274", }, "secretary": &FormElement{ Description: "DN of secretary - RFC1274", }, "otherMailbox": &FormElement{}, "lastModifiedTime": &FormElement{ Description: "Time of last modify, replaced by modifyTimestamp - RFC1274", }, "lastModifiedBy": &FormElement{ Description: "Last modifier, replaced by modifiersName - RFC1274", }, "aRecord": &FormElement{ Description: "Type A (Address) DNS resource record", }, "mDRecord": &FormElement{}, "mXRecord": &FormElement{ Description: "Mail Exchange DNS resource record", }, "nSRecord": &FormElement{ Description: "Name Server DNS resource record", }, "sOARecord": &FormElement{ Description: "Start of Authority DNS resource record", }, "cNAMERecord": &FormElement{ Description: "CNAME (Canonical Name) DNS resource record", }, "associatedName": &FormElement{ Description: "DN of entry associated with domain - RFC1274", }, "homePostalAddress": &FormElement{ Description: "Home postal address - RFC1274", Order: 15, }, "personalTitle": &FormElement{ Description: "Personal title - RFC1274", }, "mobile": &FormElement{ Description: "Mobile telephone number - RFC1274", Order: 8, }, "mobileTelephoneNumber": &FormElement{ Description: "Mobile telephone number - RFC1274", Order: 8, }, "pager": &FormElement{ Description: "Pager telephone number - RFC1274", Order: 20, }, "pagerTelephoneNumber": &FormElement{ Description: "Pager telephone number - RFC1274", Order: 20, }, "co": &FormElement{ Description: "Friendly country name - RFC1274", }, "friendlyCountryName": &FormElement{ Description: "Friendly country name - RFC1274", }, "uniqueIdentifier": &FormElement{ Description: "Unique identifer - RFC1274", }, "organizationalStatus": &FormElement{ Description: "Organizational status - RFC1274", }, "janetMailbox": &FormElement{ Description: "Janet mailbox - RFC1274", }, "mailPreferenceOption": &FormElement{ Description: "Mail preference option - RFC1274", }, "buildingName": &FormElement{ Description: "Name of building - RFC1274", }, "dSAQuality": &FormElement{ Description: "DSA Quality - RFC1274", }, "singleLevelQuality": &FormElement{ Description: "Single Level Quality - RFC1274", }, "subtreeMinimumQuality": &FormElement{ Description: "Subtree Minimum Quality - RFC1274", }, "subtreeMaximumQuality": &FormElement{ Description: "Subtree Maximum Quality - RFC1274", }, "personalSignature": &FormElement{ Description: "Personal Signature (G3 fax) - RFC1274", }, "dITRedirect": &FormElement{ Description: "DIT Redirect - RFC1274", }, "audio": &FormElement{ Description: "Audio (u-law) - RFC1274", }, "documentPublisher": &FormElement{ Description: "Publisher of document - RFC1274", }, ////////////////////////// // SCHEMA: duaconf.schema "defaultServerList": &FormElement{ Description: "Default LDAP server host address used by a DUA", }, "defaultSearchBase": &FormElement{ Description: "Default LDAP base DN used by a DUA", }, "preferredServerList": &FormElement{ Description: "Preferred LDAP server host addresses to be used by a DUA", }, "searchTimeLimit": &FormElement{ Description: "Maximum time in seconds a DUA should allow for a search to complete", }, "bindTimeLimit": &FormElement{ Description: "Maximum time in seconds a DUA should allow for the bind operation to complete", }, "followReferrals": &FormElement{ Description: "Tells DUA if it should follow referrals returned by a DSA search result", }, "dereferenceAliases": &FormElement{ Description: "Tells DUA if it should dereference aliases", }, "authenticationMethod": &FormElement{ Description: "A keystring which identifies the type of authentication method used to contact the DSA", }, "profileTTL": &FormElement{ Description: "Time to live, in seconds, before a client DUA should re-read this configuration profile", }, "serviceSearchDescriptor": &FormElement{ Description: "LDAP search descriptor list used by a DUA", }, "attributeMap": &FormElement{ Description: "Attribute mappings used by a DUA", }, "credentialLevel": &FormElement{ Description: "Identifies type of credentials a DUA should use when binding to the LDAP server", }, "objectclassMap": &FormElement{ Description: "Objectclass mappings used by a DUA", }, "defaultSearchScope": &FormElement{ Description: "Default search scope used by a DUA", }, "serviceCredentialLevel": &FormElement{ Description: "Identifies type of credentials a DUA should use when binding to the LDAP server for a specific service", }, "serviceAuthenticationMethod": &FormElement{ Description: "Authentication method used by a service of the DUA", }, ////////////////////////// // SCHEMA: dyngroup.schema "memberURL": &FormElement{ Description: "Identifies an URL associated with each member of a group. Any type of labeled URL can be used.", }, "dgIdentity": &FormElement{ Description: "Identity to use when processing the memberURL", }, "dgAuthz": &FormElement{ Description: "Optional authorization rules that determine who is allowed to assume the dgIdentity", }, ////////////////////////// // SCHEMA: java.schema "javaClassName": &FormElement{ Description: "Fully qualified name of distinguished Java class or interface", }, "javaCodebase": &FormElement{ Description: "URL(s) specifying the location of class definition", }, "javaClassNames": &FormElement{ Description: "Fully qualified Java class or interface name", }, "javaSerializedData": &FormElement{ Description: "Serialized form of a Java object", }, "javaFactory": &FormElement{ Description: "Fully qualified Java class name of a JNDI object factory", }, "javaReferenceAddress": &FormElement{ Description: "Addresses associated with a JNDI Reference", }, "javaDoc": &FormElement{ Description: "The Java documentation for the class", }, ////////////////////////// // SCHEMA: misc.schema "mailLocalAddress": &FormElement{ Description: "RFC822 email address of this recipient", }, "mailHost": &FormElement{ Description: "FQDN of the SMTP/MTA of this recipient", }, "mailRoutingAddress": &FormElement{ Description: "RFC822 routing address of this recipient", }, "rfc822MailMember": &FormElement{ Description: "Rfc822 mail address of group member(s)", }, ////////////////////////// // SCHEMA: msuser.schema "ownerBL": &FormElement{}, "msCOM-PartitionSetLink": &FormElement{}, "msCOM-UserLink": &FormElement{}, "msDS-Approx-Immed-Subordinates": &FormElement{}, "msDS-NCReplCursors": &FormElement{}, "msDS-NCReplInboundNeighbors": &FormElement{}, "msDS-NCReplOutboundNeighbors": &FormElement{}, "msDS-ReplAttributeMetaData": &FormElement{}, "msDS-ReplValueMetaData": &FormElement{}, "msDS-NonMembers": &FormElement{}, "msDS-NonMembersBL": &FormElement{}, "msDS-MembersForAzRole": &FormElement{}, "msDS-MembersForAzRoleBL": &FormElement{}, "msDS-OperationsForAzTask": &FormElement{}, "msDS-OperationsForAzTaskBL": &FormElement{}, "msDS-TasksForAzTask": &FormElement{}, "msDS-TasksForAzTaskBL": &FormElement{}, "msDS-OperationsForAzRole": &FormElement{}, "msDS-OperationsForAzRoleBL": &FormElement{}, "msDS-TasksForAzRole": &FormElement{}, "msDS-TasksForAzRoleBL": &FormElement{}, "msDs-masteredBy": &FormElement{}, "msDS-ObjectReference": &FormElement{}, "msDS-ObjectReferenceBL": &FormElement{}, "msDS-PrincipalName": &FormElement{}, "msDS-RevealedDSAs": &FormElement{}, "msDS-KrbTgtLinkBl": &FormElement{}, "msDS-IsFullReplicaFor": &FormElement{}, "msDS-IsDomainFor": &FormElement{}, "msDS-IsPartialReplicaFor": &FormElement{}, "msDS-AuthenticatedToAccountlist": &FormElement{}, "msDS-AuthenticatedAtDC": &FormElement{}, "msDS-RevealedListBL": &FormElement{}, "msDS-NC-RO-Replica-Locations-BL": &FormElement{}, "msDS-PSOApplied": &FormElement{}, "msDS-NcType": &FormElement{}, "msDS-OIDToGroupLinkBl": &FormElement{}, "isRecycled": &FormElement{}, "msDS-LocalEffectiveDeletionTime": &FormElement{}, "msDS-LocalEffectiveRecycleTime": &FormElement{}, "msDS-LastKnownRDN": &FormElement{}, "msDS-EnabledFeatureBL": &FormElement{}, "msDS-MembersOfResourcePropertyListBL": &FormElement{}, "msDS-ValueTypeReferenceBL": &FormElement{}, "msDS-TDOIngressBL": &FormElement{}, "msDS-TDOEgressBL": &FormElement{}, "msDS-parentdistname": &FormElement{}, "msDS-ReplValueMetaDataExt": &FormElement{}, "msds-memberOfTransitive": &FormElement{}, "msds-memberTransitive": &FormElement{}, "msSFU30PosixMemberOf": &FormElement{}, "msDFSR-MemberReferenceBL": &FormElement{}, "msDFSR-ComputerReferenceBL": &FormElement{}, "msDS-AzLDAPQuery": &FormElement{}, "msDS-AzBizRuleLanguage": &FormElement{}, "msDS-AzLastImportedBizRulePath": &FormElement{}, "msDS-AzApplicationData": &FormElement{}, "msDS-AzObjectGuid": &FormElement{}, "msDS-AzGenericData": &FormElement{}, "msDS-PrimaryComputer": &FormElement{}, "msSFU30Name": &FormElement{}, "msSFU30NisDomain": &FormElement{}, "msSFU30PosixMember": &FormElement{}, "msCOM-UserPartitionSetLink": &FormElement{}, "msDS-Cached-Membership": &FormElement{}, "msDS-Cached-Membership-Time-Stamp": &FormElement{}, "msDS-Site-Affinity": &FormElement{}, "msDS-User-Account-Control-Computed": &FormElement{}, "lastLogonTimestamp": &FormElement{}, "msIIS-FTPRoot": &FormElement{}, "msIIS-FTPDir": &FormElement{}, "msDRM-IdentityCertificate": &FormElement{}, "msDS-SourceObjectDN": &FormElement{}, "msPKIRoamingTimeStamp": &FormElement{}, "msPKIDPAPIMasterKeys": &FormElement{}, "msPKIAccountCredentials": &FormElement{}, "msRADIUS-FramedInterfaceId": &FormElement{}, "msRADIUS-SavedFramedInterfaceId": &FormElement{}, "msRADIUS-FramedIpv6Prefix": &FormElement{}, "msRADIUS-SavedFramedIpv6Prefix": &FormElement{}, "msRADIUS-FramedIpv6Route": &FormElement{}, "msRADIUS-SavedFramedIpv6Route": &FormElement{}, "msDS-SecondaryKrbTgtNumber": &FormElement{}, "msDS-SupportedEncryptionTypes": &FormElement{}, "msDS-LastSuccessfulInteractiveLogonTime": &FormElement{}, "msDS-LastFailedInteractiveLogonTime": &FormElement{}, "msDS-FailedInteractiveLogonCount": &FormElement{}, "msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon": &FormElement{}, "msTSProfilePath": &FormElement{}, "msTSHomeDirectory": &FormElement{}, "msTSHomeDrive": &FormElement{}, "msTSAllowLogon": &FormElement{}, "msTSRemoteControl": &FormElement{}, "msTSMaxDisconnectionTime": &FormElement{}, "msTSMaxConnectionTime": &FormElement{}, "msTSMaxIdleTime": &FormElement{}, "msTSReconnectionAction": &FormElement{}, "msTSBrokenConnectionAction": &FormElement{}, "msTSConnectClientDrives": &FormElement{}, "msTSConnectPrinterDrives": &FormElement{}, "msTSDefaultToMainPrinter": &FormElement{}, "msTSWorkDirectory": &FormElement{}, "msTSInitialProgram": &FormElement{}, "msTSProperty01": &FormElement{}, "msTSProperty02": &FormElement{}, "msTSExpireDate": &FormElement{}, "msTSLicenseVersion": &FormElement{}, "msTSManagingLS": &FormElement{}, "msDS-UserPasswordExpiryTimeComputed": &FormElement{}, "msTSManagingLS4": &FormElement{}, "msTSManagingLS3": &FormElement{}, "msTSManagingLS2": &FormElement{}, "msTSExpireDate4": &FormElement{}, "msTSExpireDate3": &FormElement{}, "msTSExpireDate2": &FormElement{}, "msTSLicenseVersion3": &FormElement{}, "msTSLicenseVersion2": &FormElement{}, "msTSLicenseVersion4": &FormElement{}, "msTSLSProperty01": &FormElement{}, "msTSLSProperty02": &FormElement{}, "msDS-ResultantPSO": &FormElement{}, "msPKI-CredentialRoamingTokens": &FormElement{}, "msTSPrimaryDesktop": &FormElement{}, "msTSSecondaryDesktops": &FormElement{}, "msDS-SyncServerUrl": &FormElement{}, "msDS-AssignedAuthNPolicySilo": &FormElement{}, "msDS-AuthNPolicySiloMembersBL": &FormElement{}, "msDS-AssignedAuthNPolicy": &FormElement{}, "msDS-Behavior-Version": &FormElement{}, "msDS-PerUserTrustQuota": &FormElement{}, "msDS-AllUsersTrustQuota": &FormElement{}, "msDS-PerUserTrustTombstonesQuota": &FormElement{}, "msDS-AdditionalDnsHostName": &FormElement{}, "msDS-AdditionalSamAccountName": &FormElement{}, "msDS-ExecuteScriptPassword": &FormElement{}, "msDS-KrbTgtLink": &FormElement{}, "msDS-RevealedUsers": &FormElement{}, "msDS-NeverRevealGroup": &FormElement{}, "msDS-RevealOnDemandGroup": &FormElement{}, "msDS-RevealedList": &FormElement{}, "msDS-isGC": &FormElement{}, "msDS-isRODC": &FormElement{}, "msDS-SiteName": &FormElement{}, "msDS-PromotionSettings": &FormElement{}, "msTPM-OwnerInformation": &FormElement{}, "msDS-IsUserCachableAtRodc": &FormElement{}, "msDS-HostServiceAccount": &FormElement{}, "msTSEndpointData": &FormElement{}, "msTSEndpointType": &FormElement{}, "msTSEndpointPlugin": &FormElement{}, "msTSPrimaryDesktopBL": &FormElement{}, "msTSSecondaryDesktopBL": &FormElement{}, "msTPM-TpmInformationForComputer": &FormElement{}, "msDS-GenerationId": &FormElement{}, "msImaging-ThumbprintHash": &FormElement{}, "msImaging-HashAlgorithm": &FormElement{}, "netbootDUID": &FormElement{}, "msSFU30Aliases": &FormElement{}, "netbootNewMachineOU": &FormElement{}, "builtinCreationTime": &FormElement{}, "pKIEnrollmentAccess": &FormElement{}, "pKIExtendedKeyUsage": &FormElement{}, "msNPCalledStationID": &FormElement{}, "initialAuthIncoming": &FormElement{}, "objectClassCategory": &FormElement{}, "generatedConnection": &FormElement{}, "allowedChildClasses": &FormElement{}, "machineArchitecture": &FormElement{}, "aCSMaxPeakBandwidth": &FormElement{}, "marshalledInterface": &FormElement{}, "rIDManagerReference": &FormElement{}, "aCSEnableACSService": &FormElement{}, "mSMQRoutingService": &FormElement{}, "mS-SQL-AllowQueuedUpdatingSubscription": &FormElement{}, "primaryTelexNumber": &FormElement{}, "userAccountControl": &FormElement{}, "shellPropertyPages": &FormElement{}, "replUpToDateVector": &FormElement{}, "fRSDirectoryFilter": &FormElement{}, "printSeparatorFile": &FormElement{}, "pKIMaxIssuingDepth": &FormElement{}, "accountNameHistory": &FormElement{}, "mS-SQL-GPSLongitude": &FormElement{}, "adminPropertyPages": &FormElement{}, "securityIdentifier": &FormElement{}, "groupMembershipSAM": &FormElement{}, "serviceDNSNameType": &FormElement{}, "meetingIsEncrypted": &FormElement{}, "mS-SQL-Applications": &FormElement{}, "lastUpdateSequence": &FormElement{}, "lastContentIndexed": &FormElement{}, "meetingDescription": &FormElement{}, "fRSTimeLastCommand": &FormElement{}, "monikerDisplayName": &FormElement{}, "requiredCategories": &FormElement{}, "upgradeProductCode": &FormElement{}, "aCSMaxNoOfLogFiles": &FormElement{}, "mS-SQL-CharacterSet": &FormElement{}, "meetingContactInfo": &FormElement{}, "mS-SQL-CreationDate": &FormElement{}, "domainPolicyObject": &FormElement{}, "dhcpObjDescription": &FormElement{}, "meetingApplication": &FormElement{}, "defaultHidingValue": &FormElement{}, "fRSMemberReference": &FormElement{}, "dhcpIdentification": &FormElement{}, "trustAuthOutgoing": &FormElement{}, "systemMustContain": &FormElement{}, "primaryGroupToken": &FormElement{}, "rpcNsProfileEntry": &FormElement{}, "trustAuthIncoming": &FormElement{}, "mSMQPrevSiteGates": &FormElement{}, "queryPolicyObject": &FormElement{}, "optionDescription": &FormElement{}, "aCSMaximumSDUSize": &FormElement{}, "nonSecurityMember": &FormElement{}, "fRSReplicaSetType": &FormElement{}, "aCSTotalNoOfFlows": &FormElement{}, "possibleInferiors": &FormElement{}, "netbootMaxClients": &FormElement{}, "mS-SQL-GPSLatitude": &FormElement{}, "aCSPermissionBits": &FormElement{}, "mSMQTransactional": &FormElement{}, "mS-SQL-Description": &FormElement{}, "allowedAttributes": &FormElement{}, "fRSFaultCondition": &FormElement{}, "tombstoneLifetime": &FormElement{}, "remoteStorageGUID": &FormElement{}, "showInAddressBook": &FormElement{}, "defaultClassStore": &FormElement{}, "meetingOriginator": &FormElement{}, "userPrincipalName": &FormElement{}, "aCSMinimumLatency": &FormElement{}, "isPrivilegeHolder": &FormElement{}, "fRSReplicaSetGUID": &FormElement{}, "rIDAllocationPool": &FormElement{}, "pKIDefaultKeySpec": &FormElement{}, "dynamicLDAPServer": &FormElement{}, "serverReferenceBL": &FormElement{}, "fRSServiceCommand": &FormElement{}, "sDRightsEffective": &FormElement{}, "proxiedObjectName": &FormElement{}, "meetingRecurrence": &FormElement{}, "cOMTreatAsClassId": &FormElement{}, "globalAddressList": &FormElement{}, "extendedClassInfo": &FormElement{}, "machineWidePolicy": &FormElement{}, "foreignIdentifier": &FormElement{}, "dNReferenceUpdate": &FormElement{}, "trustPosixOffset": &FormElement{}, "enabledConnection": &FormElement{}, "ipsecNFAReference": &FormElement{}, "userWorkstations": &FormElement{}, "garbageCollPeriod": &FormElement{}, "mSMQComputerType": &FormElement{}, "logonWorkstation": &FormElement{}, "mSMQJournalQuota": &FormElement{}, "remoteSourceType": &FormElement{}, "pwdHistoryLength": &FormElement{}, "mSMQBasePriority": &FormElement{}, "systemMayContain": &FormElement{}, "mS-SQL-ThirdParty": &FormElement{}, "mSMQQueueNameExt": &FormElement{}, "fRSUpdateTimeout": &FormElement{}, "mSMQPrivacyLevel": &FormElement{}, "shellContextMenu": &FormElement{}, "wellKnownObjects": &FormElement{}, "transportDLLName": &FormElement{}, "qualityOfService": &FormElement{}, "lockoutThreshold": &FormElement{}, "remoteServerName": &FormElement{}, "previousParentCA": &FormElement{}, "dSUIShellMaximum": &FormElement{}, "notificationList": &FormElement{}, "addressBookRoots": &FormElement{}, "fRSPrimaryMember": &FormElement{}, "meetingStartTime": &FormElement{}, "mSMQSiteGatesMig": &FormElement{}, "dhcpReservations": &FormElement{}, "adminContextMenu": &FormElement{}, "pKIOverlapPeriod": &FormElement{}, "winsockAddresses": &FormElement{}, "mSMQAuthenticate": &FormElement{}, "dSUIAdminMaximum": &FormElement{}, "appSchemaVersion": &FormElement{}, "serviceClassInfo": &FormElement{}, "aCSEventLogLevel": &FormElement{}, "userSharedFolder": &FormElement{}, "domainWidePolicy": &FormElement{}, "rIDSetReferences": &FormElement{}, "canUpgradeScript": &FormElement{}, "classDisplayName": &FormElement{}, "adminDescription": &FormElement{}, "lSAModifiedCount": &FormElement{}, "serviceClassName": &FormElement{}, "localPolicyFlags": &FormElement{}, "rpcNsInterfaceID": &FormElement{}, "adminDisplayName": &FormElement{}, "nameServiceFlags": &FormElement{}, "meetingBandwidth": &FormElement{}, "domainIdentifier": &FormElement{}, "rIDAvailablePool": &FormElement{}, "legacyExchangeDN": &FormElement{}, "trustAttributes": &FormElement{}, "fRSRootSecurity": &FormElement{}, "superiorDNSRoot": &FormElement{}, "printMaxYExtent": &FormElement{}, "printMaxXExtent": &FormElement{}, "printMinYExtent": &FormElement{}, "printMinXExtent": &FormElement{}, "attributeSyntax": &FormElement{}, "printAttributes": &FormElement{}, "groupAttributes": &FormElement{}, "fileExtPriority": &FormElement{}, "mSMQServiceType": &FormElement{}, "operatingSystem": &FormElement{}, "mS-SQL-SortOrder": &FormElement{}, "versionNumberLo": &FormElement{}, "msRRASAttribute": &FormElement{}, "lastKnownParent": &FormElement{}, "shortServerName": &FormElement{}, "lockoutDuration": &FormElement{}, "defaultPriority": &FormElement{}, "rpcNsEntryFlags": &FormElement{}, "optionsLocation": &FormElement{}, "versionNumberHi": &FormElement{}, "rpcNsAnnotation": &FormElement{}, "purportedSearch": &FormElement{}, "aCSDSBMPriority": &FormElement{}, "mSMQSiteForeign": &FormElement{}, "currentLocation": &FormElement{}, "meetingProtocol": &FormElement{}, "publicKeyPolicy": &FormElement{}, "mS-SQL-Publisher": &FormElement{}, "createWizardExt": &FormElement{}, "mS-SQL-Clustered": &FormElement{}, "volTableIdxGUID": &FormElement{}, "currentParentCA": &FormElement{}, "seqNotification": &FormElement{}, "serverReference": &FormElement{}, "msNPAllowDialin": &FormElement{}, "mS-SQL-GPSHeight": &FormElement{}, "mS-SQL-AppleTalk": &FormElement{}, "linkTrackSecret": &FormElement{}, "dnsAllowDynamic": &FormElement{}, "badPasswordTime": &FormElement{}, "privilegeHolder": &FormElement{}, "printMediaReady": &FormElement{}, "printMACAddress": &FormElement{}, "lSACreationTime": &FormElement{}, "meetingLocation": &FormElement{}, "aCSIdentityName": &FormElement{}, "mS-DS-CreatorSID": &FormElement{}, "mS-SQL-NamedPipe": &FormElement{}, "lDAPAdminLimits": &FormElement{}, "lDAPDisplayName": &FormElement{}, "applicationName": &FormElement{}, "pendingParentCA": &FormElement{}, "aCSCacheTimeout": &FormElement{}, "meetingLanguage": &FormElement{}, "aCSDSBMDeadTime": &FormElement{}, "cACertificateDN": &FormElement{}, "userParameters": &FormElement{}, "trustDirection": &FormElement{}, "mSMQQueueQuota": &FormElement{}, "mSMQEncryptKey": &FormElement{}, "terminalServer": &FormElement{}, "printStartTime": &FormElement{}, "syncWithObject": &FormElement{}, "groupsToIgnore": &FormElement{}, "syncMembership": &FormElement{}, "syncAttributes": &FormElement{}, "nextLevelStore": &FormElement{}, "sAMAccountType": &FormElement{}, "mS-SQL-Keywords": &FormElement{}, "proxyAddresses": &FormElement{}, "bytesPerMinute": &FormElement{}, "printMaxCopies": &FormElement{}, "primaryGroupID": &FormElement{}, "nTGroupMembers": &FormElement{}, "mSMQDsServices": &FormElement{}, "fRSVersionGUID": &FormElement{}, "fRSWorkingPath": &FormElement{}, "otherTelephone": &FormElement{}, "otherHomePhone": &FormElement{}, "oEMInformation": &FormElement{}, "networkAddress": &FormElement{}, "mSMQDigestsMig": &FormElement{}, "meetingKeyword": &FormElement{}, "lDAPIPDenyList": &FormElement{}, "installUiLevel": &FormElement{}, "gPCFileSysPath": &FormElement{}, "fRSStagingPath": &FormElement{}, "auxiliaryClass": &FormElement{}, "accountExpires": &FormElement{}, "dhcpProperties": &FormElement{}, "desktopProfile": &FormElement{}, "aCSServiceType": &FormElement{}, "assocNTAccount": &FormElement{}, "creationWizard": &FormElement{}, "cOMOtherProgId": &FormElement{}, "auditingPolicy": &FormElement{}, "privilegeValue": &FormElement{}, "mS-SQL-Location": &FormElement{}, "pKIDefaultCSPs": &FormElement{}, "printShareName": &FormElement{}, "isSingleValued": &FormElement{}, "domainCrossRef": &FormElement{}, "netbootSIFFile": &FormElement{}, "cOMUniqueLIBID": &FormElement{}, "serviceDNSName": &FormElement{}, "objectCategory": &FormElement{}, "serviceClassID": &FormElement{}, "dhcpUpdateTime": &FormElement{}, "sAMAccountName": &FormElement{}, "meetingEndTime": &FormElement{}, "mS-SQL-Language": &FormElement{}, "aCSDSBMRefresh": &FormElement{}, "mS-SQL-Database": &FormElement{}, "cOMInterfaceID": &FormElement{}, "mS-SQL-AllowKnownPullSubscription": &FormElement{}, "mS-SQL-AllowAnonymousSubscription": &FormElement{}, "managedObjects": &FormElement{}, "possSuperiors": &FormElement{}, "transportType": &FormElement{}, "groupPriority": &FormElement{}, "rpcNsPriority": &FormElement{}, "mSMQQueueType": &FormElement{}, "versionNumber": &FormElement{}, "uSNLastObjRem": &FormElement{}, "templateRoots": &FormElement{}, "pwdProperties": &FormElement{}, "printNumberUp": &FormElement{}, "fRSExtensions": &FormElement{}, "printRateUnit": &FormElement{}, "msiScriptSize": &FormElement{}, "printSpooling": &FormElement{}, "queryPolicyBL": &FormElement{}, "proxyLifetime": &FormElement{}, "operatorCount": &FormElement{}, "netbootServer": &FormElement{}, "fSMORoleOwner": &FormElement{}, "driverVersion": &FormElement{}, "mS-SQL-Version": &FormElement{}, "mSMQNameStyle": &FormElement{}, "schemaVersion": &FormElement{}, "directReports": &FormElement{}, "addressSyntax": &FormElement{}, "printFormName": &FormElement{}, "msiScriptPath": &FormElement{}, "aCSServerList": &FormElement{}, "moveTreeState": &FormElement{}, "mSMQSiteGates": &FormElement{}, "mSMQDsService": &FormElement{}, "objectVersion": &FormElement{}, "dNSTombstoned": &FormElement{}, "mSMQLongLived": &FormElement{}, "fRSLevelLimit": &FormElement{}, "msiScriptName": &FormElement{}, "dhcpUniqueKey": &FormElement{}, "extensionName": &FormElement{}, "rpcNsBindings": &FormElement{}, "printBinNames": &FormElement{}, "replicaSource": &FormElement{}, "printLanguage": &FormElement{}, "mS-SQL-Contact": &FormElement{}, "nTMixedDomain": &FormElement{}, "fRSFileFilter": &FormElement{}, "birthLocation": &FormElement{}, "friendlyNames": &FormElement{}, "ipsecDataType": &FormElement{}, "meetingRating": &FormElement{}, "indexedScopes": &FormElement{}, "rpcNsObjectID": &FormElement{}, "modifiedCount": &FormElement{}, "oMObjectClass": &FormElement{}, "aCSPolicyName": &FormElement{}, "timeVolChange": &FormElement{}, "currMachineId": &FormElement{}, "schemaFlagsEx": &FormElement{}, "validAccesses": &FormElement{}, "domainReplica": &FormElement{}, "mSMQInterval2": &FormElement{}, "mSMQInterval1": &FormElement{}, "canonicalName": &FormElement{}, "ntPwdHistory": &FormElement{}, "trustPartner": &FormElement{}, "lmPwdHistory": &FormElement{}, "mS-SQL-Status": &FormElement{}, "USNIntersite": &FormElement{}, "netbootTools": &FormElement{}, "priorSetTime": &FormElement{}, "mS-SQL-Memory": &FormElement{}, "mSMQServices": &FormElement{}, "currentValue": &FormElement{}, "siteLinkList": &FormElement{}, "remoteSource": &FormElement{}, "setupCommand": &FormElement{}, "dSHeuristics": &FormElement{}, "replInterval": &FormElement{}, "printEndTime": &FormElement{}, "instanceType": &FormElement{}, "otherIpPhone": &FormElement{}, "mSMQSiteName": &FormElement{}, "meetingOwner": &FormElement{}, "printCollate": &FormElement{}, "defaultGroup": &FormElement{}, "minPwdLength": &FormElement{}, "netbootSCPBL": &FormElement{}, "mhsORAddress": &FormElement{}, "rpcNsCodeset": &FormElement{}, "hasMasterNCs": &FormElement{}, "mSMQMigrated": &FormElement{}, "dSASignature": &FormElement{}, "invocationId": &FormElement{}, "cOMTypelibId": &FormElement{}, "creationTime": &FormElement{}, "meetingScope": &FormElement{}, "volTableGUID": &FormElement{}, "siteObjectBL": &FormElement{}, "aCSTimeOfDay": &FormElement{}, "aCSDirection": &FormElement{}, "maxTicketAge": &FormElement{}, "schemaUpdate": &FormElement{}, "minTicketAge": &FormElement{}, "ipsecNegotiationPolicyReference": &FormElement{}, "helpFileName": &FormElement{}, "schemaIDGUID": &FormElement{}, "createDialog": &FormElement{}, "mSMQNt4Flags": &FormElement{}, "packageFlags": &FormElement{}, "wWWHomePage": &FormElement{}, "volumeCount": &FormElement{}, "printStatus": &FormElement{}, "uPNSuffixes": &FormElement{}, "trustParent": &FormElement{}, "tokenGroups": &FormElement{}, "systemFlags": &FormElement{}, "syncWithSID": &FormElement{}, "dNSProperty": &FormElement{}, "superScopes": &FormElement{}, "sPNMappings": &FormElement{}, "printNotify": &FormElement{}, "printMemory": &FormElement{}, "serverState": &FormElement{}, "mSMQVersion": &FormElement{}, "rIDUsedPool": &FormElement{}, "queryFilter": &FormElement{}, "printerName": &FormElement{}, "preferredOU": &FormElement{}, "primaryInternationalISDNNumber": &FormElement{}, "oMTIndxGuid": &FormElement{}, "mSMQUserSid": &FormElement{}, "fRSRootPath": &FormElement{}, "mSMQJournal": &FormElement{}, "contextMenu": &FormElement{}, "aCSPriority": &FormElement{}, "mSMQSignKey": &FormElement{}, "netbootGUID": &FormElement{}, "mSMQOwnerID": &FormElement{}, "mustContain": &FormElement{}, "dnsAllowXFR": &FormElement{}, "mS-SQL-Vines": &FormElement{}, "mSMQDigests": &FormElement{}, "lockoutTime": &FormElement{}, "lastSetTime": &FormElement{}, "countryCode": &FormElement{}, "mS-SQL-TCPIP": &FormElement{}, "mSMQForeign": &FormElement{}, "meetingType": &FormElement{}, "dhcpOptions": &FormElement{}, "dhcpServers": &FormElement{}, "assetNumber": &FormElement{}, "addressType": &FormElement{}, "mSMQCSPName": &FormElement{}, "msiFileList": &FormElement{}, "dNSHostName": &FormElement{}, "dhcpSubnets": &FormElement{}, "pKIKeyUsage": &FormElement{}, "attributeID": &FormElement{}, "objectCount": &FormElement{}, "timeRefresh": &FormElement{}, "profilePath": &FormElement{}, "productCode": &FormElement{}, "otherMobile": &FormElement{}, "badPwdCount": &FormElement{}, "mS-SQL-Build": &FormElement{}, "nETBIOSName": &FormElement{}, "mS-SQL-Alias": &FormElement{}, "maxRenewAge": &FormElement{}, "treatAsLeaf": &FormElement{}, "mSMQNt4Stub": &FormElement{}, "packageType": &FormElement{}, "isEphemeral": &FormElement{}, "dMDLocation": &FormElement{}, "dhcpClasses": &FormElement{}, "forceLogoff": &FormElement{}, "whenCreated": &FormElement{}, "meetingName": &FormElement{}, "mailAddress": &FormElement{}, "meetingBlob": &FormElement{}, "machineRole": &FormElement{}, "searchFlags": &FormElement{}, "whenChanged": &FormElement{}, "dhcpObjName": &FormElement{}, "aCSMaxAggregatePeakRatePerUser": &FormElement{}, "packageName": &FormElement{}, "systemOnly": &FormElement{}, "mSMQOSType": &FormElement{}, "queryPoint": &FormElement{}, "printOwner": &FormElement{}, "uSNCreated": &FormElement{}, "siteServer": &FormElement{}, "rpcNsGroup": &FormElement{}, "sIDHistory": &FormElement{}, "fRSVersion": &FormElement{}, "logonHours": &FormElement{}, "netbootAnswerOnlyValidClients": &FormElement{}, "pwdLastSet": &FormElement{}, "printColor": &FormElement{}, "mS-SQL-Type": &FormElement{}, "fromServer": &FormElement{}, "serverRole": &FormElement{}, "priorValue": &FormElement{}, "logonCount": &FormElement{}, "unicodePwd": &FormElement{}, "subClassOf": &FormElement{}, "mS-SQL-Size": &FormElement{}, "privateKey": &FormElement{}, "siteObject": &FormElement{}, "scriptPath": &FormElement{}, "serverName": &FormElement{}, "mSMQSiteID": &FormElement{}, "rightsGuid": &FormElement{}, "rIDNextRID": &FormElement{}, "meetingURL": &FormElement{}, "addressEntryDisplayTableMSDOS": &FormElement{}, "maxStorage": &FormElement{}, "rangeUpper": &FormElement{}, "rangeLower": &FormElement{}, "otherPager": &FormElement{}, "isMemberOfPartialAttributeSet": &FormElement{}, "parentGUID": &FormElement{}, "department": &FormElement{}, "mayContain": &FormElement{}, "adminCount": &FormElement{}, "lastLogoff": &FormElement{}, "masteredBy": &FormElement{}, "employeeID": &FormElement{}, "dhcpMaxKey": &FormElement{}, "driverName": &FormElement{}, "mS-SQL-Name": &FormElement{}, "categoryId": &FormElement{}, "additionalTrustedServiceNames": &FormElement{}, "scopeFlags": &FormElement{}, "categories": &FormElement{}, "netbootNewMachineNamingPolicy": &FormElement{}, "cOMClassID": &FormElement{}, "uSNChanged": &FormElement{}, "objectGUID": &FormElement{}, "dhcpRanges": &FormElement{}, "schemaInfo": &FormElement{}, "otherFacsimileTelephoneNumber": &FormElement{}, "machinePasswordChangeInterval": &FormElement{}, "rootTrust": &FormElement{}, "trustType": &FormElement{}, "groupType": &FormElement{}, "uSNSource": &FormElement{}, "mSMQQuota": &FormElement{}, "mSMQSites": &FormElement{}, "fromEntry": &FormElement{}, "mS-SQL-SPX": &FormElement{}, "gPOptions": &FormElement{}, "msiScript": &FormElement{}, "printRate": &FormElement{}, "cRLPartitionedRevocationList": &FormElement{}, "assistant": &FormElement{}, "fRSDSPoll": &FormElement{}, "partialAttributeDeletionList": &FormElement{}, "lastLogon": &FormElement{}, "governsID": &FormElement{}, "appliesTo": &FormElement{}, "eFSPolicy": &FormElement{}, "uASCompat": &FormElement{}, "prefixMap": &FormElement{}, "isDefunct": &FormElement{}, "dhcpSites": &FormElement{}, "iPSECNegotiationPolicyAction": &FormElement{}, "dnsRecord": &FormElement{}, "cOMProgID": &FormElement{}, "homeDrive": &FormElement{}, "meetingIP": &FormElement{}, "aCSNonReservedMinPolicedSize": &FormElement{}, "dhcpState": &FormElement{}, "mSMQLabel": &FormElement{}, "maxPwdAge": &FormElement{}, "minPwdAge": &FormElement{}, "cRLObject": &FormElement{}, "objectSid": &FormElement{}, "meetingID": &FormElement{}, "ipsecName": &FormElement{}, "isDeleted": &FormElement{}, "aCSAggregateTokenRatePerUser": &FormElement{}, "ipsecData": &FormElement{}, "domainCAs": &FormElement{}, "cAConnect": &FormElement{}, "printMaxResolutionSupported": &FormElement{}, "dhcpFlags": &FormElement{}, "helpData16": &FormElement{}, "managedBy": &FormElement{}, "helpData32": &FormElement{}, "mSMQSite2": &FormElement{}, "mSMQSite1": &FormElement{}, "replTopologyStayOfExecution": &FormElement{}, "allowedChildClassesEffective": &FormElement{}, "oMSyntax": &FormElement{}, "priority": &FormElement{}, "keywords": &FormElement{}, "mSMQCost": &FormElement{}, "siteList": &FormElement{}, "revision": &FormElement{}, "repsFrom": &FormElement{}, "userCert": &FormElement{}, "mSMQQMID": &FormElement{}, "portName": &FormElement{}, "netbootLocallyInstalledOSes": &FormElement{}, "division": &FormElement{}, "aCSMaxSizeOfRSVPAccountFile": &FormElement{}, "dhcpType": &FormElement{}, "wbemPath": &FormElement{}, "siteGUID": &FormElement{}, "rDNAttID": &FormElement{}, "aCSRSVPAccountFilesLocation": &FormElement{}, "mSMQDependentClientServices": &FormElement{}, "location": &FormElement{}, "fRSFlags": &FormElement{}, "iconPath": &FormElement{}, "cAWEBURL": &FormElement{}, "mscopeId": &FormElement{}, "treeName": &FormElement{}, "schedule": &FormElement{}, "parentCA": &FormElement{}, "cOMCLSID": &FormElement{}, "catalogs": &FormElement{}, "memberOf": &FormElement{}, "cAUsages": &FormElement{}, "dhcpMask": &FormElement{}, "flatName": &FormElement{}, "domainID": &FormElement{}, "localeID": &FormElement{}, "codePage": &FormElement{}, "aCSEnableRSVPMessageLogging": &FormElement{}, "printOrientationsSupported": &FormElement{}, "msRRASVendorAttributeEntry": &FormElement{}, "interSiteTopologyGenerator": &FormElement{}, "options": &FormElement{}, "dnsRoot": &FormElement{}, "iPSECNegotiationPolicyType": &FormElement{}, "mS-SQL-InformationDirectory": &FormElement{}, "operatingSystemServicePack": &FormElement{}, "nextRid": &FormElement{}, "pekList": &FormElement{}, "subRefs": &FormElement{}, "oMTGuid": &FormElement{}, "pKTGuid": &FormElement{}, "company": &FormElement{}, "moniker": &FormElement{}, "comment": &FormElement{}, "ipPhone": &FormElement{}, "mS-DS-ConsistencyChildCount": &FormElement{}, "creator": &FormElement{}, "uNCName": &FormElement{}, "dBCSPwd": &FormElement{}, "mSMQDependentClientService": &FormElement{}, "certificateAuthorityObject": &FormElement{}, "ipsecID": &FormElement{}, "allowedAttributesEffective": &FormElement{}, "aCSMaxPeakBandwidthPerFlow": &FormElement{}, "Enabled": &FormElement{}, "perRecipDialogDisplayTable": &FormElement{}, "interSiteTopologyFailover": &FormElement{}, "transportAddressAttribute": &FormElement{}, "netbootCurrentClientCount": &FormElement{}, "rIDPreviousAllocationPool": &FormElement{}, "repsTo": &FormElement{}, "defaultSecurityDescriptor": &FormElement{}, "lastBackupRestorationTime": &FormElement{}, "fRSControlOutboundBacklog": &FormElement{}, "vendor": &FormElement{}, "gPLink": &FormElement{}, "originalDisplayTableMSDOS": &FormElement{}, "linkID": &FormElement{}, "msNPSavedCallingStationID": &FormElement{}, "mAPIID": &FormElement{}, "serviceBindingInformation": &FormElement{}, "nCName": &FormElement{}, "tokenGroupsNoGCAcceptable": &FormElement{}, "tokenGroupsGlobalAndUniversal": &FormElement{}, "msRASSavedFramedIPAddress": &FormElement{}, "aCSAllocableRSVPBandwidth": &FormElement{}, "lockOutObservationWindow": &FormElement{}, "netbootIntelliMirrorOSes": &FormElement{}, "aCSNonReservedMaxSDUSize": &FormElement{}, "notes": &FormElement{}, "retiredReplDSASignatures": &FormElement{}, "aCSMaxTokenBucketPerFlow": &FormElement{}, "addressEntryDisplayTable": &FormElement{}, "aCSMinimumDelayVariation": &FormElement{}, "fRSControlInboundBacklog": &FormElement{}, "flags": &FormElement{}, "mS-SQL-LastDiagnosticDate": &FormElement{}, "gPCMachineExtensionNames": &FormElement{}, "ms-DS-MachineAccountQuota": &FormElement{}, "perMsgDialogDisplayTable": &FormElement{}, "defaultLocalPolicyObject": &FormElement{}, "msRASSavedCallbackNumber": &FormElement{}, "parentCACertificateChain": &FormElement{}, "gPCFunctionalityVersion": &FormElement{}, "fRSServiceCommandStatus": &FormElement{}, "aCSNonReservedTokenSize": &FormElement{}, "aCSMaxSizeOfRSVPLogFile": &FormElement{}, "cost": &FormElement{}, "modifiedCountAtLastProm": &FormElement{}, "aCSRSVPLogFilesLocation": &FormElement{}, "supplementalCredentials": &FormElement{}, "bridgeheadTransportList": &FormElement{}, "mSMQSignCertificatesMig": &FormElement{}, "msRADIUSFramedIPAddress": &FormElement{}, "mS-DS-ReplicatesNCReason": &FormElement{}, "aCSEnableRSVPAccounting": &FormElement{}, "fRSTimeLastConfigChange": &FormElement{}, "printStaplingSupported": &FormElement{}, "interSiteTopologyRenew": &FormElement{}, "operatingSystemVersion": &FormElement{}, "otherLoginWorkstations": &FormElement{}, "netbootAllowNewClients": &FormElement{}, "mS-SQL-UnicodeSortOrder": &FormElement{}, "url": &FormElement{}, "pKT": &FormElement{}, "serviceInstanceVersion": &FormElement{}, "showInAdvancedViewOnly": &FormElement{}, "aCSMaxTokenRatePerFlow": &FormElement{}, "isCriticalSystemObject": &FormElement{}, "meetingMaxParticipants": &FormElement{}, "aNR": &FormElement{}, "rid": &FormElement{}, "proxyGenerationEnabled": &FormElement{}, "fRSControlDataCreation": &FormElement{}, "previousCACertificates": &FormElement{}, "contentIndexingAllowed": &FormElement{}, "policyReplicationFlags": &FormElement{}, "frsComputerReferenceBL": &FormElement{}, "aCSNonReservedPeakRate": &FormElement{}, "aCSMaxNoOfAccountFiles": &FormElement{}, "physicalLocationObject": &FormElement{}, "mSMQOutRoutingServers": &FormElement{}, "bridgeheadServerListBL": &FormElement{}, "msRADIUSCallbackNumber": &FormElement{}, "netbootMachineFilePath": &FormElement{}, "mSMQQueueJournalQuota": &FormElement{}, "netbootAnswerRequests": &FormElement{}, "operatingSystemHotfix": &FormElement{}, "attributeSecurityGUID": &FormElement{}, "superScopeDescription": &FormElement{}, "otherWellKnownObjects": &FormElement{}, "aCSNonReservedTxLimit": &FormElement{}, "authenticationOptions": &FormElement{}, "altSecurityIdentities": &FormElement{}, "gPCUserExtensionNames": &FormElement{}, "netbootInitialization": &FormElement{}, "mS-SQL-RegisteredOwner": &FormElement{}, "aCSMaxDurationPerFlow": &FormElement{}, "pKICriticalExtensions": &FormElement{}, "attributeDisplayNames": &FormElement{}, "mS-SQL-AllowImmediateUpdatingSubscription": &FormElement{}, "msRASSavedFramedRoute": &FormElement{}, "userSharedFolderOther": &FormElement{}, "extendedAttributeInfo": &FormElement{}, "netbootMirrorDataFile": &FormElement{}, "aCSMinimumPolicedSize": &FormElement{}, "localizationDisplayId": &FormElement{}, "meetingAdvertiseScope": &FormElement{}, "dSUIAdminNotification": &FormElement{}, "mS-SQL-LastUpdatedDate": &FormElement{}, "dSCorePropagationData": &FormElement{}, "implementedCategories": &FormElement{}, "defaultObjectCategory": &FormElement{}, "domainPolicyReference": &FormElement{}, "mSMQInRoutingServers": &FormElement{}, "printDuplexSupported": &FormElement{}, "pendingCACertificates": &FormElement{}, "nTSecurityDescriptor": &FormElement{}, "systemAuxiliaryClass": &FormElement{}, "aCSNonReservedTxSize": &FormElement{}, "mS-SQL-InformationURL": &FormElement{}, "replPropertyMetaData": &FormElement{}, "mS-SQL-PublicationURL": &FormElement{}, "printKeepPrintedJobs": &FormElement{}, "uSNDSALastObjRemoved": &FormElement{}, "dnsNotifySecondaries": &FormElement{}, "mS-DS-ConsistencyGuid": &FormElement{}, "frsComputerReference": &FormElement{}, "mS-SQL-ServiceAccount": &FormElement{}, "msNPCallingStationID": &FormElement{}, "mSMQSignCertificates": &FormElement{}, "ipsecOwnersReference": &FormElement{}, "builtinModifiedCount": &FormElement{}, "privilegeDisplayName": &FormElement{}, "dnsSecureSecondaries": &FormElement{}, "localizedDescription": &FormElement{}, "systemPossSuperiors": &FormElement{}, "displayNamePrintable": &FormElement{}, "servicePrincipalName": &FormElement{}, "pekKeyChangeInterval": &FormElement{}, "originalDisplayTable": &FormElement{}, "mS-SQL-LastBackupDate": &FormElement{}, "ipsecPolicyReference": &FormElement{}, "certificateTemplates": &FormElement{}, "hasPartialReplicaNCs": &FormElement{}, "localPolicyReference": &FormElement{}, "extendedCharsAllowed": &FormElement{}, "ipsecFilterReference": &FormElement{}, "ipsecISAKMPReference": &FormElement{}, "fRSMemberReferenceBL": &FormElement{}, "rpcNsTransferSyntax": &FormElement{}, "mSMQRoutingServices": &FormElement{}, "mS-SQL-MultiProtocol": &FormElement{}, "enrollmentProviders": &FormElement{}, "printNetworkAddress": &FormElement{}, "msRADIUSServiceType": &FormElement{}, "printPagesPerMinute": &FormElement{}, "printMediaSupported": &FormElement{}, "signatureAlgorithms": &FormElement{}, "fRSPartnerAuthLevel": &FormElement{}, "privilegeAttributes": &FormElement{}, "partialAttributeSet": &FormElement{}, "netbootLimitClients": &FormElement{}, "mS-SQL-ConnectionURL": &FormElement{}, "mS-SQL-AllowSnapshotFilesFTPDownloading": &FormElement{}, "pKIExpirationPeriod": &FormElement{}, "nonSecurityMemberBL": &FormElement{}, "initialAuthOutgoing": &FormElement{}, "msRADIUSFramedRoute": &FormElement{}, "controlAccessRights": &FormElement{}, ////////////////////////// // SCHEMA: nis.schema "uidNumber": &FormElement{ Description: "An integer uniquely identifying a user in an administrative domain", Order: 9, }, "gidNumber": &FormElement{ Description: "An integer uniquely identifying a group in an administrative domain", Order: 9, }, "gecos": &FormElement{ Description: "The GECOS field; the common name", }, "homeDirectory": &FormElement{ Description: "The absolute path to the home directory", Order: 6, Datalist: []string{ "/home/", "/home/{user}", }, }, "loginShell": &FormElement{ Description: "The path to the login shell", Order: 6, Datalist: []string{ "/bin/bash", "/bin/false", "/bin/sh", }, }, "shadowLastChange": &FormElement{}, "shadowMin": &FormElement{}, "shadowMax": &FormElement{}, "shadowWarning": &FormElement{}, "shadowInactive": &FormElement{}, "shadowExpire": &FormElement{}, "shadowFlag": &FormElement{}, "memberUid": &FormElement{}, "memberNisNetgroup": &FormElement{}, "nisNetgroupTriple": &FormElement{ Description: "Netgroup triple", }, "ipServicePort": &FormElement{}, "ipServiceProtocol": &FormElement{}, "ipProtocolNumber": &FormElement{}, "oncRpcNumber": &FormElement{}, "ipHostNumber": &FormElement{ Description: "IP address", }, "ipNetworkNumber": &FormElement{ Description: "IP network", }, "ipNetmaskNumber": &FormElement{ Description: "IP netmask", }, "macAddress": &FormElement{ Description: "MAC address", }, "bootParameter": &FormElement{ Description: "Rpc.bootparamd parameter", }, "bootFile": &FormElement{ Description: "Boot image name", }, "nisMapName": &FormElement{}, "nisMapEntry": &FormElement{}, ////////////////////////// // SCHEMA: openldap.schema ////////////////////////// // SCHEMA: pmi.schema "role": &FormElement{ Description: "X.509 Role attribute, use ;binary", }, "xmlPrivilegeInfo": &FormElement{ Description: "X.509 XML privilege information attribute", }, "attributeCertificateAttribute": &FormElement{ Description: "X.509 Attribute certificate attribute, use ;binary", }, "aACertificate": &FormElement{ Description: "X.509 AA certificate attribute, use ;binary", }, "attributeDescriptorCertificate": &FormElement{ Description: "X.509 Attribute descriptor certificate attribute, use ;binary", }, "attributeCertificateRevocationList": &FormElement{ Description: "X.509 Attribute certificate revocation list attribute, use ;binary", }, "attributeAuthorityRevocationList": &FormElement{ Description: "X.509 AA certificate revocation list attribute, use ;binary", }, "delegationPath": &FormElement{ Description: "X.509 Delegation path attribute, use ;binary", }, "privPolicy": &FormElement{ Description: "X.509 Privilege policy attribute, use ;binary", }, "protPrivPolicy": &FormElement{ Description: "X.509 Protected privilege policy attribute, use ;binary", }, "xmlPrivPolicy": &FormElement{ Description: "X.509 XML Protected privilege policy attribute", }, ////////////////////////// // SCHEMA: ppolicy.schema "pwdAttribute": &FormElement{ Description: "Name of the attribute to which the password policy is applied. For example, the password policy may be applied to the userPassword attribute", }, "pwdMinAge": &FormElement{ Description: "Number of seconds that must elapse between modifications to the password. If this attribute is not present, 0 seconds is assumed.", }, "pwdMaxAge": &FormElement{ Description: "Number of seconds after which a modified password will expire. If this attribute is not present, or if the value is 0 the password does not expire. If not 0, the value must be greater than or equal to the value of the pwdMinAge.", }, "pwdInHistory": &FormElement{ Description: "Maximum number of used passwords stored in the pwdHistory attribute. If this attribute is not present, or if the value is 0, used passwords are not stored in the pwdHistory attribute and thus may be reused.", }, "pwdCheckQuality": &FormElement{ Description: "Indicates how the password quality will be verified while being modified or added", }, "pwdMinLength": &FormElement{ Description: "When quality checking is enabled, this attribute holds the minimum number of characters that must be used in a password. If this attribute is not present, no minimum password length will be enforced. If the server is unable to check the length (due to a hashed password or otherwise), the server will, depending on the value of the pwdCheckQuality attribute, either accept the password without checking it ('0' or '1') or refuse it ('2').", }, "pwdExpireWarning": &FormElement{ Description: "specifies the maximum number of seconds before a password is due to expire that expiration warning messages will be returned to an authenticating user. If this attribute is not present, or if the value is 0 no warnings will be returned. If not 0, the value must be smaller than the value of the pwdMaxAge attribute.", }, "pwdGraceAuthNLimit": &FormElement{ Description: "This attribute specifies the number of times an expired password can be used to authenticate. If this attribute is not present or if the value is 0, authentication will fail.", }, "pwdLockout": &FormElement{ Description: "This attribute indicates, when its value is \"TRUE\", that the password may not be used to authenticate after a specified number of consecutive failed bind attempts. The maximum number of consecutive failed bind attempts is specified in pwdMaxFailure. If this attribute is not present, or if the value is \"FALSE\", the password may be used to authenticate when the number of failed bind attempts has been reached.", }, "pwdLockoutDuration": &FormElement{ Description: "This attribute holds the number of seconds that the password cannot be used to authenticate due to too many failed bind attempts. If this attribute is not present, or if the value is 0 the password cannot be used to authenticate until reset by a password administrator.", }, "pwdMaxFailure": &FormElement{ Description: "This attribute specifies the number of consecutive failed bind attempts after which the password may not be used to authenticate. If this attribute is not present, or if the value is 0, this policy is not checked, and the value of pwdLockout will be ignored.", }, "pwdFailureCountInterval": &FormElement{ Description: "This attribute holds the number of seconds after which the password failures are purged from the failure counter, even though no successful authentication occurred. If this attribute is not present, or if its value is 0, the failure counter is only reset by a successful authentication.", }, "pwdMustChange": &FormElement{ Description: "This attribute specifies with a value of \"TRUE\" that users must change their passwords when they first bind to the directory after a password is set or reset by a password administrator. If this attribute is not present, or if the value is \"FALSE\", users are not required to change their password upon binding after the password administrator sets or resets the password. This attribute is not set due to any actions specified by this document, it is typically set by a password administrator after resetting a user's password.", }, "pwdAllowUserChange": &FormElement{ Description: "This attribute indicates whether users can change their own passwords, although the change operation is still subject to access control. If this attribute is not present, a value of \"TRUE\" is assumed. This attribute is intended to be used in the absence of an access control mechanism.", }, "pwdSafeModify": &FormElement{ Description: "This attribute specifies whether or not the existing password must be sent along with the new password when being changed. If this attribute is not present, a \"FALSE\" value is assumed.", }, "pwdMaxRecordedFailure": &FormElement{ Description: "This attribute specifies the maximum number of consecutive failed bind attempts to record. If this attribute is not present, or if the value is 0, it defaults to the value of pwdMaxFailure. If that value is also 0, this value defaults to 5.", }, "pwdCheckModule": &FormElement{ Description: "Loadable module that instantiates check_password() function. This attribute names a user-defined loadable module that provides a check_password() function. If pwdCheckQuality is set to '1' or '2' this function will be called after all of the internal password quality checks have been passed. The function has this prototype: int check_password( char *password, char **errormessage, void *arg ) The function should return LDAP_SUCCESS for a valid password.", }, } ================================================ FILE: server/plugin/plg_backend_local/index.go ================================================ package plg_backend_local import ( . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" "io" "os" "os/user" ) func init() { Backend.Register("local", &Local{}) } type Local struct{} func (this Local) Init(params map[string]string, app *App) (IBackend, error) { backend := &Local{} if params["password"] == Config.Get("general.secret_key").String() { return backend, nil } else if err := bcrypt.CompareHashAndPassword( []byte(Config.Get("auth.admin").String()), []byte(params["password"]), ); err == nil { return backend, nil } return nil, ErrAuthenticationFailed } func (this Local) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "local", }, { Name: "password", Type: "password", Placeholder: "Admin Password", }, { Name: "path", Type: "text", Placeholder: "Path", }, }, } } func (this Local) Home() (string, error) { if home, err := os.UserHomeDir(); err == nil { return ensurePath(home), nil } if currentUser, err := user.Current(); err == nil && currentUser.HomeDir != "" { return ensurePath(currentUser.HomeDir), nil } return "/", nil } func ensurePath(path string) string { if _, err := os.Stat(path); err != nil { return "/" } return path } func (this Local) Ls(path string) ([]os.FileInfo, error) { f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } files, err := f.Readdir(-1) if err != nil { f.Close() return nil, err } return files, f.Close() } func (this Local) Stat(path string) (os.FileInfo, error) { f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } finfo, err := f.Stat() if err != nil { f.Close() return nil, err } return finfo, f.Close() } func (this Local) Cat(path string) (io.ReadCloser, error) { f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } fs, err := f.Stat() if err != nil { f.Close() return nil, err } else if fs.IsDir() { f.Close() return nil, ErrNotFound } return f, nil } func (this Local) Mkdir(path string) error { return SafeOsMkdir(path, 0755) } func (this Local) Rm(path string) error { return SafeOsRemoveAll(path) } func (this Local) Mv(from, to string) error { return SafeOsRename(from, to) } func (this Local) Save(path string, content io.Reader) error { f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664) if err != nil { return err } if _, err = io.Copy(f, content); err != nil { f.Close() return err } return f.Close() } func (this Local) Touch(path string) error { f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return err } if _, err = f.Write([]byte("")); err != nil { f.Close() return err } return f.Close() } ================================================ FILE: server/plugin/plg_backend_mysql/index.go ================================================ package plg_backend_mysql import ( "database/sql" "encoding/json" "fmt" _ "github.com/go-sql-driver/mysql" . "github.com/mickael-kerjean/filestash/server/common" "io" "os" "regexp" "sort" "strconv" "strings" "time" ) type Mysql struct { params map[string]string db *sql.DB } func init() { Backend.Register("mysql", Mysql{}) } func (this Mysql) Init(params map[string]string, app *App) (IBackend, error) { if params["host"] == "" { params["host"] = "127.0.0.1" } if params["port"] == "" { params["port"] = "3306" } db, err := sql.Open( "mysql", fmt.Sprintf( "%s:%s@tcp(%s:%s)/", params["username"], params["password"], params["host"], params["port"], ), ) if err != nil { return nil, err } return Mysql{ params: params, db: db, }, nil } func (this Mysql) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "mysql", }, FormElement{ Name: "host", Type: "text", Placeholder: "Host", }, FormElement{ Name: "username", Type: "text", Placeholder: "Username", }, FormElement{ Name: "password", Type: "password", Placeholder: "Password", }, FormElement{ Name: "port", Type: "number", Placeholder: "Port", }, }, } } func (this Mysql) Ls(path string) ([]os.FileInfo, error) { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return nil, err } files := make([]os.FileInfo, 0) if location.db == "" { // first level folder = a list all the available databases rows, err := this.db.Query("SELECT s.schema_name, t.update_time, t.create_time FROM information_schema.SCHEMATA as s LEFT JOIN ( SELECT table_schema, MAX(update_time) as update_time, MAX(create_time) as create_time FROM information_schema.tables GROUP BY table_schema ) as t ON s.schema_name = t.table_schema ORDER BY schema_name") if err != nil { return nil, err } for rows.Next() { var name string var create string var rcreate sql.RawBytes var update string var rupdate sql.RawBytes if err := rows.Scan(&name, &rcreate, &rupdate); err != nil { return nil, err } create = string(rcreate) update = string(rupdate) files = append(files, File{ FName: name, FType: "directory", FTime: func() int64 { var t time.Time var err error if create == "" && update == "" { return 0 } else if update == "" { if t, err = time.Parse("2006-01-02 15:04:05", create); err != nil { return 0 } return t.Unix() } if t, err = time.Parse("2006-01-02 15:04:05", update); err != nil { return 0 } return t.Unix() }(), }) } return files, nil } else if location.table == "" { // second level folder = a list of all the tables available in a database rows, err := this.db.Query("SELECT table_name, create_time, update_time FROM information_schema.tables WHERE table_schema = ?", location.db) if err != nil { return nil, err } for rows.Next() { var name string var create string var rcreate sql.RawBytes var update string var rupdate sql.RawBytes if err := rows.Scan(&name, &rcreate, &rupdate); err != nil { return nil, err } create = string(rcreate) update = string(rupdate) files = append(files, File{ FName: name, FType: "directory", FTime: func() int64 { var t time.Time var err error if create == "" && update == "" { return 0 } else if update == "" { if t, err = time.Parse("2006-01-02 15:04:05", create); err != nil { return 0 } return t.Unix() } if t, err = time.Parse("2006-01-02 15:04:05", update); err != nil { return 0 } return t.Unix() }(), }) } return files, nil } else if location.row == "" { // third level folder = a list of all the available rows within the selected table sqlFields, err := FindQuerySelection(this.db, location) if err != nil { return nil, err } extractSingleName := func(s QuerySelection) string { return s.Name } extractName := func(s []QuerySelection) []string { t := make([]string, 0, len(s)) for i := range s { t = append(t, extractSingleName(s[i])) } return t } extractNamePlus := func(s []QuerySelection) []string { t := make([]string, 0, len(s)) for i := range s { t = append(t, "IFNULL("+extractSingleName(s[i])+", '')") } return t } rows, err := this.db.Query(fmt.Sprintf( "SELECT CONCAT(%s) as filename %sFROM %s.%s %s LIMIT 500000", func() string { q := strings.Join(extractNamePlus(sqlFields.Select), ", ' - ', ") if len(sqlFields.Esthetics) != 0 { q += ", ' - ', " + strings.Join(extractNamePlus(sqlFields.Esthetics), ", ' ', ") } return q }(), func() string { if extractSingleName(sqlFields.Date) != "" { return ", " + extractSingleName(sqlFields.Date) + " as date " } return "" }(), location.db, location.table, func() string { if len(sqlFields.Order) != 0 { return "ORDER BY " + strings.Join(extractName(sqlFields.Order), ", ") + " DESC " } return "" }(), )) if err != nil { return nil, err } for rows.Next() { var name_raw sql.RawBytes var date sql.RawBytes if extractSingleName(sqlFields.Date) == "" { if err := rows.Scan(&name_raw); err != nil { return nil, err } } else { if err := rows.Scan(&name_raw, &date); err != nil { return nil, err } } files = append(files, File{ FName: string(name_raw) + ".form", FType: "file", FSize: -1, FTime: func() int64 { t, err := time.Parse("2006-01-02", fmt.Sprintf("%s", date)) if err != nil { return 0 } return t.Unix() }(), }) } return files, nil } return nil, ErrNotValid } func (this Mysql) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this Mysql) Cat(path string) (io.ReadCloser, error) { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return nil, err } else if location.db == "" || location.table == "" || location.row == "" { return nil, ErrNotValid } // STEP 1: Perform the database query fields, err := FindQuerySelection(this.db, location) if err != nil { return nil, err } whereSQL, whereParams := sqlWhereClause(fields, location) query := fmt.Sprintf( "SELECT * FROM %s.%s WHERE %s", location.db, location.table, whereSQL, ) rows, err := this.db.Query(query, whereParams...) if err != nil { return nil, err } columnsName, err := rows.Columns() if err != nil { return nil, err } // STEP 2: find potential foreign key on given results // those will be shown as a list of possible choice columnsChoice, err := FindForeignKeysChoices(this.db, location) if err != nil { return nil, err } // STEP 3: Encode the result of the query into a form object var forms []FormElement = []FormElement{} dummy := make([]interface{}, len(columnsName)) columnPointers := make([]interface{}, len(columnsName)) for i := range columnsName { columnPointers[i] = &dummy[i] } for rows.Next() { if err := rows.Scan(columnPointers...); err != nil { return nil, err } break } for i := range columnsName { if pval, ok := columnPointers[i].(*interface{}); ok { if pval == nil { continue } el := FormElement{ Name: columnsName[i], Type: "text", } switch fields.All[columnsName[i]].Type { case "int": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "integer": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "decimal": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "dec": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "float": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "double": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "tinyint": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "smallint": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "mediumint": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "bigint": el.Value = fmt.Sprintf("%d", *pval) el.Type = "number" case "enum": el.Type = "select" reg := regexp.MustCompile(`^'(.*)'$`) el.Opts = func() []string { r := strings.Split(strings.TrimSuffix(strings.TrimPrefix(fields.All[columnsName[i]].RawType, "enum("), ")"), ",") for i := 0; i < len(r); i++ { r[i] = reg.ReplaceAllString(r[i], `$1`) } return r }() el.Value = fmt.Sprintf("%s", *pval) case "datetime": el.Value = strings.Replace(fmt.Sprintf("%s", *pval), " ", "T", 1) el.Type = "datetime" case "timestamp": el.Value = strings.Replace(fmt.Sprintf("%s", *pval), " ", "T", 1) el.Type = "datetime" case "date": el.Value = fmt.Sprintf("%s", *pval) el.Type = "date" case "text": el.Value = fmt.Sprintf("%s", *pval) el.Type = "long_text" case "longblob": el.Value = fmt.Sprintf("%s", *pval) el.Type = "file" case "mediumblob": el.Value = fmt.Sprintf("%s", *pval) el.Type = "file" case "tinnyblob": el.Value = fmt.Sprintf("%s", *pval) el.Type = "file" default: el.Value = fmt.Sprintf("%s", *pval) } if *pval == nil { el.Value = "" } if choices, ok := columnsChoice[columnsName[i]]; ok { el.Type = "text" el.MultiValue = false el.Datalist = choices if l, err := FindWhoOwns(this.db, DBLocation{location.db, location.table, columnsName[i]}); err == nil { el.Description = fmt.Sprintf( "Relates to object in %s", generateLink(this.params["path"], l, el.Value), ) } } else if key := fields.All[columnsName[i]].Key; key == "PRI" { locations, err := FindWhoIsUsing(this.db, location) if err != nil { return nil, err } if len(locations) > 0 { text := []string{} for i := 0; i < len(locations); i++ { text = append( text, fmt.Sprintf( "%s (%d)", generateLink(this.params["path"], DBLocation{locations[i].db, locations[i].table, locations[i].row}, el.Value), FindHowManyOccurenceOfaValue(this.db, locations[i], el.Value), ), ) } el.Description = "Used in " + strings.Join(text, ", ") } } forms = append(forms, el) } } // STEP 3: Send the form back to the user b, err := Form{Elmnts: forms}.MarshalJSON() if err != nil { return nil, err } return NewReadCloserFromBytes(b), nil } func (this Mysql) Mkdir(path string) error { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return err } if location.db != "" && location.table == "" && location.row == "" { _, err = this.db.Exec(fmt.Sprintf("CREATE DATABASE %s", strings.TrimPrefix(location.db, "CREATE DATABASE "))) return err } return ErrNotAllowed } func (this Mysql) Rm(path string) error { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return err } if location.db == "" { return ErrNotValid } else if location.table == "" { _, err := this.db.Exec(fmt.Sprintf("DROP DATABASE %s", location.db)) return err } else if location.row == "" { _, err := this.db.Exec(fmt.Sprintf("DROP TABLE %s.%s", location.db, location.table)) return err } fields, err := FindQuerySelection(this.db, location) if err != nil { return err } whereSQL, whereParams := sqlWhereClause(fields, location) query := fmt.Sprintf( "DELETE FROM %s.%s WHERE %s", location.db, location.table, whereSQL, ) _, err = this.db.Exec(query, whereParams...) return err } func (this Mysql) Mv(from string, to string) error { defer this.db.Close() return ErrNotValid } func (this Mysql) Touch(path string) error { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return err } if location.db == "" { return ErrNotValid } else if location.table == "" { return ErrNotValid } else if location.row == "" { return ErrNotValid } fields, err := FindQuerySelection(this.db, location) if err != nil { return err } query := fmt.Sprintf( "INSERT INTO %s.%s (%s) VALUES(%s)", location.db, location.table, func() string { values := []string{} for i := range fields.Select { values = append(values, fields.Select[i].Name) } return strings.Join(values, ",") }(), func() string { values := make([]string, len(fields.Select)) for i := range values { values[i] = "?" } return strings.Join(values, ",") }(), ) queryValues := func() []interface{} { valuesOfQuery := make([]interface{}, 0, len(fields.Select)) valuesFromInput := strings.Split(location.row, " - ") for i := range fields.Select { if i < len(valuesFromInput) { valuesOfQuery = append(valuesOfQuery, valuesFromInput[i]) } else { if t := fields.Select[i].Type; t == "int" || t == "integer" || t == "dec" || t == "double" || t == "float" || t == "smallint" || t == "mediumint" || t == "bigint" { valuesOfQuery = append(valuesOfQuery, 0) } else if t == "datetime" || t == "date" || t == "timestamp" { valuesOfQuery = append(valuesOfQuery, time.Now()) } else { valuesOfQuery = append(valuesOfQuery, "") } } } return valuesOfQuery }() _, err = this.db.Exec(query, queryValues...) return err } type SqlKeyParams struct { Key string Value interface{} } func (this Mysql) Save(path string, file io.Reader) error { defer this.db.Close() location, err := NewDBLocation(path) if err != nil { return err } if location.db == "" || location.table == "" || location.row == "" { return ErrNotValid } sqlFields, err := FindQuerySelection(this.db, location) if err != nil { return err } var data map[string]FormElement if err := json.NewDecoder(file).Decode(&data); err != nil { return err } var d []SqlKeyParams = make([]SqlKeyParams, 0) for key, value := range data { d = append(d, SqlKeyParams{key, value.Value}) } whereSQL, whereParams := sqlWhereClause(sqlFields, location) setParams := make([]interface{}, 0, len(data)) for _, v := range d { setParams = append(setParams, v.Value) } _, err = this.db.Exec(fmt.Sprintf( "UPDATE %s.%s SET %s WHERE %s", location.db, location.table, func() string { a := make([]string, 0, len(data)) for _, v := range d { a = append(a, fmt.Sprintf("%s = ?", v.Key)) } return strings.Join(a, ", ") }(), whereSQL, ), append(setParams, whereParams...)...) if err != nil { return err } return nil } func (this Mysql) Meta(path string) Metadata { location, _ := NewDBLocation(path) return Metadata{ CanCreateDirectory: func(l DBLocation) *bool { if l.db == "" && l.table == "" && l.row == "" { return NewBool(true) } return NewBool(false) }(location), CanCreateFile: func(l DBLocation) *bool { if l.table == "" || l.db == "" { return NewBool(false) } return NewBool(true) }(location), CanRename: NewBool(false), CanMove: NewBool(false), RefreshOnCreate: NewBool(true), HideExtension: NewBool(true), } } type DBLocation struct { db string table string row string } func NewDBLocation(path string) (DBLocation, error) { var location DBLocation p := strings.Split(strings.Trim(path, "/"), "/") isValid := func(str string) bool { // https://dev.mysql.com/doc/refman/8.0/en/identifiers.html return regexp.MustCompile(`^[0-9,a-z,A-Z$_]*$`).MatchString(str) } if lPath := len(p); lPath == 0 { return DBLocation{}, nil } else if lPath == 1 { location = DBLocation{ db: p[0], } if isValid(p[0]) == false { return location, ErrNotValid } return location, nil } else if lPath == 2 { location = DBLocation{ db: p[0], table: p[1], } if isValid(p[0]) == false || isValid(p[1]) == false { return location, ErrNotValid } return location, nil } else if lPath == 3 { location = DBLocation{ db: p[0], table: p[1], row: strings.TrimSuffix(p[2], ".form"), } if isValid(p[0]) == false || isValid(p[1]) == false { return location, ErrNotValid } return location, nil } return DBLocation{}, ErrNotValid } type QuerySelection struct { Name string Type string RawType string Size int Key string Nullable bool } type SqlFields struct { Order []QuerySelection Select []QuerySelection Esthetics []QuerySelection Date QuerySelection All map[string]QuerySelection } func sqlWhereClause(s SqlFields, location DBLocation) (string, []interface{}) { where := []string{} queryParams := make([]interface{}, 0) for i := range s.Select { where = append(where, fmt.Sprintf("%s = ?", s.Select[i].Name)) } for i, value := range strings.Split(location.row, " - ") { if i < len(s.Select) { queryParams = append(queryParams, value) } } return strings.Join(where, " AND "), queryParams } func FindQuerySelection(db *sql.DB, location DBLocation) (SqlFields, error) { var queryCandidates []QuerySelection = make([]QuerySelection, 0) var fields SqlFields = SqlFields{ Order: make([]QuerySelection, 0), Select: make([]QuerySelection, 0), Esthetics: make([]QuerySelection, 0), All: make(map[string]QuerySelection, 0), } if location.db == "" || location.table == "" { return fields, ErrNotValid } // STEP 1: extract possible values from the available schema rows, err := db.Query("SELECT IS_NULLABLE, DATA_TYPE, COLUMN_TYPE, COLUMN_NAME, COLUMN_KEY FROM information_schema.COLUMNS WHERE table_schema = ? && table_name = ?", location.db, location.table) if err != nil { return fields, err } for rows.Next() { var data_type string var column_type string var column_name string var column_key string var is_nullable string if err := rows.Scan(&is_nullable, &data_type, &column_type, &column_name, &column_key); err != nil { return fields, err } q := QuerySelection{ Name: column_name, Type: data_type, Size: func() int { if strings.Contains(column_type, "(") && strings.Contains(column_type, ")") { c := regexp.MustCompile("[0-9]+").FindAllString(column_type, -1) if len(c) == 1 { if i, err := strconv.Atoi(c[0]); err == nil { return i } } } return 0 }(), Nullable: func() bool { if is_nullable == "YES" { return true } return false }(), RawType: column_type, Key: column_key, } fields.All[column_name] = q queryCandidates = append(queryCandidates, q) } if len(queryCandidates) == 0 { return fields, ErrNotValid } // STEP 2: filter out unwanted fields from the schema for i := 0; i < len(queryCandidates); i++ { if queryCandidates[i].Key == "PRI" || queryCandidates[i].Key == "UNI" { fields.Select = append(fields.Select, queryCandidates[i]) if queryCandidates[i].Type == "date" { fields.Order = append(fields.Order, queryCandidates[i]) } } else if queryCandidates[i].Type == "varchar" { fields.Esthetics = append(fields.Esthetics, queryCandidates[i]) } if queryCandidates[i].Type == "date" && queryCandidates[i].Nullable == false { fields.Date = queryCandidates[i] } } // STEP 3: Ensure the current selection is workable if len(fields.Select) == 0 { // worst case scenario with no defined keys in the schema, we populate the selection with: // - strategy 1: finding a field that can do the job (essentially COUNT(*) == DISTINCT(COUNT(*))) // - strategy 2: the key is a set of all the available fields (worst worst case) // This can be fairly slow on large tables but that's the cost to pay for bad database design sort.SliceStable(queryCandidates, func(i, j int) bool { if queryCandidates[i].Type == "varchar" && queryCandidates[i].Type != queryCandidates[j].Type { return true } else if queryCandidates[j].Type == "varchar" && queryCandidates[i].Type != queryCandidates[j].Type { return false } else if queryCandidates[i].Type == "char" && queryCandidates[i].Type != queryCandidates[j].Type { return true } else if queryCandidates[j].Type == "char" && queryCandidates[i].Type != queryCandidates[j].Type { return false } return queryCandidates[i].Size < queryCandidates[j].Size }) var size int = 0 var i int = 0 for i = range queryCandidates { query := fmt.Sprintf( "SELECT COUNT(%s), COUNT(DISTINCT(%s)) FROM %s.%s", queryCandidates[i].Name, queryCandidates[i].Name, location.db, location.table, ) size += queryCandidates[i].Size var count_all int var count_distinct int if err := db.QueryRow(query).Scan(&count_all, &count_distinct); err != nil { return fields, err } if count_all == count_distinct { fields.Select = append(fields.Select, queryCandidates[i]) fields.Esthetics = func() []QuerySelection { var i int esthetics := make([]QuerySelection, 0, len(fields.Esthetics)) for i = range fields.Esthetics { if fields.Esthetics[i].Name != queryCandidates[i].Name { esthetics = append(esthetics, queryCandidates[i]) } } return esthetics }() break } } if i == len(queryCandidates)-1 { if size > 200 { return fields, NewError("This table doesn't have any defined keys.", 405) } fields.Select = queryCandidates fields.Esthetics = make([]QuerySelection, 0) } } // STEP 4: organise our finding into a data structure that's usable sortQuerySelection := func(s []QuerySelection) func(i, j int) bool { calculateScore := func(q QuerySelection) int { score := 0 if q.Key == "UNI" { score = 4 } else if q.Key == "PRI" { score = 5 } else { return 0 } if lowerName := strings.ToLower(q.Name); lowerName == "id" || lowerName == "gid" || lowerName == "uid" { score -= 2 } if q.Type == "varchar" || q.Type == "char" { score += 1 } else if q.Type == "date" { score -= 1 } return score } return func(i, j int) bool { return calculateScore(s[i]) > calculateScore(s[j]) } } sort.SliceStable(fields.Select, sortQuerySelection(fields.Select)) sort.SliceStable(fields.Order, sortQuerySelection(fields.Order)) fields.Date.Name = func() string { if len(fields.Order) == 0 { return fields.Date.Name } return fields.Order[0].Name }() fields.Esthetics = func() []QuerySelection { // fields whose only value is to make our generated field look good var size int = 0 var i int for i = range fields.Select { size += fields.Select[i].Size } for i = range fields.Esthetics { s := fields.Esthetics[i].Size if size+s > 100 { break } size += s } if i+1 > len(fields.Esthetics) { return fields.Esthetics } return fields.Esthetics[:i+1] }() return fields, nil } func (this Mysql) Close() error { return this.db.Close() } func FindForeignKeysChoices(db *sql.DB, location DBLocation) (map[string][]string, error) { choices := make(map[string][]string, 0) rows, err := db.Query("SELECT column_name, referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE table_schema = ? AND table_name = ? AND referenced_column_name IS NOT NULL", location.db, location.table) if err != nil { return choices, err } for rows.Next() { var column_name string var referenced_table_schema string var referenced_column_name string if err := rows.Scan(&column_name, &referenced_table_schema, &referenced_column_name); err != nil { return choices, err } r, err := db.Query(fmt.Sprintf("SELECT DISTINCT(%s) FROM %s.%s LIMIT 10000", column_name, location.db, location.table)) if err != nil { return choices, err } var res []string = make([]string, 0) for r.Next() { var value string r.Scan(&value) res = append(res, value) } choices[column_name] = res } return choices, nil } func FindWhoIsUsing(db *sql.DB, location DBLocation) ([]DBLocation, error) { locations := make([]DBLocation, 0) rows, err := db.Query("SELECT table_schema, table_name, column_name FROM information_schema.key_column_usage WHERE referenced_table_schema = ? AND referenced_table_name = ? AND column_name IS NOT NULL", location.db, location.table) if err != nil { return locations, err } for rows.Next() { var table_schema string var table_name string var column_name string if err := rows.Scan(&table_schema, &table_name, &column_name); err != nil { return locations, err } locations = append(locations, DBLocation{ db: table_schema, table: table_name, row: column_name, }) } return locations, nil } func FindWhoOwns(db *sql.DB, location DBLocation) (DBLocation, error) { var referenced_table_schema string var referenced_table_name string var referenced_column_name string if err := db.QueryRow( fmt.Sprintf("SELECT referenced_table_schema, referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE table_schema = ? AND table_name = ? AND column_name = ? AND referenced_column_name IS NOT NULL"), location.db, location.table, location.row, ).Scan(&referenced_table_schema, &referenced_table_name, &referenced_column_name); err != nil { return DBLocation{}, err } return DBLocation{referenced_table_schema, referenced_table_name, referenced_column_name}, nil } func FindHowManyOccurenceOfaValue(db *sql.DB, location DBLocation, value interface{}) int { var count int if err := db.QueryRow( fmt.Sprintf("SELECT COUNT(*) FROM %s.%s WHERE %s = ?", location.db, location.table, location.row), value, ).Scan(&count); err != nil { return 0 } return count } func generateLink(chroot string, l DBLocation, value interface{}) string { chrootLocation, err := NewDBLocation(chroot) if err != nil { return fmt.Sprintf("'%s'", l.table) } if chrootLocation.db == "" { return fmt.Sprintf( "[%s](/files/%s/%s/%s)", l.table, l.db, l.table, func() string { if l.row == "" { return "" } return fmt.Sprintf("?q=%s%%3D%s", l.row, value) }(), ) } else if chrootLocation.table == "" { return fmt.Sprintf( "[%s](/files/%s/%s)", l.table, l.table, func() string { if l.row == "" { return "" } return fmt.Sprintf("?q=%s%%3D%s", l.row, value) }(), ) } else { return fmt.Sprintf("'%s'", l.table) } } ================================================ FILE: server/plugin/plg_backend_nfs/auth_helper.go ================================================ package plg_backend_nfs import ( "bufio" "os" "strconv" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) var ( cacheForEtc AppCache cacheForGroup AppCache ) func init() { cacheForEtc = NewAppCache(120, 60) cacheForGroup = NewAppCache(120, 60) } func ExtractUserInfo(uidHint string, gidHint string, gidsHint string) (uint32, uint32, []GroupLabel) { // case 1: everything is being sent as "uid=number, gid=number and gids=number,number,number" if _uid, err := strconv.Atoi(uidHint); err == nil { var ( uid uint32 = uint32(_uid) gid uint32 gids []GroupLabel ) if _gid, err := strconv.Atoi(gidHint); err == nil { gid = uint32(_gid) } else { gid = uid } for _, t := range strings.Split(gidsHint, ",") { tmp := strings.TrimSpace(t) if gid, err := strconv.Atoi(tmp); err == nil { gids = append(gids, GroupLabel{uint32(gid), tmp, 0}) } } return uid, gid, gids } // case 2: auto detect everything, aka "uid=www-data gid=www-data gids=..." based on uid=www-data if _uid, _gid, err := extractFromEtcPasswd(uidHint); err == nil { return _uid, _gid, extractFromEtcGroup(uidHint, _gid) } // case 3: base case return 0, 0, []GroupLabel{} } func extractFromEtcPasswd(username string) (uint32, uint32, error) { if v := cacheForEtc.Get(map[string]string{"username": username}); v != nil { inCache := v.([]int) return uint32(inCache[0]), uint32(inCache[1]), nil } f, err := os.OpenFile("/etc/passwd", os.O_RDONLY, os.ModePerm) if err != nil { return 0, 0, err } defer f.Close() lines := bufio.NewReader(f) for { line, _, err := lines.ReadLine() if err != nil { break } s := strings.Split(string(line), ":") if len(s) != 7 { continue } else if username != s[0] { continue } u, err := strconv.Atoi(s[2]) if err != nil { continue } g, err := strconv.Atoi(s[3]) if err != nil { continue } cacheForEtc.Set(map[string]string{"username": username}, []int{u, g}) return uint32(u), uint32(g), nil } return 0, 0, ErrNotFound } type GroupLabel struct { Id uint32 Label string Priority int } func extractFromEtcGroup(username string, primary uint32) []GroupLabel { if v := cacheForGroup.Get(map[string]string{"username": username}); v != nil { return v.([]GroupLabel) } f, err := os.OpenFile("/etc/group", os.O_RDONLY, os.ModePerm) if err != nil { return []GroupLabel{} } defer f.Close() gids := []GroupLabel{} lines := bufio.NewReader(f) for { line, _, err := lines.ReadLine() if err != nil { break } s := strings.Split(string(line), ":") if len(s) != 4 { continue } userInGroup := false for _, user := range strings.Split(s[3], ",") { if user == username { userInGroup = true break } } if userInGroup == false { continue } if gid, err := strconv.Atoi(s[2]); err == nil { ugid := uint32(gid) if ugid != primary { gids = append(gids, GroupLabel{ugid, s[0], 0}) } } cacheForGroup.Set(map[string]string{"username": username}, gids) } return gids } ================================================ FILE: server/plugin/plg_backend_nfs/auth_unix.go ================================================ package plg_backend_nfs import ( "bytes" "math/rand" "sort" "strings" "time" "github.com/vmware/go-nfs-client/nfs/rpc" "github.com/vmware/go-nfs-client/nfs/xdr" ) // ref: https://datatracker.ietf.org/doc/html/rfc5531#section-8.2 // so far we only have implemented AUTH_SYS but one day we might want to add support // for RPCSEC_GSS as detailed in https://datatracker.ietf.org/doc/html/rfc2203 type AuthUnix struct { Stamp uint32 Machinename string Uid uint32 Gid uint32 Gids []uint32 } // ref: RFC5531 - page25 func NewAuthUnix(machineName string, uid, gid uint32, gids []GroupLabel, gidsHint string) rpc.Auth { w := new(bytes.Buffer) if len(gids) > 16 { // https://www.rfc-editor.org/rfc/rfc5531.html#page-25 // when the limit of AUTH_UNIX is reached, we want to filter out the // groups that are of less of importance for i, _ := range gids { score := 0 for _, h := range strings.Split(gidsHint, ",") { if strings.Contains(gids[i].Label, strings.TrimSpace(h)) { score += 1 } } gids[i].Priority = score } sort.Slice(gids, func(i, j int) bool { return gids[i].Priority > gids[j].Priority }) gids = gids[0:16] sort.Slice(gids, func(i, j int) bool { return gids[i].Id < gids[j].Id }) } xdr.Write(w, AuthUnix{ Stamp: rand.New(rand.NewSource(time.Now().UnixNano())).Uint32(), Machinename: machineName, Uid: uid, Gid: gid, Gids: toGids(gids), }) return rpc.Auth{ 1, // = AUTH_SYS in RFC5531 w.Bytes(), } } ================================================ FILE: server/plugin/plg_backend_nfs/index.go ================================================ package plg_backend_nfs import ( "io" "os" "path/filepath" "strings" "sync" . "github.com/mickael-kerjean/filestash/server/common" "github.com/vmware/go-nfs-client/nfs" "github.com/vmware/go-nfs-client/nfs/rpc" "github.com/vmware/go-nfs-client/nfs/util" "github.com/vmware/go-nfs-client/nfs/xdr" ) var NfsCache AppCache type NfsShare struct { mount *nfs.Mount v *nfs.Target auth rpc.Auth mu *sync.Mutex wg *sync.WaitGroup uid uint32 gid uint32 gids []uint32 } func init() { Backend.Register("nfs", NfsShare{}) util.DefaultLogger.SetDebug(false) NfsCache = NewAppCache(2, 1) NfsCache.OnEvict(func(key string, value interface{}) { c := value.(*NfsShare) if c == nil { Log.Warning("plg_backend_nfs::nfs is nil on close") return } else if c.wg == nil { c.Close() Log.Warning("plg_backend_nfs::wg is nil on close") return } c.wg.Wait() Log.Debug("plg_backend_nfs::vacuum") c.Close() }) } func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error) { if params["hostname"] == "" { return nil, ErrNotFound } if params["machine_name"] == "" { params["machine_name"] = "Filestash" } params["username"] = params["uid"] if c := NfsCache.Get(params); c != nil { d := c.(*NfsShare) if d == nil { Log.Warning("plg_backend_nfs::nfs is nil on get") return nil, ErrInternal } else if d.wg == nil { Log.Warning("plg_backend_nfs::wg is nil on get") return nil, ErrInternal } d.wg.Add(1) go func() { <-app.Context.Done() d.wg.Done() }() return d, nil } uid, gid, gids := ExtractUserInfo(params["uid"], params["gid"], params["gids"]) Log.Debug("plg_backend_nfs::userInfo user=%s uid=%d gid=%d gids=%v", params["uid"], uid, gid, gids) mount, err := nfs.DialMount(params["hostname"]) if err != nil { return nil, err } auth := NewAuthUnix(params["machine_name"], uid, gid, gids, params["gids"]) v, err := mount.Mount( params["target"], auth, ) if err != nil { return nil, err } s := &NfsShare{mount, v, auth, new(sync.Mutex), new(sync.WaitGroup), uid, gid, toGids(gids)} s.wg.Add(1) go func() { <-app.Context.Done() s.wg.Done() }() NfsCache.Set(params, s) return s, nil } func toGids(gids []GroupLabel) []uint32 { g := make([]uint32, len(gids)) for i, _ := range gids { g[i] = gids[i].Id } return g } func (this NfsShare) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "nfs", }, FormElement{ Name: "hostname", Type: "text", Placeholder: "Hostname", }, FormElement{ Name: "target", Type: "text", Placeholder: "Mount Path", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"nfs_uid", "nfs_gid", "nfs_gids", "nfs_machinename", "nfs_chroot"}, }, FormElement{ Id: "nfs_uid", Name: "uid", Type: "text", Placeholder: "UID", }, FormElement{ Id: "nfs_gid", Name: "gid", Type: "text", Placeholder: "GID", }, FormElement{ Id: "nfs_gids", Name: "gids", Type: "text", Placeholder: "Auxiliary GIDs", }, FormElement{ Id: "nfs_machinename", Name: "machine_name", Type: "text", Placeholder: "Machine Name", }, FormElement{ Id: "nfs_chroot", Name: "path", Type: "text", Placeholder: "Chroot", }, }, } } func (this NfsShare) Meta(path string) Metadata { this.mu.Lock() defer this.mu.Unlock() f, _, err := this.v.Lookup(strings.TrimSuffix(path, "/")) if err != nil { return Metadata{} } else if f == nil { return Metadata{} } fattr, ok := f.(*nfs.Fattr) if ok == false { return Metadata{} } if fattr == nil { // happen at the root return Metadata{} } var ( r, w bool perms = fattr.Mode().Perm() ) if perms&0002 != 0 { w = true } if perms&0004 != 0 { r = true } if fattr.UID == this.uid { if perms&0400 != 0 { r = true } if perms&0200 != 0 { w = true } } if (fattr.GID == this.gid) || isIn(fattr.GID, this.gids) { if perms&0040 != 0 { r = true } if perms&0020 != 0 { w = true } } return Metadata{ CanSee: NewBool(r), CanCreateFile: NewBool(w), CanCreateDirectory: NewBool(w), CanRename: NewBool(w), CanMove: NewBool(w), CanUpload: NewBool(w), CanDelete: NewBool(w), } } func isIn(id uint32, list []uint32) bool { for i, _ := range list { if list[i] == id { return true } } return false } func (this NfsShare) Ls(path string) ([]os.FileInfo, error) { this.mu.Lock() defer this.mu.Unlock() files := make([]os.FileInfo, 0) dirs, err := this.v.ReadDirPlus(path) if err != nil { return files, err } for _, dir := range dirs { if dir.FileName == "." || dir.FileName == ".." { continue } else if dir.Attr.Attr.Type != 1 && dir.Attr.Attr.Type != 2 { // don't show anything else than file and folder continue } if len(this.gids) > 0 { // filter out what users don't have access hasAccess := false for _, gid := range this.gids { if gid == dir.Attr.Attr.GID { hasAccess = true } } if this.gid == dir.Attr.Attr.GID { hasAccess = true } if hasAccess == false { continue } } files = append(files, File{ FName: dir.FileName, FType: func() string { if dir.Attr.Attr.Type == 1 { return "file" } return "directory" }(), FSize: int64(dir.Attr.Attr.Filesize), FTime: int64(dir.Attr.Attr.Ctime.Seconds), }) } return files, nil } func (this NfsShare) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this NfsShare) Cat(path string) (io.ReadCloser, error) { this.mu.Lock() rc, err := this.v.Open(path) this.mu.Unlock() if err != nil { return nil, err } return &nfsReadCloser{rc, this.mu}, nil } type nfsReadCloser struct { io.ReadCloser mu *sync.Mutex } func (r *nfsReadCloser) Read(p []byte) (int, error) { r.mu.Lock() defer r.mu.Unlock() return r.ReadCloser.Read(p) } func (r *nfsReadCloser) Close() error { r.mu.Lock() defer r.mu.Unlock() return r.ReadCloser.Close() } func (this NfsShare) Mkdir(path string) error { this.mu.Lock() defer this.mu.Unlock() _, err := this.v.Mkdir(this.nfsPath(path), 0775) return err } func (this NfsShare) Rm(path string) error { this.mu.Lock() defer this.mu.Unlock() if strings.HasSuffix(path, "/") { return this.v.RemoveAll(this.nfsPath(path)) } return this.v.Remove(path) } // this wasn't implemented in the original lib and considering // PR aren't handled by vmware, we did come with the implementation as // of RFC1813 in: https://www.rfc-editor.org/rfc/rfc1813#section-3.3.14 func (this NfsShare) Mv(from string, to string) error { this.mu.Lock() defer this.mu.Unlock() f, fName := filepath.Split(this.nfsPath(from)) _, fh, err := this.v.Lookup(f) if err != nil { return err } t, tName := filepath.Split(this.nfsPath(to)) _, th, err := this.v.Lookup(t) if err != nil { return err } type RenameArgs struct { rpc.Header From nfs.Diropargs3 To nfs.Diropargs3 } const RENAME3res = 14 res, err := this.v.Call(&RenameArgs{ Header: rpc.Header{ Rpcvers: 2, Prog: nfs.Nfs3Prog, Vers: nfs.Nfs3Vers, Proc: RENAME3res, Cred: this.auth, Verf: rpc.AuthNull, }, From: nfs.Diropargs3{ FH: fh, Filename: fName, }, To: nfs.Diropargs3{ FH: th, Filename: tName, }, }) if err != nil { return err } status, err := xdr.ReadUint32(res) if err != nil { return err } return nfs.NFS3Error(status) } func (this NfsShare) Touch(path string) error { return this.Save(path, strings.NewReader("")) } func (this NfsShare) Save(path string, file io.Reader) error { this.mu.Lock() w, err := this.v.OpenFile(path, 0644) this.mu.Unlock() if err != nil { return err } nw := &nfsWriteCloser{w, this.mu} _, err = io.Copy(nw, file) nw.Close() return err } type nfsWriteCloser struct { io.WriteCloser mu *sync.Mutex } func (w *nfsWriteCloser) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() return w.WriteCloser.Write(p) } func (w *nfsWriteCloser) Close() error { w.mu.Lock() defer w.mu.Unlock() return w.WriteCloser.Close() } func (this NfsShare) Close() { this.v.Close() this.mount.Close() } func (this NfsShare) nfsPath(path string) string { return strings.TrimSuffix(path, "/") } ================================================ FILE: server/plugin/plg_backend_nfs4/index.go ================================================ package plg_backend_nfs4 import ( "context" "io" "os" "strconv" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4/repo/nfs4" ) const DEFAULT_PORT = ":2049" type Nfs4Share struct { client *nfs4.NfsClient ctx context.Context } func init() { Backend.Register("nfs4", Nfs4Share{}) } func (this Nfs4Share) Init(params map[string]string, app *App) (IBackend, error) { if params["hostname"] == "" { return nil, ErrNotFound } var ( uid uint32 = 1000 gid uint32 = 1000 ) if params["uid"] != "" { if _uid, err := strconv.Atoi(params["uid"]); err == nil { uid = uint32(_uid) } } if params["gid"] != "" { if _gid, err := strconv.Atoi(params["gid"]); err == nil { gid = uint32(_gid) } } if params["machine_name"] == "" { params["machine_name"] = APPNAME } if strings.Contains(params["hostname"], ":") == false { params["hostname"] = params["hostname"] + DEFAULT_PORT } client, err := nfs4.NewNfsClient(app.Context, params["hostname"], nfs4.AuthParams{ MachineName: params["machine_name"], Uid: uid, Gid: gid, }) if err != nil { return nil, err } return Nfs4Share{ client, app.Context, }, nil } func (this Nfs4Share) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "nfs4", }, FormElement{ Name: "hostname", Type: "text", Placeholder: "Hostname", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"nfs_uid", "nfs_gid", "nfs_machinename", "nfs_chroot"}, }, FormElement{ Id: "nfs_uid", Name: "uid", Type: "number", Placeholder: "uid", }, FormElement{ Id: "nfs_gid", Name: "gid", Type: "number", Placeholder: "gid", }, FormElement{ Id: "nfs_machinename", Name: "machine_name", Type: "text", Placeholder: "machine name", }, FormElement{ Id: "nfs_chroot", Name: "path", Type: "text", Placeholder: "chroot", }, }, } } func (this Nfs4Share) Ls(path string) ([]os.FileInfo, error) { list, err := this.client.GetFileList(path) if err != nil { return nil, err } files := make([]os.FileInfo, 0) for _, info := range list { files = append(files, File{ FName: info.Name, FType: func() string { if info.IsDir { return "directory" } return "file" }(), FSize: int64(info.Size), FTime: int64(info.Mtime.Nanosecond()), }) } return files, nil } func (this Nfs4Share) Stat(path string) (os.FileInfo, error) { finfo, err := this.client.GetFileInfo(path) if err != nil { return nil, err } return File{ FName: finfo.Name, FType: func() string { if finfo.IsDir { return "directory" } return "file" }(), FSize: int64(finfo.Size), FTime: int64(finfo.Mtime.Nanosecond()), }, nil } func (this Nfs4Share) Cat(path string) (io.ReadCloser, error) { _, err := this.client.GetFileInfo(path) if err != nil { return nil, err } pr, pw := io.Pipe() go func() { _, _ = this.client.ReadFileAll(path, pw) pw.Close() }() return pr, nil } func (this Nfs4Share) Mkdir(path string) error { return this.client.MakePath(path) } func (this Nfs4Share) Rm(path string) error { if strings.HasSuffix(path, "/") { return nfs4.RemoveRecursive(this.client, path) } return this.client.DeleteFile(path) } func (this Nfs4Share) Mv(from string, to string) error { return ErrNotImplemented } func (this Nfs4Share) Touch(path string) error { _, err := this.client.WriteFile(path, false, 0, strings.NewReader("")) return err } func (this Nfs4Share) Save(path string, file io.Reader) error { _, err := this.client.ReWriteFile(path, file) return err } func (this Nfs4Share) Close() { this.client.Close() return } ================================================ FILE: server/plugin/plg_backend_nfs4/repo/README.md ================================================ TODO: we can't go get github.com/kha7iq/go-nfs-client => fork over and fix it instead ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/cleanuper.go ================================================ package internal type cleanuper struct { cleanupErr func() error cleanup func() } func NewCleanupErr(cl func() error) *cleanuper { return &cleanuper{cleanupErr: cl} } func NewCleanup(cl func()) *cleanuper { return &cleanuper{cleanup: cl} } func (c *cleanuper) Disarm() { c.cleanupErr = nil c.cleanup = nil } func (c *cleanuper) Cleanup() { if c.cleanupErr != nil { _ = c.cleanupErr() } if c.cleanup != nil { c.cleanup() } } ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/nfs4.go ================================================ // Code generated by goxdr -B -p nfs4 nfs4/nfs4.x; DO NOT EDIT. package internal import( "bytes" "context" "encoding/binary" "fmt" "io" "math" "strings" ) var _ = fmt.Sprintf var _ context.Context // // Data types defined in XDR file // /* * Sizes */ const NFS4_FHSIZE = 128 const NFS4_VERIFIER_SIZE = 8 const NFS4_OPAQUE_LIMIT = 1024 const NFS4_SESSIONID_SIZE = 16 /* * File types */ type Nfs_ftype4 int32 const ( /* Regular File */ NF4REG Nfs_ftype4 = 1 /* Directory */ NF4DIR Nfs_ftype4 = 2 /* Special File - block device */ NF4BLK Nfs_ftype4 = 3 /* Special File - character device */ NF4CHR Nfs_ftype4 = 4 /* Symbolic Link */ NF4LNK Nfs_ftype4 = 5 /* Special File - socket */ NF4SOCK Nfs_ftype4 = 6 /* Special File - fifo */ NF4FIFO Nfs_ftype4 = 7 /* Attribute Directory */ NF4ATTRDIR Nfs_ftype4 = 8 /* Named Attribute */ NF4NAMEDATTR Nfs_ftype4 = 9 ) /* * Error status */ type Nfsstat4 int32 const ( /* everything is okay */ NFS4_OK Nfsstat4 = 0 /* caller not privileged */ NFS4ERR_PERM Nfsstat4 = 1 /* no such file/directory */ NFS4ERR_NOENT Nfsstat4 = 2 /* hard I/O error */ NFS4ERR_IO Nfsstat4 = 5 /* no such device */ NFS4ERR_NXIO Nfsstat4 = 6 /* access denied */ NFS4ERR_ACCESS Nfsstat4 = 13 /* file already exists */ NFS4ERR_EXIST Nfsstat4 = 17 /* different filesystems */ NFS4ERR_XDEV Nfsstat4 = 18 /* should be a directory */ NFS4ERR_NOTDIR Nfsstat4 = 20 /* should not be directory */ NFS4ERR_ISDIR Nfsstat4 = 21 /* invalid argument */ NFS4ERR_INVAL Nfsstat4 = 22 /* file exceeds server max */ NFS4ERR_FBIG Nfsstat4 = 27 /* no space on filesystem */ NFS4ERR_NOSPC Nfsstat4 = 28 /* read-only filesystem */ NFS4ERR_ROFS Nfsstat4 = 30 /* too many hard links */ NFS4ERR_MLINK Nfsstat4 = 31 /* name exceeds server max */ NFS4ERR_NAMETOOLONG Nfsstat4 = 63 /* directory not empty */ NFS4ERR_NOTEMPTY Nfsstat4 = 66 /* hard quota limit reached*/ NFS4ERR_DQUOT Nfsstat4 = 69 /* file no longer exists */ NFS4ERR_STALE Nfsstat4 = 70 /* Illegal filehandle */ NFS4ERR_BADHANDLE Nfsstat4 = 10001 /* READDIR cookie is stale */ NFS4ERR_BAD_COOKIE Nfsstat4 = 10003 /* operation not supported */ NFS4ERR_NOTSUPP Nfsstat4 = 10004 /* response limit exceeded */ NFS4ERR_TOOSMALL Nfsstat4 = 10005 /* undefined server error */ NFS4ERR_SERVERFAULT Nfsstat4 = 10006 /* type invalid for CREATE */ NFS4ERR_BADTYPE Nfsstat4 = 10007 /* file "busy" - retry */ NFS4ERR_DELAY Nfsstat4 = 10008 /* nverify says attrs same */ NFS4ERR_SAME Nfsstat4 = 10009 /* lock unavailable */ NFS4ERR_DENIED Nfsstat4 = 10010 /* lock lease expired */ NFS4ERR_EXPIRED Nfsstat4 = 10011 /* I/O failed due to lock */ NFS4ERR_LOCKED Nfsstat4 = 10012 /* in grace period */ NFS4ERR_GRACE Nfsstat4 = 10013 /* filehandle expired */ NFS4ERR_FHEXPIRED Nfsstat4 = 10014 /* share reserve denied */ NFS4ERR_SHARE_DENIED Nfsstat4 = 10015 /* wrong security flavor */ NFS4ERR_WRONGSEC Nfsstat4 = 10016 /* clientid in use */ NFS4ERR_CLID_INUSE Nfsstat4 = 10017 /* resource exhaustion */ NFS4ERR_RESOURCE Nfsstat4 = 10018 /* filesystem relocated */ NFS4ERR_MOVED Nfsstat4 = 10019 /* current FH is not set */ NFS4ERR_NOFILEHANDLE Nfsstat4 = 10020 /* minor vers not supp */ NFS4ERR_MINOR_VERS_MISMATCH Nfsstat4 = 10021 /* server has rebooted */ NFS4ERR_STALE_CLIENTID Nfsstat4 = 10022 /* server has rebooted */ NFS4ERR_STALE_STATEID Nfsstat4 = 10023 /* state is out of sync */ NFS4ERR_OLD_STATEID Nfsstat4 = 10024 /* incorrect stateid */ NFS4ERR_BAD_STATEID Nfsstat4 = 10025 /* request is out of seq. */ NFS4ERR_BAD_SEQID Nfsstat4 = 10026 /* verify - attrs not same */ NFS4ERR_NOT_SAME Nfsstat4 = 10027 /* lock range not supported*/ NFS4ERR_LOCK_RANGE Nfsstat4 = 10028 /* should be file/directory*/ NFS4ERR_SYMLINK Nfsstat4 = 10029 /* no saved filehandle */ NFS4ERR_RESTOREFH Nfsstat4 = 10030 /* some filesystem moved */ NFS4ERR_LEASE_MOVED Nfsstat4 = 10031 /* recommended attr not sup*/ NFS4ERR_ATTRNOTSUPP Nfsstat4 = 10032 /* reclaim outside of grace*/ NFS4ERR_NO_GRACE Nfsstat4 = 10033 /* reclaim error at server */ NFS4ERR_RECLAIM_BAD Nfsstat4 = 10034 /* conflict on reclaim */ NFS4ERR_RECLAIM_CONFLICT Nfsstat4 = 10035 /* XDR decode failed */ NFS4ERR_BADXDR Nfsstat4 = 10036 /* file locks held at CLOSE*/ NFS4ERR_LOCKS_HELD Nfsstat4 = 10037 /* conflict in OPEN and I/O*/ NFS4ERR_OPENMODE Nfsstat4 = 10038 /* owner translation bad */ NFS4ERR_BADOWNER Nfsstat4 = 10039 /* utf-8 char not supported*/ NFS4ERR_BADCHAR Nfsstat4 = 10040 /* name not supported */ NFS4ERR_BADNAME Nfsstat4 = 10041 /* lock range not supported*/ NFS4ERR_BAD_RANGE Nfsstat4 = 10042 /* no atomic up/downgrade */ NFS4ERR_LOCK_NOTSUPP Nfsstat4 = 10043 /* undefined operation */ NFS4ERR_OP_ILLEGAL Nfsstat4 = 10044 /* file locking deadlock */ NFS4ERR_DEADLOCK Nfsstat4 = 10045 /* open file blocks op. */ NFS4ERR_FILE_OPEN Nfsstat4 = 10046 /* lockowner state revoked */ NFS4ERR_ADMIN_REVOKED Nfsstat4 = 10047 /* callback path down */ NFS4ERR_CB_PATH_DOWN Nfsstat4 = 10048 NFS4ERR_BADIOMODE Nfsstat4 = 10049 NFS4ERR_BADLAYOUT Nfsstat4 = 10050 NFS4ERR_BAD_SESSION_DIGEST Nfsstat4 = 10051 NFS4ERR_BADSESSION Nfsstat4 = 10052 NFS4ERR_BADSLOT Nfsstat4 = 10053 NFS4ERR_COMPLETE_ALREADY Nfsstat4 = 10054 NFS4ERR_CONN_NOT_BOUND_TO_SESSION Nfsstat4 = 10055 NFS4ERR_DELEG_ALREADY_WANTED Nfsstat4 = 10056 NFS4ERR_BACK_CHAN_BUSY Nfsstat4 = 10057 NFS4ERR_LAYOUTTRYLATER Nfsstat4 = 10058 NFS4ERR_LAYOUTUNAVAILABLE Nfsstat4 = 10059 NFS4ERR_NOMATCHING_LAYOUT Nfsstat4 = 10060 NFS4ERR_RECALLCONFLICT Nfsstat4 = 10061 ) /* * Basic data types */ type Bitmap4 = []Uint32_t type Offset4 = Uint64_t type Count4 = Uint32_t type Length4 = Uint64_t type Clientid4 = Uint64_t type Sequenceid4 = Uint32_t type Seqid4 = Uint32_t type Slotid4 = Uint32_t type Utf8string = []byte type Utf8str_cis = Utf8string type Utf8str_cs = Utf8string type Utf8str_mixed = Utf8string type Component4 = Utf8str_cs type Pathname4 = []Component4 type Nfs_lockid4 = Uint64_t type Nfs_cookie4 = Uint64_t type Linktext4 = Utf8str_cs type Sec_oid4 = []byte type Qop4 = Uint32_t type Mode4 = Uint32_t type Changeid4 = Uint64_t type Verifier4 = [NFS4_VERIFIER_SIZE]byte type Sessionid4 = [NFS4_SESSIONID_SIZE]byte /* * Authsys_parms */ type Authsys_parms struct { Stamp uint32 Machinename string // bound 255 Uid uint32 Gid uint32 Gids []uint32 // bound 16 } const NFS4_DEVICEID4_SIZE = 16 type Deviceid4 = [NFS4_DEVICEID4_SIZE]byte type Layouttype4 int32 const ( LAYOUT4_NFSV4_1_FILES Layouttype4 = Layouttype4(0x1) LAYOUT4_OSD2_OBJECTS Layouttype4 = Layouttype4(0x2) LAYOUT4_BLOCK_VOLUME Layouttype4 = Layouttype4(0x3) ) type Layoutupdate4 struct { Lou_type Layouttype4 Lou_body []byte } type Device_addr4 struct { Da_layout_type Layouttype4 Da_addr_body []byte } /* * Timeval */ type Nfstime4 struct { Seconds Int64_t Nseconds Uint32_t } type Time_how4 int32 const ( SET_TO_SERVER_TIME4 Time_how4 = 0 SET_TO_CLIENT_TIME4 Time_how4 = 1 ) type Layoutiomode4 int32 const ( LAYOUTIOMODE4_READ Layoutiomode4 = 1 LAYOUTIOMODE4_RW Layoutiomode4 = 2 LAYOUTIOMODE4_ANY Layoutiomode4 = 3 ) type Layout_content4 struct { Loc_type Layouttype4 Loc_body []byte } type Layout4 struct { Lo_offset Offset4 Lo_length Length4 Lo_iomode Layoutiomode4 Lo_content Layout_content4 } type Settime4 struct { // The union discriminant Set_it selects among the following arms: // SET_TO_CLIENT_TIME4: // Time() *Nfstime4 // default: // void Set_it Time_how4 U interface{} } /* * File access handle */ type Nfs_fh4 = []byte // bound NFS4_FHSIZE /* * FSID structure for major/minor */ type Fsid4 struct { Major Uint64_t Minor Uint64_t } /* * Filesystem locations attribute for relocation/migration */ type Fs_location4 struct { Server []Utf8str_cis Rootpath Pathname4 } type Fs_locations4 struct { Fs_root Pathname4 Locations []Fs_location4 } /* * Mask that indicates which Access Control Entries are supported. * Values for the fattr4_aclsupport attribute. */ const ACL4_SUPPORT_ALLOW_ACL = 0x00000001 const ACL4_SUPPORT_DENY_ACL = 0x00000002 const ACL4_SUPPORT_AUDIT_ACL = 0x00000004 const ACL4_SUPPORT_ALARM_ACL = 0x00000008 type Acetype4 = Uint32_t /* * acetype4 values, others can be added as needed. */ const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000 const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001 const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002 const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003 /* * ACE flag */ type Aceflag4 = Uint32_t /* * ACE flag values */ const ACE4_FILE_INHERIT_ACE = 0x00000001 const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002 const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004 const ACE4_INHERIT_ONLY_ACE = 0x00000008 const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010 const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020 const ACE4_IDENTIFIER_GROUP = 0x00000040 /* * ACE mask */ type Acemask4 = Uint32_t /* * ACE mask values */ const ACE4_READ_DATA = 0x00000001 const ACE4_LIST_DIRECTORY = 0x00000001 const ACE4_WRITE_DATA = 0x00000002 const ACE4_ADD_FILE = 0x00000002 const ACE4_APPEND_DATA = 0x00000004 const ACE4_ADD_SUBDIRECTORY = 0x00000004 const ACE4_READ_NAMED_ATTRS = 0x00000008 const ACE4_WRITE_NAMED_ATTRS = 0x00000010 const ACE4_EXECUTE = 0x00000020 const ACE4_DELETE_CHILD = 0x00000040 const ACE4_READ_ATTRIBUTES = 0x00000080 const ACE4_WRITE_ATTRIBUTES = 0x00000100 const ACE4_DELETE = 0x00010000 const ACE4_READ_ACL = 0x00020000 const ACE4_WRITE_ACL = 0x00040000 const ACE4_WRITE_OWNER = 0x00080000 const ACE4_SYNCHRONIZE = 0x00100000 /* * ACE4_GENERIC_READ -- defined as combination of * ACE4_READ_ACL | * ACE4_READ_DATA | * ACE4_READ_ATTRIBUTES | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_READ = 0x00120081 /* * ACE4_GENERIC_WRITE -- defined as combination of * ACE4_READ_ACL | * ACE4_WRITE_DATA | * ACE4_WRITE_ATTRIBUTES | * ACE4_WRITE_ACL | * ACE4_APPEND_DATA | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_WRITE = 0x00160106 /* * ACE4_GENERIC_EXECUTE -- defined as combination of * ACE4_READ_ACL * ACE4_READ_ATTRIBUTES * ACE4_EXECUTE * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_EXECUTE = 0x001200A0 /* * Access Control Entry definition */ type Nfsace4 struct { Type Acetype4 Flag Aceflag4 Access_mask Acemask4 Who Utf8str_mixed } /* * Field definitions for the fattr4_mode attribute */ const MODE4_SUID = 0x800 /* set group id on execution */ const MODE4_SGID = 0x400 /* save text even after use */ const MODE4_SVTX = 0x200 /* read permission: owner */ const MODE4_RUSR = 0x100 /* write permission: owner */ const MODE4_WUSR = 0x080 /* execute permission: owner */ const MODE4_XUSR = 0x040 /* read permission: group */ const MODE4_RGRP = 0x020 /* write permission: group */ const MODE4_WGRP = 0x010 /* execute permission: group */ const MODE4_XGRP = 0x008 /* read permission: other */ const MODE4_ROTH = 0x004 /* write permission: other */ const MODE4_WOTH = 0x002 /* execute permission: other */ const MODE4_XOTH = 0x001 /* * Special data/attribute associated with * file types NF4BLK and NF4CHR. */ type Specdata4 struct { /* major device number */ Specdata1 Uint32_t /* minor device number */ Specdata2 Uint32_t } /* * Values for fattr4_fh_expire_type */ const FH4_PERSISTENT = 0x00000000 const FH4_NOEXPIRE_WITH_OPEN = 0x00000001 const FH4_VOLATILE_ANY = 0x00000002 const FH4_VOL_MIGRATION = 0x00000004 const FH4_VOL_RENAME = 0x00000008 type Fattr4_supported_attrs = Bitmap4 type Fattr4_type = Nfs_ftype4 type Fattr4_fh_expire_type = Uint32_t type Fattr4_change = Changeid4 type Fattr4_size = Uint64_t type Fattr4_link_support = bool type Fattr4_symlink_support = bool type Fattr4_named_attr = bool type Fattr4_fsid = Fsid4 type Fattr4_unique_handles = bool type Fattr4_lease_time = Uint32_t type Fattr4_rdattr_error = Nfsstat4 type Fattr4_acl = []Nfsace4 type Fattr4_aclsupport = Uint32_t type Fattr4_archive = bool type Fattr4_cansettime = bool type Fattr4_case_insensitive = bool type Fattr4_case_preserving = bool type Fattr4_chown_restricted = bool type Fattr4_fileid = Uint64_t type Fattr4_files_avail = Uint64_t type Fattr4_filehandle = Nfs_fh4 type Fattr4_files_free = Uint64_t type Fattr4_files_total = Uint64_t type Fattr4_fs_locations = Fs_locations4 type Fattr4_hidden = bool type Fattr4_homogeneous = bool type Fattr4_maxfilesize = Uint64_t type Fattr4_maxlink = Uint32_t type Fattr4_maxname = Uint32_t type Fattr4_maxread = Uint64_t type Fattr4_maxwrite = Uint64_t type Fattr4_mimetype = Utf8str_cs type Fattr4_mode = Mode4 type Fattr4_mounted_on_fileid = Uint64_t type Fattr4_no_trunc = bool type Fattr4_numlinks = Uint32_t type Fattr4_owner = Utf8str_mixed type Fattr4_owner_group = Utf8str_mixed type Fattr4_quota_avail_hard = Uint64_t type Fattr4_quota_avail_soft = Uint64_t type Fattr4_quota_used = Uint64_t type Fattr4_rawdev = Specdata4 type Fattr4_space_avail = Uint64_t type Fattr4_space_free = Uint64_t type Fattr4_space_total = Uint64_t type Fattr4_space_used = Uint64_t type Fattr4_system = bool type Fattr4_time_access = Nfstime4 type Fattr4_time_access_set = Settime4 type Fattr4_time_backup = Nfstime4 type Fattr4_time_create = Nfstime4 type Fattr4_time_delta = Nfstime4 type Fattr4_time_metadata = Nfstime4 type Fattr4_time_modify = Nfstime4 type Fattr4_time_modify_set = Settime4 /* * Mandatory Attributes */ const FATTR4_SUPPORTED_ATTRS = 0 const FATTR4_TYPE = 1 const FATTR4_FH_EXPIRE_TYPE = 2 const FATTR4_CHANGE = 3 const FATTR4_SIZE = 4 const FATTR4_LINK_SUPPORT = 5 const FATTR4_SYMLINK_SUPPORT = 6 const FATTR4_NAMED_ATTR = 7 const FATTR4_FSID = 8 const FATTR4_UNIQUE_HANDLES = 9 const FATTR4_LEASE_TIME = 10 const FATTR4_RDATTR_ERROR = 11 const FATTR4_FILEHANDLE = 19 /* * Recommended Attributes */ const FATTR4_ACL = 12 const FATTR4_ACLSUPPORT = 13 const FATTR4_ARCHIVE = 14 const FATTR4_CANSETTIME = 15 const FATTR4_CASE_INSENSITIVE = 16 const FATTR4_CASE_PRESERVING = 17 const FATTR4_CHOWN_RESTRICTED = 18 const FATTR4_FILEID = 20 const FATTR4_FILES_AVAIL = 21 const FATTR4_FILES_FREE = 22 const FATTR4_FILES_TOTAL = 23 const FATTR4_FS_LOCATIONS = 24 const FATTR4_HIDDEN = 25 const FATTR4_HOMOGENEOUS = 26 const FATTR4_MAXFILESIZE = 27 const FATTR4_MAXLINK = 28 const FATTR4_MAXNAME = 29 const FATTR4_MAXREAD = 30 const FATTR4_MAXWRITE = 31 const FATTR4_MIMETYPE = 32 const FATTR4_MODE = 33 const FATTR4_NO_TRUNC = 34 const FATTR4_NUMLINKS = 35 const FATTR4_OWNER = 36 const FATTR4_OWNER_GROUP = 37 const FATTR4_QUOTA_AVAIL_HARD = 38 const FATTR4_QUOTA_AVAIL_SOFT = 39 const FATTR4_QUOTA_USED = 40 const FATTR4_RAWDEV = 41 const FATTR4_SPACE_AVAIL = 42 const FATTR4_SPACE_FREE = 43 const FATTR4_SPACE_TOTAL = 44 const FATTR4_SPACE_USED = 45 const FATTR4_SYSTEM = 46 const FATTR4_TIME_ACCESS = 47 const FATTR4_TIME_ACCESS_SET = 48 const FATTR4_TIME_BACKUP = 49 const FATTR4_TIME_CREATE = 50 const FATTR4_TIME_DELTA = 51 const FATTR4_TIME_METADATA = 52 const FATTR4_TIME_MODIFY = 53 const FATTR4_TIME_MODIFY_SET = 54 const FATTR4_MOUNTED_ON_FILEID = 55 type Attrlist4 = []byte /* * File attribute container */ type Fattr4 struct { Attrmask Bitmap4 Attr_vals Attrlist4 } /* * Change info for the client */ type Change_info4 struct { Atomic bool Before Changeid4 After Changeid4 } type Clientaddr4 struct { /* see struct rpcb in RFC 1833 */ R_netid string /* universal address */ R_addr string } /* * Callback program info as provided by the client */ type Cb_client4 struct { Cb_program Uint32_t Cb_location Clientaddr4 } /* * Stateid */ type Stateid4 struct { Seqid Uint32_t Other [12]byte } /* * Client ID */ type Nfs_client_id4 struct { Verifier Verifier4 Id []byte // bound NFS4_OPAQUE_LIMIT } type Open_owner4 struct { Clientid Clientid4 Owner []byte // bound NFS4_OPAQUE_LIMIT } type Lock_owner4 struct { Clientid Clientid4 Owner []byte // bound NFS4_OPAQUE_LIMIT } type Nfs_lock_type4 int32 const ( READ_LT Nfs_lock_type4 = 1 WRITE_LT Nfs_lock_type4 = 2 /* blocking read */ READW_LT Nfs_lock_type4 = 3 /* blocking write */ WRITEW_LT Nfs_lock_type4 = 4 ) /* * ACCESS: Check access permission */ const ACCESS4_READ = 0x00000001 const ACCESS4_LOOKUP = 0x00000002 const ACCESS4_MODIFY = 0x00000004 const ACCESS4_EXTEND = 0x00000008 const ACCESS4_DELETE = 0x00000010 const ACCESS4_EXECUTE = 0x00000020 type ACCESS4args struct { /* CURRENT_FH: object */ Access Uint32_t } type ACCESS4resok struct { Supported Uint32_t Access Uint32_t } type ACCESS4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *ACCESS4resok // default: // void Status Nfsstat4 U interface{} } /* * CLOSE: Close a file and release share reservations */ type CLOSE4args struct { /* CURRENT_FH: object */ Seqid Seqid4 Open_stateid Stateid4 } type CLOSE4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Open_stateid() *Stateid4 // default: // void Status Nfsstat4 U interface{} } /* * COMMIT: Commit cached data on server to stable storage */ type COMMIT4args struct { /* CURRENT_FH: file */ Offset Offset4 Count Count4 } type COMMIT4resok struct { Writeverf Verifier4 } type COMMIT4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *COMMIT4resok // default: // void Status Nfsstat4 U interface{} } /* * CREATE: Create a non-regular file */ type Createtype4 struct { // The union discriminant Type selects among the following arms: // NF4LNK: // Linkdata() *Linktext4 // NF4BLK, NF4CHR: // Devdata() *Specdata4 // NF4SOCK, NF4FIFO, NF4DIR: // void // default: // void Type Nfs_ftype4 U interface{} } type CREATE4args struct { /* CURRENT_FH: directory for creation */ Objtype Createtype4 Objname Component4 Createattrs Fattr4 } type CREATE4resok struct { Cinfo Change_info4 /* attributes set */ Attrset Bitmap4 } type CREATE4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *CREATE4resok // default: // void Status Nfsstat4 U interface{} } /* * DELEGPURGE: Purge Delegations Awaiting Recovery */ type DELEGPURGE4args struct { Clientid Clientid4 } type DELEGPURGE4res struct { Status Nfsstat4 } /* * DELEGRETURN: Return a delegation */ type DELEGRETURN4args struct { /* CURRENT_FH: delegated file */ Deleg_stateid Stateid4 } type DELEGRETURN4res struct { Status Nfsstat4 } /* * GETATTR: Get file attributes */ type GETATTR4args struct { /* CURRENT_FH: directory or file */ Attr_request Bitmap4 } type GETATTR4resok struct { Obj_attributes Fattr4 } type GETATTR4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *GETATTR4resok // default: // void Status Nfsstat4 U interface{} } /* * GETFH: Get current filehandle */ type GETFH4resok struct { Object Nfs_fh4 } type GETFH4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *GETFH4resok // default: // void Status Nfsstat4 U interface{} } /* * LINK: Create link to an object */ type LINK4args struct { /* CURRENT_FH: target directory */ Newname Component4 } type LINK4resok struct { Cinfo Change_info4 } type LINK4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *LINK4resok // default: // void Status Nfsstat4 U interface{} } /* * For LOCK, transition from open_owner to new lock_owner */ type Open_to_lock_owner4 struct { Open_seqid Seqid4 Open_stateid Stateid4 Lock_seqid Seqid4 Lock_owner Lock_owner4 } /* * For LOCK, existing lock_owner continues to request file locks */ type Exist_lock_owner4 struct { Lock_stateid Stateid4 Lock_seqid Seqid4 } type Locker4 struct { // The union discriminant New_lock_owner selects among the following arms: // true: // Open_owner() *Open_to_lock_owner4 // false: // Lock_owner() *Exist_lock_owner4 New_lock_owner bool U interface{} } /* * LOCK/LOCKT/LOCKU: Record lock management */ type LOCK4args struct { /* CURRENT_FH: file */ Locktype Nfs_lock_type4 Reclaim bool Offset Offset4 Length Length4 Locker Locker4 } type LOCK4denied struct { Offset Offset4 Length Length4 Locktype Nfs_lock_type4 Owner Lock_owner4 } type LOCK4resok struct { Lock_stateid Stateid4 } type LOCK4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *LOCK4resok // NFS4ERR_DENIED: // Denied() *LOCK4denied // default: // void Status Nfsstat4 U interface{} } type LOCKT4args struct { /* CURRENT_FH: file */ Locktype Nfs_lock_type4 Offset Offset4 Length Length4 Owner Lock_owner4 } type LOCKT4res struct { // The union discriminant Status selects among the following arms: // NFS4ERR_DENIED: // Denied() *LOCK4denied // NFS4_OK: // void // default: // void Status Nfsstat4 U interface{} } type LOCKU4args struct { /* CURRENT_FH: file */ Locktype Nfs_lock_type4 Seqid Seqid4 Lock_stateid Stateid4 Offset Offset4 Length Length4 } type LOCKU4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Lock_stateid() *Stateid4 // default: // void Status Nfsstat4 U interface{} } /* * LOOKUP: Lookup filename */ type LOOKUP4args struct { /* CURRENT_FH: directory */ Objname Component4 } type LOOKUP4res struct { /* CURRENT_FH: object */ Status Nfsstat4 } /* * LOOKUPP: Lookup parent directory */ type LOOKUPP4res struct { /* CURRENT_FH: directory */ Status Nfsstat4 } /* * NVERIFY: Verify attributes different */ type NVERIFY4args struct { /* CURRENT_FH: object */ Obj_attributes Fattr4 } type NVERIFY4res struct { Status Nfsstat4 } /* * Various definitions for OPEN */ type Createmode4 int32 const ( UNCHECKED4 Createmode4 = 0 GUARDED4 Createmode4 = 1 EXCLUSIVE4 Createmode4 = 2 ) type Createhow4 struct { // The union discriminant Mode selects among the following arms: // UNCHECKED4, GUARDED4: // Createattrs() *Fattr4 // EXCLUSIVE4: // Createverf() *Verifier4 Mode Createmode4 U interface{} } type Opentype4 int32 const ( OPEN4_NOCREATE Opentype4 = 0 OPEN4_CREATE Opentype4 = 1 ) type Openflag4 struct { // The union discriminant Opentype selects among the following arms: // OPEN4_CREATE: // How() *Createhow4 // default: // void Opentype Opentype4 U interface{} } /* Next definitions used for OPEN delegation */ type Limit_by4 int32 const ( NFS_LIMIT_SIZE Limit_by4 = 1 NFS_LIMIT_BLOCKS Limit_by4 = 2 ) type Nfs_modified_limit4 struct { Num_blocks Uint32_t Bytes_per_block Uint32_t } type Nfs_space_limit4 struct { // The union discriminant Limitby selects among the following arms: // NFS_LIMIT_SIZE: // Filesize() *Uint64_t // NFS_LIMIT_BLOCKS: // Mod_blocks() *Nfs_modified_limit4 Limitby Limit_by4 U interface{} } /* * Share Access and Deny constants for open argument */ const OPEN4_SHARE_ACCESS_READ = 0x00000001 const OPEN4_SHARE_ACCESS_WRITE = 0x00000002 const OPEN4_SHARE_ACCESS_BOTH = 0x00000003 const OPEN4_SHARE_DENY_NONE = 0x00000000 const OPEN4_SHARE_DENY_READ = 0x00000001 const OPEN4_SHARE_DENY_WRITE = 0x00000002 const OPEN4_SHARE_DENY_BOTH = 0x00000003 type Open_delegation_type4 int32 const ( OPEN_DELEGATE_NONE Open_delegation_type4 = 0 OPEN_DELEGATE_READ Open_delegation_type4 = 1 OPEN_DELEGATE_WRITE Open_delegation_type4 = 2 ) type Open_claim_type4 int32 const ( CLAIM_NULL Open_claim_type4 = 0 CLAIM_PREVIOUS Open_claim_type4 = 1 CLAIM_DELEGATE_CUR Open_claim_type4 = 2 CLAIM_DELEGATE_PREV Open_claim_type4 = 3 /* new to v4.1 */ CLAIM_FH Open_claim_type4 = 4 /* new to v4.1 */ CLAIM_DELEG_CUR_FH Open_claim_type4 = 5 /* new to v4.1 */ CLAIM_DELEG_PREV_FH Open_claim_type4 = 6 ) type Open_claim_delegate_cur4 struct { Delegate_stateid Stateid4 File Component4 } type Open_claim4 struct { // The union discriminant Claim selects among the following arms: // CLAIM_NULL: // File() *Component4 // CLAIM_PREVIOUS: // Delegate_type() *Open_delegation_type4 // CLAIM_DELEGATE_CUR: // Delegate_cur_info() *Open_claim_delegate_cur4 // CLAIM_DELEGATE_PREV: // File_delegate_prev() *Component4 Claim Open_claim_type4 U interface{} } /* * OPEN: Open a file, potentially receiving an open delegation */ type OPEN4args struct { Seqid Seqid4 Share_access Uint32_t Share_deny Uint32_t Owner Open_owner4 Openhow Openflag4 Claim Open_claim4 } type Open_read_delegation4 struct { /* Stateid for delegation*/ Stateid Stateid4 Recall bool Permissions Nfsace4 } type Open_write_delegation4 struct { /* Stateid for delegation */ Stateid Stateid4 Recall bool Space_limit Nfs_space_limit4 Permissions Nfsace4 } type Open_delegation4 struct { // The union discriminant Delegation_type selects among the following arms: // OPEN_DELEGATE_NONE: // void // OPEN_DELEGATE_READ: // Read() *Open_read_delegation4 // OPEN_DELEGATE_WRITE: // Write() *Open_write_delegation4 Delegation_type Open_delegation_type4 U interface{} } /* Client must confirm open */ const OPEN4_RESULT_CONFIRM = 0x00000002 /* Type of file locking behavior at the server */ const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004 type OPEN4resok struct { /* Stateid for open */ Stateid Stateid4 /* Directory Change Info */ Cinfo Change_info4 /* Result flags */ Rflags Uint32_t /* attribute set for create*/ Attrset Bitmap4 Delegation Open_delegation4 } type OPEN4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *OPEN4resok // default: // void Status Nfsstat4 U interface{} } /* * OPENATTR: open named attributes directory */ type OPENATTR4args struct { /* CURRENT_FH: object */ Createdir bool } type OPENATTR4res struct { /* CURRENT_FH: named attr directory */ Status Nfsstat4 } /* * OPEN_CONFIRM: confirm the open */ type OPEN_CONFIRM4args struct { /* CURRENT_FH: opened file */ Open_stateid Stateid4 Seqid Seqid4 } type OPEN_CONFIRM4resok struct { Open_stateid Stateid4 } type OPEN_CONFIRM4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *OPEN_CONFIRM4resok // default: // void Status Nfsstat4 U interface{} } /* * OPEN_DOWNGRADE: downgrade the access/deny for a file */ type OPEN_DOWNGRADE4args struct { /* CURRENT_FH: opened file */ Open_stateid Stateid4 Seqid Seqid4 Share_access Uint32_t Share_deny Uint32_t } type OPEN_DOWNGRADE4resok struct { Open_stateid Stateid4 } type OPEN_DOWNGRADE4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *OPEN_DOWNGRADE4resok // default: // void Status Nfsstat4 U interface{} } /* * PUTFH: Set current filehandle */ type PUTFH4args struct { Object Nfs_fh4 } type PUTFH4res struct { /* CURRENT_FH: */ Status Nfsstat4 } /* * PUTPUBFH: Set public filehandle */ type PUTPUBFH4res struct { /* CURRENT_FH: public fh */ Status Nfsstat4 } /* * PUTROOTFH: Set root filehandle */ type PUTROOTFH4res struct { /* CURRENT_FH: root fh */ Status Nfsstat4 } /* * READ: Read from file */ type READ4args struct { /* CURRENT_FH: file */ Stateid Stateid4 Offset Offset4 Count Count4 } type READ4resok struct { Eof bool Data []byte } type READ4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *READ4resok // default: // void Status Nfsstat4 U interface{} } /* * READDIR: Read directory */ type READDIR4args struct { /* CURRENT_FH: directory */ Cookie Nfs_cookie4 Cookieverf Verifier4 Dircount Count4 Maxcount Count4 Attr_request Bitmap4 } type Entry4 struct { Cookie Nfs_cookie4 Name Component4 Attrs Fattr4 Nextentry *Entry4 } type Dirlist4 struct { Entries *Entry4 Eof bool } type READDIR4resok struct { Cookieverf Verifier4 Reply Dirlist4 } type READDIR4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *READDIR4resok // default: // void Status Nfsstat4 U interface{} } /* * READLINK: Read symbolic link */ type READLINK4resok struct { Link Linktext4 } type READLINK4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *READLINK4resok // default: // void Status Nfsstat4 U interface{} } /* * REMOVE: Remove filesystem object */ type REMOVE4args struct { /* CURRENT_FH: directory */ Target Component4 } type REMOVE4resok struct { Cinfo Change_info4 } type REMOVE4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *REMOVE4resok // default: // void Status Nfsstat4 U interface{} } /* * RENAME: Rename directory entry */ type RENAME4args struct { /* SAVED_FH: source directory */ Oldname Component4 Newname Component4 } type RENAME4resok struct { Source_cinfo Change_info4 Target_cinfo Change_info4 } type RENAME4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *RENAME4resok // default: // void Status Nfsstat4 U interface{} } /* * RENEW: Renew a Lease */ type RENEW4args struct { Clientid Clientid4 } type RENEW4res struct { Status Nfsstat4 } type RESTOREFH4res struct { /* CURRENT_FH: value of saved fh */ Status Nfsstat4 } /* * SAVEFH: Save current filehandle */ type SAVEFH4res struct { /* SAVED_FH: value of current fh */ Status Nfsstat4 } /* * SECINFO: Obtain Available Security Mechanisms */ type SECINFO4args struct { Name Component4 } type Rpc_gss_svc_t int32 const ( RPC_GSS_SVC_NONE Rpc_gss_svc_t = 1 RPC_GSS_SVC_INTEGRITY Rpc_gss_svc_t = 2 RPC_GSS_SVC_PRIVACY Rpc_gss_svc_t = 3 ) type Rpcsec_gss_info struct { Oid Sec_oid4 Qop Qop4 Service Rpc_gss_svc_t } const RPCSEC_GSS = 6 type Secinfo4 struct { // The union discriminant Flavor selects among the following arms: // RPCSEC_GSS: // Flavor_info() *Rpcsec_gss_info // default: // void Flavor Uint32_t U interface{} } type SECINFO4resok = []Secinfo4 type SECINFO4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *SECINFO4resok // default: // void Status Nfsstat4 U interface{} } /* * SETATTR: Set attributes */ type SETATTR4args struct { /* CURRENT_FH: target object */ Stateid Stateid4 Obj_attributes Fattr4 } type SETATTR4res struct { Status Nfsstat4 Attrsset Bitmap4 } /* * SETCLIENTID */ type SETCLIENTID4args struct { Client Nfs_client_id4 Callback Cb_client4 Callback_ident Uint32_t } type SETCLIENTID4resok struct { Clientid Clientid4 Setclientid_confirm Verifier4 } type SETCLIENTID4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *SETCLIENTID4resok // NFS4ERR_CLID_INUSE: // Client_using() *Clientaddr4 // default: // void Status Nfsstat4 U interface{} } type SETCLIENTID_CONFIRM4args struct { Clientid Clientid4 Setclientid_confirm Verifier4 } type SETCLIENTID_CONFIRM4res struct { Status Nfsstat4 } /* * VERIFY: Verify attributes same */ type VERIFY4args struct { /* CURRENT_FH: object */ Obj_attributes Fattr4 } type VERIFY4res struct { Status Nfsstat4 } /* * WRITE: Write to file */ type Stable_how4 int32 const ( UNSTABLE4 Stable_how4 = 0 DATA_SYNC4 Stable_how4 = 1 FILE_SYNC4 Stable_how4 = 2 ) type WRITE4args struct { /* CURRENT_FH: file */ Stateid Stateid4 Offset Offset4 Stable Stable_how4 Data []byte } type WRITE4resok struct { Count Count4 Committed Stable_how4 Writeverf Verifier4 } type WRITE4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *WRITE4resok // default: // void Status Nfsstat4 U interface{} } /* * RELEASE_LOCKOWNER: Notify server to release lockowner */ type RELEASE_LOCKOWNER4args struct { Lock_owner Lock_owner4 } type RELEASE_LOCKOWNER4res struct { Status Nfsstat4 } type Callback_sec_parms4 struct { // The union discriminant Cb_secflavor selects among the following arms: // AUTH_NONE: // void // AUTH_SYS: // Cbsp_sys_cred() *Authsys_parms Cb_secflavor Uint32_t U interface{} } /* * CREATE_SESSION */ type Channel_attrs4 struct { Ca_headerpadsize Count4 Ca_maxrequestsize Count4 Ca_maxresponsesize Count4 Ca_maxresponsesize_cached Count4 Ca_maxoperations Count4 Ca_maxrequests Count4 Ca_rdma_ird []Uint32_t // bound 1 } const CREATE_SESSION4_FLAG_PERSIST = 0x00000001 const CREATE_SESSION4_FLAG_CONN_BACK_CHAN = 0x00000002 const CREATE_SESSION4_FLAG_CONN_RDMA = 0x00000004 type CREATE_SESSION4args struct { Csa_clientid Clientid4 Csa_sequence Sequenceid4 Csa_flags Uint32_t Csa_fore_chan_attrs Channel_attrs4 Csa_back_chan_attrs Channel_attrs4 Csa_cb_program Uint32_t Csa_sec_parms []Callback_sec_parms4 } type CREATE_SESSION4resok struct { Csr_sessionid Sessionid4 Csr_sequence Sequenceid4 Csr_flags Uint32_t Csr_fore_chan_attrs Channel_attrs4 Csr_back_chan_attrs Channel_attrs4 } type CREATE_SESSION4res struct { // The union discriminant Csr_status selects among the following arms: // NFS4_OK: // Csr_resok4() *CREATE_SESSION4resok // default: // void Csr_status Nfsstat4 U interface{} } /* * DESTROY_SESSION */ type DESTROY_SESSION4args struct { Dsa_sessionid Sessionid4 } type DESTROY_SESSION4res struct { Dsr_status Nfsstat4 } /* * FREE_STATEID */ type FREE_STATEID4args struct { Fsa_stateid Stateid4 } type FREE_STATEID4res struct { Fsr_status Nfsstat4 } /* * GET_DIR_DELEGATION */ type Attr_notice4 = Nfstime4 type GET_DIR_DELEGATION4args struct { Gdda_signal_deleg_avail bool Gdda_notification_types Bitmap4 Gdda_child_attr_delay Attr_notice4 Gdda_dir_attr_delay Attr_notice4 Gdda_child_attributes Bitmap4 Gdda_dir_attributes Bitmap4 } type GET_DIR_DELEGATION4resok struct { Gddr_cookieverf Verifier4 Gddr_stateid Stateid4 Gddr_notification Bitmap4 Gddr_child_attributes Bitmap4 Gddr_dir_attributes Bitmap4 } type Gddrnf4_status int32 const ( GDD4_OK Gddrnf4_status = 0 GDD4_UNAVAIL Gddrnf4_status = 1 ) type GET_DIR_DELEGATION4res_non_fatal struct { // The union discriminant Gddrnf_status selects among the following arms: // GDD4_OK: // Gddrnf_resok4() *GET_DIR_DELEGATION4resok // GDD4_UNAVAIL: // Gddrnf_will_signal_deleg_avail() *bool Gddrnf_status Gddrnf4_status U interface{} } type GET_DIR_DELEGATION4res struct { // The union discriminant Gddr_status selects among the following arms: // NFS4_OK: // Gddr_res_non_fatal4() *GET_DIR_DELEGATION4res_non_fatal // default: // void Gddr_status Nfsstat4 U interface{} } /* * GETDEVICEINFO */ type GETDEVICEINFO4args struct { Gdia_device_id Deviceid4 Gdia_layout_type Layouttype4 Gdia_maxcount Count4 Gdia_notify_types Bitmap4 } type GETDEVICEINFO4resok struct { Gdir_device_addr Device_addr4 Gdir_notification Bitmap4 } type GETDEVICEINFO4res struct { // The union discriminant Gdir_status selects among the following arms: // NFS4_OK: // Gdir_resok4() *GETDEVICEINFO4resok // NFS4ERR_TOOSMALL: // Gdir_mincount() *Count4 // default: // void Gdir_status Nfsstat4 U interface{} } /* * GETDEVICELIST */ type GETDEVICELIST4args struct { Gdla_layout_type Layouttype4 Gdla_maxdevices Count4 Gdla_cookie Nfs_cookie4 Gdla_cookieverf Verifier4 } type GETDEVICELIST4resok struct { Gdlr_cookie Nfs_cookie4 Gdlr_cookieverf Verifier4 Gdlr_deviceid_list []Deviceid4 Gdlr_eof bool } type GETDEVICELIST4res struct { // The union discriminant Gdlr_status selects among the following arms: // NFS4_OK: // Gdlr_resok4() *GETDEVICELIST4resok // default: // void Gdlr_status Nfsstat4 U interface{} } /* * LAYOUTCOMMIT */ type Newtime4 struct { // The union discriminant Nt_timechanged selects among the following arms: // true: // Nt_time() *Nfstime4 // false: // void Nt_timechanged bool U interface{} } type Newoffset4 struct { // The union discriminant No_newoffset selects among the following arms: // true: // No_offset() *Offset4 // false: // void No_newoffset bool U interface{} } type LAYOUTCOMMIT4args struct { Loca_offset Offset4 Loca_length Length4 Loca_reclaim bool Loca_stateid Stateid4 Loca_last_write_offset Newoffset4 Loca_time_modify Newtime4 Loca_layoutupdate Layoutupdate4 } type Newsize4 struct { // The union discriminant Ns_sizechanged selects among the following arms: // true: // Ns_size() *Length4 // false: // void Ns_sizechanged bool U interface{} } type LAYOUTCOMMIT4resok struct { Locr_newsize Newsize4 } type LAYOUTCOMMIT4res struct { // The union discriminant Locr_status selects among the following arms: // NFS4_OK: // Locr_resok4() *LAYOUTCOMMIT4resok // default: // void Locr_status Nfsstat4 U interface{} } /* * LAYOUTGET */ type LAYOUTGET4args struct { Loga_signal_layout_avail bool Loga_layout_type Layouttype4 Loga_iomode Layoutiomode4 Loga_offset Offset4 Loga_length Length4 Loga_minlength Length4 Loga_stateid Stateid4 Loga_maxcount Count4 } type LAYOUTGET4resok struct { Logr_return_on_close bool Logr_stateid Stateid4 Logr_layout []Layout4 } type LAYOUTGET4res struct { // The union discriminant Logr_status selects among the following arms: // NFS4_OK: // Logr_resok4() *LAYOUTGET4resok // NFS4ERR_LAYOUTTRYLATER: // Logr_will_signal_layout_avail() *bool // default: // void Logr_status Nfsstat4 U interface{} } /* * LAYOUTRETURN */ const LAYOUT4_RET_REC_FILE = 1 const LAYOUT4_RET_REC_FSID = 2 const LAYOUT4_RET_REC_ALL = 3 type Layoutreturn_type4 int32 const ( LAYOUTRETURN4_FILE Layoutreturn_type4 = Layoutreturn_type4(LAYOUT4_RET_REC_FILE) LAYOUTRETURN4_FSID Layoutreturn_type4 = Layoutreturn_type4(LAYOUT4_RET_REC_FSID) LAYOUTRETURN4_ALL Layoutreturn_type4 = Layoutreturn_type4(LAYOUT4_RET_REC_ALL) ) type Layoutreturn_file4 struct { Lrf_offset Offset4 Lrf_length Length4 Lrf_stateid Stateid4 Lrf_body []byte } type Layoutreturn4 struct { // The union discriminant Lr_returntype selects among the following arms: // LAYOUTRETURN4_FILE: // Lr_layout() *Layoutreturn_file4 // default: // void Lr_returntype Layoutreturn_type4 U interface{} } type LAYOUTRETURN4args struct { Lora_reclaim bool Lora_layout_type Layouttype4 Lora_iomode Layoutiomode4 Lora_layoutreturn Layoutreturn4 } type Layoutreturn_stateid struct { // The union discriminant Lrs_present selects among the following arms: // true: // Lrs_stateid() *Stateid4 // false: // void Lrs_present bool U interface{} } type LAYOUTRETURN4res struct { // The union discriminant Lorr_status selects among the following arms: // NFS4_OK: // Lorr_stateid() *Layoutreturn_stateid // default: // void Lorr_status Nfsstat4 U interface{} } /* * SECINFO_NO_NAME */ type Secinfo_style4 int32 const ( SECINFO_STYLE4_CURRENT_FH Secinfo_style4 = 0 SECINFO_STYLE4_PARENT Secinfo_style4 = 1 ) type SECINFO_NO_NAME4args = Secinfo_style4 type SECINFO_NO_NAME4res = SECINFO4res /* * SEQUENCE */ type SEQUENCE4args struct { Sa_sessionid Sessionid4 Sa_sequenceid Sequenceid4 Sa_slotid Slotid4 Sa_highest_slotid Slotid4 Sa_cachethis bool } const SEQ4_STATUS_CB_PATH_DOWN = 0x00000001 const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING = 0x00000002 const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED = 0x00000004 const SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED = 0x00000008 const SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED = 0x00000010 const SEQ4_STATUS_ADMIN_STATE_REVOKED = 0x00000020 const SEQ4_STATUS_RECALLABLE_STATE_REVOKED = 0x00000040 const SEQ4_STATUS_LEASE_MOVED = 0x00000080 const SEQ4_STATUS_RESTART_RECLAIM_NEEDED = 0x00000100 const SEQ4_STATUS_CB_PATH_DOWN_SESSION = 0x00000200 const SEQ4_STATUS_BACKCHANNEL_FAULT = 0x00000400 const SEQ4_STATUS_DEVID_CHANGED = 0x00000800 const SEQ4_STATUS_DEVID_DELETED = 0x00001000 type SEQUENCE4resok struct { Sr_sessionid Sessionid4 Sr_sequenceid Sequenceid4 Sr_slotid Slotid4 Sr_highest_slotid Slotid4 Sr_target_highest_slotid Slotid4 Sr_status_flags Uint32_t } type SEQUENCE4res struct { // The union discriminant Sr_status selects among the following arms: // NFS4_OK: // Sr_resok4() *SEQUENCE4resok // default: // void Sr_status Nfsstat4 U interface{} } /* * SET_SSV */ type Ssa_digest_input4 struct { Sdi_seqargs SEQUENCE4args } type SET_SSV4args struct { Ssa_ssv []byte Ssa_digest []byte } type Ssr_digest_input4 struct { Sdi_seqres SEQUENCE4res } type SET_SSV4resok struct { Ssr_digest []byte } type SET_SSV4res struct { // The union discriminant Ssr_status selects among the following arms: // NFS4_OK: // Ssr_resok4() *SET_SSV4resok // default: // void Ssr_status Nfsstat4 U interface{} } /* * TEST_STATEID */ type TEST_STATEID4args struct { Ts_stateids []Stateid4 } type TEST_STATEID4resok struct { Tsr_status_codes []Nfsstat4 } type TEST_STATEID4res struct { // The union discriminant Tsr_status selects among the following arms: // NFS4_OK: // Tsr_resok4() *TEST_STATEID4resok // default: // void Tsr_status Nfsstat4 U interface{} } /* * WANT_DELEGATION */ type Deleg_claim4 struct { // The union discriminant Dc_claim selects among the following arms: // CLAIM_FH: // void // CLAIM_DELEG_PREV_FH: // void // CLAIM_PREVIOUS: // Dc_delegate_type() *Open_delegation_type4 Dc_claim Open_claim_type4 U interface{} } type WANT_DELEGATION4args struct { Wda_want Uint32_t Wda_claim Deleg_claim4 } type WANT_DELEGATION4res struct { // The union discriminant Wdr_status selects among the following arms: // NFS4_OK: // Wdr_resok4() *Open_delegation4 // default: // void Wdr_status Nfsstat4 U interface{} } /* * DESTROY_CLIENTID */ type DESTROY_CLIENTID4args struct { Dca_clientid Clientid4 } type DESTROY_CLIENTID4res struct { Dcr_status Nfsstat4 } /* * RECLAIM_COMPLETE */ type RECLAIM_COMPLETE4args struct { Rca_one_fs bool } type RECLAIM_COMPLETE4res struct { Rcr_status Nfsstat4 } /* * ILLEGAL: Response for illegal operation numbers */ type ILLEGAL4res struct { Status Nfsstat4 } type Nfs_opnum4 int32 const ( OP_ACCESS Nfs_opnum4 = 3 OP_CLOSE Nfs_opnum4 = 4 OP_COMMIT Nfs_opnum4 = 5 OP_CREATE Nfs_opnum4 = 6 OP_DELEGPURGE Nfs_opnum4 = 7 OP_DELEGRETURN Nfs_opnum4 = 8 OP_GETATTR Nfs_opnum4 = 9 OP_GETFH Nfs_opnum4 = 10 OP_LINK Nfs_opnum4 = 11 OP_LOCK Nfs_opnum4 = 12 OP_LOCKT Nfs_opnum4 = 13 OP_LOCKU Nfs_opnum4 = 14 OP_LOOKUP Nfs_opnum4 = 15 OP_LOOKUPP Nfs_opnum4 = 16 OP_NVERIFY Nfs_opnum4 = 17 OP_OPEN Nfs_opnum4 = 18 OP_OPENATTR Nfs_opnum4 = 19 OP_OPEN_CONFIRM Nfs_opnum4 = 20 OP_OPEN_DOWNGRADE Nfs_opnum4 = 21 OP_PUTFH Nfs_opnum4 = 22 OP_PUTPUBFH Nfs_opnum4 = 23 OP_PUTROOTFH Nfs_opnum4 = 24 OP_READ Nfs_opnum4 = 25 OP_READDIR Nfs_opnum4 = 26 OP_READLINK Nfs_opnum4 = 27 OP_REMOVE Nfs_opnum4 = 28 OP_RENAME Nfs_opnum4 = 29 OP_RENEW Nfs_opnum4 = 30 OP_RESTOREFH Nfs_opnum4 = 31 OP_SAVEFH Nfs_opnum4 = 32 OP_SECINFO Nfs_opnum4 = 33 OP_SETATTR Nfs_opnum4 = 34 OP_SETCLIENTID Nfs_opnum4 = 35 OP_SETCLIENTID_CONFIRM Nfs_opnum4 = 36 OP_VERIFY Nfs_opnum4 = 37 OP_WRITE Nfs_opnum4 = 38 OP_RELEASE_LOCKOWNER Nfs_opnum4 = 39 OP_CREATE_SESSION Nfs_opnum4 = 43 OP_DESTROY_SESSION Nfs_opnum4 = 44 OP_FREE_STATEID Nfs_opnum4 = 45 OP_GET_DIR_DELEGATION Nfs_opnum4 = 46 OP_GETDEVICEINFO Nfs_opnum4 = 47 OP_GETDEVICELIST Nfs_opnum4 = 48 OP_LAYOUTCOMMIT Nfs_opnum4 = 49 OP_LAYOUTGET Nfs_opnum4 = 50 OP_LAYOUTRETURN Nfs_opnum4 = 51 OP_SECINFO_NO_NAME Nfs_opnum4 = 52 OP_SEQUENCE Nfs_opnum4 = 53 OP_SET_SSV Nfs_opnum4 = 54 OP_TEST_STATEID Nfs_opnum4 = 55 OP_WANT_DELEGATION Nfs_opnum4 = 56 OP_DESTROY_CLIENTID Nfs_opnum4 = 57 OP_RECLAIM_COMPLETE Nfs_opnum4 = 58 OP_ILLEGAL Nfs_opnum4 = 10044 ) type Nfs_argop4 struct { // The union discriminant Argop selects among the following arms: // OP_ACCESS: // Opaccess() *ACCESS4args // OP_CLOSE: // Opclose() *CLOSE4args // OP_COMMIT: // Opcommit() *COMMIT4args // OP_CREATE: // Opcreate() *CREATE4args // OP_DELEGPURGE: // Opdelegpurge() *DELEGPURGE4args // OP_DELEGRETURN: // Opdelegreturn() *DELEGRETURN4args // OP_GETATTR: // Opgetattr() *GETATTR4args // OP_GETFH: // void // OP_LINK: // Oplink() *LINK4args // OP_LOCK: // Oplock() *LOCK4args // OP_LOCKT: // Oplockt() *LOCKT4args // OP_LOCKU: // Oplocku() *LOCKU4args // OP_LOOKUP: // Oplookup() *LOOKUP4args // OP_LOOKUPP: // void // OP_NVERIFY: // Opnverify() *NVERIFY4args // OP_OPEN: // Opopen() *OPEN4args // OP_OPENATTR: // Opopenattr() *OPENATTR4args // OP_OPEN_CONFIRM: // Opopen_confirm() *OPEN_CONFIRM4args // OP_OPEN_DOWNGRADE: // Opopen_downgrade() *OPEN_DOWNGRADE4args // OP_PUTFH: // Opputfh() *PUTFH4args // OP_PUTPUBFH: // void // OP_PUTROOTFH: // void // OP_READ: // Opread() *READ4args // OP_READDIR: // Opreaddir() *READDIR4args // OP_READLINK: // void // OP_REMOVE: // Opremove() *REMOVE4args // OP_RENAME: // Oprename() *RENAME4args // OP_RENEW: // Oprenew() *RENEW4args // OP_RESTOREFH: // void // OP_SAVEFH: // void // OP_SECINFO: // Opsecinfo() *SECINFO4args // OP_SETATTR: // Opsetattr() *SETATTR4args // OP_SETCLIENTID: // Opsetclientid() *SETCLIENTID4args // OP_SETCLIENTID_CONFIRM: // Opsetclientid_confirm() *SETCLIENTID_CONFIRM4args // OP_VERIFY: // Opverify() *VERIFY4args // OP_WRITE: // Opwrite() *WRITE4args // OP_RELEASE_LOCKOWNER: // Oprelease_lockowner() *RELEASE_LOCKOWNER4args // OP_CREATE_SESSION: // Opcreatesession() *CREATE_SESSION4args // OP_DESTROY_SESSION: // Opdestroysession() *DESTROY_SESSION4args // OP_FREE_STATEID: // Opfreestateid() *FREE_STATEID4args // OP_GET_DIR_DELEGATION: // Opgetdirdelegation() *GET_DIR_DELEGATION4args // OP_GETDEVICEINFO: // Opgetdeviceinfo() *GETDEVICEINFO4args // OP_GETDEVICELIST: // Opgetdevicelist() *GETDEVICELIST4args // OP_LAYOUTCOMMIT: // Oplayoutcommit() *LAYOUTCOMMIT4args // OP_LAYOUTGET: // Oplayoutget() *LAYOUTGET4args // OP_LAYOUTRETURN: // Oplayoutreturn() *LAYOUTRETURN4args // OP_SECINFO_NO_NAME: // Opsecinfononame() *SECINFO_NO_NAME4args // OP_SEQUENCE: // Opsequence() *SEQUENCE4args // OP_SET_SSV: // Opsetssv() *SET_SSV4args // OP_TEST_STATEID: // Opteststateid() *TEST_STATEID4args // OP_WANT_DELEGATION: // Opwantdelegation() *WANT_DELEGATION4args // OP_DESTROY_CLIENTID: // Opdestroyclientid() *DESTROY_CLIENTID4args // OP_RECLAIM_COMPLETE: // Opreclaimcomplete() *RECLAIM_COMPLETE4args // OP_ILLEGAL: // void Argop Nfs_opnum4 U interface{} } type Nfs_resop4 struct { // The union discriminant Resop selects among the following arms: // OP_ACCESS: // Opaccess() *ACCESS4res // OP_CLOSE: // Opclose() *CLOSE4res // OP_COMMIT: // Opcommit() *COMMIT4res // OP_CREATE: // Opcreate() *CREATE4res // OP_DELEGPURGE: // Opdelegpurge() *DELEGPURGE4res // OP_DELEGRETURN: // Opdelegreturn() *DELEGRETURN4res // OP_GETATTR: // Opgetattr() *GETATTR4res // OP_GETFH: // Opgetfh() *GETFH4res // OP_LINK: // Oplink() *LINK4res // OP_LOCK: // Oplock() *LOCK4res // OP_LOCKT: // Oplockt() *LOCKT4res // OP_LOCKU: // Oplocku() *LOCKU4res // OP_LOOKUP: // Oplookup() *LOOKUP4res // OP_LOOKUPP: // Oplookupp() *LOOKUPP4res // OP_NVERIFY: // Opnverify() *NVERIFY4res // OP_OPEN: // Opopen() *OPEN4res // OP_OPENATTR: // Opopenattr() *OPENATTR4res // OP_OPEN_CONFIRM: // Opopen_confirm() *OPEN_CONFIRM4res // OP_OPEN_DOWNGRADE: // Opopen_downgrade() *OPEN_DOWNGRADE4res // OP_PUTFH: // Opputfh() *PUTFH4res // OP_PUTPUBFH: // Opputpubfh() *PUTPUBFH4res // OP_PUTROOTFH: // Opputrootfh() *PUTROOTFH4res // OP_READ: // Opread() *READ4res // OP_READDIR: // Opreaddir() *READDIR4res // OP_READLINK: // Opreadlink() *READLINK4res // OP_REMOVE: // Opremove() *REMOVE4res // OP_RENAME: // Oprename() *RENAME4res // OP_RENEW: // Oprenew() *RENEW4res // OP_RESTOREFH: // Oprestorefh() *RESTOREFH4res // OP_SAVEFH: // Opsavefh() *SAVEFH4res // OP_SECINFO: // Opsecinfo() *SECINFO4res // OP_SETATTR: // Opsetattr() *SETATTR4res // OP_SETCLIENTID: // Opsetclientid() *SETCLIENTID4res // OP_SETCLIENTID_CONFIRM: // Opsetclientid_confirm() *SETCLIENTID_CONFIRM4res // OP_VERIFY: // Opverify() *VERIFY4res // OP_WRITE: // Opwrite() *WRITE4res // OP_RELEASE_LOCKOWNER: // Oprelease_lockowner() *RELEASE_LOCKOWNER4res // OP_CREATE_SESSION: // Opcreatesession() *CREATE_SESSION4res // OP_DESTROY_SESSION: // Opdestroysession() *DESTROY_SESSION4res // OP_FREE_STATEID: // Opfreestateid() *FREE_STATEID4res // OP_GET_DIR_DELEGATION: // Opgetdirdelegation() *GET_DIR_DELEGATION4res // OP_GETDEVICEINFO: // Opgetdeviceinfo() *GETDEVICEINFO4res // OP_GETDEVICELIST: // Opgetdevicelist() *GETDEVICELIST4res // OP_LAYOUTCOMMIT: // Oplayoutcommit() *LAYOUTCOMMIT4res // OP_LAYOUTGET: // Oplayoutget() *LAYOUTGET4res // OP_LAYOUTRETURN: // Oplayoutreturn() *LAYOUTRETURN4res // OP_SECINFO_NO_NAME: // Opsecinfononame() *SECINFO_NO_NAME4res // OP_SEQUENCE: // Opsequence() *SEQUENCE4res // OP_SET_SSV: // Opsetssv() *SET_SSV4res // OP_TEST_STATEID: // Opteststateid() *TEST_STATEID4res // OP_WANT_DELEGATION: // Opwantdelegation() *WANT_DELEGATION4res // OP_DESTROY_CLIENTID: // Opdestroyclientid() *DESTROY_CLIENTID4res // OP_RECLAIM_COMPLETE: // Opreclaimcomplete() *RECLAIM_COMPLETE4res // OP_ILLEGAL: // Opillegal() *ILLEGAL4res Resop Nfs_opnum4 U interface{} } type COMPOUND4args struct { Tag Utf8str_cs Minorversion Uint32_t Argarray []Nfs_argop4 } type COMPOUND4res struct { Status Nfsstat4 Tag Utf8str_cs Resarray []Nfs_resop4 } type NFS_V4 interface { NFSPROC4_NULL() NFSPROC4_COMPOUND(COMPOUND4args) COMPOUND4res } /* * CB_GETATTR: Get Current Attributes */ type CB_GETATTR4args struct { Fh Nfs_fh4 Attr_request Bitmap4 } type CB_GETATTR4resok struct { Obj_attributes Fattr4 } type CB_GETATTR4res struct { // The union discriminant Status selects among the following arms: // NFS4_OK: // Resok4() *CB_GETATTR4resok // default: // void Status Nfsstat4 U interface{} } /* * CB_RECALL: Recall an Open Delegation */ type CB_RECALL4args struct { Stateid Stateid4 Truncate bool Fh Nfs_fh4 } type CB_RECALL4res struct { Status Nfsstat4 } /* * CB_ILLEGAL: Response for illegal operation numbers */ type CB_ILLEGAL4res struct { Status Nfsstat4 } /* * Various definitions for CB_COMPOUND */ type Nfs_cb_opnum4 int32 const ( OP_CB_GETATTR Nfs_cb_opnum4 = 3 OP_CB_RECALL Nfs_cb_opnum4 = 4 OP_CB_ILLEGAL Nfs_cb_opnum4 = 10044 ) type Nfs_cb_argop4 struct { // The union discriminant Argop selects among the following arms: // OP_CB_GETATTR: // Opcbgetattr() *CB_GETATTR4args // OP_CB_RECALL: // Opcbrecall() *CB_RECALL4args // OP_CB_ILLEGAL: // void Argop uint32 U interface{} } type Nfs_cb_resop4 struct { // The union discriminant Resop selects among the following arms: // OP_CB_GETATTR: // Opcbgetattr() *CB_GETATTR4res // OP_CB_RECALL: // Opcbrecall() *CB_RECALL4res // OP_CB_ILLEGAL: // Opcbillegal() *CB_ILLEGAL4res Resop uint32 U interface{} } type CB_COMPOUND4args struct { Tag Utf8str_cs Minorversion Uint32_t Callback_ident Uint32_t Argarray []Nfs_cb_argop4 } type CB_COMPOUND4res struct { Status Nfsstat4 Tag Utf8str_cs Resarray []Nfs_cb_resop4 } type NFS_CB interface { CB_NULL() CB_COMPOUND(CB_COMPOUND4args) CB_COMPOUND4res } // // Helper types and generated marshaling functions // var _XdrNames_Nfs_ftype4 = map[int32]string{ int32(NF4REG): "NF4REG", int32(NF4DIR): "NF4DIR", int32(NF4BLK): "NF4BLK", int32(NF4CHR): "NF4CHR", int32(NF4LNK): "NF4LNK", int32(NF4SOCK): "NF4SOCK", int32(NF4FIFO): "NF4FIFO", int32(NF4ATTRDIR): "NF4ATTRDIR", int32(NF4NAMEDATTR): "NF4NAMEDATTR", } var _XdrValues_Nfs_ftype4 = map[string]int32{ "NF4REG": int32(NF4REG), "NF4DIR": int32(NF4DIR), "NF4BLK": int32(NF4BLK), "NF4CHR": int32(NF4CHR), "NF4LNK": int32(NF4LNK), "NF4SOCK": int32(NF4SOCK), "NF4FIFO": int32(NF4FIFO), "NF4ATTRDIR": int32(NF4ATTRDIR), "NF4NAMEDATTR": int32(NF4NAMEDATTR), } func (Nfs_ftype4) XdrEnumNames() map[int32]string { return _XdrNames_Nfs_ftype4 } func (v Nfs_ftype4) String() string { if s, ok := _XdrNames_Nfs_ftype4[int32(v)]; ok { return s } return fmt.Sprintf("Nfs_ftype4#%d", v) } func (v *Nfs_ftype4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Nfs_ftype4[stok]; ok { *v = Nfs_ftype4(val) return nil } else if stok == "Nfs_ftype4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Nfs_ftype4.", stok)) } } func (v Nfs_ftype4) GetU32() uint32 { return uint32(v) } func (v *Nfs_ftype4) SetU32(n uint32) { *v = Nfs_ftype4(n) } func (v *Nfs_ftype4) XdrPointer() interface{} { return v } func (Nfs_ftype4) XdrTypeName() string { return "Nfs_ftype4" } func (v Nfs_ftype4) XdrValue() interface{} { return v } func (v *Nfs_ftype4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Nfs_ftype4 = *Nfs_ftype4 func XDR_Nfs_ftype4(v *Nfs_ftype4) *Nfs_ftype4 { return v } func (v *Nfs_ftype4) XdrInitialize() { switch Nfs_ftype4(0) { case NF4REG, NF4DIR, NF4BLK, NF4CHR, NF4LNK, NF4SOCK, NF4FIFO, NF4ATTRDIR, NF4NAMEDATTR: default: if *v == Nfs_ftype4(0) { *v = NF4REG } } } var _XdrNames_Nfsstat4 = map[int32]string{ int32(NFS4_OK): "NFS4_OK", int32(NFS4ERR_PERM): "NFS4ERR_PERM", int32(NFS4ERR_NOENT): "NFS4ERR_NOENT", int32(NFS4ERR_IO): "NFS4ERR_IO", int32(NFS4ERR_NXIO): "NFS4ERR_NXIO", int32(NFS4ERR_ACCESS): "NFS4ERR_ACCESS", int32(NFS4ERR_EXIST): "NFS4ERR_EXIST", int32(NFS4ERR_XDEV): "NFS4ERR_XDEV", int32(NFS4ERR_NOTDIR): "NFS4ERR_NOTDIR", int32(NFS4ERR_ISDIR): "NFS4ERR_ISDIR", int32(NFS4ERR_INVAL): "NFS4ERR_INVAL", int32(NFS4ERR_FBIG): "NFS4ERR_FBIG", int32(NFS4ERR_NOSPC): "NFS4ERR_NOSPC", int32(NFS4ERR_ROFS): "NFS4ERR_ROFS", int32(NFS4ERR_MLINK): "NFS4ERR_MLINK", int32(NFS4ERR_NAMETOOLONG): "NFS4ERR_NAMETOOLONG", int32(NFS4ERR_NOTEMPTY): "NFS4ERR_NOTEMPTY", int32(NFS4ERR_DQUOT): "NFS4ERR_DQUOT", int32(NFS4ERR_STALE): "NFS4ERR_STALE", int32(NFS4ERR_BADHANDLE): "NFS4ERR_BADHANDLE", int32(NFS4ERR_BAD_COOKIE): "NFS4ERR_BAD_COOKIE", int32(NFS4ERR_NOTSUPP): "NFS4ERR_NOTSUPP", int32(NFS4ERR_TOOSMALL): "NFS4ERR_TOOSMALL", int32(NFS4ERR_SERVERFAULT): "NFS4ERR_SERVERFAULT", int32(NFS4ERR_BADTYPE): "NFS4ERR_BADTYPE", int32(NFS4ERR_DELAY): "NFS4ERR_DELAY", int32(NFS4ERR_SAME): "NFS4ERR_SAME", int32(NFS4ERR_DENIED): "NFS4ERR_DENIED", int32(NFS4ERR_EXPIRED): "NFS4ERR_EXPIRED", int32(NFS4ERR_LOCKED): "NFS4ERR_LOCKED", int32(NFS4ERR_GRACE): "NFS4ERR_GRACE", int32(NFS4ERR_FHEXPIRED): "NFS4ERR_FHEXPIRED", int32(NFS4ERR_SHARE_DENIED): "NFS4ERR_SHARE_DENIED", int32(NFS4ERR_WRONGSEC): "NFS4ERR_WRONGSEC", int32(NFS4ERR_CLID_INUSE): "NFS4ERR_CLID_INUSE", int32(NFS4ERR_RESOURCE): "NFS4ERR_RESOURCE", int32(NFS4ERR_MOVED): "NFS4ERR_MOVED", int32(NFS4ERR_NOFILEHANDLE): "NFS4ERR_NOFILEHANDLE", int32(NFS4ERR_MINOR_VERS_MISMATCH): "NFS4ERR_MINOR_VERS_MISMATCH", int32(NFS4ERR_STALE_CLIENTID): "NFS4ERR_STALE_CLIENTID", int32(NFS4ERR_STALE_STATEID): "NFS4ERR_STALE_STATEID", int32(NFS4ERR_OLD_STATEID): "NFS4ERR_OLD_STATEID", int32(NFS4ERR_BAD_STATEID): "NFS4ERR_BAD_STATEID", int32(NFS4ERR_BAD_SEQID): "NFS4ERR_BAD_SEQID", int32(NFS4ERR_NOT_SAME): "NFS4ERR_NOT_SAME", int32(NFS4ERR_LOCK_RANGE): "NFS4ERR_LOCK_RANGE", int32(NFS4ERR_SYMLINK): "NFS4ERR_SYMLINK", int32(NFS4ERR_RESTOREFH): "NFS4ERR_RESTOREFH", int32(NFS4ERR_LEASE_MOVED): "NFS4ERR_LEASE_MOVED", int32(NFS4ERR_ATTRNOTSUPP): "NFS4ERR_ATTRNOTSUPP", int32(NFS4ERR_NO_GRACE): "NFS4ERR_NO_GRACE", int32(NFS4ERR_RECLAIM_BAD): "NFS4ERR_RECLAIM_BAD", int32(NFS4ERR_RECLAIM_CONFLICT): "NFS4ERR_RECLAIM_CONFLICT", int32(NFS4ERR_BADXDR): "NFS4ERR_BADXDR", int32(NFS4ERR_LOCKS_HELD): "NFS4ERR_LOCKS_HELD", int32(NFS4ERR_OPENMODE): "NFS4ERR_OPENMODE", int32(NFS4ERR_BADOWNER): "NFS4ERR_BADOWNER", int32(NFS4ERR_BADCHAR): "NFS4ERR_BADCHAR", int32(NFS4ERR_BADNAME): "NFS4ERR_BADNAME", int32(NFS4ERR_BAD_RANGE): "NFS4ERR_BAD_RANGE", int32(NFS4ERR_LOCK_NOTSUPP): "NFS4ERR_LOCK_NOTSUPP", int32(NFS4ERR_OP_ILLEGAL): "NFS4ERR_OP_ILLEGAL", int32(NFS4ERR_DEADLOCK): "NFS4ERR_DEADLOCK", int32(NFS4ERR_FILE_OPEN): "NFS4ERR_FILE_OPEN", int32(NFS4ERR_ADMIN_REVOKED): "NFS4ERR_ADMIN_REVOKED", int32(NFS4ERR_CB_PATH_DOWN): "NFS4ERR_CB_PATH_DOWN", int32(NFS4ERR_BADIOMODE): "NFS4ERR_BADIOMODE", int32(NFS4ERR_BADLAYOUT): "NFS4ERR_BADLAYOUT", int32(NFS4ERR_BAD_SESSION_DIGEST): "NFS4ERR_BAD_SESSION_DIGEST", int32(NFS4ERR_BADSESSION): "NFS4ERR_BADSESSION", int32(NFS4ERR_BADSLOT): "NFS4ERR_BADSLOT", int32(NFS4ERR_COMPLETE_ALREADY): "NFS4ERR_COMPLETE_ALREADY", int32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION): "NFS4ERR_CONN_NOT_BOUND_TO_SESSION", int32(NFS4ERR_DELEG_ALREADY_WANTED): "NFS4ERR_DELEG_ALREADY_WANTED", int32(NFS4ERR_BACK_CHAN_BUSY): "NFS4ERR_BACK_CHAN_BUSY", int32(NFS4ERR_LAYOUTTRYLATER): "NFS4ERR_LAYOUTTRYLATER", int32(NFS4ERR_LAYOUTUNAVAILABLE): "NFS4ERR_LAYOUTUNAVAILABLE", int32(NFS4ERR_NOMATCHING_LAYOUT): "NFS4ERR_NOMATCHING_LAYOUT", int32(NFS4ERR_RECALLCONFLICT): "NFS4ERR_RECALLCONFLICT", } var _XdrValues_Nfsstat4 = map[string]int32{ "NFS4_OK": int32(NFS4_OK), "NFS4ERR_PERM": int32(NFS4ERR_PERM), "NFS4ERR_NOENT": int32(NFS4ERR_NOENT), "NFS4ERR_IO": int32(NFS4ERR_IO), "NFS4ERR_NXIO": int32(NFS4ERR_NXIO), "NFS4ERR_ACCESS": int32(NFS4ERR_ACCESS), "NFS4ERR_EXIST": int32(NFS4ERR_EXIST), "NFS4ERR_XDEV": int32(NFS4ERR_XDEV), "NFS4ERR_NOTDIR": int32(NFS4ERR_NOTDIR), "NFS4ERR_ISDIR": int32(NFS4ERR_ISDIR), "NFS4ERR_INVAL": int32(NFS4ERR_INVAL), "NFS4ERR_FBIG": int32(NFS4ERR_FBIG), "NFS4ERR_NOSPC": int32(NFS4ERR_NOSPC), "NFS4ERR_ROFS": int32(NFS4ERR_ROFS), "NFS4ERR_MLINK": int32(NFS4ERR_MLINK), "NFS4ERR_NAMETOOLONG": int32(NFS4ERR_NAMETOOLONG), "NFS4ERR_NOTEMPTY": int32(NFS4ERR_NOTEMPTY), "NFS4ERR_DQUOT": int32(NFS4ERR_DQUOT), "NFS4ERR_STALE": int32(NFS4ERR_STALE), "NFS4ERR_BADHANDLE": int32(NFS4ERR_BADHANDLE), "NFS4ERR_BAD_COOKIE": int32(NFS4ERR_BAD_COOKIE), "NFS4ERR_NOTSUPP": int32(NFS4ERR_NOTSUPP), "NFS4ERR_TOOSMALL": int32(NFS4ERR_TOOSMALL), "NFS4ERR_SERVERFAULT": int32(NFS4ERR_SERVERFAULT), "NFS4ERR_BADTYPE": int32(NFS4ERR_BADTYPE), "NFS4ERR_DELAY": int32(NFS4ERR_DELAY), "NFS4ERR_SAME": int32(NFS4ERR_SAME), "NFS4ERR_DENIED": int32(NFS4ERR_DENIED), "NFS4ERR_EXPIRED": int32(NFS4ERR_EXPIRED), "NFS4ERR_LOCKED": int32(NFS4ERR_LOCKED), "NFS4ERR_GRACE": int32(NFS4ERR_GRACE), "NFS4ERR_FHEXPIRED": int32(NFS4ERR_FHEXPIRED), "NFS4ERR_SHARE_DENIED": int32(NFS4ERR_SHARE_DENIED), "NFS4ERR_WRONGSEC": int32(NFS4ERR_WRONGSEC), "NFS4ERR_CLID_INUSE": int32(NFS4ERR_CLID_INUSE), "NFS4ERR_RESOURCE": int32(NFS4ERR_RESOURCE), "NFS4ERR_MOVED": int32(NFS4ERR_MOVED), "NFS4ERR_NOFILEHANDLE": int32(NFS4ERR_NOFILEHANDLE), "NFS4ERR_MINOR_VERS_MISMATCH": int32(NFS4ERR_MINOR_VERS_MISMATCH), "NFS4ERR_STALE_CLIENTID": int32(NFS4ERR_STALE_CLIENTID), "NFS4ERR_STALE_STATEID": int32(NFS4ERR_STALE_STATEID), "NFS4ERR_OLD_STATEID": int32(NFS4ERR_OLD_STATEID), "NFS4ERR_BAD_STATEID": int32(NFS4ERR_BAD_STATEID), "NFS4ERR_BAD_SEQID": int32(NFS4ERR_BAD_SEQID), "NFS4ERR_NOT_SAME": int32(NFS4ERR_NOT_SAME), "NFS4ERR_LOCK_RANGE": int32(NFS4ERR_LOCK_RANGE), "NFS4ERR_SYMLINK": int32(NFS4ERR_SYMLINK), "NFS4ERR_RESTOREFH": int32(NFS4ERR_RESTOREFH), "NFS4ERR_LEASE_MOVED": int32(NFS4ERR_LEASE_MOVED), "NFS4ERR_ATTRNOTSUPP": int32(NFS4ERR_ATTRNOTSUPP), "NFS4ERR_NO_GRACE": int32(NFS4ERR_NO_GRACE), "NFS4ERR_RECLAIM_BAD": int32(NFS4ERR_RECLAIM_BAD), "NFS4ERR_RECLAIM_CONFLICT": int32(NFS4ERR_RECLAIM_CONFLICT), "NFS4ERR_BADXDR": int32(NFS4ERR_BADXDR), "NFS4ERR_LOCKS_HELD": int32(NFS4ERR_LOCKS_HELD), "NFS4ERR_OPENMODE": int32(NFS4ERR_OPENMODE), "NFS4ERR_BADOWNER": int32(NFS4ERR_BADOWNER), "NFS4ERR_BADCHAR": int32(NFS4ERR_BADCHAR), "NFS4ERR_BADNAME": int32(NFS4ERR_BADNAME), "NFS4ERR_BAD_RANGE": int32(NFS4ERR_BAD_RANGE), "NFS4ERR_LOCK_NOTSUPP": int32(NFS4ERR_LOCK_NOTSUPP), "NFS4ERR_OP_ILLEGAL": int32(NFS4ERR_OP_ILLEGAL), "NFS4ERR_DEADLOCK": int32(NFS4ERR_DEADLOCK), "NFS4ERR_FILE_OPEN": int32(NFS4ERR_FILE_OPEN), "NFS4ERR_ADMIN_REVOKED": int32(NFS4ERR_ADMIN_REVOKED), "NFS4ERR_CB_PATH_DOWN": int32(NFS4ERR_CB_PATH_DOWN), "NFS4ERR_BADIOMODE": int32(NFS4ERR_BADIOMODE), "NFS4ERR_BADLAYOUT": int32(NFS4ERR_BADLAYOUT), "NFS4ERR_BAD_SESSION_DIGEST": int32(NFS4ERR_BAD_SESSION_DIGEST), "NFS4ERR_BADSESSION": int32(NFS4ERR_BADSESSION), "NFS4ERR_BADSLOT": int32(NFS4ERR_BADSLOT), "NFS4ERR_COMPLETE_ALREADY": int32(NFS4ERR_COMPLETE_ALREADY), "NFS4ERR_CONN_NOT_BOUND_TO_SESSION": int32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION), "NFS4ERR_DELEG_ALREADY_WANTED": int32(NFS4ERR_DELEG_ALREADY_WANTED), "NFS4ERR_BACK_CHAN_BUSY": int32(NFS4ERR_BACK_CHAN_BUSY), "NFS4ERR_LAYOUTTRYLATER": int32(NFS4ERR_LAYOUTTRYLATER), "NFS4ERR_LAYOUTUNAVAILABLE": int32(NFS4ERR_LAYOUTUNAVAILABLE), "NFS4ERR_NOMATCHING_LAYOUT": int32(NFS4ERR_NOMATCHING_LAYOUT), "NFS4ERR_RECALLCONFLICT": int32(NFS4ERR_RECALLCONFLICT), } func (Nfsstat4) XdrEnumNames() map[int32]string { return _XdrNames_Nfsstat4 } func (v Nfsstat4) String() string { if s, ok := _XdrNames_Nfsstat4[int32(v)]; ok { return s } return fmt.Sprintf("Nfsstat4#%d", v) } func (v *Nfsstat4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Nfsstat4[stok]; ok { *v = Nfsstat4(val) return nil } else if stok == "Nfsstat4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Nfsstat4.", stok)) } } func (v Nfsstat4) GetU32() uint32 { return uint32(v) } func (v *Nfsstat4) SetU32(n uint32) { *v = Nfsstat4(n) } func (v *Nfsstat4) XdrPointer() interface{} { return v } func (Nfsstat4) XdrTypeName() string { return "Nfsstat4" } func (v Nfsstat4) XdrValue() interface{} { return v } func (v *Nfsstat4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Nfsstat4 = *Nfsstat4 func XDR_Nfsstat4(v *Nfsstat4) *Nfsstat4 { return v } type _XdrVec_unbounded_Uint32_t []Uint32_t func (_XdrVec_unbounded_Uint32_t) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Uint32_t) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Uint32_t length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Uint32_t length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Uint32_t) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Uint32_t) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Uint32_t, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Uint32_t) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Uint32_t(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Uint32_t) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Uint32_t) XdrTypeName() string { return "Uint32_t<>" } func (v *_XdrVec_unbounded_Uint32_t) XdrPointer() interface{} { return (*[]Uint32_t)(v) } func (v _XdrVec_unbounded_Uint32_t) XdrValue() interface{} { return ([]Uint32_t)(v) } func (v *_XdrVec_unbounded_Uint32_t) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Bitmap4 struct { *_XdrVec_unbounded_Uint32_t } func XDR_Bitmap4(v *Bitmap4) XdrType_Bitmap4 { return XdrType_Bitmap4{(*_XdrVec_unbounded_Uint32_t)(v)} } func (XdrType_Bitmap4) XdrTypeName() string { return "Bitmap4" } func (v XdrType_Bitmap4) XdrUnwrap() XdrType { return v._XdrVec_unbounded_Uint32_t } type XdrType_Offset4 struct { XdrType_Uint64_t } func XDR_Offset4(v *Offset4) *XdrType_Offset4 { return &XdrType_Offset4{XDR_Uint64_t(v)} } func (XdrType_Offset4) XdrTypeName() string { return "Offset4" } func (v XdrType_Offset4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Count4 struct { XdrType_Uint32_t } func XDR_Count4(v *Count4) *XdrType_Count4 { return &XdrType_Count4{XDR_Uint32_t(v)} } func (XdrType_Count4) XdrTypeName() string { return "Count4" } func (v XdrType_Count4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Length4 struct { XdrType_Uint64_t } func XDR_Length4(v *Length4) *XdrType_Length4 { return &XdrType_Length4{XDR_Uint64_t(v)} } func (XdrType_Length4) XdrTypeName() string { return "Length4" } func (v XdrType_Length4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Clientid4 struct { XdrType_Uint64_t } func XDR_Clientid4(v *Clientid4) *XdrType_Clientid4 { return &XdrType_Clientid4{XDR_Uint64_t(v)} } func (XdrType_Clientid4) XdrTypeName() string { return "Clientid4" } func (v XdrType_Clientid4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Sequenceid4 struct { XdrType_Uint32_t } func XDR_Sequenceid4(v *Sequenceid4) *XdrType_Sequenceid4 { return &XdrType_Sequenceid4{XDR_Uint32_t(v)} } func (XdrType_Sequenceid4) XdrTypeName() string { return "Sequenceid4" } func (v XdrType_Sequenceid4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Seqid4 struct { XdrType_Uint32_t } func XDR_Seqid4(v *Seqid4) *XdrType_Seqid4 { return &XdrType_Seqid4{XDR_Uint32_t(v)} } func (XdrType_Seqid4) XdrTypeName() string { return "Seqid4" } func (v XdrType_Seqid4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Slotid4 struct { XdrType_Uint32_t } func XDR_Slotid4(v *Slotid4) *XdrType_Slotid4 { return &XdrType_Slotid4{XDR_Uint32_t(v)} } func (XdrType_Slotid4) XdrTypeName() string { return "Slotid4" } func (v XdrType_Slotid4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Utf8string struct { XdrVecOpaque } func XDR_Utf8string(v *Utf8string) XdrType_Utf8string { return XdrType_Utf8string{XdrVecOpaque{v, 0xffffffff}} } func (XdrType_Utf8string) XdrTypeName() string { return "Utf8string" } func (v XdrType_Utf8string) XdrUnwrap() XdrType { return v.XdrVecOpaque } type XdrType_Utf8str_cis struct { XdrType_Utf8string } func XDR_Utf8str_cis(v *Utf8str_cis) XdrType_Utf8str_cis { return XdrType_Utf8str_cis{XDR_Utf8string(v)} } func (XdrType_Utf8str_cis) XdrTypeName() string { return "Utf8str_cis" } func (v XdrType_Utf8str_cis) XdrUnwrap() XdrType { return v.XdrType_Utf8string } type XdrType_Utf8str_cs struct { XdrType_Utf8string } func XDR_Utf8str_cs(v *Utf8str_cs) XdrType_Utf8str_cs { return XdrType_Utf8str_cs{XDR_Utf8string(v)} } func (XdrType_Utf8str_cs) XdrTypeName() string { return "Utf8str_cs" } func (v XdrType_Utf8str_cs) XdrUnwrap() XdrType { return v.XdrType_Utf8string } type XdrType_Utf8str_mixed struct { XdrType_Utf8string } func XDR_Utf8str_mixed(v *Utf8str_mixed) XdrType_Utf8str_mixed { return XdrType_Utf8str_mixed{XDR_Utf8string(v)} } func (XdrType_Utf8str_mixed) XdrTypeName() string { return "Utf8str_mixed" } func (v XdrType_Utf8str_mixed) XdrUnwrap() XdrType { return v.XdrType_Utf8string } type XdrType_Component4 struct { XdrType_Utf8str_cs } func XDR_Component4(v *Component4) XdrType_Component4 { return XdrType_Component4{XDR_Utf8str_cs(v)} } func (XdrType_Component4) XdrTypeName() string { return "Component4" } func (v XdrType_Component4) XdrUnwrap() XdrType { return v.XdrType_Utf8str_cs } type _XdrVec_unbounded_Component4 []Component4 func (_XdrVec_unbounded_Component4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Component4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Component4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Component4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Component4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Component4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Component4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Component4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Component4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Component4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Component4) XdrTypeName() string { return "Component4<>" } func (v *_XdrVec_unbounded_Component4) XdrPointer() interface{} { return (*[]Component4)(v) } func (v _XdrVec_unbounded_Component4) XdrValue() interface{} { return ([]Component4)(v) } func (v *_XdrVec_unbounded_Component4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Pathname4 struct { *_XdrVec_unbounded_Component4 } func XDR_Pathname4(v *Pathname4) XdrType_Pathname4 { return XdrType_Pathname4{(*_XdrVec_unbounded_Component4)(v)} } func (XdrType_Pathname4) XdrTypeName() string { return "Pathname4" } func (v XdrType_Pathname4) XdrUnwrap() XdrType { return v._XdrVec_unbounded_Component4 } type XdrType_Nfs_lockid4 struct { XdrType_Uint64_t } func XDR_Nfs_lockid4(v *Nfs_lockid4) XdrType_Nfs_lockid4 { return XdrType_Nfs_lockid4{XDR_Uint64_t(v)} } func (XdrType_Nfs_lockid4) XdrTypeName() string { return "Nfs_lockid4" } func (v XdrType_Nfs_lockid4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Nfs_cookie4 struct { XdrType_Uint64_t } func XDR_Nfs_cookie4(v *Nfs_cookie4) *XdrType_Nfs_cookie4 { return &XdrType_Nfs_cookie4{XDR_Uint64_t(v)} } func (XdrType_Nfs_cookie4) XdrTypeName() string { return "Nfs_cookie4" } func (v XdrType_Nfs_cookie4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Linktext4 struct { XdrType_Utf8str_cs } func XDR_Linktext4(v *Linktext4) XdrType_Linktext4 { return XdrType_Linktext4{XDR_Utf8str_cs(v)} } func (XdrType_Linktext4) XdrTypeName() string { return "Linktext4" } func (v XdrType_Linktext4) XdrUnwrap() XdrType { return v.XdrType_Utf8str_cs } type XdrType_Sec_oid4 struct { XdrVecOpaque } func XDR_Sec_oid4(v *Sec_oid4) XdrType_Sec_oid4 { return XdrType_Sec_oid4{XdrVecOpaque{v, 0xffffffff}} } func (XdrType_Sec_oid4) XdrTypeName() string { return "Sec_oid4" } func (v XdrType_Sec_oid4) XdrUnwrap() XdrType { return v.XdrVecOpaque } type XdrType_Qop4 struct { XdrType_Uint32_t } func XDR_Qop4(v *Qop4) *XdrType_Qop4 { return &XdrType_Qop4{XDR_Uint32_t(v)} } func (XdrType_Qop4) XdrTypeName() string { return "Qop4" } func (v XdrType_Qop4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Mode4 struct { XdrType_Uint32_t } func XDR_Mode4(v *Mode4) XdrType_Mode4 { return XdrType_Mode4{XDR_Uint32_t(v)} } func (XdrType_Mode4) XdrTypeName() string { return "Mode4" } func (v XdrType_Mode4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Changeid4 struct { XdrType_Uint64_t } func XDR_Changeid4(v *Changeid4) *XdrType_Changeid4 { return &XdrType_Changeid4{XDR_Uint64_t(v)} } func (XdrType_Changeid4) XdrTypeName() string { return "Changeid4" } func (v XdrType_Changeid4) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type _XdrArray_8_opaque [8]byte func (v *_XdrArray_8_opaque) GetByteSlice() []byte { return v[:] } func (v *_XdrArray_8_opaque) XdrTypeName() string { return "opaque[]" } func (v *_XdrArray_8_opaque) XdrValue() interface{} { return v[:] } func (v *_XdrArray_8_opaque) XdrPointer() interface{} { return (*[8]byte)(v) } func (v *_XdrArray_8_opaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *_XdrArray_8_opaque) String() string { return fmt.Sprintf("%x", v[:]) } func (v *_XdrArray_8_opaque) Scan(ss fmt.ScanState, c rune) error { return XdrArrayOpaqueScan(v[:], ss, c) } func (_XdrArray_8_opaque) XdrArraySize() uint32 { const bound uint32 = 8 // Force error if not const or doesn't fit return bound } type XdrType_Verifier4 struct { *_XdrArray_8_opaque } func XDR_Verifier4(v *Verifier4) XdrType_Verifier4 { return XdrType_Verifier4{(*_XdrArray_8_opaque)(v)} } func (XdrType_Verifier4) XdrTypeName() string { return "Verifier4" } func (v XdrType_Verifier4) XdrUnwrap() XdrType { return v._XdrArray_8_opaque } type _XdrArray_16_opaque [16]byte func (v *_XdrArray_16_opaque) GetByteSlice() []byte { return v[:] } func (v *_XdrArray_16_opaque) XdrTypeName() string { return "opaque[]" } func (v *_XdrArray_16_opaque) XdrValue() interface{} { return v[:] } func (v *_XdrArray_16_opaque) XdrPointer() interface{} { return (*[16]byte)(v) } func (v *_XdrArray_16_opaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *_XdrArray_16_opaque) String() string { return fmt.Sprintf("%x", v[:]) } func (v *_XdrArray_16_opaque) Scan(ss fmt.ScanState, c rune) error { return XdrArrayOpaqueScan(v[:], ss, c) } func (_XdrArray_16_opaque) XdrArraySize() uint32 { const bound uint32 = 16 // Force error if not const or doesn't fit return bound } type XdrType_Sessionid4 struct { *_XdrArray_16_opaque } func XDR_Sessionid4(v *Sessionid4) XdrType_Sessionid4 { return XdrType_Sessionid4{(*_XdrArray_16_opaque)(v)} } func (XdrType_Sessionid4) XdrTypeName() string { return "Sessionid4" } func (v XdrType_Sessionid4) XdrUnwrap() XdrType { return v._XdrArray_16_opaque } type _XdrVec_16_uint32 []uint32 func (_XdrVec_16_uint32) XdrBound() uint32 { const bound uint32 = 16 // Force error if not const or doesn't fit return bound } func (_XdrVec_16_uint32) XdrCheckLen(length uint32) { if length > uint32(16) { XdrPanic("_XdrVec_16_uint32 length %d exceeds bound 16", length) } else if int(length) < 0 { XdrPanic("_XdrVec_16_uint32 length %d exceeds max int", length) } } func (v _XdrVec_16_uint32) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_16_uint32) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(16); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]uint32, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_16_uint32) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_uint32(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_16_uint32) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 16 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_16_uint32) XdrTypeName() string { return "uint32<>" } func (v *_XdrVec_16_uint32) XdrPointer() interface{} { return (*[]uint32)(v) } func (v _XdrVec_16_uint32) XdrValue() interface{} { return ([]uint32)(v) } func (v *_XdrVec_16_uint32) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Authsys_parms = *Authsys_parms func (v *Authsys_parms) XdrPointer() interface{} { return v } func (Authsys_parms) XdrTypeName() string { return "Authsys_parms" } func (v Authsys_parms) XdrValue() interface{} { return v } func (v *Authsys_parms) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Authsys_parms) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstamp", name), XDR_uint32(&v.Stamp)) x.Marshal(x.Sprintf("%smachinename", name), XdrString{&v.Machinename, 255}) x.Marshal(x.Sprintf("%suid", name), XDR_uint32(&v.Uid)) x.Marshal(x.Sprintf("%sgid", name), XDR_uint32(&v.Gid)) x.Marshal(x.Sprintf("%sgids", name), (*_XdrVec_16_uint32)(&v.Gids)) } func XDR_Authsys_parms(v *Authsys_parms) *Authsys_parms { return v } type XdrType_Deviceid4 struct { *_XdrArray_16_opaque } func XDR_Deviceid4(v *Deviceid4) *XdrType_Deviceid4 { return &XdrType_Deviceid4{(*_XdrArray_16_opaque)(v)} } func (XdrType_Deviceid4) XdrTypeName() string { return "Deviceid4" } func (v XdrType_Deviceid4) XdrUnwrap() XdrType { return v._XdrArray_16_opaque } var _XdrNames_Layouttype4 = map[int32]string{ int32(LAYOUT4_NFSV4_1_FILES): "LAYOUT4_NFSV4_1_FILES", int32(LAYOUT4_OSD2_OBJECTS): "LAYOUT4_OSD2_OBJECTS", int32(LAYOUT4_BLOCK_VOLUME): "LAYOUT4_BLOCK_VOLUME", } var _XdrValues_Layouttype4 = map[string]int32{ "LAYOUT4_NFSV4_1_FILES": int32(LAYOUT4_NFSV4_1_FILES), "LAYOUT4_OSD2_OBJECTS": int32(LAYOUT4_OSD2_OBJECTS), "LAYOUT4_BLOCK_VOLUME": int32(LAYOUT4_BLOCK_VOLUME), } func (Layouttype4) XdrEnumNames() map[int32]string { return _XdrNames_Layouttype4 } func (v Layouttype4) String() string { if s, ok := _XdrNames_Layouttype4[int32(v)]; ok { return s } return fmt.Sprintf("Layouttype4#%d", v) } func (v *Layouttype4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Layouttype4[stok]; ok { *v = Layouttype4(val) return nil } else if stok == "Layouttype4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Layouttype4.", stok)) } } func (v Layouttype4) GetU32() uint32 { return uint32(v) } func (v *Layouttype4) SetU32(n uint32) { *v = Layouttype4(n) } func (v *Layouttype4) XdrPointer() interface{} { return v } func (Layouttype4) XdrTypeName() string { return "Layouttype4" } func (v Layouttype4) XdrValue() interface{} { return v } func (v *Layouttype4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Layouttype4 = *Layouttype4 func XDR_Layouttype4(v *Layouttype4) *Layouttype4 { return v } func (v *Layouttype4) XdrInitialize() { switch Layouttype4(0) { case LAYOUT4_NFSV4_1_FILES, LAYOUT4_OSD2_OBJECTS, LAYOUT4_BLOCK_VOLUME: default: if *v == Layouttype4(0) { *v = LAYOUT4_NFSV4_1_FILES } } } type XdrType_Layoutupdate4 = *Layoutupdate4 func (v *Layoutupdate4) XdrPointer() interface{} { return v } func (Layoutupdate4) XdrTypeName() string { return "Layoutupdate4" } func (v Layoutupdate4) XdrValue() interface{} { return v } func (v *Layoutupdate4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Layoutupdate4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slou_type", name), XDR_Layouttype4(&v.Lou_type)) x.Marshal(x.Sprintf("%slou_body", name), XdrVecOpaque{&v.Lou_body, 0xffffffff}) } func XDR_Layoutupdate4(v *Layoutupdate4) *Layoutupdate4 { return v } type XdrType_Device_addr4 = *Device_addr4 func (v *Device_addr4) XdrPointer() interface{} { return v } func (Device_addr4) XdrTypeName() string { return "Device_addr4" } func (v Device_addr4) XdrValue() interface{} { return v } func (v *Device_addr4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Device_addr4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sda_layout_type", name), XDR_Layouttype4(&v.Da_layout_type)) x.Marshal(x.Sprintf("%sda_addr_body", name), XdrVecOpaque{&v.Da_addr_body, 0xffffffff}) } func XDR_Device_addr4(v *Device_addr4) *Device_addr4 { return v } type XdrType_Nfstime4 = *Nfstime4 func (v *Nfstime4) XdrPointer() interface{} { return v } func (Nfstime4) XdrTypeName() string { return "Nfstime4" } func (v Nfstime4) XdrValue() interface{} { return v } func (v *Nfstime4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Nfstime4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sseconds", name), XDR_Int64_t(&v.Seconds)) x.Marshal(x.Sprintf("%snseconds", name), XDR_Uint32_t(&v.Nseconds)) } func XDR_Nfstime4(v *Nfstime4) *Nfstime4 { return v } var _XdrNames_Time_how4 = map[int32]string{ int32(SET_TO_SERVER_TIME4): "SET_TO_SERVER_TIME4", int32(SET_TO_CLIENT_TIME4): "SET_TO_CLIENT_TIME4", } var _XdrValues_Time_how4 = map[string]int32{ "SET_TO_SERVER_TIME4": int32(SET_TO_SERVER_TIME4), "SET_TO_CLIENT_TIME4": int32(SET_TO_CLIENT_TIME4), } func (Time_how4) XdrEnumNames() map[int32]string { return _XdrNames_Time_how4 } func (v Time_how4) String() string { if s, ok := _XdrNames_Time_how4[int32(v)]; ok { return s } return fmt.Sprintf("Time_how4#%d", v) } func (v *Time_how4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Time_how4[stok]; ok { *v = Time_how4(val) return nil } else if stok == "Time_how4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Time_how4.", stok)) } } func (v Time_how4) GetU32() uint32 { return uint32(v) } func (v *Time_how4) SetU32(n uint32) { *v = Time_how4(n) } func (v *Time_how4) XdrPointer() interface{} { return v } func (Time_how4) XdrTypeName() string { return "Time_how4" } func (v Time_how4) XdrValue() interface{} { return v } func (v *Time_how4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Time_how4 = *Time_how4 func XDR_Time_how4(v *Time_how4) *Time_how4 { return v } var _XdrNames_Layoutiomode4 = map[int32]string{ int32(LAYOUTIOMODE4_READ): "LAYOUTIOMODE4_READ", int32(LAYOUTIOMODE4_RW): "LAYOUTIOMODE4_RW", int32(LAYOUTIOMODE4_ANY): "LAYOUTIOMODE4_ANY", } var _XdrValues_Layoutiomode4 = map[string]int32{ "LAYOUTIOMODE4_READ": int32(LAYOUTIOMODE4_READ), "LAYOUTIOMODE4_RW": int32(LAYOUTIOMODE4_RW), "LAYOUTIOMODE4_ANY": int32(LAYOUTIOMODE4_ANY), } func (Layoutiomode4) XdrEnumNames() map[int32]string { return _XdrNames_Layoutiomode4 } func (v Layoutiomode4) String() string { if s, ok := _XdrNames_Layoutiomode4[int32(v)]; ok { return s } return fmt.Sprintf("Layoutiomode4#%d", v) } func (v *Layoutiomode4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Layoutiomode4[stok]; ok { *v = Layoutiomode4(val) return nil } else if stok == "Layoutiomode4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Layoutiomode4.", stok)) } } func (v Layoutiomode4) GetU32() uint32 { return uint32(v) } func (v *Layoutiomode4) SetU32(n uint32) { *v = Layoutiomode4(n) } func (v *Layoutiomode4) XdrPointer() interface{} { return v } func (Layoutiomode4) XdrTypeName() string { return "Layoutiomode4" } func (v Layoutiomode4) XdrValue() interface{} { return v } func (v *Layoutiomode4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Layoutiomode4 = *Layoutiomode4 func XDR_Layoutiomode4(v *Layoutiomode4) *Layoutiomode4 { return v } func (v *Layoutiomode4) XdrInitialize() { switch Layoutiomode4(0) { case LAYOUTIOMODE4_READ, LAYOUTIOMODE4_RW, LAYOUTIOMODE4_ANY: default: if *v == Layoutiomode4(0) { *v = LAYOUTIOMODE4_READ } } } type XdrType_Layout_content4 = *Layout_content4 func (v *Layout_content4) XdrPointer() interface{} { return v } func (Layout_content4) XdrTypeName() string { return "Layout_content4" } func (v Layout_content4) XdrValue() interface{} { return v } func (v *Layout_content4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Layout_content4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sloc_type", name), XDR_Layouttype4(&v.Loc_type)) x.Marshal(x.Sprintf("%sloc_body", name), XdrVecOpaque{&v.Loc_body, 0xffffffff}) } func XDR_Layout_content4(v *Layout_content4) *Layout_content4 { return v } type XdrType_Layout4 = *Layout4 func (v *Layout4) XdrPointer() interface{} { return v } func (Layout4) XdrTypeName() string { return "Layout4" } func (v Layout4) XdrValue() interface{} { return v } func (v *Layout4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Layout4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slo_offset", name), XDR_Offset4(&v.Lo_offset)) x.Marshal(x.Sprintf("%slo_length", name), XDR_Length4(&v.Lo_length)) x.Marshal(x.Sprintf("%slo_iomode", name), XDR_Layoutiomode4(&v.Lo_iomode)) x.Marshal(x.Sprintf("%slo_content", name), XDR_Layout_content4(&v.Lo_content)) } func XDR_Layout4(v *Layout4) *Layout4 { return v } func (u *Settime4) Time() *Nfstime4 { switch u.Set_it { case SET_TO_CLIENT_TIME4: if v, ok := u.U.(*Nfstime4); ok { return v } else { var zero Nfstime4 u.U = &zero return &zero } default: XdrPanic("Settime4.Time accessed when Set_it == %v", u.Set_it) return nil } } func (u Settime4) XdrValid() bool { return true } func (u *Settime4) XdrUnionTag() XdrNum32 { return XDR_Time_how4(&u.Set_it) } func (u *Settime4) XdrUnionTagName() string { return "Set_it" } func (u *Settime4) XdrUnionBody() XdrType { switch u.Set_it { case SET_TO_CLIENT_TIME4: return XDR_Nfstime4(u.Time()) default: return nil } } func (u *Settime4) XdrUnionBodyName() string { switch u.Set_it { case SET_TO_CLIENT_TIME4: return "Time" default: return "" } } type XdrType_Settime4 = *Settime4 func (v *Settime4) XdrPointer() interface{} { return v } func (Settime4) XdrTypeName() string { return "Settime4" } func (v Settime4) XdrValue() interface{} { return v } func (v *Settime4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Settime4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Time_how4(&u.Set_it).XdrMarshal(x, x.Sprintf("%sset_it", name)) switch u.Set_it { case SET_TO_CLIENT_TIME4: x.Marshal(x.Sprintf("%stime", name), XDR_Nfstime4(u.Time())) return default: return } } func XDR_Settime4(v *Settime4) *Settime4 { return v} type XdrType_Nfs_fh4 struct { XdrVecOpaque } func XDR_Nfs_fh4(v *Nfs_fh4) XdrType_Nfs_fh4 { return XdrType_Nfs_fh4{XdrVecOpaque{v, NFS4_FHSIZE}} } func (XdrType_Nfs_fh4) XdrTypeName() string { return "Nfs_fh4" } func (v XdrType_Nfs_fh4) XdrUnwrap() XdrType { return v.XdrVecOpaque } type XdrType_Fsid4 = *Fsid4 func (v *Fsid4) XdrPointer() interface{} { return v } func (Fsid4) XdrTypeName() string { return "Fsid4" } func (v Fsid4) XdrValue() interface{} { return v } func (v *Fsid4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Fsid4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%smajor", name), XDR_Uint64_t(&v.Major)) x.Marshal(x.Sprintf("%sminor", name), XDR_Uint64_t(&v.Minor)) } func XDR_Fsid4(v *Fsid4) *Fsid4 { return v } type _XdrVec_unbounded_Utf8str_cis []Utf8str_cis func (_XdrVec_unbounded_Utf8str_cis) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Utf8str_cis) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Utf8str_cis length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Utf8str_cis length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Utf8str_cis) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Utf8str_cis) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Utf8str_cis, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Utf8str_cis) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Utf8str_cis(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Utf8str_cis) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Utf8str_cis) XdrTypeName() string { return "Utf8str_cis<>" } func (v *_XdrVec_unbounded_Utf8str_cis) XdrPointer() interface{} { return (*[]Utf8str_cis)(v) } func (v _XdrVec_unbounded_Utf8str_cis) XdrValue() interface{} { return ([]Utf8str_cis)(v) } func (v *_XdrVec_unbounded_Utf8str_cis) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Fs_location4 = *Fs_location4 func (v *Fs_location4) XdrPointer() interface{} { return v } func (Fs_location4) XdrTypeName() string { return "Fs_location4" } func (v Fs_location4) XdrValue() interface{} { return v } func (v *Fs_location4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Fs_location4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sserver", name), (*_XdrVec_unbounded_Utf8str_cis)(&v.Server)) x.Marshal(x.Sprintf("%srootpath", name), XDR_Pathname4(&v.Rootpath)) } func XDR_Fs_location4(v *Fs_location4) *Fs_location4 { return v } type _XdrVec_unbounded_Fs_location4 []Fs_location4 func (_XdrVec_unbounded_Fs_location4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Fs_location4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Fs_location4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Fs_location4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Fs_location4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Fs_location4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Fs_location4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Fs_location4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Fs_location4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Fs_location4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Fs_location4) XdrTypeName() string { return "Fs_location4<>" } func (v *_XdrVec_unbounded_Fs_location4) XdrPointer() interface{} { return (*[]Fs_location4)(v) } func (v _XdrVec_unbounded_Fs_location4) XdrValue() interface{} { return ([]Fs_location4)(v) } func (v *_XdrVec_unbounded_Fs_location4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Fs_locations4 = *Fs_locations4 func (v *Fs_locations4) XdrPointer() interface{} { return v } func (Fs_locations4) XdrTypeName() string { return "Fs_locations4" } func (v Fs_locations4) XdrValue() interface{} { return v } func (v *Fs_locations4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Fs_locations4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sfs_root", name), XDR_Pathname4(&v.Fs_root)) x.Marshal(x.Sprintf("%slocations", name), (*_XdrVec_unbounded_Fs_location4)(&v.Locations)) } func XDR_Fs_locations4(v *Fs_locations4) *Fs_locations4 { return v } type XdrType_Acetype4 struct { XdrType_Uint32_t } func XDR_Acetype4(v *Acetype4) *XdrType_Acetype4 { return &XdrType_Acetype4{XDR_Uint32_t(v)} } func (XdrType_Acetype4) XdrTypeName() string { return "Acetype4" } func (v XdrType_Acetype4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Aceflag4 struct { XdrType_Uint32_t } func XDR_Aceflag4(v *Aceflag4) *XdrType_Aceflag4 { return &XdrType_Aceflag4{XDR_Uint32_t(v)} } func (XdrType_Aceflag4) XdrTypeName() string { return "Aceflag4" } func (v XdrType_Aceflag4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Acemask4 struct { XdrType_Uint32_t } func XDR_Acemask4(v *Acemask4) *XdrType_Acemask4 { return &XdrType_Acemask4{XDR_Uint32_t(v)} } func (XdrType_Acemask4) XdrTypeName() string { return "Acemask4" } func (v XdrType_Acemask4) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Nfsace4 = *Nfsace4 func (v *Nfsace4) XdrPointer() interface{} { return v } func (Nfsace4) XdrTypeName() string { return "Nfsace4" } func (v Nfsace4) XdrValue() interface{} { return v } func (v *Nfsace4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Nfsace4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%stype", name), XDR_Acetype4(&v.Type)) x.Marshal(x.Sprintf("%sflag", name), XDR_Aceflag4(&v.Flag)) x.Marshal(x.Sprintf("%saccess_mask", name), XDR_Acemask4(&v.Access_mask)) x.Marshal(x.Sprintf("%swho", name), XDR_Utf8str_mixed(&v.Who)) } func XDR_Nfsace4(v *Nfsace4) *Nfsace4 { return v } type XdrType_Specdata4 = *Specdata4 func (v *Specdata4) XdrPointer() interface{} { return v } func (Specdata4) XdrTypeName() string { return "Specdata4" } func (v Specdata4) XdrValue() interface{} { return v } func (v *Specdata4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Specdata4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sspecdata1", name), XDR_Uint32_t(&v.Specdata1)) x.Marshal(x.Sprintf("%sspecdata2", name), XDR_Uint32_t(&v.Specdata2)) } func XDR_Specdata4(v *Specdata4) *Specdata4 { return v } type XdrType_Fattr4_supported_attrs struct { XdrType_Bitmap4 } func XDR_Fattr4_supported_attrs(v *Fattr4_supported_attrs) XdrType_Fattr4_supported_attrs { return XdrType_Fattr4_supported_attrs{XDR_Bitmap4(v)} } func (XdrType_Fattr4_supported_attrs) XdrTypeName() string { return "Fattr4_supported_attrs" } func (v XdrType_Fattr4_supported_attrs) XdrUnwrap() XdrType { return v.XdrType_Bitmap4 } type XdrType_Fattr4_type struct { XdrType_Nfs_ftype4 } func XDR_Fattr4_type(v *Fattr4_type) XdrType_Fattr4_type { return XdrType_Fattr4_type{XDR_Nfs_ftype4(v)} } func (XdrType_Fattr4_type) XdrTypeName() string { return "Fattr4_type" } func (v XdrType_Fattr4_type) XdrUnwrap() XdrType { return v.XdrType_Nfs_ftype4 } type XdrType_Fattr4_fh_expire_type struct { XdrType_Uint32_t } func XDR_Fattr4_fh_expire_type(v *Fattr4_fh_expire_type) XdrType_Fattr4_fh_expire_type { return XdrType_Fattr4_fh_expire_type{XDR_Uint32_t(v)} } func (XdrType_Fattr4_fh_expire_type) XdrTypeName() string { return "Fattr4_fh_expire_type" } func (v XdrType_Fattr4_fh_expire_type) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_change struct { XdrType_Changeid4 } func XDR_Fattr4_change(v *Fattr4_change) *XdrType_Fattr4_change { return &XdrType_Fattr4_change{*XDR_Changeid4(v)} } func (XdrType_Fattr4_change) XdrTypeName() string { return "Fattr4_change" } func (v XdrType_Fattr4_change) XdrUnwrap() XdrType { return &v.XdrType_Changeid4 } type XdrType_Fattr4_size struct { XdrType_Uint64_t } func XDR_Fattr4_size(v *Fattr4_size) XdrType_Fattr4_size { return XdrType_Fattr4_size{XDR_Uint64_t(v)} } func (XdrType_Fattr4_size) XdrTypeName() string { return "Fattr4_size" } func (v XdrType_Fattr4_size) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_link_support struct { XdrType_bool } func XDR_Fattr4_link_support(v *Fattr4_link_support) XdrType_Fattr4_link_support { return XdrType_Fattr4_link_support{XDR_bool(v)} } func (XdrType_Fattr4_link_support) XdrTypeName() string { return "Fattr4_link_support" } func (v XdrType_Fattr4_link_support) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_symlink_support struct { XdrType_bool } func XDR_Fattr4_symlink_support(v *Fattr4_symlink_support) XdrType_Fattr4_symlink_support { return XdrType_Fattr4_symlink_support{XDR_bool(v)} } func (XdrType_Fattr4_symlink_support) XdrTypeName() string { return "Fattr4_symlink_support" } func (v XdrType_Fattr4_symlink_support) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_named_attr struct { XdrType_bool } func XDR_Fattr4_named_attr(v *Fattr4_named_attr) XdrType_Fattr4_named_attr { return XdrType_Fattr4_named_attr{XDR_bool(v)} } func (XdrType_Fattr4_named_attr) XdrTypeName() string { return "Fattr4_named_attr" } func (v XdrType_Fattr4_named_attr) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_fsid struct { XdrType_Fsid4 } func XDR_Fattr4_fsid(v *Fattr4_fsid) XdrType_Fattr4_fsid { return XdrType_Fattr4_fsid{XDR_Fsid4(v)} } func (XdrType_Fattr4_fsid) XdrTypeName() string { return "Fattr4_fsid" } func (v XdrType_Fattr4_fsid) XdrUnwrap() XdrType { return v.XdrType_Fsid4 } type XdrType_Fattr4_unique_handles struct { XdrType_bool } func XDR_Fattr4_unique_handles(v *Fattr4_unique_handles) XdrType_Fattr4_unique_handles { return XdrType_Fattr4_unique_handles{XDR_bool(v)} } func (XdrType_Fattr4_unique_handles) XdrTypeName() string { return "Fattr4_unique_handles" } func (v XdrType_Fattr4_unique_handles) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_lease_time struct { XdrType_Uint32_t } func XDR_Fattr4_lease_time(v *Fattr4_lease_time) XdrType_Fattr4_lease_time { return XdrType_Fattr4_lease_time{XDR_Uint32_t(v)} } func (XdrType_Fattr4_lease_time) XdrTypeName() string { return "Fattr4_lease_time" } func (v XdrType_Fattr4_lease_time) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_rdattr_error struct { XdrType_Nfsstat4 } func XDR_Fattr4_rdattr_error(v *Fattr4_rdattr_error) XdrType_Fattr4_rdattr_error { return XdrType_Fattr4_rdattr_error{XDR_Nfsstat4(v)} } func (XdrType_Fattr4_rdattr_error) XdrTypeName() string { return "Fattr4_rdattr_error" } func (v XdrType_Fattr4_rdattr_error) XdrUnwrap() XdrType { return v.XdrType_Nfsstat4 } type _XdrVec_unbounded_Nfsace4 []Nfsace4 func (_XdrVec_unbounded_Nfsace4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfsace4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfsace4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfsace4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfsace4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfsace4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfsace4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfsace4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfsace4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfsace4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfsace4) XdrTypeName() string { return "Nfsace4<>" } func (v *_XdrVec_unbounded_Nfsace4) XdrPointer() interface{} { return (*[]Nfsace4)(v) } func (v _XdrVec_unbounded_Nfsace4) XdrValue() interface{} { return ([]Nfsace4)(v) } func (v *_XdrVec_unbounded_Nfsace4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Fattr4_acl struct { *_XdrVec_unbounded_Nfsace4 } func XDR_Fattr4_acl(v *Fattr4_acl) XdrType_Fattr4_acl { return XdrType_Fattr4_acl{(*_XdrVec_unbounded_Nfsace4)(v)} } func (XdrType_Fattr4_acl) XdrTypeName() string { return "Fattr4_acl" } func (v XdrType_Fattr4_acl) XdrUnwrap() XdrType { return v._XdrVec_unbounded_Nfsace4 } type XdrType_Fattr4_aclsupport struct { XdrType_Uint32_t } func XDR_Fattr4_aclsupport(v *Fattr4_aclsupport) XdrType_Fattr4_aclsupport { return XdrType_Fattr4_aclsupport{XDR_Uint32_t(v)} } func (XdrType_Fattr4_aclsupport) XdrTypeName() string { return "Fattr4_aclsupport" } func (v XdrType_Fattr4_aclsupport) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_archive struct { XdrType_bool } func XDR_Fattr4_archive(v *Fattr4_archive) XdrType_Fattr4_archive { return XdrType_Fattr4_archive{XDR_bool(v)} } func (XdrType_Fattr4_archive) XdrTypeName() string { return "Fattr4_archive" } func (v XdrType_Fattr4_archive) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_cansettime struct { XdrType_bool } func XDR_Fattr4_cansettime(v *Fattr4_cansettime) XdrType_Fattr4_cansettime { return XdrType_Fattr4_cansettime{XDR_bool(v)} } func (XdrType_Fattr4_cansettime) XdrTypeName() string { return "Fattr4_cansettime" } func (v XdrType_Fattr4_cansettime) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_case_insensitive struct { XdrType_bool } func XDR_Fattr4_case_insensitive(v *Fattr4_case_insensitive) XdrType_Fattr4_case_insensitive { return XdrType_Fattr4_case_insensitive{XDR_bool(v)} } func (XdrType_Fattr4_case_insensitive) XdrTypeName() string { return "Fattr4_case_insensitive" } func (v XdrType_Fattr4_case_insensitive) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_case_preserving struct { XdrType_bool } func XDR_Fattr4_case_preserving(v *Fattr4_case_preserving) XdrType_Fattr4_case_preserving { return XdrType_Fattr4_case_preserving{XDR_bool(v)} } func (XdrType_Fattr4_case_preserving) XdrTypeName() string { return "Fattr4_case_preserving" } func (v XdrType_Fattr4_case_preserving) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_chown_restricted struct { XdrType_bool } func XDR_Fattr4_chown_restricted(v *Fattr4_chown_restricted) XdrType_Fattr4_chown_restricted { return XdrType_Fattr4_chown_restricted{XDR_bool(v)} } func (XdrType_Fattr4_chown_restricted) XdrTypeName() string { return "Fattr4_chown_restricted" } func (v XdrType_Fattr4_chown_restricted) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_fileid struct { XdrType_Uint64_t } func XDR_Fattr4_fileid(v *Fattr4_fileid) XdrType_Fattr4_fileid { return XdrType_Fattr4_fileid{XDR_Uint64_t(v)} } func (XdrType_Fattr4_fileid) XdrTypeName() string { return "Fattr4_fileid" } func (v XdrType_Fattr4_fileid) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_files_avail struct { XdrType_Uint64_t } func XDR_Fattr4_files_avail(v *Fattr4_files_avail) XdrType_Fattr4_files_avail { return XdrType_Fattr4_files_avail{XDR_Uint64_t(v)} } func (XdrType_Fattr4_files_avail) XdrTypeName() string { return "Fattr4_files_avail" } func (v XdrType_Fattr4_files_avail) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_filehandle struct { XdrType_Nfs_fh4 } func XDR_Fattr4_filehandle(v *Fattr4_filehandle) XdrType_Fattr4_filehandle { return XdrType_Fattr4_filehandle{XDR_Nfs_fh4(v)} } func (XdrType_Fattr4_filehandle) XdrTypeName() string { return "Fattr4_filehandle" } func (v XdrType_Fattr4_filehandle) XdrUnwrap() XdrType { return v.XdrType_Nfs_fh4 } type XdrType_Fattr4_files_free struct { XdrType_Uint64_t } func XDR_Fattr4_files_free(v *Fattr4_files_free) XdrType_Fattr4_files_free { return XdrType_Fattr4_files_free{XDR_Uint64_t(v)} } func (XdrType_Fattr4_files_free) XdrTypeName() string { return "Fattr4_files_free" } func (v XdrType_Fattr4_files_free) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_files_total struct { XdrType_Uint64_t } func XDR_Fattr4_files_total(v *Fattr4_files_total) XdrType_Fattr4_files_total { return XdrType_Fattr4_files_total{XDR_Uint64_t(v)} } func (XdrType_Fattr4_files_total) XdrTypeName() string { return "Fattr4_files_total" } func (v XdrType_Fattr4_files_total) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_fs_locations struct { XdrType_Fs_locations4 } func XDR_Fattr4_fs_locations(v *Fattr4_fs_locations) XdrType_Fattr4_fs_locations { return XdrType_Fattr4_fs_locations{XDR_Fs_locations4(v)} } func (XdrType_Fattr4_fs_locations) XdrTypeName() string { return "Fattr4_fs_locations" } func (v XdrType_Fattr4_fs_locations) XdrUnwrap() XdrType { return v.XdrType_Fs_locations4 } type XdrType_Fattr4_hidden struct { XdrType_bool } func XDR_Fattr4_hidden(v *Fattr4_hidden) XdrType_Fattr4_hidden { return XdrType_Fattr4_hidden{XDR_bool(v)} } func (XdrType_Fattr4_hidden) XdrTypeName() string { return "Fattr4_hidden" } func (v XdrType_Fattr4_hidden) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_homogeneous struct { XdrType_bool } func XDR_Fattr4_homogeneous(v *Fattr4_homogeneous) XdrType_Fattr4_homogeneous { return XdrType_Fattr4_homogeneous{XDR_bool(v)} } func (XdrType_Fattr4_homogeneous) XdrTypeName() string { return "Fattr4_homogeneous" } func (v XdrType_Fattr4_homogeneous) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_maxfilesize struct { XdrType_Uint64_t } func XDR_Fattr4_maxfilesize(v *Fattr4_maxfilesize) XdrType_Fattr4_maxfilesize { return XdrType_Fattr4_maxfilesize{XDR_Uint64_t(v)} } func (XdrType_Fattr4_maxfilesize) XdrTypeName() string { return "Fattr4_maxfilesize" } func (v XdrType_Fattr4_maxfilesize) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_maxlink struct { XdrType_Uint32_t } func XDR_Fattr4_maxlink(v *Fattr4_maxlink) XdrType_Fattr4_maxlink { return XdrType_Fattr4_maxlink{XDR_Uint32_t(v)} } func (XdrType_Fattr4_maxlink) XdrTypeName() string { return "Fattr4_maxlink" } func (v XdrType_Fattr4_maxlink) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_maxname struct { XdrType_Uint32_t } func XDR_Fattr4_maxname(v *Fattr4_maxname) XdrType_Fattr4_maxname { return XdrType_Fattr4_maxname{XDR_Uint32_t(v)} } func (XdrType_Fattr4_maxname) XdrTypeName() string { return "Fattr4_maxname" } func (v XdrType_Fattr4_maxname) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_maxread struct { XdrType_Uint64_t } func XDR_Fattr4_maxread(v *Fattr4_maxread) XdrType_Fattr4_maxread { return XdrType_Fattr4_maxread{XDR_Uint64_t(v)} } func (XdrType_Fattr4_maxread) XdrTypeName() string { return "Fattr4_maxread" } func (v XdrType_Fattr4_maxread) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_maxwrite struct { XdrType_Uint64_t } func XDR_Fattr4_maxwrite(v *Fattr4_maxwrite) XdrType_Fattr4_maxwrite { return XdrType_Fattr4_maxwrite{XDR_Uint64_t(v)} } func (XdrType_Fattr4_maxwrite) XdrTypeName() string { return "Fattr4_maxwrite" } func (v XdrType_Fattr4_maxwrite) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_mimetype struct { XdrType_Utf8str_cs } func XDR_Fattr4_mimetype(v *Fattr4_mimetype) XdrType_Fattr4_mimetype { return XdrType_Fattr4_mimetype{XDR_Utf8str_cs(v)} } func (XdrType_Fattr4_mimetype) XdrTypeName() string { return "Fattr4_mimetype" } func (v XdrType_Fattr4_mimetype) XdrUnwrap() XdrType { return v.XdrType_Utf8str_cs } type XdrType_Fattr4_mode struct { XdrType_Mode4 } func XDR_Fattr4_mode(v *Fattr4_mode) XdrType_Fattr4_mode { return XdrType_Fattr4_mode{XDR_Mode4(v)} } func (XdrType_Fattr4_mode) XdrTypeName() string { return "Fattr4_mode" } func (v XdrType_Fattr4_mode) XdrUnwrap() XdrType { return &v.XdrType_Mode4 } type XdrType_Fattr4_mounted_on_fileid struct { XdrType_Uint64_t } func XDR_Fattr4_mounted_on_fileid(v *Fattr4_mounted_on_fileid) XdrType_Fattr4_mounted_on_fileid { return XdrType_Fattr4_mounted_on_fileid{XDR_Uint64_t(v)} } func (XdrType_Fattr4_mounted_on_fileid) XdrTypeName() string { return "Fattr4_mounted_on_fileid" } func (v XdrType_Fattr4_mounted_on_fileid) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_no_trunc struct { XdrType_bool } func XDR_Fattr4_no_trunc(v *Fattr4_no_trunc) XdrType_Fattr4_no_trunc { return XdrType_Fattr4_no_trunc{XDR_bool(v)} } func (XdrType_Fattr4_no_trunc) XdrTypeName() string { return "Fattr4_no_trunc" } func (v XdrType_Fattr4_no_trunc) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_numlinks struct { XdrType_Uint32_t } func XDR_Fattr4_numlinks(v *Fattr4_numlinks) XdrType_Fattr4_numlinks { return XdrType_Fattr4_numlinks{XDR_Uint32_t(v)} } func (XdrType_Fattr4_numlinks) XdrTypeName() string { return "Fattr4_numlinks" } func (v XdrType_Fattr4_numlinks) XdrUnwrap() XdrType { return v.XdrType_Uint32_t } type XdrType_Fattr4_owner struct { XdrType_Utf8str_mixed } func XDR_Fattr4_owner(v *Fattr4_owner) XdrType_Fattr4_owner { return XdrType_Fattr4_owner{XDR_Utf8str_mixed(v)} } func (XdrType_Fattr4_owner) XdrTypeName() string { return "Fattr4_owner" } func (v XdrType_Fattr4_owner) XdrUnwrap() XdrType { return v.XdrType_Utf8str_mixed } type XdrType_Fattr4_owner_group struct { XdrType_Utf8str_mixed } func XDR_Fattr4_owner_group(v *Fattr4_owner_group) XdrType_Fattr4_owner_group { return XdrType_Fattr4_owner_group{XDR_Utf8str_mixed(v)} } func (XdrType_Fattr4_owner_group) XdrTypeName() string { return "Fattr4_owner_group" } func (v XdrType_Fattr4_owner_group) XdrUnwrap() XdrType { return v.XdrType_Utf8str_mixed } type XdrType_Fattr4_quota_avail_hard struct { XdrType_Uint64_t } func XDR_Fattr4_quota_avail_hard(v *Fattr4_quota_avail_hard) XdrType_Fattr4_quota_avail_hard { return XdrType_Fattr4_quota_avail_hard{XDR_Uint64_t(v)} } func (XdrType_Fattr4_quota_avail_hard) XdrTypeName() string { return "Fattr4_quota_avail_hard" } func (v XdrType_Fattr4_quota_avail_hard) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_quota_avail_soft struct { XdrType_Uint64_t } func XDR_Fattr4_quota_avail_soft(v *Fattr4_quota_avail_soft) XdrType_Fattr4_quota_avail_soft { return XdrType_Fattr4_quota_avail_soft{XDR_Uint64_t(v)} } func (XdrType_Fattr4_quota_avail_soft) XdrTypeName() string { return "Fattr4_quota_avail_soft" } func (v XdrType_Fattr4_quota_avail_soft) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_quota_used struct { XdrType_Uint64_t } func XDR_Fattr4_quota_used(v *Fattr4_quota_used) XdrType_Fattr4_quota_used { return XdrType_Fattr4_quota_used{XDR_Uint64_t(v)} } func (XdrType_Fattr4_quota_used) XdrTypeName() string { return "Fattr4_quota_used" } func (v XdrType_Fattr4_quota_used) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_rawdev struct { XdrType_Specdata4 } func XDR_Fattr4_rawdev(v *Fattr4_rawdev) XdrType_Fattr4_rawdev { return XdrType_Fattr4_rawdev{XDR_Specdata4(v)} } func (XdrType_Fattr4_rawdev) XdrTypeName() string { return "Fattr4_rawdev" } func (v XdrType_Fattr4_rawdev) XdrUnwrap() XdrType { return v.XdrType_Specdata4 } type XdrType_Fattr4_space_avail struct { XdrType_Uint64_t } func XDR_Fattr4_space_avail(v *Fattr4_space_avail) XdrType_Fattr4_space_avail { return XdrType_Fattr4_space_avail{XDR_Uint64_t(v)} } func (XdrType_Fattr4_space_avail) XdrTypeName() string { return "Fattr4_space_avail" } func (v XdrType_Fattr4_space_avail) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_space_free struct { XdrType_Uint64_t } func XDR_Fattr4_space_free(v *Fattr4_space_free) XdrType_Fattr4_space_free { return XdrType_Fattr4_space_free{XDR_Uint64_t(v)} } func (XdrType_Fattr4_space_free) XdrTypeName() string { return "Fattr4_space_free" } func (v XdrType_Fattr4_space_free) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_space_total struct { XdrType_Uint64_t } func XDR_Fattr4_space_total(v *Fattr4_space_total) XdrType_Fattr4_space_total { return XdrType_Fattr4_space_total{XDR_Uint64_t(v)} } func (XdrType_Fattr4_space_total) XdrTypeName() string { return "Fattr4_space_total" } func (v XdrType_Fattr4_space_total) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_space_used struct { XdrType_Uint64_t } func XDR_Fattr4_space_used(v *Fattr4_space_used) XdrType_Fattr4_space_used { return XdrType_Fattr4_space_used{XDR_Uint64_t(v)} } func (XdrType_Fattr4_space_used) XdrTypeName() string { return "Fattr4_space_used" } func (v XdrType_Fattr4_space_used) XdrUnwrap() XdrType { return v.XdrType_Uint64_t } type XdrType_Fattr4_system struct { XdrType_bool } func XDR_Fattr4_system(v *Fattr4_system) XdrType_Fattr4_system { return XdrType_Fattr4_system{XDR_bool(v)} } func (XdrType_Fattr4_system) XdrTypeName() string { return "Fattr4_system" } func (v XdrType_Fattr4_system) XdrUnwrap() XdrType { return v.XdrType_bool } type XdrType_Fattr4_time_access struct { XdrType_Nfstime4 } func XDR_Fattr4_time_access(v *Fattr4_time_access) XdrType_Fattr4_time_access { return XdrType_Fattr4_time_access{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_access) XdrTypeName() string { return "Fattr4_time_access" } func (v XdrType_Fattr4_time_access) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_access_set struct { XdrType_Settime4 } func XDR_Fattr4_time_access_set(v *Fattr4_time_access_set) XdrType_Fattr4_time_access_set { return XdrType_Fattr4_time_access_set{XDR_Settime4(v)} } func (XdrType_Fattr4_time_access_set) XdrTypeName() string { return "Fattr4_time_access_set" } func (v XdrType_Fattr4_time_access_set) XdrUnwrap() XdrType { return v.XdrType_Settime4 } type XdrType_Fattr4_time_backup struct { XdrType_Nfstime4 } func XDR_Fattr4_time_backup(v *Fattr4_time_backup) XdrType_Fattr4_time_backup { return XdrType_Fattr4_time_backup{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_backup) XdrTypeName() string { return "Fattr4_time_backup" } func (v XdrType_Fattr4_time_backup) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_create struct { XdrType_Nfstime4 } func XDR_Fattr4_time_create(v *Fattr4_time_create) XdrType_Fattr4_time_create { return XdrType_Fattr4_time_create{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_create) XdrTypeName() string { return "Fattr4_time_create" } func (v XdrType_Fattr4_time_create) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_delta struct { XdrType_Nfstime4 } func XDR_Fattr4_time_delta(v *Fattr4_time_delta) XdrType_Fattr4_time_delta { return XdrType_Fattr4_time_delta{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_delta) XdrTypeName() string { return "Fattr4_time_delta" } func (v XdrType_Fattr4_time_delta) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_metadata struct { XdrType_Nfstime4 } func XDR_Fattr4_time_metadata(v *Fattr4_time_metadata) XdrType_Fattr4_time_metadata { return XdrType_Fattr4_time_metadata{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_metadata) XdrTypeName() string { return "Fattr4_time_metadata" } func (v XdrType_Fattr4_time_metadata) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_modify struct { XdrType_Nfstime4 } func XDR_Fattr4_time_modify(v *Fattr4_time_modify) XdrType_Fattr4_time_modify { return XdrType_Fattr4_time_modify{XDR_Nfstime4(v)} } func (XdrType_Fattr4_time_modify) XdrTypeName() string { return "Fattr4_time_modify" } func (v XdrType_Fattr4_time_modify) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_Fattr4_time_modify_set struct { XdrType_Settime4 } func XDR_Fattr4_time_modify_set(v *Fattr4_time_modify_set) XdrType_Fattr4_time_modify_set { return XdrType_Fattr4_time_modify_set{XDR_Settime4(v)} } func (XdrType_Fattr4_time_modify_set) XdrTypeName() string { return "Fattr4_time_modify_set" } func (v XdrType_Fattr4_time_modify_set) XdrUnwrap() XdrType { return v.XdrType_Settime4 } type XdrType_Attrlist4 struct { XdrVecOpaque } func XDR_Attrlist4(v *Attrlist4) XdrType_Attrlist4 { return XdrType_Attrlist4{XdrVecOpaque{v, 0xffffffff}} } func (XdrType_Attrlist4) XdrTypeName() string { return "Attrlist4" } func (v XdrType_Attrlist4) XdrUnwrap() XdrType { return v.XdrVecOpaque } type XdrType_Fattr4 = *Fattr4 func (v *Fattr4) XdrPointer() interface{} { return v } func (Fattr4) XdrTypeName() string { return "Fattr4" } func (v Fattr4) XdrValue() interface{} { return v } func (v *Fattr4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Fattr4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sattrmask", name), XDR_Bitmap4(&v.Attrmask)) x.Marshal(x.Sprintf("%sattr_vals", name), XDR_Attrlist4(&v.Attr_vals)) } func XDR_Fattr4(v *Fattr4) *Fattr4 { return v } type XdrType_Change_info4 = *Change_info4 func (v *Change_info4) XdrPointer() interface{} { return v } func (Change_info4) XdrTypeName() string { return "Change_info4" } func (v Change_info4) XdrValue() interface{} { return v } func (v *Change_info4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Change_info4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%satomic", name), XDR_bool(&v.Atomic)) x.Marshal(x.Sprintf("%sbefore", name), XDR_Changeid4(&v.Before)) x.Marshal(x.Sprintf("%safter", name), XDR_Changeid4(&v.After)) } func XDR_Change_info4(v *Change_info4) *Change_info4 { return v } type XdrType_Clientaddr4 = *Clientaddr4 func (v *Clientaddr4) XdrPointer() interface{} { return v } func (Clientaddr4) XdrTypeName() string { return "Clientaddr4" } func (v Clientaddr4) XdrValue() interface{} { return v } func (v *Clientaddr4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Clientaddr4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sr_netid", name), XdrString{&v.R_netid, 0xffffffff}) x.Marshal(x.Sprintf("%sr_addr", name), XdrString{&v.R_addr, 0xffffffff}) } func XDR_Clientaddr4(v *Clientaddr4) *Clientaddr4 { return v } type XdrType_Cb_client4 = *Cb_client4 func (v *Cb_client4) XdrPointer() interface{} { return v } func (Cb_client4) XdrTypeName() string { return "Cb_client4" } func (v Cb_client4) XdrValue() interface{} { return v } func (v *Cb_client4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Cb_client4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scb_program", name), XDR_Uint32_t(&v.Cb_program)) x.Marshal(x.Sprintf("%scb_location", name), XDR_Clientaddr4(&v.Cb_location)) } func XDR_Cb_client4(v *Cb_client4) *Cb_client4 { return v } type _XdrArray_12_opaque [12]byte func (v *_XdrArray_12_opaque) GetByteSlice() []byte { return v[:] } func (v *_XdrArray_12_opaque) XdrTypeName() string { return "opaque[]" } func (v *_XdrArray_12_opaque) XdrValue() interface{} { return v[:] } func (v *_XdrArray_12_opaque) XdrPointer() interface{} { return (*[12]byte)(v) } func (v *_XdrArray_12_opaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *_XdrArray_12_opaque) String() string { return fmt.Sprintf("%x", v[:]) } func (v *_XdrArray_12_opaque) Scan(ss fmt.ScanState, c rune) error { return XdrArrayOpaqueScan(v[:], ss, c) } func (_XdrArray_12_opaque) XdrArraySize() uint32 { const bound uint32 = 12 // Force error if not const or doesn't fit return bound } type XdrType_Stateid4 = *Stateid4 func (v *Stateid4) XdrPointer() interface{} { return v } func (Stateid4) XdrTypeName() string { return "Stateid4" } func (v Stateid4) XdrValue() interface{} { return v } func (v *Stateid4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Stateid4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sseqid", name), XDR_Uint32_t(&v.Seqid)) x.Marshal(x.Sprintf("%sother", name), (*_XdrArray_12_opaque)(&v.Other)) } func XDR_Stateid4(v *Stateid4) *Stateid4 { return v } type XdrType_Nfs_client_id4 = *Nfs_client_id4 func (v *Nfs_client_id4) XdrPointer() interface{} { return v } func (Nfs_client_id4) XdrTypeName() string { return "Nfs_client_id4" } func (v Nfs_client_id4) XdrValue() interface{} { return v } func (v *Nfs_client_id4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Nfs_client_id4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sverifier", name), XDR_Verifier4(&v.Verifier)) x.Marshal(x.Sprintf("%sid", name), XdrVecOpaque{&v.Id, NFS4_OPAQUE_LIMIT}) } func XDR_Nfs_client_id4(v *Nfs_client_id4) *Nfs_client_id4 { return v } type XdrType_Open_owner4 = *Open_owner4 func (v *Open_owner4) XdrPointer() interface{} { return v } func (Open_owner4) XdrTypeName() string { return "Open_owner4" } func (v Open_owner4) XdrValue() interface{} { return v } func (v *Open_owner4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Open_owner4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) x.Marshal(x.Sprintf("%sowner", name), XdrVecOpaque{&v.Owner, NFS4_OPAQUE_LIMIT}) } func XDR_Open_owner4(v *Open_owner4) *Open_owner4 { return v } type XdrType_Lock_owner4 = *Lock_owner4 func (v *Lock_owner4) XdrPointer() interface{} { return v } func (Lock_owner4) XdrTypeName() string { return "Lock_owner4" } func (v Lock_owner4) XdrValue() interface{} { return v } func (v *Lock_owner4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Lock_owner4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) x.Marshal(x.Sprintf("%sowner", name), XdrVecOpaque{&v.Owner, NFS4_OPAQUE_LIMIT}) } func XDR_Lock_owner4(v *Lock_owner4) *Lock_owner4 { return v } var _XdrNames_Nfs_lock_type4 = map[int32]string{ int32(READ_LT): "READ_LT", int32(WRITE_LT): "WRITE_LT", int32(READW_LT): "READW_LT", int32(WRITEW_LT): "WRITEW_LT", } var _XdrValues_Nfs_lock_type4 = map[string]int32{ "READ_LT": int32(READ_LT), "WRITE_LT": int32(WRITE_LT), "READW_LT": int32(READW_LT), "WRITEW_LT": int32(WRITEW_LT), } func (Nfs_lock_type4) XdrEnumNames() map[int32]string { return _XdrNames_Nfs_lock_type4 } func (v Nfs_lock_type4) String() string { if s, ok := _XdrNames_Nfs_lock_type4[int32(v)]; ok { return s } return fmt.Sprintf("Nfs_lock_type4#%d", v) } func (v *Nfs_lock_type4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Nfs_lock_type4[stok]; ok { *v = Nfs_lock_type4(val) return nil } else if stok == "Nfs_lock_type4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Nfs_lock_type4.", stok)) } } func (v Nfs_lock_type4) GetU32() uint32 { return uint32(v) } func (v *Nfs_lock_type4) SetU32(n uint32) { *v = Nfs_lock_type4(n) } func (v *Nfs_lock_type4) XdrPointer() interface{} { return v } func (Nfs_lock_type4) XdrTypeName() string { return "Nfs_lock_type4" } func (v Nfs_lock_type4) XdrValue() interface{} { return v } func (v *Nfs_lock_type4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Nfs_lock_type4 = *Nfs_lock_type4 func XDR_Nfs_lock_type4(v *Nfs_lock_type4) *Nfs_lock_type4 { return v } func (v *Nfs_lock_type4) XdrInitialize() { switch Nfs_lock_type4(0) { case READ_LT, WRITE_LT, READW_LT, WRITEW_LT: default: if *v == Nfs_lock_type4(0) { *v = READ_LT } } } type XdrType_ACCESS4args = *ACCESS4args func (v *ACCESS4args) XdrPointer() interface{} { return v } func (ACCESS4args) XdrTypeName() string { return "ACCESS4args" } func (v ACCESS4args) XdrValue() interface{} { return v } func (v *ACCESS4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *ACCESS4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%saccess", name), XDR_Uint32_t(&v.Access)) } func XDR_ACCESS4args(v *ACCESS4args) *ACCESS4args { return v } type XdrType_ACCESS4resok = *ACCESS4resok func (v *ACCESS4resok) XdrPointer() interface{} { return v } func (ACCESS4resok) XdrTypeName() string { return "ACCESS4resok" } func (v ACCESS4resok) XdrValue() interface{} { return v } func (v *ACCESS4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *ACCESS4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssupported", name), XDR_Uint32_t(&v.Supported)) x.Marshal(x.Sprintf("%saccess", name), XDR_Uint32_t(&v.Access)) } func XDR_ACCESS4resok(v *ACCESS4resok) *ACCESS4resok { return v } func (u *ACCESS4res) Resok4() *ACCESS4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*ACCESS4resok); ok { return v } else { var zero ACCESS4resok u.U = &zero return &zero } default: XdrPanic("ACCESS4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u ACCESS4res) XdrValid() bool { return true } func (u *ACCESS4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *ACCESS4res) XdrUnionTagName() string { return "Status" } func (u *ACCESS4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_ACCESS4resok(u.Resok4()) default: return nil } } func (u *ACCESS4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_ACCESS4res = *ACCESS4res func (v *ACCESS4res) XdrPointer() interface{} { return v } func (ACCESS4res) XdrTypeName() string { return "ACCESS4res" } func (v ACCESS4res) XdrValue() interface{} { return v } func (v *ACCESS4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *ACCESS4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_ACCESS4resok(u.Resok4())) return default: return } } func XDR_ACCESS4res(v *ACCESS4res) *ACCESS4res { return v} type XdrType_CLOSE4args = *CLOSE4args func (v *CLOSE4args) XdrPointer() interface{} { return v } func (CLOSE4args) XdrTypeName() string { return "CLOSE4args" } func (v CLOSE4args) XdrValue() interface{} { return v } func (v *CLOSE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CLOSE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sseqid", name), XDR_Seqid4(&v.Seqid)) x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) } func XDR_CLOSE4args(v *CLOSE4args) *CLOSE4args { return v } func (u *CLOSE4res) Open_stateid() *Stateid4 { switch u.Status { case NFS4_OK: if v, ok := u.U.(*Stateid4); ok { return v } else { var zero Stateid4 u.U = &zero return &zero } default: XdrPanic("CLOSE4res.Open_stateid accessed when Status == %v", u.Status) return nil } } func (u CLOSE4res) XdrValid() bool { return true } func (u *CLOSE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *CLOSE4res) XdrUnionTagName() string { return "Status" } func (u *CLOSE4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_Stateid4(u.Open_stateid()) default: return nil } } func (u *CLOSE4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Open_stateid" default: return "" } } type XdrType_CLOSE4res = *CLOSE4res func (v *CLOSE4res) XdrPointer() interface{} { return v } func (CLOSE4res) XdrTypeName() string { return "CLOSE4res" } func (v CLOSE4res) XdrValue() interface{} { return v } func (v *CLOSE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *CLOSE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(u.Open_stateid())) return default: return } } func XDR_CLOSE4res(v *CLOSE4res) *CLOSE4res { return v} type XdrType_COMMIT4args = *COMMIT4args func (v *COMMIT4args) XdrPointer() interface{} { return v } func (COMMIT4args) XdrTypeName() string { return "COMMIT4args" } func (v COMMIT4args) XdrValue() interface{} { return v } func (v *COMMIT4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *COMMIT4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%scount", name), XDR_Count4(&v.Count)) } func XDR_COMMIT4args(v *COMMIT4args) *COMMIT4args { return v } type XdrType_COMMIT4resok = *COMMIT4resok func (v *COMMIT4resok) XdrPointer() interface{} { return v } func (COMMIT4resok) XdrTypeName() string { return "COMMIT4resok" } func (v COMMIT4resok) XdrValue() interface{} { return v } func (v *COMMIT4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *COMMIT4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%swriteverf", name), XDR_Verifier4(&v.Writeverf)) } func XDR_COMMIT4resok(v *COMMIT4resok) *COMMIT4resok { return v } func (u *COMMIT4res) Resok4() *COMMIT4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*COMMIT4resok); ok { return v } else { var zero COMMIT4resok u.U = &zero return &zero } default: XdrPanic("COMMIT4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u COMMIT4res) XdrValid() bool { return true } func (u *COMMIT4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *COMMIT4res) XdrUnionTagName() string { return "Status" } func (u *COMMIT4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_COMMIT4resok(u.Resok4()) default: return nil } } func (u *COMMIT4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_COMMIT4res = *COMMIT4res func (v *COMMIT4res) XdrPointer() interface{} { return v } func (COMMIT4res) XdrTypeName() string { return "COMMIT4res" } func (v COMMIT4res) XdrValue() interface{} { return v } func (v *COMMIT4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *COMMIT4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_COMMIT4resok(u.Resok4())) return default: return } } func XDR_COMMIT4res(v *COMMIT4res) *COMMIT4res { return v} func (u *Createtype4) Linkdata() *Linktext4 { switch u.Type { case NF4LNK: if v, ok := u.U.(*Linktext4); ok { return v } else { var zero Linktext4 u.U = &zero return &zero } default: XdrPanic("Createtype4.Linkdata accessed when Type == %v", u.Type) return nil } } func (u *Createtype4) Devdata() *Specdata4 { switch u.Type { case NF4BLK, NF4CHR: if v, ok := u.U.(*Specdata4); ok { return v } else { var zero Specdata4 u.U = &zero return &zero } default: XdrPanic("Createtype4.Devdata accessed when Type == %v", u.Type) return nil } } func (u Createtype4) XdrValid() bool { return true } func (u *Createtype4) XdrUnionTag() XdrNum32 { return XDR_Nfs_ftype4(&u.Type) } func (u *Createtype4) XdrUnionTagName() string { return "Type" } func (u *Createtype4) XdrUnionBody() XdrType { switch u.Type { case NF4LNK: return XDR_Linktext4(u.Linkdata()) case NF4BLK, NF4CHR: return XDR_Specdata4(u.Devdata()) case NF4SOCK, NF4FIFO, NF4DIR: return nil default: return nil } } func (u *Createtype4) XdrUnionBodyName() string { switch u.Type { case NF4LNK: return "Linkdata" case NF4BLK, NF4CHR: return "Devdata" case NF4SOCK, NF4FIFO, NF4DIR: return "" default: return "" } } type XdrType_Createtype4 = *Createtype4 func (v *Createtype4) XdrPointer() interface{} { return v } func (Createtype4) XdrTypeName() string { return "Createtype4" } func (v Createtype4) XdrValue() interface{} { return v } func (v *Createtype4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Createtype4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfs_ftype4(&u.Type).XdrMarshal(x, x.Sprintf("%stype", name)) switch u.Type { case NF4LNK: x.Marshal(x.Sprintf("%slinkdata", name), XDR_Linktext4(u.Linkdata())) return case NF4BLK, NF4CHR: x.Marshal(x.Sprintf("%sdevdata", name), XDR_Specdata4(u.Devdata())) return case NF4SOCK, NF4FIFO, NF4DIR: return default: return } } func XDR_Createtype4(v *Createtype4) *Createtype4 { return v} type XdrType_CREATE4args = *CREATE4args func (v *CREATE4args) XdrPointer() interface{} { return v } func (CREATE4args) XdrTypeName() string { return "CREATE4args" } func (v CREATE4args) XdrValue() interface{} { return v } func (v *CREATE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CREATE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobjtype", name), XDR_Createtype4(&v.Objtype)) x.Marshal(x.Sprintf("%sobjname", name), XDR_Component4(&v.Objname)) x.Marshal(x.Sprintf("%screateattrs", name), XDR_Fattr4(&v.Createattrs)) } func XDR_CREATE4args(v *CREATE4args) *CREATE4args { return v } type XdrType_CREATE4resok = *CREATE4resok func (v *CREATE4resok) XdrPointer() interface{} { return v } func (CREATE4resok) XdrTypeName() string { return "CREATE4resok" } func (v CREATE4resok) XdrValue() interface{} { return v } func (v *CREATE4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CREATE4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scinfo", name), XDR_Change_info4(&v.Cinfo)) x.Marshal(x.Sprintf("%sattrset", name), XDR_Bitmap4(&v.Attrset)) } func XDR_CREATE4resok(v *CREATE4resok) *CREATE4resok { return v } func (u *CREATE4res) Resok4() *CREATE4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*CREATE4resok); ok { return v } else { var zero CREATE4resok u.U = &zero return &zero } default: XdrPanic("CREATE4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u CREATE4res) XdrValid() bool { return true } func (u *CREATE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *CREATE4res) XdrUnionTagName() string { return "Status" } func (u *CREATE4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_CREATE4resok(u.Resok4()) default: return nil } } func (u *CREATE4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_CREATE4res = *CREATE4res func (v *CREATE4res) XdrPointer() interface{} { return v } func (CREATE4res) XdrTypeName() string { return "CREATE4res" } func (v CREATE4res) XdrValue() interface{} { return v } func (v *CREATE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *CREATE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_CREATE4resok(u.Resok4())) return default: return } } func XDR_CREATE4res(v *CREATE4res) *CREATE4res { return v} type XdrType_DELEGPURGE4args = *DELEGPURGE4args func (v *DELEGPURGE4args) XdrPointer() interface{} { return v } func (DELEGPURGE4args) XdrTypeName() string { return "DELEGPURGE4args" } func (v DELEGPURGE4args) XdrValue() interface{} { return v } func (v *DELEGPURGE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DELEGPURGE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) } func XDR_DELEGPURGE4args(v *DELEGPURGE4args) *DELEGPURGE4args { return v } type XdrType_DELEGPURGE4res = *DELEGPURGE4res func (v *DELEGPURGE4res) XdrPointer() interface{} { return v } func (DELEGPURGE4res) XdrTypeName() string { return "DELEGPURGE4res" } func (v DELEGPURGE4res) XdrValue() interface{} { return v } func (v *DELEGPURGE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DELEGPURGE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_DELEGPURGE4res(v *DELEGPURGE4res) *DELEGPURGE4res { return v } type XdrType_DELEGRETURN4args = *DELEGRETURN4args func (v *DELEGRETURN4args) XdrPointer() interface{} { return v } func (DELEGRETURN4args) XdrTypeName() string { return "DELEGRETURN4args" } func (v DELEGRETURN4args) XdrValue() interface{} { return v } func (v *DELEGRETURN4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DELEGRETURN4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdeleg_stateid", name), XDR_Stateid4(&v.Deleg_stateid)) } func XDR_DELEGRETURN4args(v *DELEGRETURN4args) *DELEGRETURN4args { return v } type XdrType_DELEGRETURN4res = *DELEGRETURN4res func (v *DELEGRETURN4res) XdrPointer() interface{} { return v } func (DELEGRETURN4res) XdrTypeName() string { return "DELEGRETURN4res" } func (v DELEGRETURN4res) XdrValue() interface{} { return v } func (v *DELEGRETURN4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DELEGRETURN4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_DELEGRETURN4res(v *DELEGRETURN4res) *DELEGRETURN4res { return v } type XdrType_GETATTR4args = *GETATTR4args func (v *GETATTR4args) XdrPointer() interface{} { return v } func (GETATTR4args) XdrTypeName() string { return "GETATTR4args" } func (v GETATTR4args) XdrValue() interface{} { return v } func (v *GETATTR4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETATTR4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sattr_request", name), XDR_Bitmap4(&v.Attr_request)) } func XDR_GETATTR4args(v *GETATTR4args) *GETATTR4args { return v } type XdrType_GETATTR4resok = *GETATTR4resok func (v *GETATTR4resok) XdrPointer() interface{} { return v } func (GETATTR4resok) XdrTypeName() string { return "GETATTR4resok" } func (v GETATTR4resok) XdrValue() interface{} { return v } func (v *GETATTR4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETATTR4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobj_attributes", name), XDR_Fattr4(&v.Obj_attributes)) } func XDR_GETATTR4resok(v *GETATTR4resok) *GETATTR4resok { return v } func (u *GETATTR4res) Resok4() *GETATTR4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*GETATTR4resok); ok { return v } else { var zero GETATTR4resok u.U = &zero return &zero } default: XdrPanic("GETATTR4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u GETATTR4res) XdrValid() bool { return true } func (u *GETATTR4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *GETATTR4res) XdrUnionTagName() string { return "Status" } func (u *GETATTR4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_GETATTR4resok(u.Resok4()) default: return nil } } func (u *GETATTR4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_GETATTR4res = *GETATTR4res func (v *GETATTR4res) XdrPointer() interface{} { return v } func (GETATTR4res) XdrTypeName() string { return "GETATTR4res" } func (v GETATTR4res) XdrValue() interface{} { return v } func (v *GETATTR4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GETATTR4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_GETATTR4resok(u.Resok4())) return default: return } } func XDR_GETATTR4res(v *GETATTR4res) *GETATTR4res { return v} type XdrType_GETFH4resok = *GETFH4resok func (v *GETFH4resok) XdrPointer() interface{} { return v } func (GETFH4resok) XdrTypeName() string { return "GETFH4resok" } func (v GETFH4resok) XdrValue() interface{} { return v } func (v *GETFH4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETFH4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobject", name), XDR_Nfs_fh4(&v.Object)) } func XDR_GETFH4resok(v *GETFH4resok) *GETFH4resok { return v } func (u *GETFH4res) Resok4() *GETFH4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*GETFH4resok); ok { return v } else { var zero GETFH4resok u.U = &zero return &zero } default: XdrPanic("GETFH4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u GETFH4res) XdrValid() bool { return true } func (u *GETFH4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *GETFH4res) XdrUnionTagName() string { return "Status" } func (u *GETFH4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_GETFH4resok(u.Resok4()) default: return nil } } func (u *GETFH4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_GETFH4res = *GETFH4res func (v *GETFH4res) XdrPointer() interface{} { return v } func (GETFH4res) XdrTypeName() string { return "GETFH4res" } func (v GETFH4res) XdrValue() interface{} { return v } func (v *GETFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GETFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_GETFH4resok(u.Resok4())) return default: return } } func XDR_GETFH4res(v *GETFH4res) *GETFH4res { return v} type XdrType_LINK4args = *LINK4args func (v *LINK4args) XdrPointer() interface{} { return v } func (LINK4args) XdrTypeName() string { return "LINK4args" } func (v LINK4args) XdrValue() interface{} { return v } func (v *LINK4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LINK4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%snewname", name), XDR_Component4(&v.Newname)) } func XDR_LINK4args(v *LINK4args) *LINK4args { return v } type XdrType_LINK4resok = *LINK4resok func (v *LINK4resok) XdrPointer() interface{} { return v } func (LINK4resok) XdrTypeName() string { return "LINK4resok" } func (v LINK4resok) XdrValue() interface{} { return v } func (v *LINK4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LINK4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scinfo", name), XDR_Change_info4(&v.Cinfo)) } func XDR_LINK4resok(v *LINK4resok) *LINK4resok { return v } func (u *LINK4res) Resok4() *LINK4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*LINK4resok); ok { return v } else { var zero LINK4resok u.U = &zero return &zero } default: XdrPanic("LINK4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u LINK4res) XdrValid() bool { return true } func (u *LINK4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *LINK4res) XdrUnionTagName() string { return "Status" } func (u *LINK4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_LINK4resok(u.Resok4()) default: return nil } } func (u *LINK4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_LINK4res = *LINK4res func (v *LINK4res) XdrPointer() interface{} { return v } func (LINK4res) XdrTypeName() string { return "LINK4res" } func (v LINK4res) XdrValue() interface{} { return v } func (v *LINK4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LINK4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_LINK4resok(u.Resok4())) return default: return } } func XDR_LINK4res(v *LINK4res) *LINK4res { return v} type XdrType_Open_to_lock_owner4 = *Open_to_lock_owner4 func (v *Open_to_lock_owner4) XdrPointer() interface{} { return v } func (Open_to_lock_owner4) XdrTypeName() string { return "Open_to_lock_owner4" } func (v Open_to_lock_owner4) XdrValue() interface{} { return v } func (v *Open_to_lock_owner4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Open_to_lock_owner4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sopen_seqid", name), XDR_Seqid4(&v.Open_seqid)) x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) x.Marshal(x.Sprintf("%slock_seqid", name), XDR_Seqid4(&v.Lock_seqid)) x.Marshal(x.Sprintf("%slock_owner", name), XDR_Lock_owner4(&v.Lock_owner)) } func XDR_Open_to_lock_owner4(v *Open_to_lock_owner4) *Open_to_lock_owner4 { return v } type XdrType_Exist_lock_owner4 = *Exist_lock_owner4 func (v *Exist_lock_owner4) XdrPointer() interface{} { return v } func (Exist_lock_owner4) XdrTypeName() string { return "Exist_lock_owner4" } func (v Exist_lock_owner4) XdrValue() interface{} { return v } func (v *Exist_lock_owner4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Exist_lock_owner4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slock_stateid", name), XDR_Stateid4(&v.Lock_stateid)) x.Marshal(x.Sprintf("%slock_seqid", name), XDR_Seqid4(&v.Lock_seqid)) } func XDR_Exist_lock_owner4(v *Exist_lock_owner4) *Exist_lock_owner4 { return v } var _XdrTags_Locker4 = map[int32]bool{ XdrToI32(true): true, XdrToI32(false): true, } func (_ Locker4) XdrValidTags() map[int32]bool { return _XdrTags_Locker4 } func (u *Locker4) Open_owner() *Open_to_lock_owner4 { switch u.New_lock_owner { case true: if v, ok := u.U.(*Open_to_lock_owner4); ok { return v } else { var zero Open_to_lock_owner4 u.U = &zero return &zero } default: XdrPanic("Locker4.Open_owner accessed when New_lock_owner == %v", u.New_lock_owner) return nil } } func (u *Locker4) Lock_owner() *Exist_lock_owner4 { switch u.New_lock_owner { case false: if v, ok := u.U.(*Exist_lock_owner4); ok { return v } else { var zero Exist_lock_owner4 u.U = &zero return &zero } default: XdrPanic("Locker4.Lock_owner accessed when New_lock_owner == %v", u.New_lock_owner) return nil } } func (u Locker4) XdrValid() bool { switch u.New_lock_owner { case true,false: return true } return false } func (u *Locker4) XdrUnionTag() XdrNum32 { return XDR_bool(&u.New_lock_owner) } func (u *Locker4) XdrUnionTagName() string { return "New_lock_owner" } func (u *Locker4) XdrUnionBody() XdrType { switch u.New_lock_owner { case true: return XDR_Open_to_lock_owner4(u.Open_owner()) case false: return XDR_Exist_lock_owner4(u.Lock_owner()) } return nil } func (u *Locker4) XdrUnionBodyName() string { switch u.New_lock_owner { case true: return "Open_owner" case false: return "Lock_owner" } return "" } type XdrType_Locker4 = *Locker4 func (v *Locker4) XdrPointer() interface{} { return v } func (Locker4) XdrTypeName() string { return "Locker4" } func (v Locker4) XdrValue() interface{} { return v } func (v *Locker4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Locker4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_bool(&u.New_lock_owner).XdrMarshal(x, x.Sprintf("%snew_lock_owner", name)) switch u.New_lock_owner { case true: x.Marshal(x.Sprintf("%sopen_owner", name), XDR_Open_to_lock_owner4(u.Open_owner())) return case false: x.Marshal(x.Sprintf("%slock_owner", name), XDR_Exist_lock_owner4(u.Lock_owner())) return } XdrPanic("invalid New_lock_owner (%v) in Locker4", u.New_lock_owner) } func XDR_Locker4(v *Locker4) *Locker4 { return v} type XdrType_LOCK4args = *LOCK4args func (v *LOCK4args) XdrPointer() interface{} { return v } func (LOCK4args) XdrTypeName() string { return "LOCK4args" } func (v LOCK4args) XdrValue() interface{} { return v } func (v *LOCK4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOCK4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slocktype", name), XDR_Nfs_lock_type4(&v.Locktype)) x.Marshal(x.Sprintf("%sreclaim", name), XDR_bool(&v.Reclaim)) x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%slength", name), XDR_Length4(&v.Length)) x.Marshal(x.Sprintf("%slocker", name), XDR_Locker4(&v.Locker)) } func XDR_LOCK4args(v *LOCK4args) *LOCK4args { return v } type XdrType_LOCK4denied = *LOCK4denied func (v *LOCK4denied) XdrPointer() interface{} { return v } func (LOCK4denied) XdrTypeName() string { return "LOCK4denied" } func (v LOCK4denied) XdrValue() interface{} { return v } func (v *LOCK4denied) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOCK4denied) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%slength", name), XDR_Length4(&v.Length)) x.Marshal(x.Sprintf("%slocktype", name), XDR_Nfs_lock_type4(&v.Locktype)) x.Marshal(x.Sprintf("%sowner", name), XDR_Lock_owner4(&v.Owner)) } func XDR_LOCK4denied(v *LOCK4denied) *LOCK4denied { return v } type XdrType_LOCK4resok = *LOCK4resok func (v *LOCK4resok) XdrPointer() interface{} { return v } func (LOCK4resok) XdrTypeName() string { return "LOCK4resok" } func (v LOCK4resok) XdrValue() interface{} { return v } func (v *LOCK4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOCK4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slock_stateid", name), XDR_Stateid4(&v.Lock_stateid)) } func XDR_LOCK4resok(v *LOCK4resok) *LOCK4resok { return v } func (u *LOCK4res) Resok4() *LOCK4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*LOCK4resok); ok { return v } else { var zero LOCK4resok u.U = &zero return &zero } default: XdrPanic("LOCK4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u *LOCK4res) Denied() *LOCK4denied { switch u.Status { case NFS4ERR_DENIED: if v, ok := u.U.(*LOCK4denied); ok { return v } else { var zero LOCK4denied u.U = &zero return &zero } default: XdrPanic("LOCK4res.Denied accessed when Status == %v", u.Status) return nil } } func (u LOCK4res) XdrValid() bool { return true } func (u *LOCK4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *LOCK4res) XdrUnionTagName() string { return "Status" } func (u *LOCK4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_LOCK4resok(u.Resok4()) case NFS4ERR_DENIED: return XDR_LOCK4denied(u.Denied()) default: return nil } } func (u *LOCK4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" case NFS4ERR_DENIED: return "Denied" default: return "" } } type XdrType_LOCK4res = *LOCK4res func (v *LOCK4res) XdrPointer() interface{} { return v } func (LOCK4res) XdrTypeName() string { return "LOCK4res" } func (v LOCK4res) XdrValue() interface{} { return v } func (v *LOCK4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LOCK4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_LOCK4resok(u.Resok4())) return case NFS4ERR_DENIED: x.Marshal(x.Sprintf("%sdenied", name), XDR_LOCK4denied(u.Denied())) return default: return } } func XDR_LOCK4res(v *LOCK4res) *LOCK4res { return v} type XdrType_LOCKT4args = *LOCKT4args func (v *LOCKT4args) XdrPointer() interface{} { return v } func (LOCKT4args) XdrTypeName() string { return "LOCKT4args" } func (v LOCKT4args) XdrValue() interface{} { return v } func (v *LOCKT4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOCKT4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slocktype", name), XDR_Nfs_lock_type4(&v.Locktype)) x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%slength", name), XDR_Length4(&v.Length)) x.Marshal(x.Sprintf("%sowner", name), XDR_Lock_owner4(&v.Owner)) } func XDR_LOCKT4args(v *LOCKT4args) *LOCKT4args { return v } func (u *LOCKT4res) Denied() *LOCK4denied { switch u.Status { case NFS4ERR_DENIED: if v, ok := u.U.(*LOCK4denied); ok { return v } else { var zero LOCK4denied u.U = &zero return &zero } default: XdrPanic("LOCKT4res.Denied accessed when Status == %v", u.Status) return nil } } func (u LOCKT4res) XdrValid() bool { return true } func (u *LOCKT4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *LOCKT4res) XdrUnionTagName() string { return "Status" } func (u *LOCKT4res) XdrUnionBody() XdrType { switch u.Status { case NFS4ERR_DENIED: return XDR_LOCK4denied(u.Denied()) case NFS4_OK: return nil default: return nil } } func (u *LOCKT4res) XdrUnionBodyName() string { switch u.Status { case NFS4ERR_DENIED: return "Denied" case NFS4_OK: return "" default: return "" } } type XdrType_LOCKT4res = *LOCKT4res func (v *LOCKT4res) XdrPointer() interface{} { return v } func (LOCKT4res) XdrTypeName() string { return "LOCKT4res" } func (v LOCKT4res) XdrValue() interface{} { return v } func (v *LOCKT4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LOCKT4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4ERR_DENIED: x.Marshal(x.Sprintf("%sdenied", name), XDR_LOCK4denied(u.Denied())) return case NFS4_OK: return default: return } } func XDR_LOCKT4res(v *LOCKT4res) *LOCKT4res { return v} type XdrType_LOCKU4args = *LOCKU4args func (v *LOCKU4args) XdrPointer() interface{} { return v } func (LOCKU4args) XdrTypeName() string { return "LOCKU4args" } func (v LOCKU4args) XdrValue() interface{} { return v } func (v *LOCKU4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOCKU4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slocktype", name), XDR_Nfs_lock_type4(&v.Locktype)) x.Marshal(x.Sprintf("%sseqid", name), XDR_Seqid4(&v.Seqid)) x.Marshal(x.Sprintf("%slock_stateid", name), XDR_Stateid4(&v.Lock_stateid)) x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%slength", name), XDR_Length4(&v.Length)) } func XDR_LOCKU4args(v *LOCKU4args) *LOCKU4args { return v } func (u *LOCKU4res) Lock_stateid() *Stateid4 { switch u.Status { case NFS4_OK: if v, ok := u.U.(*Stateid4); ok { return v } else { var zero Stateid4 u.U = &zero return &zero } default: XdrPanic("LOCKU4res.Lock_stateid accessed when Status == %v", u.Status) return nil } } func (u LOCKU4res) XdrValid() bool { return true } func (u *LOCKU4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *LOCKU4res) XdrUnionTagName() string { return "Status" } func (u *LOCKU4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_Stateid4(u.Lock_stateid()) default: return nil } } func (u *LOCKU4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Lock_stateid" default: return "" } } type XdrType_LOCKU4res = *LOCKU4res func (v *LOCKU4res) XdrPointer() interface{} { return v } func (LOCKU4res) XdrTypeName() string { return "LOCKU4res" } func (v LOCKU4res) XdrValue() interface{} { return v } func (v *LOCKU4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LOCKU4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%slock_stateid", name), XDR_Stateid4(u.Lock_stateid())) return default: return } } func XDR_LOCKU4res(v *LOCKU4res) *LOCKU4res { return v} type XdrType_LOOKUP4args = *LOOKUP4args func (v *LOOKUP4args) XdrPointer() interface{} { return v } func (LOOKUP4args) XdrTypeName() string { return "LOOKUP4args" } func (v LOOKUP4args) XdrValue() interface{} { return v } func (v *LOOKUP4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOOKUP4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobjname", name), XDR_Component4(&v.Objname)) } func XDR_LOOKUP4args(v *LOOKUP4args) *LOOKUP4args { return v } type XdrType_LOOKUP4res = *LOOKUP4res func (v *LOOKUP4res) XdrPointer() interface{} { return v } func (LOOKUP4res) XdrTypeName() string { return "LOOKUP4res" } func (v LOOKUP4res) XdrValue() interface{} { return v } func (v *LOOKUP4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOOKUP4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_LOOKUP4res(v *LOOKUP4res) *LOOKUP4res { return v } type XdrType_LOOKUPP4res = *LOOKUPP4res func (v *LOOKUPP4res) XdrPointer() interface{} { return v } func (LOOKUPP4res) XdrTypeName() string { return "LOOKUPP4res" } func (v LOOKUPP4res) XdrValue() interface{} { return v } func (v *LOOKUPP4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LOOKUPP4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_LOOKUPP4res(v *LOOKUPP4res) *LOOKUPP4res { return v } type XdrType_NVERIFY4args = *NVERIFY4args func (v *NVERIFY4args) XdrPointer() interface{} { return v } func (NVERIFY4args) XdrTypeName() string { return "NVERIFY4args" } func (v NVERIFY4args) XdrValue() interface{} { return v } func (v *NVERIFY4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *NVERIFY4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobj_attributes", name), XDR_Fattr4(&v.Obj_attributes)) } func XDR_NVERIFY4args(v *NVERIFY4args) *NVERIFY4args { return v } type XdrType_NVERIFY4res = *NVERIFY4res func (v *NVERIFY4res) XdrPointer() interface{} { return v } func (NVERIFY4res) XdrTypeName() string { return "NVERIFY4res" } func (v NVERIFY4res) XdrValue() interface{} { return v } func (v *NVERIFY4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *NVERIFY4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_NVERIFY4res(v *NVERIFY4res) *NVERIFY4res { return v } var _XdrNames_Createmode4 = map[int32]string{ int32(UNCHECKED4): "UNCHECKED4", int32(GUARDED4): "GUARDED4", int32(EXCLUSIVE4): "EXCLUSIVE4", } var _XdrValues_Createmode4 = map[string]int32{ "UNCHECKED4": int32(UNCHECKED4), "GUARDED4": int32(GUARDED4), "EXCLUSIVE4": int32(EXCLUSIVE4), } func (Createmode4) XdrEnumNames() map[int32]string { return _XdrNames_Createmode4 } func (v Createmode4) String() string { if s, ok := _XdrNames_Createmode4[int32(v)]; ok { return s } return fmt.Sprintf("Createmode4#%d", v) } func (v *Createmode4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Createmode4[stok]; ok { *v = Createmode4(val) return nil } else if stok == "Createmode4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Createmode4.", stok)) } } func (v Createmode4) GetU32() uint32 { return uint32(v) } func (v *Createmode4) SetU32(n uint32) { *v = Createmode4(n) } func (v *Createmode4) XdrPointer() interface{} { return v } func (Createmode4) XdrTypeName() string { return "Createmode4" } func (v Createmode4) XdrValue() interface{} { return v } func (v *Createmode4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Createmode4 = *Createmode4 func XDR_Createmode4(v *Createmode4) *Createmode4 { return v } var _XdrTags_Createhow4 = map[int32]bool{ XdrToI32(UNCHECKED4): true, XdrToI32(GUARDED4): true, XdrToI32(EXCLUSIVE4): true, } func (_ Createhow4) XdrValidTags() map[int32]bool { return _XdrTags_Createhow4 } func (u *Createhow4) Createattrs() *Fattr4 { switch u.Mode { case UNCHECKED4, GUARDED4: if v, ok := u.U.(*Fattr4); ok { return v } else { var zero Fattr4 u.U = &zero return &zero } default: XdrPanic("Createhow4.Createattrs accessed when Mode == %v", u.Mode) return nil } } func (u *Createhow4) Createverf() *Verifier4 { switch u.Mode { case EXCLUSIVE4: if v, ok := u.U.(*Verifier4); ok { return v } else { var zero Verifier4 u.U = &zero return &zero } default: XdrPanic("Createhow4.Createverf accessed when Mode == %v", u.Mode) return nil } } func (u Createhow4) XdrValid() bool { switch u.Mode { case UNCHECKED4, GUARDED4,EXCLUSIVE4: return true } return false } func (u *Createhow4) XdrUnionTag() XdrNum32 { return XDR_Createmode4(&u.Mode) } func (u *Createhow4) XdrUnionTagName() string { return "Mode" } func (u *Createhow4) XdrUnionBody() XdrType { switch u.Mode { case UNCHECKED4, GUARDED4: return XDR_Fattr4(u.Createattrs()) case EXCLUSIVE4: return XDR_Verifier4(u.Createverf()) } return nil } func (u *Createhow4) XdrUnionBodyName() string { switch u.Mode { case UNCHECKED4, GUARDED4: return "Createattrs" case EXCLUSIVE4: return "Createverf" } return "" } type XdrType_Createhow4 = *Createhow4 func (v *Createhow4) XdrPointer() interface{} { return v } func (Createhow4) XdrTypeName() string { return "Createhow4" } func (v Createhow4) XdrValue() interface{} { return v } func (v *Createhow4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Createhow4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Createmode4(&u.Mode).XdrMarshal(x, x.Sprintf("%smode", name)) switch u.Mode { case UNCHECKED4, GUARDED4: x.Marshal(x.Sprintf("%screateattrs", name), XDR_Fattr4(u.Createattrs())) return case EXCLUSIVE4: x.Marshal(x.Sprintf("%screateverf", name), XDR_Verifier4(u.Createverf())) return } XdrPanic("invalid Mode (%v) in Createhow4", u.Mode) } func XDR_Createhow4(v *Createhow4) *Createhow4 { return v} var _XdrNames_Opentype4 = map[int32]string{ int32(OPEN4_NOCREATE): "OPEN4_NOCREATE", int32(OPEN4_CREATE): "OPEN4_CREATE", } var _XdrValues_Opentype4 = map[string]int32{ "OPEN4_NOCREATE": int32(OPEN4_NOCREATE), "OPEN4_CREATE": int32(OPEN4_CREATE), } func (Opentype4) XdrEnumNames() map[int32]string { return _XdrNames_Opentype4 } func (v Opentype4) String() string { if s, ok := _XdrNames_Opentype4[int32(v)]; ok { return s } return fmt.Sprintf("Opentype4#%d", v) } func (v *Opentype4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Opentype4[stok]; ok { *v = Opentype4(val) return nil } else if stok == "Opentype4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Opentype4.", stok)) } } func (v Opentype4) GetU32() uint32 { return uint32(v) } func (v *Opentype4) SetU32(n uint32) { *v = Opentype4(n) } func (v *Opentype4) XdrPointer() interface{} { return v } func (Opentype4) XdrTypeName() string { return "Opentype4" } func (v Opentype4) XdrValue() interface{} { return v } func (v *Opentype4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Opentype4 = *Opentype4 func XDR_Opentype4(v *Opentype4) *Opentype4 { return v } func (u *Openflag4) How() *Createhow4 { switch u.Opentype { case OPEN4_CREATE: if v, ok := u.U.(*Createhow4); ok { return v } else { var zero Createhow4 u.U = &zero return &zero } default: XdrPanic("Openflag4.How accessed when Opentype == %v", u.Opentype) return nil } } func (u Openflag4) XdrValid() bool { return true } func (u *Openflag4) XdrUnionTag() XdrNum32 { return XDR_Opentype4(&u.Opentype) } func (u *Openflag4) XdrUnionTagName() string { return "Opentype" } func (u *Openflag4) XdrUnionBody() XdrType { switch u.Opentype { case OPEN4_CREATE: return XDR_Createhow4(u.How()) default: return nil } } func (u *Openflag4) XdrUnionBodyName() string { switch u.Opentype { case OPEN4_CREATE: return "How" default: return "" } } type XdrType_Openflag4 = *Openflag4 func (v *Openflag4) XdrPointer() interface{} { return v } func (Openflag4) XdrTypeName() string { return "Openflag4" } func (v Openflag4) XdrValue() interface{} { return v } func (v *Openflag4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Openflag4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Opentype4(&u.Opentype).XdrMarshal(x, x.Sprintf("%sopentype", name)) switch u.Opentype { case OPEN4_CREATE: x.Marshal(x.Sprintf("%show", name), XDR_Createhow4(u.How())) return default: return } } func XDR_Openflag4(v *Openflag4) *Openflag4 { return v} var _XdrNames_Limit_by4 = map[int32]string{ int32(NFS_LIMIT_SIZE): "NFS_LIMIT_SIZE", int32(NFS_LIMIT_BLOCKS): "NFS_LIMIT_BLOCKS", } var _XdrValues_Limit_by4 = map[string]int32{ "NFS_LIMIT_SIZE": int32(NFS_LIMIT_SIZE), "NFS_LIMIT_BLOCKS": int32(NFS_LIMIT_BLOCKS), } func (Limit_by4) XdrEnumNames() map[int32]string { return _XdrNames_Limit_by4 } func (v Limit_by4) String() string { if s, ok := _XdrNames_Limit_by4[int32(v)]; ok { return s } return fmt.Sprintf("Limit_by4#%d", v) } func (v *Limit_by4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Limit_by4[stok]; ok { *v = Limit_by4(val) return nil } else if stok == "Limit_by4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Limit_by4.", stok)) } } func (v Limit_by4) GetU32() uint32 { return uint32(v) } func (v *Limit_by4) SetU32(n uint32) { *v = Limit_by4(n) } func (v *Limit_by4) XdrPointer() interface{} { return v } func (Limit_by4) XdrTypeName() string { return "Limit_by4" } func (v Limit_by4) XdrValue() interface{} { return v } func (v *Limit_by4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Limit_by4 = *Limit_by4 func XDR_Limit_by4(v *Limit_by4) *Limit_by4 { return v } func (v *Limit_by4) XdrInitialize() { switch Limit_by4(0) { case NFS_LIMIT_SIZE, NFS_LIMIT_BLOCKS: default: if *v == Limit_by4(0) { *v = NFS_LIMIT_SIZE } } } type XdrType_Nfs_modified_limit4 = *Nfs_modified_limit4 func (v *Nfs_modified_limit4) XdrPointer() interface{} { return v } func (Nfs_modified_limit4) XdrTypeName() string { return "Nfs_modified_limit4" } func (v Nfs_modified_limit4) XdrValue() interface{} { return v } func (v *Nfs_modified_limit4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Nfs_modified_limit4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%snum_blocks", name), XDR_Uint32_t(&v.Num_blocks)) x.Marshal(x.Sprintf("%sbytes_per_block", name), XDR_Uint32_t(&v.Bytes_per_block)) } func XDR_Nfs_modified_limit4(v *Nfs_modified_limit4) *Nfs_modified_limit4 { return v } var _XdrTags_Nfs_space_limit4 = map[int32]bool{ XdrToI32(NFS_LIMIT_SIZE): true, XdrToI32(NFS_LIMIT_BLOCKS): true, } func (_ Nfs_space_limit4) XdrValidTags() map[int32]bool { return _XdrTags_Nfs_space_limit4 } func (u *Nfs_space_limit4) Filesize() *Uint64_t { switch u.Limitby { case NFS_LIMIT_SIZE: if v, ok := u.U.(*Uint64_t); ok { return v } else { var zero Uint64_t u.U = &zero return &zero } default: XdrPanic("Nfs_space_limit4.Filesize accessed when Limitby == %v", u.Limitby) return nil } } func (u *Nfs_space_limit4) Mod_blocks() *Nfs_modified_limit4 { switch u.Limitby { case NFS_LIMIT_BLOCKS: if v, ok := u.U.(*Nfs_modified_limit4); ok { return v } else { var zero Nfs_modified_limit4 u.U = &zero return &zero } default: XdrPanic("Nfs_space_limit4.Mod_blocks accessed when Limitby == %v", u.Limitby) return nil } } func (u Nfs_space_limit4) XdrValid() bool { switch u.Limitby { case NFS_LIMIT_SIZE,NFS_LIMIT_BLOCKS: return true } return false } func (u *Nfs_space_limit4) XdrUnionTag() XdrNum32 { return XDR_Limit_by4(&u.Limitby) } func (u *Nfs_space_limit4) XdrUnionTagName() string { return "Limitby" } func (u *Nfs_space_limit4) XdrUnionBody() XdrType { switch u.Limitby { case NFS_LIMIT_SIZE: return XDR_Uint64_t(u.Filesize()) case NFS_LIMIT_BLOCKS: return XDR_Nfs_modified_limit4(u.Mod_blocks()) } return nil } func (u *Nfs_space_limit4) XdrUnionBodyName() string { switch u.Limitby { case NFS_LIMIT_SIZE: return "Filesize" case NFS_LIMIT_BLOCKS: return "Mod_blocks" } return "" } type XdrType_Nfs_space_limit4 = *Nfs_space_limit4 func (v *Nfs_space_limit4) XdrPointer() interface{} { return v } func (Nfs_space_limit4) XdrTypeName() string { return "Nfs_space_limit4" } func (v Nfs_space_limit4) XdrValue() interface{} { return v } func (v *Nfs_space_limit4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Nfs_space_limit4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Limit_by4(&u.Limitby).XdrMarshal(x, x.Sprintf("%slimitby", name)) switch u.Limitby { case NFS_LIMIT_SIZE: x.Marshal(x.Sprintf("%sfilesize", name), XDR_Uint64_t(u.Filesize())) return case NFS_LIMIT_BLOCKS: x.Marshal(x.Sprintf("%smod_blocks", name), XDR_Nfs_modified_limit4(u.Mod_blocks())) return } XdrPanic("invalid Limitby (%v) in Nfs_space_limit4", u.Limitby) } func (v *Nfs_space_limit4) XdrInitialize() { var zero Limit_by4 switch zero { case NFS_LIMIT_SIZE, NFS_LIMIT_BLOCKS: default: if v.Limitby == zero { v.Limitby = NFS_LIMIT_SIZE } } } func XDR_Nfs_space_limit4(v *Nfs_space_limit4) *Nfs_space_limit4 { return v} var _XdrNames_Open_delegation_type4 = map[int32]string{ int32(OPEN_DELEGATE_NONE): "OPEN_DELEGATE_NONE", int32(OPEN_DELEGATE_READ): "OPEN_DELEGATE_READ", int32(OPEN_DELEGATE_WRITE): "OPEN_DELEGATE_WRITE", } var _XdrValues_Open_delegation_type4 = map[string]int32{ "OPEN_DELEGATE_NONE": int32(OPEN_DELEGATE_NONE), "OPEN_DELEGATE_READ": int32(OPEN_DELEGATE_READ), "OPEN_DELEGATE_WRITE": int32(OPEN_DELEGATE_WRITE), } func (Open_delegation_type4) XdrEnumNames() map[int32]string { return _XdrNames_Open_delegation_type4 } func (v Open_delegation_type4) String() string { if s, ok := _XdrNames_Open_delegation_type4[int32(v)]; ok { return s } return fmt.Sprintf("Open_delegation_type4#%d", v) } func (v *Open_delegation_type4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Open_delegation_type4[stok]; ok { *v = Open_delegation_type4(val) return nil } else if stok == "Open_delegation_type4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Open_delegation_type4.", stok)) } } func (v Open_delegation_type4) GetU32() uint32 { return uint32(v) } func (v *Open_delegation_type4) SetU32(n uint32) { *v = Open_delegation_type4(n) } func (v *Open_delegation_type4) XdrPointer() interface{} { return v } func (Open_delegation_type4) XdrTypeName() string { return "Open_delegation_type4" } func (v Open_delegation_type4) XdrValue() interface{} { return v } func (v *Open_delegation_type4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Open_delegation_type4 = *Open_delegation_type4 func XDR_Open_delegation_type4(v *Open_delegation_type4) *Open_delegation_type4 { return v } var _XdrNames_Open_claim_type4 = map[int32]string{ int32(CLAIM_NULL): "CLAIM_NULL", int32(CLAIM_PREVIOUS): "CLAIM_PREVIOUS", int32(CLAIM_DELEGATE_CUR): "CLAIM_DELEGATE_CUR", int32(CLAIM_DELEGATE_PREV): "CLAIM_DELEGATE_PREV", int32(CLAIM_FH): "CLAIM_FH", int32(CLAIM_DELEG_CUR_FH): "CLAIM_DELEG_CUR_FH", int32(CLAIM_DELEG_PREV_FH): "CLAIM_DELEG_PREV_FH", } var _XdrValues_Open_claim_type4 = map[string]int32{ "CLAIM_NULL": int32(CLAIM_NULL), "CLAIM_PREVIOUS": int32(CLAIM_PREVIOUS), "CLAIM_DELEGATE_CUR": int32(CLAIM_DELEGATE_CUR), "CLAIM_DELEGATE_PREV": int32(CLAIM_DELEGATE_PREV), "CLAIM_FH": int32(CLAIM_FH), "CLAIM_DELEG_CUR_FH": int32(CLAIM_DELEG_CUR_FH), "CLAIM_DELEG_PREV_FH": int32(CLAIM_DELEG_PREV_FH), } func (Open_claim_type4) XdrEnumNames() map[int32]string { return _XdrNames_Open_claim_type4 } func (v Open_claim_type4) String() string { if s, ok := _XdrNames_Open_claim_type4[int32(v)]; ok { return s } return fmt.Sprintf("Open_claim_type4#%d", v) } func (v *Open_claim_type4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Open_claim_type4[stok]; ok { *v = Open_claim_type4(val) return nil } else if stok == "Open_claim_type4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Open_claim_type4.", stok)) } } func (v Open_claim_type4) GetU32() uint32 { return uint32(v) } func (v *Open_claim_type4) SetU32(n uint32) { *v = Open_claim_type4(n) } func (v *Open_claim_type4) XdrPointer() interface{} { return v } func (Open_claim_type4) XdrTypeName() string { return "Open_claim_type4" } func (v Open_claim_type4) XdrValue() interface{} { return v } func (v *Open_claim_type4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Open_claim_type4 = *Open_claim_type4 func XDR_Open_claim_type4(v *Open_claim_type4) *Open_claim_type4 { return v } type XdrType_Open_claim_delegate_cur4 = *Open_claim_delegate_cur4 func (v *Open_claim_delegate_cur4) XdrPointer() interface{} { return v } func (Open_claim_delegate_cur4) XdrTypeName() string { return "Open_claim_delegate_cur4" } func (v Open_claim_delegate_cur4) XdrValue() interface{} { return v } func (v *Open_claim_delegate_cur4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Open_claim_delegate_cur4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdelegate_stateid", name), XDR_Stateid4(&v.Delegate_stateid)) x.Marshal(x.Sprintf("%sfile", name), XDR_Component4(&v.File)) } func XDR_Open_claim_delegate_cur4(v *Open_claim_delegate_cur4) *Open_claim_delegate_cur4 { return v } var _XdrTags_Open_claim4 = map[int32]bool{ XdrToI32(CLAIM_NULL): true, XdrToI32(CLAIM_PREVIOUS): true, XdrToI32(CLAIM_DELEGATE_CUR): true, XdrToI32(CLAIM_DELEGATE_PREV): true, } func (_ Open_claim4) XdrValidTags() map[int32]bool { return _XdrTags_Open_claim4 } /* CURRENT_FH: directory */ func (u *Open_claim4) File() *Component4 { switch u.Claim { case CLAIM_NULL: if v, ok := u.U.(*Component4); ok { return v } else { var zero Component4 u.U = &zero return &zero } default: XdrPanic("Open_claim4.File accessed when Claim == %v", u.Claim) return nil } } /* CURRENT_FH: file being reclaimed */ func (u *Open_claim4) Delegate_type() *Open_delegation_type4 { switch u.Claim { case CLAIM_PREVIOUS: if v, ok := u.U.(*Open_delegation_type4); ok { return v } else { var zero Open_delegation_type4 u.U = &zero return &zero } default: XdrPanic("Open_claim4.Delegate_type accessed when Claim == %v", u.Claim) return nil } } /* CURRENT_FH: directory */ func (u *Open_claim4) Delegate_cur_info() *Open_claim_delegate_cur4 { switch u.Claim { case CLAIM_DELEGATE_CUR: if v, ok := u.U.(*Open_claim_delegate_cur4); ok { return v } else { var zero Open_claim_delegate_cur4 u.U = &zero return &zero } default: XdrPanic("Open_claim4.Delegate_cur_info accessed when Claim == %v", u.Claim) return nil } } /* CURRENT_FH: directory */ func (u *Open_claim4) File_delegate_prev() *Component4 { switch u.Claim { case CLAIM_DELEGATE_PREV: if v, ok := u.U.(*Component4); ok { return v } else { var zero Component4 u.U = &zero return &zero } default: XdrPanic("Open_claim4.File_delegate_prev accessed when Claim == %v", u.Claim) return nil } } func (u Open_claim4) XdrValid() bool { switch u.Claim { case CLAIM_NULL,CLAIM_PREVIOUS,CLAIM_DELEGATE_CUR,CLAIM_DELEGATE_PREV: return true } return false } func (u *Open_claim4) XdrUnionTag() XdrNum32 { return XDR_Open_claim_type4(&u.Claim) } func (u *Open_claim4) XdrUnionTagName() string { return "Claim" } func (u *Open_claim4) XdrUnionBody() XdrType { switch u.Claim { case CLAIM_NULL: return XDR_Component4(u.File()) case CLAIM_PREVIOUS: return XDR_Open_delegation_type4(u.Delegate_type()) case CLAIM_DELEGATE_CUR: return XDR_Open_claim_delegate_cur4(u.Delegate_cur_info()) case CLAIM_DELEGATE_PREV: return XDR_Component4(u.File_delegate_prev()) } return nil } func (u *Open_claim4) XdrUnionBodyName() string { switch u.Claim { case CLAIM_NULL: return "File" case CLAIM_PREVIOUS: return "Delegate_type" case CLAIM_DELEGATE_CUR: return "Delegate_cur_info" case CLAIM_DELEGATE_PREV: return "File_delegate_prev" } return "" } type XdrType_Open_claim4 = *Open_claim4 func (v *Open_claim4) XdrPointer() interface{} { return v } func (Open_claim4) XdrTypeName() string { return "Open_claim4" } func (v Open_claim4) XdrValue() interface{} { return v } func (v *Open_claim4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Open_claim4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Open_claim_type4(&u.Claim).XdrMarshal(x, x.Sprintf("%sclaim", name)) switch u.Claim { case CLAIM_NULL: x.Marshal(x.Sprintf("%sfile", name), XDR_Component4(u.File())) return case CLAIM_PREVIOUS: x.Marshal(x.Sprintf("%sdelegate_type", name), XDR_Open_delegation_type4(u.Delegate_type())) return case CLAIM_DELEGATE_CUR: x.Marshal(x.Sprintf("%sdelegate_cur_info", name), XDR_Open_claim_delegate_cur4(u.Delegate_cur_info())) return case CLAIM_DELEGATE_PREV: x.Marshal(x.Sprintf("%sfile_delegate_prev", name), XDR_Component4(u.File_delegate_prev())) return } XdrPanic("invalid Claim (%v) in Open_claim4", u.Claim) } func XDR_Open_claim4(v *Open_claim4) *Open_claim4 { return v} type XdrType_OPEN4args = *OPEN4args func (v *OPEN4args) XdrPointer() interface{} { return v } func (OPEN4args) XdrTypeName() string { return "OPEN4args" } func (v OPEN4args) XdrValue() interface{} { return v } func (v *OPEN4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sseqid", name), XDR_Seqid4(&v.Seqid)) x.Marshal(x.Sprintf("%sshare_access", name), XDR_Uint32_t(&v.Share_access)) x.Marshal(x.Sprintf("%sshare_deny", name), XDR_Uint32_t(&v.Share_deny)) x.Marshal(x.Sprintf("%sowner", name), XDR_Open_owner4(&v.Owner)) x.Marshal(x.Sprintf("%sopenhow", name), XDR_Openflag4(&v.Openhow)) x.Marshal(x.Sprintf("%sclaim", name), XDR_Open_claim4(&v.Claim)) } func XDR_OPEN4args(v *OPEN4args) *OPEN4args { return v } type XdrType_Open_read_delegation4 = *Open_read_delegation4 func (v *Open_read_delegation4) XdrPointer() interface{} { return v } func (Open_read_delegation4) XdrTypeName() string { return "Open_read_delegation4" } func (v Open_read_delegation4) XdrValue() interface{} { return v } func (v *Open_read_delegation4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Open_read_delegation4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%srecall", name), XDR_bool(&v.Recall)) x.Marshal(x.Sprintf("%spermissions", name), XDR_Nfsace4(&v.Permissions)) } func XDR_Open_read_delegation4(v *Open_read_delegation4) *Open_read_delegation4 { return v } type XdrType_Open_write_delegation4 = *Open_write_delegation4 func (v *Open_write_delegation4) XdrPointer() interface{} { return v } func (Open_write_delegation4) XdrTypeName() string { return "Open_write_delegation4" } func (v Open_write_delegation4) XdrValue() interface{} { return v } func (v *Open_write_delegation4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Open_write_delegation4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%srecall", name), XDR_bool(&v.Recall)) x.Marshal(x.Sprintf("%sspace_limit", name), XDR_Nfs_space_limit4(&v.Space_limit)) x.Marshal(x.Sprintf("%spermissions", name), XDR_Nfsace4(&v.Permissions)) } func XDR_Open_write_delegation4(v *Open_write_delegation4) *Open_write_delegation4 { return v } var _XdrTags_Open_delegation4 = map[int32]bool{ XdrToI32(OPEN_DELEGATE_NONE): true, XdrToI32(OPEN_DELEGATE_READ): true, XdrToI32(OPEN_DELEGATE_WRITE): true, } func (_ Open_delegation4) XdrValidTags() map[int32]bool { return _XdrTags_Open_delegation4 } func (u *Open_delegation4) Read() *Open_read_delegation4 { switch u.Delegation_type { case OPEN_DELEGATE_READ: if v, ok := u.U.(*Open_read_delegation4); ok { return v } else { var zero Open_read_delegation4 u.U = &zero return &zero } default: XdrPanic("Open_delegation4.Read accessed when Delegation_type == %v", u.Delegation_type) return nil } } func (u *Open_delegation4) Write() *Open_write_delegation4 { switch u.Delegation_type { case OPEN_DELEGATE_WRITE: if v, ok := u.U.(*Open_write_delegation4); ok { return v } else { var zero Open_write_delegation4 u.U = &zero return &zero } default: XdrPanic("Open_delegation4.Write accessed when Delegation_type == %v", u.Delegation_type) return nil } } func (u Open_delegation4) XdrValid() bool { switch u.Delegation_type { case OPEN_DELEGATE_NONE,OPEN_DELEGATE_READ,OPEN_DELEGATE_WRITE: return true } return false } func (u *Open_delegation4) XdrUnionTag() XdrNum32 { return XDR_Open_delegation_type4(&u.Delegation_type) } func (u *Open_delegation4) XdrUnionTagName() string { return "Delegation_type" } func (u *Open_delegation4) XdrUnionBody() XdrType { switch u.Delegation_type { case OPEN_DELEGATE_NONE: return nil case OPEN_DELEGATE_READ: return XDR_Open_read_delegation4(u.Read()) case OPEN_DELEGATE_WRITE: return XDR_Open_write_delegation4(u.Write()) } return nil } func (u *Open_delegation4) XdrUnionBodyName() string { switch u.Delegation_type { case OPEN_DELEGATE_NONE: return "" case OPEN_DELEGATE_READ: return "Read" case OPEN_DELEGATE_WRITE: return "Write" } return "" } type XdrType_Open_delegation4 = *Open_delegation4 func (v *Open_delegation4) XdrPointer() interface{} { return v } func (Open_delegation4) XdrTypeName() string { return "Open_delegation4" } func (v Open_delegation4) XdrValue() interface{} { return v } func (v *Open_delegation4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Open_delegation4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Open_delegation_type4(&u.Delegation_type).XdrMarshal(x, x.Sprintf("%sdelegation_type", name)) switch u.Delegation_type { case OPEN_DELEGATE_NONE: return case OPEN_DELEGATE_READ: x.Marshal(x.Sprintf("%sread", name), XDR_Open_read_delegation4(u.Read())) return case OPEN_DELEGATE_WRITE: x.Marshal(x.Sprintf("%swrite", name), XDR_Open_write_delegation4(u.Write())) return } XdrPanic("invalid Delegation_type (%v) in Open_delegation4", u.Delegation_type) } func XDR_Open_delegation4(v *Open_delegation4) *Open_delegation4 { return v} type XdrType_OPEN4resok = *OPEN4resok func (v *OPEN4resok) XdrPointer() interface{} { return v } func (OPEN4resok) XdrTypeName() string { return "OPEN4resok" } func (v OPEN4resok) XdrValue() interface{} { return v } func (v *OPEN4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%scinfo", name), XDR_Change_info4(&v.Cinfo)) x.Marshal(x.Sprintf("%srflags", name), XDR_Uint32_t(&v.Rflags)) x.Marshal(x.Sprintf("%sattrset", name), XDR_Bitmap4(&v.Attrset)) x.Marshal(x.Sprintf("%sdelegation", name), XDR_Open_delegation4(&v.Delegation)) } func XDR_OPEN4resok(v *OPEN4resok) *OPEN4resok { return v } /* CURRENT_FH: opened file */ func (u *OPEN4res) Resok4() *OPEN4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*OPEN4resok); ok { return v } else { var zero OPEN4resok u.U = &zero return &zero } default: XdrPanic("OPEN4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u OPEN4res) XdrValid() bool { return true } func (u *OPEN4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *OPEN4res) XdrUnionTagName() string { return "Status" } func (u *OPEN4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_OPEN4resok(u.Resok4()) default: return nil } } func (u *OPEN4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_OPEN4res = *OPEN4res func (v *OPEN4res) XdrPointer() interface{} { return v } func (OPEN4res) XdrTypeName() string { return "OPEN4res" } func (v OPEN4res) XdrValue() interface{} { return v } func (v *OPEN4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *OPEN4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_OPEN4resok(u.Resok4())) return default: return } } func XDR_OPEN4res(v *OPEN4res) *OPEN4res { return v} type XdrType_OPENATTR4args = *OPENATTR4args func (v *OPENATTR4args) XdrPointer() interface{} { return v } func (OPENATTR4args) XdrTypeName() string { return "OPENATTR4args" } func (v OPENATTR4args) XdrValue() interface{} { return v } func (v *OPENATTR4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPENATTR4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%screatedir", name), XDR_bool(&v.Createdir)) } func XDR_OPENATTR4args(v *OPENATTR4args) *OPENATTR4args { return v } type XdrType_OPENATTR4res = *OPENATTR4res func (v *OPENATTR4res) XdrPointer() interface{} { return v } func (OPENATTR4res) XdrTypeName() string { return "OPENATTR4res" } func (v OPENATTR4res) XdrValue() interface{} { return v } func (v *OPENATTR4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPENATTR4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_OPENATTR4res(v *OPENATTR4res) *OPENATTR4res { return v } type XdrType_OPEN_CONFIRM4args = *OPEN_CONFIRM4args func (v *OPEN_CONFIRM4args) XdrPointer() interface{} { return v } func (OPEN_CONFIRM4args) XdrTypeName() string { return "OPEN_CONFIRM4args" } func (v OPEN_CONFIRM4args) XdrValue() interface{} { return v } func (v *OPEN_CONFIRM4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN_CONFIRM4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) x.Marshal(x.Sprintf("%sseqid", name), XDR_Seqid4(&v.Seqid)) } func XDR_OPEN_CONFIRM4args(v *OPEN_CONFIRM4args) *OPEN_CONFIRM4args { return v } type XdrType_OPEN_CONFIRM4resok = *OPEN_CONFIRM4resok func (v *OPEN_CONFIRM4resok) XdrPointer() interface{} { return v } func (OPEN_CONFIRM4resok) XdrTypeName() string { return "OPEN_CONFIRM4resok" } func (v OPEN_CONFIRM4resok) XdrValue() interface{} { return v } func (v *OPEN_CONFIRM4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN_CONFIRM4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) } func XDR_OPEN_CONFIRM4resok(v *OPEN_CONFIRM4resok) *OPEN_CONFIRM4resok { return v } func (u *OPEN_CONFIRM4res) Resok4() *OPEN_CONFIRM4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*OPEN_CONFIRM4resok); ok { return v } else { var zero OPEN_CONFIRM4resok u.U = &zero return &zero } default: XdrPanic("OPEN_CONFIRM4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u OPEN_CONFIRM4res) XdrValid() bool { return true } func (u *OPEN_CONFIRM4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *OPEN_CONFIRM4res) XdrUnionTagName() string { return "Status" } func (u *OPEN_CONFIRM4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_OPEN_CONFIRM4resok(u.Resok4()) default: return nil } } func (u *OPEN_CONFIRM4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_OPEN_CONFIRM4res = *OPEN_CONFIRM4res func (v *OPEN_CONFIRM4res) XdrPointer() interface{} { return v } func (OPEN_CONFIRM4res) XdrTypeName() string { return "OPEN_CONFIRM4res" } func (v OPEN_CONFIRM4res) XdrValue() interface{} { return v } func (v *OPEN_CONFIRM4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *OPEN_CONFIRM4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_OPEN_CONFIRM4resok(u.Resok4())) return default: return } } func XDR_OPEN_CONFIRM4res(v *OPEN_CONFIRM4res) *OPEN_CONFIRM4res { return v} type XdrType_OPEN_DOWNGRADE4args = *OPEN_DOWNGRADE4args func (v *OPEN_DOWNGRADE4args) XdrPointer() interface{} { return v } func (OPEN_DOWNGRADE4args) XdrTypeName() string { return "OPEN_DOWNGRADE4args" } func (v OPEN_DOWNGRADE4args) XdrValue() interface{} { return v } func (v *OPEN_DOWNGRADE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN_DOWNGRADE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) x.Marshal(x.Sprintf("%sseqid", name), XDR_Seqid4(&v.Seqid)) x.Marshal(x.Sprintf("%sshare_access", name), XDR_Uint32_t(&v.Share_access)) x.Marshal(x.Sprintf("%sshare_deny", name), XDR_Uint32_t(&v.Share_deny)) } func XDR_OPEN_DOWNGRADE4args(v *OPEN_DOWNGRADE4args) *OPEN_DOWNGRADE4args { return v } type XdrType_OPEN_DOWNGRADE4resok = *OPEN_DOWNGRADE4resok func (v *OPEN_DOWNGRADE4resok) XdrPointer() interface{} { return v } func (OPEN_DOWNGRADE4resok) XdrTypeName() string { return "OPEN_DOWNGRADE4resok" } func (v OPEN_DOWNGRADE4resok) XdrValue() interface{} { return v } func (v *OPEN_DOWNGRADE4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *OPEN_DOWNGRADE4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sopen_stateid", name), XDR_Stateid4(&v.Open_stateid)) } func XDR_OPEN_DOWNGRADE4resok(v *OPEN_DOWNGRADE4resok) *OPEN_DOWNGRADE4resok { return v } func (u *OPEN_DOWNGRADE4res) Resok4() *OPEN_DOWNGRADE4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*OPEN_DOWNGRADE4resok); ok { return v } else { var zero OPEN_DOWNGRADE4resok u.U = &zero return &zero } default: XdrPanic("OPEN_DOWNGRADE4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u OPEN_DOWNGRADE4res) XdrValid() bool { return true } func (u *OPEN_DOWNGRADE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *OPEN_DOWNGRADE4res) XdrUnionTagName() string { return "Status" } func (u *OPEN_DOWNGRADE4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_OPEN_DOWNGRADE4resok(u.Resok4()) default: return nil } } func (u *OPEN_DOWNGRADE4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_OPEN_DOWNGRADE4res = *OPEN_DOWNGRADE4res func (v *OPEN_DOWNGRADE4res) XdrPointer() interface{} { return v } func (OPEN_DOWNGRADE4res) XdrTypeName() string { return "OPEN_DOWNGRADE4res" } func (v OPEN_DOWNGRADE4res) XdrValue() interface{} { return v } func (v *OPEN_DOWNGRADE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *OPEN_DOWNGRADE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_OPEN_DOWNGRADE4resok(u.Resok4())) return default: return } } func XDR_OPEN_DOWNGRADE4res(v *OPEN_DOWNGRADE4res) *OPEN_DOWNGRADE4res { return v} type XdrType_PUTFH4args = *PUTFH4args func (v *PUTFH4args) XdrPointer() interface{} { return v } func (PUTFH4args) XdrTypeName() string { return "PUTFH4args" } func (v PUTFH4args) XdrValue() interface{} { return v } func (v *PUTFH4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *PUTFH4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobject", name), XDR_Nfs_fh4(&v.Object)) } func XDR_PUTFH4args(v *PUTFH4args) *PUTFH4args { return v } type XdrType_PUTFH4res = *PUTFH4res func (v *PUTFH4res) XdrPointer() interface{} { return v } func (PUTFH4res) XdrTypeName() string { return "PUTFH4res" } func (v PUTFH4res) XdrValue() interface{} { return v } func (v *PUTFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *PUTFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_PUTFH4res(v *PUTFH4res) *PUTFH4res { return v } type XdrType_PUTPUBFH4res = *PUTPUBFH4res func (v *PUTPUBFH4res) XdrPointer() interface{} { return v } func (PUTPUBFH4res) XdrTypeName() string { return "PUTPUBFH4res" } func (v PUTPUBFH4res) XdrValue() interface{} { return v } func (v *PUTPUBFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *PUTPUBFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_PUTPUBFH4res(v *PUTPUBFH4res) *PUTPUBFH4res { return v } type XdrType_PUTROOTFH4res = *PUTROOTFH4res func (v *PUTROOTFH4res) XdrPointer() interface{} { return v } func (PUTROOTFH4res) XdrTypeName() string { return "PUTROOTFH4res" } func (v PUTROOTFH4res) XdrValue() interface{} { return v } func (v *PUTROOTFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *PUTROOTFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_PUTROOTFH4res(v *PUTROOTFH4res) *PUTROOTFH4res { return v } type XdrType_READ4args = *READ4args func (v *READ4args) XdrPointer() interface{} { return v } func (READ4args) XdrTypeName() string { return "READ4args" } func (v READ4args) XdrValue() interface{} { return v } func (v *READ4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *READ4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%scount", name), XDR_Count4(&v.Count)) } func XDR_READ4args(v *READ4args) *READ4args { return v } type XdrType_READ4resok = *READ4resok func (v *READ4resok) XdrPointer() interface{} { return v } func (READ4resok) XdrTypeName() string { return "READ4resok" } func (v READ4resok) XdrValue() interface{} { return v } func (v *READ4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *READ4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%seof", name), XDR_bool(&v.Eof)) x.Marshal(x.Sprintf("%sdata", name), XdrVecOpaque{&v.Data, 0xffffffff}) } func XDR_READ4resok(v *READ4resok) *READ4resok { return v } func (u *READ4res) Resok4() *READ4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*READ4resok); ok { return v } else { var zero READ4resok u.U = &zero return &zero } default: XdrPanic("READ4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u READ4res) XdrValid() bool { return true } func (u *READ4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *READ4res) XdrUnionTagName() string { return "Status" } func (u *READ4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_READ4resok(u.Resok4()) default: return nil } } func (u *READ4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_READ4res = *READ4res func (v *READ4res) XdrPointer() interface{} { return v } func (READ4res) XdrTypeName() string { return "READ4res" } func (v READ4res) XdrValue() interface{} { return v } func (v *READ4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *READ4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_READ4resok(u.Resok4())) return default: return } } func XDR_READ4res(v *READ4res) *READ4res { return v} type XdrType_READDIR4args = *READDIR4args func (v *READDIR4args) XdrPointer() interface{} { return v } func (READDIR4args) XdrTypeName() string { return "READDIR4args" } func (v READDIR4args) XdrValue() interface{} { return v } func (v *READDIR4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *READDIR4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scookie", name), XDR_Nfs_cookie4(&v.Cookie)) x.Marshal(x.Sprintf("%scookieverf", name), XDR_Verifier4(&v.Cookieverf)) x.Marshal(x.Sprintf("%sdircount", name), XDR_Count4(&v.Dircount)) x.Marshal(x.Sprintf("%smaxcount", name), XDR_Count4(&v.Maxcount)) x.Marshal(x.Sprintf("%sattr_request", name), XDR_Bitmap4(&v.Attr_request)) } func XDR_READDIR4args(v *READDIR4args) *READDIR4args { return v } type _XdrPtr_Entry4 struct { p **Entry4 } type _ptrflag_Entry4 _XdrPtr_Entry4 func (v _ptrflag_Entry4) String() string { if *v.p == nil { return "nil" } return "non-nil" } func (v _ptrflag_Entry4) Scan(ss fmt.ScanState, r rune) error { tok, err := ss.Token(true, func(c rune) bool { return c == '-' || (c >= 'a' && c <= 'z') }) if err != nil { return err } switch string(tok) { case "nil": v.SetU32(0) case "non-nil": v.SetU32(1) default: return XdrError("Entry4 flag should be \"nil\" or \"non-nil\"") } return nil } func (v _ptrflag_Entry4) GetU32() uint32 { if *v.p == nil { return 0 } return 1 } func (v _ptrflag_Entry4) SetU32(nv uint32) { switch nv { case 0: *v.p = nil case 1: if *v.p == nil { *v.p = new(Entry4) } default: XdrPanic("*Entry4 present flag value %d should be 0 or 1", nv) } } func (_ptrflag_Entry4) XdrTypeName() string { return "Entry4?" } func (v _ptrflag_Entry4) XdrPointer() interface{} { return nil } func (v _ptrflag_Entry4) XdrValue() interface{} { return v.GetU32() != 0 } func (v _ptrflag_Entry4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v _ptrflag_Entry4) XdrBound() uint32 { return 1 } func (v _XdrPtr_Entry4) GetPresent() bool { return *v.p != nil } func (v _XdrPtr_Entry4) SetPresent(present bool) { if !present { *v.p = nil } else if *v.p == nil { *v.p = new(Entry4) } } func (v _XdrPtr_Entry4) XdrMarshalValue(x XDR, name string) { if *v.p != nil { XDR_Entry4(*v.p).XdrMarshal(x, name) } } func (v _XdrPtr_Entry4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v _XdrPtr_Entry4) XdrRecurse(x XDR, name string) { x.Marshal(name, _ptrflag_Entry4(v)) v.XdrMarshalValue(x, name) } func (_XdrPtr_Entry4) XdrTypeName() string { return "Entry4*" } func (v _XdrPtr_Entry4) XdrPointer() interface{} { return v.p } func (v _XdrPtr_Entry4) XdrValue() interface{} { return *v.p } type XdrType_Entry4 = *Entry4 func (v *Entry4) XdrPointer() interface{} { return v } func (Entry4) XdrTypeName() string { return "Entry4" } func (v Entry4) XdrValue() interface{} { return v } func (v *Entry4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Entry4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scookie", name), XDR_Nfs_cookie4(&v.Cookie)) x.Marshal(x.Sprintf("%sname", name), XDR_Component4(&v.Name)) x.Marshal(x.Sprintf("%sattrs", name), XDR_Fattr4(&v.Attrs)) x.Marshal(x.Sprintf("%snextentry", name), _XdrPtr_Entry4{&v.Nextentry}) } func XDR_Entry4(v *Entry4) *Entry4 { return v } type XdrType_Dirlist4 = *Dirlist4 func (v *Dirlist4) XdrPointer() interface{} { return v } func (Dirlist4) XdrTypeName() string { return "Dirlist4" } func (v Dirlist4) XdrValue() interface{} { return v } func (v *Dirlist4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Dirlist4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sentries", name), _XdrPtr_Entry4{&v.Entries}) x.Marshal(x.Sprintf("%seof", name), XDR_bool(&v.Eof)) } func XDR_Dirlist4(v *Dirlist4) *Dirlist4 { return v } type XdrType_READDIR4resok = *READDIR4resok func (v *READDIR4resok) XdrPointer() interface{} { return v } func (READDIR4resok) XdrTypeName() string { return "READDIR4resok" } func (v READDIR4resok) XdrValue() interface{} { return v } func (v *READDIR4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *READDIR4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scookieverf", name), XDR_Verifier4(&v.Cookieverf)) x.Marshal(x.Sprintf("%sreply", name), XDR_Dirlist4(&v.Reply)) } func XDR_READDIR4resok(v *READDIR4resok) *READDIR4resok { return v } func (u *READDIR4res) Resok4() *READDIR4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*READDIR4resok); ok { return v } else { var zero READDIR4resok u.U = &zero return &zero } default: XdrPanic("READDIR4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u READDIR4res) XdrValid() bool { return true } func (u *READDIR4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *READDIR4res) XdrUnionTagName() string { return "Status" } func (u *READDIR4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_READDIR4resok(u.Resok4()) default: return nil } } func (u *READDIR4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_READDIR4res = *READDIR4res func (v *READDIR4res) XdrPointer() interface{} { return v } func (READDIR4res) XdrTypeName() string { return "READDIR4res" } func (v READDIR4res) XdrValue() interface{} { return v } func (v *READDIR4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *READDIR4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_READDIR4resok(u.Resok4())) return default: return } } func XDR_READDIR4res(v *READDIR4res) *READDIR4res { return v} type XdrType_READLINK4resok = *READLINK4resok func (v *READLINK4resok) XdrPointer() interface{} { return v } func (READLINK4resok) XdrTypeName() string { return "READLINK4resok" } func (v READLINK4resok) XdrValue() interface{} { return v } func (v *READLINK4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *READLINK4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slink", name), XDR_Linktext4(&v.Link)) } func XDR_READLINK4resok(v *READLINK4resok) *READLINK4resok { return v } func (u *READLINK4res) Resok4() *READLINK4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*READLINK4resok); ok { return v } else { var zero READLINK4resok u.U = &zero return &zero } default: XdrPanic("READLINK4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u READLINK4res) XdrValid() bool { return true } func (u *READLINK4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *READLINK4res) XdrUnionTagName() string { return "Status" } func (u *READLINK4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_READLINK4resok(u.Resok4()) default: return nil } } func (u *READLINK4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_READLINK4res = *READLINK4res func (v *READLINK4res) XdrPointer() interface{} { return v } func (READLINK4res) XdrTypeName() string { return "READLINK4res" } func (v READLINK4res) XdrValue() interface{} { return v } func (v *READLINK4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *READLINK4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_READLINK4resok(u.Resok4())) return default: return } } func XDR_READLINK4res(v *READLINK4res) *READLINK4res { return v} type XdrType_REMOVE4args = *REMOVE4args func (v *REMOVE4args) XdrPointer() interface{} { return v } func (REMOVE4args) XdrTypeName() string { return "REMOVE4args" } func (v REMOVE4args) XdrValue() interface{} { return v } func (v *REMOVE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *REMOVE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%starget", name), XDR_Component4(&v.Target)) } func XDR_REMOVE4args(v *REMOVE4args) *REMOVE4args { return v } type XdrType_REMOVE4resok = *REMOVE4resok func (v *REMOVE4resok) XdrPointer() interface{} { return v } func (REMOVE4resok) XdrTypeName() string { return "REMOVE4resok" } func (v REMOVE4resok) XdrValue() interface{} { return v } func (v *REMOVE4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *REMOVE4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scinfo", name), XDR_Change_info4(&v.Cinfo)) } func XDR_REMOVE4resok(v *REMOVE4resok) *REMOVE4resok { return v } func (u *REMOVE4res) Resok4() *REMOVE4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*REMOVE4resok); ok { return v } else { var zero REMOVE4resok u.U = &zero return &zero } default: XdrPanic("REMOVE4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u REMOVE4res) XdrValid() bool { return true } func (u *REMOVE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *REMOVE4res) XdrUnionTagName() string { return "Status" } func (u *REMOVE4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_REMOVE4resok(u.Resok4()) default: return nil } } func (u *REMOVE4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_REMOVE4res = *REMOVE4res func (v *REMOVE4res) XdrPointer() interface{} { return v } func (REMOVE4res) XdrTypeName() string { return "REMOVE4res" } func (v REMOVE4res) XdrValue() interface{} { return v } func (v *REMOVE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *REMOVE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_REMOVE4resok(u.Resok4())) return default: return } } func XDR_REMOVE4res(v *REMOVE4res) *REMOVE4res { return v} type XdrType_RENAME4args = *RENAME4args func (v *RENAME4args) XdrPointer() interface{} { return v } func (RENAME4args) XdrTypeName() string { return "RENAME4args" } func (v RENAME4args) XdrValue() interface{} { return v } func (v *RENAME4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RENAME4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%soldname", name), XDR_Component4(&v.Oldname)) x.Marshal(x.Sprintf("%snewname", name), XDR_Component4(&v.Newname)) } func XDR_RENAME4args(v *RENAME4args) *RENAME4args { return v } type XdrType_RENAME4resok = *RENAME4resok func (v *RENAME4resok) XdrPointer() interface{} { return v } func (RENAME4resok) XdrTypeName() string { return "RENAME4resok" } func (v RENAME4resok) XdrValue() interface{} { return v } func (v *RENAME4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RENAME4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssource_cinfo", name), XDR_Change_info4(&v.Source_cinfo)) x.Marshal(x.Sprintf("%starget_cinfo", name), XDR_Change_info4(&v.Target_cinfo)) } func XDR_RENAME4resok(v *RENAME4resok) *RENAME4resok { return v } func (u *RENAME4res) Resok4() *RENAME4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*RENAME4resok); ok { return v } else { var zero RENAME4resok u.U = &zero return &zero } default: XdrPanic("RENAME4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u RENAME4res) XdrValid() bool { return true } func (u *RENAME4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *RENAME4res) XdrUnionTagName() string { return "Status" } func (u *RENAME4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_RENAME4resok(u.Resok4()) default: return nil } } func (u *RENAME4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_RENAME4res = *RENAME4res func (v *RENAME4res) XdrPointer() interface{} { return v } func (RENAME4res) XdrTypeName() string { return "RENAME4res" } func (v RENAME4res) XdrValue() interface{} { return v } func (v *RENAME4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *RENAME4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_RENAME4resok(u.Resok4())) return default: return } } func XDR_RENAME4res(v *RENAME4res) *RENAME4res { return v} type XdrType_RENEW4args = *RENEW4args func (v *RENEW4args) XdrPointer() interface{} { return v } func (RENEW4args) XdrTypeName() string { return "RENEW4args" } func (v RENEW4args) XdrValue() interface{} { return v } func (v *RENEW4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RENEW4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) } func XDR_RENEW4args(v *RENEW4args) *RENEW4args { return v } type XdrType_RENEW4res = *RENEW4res func (v *RENEW4res) XdrPointer() interface{} { return v } func (RENEW4res) XdrTypeName() string { return "RENEW4res" } func (v RENEW4res) XdrValue() interface{} { return v } func (v *RENEW4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RENEW4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_RENEW4res(v *RENEW4res) *RENEW4res { return v } type XdrType_RESTOREFH4res = *RESTOREFH4res func (v *RESTOREFH4res) XdrPointer() interface{} { return v } func (RESTOREFH4res) XdrTypeName() string { return "RESTOREFH4res" } func (v RESTOREFH4res) XdrValue() interface{} { return v } func (v *RESTOREFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RESTOREFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_RESTOREFH4res(v *RESTOREFH4res) *RESTOREFH4res { return v } type XdrType_SAVEFH4res = *SAVEFH4res func (v *SAVEFH4res) XdrPointer() interface{} { return v } func (SAVEFH4res) XdrTypeName() string { return "SAVEFH4res" } func (v SAVEFH4res) XdrValue() interface{} { return v } func (v *SAVEFH4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SAVEFH4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_SAVEFH4res(v *SAVEFH4res) *SAVEFH4res { return v } type XdrType_SECINFO4args = *SECINFO4args func (v *SECINFO4args) XdrPointer() interface{} { return v } func (SECINFO4args) XdrTypeName() string { return "SECINFO4args" } func (v SECINFO4args) XdrValue() interface{} { return v } func (v *SECINFO4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SECINFO4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sname", name), XDR_Component4(&v.Name)) } func XDR_SECINFO4args(v *SECINFO4args) *SECINFO4args { return v } var _XdrNames_Rpc_gss_svc_t = map[int32]string{ int32(RPC_GSS_SVC_NONE): "RPC_GSS_SVC_NONE", int32(RPC_GSS_SVC_INTEGRITY): "RPC_GSS_SVC_INTEGRITY", int32(RPC_GSS_SVC_PRIVACY): "RPC_GSS_SVC_PRIVACY", } var _XdrValues_Rpc_gss_svc_t = map[string]int32{ "RPC_GSS_SVC_NONE": int32(RPC_GSS_SVC_NONE), "RPC_GSS_SVC_INTEGRITY": int32(RPC_GSS_SVC_INTEGRITY), "RPC_GSS_SVC_PRIVACY": int32(RPC_GSS_SVC_PRIVACY), } func (Rpc_gss_svc_t) XdrEnumNames() map[int32]string { return _XdrNames_Rpc_gss_svc_t } func (v Rpc_gss_svc_t) String() string { if s, ok := _XdrNames_Rpc_gss_svc_t[int32(v)]; ok { return s } return fmt.Sprintf("Rpc_gss_svc_t#%d", v) } func (v *Rpc_gss_svc_t) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Rpc_gss_svc_t[stok]; ok { *v = Rpc_gss_svc_t(val) return nil } else if stok == "Rpc_gss_svc_t" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Rpc_gss_svc_t.", stok)) } } func (v Rpc_gss_svc_t) GetU32() uint32 { return uint32(v) } func (v *Rpc_gss_svc_t) SetU32(n uint32) { *v = Rpc_gss_svc_t(n) } func (v *Rpc_gss_svc_t) XdrPointer() interface{} { return v } func (Rpc_gss_svc_t) XdrTypeName() string { return "Rpc_gss_svc_t" } func (v Rpc_gss_svc_t) XdrValue() interface{} { return v } func (v *Rpc_gss_svc_t) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Rpc_gss_svc_t = *Rpc_gss_svc_t func XDR_Rpc_gss_svc_t(v *Rpc_gss_svc_t) *Rpc_gss_svc_t { return v } func (v *Rpc_gss_svc_t) XdrInitialize() { switch Rpc_gss_svc_t(0) { case RPC_GSS_SVC_NONE, RPC_GSS_SVC_INTEGRITY, RPC_GSS_SVC_PRIVACY: default: if *v == Rpc_gss_svc_t(0) { *v = RPC_GSS_SVC_NONE } } } type XdrType_Rpcsec_gss_info = *Rpcsec_gss_info func (v *Rpcsec_gss_info) XdrPointer() interface{} { return v } func (Rpcsec_gss_info) XdrTypeName() string { return "Rpcsec_gss_info" } func (v Rpcsec_gss_info) XdrValue() interface{} { return v } func (v *Rpcsec_gss_info) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Rpcsec_gss_info) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%soid", name), XDR_Sec_oid4(&v.Oid)) x.Marshal(x.Sprintf("%sqop", name), XDR_Qop4(&v.Qop)) x.Marshal(x.Sprintf("%sservice", name), XDR_Rpc_gss_svc_t(&v.Service)) } func XDR_Rpcsec_gss_info(v *Rpcsec_gss_info) *Rpcsec_gss_info { return v } func (u *Secinfo4) Flavor_info() *Rpcsec_gss_info { switch u.Flavor { case RPCSEC_GSS: if v, ok := u.U.(*Rpcsec_gss_info); ok { return v } else { var zero Rpcsec_gss_info u.U = &zero return &zero } default: XdrPanic("Secinfo4.Flavor_info accessed when Flavor == %v", u.Flavor) return nil } } func (u Secinfo4) XdrValid() bool { return true } func (u *Secinfo4) XdrUnionTag() XdrNum32 { return XDR_Uint32_t(&u.Flavor) } func (u *Secinfo4) XdrUnionTagName() string { return "Flavor" } func (u *Secinfo4) XdrUnionBody() XdrType { switch u.Flavor { case RPCSEC_GSS: return XDR_Rpcsec_gss_info(u.Flavor_info()) default: return nil } } func (u *Secinfo4) XdrUnionBodyName() string { switch u.Flavor { case RPCSEC_GSS: return "Flavor_info" default: return "" } } type XdrType_Secinfo4 = *Secinfo4 func (v *Secinfo4) XdrPointer() interface{} { return v } func (Secinfo4) XdrTypeName() string { return "Secinfo4" } func (v Secinfo4) XdrValue() interface{} { return v } func (v *Secinfo4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Secinfo4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Uint32_t(&u.Flavor).XdrMarshal(x, x.Sprintf("%sflavor", name)) switch u.Flavor { case RPCSEC_GSS: x.Marshal(x.Sprintf("%sflavor_info", name), XDR_Rpcsec_gss_info(u.Flavor_info())) return default: return } } func XDR_Secinfo4(v *Secinfo4) *Secinfo4 { return v} type _XdrVec_unbounded_Secinfo4 []Secinfo4 func (_XdrVec_unbounded_Secinfo4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Secinfo4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Secinfo4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Secinfo4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Secinfo4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Secinfo4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Secinfo4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Secinfo4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Secinfo4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Secinfo4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Secinfo4) XdrTypeName() string { return "Secinfo4<>" } func (v *_XdrVec_unbounded_Secinfo4) XdrPointer() interface{} { return (*[]Secinfo4)(v) } func (v _XdrVec_unbounded_Secinfo4) XdrValue() interface{} { return ([]Secinfo4)(v) } func (v *_XdrVec_unbounded_Secinfo4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_SECINFO4resok struct { *_XdrVec_unbounded_Secinfo4 } func XDR_SECINFO4resok(v *SECINFO4resok) XdrType_SECINFO4resok { return XdrType_SECINFO4resok{(*_XdrVec_unbounded_Secinfo4)(v)} } func (XdrType_SECINFO4resok) XdrTypeName() string { return "SECINFO4resok" } func (v XdrType_SECINFO4resok) XdrUnwrap() XdrType { return v._XdrVec_unbounded_Secinfo4 } func (u *SECINFO4res) Resok4() *SECINFO4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*SECINFO4resok); ok { return v } else { var zero SECINFO4resok u.U = &zero return &zero } default: XdrPanic("SECINFO4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u SECINFO4res) XdrValid() bool { return true } func (u *SECINFO4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *SECINFO4res) XdrUnionTagName() string { return "Status" } func (u *SECINFO4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_SECINFO4resok(u.Resok4()) default: return nil } } func (u *SECINFO4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_SECINFO4res = *SECINFO4res func (v *SECINFO4res) XdrPointer() interface{} { return v } func (SECINFO4res) XdrTypeName() string { return "SECINFO4res" } func (v SECINFO4res) XdrValue() interface{} { return v } func (v *SECINFO4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *SECINFO4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_SECINFO4resok(u.Resok4())) return default: return } } func XDR_SECINFO4res(v *SECINFO4res) *SECINFO4res { return v} type XdrType_SETATTR4args = *SETATTR4args func (v *SETATTR4args) XdrPointer() interface{} { return v } func (SETATTR4args) XdrTypeName() string { return "SETATTR4args" } func (v SETATTR4args) XdrValue() interface{} { return v } func (v *SETATTR4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETATTR4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%sobj_attributes", name), XDR_Fattr4(&v.Obj_attributes)) } func XDR_SETATTR4args(v *SETATTR4args) *SETATTR4args { return v } type XdrType_SETATTR4res = *SETATTR4res func (v *SETATTR4res) XdrPointer() interface{} { return v } func (SETATTR4res) XdrTypeName() string { return "SETATTR4res" } func (v SETATTR4res) XdrValue() interface{} { return v } func (v *SETATTR4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETATTR4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) x.Marshal(x.Sprintf("%sattrsset", name), XDR_Bitmap4(&v.Attrsset)) } func XDR_SETATTR4res(v *SETATTR4res) *SETATTR4res { return v } type XdrType_SETCLIENTID4args = *SETCLIENTID4args func (v *SETCLIENTID4args) XdrPointer() interface{} { return v } func (SETCLIENTID4args) XdrTypeName() string { return "SETCLIENTID4args" } func (v SETCLIENTID4args) XdrValue() interface{} { return v } func (v *SETCLIENTID4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETCLIENTID4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclient", name), XDR_Nfs_client_id4(&v.Client)) x.Marshal(x.Sprintf("%scallback", name), XDR_Cb_client4(&v.Callback)) x.Marshal(x.Sprintf("%scallback_ident", name), XDR_Uint32_t(&v.Callback_ident)) } func XDR_SETCLIENTID4args(v *SETCLIENTID4args) *SETCLIENTID4args { return v } type XdrType_SETCLIENTID4resok = *SETCLIENTID4resok func (v *SETCLIENTID4resok) XdrPointer() interface{} { return v } func (SETCLIENTID4resok) XdrTypeName() string { return "SETCLIENTID4resok" } func (v SETCLIENTID4resok) XdrValue() interface{} { return v } func (v *SETCLIENTID4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETCLIENTID4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) x.Marshal(x.Sprintf("%ssetclientid_confirm", name), XDR_Verifier4(&v.Setclientid_confirm)) } func XDR_SETCLIENTID4resok(v *SETCLIENTID4resok) *SETCLIENTID4resok { return v } func (u *SETCLIENTID4res) Resok4() *SETCLIENTID4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*SETCLIENTID4resok); ok { return v } else { var zero SETCLIENTID4resok u.U = &zero return &zero } default: XdrPanic("SETCLIENTID4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u *SETCLIENTID4res) Client_using() *Clientaddr4 { switch u.Status { case NFS4ERR_CLID_INUSE: if v, ok := u.U.(*Clientaddr4); ok { return v } else { var zero Clientaddr4 u.U = &zero return &zero } default: XdrPanic("SETCLIENTID4res.Client_using accessed when Status == %v", u.Status) return nil } } func (u SETCLIENTID4res) XdrValid() bool { return true } func (u *SETCLIENTID4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *SETCLIENTID4res) XdrUnionTagName() string { return "Status" } func (u *SETCLIENTID4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_SETCLIENTID4resok(u.Resok4()) case NFS4ERR_CLID_INUSE: return XDR_Clientaddr4(u.Client_using()) default: return nil } } func (u *SETCLIENTID4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" case NFS4ERR_CLID_INUSE: return "Client_using" default: return "" } } type XdrType_SETCLIENTID4res = *SETCLIENTID4res func (v *SETCLIENTID4res) XdrPointer() interface{} { return v } func (SETCLIENTID4res) XdrTypeName() string { return "SETCLIENTID4res" } func (v SETCLIENTID4res) XdrValue() interface{} { return v } func (v *SETCLIENTID4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *SETCLIENTID4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_SETCLIENTID4resok(u.Resok4())) return case NFS4ERR_CLID_INUSE: x.Marshal(x.Sprintf("%sclient_using", name), XDR_Clientaddr4(u.Client_using())) return default: return } } func XDR_SETCLIENTID4res(v *SETCLIENTID4res) *SETCLIENTID4res { return v} type XdrType_SETCLIENTID_CONFIRM4args = *SETCLIENTID_CONFIRM4args func (v *SETCLIENTID_CONFIRM4args) XdrPointer() interface{} { return v } func (SETCLIENTID_CONFIRM4args) XdrTypeName() string { return "SETCLIENTID_CONFIRM4args" } func (v SETCLIENTID_CONFIRM4args) XdrValue() interface{} { return v } func (v *SETCLIENTID_CONFIRM4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETCLIENTID_CONFIRM4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sclientid", name), XDR_Clientid4(&v.Clientid)) x.Marshal(x.Sprintf("%ssetclientid_confirm", name), XDR_Verifier4(&v.Setclientid_confirm)) } func XDR_SETCLIENTID_CONFIRM4args(v *SETCLIENTID_CONFIRM4args) *SETCLIENTID_CONFIRM4args { return v } type XdrType_SETCLIENTID_CONFIRM4res = *SETCLIENTID_CONFIRM4res func (v *SETCLIENTID_CONFIRM4res) XdrPointer() interface{} { return v } func (SETCLIENTID_CONFIRM4res) XdrTypeName() string { return "SETCLIENTID_CONFIRM4res" } func (v SETCLIENTID_CONFIRM4res) XdrValue() interface{} { return v } func (v *SETCLIENTID_CONFIRM4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SETCLIENTID_CONFIRM4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_SETCLIENTID_CONFIRM4res(v *SETCLIENTID_CONFIRM4res) *SETCLIENTID_CONFIRM4res { return v } type XdrType_VERIFY4args = *VERIFY4args func (v *VERIFY4args) XdrPointer() interface{} { return v } func (VERIFY4args) XdrTypeName() string { return "VERIFY4args" } func (v VERIFY4args) XdrValue() interface{} { return v } func (v *VERIFY4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *VERIFY4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobj_attributes", name), XDR_Fattr4(&v.Obj_attributes)) } func XDR_VERIFY4args(v *VERIFY4args) *VERIFY4args { return v } type XdrType_VERIFY4res = *VERIFY4res func (v *VERIFY4res) XdrPointer() interface{} { return v } func (VERIFY4res) XdrTypeName() string { return "VERIFY4res" } func (v VERIFY4res) XdrValue() interface{} { return v } func (v *VERIFY4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *VERIFY4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_VERIFY4res(v *VERIFY4res) *VERIFY4res { return v } var _XdrNames_Stable_how4 = map[int32]string{ int32(UNSTABLE4): "UNSTABLE4", int32(DATA_SYNC4): "DATA_SYNC4", int32(FILE_SYNC4): "FILE_SYNC4", } var _XdrValues_Stable_how4 = map[string]int32{ "UNSTABLE4": int32(UNSTABLE4), "DATA_SYNC4": int32(DATA_SYNC4), "FILE_SYNC4": int32(FILE_SYNC4), } func (Stable_how4) XdrEnumNames() map[int32]string { return _XdrNames_Stable_how4 } func (v Stable_how4) String() string { if s, ok := _XdrNames_Stable_how4[int32(v)]; ok { return s } return fmt.Sprintf("Stable_how4#%d", v) } func (v *Stable_how4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Stable_how4[stok]; ok { *v = Stable_how4(val) return nil } else if stok == "Stable_how4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Stable_how4.", stok)) } } func (v Stable_how4) GetU32() uint32 { return uint32(v) } func (v *Stable_how4) SetU32(n uint32) { *v = Stable_how4(n) } func (v *Stable_how4) XdrPointer() interface{} { return v } func (Stable_how4) XdrTypeName() string { return "Stable_how4" } func (v Stable_how4) XdrValue() interface{} { return v } func (v *Stable_how4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Stable_how4 = *Stable_how4 func XDR_Stable_how4(v *Stable_how4) *Stable_how4 { return v } type XdrType_WRITE4args = *WRITE4args func (v *WRITE4args) XdrPointer() interface{} { return v } func (WRITE4args) XdrTypeName() string { return "WRITE4args" } func (v WRITE4args) XdrValue() interface{} { return v } func (v *WRITE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *WRITE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%soffset", name), XDR_Offset4(&v.Offset)) x.Marshal(x.Sprintf("%sstable", name), XDR_Stable_how4(&v.Stable)) x.Marshal(x.Sprintf("%sdata", name), XdrVecOpaque{&v.Data, 0xffffffff}) } func XDR_WRITE4args(v *WRITE4args) *WRITE4args { return v } type XdrType_WRITE4resok = *WRITE4resok func (v *WRITE4resok) XdrPointer() interface{} { return v } func (WRITE4resok) XdrTypeName() string { return "WRITE4resok" } func (v WRITE4resok) XdrValue() interface{} { return v } func (v *WRITE4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *WRITE4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scount", name), XDR_Count4(&v.Count)) x.Marshal(x.Sprintf("%scommitted", name), XDR_Stable_how4(&v.Committed)) x.Marshal(x.Sprintf("%swriteverf", name), XDR_Verifier4(&v.Writeverf)) } func XDR_WRITE4resok(v *WRITE4resok) *WRITE4resok { return v } func (u *WRITE4res) Resok4() *WRITE4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*WRITE4resok); ok { return v } else { var zero WRITE4resok u.U = &zero return &zero } default: XdrPanic("WRITE4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u WRITE4res) XdrValid() bool { return true } func (u *WRITE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *WRITE4res) XdrUnionTagName() string { return "Status" } func (u *WRITE4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_WRITE4resok(u.Resok4()) default: return nil } } func (u *WRITE4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_WRITE4res = *WRITE4res func (v *WRITE4res) XdrPointer() interface{} { return v } func (WRITE4res) XdrTypeName() string { return "WRITE4res" } func (v WRITE4res) XdrValue() interface{} { return v } func (v *WRITE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *WRITE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_WRITE4resok(u.Resok4())) return default: return } } func XDR_WRITE4res(v *WRITE4res) *WRITE4res { return v} type XdrType_RELEASE_LOCKOWNER4args = *RELEASE_LOCKOWNER4args func (v *RELEASE_LOCKOWNER4args) XdrPointer() interface{} { return v } func (RELEASE_LOCKOWNER4args) XdrTypeName() string { return "RELEASE_LOCKOWNER4args" } func (v RELEASE_LOCKOWNER4args) XdrValue() interface{} { return v } func (v *RELEASE_LOCKOWNER4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RELEASE_LOCKOWNER4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slock_owner", name), XDR_Lock_owner4(&v.Lock_owner)) } func XDR_RELEASE_LOCKOWNER4args(v *RELEASE_LOCKOWNER4args) *RELEASE_LOCKOWNER4args { return v } type XdrType_RELEASE_LOCKOWNER4res = *RELEASE_LOCKOWNER4res func (v *RELEASE_LOCKOWNER4res) XdrPointer() interface{} { return v } func (RELEASE_LOCKOWNER4res) XdrTypeName() string { return "RELEASE_LOCKOWNER4res" } func (v RELEASE_LOCKOWNER4res) XdrValue() interface{} { return v } func (v *RELEASE_LOCKOWNER4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RELEASE_LOCKOWNER4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_RELEASE_LOCKOWNER4res(v *RELEASE_LOCKOWNER4res) *RELEASE_LOCKOWNER4res { return v } var _XdrTags_Callback_sec_parms4 = map[int32]bool{ XdrToI32(AUTH_NONE): true, XdrToI32(AUTH_SYS): true, } func (_ Callback_sec_parms4) XdrValidTags() map[int32]bool { return _XdrTags_Callback_sec_parms4 } /* RFC 1831 */ func (u *Callback_sec_parms4) Cbsp_sys_cred() *Authsys_parms { switch Auth_flavor(u.Cb_secflavor) { case AUTH_SYS: if v, ok := u.U.(*Authsys_parms); ok { return v } else { var zero Authsys_parms u.U = &zero return &zero } default: XdrPanic("Callback_sec_parms4.Cbsp_sys_cred accessed when Cb_secflavor == %v", u.Cb_secflavor) return nil } } func (u Callback_sec_parms4) XdrValid() bool { switch Auth_flavor(u.Cb_secflavor) { case AUTH_NONE,AUTH_SYS: return true } return false } func (u *Callback_sec_parms4) XdrUnionTag() XdrNum32 { return XDR_Uint32_t(&u.Cb_secflavor) } func (u *Callback_sec_parms4) XdrUnionTagName() string { return "Cb_secflavor" } func (u *Callback_sec_parms4) XdrUnionBody() XdrType { switch Auth_flavor(u.Cb_secflavor) { case AUTH_NONE: return nil case AUTH_SYS: return XDR_Authsys_parms(u.Cbsp_sys_cred()) } return nil } func (u *Callback_sec_parms4) XdrUnionBodyName() string { switch Auth_flavor(u.Cb_secflavor) { case AUTH_NONE: return "" case AUTH_SYS: return "Cbsp_sys_cred" } return "" } type XdrType_Callback_sec_parms4 = *Callback_sec_parms4 func (v *Callback_sec_parms4) XdrPointer() interface{} { return v } func (Callback_sec_parms4) XdrTypeName() string { return "Callback_sec_parms4" } func (v Callback_sec_parms4) XdrValue() interface{} { return v } func (v *Callback_sec_parms4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Callback_sec_parms4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Uint32_t(&u.Cb_secflavor).XdrMarshal(x, x.Sprintf("%scb_secflavor", name)) switch Auth_flavor(u.Cb_secflavor) { case AUTH_NONE: return case AUTH_SYS: x.Marshal(x.Sprintf("%scbsp_sys_cred", name), XDR_Authsys_parms(u.Cbsp_sys_cred())) return } XdrPanic("invalid Cb_secflavor (%v) in Callback_sec_parms4", u.Cb_secflavor) } func (v *Callback_sec_parms4) XdrInitialize() { var zero Uint32_t switch Auth_flavor(zero) { case AUTH_NONE, AUTH_SYS: default: if v.Cb_secflavor == zero { v.Cb_secflavor = uint32(AUTH_NONE) } } } func XDR_Callback_sec_parms4(v *Callback_sec_parms4) *Callback_sec_parms4 { return v} type _XdrVec_1_Uint32_t []Uint32_t func (_XdrVec_1_Uint32_t) XdrBound() uint32 { const bound uint32 = 1 // Force error if not const or doesn't fit return bound } func (_XdrVec_1_Uint32_t) XdrCheckLen(length uint32) { if length > uint32(1) { XdrPanic("_XdrVec_1_Uint32_t length %d exceeds bound 1", length) } else if int(length) < 0 { XdrPanic("_XdrVec_1_Uint32_t length %d exceeds max int", length) } } func (v _XdrVec_1_Uint32_t) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_1_Uint32_t) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(1); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Uint32_t, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_1_Uint32_t) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Uint32_t(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_1_Uint32_t) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 1 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_1_Uint32_t) XdrTypeName() string { return "Uint32_t<>" } func (v *_XdrVec_1_Uint32_t) XdrPointer() interface{} { return (*[]Uint32_t)(v) } func (v _XdrVec_1_Uint32_t) XdrValue() interface{} { return ([]Uint32_t)(v) } func (v *_XdrVec_1_Uint32_t) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Channel_attrs4 = *Channel_attrs4 func (v *Channel_attrs4) XdrPointer() interface{} { return v } func (Channel_attrs4) XdrTypeName() string { return "Channel_attrs4" } func (v Channel_attrs4) XdrValue() interface{} { return v } func (v *Channel_attrs4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Channel_attrs4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sca_headerpadsize", name), XDR_Count4(&v.Ca_headerpadsize)) x.Marshal(x.Sprintf("%sca_maxrequestsize", name), XDR_Count4(&v.Ca_maxrequestsize)) x.Marshal(x.Sprintf("%sca_maxresponsesize", name), XDR_Count4(&v.Ca_maxresponsesize)) x.Marshal(x.Sprintf("%sca_maxresponsesize_cached", name), XDR_Count4(&v.Ca_maxresponsesize_cached)) x.Marshal(x.Sprintf("%sca_maxoperations", name), XDR_Count4(&v.Ca_maxoperations)) x.Marshal(x.Sprintf("%sca_maxrequests", name), XDR_Count4(&v.Ca_maxrequests)) x.Marshal(x.Sprintf("%sca_rdma_ird", name), (*_XdrVec_1_Uint32_t)(&v.Ca_rdma_ird)) } func XDR_Channel_attrs4(v *Channel_attrs4) *Channel_attrs4 { return v } type _XdrVec_unbounded_Callback_sec_parms4 []Callback_sec_parms4 func (_XdrVec_unbounded_Callback_sec_parms4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Callback_sec_parms4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Callback_sec_parms4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Callback_sec_parms4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Callback_sec_parms4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Callback_sec_parms4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Callback_sec_parms4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Callback_sec_parms4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Callback_sec_parms4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Callback_sec_parms4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Callback_sec_parms4) XdrTypeName() string { return "Callback_sec_parms4<>" } func (v *_XdrVec_unbounded_Callback_sec_parms4) XdrPointer() interface{} { return (*[]Callback_sec_parms4)(v) } func (v _XdrVec_unbounded_Callback_sec_parms4) XdrValue() interface{} { return ([]Callback_sec_parms4)(v) } func (v *_XdrVec_unbounded_Callback_sec_parms4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_CREATE_SESSION4args = *CREATE_SESSION4args func (v *CREATE_SESSION4args) XdrPointer() interface{} { return v } func (CREATE_SESSION4args) XdrTypeName() string { return "CREATE_SESSION4args" } func (v CREATE_SESSION4args) XdrValue() interface{} { return v } func (v *CREATE_SESSION4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CREATE_SESSION4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scsa_clientid", name), XDR_Clientid4(&v.Csa_clientid)) x.Marshal(x.Sprintf("%scsa_sequence", name), XDR_Sequenceid4(&v.Csa_sequence)) x.Marshal(x.Sprintf("%scsa_flags", name), XDR_Uint32_t(&v.Csa_flags)) x.Marshal(x.Sprintf("%scsa_fore_chan_attrs", name), XDR_Channel_attrs4(&v.Csa_fore_chan_attrs)) x.Marshal(x.Sprintf("%scsa_back_chan_attrs", name), XDR_Channel_attrs4(&v.Csa_back_chan_attrs)) x.Marshal(x.Sprintf("%scsa_cb_program", name), XDR_Uint32_t(&v.Csa_cb_program)) x.Marshal(x.Sprintf("%scsa_sec_parms", name), (*_XdrVec_unbounded_Callback_sec_parms4)(&v.Csa_sec_parms)) } func XDR_CREATE_SESSION4args(v *CREATE_SESSION4args) *CREATE_SESSION4args { return v } type XdrType_CREATE_SESSION4resok = *CREATE_SESSION4resok func (v *CREATE_SESSION4resok) XdrPointer() interface{} { return v } func (CREATE_SESSION4resok) XdrTypeName() string { return "CREATE_SESSION4resok" } func (v CREATE_SESSION4resok) XdrValue() interface{} { return v } func (v *CREATE_SESSION4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CREATE_SESSION4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%scsr_sessionid", name), XDR_Sessionid4(&v.Csr_sessionid)) x.Marshal(x.Sprintf("%scsr_sequence", name), XDR_Sequenceid4(&v.Csr_sequence)) x.Marshal(x.Sprintf("%scsr_flags", name), XDR_Uint32_t(&v.Csr_flags)) x.Marshal(x.Sprintf("%scsr_fore_chan_attrs", name), XDR_Channel_attrs4(&v.Csr_fore_chan_attrs)) x.Marshal(x.Sprintf("%scsr_back_chan_attrs", name), XDR_Channel_attrs4(&v.Csr_back_chan_attrs)) } func XDR_CREATE_SESSION4resok(v *CREATE_SESSION4resok) *CREATE_SESSION4resok { return v } func (u *CREATE_SESSION4res) Csr_resok4() *CREATE_SESSION4resok { switch u.Csr_status { case NFS4_OK: if v, ok := u.U.(*CREATE_SESSION4resok); ok { return v } else { var zero CREATE_SESSION4resok u.U = &zero return &zero } default: XdrPanic("CREATE_SESSION4res.Csr_resok4 accessed when Csr_status == %v", u.Csr_status) return nil } } func (u CREATE_SESSION4res) XdrValid() bool { return true } func (u *CREATE_SESSION4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Csr_status) } func (u *CREATE_SESSION4res) XdrUnionTagName() string { return "Csr_status" } func (u *CREATE_SESSION4res) XdrUnionBody() XdrType { switch u.Csr_status { case NFS4_OK: return XDR_CREATE_SESSION4resok(u.Csr_resok4()) default: return nil } } func (u *CREATE_SESSION4res) XdrUnionBodyName() string { switch u.Csr_status { case NFS4_OK: return "Csr_resok4" default: return "" } } type XdrType_CREATE_SESSION4res = *CREATE_SESSION4res func (v *CREATE_SESSION4res) XdrPointer() interface{} { return v } func (CREATE_SESSION4res) XdrTypeName() string { return "CREATE_SESSION4res" } func (v CREATE_SESSION4res) XdrValue() interface{} { return v } func (v *CREATE_SESSION4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *CREATE_SESSION4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Csr_status).XdrMarshal(x, x.Sprintf("%scsr_status", name)) switch u.Csr_status { case NFS4_OK: x.Marshal(x.Sprintf("%scsr_resok4", name), XDR_CREATE_SESSION4resok(u.Csr_resok4())) return default: return } } func XDR_CREATE_SESSION4res(v *CREATE_SESSION4res) *CREATE_SESSION4res { return v} type XdrType_DESTROY_SESSION4args = *DESTROY_SESSION4args func (v *DESTROY_SESSION4args) XdrPointer() interface{} { return v } func (DESTROY_SESSION4args) XdrTypeName() string { return "DESTROY_SESSION4args" } func (v DESTROY_SESSION4args) XdrValue() interface{} { return v } func (v *DESTROY_SESSION4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DESTROY_SESSION4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdsa_sessionid", name), XDR_Sessionid4(&v.Dsa_sessionid)) } func XDR_DESTROY_SESSION4args(v *DESTROY_SESSION4args) *DESTROY_SESSION4args { return v } type XdrType_DESTROY_SESSION4res = *DESTROY_SESSION4res func (v *DESTROY_SESSION4res) XdrPointer() interface{} { return v } func (DESTROY_SESSION4res) XdrTypeName() string { return "DESTROY_SESSION4res" } func (v DESTROY_SESSION4res) XdrValue() interface{} { return v } func (v *DESTROY_SESSION4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DESTROY_SESSION4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdsr_status", name), XDR_Nfsstat4(&v.Dsr_status)) } func XDR_DESTROY_SESSION4res(v *DESTROY_SESSION4res) *DESTROY_SESSION4res { return v } type XdrType_FREE_STATEID4args = *FREE_STATEID4args func (v *FREE_STATEID4args) XdrPointer() interface{} { return v } func (FREE_STATEID4args) XdrTypeName() string { return "FREE_STATEID4args" } func (v FREE_STATEID4args) XdrValue() interface{} { return v } func (v *FREE_STATEID4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *FREE_STATEID4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sfsa_stateid", name), XDR_Stateid4(&v.Fsa_stateid)) } func XDR_FREE_STATEID4args(v *FREE_STATEID4args) *FREE_STATEID4args { return v } type XdrType_FREE_STATEID4res = *FREE_STATEID4res func (v *FREE_STATEID4res) XdrPointer() interface{} { return v } func (FREE_STATEID4res) XdrTypeName() string { return "FREE_STATEID4res" } func (v FREE_STATEID4res) XdrValue() interface{} { return v } func (v *FREE_STATEID4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *FREE_STATEID4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sfsr_status", name), XDR_Nfsstat4(&v.Fsr_status)) } func XDR_FREE_STATEID4res(v *FREE_STATEID4res) *FREE_STATEID4res { return v } type XdrType_Attr_notice4 struct { XdrType_Nfstime4 } func XDR_Attr_notice4(v *Attr_notice4) *XdrType_Attr_notice4 { return &XdrType_Attr_notice4{XDR_Nfstime4(v)} } func (XdrType_Attr_notice4) XdrTypeName() string { return "Attr_notice4" } func (v XdrType_Attr_notice4) XdrUnwrap() XdrType { return v.XdrType_Nfstime4 } type XdrType_GET_DIR_DELEGATION4args = *GET_DIR_DELEGATION4args func (v *GET_DIR_DELEGATION4args) XdrPointer() interface{} { return v } func (GET_DIR_DELEGATION4args) XdrTypeName() string { return "GET_DIR_DELEGATION4args" } func (v GET_DIR_DELEGATION4args) XdrValue() interface{} { return v } func (v *GET_DIR_DELEGATION4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GET_DIR_DELEGATION4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgdda_signal_deleg_avail", name), XDR_bool(&v.Gdda_signal_deleg_avail)) x.Marshal(x.Sprintf("%sgdda_notification_types", name), XDR_Bitmap4(&v.Gdda_notification_types)) x.Marshal(x.Sprintf("%sgdda_child_attr_delay", name), XDR_Attr_notice4(&v.Gdda_child_attr_delay)) x.Marshal(x.Sprintf("%sgdda_dir_attr_delay", name), XDR_Attr_notice4(&v.Gdda_dir_attr_delay)) x.Marshal(x.Sprintf("%sgdda_child_attributes", name), XDR_Bitmap4(&v.Gdda_child_attributes)) x.Marshal(x.Sprintf("%sgdda_dir_attributes", name), XDR_Bitmap4(&v.Gdda_dir_attributes)) } func XDR_GET_DIR_DELEGATION4args(v *GET_DIR_DELEGATION4args) *GET_DIR_DELEGATION4args { return v } type XdrType_GET_DIR_DELEGATION4resok = *GET_DIR_DELEGATION4resok func (v *GET_DIR_DELEGATION4resok) XdrPointer() interface{} { return v } func (GET_DIR_DELEGATION4resok) XdrTypeName() string { return "GET_DIR_DELEGATION4resok" } func (v GET_DIR_DELEGATION4resok) XdrValue() interface{} { return v } func (v *GET_DIR_DELEGATION4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GET_DIR_DELEGATION4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgddr_cookieverf", name), XDR_Verifier4(&v.Gddr_cookieverf)) x.Marshal(x.Sprintf("%sgddr_stateid", name), XDR_Stateid4(&v.Gddr_stateid)) x.Marshal(x.Sprintf("%sgddr_notification", name), XDR_Bitmap4(&v.Gddr_notification)) x.Marshal(x.Sprintf("%sgddr_child_attributes", name), XDR_Bitmap4(&v.Gddr_child_attributes)) x.Marshal(x.Sprintf("%sgddr_dir_attributes", name), XDR_Bitmap4(&v.Gddr_dir_attributes)) } func XDR_GET_DIR_DELEGATION4resok(v *GET_DIR_DELEGATION4resok) *GET_DIR_DELEGATION4resok { return v } var _XdrNames_Gddrnf4_status = map[int32]string{ int32(GDD4_OK): "GDD4_OK", int32(GDD4_UNAVAIL): "GDD4_UNAVAIL", } var _XdrValues_Gddrnf4_status = map[string]int32{ "GDD4_OK": int32(GDD4_OK), "GDD4_UNAVAIL": int32(GDD4_UNAVAIL), } func (Gddrnf4_status) XdrEnumNames() map[int32]string { return _XdrNames_Gddrnf4_status } func (v Gddrnf4_status) String() string { if s, ok := _XdrNames_Gddrnf4_status[int32(v)]; ok { return s } return fmt.Sprintf("Gddrnf4_status#%d", v) } func (v *Gddrnf4_status) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Gddrnf4_status[stok]; ok { *v = Gddrnf4_status(val) return nil } else if stok == "Gddrnf4_status" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Gddrnf4_status.", stok)) } } func (v Gddrnf4_status) GetU32() uint32 { return uint32(v) } func (v *Gddrnf4_status) SetU32(n uint32) { *v = Gddrnf4_status(n) } func (v *Gddrnf4_status) XdrPointer() interface{} { return v } func (Gddrnf4_status) XdrTypeName() string { return "Gddrnf4_status" } func (v Gddrnf4_status) XdrValue() interface{} { return v } func (v *Gddrnf4_status) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Gddrnf4_status = *Gddrnf4_status func XDR_Gddrnf4_status(v *Gddrnf4_status) *Gddrnf4_status { return v } var _XdrTags_GET_DIR_DELEGATION4res_non_fatal = map[int32]bool{ XdrToI32(GDD4_OK): true, XdrToI32(GDD4_UNAVAIL): true, } func (_ GET_DIR_DELEGATION4res_non_fatal) XdrValidTags() map[int32]bool { return _XdrTags_GET_DIR_DELEGATION4res_non_fatal } func (u *GET_DIR_DELEGATION4res_non_fatal) Gddrnf_resok4() *GET_DIR_DELEGATION4resok { switch u.Gddrnf_status { case GDD4_OK: if v, ok := u.U.(*GET_DIR_DELEGATION4resok); ok { return v } else { var zero GET_DIR_DELEGATION4resok u.U = &zero return &zero } default: XdrPanic("GET_DIR_DELEGATION4res_non_fatal.Gddrnf_resok4 accessed when Gddrnf_status == %v", u.Gddrnf_status) return nil } } func (u *GET_DIR_DELEGATION4res_non_fatal) Gddrnf_will_signal_deleg_avail() *bool { switch u.Gddrnf_status { case GDD4_UNAVAIL: if v, ok := u.U.(*bool); ok { return v } else { var zero bool u.U = &zero return &zero } default: XdrPanic("GET_DIR_DELEGATION4res_non_fatal.Gddrnf_will_signal_deleg_avail accessed when Gddrnf_status == %v", u.Gddrnf_status) return nil } } func (u GET_DIR_DELEGATION4res_non_fatal) XdrValid() bool { switch u.Gddrnf_status { case GDD4_OK,GDD4_UNAVAIL: return true } return false } func (u *GET_DIR_DELEGATION4res_non_fatal) XdrUnionTag() XdrNum32 { return XDR_Gddrnf4_status(&u.Gddrnf_status) } func (u *GET_DIR_DELEGATION4res_non_fatal) XdrUnionTagName() string { return "Gddrnf_status" } func (u *GET_DIR_DELEGATION4res_non_fatal) XdrUnionBody() XdrType { switch u.Gddrnf_status { case GDD4_OK: return XDR_GET_DIR_DELEGATION4resok(u.Gddrnf_resok4()) case GDD4_UNAVAIL: return XDR_bool(u.Gddrnf_will_signal_deleg_avail()) } return nil } func (u *GET_DIR_DELEGATION4res_non_fatal) XdrUnionBodyName() string { switch u.Gddrnf_status { case GDD4_OK: return "Gddrnf_resok4" case GDD4_UNAVAIL: return "Gddrnf_will_signal_deleg_avail" } return "" } type XdrType_GET_DIR_DELEGATION4res_non_fatal = *GET_DIR_DELEGATION4res_non_fatal func (v *GET_DIR_DELEGATION4res_non_fatal) XdrPointer() interface{} { return v } func (GET_DIR_DELEGATION4res_non_fatal) XdrTypeName() string { return "GET_DIR_DELEGATION4res_non_fatal" } func (v GET_DIR_DELEGATION4res_non_fatal) XdrValue() interface{} { return v } func (v *GET_DIR_DELEGATION4res_non_fatal) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GET_DIR_DELEGATION4res_non_fatal) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Gddrnf4_status(&u.Gddrnf_status).XdrMarshal(x, x.Sprintf("%sgddrnf_status", name)) switch u.Gddrnf_status { case GDD4_OK: x.Marshal(x.Sprintf("%sgddrnf_resok4", name), XDR_GET_DIR_DELEGATION4resok(u.Gddrnf_resok4())) return case GDD4_UNAVAIL: x.Marshal(x.Sprintf("%sgddrnf_will_signal_deleg_avail", name), XDR_bool(u.Gddrnf_will_signal_deleg_avail())) return } XdrPanic("invalid Gddrnf_status (%v) in GET_DIR_DELEGATION4res_non_fatal", u.Gddrnf_status) } func XDR_GET_DIR_DELEGATION4res_non_fatal(v *GET_DIR_DELEGATION4res_non_fatal) *GET_DIR_DELEGATION4res_non_fatal { return v} func (u *GET_DIR_DELEGATION4res) Gddr_res_non_fatal4() *GET_DIR_DELEGATION4res_non_fatal { switch u.Gddr_status { case NFS4_OK: if v, ok := u.U.(*GET_DIR_DELEGATION4res_non_fatal); ok { return v } else { var zero GET_DIR_DELEGATION4res_non_fatal u.U = &zero return &zero } default: XdrPanic("GET_DIR_DELEGATION4res.Gddr_res_non_fatal4 accessed when Gddr_status == %v", u.Gddr_status) return nil } } func (u GET_DIR_DELEGATION4res) XdrValid() bool { return true } func (u *GET_DIR_DELEGATION4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Gddr_status) } func (u *GET_DIR_DELEGATION4res) XdrUnionTagName() string { return "Gddr_status" } func (u *GET_DIR_DELEGATION4res) XdrUnionBody() XdrType { switch u.Gddr_status { case NFS4_OK: return XDR_GET_DIR_DELEGATION4res_non_fatal(u.Gddr_res_non_fatal4()) default: return nil } } func (u *GET_DIR_DELEGATION4res) XdrUnionBodyName() string { switch u.Gddr_status { case NFS4_OK: return "Gddr_res_non_fatal4" default: return "" } } type XdrType_GET_DIR_DELEGATION4res = *GET_DIR_DELEGATION4res func (v *GET_DIR_DELEGATION4res) XdrPointer() interface{} { return v } func (GET_DIR_DELEGATION4res) XdrTypeName() string { return "GET_DIR_DELEGATION4res" } func (v GET_DIR_DELEGATION4res) XdrValue() interface{} { return v } func (v *GET_DIR_DELEGATION4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GET_DIR_DELEGATION4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Gddr_status).XdrMarshal(x, x.Sprintf("%sgddr_status", name)) switch u.Gddr_status { case NFS4_OK: x.Marshal(x.Sprintf("%sgddr_res_non_fatal4", name), XDR_GET_DIR_DELEGATION4res_non_fatal(u.Gddr_res_non_fatal4())) return default: return } } func XDR_GET_DIR_DELEGATION4res(v *GET_DIR_DELEGATION4res) *GET_DIR_DELEGATION4res { return v} type XdrType_GETDEVICEINFO4args = *GETDEVICEINFO4args func (v *GETDEVICEINFO4args) XdrPointer() interface{} { return v } func (GETDEVICEINFO4args) XdrTypeName() string { return "GETDEVICEINFO4args" } func (v GETDEVICEINFO4args) XdrValue() interface{} { return v } func (v *GETDEVICEINFO4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETDEVICEINFO4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgdia_device_id", name), XDR_Deviceid4(&v.Gdia_device_id)) x.Marshal(x.Sprintf("%sgdia_layout_type", name), XDR_Layouttype4(&v.Gdia_layout_type)) x.Marshal(x.Sprintf("%sgdia_maxcount", name), XDR_Count4(&v.Gdia_maxcount)) x.Marshal(x.Sprintf("%sgdia_notify_types", name), XDR_Bitmap4(&v.Gdia_notify_types)) } func XDR_GETDEVICEINFO4args(v *GETDEVICEINFO4args) *GETDEVICEINFO4args { return v } type XdrType_GETDEVICEINFO4resok = *GETDEVICEINFO4resok func (v *GETDEVICEINFO4resok) XdrPointer() interface{} { return v } func (GETDEVICEINFO4resok) XdrTypeName() string { return "GETDEVICEINFO4resok" } func (v GETDEVICEINFO4resok) XdrValue() interface{} { return v } func (v *GETDEVICEINFO4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETDEVICEINFO4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgdir_device_addr", name), XDR_Device_addr4(&v.Gdir_device_addr)) x.Marshal(x.Sprintf("%sgdir_notification", name), XDR_Bitmap4(&v.Gdir_notification)) } func XDR_GETDEVICEINFO4resok(v *GETDEVICEINFO4resok) *GETDEVICEINFO4resok { return v } func (u *GETDEVICEINFO4res) Gdir_resok4() *GETDEVICEINFO4resok { switch u.Gdir_status { case NFS4_OK: if v, ok := u.U.(*GETDEVICEINFO4resok); ok { return v } else { var zero GETDEVICEINFO4resok u.U = &zero return &zero } default: XdrPanic("GETDEVICEINFO4res.Gdir_resok4 accessed when Gdir_status == %v", u.Gdir_status) return nil } } func (u *GETDEVICEINFO4res) Gdir_mincount() *Count4 { switch u.Gdir_status { case NFS4ERR_TOOSMALL: if v, ok := u.U.(*Count4); ok { return v } else { var zero Count4 u.U = &zero return &zero } default: XdrPanic("GETDEVICEINFO4res.Gdir_mincount accessed when Gdir_status == %v", u.Gdir_status) return nil } } func (u GETDEVICEINFO4res) XdrValid() bool { return true } func (u *GETDEVICEINFO4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Gdir_status) } func (u *GETDEVICEINFO4res) XdrUnionTagName() string { return "Gdir_status" } func (u *GETDEVICEINFO4res) XdrUnionBody() XdrType { switch u.Gdir_status { case NFS4_OK: return XDR_GETDEVICEINFO4resok(u.Gdir_resok4()) case NFS4ERR_TOOSMALL: return XDR_Count4(u.Gdir_mincount()) default: return nil } } func (u *GETDEVICEINFO4res) XdrUnionBodyName() string { switch u.Gdir_status { case NFS4_OK: return "Gdir_resok4" case NFS4ERR_TOOSMALL: return "Gdir_mincount" default: return "" } } type XdrType_GETDEVICEINFO4res = *GETDEVICEINFO4res func (v *GETDEVICEINFO4res) XdrPointer() interface{} { return v } func (GETDEVICEINFO4res) XdrTypeName() string { return "GETDEVICEINFO4res" } func (v GETDEVICEINFO4res) XdrValue() interface{} { return v } func (v *GETDEVICEINFO4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GETDEVICEINFO4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Gdir_status).XdrMarshal(x, x.Sprintf("%sgdir_status", name)) switch u.Gdir_status { case NFS4_OK: x.Marshal(x.Sprintf("%sgdir_resok4", name), XDR_GETDEVICEINFO4resok(u.Gdir_resok4())) return case NFS4ERR_TOOSMALL: x.Marshal(x.Sprintf("%sgdir_mincount", name), XDR_Count4(u.Gdir_mincount())) return default: return } } func XDR_GETDEVICEINFO4res(v *GETDEVICEINFO4res) *GETDEVICEINFO4res { return v} type XdrType_GETDEVICELIST4args = *GETDEVICELIST4args func (v *GETDEVICELIST4args) XdrPointer() interface{} { return v } func (GETDEVICELIST4args) XdrTypeName() string { return "GETDEVICELIST4args" } func (v GETDEVICELIST4args) XdrValue() interface{} { return v } func (v *GETDEVICELIST4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETDEVICELIST4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgdla_layout_type", name), XDR_Layouttype4(&v.Gdla_layout_type)) x.Marshal(x.Sprintf("%sgdla_maxdevices", name), XDR_Count4(&v.Gdla_maxdevices)) x.Marshal(x.Sprintf("%sgdla_cookie", name), XDR_Nfs_cookie4(&v.Gdla_cookie)) x.Marshal(x.Sprintf("%sgdla_cookieverf", name), XDR_Verifier4(&v.Gdla_cookieverf)) } func XDR_GETDEVICELIST4args(v *GETDEVICELIST4args) *GETDEVICELIST4args { return v } type _XdrVec_unbounded_Deviceid4 []Deviceid4 func (_XdrVec_unbounded_Deviceid4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Deviceid4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Deviceid4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Deviceid4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Deviceid4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Deviceid4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Deviceid4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Deviceid4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Deviceid4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Deviceid4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Deviceid4) XdrTypeName() string { return "Deviceid4<>" } func (v *_XdrVec_unbounded_Deviceid4) XdrPointer() interface{} { return (*[]Deviceid4)(v) } func (v _XdrVec_unbounded_Deviceid4) XdrValue() interface{} { return ([]Deviceid4)(v) } func (v *_XdrVec_unbounded_Deviceid4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_GETDEVICELIST4resok = *GETDEVICELIST4resok func (v *GETDEVICELIST4resok) XdrPointer() interface{} { return v } func (GETDEVICELIST4resok) XdrTypeName() string { return "GETDEVICELIST4resok" } func (v GETDEVICELIST4resok) XdrValue() interface{} { return v } func (v *GETDEVICELIST4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *GETDEVICELIST4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sgdlr_cookie", name), XDR_Nfs_cookie4(&v.Gdlr_cookie)) x.Marshal(x.Sprintf("%sgdlr_cookieverf", name), XDR_Verifier4(&v.Gdlr_cookieverf)) x.Marshal(x.Sprintf("%sgdlr_deviceid_list", name), (*_XdrVec_unbounded_Deviceid4)(&v.Gdlr_deviceid_list)) x.Marshal(x.Sprintf("%sgdlr_eof", name), XDR_bool(&v.Gdlr_eof)) } func XDR_GETDEVICELIST4resok(v *GETDEVICELIST4resok) *GETDEVICELIST4resok { return v } func (u *GETDEVICELIST4res) Gdlr_resok4() *GETDEVICELIST4resok { switch u.Gdlr_status { case NFS4_OK: if v, ok := u.U.(*GETDEVICELIST4resok); ok { return v } else { var zero GETDEVICELIST4resok u.U = &zero return &zero } default: XdrPanic("GETDEVICELIST4res.Gdlr_resok4 accessed when Gdlr_status == %v", u.Gdlr_status) return nil } } func (u GETDEVICELIST4res) XdrValid() bool { return true } func (u *GETDEVICELIST4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Gdlr_status) } func (u *GETDEVICELIST4res) XdrUnionTagName() string { return "Gdlr_status" } func (u *GETDEVICELIST4res) XdrUnionBody() XdrType { switch u.Gdlr_status { case NFS4_OK: return XDR_GETDEVICELIST4resok(u.Gdlr_resok4()) default: return nil } } func (u *GETDEVICELIST4res) XdrUnionBodyName() string { switch u.Gdlr_status { case NFS4_OK: return "Gdlr_resok4" default: return "" } } type XdrType_GETDEVICELIST4res = *GETDEVICELIST4res func (v *GETDEVICELIST4res) XdrPointer() interface{} { return v } func (GETDEVICELIST4res) XdrTypeName() string { return "GETDEVICELIST4res" } func (v GETDEVICELIST4res) XdrValue() interface{} { return v } func (v *GETDEVICELIST4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *GETDEVICELIST4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Gdlr_status).XdrMarshal(x, x.Sprintf("%sgdlr_status", name)) switch u.Gdlr_status { case NFS4_OK: x.Marshal(x.Sprintf("%sgdlr_resok4", name), XDR_GETDEVICELIST4resok(u.Gdlr_resok4())) return default: return } } func XDR_GETDEVICELIST4res(v *GETDEVICELIST4res) *GETDEVICELIST4res { return v} var _XdrTags_Newtime4 = map[int32]bool{ XdrToI32(true): true, XdrToI32(false): true, } func (_ Newtime4) XdrValidTags() map[int32]bool { return _XdrTags_Newtime4 } func (u *Newtime4) Nt_time() *Nfstime4 { switch u.Nt_timechanged { case true: if v, ok := u.U.(*Nfstime4); ok { return v } else { var zero Nfstime4 u.U = &zero return &zero } default: XdrPanic("Newtime4.Nt_time accessed when Nt_timechanged == %v", u.Nt_timechanged) return nil } } func (u Newtime4) XdrValid() bool { switch u.Nt_timechanged { case true,false: return true } return false } func (u *Newtime4) XdrUnionTag() XdrNum32 { return XDR_bool(&u.Nt_timechanged) } func (u *Newtime4) XdrUnionTagName() string { return "Nt_timechanged" } func (u *Newtime4) XdrUnionBody() XdrType { switch u.Nt_timechanged { case true: return XDR_Nfstime4(u.Nt_time()) case false: return nil } return nil } func (u *Newtime4) XdrUnionBodyName() string { switch u.Nt_timechanged { case true: return "Nt_time" case false: return "" } return "" } type XdrType_Newtime4 = *Newtime4 func (v *Newtime4) XdrPointer() interface{} { return v } func (Newtime4) XdrTypeName() string { return "Newtime4" } func (v Newtime4) XdrValue() interface{} { return v } func (v *Newtime4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Newtime4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_bool(&u.Nt_timechanged).XdrMarshal(x, x.Sprintf("%snt_timechanged", name)) switch u.Nt_timechanged { case true: x.Marshal(x.Sprintf("%snt_time", name), XDR_Nfstime4(u.Nt_time())) return case false: return } XdrPanic("invalid Nt_timechanged (%v) in Newtime4", u.Nt_timechanged) } func XDR_Newtime4(v *Newtime4) *Newtime4 { return v} var _XdrTags_Newoffset4 = map[int32]bool{ XdrToI32(true): true, XdrToI32(false): true, } func (_ Newoffset4) XdrValidTags() map[int32]bool { return _XdrTags_Newoffset4 } func (u *Newoffset4) No_offset() *Offset4 { switch u.No_newoffset { case true: if v, ok := u.U.(*Offset4); ok { return v } else { var zero Offset4 u.U = &zero return &zero } default: XdrPanic("Newoffset4.No_offset accessed when No_newoffset == %v", u.No_newoffset) return nil } } func (u Newoffset4) XdrValid() bool { switch u.No_newoffset { case true,false: return true } return false } func (u *Newoffset4) XdrUnionTag() XdrNum32 { return XDR_bool(&u.No_newoffset) } func (u *Newoffset4) XdrUnionTagName() string { return "No_newoffset" } func (u *Newoffset4) XdrUnionBody() XdrType { switch u.No_newoffset { case true: return XDR_Offset4(u.No_offset()) case false: return nil } return nil } func (u *Newoffset4) XdrUnionBodyName() string { switch u.No_newoffset { case true: return "No_offset" case false: return "" } return "" } type XdrType_Newoffset4 = *Newoffset4 func (v *Newoffset4) XdrPointer() interface{} { return v } func (Newoffset4) XdrTypeName() string { return "Newoffset4" } func (v Newoffset4) XdrValue() interface{} { return v } func (v *Newoffset4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Newoffset4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_bool(&u.No_newoffset).XdrMarshal(x, x.Sprintf("%sno_newoffset", name)) switch u.No_newoffset { case true: x.Marshal(x.Sprintf("%sno_offset", name), XDR_Offset4(u.No_offset())) return case false: return } XdrPanic("invalid No_newoffset (%v) in Newoffset4", u.No_newoffset) } func XDR_Newoffset4(v *Newoffset4) *Newoffset4 { return v} type XdrType_LAYOUTCOMMIT4args = *LAYOUTCOMMIT4args func (v *LAYOUTCOMMIT4args) XdrPointer() interface{} { return v } func (LAYOUTCOMMIT4args) XdrTypeName() string { return "LAYOUTCOMMIT4args" } func (v LAYOUTCOMMIT4args) XdrValue() interface{} { return v } func (v *LAYOUTCOMMIT4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LAYOUTCOMMIT4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sloca_offset", name), XDR_Offset4(&v.Loca_offset)) x.Marshal(x.Sprintf("%sloca_length", name), XDR_Length4(&v.Loca_length)) x.Marshal(x.Sprintf("%sloca_reclaim", name), XDR_bool(&v.Loca_reclaim)) x.Marshal(x.Sprintf("%sloca_stateid", name), XDR_Stateid4(&v.Loca_stateid)) x.Marshal(x.Sprintf("%sloca_last_write_offset", name), XDR_Newoffset4(&v.Loca_last_write_offset)) x.Marshal(x.Sprintf("%sloca_time_modify", name), XDR_Newtime4(&v.Loca_time_modify)) x.Marshal(x.Sprintf("%sloca_layoutupdate", name), XDR_Layoutupdate4(&v.Loca_layoutupdate)) } func XDR_LAYOUTCOMMIT4args(v *LAYOUTCOMMIT4args) *LAYOUTCOMMIT4args { return v } var _XdrTags_Newsize4 = map[int32]bool{ XdrToI32(true): true, XdrToI32(false): true, } func (_ Newsize4) XdrValidTags() map[int32]bool { return _XdrTags_Newsize4 } func (u *Newsize4) Ns_size() *Length4 { switch u.Ns_sizechanged { case true: if v, ok := u.U.(*Length4); ok { return v } else { var zero Length4 u.U = &zero return &zero } default: XdrPanic("Newsize4.Ns_size accessed when Ns_sizechanged == %v", u.Ns_sizechanged) return nil } } func (u Newsize4) XdrValid() bool { switch u.Ns_sizechanged { case true,false: return true } return false } func (u *Newsize4) XdrUnionTag() XdrNum32 { return XDR_bool(&u.Ns_sizechanged) } func (u *Newsize4) XdrUnionTagName() string { return "Ns_sizechanged" } func (u *Newsize4) XdrUnionBody() XdrType { switch u.Ns_sizechanged { case true: return XDR_Length4(u.Ns_size()) case false: return nil } return nil } func (u *Newsize4) XdrUnionBodyName() string { switch u.Ns_sizechanged { case true: return "Ns_size" case false: return "" } return "" } type XdrType_Newsize4 = *Newsize4 func (v *Newsize4) XdrPointer() interface{} { return v } func (Newsize4) XdrTypeName() string { return "Newsize4" } func (v Newsize4) XdrValue() interface{} { return v } func (v *Newsize4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Newsize4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_bool(&u.Ns_sizechanged).XdrMarshal(x, x.Sprintf("%sns_sizechanged", name)) switch u.Ns_sizechanged { case true: x.Marshal(x.Sprintf("%sns_size", name), XDR_Length4(u.Ns_size())) return case false: return } XdrPanic("invalid Ns_sizechanged (%v) in Newsize4", u.Ns_sizechanged) } func XDR_Newsize4(v *Newsize4) *Newsize4 { return v} type XdrType_LAYOUTCOMMIT4resok = *LAYOUTCOMMIT4resok func (v *LAYOUTCOMMIT4resok) XdrPointer() interface{} { return v } func (LAYOUTCOMMIT4resok) XdrTypeName() string { return "LAYOUTCOMMIT4resok" } func (v LAYOUTCOMMIT4resok) XdrValue() interface{} { return v } func (v *LAYOUTCOMMIT4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LAYOUTCOMMIT4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slocr_newsize", name), XDR_Newsize4(&v.Locr_newsize)) } func XDR_LAYOUTCOMMIT4resok(v *LAYOUTCOMMIT4resok) *LAYOUTCOMMIT4resok { return v } func (u *LAYOUTCOMMIT4res) Locr_resok4() *LAYOUTCOMMIT4resok { switch u.Locr_status { case NFS4_OK: if v, ok := u.U.(*LAYOUTCOMMIT4resok); ok { return v } else { var zero LAYOUTCOMMIT4resok u.U = &zero return &zero } default: XdrPanic("LAYOUTCOMMIT4res.Locr_resok4 accessed when Locr_status == %v", u.Locr_status) return nil } } func (u LAYOUTCOMMIT4res) XdrValid() bool { return true } func (u *LAYOUTCOMMIT4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Locr_status) } func (u *LAYOUTCOMMIT4res) XdrUnionTagName() string { return "Locr_status" } func (u *LAYOUTCOMMIT4res) XdrUnionBody() XdrType { switch u.Locr_status { case NFS4_OK: return XDR_LAYOUTCOMMIT4resok(u.Locr_resok4()) default: return nil } } func (u *LAYOUTCOMMIT4res) XdrUnionBodyName() string { switch u.Locr_status { case NFS4_OK: return "Locr_resok4" default: return "" } } type XdrType_LAYOUTCOMMIT4res = *LAYOUTCOMMIT4res func (v *LAYOUTCOMMIT4res) XdrPointer() interface{} { return v } func (LAYOUTCOMMIT4res) XdrTypeName() string { return "LAYOUTCOMMIT4res" } func (v LAYOUTCOMMIT4res) XdrValue() interface{} { return v } func (v *LAYOUTCOMMIT4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LAYOUTCOMMIT4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Locr_status).XdrMarshal(x, x.Sprintf("%slocr_status", name)) switch u.Locr_status { case NFS4_OK: x.Marshal(x.Sprintf("%slocr_resok4", name), XDR_LAYOUTCOMMIT4resok(u.Locr_resok4())) return default: return } } func XDR_LAYOUTCOMMIT4res(v *LAYOUTCOMMIT4res) *LAYOUTCOMMIT4res { return v} type XdrType_LAYOUTGET4args = *LAYOUTGET4args func (v *LAYOUTGET4args) XdrPointer() interface{} { return v } func (LAYOUTGET4args) XdrTypeName() string { return "LAYOUTGET4args" } func (v LAYOUTGET4args) XdrValue() interface{} { return v } func (v *LAYOUTGET4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LAYOUTGET4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sloga_signal_layout_avail", name), XDR_bool(&v.Loga_signal_layout_avail)) x.Marshal(x.Sprintf("%sloga_layout_type", name), XDR_Layouttype4(&v.Loga_layout_type)) x.Marshal(x.Sprintf("%sloga_iomode", name), XDR_Layoutiomode4(&v.Loga_iomode)) x.Marshal(x.Sprintf("%sloga_offset", name), XDR_Offset4(&v.Loga_offset)) x.Marshal(x.Sprintf("%sloga_length", name), XDR_Length4(&v.Loga_length)) x.Marshal(x.Sprintf("%sloga_minlength", name), XDR_Length4(&v.Loga_minlength)) x.Marshal(x.Sprintf("%sloga_stateid", name), XDR_Stateid4(&v.Loga_stateid)) x.Marshal(x.Sprintf("%sloga_maxcount", name), XDR_Count4(&v.Loga_maxcount)) } func XDR_LAYOUTGET4args(v *LAYOUTGET4args) *LAYOUTGET4args { return v } type _XdrVec_unbounded_Layout4 []Layout4 func (_XdrVec_unbounded_Layout4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Layout4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Layout4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Layout4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Layout4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Layout4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Layout4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Layout4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Layout4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Layout4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Layout4) XdrTypeName() string { return "Layout4<>" } func (v *_XdrVec_unbounded_Layout4) XdrPointer() interface{} { return (*[]Layout4)(v) } func (v _XdrVec_unbounded_Layout4) XdrValue() interface{} { return ([]Layout4)(v) } func (v *_XdrVec_unbounded_Layout4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_LAYOUTGET4resok = *LAYOUTGET4resok func (v *LAYOUTGET4resok) XdrPointer() interface{} { return v } func (LAYOUTGET4resok) XdrTypeName() string { return "LAYOUTGET4resok" } func (v LAYOUTGET4resok) XdrValue() interface{} { return v } func (v *LAYOUTGET4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LAYOUTGET4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slogr_return_on_close", name), XDR_bool(&v.Logr_return_on_close)) x.Marshal(x.Sprintf("%slogr_stateid", name), XDR_Stateid4(&v.Logr_stateid)) x.Marshal(x.Sprintf("%slogr_layout", name), (*_XdrVec_unbounded_Layout4)(&v.Logr_layout)) } func XDR_LAYOUTGET4resok(v *LAYOUTGET4resok) *LAYOUTGET4resok { return v } func (u *LAYOUTGET4res) Logr_resok4() *LAYOUTGET4resok { switch u.Logr_status { case NFS4_OK: if v, ok := u.U.(*LAYOUTGET4resok); ok { return v } else { var zero LAYOUTGET4resok u.U = &zero return &zero } default: XdrPanic("LAYOUTGET4res.Logr_resok4 accessed when Logr_status == %v", u.Logr_status) return nil } } func (u *LAYOUTGET4res) Logr_will_signal_layout_avail() *bool { switch u.Logr_status { case NFS4ERR_LAYOUTTRYLATER: if v, ok := u.U.(*bool); ok { return v } else { var zero bool u.U = &zero return &zero } default: XdrPanic("LAYOUTGET4res.Logr_will_signal_layout_avail accessed when Logr_status == %v", u.Logr_status) return nil } } func (u LAYOUTGET4res) XdrValid() bool { return true } func (u *LAYOUTGET4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Logr_status) } func (u *LAYOUTGET4res) XdrUnionTagName() string { return "Logr_status" } func (u *LAYOUTGET4res) XdrUnionBody() XdrType { switch u.Logr_status { case NFS4_OK: return XDR_LAYOUTGET4resok(u.Logr_resok4()) case NFS4ERR_LAYOUTTRYLATER: return XDR_bool(u.Logr_will_signal_layout_avail()) default: return nil } } func (u *LAYOUTGET4res) XdrUnionBodyName() string { switch u.Logr_status { case NFS4_OK: return "Logr_resok4" case NFS4ERR_LAYOUTTRYLATER: return "Logr_will_signal_layout_avail" default: return "" } } type XdrType_LAYOUTGET4res = *LAYOUTGET4res func (v *LAYOUTGET4res) XdrPointer() interface{} { return v } func (LAYOUTGET4res) XdrTypeName() string { return "LAYOUTGET4res" } func (v LAYOUTGET4res) XdrValue() interface{} { return v } func (v *LAYOUTGET4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LAYOUTGET4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Logr_status).XdrMarshal(x, x.Sprintf("%slogr_status", name)) switch u.Logr_status { case NFS4_OK: x.Marshal(x.Sprintf("%slogr_resok4", name), XDR_LAYOUTGET4resok(u.Logr_resok4())) return case NFS4ERR_LAYOUTTRYLATER: x.Marshal(x.Sprintf("%slogr_will_signal_layout_avail", name), XDR_bool(u.Logr_will_signal_layout_avail())) return default: return } } func XDR_LAYOUTGET4res(v *LAYOUTGET4res) *LAYOUTGET4res { return v} var _XdrNames_Layoutreturn_type4 = map[int32]string{ int32(LAYOUTRETURN4_FILE): "LAYOUTRETURN4_FILE", int32(LAYOUTRETURN4_FSID): "LAYOUTRETURN4_FSID", int32(LAYOUTRETURN4_ALL): "LAYOUTRETURN4_ALL", } var _XdrValues_Layoutreturn_type4 = map[string]int32{ "LAYOUTRETURN4_FILE": int32(LAYOUTRETURN4_FILE), "LAYOUTRETURN4_FSID": int32(LAYOUTRETURN4_FSID), "LAYOUTRETURN4_ALL": int32(LAYOUTRETURN4_ALL), } func (Layoutreturn_type4) XdrEnumNames() map[int32]string { return _XdrNames_Layoutreturn_type4 } func (v Layoutreturn_type4) String() string { if s, ok := _XdrNames_Layoutreturn_type4[int32(v)]; ok { return s } return fmt.Sprintf("Layoutreturn_type4#%d", v) } func (v *Layoutreturn_type4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Layoutreturn_type4[stok]; ok { *v = Layoutreturn_type4(val) return nil } else if stok == "Layoutreturn_type4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Layoutreturn_type4.", stok)) } } func (v Layoutreturn_type4) GetU32() uint32 { return uint32(v) } func (v *Layoutreturn_type4) SetU32(n uint32) { *v = Layoutreturn_type4(n) } func (v *Layoutreturn_type4) XdrPointer() interface{} { return v } func (Layoutreturn_type4) XdrTypeName() string { return "Layoutreturn_type4" } func (v Layoutreturn_type4) XdrValue() interface{} { return v } func (v *Layoutreturn_type4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Layoutreturn_type4 = *Layoutreturn_type4 func XDR_Layoutreturn_type4(v *Layoutreturn_type4) *Layoutreturn_type4 { return v } func (v *Layoutreturn_type4) XdrInitialize() { switch Layoutreturn_type4(0) { case LAYOUTRETURN4_FILE, LAYOUTRETURN4_FSID, LAYOUTRETURN4_ALL: default: if *v == Layoutreturn_type4(0) { *v = LAYOUTRETURN4_FILE } } } type XdrType_Layoutreturn_file4 = *Layoutreturn_file4 func (v *Layoutreturn_file4) XdrPointer() interface{} { return v } func (Layoutreturn_file4) XdrTypeName() string { return "Layoutreturn_file4" } func (v Layoutreturn_file4) XdrValue() interface{} { return v } func (v *Layoutreturn_file4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Layoutreturn_file4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slrf_offset", name), XDR_Offset4(&v.Lrf_offset)) x.Marshal(x.Sprintf("%slrf_length", name), XDR_Length4(&v.Lrf_length)) x.Marshal(x.Sprintf("%slrf_stateid", name), XDR_Stateid4(&v.Lrf_stateid)) x.Marshal(x.Sprintf("%slrf_body", name), XdrVecOpaque{&v.Lrf_body, 0xffffffff}) } func XDR_Layoutreturn_file4(v *Layoutreturn_file4) *Layoutreturn_file4 { return v } func (u *Layoutreturn4) Lr_layout() *Layoutreturn_file4 { switch u.Lr_returntype { case LAYOUTRETURN4_FILE: if v, ok := u.U.(*Layoutreturn_file4); ok { return v } else { var zero Layoutreturn_file4 u.U = &zero return &zero } default: XdrPanic("Layoutreturn4.Lr_layout accessed when Lr_returntype == %v", u.Lr_returntype) return nil } } func (u Layoutreturn4) XdrValid() bool { return true } func (u *Layoutreturn4) XdrUnionTag() XdrNum32 { return XDR_Layoutreturn_type4(&u.Lr_returntype) } func (u *Layoutreturn4) XdrUnionTagName() string { return "Lr_returntype" } func (u *Layoutreturn4) XdrUnionBody() XdrType { switch u.Lr_returntype { case LAYOUTRETURN4_FILE: return XDR_Layoutreturn_file4(u.Lr_layout()) default: return nil } } func (u *Layoutreturn4) XdrUnionBodyName() string { switch u.Lr_returntype { case LAYOUTRETURN4_FILE: return "Lr_layout" default: return "" } } type XdrType_Layoutreturn4 = *Layoutreturn4 func (v *Layoutreturn4) XdrPointer() interface{} { return v } func (Layoutreturn4) XdrTypeName() string { return "Layoutreturn4" } func (v Layoutreturn4) XdrValue() interface{} { return v } func (v *Layoutreturn4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Layoutreturn4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Layoutreturn_type4(&u.Lr_returntype).XdrMarshal(x, x.Sprintf("%slr_returntype", name)) switch u.Lr_returntype { case LAYOUTRETURN4_FILE: x.Marshal(x.Sprintf("%slr_layout", name), XDR_Layoutreturn_file4(u.Lr_layout())) return default: return } } func XDR_Layoutreturn4(v *Layoutreturn4) *Layoutreturn4 { return v} type XdrType_LAYOUTRETURN4args = *LAYOUTRETURN4args func (v *LAYOUTRETURN4args) XdrPointer() interface{} { return v } func (LAYOUTRETURN4args) XdrTypeName() string { return "LAYOUTRETURN4args" } func (v LAYOUTRETURN4args) XdrValue() interface{} { return v } func (v *LAYOUTRETURN4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *LAYOUTRETURN4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slora_reclaim", name), XDR_bool(&v.Lora_reclaim)) x.Marshal(x.Sprintf("%slora_layout_type", name), XDR_Layouttype4(&v.Lora_layout_type)) x.Marshal(x.Sprintf("%slora_iomode", name), XDR_Layoutiomode4(&v.Lora_iomode)) x.Marshal(x.Sprintf("%slora_layoutreturn", name), XDR_Layoutreturn4(&v.Lora_layoutreturn)) } func XDR_LAYOUTRETURN4args(v *LAYOUTRETURN4args) *LAYOUTRETURN4args { return v } var _XdrTags_Layoutreturn_stateid = map[int32]bool{ XdrToI32(true): true, XdrToI32(false): true, } func (_ Layoutreturn_stateid) XdrValidTags() map[int32]bool { return _XdrTags_Layoutreturn_stateid } func (u *Layoutreturn_stateid) Lrs_stateid() *Stateid4 { switch u.Lrs_present { case true: if v, ok := u.U.(*Stateid4); ok { return v } else { var zero Stateid4 u.U = &zero return &zero } default: XdrPanic("Layoutreturn_stateid.Lrs_stateid accessed when Lrs_present == %v", u.Lrs_present) return nil } } func (u Layoutreturn_stateid) XdrValid() bool { switch u.Lrs_present { case true,false: return true } return false } func (u *Layoutreturn_stateid) XdrUnionTag() XdrNum32 { return XDR_bool(&u.Lrs_present) } func (u *Layoutreturn_stateid) XdrUnionTagName() string { return "Lrs_present" } func (u *Layoutreturn_stateid) XdrUnionBody() XdrType { switch u.Lrs_present { case true: return XDR_Stateid4(u.Lrs_stateid()) case false: return nil } return nil } func (u *Layoutreturn_stateid) XdrUnionBodyName() string { switch u.Lrs_present { case true: return "Lrs_stateid" case false: return "" } return "" } type XdrType_Layoutreturn_stateid = *Layoutreturn_stateid func (v *Layoutreturn_stateid) XdrPointer() interface{} { return v } func (Layoutreturn_stateid) XdrTypeName() string { return "Layoutreturn_stateid" } func (v Layoutreturn_stateid) XdrValue() interface{} { return v } func (v *Layoutreturn_stateid) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Layoutreturn_stateid) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_bool(&u.Lrs_present).XdrMarshal(x, x.Sprintf("%slrs_present", name)) switch u.Lrs_present { case true: x.Marshal(x.Sprintf("%slrs_stateid", name), XDR_Stateid4(u.Lrs_stateid())) return case false: return } XdrPanic("invalid Lrs_present (%v) in Layoutreturn_stateid", u.Lrs_present) } func XDR_Layoutreturn_stateid(v *Layoutreturn_stateid) *Layoutreturn_stateid { return v} func (u *LAYOUTRETURN4res) Lorr_stateid() *Layoutreturn_stateid { switch u.Lorr_status { case NFS4_OK: if v, ok := u.U.(*Layoutreturn_stateid); ok { return v } else { var zero Layoutreturn_stateid u.U = &zero return &zero } default: XdrPanic("LAYOUTRETURN4res.Lorr_stateid accessed when Lorr_status == %v", u.Lorr_status) return nil } } func (u LAYOUTRETURN4res) XdrValid() bool { return true } func (u *LAYOUTRETURN4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Lorr_status) } func (u *LAYOUTRETURN4res) XdrUnionTagName() string { return "Lorr_status" } func (u *LAYOUTRETURN4res) XdrUnionBody() XdrType { switch u.Lorr_status { case NFS4_OK: return XDR_Layoutreturn_stateid(u.Lorr_stateid()) default: return nil } } func (u *LAYOUTRETURN4res) XdrUnionBodyName() string { switch u.Lorr_status { case NFS4_OK: return "Lorr_stateid" default: return "" } } type XdrType_LAYOUTRETURN4res = *LAYOUTRETURN4res func (v *LAYOUTRETURN4res) XdrPointer() interface{} { return v } func (LAYOUTRETURN4res) XdrTypeName() string { return "LAYOUTRETURN4res" } func (v LAYOUTRETURN4res) XdrValue() interface{} { return v } func (v *LAYOUTRETURN4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *LAYOUTRETURN4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Lorr_status).XdrMarshal(x, x.Sprintf("%slorr_status", name)) switch u.Lorr_status { case NFS4_OK: x.Marshal(x.Sprintf("%slorr_stateid", name), XDR_Layoutreturn_stateid(u.Lorr_stateid())) return default: return } } func XDR_LAYOUTRETURN4res(v *LAYOUTRETURN4res) *LAYOUTRETURN4res { return v} var _XdrNames_Secinfo_style4 = map[int32]string{ int32(SECINFO_STYLE4_CURRENT_FH): "SECINFO_STYLE4_CURRENT_FH", int32(SECINFO_STYLE4_PARENT): "SECINFO_STYLE4_PARENT", } var _XdrValues_Secinfo_style4 = map[string]int32{ "SECINFO_STYLE4_CURRENT_FH": int32(SECINFO_STYLE4_CURRENT_FH), "SECINFO_STYLE4_PARENT": int32(SECINFO_STYLE4_PARENT), } func (Secinfo_style4) XdrEnumNames() map[int32]string { return _XdrNames_Secinfo_style4 } func (v Secinfo_style4) String() string { if s, ok := _XdrNames_Secinfo_style4[int32(v)]; ok { return s } return fmt.Sprintf("Secinfo_style4#%d", v) } func (v *Secinfo_style4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Secinfo_style4[stok]; ok { *v = Secinfo_style4(val) return nil } else if stok == "Secinfo_style4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Secinfo_style4.", stok)) } } func (v Secinfo_style4) GetU32() uint32 { return uint32(v) } func (v *Secinfo_style4) SetU32(n uint32) { *v = Secinfo_style4(n) } func (v *Secinfo_style4) XdrPointer() interface{} { return v } func (Secinfo_style4) XdrTypeName() string { return "Secinfo_style4" } func (v Secinfo_style4) XdrValue() interface{} { return v } func (v *Secinfo_style4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Secinfo_style4 = *Secinfo_style4 func XDR_Secinfo_style4(v *Secinfo_style4) *Secinfo_style4 { return v } type XdrType_SECINFO_NO_NAME4args struct { XdrType_Secinfo_style4 } func XDR_SECINFO_NO_NAME4args(v *SECINFO_NO_NAME4args) *XdrType_SECINFO_NO_NAME4args { return &XdrType_SECINFO_NO_NAME4args{XDR_Secinfo_style4(v)} } func (XdrType_SECINFO_NO_NAME4args) XdrTypeName() string { return "SECINFO_NO_NAME4args" } func (v XdrType_SECINFO_NO_NAME4args) XdrUnwrap() XdrType { return v.XdrType_Secinfo_style4 } type XdrType_SECINFO_NO_NAME4res struct { XdrType_SECINFO4res } func XDR_SECINFO_NO_NAME4res(v *SECINFO_NO_NAME4res) *XdrType_SECINFO_NO_NAME4res { return &XdrType_SECINFO_NO_NAME4res{XDR_SECINFO4res(v)} } func (XdrType_SECINFO_NO_NAME4res) XdrTypeName() string { return "SECINFO_NO_NAME4res" } func (v XdrType_SECINFO_NO_NAME4res) XdrUnwrap() XdrType { return v.XdrType_SECINFO4res } type XdrType_SEQUENCE4args = *SEQUENCE4args func (v *SEQUENCE4args) XdrPointer() interface{} { return v } func (SEQUENCE4args) XdrTypeName() string { return "SEQUENCE4args" } func (v SEQUENCE4args) XdrValue() interface{} { return v } func (v *SEQUENCE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SEQUENCE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssa_sessionid", name), XDR_Sessionid4(&v.Sa_sessionid)) x.Marshal(x.Sprintf("%ssa_sequenceid", name), XDR_Sequenceid4(&v.Sa_sequenceid)) x.Marshal(x.Sprintf("%ssa_slotid", name), XDR_Slotid4(&v.Sa_slotid)) x.Marshal(x.Sprintf("%ssa_highest_slotid", name), XDR_Slotid4(&v.Sa_highest_slotid)) x.Marshal(x.Sprintf("%ssa_cachethis", name), XDR_bool(&v.Sa_cachethis)) } func XDR_SEQUENCE4args(v *SEQUENCE4args) *SEQUENCE4args { return v } type XdrType_SEQUENCE4resok = *SEQUENCE4resok func (v *SEQUENCE4resok) XdrPointer() interface{} { return v } func (SEQUENCE4resok) XdrTypeName() string { return "SEQUENCE4resok" } func (v SEQUENCE4resok) XdrValue() interface{} { return v } func (v *SEQUENCE4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SEQUENCE4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssr_sessionid", name), XDR_Sessionid4(&v.Sr_sessionid)) x.Marshal(x.Sprintf("%ssr_sequenceid", name), XDR_Sequenceid4(&v.Sr_sequenceid)) x.Marshal(x.Sprintf("%ssr_slotid", name), XDR_Slotid4(&v.Sr_slotid)) x.Marshal(x.Sprintf("%ssr_highest_slotid", name), XDR_Slotid4(&v.Sr_highest_slotid)) x.Marshal(x.Sprintf("%ssr_target_highest_slotid", name), XDR_Slotid4(&v.Sr_target_highest_slotid)) x.Marshal(x.Sprintf("%ssr_status_flags", name), XDR_Uint32_t(&v.Sr_status_flags)) } func XDR_SEQUENCE4resok(v *SEQUENCE4resok) *SEQUENCE4resok { return v } func (u *SEQUENCE4res) Sr_resok4() *SEQUENCE4resok { switch u.Sr_status { case NFS4_OK: if v, ok := u.U.(*SEQUENCE4resok); ok { return v } else { var zero SEQUENCE4resok u.U = &zero return &zero } default: XdrPanic("SEQUENCE4res.Sr_resok4 accessed when Sr_status == %v", u.Sr_status) return nil } } func (u SEQUENCE4res) XdrValid() bool { return true } func (u *SEQUENCE4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Sr_status) } func (u *SEQUENCE4res) XdrUnionTagName() string { return "Sr_status" } func (u *SEQUENCE4res) XdrUnionBody() XdrType { switch u.Sr_status { case NFS4_OK: return XDR_SEQUENCE4resok(u.Sr_resok4()) default: return nil } } func (u *SEQUENCE4res) XdrUnionBodyName() string { switch u.Sr_status { case NFS4_OK: return "Sr_resok4" default: return "" } } type XdrType_SEQUENCE4res = *SEQUENCE4res func (v *SEQUENCE4res) XdrPointer() interface{} { return v } func (SEQUENCE4res) XdrTypeName() string { return "SEQUENCE4res" } func (v SEQUENCE4res) XdrValue() interface{} { return v } func (v *SEQUENCE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *SEQUENCE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Sr_status).XdrMarshal(x, x.Sprintf("%ssr_status", name)) switch u.Sr_status { case NFS4_OK: x.Marshal(x.Sprintf("%ssr_resok4", name), XDR_SEQUENCE4resok(u.Sr_resok4())) return default: return } } func XDR_SEQUENCE4res(v *SEQUENCE4res) *SEQUENCE4res { return v} type XdrType_Ssa_digest_input4 = *Ssa_digest_input4 func (v *Ssa_digest_input4) XdrPointer() interface{} { return v } func (Ssa_digest_input4) XdrTypeName() string { return "Ssa_digest_input4" } func (v Ssa_digest_input4) XdrValue() interface{} { return v } func (v *Ssa_digest_input4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Ssa_digest_input4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssdi_seqargs", name), XDR_SEQUENCE4args(&v.Sdi_seqargs)) } func XDR_Ssa_digest_input4(v *Ssa_digest_input4) *Ssa_digest_input4 { return v } type XdrType_SET_SSV4args = *SET_SSV4args func (v *SET_SSV4args) XdrPointer() interface{} { return v } func (SET_SSV4args) XdrTypeName() string { return "SET_SSV4args" } func (v SET_SSV4args) XdrValue() interface{} { return v } func (v *SET_SSV4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SET_SSV4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sssa_ssv", name), XdrVecOpaque{&v.Ssa_ssv, 0xffffffff}) x.Marshal(x.Sprintf("%sssa_digest", name), XdrVecOpaque{&v.Ssa_digest, 0xffffffff}) } func XDR_SET_SSV4args(v *SET_SSV4args) *SET_SSV4args { return v } type XdrType_Ssr_digest_input4 = *Ssr_digest_input4 func (v *Ssr_digest_input4) XdrPointer() interface{} { return v } func (Ssr_digest_input4) XdrTypeName() string { return "Ssr_digest_input4" } func (v Ssr_digest_input4) XdrValue() interface{} { return v } func (v *Ssr_digest_input4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Ssr_digest_input4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%ssdi_seqres", name), XDR_SEQUENCE4res(&v.Sdi_seqres)) } func XDR_Ssr_digest_input4(v *Ssr_digest_input4) *Ssr_digest_input4 { return v } type XdrType_SET_SSV4resok = *SET_SSV4resok func (v *SET_SSV4resok) XdrPointer() interface{} { return v } func (SET_SSV4resok) XdrTypeName() string { return "SET_SSV4resok" } func (v SET_SSV4resok) XdrValue() interface{} { return v } func (v *SET_SSV4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *SET_SSV4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sssr_digest", name), XdrVecOpaque{&v.Ssr_digest, 0xffffffff}) } func XDR_SET_SSV4resok(v *SET_SSV4resok) *SET_SSV4resok { return v } func (u *SET_SSV4res) Ssr_resok4() *SET_SSV4resok { switch u.Ssr_status { case NFS4_OK: if v, ok := u.U.(*SET_SSV4resok); ok { return v } else { var zero SET_SSV4resok u.U = &zero return &zero } default: XdrPanic("SET_SSV4res.Ssr_resok4 accessed when Ssr_status == %v", u.Ssr_status) return nil } } func (u SET_SSV4res) XdrValid() bool { return true } func (u *SET_SSV4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Ssr_status) } func (u *SET_SSV4res) XdrUnionTagName() string { return "Ssr_status" } func (u *SET_SSV4res) XdrUnionBody() XdrType { switch u.Ssr_status { case NFS4_OK: return XDR_SET_SSV4resok(u.Ssr_resok4()) default: return nil } } func (u *SET_SSV4res) XdrUnionBodyName() string { switch u.Ssr_status { case NFS4_OK: return "Ssr_resok4" default: return "" } } type XdrType_SET_SSV4res = *SET_SSV4res func (v *SET_SSV4res) XdrPointer() interface{} { return v } func (SET_SSV4res) XdrTypeName() string { return "SET_SSV4res" } func (v SET_SSV4res) XdrValue() interface{} { return v } func (v *SET_SSV4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *SET_SSV4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Ssr_status).XdrMarshal(x, x.Sprintf("%sssr_status", name)) switch u.Ssr_status { case NFS4_OK: x.Marshal(x.Sprintf("%sssr_resok4", name), XDR_SET_SSV4resok(u.Ssr_resok4())) return default: return } } func XDR_SET_SSV4res(v *SET_SSV4res) *SET_SSV4res { return v} type _XdrVec_unbounded_Stateid4 []Stateid4 func (_XdrVec_unbounded_Stateid4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Stateid4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Stateid4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Stateid4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Stateid4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Stateid4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Stateid4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Stateid4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Stateid4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Stateid4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Stateid4) XdrTypeName() string { return "Stateid4<>" } func (v *_XdrVec_unbounded_Stateid4) XdrPointer() interface{} { return (*[]Stateid4)(v) } func (v _XdrVec_unbounded_Stateid4) XdrValue() interface{} { return ([]Stateid4)(v) } func (v *_XdrVec_unbounded_Stateid4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_TEST_STATEID4args = *TEST_STATEID4args func (v *TEST_STATEID4args) XdrPointer() interface{} { return v } func (TEST_STATEID4args) XdrTypeName() string { return "TEST_STATEID4args" } func (v TEST_STATEID4args) XdrValue() interface{} { return v } func (v *TEST_STATEID4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *TEST_STATEID4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sts_stateids", name), (*_XdrVec_unbounded_Stateid4)(&v.Ts_stateids)) } func XDR_TEST_STATEID4args(v *TEST_STATEID4args) *TEST_STATEID4args { return v } type _XdrVec_unbounded_Nfsstat4 []Nfsstat4 func (_XdrVec_unbounded_Nfsstat4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfsstat4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfsstat4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfsstat4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfsstat4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfsstat4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfsstat4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfsstat4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfsstat4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfsstat4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfsstat4) XdrTypeName() string { return "Nfsstat4<>" } func (v *_XdrVec_unbounded_Nfsstat4) XdrPointer() interface{} { return (*[]Nfsstat4)(v) } func (v _XdrVec_unbounded_Nfsstat4) XdrValue() interface{} { return ([]Nfsstat4)(v) } func (v *_XdrVec_unbounded_Nfsstat4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_TEST_STATEID4resok = *TEST_STATEID4resok func (v *TEST_STATEID4resok) XdrPointer() interface{} { return v } func (TEST_STATEID4resok) XdrTypeName() string { return "TEST_STATEID4resok" } func (v TEST_STATEID4resok) XdrValue() interface{} { return v } func (v *TEST_STATEID4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *TEST_STATEID4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%stsr_status_codes", name), (*_XdrVec_unbounded_Nfsstat4)(&v.Tsr_status_codes)) } func XDR_TEST_STATEID4resok(v *TEST_STATEID4resok) *TEST_STATEID4resok { return v } func (u *TEST_STATEID4res) Tsr_resok4() *TEST_STATEID4resok { switch u.Tsr_status { case NFS4_OK: if v, ok := u.U.(*TEST_STATEID4resok); ok { return v } else { var zero TEST_STATEID4resok u.U = &zero return &zero } default: XdrPanic("TEST_STATEID4res.Tsr_resok4 accessed when Tsr_status == %v", u.Tsr_status) return nil } } func (u TEST_STATEID4res) XdrValid() bool { return true } func (u *TEST_STATEID4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Tsr_status) } func (u *TEST_STATEID4res) XdrUnionTagName() string { return "Tsr_status" } func (u *TEST_STATEID4res) XdrUnionBody() XdrType { switch u.Tsr_status { case NFS4_OK: return XDR_TEST_STATEID4resok(u.Tsr_resok4()) default: return nil } } func (u *TEST_STATEID4res) XdrUnionBodyName() string { switch u.Tsr_status { case NFS4_OK: return "Tsr_resok4" default: return "" } } type XdrType_TEST_STATEID4res = *TEST_STATEID4res func (v *TEST_STATEID4res) XdrPointer() interface{} { return v } func (TEST_STATEID4res) XdrTypeName() string { return "TEST_STATEID4res" } func (v TEST_STATEID4res) XdrValue() interface{} { return v } func (v *TEST_STATEID4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *TEST_STATEID4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Tsr_status).XdrMarshal(x, x.Sprintf("%stsr_status", name)) switch u.Tsr_status { case NFS4_OK: x.Marshal(x.Sprintf("%stsr_resok4", name), XDR_TEST_STATEID4resok(u.Tsr_resok4())) return default: return } } func XDR_TEST_STATEID4res(v *TEST_STATEID4res) *TEST_STATEID4res { return v} var _XdrTags_Deleg_claim4 = map[int32]bool{ XdrToI32(CLAIM_FH): true, XdrToI32(CLAIM_DELEG_PREV_FH): true, XdrToI32(CLAIM_PREVIOUS): true, } func (_ Deleg_claim4) XdrValidTags() map[int32]bool { return _XdrTags_Deleg_claim4 } func (u *Deleg_claim4) Dc_delegate_type() *Open_delegation_type4 { switch u.Dc_claim { case CLAIM_PREVIOUS: if v, ok := u.U.(*Open_delegation_type4); ok { return v } else { var zero Open_delegation_type4 u.U = &zero return &zero } default: XdrPanic("Deleg_claim4.Dc_delegate_type accessed when Dc_claim == %v", u.Dc_claim) return nil } } func (u Deleg_claim4) XdrValid() bool { switch u.Dc_claim { case CLAIM_FH,CLAIM_DELEG_PREV_FH,CLAIM_PREVIOUS: return true } return false } func (u *Deleg_claim4) XdrUnionTag() XdrNum32 { return XDR_Open_claim_type4(&u.Dc_claim) } func (u *Deleg_claim4) XdrUnionTagName() string { return "Dc_claim" } func (u *Deleg_claim4) XdrUnionBody() XdrType { switch u.Dc_claim { case CLAIM_FH: return nil case CLAIM_DELEG_PREV_FH: return nil case CLAIM_PREVIOUS: return XDR_Open_delegation_type4(u.Dc_delegate_type()) } return nil } func (u *Deleg_claim4) XdrUnionBodyName() string { switch u.Dc_claim { case CLAIM_FH: return "" case CLAIM_DELEG_PREV_FH: return "" case CLAIM_PREVIOUS: return "Dc_delegate_type" } return "" } type XdrType_Deleg_claim4 = *Deleg_claim4 func (v *Deleg_claim4) XdrPointer() interface{} { return v } func (Deleg_claim4) XdrTypeName() string { return "Deleg_claim4" } func (v Deleg_claim4) XdrValue() interface{} { return v } func (v *Deleg_claim4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Deleg_claim4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Open_claim_type4(&u.Dc_claim).XdrMarshal(x, x.Sprintf("%sdc_claim", name)) switch u.Dc_claim { case CLAIM_FH: return case CLAIM_DELEG_PREV_FH: return case CLAIM_PREVIOUS: x.Marshal(x.Sprintf("%sdc_delegate_type", name), XDR_Open_delegation_type4(u.Dc_delegate_type())) return } XdrPanic("invalid Dc_claim (%v) in Deleg_claim4", u.Dc_claim) } func (v *Deleg_claim4) XdrInitialize() { var zero Open_claim_type4 switch zero { case CLAIM_FH, CLAIM_DELEG_PREV_FH, CLAIM_PREVIOUS: default: if v.Dc_claim == zero { v.Dc_claim = CLAIM_FH } } } func XDR_Deleg_claim4(v *Deleg_claim4) *Deleg_claim4 { return v} type XdrType_WANT_DELEGATION4args = *WANT_DELEGATION4args func (v *WANT_DELEGATION4args) XdrPointer() interface{} { return v } func (WANT_DELEGATION4args) XdrTypeName() string { return "WANT_DELEGATION4args" } func (v WANT_DELEGATION4args) XdrValue() interface{} { return v } func (v *WANT_DELEGATION4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *WANT_DELEGATION4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%swda_want", name), XDR_Uint32_t(&v.Wda_want)) x.Marshal(x.Sprintf("%swda_claim", name), XDR_Deleg_claim4(&v.Wda_claim)) } func XDR_WANT_DELEGATION4args(v *WANT_DELEGATION4args) *WANT_DELEGATION4args { return v } func (u *WANT_DELEGATION4res) Wdr_resok4() *Open_delegation4 { switch u.Wdr_status { case NFS4_OK: if v, ok := u.U.(*Open_delegation4); ok { return v } else { var zero Open_delegation4 u.U = &zero return &zero } default: XdrPanic("WANT_DELEGATION4res.Wdr_resok4 accessed when Wdr_status == %v", u.Wdr_status) return nil } } func (u WANT_DELEGATION4res) XdrValid() bool { return true } func (u *WANT_DELEGATION4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Wdr_status) } func (u *WANT_DELEGATION4res) XdrUnionTagName() string { return "Wdr_status" } func (u *WANT_DELEGATION4res) XdrUnionBody() XdrType { switch u.Wdr_status { case NFS4_OK: return XDR_Open_delegation4(u.Wdr_resok4()) default: return nil } } func (u *WANT_DELEGATION4res) XdrUnionBodyName() string { switch u.Wdr_status { case NFS4_OK: return "Wdr_resok4" default: return "" } } type XdrType_WANT_DELEGATION4res = *WANT_DELEGATION4res func (v *WANT_DELEGATION4res) XdrPointer() interface{} { return v } func (WANT_DELEGATION4res) XdrTypeName() string { return "WANT_DELEGATION4res" } func (v WANT_DELEGATION4res) XdrValue() interface{} { return v } func (v *WANT_DELEGATION4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *WANT_DELEGATION4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Wdr_status).XdrMarshal(x, x.Sprintf("%swdr_status", name)) switch u.Wdr_status { case NFS4_OK: x.Marshal(x.Sprintf("%swdr_resok4", name), XDR_Open_delegation4(u.Wdr_resok4())) return default: return } } func XDR_WANT_DELEGATION4res(v *WANT_DELEGATION4res) *WANT_DELEGATION4res { return v} type XdrType_DESTROY_CLIENTID4args = *DESTROY_CLIENTID4args func (v *DESTROY_CLIENTID4args) XdrPointer() interface{} { return v } func (DESTROY_CLIENTID4args) XdrTypeName() string { return "DESTROY_CLIENTID4args" } func (v DESTROY_CLIENTID4args) XdrValue() interface{} { return v } func (v *DESTROY_CLIENTID4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DESTROY_CLIENTID4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdca_clientid", name), XDR_Clientid4(&v.Dca_clientid)) } func XDR_DESTROY_CLIENTID4args(v *DESTROY_CLIENTID4args) *DESTROY_CLIENTID4args { return v } type XdrType_DESTROY_CLIENTID4res = *DESTROY_CLIENTID4res func (v *DESTROY_CLIENTID4res) XdrPointer() interface{} { return v } func (DESTROY_CLIENTID4res) XdrTypeName() string { return "DESTROY_CLIENTID4res" } func (v DESTROY_CLIENTID4res) XdrValue() interface{} { return v } func (v *DESTROY_CLIENTID4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *DESTROY_CLIENTID4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sdcr_status", name), XDR_Nfsstat4(&v.Dcr_status)) } func XDR_DESTROY_CLIENTID4res(v *DESTROY_CLIENTID4res) *DESTROY_CLIENTID4res { return v } type XdrType_RECLAIM_COMPLETE4args = *RECLAIM_COMPLETE4args func (v *RECLAIM_COMPLETE4args) XdrPointer() interface{} { return v } func (RECLAIM_COMPLETE4args) XdrTypeName() string { return "RECLAIM_COMPLETE4args" } func (v RECLAIM_COMPLETE4args) XdrValue() interface{} { return v } func (v *RECLAIM_COMPLETE4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RECLAIM_COMPLETE4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%srca_one_fs", name), XDR_bool(&v.Rca_one_fs)) } func XDR_RECLAIM_COMPLETE4args(v *RECLAIM_COMPLETE4args) *RECLAIM_COMPLETE4args { return v } type XdrType_RECLAIM_COMPLETE4res = *RECLAIM_COMPLETE4res func (v *RECLAIM_COMPLETE4res) XdrPointer() interface{} { return v } func (RECLAIM_COMPLETE4res) XdrTypeName() string { return "RECLAIM_COMPLETE4res" } func (v RECLAIM_COMPLETE4res) XdrValue() interface{} { return v } func (v *RECLAIM_COMPLETE4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *RECLAIM_COMPLETE4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%srcr_status", name), XDR_Nfsstat4(&v.Rcr_status)) } func XDR_RECLAIM_COMPLETE4res(v *RECLAIM_COMPLETE4res) *RECLAIM_COMPLETE4res { return v } type XdrType_ILLEGAL4res = *ILLEGAL4res func (v *ILLEGAL4res) XdrPointer() interface{} { return v } func (ILLEGAL4res) XdrTypeName() string { return "ILLEGAL4res" } func (v ILLEGAL4res) XdrValue() interface{} { return v } func (v *ILLEGAL4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *ILLEGAL4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_ILLEGAL4res(v *ILLEGAL4res) *ILLEGAL4res { return v } var _XdrNames_Nfs_opnum4 = map[int32]string{ int32(OP_ACCESS): "OP_ACCESS", int32(OP_CLOSE): "OP_CLOSE", int32(OP_COMMIT): "OP_COMMIT", int32(OP_CREATE): "OP_CREATE", int32(OP_DELEGPURGE): "OP_DELEGPURGE", int32(OP_DELEGRETURN): "OP_DELEGRETURN", int32(OP_GETATTR): "OP_GETATTR", int32(OP_GETFH): "OP_GETFH", int32(OP_LINK): "OP_LINK", int32(OP_LOCK): "OP_LOCK", int32(OP_LOCKT): "OP_LOCKT", int32(OP_LOCKU): "OP_LOCKU", int32(OP_LOOKUP): "OP_LOOKUP", int32(OP_LOOKUPP): "OP_LOOKUPP", int32(OP_NVERIFY): "OP_NVERIFY", int32(OP_OPEN): "OP_OPEN", int32(OP_OPENATTR): "OP_OPENATTR", int32(OP_OPEN_CONFIRM): "OP_OPEN_CONFIRM", int32(OP_OPEN_DOWNGRADE): "OP_OPEN_DOWNGRADE", int32(OP_PUTFH): "OP_PUTFH", int32(OP_PUTPUBFH): "OP_PUTPUBFH", int32(OP_PUTROOTFH): "OP_PUTROOTFH", int32(OP_READ): "OP_READ", int32(OP_READDIR): "OP_READDIR", int32(OP_READLINK): "OP_READLINK", int32(OP_REMOVE): "OP_REMOVE", int32(OP_RENAME): "OP_RENAME", int32(OP_RENEW): "OP_RENEW", int32(OP_RESTOREFH): "OP_RESTOREFH", int32(OP_SAVEFH): "OP_SAVEFH", int32(OP_SECINFO): "OP_SECINFO", int32(OP_SETATTR): "OP_SETATTR", int32(OP_SETCLIENTID): "OP_SETCLIENTID", int32(OP_SETCLIENTID_CONFIRM): "OP_SETCLIENTID_CONFIRM", int32(OP_VERIFY): "OP_VERIFY", int32(OP_WRITE): "OP_WRITE", int32(OP_RELEASE_LOCKOWNER): "OP_RELEASE_LOCKOWNER", int32(OP_CREATE_SESSION): "OP_CREATE_SESSION", int32(OP_DESTROY_SESSION): "OP_DESTROY_SESSION", int32(OP_FREE_STATEID): "OP_FREE_STATEID", int32(OP_GET_DIR_DELEGATION): "OP_GET_DIR_DELEGATION", int32(OP_GETDEVICEINFO): "OP_GETDEVICEINFO", int32(OP_GETDEVICELIST): "OP_GETDEVICELIST", int32(OP_LAYOUTCOMMIT): "OP_LAYOUTCOMMIT", int32(OP_LAYOUTGET): "OP_LAYOUTGET", int32(OP_LAYOUTRETURN): "OP_LAYOUTRETURN", int32(OP_SECINFO_NO_NAME): "OP_SECINFO_NO_NAME", int32(OP_SEQUENCE): "OP_SEQUENCE", int32(OP_SET_SSV): "OP_SET_SSV", int32(OP_TEST_STATEID): "OP_TEST_STATEID", int32(OP_WANT_DELEGATION): "OP_WANT_DELEGATION", int32(OP_DESTROY_CLIENTID): "OP_DESTROY_CLIENTID", int32(OP_RECLAIM_COMPLETE): "OP_RECLAIM_COMPLETE", int32(OP_ILLEGAL): "OP_ILLEGAL", } var _XdrValues_Nfs_opnum4 = map[string]int32{ "OP_ACCESS": int32(OP_ACCESS), "OP_CLOSE": int32(OP_CLOSE), "OP_COMMIT": int32(OP_COMMIT), "OP_CREATE": int32(OP_CREATE), "OP_DELEGPURGE": int32(OP_DELEGPURGE), "OP_DELEGRETURN": int32(OP_DELEGRETURN), "OP_GETATTR": int32(OP_GETATTR), "OP_GETFH": int32(OP_GETFH), "OP_LINK": int32(OP_LINK), "OP_LOCK": int32(OP_LOCK), "OP_LOCKT": int32(OP_LOCKT), "OP_LOCKU": int32(OP_LOCKU), "OP_LOOKUP": int32(OP_LOOKUP), "OP_LOOKUPP": int32(OP_LOOKUPP), "OP_NVERIFY": int32(OP_NVERIFY), "OP_OPEN": int32(OP_OPEN), "OP_OPENATTR": int32(OP_OPENATTR), "OP_OPEN_CONFIRM": int32(OP_OPEN_CONFIRM), "OP_OPEN_DOWNGRADE": int32(OP_OPEN_DOWNGRADE), "OP_PUTFH": int32(OP_PUTFH), "OP_PUTPUBFH": int32(OP_PUTPUBFH), "OP_PUTROOTFH": int32(OP_PUTROOTFH), "OP_READ": int32(OP_READ), "OP_READDIR": int32(OP_READDIR), "OP_READLINK": int32(OP_READLINK), "OP_REMOVE": int32(OP_REMOVE), "OP_RENAME": int32(OP_RENAME), "OP_RENEW": int32(OP_RENEW), "OP_RESTOREFH": int32(OP_RESTOREFH), "OP_SAVEFH": int32(OP_SAVEFH), "OP_SECINFO": int32(OP_SECINFO), "OP_SETATTR": int32(OP_SETATTR), "OP_SETCLIENTID": int32(OP_SETCLIENTID), "OP_SETCLIENTID_CONFIRM": int32(OP_SETCLIENTID_CONFIRM), "OP_VERIFY": int32(OP_VERIFY), "OP_WRITE": int32(OP_WRITE), "OP_RELEASE_LOCKOWNER": int32(OP_RELEASE_LOCKOWNER), "OP_CREATE_SESSION": int32(OP_CREATE_SESSION), "OP_DESTROY_SESSION": int32(OP_DESTROY_SESSION), "OP_FREE_STATEID": int32(OP_FREE_STATEID), "OP_GET_DIR_DELEGATION": int32(OP_GET_DIR_DELEGATION), "OP_GETDEVICEINFO": int32(OP_GETDEVICEINFO), "OP_GETDEVICELIST": int32(OP_GETDEVICELIST), "OP_LAYOUTCOMMIT": int32(OP_LAYOUTCOMMIT), "OP_LAYOUTGET": int32(OP_LAYOUTGET), "OP_LAYOUTRETURN": int32(OP_LAYOUTRETURN), "OP_SECINFO_NO_NAME": int32(OP_SECINFO_NO_NAME), "OP_SEQUENCE": int32(OP_SEQUENCE), "OP_SET_SSV": int32(OP_SET_SSV), "OP_TEST_STATEID": int32(OP_TEST_STATEID), "OP_WANT_DELEGATION": int32(OP_WANT_DELEGATION), "OP_DESTROY_CLIENTID": int32(OP_DESTROY_CLIENTID), "OP_RECLAIM_COMPLETE": int32(OP_RECLAIM_COMPLETE), "OP_ILLEGAL": int32(OP_ILLEGAL), } func (Nfs_opnum4) XdrEnumNames() map[int32]string { return _XdrNames_Nfs_opnum4 } func (v Nfs_opnum4) String() string { if s, ok := _XdrNames_Nfs_opnum4[int32(v)]; ok { return s } return fmt.Sprintf("Nfs_opnum4#%d", v) } func (v *Nfs_opnum4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Nfs_opnum4[stok]; ok { *v = Nfs_opnum4(val) return nil } else if stok == "Nfs_opnum4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Nfs_opnum4.", stok)) } } func (v Nfs_opnum4) GetU32() uint32 { return uint32(v) } func (v *Nfs_opnum4) SetU32(n uint32) { *v = Nfs_opnum4(n) } func (v *Nfs_opnum4) XdrPointer() interface{} { return v } func (Nfs_opnum4) XdrTypeName() string { return "Nfs_opnum4" } func (v Nfs_opnum4) XdrValue() interface{} { return v } func (v *Nfs_opnum4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Nfs_opnum4 = *Nfs_opnum4 func XDR_Nfs_opnum4(v *Nfs_opnum4) *Nfs_opnum4 { return v } func (v *Nfs_opnum4) XdrInitialize() { switch Nfs_opnum4(0) { case OP_ACCESS, OP_CLOSE, OP_COMMIT, OP_CREATE, OP_DELEGPURGE, OP_DELEGRETURN, OP_GETATTR, OP_GETFH, OP_LINK, OP_LOCK, OP_LOCKT, OP_LOCKU, OP_LOOKUP, OP_LOOKUPP, OP_NVERIFY, OP_OPEN, OP_OPENATTR, OP_OPEN_CONFIRM, OP_OPEN_DOWNGRADE, OP_PUTFH, OP_PUTPUBFH, OP_PUTROOTFH, OP_READ, OP_READDIR, OP_READLINK, OP_REMOVE, OP_RENAME, OP_RENEW, OP_RESTOREFH, OP_SAVEFH, OP_SECINFO, OP_SETATTR, OP_SETCLIENTID, OP_SETCLIENTID_CONFIRM, OP_VERIFY, OP_WRITE, OP_RELEASE_LOCKOWNER, OP_CREATE_SESSION, OP_DESTROY_SESSION, OP_FREE_STATEID, OP_GET_DIR_DELEGATION, OP_GETDEVICEINFO, OP_GETDEVICELIST, OP_LAYOUTCOMMIT, OP_LAYOUTGET, OP_LAYOUTRETURN, OP_SECINFO_NO_NAME, OP_SEQUENCE, OP_SET_SSV, OP_TEST_STATEID, OP_WANT_DELEGATION, OP_DESTROY_CLIENTID, OP_RECLAIM_COMPLETE, OP_ILLEGAL: default: if *v == Nfs_opnum4(0) { *v = OP_ACCESS } } } var _XdrTags_Nfs_argop4 = map[int32]bool{ XdrToI32(OP_ACCESS): true, XdrToI32(OP_CLOSE): true, XdrToI32(OP_COMMIT): true, XdrToI32(OP_CREATE): true, XdrToI32(OP_DELEGPURGE): true, XdrToI32(OP_DELEGRETURN): true, XdrToI32(OP_GETATTR): true, XdrToI32(OP_GETFH): true, XdrToI32(OP_LINK): true, XdrToI32(OP_LOCK): true, XdrToI32(OP_LOCKT): true, XdrToI32(OP_LOCKU): true, XdrToI32(OP_LOOKUP): true, XdrToI32(OP_LOOKUPP): true, XdrToI32(OP_NVERIFY): true, XdrToI32(OP_OPEN): true, XdrToI32(OP_OPENATTR): true, XdrToI32(OP_OPEN_CONFIRM): true, XdrToI32(OP_OPEN_DOWNGRADE): true, XdrToI32(OP_PUTFH): true, XdrToI32(OP_PUTPUBFH): true, XdrToI32(OP_PUTROOTFH): true, XdrToI32(OP_READ): true, XdrToI32(OP_READDIR): true, XdrToI32(OP_READLINK): true, XdrToI32(OP_REMOVE): true, XdrToI32(OP_RENAME): true, XdrToI32(OP_RENEW): true, XdrToI32(OP_RESTOREFH): true, XdrToI32(OP_SAVEFH): true, XdrToI32(OP_SECINFO): true, XdrToI32(OP_SETATTR): true, XdrToI32(OP_SETCLIENTID): true, XdrToI32(OP_SETCLIENTID_CONFIRM): true, XdrToI32(OP_VERIFY): true, XdrToI32(OP_WRITE): true, XdrToI32(OP_RELEASE_LOCKOWNER): true, XdrToI32(OP_CREATE_SESSION): true, XdrToI32(OP_DESTROY_SESSION): true, XdrToI32(OP_FREE_STATEID): true, XdrToI32(OP_GET_DIR_DELEGATION): true, XdrToI32(OP_GETDEVICEINFO): true, XdrToI32(OP_GETDEVICELIST): true, XdrToI32(OP_LAYOUTCOMMIT): true, XdrToI32(OP_LAYOUTGET): true, XdrToI32(OP_LAYOUTRETURN): true, XdrToI32(OP_SECINFO_NO_NAME): true, XdrToI32(OP_SEQUENCE): true, XdrToI32(OP_SET_SSV): true, XdrToI32(OP_TEST_STATEID): true, XdrToI32(OP_WANT_DELEGATION): true, XdrToI32(OP_DESTROY_CLIENTID): true, XdrToI32(OP_RECLAIM_COMPLETE): true, XdrToI32(OP_ILLEGAL): true, } func (_ Nfs_argop4) XdrValidTags() map[int32]bool { return _XdrTags_Nfs_argop4 } func (u *Nfs_argop4) Opaccess() *ACCESS4args { switch u.Argop { case OP_ACCESS: if v, ok := u.U.(*ACCESS4args); ok { return v } else { var zero ACCESS4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opaccess accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opclose() *CLOSE4args { switch u.Argop { case OP_CLOSE: if v, ok := u.U.(*CLOSE4args); ok { return v } else { var zero CLOSE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opclose accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opcommit() *COMMIT4args { switch u.Argop { case OP_COMMIT: if v, ok := u.U.(*COMMIT4args); ok { return v } else { var zero COMMIT4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opcommit accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opcreate() *CREATE4args { switch u.Argop { case OP_CREATE: if v, ok := u.U.(*CREATE4args); ok { return v } else { var zero CREATE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opcreate accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opdelegpurge() *DELEGPURGE4args { switch u.Argop { case OP_DELEGPURGE: if v, ok := u.U.(*DELEGPURGE4args); ok { return v } else { var zero DELEGPURGE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opdelegpurge accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opdelegreturn() *DELEGRETURN4args { switch u.Argop { case OP_DELEGRETURN: if v, ok := u.U.(*DELEGRETURN4args); ok { return v } else { var zero DELEGRETURN4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opdelegreturn accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opgetattr() *GETATTR4args { switch u.Argop { case OP_GETATTR: if v, ok := u.U.(*GETATTR4args); ok { return v } else { var zero GETATTR4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opgetattr accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplink() *LINK4args { switch u.Argop { case OP_LINK: if v, ok := u.U.(*LINK4args); ok { return v } else { var zero LINK4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplink accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplock() *LOCK4args { switch u.Argop { case OP_LOCK: if v, ok := u.U.(*LOCK4args); ok { return v } else { var zero LOCK4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplock accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplockt() *LOCKT4args { switch u.Argop { case OP_LOCKT: if v, ok := u.U.(*LOCKT4args); ok { return v } else { var zero LOCKT4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplockt accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplocku() *LOCKU4args { switch u.Argop { case OP_LOCKU: if v, ok := u.U.(*LOCKU4args); ok { return v } else { var zero LOCKU4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplocku accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplookup() *LOOKUP4args { switch u.Argop { case OP_LOOKUP: if v, ok := u.U.(*LOOKUP4args); ok { return v } else { var zero LOOKUP4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplookup accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opnverify() *NVERIFY4args { switch u.Argop { case OP_NVERIFY: if v, ok := u.U.(*NVERIFY4args); ok { return v } else { var zero NVERIFY4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opnverify accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opopen() *OPEN4args { switch u.Argop { case OP_OPEN: if v, ok := u.U.(*OPEN4args); ok { return v } else { var zero OPEN4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opopen accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opopenattr() *OPENATTR4args { switch u.Argop { case OP_OPENATTR: if v, ok := u.U.(*OPENATTR4args); ok { return v } else { var zero OPENATTR4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opopenattr accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opopen_confirm() *OPEN_CONFIRM4args { switch u.Argop { case OP_OPEN_CONFIRM: if v, ok := u.U.(*OPEN_CONFIRM4args); ok { return v } else { var zero OPEN_CONFIRM4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opopen_confirm accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opopen_downgrade() *OPEN_DOWNGRADE4args { switch u.Argop { case OP_OPEN_DOWNGRADE: if v, ok := u.U.(*OPEN_DOWNGRADE4args); ok { return v } else { var zero OPEN_DOWNGRADE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opopen_downgrade accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opputfh() *PUTFH4args { switch u.Argop { case OP_PUTFH: if v, ok := u.U.(*PUTFH4args); ok { return v } else { var zero PUTFH4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opputfh accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opread() *READ4args { switch u.Argop { case OP_READ: if v, ok := u.U.(*READ4args); ok { return v } else { var zero READ4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opread accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opreaddir() *READDIR4args { switch u.Argop { case OP_READDIR: if v, ok := u.U.(*READDIR4args); ok { return v } else { var zero READDIR4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opreaddir accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opremove() *REMOVE4args { switch u.Argop { case OP_REMOVE: if v, ok := u.U.(*REMOVE4args); ok { return v } else { var zero REMOVE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opremove accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oprename() *RENAME4args { switch u.Argop { case OP_RENAME: if v, ok := u.U.(*RENAME4args); ok { return v } else { var zero RENAME4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oprename accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oprenew() *RENEW4args { switch u.Argop { case OP_RENEW: if v, ok := u.U.(*RENEW4args); ok { return v } else { var zero RENEW4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oprenew accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsecinfo() *SECINFO4args { switch u.Argop { case OP_SECINFO: if v, ok := u.U.(*SECINFO4args); ok { return v } else { var zero SECINFO4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsecinfo accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsetattr() *SETATTR4args { switch u.Argop { case OP_SETATTR: if v, ok := u.U.(*SETATTR4args); ok { return v } else { var zero SETATTR4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsetattr accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsetclientid() *SETCLIENTID4args { switch u.Argop { case OP_SETCLIENTID: if v, ok := u.U.(*SETCLIENTID4args); ok { return v } else { var zero SETCLIENTID4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsetclientid accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsetclientid_confirm() *SETCLIENTID_CONFIRM4args { switch u.Argop { case OP_SETCLIENTID_CONFIRM: if v, ok := u.U.(*SETCLIENTID_CONFIRM4args); ok { return v } else { var zero SETCLIENTID_CONFIRM4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsetclientid_confirm accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opverify() *VERIFY4args { switch u.Argop { case OP_VERIFY: if v, ok := u.U.(*VERIFY4args); ok { return v } else { var zero VERIFY4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opverify accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opwrite() *WRITE4args { switch u.Argop { case OP_WRITE: if v, ok := u.U.(*WRITE4args); ok { return v } else { var zero WRITE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opwrite accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oprelease_lockowner() *RELEASE_LOCKOWNER4args { switch u.Argop { case OP_RELEASE_LOCKOWNER: if v, ok := u.U.(*RELEASE_LOCKOWNER4args); ok { return v } else { var zero RELEASE_LOCKOWNER4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oprelease_lockowner accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opcreatesession() *CREATE_SESSION4args { switch u.Argop { case OP_CREATE_SESSION: if v, ok := u.U.(*CREATE_SESSION4args); ok { return v } else { var zero CREATE_SESSION4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opcreatesession accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opdestroysession() *DESTROY_SESSION4args { switch u.Argop { case OP_DESTROY_SESSION: if v, ok := u.U.(*DESTROY_SESSION4args); ok { return v } else { var zero DESTROY_SESSION4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opdestroysession accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opfreestateid() *FREE_STATEID4args { switch u.Argop { case OP_FREE_STATEID: if v, ok := u.U.(*FREE_STATEID4args); ok { return v } else { var zero FREE_STATEID4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opfreestateid accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opgetdirdelegation() *GET_DIR_DELEGATION4args { switch u.Argop { case OP_GET_DIR_DELEGATION: if v, ok := u.U.(*GET_DIR_DELEGATION4args); ok { return v } else { var zero GET_DIR_DELEGATION4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opgetdirdelegation accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opgetdeviceinfo() *GETDEVICEINFO4args { switch u.Argop { case OP_GETDEVICEINFO: if v, ok := u.U.(*GETDEVICEINFO4args); ok { return v } else { var zero GETDEVICEINFO4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opgetdeviceinfo accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opgetdevicelist() *GETDEVICELIST4args { switch u.Argop { case OP_GETDEVICELIST: if v, ok := u.U.(*GETDEVICELIST4args); ok { return v } else { var zero GETDEVICELIST4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opgetdevicelist accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplayoutcommit() *LAYOUTCOMMIT4args { switch u.Argop { case OP_LAYOUTCOMMIT: if v, ok := u.U.(*LAYOUTCOMMIT4args); ok { return v } else { var zero LAYOUTCOMMIT4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplayoutcommit accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplayoutget() *LAYOUTGET4args { switch u.Argop { case OP_LAYOUTGET: if v, ok := u.U.(*LAYOUTGET4args); ok { return v } else { var zero LAYOUTGET4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplayoutget accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Oplayoutreturn() *LAYOUTRETURN4args { switch u.Argop { case OP_LAYOUTRETURN: if v, ok := u.U.(*LAYOUTRETURN4args); ok { return v } else { var zero LAYOUTRETURN4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Oplayoutreturn accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsecinfononame() *SECINFO_NO_NAME4args { switch u.Argop { case OP_SECINFO_NO_NAME: if v, ok := u.U.(*SECINFO_NO_NAME4args); ok { return v } else { var zero SECINFO_NO_NAME4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsecinfononame accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsequence() *SEQUENCE4args { switch u.Argop { case OP_SEQUENCE: if v, ok := u.U.(*SEQUENCE4args); ok { return v } else { var zero SEQUENCE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsequence accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opsetssv() *SET_SSV4args { switch u.Argop { case OP_SET_SSV: if v, ok := u.U.(*SET_SSV4args); ok { return v } else { var zero SET_SSV4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opsetssv accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opteststateid() *TEST_STATEID4args { switch u.Argop { case OP_TEST_STATEID: if v, ok := u.U.(*TEST_STATEID4args); ok { return v } else { var zero TEST_STATEID4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opteststateid accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opwantdelegation() *WANT_DELEGATION4args { switch u.Argop { case OP_WANT_DELEGATION: if v, ok := u.U.(*WANT_DELEGATION4args); ok { return v } else { var zero WANT_DELEGATION4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opwantdelegation accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opdestroyclientid() *DESTROY_CLIENTID4args { switch u.Argop { case OP_DESTROY_CLIENTID: if v, ok := u.U.(*DESTROY_CLIENTID4args); ok { return v } else { var zero DESTROY_CLIENTID4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opdestroyclientid accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_argop4) Opreclaimcomplete() *RECLAIM_COMPLETE4args { switch u.Argop { case OP_RECLAIM_COMPLETE: if v, ok := u.U.(*RECLAIM_COMPLETE4args); ok { return v } else { var zero RECLAIM_COMPLETE4args u.U = &zero return &zero } default: XdrPanic("Nfs_argop4.Opreclaimcomplete accessed when Argop == %v", u.Argop) return nil } } func (u Nfs_argop4) XdrValid() bool { switch u.Argop { case OP_ACCESS,OP_CLOSE,OP_COMMIT,OP_CREATE,OP_DELEGPURGE,OP_DELEGRETURN,OP_GETATTR,OP_GETFH,OP_LINK,OP_LOCK,OP_LOCKT,OP_LOCKU,OP_LOOKUP,OP_LOOKUPP,OP_NVERIFY,OP_OPEN,OP_OPENATTR,OP_OPEN_CONFIRM,OP_OPEN_DOWNGRADE,OP_PUTFH,OP_PUTPUBFH,OP_PUTROOTFH,OP_READ,OP_READDIR,OP_READLINK,OP_REMOVE,OP_RENAME,OP_RENEW,OP_RESTOREFH,OP_SAVEFH,OP_SECINFO,OP_SETATTR,OP_SETCLIENTID,OP_SETCLIENTID_CONFIRM,OP_VERIFY,OP_WRITE,OP_RELEASE_LOCKOWNER,OP_CREATE_SESSION,OP_DESTROY_SESSION,OP_FREE_STATEID,OP_GET_DIR_DELEGATION,OP_GETDEVICEINFO,OP_GETDEVICELIST,OP_LAYOUTCOMMIT,OP_LAYOUTGET,OP_LAYOUTRETURN,OP_SECINFO_NO_NAME,OP_SEQUENCE,OP_SET_SSV,OP_TEST_STATEID,OP_WANT_DELEGATION,OP_DESTROY_CLIENTID,OP_RECLAIM_COMPLETE,OP_ILLEGAL: return true } return false } func (u *Nfs_argop4) XdrUnionTag() XdrNum32 { return XDR_Nfs_opnum4(&u.Argop) } func (u *Nfs_argop4) XdrUnionTagName() string { return "Argop" } func (u *Nfs_argop4) XdrUnionBody() XdrType { switch u.Argop { case OP_ACCESS: return XDR_ACCESS4args(u.Opaccess()) case OP_CLOSE: return XDR_CLOSE4args(u.Opclose()) case OP_COMMIT: return XDR_COMMIT4args(u.Opcommit()) case OP_CREATE: return XDR_CREATE4args(u.Opcreate()) case OP_DELEGPURGE: return XDR_DELEGPURGE4args(u.Opdelegpurge()) case OP_DELEGRETURN: return XDR_DELEGRETURN4args(u.Opdelegreturn()) case OP_GETATTR: return XDR_GETATTR4args(u.Opgetattr()) case OP_GETFH: return nil case OP_LINK: return XDR_LINK4args(u.Oplink()) case OP_LOCK: return XDR_LOCK4args(u.Oplock()) case OP_LOCKT: return XDR_LOCKT4args(u.Oplockt()) case OP_LOCKU: return XDR_LOCKU4args(u.Oplocku()) case OP_LOOKUP: return XDR_LOOKUP4args(u.Oplookup()) case OP_LOOKUPP: return nil case OP_NVERIFY: return XDR_NVERIFY4args(u.Opnverify()) case OP_OPEN: return XDR_OPEN4args(u.Opopen()) case OP_OPENATTR: return XDR_OPENATTR4args(u.Opopenattr()) case OP_OPEN_CONFIRM: return XDR_OPEN_CONFIRM4args(u.Opopen_confirm()) case OP_OPEN_DOWNGRADE: return XDR_OPEN_DOWNGRADE4args(u.Opopen_downgrade()) case OP_PUTFH: return XDR_PUTFH4args(u.Opputfh()) case OP_PUTPUBFH: return nil case OP_PUTROOTFH: return nil case OP_READ: return XDR_READ4args(u.Opread()) case OP_READDIR: return XDR_READDIR4args(u.Opreaddir()) case OP_READLINK: return nil case OP_REMOVE: return XDR_REMOVE4args(u.Opremove()) case OP_RENAME: return XDR_RENAME4args(u.Oprename()) case OP_RENEW: return XDR_RENEW4args(u.Oprenew()) case OP_RESTOREFH: return nil case OP_SAVEFH: return nil case OP_SECINFO: return XDR_SECINFO4args(u.Opsecinfo()) case OP_SETATTR: return XDR_SETATTR4args(u.Opsetattr()) case OP_SETCLIENTID: return XDR_SETCLIENTID4args(u.Opsetclientid()) case OP_SETCLIENTID_CONFIRM: return XDR_SETCLIENTID_CONFIRM4args(u.Opsetclientid_confirm()) case OP_VERIFY: return XDR_VERIFY4args(u.Opverify()) case OP_WRITE: return XDR_WRITE4args(u.Opwrite()) case OP_RELEASE_LOCKOWNER: return XDR_RELEASE_LOCKOWNER4args(u.Oprelease_lockowner()) case OP_CREATE_SESSION: return XDR_CREATE_SESSION4args(u.Opcreatesession()) case OP_DESTROY_SESSION: return XDR_DESTROY_SESSION4args(u.Opdestroysession()) case OP_FREE_STATEID: return XDR_FREE_STATEID4args(u.Opfreestateid()) case OP_GET_DIR_DELEGATION: return XDR_GET_DIR_DELEGATION4args(u.Opgetdirdelegation()) case OP_GETDEVICEINFO: return XDR_GETDEVICEINFO4args(u.Opgetdeviceinfo()) case OP_GETDEVICELIST: return XDR_GETDEVICELIST4args(u.Opgetdevicelist()) case OP_LAYOUTCOMMIT: return XDR_LAYOUTCOMMIT4args(u.Oplayoutcommit()) case OP_LAYOUTGET: return XDR_LAYOUTGET4args(u.Oplayoutget()) case OP_LAYOUTRETURN: return XDR_LAYOUTRETURN4args(u.Oplayoutreturn()) case OP_SECINFO_NO_NAME: return XDR_SECINFO_NO_NAME4args(u.Opsecinfononame()) case OP_SEQUENCE: return XDR_SEQUENCE4args(u.Opsequence()) case OP_SET_SSV: return XDR_SET_SSV4args(u.Opsetssv()) case OP_TEST_STATEID: return XDR_TEST_STATEID4args(u.Opteststateid()) case OP_WANT_DELEGATION: return XDR_WANT_DELEGATION4args(u.Opwantdelegation()) case OP_DESTROY_CLIENTID: return XDR_DESTROY_CLIENTID4args(u.Opdestroyclientid()) case OP_RECLAIM_COMPLETE: return XDR_RECLAIM_COMPLETE4args(u.Opreclaimcomplete()) case OP_ILLEGAL: return nil } return nil } func (u *Nfs_argop4) XdrUnionBodyName() string { switch u.Argop { case OP_ACCESS: return "Opaccess" case OP_CLOSE: return "Opclose" case OP_COMMIT: return "Opcommit" case OP_CREATE: return "Opcreate" case OP_DELEGPURGE: return "Opdelegpurge" case OP_DELEGRETURN: return "Opdelegreturn" case OP_GETATTR: return "Opgetattr" case OP_GETFH: return "" case OP_LINK: return "Oplink" case OP_LOCK: return "Oplock" case OP_LOCKT: return "Oplockt" case OP_LOCKU: return "Oplocku" case OP_LOOKUP: return "Oplookup" case OP_LOOKUPP: return "" case OP_NVERIFY: return "Opnverify" case OP_OPEN: return "Opopen" case OP_OPENATTR: return "Opopenattr" case OP_OPEN_CONFIRM: return "Opopen_confirm" case OP_OPEN_DOWNGRADE: return "Opopen_downgrade" case OP_PUTFH: return "Opputfh" case OP_PUTPUBFH: return "" case OP_PUTROOTFH: return "" case OP_READ: return "Opread" case OP_READDIR: return "Opreaddir" case OP_READLINK: return "" case OP_REMOVE: return "Opremove" case OP_RENAME: return "Oprename" case OP_RENEW: return "Oprenew" case OP_RESTOREFH: return "" case OP_SAVEFH: return "" case OP_SECINFO: return "Opsecinfo" case OP_SETATTR: return "Opsetattr" case OP_SETCLIENTID: return "Opsetclientid" case OP_SETCLIENTID_CONFIRM: return "Opsetclientid_confirm" case OP_VERIFY: return "Opverify" case OP_WRITE: return "Opwrite" case OP_RELEASE_LOCKOWNER: return "Oprelease_lockowner" case OP_CREATE_SESSION: return "Opcreatesession" case OP_DESTROY_SESSION: return "Opdestroysession" case OP_FREE_STATEID: return "Opfreestateid" case OP_GET_DIR_DELEGATION: return "Opgetdirdelegation" case OP_GETDEVICEINFO: return "Opgetdeviceinfo" case OP_GETDEVICELIST: return "Opgetdevicelist" case OP_LAYOUTCOMMIT: return "Oplayoutcommit" case OP_LAYOUTGET: return "Oplayoutget" case OP_LAYOUTRETURN: return "Oplayoutreturn" case OP_SECINFO_NO_NAME: return "Opsecinfononame" case OP_SEQUENCE: return "Opsequence" case OP_SET_SSV: return "Opsetssv" case OP_TEST_STATEID: return "Opteststateid" case OP_WANT_DELEGATION: return "Opwantdelegation" case OP_DESTROY_CLIENTID: return "Opdestroyclientid" case OP_RECLAIM_COMPLETE: return "Opreclaimcomplete" case OP_ILLEGAL: return "" } return "" } type XdrType_Nfs_argop4 = *Nfs_argop4 func (v *Nfs_argop4) XdrPointer() interface{} { return v } func (Nfs_argop4) XdrTypeName() string { return "Nfs_argop4" } func (v Nfs_argop4) XdrValue() interface{} { return v } func (v *Nfs_argop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Nfs_argop4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfs_opnum4(&u.Argop).XdrMarshal(x, x.Sprintf("%sargop", name)) switch u.Argop { case OP_ACCESS: x.Marshal(x.Sprintf("%sopaccess", name), XDR_ACCESS4args(u.Opaccess())) return case OP_CLOSE: x.Marshal(x.Sprintf("%sopclose", name), XDR_CLOSE4args(u.Opclose())) return case OP_COMMIT: x.Marshal(x.Sprintf("%sopcommit", name), XDR_COMMIT4args(u.Opcommit())) return case OP_CREATE: x.Marshal(x.Sprintf("%sopcreate", name), XDR_CREATE4args(u.Opcreate())) return case OP_DELEGPURGE: x.Marshal(x.Sprintf("%sopdelegpurge", name), XDR_DELEGPURGE4args(u.Opdelegpurge())) return case OP_DELEGRETURN: x.Marshal(x.Sprintf("%sopdelegreturn", name), XDR_DELEGRETURN4args(u.Opdelegreturn())) return case OP_GETATTR: x.Marshal(x.Sprintf("%sopgetattr", name), XDR_GETATTR4args(u.Opgetattr())) return case OP_GETFH: return case OP_LINK: x.Marshal(x.Sprintf("%soplink", name), XDR_LINK4args(u.Oplink())) return case OP_LOCK: x.Marshal(x.Sprintf("%soplock", name), XDR_LOCK4args(u.Oplock())) return case OP_LOCKT: x.Marshal(x.Sprintf("%soplockt", name), XDR_LOCKT4args(u.Oplockt())) return case OP_LOCKU: x.Marshal(x.Sprintf("%soplocku", name), XDR_LOCKU4args(u.Oplocku())) return case OP_LOOKUP: x.Marshal(x.Sprintf("%soplookup", name), XDR_LOOKUP4args(u.Oplookup())) return case OP_LOOKUPP: return case OP_NVERIFY: x.Marshal(x.Sprintf("%sopnverify", name), XDR_NVERIFY4args(u.Opnverify())) return case OP_OPEN: x.Marshal(x.Sprintf("%sopopen", name), XDR_OPEN4args(u.Opopen())) return case OP_OPENATTR: x.Marshal(x.Sprintf("%sopopenattr", name), XDR_OPENATTR4args(u.Opopenattr())) return case OP_OPEN_CONFIRM: x.Marshal(x.Sprintf("%sopopen_confirm", name), XDR_OPEN_CONFIRM4args(u.Opopen_confirm())) return case OP_OPEN_DOWNGRADE: x.Marshal(x.Sprintf("%sopopen_downgrade", name), XDR_OPEN_DOWNGRADE4args(u.Opopen_downgrade())) return case OP_PUTFH: x.Marshal(x.Sprintf("%sopputfh", name), XDR_PUTFH4args(u.Opputfh())) return case OP_PUTPUBFH: return case OP_PUTROOTFH: return case OP_READ: x.Marshal(x.Sprintf("%sopread", name), XDR_READ4args(u.Opread())) return case OP_READDIR: x.Marshal(x.Sprintf("%sopreaddir", name), XDR_READDIR4args(u.Opreaddir())) return case OP_READLINK: return case OP_REMOVE: x.Marshal(x.Sprintf("%sopremove", name), XDR_REMOVE4args(u.Opremove())) return case OP_RENAME: x.Marshal(x.Sprintf("%soprename", name), XDR_RENAME4args(u.Oprename())) return case OP_RENEW: x.Marshal(x.Sprintf("%soprenew", name), XDR_RENEW4args(u.Oprenew())) return case OP_RESTOREFH: return case OP_SAVEFH: return case OP_SECINFO: x.Marshal(x.Sprintf("%sopsecinfo", name), XDR_SECINFO4args(u.Opsecinfo())) return case OP_SETATTR: x.Marshal(x.Sprintf("%sopsetattr", name), XDR_SETATTR4args(u.Opsetattr())) return case OP_SETCLIENTID: x.Marshal(x.Sprintf("%sopsetclientid", name), XDR_SETCLIENTID4args(u.Opsetclientid())) return case OP_SETCLIENTID_CONFIRM: x.Marshal(x.Sprintf("%sopsetclientid_confirm", name), XDR_SETCLIENTID_CONFIRM4args(u.Opsetclientid_confirm())) return case OP_VERIFY: x.Marshal(x.Sprintf("%sopverify", name), XDR_VERIFY4args(u.Opverify())) return case OP_WRITE: x.Marshal(x.Sprintf("%sopwrite", name), XDR_WRITE4args(u.Opwrite())) return case OP_RELEASE_LOCKOWNER: x.Marshal(x.Sprintf("%soprelease_lockowner", name), XDR_RELEASE_LOCKOWNER4args(u.Oprelease_lockowner())) return case OP_CREATE_SESSION: x.Marshal(x.Sprintf("%sopcreatesession", name), XDR_CREATE_SESSION4args(u.Opcreatesession())) return case OP_DESTROY_SESSION: x.Marshal(x.Sprintf("%sopdestroysession", name), XDR_DESTROY_SESSION4args(u.Opdestroysession())) return case OP_FREE_STATEID: x.Marshal(x.Sprintf("%sopfreestateid", name), XDR_FREE_STATEID4args(u.Opfreestateid())) return case OP_GET_DIR_DELEGATION: x.Marshal(x.Sprintf("%sopgetdirdelegation", name), XDR_GET_DIR_DELEGATION4args(u.Opgetdirdelegation())) return case OP_GETDEVICEINFO: x.Marshal(x.Sprintf("%sopgetdeviceinfo", name), XDR_GETDEVICEINFO4args(u.Opgetdeviceinfo())) return case OP_GETDEVICELIST: x.Marshal(x.Sprintf("%sopgetdevicelist", name), XDR_GETDEVICELIST4args(u.Opgetdevicelist())) return case OP_LAYOUTCOMMIT: x.Marshal(x.Sprintf("%soplayoutcommit", name), XDR_LAYOUTCOMMIT4args(u.Oplayoutcommit())) return case OP_LAYOUTGET: x.Marshal(x.Sprintf("%soplayoutget", name), XDR_LAYOUTGET4args(u.Oplayoutget())) return case OP_LAYOUTRETURN: x.Marshal(x.Sprintf("%soplayoutreturn", name), XDR_LAYOUTRETURN4args(u.Oplayoutreturn())) return case OP_SECINFO_NO_NAME: x.Marshal(x.Sprintf("%sopsecinfononame", name), XDR_SECINFO_NO_NAME4args(u.Opsecinfononame())) return case OP_SEQUENCE: x.Marshal(x.Sprintf("%sopsequence", name), XDR_SEQUENCE4args(u.Opsequence())) return case OP_SET_SSV: x.Marshal(x.Sprintf("%sopsetssv", name), XDR_SET_SSV4args(u.Opsetssv())) return case OP_TEST_STATEID: x.Marshal(x.Sprintf("%sopteststateid", name), XDR_TEST_STATEID4args(u.Opteststateid())) return case OP_WANT_DELEGATION: x.Marshal(x.Sprintf("%sopwantdelegation", name), XDR_WANT_DELEGATION4args(u.Opwantdelegation())) return case OP_DESTROY_CLIENTID: x.Marshal(x.Sprintf("%sopdestroyclientid", name), XDR_DESTROY_CLIENTID4args(u.Opdestroyclientid())) return case OP_RECLAIM_COMPLETE: x.Marshal(x.Sprintf("%sopreclaimcomplete", name), XDR_RECLAIM_COMPLETE4args(u.Opreclaimcomplete())) return case OP_ILLEGAL: return } XdrPanic("invalid Argop (%v) in Nfs_argop4", u.Argop) } func (v *Nfs_argop4) XdrInitialize() { var zero Nfs_opnum4 switch zero { case OP_ACCESS, OP_CLOSE, OP_COMMIT, OP_CREATE, OP_DELEGPURGE, OP_DELEGRETURN, OP_GETATTR, OP_GETFH, OP_LINK, OP_LOCK, OP_LOCKT, OP_LOCKU, OP_LOOKUP, OP_LOOKUPP, OP_NVERIFY, OP_OPEN, OP_OPENATTR, OP_OPEN_CONFIRM, OP_OPEN_DOWNGRADE, OP_PUTFH, OP_PUTPUBFH, OP_PUTROOTFH, OP_READ, OP_READDIR, OP_READLINK, OP_REMOVE, OP_RENAME, OP_RENEW, OP_RESTOREFH, OP_SAVEFH, OP_SECINFO, OP_SETATTR, OP_SETCLIENTID, OP_SETCLIENTID_CONFIRM, OP_VERIFY, OP_WRITE, OP_RELEASE_LOCKOWNER, OP_CREATE_SESSION, OP_DESTROY_SESSION, OP_FREE_STATEID, OP_GET_DIR_DELEGATION, OP_GETDEVICEINFO, OP_GETDEVICELIST, OP_LAYOUTCOMMIT, OP_LAYOUTGET, OP_LAYOUTRETURN, OP_SECINFO_NO_NAME, OP_SEQUENCE, OP_SET_SSV, OP_TEST_STATEID, OP_WANT_DELEGATION, OP_DESTROY_CLIENTID, OP_RECLAIM_COMPLETE, OP_ILLEGAL: default: if v.Argop == zero { v.Argop = OP_ACCESS } } } func XDR_Nfs_argop4(v *Nfs_argop4) *Nfs_argop4 { return v} var _XdrTags_Nfs_resop4 = map[int32]bool{ XdrToI32(OP_ACCESS): true, XdrToI32(OP_CLOSE): true, XdrToI32(OP_COMMIT): true, XdrToI32(OP_CREATE): true, XdrToI32(OP_DELEGPURGE): true, XdrToI32(OP_DELEGRETURN): true, XdrToI32(OP_GETATTR): true, XdrToI32(OP_GETFH): true, XdrToI32(OP_LINK): true, XdrToI32(OP_LOCK): true, XdrToI32(OP_LOCKT): true, XdrToI32(OP_LOCKU): true, XdrToI32(OP_LOOKUP): true, XdrToI32(OP_LOOKUPP): true, XdrToI32(OP_NVERIFY): true, XdrToI32(OP_OPEN): true, XdrToI32(OP_OPENATTR): true, XdrToI32(OP_OPEN_CONFIRM): true, XdrToI32(OP_OPEN_DOWNGRADE): true, XdrToI32(OP_PUTFH): true, XdrToI32(OP_PUTPUBFH): true, XdrToI32(OP_PUTROOTFH): true, XdrToI32(OP_READ): true, XdrToI32(OP_READDIR): true, XdrToI32(OP_READLINK): true, XdrToI32(OP_REMOVE): true, XdrToI32(OP_RENAME): true, XdrToI32(OP_RENEW): true, XdrToI32(OP_RESTOREFH): true, XdrToI32(OP_SAVEFH): true, XdrToI32(OP_SECINFO): true, XdrToI32(OP_SETATTR): true, XdrToI32(OP_SETCLIENTID): true, XdrToI32(OP_SETCLIENTID_CONFIRM): true, XdrToI32(OP_VERIFY): true, XdrToI32(OP_WRITE): true, XdrToI32(OP_RELEASE_LOCKOWNER): true, XdrToI32(OP_CREATE_SESSION): true, XdrToI32(OP_DESTROY_SESSION): true, XdrToI32(OP_FREE_STATEID): true, XdrToI32(OP_GET_DIR_DELEGATION): true, XdrToI32(OP_GETDEVICEINFO): true, XdrToI32(OP_GETDEVICELIST): true, XdrToI32(OP_LAYOUTCOMMIT): true, XdrToI32(OP_LAYOUTGET): true, XdrToI32(OP_LAYOUTRETURN): true, XdrToI32(OP_SECINFO_NO_NAME): true, XdrToI32(OP_SEQUENCE): true, XdrToI32(OP_SET_SSV): true, XdrToI32(OP_TEST_STATEID): true, XdrToI32(OP_WANT_DELEGATION): true, XdrToI32(OP_DESTROY_CLIENTID): true, XdrToI32(OP_RECLAIM_COMPLETE): true, XdrToI32(OP_ILLEGAL): true, } func (_ Nfs_resop4) XdrValidTags() map[int32]bool { return _XdrTags_Nfs_resop4 } func (u *Nfs_resop4) Opaccess() *ACCESS4res { switch u.Resop { case OP_ACCESS: if v, ok := u.U.(*ACCESS4res); ok { return v } else { var zero ACCESS4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opaccess accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opclose() *CLOSE4res { switch u.Resop { case OP_CLOSE: if v, ok := u.U.(*CLOSE4res); ok { return v } else { var zero CLOSE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opclose accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opcommit() *COMMIT4res { switch u.Resop { case OP_COMMIT: if v, ok := u.U.(*COMMIT4res); ok { return v } else { var zero COMMIT4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opcommit accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opcreate() *CREATE4res { switch u.Resop { case OP_CREATE: if v, ok := u.U.(*CREATE4res); ok { return v } else { var zero CREATE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opcreate accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opdelegpurge() *DELEGPURGE4res { switch u.Resop { case OP_DELEGPURGE: if v, ok := u.U.(*DELEGPURGE4res); ok { return v } else { var zero DELEGPURGE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opdelegpurge accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opdelegreturn() *DELEGRETURN4res { switch u.Resop { case OP_DELEGRETURN: if v, ok := u.U.(*DELEGRETURN4res); ok { return v } else { var zero DELEGRETURN4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opdelegreturn accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opgetattr() *GETATTR4res { switch u.Resop { case OP_GETATTR: if v, ok := u.U.(*GETATTR4res); ok { return v } else { var zero GETATTR4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opgetattr accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opgetfh() *GETFH4res { switch u.Resop { case OP_GETFH: if v, ok := u.U.(*GETFH4res); ok { return v } else { var zero GETFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opgetfh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplink() *LINK4res { switch u.Resop { case OP_LINK: if v, ok := u.U.(*LINK4res); ok { return v } else { var zero LINK4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplink accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplock() *LOCK4res { switch u.Resop { case OP_LOCK: if v, ok := u.U.(*LOCK4res); ok { return v } else { var zero LOCK4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplock accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplockt() *LOCKT4res { switch u.Resop { case OP_LOCKT: if v, ok := u.U.(*LOCKT4res); ok { return v } else { var zero LOCKT4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplockt accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplocku() *LOCKU4res { switch u.Resop { case OP_LOCKU: if v, ok := u.U.(*LOCKU4res); ok { return v } else { var zero LOCKU4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplocku accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplookup() *LOOKUP4res { switch u.Resop { case OP_LOOKUP: if v, ok := u.U.(*LOOKUP4res); ok { return v } else { var zero LOOKUP4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplookup accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplookupp() *LOOKUPP4res { switch u.Resop { case OP_LOOKUPP: if v, ok := u.U.(*LOOKUPP4res); ok { return v } else { var zero LOOKUPP4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplookupp accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opnverify() *NVERIFY4res { switch u.Resop { case OP_NVERIFY: if v, ok := u.U.(*NVERIFY4res); ok { return v } else { var zero NVERIFY4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opnverify accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opopen() *OPEN4res { switch u.Resop { case OP_OPEN: if v, ok := u.U.(*OPEN4res); ok { return v } else { var zero OPEN4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opopen accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opopenattr() *OPENATTR4res { switch u.Resop { case OP_OPENATTR: if v, ok := u.U.(*OPENATTR4res); ok { return v } else { var zero OPENATTR4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opopenattr accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opopen_confirm() *OPEN_CONFIRM4res { switch u.Resop { case OP_OPEN_CONFIRM: if v, ok := u.U.(*OPEN_CONFIRM4res); ok { return v } else { var zero OPEN_CONFIRM4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opopen_confirm accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opopen_downgrade() *OPEN_DOWNGRADE4res { switch u.Resop { case OP_OPEN_DOWNGRADE: if v, ok := u.U.(*OPEN_DOWNGRADE4res); ok { return v } else { var zero OPEN_DOWNGRADE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opopen_downgrade accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opputfh() *PUTFH4res { switch u.Resop { case OP_PUTFH: if v, ok := u.U.(*PUTFH4res); ok { return v } else { var zero PUTFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opputfh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opputpubfh() *PUTPUBFH4res { switch u.Resop { case OP_PUTPUBFH: if v, ok := u.U.(*PUTPUBFH4res); ok { return v } else { var zero PUTPUBFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opputpubfh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opputrootfh() *PUTROOTFH4res { switch u.Resop { case OP_PUTROOTFH: if v, ok := u.U.(*PUTROOTFH4res); ok { return v } else { var zero PUTROOTFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opputrootfh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opread() *READ4res { switch u.Resop { case OP_READ: if v, ok := u.U.(*READ4res); ok { return v } else { var zero READ4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opread accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opreaddir() *READDIR4res { switch u.Resop { case OP_READDIR: if v, ok := u.U.(*READDIR4res); ok { return v } else { var zero READDIR4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opreaddir accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opreadlink() *READLINK4res { switch u.Resop { case OP_READLINK: if v, ok := u.U.(*READLINK4res); ok { return v } else { var zero READLINK4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opreadlink accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opremove() *REMOVE4res { switch u.Resop { case OP_REMOVE: if v, ok := u.U.(*REMOVE4res); ok { return v } else { var zero REMOVE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opremove accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oprename() *RENAME4res { switch u.Resop { case OP_RENAME: if v, ok := u.U.(*RENAME4res); ok { return v } else { var zero RENAME4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oprename accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oprenew() *RENEW4res { switch u.Resop { case OP_RENEW: if v, ok := u.U.(*RENEW4res); ok { return v } else { var zero RENEW4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oprenew accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oprestorefh() *RESTOREFH4res { switch u.Resop { case OP_RESTOREFH: if v, ok := u.U.(*RESTOREFH4res); ok { return v } else { var zero RESTOREFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oprestorefh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsavefh() *SAVEFH4res { switch u.Resop { case OP_SAVEFH: if v, ok := u.U.(*SAVEFH4res); ok { return v } else { var zero SAVEFH4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsavefh accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsecinfo() *SECINFO4res { switch u.Resop { case OP_SECINFO: if v, ok := u.U.(*SECINFO4res); ok { return v } else { var zero SECINFO4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsecinfo accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsetattr() *SETATTR4res { switch u.Resop { case OP_SETATTR: if v, ok := u.U.(*SETATTR4res); ok { return v } else { var zero SETATTR4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsetattr accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsetclientid() *SETCLIENTID4res { switch u.Resop { case OP_SETCLIENTID: if v, ok := u.U.(*SETCLIENTID4res); ok { return v } else { var zero SETCLIENTID4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsetclientid accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsetclientid_confirm() *SETCLIENTID_CONFIRM4res { switch u.Resop { case OP_SETCLIENTID_CONFIRM: if v, ok := u.U.(*SETCLIENTID_CONFIRM4res); ok { return v } else { var zero SETCLIENTID_CONFIRM4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsetclientid_confirm accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opverify() *VERIFY4res { switch u.Resop { case OP_VERIFY: if v, ok := u.U.(*VERIFY4res); ok { return v } else { var zero VERIFY4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opverify accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opwrite() *WRITE4res { switch u.Resop { case OP_WRITE: if v, ok := u.U.(*WRITE4res); ok { return v } else { var zero WRITE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opwrite accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oprelease_lockowner() *RELEASE_LOCKOWNER4res { switch u.Resop { case OP_RELEASE_LOCKOWNER: if v, ok := u.U.(*RELEASE_LOCKOWNER4res); ok { return v } else { var zero RELEASE_LOCKOWNER4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oprelease_lockowner accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opcreatesession() *CREATE_SESSION4res { switch u.Resop { case OP_CREATE_SESSION: if v, ok := u.U.(*CREATE_SESSION4res); ok { return v } else { var zero CREATE_SESSION4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opcreatesession accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opdestroysession() *DESTROY_SESSION4res { switch u.Resop { case OP_DESTROY_SESSION: if v, ok := u.U.(*DESTROY_SESSION4res); ok { return v } else { var zero DESTROY_SESSION4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opdestroysession accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opfreestateid() *FREE_STATEID4res { switch u.Resop { case OP_FREE_STATEID: if v, ok := u.U.(*FREE_STATEID4res); ok { return v } else { var zero FREE_STATEID4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opfreestateid accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opgetdirdelegation() *GET_DIR_DELEGATION4res { switch u.Resop { case OP_GET_DIR_DELEGATION: if v, ok := u.U.(*GET_DIR_DELEGATION4res); ok { return v } else { var zero GET_DIR_DELEGATION4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opgetdirdelegation accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opgetdeviceinfo() *GETDEVICEINFO4res { switch u.Resop { case OP_GETDEVICEINFO: if v, ok := u.U.(*GETDEVICEINFO4res); ok { return v } else { var zero GETDEVICEINFO4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opgetdeviceinfo accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opgetdevicelist() *GETDEVICELIST4res { switch u.Resop { case OP_GETDEVICELIST: if v, ok := u.U.(*GETDEVICELIST4res); ok { return v } else { var zero GETDEVICELIST4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opgetdevicelist accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplayoutcommit() *LAYOUTCOMMIT4res { switch u.Resop { case OP_LAYOUTCOMMIT: if v, ok := u.U.(*LAYOUTCOMMIT4res); ok { return v } else { var zero LAYOUTCOMMIT4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplayoutcommit accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplayoutget() *LAYOUTGET4res { switch u.Resop { case OP_LAYOUTGET: if v, ok := u.U.(*LAYOUTGET4res); ok { return v } else { var zero LAYOUTGET4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplayoutget accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Oplayoutreturn() *LAYOUTRETURN4res { switch u.Resop { case OP_LAYOUTRETURN: if v, ok := u.U.(*LAYOUTRETURN4res); ok { return v } else { var zero LAYOUTRETURN4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Oplayoutreturn accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsecinfononame() *SECINFO_NO_NAME4res { switch u.Resop { case OP_SECINFO_NO_NAME: if v, ok := u.U.(*SECINFO_NO_NAME4res); ok { return v } else { var zero SECINFO_NO_NAME4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsecinfononame accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsequence() *SEQUENCE4res { switch u.Resop { case OP_SEQUENCE: if v, ok := u.U.(*SEQUENCE4res); ok { return v } else { var zero SEQUENCE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsequence accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opsetssv() *SET_SSV4res { switch u.Resop { case OP_SET_SSV: if v, ok := u.U.(*SET_SSV4res); ok { return v } else { var zero SET_SSV4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opsetssv accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opteststateid() *TEST_STATEID4res { switch u.Resop { case OP_TEST_STATEID: if v, ok := u.U.(*TEST_STATEID4res); ok { return v } else { var zero TEST_STATEID4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opteststateid accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opwantdelegation() *WANT_DELEGATION4res { switch u.Resop { case OP_WANT_DELEGATION: if v, ok := u.U.(*WANT_DELEGATION4res); ok { return v } else { var zero WANT_DELEGATION4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opwantdelegation accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opdestroyclientid() *DESTROY_CLIENTID4res { switch u.Resop { case OP_DESTROY_CLIENTID: if v, ok := u.U.(*DESTROY_CLIENTID4res); ok { return v } else { var zero DESTROY_CLIENTID4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opdestroyclientid accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opreclaimcomplete() *RECLAIM_COMPLETE4res { switch u.Resop { case OP_RECLAIM_COMPLETE: if v, ok := u.U.(*RECLAIM_COMPLETE4res); ok { return v } else { var zero RECLAIM_COMPLETE4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opreclaimcomplete accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_resop4) Opillegal() *ILLEGAL4res { switch u.Resop { case OP_ILLEGAL: if v, ok := u.U.(*ILLEGAL4res); ok { return v } else { var zero ILLEGAL4res u.U = &zero return &zero } default: XdrPanic("Nfs_resop4.Opillegal accessed when Resop == %v", u.Resop) return nil } } func (u Nfs_resop4) XdrValid() bool { switch u.Resop { case OP_ACCESS,OP_CLOSE,OP_COMMIT,OP_CREATE,OP_DELEGPURGE,OP_DELEGRETURN,OP_GETATTR,OP_GETFH,OP_LINK,OP_LOCK,OP_LOCKT,OP_LOCKU,OP_LOOKUP,OP_LOOKUPP,OP_NVERIFY,OP_OPEN,OP_OPENATTR,OP_OPEN_CONFIRM,OP_OPEN_DOWNGRADE,OP_PUTFH,OP_PUTPUBFH,OP_PUTROOTFH,OP_READ,OP_READDIR,OP_READLINK,OP_REMOVE,OP_RENAME,OP_RENEW,OP_RESTOREFH,OP_SAVEFH,OP_SECINFO,OP_SETATTR,OP_SETCLIENTID,OP_SETCLIENTID_CONFIRM,OP_VERIFY,OP_WRITE,OP_RELEASE_LOCKOWNER,OP_CREATE_SESSION,OP_DESTROY_SESSION,OP_FREE_STATEID,OP_GET_DIR_DELEGATION,OP_GETDEVICEINFO,OP_GETDEVICELIST,OP_LAYOUTCOMMIT,OP_LAYOUTGET,OP_LAYOUTRETURN,OP_SECINFO_NO_NAME,OP_SEQUENCE,OP_SET_SSV,OP_TEST_STATEID,OP_WANT_DELEGATION,OP_DESTROY_CLIENTID,OP_RECLAIM_COMPLETE,OP_ILLEGAL: return true } return false } func (u *Nfs_resop4) XdrUnionTag() XdrNum32 { return XDR_Nfs_opnum4(&u.Resop) } func (u *Nfs_resop4) XdrUnionTagName() string { return "Resop" } func (u *Nfs_resop4) XdrUnionBody() XdrType { switch u.Resop { case OP_ACCESS: return XDR_ACCESS4res(u.Opaccess()) case OP_CLOSE: return XDR_CLOSE4res(u.Opclose()) case OP_COMMIT: return XDR_COMMIT4res(u.Opcommit()) case OP_CREATE: return XDR_CREATE4res(u.Opcreate()) case OP_DELEGPURGE: return XDR_DELEGPURGE4res(u.Opdelegpurge()) case OP_DELEGRETURN: return XDR_DELEGRETURN4res(u.Opdelegreturn()) case OP_GETATTR: return XDR_GETATTR4res(u.Opgetattr()) case OP_GETFH: return XDR_GETFH4res(u.Opgetfh()) case OP_LINK: return XDR_LINK4res(u.Oplink()) case OP_LOCK: return XDR_LOCK4res(u.Oplock()) case OP_LOCKT: return XDR_LOCKT4res(u.Oplockt()) case OP_LOCKU: return XDR_LOCKU4res(u.Oplocku()) case OP_LOOKUP: return XDR_LOOKUP4res(u.Oplookup()) case OP_LOOKUPP: return XDR_LOOKUPP4res(u.Oplookupp()) case OP_NVERIFY: return XDR_NVERIFY4res(u.Opnverify()) case OP_OPEN: return XDR_OPEN4res(u.Opopen()) case OP_OPENATTR: return XDR_OPENATTR4res(u.Opopenattr()) case OP_OPEN_CONFIRM: return XDR_OPEN_CONFIRM4res(u.Opopen_confirm()) case OP_OPEN_DOWNGRADE: return XDR_OPEN_DOWNGRADE4res(u.Opopen_downgrade()) case OP_PUTFH: return XDR_PUTFH4res(u.Opputfh()) case OP_PUTPUBFH: return XDR_PUTPUBFH4res(u.Opputpubfh()) case OP_PUTROOTFH: return XDR_PUTROOTFH4res(u.Opputrootfh()) case OP_READ: return XDR_READ4res(u.Opread()) case OP_READDIR: return XDR_READDIR4res(u.Opreaddir()) case OP_READLINK: return XDR_READLINK4res(u.Opreadlink()) case OP_REMOVE: return XDR_REMOVE4res(u.Opremove()) case OP_RENAME: return XDR_RENAME4res(u.Oprename()) case OP_RENEW: return XDR_RENEW4res(u.Oprenew()) case OP_RESTOREFH: return XDR_RESTOREFH4res(u.Oprestorefh()) case OP_SAVEFH: return XDR_SAVEFH4res(u.Opsavefh()) case OP_SECINFO: return XDR_SECINFO4res(u.Opsecinfo()) case OP_SETATTR: return XDR_SETATTR4res(u.Opsetattr()) case OP_SETCLIENTID: return XDR_SETCLIENTID4res(u.Opsetclientid()) case OP_SETCLIENTID_CONFIRM: return XDR_SETCLIENTID_CONFIRM4res(u.Opsetclientid_confirm()) case OP_VERIFY: return XDR_VERIFY4res(u.Opverify()) case OP_WRITE: return XDR_WRITE4res(u.Opwrite()) case OP_RELEASE_LOCKOWNER: return XDR_RELEASE_LOCKOWNER4res(u.Oprelease_lockowner()) case OP_CREATE_SESSION: return XDR_CREATE_SESSION4res(u.Opcreatesession()) case OP_DESTROY_SESSION: return XDR_DESTROY_SESSION4res(u.Opdestroysession()) case OP_FREE_STATEID: return XDR_FREE_STATEID4res(u.Opfreestateid()) case OP_GET_DIR_DELEGATION: return XDR_GET_DIR_DELEGATION4res(u.Opgetdirdelegation()) case OP_GETDEVICEINFO: return XDR_GETDEVICEINFO4res(u.Opgetdeviceinfo()) case OP_GETDEVICELIST: return XDR_GETDEVICELIST4res(u.Opgetdevicelist()) case OP_LAYOUTCOMMIT: return XDR_LAYOUTCOMMIT4res(u.Oplayoutcommit()) case OP_LAYOUTGET: return XDR_LAYOUTGET4res(u.Oplayoutget()) case OP_LAYOUTRETURN: return XDR_LAYOUTRETURN4res(u.Oplayoutreturn()) case OP_SECINFO_NO_NAME: return XDR_SECINFO_NO_NAME4res(u.Opsecinfononame()) case OP_SEQUENCE: return XDR_SEQUENCE4res(u.Opsequence()) case OP_SET_SSV: return XDR_SET_SSV4res(u.Opsetssv()) case OP_TEST_STATEID: return XDR_TEST_STATEID4res(u.Opteststateid()) case OP_WANT_DELEGATION: return XDR_WANT_DELEGATION4res(u.Opwantdelegation()) case OP_DESTROY_CLIENTID: return XDR_DESTROY_CLIENTID4res(u.Opdestroyclientid()) case OP_RECLAIM_COMPLETE: return XDR_RECLAIM_COMPLETE4res(u.Opreclaimcomplete()) case OP_ILLEGAL: return XDR_ILLEGAL4res(u.Opillegal()) } return nil } func (u *Nfs_resop4) XdrUnionBodyName() string { switch u.Resop { case OP_ACCESS: return "Opaccess" case OP_CLOSE: return "Opclose" case OP_COMMIT: return "Opcommit" case OP_CREATE: return "Opcreate" case OP_DELEGPURGE: return "Opdelegpurge" case OP_DELEGRETURN: return "Opdelegreturn" case OP_GETATTR: return "Opgetattr" case OP_GETFH: return "Opgetfh" case OP_LINK: return "Oplink" case OP_LOCK: return "Oplock" case OP_LOCKT: return "Oplockt" case OP_LOCKU: return "Oplocku" case OP_LOOKUP: return "Oplookup" case OP_LOOKUPP: return "Oplookupp" case OP_NVERIFY: return "Opnverify" case OP_OPEN: return "Opopen" case OP_OPENATTR: return "Opopenattr" case OP_OPEN_CONFIRM: return "Opopen_confirm" case OP_OPEN_DOWNGRADE: return "Opopen_downgrade" case OP_PUTFH: return "Opputfh" case OP_PUTPUBFH: return "Opputpubfh" case OP_PUTROOTFH: return "Opputrootfh" case OP_READ: return "Opread" case OP_READDIR: return "Opreaddir" case OP_READLINK: return "Opreadlink" case OP_REMOVE: return "Opremove" case OP_RENAME: return "Oprename" case OP_RENEW: return "Oprenew" case OP_RESTOREFH: return "Oprestorefh" case OP_SAVEFH: return "Opsavefh" case OP_SECINFO: return "Opsecinfo" case OP_SETATTR: return "Opsetattr" case OP_SETCLIENTID: return "Opsetclientid" case OP_SETCLIENTID_CONFIRM: return "Opsetclientid_confirm" case OP_VERIFY: return "Opverify" case OP_WRITE: return "Opwrite" case OP_RELEASE_LOCKOWNER: return "Oprelease_lockowner" case OP_CREATE_SESSION: return "Opcreatesession" case OP_DESTROY_SESSION: return "Opdestroysession" case OP_FREE_STATEID: return "Opfreestateid" case OP_GET_DIR_DELEGATION: return "Opgetdirdelegation" case OP_GETDEVICEINFO: return "Opgetdeviceinfo" case OP_GETDEVICELIST: return "Opgetdevicelist" case OP_LAYOUTCOMMIT: return "Oplayoutcommit" case OP_LAYOUTGET: return "Oplayoutget" case OP_LAYOUTRETURN: return "Oplayoutreturn" case OP_SECINFO_NO_NAME: return "Opsecinfononame" case OP_SEQUENCE: return "Opsequence" case OP_SET_SSV: return "Opsetssv" case OP_TEST_STATEID: return "Opteststateid" case OP_WANT_DELEGATION: return "Opwantdelegation" case OP_DESTROY_CLIENTID: return "Opdestroyclientid" case OP_RECLAIM_COMPLETE: return "Opreclaimcomplete" case OP_ILLEGAL: return "Opillegal" } return "" } type XdrType_Nfs_resop4 = *Nfs_resop4 func (v *Nfs_resop4) XdrPointer() interface{} { return v } func (Nfs_resop4) XdrTypeName() string { return "Nfs_resop4" } func (v Nfs_resop4) XdrValue() interface{} { return v } func (v *Nfs_resop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Nfs_resop4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfs_opnum4(&u.Resop).XdrMarshal(x, x.Sprintf("%sresop", name)) switch u.Resop { case OP_ACCESS: x.Marshal(x.Sprintf("%sopaccess", name), XDR_ACCESS4res(u.Opaccess())) return case OP_CLOSE: x.Marshal(x.Sprintf("%sopclose", name), XDR_CLOSE4res(u.Opclose())) return case OP_COMMIT: x.Marshal(x.Sprintf("%sopcommit", name), XDR_COMMIT4res(u.Opcommit())) return case OP_CREATE: x.Marshal(x.Sprintf("%sopcreate", name), XDR_CREATE4res(u.Opcreate())) return case OP_DELEGPURGE: x.Marshal(x.Sprintf("%sopdelegpurge", name), XDR_DELEGPURGE4res(u.Opdelegpurge())) return case OP_DELEGRETURN: x.Marshal(x.Sprintf("%sopdelegreturn", name), XDR_DELEGRETURN4res(u.Opdelegreturn())) return case OP_GETATTR: x.Marshal(x.Sprintf("%sopgetattr", name), XDR_GETATTR4res(u.Opgetattr())) return case OP_GETFH: x.Marshal(x.Sprintf("%sopgetfh", name), XDR_GETFH4res(u.Opgetfh())) return case OP_LINK: x.Marshal(x.Sprintf("%soplink", name), XDR_LINK4res(u.Oplink())) return case OP_LOCK: x.Marshal(x.Sprintf("%soplock", name), XDR_LOCK4res(u.Oplock())) return case OP_LOCKT: x.Marshal(x.Sprintf("%soplockt", name), XDR_LOCKT4res(u.Oplockt())) return case OP_LOCKU: x.Marshal(x.Sprintf("%soplocku", name), XDR_LOCKU4res(u.Oplocku())) return case OP_LOOKUP: x.Marshal(x.Sprintf("%soplookup", name), XDR_LOOKUP4res(u.Oplookup())) return case OP_LOOKUPP: x.Marshal(x.Sprintf("%soplookupp", name), XDR_LOOKUPP4res(u.Oplookupp())) return case OP_NVERIFY: x.Marshal(x.Sprintf("%sopnverify", name), XDR_NVERIFY4res(u.Opnverify())) return case OP_OPEN: x.Marshal(x.Sprintf("%sopopen", name), XDR_OPEN4res(u.Opopen())) return case OP_OPENATTR: x.Marshal(x.Sprintf("%sopopenattr", name), XDR_OPENATTR4res(u.Opopenattr())) return case OP_OPEN_CONFIRM: x.Marshal(x.Sprintf("%sopopen_confirm", name), XDR_OPEN_CONFIRM4res(u.Opopen_confirm())) return case OP_OPEN_DOWNGRADE: x.Marshal(x.Sprintf("%sopopen_downgrade", name), XDR_OPEN_DOWNGRADE4res(u.Opopen_downgrade())) return case OP_PUTFH: x.Marshal(x.Sprintf("%sopputfh", name), XDR_PUTFH4res(u.Opputfh())) return case OP_PUTPUBFH: x.Marshal(x.Sprintf("%sopputpubfh", name), XDR_PUTPUBFH4res(u.Opputpubfh())) return case OP_PUTROOTFH: x.Marshal(x.Sprintf("%sopputrootfh", name), XDR_PUTROOTFH4res(u.Opputrootfh())) return case OP_READ: x.Marshal(x.Sprintf("%sopread", name), XDR_READ4res(u.Opread())) return case OP_READDIR: x.Marshal(x.Sprintf("%sopreaddir", name), XDR_READDIR4res(u.Opreaddir())) return case OP_READLINK: x.Marshal(x.Sprintf("%sopreadlink", name), XDR_READLINK4res(u.Opreadlink())) return case OP_REMOVE: x.Marshal(x.Sprintf("%sopremove", name), XDR_REMOVE4res(u.Opremove())) return case OP_RENAME: x.Marshal(x.Sprintf("%soprename", name), XDR_RENAME4res(u.Oprename())) return case OP_RENEW: x.Marshal(x.Sprintf("%soprenew", name), XDR_RENEW4res(u.Oprenew())) return case OP_RESTOREFH: x.Marshal(x.Sprintf("%soprestorefh", name), XDR_RESTOREFH4res(u.Oprestorefh())) return case OP_SAVEFH: x.Marshal(x.Sprintf("%sopsavefh", name), XDR_SAVEFH4res(u.Opsavefh())) return case OP_SECINFO: x.Marshal(x.Sprintf("%sopsecinfo", name), XDR_SECINFO4res(u.Opsecinfo())) return case OP_SETATTR: x.Marshal(x.Sprintf("%sopsetattr", name), XDR_SETATTR4res(u.Opsetattr())) return case OP_SETCLIENTID: x.Marshal(x.Sprintf("%sopsetclientid", name), XDR_SETCLIENTID4res(u.Opsetclientid())) return case OP_SETCLIENTID_CONFIRM: x.Marshal(x.Sprintf("%sopsetclientid_confirm", name), XDR_SETCLIENTID_CONFIRM4res(u.Opsetclientid_confirm())) return case OP_VERIFY: x.Marshal(x.Sprintf("%sopverify", name), XDR_VERIFY4res(u.Opverify())) return case OP_WRITE: x.Marshal(x.Sprintf("%sopwrite", name), XDR_WRITE4res(u.Opwrite())) return case OP_RELEASE_LOCKOWNER: x.Marshal(x.Sprintf("%soprelease_lockowner", name), XDR_RELEASE_LOCKOWNER4res(u.Oprelease_lockowner())) return case OP_CREATE_SESSION: x.Marshal(x.Sprintf("%sopcreatesession", name), XDR_CREATE_SESSION4res(u.Opcreatesession())) return case OP_DESTROY_SESSION: x.Marshal(x.Sprintf("%sopdestroysession", name), XDR_DESTROY_SESSION4res(u.Opdestroysession())) return case OP_FREE_STATEID: x.Marshal(x.Sprintf("%sopfreestateid", name), XDR_FREE_STATEID4res(u.Opfreestateid())) return case OP_GET_DIR_DELEGATION: x.Marshal(x.Sprintf("%sopgetdirdelegation", name), XDR_GET_DIR_DELEGATION4res(u.Opgetdirdelegation())) return case OP_GETDEVICEINFO: x.Marshal(x.Sprintf("%sopgetdeviceinfo", name), XDR_GETDEVICEINFO4res(u.Opgetdeviceinfo())) return case OP_GETDEVICELIST: x.Marshal(x.Sprintf("%sopgetdevicelist", name), XDR_GETDEVICELIST4res(u.Opgetdevicelist())) return case OP_LAYOUTCOMMIT: x.Marshal(x.Sprintf("%soplayoutcommit", name), XDR_LAYOUTCOMMIT4res(u.Oplayoutcommit())) return case OP_LAYOUTGET: x.Marshal(x.Sprintf("%soplayoutget", name), XDR_LAYOUTGET4res(u.Oplayoutget())) return case OP_LAYOUTRETURN: x.Marshal(x.Sprintf("%soplayoutreturn", name), XDR_LAYOUTRETURN4res(u.Oplayoutreturn())) return case OP_SECINFO_NO_NAME: x.Marshal(x.Sprintf("%sopsecinfononame", name), XDR_SECINFO_NO_NAME4res(u.Opsecinfononame())) return case OP_SEQUENCE: x.Marshal(x.Sprintf("%sopsequence", name), XDR_SEQUENCE4res(u.Opsequence())) return case OP_SET_SSV: x.Marshal(x.Sprintf("%sopsetssv", name), XDR_SET_SSV4res(u.Opsetssv())) return case OP_TEST_STATEID: x.Marshal(x.Sprintf("%sopteststateid", name), XDR_TEST_STATEID4res(u.Opteststateid())) return case OP_WANT_DELEGATION: x.Marshal(x.Sprintf("%sopwantdelegation", name), XDR_WANT_DELEGATION4res(u.Opwantdelegation())) return case OP_DESTROY_CLIENTID: x.Marshal(x.Sprintf("%sopdestroyclientid", name), XDR_DESTROY_CLIENTID4res(u.Opdestroyclientid())) return case OP_RECLAIM_COMPLETE: x.Marshal(x.Sprintf("%sopreclaimcomplete", name), XDR_RECLAIM_COMPLETE4res(u.Opreclaimcomplete())) return case OP_ILLEGAL: x.Marshal(x.Sprintf("%sopillegal", name), XDR_ILLEGAL4res(u.Opillegal())) return } XdrPanic("invalid Resop (%v) in Nfs_resop4", u.Resop) } func (v *Nfs_resop4) XdrInitialize() { var zero Nfs_opnum4 switch zero { case OP_ACCESS, OP_CLOSE, OP_COMMIT, OP_CREATE, OP_DELEGPURGE, OP_DELEGRETURN, OP_GETATTR, OP_GETFH, OP_LINK, OP_LOCK, OP_LOCKT, OP_LOCKU, OP_LOOKUP, OP_LOOKUPP, OP_NVERIFY, OP_OPEN, OP_OPENATTR, OP_OPEN_CONFIRM, OP_OPEN_DOWNGRADE, OP_PUTFH, OP_PUTPUBFH, OP_PUTROOTFH, OP_READ, OP_READDIR, OP_READLINK, OP_REMOVE, OP_RENAME, OP_RENEW, OP_RESTOREFH, OP_SAVEFH, OP_SECINFO, OP_SETATTR, OP_SETCLIENTID, OP_SETCLIENTID_CONFIRM, OP_VERIFY, OP_WRITE, OP_RELEASE_LOCKOWNER, OP_CREATE_SESSION, OP_DESTROY_SESSION, OP_FREE_STATEID, OP_GET_DIR_DELEGATION, OP_GETDEVICEINFO, OP_GETDEVICELIST, OP_LAYOUTCOMMIT, OP_LAYOUTGET, OP_LAYOUTRETURN, OP_SECINFO_NO_NAME, OP_SEQUENCE, OP_SET_SSV, OP_TEST_STATEID, OP_WANT_DELEGATION, OP_DESTROY_CLIENTID, OP_RECLAIM_COMPLETE, OP_ILLEGAL: default: if v.Resop == zero { v.Resop = OP_ACCESS } } } func XDR_Nfs_resop4(v *Nfs_resop4) *Nfs_resop4 { return v} type _XdrVec_unbounded_Nfs_argop4 []Nfs_argop4 func (_XdrVec_unbounded_Nfs_argop4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfs_argop4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfs_argop4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfs_argop4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfs_argop4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfs_argop4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfs_argop4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfs_argop4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfs_argop4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfs_argop4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfs_argop4) XdrTypeName() string { return "Nfs_argop4<>" } func (v *_XdrVec_unbounded_Nfs_argop4) XdrPointer() interface{} { return (*[]Nfs_argop4)(v) } func (v _XdrVec_unbounded_Nfs_argop4) XdrValue() interface{} { return ([]Nfs_argop4)(v) } func (v *_XdrVec_unbounded_Nfs_argop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_COMPOUND4args = *COMPOUND4args func (v *COMPOUND4args) XdrPointer() interface{} { return v } func (COMPOUND4args) XdrTypeName() string { return "COMPOUND4args" } func (v COMPOUND4args) XdrValue() interface{} { return v } func (v *COMPOUND4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *COMPOUND4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%stag", name), XDR_Utf8str_cs(&v.Tag)) x.Marshal(x.Sprintf("%sminorversion", name), XDR_Uint32_t(&v.Minorversion)) x.Marshal(x.Sprintf("%sargarray", name), (*_XdrVec_unbounded_Nfs_argop4)(&v.Argarray)) } func XDR_COMPOUND4args(v *COMPOUND4args) *COMPOUND4args { return v } type _XdrVec_unbounded_Nfs_resop4 []Nfs_resop4 func (_XdrVec_unbounded_Nfs_resop4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfs_resop4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfs_resop4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfs_resop4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfs_resop4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfs_resop4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfs_resop4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfs_resop4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfs_resop4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfs_resop4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfs_resop4) XdrTypeName() string { return "Nfs_resop4<>" } func (v *_XdrVec_unbounded_Nfs_resop4) XdrPointer() interface{} { return (*[]Nfs_resop4)(v) } func (v _XdrVec_unbounded_Nfs_resop4) XdrValue() interface{} { return ([]Nfs_resop4)(v) } func (v *_XdrVec_unbounded_Nfs_resop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_COMPOUND4res = *COMPOUND4res func (v *COMPOUND4res) XdrPointer() interface{} { return v } func (COMPOUND4res) XdrTypeName() string { return "COMPOUND4res" } func (v COMPOUND4res) XdrValue() interface{} { return v } func (v *COMPOUND4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *COMPOUND4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) x.Marshal(x.Sprintf("%stag", name), XDR_Utf8str_cs(&v.Tag)) x.Marshal(x.Sprintf("%sresarray", name), (*_XdrVec_unbounded_Nfs_resop4)(&v.Resarray)) } func XDR_COMPOUND4res(v *COMPOUND4res) *COMPOUND4res { return v } type XdrProc_NFSPROC4_NULL struct { Arg *XdrVoid Res *XdrVoid } func (XdrProc_NFSPROC4_NULL) Prog() uint32 { return 100003 } func (XdrProc_NFSPROC4_NULL) Vers() uint32 { return 4 } func (XdrProc_NFSPROC4_NULL) Proc() uint32 { return 0 } func (XdrProc_NFSPROC4_NULL) ProgName() string { return "NFS4_PROGRAM" } func (XdrProc_NFSPROC4_NULL) VersName() string { return "NFS_V4" } func (XdrProc_NFSPROC4_NULL) ProcName() string { return "NFSPROC4_NULL" } func (p *XdrProc_NFSPROC4_NULL) GetArg() XdrType { if p.Arg == nil { p.Arg = new(XdrVoid) } return XDR_XdrVoid(p.Arg) } func (p *XdrProc_NFSPROC4_NULL) GetRes() XdrType { if p.Res == nil { p.Res = new(XdrVoid) } return XDR_XdrVoid(p.Res) } var _ XdrProc = &XdrProc_NFSPROC4_NULL{} // XXX type xdrSrvProc_NFSPROC4_NULL struct { XdrProc_NFSPROC4_NULL Srv NFS_V4 } func (p *xdrSrvProc_NFSPROC4_NULL) SetContext(ctx context.Context) { if wc, ok := p.Srv.(interface { WithContext(context.Context) NFS_V4 }); ok { p.Srv = wc.WithContext(ctx) } } func (p *xdrSrvProc_NFSPROC4_NULL) Do() { p.Srv.NFSPROC4_NULL() } var _ XdrSrvProc = &xdrSrvProc_NFSPROC4_NULL{} // XXX type XdrProc_NFSPROC4_COMPOUND struct { Arg *COMPOUND4args Res *COMPOUND4res } func (XdrProc_NFSPROC4_COMPOUND) Prog() uint32 { return 100003 } func (XdrProc_NFSPROC4_COMPOUND) Vers() uint32 { return 4 } func (XdrProc_NFSPROC4_COMPOUND) Proc() uint32 { return 1 } func (XdrProc_NFSPROC4_COMPOUND) ProgName() string { return "NFS4_PROGRAM" } func (XdrProc_NFSPROC4_COMPOUND) VersName() string { return "NFS_V4" } func (XdrProc_NFSPROC4_COMPOUND) ProcName() string { return "NFSPROC4_COMPOUND" } func (p *XdrProc_NFSPROC4_COMPOUND) GetArg() XdrType { if p.Arg == nil { p.Arg = new(COMPOUND4args) } return XDR_COMPOUND4args(p.Arg) } func (p *XdrProc_NFSPROC4_COMPOUND) GetRes() XdrType { if p.Res == nil { p.Res = new(COMPOUND4res) } return XDR_COMPOUND4res(p.Res) } var _ XdrProc = &XdrProc_NFSPROC4_COMPOUND{} // XXX type xdrSrvProc_NFSPROC4_COMPOUND struct { XdrProc_NFSPROC4_COMPOUND Srv NFS_V4 } func (p *xdrSrvProc_NFSPROC4_COMPOUND) SetContext(ctx context.Context) { if wc, ok := p.Srv.(interface { WithContext(context.Context) NFS_V4 }); ok { p.Srv = wc.WithContext(ctx) } } func (p *xdrSrvProc_NFSPROC4_COMPOUND) Do() { r := p.Srv.NFSPROC4_COMPOUND(*p.Arg) p.Res = &r } var _ XdrSrvProc = &xdrSrvProc_NFSPROC4_COMPOUND{} // XXX func init() { XdrCatalog[100003<<32|4] = func(p uint32) XdrProc { switch(p) { case 0: return &XdrProc_NFSPROC4_NULL{} case 1: return &XdrProc_NFSPROC4_COMPOUND{} } return nil } } type NFS_V4_Server struct { Srv NFS_V4 } func (NFS_V4_Server) Prog() uint32 { return 100003 } func (NFS_V4_Server) Vers() uint32 { return 4 } func (NFS_V4_Server) ProgName() string { return "NFS4_PROGRAM" } func (NFS_V4_Server) VersName() string { return "NFS_V4" } func (s NFS_V4_Server) GetProc(p uint32) XdrSrvProc { switch p { case 0: // NFSPROC4_NULL return &xdrSrvProc_NFSPROC4_NULL{ Srv: s.Srv } case 1: // NFSPROC4_COMPOUND return &xdrSrvProc_NFSPROC4_COMPOUND{ Srv: s.Srv } default: return nil } } var _ XdrSrv = NFS_V4_Server{} // XXX type NFS_V4_Client struct { Send XdrSendCall Ctx context.Context } var _ NFS_V4 = NFS_V4_Client{} // XXX func (c NFS_V4_Client) WithContext(ctx context.Context) NFS_V4 { c.Ctx = ctx return c } func (c NFS_V4_Client) NFSPROC4_NULL() { var proc XdrProc_NFSPROC4_NULL if err := c.Send.SendCall(c.Ctx, &proc); err != nil { panic(err) } } func (c NFS_V4_Client) NFSPROC4_COMPOUND(a1 COMPOUND4args) COMPOUND4res { var proc XdrProc_NFSPROC4_COMPOUND proc.Arg = &a1 if err := c.Send.SendCall(c.Ctx, &proc); err != nil { panic(err) } return *proc.Res } type XdrType_CB_GETATTR4args = *CB_GETATTR4args func (v *CB_GETATTR4args) XdrPointer() interface{} { return v } func (CB_GETATTR4args) XdrTypeName() string { return "CB_GETATTR4args" } func (v CB_GETATTR4args) XdrValue() interface{} { return v } func (v *CB_GETATTR4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_GETATTR4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sfh", name), XDR_Nfs_fh4(&v.Fh)) x.Marshal(x.Sprintf("%sattr_request", name), XDR_Bitmap4(&v.Attr_request)) } func XDR_CB_GETATTR4args(v *CB_GETATTR4args) *CB_GETATTR4args { return v } type XdrType_CB_GETATTR4resok = *CB_GETATTR4resok func (v *CB_GETATTR4resok) XdrPointer() interface{} { return v } func (CB_GETATTR4resok) XdrTypeName() string { return "CB_GETATTR4resok" } func (v CB_GETATTR4resok) XdrValue() interface{} { return v } func (v *CB_GETATTR4resok) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_GETATTR4resok) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sobj_attributes", name), XDR_Fattr4(&v.Obj_attributes)) } func XDR_CB_GETATTR4resok(v *CB_GETATTR4resok) *CB_GETATTR4resok { return v } func (u *CB_GETATTR4res) Resok4() *CB_GETATTR4resok { switch u.Status { case NFS4_OK: if v, ok := u.U.(*CB_GETATTR4resok); ok { return v } else { var zero CB_GETATTR4resok u.U = &zero return &zero } default: XdrPanic("CB_GETATTR4res.Resok4 accessed when Status == %v", u.Status) return nil } } func (u CB_GETATTR4res) XdrValid() bool { return true } func (u *CB_GETATTR4res) XdrUnionTag() XdrNum32 { return XDR_Nfsstat4(&u.Status) } func (u *CB_GETATTR4res) XdrUnionTagName() string { return "Status" } func (u *CB_GETATTR4res) XdrUnionBody() XdrType { switch u.Status { case NFS4_OK: return XDR_CB_GETATTR4resok(u.Resok4()) default: return nil } } func (u *CB_GETATTR4res) XdrUnionBodyName() string { switch u.Status { case NFS4_OK: return "Resok4" default: return "" } } type XdrType_CB_GETATTR4res = *CB_GETATTR4res func (v *CB_GETATTR4res) XdrPointer() interface{} { return v } func (CB_GETATTR4res) XdrTypeName() string { return "CB_GETATTR4res" } func (v CB_GETATTR4res) XdrValue() interface{} { return v } func (v *CB_GETATTR4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *CB_GETATTR4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Nfsstat4(&u.Status).XdrMarshal(x, x.Sprintf("%sstatus", name)) switch u.Status { case NFS4_OK: x.Marshal(x.Sprintf("%sresok4", name), XDR_CB_GETATTR4resok(u.Resok4())) return default: return } } func XDR_CB_GETATTR4res(v *CB_GETATTR4res) *CB_GETATTR4res { return v} type XdrType_CB_RECALL4args = *CB_RECALL4args func (v *CB_RECALL4args) XdrPointer() interface{} { return v } func (CB_RECALL4args) XdrTypeName() string { return "CB_RECALL4args" } func (v CB_RECALL4args) XdrValue() interface{} { return v } func (v *CB_RECALL4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_RECALL4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstateid", name), XDR_Stateid4(&v.Stateid)) x.Marshal(x.Sprintf("%struncate", name), XDR_bool(&v.Truncate)) x.Marshal(x.Sprintf("%sfh", name), XDR_Nfs_fh4(&v.Fh)) } func XDR_CB_RECALL4args(v *CB_RECALL4args) *CB_RECALL4args { return v } type XdrType_CB_RECALL4res = *CB_RECALL4res func (v *CB_RECALL4res) XdrPointer() interface{} { return v } func (CB_RECALL4res) XdrTypeName() string { return "CB_RECALL4res" } func (v CB_RECALL4res) XdrValue() interface{} { return v } func (v *CB_RECALL4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_RECALL4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_CB_RECALL4res(v *CB_RECALL4res) *CB_RECALL4res { return v } type XdrType_CB_ILLEGAL4res = *CB_ILLEGAL4res func (v *CB_ILLEGAL4res) XdrPointer() interface{} { return v } func (CB_ILLEGAL4res) XdrTypeName() string { return "CB_ILLEGAL4res" } func (v CB_ILLEGAL4res) XdrValue() interface{} { return v } func (v *CB_ILLEGAL4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_ILLEGAL4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) } func XDR_CB_ILLEGAL4res(v *CB_ILLEGAL4res) *CB_ILLEGAL4res { return v } var _XdrNames_Nfs_cb_opnum4 = map[int32]string{ int32(OP_CB_GETATTR): "OP_CB_GETATTR", int32(OP_CB_RECALL): "OP_CB_RECALL", int32(OP_CB_ILLEGAL): "OP_CB_ILLEGAL", } var _XdrValues_Nfs_cb_opnum4 = map[string]int32{ "OP_CB_GETATTR": int32(OP_CB_GETATTR), "OP_CB_RECALL": int32(OP_CB_RECALL), "OP_CB_ILLEGAL": int32(OP_CB_ILLEGAL), } func (Nfs_cb_opnum4) XdrEnumNames() map[int32]string { return _XdrNames_Nfs_cb_opnum4 } func (v Nfs_cb_opnum4) String() string { if s, ok := _XdrNames_Nfs_cb_opnum4[int32(v)]; ok { return s } return fmt.Sprintf("Nfs_cb_opnum4#%d", v) } func (v *Nfs_cb_opnum4) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Nfs_cb_opnum4[stok]; ok { *v = Nfs_cb_opnum4(val) return nil } else if stok == "Nfs_cb_opnum4" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Nfs_cb_opnum4.", stok)) } } func (v Nfs_cb_opnum4) GetU32() uint32 { return uint32(v) } func (v *Nfs_cb_opnum4) SetU32(n uint32) { *v = Nfs_cb_opnum4(n) } func (v *Nfs_cb_opnum4) XdrPointer() interface{} { return v } func (Nfs_cb_opnum4) XdrTypeName() string { return "Nfs_cb_opnum4" } func (v Nfs_cb_opnum4) XdrValue() interface{} { return v } func (v *Nfs_cb_opnum4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Nfs_cb_opnum4 = *Nfs_cb_opnum4 func XDR_Nfs_cb_opnum4(v *Nfs_cb_opnum4) *Nfs_cb_opnum4 { return v } func (v *Nfs_cb_opnum4) XdrInitialize() { switch Nfs_cb_opnum4(0) { case OP_CB_GETATTR, OP_CB_RECALL, OP_CB_ILLEGAL: default: if *v == Nfs_cb_opnum4(0) { *v = OP_CB_GETATTR } } } var _XdrTags_Nfs_cb_argop4 = map[int32]bool{ XdrToI32(OP_CB_GETATTR): true, XdrToI32(OP_CB_RECALL): true, XdrToI32(OP_CB_ILLEGAL): true, } func (_ Nfs_cb_argop4) XdrValidTags() map[int32]bool { return _XdrTags_Nfs_cb_argop4 } func (u *Nfs_cb_argop4) Opcbgetattr() *CB_GETATTR4args { switch Nfs_cb_opnum4(u.Argop) { case OP_CB_GETATTR: if v, ok := u.U.(*CB_GETATTR4args); ok { return v } else { var zero CB_GETATTR4args u.U = &zero return &zero } default: XdrPanic("Nfs_cb_argop4.Opcbgetattr accessed when Argop == %v", u.Argop) return nil } } func (u *Nfs_cb_argop4) Opcbrecall() *CB_RECALL4args { switch Nfs_cb_opnum4(u.Argop) { case OP_CB_RECALL: if v, ok := u.U.(*CB_RECALL4args); ok { return v } else { var zero CB_RECALL4args u.U = &zero return &zero } default: XdrPanic("Nfs_cb_argop4.Opcbrecall accessed when Argop == %v", u.Argop) return nil } } func (u Nfs_cb_argop4) XdrValid() bool { switch Nfs_cb_opnum4(u.Argop) { case OP_CB_GETATTR,OP_CB_RECALL,OP_CB_ILLEGAL: return true } return false } func (u *Nfs_cb_argop4) XdrUnionTag() XdrNum32 { return XDR_uint32(&u.Argop) } func (u *Nfs_cb_argop4) XdrUnionTagName() string { return "Argop" } func (u *Nfs_cb_argop4) XdrUnionBody() XdrType { switch Nfs_cb_opnum4(u.Argop) { case OP_CB_GETATTR: return XDR_CB_GETATTR4args(u.Opcbgetattr()) case OP_CB_RECALL: return XDR_CB_RECALL4args(u.Opcbrecall()) case OP_CB_ILLEGAL: return nil } return nil } func (u *Nfs_cb_argop4) XdrUnionBodyName() string { switch Nfs_cb_opnum4(u.Argop) { case OP_CB_GETATTR: return "Opcbgetattr" case OP_CB_RECALL: return "Opcbrecall" case OP_CB_ILLEGAL: return "" } return "" } type XdrType_Nfs_cb_argop4 = *Nfs_cb_argop4 func (v *Nfs_cb_argop4) XdrPointer() interface{} { return v } func (Nfs_cb_argop4) XdrTypeName() string { return "Nfs_cb_argop4" } func (v Nfs_cb_argop4) XdrValue() interface{} { return v } func (v *Nfs_cb_argop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Nfs_cb_argop4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_uint32(&u.Argop).XdrMarshal(x, x.Sprintf("%sargop", name)) switch Nfs_cb_opnum4(u.Argop) { case OP_CB_GETATTR: x.Marshal(x.Sprintf("%sopcbgetattr", name), XDR_CB_GETATTR4args(u.Opcbgetattr())) return case OP_CB_RECALL: x.Marshal(x.Sprintf("%sopcbrecall", name), XDR_CB_RECALL4args(u.Opcbrecall())) return case OP_CB_ILLEGAL: return } XdrPanic("invalid Argop (%v) in Nfs_cb_argop4", u.Argop) } func (v *Nfs_cb_argop4) XdrInitialize() { var zero uint32 switch Nfs_cb_opnum4(zero) { case OP_CB_GETATTR, OP_CB_RECALL, OP_CB_ILLEGAL: default: if v.Argop == zero { v.Argop = uint32(OP_CB_GETATTR) } } } func XDR_Nfs_cb_argop4(v *Nfs_cb_argop4) *Nfs_cb_argop4 { return v} var _XdrTags_Nfs_cb_resop4 = map[int32]bool{ XdrToI32(OP_CB_GETATTR): true, XdrToI32(OP_CB_RECALL): true, XdrToI32(OP_CB_ILLEGAL): true, } func (_ Nfs_cb_resop4) XdrValidTags() map[int32]bool { return _XdrTags_Nfs_cb_resop4 } func (u *Nfs_cb_resop4) Opcbgetattr() *CB_GETATTR4res { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_GETATTR: if v, ok := u.U.(*CB_GETATTR4res); ok { return v } else { var zero CB_GETATTR4res u.U = &zero return &zero } default: XdrPanic("Nfs_cb_resop4.Opcbgetattr accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_cb_resop4) Opcbrecall() *CB_RECALL4res { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_RECALL: if v, ok := u.U.(*CB_RECALL4res); ok { return v } else { var zero CB_RECALL4res u.U = &zero return &zero } default: XdrPanic("Nfs_cb_resop4.Opcbrecall accessed when Resop == %v", u.Resop) return nil } } func (u *Nfs_cb_resop4) Opcbillegal() *CB_ILLEGAL4res { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_ILLEGAL: if v, ok := u.U.(*CB_ILLEGAL4res); ok { return v } else { var zero CB_ILLEGAL4res u.U = &zero return &zero } default: XdrPanic("Nfs_cb_resop4.Opcbillegal accessed when Resop == %v", u.Resop) return nil } } func (u Nfs_cb_resop4) XdrValid() bool { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_GETATTR,OP_CB_RECALL,OP_CB_ILLEGAL: return true } return false } func (u *Nfs_cb_resop4) XdrUnionTag() XdrNum32 { return XDR_uint32(&u.Resop) } func (u *Nfs_cb_resop4) XdrUnionTagName() string { return "Resop" } func (u *Nfs_cb_resop4) XdrUnionBody() XdrType { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_GETATTR: return XDR_CB_GETATTR4res(u.Opcbgetattr()) case OP_CB_RECALL: return XDR_CB_RECALL4res(u.Opcbrecall()) case OP_CB_ILLEGAL: return XDR_CB_ILLEGAL4res(u.Opcbillegal()) } return nil } func (u *Nfs_cb_resop4) XdrUnionBodyName() string { switch Nfs_cb_opnum4(u.Resop) { case OP_CB_GETATTR: return "Opcbgetattr" case OP_CB_RECALL: return "Opcbrecall" case OP_CB_ILLEGAL: return "Opcbillegal" } return "" } type XdrType_Nfs_cb_resop4 = *Nfs_cb_resop4 func (v *Nfs_cb_resop4) XdrPointer() interface{} { return v } func (Nfs_cb_resop4) XdrTypeName() string { return "Nfs_cb_resop4" } func (v Nfs_cb_resop4) XdrValue() interface{} { return v } func (v *Nfs_cb_resop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Nfs_cb_resop4) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_uint32(&u.Resop).XdrMarshal(x, x.Sprintf("%sresop", name)) switch Nfs_cb_opnum4(u.Resop) { case OP_CB_GETATTR: x.Marshal(x.Sprintf("%sopcbgetattr", name), XDR_CB_GETATTR4res(u.Opcbgetattr())) return case OP_CB_RECALL: x.Marshal(x.Sprintf("%sopcbrecall", name), XDR_CB_RECALL4res(u.Opcbrecall())) return case OP_CB_ILLEGAL: x.Marshal(x.Sprintf("%sopcbillegal", name), XDR_CB_ILLEGAL4res(u.Opcbillegal())) return } XdrPanic("invalid Resop (%v) in Nfs_cb_resop4", u.Resop) } func (v *Nfs_cb_resop4) XdrInitialize() { var zero uint32 switch Nfs_cb_opnum4(zero) { case OP_CB_GETATTR, OP_CB_RECALL, OP_CB_ILLEGAL: default: if v.Resop == zero { v.Resop = uint32(OP_CB_GETATTR) } } } func XDR_Nfs_cb_resop4(v *Nfs_cb_resop4) *Nfs_cb_resop4 { return v} type _XdrVec_unbounded_Nfs_cb_argop4 []Nfs_cb_argop4 func (_XdrVec_unbounded_Nfs_cb_argop4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfs_cb_argop4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfs_cb_argop4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfs_cb_argop4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfs_cb_argop4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfs_cb_argop4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfs_cb_argop4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfs_cb_argop4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfs_cb_argop4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfs_cb_argop4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfs_cb_argop4) XdrTypeName() string { return "Nfs_cb_argop4<>" } func (v *_XdrVec_unbounded_Nfs_cb_argop4) XdrPointer() interface{} { return (*[]Nfs_cb_argop4)(v) } func (v _XdrVec_unbounded_Nfs_cb_argop4) XdrValue() interface{} { return ([]Nfs_cb_argop4)(v) } func (v *_XdrVec_unbounded_Nfs_cb_argop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_CB_COMPOUND4args = *CB_COMPOUND4args func (v *CB_COMPOUND4args) XdrPointer() interface{} { return v } func (CB_COMPOUND4args) XdrTypeName() string { return "CB_COMPOUND4args" } func (v CB_COMPOUND4args) XdrValue() interface{} { return v } func (v *CB_COMPOUND4args) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_COMPOUND4args) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%stag", name), XDR_Utf8str_cs(&v.Tag)) x.Marshal(x.Sprintf("%sminorversion", name), XDR_Uint32_t(&v.Minorversion)) x.Marshal(x.Sprintf("%scallback_ident", name), XDR_Uint32_t(&v.Callback_ident)) x.Marshal(x.Sprintf("%sargarray", name), (*_XdrVec_unbounded_Nfs_cb_argop4)(&v.Argarray)) } func XDR_CB_COMPOUND4args(v *CB_COMPOUND4args) *CB_COMPOUND4args { return v } type _XdrVec_unbounded_Nfs_cb_resop4 []Nfs_cb_resop4 func (_XdrVec_unbounded_Nfs_cb_resop4) XdrBound() uint32 { const bound uint32 = 4294967295 // Force error if not const or doesn't fit return bound } func (_XdrVec_unbounded_Nfs_cb_resop4) XdrCheckLen(length uint32) { if length > uint32(4294967295) { XdrPanic("_XdrVec_unbounded_Nfs_cb_resop4 length %d exceeds bound 4294967295", length) } else if int(length) < 0 { XdrPanic("_XdrVec_unbounded_Nfs_cb_resop4 length %d exceeds max int", length) } } func (v _XdrVec_unbounded_Nfs_cb_resop4) GetVecLen() uint32 { return uint32(len(v)) } func (v *_XdrVec_unbounded_Nfs_cb_resop4) SetVecLen(length uint32) { v.XdrCheckLen(length) if int(length) <= cap(*v) { if int(length) != len(*v) { *v = (*v)[:int(length)] } return } newcap := 2*cap(*v) if newcap < int(length) { // also catches overflow where 2*cap < 0 newcap = int(length) } else if bound := uint(4294967295); uint(newcap) > bound { if int(bound) < 0 { bound = ^uint(0) >> 1 } newcap = int(bound) } nv := make([]Nfs_cb_resop4, int(length), newcap) copy(nv, *v) *v = nv } func (v *_XdrVec_unbounded_Nfs_cb_resop4) XdrMarshalN(x XDR, name string, n uint32) { v.XdrCheckLen(n) for i := 0; i < int(n); i++ { if (i >= len(*v)) { v.SetVecLen(uint32(i+1)) } XDR_Nfs_cb_resop4(&(*v)[i]).XdrMarshal(x, x.Sprintf("%s[%d]", name, i)) } if int(n) < len(*v) { *v = (*v)[:int(n)] } } func (v *_XdrVec_unbounded_Nfs_cb_resop4) XdrRecurse(x XDR, name string) { size := XdrSize{ Size: uint32(len(*v)), Bound: 4294967295 } x.Marshal(name, &size) v.XdrMarshalN(x, name, size.Size) } func (_XdrVec_unbounded_Nfs_cb_resop4) XdrTypeName() string { return "Nfs_cb_resop4<>" } func (v *_XdrVec_unbounded_Nfs_cb_resop4) XdrPointer() interface{} { return (*[]Nfs_cb_resop4)(v) } func (v _XdrVec_unbounded_Nfs_cb_resop4) XdrValue() interface{} { return ([]Nfs_cb_resop4)(v) } func (v *_XdrVec_unbounded_Nfs_cb_resop4) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_CB_COMPOUND4res = *CB_COMPOUND4res func (v *CB_COMPOUND4res) XdrPointer() interface{} { return v } func (CB_COMPOUND4res) XdrTypeName() string { return "CB_COMPOUND4res" } func (v CB_COMPOUND4res) XdrValue() interface{} { return v } func (v *CB_COMPOUND4res) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *CB_COMPOUND4res) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sstatus", name), XDR_Nfsstat4(&v.Status)) x.Marshal(x.Sprintf("%stag", name), XDR_Utf8str_cs(&v.Tag)) x.Marshal(x.Sprintf("%sresarray", name), (*_XdrVec_unbounded_Nfs_cb_resop4)(&v.Resarray)) } func XDR_CB_COMPOUND4res(v *CB_COMPOUND4res) *CB_COMPOUND4res { return v } type xdrProc_CB_NULL struct { Arg *XdrVoid Res *XdrVoid } func (xdrProc_CB_NULL) Prog() uint32 { return 1073741824 } func (xdrProc_CB_NULL) Vers() uint32 { return 1 } func (xdrProc_CB_NULL) Proc() uint32 { return 0 } func (xdrProc_CB_NULL) ProgName() string { return "NFS4_CALLBACK" } func (xdrProc_CB_NULL) VersName() string { return "NFS_CB" } func (xdrProc_CB_NULL) ProcName() string { return "CB_NULL" } func (p *xdrProc_CB_NULL) GetArg() XdrType { if p.Arg == nil { p.Arg = new(XdrVoid) } return XDR_XdrVoid(p.Arg) } func (p *xdrProc_CB_NULL) GetRes() XdrType { if p.Res == nil { p.Res = new(XdrVoid) } return XDR_XdrVoid(p.Res) } var _ XdrProc = &xdrProc_CB_NULL{} // XXX type xdrSrvProc_CB_NULL struct { xdrProc_CB_NULL Srv NFS_CB } func (p *xdrSrvProc_CB_NULL) SetContext(ctx context.Context) { if wc, ok := p.Srv.(interface { WithContext(context.Context) NFS_CB }); ok { p.Srv = wc.WithContext(ctx) } } func (p *xdrSrvProc_CB_NULL) Do() { p.Srv.CB_NULL() } var _ XdrSrvProc = &xdrSrvProc_CB_NULL{} // XXX type xdrProc_CB_COMPOUND struct { Arg *CB_COMPOUND4args Res *CB_COMPOUND4res } func (xdrProc_CB_COMPOUND) Prog() uint32 { return 1073741824 } func (xdrProc_CB_COMPOUND) Vers() uint32 { return 1 } func (xdrProc_CB_COMPOUND) Proc() uint32 { return 1 } func (xdrProc_CB_COMPOUND) ProgName() string { return "NFS4_CALLBACK" } func (xdrProc_CB_COMPOUND) VersName() string { return "NFS_CB" } func (xdrProc_CB_COMPOUND) ProcName() string { return "CB_COMPOUND" } func (p *xdrProc_CB_COMPOUND) GetArg() XdrType { if p.Arg == nil { p.Arg = new(CB_COMPOUND4args) } return XDR_CB_COMPOUND4args(p.Arg) } func (p *xdrProc_CB_COMPOUND) GetRes() XdrType { if p.Res == nil { p.Res = new(CB_COMPOUND4res) } return XDR_CB_COMPOUND4res(p.Res) } var _ XdrProc = &xdrProc_CB_COMPOUND{} // XXX type xdrSrvProc_CB_COMPOUND struct { xdrProc_CB_COMPOUND Srv NFS_CB } func (p *xdrSrvProc_CB_COMPOUND) SetContext(ctx context.Context) { if wc, ok := p.Srv.(interface { WithContext(context.Context) NFS_CB }); ok { p.Srv = wc.WithContext(ctx) } } func (p *xdrSrvProc_CB_COMPOUND) Do() { r := p.Srv.CB_COMPOUND(*p.Arg) p.Res = &r } var _ XdrSrvProc = &xdrSrvProc_CB_COMPOUND{} // XXX func init() { XdrCatalog[1073741824<<32|1] = func(p uint32) XdrProc { switch(p) { case 0: return &xdrProc_CB_NULL{} case 1: return &xdrProc_CB_COMPOUND{} } return nil } } type NFS_CB_Server struct { Srv NFS_CB } func (NFS_CB_Server) Prog() uint32 { return 1073741824 } func (NFS_CB_Server) Vers() uint32 { return 1 } func (NFS_CB_Server) ProgName() string { return "NFS4_CALLBACK" } func (NFS_CB_Server) VersName() string { return "NFS_CB" } func (s NFS_CB_Server) GetProc(p uint32) XdrSrvProc { switch p { case 0: // CB_NULL return &xdrSrvProc_CB_NULL{ Srv: s.Srv } case 1: // CB_COMPOUND return &xdrSrvProc_CB_COMPOUND{ Srv: s.Srv } default: return nil } } var _ XdrSrv = NFS_CB_Server{} // XXX type NFS_CB_Client struct { Send XdrSendCall Ctx context.Context } var _ NFS_CB = NFS_CB_Client{} // XXX func (c NFS_CB_Client) WithContext(ctx context.Context) NFS_CB { c.Ctx = ctx return c } func (c NFS_CB_Client) CB_NULL() { var proc xdrProc_CB_NULL if err := c.Send.SendCall(c.Ctx, &proc); err != nil { panic(err) } } func (c NFS_CB_Client) CB_COMPOUND(a1 CB_COMPOUND4args) CB_COMPOUND4res { var proc xdrProc_CB_COMPOUND proc.Arg = &a1 if err := c.Send.SendCall(c.Ctx, &proc); err != nil { panic(err) } return *proc.Res } // // begin boilerplate // // Types passed to the XDR Marshal method implementing the XdrType // interface. Pointers to some generated types T already implement // the interface. However, since built-in types such as string and // int32 cannot implement interfaces, each type T has a function // XDR_T(*T) that returns some type implementing XdrType. These // XdrType interfaces also encode limits on string and vector sizes // and simplify code by collapsing similar cases (e.g., all 32-bit // numbers are cast to a pointer implementing XdrNum32). // // For most XdrType instances, XdrPointer() and XdrValue() provide // access to the underlying type (which might not implement XdrType), // with the following exceptions: // // * opaque[] (fixed-length arrays of bytes) are returned as // XdrArrayOpaque (by XdrValue()) or nil (by XdrPointer()). This is // because XdrArrayOpaque wraps a byte slice rather than an actual // array. One generally doesn't want to pass arrays around; moreover, // getting a pointer to the actual array provides less information, // because one can't test for arrays in a type switch without knowing // the exact length of the array, while one can always dynamically // test the length of a slice. // // * Pointer types, in their XdrRecurse methods, marshal a special // XdrNum32 type of 0 or 1 to record whether the pointer is nil or the // value is present. Since this bool does not exist, XdrPointer() // returns nil (while XdrValue returns a bool). // // * For arrays, XdrValue() passes a slice, rather than the full // array, so as to avoid copying the array. type XdrType interface { // Return a string describing the name of the type (which will // reflect the name of a typedef). XdrTypeName() string // Return the underlying native representation of an XdrType // (e.g., int32 for an XdrInt32). XdrValue() interface{} // Return a pointer to the underlying native representation of an // XdrType (often just the same type as the XdrType, but sometimes // not, e.g., for primitive types that can't have methods // defined). XdrPointer() interface{} // Calls x.Marshal(name, v). XdrMarshal(XDR, string) } // The interface through which values are serialized, printed, etc. type XDR interface { // A function that gets called for each value to be marshalled. // The val XdrType argument will be one of the following: // // * A pointer type implementing XdrNum32 for bool, int, unsigned // int, float, the size of variable-length arrays except string // and opaque, and enums. In the case of enums, that instance // will just be a pointer to the enum. In the case of the other // types, it will be a pointer to a defined type that implements // the XdrNum32 interface (since plain go types cannot implement // methods)--e.g., *XdrUint32 for unsigned int. Variable array // sizes are passed as *XdrSize, which enforces the bound. // // * A pointer type implementing XdrNum64 for hyper, unsigned // hyper, and double. These are user-defined versions of int64, // uint64, and float64, called XdrInt64, XdrUint64, and // XdrFloat64, respectively. // // * An instance of XdrBytes for strings and opaque. // Specifically, strings are passed as XdrString, and // variable-length opaque<> vectors are passed as XdrVecOpaque, // both of which implement XdrVarBytes. // // * An XdrArrayOpaque containing a slice referencing a byte array // for fixed-length opaque[]. XdrArrayOpaque implements // XdrBytes, but not XdrVarBytes. // // * An instance of XdrAggregate for structs, unions, and // pointers. Note that structs and unions are just passed as // pointers to the underlying structures (which all implement // XdrAggregate). Pointers are passed as an XdrPtr interface // implemented by a defined pointer type (since plain pointers // cannot have methods). // // Note that the Marshal method is responsible for recursing into // XdrAggregate instance by calling the XdrRecurse method. // Requiring the Marshal method to recurse manually allows it to // refrain from recursing in cases where it needs to special-case // the handling of specific types. Marshal(name string, val XdrType) // This method should just be fmt.Sprintf for XDRs that use name. // Those that don't use name can use a trivial method returning "" Sprintf(string, ...interface{}) string } // The error thrown by marshaling functions when data has a bad value. type XdrError string func (v XdrError) Error() string { return string(v) } // Throws an XdrError. func XdrPanic(s string, args ...interface{}) { panic(XdrError(fmt.Sprintf(s, args...))) } // RFC4506 defines bool as equivalent to an enum with values all-caps // TRUE and FALSE. For convenience, we represent an XDR bool as a Go // bool instead, and so define these constants for use in union cases. // (XDR source files should not use lower-case true and false, as // these will not work in other languages or with other XDR // compilers.) const ( TRUE = true FALSE = false ) // Unfortunately, you can't cast a bool to an int. Hence, in // situations like a union discriminant where we don't know if a type // is equivalent to bool or not, then, short of using reflection, this // is how we have to convert to an integer. Note that this expects // types int, [u]int32, enum, and bool, rather than pointers to these // types. func XdrToI32(i interface{}) int32 { switch v := i.(type) { case interface{ GetU32() uint32 }: return int32(v.GetU32()) case int32: return v case uint32: return int32(v) case int: return int32(v) case bool: if v { return 1 } return 0 } panic(XdrError(fmt.Sprintf("XdrToI32: bad type %T", i))) } // // User-defined types used to place methods on basic types // // All quantities that should be serialized as 32-bit numbers // (including bools, enums, union discriminants, the bit saying // whether or not a pointer is NULL, floats, and vector lenghts) are // passed to the XDR.Marshal function as a pointer to a defined type // implementing the XdrNum32 interface. The one exception is string<> // and opaque<>, for which it is the job of XDR.Marshal to serialize // the 32-bit length. type XdrNum32 interface { XdrType fmt.Stringer fmt.Scanner GetU32() uint32 SetU32(uint32) } // Enums additionally provide access to a map of values to names. You // generally don't need to invoke the XdrEnumNames() method (since the // String and Scan methods already access the underlying maps), but // the presence of this method can be useful to differentiate enums // from other XdrNum32 types. type XdrEnum interface { XdrNum32 XdrEnumNames() map[int32]string } // All 64-bit numbers (hyper, unsigned hyper, and double) are passed // to XDR.Marshal as a pointer to a defined type implementing // XdrNum64. type XdrNum64 interface { XdrType fmt.Stringer fmt.Scanner GetU64() uint64 SetU64(uint64) } // A common interface implemented by XdrString, XdrVecOpaque, and // XdrArrayOpaque (and hence used to operate on the XdrTypes // corresponding to value os ftype string<>, opaque<>, and opaque[]). type XdrBytes interface { XdrType GetByteSlice() []byte } // A common interface implemented by XdrString and XdrVecOpaque, the // XdrTypes for opaque<> and string<>. Since XdrVarBytes is a subtype // of XdrBytes, Marshal functions that want to distinguish between // opaque<> and opaque[] should first try a type assertion for // XdrVarBytes (to check for opaque<>) and then if that fails try // XdrBytes (to check for opaque[]). type XdrVarBytes interface { XdrBytes XdrBound() uint32 SetByteSlice([]byte) } // Any struct, union, pointer, or variable-length array type (except // opaque<> and string<>) is passed to XDR.Marshal as a pointer // implementing the XdrAggregate interface. It is the responsibility // of the XDR.Marshal function to call the XdrRecurse method so as to // recurse into the data structure. Placing reponsibility on // XDR.Marshal for recursing allows a custom XDR to prune the // serialization at particular types (e.g., for pretty-printing a // partucular struct in a non-standard way). type XdrAggregate interface { XdrType XdrRecurse(XDR, string) } // A union type is an XdrAggregate with extra methods for individually // accessing the tag and active branch. type XdrUnion interface { XdrAggregate XdrValid() bool XdrValidTags() map[int32]bool XdrUnionTag() XdrNum32 XdrUnionTagName() string XdrUnionBody() XdrType XdrUnionBodyName() string } // Any pointer type is converted to an XdrType implementing the XdrPtr // interface for marshaling. Note XdrPtr is a subtype of // XdrAggregate. When a Marshal function does nothing special for // XdrPtr and treats the XdrPtr like any other XdrAggregate (calling // XdrRecurse), the Marshal function will then get called one or two // more times, first with an XdrSize (implementing XdrNum32) to // marshal the non-NULL bit, and then again with the underlying value // if the pointer is non-NULL. An XDR.Marshal function that wants to // special case pointers can access the present bit from the // GetPresent and SetPresent methods, then bypass marshaling of the // bit by calling XdrMarshalValue instead of XdrRecurse. type XdrPtr interface { // Marshals first the present/not-present bool, then if true, the // underlying value. XdrAggregate GetPresent() bool SetPresent(bool) // If the present/not-present bool is false, this function does // nothing. Otherwise, it marshals just the value, not the bit. XdrMarshalValue(XDR, string) } // Any vector type is passed as a pointer to a user-defined slice type // that implements the XdrVec interface. XdrVec is a superset of // XdrAggregate, so calling XdrRecurse will recurse to call // XDR.Marshal first on the size (of type XdrSize), then on each // element of the slice. An XDR.Marshal function can manually marshal // the size and then call XdrMarshalN to marshal n vector elements. // (When deserializing, it is advisable *not* to call SetVecLen before // calling XdrMarshalN in case the length is huge and would exhaust // memory; XdrMarshalN gradually grows the slice size as needed so as // to throw a premature EOF error if N is larger than the actual input // data.) type XdrVec interface { XdrAggregate XdrBound() uint32 GetVecLen() uint32 SetVecLen(uint32) XdrMarshalN(XDR, string, uint32) } // Any array type is passed as a pointer to a user-defined array // supporting the XdrArray interface. This is a subtype of // XdrAggregate, and so supports recursing to marshal each individual // array member. type XdrArray interface { XdrAggregate XdrArraySize() uint32 } type XdrTypedef interface { XdrType XdrUnwrap() XdrType } // Unwrap typedefs to obtain the underlying XdrType. func XdrBaseType(v XdrType) XdrType { for { t, ok := v.(XdrTypedef) if !ok { return v } v = t.XdrUnwrap() } } // Returns true for A-Z, a-z, 0-9, and _. (Used by the generated code // that implements Scanner for enums.) func XdrSymChar(r rune) bool { return (r|0x20 >= 'a' && r|0x20 <= 'z') || (r >= '0' && r <= '9') || r == '_' } // An XdrType that marshals as zero bytes, and hence is used to // represent void argument and return types of functions. type XdrVoid struct{} type XdrType_void = *XdrVoid func (XdrVoid) XdrTypeName() string { return "void" } func (XdrVoid) XdrValue() interface{} { return nil } func (XdrVoid) XdrPointer() interface{} { return nil } func (XdrVoid) XdrMarshal(XDR, string) {} func XDR_XdrVoid(v *XdrVoid) XdrType_void { return v } // The XdrType that bool gets turned into for marshaling. type XdrBool bool type XdrType_bool = *XdrBool func (XdrBool) XdrTypeName() string { return "bool" } func (v XdrBool) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrBool) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrBool) GetU32() uint32 { if v { return 1 }; return 0 } func (v *XdrBool) SetU32(nv uint32) { switch nv { case 0: *v = false case 1: *v = true default: XdrPanic("bool must be 0 or 1") } } func (v *XdrBool) XdrPointer() interface{} { return (*bool)(v) } func (v XdrBool) XdrValue() interface{} { return bool(v) } func (v *XdrBool) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_bool(v *bool) *XdrBool { return (*XdrBool)(v) } // The XdrType that int gets converted to for marshaling. type XdrInt32 int32 type XdrType_int32 = *XdrInt32 func (XdrInt32) XdrTypeName() string { return "int32" } func (v XdrInt32) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrInt32) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrInt32) GetU32() uint32 { return uint32(v) } func (v *XdrInt32) SetU32(nv uint32) { *v = XdrInt32(nv) } func (v *XdrInt32) XdrPointer() interface{} { return (*int32)(v) } func (v XdrInt32) XdrValue() interface{} { return int32(v) } func (v *XdrInt32) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_int32(v *int32) *XdrInt32 { return (*XdrInt32)(v) } // The XdrType that unsigned int gets converted to for marshaling. type XdrUint32 uint32 type XdrType_uint32 = *XdrUint32 func (XdrUint32) XdrTypeName() string { return "uint32" } func (v XdrUint32) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrUint32) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrUint32) GetU32() uint32 { return uint32(v) } func (v *XdrUint32) SetU32(nv uint32) { *v = XdrUint32(nv) } func (v *XdrUint32) XdrPointer() interface{} { return (*uint32)(v) } func (v XdrUint32) XdrValue() interface{} { return uint32(v) } func (v *XdrUint32) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_uint32(v *uint32) *XdrUint32 { return (*XdrUint32)(v) } // The XdrType that float gets converted to for marshaling. type XdrFloat32 float32 type XdrType_float32 = *XdrFloat32 func (XdrFloat32) XdrTypeName() string { return "float32" } func (v XdrFloat32) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrFloat32) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrFloat32) GetU32() uint32 { return math.Float32bits(float32(v)) } func (v *XdrFloat32) SetU32(nv uint32) { *v = XdrFloat32(math.Float32frombits(nv)) } func (v *XdrFloat32) XdrPointer() interface{} { return (*float32)(v) } func (v XdrFloat32) XdrValue() interface{} { return float32(v) } func (v *XdrFloat32) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_float32(v *float32) *XdrFloat32 { return (*XdrFloat32)(v) } // This XdrType is used to marshal the length of vectors and the // present/not-present value of pointers. type XdrSize struct { Size uint32 Bound uint32 } func (v XdrSize) String() string { return fmt.Sprintf("%v", v.Size) } func (v *XdrSize) Scan(ss fmt.ScanState, r rune) (err error) { defer func() { if err == nil { err = recover().(error) } }() var nv uint32 if _, err := fmt.Fscanf(ss, string([]rune{'%', r}), &nv); err != nil { return err } v.SetU32(nv) return } func (v XdrSize) GetU32() uint32 { return v.Size } func (v *XdrSize) SetU32(nv uint32) { if nv > v.Bound { XdrPanic("size %d greater than bound %d", nv, v.Bound) } else if int(nv) < 0 { XdrPanic("size %d greater than max slice len", nv) } v.Size = nv } func (XdrSize) XdrTypeName() string { return "len" } func (v *XdrSize) XdrPointer() interface{} { return &v.Size } func (v XdrSize) XdrValue() interface{} { return v.Size } func (v *XdrSize) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v XdrSize) XdrBound() uint32 { return v.Bound } // The XdrType that hyper gets converted to for marshaling. type XdrInt64 int64 type XdrType_int64 = *XdrInt64 func (XdrInt64) XdrTypeName() string { return "int64" } func (v XdrInt64) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrInt64) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrInt64) GetU64() uint64 { return uint64(v) } func (v *XdrInt64) SetU64(nv uint64) { *v = XdrInt64(nv) } func (v *XdrInt64) XdrPointer() interface{} { return (*int64)(v) } func (v XdrInt64) XdrValue() interface{} { return int64(v) } func (v *XdrInt64) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_int64(v *int64) *XdrInt64 { return (*XdrInt64)(v) } // The XdrType that unsigned hyper gets converted to for marshaling. type XdrUint64 uint64 type XdrType_uint64 = *XdrUint64 func (XdrUint64) XdrTypeName() string { return "uint64" } func (v XdrUint64) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrUint64) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrUint64) GetU64() uint64 { return uint64(v) } func (v *XdrUint64) SetU64(nv uint64) { *v = XdrUint64(nv) } func (v *XdrUint64) XdrPointer() interface{} { return (*uint64)(v) } func (v XdrUint64) XdrValue() interface{} { return uint64(v) } func (v *XdrUint64) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_uint64(v *uint64) *XdrUint64 { return (*XdrUint64)(v) } // The XdrType that double gets converted to for marshaling. type XdrFloat64 float64 type XdrType_float64 = *XdrFloat64 func (XdrFloat64) XdrTypeName() string { return "float64" } func (v XdrFloat64) String() string { return fmt.Sprintf("%v", v.XdrValue()) } func (v *XdrFloat64) Scan(ss fmt.ScanState, r rune) error { _, err := fmt.Fscanf(ss, string([]rune{'%', r}), v.XdrPointer()) return err } func (v XdrFloat64) GetU64() uint64 { return math.Float64bits(float64(v)) } func (v *XdrFloat64) SetU64(nv uint64) { *v = XdrFloat64(math.Float64frombits(nv)) } func (v *XdrFloat64) XdrPointer() interface{} { return (*float64)(v) } func (v XdrFloat64) XdrValue() interface{} { return float64(v) } func (v *XdrFloat64) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func XDR_float64(v *float64) *XdrFloat64 { return (*XdrFloat64)(v) } // The XdrType that strings get converted to for marshaling. type XdrString struct { Str *string Bound uint32 } func (v XdrString) String() string { return fmt.Sprintf("%q", *v.Str) } func (v XdrString) Scan(ss fmt.ScanState, _ rune) error { var s string if _, err := fmt.Fscanf(ss, "%q", &s); err != nil { return err } else if int64(len(s)) > int64(v.Bound) { return XdrError(fmt.Sprintf("Cannot store %d bytes in string<%d>", len(s), v.Bound)) } *v.Str = s return nil } func (v XdrString) XdrBound() uint32 { return v.Bound } func (v XdrString) GetString() string { return *v.Str } func (v XdrString) SetString(s string) { if uint(len(s)) > uint(v.Bound) { XdrPanic("Cannot store %d bytes in string<%d>", len(s), v.Bound) } *v.Str = s } func (v XdrString) GetByteSlice() []byte { return ([]byte)(*v.Str) } func (v XdrString) SetByteSlice(bs []byte) { if uint(len(bs)) > uint(v.Bound) { XdrPanic("Cannot store %d bytes in string<%d>", len(bs), v.Bound) } *v.Str = string(bs) } func (XdrString) XdrTypeName() string { return "string" } func (v XdrString) XdrPointer() interface{} { return v.Str } func (v XdrString) XdrValue() interface{} { return *v.Str } func (v XdrString) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } // The XdrType that opaque<> gets converted to for marshaling. type XdrVecOpaque struct { Bytes *[]byte Bound uint32 } func (v XdrVecOpaque) String() string { return fmt.Sprintf("%x", []byte(*v.Bytes)) } func (v XdrVecOpaque) Scan(ss fmt.ScanState, _ rune) error { var bs []byte _, err := fmt.Fscanf(ss, "%x", &bs) if err == nil { v.SetByteSlice(bs) } return err } func (v XdrVecOpaque) GetByteSlice() []byte { return *v.Bytes } func (v XdrVecOpaque) XdrBound() uint32 { return v.Bound } func (v XdrVecOpaque) SetByteSlice(bs []byte) { if uint(len(bs)) > uint(v.Bound) { XdrPanic("Cannot store %d bytes in string<%d>", len(bs), v.Bound) } *v.Bytes = bs } func (XdrVecOpaque) XdrTypeName() string { return "opaque<>" } func (v XdrVecOpaque) XdrPointer() interface{} { return v.Bytes } func (v XdrVecOpaque) XdrValue() interface{} { return *v.Bytes } func (v XdrVecOpaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } // Helper function for scanning fix-size opaque arrays. func XdrArrayOpaqueScan(dest []byte, ss fmt.ScanState, _ rune) error { var bs []byte _, err := fmt.Fscanf(ss, "%x", &bs) if err != nil { return err } else if len(bs) != len (dest) { return XdrError("Wrong number of bytes when scanning opaque[]") } copy(dest, bs) return nil } // The interface of XdrTypes of opaque[n] for all n type XdrArrayOpaque interface { XdrBytes XdrArraySize() uint32 } // // Basic implementations of XDR interface // // Back end that renders an XDR data structure as simple text. // Example: // // XDR_MyType(&myVal).XdrMarshal(&XdrPrint{os.Stdout}, "") // type XdrPrint struct { Out io.Writer } // Generic function that converts any compiled XDR type to a // multi-line string. Useful for debugging. func XdrToString(t XdrType) string { out := &strings.Builder{} t.XdrMarshal(&XdrPrint{out}, "") return out.String() } func (xp XdrPrint) Sprintf(f string, args ...interface{}) string { return fmt.Sprintf(f, args...) } func (xp XdrPrint) Marshal(name string, i XdrType) { switch v := i.(type) { case fmt.Stringer: fmt.Fprintf(xp.Out, "%s: %s\n", name, v.String()) case XdrPtr: fmt.Fprintf(xp.Out, "%s.present: %v\n", name, v.GetPresent()) v.XdrMarshalValue(xp, fmt.Sprintf("(*%s)", name)) case XdrVec: fmt.Fprintf(xp.Out, "%s.len: %d\n", name, v.GetVecLen()) v.XdrMarshalN(xp, name, v.GetVecLen()) case XdrAggregate: v.XdrRecurse(xp, name) default: fmt.Fprintf(xp.Out, "%s: %v\n", name, i) } } var xdrZerofill [4][]byte = [...][]byte{ {}, {0,0,0}, {0,0}, {0}, } func xdrPutBytes(out io.Writer, val []byte) { out.Write(val) out.Write(xdrZerofill[len(val)&3]) } func xdrPut32(out io.Writer, val uint32) { b := make([]byte, 4) binary.BigEndian.PutUint32(b, val) out.Write(b) } func xdrPut64(out io.Writer, val uint64) { b := make([]byte, 8) binary.BigEndian.PutUint64(b, val) out.Write(b) } // XDR that marshals to canonical binary format type XdrOut struct { Out io.Writer } func (xp XdrOut) Sprintf(f string, args ...interface{}) string { return "" } func (xo XdrOut) Marshal(name string, i XdrType) { switch v := i.(type) { case XdrNum32: xdrPut32(xo.Out, v.GetU32()) case XdrNum64: xdrPut64(xo.Out, v.GetU64()) case XdrString: s := v.GetString() xdrPut32(xo.Out, uint32(len(s))) io.WriteString(xo.Out, s) xo.Out.Write(xdrZerofill[len(s)&3]) case XdrVarBytes: xdrPut32(xo.Out, uint32(len(v.GetByteSlice()))) xdrPutBytes(xo.Out, v.GetByteSlice()) case XdrBytes: xdrPutBytes(xo.Out, v.GetByteSlice()) case XdrAggregate: v.XdrRecurse(xo, name) default: panic(fmt.Sprintf("XdrOut: unhandled type %T", i)) } } func xdrReadN(in io.Reader, n uint32) []byte { var b bytes.Buffer if _, err := io.CopyN(&b, in, int64(n)); err != nil { XdrPanic(err.Error()) } return b.Bytes() } func xdrReadPad(in io.Reader, n uint32) { if n & 3 != 0 { got := xdrReadN(in, 4-(n&3)) for _, b := range got { if b != 0 { XdrPanic("padding contained non-zero bytes") } } } } func xdrGet32(in io.Reader) uint32 { b := xdrReadN(in, 4) return binary.BigEndian.Uint32(b) } func xdrGet64(in io.Reader) uint64 { b := xdrReadN(in, 8) return binary.BigEndian.Uint64(b) } // XDR that unmarshals from canonical binary format type XdrIn struct { In io.Reader } func (xp XdrIn) Sprintf(f string, args ...interface{}) string { return "" } func (xi XdrIn) Marshal(name string, i XdrType) { switch v := i.(type) { case XdrNum32: v.SetU32(xdrGet32(xi.In)) case XdrNum64: v.SetU64(xdrGet64(xi.In)) case XdrVarBytes: n := xdrGet32(xi.In) v.SetByteSlice(xdrReadN(xi.In, n)) xdrReadPad(xi.In, n) case XdrBytes: if _, err := io.ReadFull(xi.In, v.GetByteSlice()); err != nil { panic(err) } xdrReadPad(xi.In, uint32(len(v.GetByteSlice()))) case XdrAggregate: v.XdrRecurse(xi, name) } } // // RFC5531 RPC support // // Interface for a data structure containing the argument and result // types of a call. type XdrProc interface { Prog() uint32 Vers() uint32 Proc() uint32 ProgName() string VersName() string ProcName() string GetArg() XdrType GetRes() XdrType } // Interface for a data structure containing argument and results of a // procedure plus a way to invoke the procedure on an instance of the // RPC version. In other words, Do() actually performs the work of an // RPC on the server side. type XdrSrvProc interface { XdrProc Do() SetContext(context.Context) } // Interface for an RPC version interface bound to a server // implementation. The result of GetProc() can be used to marshal the // arguments and results as well as actually invoke the function // (though Do()). type XdrSrv interface { Prog() uint32 Vers() uint32 ProgName() string VersName() string GetProc(uint32) XdrSrvProc } // An interface for types that can send remote procedure calls and // await their reply. type XdrSendCall interface { SendCall(context.Context, XdrProc) error } // A catalog of procedures of different RPC programs and versions, // mostly useful for debugging (e.g., pretty-printing a log of RPC // messages). The key to the map is prog<<32|vers. The function // returns a new instance of an appropriate XdrProc. var XdrCatalog = make(map[uint64]func(uint32)XdrProc) // // end boilerplate // ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/nfs4.x ================================================ /* This is based on RFC3530 */ /* * NFS v4 Definitions */ /* * Copyright (C) The Internet Society (1998,1999,2000,2001,2002). * All Rights Reserved. */ /* * nfs4_prot.x * */ /* * Sizes */ const NFS4_FHSIZE = 128; const NFS4_VERIFIER_SIZE = 8; const NFS4_OPAQUE_LIMIT = 1024; const NFS4_SESSIONID_SIZE = 16; /* * File types */ enum nfs_ftype4 { NF4REG = 1, /* Regular File */ NF4DIR = 2, /* Directory */ NF4BLK = 3, /* Special File - block device */ NF4CHR = 4, /* Special File - character device */ NF4LNK = 5, /* Symbolic Link */ NF4SOCK = 6, /* Special File - socket */ NF4FIFO = 7, /* Special File - fifo */ NF4ATTRDIR = 8, /* Attribute Directory */ NF4NAMEDATTR = 9 /* Named Attribute */ }; /* * Error status */ enum nfsstat4 { NFS4_OK = 0, /* everything is okay */ NFS4ERR_PERM = 1, /* caller not privileged */ NFS4ERR_NOENT = 2, /* no such file/directory */ NFS4ERR_IO = 5, /* hard I/O error */ NFS4ERR_NXIO = 6, /* no such device */ NFS4ERR_ACCESS = 13, /* access denied */ NFS4ERR_EXIST = 17, /* file already exists */ NFS4ERR_XDEV = 18, /* different filesystems */ /* Unused/reserved 19 */ NFS4ERR_NOTDIR = 20, /* should be a directory */ NFS4ERR_ISDIR = 21, /* should not be directory */ NFS4ERR_INVAL = 22, /* invalid argument */ NFS4ERR_FBIG = 27, /* file exceeds server max */ NFS4ERR_NOSPC = 28, /* no space on filesystem */ NFS4ERR_ROFS = 30, /* read-only filesystem */ NFS4ERR_MLINK = 31, /* too many hard links */ NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ NFS4ERR_NOTEMPTY = 66, /* directory not empty */ NFS4ERR_DQUOT = 69, /* hard quota limit reached*/ NFS4ERR_STALE = 70, /* file no longer exists */ NFS4ERR_BADHANDLE = 10001,/* Illegal filehandle */ NFS4ERR_BAD_COOKIE = 10003,/* READDIR cookie is stale */ NFS4ERR_NOTSUPP = 10004,/* operation not supported */ NFS4ERR_TOOSMALL = 10005,/* response limit exceeded */ NFS4ERR_SERVERFAULT = 10006,/* undefined server error */ NFS4ERR_BADTYPE = 10007,/* type invalid for CREATE */ NFS4ERR_DELAY = 10008,/* file "busy" - retry */ NFS4ERR_SAME = 10009,/* nverify says attrs same */ NFS4ERR_DENIED = 10010,/* lock unavailable */ NFS4ERR_EXPIRED = 10011,/* lock lease expired */ NFS4ERR_LOCKED = 10012,/* I/O failed due to lock */ NFS4ERR_GRACE = 10013,/* in grace period */ NFS4ERR_FHEXPIRED = 10014,/* filehandle expired */ NFS4ERR_SHARE_DENIED = 10015,/* share reserve denied */ NFS4ERR_WRONGSEC = 10016,/* wrong security flavor */ NFS4ERR_CLID_INUSE = 10017,/* clientid in use */ NFS4ERR_RESOURCE = 10018,/* resource exhaustion */ NFS4ERR_MOVED = 10019,/* filesystem relocated */ NFS4ERR_NOFILEHANDLE = 10020,/* current FH is not set */ NFS4ERR_MINOR_VERS_MISMATCH = 10021,/* minor vers not supp */ NFS4ERR_STALE_CLIENTID = 10022,/* server has rebooted */ NFS4ERR_STALE_STATEID = 10023,/* server has rebooted */ NFS4ERR_OLD_STATEID = 10024,/* state is out of sync */ NFS4ERR_BAD_STATEID = 10025,/* incorrect stateid */ NFS4ERR_BAD_SEQID = 10026,/* request is out of seq. */ NFS4ERR_NOT_SAME = 10027,/* verify - attrs not same */ NFS4ERR_LOCK_RANGE = 10028,/* lock range not supported*/ NFS4ERR_SYMLINK = 10029,/* should be file/directory*/ NFS4ERR_RESTOREFH = 10030,/* no saved filehandle */ NFS4ERR_LEASE_MOVED = 10031,/* some filesystem moved */ NFS4ERR_ATTRNOTSUPP = 10032,/* recommended attr not sup*/ NFS4ERR_NO_GRACE = 10033,/* reclaim outside of grace*/ NFS4ERR_RECLAIM_BAD = 10034,/* reclaim error at server */ NFS4ERR_RECLAIM_CONFLICT = 10035,/* conflict on reclaim */ NFS4ERR_BADXDR = 10036,/* XDR decode failed */ NFS4ERR_LOCKS_HELD = 10037,/* file locks held at CLOSE*/ NFS4ERR_OPENMODE = 10038,/* conflict in OPEN and I/O*/ NFS4ERR_BADOWNER = 10039,/* owner translation bad */ NFS4ERR_BADCHAR = 10040,/* utf-8 char not supported*/ NFS4ERR_BADNAME = 10041,/* name not supported */ NFS4ERR_BAD_RANGE = 10042,/* lock range not supported*/ NFS4ERR_LOCK_NOTSUPP = 10043,/* no atomic up/downgrade */ NFS4ERR_OP_ILLEGAL = 10044,/* undefined operation */ NFS4ERR_DEADLOCK = 10045,/* file locking deadlock */ NFS4ERR_FILE_OPEN = 10046,/* open file blocks op. */ NFS4ERR_ADMIN_REVOKED = 10047,/* lockowner state revoked */ NFS4ERR_CB_PATH_DOWN = 10048,/* callback path down */ NFS4ERR_BADIOMODE = 10049, NFS4ERR_BADLAYOUT = 10050, NFS4ERR_BAD_SESSION_DIGEST = 10051, NFS4ERR_BADSESSION = 10052, NFS4ERR_BADSLOT = 10053, NFS4ERR_COMPLETE_ALREADY = 10054, NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, NFS4ERR_DELEG_ALREADY_WANTED = 10056, NFS4ERR_BACK_CHAN_BUSY = 10057, NFS4ERR_LAYOUTTRYLATER = 10058, NFS4ERR_LAYOUTUNAVAILABLE = 10059, NFS4ERR_NOMATCHING_LAYOUT = 10060, NFS4ERR_RECALLCONFLICT = 10061 }; /* * Basic data types */ typedef uint32_t bitmap4<>; typedef uint64_t offset4; typedef uint32_t count4; typedef uint64_t length4; typedef uint64_t clientid4; typedef uint32_t sequenceid4; typedef uint32_t seqid4; typedef uint32_t slotid4; typedef opaque utf8string<>; typedef utf8string utf8str_cis; typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; typedef utf8str_cs component4; typedef component4 pathname4<>; typedef uint64_t nfs_lockid4; typedef uint64_t nfs_cookie4; typedef utf8str_cs linktext4; typedef opaque sec_oid4<>; typedef uint32_t qop4; typedef uint32_t mode4; typedef uint64_t changeid4; typedef opaque verifier4[NFS4_VERIFIER_SIZE]; typedef opaque sessionid4[NFS4_SESSIONID_SIZE]; /* * Authsys_parms */ struct authsys_parms { unsigned int stamp; string machinename<255>; unsigned int uid; unsigned int gid; unsigned int gids<16>; }; const NFS4_DEVICEID4_SIZE = 16; typedef opaque deviceid4[NFS4_DEVICEID4_SIZE]; enum layouttype4 { LAYOUT4_NFSV4_1_FILES = 0x1, LAYOUT4_OSD2_OBJECTS = 0x2, LAYOUT4_BLOCK_VOLUME = 0x3 }; struct layoutupdate4 { layouttype4 lou_type; opaque lou_body<>; }; struct device_addr4 { layouttype4 da_layout_type; opaque da_addr_body<>; }; /* * Timeval */ struct nfstime4 { int64_t seconds; uint32_t nseconds; }; enum time_how4 { SET_TO_SERVER_TIME4 = 0, SET_TO_CLIENT_TIME4 = 1 }; enum layoutiomode4 { LAYOUTIOMODE4_READ = 1, LAYOUTIOMODE4_RW = 2, LAYOUTIOMODE4_ANY = 3 }; struct layout_content4 { layouttype4 loc_type; opaque loc_body<>; }; struct layout4 { offset4 lo_offset; length4 lo_length; layoutiomode4 lo_iomode; layout_content4 lo_content; }; union settime4 switch (time_how4 set_it) { case SET_TO_CLIENT_TIME4: nfstime4 time; default: void; }; /* * File access handle */ typedef opaque nfs_fh4; /* * File attribute definitions */ /* * FSID structure for major/minor */ struct fsid4 { uint64_t major; uint64_t minor; }; /* * Filesystem locations attribute for relocation/migration */ struct fs_location4 { utf8str_cis server<>; pathname4 rootpath; }; struct fs_locations4 { pathname4 fs_root; fs_location4 locations<>; }; /* * Various Access Control Entry definitions */ /* * Mask that indicates which Access Control Entries are supported. * Values for the fattr4_aclsupport attribute. */ const ACL4_SUPPORT_ALLOW_ACL = 0x00000001; const ACL4_SUPPORT_DENY_ACL = 0x00000002; const ACL4_SUPPORT_AUDIT_ACL = 0x00000004; const ACL4_SUPPORT_ALARM_ACL = 0x00000008; typedef uint32_t acetype4; /* * acetype4 values, others can be added as needed. */ const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; /* * ACE flag */ typedef uint32_t aceflag4; /* * ACE flag values */ const ACE4_FILE_INHERIT_ACE = 0x00000001; const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; const ACE4_INHERIT_ONLY_ACE = 0x00000008; const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; const ACE4_IDENTIFIER_GROUP = 0x00000040; /* * ACE mask */ typedef uint32_t acemask4; /* * ACE mask values */ const ACE4_READ_DATA = 0x00000001; const ACE4_LIST_DIRECTORY = 0x00000001; const ACE4_WRITE_DATA = 0x00000002; const ACE4_ADD_FILE = 0x00000002; const ACE4_APPEND_DATA = 0x00000004; const ACE4_ADD_SUBDIRECTORY = 0x00000004; const ACE4_READ_NAMED_ATTRS = 0x00000008; const ACE4_WRITE_NAMED_ATTRS = 0x00000010; const ACE4_EXECUTE = 0x00000020; const ACE4_DELETE_CHILD = 0x00000040; const ACE4_READ_ATTRIBUTES = 0x00000080; const ACE4_WRITE_ATTRIBUTES = 0x00000100; const ACE4_DELETE = 0x00010000; const ACE4_READ_ACL = 0x00020000; const ACE4_WRITE_ACL = 0x00040000; const ACE4_WRITE_OWNER = 0x00080000; const ACE4_SYNCHRONIZE = 0x00100000; /* * ACE4_GENERIC_READ -- defined as combination of * ACE4_READ_ACL | * ACE4_READ_DATA | * ACE4_READ_ATTRIBUTES | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_READ = 0x00120081; /* * ACE4_GENERIC_WRITE -- defined as combination of * ACE4_READ_ACL | * ACE4_WRITE_DATA | * ACE4_WRITE_ATTRIBUTES | * ACE4_WRITE_ACL | * ACE4_APPEND_DATA | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_WRITE = 0x00160106; /* * ACE4_GENERIC_EXECUTE -- defined as combination of * ACE4_READ_ACL * ACE4_READ_ATTRIBUTES * ACE4_EXECUTE * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_EXECUTE = 0x001200A0; /* * Access Control Entry definition */ struct nfsace4 { acetype4 type; aceflag4 flag; acemask4 access_mask; utf8str_mixed who; }; /* * Field definitions for the fattr4_mode attribute */ const MODE4_SUID = 0x800; /* set user id on execution */ const MODE4_SGID = 0x400; /* set group id on execution */ const MODE4_SVTX = 0x200; /* save text even after use */ const MODE4_RUSR = 0x100; /* read permission: owner */ const MODE4_WUSR = 0x080; /* write permission: owner */ const MODE4_XUSR = 0x040; /* execute permission: owner */ const MODE4_RGRP = 0x020; /* read permission: group */ const MODE4_WGRP = 0x010; /* write permission: group */ const MODE4_XGRP = 0x008; /* execute permission: group */ const MODE4_ROTH = 0x004; /* read permission: other */ const MODE4_WOTH = 0x002; /* write permission: other */ const MODE4_XOTH = 0x001; /* execute permission: other */ /* * Special data/attribute associated with * file types NF4BLK and NF4CHR. */ struct specdata4 { uint32_t specdata1; /* major device number */ uint32_t specdata2; /* minor device number */ }; /* * Values for fattr4_fh_expire_type */ const FH4_PERSISTENT = 0x00000000; const FH4_NOEXPIRE_WITH_OPEN = 0x00000001; const FH4_VOLATILE_ANY = 0x00000002; const FH4_VOL_MIGRATION = 0x00000004; const FH4_VOL_RENAME = 0x00000008; typedef bitmap4 fattr4_supported_attrs; typedef nfs_ftype4 fattr4_type; typedef uint32_t fattr4_fh_expire_type; typedef changeid4 fattr4_change; typedef uint64_t fattr4_size; typedef bool fattr4_link_support; typedef bool fattr4_symlink_support; typedef bool fattr4_named_attr; typedef fsid4 fattr4_fsid; typedef bool fattr4_unique_handles; typedef uint32_t fattr4_lease_time; typedef nfsstat4 fattr4_rdattr_error; typedef nfsace4 fattr4_acl<>; typedef uint32_t fattr4_aclsupport; typedef bool fattr4_archive; typedef bool fattr4_cansettime; typedef bool fattr4_case_insensitive; typedef bool fattr4_case_preserving; typedef bool fattr4_chown_restricted; typedef uint64_t fattr4_fileid; typedef uint64_t fattr4_files_avail; typedef nfs_fh4 fattr4_filehandle; typedef uint64_t fattr4_files_free; typedef uint64_t fattr4_files_total; typedef fs_locations4 fattr4_fs_locations; typedef bool fattr4_hidden; typedef bool fattr4_homogeneous; typedef uint64_t fattr4_maxfilesize; typedef uint32_t fattr4_maxlink; typedef uint32_t fattr4_maxname; typedef uint64_t fattr4_maxread; typedef uint64_t fattr4_maxwrite; typedef utf8str_cs fattr4_mimetype; typedef mode4 fattr4_mode; typedef uint64_t fattr4_mounted_on_fileid; typedef bool fattr4_no_trunc; typedef uint32_t fattr4_numlinks; typedef utf8str_mixed fattr4_owner; typedef utf8str_mixed fattr4_owner_group; typedef uint64_t fattr4_quota_avail_hard; typedef uint64_t fattr4_quota_avail_soft; typedef uint64_t fattr4_quota_used; typedef specdata4 fattr4_rawdev; typedef uint64_t fattr4_space_avail; typedef uint64_t fattr4_space_free; typedef uint64_t fattr4_space_total; typedef uint64_t fattr4_space_used; typedef bool fattr4_system; typedef nfstime4 fattr4_time_access; typedef settime4 fattr4_time_access_set; typedef nfstime4 fattr4_time_backup; typedef nfstime4 fattr4_time_create; typedef nfstime4 fattr4_time_delta; typedef nfstime4 fattr4_time_metadata; typedef nfstime4 fattr4_time_modify; typedef settime4 fattr4_time_modify_set; /* * Mandatory Attributes */ const FATTR4_SUPPORTED_ATTRS = 0; const FATTR4_TYPE = 1; const FATTR4_FH_EXPIRE_TYPE = 2; const FATTR4_CHANGE = 3; const FATTR4_SIZE = 4; const FATTR4_LINK_SUPPORT = 5; const FATTR4_SYMLINK_SUPPORT = 6; const FATTR4_NAMED_ATTR = 7; const FATTR4_FSID = 8; const FATTR4_UNIQUE_HANDLES = 9; const FATTR4_LEASE_TIME = 10; const FATTR4_RDATTR_ERROR = 11; const FATTR4_FILEHANDLE = 19; /* * Recommended Attributes */ const FATTR4_ACL = 12; const FATTR4_ACLSUPPORT = 13; const FATTR4_ARCHIVE = 14; const FATTR4_CANSETTIME = 15; const FATTR4_CASE_INSENSITIVE = 16; const FATTR4_CASE_PRESERVING = 17; const FATTR4_CHOWN_RESTRICTED = 18; const FATTR4_FILEID = 20; const FATTR4_FILES_AVAIL = 21; const FATTR4_FILES_FREE = 22; const FATTR4_FILES_TOTAL = 23; const FATTR4_FS_LOCATIONS = 24; const FATTR4_HIDDEN = 25; const FATTR4_HOMOGENEOUS = 26; const FATTR4_MAXFILESIZE = 27; const FATTR4_MAXLINK = 28; const FATTR4_MAXNAME = 29; const FATTR4_MAXREAD = 30; const FATTR4_MAXWRITE = 31; const FATTR4_MIMETYPE = 32; const FATTR4_MODE = 33; const FATTR4_NO_TRUNC = 34; const FATTR4_NUMLINKS = 35; const FATTR4_OWNER = 36; const FATTR4_OWNER_GROUP = 37; const FATTR4_QUOTA_AVAIL_HARD = 38; const FATTR4_QUOTA_AVAIL_SOFT = 39; const FATTR4_QUOTA_USED = 40; const FATTR4_RAWDEV = 41; const FATTR4_SPACE_AVAIL = 42; const FATTR4_SPACE_FREE = 43; const FATTR4_SPACE_TOTAL = 44; const FATTR4_SPACE_USED = 45; const FATTR4_SYSTEM = 46; const FATTR4_TIME_ACCESS = 47; const FATTR4_TIME_ACCESS_SET = 48; const FATTR4_TIME_BACKUP = 49; const FATTR4_TIME_CREATE = 50; const FATTR4_TIME_DELTA = 51; const FATTR4_TIME_METADATA = 52; const FATTR4_TIME_MODIFY = 53; const FATTR4_TIME_MODIFY_SET = 54; const FATTR4_MOUNTED_ON_FILEID = 55; typedef opaque attrlist4<>; /* * File attribute container */ struct fattr4 { bitmap4 attrmask; attrlist4 attr_vals; }; /* * Change info for the client */ struct change_info4 { bool atomic; changeid4 before; changeid4 after; }; struct clientaddr4 { /* see struct rpcb in RFC 1833 */ string r_netid<>; /* network id */ string r_addr<>; /* universal address */ }; /* * Callback program info as provided by the client */ struct cb_client4 { uint32_t cb_program; clientaddr4 cb_location; }; /* * Stateid */ struct stateid4 { uint32_t seqid; opaque other[12]; }; /* * Client ID */ struct nfs_client_id4 { verifier4 verifier; opaque id; }; struct open_owner4 { clientid4 clientid; opaque owner; }; struct lock_owner4 { clientid4 clientid; opaque owner; }; enum nfs_lock_type4 { READ_LT = 1, WRITE_LT = 2, READW_LT = 3, /* blocking read */ WRITEW_LT = 4 /* blocking write */ }; /* * ACCESS: Check access permission */ const ACCESS4_READ = 0x00000001; const ACCESS4_LOOKUP = 0x00000002; const ACCESS4_MODIFY = 0x00000004; const ACCESS4_EXTEND = 0x00000008; const ACCESS4_DELETE = 0x00000010; const ACCESS4_EXECUTE = 0x00000020; struct ACCESS4args { /* CURRENT_FH: object */ uint32_t access; }; struct ACCESS4resok { uint32_t supported; uint32_t access; }; union ACCESS4res switch (nfsstat4 status) { case NFS4_OK: ACCESS4resok resok4; default: void; }; /* * CLOSE: Close a file and release share reservations */ struct CLOSE4args { /* CURRENT_FH: object */ seqid4 seqid; stateid4 open_stateid; }; union CLOSE4res switch (nfsstat4 status) { case NFS4_OK: stateid4 open_stateid; default: void; }; /* * COMMIT: Commit cached data on server to stable storage */ struct COMMIT4args { /* CURRENT_FH: file */ offset4 offset; count4 count; }; struct COMMIT4resok { verifier4 writeverf; }; union COMMIT4res switch (nfsstat4 status) { case NFS4_OK: COMMIT4resok resok4; default: void; }; /* * CREATE: Create a non-regular file */ union createtype4 switch (nfs_ftype4 type) { case NF4LNK: linktext4 linkdata; case NF4BLK: case NF4CHR: specdata4 devdata; case NF4SOCK: case NF4FIFO: case NF4DIR: void; default: void; /* server should return NFS4ERR_BADTYPE */ }; struct CREATE4args { /* CURRENT_FH: directory for creation */ createtype4 objtype; component4 objname; fattr4 createattrs; }; struct CREATE4resok { change_info4 cinfo; bitmap4 attrset; /* attributes set */ }; union CREATE4res switch (nfsstat4 status) { case NFS4_OK: CREATE4resok resok4; default: void; }; /* * DELEGPURGE: Purge Delegations Awaiting Recovery */ struct DELEGPURGE4args { clientid4 clientid; }; struct DELEGPURGE4res { nfsstat4 status; }; /* * DELEGRETURN: Return a delegation */ struct DELEGRETURN4args { /* CURRENT_FH: delegated file */ stateid4 deleg_stateid; }; struct DELEGRETURN4res { nfsstat4 status; }; /* * GETATTR: Get file attributes */ struct GETATTR4args { /* CURRENT_FH: directory or file */ bitmap4 attr_request; }; struct GETATTR4resok { fattr4 obj_attributes; }; union GETATTR4res switch (nfsstat4 status) { case NFS4_OK: GETATTR4resok resok4; default: void; }; /* * GETFH: Get current filehandle */ struct GETFH4resok { nfs_fh4 object; }; union GETFH4res switch (nfsstat4 status) { case NFS4_OK: GETFH4resok resok4; default: void; }; /* * LINK: Create link to an object */ struct LINK4args { /* SAVED_FH: source object */ /* CURRENT_FH: target directory */ component4 newname; }; struct LINK4resok { change_info4 cinfo; }; union LINK4res switch (nfsstat4 status) { case NFS4_OK: LINK4resok resok4; default: void; }; /* * For LOCK, transition from open_owner to new lock_owner */ struct open_to_lock_owner4 { seqid4 open_seqid; stateid4 open_stateid; seqid4 lock_seqid; lock_owner4 lock_owner; }; /* * For LOCK, existing lock_owner continues to request file locks */ struct exist_lock_owner4 { stateid4 lock_stateid; seqid4 lock_seqid; }; union locker4 switch (bool new_lock_owner) { case TRUE: open_to_lock_owner4 open_owner; case FALSE: exist_lock_owner4 lock_owner; }; /* * LOCK/LOCKT/LOCKU: Record lock management */ struct LOCK4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; bool reclaim; offset4 offset; length4 length; locker4 locker; }; struct LOCK4denied { offset4 offset; length4 length; nfs_lock_type4 locktype; lock_owner4 owner; }; struct LOCK4resok { stateid4 lock_stateid; }; union LOCK4res switch (nfsstat4 status) { case NFS4_OK: LOCK4resok resok4; case NFS4ERR_DENIED: LOCK4denied denied; default: void; }; struct LOCKT4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; offset4 offset; length4 length; lock_owner4 owner; }; union LOCKT4res switch (nfsstat4 status) { case NFS4ERR_DENIED: LOCK4denied denied; case NFS4_OK: void; default: void; }; struct LOCKU4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; seqid4 seqid; stateid4 lock_stateid; offset4 offset; length4 length; }; union LOCKU4res switch (nfsstat4 status) { case NFS4_OK: stateid4 lock_stateid; default: void; }; /* * LOOKUP: Lookup filename */ struct LOOKUP4args { /* CURRENT_FH: directory */ component4 objname; }; struct LOOKUP4res { /* CURRENT_FH: object */ nfsstat4 status; }; /* * LOOKUPP: Lookup parent directory */ struct LOOKUPP4res { /* CURRENT_FH: directory */ nfsstat4 status; }; /* * NVERIFY: Verify attributes different */ struct NVERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct NVERIFY4res { nfsstat4 status; }; /* * Various definitions for OPEN */ enum createmode4 { UNCHECKED4 = 0, GUARDED4 = 1, EXCLUSIVE4 = 2 }; union createhow4 switch (createmode4 mode) { case UNCHECKED4: case GUARDED4: fattr4 createattrs; case EXCLUSIVE4: verifier4 createverf; }; enum opentype4 { OPEN4_NOCREATE = 0, OPEN4_CREATE = 1 }; union openflag4 switch (opentype4 opentype) { case OPEN4_CREATE: createhow4 how; default: void; }; /* Next definitions used for OPEN delegation */ enum limit_by4 { NFS_LIMIT_SIZE = 1, NFS_LIMIT_BLOCKS = 2 /* others as needed */ }; struct nfs_modified_limit4 { uint32_t num_blocks; uint32_t bytes_per_block; }; union nfs_space_limit4 switch (limit_by4 limitby) { /* limit specified as file size */ case NFS_LIMIT_SIZE: uint64_t filesize; /* limit specified by number of blocks */ case NFS_LIMIT_BLOCKS: nfs_modified_limit4 mod_blocks; } ; /* * Share Access and Deny constants for open argument */ const OPEN4_SHARE_ACCESS_READ = 0x00000001; const OPEN4_SHARE_ACCESS_WRITE = 0x00000002; const OPEN4_SHARE_ACCESS_BOTH = 0x00000003; const OPEN4_SHARE_DENY_NONE = 0x00000000; const OPEN4_SHARE_DENY_READ = 0x00000001; const OPEN4_SHARE_DENY_WRITE = 0x00000002; const OPEN4_SHARE_DENY_BOTH = 0x00000003; enum open_delegation_type4 { OPEN_DELEGATE_NONE = 0, OPEN_DELEGATE_READ = 1, OPEN_DELEGATE_WRITE = 2 }; enum open_claim_type4 { CLAIM_NULL = 0, CLAIM_PREVIOUS = 1, CLAIM_DELEGATE_CUR = 2, CLAIM_DELEGATE_PREV = 3, CLAIM_FH = 4, /* new to v4.1 */ CLAIM_DELEG_CUR_FH = 5, /* new to v4.1 */ CLAIM_DELEG_PREV_FH = 6 /* new to v4.1 */ }; struct open_claim_delegate_cur4 { stateid4 delegate_stateid; component4 file; }; union open_claim4 switch (open_claim_type4 claim) { /* * No special rights to file. Ordinary OPEN of the specified file. */ case CLAIM_NULL: /* CURRENT_FH: directory */ component4 file; /* * Right to the file established by an open previous to server * reboot. File identified by filehandle obtained at that time * rather than by name. */ case CLAIM_PREVIOUS: /* CURRENT_FH: file being reclaimed */ open_delegation_type4 delegate_type; /* * Right to file based on a delegation granted by the server. * File is specified by name. */ case CLAIM_DELEGATE_CUR: /* CURRENT_FH: directory */ open_claim_delegate_cur4 delegate_cur_info; /* Right to file based on a delegation granted to a previous boot * instance of the client. File is specified by name. */ case CLAIM_DELEGATE_PREV: /* CURRENT_FH: directory */ component4 file_delegate_prev; }; /* * OPEN: Open a file, potentially receiving an open delegation */ struct OPEN4args { seqid4 seqid; uint32_t share_access; uint32_t share_deny; open_owner4 owner; openflag4 openhow; open_claim4 claim; }; struct open_read_delegation4 { stateid4 stateid; /* Stateid for delegation*/ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS) */ nfsace4 permissions; /* Defines users who don't need an ACCESS call to open for read */ }; struct open_write_delegation4 { stateid4 stateid; /* Stateid for delegation */ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS) */ nfs_space_limit4 space_limit; /* Defines condition that the client must check to determine whether the file needs to be flushed to the server on close. */ nfsace4 permissions; /* Defines users who don't need an ACCESS call as part of a delegated open. */ }; union open_delegation4 switch (open_delegation_type4 delegation_type) { case OPEN_DELEGATE_NONE: void; case OPEN_DELEGATE_READ: open_read_delegation4 read; case OPEN_DELEGATE_WRITE: open_write_delegation4 write; }; /* * Result flags */ /* Client must confirm open */ const OPEN4_RESULT_CONFIRM = 0x00000002; /* Type of file locking behavior at the server */ const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004; struct OPEN4resok { stateid4 stateid; /* Stateid for open */ change_info4 cinfo; /* Directory Change Info */ uint32_t rflags; /* Result flags */ bitmap4 attrset; /* attribute set for create*/ open_delegation4 delegation; /* Info on any open delegation */ }; union OPEN4res switch (nfsstat4 status) { case NFS4_OK: /* CURRENT_FH: opened file */ OPEN4resok resok4; default: void; }; /* * OPENATTR: open named attributes directory */ struct OPENATTR4args { /* CURRENT_FH: object */ bool createdir; }; struct OPENATTR4res { /* CURRENT_FH: named attr directory */ nfsstat4 status; }; /* * OPEN_CONFIRM: confirm the open */ struct OPEN_CONFIRM4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; }; struct OPEN_CONFIRM4resok { stateid4 open_stateid; }; union OPEN_CONFIRM4res switch (nfsstat4 status) { case NFS4_OK: OPEN_CONFIRM4resok resok4; default: void; }; /* * OPEN_DOWNGRADE: downgrade the access/deny for a file */ struct OPEN_DOWNGRADE4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; uint32_t share_access; uint32_t share_deny; }; struct OPEN_DOWNGRADE4resok { stateid4 open_stateid; }; union OPEN_DOWNGRADE4res switch(nfsstat4 status) { case NFS4_OK: OPEN_DOWNGRADE4resok resok4; default: void; }; /* * PUTFH: Set current filehandle */ struct PUTFH4args { nfs_fh4 object; }; struct PUTFH4res { /* CURRENT_FH: */ nfsstat4 status; }; /* * PUTPUBFH: Set public filehandle */ struct PUTPUBFH4res { /* CURRENT_FH: public fh */ nfsstat4 status; }; /* * PUTROOTFH: Set root filehandle */ struct PUTROOTFH4res { /* CURRENT_FH: root fh */ nfsstat4 status; }; /* * READ: Read from file */ struct READ4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; count4 count; }; struct READ4resok { bool eof; opaque data<>; }; union READ4res switch (nfsstat4 status) { case NFS4_OK: READ4resok resok4; default: void; }; /* * READDIR: Read directory */ struct READDIR4args { /* CURRENT_FH: directory */ nfs_cookie4 cookie; verifier4 cookieverf; count4 dircount; count4 maxcount; bitmap4 attr_request; }; struct entry4 { nfs_cookie4 cookie; component4 name; fattr4 attrs; entry4 *nextentry; }; struct dirlist4 { entry4 *entries; bool eof; }; struct READDIR4resok { verifier4 cookieverf; dirlist4 reply; }; union READDIR4res switch (nfsstat4 status) { case NFS4_OK: READDIR4resok resok4; default: void; }; /* * READLINK: Read symbolic link */ struct READLINK4resok { linktext4 link; }; union READLINK4res switch (nfsstat4 status) { case NFS4_OK: READLINK4resok resok4; default: void; }; /* * REMOVE: Remove filesystem object */ struct REMOVE4args { /* CURRENT_FH: directory */ component4 target; }; struct REMOVE4resok { change_info4 cinfo; }; union REMOVE4res switch (nfsstat4 status) { case NFS4_OK: REMOVE4resok resok4; default: void; }; /* * RENAME: Rename directory entry */ struct RENAME4args { /* SAVED_FH: source directory */ component4 oldname; /* CURRENT_FH: target directory */ component4 newname; }; struct RENAME4resok { change_info4 source_cinfo; change_info4 target_cinfo; }; union RENAME4res switch (nfsstat4 status) { case NFS4_OK: RENAME4resok resok4; default: void; }; /* * RENEW: Renew a Lease */ struct RENEW4args { clientid4 clientid; }; struct RENEW4res { nfsstat4 status; }; /* * RESTOREFH: Restore saved filehandle */ struct RESTOREFH4res { /* CURRENT_FH: value of saved fh */ nfsstat4 status; }; /* * SAVEFH: Save current filehandle */ struct SAVEFH4res { /* SAVED_FH: value of current fh */ nfsstat4 status; }; /* * SECINFO: Obtain Available Security Mechanisms */ struct SECINFO4args { component4 name; }; enum rpc_gss_svc_t { RPC_GSS_SVC_NONE = 1, RPC_GSS_SVC_INTEGRITY = 2, RPC_GSS_SVC_PRIVACY = 3 }; struct rpcsec_gss_info { sec_oid4 oid; qop4 qop; rpc_gss_svc_t service; }; const RPCSEC_GSS = 6; union secinfo4 switch (uint32_t flavor) { case RPCSEC_GSS: rpcsec_gss_info flavor_info; default: void; }; typedef secinfo4 SECINFO4resok<>; union SECINFO4res switch (nfsstat4 status) { case NFS4_OK: SECINFO4resok resok4; default: void; }; /* * SETATTR: Set attributes */ struct SETATTR4args { /* CURRENT_FH: target object */ stateid4 stateid; fattr4 obj_attributes; }; struct SETATTR4res { nfsstat4 status; bitmap4 attrsset; }; /* * SETCLIENTID */ struct SETCLIENTID4args { nfs_client_id4 client; cb_client4 callback; uint32_t callback_ident; }; struct SETCLIENTID4resok { clientid4 clientid; verifier4 setclientid_confirm; }; union SETCLIENTID4res switch (nfsstat4 status) { case NFS4_OK: SETCLIENTID4resok resok4; case NFS4ERR_CLID_INUSE: clientaddr4 client_using; default: void; }; struct SETCLIENTID_CONFIRM4args { clientid4 clientid; verifier4 setclientid_confirm; }; struct SETCLIENTID_CONFIRM4res { nfsstat4 status; }; /* * VERIFY: Verify attributes same */ struct VERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct VERIFY4res { nfsstat4 status; }; /* * WRITE: Write to file */ enum stable_how4 { UNSTABLE4 = 0, DATA_SYNC4 = 1, FILE_SYNC4 = 2 }; struct WRITE4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; stable_how4 stable; opaque data<>; }; struct WRITE4resok { count4 count; stable_how4 committed; verifier4 writeverf; }; union WRITE4res switch (nfsstat4 status) { case NFS4_OK: WRITE4resok resok4; default: void; }; /* * RELEASE_LOCKOWNER: Notify server to release lockowner */ struct RELEASE_LOCKOWNER4args { lock_owner4 lock_owner; }; struct RELEASE_LOCKOWNER4res { nfsstat4 status; }; /* * BACKCHANNEL_CTL */ /* typedef opaque gsshandle4_t<>; struct gss_cb_handles4 { rpc_gss_svc_t gcbp_service; RFC 2203 gsshandle4_t gcbp_handle_from_server; gsshandle4_t gcbp_handle_from_client; }; */ union callback_sec_parms4 switch (uint32_t cb_secflavor) { case AUTH_NONE: void; case AUTH_SYS: authsys_parms cbsp_sys_cred; /* RFC 1831 */ /* * case RPCSEC_GSS: * gss_cb_handles4 cbsp_gss_handles; */ }; /* struct BACKCHANNEL_CTL4args { uint32_t bca_cb_program; callback_sec_parms4 bca_sec_parms<>; }; */ /* * CREATE_SESSION */ struct channel_attrs4 { count4 ca_headerpadsize; count4 ca_maxrequestsize; count4 ca_maxresponsesize; count4 ca_maxresponsesize_cached; count4 ca_maxoperations; count4 ca_maxrequests; uint32_t ca_rdma_ird<1>; }; const CREATE_SESSION4_FLAG_PERSIST = 0x00000001; const CREATE_SESSION4_FLAG_CONN_BACK_CHAN = 0x00000002; const CREATE_SESSION4_FLAG_CONN_RDMA = 0x00000004; struct CREATE_SESSION4args { clientid4 csa_clientid; sequenceid4 csa_sequence; uint32_t csa_flags; channel_attrs4 csa_fore_chan_attrs; channel_attrs4 csa_back_chan_attrs; uint32_t csa_cb_program; callback_sec_parms4 csa_sec_parms<>; }; struct CREATE_SESSION4resok { sessionid4 csr_sessionid; sequenceid4 csr_sequence; uint32_t csr_flags; channel_attrs4 csr_fore_chan_attrs; channel_attrs4 csr_back_chan_attrs; }; union CREATE_SESSION4res switch (nfsstat4 csr_status) { case NFS4_OK: CREATE_SESSION4resok csr_resok4; default: void; }; /* * DESTROY_SESSION */ struct DESTROY_SESSION4args { sessionid4 dsa_sessionid; }; struct DESTROY_SESSION4res { nfsstat4 dsr_status; }; /* * FREE_STATEID */ struct FREE_STATEID4args { stateid4 fsa_stateid; }; struct FREE_STATEID4res { nfsstat4 fsr_status; }; /* * GET_DIR_DELEGATION */ typedef nfstime4 attr_notice4; struct GET_DIR_DELEGATION4args { bool gdda_signal_deleg_avail; bitmap4 gdda_notification_types; attr_notice4 gdda_child_attr_delay; attr_notice4 gdda_dir_attr_delay; bitmap4 gdda_child_attributes; bitmap4 gdda_dir_attributes; }; struct GET_DIR_DELEGATION4resok { verifier4 gddr_cookieverf; stateid4 gddr_stateid; bitmap4 gddr_notification; bitmap4 gddr_child_attributes; bitmap4 gddr_dir_attributes; }; enum gddrnf4_status { GDD4_OK = 0, GDD4_UNAVAIL = 1 }; union GET_DIR_DELEGATION4res_non_fatal switch (gddrnf4_status gddrnf_status) { case GDD4_OK: GET_DIR_DELEGATION4resok gddrnf_resok4; case GDD4_UNAVAIL: bool gddrnf_will_signal_deleg_avail; }; union GET_DIR_DELEGATION4res switch (nfsstat4 gddr_status) { case NFS4_OK: GET_DIR_DELEGATION4res_non_fatal gddr_res_non_fatal4; default: void; }; /* * GETDEVICEINFO */ struct GETDEVICEINFO4args { deviceid4 gdia_device_id; layouttype4 gdia_layout_type; count4 gdia_maxcount; bitmap4 gdia_notify_types; }; struct GETDEVICEINFO4resok { device_addr4 gdir_device_addr; bitmap4 gdir_notification; }; union GETDEVICEINFO4res switch (nfsstat4 gdir_status) { case NFS4_OK: GETDEVICEINFO4resok gdir_resok4; case NFS4ERR_TOOSMALL: count4 gdir_mincount; default: void; }; /* * GETDEVICELIST */ struct GETDEVICELIST4args { layouttype4 gdla_layout_type; count4 gdla_maxdevices; nfs_cookie4 gdla_cookie; verifier4 gdla_cookieverf; }; struct GETDEVICELIST4resok { nfs_cookie4 gdlr_cookie; verifier4 gdlr_cookieverf; deviceid4 gdlr_deviceid_list<>; bool gdlr_eof; }; union GETDEVICELIST4res switch (nfsstat4 gdlr_status) { case NFS4_OK: GETDEVICELIST4resok gdlr_resok4; default: void; }; /* * LAYOUTCOMMIT */ union newtime4 switch (bool nt_timechanged) { case TRUE: nfstime4 nt_time; case FALSE: void; }; union newoffset4 switch (bool no_newoffset) { case TRUE: offset4 no_offset; case FALSE: void; }; struct LAYOUTCOMMIT4args { offset4 loca_offset; length4 loca_length; bool loca_reclaim; stateid4 loca_stateid; newoffset4 loca_last_write_offset; newtime4 loca_time_modify; layoutupdate4 loca_layoutupdate; }; union newsize4 switch (bool ns_sizechanged) { case TRUE: length4 ns_size; case FALSE: void; }; struct LAYOUTCOMMIT4resok { newsize4 locr_newsize; }; union LAYOUTCOMMIT4res switch (nfsstat4 locr_status) { case NFS4_OK: LAYOUTCOMMIT4resok locr_resok4; default: void; }; /* * LAYOUTGET */ struct LAYOUTGET4args { bool loga_signal_layout_avail; layouttype4 loga_layout_type; layoutiomode4 loga_iomode; offset4 loga_offset; length4 loga_length; length4 loga_minlength; stateid4 loga_stateid; count4 loga_maxcount; }; struct LAYOUTGET4resok { bool logr_return_on_close; stateid4 logr_stateid; layout4 logr_layout<>; }; union LAYOUTGET4res switch (nfsstat4 logr_status) { case NFS4_OK: LAYOUTGET4resok logr_resok4; case NFS4ERR_LAYOUTTRYLATER: bool logr_will_signal_layout_avail; default: void; }; /* * LAYOUTRETURN */ const LAYOUT4_RET_REC_FILE = 1; const LAYOUT4_RET_REC_FSID = 2; const LAYOUT4_RET_REC_ALL = 3; enum layoutreturn_type4 { LAYOUTRETURN4_FILE = LAYOUT4_RET_REC_FILE, LAYOUTRETURN4_FSID = LAYOUT4_RET_REC_FSID, LAYOUTRETURN4_ALL = LAYOUT4_RET_REC_ALL }; struct layoutreturn_file4 { offset4 lrf_offset; length4 lrf_length; stateid4 lrf_stateid; opaque lrf_body<>; }; union layoutreturn4 switch(layoutreturn_type4 lr_returntype) { case LAYOUTRETURN4_FILE: layoutreturn_file4 lr_layout; default: void; }; struct LAYOUTRETURN4args { bool lora_reclaim; layouttype4 lora_layout_type; layoutiomode4 lora_iomode; layoutreturn4 lora_layoutreturn; }; union layoutreturn_stateid switch (bool lrs_present) { case TRUE: stateid4 lrs_stateid; case FALSE: void; }; union LAYOUTRETURN4res switch (nfsstat4 lorr_status) { case NFS4_OK: layoutreturn_stateid lorr_stateid; default: void; }; /* * SECINFO_NO_NAME */ enum secinfo_style4 { SECINFO_STYLE4_CURRENT_FH = 0, SECINFO_STYLE4_PARENT = 1 }; typedef secinfo_style4 SECINFO_NO_NAME4args; typedef SECINFO4res SECINFO_NO_NAME4res; /* * SEQUENCE */ struct SEQUENCE4args { sessionid4 sa_sessionid; sequenceid4 sa_sequenceid; slotid4 sa_slotid; slotid4 sa_highest_slotid; bool sa_cachethis; }; const SEQ4_STATUS_CB_PATH_DOWN = 0x00000001; const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING = 0x00000002; const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED = 0x00000004; const SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED = 0x00000008; const SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED = 0x00000010; const SEQ4_STATUS_ADMIN_STATE_REVOKED = 0x00000020; const SEQ4_STATUS_RECALLABLE_STATE_REVOKED = 0x00000040; const SEQ4_STATUS_LEASE_MOVED = 0x00000080; const SEQ4_STATUS_RESTART_RECLAIM_NEEDED = 0x00000100; const SEQ4_STATUS_CB_PATH_DOWN_SESSION = 0x00000200; const SEQ4_STATUS_BACKCHANNEL_FAULT = 0x00000400; const SEQ4_STATUS_DEVID_CHANGED = 0x00000800; const SEQ4_STATUS_DEVID_DELETED = 0x00001000; struct SEQUENCE4resok { sessionid4 sr_sessionid; sequenceid4 sr_sequenceid; slotid4 sr_slotid; slotid4 sr_highest_slotid; slotid4 sr_target_highest_slotid; uint32_t sr_status_flags; }; union SEQUENCE4res switch (nfsstat4 sr_status) { case NFS4_OK: SEQUENCE4resok sr_resok4; default: void; }; /* * SET_SSV */ struct ssa_digest_input4 { SEQUENCE4args sdi_seqargs; }; struct SET_SSV4args { opaque ssa_ssv<>; opaque ssa_digest<>; }; struct ssr_digest_input4 { SEQUENCE4res sdi_seqres; }; struct SET_SSV4resok { opaque ssr_digest<>; }; union SET_SSV4res switch (nfsstat4 ssr_status) { case NFS4_OK: SET_SSV4resok ssr_resok4; default: void; }; /* * TEST_STATEID */ struct TEST_STATEID4args { stateid4 ts_stateids<>; }; struct TEST_STATEID4resok { nfsstat4 tsr_status_codes<>; }; union TEST_STATEID4res switch (nfsstat4 tsr_status) { case NFS4_OK: TEST_STATEID4resok tsr_resok4; default: void; }; /* * WANT_DELEGATION */ union deleg_claim4 switch (open_claim_type4 dc_claim) { case CLAIM_FH: void; case CLAIM_DELEG_PREV_FH: void; case CLAIM_PREVIOUS: open_delegation_type4 dc_delegate_type; }; struct WANT_DELEGATION4args { uint32_t wda_want; deleg_claim4 wda_claim; }; union WANT_DELEGATION4res switch (nfsstat4 wdr_status) { case NFS4_OK: open_delegation4 wdr_resok4; default: void; }; /* * DESTROY_CLIENTID */ struct DESTROY_CLIENTID4args { clientid4 dca_clientid; }; struct DESTROY_CLIENTID4res { nfsstat4 dcr_status; }; /* * RECLAIM_COMPLETE */ struct RECLAIM_COMPLETE4args { bool rca_one_fs; }; struct RECLAIM_COMPLETE4res { nfsstat4 rcr_status; }; /* * ILLEGAL: Response for illegal operation numbers */ struct ILLEGAL4res { nfsstat4 status; }; /* * Operation arrays */ enum nfs_opnum4 { OP_ACCESS = 3, OP_CLOSE = 4, OP_COMMIT = 5, OP_CREATE = 6, OP_DELEGPURGE = 7, OP_DELEGRETURN = 8, OP_GETATTR = 9, OP_GETFH = 10, OP_LINK = 11, OP_LOCK = 12, OP_LOCKT = 13, OP_LOCKU = 14, OP_LOOKUP = 15, OP_LOOKUPP = 16, OP_NVERIFY = 17, OP_OPEN = 18, OP_OPENATTR = 19, OP_OPEN_CONFIRM = 20, OP_OPEN_DOWNGRADE = 21, OP_PUTFH = 22, OP_PUTPUBFH = 23, OP_PUTROOTFH = 24, OP_READ = 25, OP_READDIR = 26, OP_READLINK = 27, OP_REMOVE = 28, OP_RENAME = 29, OP_RENEW = 30, OP_RESTOREFH = 31, OP_SAVEFH = 32, OP_SECINFO = 33, OP_SETATTR = 34, OP_SETCLIENTID = 35, OP_SETCLIENTID_CONFIRM = 36, OP_VERIFY = 37, OP_WRITE = 38, OP_RELEASE_LOCKOWNER = 39, OP_CREATE_SESSION = 43, OP_DESTROY_SESSION = 44, OP_FREE_STATEID = 45, OP_GET_DIR_DELEGATION = 46, OP_GETDEVICEINFO = 47, OP_GETDEVICELIST = 48, OP_LAYOUTCOMMIT = 49, OP_LAYOUTGET = 50, OP_LAYOUTRETURN = 51, OP_SECINFO_NO_NAME = 52, OP_SEQUENCE = 53, OP_SET_SSV = 54, OP_TEST_STATEID = 55, OP_WANT_DELEGATION = 56, OP_DESTROY_CLIENTID = 57, OP_RECLAIM_COMPLETE = 58, OP_ILLEGAL = 10044 }; union nfs_argop4 switch (nfs_opnum4 argop) { case OP_ACCESS: ACCESS4args opaccess; case OP_CLOSE: CLOSE4args opclose; case OP_COMMIT: COMMIT4args opcommit; case OP_CREATE: CREATE4args opcreate; case OP_DELEGPURGE: DELEGPURGE4args opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4args opdelegreturn; case OP_GETATTR: GETATTR4args opgetattr; case OP_GETFH: void; case OP_LINK: LINK4args oplink; case OP_LOCK: LOCK4args oplock; case OP_LOCKT: LOCKT4args oplockt; case OP_LOCKU: LOCKU4args oplocku; case OP_LOOKUP: LOOKUP4args oplookup; case OP_LOOKUPP: void; case OP_NVERIFY: NVERIFY4args opnverify; case OP_OPEN: OPEN4args opopen; case OP_OPENATTR: OPENATTR4args opopenattr; case OP_OPEN_CONFIRM: OPEN_CONFIRM4args opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4args opopen_downgrade; case OP_PUTFH: PUTFH4args opputfh; case OP_PUTPUBFH: void; case OP_PUTROOTFH: void; case OP_READ: READ4args opread; case OP_READDIR: READDIR4args opreaddir; case OP_READLINK: void; case OP_REMOVE: REMOVE4args opremove; case OP_RENAME: RENAME4args oprename; case OP_RENEW: RENEW4args oprenew; case OP_RESTOREFH: void; case OP_SAVEFH: void; case OP_SECINFO: SECINFO4args opsecinfo; case OP_SETATTR: SETATTR4args opsetattr; case OP_SETCLIENTID: SETCLIENTID4args opsetclientid; case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4args opsetclientid_confirm; case OP_VERIFY: VERIFY4args opverify; case OP_WRITE: WRITE4args opwrite; case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4args oprelease_lockowner; case OP_CREATE_SESSION: CREATE_SESSION4args opcreatesession; case OP_DESTROY_SESSION: DESTROY_SESSION4args opdestroysession; case OP_FREE_STATEID: FREE_STATEID4args opfreestateid; case OP_GET_DIR_DELEGATION: GET_DIR_DELEGATION4args opgetdirdelegation; case OP_GETDEVICEINFO: GETDEVICEINFO4args opgetdeviceinfo; case OP_GETDEVICELIST: GETDEVICELIST4args opgetdevicelist; case OP_LAYOUTCOMMIT: LAYOUTCOMMIT4args oplayoutcommit; case OP_LAYOUTGET: LAYOUTGET4args oplayoutget; case OP_LAYOUTRETURN: LAYOUTRETURN4args oplayoutreturn; case OP_SECINFO_NO_NAME: SECINFO_NO_NAME4args opsecinfononame; case OP_SEQUENCE: SEQUENCE4args opsequence; case OP_SET_SSV: SET_SSV4args opsetssv; case OP_TEST_STATEID: TEST_STATEID4args opteststateid; case OP_WANT_DELEGATION: WANT_DELEGATION4args opwantdelegation; case OP_DESTROY_CLIENTID: DESTROY_CLIENTID4args opdestroyclientid; case OP_RECLAIM_COMPLETE: RECLAIM_COMPLETE4args opreclaimcomplete; case OP_ILLEGAL: void; }; union nfs_resop4 switch (nfs_opnum4 resop){ case OP_ACCESS: ACCESS4res opaccess; case OP_CLOSE: CLOSE4res opclose; case OP_COMMIT: COMMIT4res opcommit; case OP_CREATE: CREATE4res opcreate; case OP_DELEGPURGE: DELEGPURGE4res opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4res opdelegreturn; case OP_GETATTR: GETATTR4res opgetattr; case OP_GETFH: GETFH4res opgetfh; case OP_LINK: LINK4res oplink; case OP_LOCK: LOCK4res oplock; case OP_LOCKT: LOCKT4res oplockt; case OP_LOCKU: LOCKU4res oplocku; case OP_LOOKUP: LOOKUP4res oplookup; case OP_LOOKUPP: LOOKUPP4res oplookupp; case OP_NVERIFY: NVERIFY4res opnverify; case OP_OPEN: OPEN4res opopen; case OP_OPENATTR: OPENATTR4res opopenattr; case OP_OPEN_CONFIRM: OPEN_CONFIRM4res opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4res opopen_downgrade; case OP_PUTFH: PUTFH4res opputfh; case OP_PUTPUBFH: PUTPUBFH4res opputpubfh; case OP_PUTROOTFH: PUTROOTFH4res opputrootfh; case OP_READ: READ4res opread; case OP_READDIR: READDIR4res opreaddir; case OP_READLINK: READLINK4res opreadlink; case OP_REMOVE: REMOVE4res opremove; case OP_RENAME: RENAME4res oprename; case OP_RENEW: RENEW4res oprenew; case OP_RESTOREFH: RESTOREFH4res oprestorefh; case OP_SAVEFH: SAVEFH4res opsavefh; case OP_SECINFO: SECINFO4res opsecinfo; case OP_SETATTR: SETATTR4res opsetattr; case OP_SETCLIENTID: SETCLIENTID4res opsetclientid; case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4res opsetclientid_confirm; case OP_VERIFY: VERIFY4res opverify; case OP_WRITE: WRITE4res opwrite; case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4res oprelease_lockowner; case OP_CREATE_SESSION: CREATE_SESSION4res opcreatesession; case OP_DESTROY_SESSION: DESTROY_SESSION4res opdestroysession; case OP_FREE_STATEID: FREE_STATEID4res opfreestateid; case OP_GET_DIR_DELEGATION: GET_DIR_DELEGATION4res opgetdirdelegation; case OP_GETDEVICEINFO: GETDEVICEINFO4res opgetdeviceinfo; case OP_GETDEVICELIST: GETDEVICELIST4res opgetdevicelist; case OP_LAYOUTCOMMIT: LAYOUTCOMMIT4res oplayoutcommit; case OP_LAYOUTGET: LAYOUTGET4res oplayoutget; case OP_LAYOUTRETURN: LAYOUTRETURN4res oplayoutreturn; case OP_SECINFO_NO_NAME: SECINFO_NO_NAME4res opsecinfononame; case OP_SEQUENCE: SEQUENCE4res opsequence; case OP_SET_SSV: SET_SSV4res opsetssv; case OP_TEST_STATEID: TEST_STATEID4res opteststateid; case OP_WANT_DELEGATION: WANT_DELEGATION4res opwantdelegation; case OP_DESTROY_CLIENTID: DESTROY_CLIENTID4res opdestroyclientid; case OP_RECLAIM_COMPLETE: RECLAIM_COMPLETE4res opreclaimcomplete; case OP_ILLEGAL: ILLEGAL4res opillegal; }; struct COMPOUND4args { utf8str_cs tag; uint32_t minorversion; nfs_argop4 argarray<>; }; struct COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_resop4 resarray<>; }; /* * Remote file service routines */ program NFS4_PROGRAM { version NFS_V4 { void NFSPROC4_NULL(void) = 0; COMPOUND4res NFSPROC4_COMPOUND(COMPOUND4args) = 1; } = 4; } = 100003; /* * NFS4 Callback Procedure Definitions and Program */ /* * CB_GETATTR: Get Current Attributes */ struct CB_GETATTR4args { nfs_fh4 fh; bitmap4 attr_request; }; struct CB_GETATTR4resok { fattr4 obj_attributes; }; union CB_GETATTR4res switch (nfsstat4 status) { case NFS4_OK: CB_GETATTR4resok resok4; default: void; }; /* * CB_RECALL: Recall an Open Delegation */ struct CB_RECALL4args { stateid4 stateid; bool truncate; nfs_fh4 fh; }; struct CB_RECALL4res { nfsstat4 status; }; /* * CB_ILLEGAL: Response for illegal operation numbers */ struct CB_ILLEGAL4res { nfsstat4 status; }; /* * Various definitions for CB_COMPOUND */ enum nfs_cb_opnum4 { OP_CB_GETATTR = 3, OP_CB_RECALL = 4, OP_CB_ILLEGAL = 10044 }; union nfs_cb_argop4 switch (unsigned argop) { case OP_CB_GETATTR: CB_GETATTR4args opcbgetattr; case OP_CB_RECALL: CB_RECALL4args opcbrecall; case OP_CB_ILLEGAL: void; }; union nfs_cb_resop4 switch (unsigned resop){ case OP_CB_GETATTR: CB_GETATTR4res opcbgetattr; case OP_CB_RECALL: CB_RECALL4res opcbrecall; case OP_CB_ILLEGAL: CB_ILLEGAL4res opcbillegal; }; struct CB_COMPOUND4args { utf8str_cs tag; uint32_t minorversion; uint32_t callback_ident; nfs_cb_argop4 argarray<>; }; struct CB_COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_cb_resop4 resarray<>; }; /* * Program number is in the transient range since the client * will assign the exact transient program number and provide * that to the server via the SETCLIENTID operation. */ program NFS4_CALLBACK { version NFS_CB { void CB_NULL(void) = 0; CB_COMPOUND4res CB_COMPOUND(CB_COMPOUND4args) = 1; } = 1; } = 0x40000000; ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/nfsconst.go ================================================ package internal /* const OPEN4_SHARE_ACCESS_READ = 0x00000001 const OPEN4_SHARE_ACCESS_WRITE = 0x00000002 const OPEN4_SHARE_ACCESS_BOTH = 0x00000003 const OPEN4_SHARE_DENY_NONE = 0x00000000 const OPEN4_SHARE_DENY_READ = 0x00000001 const OPEN4_SHARE_DENY_WRITE = 0x00000002 const OPEN4_SHARE_DENY_BOTH = 0x00000003 */ ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/rpc.go ================================================ // Code generated by goxdr -B -p nfs4 nfs4/rpc.x; DO NOT EDIT. package internal import ( "context" "fmt" ) var _ = fmt.Sprintf var _ context.Context // // Data types defined in XDR file // type Auth_flavor int32 const ( AUTH_NONE Auth_flavor = 0 AUTH_SYS Auth_flavor = 1 AUTH_SHORT Auth_flavor = 2 AUTH_DH Auth_flavor = 3 ) type Opaque_auth struct { Flavor Auth_flavor Body []byte // bound 400 } type Msg_type int32 const ( CALL Msg_type = 0 REPLY Msg_type = 1 ) /* A reply to a call message can take on two forms: the message was either accepted or rejected. */ type Reply_stat int32 const ( MSG_ACCEPTED Reply_stat = 0 MSG_DENIED Reply_stat = 1 ) /* Given that a call message was accepted, the following is the status of an attempt to call a remote procedure. */ type Accept_stat int32 const ( /* RPC executed successfully */ SUCCESS Accept_stat = 0 /* remote hasn't exported program */ PROG_UNAVAIL Accept_stat = 1 /* remote can't support version # */ PROG_MISMATCH Accept_stat = 2 /* program can't support procedure */ PROC_UNAVAIL Accept_stat = 3 /* procedure can't decode params */ GARBAGE_ARGS Accept_stat = 4 /* e.g. memory allocation failure */ SYSTEM_ERR Accept_stat = 5 ) /* Reasons why a call message was rejected: */ type Reject_stat int32 const ( /* RPC version number != 2 */ RPC_MISMATCH Reject_stat = 0 /* remote can't authenticate caller */ AUTH_ERROR Reject_stat = 1 ) /* Why authentication failed: */ type Auth_stat int32 const ( /* success */ AUTH_OK Auth_stat = 0 /* bad credential (seal broken) */ AUTH_BADCRED Auth_stat = 1 /* client must begin new session */ AUTH_REJECTEDCRED Auth_stat = 2 /* bad verifier (seal broken) */ AUTH_BADVERF Auth_stat = 3 /* verifier expired or replayed */ AUTH_REJECTEDVERF Auth_stat = 4 /* rejected for security reasons */ AUTH_TOOWEAK Auth_stat = 5 /* bogus response verifier */ AUTH_INVALIDRESP Auth_stat = 6 /* reason unknown */ AUTH_FAILED Auth_stat = 7 /* kerberos generic error */ AUTH_KERB_GENERIC Auth_stat = 8 /* time of credential expired */ AUTH_TIMEEXPIRE Auth_stat = 9 /* problem with ticket file */ AUTH_TKT_FILE Auth_stat = 10 /* can't decode authenticator */ AUTH_DECODE Auth_stat = 11 /* wrong net address in ticket */ AUTH_NET_ADDR Auth_stat = 12 /* no credentials for user */ RPCSEC_GSS_CREDPROBLEM Auth_stat = 13 /* problem with context */ RPCSEC_GSS_CTXPROBLEM Auth_stat = 14 ) /* Body of an RPC call: */ type Call_body struct { /* must be equal to two (2) */ Rpcvers uint32 Prog uint32 Vers uint32 Proc uint32 Cred Opaque_auth Verf Opaque_auth } /* Reply to an RPC call that was accepted by the server: */ type Accepted_reply struct { Verf Opaque_auth Reply_data XdrAnon_Accepted_reply_Reply_data } type XdrAnon_Accepted_reply_Reply_data struct { // The union discriminant Stat selects among the following arms: // SUCCESS: // Results() *[0]byte // PROG_MISMATCH: // Mismatch_info() *XdrAnon_Accepted_reply_Reply_data_Mismatch_info // default: // void Stat Accept_stat U interface{} } type XdrAnon_Accepted_reply_Reply_data_Mismatch_info struct { Low uint32 High uint32 } /* Reply to an RPC call that was rejected by the server: */ type Rejected_reply struct { // The union discriminant Stat selects among the following arms: // RPC_MISMATCH: // Mismatch_info() *XdrAnon_Rejected_reply_Mismatch_info // AUTH_ERROR: // Rj_why() *Auth_stat Stat Reject_stat U interface{} } type XdrAnon_Rejected_reply_Mismatch_info struct { Low uint32 High uint32 } /* Body of a reply to an RPC call: */ type Reply_body struct { // The union discriminant Stat selects among the following arms: // MSG_ACCEPTED: // Areply() *Accepted_reply // MSG_DENIED: // Rreply() *Rejected_reply Stat Reply_stat U interface{} } /* The RPC message: */ type Rpc_msg struct { Xid uint32 Body XdrAnon_Rpc_msg_Body } type XdrAnon_Rpc_msg_Body struct { // The union discriminant Mtype selects among the following arms: // CALL: // Cbody() *Call_body // REPLY: // Rbody() *Reply_body Mtype Msg_type U interface{} } // // Helper types and generated marshaling functions // var _XdrNames_Auth_flavor = map[int32]string{ int32(AUTH_NONE): "AUTH_NONE", int32(AUTH_SYS): "AUTH_SYS", int32(AUTH_SHORT): "AUTH_SHORT", int32(AUTH_DH): "AUTH_DH", int32(RPCSEC_GSS): "RPCSEC_GSS", } var _XdrValues_Auth_flavor = map[string]int32{ "AUTH_NONE": int32(AUTH_NONE), "AUTH_SYS": int32(AUTH_SYS), "AUTH_SHORT": int32(AUTH_SHORT), "AUTH_DH": int32(AUTH_DH), "RPCSEC_GSS": int32(RPCSEC_GSS), } func (Auth_flavor) XdrEnumNames() map[int32]string { return _XdrNames_Auth_flavor } func (v Auth_flavor) String() string { if s, ok := _XdrNames_Auth_flavor[int32(v)]; ok { return s } return fmt.Sprintf("Auth_flavor#%d", v) } func (v *Auth_flavor) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Auth_flavor[stok]; ok { *v = Auth_flavor(val) return nil } else if stok == "Auth_flavor" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Auth_flavor.", stok)) } } func (v Auth_flavor) GetU32() uint32 { return uint32(v) } func (v *Auth_flavor) SetU32(n uint32) { *v = Auth_flavor(n) } func (v *Auth_flavor) XdrPointer() interface{} { return v } func (Auth_flavor) XdrTypeName() string { return "Auth_flavor" } func (v Auth_flavor) XdrValue() interface{} { return v } func (v *Auth_flavor) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Auth_flavor = *Auth_flavor func XDR_Auth_flavor(v *Auth_flavor) *Auth_flavor { return v } type XdrType_Opaque_auth = *Opaque_auth func (v *Opaque_auth) XdrPointer() interface{} { return v } func (Opaque_auth) XdrTypeName() string { return "Opaque_auth" } func (v Opaque_auth) XdrValue() interface{} { return v } func (v *Opaque_auth) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Opaque_auth) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sflavor", name), XDR_Auth_flavor(&v.Flavor)) x.Marshal(x.Sprintf("%sbody", name), XdrVecOpaque{&v.Body, 400}) } func XDR_Opaque_auth(v *Opaque_auth) *Opaque_auth { return v } var _XdrNames_Msg_type = map[int32]string{ int32(CALL): "CALL", int32(REPLY): "REPLY", } var _XdrValues_Msg_type = map[string]int32{ "CALL": int32(CALL), "REPLY": int32(REPLY), } func (Msg_type) XdrEnumNames() map[int32]string { return _XdrNames_Msg_type } func (v Msg_type) String() string { if s, ok := _XdrNames_Msg_type[int32(v)]; ok { return s } return fmt.Sprintf("Msg_type#%d", v) } func (v *Msg_type) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Msg_type[stok]; ok { *v = Msg_type(val) return nil } else if stok == "Msg_type" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Msg_type.", stok)) } } func (v Msg_type) GetU32() uint32 { return uint32(v) } func (v *Msg_type) SetU32(n uint32) { *v = Msg_type(n) } func (v *Msg_type) XdrPointer() interface{} { return v } func (Msg_type) XdrTypeName() string { return "Msg_type" } func (v Msg_type) XdrValue() interface{} { return v } func (v *Msg_type) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Msg_type = *Msg_type func XDR_Msg_type(v *Msg_type) *Msg_type { return v } var _XdrNames_Reply_stat = map[int32]string{ int32(MSG_ACCEPTED): "MSG_ACCEPTED", int32(MSG_DENIED): "MSG_DENIED", } var _XdrValues_Reply_stat = map[string]int32{ "MSG_ACCEPTED": int32(MSG_ACCEPTED), "MSG_DENIED": int32(MSG_DENIED), } func (Reply_stat) XdrEnumNames() map[int32]string { return _XdrNames_Reply_stat } func (v Reply_stat) String() string { if s, ok := _XdrNames_Reply_stat[int32(v)]; ok { return s } return fmt.Sprintf("Reply_stat#%d", v) } func (v *Reply_stat) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Reply_stat[stok]; ok { *v = Reply_stat(val) return nil } else if stok == "Reply_stat" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Reply_stat.", stok)) } } func (v Reply_stat) GetU32() uint32 { return uint32(v) } func (v *Reply_stat) SetU32(n uint32) { *v = Reply_stat(n) } func (v *Reply_stat) XdrPointer() interface{} { return v } func (Reply_stat) XdrTypeName() string { return "Reply_stat" } func (v Reply_stat) XdrValue() interface{} { return v } func (v *Reply_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Reply_stat = *Reply_stat func XDR_Reply_stat(v *Reply_stat) *Reply_stat { return v } var _XdrNames_Accept_stat = map[int32]string{ int32(SUCCESS): "SUCCESS", int32(PROG_UNAVAIL): "PROG_UNAVAIL", int32(PROG_MISMATCH): "PROG_MISMATCH", int32(PROC_UNAVAIL): "PROC_UNAVAIL", int32(GARBAGE_ARGS): "GARBAGE_ARGS", int32(SYSTEM_ERR): "SYSTEM_ERR", } var _XdrValues_Accept_stat = map[string]int32{ "SUCCESS": int32(SUCCESS), "PROG_UNAVAIL": int32(PROG_UNAVAIL), "PROG_MISMATCH": int32(PROG_MISMATCH), "PROC_UNAVAIL": int32(PROC_UNAVAIL), "GARBAGE_ARGS": int32(GARBAGE_ARGS), "SYSTEM_ERR": int32(SYSTEM_ERR), } func (Accept_stat) XdrEnumNames() map[int32]string { return _XdrNames_Accept_stat } func (v Accept_stat) String() string { if s, ok := _XdrNames_Accept_stat[int32(v)]; ok { return s } return fmt.Sprintf("Accept_stat#%d", v) } func (v *Accept_stat) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Accept_stat[stok]; ok { *v = Accept_stat(val) return nil } else if stok == "Accept_stat" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Accept_stat.", stok)) } } func (v Accept_stat) GetU32() uint32 { return uint32(v) } func (v *Accept_stat) SetU32(n uint32) { *v = Accept_stat(n) } func (v *Accept_stat) XdrPointer() interface{} { return v } func (Accept_stat) XdrTypeName() string { return "Accept_stat" } func (v Accept_stat) XdrValue() interface{} { return v } func (v *Accept_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Accept_stat = *Accept_stat func XDR_Accept_stat(v *Accept_stat) *Accept_stat { return v } var _XdrNames_Reject_stat = map[int32]string{ int32(RPC_MISMATCH): "RPC_MISMATCH", int32(AUTH_ERROR): "AUTH_ERROR", } var _XdrValues_Reject_stat = map[string]int32{ "RPC_MISMATCH": int32(RPC_MISMATCH), "AUTH_ERROR": int32(AUTH_ERROR), } func (Reject_stat) XdrEnumNames() map[int32]string { return _XdrNames_Reject_stat } func (v Reject_stat) String() string { if s, ok := _XdrNames_Reject_stat[int32(v)]; ok { return s } return fmt.Sprintf("Reject_stat#%d", v) } func (v *Reject_stat) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Reject_stat[stok]; ok { *v = Reject_stat(val) return nil } else if stok == "Reject_stat" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Reject_stat.", stok)) } } func (v Reject_stat) GetU32() uint32 { return uint32(v) } func (v *Reject_stat) SetU32(n uint32) { *v = Reject_stat(n) } func (v *Reject_stat) XdrPointer() interface{} { return v } func (Reject_stat) XdrTypeName() string { return "Reject_stat" } func (v Reject_stat) XdrValue() interface{} { return v } func (v *Reject_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Reject_stat = *Reject_stat func XDR_Reject_stat(v *Reject_stat) *Reject_stat { return v } var _XdrNames_Auth_stat = map[int32]string{ int32(AUTH_OK): "AUTH_OK", int32(AUTH_BADCRED): "AUTH_BADCRED", int32(AUTH_REJECTEDCRED): "AUTH_REJECTEDCRED", int32(AUTH_BADVERF): "AUTH_BADVERF", int32(AUTH_REJECTEDVERF): "AUTH_REJECTEDVERF", int32(AUTH_TOOWEAK): "AUTH_TOOWEAK", int32(AUTH_INVALIDRESP): "AUTH_INVALIDRESP", int32(AUTH_FAILED): "AUTH_FAILED", int32(AUTH_KERB_GENERIC): "AUTH_KERB_GENERIC", int32(AUTH_TIMEEXPIRE): "AUTH_TIMEEXPIRE", int32(AUTH_TKT_FILE): "AUTH_TKT_FILE", int32(AUTH_DECODE): "AUTH_DECODE", int32(AUTH_NET_ADDR): "AUTH_NET_ADDR", int32(RPCSEC_GSS_CREDPROBLEM): "RPCSEC_GSS_CREDPROBLEM", int32(RPCSEC_GSS_CTXPROBLEM): "RPCSEC_GSS_CTXPROBLEM", } var _XdrValues_Auth_stat = map[string]int32{ "AUTH_OK": int32(AUTH_OK), "AUTH_BADCRED": int32(AUTH_BADCRED), "AUTH_REJECTEDCRED": int32(AUTH_REJECTEDCRED), "AUTH_BADVERF": int32(AUTH_BADVERF), "AUTH_REJECTEDVERF": int32(AUTH_REJECTEDVERF), "AUTH_TOOWEAK": int32(AUTH_TOOWEAK), "AUTH_INVALIDRESP": int32(AUTH_INVALIDRESP), "AUTH_FAILED": int32(AUTH_FAILED), "AUTH_KERB_GENERIC": int32(AUTH_KERB_GENERIC), "AUTH_TIMEEXPIRE": int32(AUTH_TIMEEXPIRE), "AUTH_TKT_FILE": int32(AUTH_TKT_FILE), "AUTH_DECODE": int32(AUTH_DECODE), "AUTH_NET_ADDR": int32(AUTH_NET_ADDR), "RPCSEC_GSS_CREDPROBLEM": int32(RPCSEC_GSS_CREDPROBLEM), "RPCSEC_GSS_CTXPROBLEM": int32(RPCSEC_GSS_CTXPROBLEM), } func (Auth_stat) XdrEnumNames() map[int32]string { return _XdrNames_Auth_stat } func (v Auth_stat) String() string { if s, ok := _XdrNames_Auth_stat[int32(v)]; ok { return s } return fmt.Sprintf("Auth_stat#%d", v) } func (v *Auth_stat) Scan(ss fmt.ScanState, _ rune) error { if tok, err := ss.Token(true, XdrSymChar); err != nil { return err } else { stok := string(tok) if val, ok := _XdrValues_Auth_stat[stok]; ok { *v = Auth_stat(val) return nil } else if stok == "Auth_stat" { if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v)); n == 1 && err == nil { return nil } } return XdrError(fmt.Sprintf("%s is not a valid Auth_stat.", stok)) } } func (v Auth_stat) GetU32() uint32 { return uint32(v) } func (v *Auth_stat) SetU32(n uint32) { *v = Auth_stat(n) } func (v *Auth_stat) XdrPointer() interface{} { return v } func (Auth_stat) XdrTypeName() string { return "Auth_stat" } func (v Auth_stat) XdrValue() interface{} { return v } func (v *Auth_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } type XdrType_Auth_stat = *Auth_stat func XDR_Auth_stat(v *Auth_stat) *Auth_stat { return v } type XdrType_Call_body = *Call_body func (v *Call_body) XdrPointer() interface{} { return v } func (Call_body) XdrTypeName() string { return "Call_body" } func (v Call_body) XdrValue() interface{} { return v } func (v *Call_body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Call_body) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%srpcvers", name), XDR_uint32(&v.Rpcvers)) x.Marshal(x.Sprintf("%sprog", name), XDR_uint32(&v.Prog)) x.Marshal(x.Sprintf("%svers", name), XDR_uint32(&v.Vers)) x.Marshal(x.Sprintf("%sproc", name), XDR_uint32(&v.Proc)) x.Marshal(x.Sprintf("%scred", name), XDR_Opaque_auth(&v.Cred)) x.Marshal(x.Sprintf("%sverf", name), XDR_Opaque_auth(&v.Verf)) } func XDR_Call_body(v *Call_body) *Call_body { return v } type XdrType_XdrAnon_Accepted_reply_Reply_data_Mismatch_info = *XdrAnon_Accepted_reply_Reply_data_Mismatch_info func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrPointer() interface{} { return v } func (XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrTypeName() string { return "XdrAnon_Accepted_reply_Reply_data_Mismatch_info" } func (v XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrValue() interface{} { return v } func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slow", name), XDR_uint32(&v.Low)) x.Marshal(x.Sprintf("%shigh", name), XDR_uint32(&v.High)) } func XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) *XdrAnon_Accepted_reply_Reply_data_Mismatch_info { return v } type _XdrArray_0_opaque [0]byte func (v *_XdrArray_0_opaque) GetByteSlice() []byte { return v[:] } func (v *_XdrArray_0_opaque) XdrTypeName() string { return "opaque[]" } func (v *_XdrArray_0_opaque) XdrValue() interface{} { return v[:] } func (v *_XdrArray_0_opaque) XdrPointer() interface{} { return (*[0]byte)(v) } func (v *_XdrArray_0_opaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *_XdrArray_0_opaque) String() string { return fmt.Sprintf("%x", v[:]) } func (v *_XdrArray_0_opaque) Scan(ss fmt.ScanState, c rune) error { return XdrArrayOpaqueScan(v[:], ss, c) } func (_XdrArray_0_opaque) XdrArraySize() uint32 { const bound uint32 = 0 // Force error if not const or doesn't fit return bound } func (u *XdrAnon_Accepted_reply_Reply_data) Results() *[0]byte { switch u.Stat { case SUCCESS: if v, ok := u.U.(*[0]byte); ok { return v } else { var zero [0]byte u.U = &zero return &zero } default: XdrPanic("XdrAnon_Accepted_reply_Reply_data.Results accessed when Stat == %v", u.Stat) return nil } } func (u *XdrAnon_Accepted_reply_Reply_data) Mismatch_info() *XdrAnon_Accepted_reply_Reply_data_Mismatch_info { switch u.Stat { case PROG_MISMATCH: if v, ok := u.U.(*XdrAnon_Accepted_reply_Reply_data_Mismatch_info); ok { return v } else { var zero XdrAnon_Accepted_reply_Reply_data_Mismatch_info u.U = &zero return &zero } default: XdrPanic("XdrAnon_Accepted_reply_Reply_data.Mismatch_info accessed when Stat == %v", u.Stat) return nil } } func (u XdrAnon_Accepted_reply_Reply_data) XdrValid() bool { return true } func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionTag() XdrNum32 { return XDR_Accept_stat(&u.Stat) } func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionTagName() string { return "Stat" } func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionBody() XdrType { switch u.Stat { case SUCCESS: return (*_XdrArray_0_opaque)(u.Results()) case PROG_MISMATCH: return XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(u.Mismatch_info()) default: return nil } } func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionBodyName() string { switch u.Stat { case SUCCESS: return "Results" case PROG_MISMATCH: return "Mismatch_info" default: return "" } } type XdrType_XdrAnon_Accepted_reply_Reply_data = *XdrAnon_Accepted_reply_Reply_data func (v *XdrAnon_Accepted_reply_Reply_data) XdrPointer() interface{} { return v } func (XdrAnon_Accepted_reply_Reply_data) XdrTypeName() string { return "XdrAnon_Accepted_reply_Reply_data" } func (v XdrAnon_Accepted_reply_Reply_data) XdrValue() interface{} { return v } func (v *XdrAnon_Accepted_reply_Reply_data) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *XdrAnon_Accepted_reply_Reply_data) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Accept_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name)) switch u.Stat { case SUCCESS: x.Marshal(x.Sprintf("%sresults", name), (*_XdrArray_0_opaque)(u.Results())) return case PROG_MISMATCH: x.Marshal(x.Sprintf("%smismatch_info", name), XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(u.Mismatch_info())) return default: return } } func XDR_XdrAnon_Accepted_reply_Reply_data(v *XdrAnon_Accepted_reply_Reply_data) *XdrAnon_Accepted_reply_Reply_data { return v} type XdrType_Accepted_reply = *Accepted_reply func (v *Accepted_reply) XdrPointer() interface{} { return v } func (Accepted_reply) XdrTypeName() string { return "Accepted_reply" } func (v Accepted_reply) XdrValue() interface{} { return v } func (v *Accepted_reply) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Accepted_reply) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sverf", name), XDR_Opaque_auth(&v.Verf)) x.Marshal(x.Sprintf("%sreply_data", name), XDR_XdrAnon_Accepted_reply_Reply_data(&v.Reply_data)) } func XDR_Accepted_reply(v *Accepted_reply) *Accepted_reply { return v } type XdrType_XdrAnon_Rejected_reply_Mismatch_info = *XdrAnon_Rejected_reply_Mismatch_info func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrPointer() interface{} { return v } func (XdrAnon_Rejected_reply_Mismatch_info) XdrTypeName() string { return "XdrAnon_Rejected_reply_Mismatch_info" } func (v XdrAnon_Rejected_reply_Mismatch_info) XdrValue() interface{} { return v } func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%slow", name), XDR_uint32(&v.Low)) x.Marshal(x.Sprintf("%shigh", name), XDR_uint32(&v.High)) } func XDR_XdrAnon_Rejected_reply_Mismatch_info(v *XdrAnon_Rejected_reply_Mismatch_info) *XdrAnon_Rejected_reply_Mismatch_info { return v } var _XdrTags_Rejected_reply = map[int32]bool{ XdrToI32(RPC_MISMATCH): true, XdrToI32(AUTH_ERROR): true, } func (_ Rejected_reply) XdrValidTags() map[int32]bool { return _XdrTags_Rejected_reply } func (u *Rejected_reply) Mismatch_info() *XdrAnon_Rejected_reply_Mismatch_info { switch u.Stat { case RPC_MISMATCH: if v, ok := u.U.(*XdrAnon_Rejected_reply_Mismatch_info); ok { return v } else { var zero XdrAnon_Rejected_reply_Mismatch_info u.U = &zero return &zero } default: XdrPanic("Rejected_reply.Mismatch_info accessed when Stat == %v", u.Stat) return nil } } func (u *Rejected_reply) Rj_why() *Auth_stat { switch u.Stat { case AUTH_ERROR: if v, ok := u.U.(*Auth_stat); ok { return v } else { var zero Auth_stat u.U = &zero return &zero } default: XdrPanic("Rejected_reply.Rj_why accessed when Stat == %v", u.Stat) return nil } } func (u Rejected_reply) XdrValid() bool { switch u.Stat { case RPC_MISMATCH,AUTH_ERROR: return true } return false } func (u *Rejected_reply) XdrUnionTag() XdrNum32 { return XDR_Reject_stat(&u.Stat) } func (u *Rejected_reply) XdrUnionTagName() string { return "Stat" } func (u *Rejected_reply) XdrUnionBody() XdrType { switch u.Stat { case RPC_MISMATCH: return XDR_XdrAnon_Rejected_reply_Mismatch_info(u.Mismatch_info()) case AUTH_ERROR: return XDR_Auth_stat(u.Rj_why()) } return nil } func (u *Rejected_reply) XdrUnionBodyName() string { switch u.Stat { case RPC_MISMATCH: return "Mismatch_info" case AUTH_ERROR: return "Rj_why" } return "" } type XdrType_Rejected_reply = *Rejected_reply func (v *Rejected_reply) XdrPointer() interface{} { return v } func (Rejected_reply) XdrTypeName() string { return "Rejected_reply" } func (v Rejected_reply) XdrValue() interface{} { return v } func (v *Rejected_reply) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Rejected_reply) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Reject_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name)) switch u.Stat { case RPC_MISMATCH: x.Marshal(x.Sprintf("%smismatch_info", name), XDR_XdrAnon_Rejected_reply_Mismatch_info(u.Mismatch_info())) return case AUTH_ERROR: x.Marshal(x.Sprintf("%srj_why", name), XDR_Auth_stat(u.Rj_why())) return } XdrPanic("invalid Stat (%v) in Rejected_reply", u.Stat) } func XDR_Rejected_reply(v *Rejected_reply) *Rejected_reply { return v} var _XdrTags_Reply_body = map[int32]bool{ XdrToI32(MSG_ACCEPTED): true, XdrToI32(MSG_DENIED): true, } func (_ Reply_body) XdrValidTags() map[int32]bool { return _XdrTags_Reply_body } func (u *Reply_body) Areply() *Accepted_reply { switch u.Stat { case MSG_ACCEPTED: if v, ok := u.U.(*Accepted_reply); ok { return v } else { var zero Accepted_reply u.U = &zero return &zero } default: XdrPanic("Reply_body.Areply accessed when Stat == %v", u.Stat) return nil } } func (u *Reply_body) Rreply() *Rejected_reply { switch u.Stat { case MSG_DENIED: if v, ok := u.U.(*Rejected_reply); ok { return v } else { var zero Rejected_reply u.U = &zero return &zero } default: XdrPanic("Reply_body.Rreply accessed when Stat == %v", u.Stat) return nil } } func (u Reply_body) XdrValid() bool { switch u.Stat { case MSG_ACCEPTED,MSG_DENIED: return true } return false } func (u *Reply_body) XdrUnionTag() XdrNum32 { return XDR_Reply_stat(&u.Stat) } func (u *Reply_body) XdrUnionTagName() string { return "Stat" } func (u *Reply_body) XdrUnionBody() XdrType { switch u.Stat { case MSG_ACCEPTED: return XDR_Accepted_reply(u.Areply()) case MSG_DENIED: return XDR_Rejected_reply(u.Rreply()) } return nil } func (u *Reply_body) XdrUnionBodyName() string { switch u.Stat { case MSG_ACCEPTED: return "Areply" case MSG_DENIED: return "Rreply" } return "" } type XdrType_Reply_body = *Reply_body func (v *Reply_body) XdrPointer() interface{} { return v } func (Reply_body) XdrTypeName() string { return "Reply_body" } func (v Reply_body) XdrValue() interface{} { return v } func (v *Reply_body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *Reply_body) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Reply_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name)) switch u.Stat { case MSG_ACCEPTED: x.Marshal(x.Sprintf("%sareply", name), XDR_Accepted_reply(u.Areply())) return case MSG_DENIED: x.Marshal(x.Sprintf("%srreply", name), XDR_Rejected_reply(u.Rreply())) return } XdrPanic("invalid Stat (%v) in Reply_body", u.Stat) } func XDR_Reply_body(v *Reply_body) *Reply_body { return v} var _XdrTags_XdrAnon_Rpc_msg_Body = map[int32]bool{ XdrToI32(CALL): true, XdrToI32(REPLY): true, } func (_ XdrAnon_Rpc_msg_Body) XdrValidTags() map[int32]bool { return _XdrTags_XdrAnon_Rpc_msg_Body } func (u *XdrAnon_Rpc_msg_Body) Cbody() *Call_body { switch u.Mtype { case CALL: if v, ok := u.U.(*Call_body); ok { return v } else { var zero Call_body u.U = &zero return &zero } default: XdrPanic("XdrAnon_Rpc_msg_Body.Cbody accessed when Mtype == %v", u.Mtype) return nil } } func (u *XdrAnon_Rpc_msg_Body) Rbody() *Reply_body { switch u.Mtype { case REPLY: if v, ok := u.U.(*Reply_body); ok { return v } else { var zero Reply_body u.U = &zero return &zero } default: XdrPanic("XdrAnon_Rpc_msg_Body.Rbody accessed when Mtype == %v", u.Mtype) return nil } } func (u XdrAnon_Rpc_msg_Body) XdrValid() bool { switch u.Mtype { case CALL,REPLY: return true } return false } func (u *XdrAnon_Rpc_msg_Body) XdrUnionTag() XdrNum32 { return XDR_Msg_type(&u.Mtype) } func (u *XdrAnon_Rpc_msg_Body) XdrUnionTagName() string { return "Mtype" } func (u *XdrAnon_Rpc_msg_Body) XdrUnionBody() XdrType { switch u.Mtype { case CALL: return XDR_Call_body(u.Cbody()) case REPLY: return XDR_Reply_body(u.Rbody()) } return nil } func (u *XdrAnon_Rpc_msg_Body) XdrUnionBodyName() string { switch u.Mtype { case CALL: return "Cbody" case REPLY: return "Rbody" } return "" } type XdrType_XdrAnon_Rpc_msg_Body = *XdrAnon_Rpc_msg_Body func (v *XdrAnon_Rpc_msg_Body) XdrPointer() interface{} { return v } func (XdrAnon_Rpc_msg_Body) XdrTypeName() string { return "XdrAnon_Rpc_msg_Body" } func (v XdrAnon_Rpc_msg_Body) XdrValue() interface{} { return v } func (v *XdrAnon_Rpc_msg_Body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (u *XdrAnon_Rpc_msg_Body) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } XDR_Msg_type(&u.Mtype).XdrMarshal(x, x.Sprintf("%smtype", name)) switch u.Mtype { case CALL: x.Marshal(x.Sprintf("%scbody", name), XDR_Call_body(u.Cbody())) return case REPLY: x.Marshal(x.Sprintf("%srbody", name), XDR_Reply_body(u.Rbody())) return } XdrPanic("invalid Mtype (%v) in XdrAnon_Rpc_msg_Body", u.Mtype) } func XDR_XdrAnon_Rpc_msg_Body(v *XdrAnon_Rpc_msg_Body) *XdrAnon_Rpc_msg_Body { return v} type XdrType_Rpc_msg = *Rpc_msg func (v *Rpc_msg) XdrPointer() interface{} { return v } func (Rpc_msg) XdrTypeName() string { return "Rpc_msg" } func (v Rpc_msg) XdrValue() interface{} { return v } func (v *Rpc_msg) XdrMarshal(x XDR, name string) { x.Marshal(name, v) } func (v *Rpc_msg) XdrRecurse(x XDR, name string) { if name != "" { name = x.Sprintf("%s.", name) } x.Marshal(x.Sprintf("%sxid", name), XDR_uint32(&v.Xid)) x.Marshal(x.Sprintf("%sbody", name), XDR_XdrAnon_Rpc_msg_Body(&v.Body)) } func XDR_Rpc_msg(v *Rpc_msg) *Rpc_msg { return v } ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/rpc.x ================================================ /* RPC message format as defined by RFC 5531 */ enum auth_flavor { AUTH_NONE = 0, AUTH_SYS = 1, AUTH_SHORT = 2, AUTH_DH = 3, RPCSEC_GSS = 6 }; struct opaque_auth { auth_flavor flavor; opaque body<400>; }; enum msg_type { CALL = 0, REPLY = 1 }; /* A reply to a call message can take on two forms: the message was either accepted or rejected. */ enum reply_stat { MSG_ACCEPTED = 0, MSG_DENIED = 1 }; /* Given that a call message was accepted, the following is the status of an attempt to call a remote procedure. */ enum accept_stat { SUCCESS = 0, /* RPC executed successfully */ PROG_UNAVAIL = 1, /* remote hasn't exported program */ PROG_MISMATCH = 2, /* remote can't support version # */ PROC_UNAVAIL = 3, /* program can't support procedure */ GARBAGE_ARGS = 4, /* procedure can't decode params */ SYSTEM_ERR = 5 /* e.g. memory allocation failure */ }; /* Reasons why a call message was rejected: */ enum reject_stat { RPC_MISMATCH = 0, /* RPC version number != 2 */ AUTH_ERROR = 1 /* remote can't authenticate caller */ }; /* Why authentication failed: */ enum auth_stat { AUTH_OK = 0, /* success */ /* * failed at remote end */ AUTH_BADCRED = 1, /* bad credential (seal broken) */ AUTH_REJECTEDCRED = 2, /* client must begin new session */ AUTH_BADVERF = 3, /* bad verifier (seal broken) */ AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */ AUTH_TOOWEAK = 5, /* rejected for security reasons */ /* * failed locally */ AUTH_INVALIDRESP = 6, /* bogus response verifier */ AUTH_FAILED = 7, /* reason unknown */ /* * AUTH_KERB errors; deprecated. See [RFC2695] */ AUTH_KERB_GENERIC = 8, /* kerberos generic error */ AUTH_TIMEEXPIRE = 9, /* time of credential expired */ AUTH_TKT_FILE = 10, /* problem with ticket file */ AUTH_DECODE = 11, /* can't decode authenticator */ AUTH_NET_ADDR = 12, /* wrong net address in ticket */ /* * RPCSEC_GSS GSS related errors */ RPCSEC_GSS_CREDPROBLEM = 13, /* no credentials for user */ RPCSEC_GSS_CTXPROBLEM = 14 /* problem with context */ }; /* Body of an RPC call: */ struct call_body { unsigned int rpcvers; /* must be equal to two (2) */ unsigned int prog; unsigned int vers; unsigned int proc; opaque_auth cred; opaque_auth verf; /* procedure-specific parameters start here */ }; /* Reply to an RPC call that was accepted by the server: */ struct accepted_reply { opaque_auth verf; union switch (accept_stat stat) { case SUCCESS: opaque results[0]; /* * procedure-specific results start here */ case PROG_MISMATCH: struct { unsigned int low; unsigned int high; } mismatch_info; default: /* * Void. Cases include PROG_UNAVAIL, PROC_UNAVAIL, * GARBAGE_ARGS, and SYSTEM_ERR. */ void; } reply_data; }; /* Reply to an RPC call that was rejected by the server: */ union rejected_reply switch (reject_stat stat) { case RPC_MISMATCH: struct { unsigned int low; unsigned int high; } mismatch_info; case AUTH_ERROR: auth_stat rj_why; }; /* Body of a reply to an RPC call: */ union reply_body switch (reply_stat stat) { case MSG_ACCEPTED: accepted_reply areply; case MSG_DENIED: rejected_reply rreply; }; /* The RPC message: */ struct rpc_msg { unsigned int xid; union switch (msg_type mtype) { case CALL: call_body cbody; case REPLY: reply_body rbody; } body; }; ================================================ FILE: server/plugin/plg_backend_nfs4/repo/internal/types.go ================================================ package internal //goland:noinspection GoSnakeCaseUsage type Uint32_t = uint32 //goland:noinspection GoSnakeCaseUsage type Uint64_t = uint64 //goland:noinspection GoSnakeCaseUsage type Int64_t = int64 //goland:noinspection GoSnakeCaseUsage type XDR_Uint32_t = *XdrUint32 //goland:noinspection GoSnakeCaseUsage type XdrType_Uint32_t = XdrType_uint32 //goland:noinspection GoSnakeCaseUsage type XDR_Uint64_t = *XdrUint64 //goland:noinspection GoSnakeCaseUsage type XdrType_Uint64_t = XdrType_uint64 //goland:noinspection GoSnakeCaseUsage type XdrType_Int64_t = XdrType_int64 //goland:noinspection GoSnakeCaseUsage type XDR_Int64_t = *XdrInt64 func MinUint64(i1, i2 uint64) uint64 { if i1 < i2 { return i1 } return i2 } ================================================ FILE: server/plugin/plg_backend_nfs4/repo/nfs4/client.go ================================================ package nfs4 import ( "bytes" "context" "crypto/rand" "encoding/binary" "encoding/hex" "fmt" "io" "math" "net" "os" "strings" "time" . "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4/repo/internal" ) const NfsReadBlockLen = 512 * 1024 var standardNfsAttrs = Bitmap4{ 1< 255 { machName = machName[0:255] } ap := Authsys_parms{ Machinename: machName, Uid: auth.Uid, Gid: auth.Gid, Gids: nil, } // Fill the machine ID (needs not to be super-unique but nice to have them distinct) _ = binary.Read(rand.Reader, binary.LittleEndian, &ap.Stamp) apBuf := bytes.NewBuffer([]byte{}) XdrOut{Out: apBuf}.Marshal("", &ap) return apBuf.Bytes() } func (c *NfsClient) sendMessage(proc XdrProc) (xid uint32, err error) { xid = c.xid c.xid++ msg := Rpc_msg{ Xid: xid, Body: XdrAnon_Rpc_msg_Body{ Mtype: CALL, U: &Call_body{ Rpcvers: 2, Prog: proc.Prog(), Vers: proc.Vers(), Proc: proc.Proc(), Cred: Opaque_auth{ Flavor: c.authType, Body: c.authData, }, Verf: Opaque_auth{ Flavor: AUTH_NONE, }, }, }, } // The marshaller for some reason loves to panic. defer func() { if i := recover(); i != nil { if e, ok := i.(XdrError); ok { err = e } else { panic(i) } } }() buffer := bytes.NewBuffer([]byte{}) out := XdrOut{Out: buffer} out.Marshal("", &msg) if _, ok := proc.GetArg().(XdrType_void); !ok { out.Marshal("", proc.GetArg()) } // Yep, the RPC protocol requires this strange OR err = binary.Write(c.conn, binary.BigEndian, 0x80000000|uint32(buffer.Len())) if err != nil { return } _, err = c.conn.Write(buffer.Bytes()) if err != nil { return } return } func (c *NfsClient) readNfsMessage(result XdrType) (xid uint32, err error) { lenBuf := make([]byte, 4) _, err = io.ReadFull(c.conn, lenBuf) if err != nil { return } // The RPC protocol sets the MSB to 1 for length fields. Don't ask me why. msgLen := binary.BigEndian.Uint32(lenBuf) & 0x7fffffff msgBuf := make([]byte, msgLen) _, err = io.ReadFull(c.conn, msgBuf) if err != nil { return } // The unmarshaller for some reason loves to panic. Sigh. defer func() { if i := recover(); i != nil { if e, ok := i.(XdrError); ok { err = e } else { panic(i) } } }() reply := Rpc_msg{} in := XdrIn{In: bytes.NewReader(msgBuf)} in.Marshal("", &reply) if _, ok := result.(XdrType_void); !ok { in.Marshal("", result) } if !c.isRpcSuccess(&reply) { err = fmt.Errorf("RPC error: %s", c.getRpcError(&reply)) return } xid = reply.Xid return } // Returns true iff msg is an accepted REPLY with status SUCCESS. func (c *NfsClient) isRpcSuccess(msg *Rpc_msg) bool { return msg != nil && msg.Body.Mtype == REPLY && msg.Body.Rbody().Stat == MSG_ACCEPTED && msg.Body.Rbody().Areply().Reply_data.Stat == SUCCESS } // An *Rpc_msg can represent an error. Call IsSuccess to see if there // was actually an error. func (c *NfsClient) getRpcError(m *Rpc_msg) string { if m.Body.Mtype != REPLY { return "RPC message not a REPLY" } else if m.Body.Rbody().Stat == MSG_ACCEPTED { stat := m.Body.Rbody().Areply().Reply_data.Stat c := stat.String() if stat == PROG_MISMATCH { mmi := m.Body.Rbody().Areply().Reply_data.Mismatch_info() c = fmt.Sprintf("%s (low %d, high %d)", c, mmi.Low, mmi.High) } return c } else if m.Body.Rbody().Stat == MSG_DENIED { stat := m.Body.Rbody().Rreply().Stat c := stat.String() return c } return "Invalid reply_stat" } func (c *NfsClient) Ping() error { nullProc := XdrProc_NFSPROC4_NULL{} xid, err := c.sendMessage(&nullProc) if err != nil { return err } xidRes, err := c.readNfsMessage(nullProc.GetRes()) if err != nil { return err } if xidRes != xid { return fmt.Errorf("mismathced xids: %d and %d", xid, xidRes) } return nil } func (c *NfsClient) runNfsTransaction(ops []Nfs_argop4, pathHint string) ([]Nfs_resop4, error) { compound := XdrProc_NFSPROC4_COMPOUND{} args := compound.GetArg().(*COMPOUND4args) args.Argarray = ops xid, err := c.sendMessage(&compound) if err != nil { return nil, err } xid2, err := c.readNfsMessage(compound.GetRes()) if err != nil { return nil, err } if xid != xid2 { return nil, fmt.Errorf("xids don't match: %d and %d", xid, xid2) } res := compound.GetRes().(*COMPOUND4res) // TODO: translate the error better if res.Status != NFS4_OK { return nil, &NfsError{ Path: pathHint, ErrorCode: NfsErrorCode(res.Status), ErrorString: fmt.Sprintf("NFS error: %s (%d), path='%s'", res.Status.String(), int32(res.Status), pathHint), } } return res.Resarray, nil } func (c *NfsClient) setClientId() error { res, err := c.runNfsTransaction([]Nfs_argop4{{ Argop: OP_SETCLIENTID, U: &SETCLIENTID4args{ Client: Nfs_client_id4{ Verifier: Verifier4{}, Id: []byte(c.clientId), }, Callback: Cb_client4{}, Callback_ident: 0, }, }}, "") if err != nil { return err } resOk := res[0].Opsetclientid().Resok4() c.clientIdShort = resOk.Clientid _, err = c.runNfsTransaction([]Nfs_argop4{{ Argop: OP_SETCLIENTID_CONFIRM, U: &SETCLIENTID_CONFIRM4args{ Clientid: resOk.Clientid, Setclientid_confirm: resOk.Setclientid_confirm, }, }}, "") if err != nil { return err } return nil } func (c *NfsClient) retrieveRootFh() error { res, err := c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTROOTFH, }, { Argop: OP_GETFH, }, }, "/") if err != nil { return err } c.rootFh = res[1].Opgetfh().Resok4().Object return nil } func splitPath(path string) []string { splits := strings.Split(path, "/") curPos := 0 for _, s := range splits { if s == "" { continue } splits[curPos] = s curPos++ } return splits[0:curPos] } func (c *NfsClient) GetFileList(path string) ([]FileInfo, error) { var args = c.makePathLookupArgs(splitPath(path)) args = append(args, Nfs_argop4{Argop: OP_GETFH}, Nfs_argop4{Argop: OP_READDIR, U: &READDIR4args{ Cookie: 0, Cookieverf: Verifier4{}, Dircount: 1024 * 128, Maxcount: 1024 * 128, Attr_request: standardNfsAttrs, }}, ) res, err := c.runNfsTransaction(args, path) if err != nil { return nil, err } var fileList []FileInfo dirFh := res[len(res)-2].Opgetfh().Resok4().Object curDirList := res[len(res)-1].Opreaddir().Resok4() for { ent := curDirList.Reply.Entries if ent == nil { break } for { fileList = append(fileList, c.translateFileMeta(string(ent.Name), ent.Attrs)) if ent.Nextentry == nil { break } ent = ent.Nextentry } if curDirList.Reply.Eof { break } res, err := c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTFH, U: &PUTFH4args{Object: dirFh}, }, { Argop: OP_READDIR, U: &READDIR4args{ Cookie: ent.Cookie, Cookieverf: curDirList.Cookieverf, Dircount: 1024 * 128, Maxcount: 1024 * 128, Attr_request: standardNfsAttrs, }, }, }, path) if err != nil { return nil, err } curDirList = res[1].Opreaddir().Resok4() } return fileList, nil } // Make the commands to navigate the path to its leaf func (c *NfsClient) makePathLookupArgs(path []string) []Nfs_argop4 { var args []Nfs_argop4 args = append(args, Nfs_argop4{Argop: OP_PUTROOTFH}) // Add lookups for the path components for _, p := range path { args = append(args, Nfs_argop4{ Argop: OP_LOOKUP, U: &LOOKUP4args{Objname: Component4(p)}, }) } return args } func (c *NfsClient) translateFileMeta(name string, attrs Fattr4) FileInfo { res := FileInfo{ Name: name, } curOff := 0 atm := attrs.Attrmask if len(atm) > 0 && atm[0]&(1< 0 && atm[0]&(1< 1 && atm[1]&(1<<(FATTR4_TIME_MODIFY-32)) != 0 { mtimeSec := binary.BigEndian.Uint64(attrs.Attr_vals[curOff : curOff+8]) curOff += 8 mtimeNsec := binary.BigEndian.Uint32(attrs.Attr_vals[curOff : curOff+4]) curOff += 4 // I hope this works for times before 1970-01-01... res.Mtime = time.Unix(int64(mtimeSec), int64(mtimeNsec)) } return res } func (c *NfsClient) GetFileInfo(path string) (FileInfo, error) { args := c.makePathLookupArgs(splitPath(path)) args = append(args, Nfs_argop4{ Argop: OP_GETATTR, U: &GETATTR4args{ Attr_request: standardNfsAttrs, }, }) res, err := c.runNfsTransaction(args, path) if err != nil { return FileInfo{}, err } splits := splitPath(path) var name string if len(splits) == 1 { name = path } else { name = splits[len(splits)-1] } resInfo := c.translateFileMeta(name, res[len(res)-1].Opgetattr().Resok4().Obj_attributes) return resInfo, nil } func (c *NfsClient) ReadFileAll(path string, writer io.Writer) (uint64, error) { return c.ReadFile(path, 0, math.MaxUint64, writer) } func (c *NfsClient) ReadFile(path string, offset, count uint64, writer io.Writer) (uint64, error) { anonymousStateId := Stateid4{} args := c.makePathLookupArgs(splitPath(path)) args = append(args, Nfs_argop4{Argop: OP_GETFH}, Nfs_argop4{ Argop: OP_READ, U: &READ4args{ Stateid: anonymousStateId, Offset: offset, Count: Count4(MinUint64(NfsReadBlockLen, count)), }, }) res, err := c.runNfsTransaction(args, path) if err != nil { return 0, err } flDataBlock := res[len(res)-1].Opread().Resok4() fileFh := res[len(res)-2].Opgetfh().Resok4().Object var dataRead uint64 for { _, err := writer.Write(flDataBlock.Data) if err != nil { return 0, err } ln := len(flDataBlock.Data) offset += uint64(ln) count -= uint64(ln) dataRead += uint64(ln) if flDataBlock.Eof || count == 0 { break } // Get the next file block res, err := c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTFH, U: &PUTFH4args{Object: fileFh}, }, { Argop: OP_READ, U: &READ4args{ Stateid: anonymousStateId, Offset: offset, Count: Count4(MinUint64(NfsReadBlockLen, count)), }, }, }, path) if err != nil { return 0, err } flDataBlock = res[1].Opread().Resok4() } return dataRead, nil } func (c *NfsClient) openFileForWrite(path string, truncate bool) (Stateid4, Nfs_fh4, error) { splits := splitPath(path) // Put the directory FH as the current one args := c.makePathLookupArgs(splits[0 : len(splits)-1]) // Make the file claim (i.e. the file name on top of the directory FH) flClaim := Component4(splits[len(splits)-1]) var fileAttrs Fattr4 if truncate { // Set the file size and mode (Unix access mask) fileAttrs.Attr_vals = make([]byte, 12) // This file size is set to 0 binary.BigEndian.PutUint32(fileAttrs.Attr_vals[8:], MODE4_WUSR|MODE4_RUSR|MODE4_WGRP|MODE4_RGRP) fileAttrs.Attrmask = Bitmap4{1 << FATTR4_SIZE, 1 << (FATTR4_MODE - 32)} } else { // Set the file mode (Unix access mask) fileAttrs.Attr_vals = make([]byte, 4) binary.BigEndian.PutUint32(fileAttrs.Attr_vals, MODE4_WUSR|MODE4_RUSR|MODE4_WGRP|MODE4_RGRP) fileAttrs.Attrmask = Bitmap4{0, 1 << (FATTR4_MODE - 32)} } args = append(args, Nfs_argop4{ Argop: OP_OPEN, U: &OPEN4args{ Seqid: c.nfsSeqId, Share_access: OPEN4_SHARE_ACCESS_WRITE, Share_deny: OPEN4_SHARE_DENY_NONE, Owner: Open_owner4{ Clientid: c.clientIdShort, Owner: []byte(c.clientId), }, Openhow: Openflag4{ Opentype: OPEN4_CREATE, U: &Createhow4{ Mode: UNCHECKED4, U: &fileAttrs, }, }, Claim: Open_claim4{ Claim: CLAIM_NULL, U: &flClaim, }, }, }, Nfs_argop4{ Argop: OP_GETFH, }) res, err := c.runNfsTransaction(args, path) c.incrementNfsSeq(err) if err != nil { return Stateid4{}, Nfs_fh4{}, err } openRes := res[len(res)-2].Opopen().Resok4() openFh := res[len(res)-1].Opgetfh().Resok4().Object // We need to confirm the opened file receipt the first time we run the operation if !c.openConfirmed { res, err = c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTFH, U: &PUTFH4args{Object: openFh}, }, { Argop: OP_OPEN_CONFIRM, U: &OPEN_CONFIRM4args{ Open_stateid: openRes.Stateid, Seqid: c.nfsSeqId, }, }, }, path) c.incrementNfsSeq(err) if err != nil { return Stateid4{}, Nfs_fh4{}, err } c.openConfirmed = true return res[len(res)-1].Opopen_confirm().Resok4().Open_stateid, openFh, nil } return openRes.Stateid, openFh, nil } func (c *NfsClient) closeFile(stateId Stateid4, fh Nfs_fh4, path string) error { _, err := c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTFH, U: &PUTFH4args{Object: fh}, }, { Argop: OP_CLOSE, U: &CLOSE4args{ Open_stateid: stateId, Seqid: c.nfsSeqId, }, }, }, path) c.incrementNfsSeq(err) if err != nil { return err } return nil } func (c *NfsClient) ReWriteFile(path string, reader io.Reader) (written uint64, err error) { return c.WriteFile(path, true, 0, reader) } func (c *NfsClient) WriteFile(path string, truncate bool, offset uint64, reader io.Reader) (written uint64, err error) { stateId, fh, err := c.openFileForWrite(path, truncate) if err != nil { return } defer func() { err = c.closeFile(stateId, fh, path) }() block := make([]byte, NfsReadBlockLen) for { var curRead int curRead, err = reader.Read(block) if curRead == 0 || err == io.EOF { break } if err != nil { return } // Write the block! err = c.writeBlock(stateId, fh, written+offset, block[0:curRead], path) if err != nil { return } written += uint64(curRead) } return } func (c *NfsClient) writeBlock(id Stateid4, fh Nfs_fh4, offset uint64, data []byte, path string) error { for len(data) > 0 { res, err := c.runNfsTransaction([]Nfs_argop4{ { Argop: OP_PUTFH, U: &PUTFH4args{Object: fh}, }, { Argop: OP_WRITE, U: &WRITE4args{ Stateid: id, Offset: offset, Stable: UNSTABLE4, Data: data, }, }, }, path) if err != nil { return err } written := res[1].Opwrite().Resok4().Count data = data[written:] } return nil } func (c *NfsClient) DeleteFile(path string) error { splits := splitPath(path) // Put the directory FH as the current one args := c.makePathLookupArgs(splits[0 : len(splits)-1]) // Make the file claim (i.e. the file name on top of the directory FH) flClaim := Component4(splits[len(splits)-1]) args = append(args, Nfs_argop4{ Argop: OP_REMOVE, U: &REMOVE4args{ Target: flClaim, }, }) _, err := c.runNfsTransaction(args, path) if err != nil { return err } return nil } func (c *NfsClient) MakePath(path string) error { curPath := "" var curPathElems []string for _, curElem := range splitPath(path) { if curPath != "" { curPath += "/" } curPath += curElem curPathElems = append(curPathElems, curElem) fi, err := c.GetFileInfo(curPath) if err == nil && !fi.IsDir { return &NfsError{ ErrorCode: ERROR_NOTDIR, ErrorString: fmt.Sprintf("NFS error: should be a directory (%d), path='%s'", ERROR_NOTDIR, curPath), Path: curPath, } } if err != nil { if IsNfsError(err, ERROR_NOENT) { args := c.makePathLookupArgs(curPathElems[0 : len(curPathElems)-1]) // Make the file claim (i.e. the file name on top of the directory FH) flClaim := Component4(curElem) // Set the file mode (Unix access mask) var dirAttrs Fattr4 dirAttrs.Attr_vals = make([]byte, 4) binary.BigEndian.PutUint32(dirAttrs.Attr_vals, MODE4_WUSR|MODE4_RUSR|MODE4_XUSR|MODE4_WGRP|MODE4_RGRP|MODE4_XGRP) dirAttrs.Attrmask = Bitmap4{0, 1 << (FATTR4_MODE - 32)} args = append(args, Nfs_argop4{ Argop: OP_CREATE, U: &CREATE4args{ Objtype: Createtype4{ Type: NF4DIR, }, Objname: flClaim, Createattrs: dirAttrs, }, }) _, err := c.runNfsTransaction(args, curPath) if err != nil { return err } } else { return err } } } return nil } // Increment NFS sequence ID for all operations, even the ones that return // errors, except for a pre-defined list in https://tools.ietf.org/html/rfc3530#section-8.1.5 func (c *NfsClient) incrementNfsSeq(err error) { if err == nil { c.nfsSeqId++ return } nfsErr, ok := err.(*NfsError) if !ok { c.nfsSeqId++ return } switch Nfsstat4(nfsErr.ErrorCode) { case NFS4ERR_STALE_CLIENTID, NFS4ERR_STALE_STATEID, NFS4ERR_BAD_STATEID, NFS4ERR_BAD_SEQID, NFS4ERR_BADXDR, NFS4ERR_RESOURCE, NFS4ERR_NOFILEHANDLE: return default: c.nfsSeqId++ } } func RemoveRecursive(nfs NfsInterface, path string) error { list, err := nfs.GetFileList(path) if IsNfsError(err, ERROR_NOENT) { return nil } if err != nil { return err } for _, fl := range list { curPath := path + "/" + fl.Name if fl.IsDir { err = RemoveRecursive(nfs, curPath) if err != nil { return err } } else { err = nfs.DeleteFile(curPath) if err != nil { return err } } } err = nfs.DeleteFile(path) if err != nil { return err } return nil } ================================================ FILE: server/plugin/plg_backend_nfs4/repo/nfs4/nfs_err.go ================================================ package nfs4 type NfsError struct { Path string ErrorCode NfsErrorCode ErrorString string } func (n *NfsError) Error() string { return n.ErrorString } func IsNfsError(err error, code NfsErrorCode) bool { ne, ok := err.(*NfsError) return ok && ne.ErrorCode == code } /* * Error status. See https://www.rfc-editor.org/rfc/rfc7530 */ type NfsErrorCode int32 const ( /* everything is okay */ OK NfsErrorCode = 0 /* caller not privileged */ ERROR_PERM NfsErrorCode = 1 /* no such file/directory */ ERROR_NOENT NfsErrorCode = 2 /* hard I/O error */ ERROR_IO NfsErrorCode = 5 /* no such device */ ERROR_NXIO NfsErrorCode = 6 /* access denied */ ERROR_ACCESS NfsErrorCode = 13 /* file already exists */ ERROR_EXIST NfsErrorCode = 17 /* different filesystems */ ERROR_XDEV NfsErrorCode = 18 /* should be a directory */ ERROR_NOTDIR NfsErrorCode = 20 /* should not be directory */ ERROR_ISDIR NfsErrorCode = 21 /* invalid argument */ ERROR_INVAL NfsErrorCode = 22 /* file exceeds server max */ ERROR_FBIG NfsErrorCode = 27 /* no space on filesystem */ ERROR_NOSPC NfsErrorCode = 28 /* read-only filesystem */ ERROR_ROFS NfsErrorCode = 30 /* too many hard links */ ERROR_MLINK NfsErrorCode = 31 /* name exceeds server max */ ERROR_NAMETOOLONG NfsErrorCode = 63 /* directory not empty */ ERROR_NOTEMPTY NfsErrorCode = 66 /* hard quota limit reached*/ ERROR_DQUOT NfsErrorCode = 69 /* file no longer exists */ ERROR_STALE NfsErrorCode = 70 /* Illegal filehandle */ ERROR_BADHANDLE NfsErrorCode = 10001 /* READDIR cookie is stale */ ERROR_BAD_COOKIE NfsErrorCode = 10003 /* operation not supported */ ERROR_NOTSUPP NfsErrorCode = 10004 /* response limit exceeded */ ERROR_TOOSMALL NfsErrorCode = 10005 /* undefined server error */ ERROR_SERVERFAULT NfsErrorCode = 10006 /* type invalid for CREATE */ ERROR_BADTYPE NfsErrorCode = 10007 /* file "busy" - retry */ ERROR_DELAY NfsErrorCode = 10008 /* nverify says attrs same */ ERROR_SAME NfsErrorCode = 10009 /* lock unavailable */ ERROR_DENIED NfsErrorCode = 10010 /* lock lease expired */ ERROR_EXPIRED NfsErrorCode = 10011 /* I/O failed due to lock */ ERROR_LOCKED NfsErrorCode = 10012 /* in grace period */ ERROR_GRACE NfsErrorCode = 10013 /* filehandle expired */ ERROR_FHEXPIRED NfsErrorCode = 10014 /* share reserve denied */ ERROR_SHARE_DENIED NfsErrorCode = 10015 /* wrong security flavor */ ERROR_WRONGSEC NfsErrorCode = 10016 /* clientid in use */ ERROR_CLID_INUSE NfsErrorCode = 10017 /* resource exhaustion */ ERROR_RESOURCE NfsErrorCode = 10018 /* filesystem relocated */ ERROR_MOVED NfsErrorCode = 10019 /* current FH is not set */ ERROR_NOFILEHANDLE NfsErrorCode = 10020 /* minor vers not supp */ ERROR_MINOR_VERS_MISMATCH NfsErrorCode = 10021 /* server has rebooted */ ERROR_STALE_CLIENTID NfsErrorCode = 10022 /* server has rebooted */ ERROR_STALE_STATEID NfsErrorCode = 10023 /* state is out of sync */ ERROR_OLD_STATEID NfsErrorCode = 10024 /* incorrect stateid */ ERROR_BAD_STATEID NfsErrorCode = 10025 /* request is out of seq. */ ERROR_BAD_SEQID NfsErrorCode = 10026 /* verify - attrs not same */ ERROR_NOT_SAME NfsErrorCode = 10027 /* lock range not supported*/ ERROR_LOCK_RANGE NfsErrorCode = 10028 /* should be file/directory*/ ERROR_SYMLINK NfsErrorCode = 10029 /* no saved filehandle */ ERROR_RESTOREFH NfsErrorCode = 10030 /* some filesystem moved */ ERROR_LEASE_MOVED NfsErrorCode = 10031 /* recommended attr not sup*/ ERROR_ATTRNOTSUPP NfsErrorCode = 10032 /* reclaim outside of grace*/ ERROR_NO_GRACE NfsErrorCode = 10033 /* reclaim error at server */ ERROR_RECLAIM_BAD NfsErrorCode = 10034 /* conflict on reclaim */ ERROR_RECLAIM_CONFLICT NfsErrorCode = 10035 /* ZDR decode failed */ ERROR_BADZDR NfsErrorCode = 10036 /* file locks held at CLOSE*/ ERROR_LOCKS_HELD NfsErrorCode = 10037 /* conflict in OPEN and I/O*/ ERROR_OPENMODE NfsErrorCode = 10038 /* owner translation bad */ ERROR_BADOWNER NfsErrorCode = 10039 /* utf-8 char not supported*/ ERROR_BADCHAR NfsErrorCode = 10040 /* name not supported */ ERROR_BADNAME NfsErrorCode = 10041 /* lock range not supported*/ ERROR_BAD_RANGE NfsErrorCode = 10042 /* no atomic up/downgrade */ ERROR_LOCK_NOTSUPP NfsErrorCode = 10043 /* undefined operation */ ERROR_OP_ILLEGAL NfsErrorCode = 10044 /* file locking deadlock */ ERROR_DEADLOCK NfsErrorCode = 10045 /* open file blocks op. */ ERROR_FILE_OPEN NfsErrorCode = 10046 /* lockowner state revoked */ ERROR_ADMIN_REVOKED NfsErrorCode = 10047 ) ================================================ FILE: server/plugin/plg_backend_nfs4/repo/nfs4/supervised_conn.go ================================================ package nfs4 import ( "context" "io" "net" "sync" "time" ) // A wrapper for net.Conn that adds ability to cancel operations via a context.Context and // also supports deadlines. type SupervisedConnection struct { delegate net.Conn ctx context.Context deadline time.Time mtx sync.Mutex closeSignal chan bool closeErr error isClosed int32 } func NewSupervisedConnection(delegate net.Conn, ctx context.Context) (*SupervisedConnection, error) { res := &SupervisedConnection{ delegate: delegate, ctx: ctx, closeSignal: make(chan bool), } go func() { select { case <-ctx.Done(): res.signalDone() case <-res.closeSignal: } }() deadline, ok := ctx.Deadline() if ok { res.deadline = deadline err := delegate.SetDeadline(deadline) if err != nil { return nil, err } } return res, nil } func (s *SupervisedConnection) signalDone() { s.mtx.Lock() defer s.mtx.Unlock() if s.isClosed != 0 { return } // Cancel pending operations s.closeErr = s.delegate.Close() s.isClosed = 1 } func (s *SupervisedConnection) SetReadDeadline(t time.Time) error { s.mtx.Lock() defer s.mtx.Unlock() // Don't allow deadline extensions if we're getting interrupted if !s.deadline.IsZero() && s.deadline.Before(t) { return nil } return s.delegate.SetReadDeadline(t) } func (s *SupervisedConnection) SetWriteDeadline(t time.Time) error { s.mtx.Lock() defer s.mtx.Unlock() // Don't allow deadline extensions if we're getting interrupted if !s.deadline.IsZero() && s.deadline.Before(t) { return nil } return s.delegate.SetWriteDeadline(t) } func (s *SupervisedConnection) SetDeadline(t time.Time) error { s.mtx.Lock() defer s.mtx.Unlock() // Don't allow deadline extensions if we're getting interrupted if !s.deadline.IsZero() && s.deadline.Before(t) { return nil } return s.delegate.SetDeadline(t) } func (s *SupervisedConnection) Close() error { s.mtx.Lock() defer s.mtx.Unlock() if s.isClosed != 0 { return s.closeErr } close(s.closeSignal) s.isClosed = 1 s.closeErr = s.delegate.Close() return s.closeErr } func (s *SupervisedConnection) Read(b []byte) (n int, err error) { // s.isClosed can only go from 0 to 1, so there's no need to do // synchronization here. if s.isClosed != 0 { return 0, io.EOF } return s.delegate.Read(b) } func (s *SupervisedConnection) Write(b []byte) (n int, err error) { // s.isClosed can only go from 0 to 1, so there's no need to do // synchronization here. if s.isClosed != 0 { return 0, io.EOF } return s.delegate.Write(b) } func (s *SupervisedConnection) LocalAddr() net.Addr { return s.delegate.LocalAddr() } func (s *SupervisedConnection) RemoteAddr() net.Addr { return s.delegate.RemoteAddr() } ================================================ FILE: server/plugin/plg_backend_nop/index.go ================================================ package plg_backend_nop import ( . "github.com/mickael-kerjean/filestash/server/common" "io" "os" "strconv" "strings" ) func init() { Backend.Register("blackhole", BlackHole{}) } type LargeFile struct { Counter int } func (this *LargeFile) Read(p []byte) (n int, err error) { if this.Counter <= 0 { return 0, io.EOF } this.Counter = this.Counter - len(p) lenp := len(p) if lenp > 0 { p[0] = '_' } for i := 0; i < lenp; i += 100 { p[i] = '_' } return lenp, nil } func (this LargeFile) Close() error { return nil } type BlackHole struct{} func (this BlackHole) Init(params map[string]string, app *App) (IBackend, error) { Log.Debug("plg_backend_nop::init params[%s]", params) return &BlackHole{}, nil } func (this BlackHole) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "blackhole", }, }, } } func (this BlackHole) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) files = append( files, File{FName: "1M.bin", FType: "file", FSize: 1024 * 1024}, File{FName: "10M.bin", FType: "file", FSize: 1024 * 1024 * 10}, File{FName: "100M.bin", FType: "file", FSize: 1024 * 1024 * 100}, File{FName: "1G.bin", FType: "file", FSize: 1024 * 1024 * 1024}, File{FName: "10G.bin", FType: "file", FSize: 1024 * 1024 * 1024 * 1024}, File{FName: "100G.bin", FType: "file", FSize: 1024 * 1024 * 1024 * 1024 * 1024}, ) return files, nil } func (this BlackHole) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this BlackHole) Cat(path string) (io.ReadCloser, error) { path = strings.TrimPrefix(path, "/") if strings.HasSuffix(path, ".bin") == false { return nil, ErrNotImplemented } path = strings.TrimSuffix(path, ".bin") order := 1 if strings.HasSuffix(path, "K") { path = strings.TrimSuffix(path, "K") order = order * 1024 } else if strings.HasSuffix(path, "M") { path = strings.TrimSuffix(path, "M") order = order * 1024 * 1024 } else if strings.HasSuffix(path, "G") { path = strings.TrimSuffix(path, "G") order = order * 1024 * 1024 * 1024 } i, err := strconv.Atoi(path) if err != nil { return nil, ErrNotImplemented } return &LargeFile{i * order}, nil } func (this BlackHole) Mkdir(path string) error { return nil } func (this BlackHole) Rm(path string) error { return ErrNotImplemented } func (this BlackHole) Mv(from, to string) error { return ErrNotImplemented } func (this BlackHole) Save(path string, content io.Reader) error { b := make([]byte, 32*1024*1024) // 32MB for { _, err := content.Read(b) if err == io.EOF { break } } return nil } func (this BlackHole) Touch(path string) error { return nil } ================================================ FILE: server/plugin/plg_backend_perkeep/index.go ================================================ package plg_backend_perkeep import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Backend.Register("perkeep", &Perkeep{}) } type Perkeep struct { serverURL string } func (this Perkeep) Init(params map[string]string, app *App) (IBackend, error) { url := params["url"] if url == "" { url = "http://localhost:3179/" } if !strings.HasSuffix(url, "/") { url += "/" } return &Perkeep{ serverURL: url, }, nil } func (this Perkeep) Meta(path string) Metadata { return Metadata{ CanCreateFile: NewBool(false), // TODO CanCreateDirectory: NewBool(false), // TODO CanRename: NewBool(false), // TODO CanMove: NewBool(false), // TODO CanUpload: NewBool(false), // TODO: see http://localhost:3179/bs-and-maybe-also-index/camli/upload CanDelete: NewBool(false), // TODO } } func (this Perkeep) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "perkeep", }, { Name: "url", Type: "text", Placeholder: "eg: http://localhost:3179", }, }, } } func (this Perkeep) Ls(path string) ([]os.FileInfo, error) { var files []os.FileInfo if path == "/" { response, err := this.query(map[string]interface{}{ // curl 'http://localhost:3179/my-search/camli/search/query' -d '{"sort":"-created","constraint":{"permanode":{"attr": "camliRoot","valueMatches": {}}},"describe":{},"limit":-1}' "sort": "-created", "constraint": map[string]interface{}{ "permanode": map[string]interface{}{ "attr": "camliRoot", "valueMatches": map[string]interface{}{}, }, }, "describe": map[string]interface{}{}, "limit": 1, }) if err != nil { return nil, err } for _, blob := range response.Blobs { if meta, ok := response.Description.Meta[blob.Blob]; ok { if rootNames, ok := meta.Permanode.Attr["camliRoot"]; ok && len(rootNames) > 0 { files = append(files, File{ FName: rootNames[0], FType: "directory", FTime: meta.Permanode.ModTime.Unix(), }) } } } return files, nil } ref, err := this.getRef(path) if err != nil { return nil, err } response, err := this.query(map[string]interface{}{ // curl 'http://localhost:3179/my-search/camli/search/query' -d '{"sort":"-created","constraint":{"permanode":{"relation":{"relation":"parent","any":{"blobRefPrefix":"sha224-ff8f64ab406dc5aec7a35bf182dee79ea20d41bfffc2311fcb4acd9f"}}}},"describe":{"rules":[{"attrs": ["camliContent"]}]},"limit":50}' "sort": "-created", "constraint": map[string]interface{}{ "permanode": map[string]interface{}{ "relation": map[string]interface{}{ "relation": "parent", "any": map[string]interface{}{ "blobRefPrefix": ref, }, }, }, }, "describe": map[string]interface{}{ "rules": []map[string]interface{}{ { "attrs": []string{"camliContent", "title", "camliNodeType"}, }, }, }, "limit": 50, }) if err != nil { return nil, err } for _, blob := range response.Blobs { if meta, ok := response.Description.Meta[blob.Blob]; ok { var ( fileName string fileType string fileSize int64 fileTime int64 = -1 ) if nodeType, ok := meta.Permanode.Attr["camliNodeType"]; ok && len(nodeType) > 0 && nodeType[0] == "directory" { if titles, ok := meta.Permanode.Attr["title"]; ok && len(titles) > 0 { fileType = "directory" fileName = titles[0] fileTime = meta.Permanode.ModTime.Unix() } } else if contentRefs, hasContent := meta.Permanode.Attr["camliContent"]; hasContent && len(contentRefs) > 0 { contentRef := contentRefs[0] if contentMeta, ok := response.Description.Meta[contentRef]; ok && contentMeta.File != nil { fileType = "file" fileName = contentMeta.File.FileName fileTime = contentMeta.File.Time.Unix() fileSize = contentMeta.File.Size } } if fileName != "" && fileType != "" { files = append(files, File{ FName: fileName, FType: fileType, FSize: fileSize, FTime: fileTime, }) } } } return files, nil } func (this Perkeep) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func (this Perkeep) Cat(path string) (io.ReadCloser, error) { ref, err := this.getRef(path) if err != nil { return nil, err } response, err := this.describe(ref) if err != nil { return nil, err } contentRefs, hasContent := response.Meta[ref].Permanode.Attr["camliContent"] if !hasContent || len(contentRefs) == 0 { return nil, NewError("No content", 400) } resp, err := http.Get(this.serverURL + "ui/download/" + contentRefs[0]) if err != nil { return nil, NewError("Failed to fetch file: "+err.Error(), 500) } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, NewError("Failed to fetch file", resp.StatusCode) } return resp.Body, nil } func (this Perkeep) Mkdir(path string) error { return ErrNotImplemented } func (this Perkeep) Rm(path string) error { return ErrNotImplemented } func (this Perkeep) Mv(from, to string) error { return ErrNotImplemented } func (this Perkeep) Save(path string, content io.Reader) error { return ErrNotImplemented } func (this Perkeep) Touch(path string) error { return ErrNotImplemented } func (this *Perkeep) query(searchRequest any) (*SearchResponse, error) { queryJSON, err := json.Marshal(searchRequest) if err != nil { return nil, NewError("Failed to marshal search query: "+err.Error(), 500) } req, err := http.NewRequest( "POST", this.serverURL+"my-search/camli/search/query", bytes.NewBuffer(queryJSON), ) if err != nil { return nil, NewError("Failed to create request: "+err.Error(), 500) } resp, err := HTTPClient.Do(req) if err != nil { return nil, NewError("Failed to query perkeep: "+err.Error(), 500) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, NewError(fmt.Sprintf("Perkeep API error (%d): %s", resp.StatusCode, string(body)), 500) } var result SearchResponse err = json.NewDecoder(resp.Body).Decode(&result) return &result, err } func (this *Perkeep) describe(blobRef string) (*DescribeResponse, error) { describeJSON, err := json.Marshal(map[string]interface{}{ "blobRef": blobRef, }) if err != nil { return nil, NewError("Failed to marshal describe request: "+err.Error(), 500) } req, err := http.NewRequest( "POST", this.serverURL+"my-search/camli/search/describe", bytes.NewBuffer(describeJSON), ) if err != nil { return nil, NewError("Failed to create request: "+err.Error(), 500) } resp, err := HTTPClient.Do(req) if err != nil { return nil, NewError("Failed to describe perkeep: "+err.Error(), 500) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, NewError(fmt.Sprintf("Perkeep API error (%d): %s", resp.StatusCode, string(body)), 500) } var result DescribeResponse err = json.NewDecoder(resp.Body).Decode(&result) return &result, err } func (this *Perkeep) getRef(path string) (string, error) { path = strings.Trim(path, "/") if path == "" { return "", NewError("Empty path", 400) } pathChunks := strings.Split(path, "/") response, err := this.query(map[string]interface{}{ "constraint": map[string]interface{}{ "permanode": map[string]interface{}{ "attr": "camliRoot", "value": pathChunks[0], }, }, "describe": map[string]interface{}{}, "limit": 1, }) if err != nil { return "", err } else if len(response.Blobs) == 0 { return "", NewError("Root folder not found: "+pathChunks[0], 404) } currentRef := response.Blobs[0].Blob for i := 1; i < len(pathChunks); i++ { childResponse, err := this.query(map[string]interface{}{ "constraint": map[string]interface{}{ "permanode": map[string]interface{}{ "relation": map[string]interface{}{ "relation": "parent", "any": map[string]interface{}{ "blobRefPrefix": currentRef, }, }, }, }, "describe": map[string]interface{}{ "rules": []map[string]interface{}{ { "attrs": []string{"camliContent"}, }, }, }, "limit": -1, }) if err != nil { return "", err } found := false for _, blob := range childResponse.Blobs { if meta, ok := childResponse.Description.Meta[blob.Blob]; ok { if titles, ok := meta.Permanode.Attr["title"]; ok && len(titles) > 0 { if titles[0] == pathChunks[i] { currentRef = blob.Blob found = true break } } else if contentRefs, hasContent := meta.Permanode.Attr["camliContent"]; hasContent && len(contentRefs) > 0 { if contentMeta, ok := childResponse.Description.Meta[contentRefs[0]]; ok && contentMeta.File != nil { if contentMeta.File.FileName == pathChunks[i] { currentRef = blob.Blob found = true break } } } } } if !found { return "", NewError("Path element not found: "+pathChunks[i], 404) } } return currentRef, nil } ================================================ FILE: server/plugin/plg_backend_perkeep/types.go ================================================ package plg_backend_perkeep import ( "time" ) type SearchResponse struct { Blobs []struct { Blob string `json:"blob"` } `json:"blobs"` Description struct { Meta map[string]struct { Permanode struct { Attr map[string][]string `json:"attr"` ModTime time.Time } `json:"permanode"` File *struct { FileName string `json:"fileName"` Size int64 `json:"size"` Time time.Time `json:"time"` WholeRef string `json:"wholeRef"` } `json:"file"` CamliType string `json:"camliType"` } `json:"meta"` } `json:"description"` } type DescribeResponse struct { Meta map[string]struct { Permanode struct { Attr map[string][]string `json:"attr"` ModTime time.Time Size int64 } `json:"permanode"` CamliType string `json:"camliType"` } `json:"meta"` } ================================================ FILE: server/plugin/plg_backend_psql/index.go ================================================ package plg_backend_psql import ( "context" "database/sql" "fmt" "strings" . "github.com/mickael-kerjean/filestash/server/common" _ "github.com/lib/pq" ) type PSQL struct { db *sql.DB ctx context.Context } func init() { Backend.Register("psql", PSQL{}) } func (this PSQL) Init(params map[string]string, app *App) (IBackend, error) { host := params["host"] port := withDefault(params["port"], "5432") user := params["user"] password := params["password"] dbname := withDefault(params["dbname"], "postgres") sslmode := withDefault(params["sslmode"], "disable") if host == "" || user == "" || password == "" { return nil, ErrNotValid } db, err := sql.Open( "postgres", fmt.Sprintf( "host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", host, port, user, password, dbname, sslmode, ), ) if err != nil { return nil, err } else if err := db.Ping(); err != nil { Log.Debug("plg_backend_psql::init err=%s", err.Error()) return nil, ErrNotValid } backend := &PSQL{ db: db, ctx: app.Context, } return backend, nil } func withDefault(val string, def string) string { if val == "" { return def } return val } func (this PSQL) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "psql", }, FormElement{ Name: "host", Type: "text", Placeholder: "Host", }, FormElement{ Name: "port", Type: "number", Placeholder: "Port", }, FormElement{ Name: "user", Type: "text", Placeholder: "User", }, FormElement{ Name: "password", Type: "password", Placeholder: "Password", }, FormElement{ Name: "dbname", Type: "text", Placeholder: "DB Name", }, FormElement{ Name: "sslmode", Type: "text", Placeholder: "SSL Mode", }, }, } } func (this PSQL) Touch(path string) error { defer this.Close() if !strings.HasSuffix(path, ".form") { return NewError("Create a form file instead. eg: xxxx.form", 403) } return nil } func (this PSQL) Mkdir(path string) error { defer this.Close() return ErrNotValid } func (this PSQL) Mv(from string, to string) error { defer this.Close() return ErrNotValid } func (this PSQL) Meta(path string) Metadata { location, _ := getPath(path) return Metadata{ CanCreateDirectory: NewBool(false), CanCreateFile: func(l LocationRow) *bool { if l.table == "" { return NewBool(false) } return NewBool(true) }(location), CanRename: NewBool(false), CanDelete: func(l LocationRow) *bool { if l.table == "" { return NewBool(false) } return NewBool(true) }(location), CanMove: NewBool(false), CanUpload: func(l LocationRow) *bool { if l.row == "" { return NewBool(false) } return NewBool(true) }(location), RefreshOnCreate: NewBool(true), HideExtension: NewBool(true), } } func (this PSQL) Close() error { this.db.Close() return nil } ================================================ FILE: server/plugin/plg_backend_psql/index_cat.go ================================================ package plg_backend_psql import ( "context" "database/sql" "fmt" "io" "os" "slices" . "github.com/mickael-kerjean/filestash/server/common" ) func (this PSQL) Cat(path string) (io.ReadCloser, error) { defer this.Close() l, err := getPath(path) if err != nil { return nil, err } columns, columnName, err := processTable(this.ctx, this.db, l.table) if err != nil { return nil, err } rows, err := this.db.QueryContext(this.ctx, ` SELECT * FROM "`+l.table+`" WHERE "`+columnName+`"=$1 `, l.row) if err != nil { return nil, err } defer rows.Close() c, err := rows.Columns() if err != nil { return nil, err } else if len(columns) != len(c) { Log.Error("plg_backend_psql::index_cat columns is not of the expected size columns[%d]=%v c[%d]=%v", len(columns), columns, len(c), c) return nil, ErrNotValid } i := 0 col := make([]interface{}, len(c)) for rows.Next() { if i != 0 { return nil, ErrNotValid } pcol := make([]any, len(c)) for i, _ := range pcol { pcol[i] = &col[i] } if err := rows.Scan(pcol...); err != nil { return nil, err } } forms := make([]FormElement, len(c)) for i, _ := range columns { forms[i] = createFormElement(col[i], columns[i]) if columnComment := _findCommentColumn(this.ctx, this.db, l.table, columns[i].Name); columnComment != "" { forms[i].Description = columnComment } if slices.Contains(columns[i].Constraint, "PRIMARY KEY") && forms[i].Value != nil { forms[i].ReadOnly = true } else if slices.Contains(columns[i].Constraint, "FOREIGN KEY") { if link, err := _findRelation(this.ctx, this.db, columns[i]); err == nil { if len(link.values) > 0 { forms[i].Type = "select" forms[i].Opts = link.values } if forms[i].Description == "" { forms[i].Description = _createDescription(columns[i], link) } } } else if values, err := _findEnumValues(this.ctx, this.db, columns[i]); err == nil && len(values) > 0 { forms[i].Type = "select" forms[i].Opts = values } } if comment := _findCommentTable(this.ctx, this.db, l.table); comment != "" { forms = append([]FormElement{ { Name: "banner", Type: "hidden", Description: comment, }, }, forms...) } b, err := Form{Elmnts: forms}.MarshalJSON() if err != nil { return nil, err } return NewReadCloserFromBytes(b), nil } func (this PSQL) Stat(path string) (os.FileInfo, error) { return nil, ErrNotImplemented } func _createDescription(el Column, link LocationColumn) string { if slices.Contains(el.Constraint, "FOREIGN KEY") { return fmt.Sprintf("points to [<%s> → <%s>](/files/%s/)", link.table, link.column, link.table) } return "" } func _findCommentTable(ctx context.Context, db *sql.DB, tableName string) string { var comment string if err := db.QueryRowContext(ctx, ` SELECT obj_description(c.oid) FROM pg_class c WHERE c.relname = $1 AND c.relkind = 'r' `, tableName).Scan(&comment); err != nil { return "" } return comment } func _findCommentColumn(ctx context.Context, db *sql.DB, tableName, columnName string) string { var comment string if err := db.QueryRowContext(ctx, ` SELECT col_description(c.oid, a.attnum) FROM pg_class c JOIN pg_attribute a ON a.attrelid = c.oid WHERE c.relname = $1 AND a.attname = $2 AND c.relkind = 'r' `, tableName, columnName).Scan(&comment); err != nil { return "" } return comment } func _findRelation(ctx context.Context, db *sql.DB, el Column) (LocationColumn, error) { l := LocationColumn{} rows, err := db.QueryContext(ctx, ` SELECT ccu.table_name, ccu.column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu USING (constraint_name) JOIN information_schema.constraint_column_usage AS ccu USING (constraint_name) WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND kcu.column_name = $2 `, el.Table, el.Name) if err != nil { return l, err } defer rows.Close() for rows.Next() { if err := rows.Scan(&l.table, &l.column); err != nil { return l, err } } valueRows, err := db.QueryContext(ctx, fmt.Sprintf( `SELECT DISTINCT "%s" FROM "%s" ORDER BY "%s" LIMIT 5000`, l.column, l.table, l.column, )) if err != nil { return l, err } defer valueRows.Close() l.values = []string{} for valueRows.Next() { var value string if err := valueRows.Scan(&value); err != nil { return l, err } l.values = append(l.values, value) } return l, nil } func _findEnumValues(ctx context.Context, db *sql.DB, el Column) ([]string, error) { var count int if err := db.QueryRowContext(ctx, ` SELECT COUNT(*) FROM pg_type WHERE typname = $1 AND typtype = 'e' `, el.Type).Scan(&count); err != nil || count == 0 { return nil, err } rows, err := db.QueryContext(ctx, `SELECT unnest(enum_range(NULL::`+el.Type+`))`) if err != nil { return nil, err } values := []string{} for rows.Next() { var value string if err := rows.Scan(&value); err != nil { return nil, err } values = append(values, value) } rows.Close() return values, nil } ================================================ FILE: server/plugin/plg_backend_psql/index_ls.go ================================================ package plg_backend_psql import ( "os" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func (this PSQL) Ls(path string) ([]os.FileInfo, error) { defer this.Close() l, err := getPath(path) if err != nil { Log.Debug("pl_backend_psql::ls method=getPath err=%s", err.Error()) return nil, err } if l.table == "" { rows, err := this.db.QueryContext(this.ctx, ` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' `) if err != nil { Log.Debug("plg_backend_psql::ls method=query err=%s", err.Error()) return nil, err } defer rows.Close() out := []os.FileInfo{} for rows.Next() { var name string if err := rows.Scan(&name); err != nil { Log.Debug("plg_backend_psql::ls method=scan err=%s", err.Error()) return nil, err } out = append(out, File{ FName: name, FType: "directory", }) } return out, nil } else if l.row == "" { columns, key, err := processTable(this.ctx, this.db, l.table) if err != nil { return nil, err } query := `SELECT "` + key + `", NULL FROM "` + l.table + `" LIMIT 500000` for _, c := range columns { if c.Type == "timestamptz" { query = `SELECT "` + key + `", "` + c.Name + `" FROM "` + l.table + `" LIMIT 500000` break } } rows, err := this.db.QueryContext(this.ctx, query) if err != nil { return nil, err } defer rows.Close() out := []os.FileInfo{} for rows.Next() { var name string var t *time.Time if err = rows.Scan(&name, &t); err != nil { return nil, err } out = append(out, File{ FName: name + ".form", FType: "file", FTime: func() int64 { if t == nil { return 0 } return t.Unix() }(), FSize: -1, }) } return out, nil } return []os.FileInfo{}, ErrNotValid } ================================================ FILE: server/plugin/plg_backend_psql/index_rm.go ================================================ package plg_backend_psql import ( . "github.com/mickael-kerjean/filestash/server/common" ) func (this PSQL) Rm(path string) error { defer this.Close() l, err := getPath(path) if err != nil { return err } else if l.table == "" { return ErrNotFound } _, key, err := processTable(this.ctx, this.db, l.table) if err != nil { return err } _, err = this.db.ExecContext( this.ctx, `DELETE FROM "`+l.table+`" WHERE "`+key+`" = $1`, l.row, ) return err } ================================================ FILE: server/plugin/plg_backend_psql/index_save.go ================================================ package plg_backend_psql import ( "context" "database/sql" "encoding/json" "fmt" "io" "slices" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func (this PSQL) Save(path string, file io.Reader) error { defer this.Close() l, err := getPath(path) if err != nil { return err } columns, key, err := processTable(this.ctx, this.db, l.table) if err != nil { return err } f := map[string]FormElement{} if err := json.NewDecoder(file).Decode(&f); err != nil { return err } tx, err := this.db.BeginTx(this.ctx, nil) if err != nil { return err } defer tx.Rollback() rows, err := tx.QueryContext(this.ctx, `SELECT * FROM "`+l.table+`" WHERE "`+key+`" = $1`, l.row) if err != nil { return err } i := 0 dbvals := make([]any, len(columns)) for rows.Next() { currentPtrs := make([]any, len(columns)) for i := range dbvals { currentPtrs[i] = &dbvals[i] } if serr := rows.Scan(currentPtrs...); serr != nil { rows.Close() err = serr break } else if i >= 1 { err = ErrNotValid break } i += 1 } rows.Close() if i == 0 { err = _createRow(tx, this.ctx, l.table, columns, f) } if err == nil && i == 1 { err = _updateRow(tx, this.ctx, l.table, columns, f, key, l.row, dbvals) } if err != nil { return err } return tx.Commit() } func _createRow(tx *sql.Tx, ctx context.Context, table string, columns []Column, f map[string]FormElement) error { colNames := []string{} placeholders := []string{} values := []interface{}{} paramIndex := 1 for _, col := range columns { if formEl, exists := f[col.Name]; exists { if slices.Contains(col.Constraint, "PRIMARY KEY") && col.Default { continue } colNames = append(colNames, `"`+col.Name+`"`) placeholders = append(placeholders, fmt.Sprintf("$%d", paramIndex)) values = append(values, formEl.Value) paramIndex++ } } if len(colNames) == 0 { return ErrNotValid } _, err := tx.ExecContext( ctx, `INSERT INTO "`+table+`" (`+strings.Join(colNames, ", ")+`) VALUES (`+strings.Join(placeholders, ", ")+`)`, values..., ) return err } func _updateRow(tx *sql.Tx, ctx context.Context, table string, columns []Column, f map[string]FormElement, keyName string, keyValue any, dbvals []any) error { for i, col := range columns { dbval := convertFromDB(dbvals[i]) formval, ok := f[col.Name] if !ok || formval.Value == dbval { continue } if _, err := tx.ExecContext( ctx, `UPDATE "`+table+`" SET "`+col.Name+`" = $1 WHERE "`+keyName+`" = $2`, formval.Value, keyValue, ); err != nil { return err } } return nil } ================================================ FILE: server/plugin/plg_backend_psql/types.go ================================================ package plg_backend_psql type Column struct { Table string Name string Type string Nullable bool Default bool Constraint []string } type LocationRow struct { table string row string } type LocationColumn struct { table string column string values []string } ================================================ FILE: server/plugin/plg_backend_psql/utils.go ================================================ package plg_backend_psql import ( "context" "database/sql" "slices" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func getPath(path string) (LocationRow, error) { l := LocationRow{} for i, chunk := range strings.Split(path, "/") { if i == 0 { if chunk != "" { return l, ErrNotValid } } else if i == 1 { if strings.Contains(chunk, `"`) { return l, ErrNotValid } l.table = chunk } else if i == 2 { l.row = strings.TrimSuffix(chunk, ".form") } else { return l, ErrNotValid } } return l, nil } func processTable(ctx context.Context, db *sql.DB, table string) ([]Column, string, error) { columns, err := _getColumns(ctx, db, table) if err != nil { return nil, "", err } key := "" score := 0 for _, column := range columns { if c := _calculateScore(column); c > score { key = column.Name score = c } } if key == "" { return columns, "", ErrNotValid } return columns, key, nil } func _getColumns(ctx context.Context, db *sql.DB, table string) ([]Column, error) { rows, err := db.QueryContext(ctx, ` SELECT c.column_name, c.udt_name as type, (c.is_nullable = 'YES') AS nullable, (c.column_default IS NOT NULL) AS has_default, coalesce(string_agg(tc.constraint_type, ', '), '') as constraint FROM information_schema.columns AS c LEFT JOIN information_schema.key_column_usage kcu USING (table_name, column_name) LEFT JOIN information_schema.table_constraints tc USING (table_name, constraint_name) WHERE c.table_name = $1 GROUP BY c.column_name, c.is_nullable, c.udt_name, c.column_default ORDER BY MIN(c.ordinal_position) `, table) if err != nil { return nil, err } columns := []Column{} for rows.Next() { var c Column var constraints string if err := rows.Scan(&c.Name, &c.Type, &c.Nullable, &c.Default, &constraints); err != nil { return nil, err } c.Constraint = strings.Split(constraints, ", ") c.Table = table columns = append(columns, c) } return columns, rows.Close() } func _calculateScore(column Column) int { scoreType := 0 scoreName := 1 if slices.Contains(column.Constraint, "PRIMARY KEY") { scoreType = 3 if column.Type == "uuid" { scoreType = 1 } } else if slices.Contains(column.Constraint, "UNIQUE") { scoreType = 2 } switch strings.ToLower(column.Name) { case "name": scoreName = 2 case "label": scoreName = 2 case "email": scoreName = 5 } return scoreType * scoreName } func convertFromDB(val any) any { switch tmp := val.(type) { case []byte: return string(tmp) case time.Time: return tmp.UTC().Format("2006-01-02T15:04") } return val } func createFormElement(val any, column Column) FormElement { f := FormElement{ Type: "text", } switch column.Type { case "timestamptz": f.Type = "datetime" case "bool": f.Type = "boolean" } f.Value = convertFromDB(val) f.Name = column.Name f.Required = !column.Nullable && !column.Default if strings.Contains(strings.ToLower(column.Name), "password") { f.Type = "password" } return f } ================================================ FILE: server/plugin/plg_backend_s3/index.go ================================================ package plg_backend_s3 import ( "context" "fmt" "strconv" "sync" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/defaults" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/sts" . "github.com/mickael-kerjean/filestash/server/common" "io" "os" "path/filepath" "strings" ) var S3Cache AppCache type S3Backend struct { client *s3.S3 config *aws.Config params map[string]string Context context.Context threadSize int timeout time.Duration } func init() { Backend.Register("s3", S3Backend{}) S3Cache = NewAppCache(2, 1) } func (this S3Backend) Init(params map[string]string, app *App) (IBackend, error) { if params["encryption_key"] != "" && len(params["encryption_key"]) != 32 { return nil, NewError(fmt.Sprintf("Encryption key needs to be 32 characters (current: %d)", len(params["encryption_key"])), 400) } region := params["region"] if region == "" { region = "us-east-1" if strings.HasSuffix(params["endpoint"], ".cloudflarestorage.com") { region = "auto" } } creds := []credentials.Provider{} if params["access_key_id"] != "" || params["secret_access_key"] != "" { creds = append(creds, &credentials.StaticProvider{Value: credentials.Value{ AccessKeyID: params["access_key_id"], SecretAccessKey: params["secret_access_key"], SessionToken: params["session_token"], }}) } if params["role_arn"] != "" { creds = append(creds, &stscreds.AssumeRoleProvider{ Client: sts.New(session.Must(session.NewSessionWithOptions(session.Options{Config: aws.Config{Region: aws.String(region)}}))), RoleARN: params["role_arn"], Duration: stscreds.DefaultDuration, }) } creds = append( creds, &credentials.EnvProvider{}, defaults.RemoteCredProvider(*defaults.Config(), defaults.Handlers()), ) config := &aws.Config{ Credentials: credentials.NewChainCredentials(creds), CredentialsChainVerboseErrors: aws.Bool(true), S3ForcePathStyle: aws.Bool(true), Region: aws.String(region), } if params["endpoint"] != "" { config.Endpoint = aws.String(params["endpoint"]) } var timeout time.Duration if secs, err := strconv.Atoi(params["timeout"]); err == nil { timeout = time.Duration(secs) * time.Second } threadSize, err := strconv.Atoi(params["number_thread"]) if err != nil { threadSize = 50 } else if threadSize > 5000 || threadSize < 1 { threadSize = 2 } backend := &S3Backend{ config: config, params: params, client: s3.New(session.New(config)), Context: app.Context, threadSize: threadSize, timeout: timeout, } return backend, nil } func (this S3Backend) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "s3", }, FormElement{ Name: "access_key_id", Type: "text", Placeholder: "Access Key ID*", }, FormElement{ Name: "secret_access_key", Type: "password", Placeholder: "Secret Access Key*", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{ "s3_region", "s3_endpoint", "s3_role_arn", "s3_session_token", "s3_path", "s3_encryption_key", "s3_number_thread", "s3_timeout", }, }, FormElement{ Id: "s3_region", Name: "region", Type: "text", Placeholder: "Region", }, FormElement{ Id: "s3_endpoint", Name: "endpoint", Type: "text", Placeholder: "Endpoint", }, FormElement{ Id: "s3_role_arn", Name: "role_arn", Type: "text", Placeholder: "Role ARN", }, FormElement{ Id: "s3_session_token", Name: "session_token", Type: "text", Placeholder: "Session Token", }, FormElement{ Id: "s3_path", Name: "path", Type: "text", Placeholder: "Path", }, FormElement{ Id: "s3_encryption_key", Name: "encryption_key", Type: "text", Placeholder: "Encryption Key", }, FormElement{ Id: "s3_number_thread", Name: "number_thread", Type: "text", Placeholder: "Num. Thread", }, FormElement{ Id: "s3_timeout", Name: "timeout", Type: "number", Placeholder: "List Object Timeout", }, }, } } func (this S3Backend) Meta(path string) Metadata { if path == "/" { return Metadata{ CanCreateFile: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), } } return Metadata{} } func (this S3Backend) Ls(path string) (files []os.FileInfo, err error) { files = make([]os.FileInfo, 0) p := this.path(path) if p.bucket == "" { b, err := this.client.ListBuckets(&s3.ListBucketsInput{}) if err != nil { return nil, err } for _, bucket := range b.Buckets { files = append(files, &File{ FName: *bucket.Name, FType: "directory", FTime: bucket.CreationDate.Unix(), }) } return files, nil } client := s3.New(this.createSession(p.bucket)) start := time.Now() err = client.ListObjectsV2PagesWithContext( this.Context, &s3.ListObjectsV2Input{ Bucket: aws.String(p.bucket), Prefix: aws.String(p.path), Delimiter: aws.String("/"), }, func(objs *s3.ListObjectsV2Output, lastPage bool) bool { for i, object := range objs.Contents { if i == 0 && *object.Key == p.path { continue } var size int64 = -1 if object.Size != nil { size = *object.Size } isOffline := false if object.StorageClass != nil && *object.StorageClass == "GLACIER" { isOffline = true } files = append(files, &File{ FName: filepath.Base(*object.Key), FType: "file", FTime: object.LastModified.Unix(), FSize: size, Offline: isOffline, }) } for _, object := range objs.CommonPrefixes { files = append(files, &File{ FName: filepath.Base(*object.Prefix), FType: "directory", FTime: 0, }) } if this.timeout > 0 && time.Since(start) > this.timeout { return false } return aws.BoolValue(objs.IsTruncated) }, ) return files, err } func (this S3Backend) Stat(path string) (os.FileInfo, error) { p := this.path(path) if p.path == "" { b, err := this.client.ListBuckets(&s3.ListBucketsInput{}) if err != nil { return nil, err } for _, bucket := range b.Buckets { if bucket.Name != nil && *bucket.Name == p.bucket { return &File{ FName: *bucket.Name, FType: "directory", FTime: bucket.CreationDate.Unix(), }, nil } } return nil, ErrNotFound } client := s3.New(this.createSession(p.bucket)) input := &s3.HeadObjectInput{ Bucket: aws.String(p.bucket), Key: aws.String(p.path), } obj, err := client.HeadObjectWithContext(this.Context, input) if err != nil { awsErr, ok := err.(awserr.Error) if ok == false || awsErr.Code() != "NotFound" { return nil, err } return File{ FName: filepath.Base(path), FType: "directory", FTime: -1, }, nil } else if obj.ContentLength == nil || obj.LastModified == nil { return nil, ErrNotValid } return File{ FName: filepath.Base(path), FType: "file", FSize: (*obj.ContentLength), FTime: (*obj.LastModified).Unix(), }, err } func (this S3Backend) Cat(path string) (io.ReadCloser, error) { p := this.path(path) client := s3.New(this.createSession(p.bucket)) input := &s3.GetObjectInput{ Bucket: aws.String(p.bucket), Key: aws.String(p.path), } if this.params["encryption_key"] != "" { input.SSECustomerAlgorithm = aws.String("AES256") input.SSECustomerKey = aws.String(this.params["encryption_key"]) } obj, err := client.GetObjectWithContext(this.Context, input) if err != nil { awsErr, ok := err.(awserr.Error) if ok == false { return nil, err } if awsErr.Code() == "InvalidRequest" && strings.Contains(awsErr.Message(), "encryption") { input.SSECustomerAlgorithm = nil input.SSECustomerKey = nil obj, err = client.GetObject(input) return obj.Body, err } else if awsErr.Code() == "InvalidArgument" && strings.Contains(awsErr.Message(), "secret key was invalid") { return nil, NewError("This file is encrypted file, you need the correct key!", 400) } else if awsErr.Code() == "AccessDenied" { return nil, ErrNotAllowed } else if awsErr.Code() == "InvalidObjectState" { return nil, ErrNotReachable } return nil, err } return obj.Body, nil } func (this S3Backend) Mkdir(path string) error { p := this.path(path) client := s3.New(this.createSession(p.bucket)) if p.path == "" { _, err := client.CreateBucket(&s3.CreateBucketInput{ Bucket: aws.String(path), }) return err } _, err := client.PutObject(&s3.PutObjectInput{ Bucket: aws.String(p.bucket), Key: aws.String(EnforceDirectory(p.path)), }) return err } func (this S3Backend) Rm(path string) error { p := this.path(path) client := s3.New(this.createSession(p.bucket)) if p.bucket == "" { return ErrNotFound } finfo, err := this.Stat(path) if err != nil { return err } // CASE 1: remove a file if finfo.IsDir() == false { _, err := client.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(p.bucket), Key: aws.String(p.path), }) return err } // CASE 2: remove a folder jobChan := make(chan S3Path, this.threadSize) errChan := make(chan error, this.threadSize) ctx, cancel := context.WithCancel(this.Context) var wg sync.WaitGroup for i := 1; i <= this.threadSize; i++ { wg.Add(1) go func() { for spath := range jobChan { if ctx.Err() != nil { continue } if _, err := client.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(spath.bucket), Key: aws.String(spath.path), }); err != nil { cancel() errChan <- err } } wg.Done() }() } err = client.ListObjectsV2PagesWithContext( this.Context, &s3.ListObjectsV2Input{ Bucket: aws.String(p.bucket), Prefix: aws.String(p.path), }, func(objs *s3.ListObjectsV2Output, lastPage bool) bool { if ctx.Err() != nil { return false } for _, object := range objs.Contents { jobChan <- S3Path{p.bucket, *object.Key} } return aws.BoolValue(objs.IsTruncated) }, ) close(jobChan) wg.Wait() close(errChan) if err != nil { return err } for err := range errChan { return err } if p.path == "" { _, err := client.DeleteBucket(&s3.DeleteBucketInput{ Bucket: aws.String(p.bucket), }) return err } return err } func (this S3Backend) Mv(from string, to string) error { if from == to { return nil } f := this.path(from) t := this.path(to) client := s3.New(this.createSession(f.bucket)) // CASE 1: Rename a bucket if f.path == "" { return ErrNotImplemented } finfo, err := this.Stat(from) if err != nil { return err } // CASE 2: Rename/Move a file if finfo.IsDir() == false { input := &s3.CopyObjectInput{ CopySource: aws.String(fmt.Sprintf("%s/%s", f.bucket, f.path)), Bucket: aws.String(t.bucket), Key: aws.String(t.path), } if this.params["encryption_key"] != "" { input.CopySourceSSECustomerAlgorithm = aws.String("AES256") input.CopySourceSSECustomerKey = aws.String(this.params["encryption_key"]) input.SSECustomerAlgorithm = aws.String("AES256") input.SSECustomerKey = aws.String(this.params["encryption_key"]) } _, err := client.CopyObject(input) if err != nil { return err } _, err = client.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(f.bucket), Key: aws.String(f.path), }) return err } // CASE 3: Rename/Move a folder jobChan := make(chan []S3Path, this.threadSize) errChan := make(chan error, this.threadSize) ctx, cancel := context.WithCancel(this.Context) var wg sync.WaitGroup for i := 1; i <= this.threadSize; i++ { wg.Add(1) go func() { for spath := range jobChan { if ctx.Err() != nil { continue } input := &s3.CopyObjectInput{ CopySource: aws.String(fmt.Sprintf("%s/%s", spath[0].bucket, spath[0].path)), Bucket: aws.String(spath[1].bucket), Key: aws.String(spath[1].path), } if this.params["encryption_key"] != "" { input.CopySourceSSECustomerAlgorithm = aws.String("AES256") input.CopySourceSSECustomerKey = aws.String(this.params["encryption_key"]) input.SSECustomerAlgorithm = aws.String("AES256") input.SSECustomerKey = aws.String(this.params["encryption_key"]) } _, err := client.CopyObject(input) if err != nil { cancel() errChan <- err continue } _, err = client.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(spath[0].bucket), Key: aws.String(spath[0].path), }) if err != nil { cancel() errChan <- err continue } } wg.Done() }() } err = client.ListObjectsV2PagesWithContext( this.Context, &s3.ListObjectsV2Input{ Bucket: aws.String(f.bucket), Prefix: aws.String(f.path), }, func(objs *s3.ListObjectsV2Output, lastPage bool) bool { if ctx.Err() != nil { return false } for _, object := range objs.Contents { jobChan <- []S3Path{ {f.bucket, *object.Key}, {t.bucket, t.path + strings.TrimPrefix(*object.Key, f.path)}, } } return aws.BoolValue(objs.IsTruncated) }, ) close(jobChan) wg.Wait() close(errChan) if err != nil { return err } for err := range errChan { return err } return nil } func (this S3Backend) Touch(path string) error { p := this.path(path) client := s3.New(this.createSession(p.bucket)) if p.bucket == "" { return ErrNotValid } input := &s3.PutObjectInput{ Body: strings.NewReader(""), ContentLength: aws.Int64(0), Bucket: aws.String(p.bucket), Key: aws.String(p.path), ContentType: aws.String(GetMimeType(path)), } if this.params["encryption_key"] != "" { input.SSECustomerAlgorithm = aws.String("AES256") input.SSECustomerKey = aws.String(this.params["encryption_key"]) } _, err := client.PutObject(input) return err } func (this S3Backend) Save(path string, file io.Reader) error { p := this.path(path) if p.bucket == "" { return ErrNotValid } uploader := s3manager.NewUploader(this.createSession(p.bucket)) input := s3manager.UploadInput{ Body: file, Bucket: aws.String(p.bucket), Key: aws.String(p.path), ContentType: aws.String(GetMimeType(path)), } if this.params["encryption_key"] != "" { input.SSECustomerAlgorithm = aws.String("AES256") input.SSECustomerKey = aws.String(this.params["encryption_key"]) } _, err := uploader.Upload(&input) return err } func (this S3Backend) createSession(bucket string) *session.Session { if this.params["region"] == "" { newParams := map[string]string{"bucket": bucket} for k, v := range this.params { newParams[k] = v } if c := S3Cache.Get(newParams); c == nil { res, err := this.client.GetBucketLocation(&s3.GetBucketLocationInput{ Bucket: aws.String(bucket), }) if err == nil && res.LocationConstraint != nil { this.config.Region = res.LocationConstraint } S3Cache.Set(newParams, this.config.Region) } else { this.config.Region = c.(*string) } } sess := session.New(this.config) return sess } type S3Path struct { bucket string path string } func (s S3Backend) path(p string) S3Path { sp := strings.Split(p, "/") bucket := "" if len(sp) > 1 { bucket = sp[1] } path := "" if len(sp) > 2 { path = strings.Join(sp[2:], "/") } return S3Path{ bucket, path, } } ================================================ FILE: server/plugin/plg_backend_samba/index.go ================================================ package plg_backend_samba import ( "fmt" "io" "net" "net/url" "os" "strings" "time" "github.com/hirochachacha/go-smb2" . "github.com/mickael-kerjean/filestash/server/common" ) var SambaCache AppCache func init() { Backend.Register("samba", Samba{}) SambaCache = NewAppCache(30) SambaCache.OnEvict(func(key string, value interface{}) { smb := value.(*Samba) for key, _ := range smb.share { if err := smb.share[key].Umount(); err != nil { Log.Warning("samba: error unmounting share: %v", err) } } if err := smb.session.Logoff(); err != nil { Log.Warning("samba: error logging out: %v", err) } }) } type Samba struct { session *smb2.Session share map[string]*smb2.Share } func (smb Samba) Init(params map[string]string, app *App) (IBackend, error) { if c := SambaCache.Get(params); c != nil { return c.(*Samba), nil } if strings.HasPrefix(params["host"], "smb://") == false { params["host"] = "smb://" + params["host"] } if u, err := url.Parse(params["host"]); err == nil { params["host"] = u.Host if params["port"] == "" { params["port"] = u.Port() } if params["share"] == "" { params["share"] = strings.ReplaceAll(u.Path, "/", "") } if params["username"] == "" && u.User != nil { params["username"] = u.User.Username() } if params["password"] == "" && u.User != nil { params["password"], _ = u.User.Password() } } if params["port"] == "" { params["port"] = "445" } host := fmt.Sprintf("%s:%s", params["host"], params["port"]) conn, err := net.DialTimeout("tcp", host, 10*time.Second) if err != nil { Log.Debug("plg_backend_samba::netdial host[%s] err[%s]", host, err.Error()) return nil, err } smb.share = make(map[string]*smb2.Share, 0) smb.session, err = (&smb2.Dialer{ Initiator: &smb2.NTLMInitiator{ User: func() string { if params["username"] == "" { return "Guest" } return params["username"] }(), Password: params["password"], Domain: params["domain"], }, }).Dial(conn) if err != nil { Log.Debug("plg_backend_samba::smbdial host[%s] err[%s] username[%s] domain[%s]", host, err.Error(), params["username"], params["domain"]) return nil, err } if params["share"] == "" { names, err := smb.session.ListSharenames() if err != nil { Log.Debug("plg_backend_samba::list host[%s] err[%s]", host, err.Error()) return nil, err } for _, name := range names { if strings.HasSuffix(name, "$") { continue } if m, err := smb.session.Mount(name); err == nil { smb.share[name] = m } } } else { if m, err := smb.session.Mount(params["share"]); err == nil { smb.share[params["share"]] = m } } SambaCache.Set(params, &smb) return &smb, nil } func (smb Samba) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "samba", }, { Name: "host", Type: "text", Placeholder: "Hostname", }, { Name: "username", Type: "text", Placeholder: "Username", }, { Name: "password", Type: "password", Placeholder: "Password", }, { Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"samba_port", "samba_path", "samba_domain", "samba_share"}, }, { Id: "samba_path", Name: "path", Type: "text", Placeholder: "Path", }, { Id: "samba_port", Name: "port", Type: "number", Placeholder: "Port - eg: 445", }, { Id: "samba_domain", Name: "domain", Type: "text", Placeholder: "Domain", }, { Id: "samba_share", Name: "share", Type: "text", Placeholder: "Share Name", }, }, } } func (smb Samba) Ls(path string) ([]os.FileInfo, error) { if path == "/" { f := make([]os.FileInfo, 0) for key, _ := range smb.share { f = append(f, File{ FName: key, FType: "directory", }) } return f, nil } share, path, err := smb.toSambaPath(path) if err != nil { return nil, err } dir, err := share.Open(path) if err != nil { return nil, fromSambaErr(err) } defer dir.Close() fs, err := dir.Readdir(-1) return fs, fromSambaErr(err) } func (smb Samba) Stat(path string) (os.FileInfo, error) { share, path, err := smb.toSambaPath(path) if err != nil { return nil, err } f, err := share.Stat(path) return f, fromSambaErr(err) } func (smb Samba) Cat(path string) (io.ReadCloser, error) { share, path, err := smb.toSambaPath(path) if err != nil { return nil, err } f, err := share.Open(path) return f, fromSambaErr(err) } func (smb Samba) Mkdir(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } return fromSambaErr(share.Mkdir(path, os.ModeDir)) } func (smb Samba) Rm(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } return fromSambaErr(share.RemoveAll(path)) } func (smb Samba) Mv(from, to string) error { fromShare, fromPath, err := smb.toSambaPath(from) if err != nil { return err } toShare, toPath, err := smb.toSambaPath(to) if err != nil { return err } if fromShare != toShare { return ErrNotImplemented } return fromSambaErr(fromShare.Rename(fromPath, toPath)) } func (smb Samba) Save(path string, content io.Reader) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } f, err := share.Create(path) if err != nil { return fromSambaErr(err) } if _, err = io.Copy(f, content); err != nil { f.Close() return fromSambaErr(err) } return f.Close() } func (smb Samba) Touch(path string) error { share, path, err := smb.toSambaPath(path) if err != nil { return err } f, err := share.Create(path) if err != nil { return fromSambaErr(err) } return fromSambaErr(f.Close()) } func (smb Samba) toSambaPath(path string) (*smb2.Share, string, error) { p := strings.Split(strings.Trim(path, "/"), "/") if len(p) == 0 { return nil, "", ErrNotAllowed } sharename := p[0] oPath := strings.TrimLeft(strings.Join(p[1:], "\\"), "\\") if smb.share[sharename] == nil { return nil, "", ErrNotFound } return smb.share[sharename], oPath, nil } func fromSambaErr(err error) error { switch { case os.IsPermission(err): return ErrPermissionDenied case os.IsNotExist(err): return ErrNotFound default: return err } } ================================================ FILE: server/plugin/plg_backend_sftp/index.go ================================================ package plg_backend_sftp import ( "io" "net" "os" "regexp" "strings" "sync" . "github.com/mickael-kerjean/filestash/server/common" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" ) var SftpCache AppCache type Sftp struct { SSHClient *ssh.Client SFTPClient *sftp.Client wg *sync.WaitGroup } func init() { Backend.Register("sftp", Sftp{}) SftpCache = NewAppCache(1, 1) SftpCache.OnEvict(func(key string, value interface{}) { c := value.(*Sftp) if c == nil { Log.Warning("plg_backend_sftp::sftp is nil on close") return } else if c.wg == nil { c.Close() Log.Warning("plg_backend_sftp::wg is nil on close") return } c.wg.Wait() Log.Debug("plg_backend_sftp::vacuum") c.Close() }) } func (s Sftp) Init(params map[string]string, app *App) (IBackend, error) { p := struct { hostname string port string username string password string passphrase string }{ params["hostname"], params["port"], params["username"], params["password"], params["passphrase"], } if p.port == "" { p.port = "22" } c := SftpCache.Get(params) if c != nil { d := c.(*Sftp) if d == nil { Log.Warning("plg_backend_sftp::sftp is nil on get") return nil, ErrInternal } else if d.wg == nil { Log.Warning("plg_backend_sftp::wg is nil on get") return nil, ErrInternal } d.wg.Add(1) go func() { <-app.Context.Done() d.wg.Done() }() return d, nil } addr := p.hostname + ":" + p.port keyStartMatcher := regexp.MustCompile(`^-----BEGIN [A-Z\ ]+-----`) keyEndMatcher := regexp.MustCompile(`-----END [A-Z\ ]+-----$`) keyContentMatcher := regexp.MustCompile(`^[a-zA-Z0-9\+\/\=\n]+$`) isPrivateKey := func(pass string) bool { p := strings.TrimSpace(pass) // match private key beginning if keyStartMatcher.FindStringIndex(p) == nil { return false } p = keyStartMatcher.ReplaceAllString(p, "") // match private key ending if keyEndMatcher.FindStringIndex(p) == nil { return false } p = keyEndMatcher.ReplaceAllString(p, "") p = strings.Replace(p, " ", "", -1) // match private key content if keyContentMatcher.FindStringIndex(p) == nil { return false } return true } restorePrivateKeyLineBreaks := func(pass string) string { p := strings.TrimSpace(pass) keyStartString := keyStartMatcher.FindString(p) p = keyStartMatcher.ReplaceAllString(p, "") keyEndString := keyEndMatcher.FindString(p) p = keyEndMatcher.ReplaceAllString(p, "") p = strings.Replace(p, " ", "", -1) keyContentString := keyContentMatcher.FindString(p) return keyStartString + "\n" + keyContentString + "\n" + keyEndString } /* * SSH has a range of authentication methods available: publickey, password, * keyboard-interactive, password-callback, publickey-callback, gss, .... Typical sftp * servers only have those 2: 'publickey' and 'password' but some exotic ones we've seen * have 'publickey' and 'keyboard-interactive'. If you're unlucky enough to have to work * with something else than those 3 we provide support for, either create a PR here * or contact us */ var auth []ssh.AuthMethod if isPrivateKey(p.password) { privateKey := restorePrivateKeyLineBreaks(p.password) signer, err := func() (ssh.Signer, error) { if p.passphrase == "" { return ssh.ParsePrivateKey([]byte(privateKey)) } return ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(p.passphrase)) }() if err != nil { return nil, err } auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} } else { auth = []ssh.AuthMethod{ ssh.Password(p.password), ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { answers := make([]string, len(questions)) for i, _ := range answers { answers[i] = p.password } return answers, nil }), } } config := &ssh.ClientConfig{ User: p.username, Auth: auth, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { if params["hostkey"] == "" { return nil } fsha := ssh.FingerprintSHA256(key) if fsha == params["hostkey"] { return nil } fmd := ssh.FingerprintLegacyMD5(key) if fmd == params["hostkey"] { return nil } Log.Debug("plg_backend_sftp::fingerprint host key isn't correct on %s => '%s'", hostname, fsha) return ErrNotValid }, } client, err := ssh.Dial("tcp", addr, config) if err != nil { config.User = strings.ToLower(p.username) client, err = ssh.Dial("tcp", addr, config) if err != nil { return &s, ErrAuthenticationFailed } } s.SSHClient = client session, err := sftp.NewClient(s.SSHClient) if err != nil { return &s, err } s.SFTPClient = session s.wg = new(sync.WaitGroup) s.wg.Add(1) go func() { <-app.Context.Done() s.wg.Done() }() SftpCache.Set(params, &s) return &s, nil } func (b Sftp) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "sftp", }, FormElement{ Name: "hostname", Type: "text", Placeholder: "Hostname*", }, FormElement{ Name: "username", Type: "text", Placeholder: "Username", }, FormElement{ Name: "password", Type: "password", Placeholder: "Password", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"sftp_path", "sftp_port", "sftp_passphrase", "sftp_hostkey"}, }, FormElement{ Id: "sftp_path", Name: "path", Type: "text", Placeholder: "Path", }, FormElement{ Id: "sftp_port", Name: "port", Type: "number", Placeholder: "Port", }, FormElement{ Id: "sftp_passphrase", Name: "passphrase", Type: "password", Placeholder: "Passphrase", }, FormElement{ Id: "sftp_hostkey", Name: "hostkey", Type: "text", Placeholder: "Host key", }, }, } } func (b Sftp) Home() (string, error) { cwd, err := b.SFTPClient.Getwd() if err != nil { return "", b.err(err) } length := len(cwd) if length > 0 && cwd[length-1:] != "/" { return cwd + "/", nil } return cwd, nil } func (b Sftp) Ls(path string) ([]os.FileInfo, error) { files, err := b.SFTPClient.ReadDir(path) return files, b.err(err) } func (b Sftp) Cat(path string) (io.ReadCloser, error) { remoteFile, err := b.SFTPClient.OpenFile(path, os.O_RDONLY) if err != nil { return nil, b.err(err) } return remoteFile, nil } func (b Sftp) Mkdir(path string) error { err := b.SFTPClient.Mkdir(path) return b.err(err) } func (b Sftp) Rm(path string) error { if IsDirectory(path) { list, err := b.SFTPClient.ReadDir(path) if err != nil { return b.err(err) } for _, entry := range list { p := path + entry.Name() if entry.IsDir() { p += "/" err := b.Rm(p) if err != nil { return b.err(err) } } else { err := b.SFTPClient.Remove(p) if err != nil { return b.err(err) } } } err = b.SFTPClient.RemoveDirectory(path) if err != nil { return b.err(err) } } else { err := b.SFTPClient.Remove(path) return b.err(err) } return nil } func (b Sftp) Mv(from string, to string) error { err := b.SFTPClient.Rename(from, to) return b.err(err) } func (b Sftp) Touch(path string) error { file, err := b.SFTPClient.OpenFile(path, os.O_WRONLY|os.O_CREATE) if err != nil { return b.err(err) } _, err = file.ReadFrom(strings.NewReader("")) file.Close() return b.err(err) } func (b Sftp) Save(path string, file io.Reader) error { remoteFile, err := b.SFTPClient.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) if err != nil { return b.err(err) } _, err = io.Copy(remoteFile, file) remoteFile.Close() return b.err(err) } func (b Sftp) Stat(path string) (os.FileInfo, error) { f, err := b.SFTPClient.Stat(path) return f, b.err(err) } func (b Sftp) Close() error { err0 := b.SFTPClient.Close() err1 := b.SSHClient.Close() if err0 != nil { return err0 } return err1 } func (b Sftp) err(e error) error { f, ok := e.(*sftp.StatusError) if ok == false { if e == os.ErrNotExist { return ErrNotFound } return e } switch f.Code { case 0: return nil case 1: return NewError("There's nothing more to see", 404) case 2: return NewError("Does not exist", 404) case 3: return NewError("Permission denied", 403) case 4: return NewError("Failure", 409) case 5: return NewError("Not Compatible", 400) case 6: return NewError("No Connection", 503) case 7: return NewError("Connection Lost", 503) case 8: return NewError("Operation not supported", 501) case 9: return NewError("Not valid", 400) case 10: return NewError("No such path", 404) case 11: return NewError("File already exists", 409) case 12: return NewError("Write protected", 403) case 13: return NewError("No media", 404) case 14: return NewError("No space left", 400) case 15: return NewError("Quota exceeded", 400) case 16: return NewError("Unknown", 400) case 17: return NewError("Lock conflict", 409) case 18: return NewError("Directory not empty", 400) case 19: return NewError("Not a directory", 400) case 20: return NewError("Invalid filename", 400) case 21: return NewError("Link loop", 508) case 22: return NewError("Cannot delete", 400) case 23: return NewError("Invalid query", 400) case 24: return NewError("File is a directory", 400) case 25: return NewError("Lock conflict", 409) case 26: return NewError("Lock refused", 400) case 27: return NewError("Delete pending", 400) case 28: return NewError("File corrupt", 400) case 29: return NewError("Invalid owner", 400) case 30: return NewError("Invalid group", 400) case 31: return NewError("Lock wasn't granted", 400) default: return NewError("Oops! Something went wrong", 500) } } ================================================ FILE: server/plugin/plg_backend_storj/index.go ================================================ package plg_backend_storj import ( "context" "io" "os" "path/filepath" "storj.io/uplink" "strings" "sync" "time" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/sync/semaphore" ) const CONCURRENCY_LEVEL = 20 type Storj struct { Context context.Context Project *uplink.Project } func init() { Backend.Register("storj", Storj{}) } func (this Storj) Init(params map[string]string, app *App) (IBackend, error) { access, err := uplink.ParseAccess(params["access-grant"]) if err != nil { return nil, ErrAuthenticationFailed } project, err := uplink.OpenProject(app.Context, access) if err != nil { return nil, ErrAuthenticationFailed } return Storj{app.Context, project}, nil } func (this Storj) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "storj", }, { Name: "access-grant", Type: "text", Placeholder: "Access Grant", }, }, } } func (this Storj) Meta(path string) Metadata { defer this.Project.Close() if path == "/" { return Metadata{ CanCreateFile: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), } } return Metadata{} } func (this Storj) Ls(path string) ([]os.FileInfo, error) { defer this.Project.Close() files := make([]os.FileInfo, 0) bucket, prefix := this.path(path) if bucket == "" { buckets := this.Project.ListBuckets(this.Context, nil) for buckets.Next() { item := buckets.Item() files = append(files, File{ FName: item.Name, FType: "directory", FTime: item.Created.Unix(), }) } if err := buckets.Err(); err != nil { return files, err } } else { objects := this.Project.ListObjects(this.Context, bucket, &uplink.ListObjectsOptions{ Prefix: prefix, Recursive: false, }) for objects.Next() { item := objects.Item() fname := filepath.Base(item.Key) if fname == ".file_placeholder" { continue } files = append(files, File{ FName: fname, FType: func() string { if item.IsPrefix { return "directory" } return "file" }(), FSize: -1, FTime: -1, }) } if err := objects.Err(); err != nil { return files, err } } return files, nil } func (this Storj) Stat(path string) (os.FileInfo, error) { defer this.Project.Close() bucket, prefix := this.path(path) if finfo, err := this.Project.StatObject(this.Context, bucket, prefix); err == nil { return File{ FName: filepath.Base(path), FTime: finfo.System.Created.Unix(), FType: func() string { if finfo.IsPrefix { return "directory" } return "file" }(), FSize: finfo.System.ContentLength, }, nil } return File{ FName: filepath.Base(path), FType: "directory", FTime: -1, }, nil } func (this Storj) Cat(path string) (io.ReadCloser, error) { defer this.Project.Close() bucket, prefix := this.path(path) return this.Project.DownloadObject(this.Context, bucket, prefix, nil) } func (this Storj) Mkdir(path string) error { if bucket, prefix := this.path(path); prefix == "" { _, err := this.Project.CreateBucket(this.Context, bucket) this.Project.Close() return err } return this.Touch(path + ".keep") } func (this Storj) Rm(path string) error { defer this.Project.Close() bucket, prefix := this.path(path) if prefix == "" { // remove an entire bucket with its content _, err := this.Project.DeleteBucketWithObjects(this.Context, bucket) return err } else if strings.HasSuffix(path, "/") == false { // remove a file _, err := this.Project.DeleteObject(this.Context, bucket, prefix) return err } // remove a directory objects := this.Project.ListObjects(this.Context, bucket, &uplink.ListObjectsOptions{ Prefix: prefix, Recursive: true, }) var ( err error mu sync.Mutex ) sem := semaphore.NewWeighted(CONCURRENCY_LEVEL) for objects.Next() { item := objects.Item() if item.IsPrefix { continue } mu.Lock() if err != nil { mu.Unlock() break } mu.Unlock() sem.Acquire(this.Context, 1) go func() { _, _err := this.Project.DeleteObject(this.Context, bucket, item.Key) if _err != nil { mu.Lock() err = _err mu.Unlock() } sem.Release(1) }() } sem.Acquire(this.Context, CONCURRENCY_LEVEL) // wait return err } func (this Storj) Mv(from, to string) error { // TODO defer this.Project.Close() fromBucket, fromPrefix := this.path(from) toBucket, toPrefix := this.path(to) if strings.HasSuffix(from, "/") == false && strings.HasSuffix(to, "/") == false { // move single file return this.Project.MoveObject( this.Context, fromBucket, fromPrefix, toBucket, toPrefix, nil, ) } else if strings.HasSuffix(from, "/") && strings.HasSuffix(to, "/") { // move a directory objects := this.Project.ListObjects(this.Context, fromBucket, &uplink.ListObjectsOptions{ Prefix: fromPrefix, Recursive: true, }) var ( err error mu sync.Mutex ) sem := semaphore.NewWeighted(CONCURRENCY_LEVEL) for objects.Next() { item := objects.Item() if item.IsPrefix { continue } mu.Lock() if err != nil { mu.Unlock() break } mu.Unlock() sem.Acquire(this.Context, 1) go func() { _err := this.Project.MoveObject( this.Context, fromBucket, item.Key, toBucket, strings.Replace(item.Key, fromPrefix, toPrefix, 1), nil, ) if _err != nil { mu.Lock() err = _err mu.Unlock() } sem.Release(1) }() } sem.Acquire(this.Context, CONCURRENCY_LEVEL) // wait return err } // attempt to move a directory onto a file or some other weird combination return ErrNotValid } func (this Storj) Save(path string, content io.Reader) error { defer this.Project.Close() bucket, prefix := this.path(path) upload, err := this.Project.UploadObject(this.Context, bucket, prefix, nil) if err != nil { return err } if _, err = io.Copy(upload, content); err != nil { _ = upload.Abort() return err } _ = upload.SetCustomMetadata(this.Context, map[string]string{ "creation_date": time.Now().Format(time.RFC3339), "created_by": "Filestash", }) return upload.Commit() } func (this Storj) Touch(path string) error { return this.Save(path, strings.NewReader("")) } func (this Storj) path(path string) (string, string) { path = strings.TrimPrefix(path, "/") sp := strings.SplitN(path, "/", 2) if len(sp) != 2 { return "", "" } return sp[0], sp[1] } ================================================ FILE: server/plugin/plg_backend_syncthing/README.md ================================================ ``` # Test instance: docker run --name=syncthing-sync -d -e PUID=1000 -e PGID=1000 -p 8384:8384 -v ./syncthing/config:/var/syncthing -v ./syncthing/sync:/sync syncthing/syncthing ``` ref: https://docs.syncthing.net/v1.0.0/rest/system-browse-get.html ================================================ FILE: server/plugin/plg_backend_syncthing/index.go ================================================ package plg_backend_syncthing import ( "io" "net/http" "os" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Backend.Register("syncthing", &Syncthing{}) } type Syncthing struct { baseURL string apiKey string client *http.Client syncPath string folders []SyncFolder } func (s *Syncthing) Init(params map[string]string, app *App) (IBackend, error) { url := params["url"] if url == "" { url = "http://localhost:8384" } backend := &Syncthing{ baseURL: url, apiKey: params["api_key"], syncPath: params["sync_path"], client: &http.Client{}, } var err error backend.folders, err = backend.fetchFolders() if err != nil { return nil, err } return backend, nil } func (s *Syncthing) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "syncthing", }, { Name: "url", Type: "text", Placeholder: "Address", }, { Name: "api_key", Type: "text", Placeholder: "API Key", }, { Name: "sync_path", Type: "text", Placeholder: "Sync directory", }, }, } } func (s *Syncthing) Meta(path string) Metadata { if path == "/" || s.syncPath == "" { return Metadata{ CanCreateDirectory: NewBool(false), CanCreateFile: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), CanDelete: NewBool(false), } } return Metadata{} } func (s *Syncthing) Ls(path string) ([]os.FileInfo, error) { if path == "/" { var fileInfos []os.FileInfo for _, folder := range s.folders { fileInfos = append(fileInfos, &File{ FName: folder.Name, FType: "directory", }) } return fileInfos, nil } if s.syncPath != "" { fullpath, err := s.resolvePath(path) if err != nil { return nil, err } f, err := SafeOsOpenFile(fullpath, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } files, err := f.Readdir(-1) if err != nil { f.Close() return nil, err } return files, f.Close() } return s.listDirectory(path) } func (s *Syncthing) Stat(path string) (os.FileInfo, error) { fullpath, err := s.resolvePath(path) if err != nil { return nil, err } f, err := SafeOsOpenFile(fullpath, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } defer f.Close() return f.Stat() } func (s *Syncthing) Cat(path string) (io.ReadCloser, error) { fullpath, err := s.resolvePath(path) if err != nil { return nil, err } f, err := SafeOsOpenFile(fullpath, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } fs, err := f.Stat() if err != nil { f.Close() return nil, err } if fs.IsDir() { f.Close() return nil, ErrNotFound } return f, nil } func (s *Syncthing) Mkdir(path string) error { fullpath, err := s.resolvePath(path) if err != nil { return err } return SafeOsMkdir(fullpath, 0755) } func (s *Syncthing) Rm(path string) error { fullpath, err := s.resolvePath(path) if err != nil { return err } return SafeOsRemoveAll(fullpath) } func (s *Syncthing) Mv(from, to string) error { fromPath, err := s.resolvePath(from) if err != nil { return err } toPath, err := s.resolvePath(to) if err != nil { return err } return SafeOsRename(fromPath, toPath) } func (s *Syncthing) Save(path string, content io.Reader) error { fullpath, err := s.resolvePath(path) if err != nil { return err } f, err := SafeOsOpenFile(fullpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664) if err != nil { return err } if _, err = io.Copy(f, content); err != nil { f.Close() return err } return f.Close() } func (s *Syncthing) Touch(path string) error { fullpath, err := s.resolvePath(path) if err != nil { return err } f, err := SafeOsOpenFile(fullpath, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return err } return f.Close() } ================================================ FILE: server/plugin/plg_backend_syncthing/index_helper.go ================================================ package plg_backend_syncthing import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) type SyncFolder struct { ID string Name string Path string SyncPath string } func (s *Syncthing) makeAPICall(endpoint string, params map[string]string) ([]byte, error) { query := url.Values{} for k, v := range params { query.Set(k, v) } apiURL := fmt.Sprintf("%s%s", s.baseURL, endpoint) if len(query) > 0 { apiURL += "?" + query.Encode() } req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return nil, err } req.Header.Set("X-API-Key", s.apiKey) resp, err := s.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode != 200 { return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body)) } return body, nil } func (s *Syncthing) fetchFolders() ([]SyncFolder, error) { data, err := s.makeAPICall("/rest/config", nil) if err != nil { return nil, err } var config struct { Folders []struct { ID string `json:"id"` Label string `json:"label"` Path string `json:"path"` } `json:"folders"` } if err := json.Unmarshal(data, &config); err != nil { return nil, err } folders := make([]SyncFolder, 0, len(config.Folders)) for _, folder := range config.Folders { name := folder.Label if name == "" { name = folder.ID } folders = append(folders, SyncFolder{ ID: folder.ID, Name: name, Path: folder.Path, }) } return folders, nil } func (s *Syncthing) listDirectory(path string) ([]os.FileInfo, error) { parts := strings.Split(strings.Trim(path, "/"), "/") if len(parts) == 0 { return nil, ErrNotValid } folderName := parts[0] prefix := "" if len(parts) > 1 { prefix = strings.Join(parts[1:], "/") } var folderID string for _, folder := range s.folders { if folder.Name == folderName { folderID = folder.ID break } } if folderID == "" { return nil, ErrNotFound } data, err := s.makeAPICall("/rest/db/browse", map[string]string{ "folder": folderID, "prefix": prefix, "levels": "1", }) if err != nil { return nil, err } var result []struct { Name string `json:"name"` Type string `json:"type"` Size int64 `json:"size"` Modified string `json:"modTime"` } if err := json.Unmarshal(data, &result); err != nil { return nil, err } var fileInfos []os.FileInfo for _, item := range result { modTime := time.Time{} if item.Modified != "" { if t, err := time.Parse(time.RFC3339Nano, item.Modified); err == nil { modTime = t } } if item.Type == "FILE_INFO_TYPE_DIRECTORY" { fileInfos = append(fileInfos, &File{ FName: item.Name, FType: "directory", FTime: modTime.Unix(), }) } else if item.Type == "FILE_INFO_TYPE_FILE" { isOffline := s.syncPath == "" fileInfos = append(fileInfos, &File{ FName: item.Name, FType: "file", FSize: item.Size, FTime: modTime.Unix(), Offline: isOffline, }) } else { Log.Error("plg_backend_syncthing::ls error=unknownItemType type=%s", item.Type) } } return fileInfos, nil } func (s *Syncthing) resolvePath(path string) (string, error) { parts := strings.Split(strings.Trim(path, "/"), "/") if len(parts) == 0 { return "", ErrNotValid } folderName := parts[0] relativePath := "" if len(parts) > 1 { relativePath = strings.Join(parts[1:], "/") } for _, folder := range s.folders { if folder.Name == folderName { return filepath.Join(s.syncPath, folder.Path, relativePath), nil } } return "", ErrNotFound } ================================================ FILE: server/plugin/plg_backend_tmp/index.go ================================================ package plg_backend_tmp import ( "encoding/base64" . "github.com/mickael-kerjean/filestash/server/common" "io" "os" "path/filepath" "regexp" "strings" ) const ( FILESTASH_DIRECTORY = "/tmp/filestash_tmp/" EMPTY_DOCX = "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" ) var ChrootCache AppCache func init() { Backend.Register("tmp", TmpStorage{}) ChrootCache = NewAppCache(60 * 24 * 30) ChrootCache.OnEvict(func(key string, value interface{}) { chroot := value.(string) if strings.HasPrefix(chroot, FILESTASH_DIRECTORY) { os.RemoveAll(chroot) } }) os.RemoveAll(FILESTASH_DIRECTORY) } type TmpStorage struct { userID string } func (this TmpStorage) Init(params map[string]string, app *App) (IBackend, error) { if len(params["userID"]) == 0 { return nil, ErrAuthenticationFailed } else if regexp.MustCompile(`^[a-zA-Z0-9]*$`).MatchString(params["userID"]) == false { return nil, ErrAuthenticationFailed } this.userID = params["userID"] root, err := this.fullpath("/") if err != nil { return nil, ErrAuthenticationFailed } if c := ChrootCache.Get(params); c == nil { ChrootCache.Set(params, root) } os.MkdirAll(root, 0755) return &this, nil } func (this TmpStorage) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "tmp", }, { Name: "userID", Type: "text", Placeholder: "user ID", }, }, } } func (this TmpStorage) Ls(path string) ([]os.FileInfo, error) { path, err := this.fullpath(path) if err != nil { return nil, err } f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } files, err := f.Readdir(-1) if err != nil { f.Close() return nil, err } return files, f.Close() } func (this TmpStorage) Stat(path string) (os.FileInfo, error) { path, err := this.fullpath(path) if err != nil { return nil, err } f, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } finfo, err := f.Stat() if err != nil { f.Close() return nil, err } return finfo, f.Close() } func (this TmpStorage) Cat(path string) (io.ReadCloser, error) { path, err := this.fullpath(path) if err != nil { return nil, err } reader, err := SafeOsOpenFile(path, os.O_RDONLY, os.ModePerm) if err == nil { return reader, nil } else if os.IsExist(err) == false { if strings.HasSuffix(path, ".doc") || strings.HasSuffix(path, ".docx") { docx, err := base64.StdEncoding.DecodeString(EMPTY_DOCX) if err != nil { return nil, err } return NewReadCloserFromBytes(docx), nil } return NewReadCloserFromBytes([]byte("")), nil } return reader, err } func (this TmpStorage) Mkdir(path string) error { path, err := this.fullpath(path) if err != nil { return err } return SafeOsMkdir(path, 0755) } func (this TmpStorage) Rm(path string) error { path, err := this.fullpath(path) if err != nil { return err } return SafeOsRemoveAll(path) } func (this TmpStorage) Mv(from, to string) error { from, err := this.fullpath(from) if err != nil { return err } to, err = this.fullpath(to) if err != nil { return err } return SafeOsRename(from, to) } func (this TmpStorage) Save(path string, content io.Reader) error { path, err := this.fullpath(path) if err != nil { return err } f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { return err } if _, err = io.Copy(f, content); err != nil { f.Close() return nil } return f.Close() } func (this TmpStorage) Touch(path string) error { path, err := this.fullpath(path) if err != nil { return err } f, err := SafeOsOpenFile(path, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return err } if _, err = f.Write([]byte("")); err != nil { f.Close() return err } return f.Close() } func (this TmpStorage) fullpath(path string) (string, error) { path = filepath.Join(FILESTASH_DIRECTORY, this.userID, path) if strings.HasPrefix(path, FILESTASH_DIRECTORY) == false { Log.Warning("plg_backend_tmp::chroot attempt to circumvent chroot via path[%s]", path) return "", ErrPermissionDenied } return path, nil } ================================================ FILE: server/plugin/plg_backend_url/index.go ================================================ package plg_backend_url import ( "context" "crypto/tls" "fmt" "io" "net" "net/http" "net/url" "os" "path/filepath" "regexp" "slices" "strconv" "strings" "sync" "time" "golang.org/x/net/html" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Backend.Register("url", &Url{}) } type Url struct { root url.URL home string ctx context.Context } func (this Url) Meta(path string) Metadata { return Metadata{ CanCreateFile: NewBool(false), CanCreateDirectory: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), CanDelete: NewBool(false), } } func (this Url) Init(params map[string]string, app *App) (IBackend, error) { u, err := url.Parse(params["url"]) if err != nil { return nil, err } home := u.Path u.Path = "/" return &Url{*u, home, app.Context}, nil } func (this *Url) LoginForm() Form { return Form{ Elmnts: []FormElement{ { Name: "type", Type: "hidden", Value: "url", }, { Name: "url", Type: "text", Placeholder: "base URL", }, }, } } func (this *Url) Ls(path string) ([]os.FileInfo, error) { this.root.Path = path if strings.HasSuffix(this.root.Path, "/") == false { this.root.Path += "/" } resp, err := request(this.ctx, http.MethodGet, this.root.String(), "") if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { return nil, ErrNotFound } else if resp.StatusCode == http.StatusForbidden { return nil, ErrNotAllowed } return nil, fmt.Errorf("HTTP Error %d", resp.StatusCode) } doc, err := html.Parse(resp.Body) if err != nil { return nil, err } var links []os.FileInfo var crawler func(*html.Node) crawler = func(node *html.Node) { if node.Type == html.ElementNode && slices.Contains([]string{"a", "img", "object", "iframe"}, strings.ToLower(node.Data)) { for _, attr := range node.Attr { link := "" if strings.ToLower(attr.Key) == "href" { link = attr.Val } if link == "" { continue } if f := this.processLink(attr.Val, node); f != nil { insertPos := -1 for i := 0; i < len(links); i++ { if links[i].Name() == f.Name() { insertPos = i } } if insertPos < 0 { links = append(links, f) } else if links[insertPos].(*File).FTime == 0 { links[insertPos] = f } } break } } for child := node.FirstChild; child != nil; child = child.NextSibling { crawler(child) } } crawler(doc) return links, nil } func (this *Url) Stat(path string) (os.FileInfo, error) { this.root.Path = path resp, err := request(this.ctx, http.MethodHead, this.root.String(), "") if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { return nil, ErrNotFound } else if resp.StatusCode == http.StatusForbidden { return nil, ErrNotAllowed } return nil, fmt.Errorf("HTTP Error %d", resp.StatusCode) } finfo := &File{ FName: filepath.Base(path), FType: "file", FSize: -1, FTime: -1, } if cl := resp.Header.Get("Content-Length"); cl != "" { if s, err := strconv.ParseInt(cl, 10, 64); err == nil { finfo.FSize = s } } if lm := resp.Header.Get("Last-Modified"); lm != "" { if t, err := time.Parse(time.RFC1123, lm); err == nil { finfo.FTime = t.Unix() } } if strings.HasSuffix(path, "/") { finfo.FType = "directory" } return finfo, nil } func (this Url) processLink(link string, n *html.Node) *File { u, err := url.Parse(link) if err != nil { return nil } else if u.Host != "" && u.Host != this.root.Host { return nil } else if u.Path == "" { return nil } fType := "file" fullpath := this.JoinPath(u) if !strings.HasPrefix(fullpath, this.root.Path) { return nil } fName := strings.TrimPrefix(fullpath, this.root.Path) var fSize int64 = -1 var fTime int64 = 0 if strings.HasSuffix(fName, "/") { fType = "directory" fName = strings.Trim(fName, "/") } if fName == "" || fName == "." || fName == "/" { return nil } for _, extr := range []func(node *html.Node) (int64, int64, error){ extractNginxList, extractASPNetList, extractApacheList, extractApacheList2, extractApacheList3, } { if s, t, err := extr(n); err == nil { fSize = s fTime = t break } } f := &File{ FName: fName, FType: fType, FTime: fTime, FSize: fSize, } return f } func extract(reg *regexp.Regexp, layout string, toText func(n *html.Node) string) func(node *html.Node) (int64, int64, error) { return func(node *html.Node) (int64, int64, error) { nodeData := toText(node) if nodeData == "" { return -1, -1, ErrNotFound } match := reg.FindStringSubmatch(nodeData) if len(match) != 3 { return -1, -1, ErrNotFound } sizeStr := strings.ToUpper(match[2]) var m int64 = 1 if strings.HasSuffix(sizeStr, "K") { sizeStr = strings.TrimSuffix(sizeStr, "K") m = 1024 } if strings.HasSuffix(sizeStr, "M") { sizeStr = strings.TrimSuffix(sizeStr, "M") m = 1024 * 1024 } if strings.HasSuffix(sizeStr, "G") { sizeStr = strings.TrimSuffix(sizeStr, "G") m = 1024 * 1024 * 1024 } if strings.HasSuffix(sizeStr, "T") { sizeStr = strings.TrimSuffix(sizeStr, "T") m = 1024 * 1024 * 1024 * 1024 } s, err := strconv.ParseFloat(strings.TrimSpace(sizeStr), 64) if err != nil { s = 0 } size := int64(s*1000) * m / 1000 t, err := time.Parse(layout, match[1]) if err != nil { return -1, -1, ErrNotFound } return size, t.Unix(), nil } } func request(ctx context.Context, method string, url string, rangeHeader string) (*http.Response, error) { r, err := http.NewRequestWithContext(ctx, method, url, nil) if rangeHeader != "" { r.Header.Set("Range", rangeHeader) } if err != nil { return nil, err } return (&http.Client{ Timeout: 5 * time.Hour, Transport: NewTransformedTransport(&http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, Dial: (&net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 10 * time.Second, }).Dial, TLSHandshakeTimeout: 5 * time.Second, IdleConnTimeout: 60 * time.Second, ResponseHeaderTimeout: 60 * time.Second, }), }).Do(r) } var extractASPNetList = extract( regexp.MustCompile(`^\s*([0-9]{1,2}\/[0-9]{1,2}\/[0-9]{4}\s+[0-9]{1,2}:[0-9]{1,2} [AM|PM]{2})\s+([0-9]+|)\s*$`), "1/2/2006 3:4 PM", func(n *html.Node) string { if n.PrevSibling == nil { return "" } else if n.Parent == nil { return "" } else if n.Parent.Type == 3 && n.Parent.Data != "pre" { return "" } return n.PrevSibling.Data }, ) var extractNginxList = extract( regexp.MustCompile(`\s*([0-9]{2}-[A-Z][a-z]{2}-[0-9]{4} [0-9]{2}:[0-9]{2})\s+([0-9\-]+[KMGT]?)`), "_2-Jan-2006 15:04", func(n *html.Node) string { if n.NextSibling == nil { return "" } else if len(n.NextSibling.Attr) > 0 { return "" } else if n.Parent == nil { return "" } else if n.Parent.Type == 3 && n.Parent.Data != "pre" { return "" } return n.NextSibling.Data }, ) var nodeApacheExtract = func(n *html.Node) (msg string) { if n.Parent == nil { return "" } defer func() { if msg != "" { return } if n.NextSibling != nil { msg = n.NextSibling.Data } }() if n.Parent.NextSibling == nil || n.Parent.NextSibling.FirstChild == nil { return msg } else if n.Parent.NextSibling.NextSibling == nil || n.Parent.NextSibling.NextSibling.FirstChild == nil { return msg } col0 := n.Parent.NextSibling.FirstChild.Data col1 := n.Parent.NextSibling.NextSibling.FirstChild.Data msg = col0 + col1 if n.Parent.NextSibling.NextSibling.NextSibling != nil && n.Parent.NextSibling.NextSibling.NextSibling.FirstChild != nil { msg += n.Parent.NextSibling.NextSibling.NextSibling.FirstChild.Data } msg += " " + col0 return msg } var extractApacheList = extract( regexp.MustCompile(`([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2})\s+([0-9\.\-]+\s?[kKMGT]?)`), "2006-01-02 15:04", nodeApacheExtract, ) var extractApacheList2 = extract( regexp.MustCompile(`([0-9]{2}-[A-Z][a-z]{2}-[0-9]{4} [0-9]{2}:[0-9]{2})\s+([0-9\.\-]+\s?[kKMGT]?)`), "02-Jan-2006 15:04", nodeApacheExtract, ) var extractApacheList3 = extract( regexp.MustCompile(`([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2})`+`[ \xA0]+`+`([0-9]+(?:\.[0-9]+)?|-)`+`[ \xA0]*`+`[kKMGT]?`), "2006-01-02 15:04", nodeApacheExtract, ) func (this *Url) Home() (string, error) { return this.home, nil } func (this *Url) Cat(path string) (io.ReadCloser, error) { u := this.root u.Path = filepath.Join(u.Path, path) url := u.String() resp, err := request(this.ctx, http.MethodHead, url, "") if err != nil { return nil, err } resp.Body.Close() var r int64 = -1 if resp.Header.Get("Accept-Ranges") == "bytes" { r, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) if err != nil { return nil, err } } return &urlFilecat{ offset: 0, url: url, ctx: this.ctx, contentLength: r, reader: nil, }, nil } type urlFilecat struct { offset int64 url string ctx context.Context contentLength int64 reader io.ReadCloser mu sync.Mutex } func (this *urlFilecat) Read(p []byte) (n int, err error) { this.mu.Lock() defer this.mu.Unlock() if this.reader == nil { rangeHeader := "" statusOK := http.StatusOK if this.contentLength > 0 { rangeHeader = fmt.Sprintf("bytes=%d-%d", this.offset, this.contentLength-1) statusOK = http.StatusPartialContent } resp, err := request(this.ctx, http.MethodGet, this.url, rangeHeader) if err != nil { return 0, err } if resp.StatusCode != statusOK { resp.Body.Close() return -1, ErrNotFound } this.reader = resp.Body } n, err = this.reader.Read(p) return n, err } func (this *urlFilecat) Close() error { this.mu.Lock() defer this.mu.Unlock() if this.reader == nil { return nil } return this.reader.Close() } func (this *urlFilecat) Seek(offset int64, whence int) (int64, error) { this.mu.Lock() defer this.mu.Unlock() if offset < 0 { return this.offset, os.ErrInvalid } switch whence { case io.SeekStart: case io.SeekCurrent: offset += this.offset case io.SeekEnd: offset = this.contentLength default: return this.offset, ErrNotImplemented } this.offset = offset return this.offset, nil } func (this *Url) Mkdir(path string) error { return ErrNotAllowed } func (this *Url) Rm(path string) error { return ErrNotAllowed } func (this *Url) Mv(from, to string) error { return ErrNotAllowed } func (this *Url) Save(path string, content io.Reader) error { return ErrNotAllowed } func (this *Url) Touch(path string) error { return ErrNotAllowed } func (this *Url) JoinPath(link *url.URL) string { if strings.HasPrefix(link.Path, "/") { return link.Path } return this.root.JoinPath(link.Path).Path } ================================================ FILE: server/plugin/plg_backend_webdav/index.go ================================================ package plg_backend_webdav import ( "encoding/xml" . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "net/url" "os" "path/filepath" "regexp" "strings" "time" ) type WebDav struct { params *WebDavParams } type WebDavParams struct { url string username string password string path string } func init() { Backend.Register("webdav", WebDav{}) } func (w WebDav) Init(params map[string]string, app *App) (IBackend, error) { params["url"] = regexp.MustCompile(`\/$`).ReplaceAllString(params["url"], "") if strings.HasPrefix(params["url"], "http://") == false && strings.HasPrefix(params["url"], "https://") == false { return nil, NewError("Malformed URL - missing http or https", 400) } backend := WebDav{ params: &WebDavParams{ strings.ReplaceAll(params["url"], "%{username}", url.PathEscape(params["username"])), params["username"], params["password"], params["path"], }, } return backend, nil } func (w WebDav) LoginForm() Form { return Form{ Elmnts: []FormElement{ FormElement{ Name: "type", Type: "hidden", Value: "webdav", }, FormElement{ Name: "url", Type: "text", Placeholder: "Address", }, FormElement{ Name: "username", Type: "text", Placeholder: "Username", }, FormElement{ Name: "password", Type: "password", Placeholder: "Password", }, FormElement{ Name: "advanced", Type: "enable", Placeholder: "Advanced", Target: []string{"webdav_path"}, }, FormElement{ Id: "webdav_path", Name: "path", Type: "text", Placeholder: "Path", }, }, } } func (w WebDav) Ls(path string) ([]os.FileInfo, error) { files := make([]os.FileInfo, 0) query := ` ` res, err := w.request("PROPFIND", w.params.url+encodeURL(path), strings.NewReader(query), func(req *http.Request) { req.Header.Add("Depth", "1") }) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 400 { return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't get things in "+filepath.Base(path), res.StatusCode) } var r WebDavResp decoder := xml.NewDecoder(res.Body) decoder.Decode(&r) if len(r.Responses) == 0 { return nil, NewError("Server not found", 404) } LongURLDav := w.params.url + path ShortURLDav := regexp.MustCompile(`^http[s]?://[^/]*`).ReplaceAllString(LongURLDav, "") for _, tag := range r.Responses { decodedHref := decodeURL(tag.Href) if decodedHref == ShortURLDav || decodedHref == LongURLDav { continue } for i, prop := range tag.Props { if i > 0 { break } files = append(files, File{ FName: filepath.Base(decodedHref), FType: func(p string) string { if p == "collection" { return "directory" } return "file" }(prop.Type.Local), FTime: func() int64 { t, err := time.Parse(time.RFC1123, prop.Modified) if err != nil { return 0 } return t.Unix() }(), FSize: prop.Size, }) } } return files, nil } func (w WebDav) Stat(path string) (os.FileInfo, error) { query := ` ` res, err := w.request("PROPFIND", w.params.url+encodeURL(path), strings.NewReader(query), func(req *http.Request) { req.Header.Add("Depth", "0") }) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode >= 400 { return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't get things in "+filepath.Base(path), res.StatusCode) } var r WebDavResp if err := xml.NewDecoder(res.Body).Decode(&r); err != nil { return nil, err } if len(r.Responses) == 0 || len(r.Responses[0].Props) == 0 { return nil, ErrNotFound } prop := r.Responses[0].Props[0] var modTime int64 if prop.Modified != "" { if t, err := time.Parse(time.RFC1123, prop.Modified); err == nil { modTime = t.Unix() } } return File{ FName: filepath.Base(decodeURL(r.Responses[0].Href)), FType: func() string { if prop.Type.Local == "collection" { return "directory" } return "file" }(), FTime: modTime, FSize: prop.Size, }, nil } func (w WebDav) Cat(path string) (io.ReadCloser, error) { res, err := w.request("GET", w.params.url+encodeURL(path), nil, nil) if err != nil { return nil, err } if res.StatusCode >= 400 { return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't fetch "+filepath.Base(path), res.StatusCode) } return res.Body, nil } func (w WebDav) Mkdir(path string) error { res, err := w.request("MKCOL", w.params.url+encodeURL(path), nil, func(req *http.Request) { req.Header.Add("Overwrite", "F") }) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't create "+filepath.Base(path), res.StatusCode) } return nil } func (w WebDav) Rm(path string) error { res, err := w.request("DELETE", w.params.url+encodeURL(path), nil, nil) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't remove "+filepath.Base(path), res.StatusCode) } return nil } func (w WebDav) Mv(from string, to string) error { res, err := w.request("MOVE", w.params.url+encodeURL(from), nil, func(req *http.Request) { req.Header.Add("Destination", w.params.url+encodeURL(to)) req.Header.Add("Overwrite", "T") }) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode) } return nil } func (w WebDav) Touch(path string) error { return w.Save(path, strings.NewReader("")) } func (w WebDav) Save(path string, file io.Reader) error { res, err := w.request("PUT", w.params.url+encodeURL(path), file, nil) if err != nil { return err } res.Body.Close() if res.StatusCode >= 400 { return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode) } return nil } func (w WebDav) request(method string, url string, body io.Reader, fn func(req *http.Request)) (*http.Response, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } if w.params.username != "" { req.SetBasicAuth(w.params.username, w.params.password) } req.Header.Add("Content-Type", "text/xml;charset=UTF-8") req.Header.Add("Accept-Charset", "utf-8") switch method { case "GET": req.Header.Add("Accept", "*/*") default: req.Header.Add("Accept", "application/xml,text/xml") } if req.Body != nil { defer req.Body.Close() } if fn != nil { fn(req) } return HTTPClient.Do(req) } type WebDavResp struct { Responses []struct { Href string `xml:"href"` Props []struct { Name string `xml:"prop>displayname,omitempty"` Type xml.Name `xml:"prop>resourcetype>collection,omitempty"` Size int64 `xml:"prop>getcontentlength,omitempty"` Modified string `xml:"prop>getlastmodified,omitempty"` } `xml:"propstat"` } `xml:"response"` } func encodeURL(path string) string { p := url.PathEscape(path) return strings.Replace(p, "%2F", "/", -1) } func decodeURL(path string) string { str, err := url.PathUnescape(path) if err != nil { return path } return str } ================================================ FILE: server/plugin/plg_editor_onlyoffice/index.go ================================================ package plg_editor_onlyoffice import ( "encoding/json" "fmt" "io" "net" "net/http" "net/http/httputil" "net/url" "os" "path/filepath" "strings" "text/template" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/ctrl" "github.com/mickael-kerjean/filestash/server/middleware" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" "github.com/patrickmn/go-cache" ) var ( SECRET_KEY_DERIVATE_FOR_ONLYOFFICE string onlyoffice_cache *cache.Cache plugin_enable func() bool server_url func() string can_chat func() bool can_copy func() bool can_comment func() bool can_download func() bool can_edit func() bool can_print func() bool ) type onlyOfficeCacheData struct { Path string Save func(path string, file io.Reader) error Cat func(path string) (io.ReadCloser, error) } func init() { SECRET_KEY_DERIVATE_FOR_ONLYOFFICE = Hash("ONLYOFFICE_"+SECRET_KEY, len(SECRET_KEY)) onlyoffice_cache = cache.New(720*time.Minute, 720*time.Minute) plugin_enable = func() bool { return Config.Get("features.office.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{"onlyoffice_server", "onlyoffice_can_chat", "onlyoffice_can_copy", "onlyoffice_can_comment", "onlyoffice_can_download", "onlyoffice_can_edit", "onlyoffice_can_print"} f.Description = "Enable/Disable the office suite and options to manage word, excel and powerpoint documents." f.Default = false if u := os.Getenv("ONLYOFFICE_URL"); u != "" { f.Default = true } return f }).Bool() } server_url = func() string { return Config.Get("features.office.onlyoffice_server").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_server" f.Name = "onlyoffice_server" f.Type = "text" f.Description = "Location of your OnlyOffice server" f.Default = "http://127.0.0.1:8080" f.Placeholder = "Eg: http://127.0.0.1:8080" if u := os.Getenv("ONLYOFFICE_URL"); u != "" { f.Default = u f.Placeholder = fmt.Sprintf("Default: '%s'", u) } return f }).String() } can_chat = func() bool { return Config.Get("features.office.can_chat").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_chat" f.Name = "can_chat" f.Type = "boolean" f.Description = "Enable/Disable chat in onlyoffice" f.Default = false return f }).Bool() } can_copy = func() bool { return Config.Get("features.office.can_copy").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_copy" f.Name = "can_copy" f.Type = "boolean" f.Description = "Enable/Disable copy text in onlyoffice" f.Default = false return f }).Bool() } can_comment = func() bool { return Config.Get("features.office.can_comment").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_comment" f.Name = "can_comment" f.Type = "boolean" f.Description = "Enable/Disable comments in onlyoffice" f.Default = false return f }).Bool() } can_edit = func() bool { return Config.Get("features.office.can_edit").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_edit" f.Name = "can_edit" f.Type = "boolean" f.Description = "Enable/Disable editing in onlyoffice" f.Default = true return f }).Bool() } can_download = func() bool { return Config.Get("features.office.can_download").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_download" f.Name = "can_download" f.Type = "boolean" f.Description = "Display Download button in onlyoffice" f.Default = true return f }).Bool() } can_print = func() bool { return Config.Get("features.office.can_print").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "onlyoffice_can_print" f.Name = "can_print" f.Type = "boolean" f.Description = "Enable/Disable printing in onlyoffice" f.Default = false return f }).Bool() } Hooks.Register.Onload(func() { plugin_enable() server_url() can_chat() can_copy() can_comment() can_download() can_edit() can_print() }) Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { oods := r.PathPrefix("/onlyoffice").Subrouter() oods.PathPrefix("/static/").HandlerFunc(StaticHandler).Methods("GET", "POST") oods.HandleFunc("/event", OnlyOfficeEventHandler).Methods("POST") oods.HandleFunc("/content", FetchContentHandler).Methods("GET") r.HandleFunc( COOKIE_PATH+"onlyoffice/iframe", middleware.NewMiddlewareChain( IframeContentHandler, []Middleware{middleware.SessionStart, middleware.LoggedInOnly}, ), ).Methods("GET") return nil }) Hooks.Register.XDGOpen(` if(mime === "application/word" || mime === "application/msword" || mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" || mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" || mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) { return ["appframe", {"endpoint": "/api/onlyoffice/iframe"}]; } `) } func StaticHandler(res http.ResponseWriter, req *http.Request) { if plugin_enable() == false { return } req.URL.Path = strings.TrimPrefix(req.URL.Path, "/onlyoffice/static") u, err := url.Parse(server_url()) if err != nil { SendErrorResult(res, err) return } req.Header.Set("X-Forwarded-Host", req.Host+"/onlyoffice/static") req.Header.Set("X-Forwarded-Proto", func() string { if scheme := req.Header.Get("X-Forwarded-Proto"); scheme != "" { return scheme } else if req.TLS != nil { return "https" } return "http" }()) // This code is a copy and paste from httputil.NewSingleHostReverseProxy with 1 single change // to do SSL termination. reverseProxy := &httputil.ReverseProxy{ Director: func(rq *http.Request) { rq.URL.Scheme = "http" // <- this is the only change from NewSingleHostReverseProxy rq.URL.Host = u.Host rq.URL.Path = func(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b }(u.Path, rq.URL.Path) if u.RawQuery == "" || rq.URL.RawQuery == "" { rq.URL.RawQuery = u.RawQuery + rq.URL.RawQuery } else { rq.URL.RawQuery = u.RawQuery + "&" + rq.URL.RawQuery } }, } reverseProxy.ErrorHandler = func(rw http.ResponseWriter, rq *http.Request, err error) { Log.Warning("[onlyoffice] %s", err.Error()) SendErrorResult(rw, NewError(err.Error(), http.StatusBadGateway)) } reverseProxy.ServeHTTP(res, req) } func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if plugin_enable() == false { Log.Warning("plg_editor_onlyoffice::handler request_disabled") return } if model.CanRead(ctx) == false { SendErrorResult(res, ErrPermissionDenied) return } else if server_url() == "" { res.WriteHeader(http.StatusServiceUnavailable) res.Write([]byte("

    The Onlyoffice server hasn't been configured

    ")) res.Write([]byte("")) return } var ( path string // path of the file we want to open via onlyoffice filestashServerLocation string // location from which the oods server can reach filestash userId string // as seen by onlyoffice to distinguish different users username string // username as displayed by only office key string // unique identifier for a file as seen be only office contentType string // name of the application in onlyoffice filetype string // extension of the document filename string // filename of the document oodsMode string // edit mode oodsDevice string // mobile, desktop of embedded localip string ) query := req.URL.Query() path, err := ctrl.PathBuilder(ctx, query.Get("path")) if err != nil { SendErrorResult(res, err) return } userId = GenerateID(ctx.Session) f, err := ctx.Backend.Cat(path) if err != nil { SendErrorResult(res, err) return } key = HashStream(f, 20) key = Hash(key+userId+path, 20) filename = filepath.Base(path) oodsMode = func() string { if model.CanEdit(ctx) == false { return "view" } return "edit" }() oodsDevice = func() string { ua := req.Header.Get("User-Agent") if ua == "" { return "desktop" } else if strings.Contains(ua, "iPhone") { return "mobile" } else if strings.Contains(ua, "iPad") { return "mobile" } else if strings.Contains(ua, "Android") { return "mobile" } else if strings.Contains(ua, "Mobile") { return "mobile" } if oodsMode == "view" { return "embedded" } return "desktop" }() username = func() string { if ctx.Session["username"] != "" { return ctx.Session["username"] } return "Me" }() if ctx.Share.Id != "" { username = "Anonymous" userId = RandomString(10) } localip = func() string { // https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go#23558495 addrs, err := net.InterfaceAddrs() if err != nil { return "" } maybeips := []string{} for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { maybeips = append(maybeips, ipnet.IP.String()) } } } // if there is just one interface, we can just pick that one if len(maybeips) == 1 { return maybeips[0] } // if not, fallback to capturing our outgoing local ip conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { return "" } defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) return localAddr.IP.String() }() filestashServerLocation = fmt.Sprintf( "%s://%s:%d", func() string { // proto if req.TLS == nil { return "http" } return "https" }(), localip, Config.Get("general.port").Int(), ) contentType = func(p string) string { var ( word string = "text" excel string = "spreadsheet" powerpoint string = "presentation" ) switch GetMimeType(p) { case "application/word": return word case "application/msword": return word case "application/vnd.oasis.opendocument.text": return word case "application/vnd.oasis.opendocument.spreadsheet": return excel case "application/excel": return excel case "application/vnd.ms-excel": return excel case "application/powerpoint": return powerpoint case "application/vnd.ms-powerpoint": return powerpoint case "application/vnd.oasis.opendocument.presentation": return powerpoint } return "" }(path) filetype = strings.TrimPrefix(filepath.Ext(filename), ".") onlyoffice_cache.Set(key, &onlyOfficeCacheData{path, ctx.Backend.Save, ctx.Backend.Cat}, cache.DefaultExpiration) tmpl, err := template.New("onlyoffice").Parse(`
    `) if err != nil { res.Write([]byte(err.Error())) return } if err := tmpl.Execute(res, map[string]interface{}{ "base": filestashServerLocation, "can_chat": can_chat(), "can_copy": can_copy(), "can_comment": can_comment(), "can_download": can_download(), "can_edit": can_edit(), "can_print": can_print(), "contentType": contentType, "device": oodsDevice, "filename": filename, "filetype": filetype, "key": key, "mode": oodsMode, "token": "foobar", "type": contentType, "userID": userId, "userName": username, }); err != nil { res.Write([]byte(err.Error())) return } } func FetchContentHandler(res http.ResponseWriter, req *http.Request) { if plugin_enable() == false { return } var key string if key = req.URL.Query().Get("key"); key == "" { SendErrorResult(res, NewError("unspecified key", http.StatusBadRequest)) return } c, found := onlyoffice_cache.Get(key) if found == false { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "missing data fetcher handler"}`)) return } cData, valid := c.(*onlyOfficeCacheData) if valid == false { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "invalid cache"}`)) return } f, err := cData.Cat(cData.Path) if err != nil { res.WriteHeader(http.StatusNotFound) res.Write([]byte(`{"error": 1, "message": "error while fetching data"}`)) return } io.Copy(res, f) f.Close() } type onlyOfficeEventObject struct { Actions []struct { Type int `json: "type"` UserId string `json: "userid" ` } `json: "actions"` ChangesURL string `json: "changesurl"` Forcesavetype int `json: "forcesavetype"` History struct { ServerVersion string `json: "serverVersion"` Changes []struct { Created string `json: "created"` User struct { Id string `json: "id"` Name string `json: "name"` } } `json: "changes"` } `json: "history"` Key string `json: "key"` Status int `json: "status"` Url string `json: "url"` UserData string `json: "userdata"` Lastsave string `json: "lastsave"` Users []string `json: "users"` } func OnlyOfficeEventHandler(res http.ResponseWriter, req *http.Request) { if plugin_enable() == false { return } event := onlyOfficeEventObject{} if err := json.NewDecoder(req.Body).Decode(&event); err != nil { SendErrorResult(res, err) return } req.Body.Close() switch event.Status { case 0: Log.Warning("[onlyoffice] no document with the key identifier could be found. %+v", event) case 1: // document is being edited case 2: // document is ready for saving case 3: // document saving error has occurred Log.Warning("[onlyoffice] document saving error has occurred. %+v", event) case 4: // document is closed with no changes case 5: Log.Warning("[onlyoffice] undocumented status. %+v", event) case 6: // document is being edited, but the current document state is saved saveObject, found := onlyoffice_cache.Get(event.Key) if found == false { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "doens't know where to store the given data"}`)) return } cData, valid := saveObject.(*onlyOfficeCacheData) if valid == false { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "[internal error] invalid save handler"}`)) return } r, err := http.NewRequest("GET", event.Url, nil) if err != nil { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "couldn't fetch the document on the oods server"}`)) return } f, err := HTTPClient.Do(r) if err = cData.Save(cData.Path, f.Body); err != nil { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(`{"error": 1, "message": "error while saving the document"}`)) return } f.Body.Close() case 7: Log.Warning("[onlyoffice] error has occurred while force saving the document. %+v", event) default: Log.Warning("[onlyoffice] undocumented status. %+v", event) } res.Write([]byte(`{"error": 0}`)) } ================================================ FILE: server/plugin/plg_editor_wopi/config.go ================================================ package plg_editor_wopi import ( "fmt" "os" . "github.com/mickael-kerjean/filestash/server/common" ) func plugin_enable() bool { return Config.Get("features.office.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{"office_server", "filestash_server", "rewrite_discovery_url"} f.Description = "Enable/Disable the wopi office suite and options to manage word, excel and powerpoint documents." f.Default = false if u := os.Getenv("OFFICE_URL"); u != "" { f.Default = true } return f }).Bool() } func server_url() string { return Config.Get("features.office.office_server").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "office_server" f.Name = "office_server" f.Type = "text" f.Description = "Location of your WOPI Office server" f.Default = "http://127.0.0.1:9980" f.Placeholder = "Eg: http://127.0.0.1:9980" if u := os.Getenv("OFFICE_URL"); u != "" { f.Default = u f.Placeholder = fmt.Sprintf("Default: '%s'", u) } return f }).String() } func origin() string { return Config.Get("features.office.filestash_server").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "filestash_server" f.Name = "filestash_server" f.Type = "text" f.Description = "Location of your Filestash server from the point of view of the office server. Keep blank if you don't use fancy networking via docker/k8s,..." f.Default = "http://app:8334" f.Placeholder = "Eg: http://app:8334" if u := os.Getenv("OFFICE_FILESTASH_URL"); u != "" { f.Default = u f.Placeholder = fmt.Sprintf("Default: '%s'", u) } return f }).String() } func rewrite_url() string { return Config.Get("features.office.rewrite_discovery_url").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "rewrite_discovery_url" f.Name = "rewrite_discovery_url" f.Type = "text" f.Description = "Rewrite the discovery URL to something else. Typical example is a deployment via docker where your office server resolve via http://wopi_service:9980 but such URL would be unknown when given to a browser. Keep empty if you're not doing a docker / k8s deployment" f.Default = "" f.Placeholder = "Eg: http://localhost:9980" if u := os.Getenv("OFFICE_REWRITE_URL"); u != "" { f.Default = u f.Placeholder = fmt.Sprintf("Default: '%s'", u) } return f }).String() } ================================================ FILE: server/plugin/plg_editor_wopi/handler.go ================================================ package plg_editor_wopi import ( "encoding/base64" "encoding/json" "encoding/xml" "io" "net/http" "net/url" "path/filepath" "strings" "text/template" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/ctrl" "github.com/mickael-kerjean/filestash/server/middleware" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" ) var WOPIRoutes = func(r *mux.Router) error { r.HandleFunc( "/api/wopi/iframe", middleware.NewMiddlewareChain( IframeContentHandler, []Middleware{middleware.SessionStart, middleware.LoggedInOnly}, ), ).Methods("GET") r.HandleFunc("/api/wopi/files/{path64}", WOPIHandler_CheckFileInfo).Methods("GET") r.HandleFunc("/api/wopi/files/{path64}/contents", WOPIHandler_GetFile).Methods("GET") r.HandleFunc("/api/wopi/files/{path64}/contents", WOPIHandler_PutFile).Methods("POST") return nil } var WOPIOverrides = ` if (mime === "application/word" || mime === "application/msword" || mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" || mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" || mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) { return ["appframe", {"endpoint": "/api/wopi/iframe"}]; } ` func WOPIHandler_CheckFileInfo(w http.ResponseWriter, r *http.Request) { if plugin_enable() == false { SendErrorResult(w, ErrNotFound) return } WOPIExecute(w, r)(func(ctx *App, fullpath string, w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(map[string]any{ "BaseFileName": filepath.Base(fullpath), "UserFriendlyName": "Unknown", "UserCanWrite": model.CanEdit(ctx), "IsAdminUser": false, "IsAnonymousUser": true, }); err != nil { SendErrorResult(w, err) return } }) } func WOPIHandler_GetFile(w http.ResponseWriter, r *http.Request) { WOPIExecute(w, r)(func(ctx *App, fullpath string, w http.ResponseWriter) { f, err := ctx.Backend.Cat(fullpath) if err != nil { SendErrorResult(w, err) return } io.Copy(w, f) }) } func WOPIHandler_PutFile(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() WOPIExecute(w, r)(func(ctx *App, fullpath string, w http.ResponseWriter) { err := ctx.Backend.Save(fullpath, r.Body) if err != nil { SendErrorResult(w, err) return } SendSuccessResult(w, nil) }) } func WOPIExecute(w http.ResponseWriter, r *http.Request) func(func(*App, string, http.ResponseWriter)) { return func(fn func(*App, string, http.ResponseWriter)) { middleware.NewMiddlewareChain( func(ctx *App, w http.ResponseWriter, r *http.Request) { fullpath, err := ctrl.PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } fn(ctx, fullpath, w) }, []Middleware{wopiToCommonAPI, middleware.SessionStart}, ).ServeHTTP(w, r) } } func wopiToCommonAPI(fn HandlerFunc) HandlerFunc { extractInfo := func(encodedString string) (path string, shareID string) { tmp := strings.Split(encodedString, "::") // eg: backendID::b64(path)::shareID if len(tmp) < 2 { return "", "" } bpath, err := base64.StdEncoding.DecodeString(tmp[1]) if err != nil { return "", "" } else if len(tmp) > 2 { shareID = tmp[2] } return string(bpath), shareID } return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { path, shareID := extractInfo(mux.Vars(req)["path64"]) if path == "" { SendErrorResult(res, ErrNotValid) return } urlQuery := req.URL.Query() urlQuery.Set("path", path) if shareID != "" { urlQuery.Set("share", shareID) urlQuery.Del("access_key") } else { urlQuery.Set("authorization", urlQuery.Get("access_token")) urlQuery.Del("access_key") } req.URL.RawQuery = urlQuery.Encode() fn(ctx, res, req) }) } func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request) { u, err := wopiDiscovery(ctx, req.URL.Query().Get("path")) if err != nil { Log.Warning("plg_editor_wopi::discovery err=%s", err.Error()) SendErrorResult(res, ErrNotValid) return } tmpl, err := template.New("wopi").Parse(` `) if err != nil { res.Write([]byte(err.Error())) return } if err := tmpl.Execute(res, map[string]interface{}{ "server": u, }); err != nil { res.Write([]byte(err.Error())) return } } type WOPIDiscovery struct { XMLName xml.Name `xml:"wopi-discovery"` NetZones []WOPINetZone `xml:"net-zone"` } type WOPINetZone struct { Apps []WOPIApp `xml:"app"` } type WOPIApp struct { Name string `xml:"name,attr"` Actions []WOPIAction `xml:"action"` } type WOPIAction struct { Ext string `xml:"ext,attr"` URLSrc string `xml:"urlsrc,attr"` } func wopiDiscovery(ctx *App, fullpath string) (string, error) { // STEP1: fetch discovery resp, err := http.Get(server_url() + "/hosting/discovery") if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", ErrInternal } body, err := io.ReadAll(resp.Body) if err != nil { return "", err } // STEP2: parse discovery var discovery WOPIDiscovery if err := xml.Unmarshal(body, &discovery); err != nil { return "", err } // STEP3: find the right URLsrc for the desired filetype var urlsrc = "" fileType := strings.TrimPrefix(filepath.Ext(fullpath), ".") for _, netZone := range discovery.NetZones { for _, app := range netZone.Apps { for _, action := range app.Actions { if action.Ext == fileType { urlsrc = action.URLSrc } } } } if urlsrc == "" { return "", ErrNotFound } // STEP4: build the iframe URL u, err := url.Parse(urlsrc) if err != nil { return "", err } wopiSRC := origin() if wopiSRC == "" { wopiSRC := "http://" if Config.Get("general.force_ssl").Bool() { wopiSRC = "https://" } wopiSRC += Config.Get("general.host").String() } wopiSRC += "/api/wopi/files/" wopiSRC += GenerateID(map[string]string{ "id": GenerateID(ctx.Session), "path": fullpath, }) wopiSRC += "::" + base64.StdEncoding.EncodeToString([]byte(fullpath)) if ctx.Share.Id != "" { wopiSRC += "::" + ctx.Share.Id } p := u.Query() p.Set("WOPISrc", wopiSRC) p.Set("access_token", ctx.Authorization) if len(ctx.Languages) > 0 { p.Set("lang", ctx.Languages[0]) } u.RawQuery = p.Encode() if newHost := rewrite_url(); newHost != "" { if p, err := url.Parse(newHost); err == nil { u.Host = p.Host u.Scheme = p.Scheme } } return u.String(), nil } ================================================ FILE: server/plugin/plg_editor_wopi/index.go ================================================ package plg_editor_wopi import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { server_url() origin() rewrite_url() if plugin_enable() { Hooks.Register.XDGOpen(WOPIOverrides) } }) Hooks.Register.HttpEndpoint(WOPIRoutes) } ================================================ FILE: server/plugin/plg_handler_console/generator.go ================================================ //go:build ignore package main import ( "io" "log" "net/http" "os" ) func main() { download( "src/xterm.js", "https://cdnjs.cloudflare.com/ajax/libs/xterm/3.12.2/xterm.js", "https://cdnjs.cloudflare.com/ajax/libs/xterm/3.12.2/addons/fit/fit.js", ) download( "src/xterm.css", "https://cdnjs.cloudflare.com/ajax/libs/xterm/3.12.2/xterm.css", ) } func download(dst string, urls ...string) { f, err := os.Create(dst) if err != nil { log.Fatalf("create %s: %v", dst, err) } defer f.Close() for _, url := range urls { resp, err := http.Get(url) if err != nil { log.Fatalf("fetch %s: %v", url, err) } if resp.StatusCode != http.StatusOK { log.Fatalf("fetch %s: status %d", url, resp.StatusCode) } if _, err = io.Copy(f, resp.Body); err != nil { log.Fatalf("write %s: %v", dst, err) } resp.Body.Close() } log.Printf("wrote %s", dst) } ================================================ FILE: server/plugin/plg_handler_console/index.go ================================================ //go:generate go run generator.go package plg_handler_console ================================================ FILE: server/plugin/plg_handler_console/index_linux.go ================================================ /* * This plugin provide a full fledge terminal application. The code was * adapted from https://github.com/freman/goterm */ package plg_handler_console import ( _ "embed" "encoding/base64" "encoding/json" "io" "net/http" "os" "os/exec" "strings" "syscall" "time" "unsafe" . "github.com/mickael-kerjean/filestash/server/common" "github.com/creack/pty" "github.com/gorilla/mux" "github.com/gorilla/websocket" "golang.org/x/crypto/bcrypt" ) //go:embed src/app.css var AppStyle []byte //go:embed src/xterm.js var VendorScript []byte // made of xterm.js (https://cdnjs.cloudflare.com/ajax/libs/xterm/3.12.2/xterm.js) and the fit addon(https://cdnjs.cloudflare.com/ajax/libs/xterm/3.12.2/addons/fit/fit.js) //go:embed src/xterm.css var VendorStyle []byte var console_enable = func() bool { return Config.Get("features.server.console_enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Default = false f.Name = "console_enable" f.Type = "boolean" f.Description = "Enable/Disable the interactive web console on your instance. It will be available under `/admin/tty/` where username is 'admin' and password is your admin console" f.Placeholder = "Default: false" return f }).Bool() } func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { if console_enable() == false { return ErrNotFound } r.PathPrefix("/admin/tty/").Handler( AuthBasic( func() (string, string) { return "admin", Config.Get("auth.admin").String() }, TTYHandler("/admin/tty/"), ), ) return nil }) } var notAuthorised = func(res http.ResponseWriter, req *http.Request) { time.Sleep(1 * time.Second) res.Header().Set("WWW-Authenticate", `Basic realm="User protect", charset="UTF-8"`) res.WriteHeader(http.StatusUnauthorized) res.Write([]byte("Not Authorised")) return } func AuthBasic(credentials func() (string, string), fn http.Handler) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(Config.Get("general.host").String(), "filestash.app") { http.NotFoundHandler().ServeHTTP(res, req) return } else if console_enable() == false { http.NotFoundHandler().ServeHTTP(res, req) return } auth := req.Header.Get("Authorization") if strings.HasPrefix(auth, "Basic ") == false { notAuthorised(res, req) return } auth = strings.TrimPrefix(auth, "Basic ") decoded, err := base64.StdEncoding.DecodeString(auth) if err != nil { notAuthorised(res, req) return } auth = string(decoded) stuffs := strings.Split(auth, ":") if len(stuffs) < 2 { notAuthorised(res, req) return } username := stuffs[0] password := strings.Join(stuffs[1:], ":") refUsername, refPassword := credentials() if refUsername != username { Log.Info("[tty] username is 'admin'") notAuthorised(res, req) return } else if len(strings.TrimSpace(password)) < 5 { Log.Info("[tty] password is too short") notAuthorised(res, req) return } else if err = bcrypt.CompareHashAndPassword([]byte(refPassword), []byte(password)); err != nil { notAuthorised(res, req) return } fn.ServeHTTP(res, req) return } } func TTYHandler(pathPrefix string) http.Handler { if strings.HasSuffix(pathPrefix, "/") == false { return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte("unsafe path prefix, use a '/'")) return }) } return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { req.URL.Path = "/" + strings.TrimPrefix(req.URL.Path, pathPrefix) if req.Method == "GET" { if req.URL.Path == "/" { res.Header().Set("Content-Type", "text/html") res.Write(htmlIndex(pathPrefix)) return } } if req.URL.Path == "/socket" { handleSocket(res, req) return } res.WriteHeader(http.StatusNotFound) res.Write([]byte("NOT FOUND")) }) } var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } var resizeMessage = struct { Rows uint16 `json:"rows"` Cols uint16 `json:"cols"` X uint16 Y uint16 }{} func handleSocket(res http.ResponseWriter, req *http.Request) { conn, err := upgrader.Upgrade(res, req, nil) if err != nil { res.WriteHeader(http.StatusInternalServerError) res.Write([]byte("upgrade error")) return } defer conn.Close() var cmd *exec.Cmd if _, err = exec.LookPath("/bin/bash"); err == nil { bashCommand := `bash --noprofile --init-file <(cat <
    `) } func AppScript(pathPrefix string) string { return ` (function() { Terminal.applyAddon(fit); var term; function Boot() { term = new Terminal({ cursorBlink: true, theme: { background: "#1d1f21", foreground: "#c5c8c6", cursor: "#c5c8c6", black: "#282a2e", brightBlack: "#373b41", red: "#cc645a", brightRed: "#cc6666", green: "#5fa88d", brightGreen: "#aebd66", yellow: "#f0c666", brightYellow: "#f0c673", blue: "#709dbe", brightBlue: "#81a2be", magenta: "#b394ba", brightMagenta: "#b394ba", cyan: "#88beb3", brightCyan: "#8bbfb6", white: "#707880" } }); var websocket = new WebSocket( (location.protocol === "https:" ? "wss://" : "ws://") + location.hostname + ((location.port) ? (":" + location.port) : "") + "` + pathPrefix + `socket" ); websocket.binaryType = "arraybuffer"; websocket.onopen = function(e) { term.open(document.getElementById("terminal")); term.fit(); term.on("data", function(data) { websocket.send(new TextEncoder().encode("\x00" + data)); websocket.send(new TextEncoder().encode("\x01" + JSON.stringify({cols: term.cols, rows: term.rows}))) }); term.on('resize', function(evt) { term.fit(); websocket.send(new TextEncoder().encode("\x01" + JSON.stringify({cols: evt.cols, rows: evt.rows}))) }); window.onresize = function() { term.fit(); } term.on('title', function(title) { document.title = title; }); } websocket.onmessage = function(e) { if (e.data instanceof ArrayBuffer) { term.write(String.fromCharCode.apply(null, new Uint8Array(e.data))); return; } websocket.close() term.destroy(); alert("Something went wrong"); } websocket.onclose = function(){ term.write("Session terminated"); term.destroy(); } websocket.onerror = function(e){ var $term = document.getElementById("terminal"); if($term) $term.remove(); document.getElementById("terminal").remove() document.getElementById("error-message").innerText = "Websocket Error"; } } Boot(); })()` } ================================================ FILE: server/plugin/plg_handler_console/src/app.css ================================================ html{ height: 100%; } body{ margin: 0; height: 100%; padding: 10px 0 0px 10px; box-sizing: border-box; } #terminal{ height: 100%; } #error-message{ text-align: center; font-size: 1.5em; color: #333; } ================================================ FILE: server/plugin/plg_handler_mcp/README.md ================================================ This is a MCP plugin acting as an interface for AI agent to do stuff on a remote storage you have configured in Filestash, be it a FTP server, SFTP, S3, WebDAV, SMB, GIT, NFS, .... - demo server: https://demo.filestash.app/sse - release note: https://www.filestash.app/2025/04/01/mcp-feature/ ================================================ FILE: server/plugin/plg_handler_mcp/config/config.go ================================================ package plg_handler_mcp import ( . "github.com/mickael-kerjean/filestash/server/common" ) var PluginEnable = func() bool { return Config.Get("features.mcp.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{} f.Description = "Enable/Disable the Model Context Protocol" f.Default = false return f }).Bool() } ================================================ FILE: server/plugin/plg_handler_mcp/handler.go ================================================ package plg_handler_mcp import ( "context" "encoding/json" "fmt" "net/http" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/impl" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/utils" "github.com/google/uuid" ) const SESSION_TIME = 60 func (this *Server) messageHandler(_ *App, w http.ResponseWriter, r *http.Request) { sessionID := r.URL.Query().Get("sessionId") if r.Method != http.MethodPost { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Invalid Request")) return } request := JSONRPCRequest{} if err := json.NewDecoder(r.Body).Decode(&request); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("ERR: " + err.Error())) return } this.GetSession(sessionID).Chan <- request w.WriteHeader(http.StatusNoContent) } func (this *Server) sseHandler(_ *App, w http.ResponseWriter, r *http.Request) { token := ExtractToken(r) if token == "" { Log.Debug("plg_handler_mcp::sse msg=invalid_token") w.Header().Add("Content-Type", "application/json") w.Header().Add("WWW-Authenticate", "Bearer resource_metadata=\""+this.baseURL(r)+"/.well-known/oauth-protected-resource\"") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(JSONRPCResponse{ JSONRPC: "2.0", Error: &JSONRPCError{ Code: http.StatusUnauthorized, Message: "Missing or invalid access token", }, }) return } userSession := this.GetSession(uuid.New().String()) userSession.Token = token if b, err := getBackend(userSession.Token); err == nil { userSession.HomeDir, _ = model.GetHome(b, "/") userSession.CurrDir = ToString(userSession.HomeDir, "/") } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") fmt.Fprintf(w, "event: endpoint\ndata: %s?sessionId=%s\n\n", "/messages", userSession.Id) w.(http.Flusher).Flush() for { select { case request := <-userSession.Chan: b, err := getBackend(userSession.Token) if err != nil { if err == ErrNotAuthorized { err = JSONRPCError{ Code: ErrNotAuthorized.Status(), Message: "You aren't authenticated", } } SendError(w, request.ID, err) break } userSession.Backend = b switch request.Method { case "initialize": SendMessage(w, request.ID, InitializeResponse{ ProtocolVersion: "2024-11-05", ServerInfo: ServerInfo{ Name: "Universal Storage Server", Version: "1.0.0", }, Capabilities: Capabilities{ Tools: map[string]interface{}{}, Resources: map[string]interface{}{}, Prompts: map[string]interface{}{}, }, }) case "resources/list": SendMessage(w, request.ID, &ResourcesListResponse{ Resources: AllResources(), }) case "resources/templates/list": SendMessage(w, request.ID, &ResourceTemplatesListResponse{ ResourceTemplates: AllResourceTemplates(), }) case "resources/read": if uri, ok := request.Params["uri"].(string); ok { if resource, err := FindResource(uri); err != nil { SendError(w, request.ID, JSONRPCError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unknown tool: %s", request.Params["name"]), }) } else { SendMessage(w, request.ID, &ResourceReadResponse{ Contents: []ResourceContent{ { URI: uri, MimeType: resource.MimeType, Text: resource.Content, Meta: resource.Meta, }, }, }) } } else { SendError(w, request.ID, JSONRPCError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unexpected parameters: %v", request.Params), }) } SendMessage(w, request.ID, &ResourceReadResponse{ Contents: ExecResourceRead(request.Params), }) case "prompts/list": SendMessage(w, request.ID, &PromptsListResponse{ Prompts: AllPrompts(), }) case "prompts/get": if m, ok := request.Params["name"].(string); ok { res, err := ExecPromptGet(m, request.Params, &userSession) if err == nil { SendMessage(w, request.ID, PromptGetResponse{ Messages: res, Description: ExecPromptDescription(request.Params), }) } else { SendError(w, request.ID, err) } } else { SendError(w, request.ID, JSONRPCError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unexpected parameters: %v", request.Params), }) } case "tools/list": SendMessage(w, request.ID, &ListToolsResponse{ Tools: AllTools(), }) case "tools/call": if tname, ok := request.Params["name"].(string); ok { if tool, err := FindTool(tname); err != nil { SendError(w, request.ID, JSONRPCError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unknown tool: %s", request.Params["name"]), }) } else if res, err := tool.Run(request.Params, &userSession); err != nil { SendMessage(w, request.ID, ToolResponse{ Content: []TextContent{{"text", err.Error()}}, IsError: true, }) } else { SendMessage(w, request.ID, res) } } else { SendError(w, request.ID, JSONRPCError{ Code: http.StatusBadRequest, Message: fmt.Sprintf("Unexpected parameters: %v", request.Params), }) } case "notifications/initialized": SendMessage(w, request.ID, map[string]string{}) case "completion/complete": SendMessage(w, request.ID, CompletionResponse{ Completion: ExecCompletion(request.Params, &userSession), }) case "ping": SendMessage(w, request.ID, map[string]string{}) default: if request.Method == "" && userSession.Ping.ID == request.ID { // response to ping userSession.Ping.LastResponse = time.Now() userSession.Ping.ID += 1 } else { Log.Warning("plg_handler_mcp::sse message=unknown_method method=%s requestID=%d", request.Method, request.ID) SendError(w, request.ID, JSONRPCError{ Code: http.StatusMethodNotAllowed, Message: fmt.Sprintf("Unknown request: %s", request.Method), }) } } case <-r.Context().Done(): this.RemoveSession(&userSession) return case <-time.After(15 * time.Second): SendPing(w, userSession.Ping.ID) if time.Since(userSession.Ping.LastResponse) > SESSION_TIME*time.Second { SendMethod(w, userSession.Ping.ID+1, "notifications/cancelled", map[string]interface{}{ "requestId": userSession.Ping.ID, "reason": "Request timed out", }) time.Sleep(2 * time.Second) this.RemoveSession(&userSession) if hi, ok := w.(http.Hijacker); ok { if conn, rw, err := hi.Hijack(); err == nil { rw.WriteString("0\r\n\r\n") rw.Flush() time.Sleep(1 * time.Second) conn.Close() } } return } } } } func getBackend(token string) (IBackend, error) { str, err := DecryptString(SECRET_KEY_DERIVATE_FOR_USER, token) if err != nil { return nil, ErrNotAuthorized } session := map[string]string{} if err = json.Unmarshal([]byte(str), &session); err != nil { return nil, err } return model.NewBackend(&App{ Context: context.Background(), }, session) } ================================================ FILE: server/plugin/plg_handler_mcp/handler_auth.go ================================================ package plg_handler_mcp import ( "encoding/json" "fmt" "net/http" "regexp" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) const ( DEFAULT_TOKEN_EXPIRY = 3600 DEFAULT_SECRET_EXPIRY = 30 * 24 * 3600 ) var KEY_FOR_CODE string func init() { Hooks.Register.Onload(func() { KEY_FOR_CODE = Hash("MCP_CODE_"+SECRET_KEY, len(SECRET_KEY)) }) } func (this Server) WellKnownOAuthAuthorizationServerHandler(_ *App, w http.ResponseWriter, r *http.Request) { baseURL := this.baseURL(r) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "issuer": baseURL, "authorization_endpoint": fmt.Sprintf("%s/mcp/authorize", baseURL), "token_endpoint": fmt.Sprintf("%s/mcp/token", baseURL), "registration_endpoint": fmt.Sprintf("%s/mcp/register", baseURL), "response_types_supported": []string{"code"}, "grant_types_supported": []string{"authorization_code"}, "token_endpoint_auth_methods_supported": []string{ "none", }, "code_challenge_methods_supported": []string{ "S256", }, }) } func (this Server) WellKnownOAuthProtectedResourceHandler(_ *App, w http.ResponseWriter, r *http.Request) { baseURL := this.baseURL(r) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "resource": baseURL, "authorization_servers": []string{baseURL}, "bearer_methods_supported": []string{"header"}, "scopes_supported": []string{"openid"}, }) } func (this Server) baseURL(r *http.Request) string { scheme := "https" host := r.Host if strings.HasPrefix(host, "localhost") || strings.HasPrefix(host, "127.0.0.1") { scheme = "http" } return fmt.Sprintf("%s://%s", scheme, host) } func (this Server) AuthorizeHandler(_ *App, w http.ResponseWriter, r *http.Request) { responseType := r.URL.Query().Get("response_type") clientID := r.URL.Query().Get("client_id") redirectURI := r.URL.Query().Get("redirect_uri") state := r.URL.Query().Get("state") if responseType != "code" { http.Error(w, "response_type must be 'code'", http.StatusBadRequest) return } else if clientID == "" { http.Error(w, "client_id is required", http.StatusBadRequest) return } else if redirectURI == "" { http.Error(w, "redirect_uri is required", http.StatusBadRequest) return } http.Redirect(w, r, fmt.Sprintf( "/login?next=/api/mcp?redirect_uri=%s%%26state=%s%%26client_id=%s", redirectURI, state, clientID, ), http.StatusSeeOther) } func (this Server) TokenHandler(_ *App, w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } if grantType := r.FormValue("grant_type"); grantType != "authorization_code" { http.Error(w, "Invalid Grant Type", http.StatusBadRequest) return } token, err := DecryptString(KEY_FOR_CODE, r.FormValue("code")) if err != nil { http.Error(w, "Invalid authorization code", http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": token, "token_type": "Bearer", }) } func (this Server) RegisterHandler(ctx *App, w http.ResponseWriter, r *http.Request) { clientName := regexp.MustCompile("[^a-zA-Z0-9\\-]+").ReplaceAllString( fmt.Sprintf("%s", ctx.Body["client_name"]), "", ) clientID := clientName + "." + Hash(clientName+time.Now().String(), 8) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(struct { ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` ClientIDIssuedAt int64 `json:"client_id_issued_at"` ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` ClientName string `json:"client_name"` RedirectURIs []string `json:"redirect_uris"` GrantTypes []string `json:"grant_types"` TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"` }{ ClientID: clientID, ClientSecret: Hash(clientID, 32), // unused. eg: chatgpt act as public client ClientIDIssuedAt: time.Now().Unix(), ClientSecretExpiresAt: time.Now().Unix() + DEFAULT_SECRET_EXPIRY, ClientName: clientName, RedirectURIs: []string{}, GrantTypes: []string{"authorization_code"}, TokenEndpointAuthMethod: "none", }) } func (this Server) CallbackHandler(ctx *App, res http.ResponseWriter, req *http.Request) { uri := req.URL.Query().Get("redirect_uri") state := req.URL.Query().Get("state") if uri == "" { SendErrorResult(res, ErrNotValid) return } code, err := EncryptString(KEY_FOR_CODE, ctx.Authorization) if err != nil { SendErrorResult(res, ErrNotValid) return } uri += "?code=" + code if state != "" { uri += "&state=" + state } http.Redirect(res, req, uri, http.StatusSeeOther) } ================================================ FILE: server/plugin/plg_handler_mcp/handler_state.go ================================================ package plg_handler_mcp import ( "net/http" "strings" "time" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) func (this *Server) RemoveSession(userSession *UserSession) { this.sessions.Delete(userSession.Id) } func ExtractToken(r *http.Request) string { authHeader := r.Header.Get("Authorization") if hasToken := strings.HasPrefix(authHeader, "Bearer "); hasToken == false { return "" } return strings.TrimPrefix(authHeader, "Bearer ") } func (this *Server) GetSession(uuid string) UserSession { ch, _ := this.sessions.LoadOrStore(uuid, UserSession{ Id: uuid, Chan: make(chan JSONRPCRequest), CurrDir: "/", HomeDir: "/", Ping: Ping{ ID: 0, LastResponse: time.Now(), }, }) return ch.(UserSession) } ================================================ FILE: server/plugin/plg_handler_mcp/impl/completion.go ================================================ package impl import ( "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/utils" ) func ExecCompletion(params map[string]any, userSession *UserSession) Completion { if path := GetArgumentString(params, "value"); path != "" && GetArgumentString(params, "name") == "path" { fpath := filepath.Dir(path) fname := filepath.Base(path) if strings.HasSuffix(path, "/") { fname = "" } files, err := userSession.Backend.Ls(EnforceDirectory(fpath)) if err == nil { values := []string{} for _, file := range files { val := JoinPath(fpath, file.Name()) if file.IsDir() { val = EnforceDirectory(val) } if fname == "" && strings.HasPrefix(file.Name(), ".") == false { values = append(values, val) } else if fname != "" && strings.HasPrefix(file.Name(), fname) { values = append(values, val) } if len(values) >= 100 { break } } return Completion{ Values: values, Total: uint64(len(values)), HasMore: false, } } } return Completion{ Values: []string{}, Total: 0, HasMore: false, } } ================================================ FILE: server/plugin/plg_handler_mcp/impl/prompts.go ================================================ package impl import ( "net/http" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) var listOfPrompts = map[string]PromptDefinition{} type PromptDefinition struct { Prompt ExecMessage func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) ExecDescription func(params map[string]any) string } func RegisterPrompt(t PromptDefinition) { listOfPrompts[t.Name] = t } func AllPrompts() []Prompt { t := []Prompt{} for _, v := range listOfPrompts { t = append(t, v.Prompt) } return t } func ExecPromptGet(name string, params map[string]any, userSession *UserSession) ([]PromptMessage, error) { t, ok := listOfPrompts[name] if !ok { return nil, &JSONRPCError{ Code: http.StatusNotFound, Message: "Not Found", } } return t.ExecMessage(params, userSession) } func ExecPromptDescription(params map[string]any) string { n, ok := params["name"].(string) if !ok { return "" } return n } ================================================ FILE: server/plugin/plg_handler_mcp/impl/prompts_fs.go ================================================ package impl import ( . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) func init() { Hooks.Register.Onload(func() { RegisterPrompt(PromptDefinition{ Prompt: Prompt{ Name: "ls", Description: "list directory contents", Arguments: []PromptArgument{ { Name: "path", Description: "path where the query is made", Required: false, }, }, }, ExecMessage: func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) { return []PromptMessage{ { Role: "user", Content: TextContent{ Type: "text", Text: "call ls(path)", }, }, }, nil }, ExecDescription: func(params map[string]any) string { return "list directory contents" }, }) RegisterPrompt(PromptDefinition{ Prompt: Prompt{ Name: "cat", Description: "read a file at a specified path", Arguments: []PromptArgument{ { Name: "path", Description: "path where the query is made", Required: true, }, }, }, ExecMessage: func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) { return []PromptMessage{ { Role: "user", Content: TextContent{ Type: "text", Text: "call cat(path)", }, }, }, nil }, ExecDescription: func(params map[string]any) string { return "read a file at a specified path" }, }) RegisterPrompt(PromptDefinition{ Prompt: Prompt{ Name: "pwd", Description: "print name of current/working directory", Arguments: []PromptArgument{}, }, ExecMessage: func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) { return []PromptMessage{ { Role: "user", Content: TextContent{ Type: "text", Text: "call pwd", }, }, }, nil }, ExecDescription: func(params map[string]any) string { return "print name of current/working directory" }, }) RegisterPrompt(PromptDefinition{ Prompt: Prompt{ Name: "cd", Description: "change the working directory", Arguments: []PromptArgument{ { Name: "path", Description: "path where the query is made", Required: false, }, }, }, ExecMessage: func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) { return []PromptMessage{ { Role: "user", Content: TextContent{ Type: "text", Text: "call cd(path)", }, }, }, nil }, ExecDescription: func(params map[string]any) string { return "change the working directory" }, }) RegisterPrompt(PromptDefinition{ Prompt: Prompt{ Name: "save", Description: "save content to a file at the desired path", Arguments: []PromptArgument{ { Name: "path", Description: "path where the file is stored", Required: true, }, { Name: "content", Description: "content of the file", Required: true, }, }, }, ExecMessage: func(params map[string]any, userSession *UserSession) ([]PromptMessage, error) { return []PromptMessage{ { Role: "user", Content: TextContent{ Type: "text", Text: "call save(path, content)", }, }, }, nil }, ExecDescription: func(params map[string]any) string { return "save content to a file at the desired path" }, }) }) } ================================================ FILE: server/plugin/plg_handler_mcp/impl/public/file-list.html ================================================ File List
    ================================================ FILE: server/plugin/plg_handler_mcp/impl/resources.go ================================================ package impl import ( "fmt" "net/http" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) var listOfResources = map[string]Resource{} func RegisterResource(r Resource) { listOfResources[r.URI] = r } func AllResources() []Resource { r := []Resource{} for _, val := range listOfResources { r = append(r, val) } return r } func AllResourceTemplates() []ResourceTemplate { return []ResourceTemplate{} } func FindResource(uri string) (*Resource, error) { r, ok := listOfResources[uri] if !ok { return nil, JSONRPCError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Unknown resource: %s", uri), } } return &r, nil } func ExecResourceRead(params map[string]any) []ResourceContent { return []ResourceContent{} } ================================================ FILE: server/plugin/plg_handler_mcp/impl/tools.go ================================================ package impl import ( "fmt" "net/http" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) var listOfTools = map[string]Tool{} func RegisterTool(t Tool) { listOfTools[t.Name] = t } func AllTools() []Tool { t := []Tool{} for _, v := range listOfTools { t = append(t, v) } return t } func FindTool(name string) (*Tool, error) { t, ok := listOfTools[name] if !ok { return nil, JSONRPCError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Unknown tool: %s", name), } } return &t, nil } ================================================ FILE: server/plugin/plg_handler_mcp/impl/tools_fs.go ================================================ package impl import ( "bytes" _ "embed" "errors" "io" "path/filepath" "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/utils" ) //go:embed public/file-list.html var widget_ls string func init() { Hooks.Register.Onload(func() { RegisterTool(Tool{ Name: "ls", Description: "Use this when you need to list files and subdirectories in a directory, based on the Unix command: ls. If path is omitted, the current working directory is used", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{}, }), Run: ToolFSLs, Meta: Meta{ "openai/outputTemplate": "ui://widget/render-ls.html", "openai/toolInvocation/invoking": "Querying…", "openai/toolInvocation/invoked": "Results ready", }, Annotations: Meta{ "destructiveHint": false, "openWorldHint": true, "readOnlyHint": true, }, }) RegisterResource(Resource{ URI: "ui://widget/render-ls.html", Name: "render-ls.html", Description: "file listing widget", MimeType: "text/html+skybridge", Content: widget_ls, Meta: Meta{ "openai/widgetPrefersBorder": true, "openai/widgetDomain": "https://chatgpt.com", "openai/widgetCSP": map[string][]string{ "connect_domains": []string{"https://chatgpt.com"}, "resource_domains": []string{"https://*.oaistatic.com"}, "frame_domains": []string{}, }, }, }) RegisterTool(Tool{ Name: "cat", Description: "Use this when you need to read and return the contents of a file at a specific path, based on the Unix command: `cat`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{"path"}, }), Run: ToolFSCat, Annotations: Meta{ "destructiveHint": false, "openWorldHint": true, "readOnlyHint": true, }, }) RegisterTool(Tool{ Name: "pwd", Description: "Use this when you need to know the current working directory, based on the Unix command: `pwd`. The initial working directory is the user home directory, or `/` if none is defined.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{}, "required": []string{}, }), Run: ToolFSPwd, Annotations: Meta{ "destructiveHint": false, "openWorldHint": true, "readOnlyHint": true, }, }) RegisterTool(Tool{ Name: "cd", Description: "Use this when you need to change the current working directory so that subsequent file operations run relative to a different path, based on the Unix command: `cd`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{"path"}, }), Run: ToolFSCd, Annotations: Meta{ "destructiveHint": false, "openWorldHint": true, "readOnlyHint": true, }, }) RegisterTool(Tool{ Name: "mv", Description: "Use this when you need to move or rename a file or directory from one path to another, based on the Unix command: `mv`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "from": map[string]string{ "type": "string", "description": "origin path", }, "to": map[string]string{ "type": "string", "description": "destination path", }, }, "required": []string{"from", "to"}, }), Run: ToolFSMv, Annotations: Meta{ "destructiveHint": true, "openWorldHint": true, "readOnlyHint": false, }, }) RegisterTool(Tool{ Name: "mkdir", Description: "Use this when you need to create a new directory at a specified path, based on the Unix command: `mkdir`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{"path"}, }), Run: ToolFSMkdir, Annotations: Meta{ "destructiveHint": true, "openWorldHint": true, "readOnlyHint": false, }, }) RegisterTool(Tool{ Name: "touch", Description: "Use this when you need to create an empty file at a specified path, based on the Unix command: `touch`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{"path"}, }), Run: ToolFSTouch, Annotations: Meta{ "destructiveHint": true, "openWorldHint": true, "readOnlyHint": false, }, }) RegisterTool(Tool{ Name: "rm", Description: "Use this when you need to remove a file or directory at a specified path, based on the Unix command: `rm`.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, }, "required": []string{"path"}, }), Run: ToolFSRm, Annotations: Meta{ "destructiveHint": true, "openWorldHint": true, "readOnlyHint": false, }, }) RegisterTool(Tool{ Name: "save", Description: "Use this when you need to write or overwrite the contents of a file at a specified path.", InputSchema: JsonSchema(map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]string{ "type": "string", "description": "path where the query is made", }, "content": map[string]string{ "type": "string", "description": "content of the file", }, }, "required": []string{"path"}, }), Run: ToolFSSave, Annotations: Meta{ "destructiveHint": true, "openWorldHint": true, "readOnlyHint": false, }, }) }) } func ToolFSLs(params map[string]any, userSession *UserSession) (*ToolResponse, error) { path := getPath(params, userSession, "path") files, err := userSession.Backend.Ls(EnforceDirectory(path)) if err != nil { return nil, err } structuredContent := make([]File, len(files)) content := bytes.Buffer{} for i, file := range files { if file.IsDir() { content.Write([]byte("[DIR] ")) } else { content.Write([]byte("[FILE] ")) } content.Write([]byte(file.Name())) content.Write([]byte("\n")) ftype := "file" if file.IsDir() { ftype = "directory" } structuredContent[i] = File{ FName: file.Name(), FType: ftype, FSize: file.Size(), FTime: file.ModTime().Unix(), } } return &ToolResponse{ StructuredContent: map[string]any{ "files": structuredContent, }, Content: []TextContent{ { Type: "text", Text: content.String(), }, }, }, nil } func ToolFSCat(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "path") { return nil, ErrNotValid } r, err := userSession.Backend.Cat(getPath(params, userSession, "path")) if err != nil { return nil, err } b, err := io.ReadAll(r) r.Close() if err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: string(b), }, }, }, nil } func ToolFSPwd(params map[string]any, userSession *UserSession) (*ToolResponse, error) { return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: userSession.CurrDir, }, }, }, nil } func ToolFSCd(params map[string]any, userSession *UserSession) (*ToolResponse, error) { path := EnforceDirectory(getPath(params, userSession, "path")) if _, err := userSession.Backend.Ls(path); err != nil { return nil, errors.New("No such file or directory") } userSession.CurrDir = EnforceDirectory(path) return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: userSession.CurrDir, }, }, }, nil } func ToolFSMv(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "from") || isArgEmpty(params, "to") { return nil, ErrNotValid } if err := userSession.Backend.Mv( getPath(params, userSession, "from"), getPath(params, userSession, "to"), ); err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: "done", }, }, }, nil } func ToolFSMkdir(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "path") { return nil, ErrNotValid } if err := userSession.Backend.Mkdir(EnforceDirectory(getPath(params, userSession, "path"))); err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: "done", }, }, }, nil } func ToolFSTouch(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "path") { return nil, ErrNotValid } if err := userSession.Backend.Touch(getPath(params, userSession, "path")); err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: "done", }, }, }, nil } func ToolFSRm(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "path") { return nil, ErrNotValid } if err := userSession.Backend.Rm(getPath(params, userSession, "path")); err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: "done", }, }, }, nil } func ToolFSSave(params map[string]any, userSession *UserSession) (*ToolResponse, error) { if isArgEmpty(params, "path") { return nil, ErrNotValid } if err := userSession.Backend.Save( getPath(params, userSession, "path"), NewReadCloserFromBytes([]byte(GetArgumentsString(params, "content"))), ); err != nil { return nil, err } return &ToolResponse{ Content: []TextContent{ { Type: "text", Text: "done", }, }, }, nil } func getPath(params map[string]any, userSession *UserSession, name string) string { path := GetArgumentsString(params, name) currDir := "" if path == "" { currDir = userSession.CurrDir } else if strings.HasPrefix(path, "~/") { currDir = "." + strings.TrimPrefix(path, "~") currDir = JoinPath(userSession.HomeDir, currDir) } else if strings.HasPrefix(path, "/") { currDir = path } else { currDir = filepath.Join(userSession.CurrDir, ToString(path, "./")) } return currDir } func isArgEmpty(params map[string]any, name string) bool { if arg := GetArgumentsString(params, name); arg == "" { return true } return false } ================================================ FILE: server/plugin/plg_handler_mcp/index.go ================================================ package plg_handler_mcp import ( "sync" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/middleware" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/config" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/utils" "github.com/gorilla/mux" ) type Server struct { sessions sync.Map } func init() { Hooks.Register.Onload(func() { PluginEnable() }) Hooks.Register.HttpEndpoint(func(r *mux.Router) error { if !PluginEnable() { return nil } srv := Server{} m := []Middleware{WithCORS} r.HandleFunc("/sse", NewMiddlewareChain(srv.sseHandler, m)).Methods("GET", "OPTIONS") r.HandleFunc("/messages", NewMiddlewareChain(srv.messageHandler, m)).Methods("POST", "OPTIONS") r.HandleFunc("/.well-known/oauth-authorization-server", NewMiddlewareChain(srv.WellKnownOAuthAuthorizationServerHandler, m)).Methods("GET", "OPTIONS") r.HandleFunc("/.well-known/oauth-protected-resource", NewMiddlewareChain(srv.WellKnownOAuthProtectedResourceHandler, m)).Methods("GET", "OPTIONS") r.HandleFunc("/.well-known/oauth-protected-resource/sse", NewMiddlewareChain(srv.WellKnownOAuthProtectedResourceHandler, m)).Methods("GET", "OPTIONS") r.HandleFunc("/mcp/token", NewMiddlewareChain(srv.TokenHandler, m)).Methods("POST") m = []Middleware{} r.HandleFunc("/mcp/authorize", NewMiddlewareChain(srv.AuthorizeHandler, m)).Methods("GET") m = []Middleware{BodyParser} r.HandleFunc("/mcp/register", NewMiddlewareChain(srv.RegisterHandler, m)).Methods("POST") m = []Middleware{SessionStart, LoggedInOnly} r.HandleFunc("/api/mcp", NewMiddlewareChain(srv.CallbackHandler, m)).Methods("GET") return nil }) } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_completion.go ================================================ package types type CompletionResponse struct { Completion Completion `json:"completion"` } type Completion struct { Values []string `json:"values"` Total uint64 `json:"total"` HasMore bool `json:"hasMore"` } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_init.go ================================================ package types type InitializeResponse struct { ProtocolVersion string `json:"protocolVersion"` ServerInfo ServerInfo `json:"serverInfo"` Capabilities Capabilities `json:"capabilities"` } type ServerInfo struct { Name string `json:"name"` Version string `json:"version"` } type Capabilities struct { Tools map[string]interface{} `json:"tools",omitempty` Resources map[string]interface{} `json:"resources",omitempty` Prompts map[string]interface{} `json:"prompts",omitempty` } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_notification.go ================================================ package types var ( ErrToolsListChanges = newError("notifications/tools/list_changed") ErrDisconnect = newError("internal/disconnect") ) func newError(s string) error { return notification{s} } type notification struct { msg string } func (this notification) Error() string { return this.msg } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_prompts.go ================================================ package types type PromptsListResponse struct { Prompts []Prompt `json:"prompts"` NextCursor string `json:"nextCursor",omitempty` } type Prompt struct { Name string `json:"name"` Description string `json:"description"` Arguments []PromptArgument `json:"arguments"` } type PromptArgument struct { Name string `json:"name"` Description string `json:"description"` Required bool `json:"required"` } type PromptGetResponse struct { Description string `json:"description"` Messages []PromptMessage `json:"messages"` } type PromptMessage struct { Role string `json:"role"` Content TextContent `json:"content"` } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_resources.go ================================================ package types type ResourcesListResponse struct { Resources []Resource `json:"resources"` } type ResourceTemplatesListResponse struct { ResourceTemplates []ResourceTemplate `json:"resourceTemplates"` } type ResourceReadResponse struct { Contents []ResourceContent `json:"contents"` } type Resource struct { URI string `json:"uri"` Name string `json:"name"` Description string `json:"description"` MimeType string `json:"mimeType"` Content string `json:"-"` Meta Meta `json:"-"` } type ResourceTemplate struct { URITemplate string `json:"uriTemplate"` Name string `json:"name"` Description string `json:"description"` MimeType string `json:"mimeType"` } type ResourceContent struct { URI string `json:"uri"` MimeType string `json:"mimeType"` Text string `json:"text"` Meta Meta `json:"_meta,omitempty"` } ================================================ FILE: server/plugin/plg_handler_mcp/types/mcp_tools.go ================================================ package types import ( "encoding/json" ) type Meta map[string]any type Run func(params map[string]any, userSession *UserSession) (*ToolResponse, error) type Tool struct { Name string `json:"name"` Description string `json:"description"` InputSchema json.RawMessage `json:"inputSchema"` OutputSchema json.RawMessage `json:"outputSchema,omitempty"` Meta Meta `json:"_meta,omitempty"` Run Run `json:"-"` Annotations Meta `json:"annotations,omitempty"` } type ListToolsResponse struct { Tools []Tool `json:"tools"` } type ToolResponse struct { Content []TextContent `json:"content"` StructuredContent map[string]any `json:"structuredContent,omitempty"` IsError bool `json:"isError"` } ================================================ FILE: server/plugin/plg_handler_mcp/types/resources.go ================================================ package types import "encoding/json" type IResource interface { Resource() ([]json.RawMessage, error) } type TextContent struct { Type string `json:"type"` Text string `json:"text"` } type BinaryContent struct { URI string `json:uri"` MimeType string `json:"mimeType"` Blob []byte `json:"blob"` } func (this TextContent) Resource() ([]byte, error) { return json.Marshal(this) } func (this BinaryContent) Resource() ([]byte, error) { return json.Marshal(this) } ================================================ FILE: server/plugin/plg_handler_mcp/types/rpc.go ================================================ package types type JSONRPCRequest struct { JSONRPC string `json:"jsonrpc"` ID uint64 `json:"id"` Method string `json:"method"` Params map[string]any `json:"params,omitempty"` } type JSONRPCResponse struct { JSONRPC string `json:"jsonrpc"` ID uint64 `json:"id"` Result *any `json:"result,omitempty"` Error *JSONRPCError `json:"error,omitempty"` } type JSONRPCMethod struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` Params interface{} `json:"params",omitempty` } type JSONRPCError struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } func (this JSONRPCError) Error() string { return this.Message } ================================================ FILE: server/plugin/plg_handler_mcp/types/session.go ================================================ package types import ( "time" . "github.com/mickael-kerjean/filestash/server/common" ) type UserSession struct { Id string Chan chan JSONRPCRequest HomeDir string CurrDir string Token string Backend IBackend Ping Ping } type Ping struct { ID uint64 LastResponse time.Time } ================================================ FILE: server/plugin/plg_handler_mcp/utils/cors.go ================================================ package utils import ( "net/http" . "github.com/mickael-kerjean/filestash/server/common" ) func WithCORS(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "mcp-protocol-version, Content-Type, Authorization") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } fn(ctx, w, r) }) } ================================================ FILE: server/plugin/plg_handler_mcp/utils/default.go ================================================ package utils import ( "fmt" ) func ToString(val any, def string) string { if val == nil { return def } else if val == "" { return def } return fmt.Sprintf("%v", val) } ================================================ FILE: server/plugin/plg_handler_mcp/utils/json.go ================================================ package utils import ( "encoding/json" ) func JsonSchema(in any) json.RawMessage { b, _ := json.Marshal(in) return json.RawMessage(b) } func JsonText(in any) string { b, _ := json.MarshalIndent(in, "", " ") return string(b) } ================================================ FILE: server/plugin/plg_handler_mcp/utils/mcp.go ================================================ package utils func GetArgumentsString(params map[string]any, name string) string { m, ok := params["arguments"].(map[string]any) if !ok { return "" } p, ok := m[name].(string) if !ok { return "" } return p } func GetArgumentString(params map[string]any, name string) string { m, ok := params["argument"].(map[string]any) if !ok { return "" } p, ok := m[name].(string) if !ok { return "" } return p } ================================================ FILE: server/plugin/plg_handler_mcp/utils/response.go ================================================ package utils import ( "encoding/json" "fmt" "io" "net/http" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp/types" ) func SendMessage(w io.Writer, requestID uint64, response any) { b, err := json.Marshal(JSONRPCResponse{ JSONRPC: "2.0", ID: requestID, Result: &response, }) if err != nil { SendError(w, requestID, JSONRPCError{ Code: http.StatusInternalServerError, Message: err.Error(), }) } fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(b)) w.(http.Flusher).Flush() } func SendPing(w io.Writer, requestID uint64) { b, err := json.Marshal(JSONRPCRequest{ JSONRPC: "2.0", ID: requestID, Method: "ping", }) if err != nil { SendError(w, requestID, JSONRPCError{ Code: http.StatusInternalServerError, Message: err.Error(), }) } fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(b)) w.(http.Flusher).Flush() } func SendMethod(w io.Writer, requestID uint64, method string, args ...map[string]any) { var params map[string]any if len(args) == 1 { params = args[0] } b, err := json.Marshal(JSONRPCMethod{ JSONRPC: "2.0", Method: method, Params: params, }) if err != nil { SendError(w, requestID, JSONRPCError{ Code: http.StatusInternalServerError, Message: err.Error(), }) } fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(b)) w.(http.Flusher).Flush() } func SendError(w io.Writer, requestID uint64, d error) { var rpcErr JSONRPCError switch v := d.(type) { case JSONRPCError: rpcErr = v case AppError: rpcErr = JSONRPCError{ Code: v.Status(), Message: v.Error(), } default: rpcErr = JSONRPCError{ Code: http.StatusInternalServerError, Message: d.Error(), } } b, err := json.Marshal(JSONRPCResponse{ JSONRPC: "2.0", ID: requestID, Error: &rpcErr, }) if err != nil { fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(`nil`)) } else { fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(b)) } w.(http.Flusher).Flush() } ================================================ FILE: server/plugin/plg_handler_site/config.go ================================================ package plg_handler_site import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() PluginParamAutoindex() PluginParamCORSAllowOrigins() }) } var PluginEnable = func() bool { return Config.Get("features.site.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{"site_autoindex", "site_cors_allow_origins"} f.Description = "Enable/Disable the creation of site via shared links. Sites will be made available under /public/{shareID}/" f.Default = false return f }).Bool() } var PluginParamAutoindex = func() bool { return Config.Get("features.site.autoindex").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "site_autoindex" f.Name = "autoindex" f.Type = "boolean" f.Description = "Enables or disables automatic directory listing when no index file is present." f.Default = false return f }).Bool() } var PluginParamCORSAllowOrigins = func() string { return Config.Get("features.site.cors_allow_origins").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "site_cors_allow_origins" f.Name = "cors_allow_origins" f.Type = "text" f.Placeholder = "* or https://example.com, https://app.example.com" f.Description = "List of allowed origins for CORS. Use '*' to allow all origins, or provide a comma-separated list." return f }).String() } ================================================ FILE: server/plugin/plg_handler_site/index.go ================================================ package plg_handler_site import ( "io" "net/http" "os" "strings" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/ctrl" . "github.com/mickael-kerjean/filestash/server/middleware" "github.com/mickael-kerjean/filestash/server/model" "github.com/gorilla/mux" ) func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { if PluginEnable() == false { return nil } r.PathPrefix("/public/{share}/").HandlerFunc(NewMiddlewareChain( SiteHandler, []Middleware{SessionStart, SecureHeaders, cors}, )).Methods("GET", "HEAD") r.HandleFunc("/public/", NewMiddlewareChain( SharesListHandler, []Middleware{SecureHeaders, basicAdmin}, )).Methods("GET") return nil }) } func SiteHandler(app *App, w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } else if app.Backend == nil { SendErrorResult(w, ErrNotFound) return } else if model.CanRead(app) == false { SendErrorResult(w, ErrPermissionDenied) return } path := strings.TrimPrefix(r.URL.Path, "/public/"+app.Share.Id) if strings.HasSuffix(path, "/") { path += "index.html" } path, err := ctrl.PathBuilder(app, path) if err != nil { SendErrorResult(w, err) return } for _, auth := range Hooks.Get.AuthorisationMiddleware() { if err = auth.Cat(app, path); err != nil { SendErrorResult(w, ErrNotAuthorized) return } } if f, err := app.Backend.Cat(path); err == nil { w.Header().Set("Content-Type", GetMimeType(path)) io.Copy(w, f) f.Close() return } else if err == ErrNotFound && PluginParamAutoindex() { if files, err := app.Backend.Ls(strings.TrimSuffix(path, "index.html")); err == nil { if strings.HasSuffix(r.URL.Path, "/") == false { http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := TmplAutoindex.Execute(w, map[string]any{ "Base": r.URL.Path, "Files": files, }); err != nil { SendErrorResult(w, err) } return } } SendErrorResult(w, ErrNotFound) } func SharesListHandler(app *App, w http.ResponseWriter, r *http.Request) { shares, err := model.ShareAll() if err != nil { SendErrorResult(w, err) return } files := make([]os.FileInfo, len(shares)) for i, share := range shares { t := int64(-1) if share.Expire != nil { t = *share.Expire } files[i] = File{ FName: share.Id, FType: "directory", FTime: t, } } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := TmplAutoindex.Execute(w, map[string]any{ "Base": r.URL.Path, "Files": files, }); err != nil { SendErrorResult(w, err) } } ================================================ FILE: server/plugin/plg_handler_site/middleware.go ================================================ package plg_handler_site import ( "net/http" "strings" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" ) func cors(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, w http.ResponseWriter, r *http.Request) { if allowed := PluginParamCORSAllowOrigins(); allowed != "" { w.Header().Add("Vary", "Origin") w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS") switch allowed { case "*": w.Header().Set("Access-Control-Allow-Origin", "*") default: origin := r.Header.Get("Origin") for _, o := range strings.Split(allowed, ",") { if strings.TrimSpace(o) == origin { w.Header().Set("Access-Control-Allow-Origin", origin) break } } } } fn(ctx, w, r) }) } func basicAdmin(fn HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "admin" { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } else if err := bcrypt.CompareHashAndPassword([]byte(Config.Get("auth.admin").String()), []byte(pass)); err != nil { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } fn(ctx, w, r) }) } ================================================ FILE: server/plugin/plg_handler_site/template.go ================================================ package plg_handler_site import "html/template" var TmplAutoindex = template.Must(template.New("autoindex").Parse(` Index of {{ .Base }}

    Index of {{ .Base }}

    ../
    {{- range .Files -}} {{- if .IsDir -}} {{ printf "%-40.40s" (printf "%s/" .Name) }} {{- else -}} {{ printf "%-40.40s" .Name }} {{- end -}} {{ (.ModTime).Format "2006-01-02 15:04:05" }} {{ printf "%8d" .Size }}
    {{- end }}
    `)) ================================================ FILE: server/plugin/plg_handler_syncthing/index.go ================================================ /* * This plugin expose syncthing to the admin user */ package plg_handler_syncthing import ( "encoding/base64" "fmt" "github.com/gorilla/mux" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/crypto/bcrypt" "net/http" "net/http/httputil" "net/url" "os" "strings" "time" ) const SYNCTHING_URI = "/admin/syncthing" var ( plugin_enable func() bool server_url func() string ) func init() { plugin_enable = func() bool { return Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{"syncthing_server_url"} f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`" f.Default = false if u := os.Getenv("SYNCTHING_URL"); u != "" { f.Default = true } return f }).Bool() } server_url = func() string { return Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "syncthing_server_url" f.Name = "server_url" f.Type = "text" f.Description = "Location of your Syncthing server" f.Default = "" f.Placeholder = "Eg: http://127.0.0.1:8080" if u := os.Getenv("SYNCTHING_URL"); u != "" { f.Default = u f.Placeholder = fmt.Sprintf("Default: '%s'", u) } return f }).String() } Hooks.Register.Onload(func() { plugin_enable() server_url() }) Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { if plugin_enable() == false { return nil } r.HandleFunc(SYNCTHING_URI, func(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, SYNCTHING_URI+"/", http.StatusTemporaryRedirect) }) r.Handle(SYNCTHING_URI+"/", AuthBasic( func() (string, string) { return "admin", Config.Get("auth.admin").String() }, http.HandlerFunc(SyncthingProxyHandler), )) r.PathPrefix(SYNCTHING_URI + "/").HandlerFunc(SyncthingProxyHandler) return nil }) } func AuthBasic(credentials func() (string, string), fn http.Handler) http.HandlerFunc { var notAuthorised = func(res http.ResponseWriter, req *http.Request) { time.Sleep(1 * time.Second) res.Header().Set("WWW-Authenticate", `Basic realm="User protect", charset="UTF-8"`) res.WriteHeader(http.StatusUnauthorized) res.Write([]byte("Not Authorised")) return } return func(res http.ResponseWriter, req *http.Request) { auth := req.Header.Get("Authorization") if strings.HasPrefix(auth, "Basic ") == false { notAuthorised(res, req) return } auth = strings.TrimPrefix(auth, "Basic ") decoded, err := base64.StdEncoding.DecodeString(auth) if err != nil { notAuthorised(res, req) return } auth = string(decoded) stuffs := strings.Split(auth, ":") if len(stuffs) < 2 { notAuthorised(res, req) return } username := stuffs[0] password := strings.Join(stuffs[1:], ":") refUsername, refPassword := credentials() if refUsername != username { notAuthorised(res, req) return } else if err = bcrypt.CompareHashAndPassword([]byte(refPassword), []byte(password)); err != nil { notAuthorised(res, req) return } fn.ServeHTTP(res, req) return } } func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) { req.URL.Path = strings.TrimPrefix(req.URL.Path, SYNCTHING_URI) req.Header.Set("X-Forwarded-Host", req.Host+SYNCTHING_URI) req.Header.Set("X-Forwarded-Proto", func() string { if scheme := req.Header.Get("X-Forwarded-Proto"); scheme != "" { return scheme } else if req.TLS != nil { return "https" } return "http" }()) u, err := url.Parse(server_url()) if err != nil { SendErrorResult(res, err) return } reverseProxy := &httputil.ReverseProxy{ Director: func(rq *http.Request) { rq.URL.Scheme = "http" rq.URL.Host = u.Host rq.URL.Path = func(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b }(u.Path, rq.URL.Path) if u.RawQuery == "" || rq.URL.RawQuery == "" { rq.URL.RawQuery = u.RawQuery + rq.URL.RawQuery } else { rq.URL.RawQuery = u.RawQuery + "&" + rq.URL.RawQuery } }, } reverseProxy.ErrorHandler = func(rw http.ResponseWriter, rq *http.Request, err error) { Log.Warning("[syncthing] %s", err.Error()) SendErrorResult(rw, NewError(err.Error(), http.StatusBadGateway)) } reverseProxy.ServeHTTP(res, req) } ================================================ FILE: server/plugin/plg_image_ascii/index.go ================================================ package plg_image_ascii import ( . "github.com/mickael-kerjean/filestash/server/common" "github.com/qeesung/image2ascii/convert" "image" "io" "net/http" "strings" ) func init() { Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, bool, error) { value, isIn := req.URL.Query()["ascii"] if isIn == false { return reader, false, nil } else if strings.Join(value, "") == "false" { return reader, false, nil } img, _, err := image.Decode(reader) reader.Close() if err != nil { return NewReadCloserFromBytes([]byte("")), true, err } opt := convert.DefaultOptions opt.FixedWidth = 80 opt.FixedHeight = 40 out := convert.NewImageConverter().Image2ASCIIString(img, &opt) (*res).Header().Set("Content-Type", "application/octet-stream") return NewReadCloserFromBytes([]byte(out)), true, nil }) } ================================================ FILE: server/plugin/plg_image_bimg/index.go ================================================ package plg_image_golang import ( "github.com/h2non/bimg" . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" ) const THUMB_SIZE int = 150 func init() { Hooks.Register.Thumbnailer("image/jpeg", thumbnailer{}) Hooks.Register.Thumbnailer("image/png", thumbnailer{}) Hooks.Register.Thumbnailer("image/gif", thumbnailer{}) } type thumbnailer struct{} func (this thumbnailer) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { query := req.URL.Query() mType := GetMimeType(query.Get("path")) if query.Get("thumbnail") != "true" { return reader, nil } else if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" { return reader, nil } b, err := io.ReadAll(reader) if err != nil { return reader, err } newImage, err := bimg.NewImage(b).Thumbnail(THUMB_SIZE) if err != nil { return reader, err } return NewReadCloserFromBytes(newImage), err } ================================================ FILE: server/plugin/plg_image_c/image_gif.c ================================================ #include #include #include #include #include #include #include "utils.h" #include "image_gif_vendor.h" int DGifSlurp2(GifFileType *GifFile); int DGifCloseFile2(GifFileType *GifFile, int *ErrorCode); int gif_to_webp(int inputDesc, int outputDesc, int targetSize) { #ifdef HAS_DEBUG clock_t t; t = clock(); #endif int status = 0; int error; if (targetSize < 0) targetSize = -targetSize; // STEP1: setup gif GifFileType* gif; uint8_t* gif_rgba; uint8_t* scaled_rgba; if((gif = DGifOpenFileHandle(inputDesc, &error)) == NULL) { status = 1; goto CLEANUP_AND_ABORT; } DEBUG("after gif opened"); if (DGifSlurp2(gif) != GIF_OK) { status = 1; goto CLEANUP_AND_ABORT_A; } int width = gif->SWidth; int height = gif->SHeight; int scale_factor = (width > targetSize) ? width / targetSize : 1; int thumb_width = width / scale_factor; int thumb_height = height / scale_factor; DEBUG("after gif ready"); // STEP2: convert frame to RGBA if (gif->ImageCount == 0) { status = 1; goto CLEANUP_AND_ABORT_A; } else if (!(gif_rgba = (uint8_t*)malloc(width * height * 4))) { status = 1; goto CLEANUP_AND_ABORT_A; } GifColorType* colorMapEntry; ColorMapObject* colorMap = (gif->Image.ColorMap) ? gif->Image.ColorMap : gif->SColorMap; SavedImage* firstFrame = &gif->SavedImages[0]; GifByteType* gifBytes = firstFrame->RasterBits; for (int i = 0; i < gif->SWidth * gif->SHeight; ++i) { colorMapEntry = &colorMap->Colors[gifBytes[i]]; gif_rgba[i * 4 + 0] = colorMapEntry->Red; gif_rgba[i * 4 + 1] = colorMapEntry->Green; gif_rgba[i * 4 + 2] = colorMapEntry->Blue; gif_rgba[i * 4 + 3] = 0xFF; } DEBUG("after gif rgba convert"); // STEP3: scale the image if (!(scaled_rgba = (uint8_t*)malloc(thumb_width * thumb_height * 4))) { free(gif_rgba); status = 1; goto CLEANUP_AND_ABORT_A; } int x, y, srcIndex, destIndex; for (int i = 0; i < thumb_height; ++i) { for (int j = 0; j < thumb_width; ++j) { x = j * width / thumb_width; y = i * height / thumb_height; srcIndex = (y * width + x) * 4; destIndex = (i * thumb_width + j) * 4; memcpy(&scaled_rgba[destIndex], &gif_rgba[srcIndex], 4); } } free(gif_rgba); DEBUG("after image scaled"); // STEP4: write image as webp uint8_t* webp_output_data; size_t webp_output_size = WebPEncodeRGBA(scaled_rgba, thumb_width, thumb_height, thumb_width * 4, 75, &webp_output_data); free(scaled_rgba); if (webp_output_size == 0) { status = 1; goto CLEANUP_AND_ABORT_A; } if (write(outputDesc, webp_output_data, webp_output_size) != webp_output_size) { status = 1; ERROR("unexpected number of bytes written"); } WebPFree(webp_output_data); DEBUG("after webp written"); CLEANUP_AND_ABORT_A: DGifCloseFile2(gif, &error); CLEANUP_AND_ABORT: return status; } // adapted from https://android.googlesource.com/platform/external/giflib/+/dc07290edccc2c3fc4062da835306f809cea1fdc/dgif_lib.c // we got rid of unecessary stuff for our use case and reduce the processing // to the first frame only which isn't possible using stock libgif functions int DGifSlurp2(GifFileType *GifFile) { clock_t t = clock(); size_t ImageSize; GifRecordType RecordType; SavedImage *sp; GifByteType *ExtData; int ExtFunction; GifFile->ExtensionBlocks = NULL; GifFile->ExtensionBlockCount = 0; do { if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { return GIF_ERROR; } if (RecordType == IMAGE_DESC_RECORD_TYPE) { if (DGifGetImageDesc(GifFile) == GIF_ERROR) { return GIF_ERROR; } sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 && sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height)) { return GIF_ERROR; } ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) { return GIF_ERROR; } sp->RasterBits = (unsigned char *)reallocarray(NULL, ImageSize, sizeof(GifPixelType)); if (sp->RasterBits == NULL) { return GIF_ERROR; } if (DGifGetLine(GifFile, sp->RasterBits, ImageSize) == GIF_ERROR) { return GIF_ERROR; } return GIF_OK; } else if (RecordType == EXTENSION_RECORD_TYPE) { if (DGifGetExtension(GifFile, &ExtFunction, &ExtData) == GIF_ERROR) { return GIF_ERROR; } while (ExtData != NULL) { if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR) { return GIF_ERROR; } } } } while (RecordType != TERMINATE_RECORD_TYPE); return GIF_OK; } // adapted from: https://android.googlesource.com/platform/external/giflib/+/dc07290edccc2c3fc4062da835306f809cea1fdc/dgif_lib.c#626 // as we don't want libgif to manage the lifecycle of the file descriptor, in our case // this is the responsibility of the downstream program, that's why we've recopied it here // a commented the fclose call int DGifCloseFile2(GifFileType *GifFile, int *ErrorCode) { GifFilePrivateType *Private; if (GifFile == NULL || GifFile->Private == NULL) { return GIF_ERROR; } if (GifFile->Image.ColorMap) { GifFreeMapObject(GifFile->Image.ColorMap); GifFile->Image.ColorMap = NULL; } if (GifFile->SColorMap) { GifFreeMapObject(GifFile->SColorMap); GifFile->SColorMap = NULL; } if (GifFile->SavedImages) { GifFreeSavedImages(GifFile); GifFile->SavedImages = NULL; } GifFreeExtensions(&GifFile->ExtensionBlockCount, &GifFile->ExtensionBlocks); Private = (GifFilePrivateType *) GifFile->Private; if (!IS_READABLE(Private)) { if (ErrorCode != NULL) { *ErrorCode = D_GIF_ERR_NOT_READABLE; } free((char *)GifFile->Private); free(GifFile); return GIF_ERROR; } if (Private->File /*&& (fclose(Private->File) != 0)*/) { if (ErrorCode != NULL) { *ErrorCode = D_GIF_ERR_CLOSE_FAILED; } free((char *)GifFile->Private); free(GifFile); return GIF_ERROR; } free((char *)GifFile->Private); free(GifFile); if (ErrorCode != NULL) { *ErrorCode = D_GIF_SUCCEEDED; } return GIF_OK; } ================================================ FILE: server/plugin/plg_image_c/image_gif.go ================================================ package plg_image_c // #include "image_gif.h" // #cgo LDFLAGS: -l:libgif.a -l:libwebp.a import "C" func gif(input uintptr, output uintptr, size int) { C.gif_to_webp(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_gif.h ================================================ #include #include int gif_to_webp(int inputDesc, int outputDesc, int targetSize); ================================================ FILE: server/plugin/plg_image_c/image_gif_vendor.h ================================================ #define FILE_STATE_READ 0x08 #define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) #define INT_MAX 2147483647 typedef struct GifFilePrivateType { GifWord FileState; FILE *File; } GifFilePrivateType; ================================================ FILE: server/plugin/plg_image_c/image_jpeg.c ================================================ #include #include #include #include #include "utils.h" #define JPEG_QUALITY 50 typedef struct filestash_jpeg_error_mgr { struct jpeg_error_mgr pub; jmp_buf jmp; } *filestash_jpeg_error_ptr; void filestash_jpeg_error_exit (j_common_ptr cinfo); int jpeg_to_jpeg(int inputDesc, int outputDesc, int targetSize) { #ifdef HAS_DEBUG clock_t t; t = clock(); #endif int status = 0; FILE* input = fdopen(inputDesc, "rb"); FILE* output = fdopen(outputDesc, "wb"); if (!input || !output) { return 1; } struct jpeg_decompress_struct jpeg_config_input; struct jpeg_compress_struct jpeg_config_output; struct filestash_jpeg_error_mgr jerr; jpeg_config_input.err = jpeg_std_error(&jerr.pub); jpeg_config_output.err = jpeg_std_error(&jerr.pub); jpeg_config_input.dct_method = JDCT_IFAST; jpeg_config_input.do_fancy_upsampling = FALSE; jpeg_config_input.two_pass_quantize = FALSE; jpeg_config_input.dither_mode = JDITHER_ORDERED; jpeg_create_decompress(&jpeg_config_input); jpeg_create_compress(&jpeg_config_output); jpeg_stdio_src(&jpeg_config_input, input); jpeg_stdio_dest(&jpeg_config_output, output); jerr.pub.error_exit = filestash_jpeg_error_exit; if (setjmp(jerr.jmp)) { ERROR("exception"); goto CLEANUP_AND_ABORT; } DEBUG("after constructor decompress"); if(jpeg_read_header(&jpeg_config_input, TRUE) != JPEG_HEADER_OK) { status = 1; ERROR("not a jpeg"); goto CLEANUP_AND_ABORT; } DEBUG("after header read"); jpeg_config_input.dct_method = JDCT_IFAST; jpeg_config_input.do_fancy_upsampling = FALSE; jpeg_config_input.two_pass_quantize = FALSE; jpeg_config_input.dither_mode = JDITHER_ORDERED; jpeg_calc_output_dimensions(&jpeg_config_input); int image_min_size = min(jpeg_config_input.output_width, jpeg_config_input.output_height); jpeg_config_input.scale_num = 1; jpeg_config_input.scale_denom = 1; int targetSizeAbs = abs(targetSize); if (image_min_size / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 1; jpeg_config_input.scale_denom = 8; } else if (image_min_size * 2 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 1; jpeg_config_input.scale_denom = 4; } else if (image_min_size * 3 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 3; jpeg_config_input.scale_denom = 8; } else if (image_min_size * 4 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 4; jpeg_config_input.scale_denom = 8; } else if (image_min_size * 5 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 5; jpeg_config_input.scale_denom = 8; } else if (image_min_size * 6 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 6; jpeg_config_input.scale_denom = 8; } else if (image_min_size * 7 / 8 >= targetSizeAbs) { jpeg_config_input.scale_num = 7; jpeg_config_input.scale_denom = 8; } DEBUG("start decompress"); if(jpeg_start_decompress(&jpeg_config_input) == FALSE) { ERROR("jpeg_start_decompress"); status = 1; goto CLEANUP_AND_ABORT; } DEBUG("processing image setup"); int jpeg_row_stride = jpeg_config_input.output_width * jpeg_config_input.output_components; jpeg_config_output.image_width = jpeg_config_input.output_width; jpeg_config_output.image_height = jpeg_config_input.output_height; jpeg_config_output.input_components = jpeg_config_input.num_components; jpeg_config_output.in_color_space = jpeg_config_input.out_color_space; jpeg_set_defaults(&jpeg_config_output); jpeg_set_quality(&jpeg_config_output, JPEG_QUALITY, TRUE); jpeg_start_compress(&jpeg_config_output, TRUE); JSAMPARRAY buffer = jpeg_config_input.mem->alloc_sarray((j_common_ptr) &jpeg_config_input, JPOOL_IMAGE, jpeg_row_stride, 1); DEBUG("processing image"); while (jpeg_config_output.next_scanline < jpeg_config_output.image_height) { jpeg_read_scanlines(&jpeg_config_input, buffer, 1); jpeg_write_scanlines(&jpeg_config_output, buffer, 1); } DEBUG("end decompress"); jpeg_finish_decompress(&jpeg_config_input); DEBUG("finish decompress"); jpeg_finish_compress(&jpeg_config_output); CLEANUP_AND_ABORT: jpeg_destroy_decompress(&jpeg_config_input); jpeg_destroy_compress(&jpeg_config_output); DEBUG("final"); return status; } void filestash_jpeg_error_exit (j_common_ptr cinfo) { filestash_jpeg_error_ptr filestash_err = (filestash_jpeg_error_ptr) cinfo->err; longjmp(filestash_err->jmp, 1); } ================================================ FILE: server/plugin/plg_image_c/image_jpeg.h ================================================ int jpeg_to_jpeg(int input, int output, int targetSize); ================================================ FILE: server/plugin/plg_image_c/image_jpeg_freebsd.go ================================================ package plg_image_c // #include "image_jpeg.h" // #cgo LDFLAGS: -L /usr/local/lib -L /usr/lib -L /lib -l:libjpeg.a // #cgo CFLAGS: -I /usr/local/include import "C" func jpeg(input uintptr, output uintptr, size int) { C.jpeg_to_jpeg(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_jpeg_linux.go ================================================ package plg_image_c // #include "image_jpeg.h" // #cgo LDFLAGS: -l:libjpeg.a import "C" func jpeg(input uintptr, output uintptr, size int) { C.jpeg_to_jpeg(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_png.c ================================================ #include #include #include #include #include #include "utils.h" void png_read_error(png_structp png_ptr, png_const_charp error_msg) { longjmp(png_jmpbuf(png_ptr), 1); } void png_read_warning(png_structp png_ptr, png_const_charp warning_msg) { longjmp(png_jmpbuf(png_ptr), 1); } int png_to_webp(int inputDesc, int outputDesc, int targetSize) { #ifdef HAS_DEBUG clock_t t; t = clock(); #endif if (targetSize < 0 ) { targetSize = -targetSize; } int status = 0; FILE* input = fdopen(inputDesc, "rb"); FILE* output = fdopen(outputDesc, "wb"); if (!input || !output) { return 1; } // STEP1: setup png png_structp png_ptr = NULL; png_infop info_ptr = NULL; if(!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, png_read_error, png_read_warning))) { status = 1; goto CLEANUP_AND_ABORT; } if (!(info_ptr = png_create_info_struct(png_ptr))) { status = 1; goto CLEANUP_AND_ABORT_A; } if (setjmp(png_jmpbuf(png_ptr))) { status = 1; goto CLEANUP_AND_ABORT_B; } png_init_io(png_ptr, input); png_read_info(png_ptr, info_ptr); png_set_strip_alpha(png_ptr); png_uint_32 width = png_get_image_width(png_ptr, info_ptr); png_uint_32 height = png_get_image_height(png_ptr, info_ptr); png_byte color_type = png_get_color_type(png_ptr, info_ptr); png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY) { png_set_expand_gray_1_2_4_to_8(png_ptr); } if (color_type & PNG_COLOR_MASK_ALPHA) { png_set_strip_alpha(png_ptr); } png_read_update_info(png_ptr, info_ptr); DEBUG("after png construct"); // STEP2: process the image int scale_factor = height > targetSize ? height / targetSize : 1; png_uint_32 thumb_width = width / scale_factor; png_uint_32 thumb_height = height / scale_factor; if (thumb_width == 0 || thumb_height == 0) { ERROR("0 dimensions"); status = 1; goto CLEANUP_AND_ABORT_B; } uint8_t* webp_image_data = (uint8_t*)malloc(thumb_width * thumb_height * 3); if (!webp_image_data) { ERROR("malloc error"); status = 1; goto CLEANUP_AND_ABORT_B; } png_bytep row = (png_bytep)malloc(png_get_rowbytes(png_ptr, info_ptr)); if (!row) { ERROR("malloc error"); status = 1; goto CLEANUP_AND_ABORT_B; } DEBUG("after png malloc"); for (png_uint_32 y = 0; y < height; y++) { png_read_row(png_ptr, row, NULL); if (y % scale_factor == 0 && (y / scale_factor < thumb_height)) { for (png_uint_32 x = 0; x < width; x += scale_factor) { if (x / scale_factor < thumb_width) { png_uint_32 thumb_x = x / scale_factor; png_uint_32 thumb_y = y / scale_factor; memcpy(webp_image_data + (thumb_y * thumb_width + thumb_x) * 3, row + x * 3, 3); } } } } DEBUG("after png process"); free(row); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); DEBUG("after png cleanup"); // STEP3: save as webp uint8_t* webp_output_data = NULL; size_t webp_output_size = WebPEncodeRGB(webp_image_data, thumb_width, thumb_height, thumb_width * 3, 75, &webp_output_data); free(webp_image_data); DEBUG("after webp init"); if (webp_output_data == NULL) { status = 1; goto CLEANUP_AND_ABORT_B; } else if (webp_output_size == 0) { status = 1; goto CLEANUP_AND_ABORT_C; } fwrite(webp_output_data, webp_output_size, 1, output); fflush(output); DEBUG("after webp written"); CLEANUP_AND_ABORT_C: if (webp_output_data != NULL) WebPFree(webp_output_data); CLEANUP_AND_ABORT_B: if (info_ptr != NULL) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); CLEANUP_AND_ABORT_A: if (png_ptr != NULL) png_destroy_read_struct(&png_ptr, (info_ptr != NULL) ? &info_ptr : NULL, NULL); CLEANUP_AND_ABORT: return status; } ================================================ FILE: server/plugin/plg_image_c/image_png.h ================================================ #include #include int png_to_png(int input, int output, int targetSize); int png_to_webp(int input, int output, int targetSize); ================================================ FILE: server/plugin/plg_image_c/image_png_freebsd.go ================================================ package plg_image_c // #include "image_png.h" // #cgo LDFLAGS: -L /usr/local/lib -L /usr/lib -L /lib -l:libsharpyuv.a -l:libpng.a -l:libz.a -l:libwebp.a -l:libpthread.a -fopenmp // #cgo CFLAGS: -I /usr/local/include import "C" func png(input uintptr, output uintptr, size int) { C.png_to_webp(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_png_linux.go ================================================ package plg_image_c // #include "image_png.h" // #cgo LDFLAGS: -l:libpng.a -l:libz.a -l:libwebp.a -fopenmp import "C" func png(input uintptr, output uintptr, size int) { C.png_to_webp(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_psd.c ================================================ #define STB_IMAGE_IMPLEMENTATION #include "image_psd_vendor.h" #include #include #include #include "utils.h" #define BUF_SIZE 1024 * 16 #define WEBP_QUALITY 50 int psd_to_webp(int inputDesc, int outputDesc, int targetSize) { #ifdef HAS_DEBUG clock_t t; t = clock(); #endif FILE* input = fdopen(inputDesc, "rb"); FILE* output = fdopen(outputDesc, "wb"); int status = 0; if (!input || !output) { return 1; } // STEP1: write input to a file as stb doesn't work out well with our descriptor char fname_in[32] = "/tmp/filestash.XXXXXX"; int _mkstemp_in = mkstemp(fname_in); if (!_mkstemp_in) { ERROR("mkstemp_in"); return 1; } FILE* f_in = fdopen(_mkstemp_in, "wb"); if (!f_in) { ERROR("fdopen"); return 1; } char content[BUF_SIZE]; int read; while ((read = fread(content, sizeof(char), BUF_SIZE, input))) { fwrite(content, read, sizeof(char), f_in); } fclose(f_in); DEBUG("setup"); // STEP2: decode psd DEBUG("init"); int width, height, channels; unsigned char* imageData = stbi_load(fname_in, &width, &height, &channels, 0); if (!imageData) { ERROR("cannot_load"); status = 1; goto CLEANUP_AND_ABORT; } DEBUG("decoded"); size_t webp_output_size; uint8_t* webp_output_data = NULL; int success = 0; if (channels == 3) { success = WebPEncodeRGB(imageData, width, height, width * channels, WEBP_QUALITY, &webp_output_data); } else if (channels == 4) { success = WebPEncodeRGBA(imageData, width, height, width * channels, WEBP_QUALITY, &webp_output_data); } DEBUG("encoded"); if (!success) { stbi_image_free(imageData); status = 1; goto CLEANUP_AND_ABORT; } fwrite(webp_output_data, webp_output_size, 1, output); fprintf(stderr, "WRITEN[%d]", webp_output_size); fflush(output); WebPFree(webp_output_data); stbi_image_free(imageData); DEBUG("done"); CLEANUP_AND_ABORT: remove(fname_in); return 0; } ================================================ FILE: server/plugin/plg_image_c/image_psd.go ================================================ //go:generate go run image_psd_generator.go package plg_image_c // #include "image_psd.h" // #cgo LDFLAGS: -l:libwebp.a import "C" func psd(input uintptr, output uintptr, size int) { C.psd_to_webp(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_psd.h ================================================ #include #include int psd_to_webp(int input, int output, int targetSize); ================================================ FILE: server/plugin/plg_image_c/image_psd_generator.go ================================================ //go:build ignore package main import ( "io" "log" "net/http" "os" ) // stb_image v2.28 - https://github.com/nothings/stb const stbImageURL = "https://raw.githubusercontent.com/nothings/stb/5736b15f7ea0ffb08dd38af21067c314d6a3aae9/stb_image.h" func main() { resp, err := http.Get(stbImageURL) if err != nil { log.Fatalf("fetch stb_image.h: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Fatalf("fetch stb_image.h: status %d", resp.StatusCode) } f, err := os.Create("image_psd_vendor.h") if err != nil { log.Fatalf("create image_psd_vendor.h: %v", err) } defer f.Close() if _, err = io.Copy(f, resp.Body); err != nil { log.Fatalf("write image_psd_vendor.h: %v", err) } log.Println("wrote image_psd_vendor.h") } ================================================ FILE: server/plugin/plg_image_c/image_raw.c ================================================ #include #include #include #include #include #include #include #include static bool write_preview(const uint8_t *buf, size_t len, int output); static bool write_thumbnail(const uint8_t *buf, size_t len, int output, int targetSize); void raw_to_jpeg(int inputDesc, int outputDesc, int targetSize) { FILE *in = fdopen(inputDesc, "rb"); if (!in) { perror("fdopen"); return; } uint8_t chunk[4096]; uint8_t last = 0, curr; bool dumping = false; uint8_t *cur_buf = NULL; size_t cur_len = 0, cur_cap = 0; while (!feof(in)) { size_t n = fread(chunk, 1, sizeof(chunk), in); if (n == 0) break; for (size_t i = 0; i < n; i++) { curr = chunk[i]; if (dumping == true && cur_len + 1 > cur_cap) { cur_cap = (cur_cap == 0 ? 4096 : cur_cap * 2); cur_buf = realloc(cur_buf, cur_cap); if (!cur_buf) { free(cur_buf); return; } } // start of jpeg if (dumping == false && last == 0xFF && curr == 0xD8) { dumping = true; cur_cap = 4096; cur_len = 0; cur_buf = malloc(cur_cap); if (!cur_buf) return; cur_buf[cur_len++] = 0xFF; cur_buf[cur_len++] = 0xD8; } // end of jpeg else if (dumping == true && last == 0xFF && curr == 0xD9) { cur_buf[cur_len++] = curr; if (targetSize > 0 && write_preview(cur_buf, cur_len, outputDesc)) { free(cur_buf); return; } else if (targetSize <= 0 && write_thumbnail(cur_buf, cur_len, outputDesc, -targetSize)) { free(cur_buf); return; } cur_buf = NULL; cur_len = cur_cap = 0; dumping = false; } // body of jpeg else if (dumping == true) { cur_buf[cur_len++] = curr; } last = curr; } } free(cur_buf); } typedef struct filestash_raw_error_mgr { struct jpeg_error_mgr pub; jmp_buf jmp; } *filestash_raw_error_ptr; static void filestash_raw_error_exit (j_common_ptr cinfo) { filestash_raw_error_ptr filestash_err = (filestash_raw_error_ptr) cinfo->err; longjmp(filestash_err->jmp, 1); } static bool write_preview(const uint8_t *buf, size_t len, int output) { struct jpeg_decompress_struct cinfo; struct filestash_raw_error_mgr jerr; bool ok = true; jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, buf, len); cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = filestash_raw_error_exit; if (setjmp(jerr.jmp)) { jpeg_destroy_decompress(&cinfo); return false; } if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) ok = false; else if (cinfo.image_width < 700) ok = false; jpeg_destroy_decompress(&cinfo); if (ok == true && write(output, buf, len) != (ssize_t)len) { perror("write"); } return ok; } static bool write_thumbnail(const uint8_t *buf, size_t len, int output, int targetSize) { struct jpeg_decompress_struct dinfo; struct filestash_raw_error_mgr jerr; bool ok = true; jpeg_create_decompress(&dinfo); jpeg_mem_src(&dinfo, buf, len); dinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = filestash_raw_error_exit; if (setjmp(jerr.jmp)) { jpeg_destroy_decompress(&dinfo); return false; } if (jpeg_read_header(&dinfo, TRUE) != JPEG_HEADER_OK || dinfo.image_width < 500) { jpeg_destroy_decompress(&dinfo); return false; } if (dinfo.image_width / 8 >= targetSize) { dinfo.scale_num = 1; dinfo.scale_denom = 8; } else if (dinfo.image_width * 2 / 8 >= targetSize) { dinfo.scale_num = 1; dinfo.scale_denom = 4; } else if (dinfo.image_width * 3 / 8 >= targetSize) { dinfo.scale_num = 3; dinfo.scale_denom = 8; } else if (dinfo.image_width * 4 / 8 >= targetSize) { dinfo.scale_num = 4; dinfo.scale_denom = 8; } else if (dinfo.image_width * 5 / 8 >= targetSize) { dinfo.scale_num = 5; dinfo.scale_denom = 8; } else if (dinfo.image_width * 6 / 8 >= targetSize) { dinfo.scale_num = 6; dinfo.scale_denom = 8; } else if (dinfo.image_width * 7 / 8 >= targetSize) { dinfo.scale_num = 7; dinfo.scale_denom = 8; } jpeg_start_decompress(&dinfo); size_t stride = dinfo.output_width * dinfo.output_components; JSAMPARRAY rowbuf = dinfo.mem->alloc_sarray((j_common_ptr)&dinfo, JPOOL_IMAGE, stride, 1); struct jpeg_compress_struct cinfo; struct jpeg_error_mgr cerr; cinfo.err = jpeg_std_error(&cerr); jpeg_create_compress(&cinfo); unsigned char *outbuf = NULL; unsigned long outlen = 0; jpeg_mem_dest(&cinfo, &outbuf, &outlen); cinfo.image_width = dinfo.output_width; cinfo.image_height = dinfo.output_height; cinfo.input_components = dinfo.output_components; cinfo.in_color_space = dinfo.out_color_space; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, 70, TRUE); jpeg_start_compress(&cinfo, TRUE); while (cinfo.next_scanline < cinfo.image_height) { jpeg_read_scanlines(&dinfo, rowbuf, 1); jpeg_write_scanlines(&cinfo, rowbuf, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); jpeg_finish_decompress(&dinfo); jpeg_destroy_decompress(&dinfo); if (write(output, outbuf, outlen) != (ssize_t)outlen) { perror("write"); ok = false; } free(outbuf); return ok; } ================================================ FILE: server/plugin/plg_image_c/image_raw.h ================================================ #include #include #include #include "utils.h" #include "image_jpeg.h" int raw_to_jpeg(int inputDesc, int outputDesc, int targetSize); ================================================ FILE: server/plugin/plg_image_c/image_raw_freebsd.go ================================================ package plg_image_c // #include "image_raw.h" // #cgo LDFLAGS: -L /usr/local/lib -L /usr/lib -L /lib -l:libyuv.a -l:libjpeg.a -l:libraw.a -fopenmp -l:libc++.a -llcms2 -lm // #cgo CFLAGS: -I /usr/local/include import "C" func raw(input uintptr, output uintptr, size int) { C.raw_to_jpeg(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_raw_linux.go ================================================ package plg_image_c // #include "image_raw.h" // #cgo LDFLAGS: -l:libjpeg.a -l:libraw.a -fopenmp -l:libstdc++.a -llcms2 -lm import "C" func raw(input uintptr, output uintptr, size int) { C.raw_to_jpeg(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_webp.c ================================================ #include #include #include #include #include "utils.h" #define WEBP_QUALITY 75 #define INITIAL_BUFFER_SIZE 1024*64 // 128kB #define MAX_BUFFER_SIZE 1024*1024*2 // 2MB int webp_to_webp(int inputDesc, int outputDesc, int targetSize) { #ifdef HAS_DEBUG clock_t t; t = clock(); #endif if (targetSize < 0) { targetSize = -targetSize; } int status = 0; FILE* input = fdopen(inputDesc, "rb"); FILE* output = fdopen(outputDesc, "wb"); if (!input || !output) { ERROR("setup"); return 1; } // STEP1: setup everything size_t data_size = 0; size_t buffer_size = INITIAL_BUFFER_SIZE; uint8_t* data = (uint8_t*)malloc(buffer_size); if (!data) { ERROR("malloc"); return 1; } size_t bytes_read; while ((bytes_read = fread(data + data_size, 1, buffer_size - data_size, input)) > 0) { data_size += bytes_read; if (buffer_size - data_size == 0) { DEBUG("realloc"); if (buffer_size >= MAX_BUFFER_SIZE) { free(data); ERROR("abort"); return 1; } buffer_size *= 2; if (buffer_size > MAX_BUFFER_SIZE) buffer_size = MAX_BUFFER_SIZE; uint8_t* new_data = (uint8_t*)realloc(data, buffer_size); if (!new_data) { free(data); ERROR("realloc"); return 1; } data = new_data; } } // STEP2: decode int width, height, scale_factor; if (!WebPGetInfo(data, data_size, &width, &height)) { free(data); ERROR("init"); return 1; } DEBUG("init"); WebPDecoderConfig config; if (!WebPInitDecoderConfig(&config)) { free(data); ERROR("config"); return 1; } scale_factor = (height > targetSize) ? height / targetSize : 1; config.options.use_scaling = 1; config.options.scaled_width = width / scale_factor; config.options.scaled_height = height / scale_factor; config.output.colorspace = MODE_rgbA; DEBUG("config"); if (WebPDecode(data, data_size, &config) != VP8_STATUS_OK) { WebPFreeDecBuffer(&config.output); free(data); ERROR("decode"); return 1; } free(data); DEBUG("decode"); // STEP3: encode size_t output_size = 0; uint8_t* output_data = NULL; output_size = WebPEncodeRGBA( config.output.u.RGBA.rgba, config.options.scaled_width, config.options.scaled_height, config.output.u.RGBA.stride, WEBP_QUALITY, &output_data ); if (output_data == NULL) { WebPFreeDecBuffer(&config.output); ERROR("encode"); return 1; } DEBUG("encode"); fwrite(output_data, output_size, 1, output); fflush(output); WebPFree(output_data); WebPFreeDecBuffer(&config.output); DEBUG("done"); return status; } ================================================ FILE: server/plugin/plg_image_c/image_webp.go ================================================ package plg_image_c // #include "image_webp.h" // #cgo LDFLAGS: -l:libwebp.a -l:libsharpyuv.a import "C" func webp(input uintptr, output uintptr, size int) { C.webp_to_webp(C.int(input), C.int(output), C.int(size)) return } ================================================ FILE: server/plugin/plg_image_c/image_webp.h ================================================ int webp_to_webp(int inputDesc, int outputDesc, int targetSize); ================================================ FILE: server/plugin/plg_image_c/index.go ================================================ package plg_image_c import ( "io" "net/http" "os" "strconv" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) /* * All the transcoders are reponsible for: * 1. create thumbnails if needed * 2. transcode various files if needed * * Under the hood, our transcoders are C programs that takes 3 arguments: * 1/2. the input/output file descriptors. We use file descriptors to communicate from go -> C -> go * 3. the target size. by convention those program handles: * - positive size: when we want to transcode a file with best effort in regards to quality and * not lose metadata, typically when this will be open in an image viewer from which we might have * frontend code to extract exif/xmp metadata, ... * - negative size: when we want transcode to be done as quickly as possible, typically when we want * to create a thumbnail and don't care/need anything else than speed */ func init() { Hooks.Register.Thumbnailer("image/jpeg", &transcoder{runner(jpeg), "image/jpeg", -200}) Hooks.Register.Thumbnailer("image/png", &transcoder{runner(png), "image/webp", -200}) Hooks.Register.Thumbnailer("image/gif", &transcoder{runner(gif), "image/webp", -300}) Hooks.Register.Thumbnailer("image/webp", &transcoder{runner(webp), "image/webp", -200}) rawMimeType := []string{ "image/x-canon-cr2", "image/x-tif", "image/x-canon-cr2", "image/x-canon-crw", "image/x-nikon-nef", "image/x-nikon-nrw", "image/x-sony-arw", "image/x-sony-sr2", "image/x-minolta-mrw", "image/x-minolta-mdc", "image/x-olympus-orf", "image/x-panasonic-rw2", "image/x-pentax-pef", "image/x-epson-erf", "image/x-raw", "image/x-x3f", "image/x-fuji-raf", "image/x-aptus-mos", "image/x-mamiya-mef", "image/x-hasselblad-3fr", "image/x-adobe-dng", "image/x-samsung-srw", "image/x-kodak-kdc", "image/x-kodak-dcr", } for _, mType := range rawMimeType { Hooks.Register.Thumbnailer(mType, &transcoder{runner(raw), "image/jpeg", -200}) } Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, bool, error) { query := req.URL.Query() mType := GetMimeType(query.Get("path")) if strings.HasPrefix(mType, "image/") == false { return reader, false, nil } else if query.Get("thumbnail") == "true" { return reader, false, nil } else if query.Get("size") == "" { return reader, false, nil } sizeInt, err := strconv.Atoi(query.Get("size")) if err != nil { return reader, false, nil } if !contains(rawMimeType, mType) { return reader, false, nil } thumb, err := transcoder{runner(raw), "image/jpeg", sizeInt}.Generate(reader, ctx, res, req) return thumb, true, err }) } type transcoder struct { fn func(input io.ReadCloser, size int) (io.ReadCloser, error) mime string size int } func (this transcoder) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { thumb, err := this.fn(reader, this.size) if err == nil && this.mime != "" { (*res).Header().Set("Content-Type", this.mime) } return thumb, err } /* * uuuh, what is this stuff you might rightly wonder? Trying to send a go stream to C isn't obvious, * but if you try to stream from C back to go in the same time, this is what you endup with. * To my knowledge using file descriptor is the best way we can do that if we don't make the assumption * that everything fits in memory. */ func runner(fn func(uintptr, uintptr, int)) func(io.ReadCloser, int) (io.ReadCloser, error) { return func(inputGo io.ReadCloser, size int) (io.ReadCloser, error) { inputC, tmpw, err := os.Pipe() logErrors(err, "plg_image_c::pipe") if err != nil { return nil, err } outputGo, outputC, err := os.Pipe() logErrors(err, "plg_image_c::pipe") if err != nil { tmpw.Close() return nil, err } go func() { fn(inputC.Fd(), outputC.Fd(), size) // <-- all this code so we can do that logErrors(inputC.Close(), "plg_image_c::inputC") logErrors(inputGo.Close(), "plg_image_c::inputGo") logErrors(outputC.Close(), "plg_image_c::outputC") }() go func() { io.Copy(tmpw, inputGo) logErrors(tmpw.Close(), "plg_image_c::tmpw") }() return outputGo, nil } } func logErrors(err error, msg string) { if err == nil { return } Log.Debug(msg + ": " + err.Error()) } func contains(s []string, str string) bool { for _, v := range s { if v == str { return true } } return false } ================================================ FILE: server/plugin/plg_image_c/utils.h ================================================ #define HAS_DEBUG 0 #include #if HAS_DEBUG == 1 #define DEBUG(r) (fprintf(stderr, "[DEBUG::('" r "')(%.2Fms)]", ((double)clock() - t)/CLOCKS_PER_SEC * 1000)) #else #define DEBUG(r) ((void)0) #endif #define ERROR(r) (fprintf(stderr, "[ERROR:('" r "')]")) #define min(a, b) (a > b ? b : a) ================================================ FILE: server/plugin/plg_image_golang/index.go ================================================ package plg_image_golang import ( "bytes" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/image/draw" "image" _ "image/gif" _ "image/jpeg" "image/png" "io" "net/http" ) const THUMB_SIZE int = 400 func init() { Hooks.Register.Thumbnailer("image/jpeg", thumbnailer{}) Hooks.Register.Thumbnailer("image/png", thumbnailer{}) Hooks.Register.Thumbnailer("image/gif", thumbnailer{}) } type thumbnailer struct{} func (this thumbnailer) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { query := req.URL.Query() mType := GetMimeType(query.Get("path")) if query.Get("thumbnail") != "true" { return reader, nil } else if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" { return reader, nil } src, _, err := image.Decode(reader) if err != nil { return reader, nil } ratio := func(i image.Image) int { b := src.Bounds() max := b.Max.X if b.Max.X < b.Max.Y { max = b.Max.Y } r := max / THUMB_SIZE if r <= 1 { return 1 } return r }(src) dst := image.NewRGBA(image.Rect(0, 0, src.Bounds().Max.X/ratio, src.Bounds().Max.Y/ratio)) draw.ApproxBiLinear.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil) output := bytes.NewBuffer([]byte("")) if err = png.Encode(output, dst); err != nil { return reader, err } return NewReadCloserFromBytes(output.Bytes()), nil } ================================================ FILE: server/plugin/plg_image_light/deps/README.md ================================================ plg_image_light rely on a few libraries for: - image transcoding: libtranscode.a: a library build on top of of libraw - image resizing: libresize.a: a library built on top of libvips To create the libraries to be used by Filestash: ``` ./create_libtranscode.sh ./create_libresize.sh ``` To test the libraries are working fine: ``` # libtranscode: gcc -Wall -c src/libtranscode_test.c gcc -o main_transcode.bin libtranscode_test.o -lm -lpthread -L. -l:libtranscode.a curl -O https://archive.kerjean.me/public/2020/sample.CR2 ./main_transcode.bin ./sample.CR2 # libresize: gcc -Wall -c src/libresize_test.c `pkg-config --cflags glib-2.0` gcc -o main_resize.bin libresize_test.o -lm -lgmodule-2.0 -lgobject-2.0 -lglib-2.0 -L. -l:libresize.a curl -O https://archive.kerjean.me/public/2020/sample.jpg ./main_resize.bin ./sample.jpg ``` ================================================ FILE: server/plugin/plg_image_light/deps/create_libresize.sh ================================================ #!/bin/sh # # This script handle the creation of the static library used by the plugin to # # perform image resizing jobs. You can run it like this: # docker run -ti --name debian_build_dep -v /home/:/home/ debian:9 bash # apt-get -y update && apt-get -y install git # git clone https://github.com/mickael-kerjean/filestash # cd filestash/server/plugin/plg_image_light/deps/ # ./create_libresize.sh set -e arch=$(dpkg --print-architecture) if [ $arch != "amd64" ] && [ $arch != "armhf" ]; then echo "PLATFORM NOT SUPPORTED" exit 1 fi ################################################ # Tooling apt install -y curl make gcc g++ xz-utils pkg-config python3-pip autoconf libtool unzip python-setuptools cmake git #pip3 install --user meson ninja export PATH=~/.local/bin:$PATH ################################################ # Stage 1: Get libvips and its dependencies + recompile for less headaches INITIAL_PATH=`pwd` mkdir -p /tmp/filestash/libresize/tmp cd /tmp/filestash/libresize apt install -y libvips-dev cd tmp curl -L -X GET https://github.com/libvips/libvips/releases/download/v8.7.0/vips-8.7.0.tar.gz > libvips.tar.gz tar -zxf libvips.tar.gz cd vips-8.7.0/ ./configure --enable-static --without-magick --without-lcms --without-OpenEXR --without-nifti --without-pdfium --without-rsvg --without-matio --without-libwebp --without-cfitsio --without-zlib --without-poppler --without-pangoft2 --enable-introspection=no --without-openslide make -j 8 make install cd $INITIAL_PATH ################################################ # Stage 2: Create our own library as a static build gcc -Wall -c src/libresize.c `pkg-config --cflags glib-2.0` ar rcs libresize.a libresize.o ################################################ # Stage 3: Gather and assemble all the bits and pieces together libpath=$( if [ $arch = "amd64" ]; then echo "x86_64-linux-gnu"; elif [ $arch = "armhf" ]; then echo "arm-linux-gnueabihf" fi ) #ar x /tmp/libresize.a ar x /usr/local/lib/libvips.a ar x /usr/lib/$libpath/libz.a ar x /usr/lib/$libpath/libbz2.a ar x /usr/lib/$libpath/libjpeg.a ar x /usr/lib/$libpath/libgif.a ar x /usr/lib/$libpath/libdl.a ar x /usr/lib/$libpath/libicui18n.a ar x /usr/lib/$libpath/libgsf-1.a ar x /usr/lib/$libpath/libicuuc.a ar x /usr/lib/$libpath/libicudata.a ar x /usr/lib/$libpath/liblzma.a ar x /usr/lib/$libpath/libfreetype.a ar x /usr/lib/$libpath/libfftw3.a ar x /usr/lib/$libpath/libfontconfig.a ar x /usr/lib/$libpath/libXext.a ar x /usr/lib/$libpath/libSM.a ar x /usr/lib/$libpath/libX11.a ar x /usr/lib/$libpath/liborc-0.4.a ar x /usr/lib/$libpath/libltdl.a ar x /usr/lib/$libpath/librt.a ar x /usr/lib/$libpath/libharfbuzz.a ar x /usr/lib/$libpath/libexpat.a ar x /usr/lib/$libpath/libgio-2.0.a ar x /usr/lib/$libpath/libpng16.a ar x /usr/lib/$libpath/libpixman-1.a ar x /usr/lib/$libpath/libxcb.a ar x /usr/lib/$libpath/libjbig.a ar x /usr/lib/$libpath/libexif.a ar x /usr/lib/$libpath/libpcre.a ar x /usr/lib/$libpath/libtiff.a ar x /usr/lib/$libpath/libpangoft2-1.0.a ar x /usr/lib/$libpath/libpoppler.a ar rcs libresize.a *.o rm *.o *.ao #scp libresize.a mickael@hal.kerjean.me:/home/app/pages/data/projects/filestash/downloads/upload/libresize_`uname -s`-`uname -m`.a ################################################ # Stage 4: Gather all the related headers #cd /usr/include/ #tar zcf /tmp/libresize-headers.tar.gz . #scp /tmp/libresize-headers.tar.gz mickael@hal.kerjean.me:/home/app/pages/data/projects/filestash/downloads/upload/libresize_`uname -s`-`uname -m`_headers.tar.gz ================================================ FILE: server/plugin/plg_image_light/deps/create_libtranscode.sh ================================================ #!/bin/sh # # This script handle the creation of the static library used by the plugin to # # perform transcoding jobs. You can run it like this: # docker run -ti --name debian_build_dep -v /home/:/home/ debian:9 bash # apt-get -y update && apt-get -y install git # git clone https://github.com/mickael-kerjean/filestash # cd filestash/server/plugin/plg_image_light/deps/ # ./create_libtranscode.sh set -e arch=$(dpkg --print-architecture) if [ $arch != "amd64" ] && [ $arch != "armhf" ]; then echo "PLATFORM NOT SUPPORTED" exit 1 fi ################################################ # Tooling apt install -y curl make gcc g++ xz-utils pkg-config python3-pip autoconf libtool unzip python-setuptools cmake git export PATH=~/.local/bin:$PATH ################################################ # Stage 1: Get libraw and its dependencies INITIAL_PATH=`pwd` apt install -y libraw-dev cd /tmp/ # libgomp and libstdc++ apt-get install -y libgcc-6-dev # libjpeg apt-get install -y libjpeg-dev # liblcms2 curl -L -O https://downloads.sourceforge.net/project/lcms/lcms/2.9/lcms2-2.9.tar.gz tar zxf lcms2-2.9.tar.gz cd lcms2-2.9 ./configure --enable-static --without-zlib --without-threads make -j 8 make install cd $INITIAL_PATH ################################################ # Stage 2: Create our own library as a static build gcc -Wall -c src/libtranscode.c ################################################ # Stage 3: Gather and assemble all the bits and pieces together libpath=$( if [ $arch = "amd64" ]; then echo "x86_64-linux-gnu"; elif [ $arch = "armhf" ]; then echo "arm-linux-gnueabihf" fi ) ar x /usr/lib/$libpath/libraw.a ar x /usr/lib/$libpath/libjpeg.a ar x /usr/local/lib/liblcms2.a ar x /usr/lib/gcc/$libpath/6/libstdc++.a ar x /usr/lib/gcc/$libpath/6/libgomp.a ar x /usr/lib/$libpath/libpthread.a ar rcs libtranscode.a *.o rm *.o #scp libtranscode.a mickael@hal.kerjean.me:/home/app/pages/data/projects/filestash/downloads/upload/libtranscode_`uname -s`-`uname -m`.a ################################################ # Stage 4: Gather all the related headers #cd /usr/include/ #tar zcf /tmp/libtranscode-headers.tar.gz . #scp /tmp/libtranscode-headers.tar.gz mickael@hal.kerjean.me:/home/app/pages/data/projects/filestash/downloads/upload/libtranscode_`uname -s`-`uname -m`_headers.tar.gz ================================================ FILE: server/plugin/plg_image_light/deps/src/libresize.c ================================================ #include #include int image_resize(const char *filename, void **buf, size_t *len, int size, int crop, int quality, int exif){ VipsImage *img; int err; size = size > 4000 || size < 0 ? 1000 : size; crop = crop == 0 ? VIPS_INTERESTING_NONE : VIPS_INTERESTING_CENTRE; quality = quality > 100 || quality < 0 ? 80 : quality; exif = exif == 0 ? TRUE : FALSE; if(crop == VIPS_INTERESTING_CENTRE){ // Generate a thumbnails: a square picture crop in the center err = vips_thumbnail(filename, &img, size, "size", VIPS_SIZE_BOTH, "auto_rotate", TRUE, "crop", VIPS_INTERESTING_CENTRE, NULL ); }else{ // normal resize of an image with libvips err = vips_thumbnail(filename, &img, size, "size", VIPS_SIZE_DOWN, "auto_rotate", TRUE, "crop", VIPS_INTERESTING_NONE, NULL ); } if(err != 0){ return err; } err = vips_jpegsave_buffer(img, buf, len, "Q", quality, "strip", exif, NULL); g_object_unref(img); return err; } void null_log_handler (const gchar *a, GLogLevelFlags l, const gchar *m, gpointer ud){ } void __attribute__ ((constructor)) initLibrary(void) { VIPS_INIT("imagevips"); vips_cache_set_max(0); g_log_set_handler( "VIPS", G_LOG_LEVEL_WARNING, null_log_handler, NULL); } void __attribute__ ((destructor)) cleanUpLibrary(void) { vips_shutdown(); } ================================================ FILE: server/plugin/plg_image_light/deps/src/libresize.h ================================================ #include int image_resize(const char *filename, void **buf, size_t *len, int size, int crop, int quality, int exif); ================================================ FILE: server/plugin/plg_image_light/deps/src/libresize_test.c ================================================ #include #include #include #include "libresize.h" double benchmark_image_resize(int n, const char*input); int main(int argc, char **argv) { if(argc != 2){ printf("missing argument: need a path to an image\n"); exit(1); } printf("=> benchmark %s: %.2fms\n", argv[1], benchmark_image_resize(20, argv[1])); } double benchmark_image_resize(int n, const char* input) { double total = 0; void *buffer; size_t len; int i = 0; for(i=0; i #include #define FALSE 0 #define TRUE !FALSE int image_transcode_compute(const char* filename, int min_width) { int err; libraw_data_t *raw; int has_thumbnail = FALSE; ////////////////////// // boot up libraw raw = libraw_init(0); if(libraw_open_file(raw, filename) != 0){ libraw_close(raw); return 1; } raw->params.output_tiff = 1; ////////////////////// // use thumbnail if available if(libraw_unpack_thumb(raw) == 0){ has_thumbnail = TRUE; if(raw->thumbnail.twidth > min_width && raw->thumbnail.tformat == LIBRAW_THUMBNAIL_JPEG){ err = libraw_dcraw_thumb_writer(raw, filename); libraw_close(raw); return err; } } fflush(stdout); ////////////////////// // transcode image if(libraw_unpack(raw) != 0){ if(has_thumbnail == TRUE){ err = libraw_dcraw_thumb_writer(raw, filename); libraw_close(raw); return err; } libraw_close(raw); return 0; } err = libraw_dcraw_process(raw); if(err != 0){ if(err == LIBRAW_UNSUFFICIENT_MEMORY){ libraw_close(raw); return -1; } if(has_thumbnail == TRUE){ err = libraw_dcraw_thumb_writer(raw, filename); libraw_close(raw); return err; } libraw_close(raw); return 1; } if(libraw_dcraw_ppm_tiff_writer(raw, filename) != 0){ if(has_thumbnail == TRUE){ err = libraw_dcraw_thumb_writer(raw, filename); libraw_close(raw); return err; } libraw_close(raw); return 1; } libraw_close(raw); return 0; } ================================================ FILE: server/plugin/plg_image_light/deps/src/libtranscode.h ================================================ #include int image_transcode_compute(const char* filename, int min_width); ================================================ FILE: server/plugin/plg_image_light/deps/src/libtranscode_test.c ================================================ #include #include #include "libtranscode.h" double benchmark_image_transcode(int n, const char*input); int main(int argc, char **argv) { if(argc != 2){ printf("missing argument: need a path to an image\n"); exit(1); } printf("=> benchmark %s: %.2fms\n", argv[1], benchmark_image_transcode(20, argv[1])); } double benchmark_image_transcode(int n, const char* input) { double total = 0; int i = 0; for(i=0; i impedance matching with something usable by CGO file, err := os.OpenFile(transform.Input, os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { return reader, ErrFilesystemError } io.Copy(file, reader) file.Close() reader.Close() defer func() { os.Remove(transform.Input) }() ///////////////////////// // Transcode RAW image if IsRaw(mType) { if ExtractPreview(transform) == nil { mType = "image/jpeg" (*res).Header().Set("Content-Type", mType) } else { return reader, nil } } ///////////////////////// // final stage: resizing if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" { return reader, nil } return CreateThumbnail(transform) }) } type Transform struct { Input string Size int Crop bool Quality int Exif bool } ================================================ FILE: server/plugin/plg_image_light/install.sh ================================================ #/bin/bash set -e echo "= INSTALL LIBS" SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" cd "$(dirname "$0")"/deps # AMD64 curl -sk https://downloads.filestash.app/upload/libresize_Linux-x86_64.a > libresize_linux_amd64.a & curl -sk https://downloads.filestash.app/upload/libtranscode_Linux-x86_64.a > libtranscode_linux_amd64.a & # ARM curl -sk https://downloads.filestash.app/upload/libresize_Linux-armv7l.a > libresize_linux_arm.a & curl -sk https://downloads.filestash.app/upload/libtranscode_Linux-armv7l.a > libtranscode_linux_arm.a & wait ================================================ FILE: server/plugin/plg_image_light/lib_resize.go ================================================ package plg_image_light // #cgo CFLAGS: -I./deps/src // #cgo pkg-config:glib-2.0 // #include "glib-2.0/glib.h" // #include "libresize.h" import "C" import ( "context" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/sync/semaphore" "io" "time" "unsafe" ) const ( THUMBNAIL_TIMEOUT = 5 * time.Second THUMBNAIL_MAX_CONCURRENT = 50 ) var VIPS_LOCK = semaphore.NewWeighted(THUMBNAIL_MAX_CONCURRENT) func CreateThumbnail(t *Transform) (io.ReadCloser, error) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(THUMBNAIL_TIMEOUT)) defer cancel() if err := VIPS_LOCK.Acquire(ctx, 1); err != nil { return nil, ErrCongestion } defer VIPS_LOCK.Release(1) imageChannel := make(chan io.ReadCloser, 1) go func() { filename := C.CString(t.Input) len := C.size_t(0) var buffer unsafe.Pointer if C.image_resize(filename, &buffer, &len, C.int(t.Size), boolToCInt(t.Crop), C.int(t.Quality), boolToCInt(t.Exif)) != 0 { C.free(unsafe.Pointer(filename)) imageChannel <- nil return } C.free(unsafe.Pointer(filename)) buf := C.GoBytes(buffer, C.int(len)) C.g_free(C.gpointer(buffer)) imageChannel <- NewReadCloserFromBytes(buf) }() select { case img := <-imageChannel: if img == nil { return nil, ErrNotValid } return img, nil case <-ctx.Done(): return nil, ErrTimeout } } func boolToCInt(val bool) C.int { if val == false { return C.int(0) } return C.int(1) } ================================================ FILE: server/plugin/plg_image_light/lib_resize_linux_amd64.go ================================================ package plg_image_light // #cgo LDFLAGS: -lm -lgmodule-2.0 -lgobject-2.0 -lglib-2.0 -ldl -L./deps -l:libresize_linux_amd64.a import "C" ================================================ FILE: server/plugin/plg_image_light/lib_resize_linux_arm.go ================================================ package plg_image_light // #cgo LDFLAGS: -lm -lgmodule-2.0 -lgobject-2.0 -lglib-2.0 -ldl -L./deps -l:libresize_linux_arm.a import "C" ================================================ FILE: server/plugin/plg_image_light/lib_transcode.go ================================================ package plg_image_light // #cgo CFLAGS: -I./deps/src // #include "libtranscode.h" import "C" import ( "context" . "github.com/mickael-kerjean/filestash/server/common" "golang.org/x/sync/semaphore" "time" "unsafe" ) const ( TRANSCODE_TIMEOUT = 10 * time.Second TRANSCODE_MAX_CONCURRENT = 5 ) var LIBRAW_LOCK = semaphore.NewWeighted(int64(TRANSCODE_MAX_CONCURRENT)) func IsRaw(mType string) bool { switch mType { case "image/x-tif": case "image/x-canon-cr2": case "image/x-canon-crw": case "image/x-nikon-nef": case "image/x-nikon-nrw": case "image/x-sony-arw": case "image/x-sony-sr2": case "image/x-minolta-mrw": case "image/x-minolta-mdc": case "image/x-olympus-orf": case "image/x-panasonic-rw2": case "image/x-pentax-pef": case "image/x-epson-erf": case "image/x-raw": case "image/x-x3f": case "image/x-fuji-raf": case "image/x-aptus-mos": case "image/x-mamiya-mef": case "image/x-hasselblad-3fr": case "image/x-adobe-dng": case "image/x-samsung-srw": case "image/x-kodak-kdc": case "image/x-kodak-dcr": default: return false } return true } func ExtractPreview(t *Transform) error { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(TRANSCODE_TIMEOUT)) defer cancel() if err := LIBRAW_LOCK.Acquire(ctx, 1); err != nil { return ErrCongestion } defer LIBRAW_LOCK.Release(1) transcodeChannel := make(chan error, 1) go func() { filename := C.CString(t.Input) defer C.free(unsafe.Pointer(filename)) if err := C.image_transcode_compute(filename, C.int(t.Size)); err != 0 { transcodeChannel <- ErrNotValid } transcodeChannel <- nil }() select { case err := <-transcodeChannel: return err case <-ctx.Done(): return ErrTimeout } } ================================================ FILE: server/plugin/plg_image_light/lib_transcode_linux_amd64.go ================================================ package plg_image_light // #cgo LDFLAGS: -lm -lpthread -L./deps -l:libtranscode_linux_amd64.a import "C" ================================================ FILE: server/plugin/plg_image_light/lib_transcode_linux_arm.go ================================================ package plg_image_light // #cgo LDFLAGS: -lm -lpthread -L./deps -l:libtranscode_linux_arm.a import "C" ================================================ FILE: server/plugin/plg_image_transcode/index.go ================================================ package plg_image_transcode import ( . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" ) func init() { Hooks.Register.ProcessFileContentBeforeSend(renderImages) } func renderImages(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, bool, error) { query := req.URL.Query() if query.Get("thumbnail") == "true" { return reader, false, nil } else if query.Get("size") == "" { return reader, false, nil } var ( out io.ReadCloser = nil err error = nil ) mType := GetMimeType(query.Get("path")) switch mType { case "image/x-ms-bmp": out, mType, err = transcodeBmp(reader) case "image/tiff": out, mType, err = transcodeTiff(reader) case "image/dicom": out, mType, err = transcodeDicom(reader) default: return reader, false, nil } reader.Close() if err == nil { (*res).Header().Set("Content-Type", mType) } if err != nil && err != ErrNotImplemented && err != ErrNotValid { Log.Debug("plg_image_transcode::err %s", err.Error()) return nil, false, ErrNotValid } return out, true, err } ================================================ FILE: server/plugin/plg_image_transcode/transcode_bmp.go ================================================ package plg_image_transcode import ( . "github.com/mickael-kerjean/filestash/server/common" _ "golang.org/x/image/bmp" "image" "image/jpeg" "io" ) func transcodeBmp(reader io.Reader) (io.ReadCloser, string, error) { img, _, err := image.Decode(reader) if err != nil { return nil, "", err } r, w := io.Pipe() go func() { err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) w.Close() if err != nil { Log.Debug("plg_image_transcode::bmp jpeg encoding error '%s'", err.Error()) } }() return NewReadCloserFromReader(r), "image/jpeg", nil } ================================================ FILE: server/plugin/plg_image_transcode/transcode_dicom.go ================================================ package plg_image_transcode import ( "bufio" "bytes" . "github.com/mickael-kerjean/filestash/server/common" "github.com/suyashkumar/dicom" "github.com/suyashkumar/dicom/pkg/tag" "image/jpeg" "io" ) func transcodeDicom(reader io.Reader) (io.ReadCloser, string, error) { var b bytes.Buffer w := bufio.NewWriter(&b) io.Copy(w, reader) dataset, err := dicom.Parse(&b, int64(len(b.Bytes())), nil) if err != nil { Log.Debug("plg_image_transcode::dicom::parse '%s'", err.Error()) return nil, "", ErrNotValid } pixelDataElement, err := dataset.FindElementByTag(tag.PixelData) if err != nil { Log.Debug("plg_image_transcode::dicom::findElementByTag '%s'", err.Error()) return nil, "", ErrNotValid } pixelDataInfo := dicom.MustGetPixelDataInfo(pixelDataElement.Value) for _, fr := range pixelDataInfo.Frames { img, err := fr.GetImage() if err != nil { if err.Error() == "unsupported JPEG feature: unknown marker" { // known issue with lossless jpeg codec which isn't supported in golang // and is not trivial to support in Filestash return nil, "", ErrNotImplemented } Log.Stdout("plg_image_transcode_dicom::getImage '%s'", err.Error()) return nil, "", err } r, w := io.Pipe() go func() { err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) w.Close() if err != nil { Log.Debug("plg_image_transcode::dicom jpeg encoding error '%s'", err.Error()) } }() return NewReadCloserFromReader(r), "image/jpeg", nil } return nil, "", ErrNotValid } ================================================ FILE: server/plugin/plg_image_transcode/transcode_svg.go ================================================ package plg_image_transcode import ( . "github.com/mickael-kerjean/filestash/server/common" "github.com/srwiley/oksvg" "github.com/srwiley/rasterx" "image" "image/png" "io" ) /* * This bit isn't used because the rendering is very poor and would * generate too many bug reports */ func transcodeSvg(reader io.Reader) (io.ReadCloser, string, error) { icon, err := oksvg.ReadIconStream(reader) if err != nil { return nil, "", err } icon.SetTarget(0, 0, icon.ViewBox.W, icon.ViewBox.H) width := int(icon.ViewBox.W) height := int(icon.ViewBox.H) img := image.NewRGBA(image.Rect(0, 0, width, height)) icon.Draw( rasterx.NewDasher( width, height, rasterx.NewScannerGV(width, height, img, img.Bounds()), ), 1, ) r, w := io.Pipe() go func() { err := png.Encode(w, img) w.Close() if err != nil { Log.Debug("plg_image_transcode::svg png encoding error '%s'", err.Error()) } }() return NewReadCloserFromReader(r), "image/png", nil } ================================================ FILE: server/plugin/plg_image_transcode/transcode_tiff.go ================================================ package plg_image_transcode import ( . "github.com/mickael-kerjean/filestash/server/common" _ "golang.org/x/image/tiff" "image" "image/jpeg" "io" ) func transcodeTiff(reader io.Reader) (io.ReadCloser, string, error) { img, _, err := image.Decode(reader) if err != nil { return nil, "", err } r, w := io.Pipe() go func() { err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) w.Close() if err != nil { Log.Debug("plg_image_transcode::tiff jpeg encoding error '%s'", err.Error()) } }() return NewReadCloserFromReader(r), "image/jpeg", nil } ================================================ FILE: server/plugin/plg_license/index.go ================================================ package plg_license import ( "encoding/json" "fmt" "os" "time" . "github.com/mickael-kerjean/filestash/server/common" ) type license struct { Expiry time.Time `json:"expiry"` Name string `json:"name"` } func init() { lenv := os.Getenv("LICENSE") Hooks.Register.Onload(func() { if LICENSE != "agpl" && lenv == "" { return } data, err := DecryptString(fmt.Sprintf("%-16s", "filestash"), Config.Get("general.license").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "license" f.Type = "text" f.Placeholder = "License Key" f.Description = "Reach out to support@filestash.app to get your license" if lenv != "" { f.Value = lenv f.ReadOnly = true } return f }).String()) if err != nil { return } var lic license if err := json.Unmarshal([]byte(data), &lic); err != nil { return } if time.Now().After(lic.Expiry) { Log.Error("License expired. expiry=%s name=%s", lic.Expiry, lic.Name) Log.Error("Contact support at support@filestash.app") os.Exit(1) return } suffix := LICENSE if suffix == "agpl" { suffix = "base" } LICENSE = lic.Name + "::" + suffix Log.Info("You are running Filestash \"%s\"", LICENSE) }) } ================================================ FILE: server/plugin/plg_metadata_sqlite/index.go ================================================ package plg_metadata_sqlite import ( "database/sql" "encoding/json" "fmt" "os" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { db, err := sql.Open("sqlite3", GetAbsolutePath(DB_PATH, "metadata.sql")) if err != nil { Log.Error("plg_metadata_sqlite - cannot open sqlite metadata db: %s", err.Error()) os.Exit(1) return } db.Exec(`CREATE TABLE IF NOT EXISTS metadata ( tenantID TEXT NOT NULL, path TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (tenantID, path) )`) Hooks.Register.Metadata(MetaImpl{db: db}) } type MetaImpl struct { db *sql.DB } func (this MetaImpl) Get(ctx *App, path string) ([]FormElement, error) { tenantID := GenerateID(ctx.Session) forms := []FormElement{} var blob string err := this.db.QueryRowContext(ctx.Context, "SELECT value FROM metadata WHERE tenantID=? AND path = ?", tenantID, path).Scan(&blob) if err == sql.ErrNoRows { return forms, nil } else if err != nil { return forms, err } err = json.Unmarshal([]byte(blob), &forms) return forms, err } func (this MetaImpl) Set(ctx *App, path string, value []FormElement) error { tenantID := GenerateID(ctx.Session) if len(value) == 0 { _, err := this.db.Exec("DELETE FROM metadata WHERE tenantID=? AND path=?", tenantID, path) return err } blob, err := json.Marshal(value) if err != nil { return err } _, err = this.db.Exec(` INSERT INTO metadata (tenantID, path, value) VALUES (?, ?, ?) ON CONFLICT(tenantID, path) DO UPDATE SET value=excluded.value `, tenantID, path, string(blob)) return err } func (this MetaImpl) Search(ctx *App, path string, facets map[string]any) (map[string][]FormElement, error) { tenantID := GenerateID(ctx.Session) rows, err := this.db.QueryContext(ctx.Context, ` SELECT path, value FROM metadata WHERE tenantID=? AND path LIKE ? ORDER BY path `, tenantID, path+"%") if err != nil { return nil, err } out := make(map[string][]FormElement) for rows.Next() { var ( metapath string metavalue []byte metaforms []FormElement ) if err = rows.Scan(&metapath, &metavalue); err != nil { break } if err = json.Unmarshal(metavalue, &metaforms); err != nil { break } isResult := false for _, form := range metaforms { if form.Id == "" || facets[form.Id] == "" { continue } facetValues, ok := facets[form.Id].([]any) if !ok { continue } formValue := fmt.Sprintf("%s", form.Value) isOK := true for _, facetValue := range facetValues { if strings.Contains(formValue, fmt.Sprintf("%s", facetValue)) == false { isOK = false break } } if isOK { isResult = true } } if isResult { chroot := ctx.Session["path"] if key := strings.TrimPrefix(metapath, chroot); key != "" { if !strings.HasPrefix(key, "/") { key = "/" + key } out[key] = metaforms } } } rows.Close() return out, nil } ================================================ FILE: server/plugin/plg_override_actiondelete/Makefile ================================================ all: make install install: zip -r override_actiondelete.zip . mv override_actiondelete.zip ../../../dist/data/state/plugins/ ================================================ FILE: server/plugin/plg_override_actiondelete/README.md ================================================ This plugin brings back the delete button from the original version of Filestash: You can install it either as a [compiled time plugin](https://www.filestash.app/docs/guide/plugin-development.html#compiled-plugin) or a [runtime plugin](https://www.filestash.app/docs/guide/plugin-development.html#runtime-plugins) ================================================ FILE: server/plugin/plg_override_actiondelete/filespage_thing.diff ================================================ diff --git a/public/assets/pages/filespage/thing.js b/public/assets/pages/filespage/thing.js index f6f0ec48..3c86ffcb 100644 --- a/public/assets/pages/filespage/thing.js +++ b/public/assets/pages/filespage/thing.js @@ -1,0 +1,8 @@ import { addSelection, isSelected, clearSelection } from "./state_selection.js"; +import rxjs from "../../lib/rx.js"; +import { rm as rm$ } from "./model_files.js"; +import { rm as rmVL } from "./model_virtual_layer.js"; +import { basename } from "../../lib/path.js"; +import t from "../../locales/index.js"; +import { createModal } from "../../components/modal.js"; +import componentDelete from "./modal_delete.js"; + @@ -156,3 +156,24 @@ export function createThing({ return $thing; } + let $action = $thing.querySelector(".component_action"); + $action.innerHTML = ``; + $action.onclick = (e) => { + e.preventDefault(); + e.stopPropagation(); + const rm = (...paths) => withVirtualLayer( + rm$(...paths), + rmVL(...paths), + ); + return rxjs.from(componentDelete( + createModal({ + withButtonsRight: t("OK"), + withButtonsLeft: t("CANCEL"), + }), + basename(path.replace(new RegExp("/$"), "")).substr(0, 15), + )).pipe( + rxjs.mergeMap(() => rm(path)), + rxjs.tap(() => window.dispatchEvent(new KeyboardEvent("keydown", { keyCode: 27 }))), + ).subscribe(); + }; + ================================================ FILE: server/plugin/plg_override_actiondelete/index.go ================================================ package plg_override_actiondelete import ( _ "embed" . "github.com/mickael-kerjean/filestash/server/common" ) //go:embed filespage_thing.diff var PATCH []byte func init() { Hooks.Register.StaticPatch(PATCH) } ================================================ FILE: server/plugin/plg_override_actiondelete/manifest.json ================================================ { "author": "Filestash Pty Ltd", "version": "v0.0", "modules": [ { "type": "patch", "entrypoint": "filespage_thing.diff" } ] } ================================================ FILE: server/plugin/plg_override_download/README.md ================================================ ``` git diff public/assets/pages/filespage/thing.js > server/plugin/plg_override_download/assets/pages/filespage/thing.js ``` ================================================ FILE: server/plugin/plg_override_download/assets/pages/filespage/thing.js ================================================ diff --git a/public/assets/pages/filespage/thing.js b/public/assets/pages/filespage/thing.js index dd4f69ec..3bdcb97c 100644 --- a/public/assets/pages/filespage/thing.js +++ b/public/assets/pages/filespage/thing.js @@ -35,6 +35,20 @@ export function init() { }; } +window.onDownload = function(e, self) { + e.preventDefault(); e.stopPropagation(); + const path = self.parentElement.getAttribute("data-path"); + const $link = document.createElement("a"); + $link.download = path.split("/").pop(); + if (isDir(path)) $link.download += ".zip"; + $link.href = `/api/files/{verb}?path=${path}`.replace( + "{verb}", + isDir(path) ? "zip" : "cat", + ); + $link.click(); + $link.remove(); +}; + const $tmpl = createElement(`
    @@ -46,6 +60,15 @@ const $tmpl = createElement(` +
    + download +
    +
    `); ================================================ FILE: server/plugin/plg_override_download/index.go ================================================ package plg_override_download import ( "embed" . "github.com/mickael-kerjean/filestash/server/common" ) //go:embed assets/* var STATIC embed.FS func init() { Hooks.Register.StaticPatch(STATIC) } ================================================ FILE: server/plugin/plg_search_example/index.go ================================================ package plg_search_example import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.SearchEngine(ExampleSearch{}) } type ExampleSearch struct{} func (this ExampleSearch) Query(app App, path string, keyword string) ([]IFile, error) { files := []IFile{} files = append(files, File{ FName: "keyword-" + keyword + ".txt", FType: "file", // ENUM("file", "directory") FSize: 42, FPath: "/fullpath/keyword-" + keyword + ".txt", }) return files, nil } ================================================ FILE: server/plugin/plg_search_sqlitefts/config/configuration.go ================================================ package plg_search_sqlitefts import ( "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { SEARCH_ENABLE() SEARCH_EXCLUSION() SEARCH_PROCESS_MAX() SEARCH_PROCESS_PAR() SEARCH_REINDEX() SEARCH_SHARED_INDEX() CYCLE_TIME() MAX_INDEXING_FSIZE() INDEXING_EXT() }) } var SEARCH_ENABLE = func() bool { return Config.Get("features.search.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "boolean" f.Description = "Enable/Disable full text search automatic indexing" f.Placeholder = "Default: false" f.Default = false return f }).Bool() } var SEARCH_EXCLUSION = func() []string { listOfFolders := Config.Get("features.search.folder_exclusion").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "folder_exclusion" f.Type = "text" f.Description = "Exclude some specific folder from the crawl / index" f.Placeholder = "Default: node_modules,bower_components,.cache,.npm,.git" f.Default = "node_modules,bower_components,.cache,.npm,.git" return f }).String() out := []string{} for _, folder := range strings.Split(listOfFolders, ",") { out = append(out, strings.TrimSpace(folder)) } return out } var SEARCH_PROCESS_MAX = func() int { return Config.Get("features.search.process_max").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "process_max" f.Type = "number" f.Description = "Size of the pool containing the indexers" f.Placeholder = "Default: 5" f.Default = 5 return f }).Int() } var SEARCH_PROCESS_PAR = func() int { return Config.Get("features.search.process_par").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "process_par" f.Type = "number" f.Description = "How many concurrent indexers are running in the same time (requires a restart)" f.Placeholder = "Default: 2" f.Default = 2 return f }).Int() } var SEARCH_REINDEX = func() int { return Config.Get("features.search.reindex_time").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "reindex_time" f.Type = "number" f.Description = "Time in hours after which we consider our index to be stale and needs to be reindexed" f.Placeholder = "Default: 24h" f.Default = 24 return f }).Int() } var SEARCH_SHARED_INDEX = func() bool { return Config.Get("features.search.shared_index").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "shared_index" f.Type = "boolean" f.Description = "Use a single search index shared across all users" f.Default = false return f }).Bool() } var CYCLE_TIME = func() int { return Config.Get("features.search.cycle_time").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "cycle_time" f.Type = "number" f.Description = "Time allocation for each cycle in seconds (discovery, indexing and maintenance)" f.Placeholder = "Default: 10s" f.Default = 10 return f }).Int() } var MAX_INDEXING_FSIZE = func() int { return Config.Get("features.search.max_size").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "max_size" f.Type = "number" f.Description = "Maximum size of files the indexer will perform full text search" f.Placeholder = "Default: 524288000 => 512MB" f.Default = 524288000 return f }).Int() } var INDEXING_EXT = func() string { return Config.Get("features.search.indexer_ext").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "indexer_ext" f.Type = "text" f.Description = "Extensions that will be handled by the full text search engine" f.Placeholder = "Default: org,txt,docx,pdf,md,form,xlsx,pptx" f.Default = "org,txt,docx,pdf,md,form,xlsx,pptx" return f }).String() } ================================================ FILE: server/plugin/plg_search_sqlitefts/converter/index.go ================================================ package converter import ( "io" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/model/formater" ) func Convert(path string, reader io.ReadCloser) (out io.ReadCloser, err error) { switch GetMimeType(path) { case "text/plain": out, err = formater.TxtFormater(reader) case "text/org": out, err = formater.TxtFormater(reader) case "text/markdown": out, err = formater.TxtFormater(reader) case "application/x-form": out, err = formater.TxtFormater(reader) case "application/pdf": out, err = formater.PdfFormater(reader) case "application/excel": out, err = formater.OfficeFormater(reader) case "application/powerpoint": out, err = formater.OfficeFormater(reader) case "application/vnd.ms-powerpoint": out, err = formater.OfficeFormater(reader) case "application/word": out, err = formater.OfficeFormater(reader) case "application/msword": out, err = formater.OfficeFormater(reader) default: err = ErrNotImplemented } return out, err } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/daemon.go ================================================ package plg_search_sqlitefts import ( "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" ) func init() { Hooks.Register.Onload(func() { for i := 0; i < SEARCH_PROCESS_PAR(); i++ { go runner() } }) } func runner() { for { if SEARCH_ENABLE() == false { time.Sleep(60 * 5 * time.Second) continue } crwlr := NextCrawler() if crwlr == nil { time.Sleep(5 * time.Second) continue } crwlr.mu.Lock() crwlr.Run() crwlr.mu.Unlock() } } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/daemon_state.go ================================================ package plg_search_sqlitefts import ( "container/heap" "sync" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) var DaemonState = daemonState{ idx: make([]Crawler, 0), n: -1, } type daemonState struct { idx []Crawler n int mu sync.RWMutex } type Crawler struct { Id string FoldersUnknown HeapDoc CurrentPhase string Backend IBackend State indexer.Index mu sync.Mutex } func NewCrawler(app *App) (Crawler, error) { id := GenerateID(app.Session) idpath := id if SEARCH_SHARED_INDEX() { idpath = "" } s := Crawler{ Id: id, Backend: app.Backend, State: indexer.NewIndex(idpath), FoldersUnknown: make(HeapDoc, 0, 1), } if err := s.State.Init(); err != nil { return s, err } heap.Init(&s.FoldersUnknown) return s, nil } func GetCrawler(app *App) *Crawler { id := GenerateID(app.Session) DaemonState.mu.RLock() defer DaemonState.mu.RUnlock() for i := len(DaemonState.idx) - 1; i >= 0; i-- { if id == DaemonState.idx[i].Id { return &DaemonState.idx[i] } } return nil } func NextCrawler() *Crawler { DaemonState.mu.Lock() defer DaemonState.mu.Unlock() if len(DaemonState.idx) == 0 { return nil } if DaemonState.n >= len(DaemonState.idx)-1 || DaemonState.n < 0 { DaemonState.n = 0 } else { DaemonState.n = DaemonState.n + 1 } return &DaemonState.idx[DaemonState.n] } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/events.go ================================================ package plg_search_sqlitefts import ( "container/heap" "context" "path/filepath" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" ) /* * We're listening to what the user is doing to hint the crawler over * what needs to be updated in priority, what file got updated and would need * to be reindexed, what should disappear from the index, .... * This way we can fine tune how full text search is behaving */ type FileHook struct{} func (this FileHook) Ls(ctx *App, path string) error { if this.record(ctx) { go DaemonState.HintLs(ctx, path) } return nil } func (this FileHook) Cat(ctx *App, path string) error { if this.record(ctx) { go DaemonState.HintLs(ctx, filepath.Dir(path)+"/") } return nil } func (this FileHook) Stat(ctx *App, path string) error { return nil } func (this FileHook) Mkdir(ctx *App, path string) error { if this.record(ctx) { go func() { DaemonState.HintLs(ctx, filepath.Dir(path)+"/") DaemonState.HintLs(ctx, path) }() } return nil } func (this FileHook) Rm(ctx *App, path string) error { if this.record(ctx) { go DaemonState.HintRm(ctx, path) } return nil } func (this FileHook) Mv(ctx *App, from string, to string) error { if this.record(ctx) { go func() { DaemonState.HintRm(ctx, filepath.Dir(from)+"/") DaemonState.HintLs(ctx, to+"/") DaemonState.HintLs(ctx, filepath.Dir(to)+"/") }() } return nil } func (this FileHook) Save(ctx *App, path string) error { if this.record(ctx) { go func() { DaemonState.HintLs(ctx, filepath.Dir(path)+"/") DaemonState.HintFile(ctx, path) }() } return nil } func (this FileHook) Touch(ctx *App, path string) error { if this.record(ctx) { go func() { DaemonState.HintLs(ctx, filepath.Dir(path)+"/") DaemonState.HintFile(ctx, path) }() } return nil } func (this FileHook) record(ctx *App) bool { if ctx.Context.Value("AUDIT") == false { return false } return true } func (this *daemonState) HintLs(app *App, path string) { id := GenerateID(app.Session) this.mu.Lock() defer this.mu.Unlock() // try to find the search indexer among the existing ones for i := len(this.idx) - 1; i >= 0; i-- { if id != this.idx[i].Id { continue } alreadyHasPath := false for j := 0; j < len(this.idx[i].FoldersUnknown); j++ { if this.idx[i].FoldersUnknown[j].Path == path { alreadyHasPath = true break } } if alreadyHasPath == false { heap.Push(&this.idx[i].FoldersUnknown, &Document{ Type: "directory", Path: path, InitialPath: path, Name: filepath.Base(path), }) } return } // Having all indexers running in memory could be expensive => instead we're cycling a pool search_process_max := SEARCH_PROCESS_MAX() lenIdx := len(this.idx) if lenIdx > 0 && search_process_max > 0 && lenIdx > (search_process_max-1) { toDel := this.idx[0 : lenIdx-(search_process_max-1)] for i := range toDel { toDel[i].Close() } this.idx = this.idx[lenIdx-(search_process_max-1):] } // instantiate the new indexer app.Context = context.Background() crawlerBackend, err := app.Backend.Init(app.Session, app) if err != nil { Log.Warning("plg_search_sqlitefs::init message=cannot_create_crawler err=%s", err.Error()) return } app.Backend = crawlerBackend s, err := NewCrawler(app) if err != nil { Log.Warning("plg_search_sqlitefs::init message=cannot_create_crawler err=%s", err.Error()) return } defer func() { // recover from panic if one occurred. Set err to nil otherwise. if r := recover(); r != nil { name := "na" for _, el := range crawlerBackend.LoginForm().Elmnts { if el.Name == "type" { name = el.Value.(string) } } Log.Error("plg_search_sqlitefs::panic backend=\"%s\" recover=\"%s\"", name, r) } }() heap.Push(&s.FoldersUnknown, &Document{ Type: "directory", Path: path, InitialPath: path, Name: filepath.Base(path), }) this.idx = append(this.idx, s) } func (this *daemonState) HintRm(app *App, path string) { id := GenerateID(app.Session) this.mu.RLock() for i := len(this.idx) - 1; i >= 0; i-- { if id != this.idx[i].Id { continue } if op, err := this.idx[i].State.Change(); err == nil { op.RemoveAll(path) op.Commit() } break } this.mu.RUnlock() } func (this *daemonState) HintFile(app *App, path string) { id := GenerateID(app.Session) this.mu.RLock() for i := len(this.idx) - 1; i >= 0; i-- { if id != this.idx[i].Id { continue } if op, err := this.idx[i].State.Change(); err == nil { op.IndexTimeClear(path) op.Commit() } break } this.mu.RUnlock() } func (this *daemonState) Reset() { this.mu.Lock() for i := range this.idx { this.idx[i].Close() } this.idx = make([]Crawler, 0) this.n = -1 this.mu.Unlock() } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase.go ================================================ package plg_search_sqlitefts import ( "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func (this *Crawler) Run() { if this.CurrentPhase == "" { this.CurrentPhase = PHASE_EXPLORE } Log.Debug("search::phase Execute %s", this.CurrentPhase) cycleExecute := func(fn func(indexer.Manager) bool) { op, err := this.State.Change() if err != nil { Log.Warning("search::index cycle_begin (%+v)", err) time.Sleep(5 * time.Second) } stopTime := time.Now().Add(time.Duration(CYCLE_TIME()) * time.Second) for { if fn(op) == false { break } if time.Now().After(stopTime) { this.Next() break } } if err = op.Commit(); err != nil { Log.Warning("search::index cycle_commit (%+v)", err) } } if this.CurrentPhase == PHASE_EXPLORE { cycleExecute(this.Discover) return } else if this.CurrentPhase == PHASE_INDEXING { cycleExecute(this.Indexing) return } else if this.CurrentPhase == PHASE_MAINTAIN { cycleExecute(this.Consolidate) return } else if this.CurrentPhase == PHASE_PAUSE { cycleExecute(this.Pause) return } Log.Error("plg_search_sqlitefts::error message=unknown_phase phase=%s", this.CurrentPhase) return } func (this *Crawler) Close() error { return this.State.Close() } func (this *Crawler) Next() { switch this.CurrentPhase { case PHASE_EXPLORE: this.CurrentPhase = PHASE_INDEXING case PHASE_INDEXING: this.CurrentPhase = PHASE_MAINTAIN case PHASE_MAINTAIN: this.CurrentPhase = PHASE_PAUSE case PHASE_PAUSE: this.CurrentPhase = PHASE_EXPLORE } } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase_explore.go ================================================ package plg_search_sqlitefts import ( "container/heap" "os" "path/filepath" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func (this *Crawler) Discover(tx indexer.Manager) bool { doc := this.DiscoverPop() if doc == nil { this.Next() return false } Log.Debug("search::debug phase=discovery path=%s", doc.Path) files, err := this.Backend.Ls(doc.Path) if err != nil { this.CurrentPhase = PHASE_PAUSE return true } return this.DiscoverPush(doc, files, tx) } func (this *Crawler) DiscoverPop() *Document { if this.FoldersUnknown.Len() == 0 { return nil } return heap.Pop(&this.FoldersUnknown).(*Document) } func (this *Crawler) DiscoverPush(doc *Document, files []os.FileInfo, tx indexer.Manager) bool { existing := make(map[string]bool, len(files)) excluded := SEARCH_EXCLUSION() for i := range files { f := files[i] name := f.Name() existing[name] = true skip := false for i := 0; i < len(excluded); i++ { if name == excluded[i] || strings.Contains(doc.Path, excluded[i]) { skip = true } } if skip { continue } if f.IsDir() { var performPush bool = false p := filepath.Join(doc.Path, name) + "/" if err := dbInsert(doc.Path, f, tx); err == nil { performPush = true } else if err == indexer.ErrConstraint { performPush = func(path string) bool { tm, err := tx.IndexTimeGet(p) if err != nil { Log.Warning("search::discovery unknown_path (%v)", err) return false } if time.Now().Add(time.Duration(-SEARCH_REINDEX()) * time.Hour).Before(tm) { return false } if err = tx.IndexTimeUpdate(p, time.Now()); err != nil { Log.Warning("search::discovery insertion_failed (%v)", err) return false } return true }(p) } else { Log.Error("search::indexing insert_index (%v)", err) } if performPush { heap.Push(&this.FoldersUnknown, &Document{ Type: "directory", Name: name, Path: p, Size: f.Size(), ModTime: f.ModTime(), }) } } else { if err := dbUpsert(doc.Path, f, tx); err != nil { return false } } } if rows, err := tx.FindParent(doc.Path); err == nil { for rows.Next() { if r, err := rows.Value(); err == nil { if !existing[r.Name] { tx.RemoveAll(r.Path) } } } rows.Close() } return true } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase_indexing.go ================================================ package plg_search_sqlitefts import ( "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func (this *Crawler) Indexing(tx indexer.Manager) bool { rows, err := tx.FindNew(MAX_INDEXING_FSIZE(), strings.Split(INDEXING_EXT(), ",")) if err != nil { Log.Warning("search::insert index_query (%v)", err) return false } defer rows.Close() hasRows := false for rows.Next() { hasRows = true r, err := rows.Value() if err != nil { Log.Warning("search::indexing index_scan (%v)", err) return false } Log.Debug("search::debug phase=indexing path=%s", r.Path) if err = updateFile(r.Path, this.Backend, tx); err != nil { Log.Warning("search::indexing index_update (%v)", err) return false } } if hasRows == false { this.Next() return false } return true } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase_maintain.go ================================================ package plg_search_sqlitefts import ( "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func (this *Crawler) Consolidate(tx indexer.Manager) bool { rows, err := tx.FindBefore(time.Now().Add(-time.Duration(SEARCH_REINDEX())*time.Hour - time.Duration(CYCLE_TIME())*time.Second).UTC()) if err != nil { if err == indexer.ErrNoRows { this.Next() return false } this.CurrentPhase = "" return false } defer rows.Close() hasRows := false for rows.Next() { hasRows = true r, err := rows.Value() if err != nil { Log.Warning("search::index db_stale (%v)", err) return false } Log.Debug("search::debug phase=maintenance path=%s", r.Path) if r.CType == "directory" { updateFolder(r.Path, this.Backend, tx) } else { updateFile(r.Path, this.Backend, tx) } } if hasRows == false { this.Next() return false } return true } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase_pause.go ================================================ package plg_search_sqlitefts import ( "time" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func (this *Crawler) Pause(tx indexer.Manager) bool { if this.FoldersUnknown.Len() == 0 { time.Sleep(10 * time.Second) } this.Next() return false } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/phase_utils.go ================================================ package plg_search_sqlitefts import ( "io/fs" "os" "path/filepath" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/converter" "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/indexer" ) func updateFile(path string, backend IBackend, tx indexer.Manager) error { if err := tx.IndexTimeUpdate(path, time.Now().UTC()); err != nil { return err } reader, err := backend.Cat(path) if err != nil { tx.RemoveAll(path) return err } defer reader.Close() convertedReader, err := converter.Convert(path, reader) if err != nil { return nil } defer convertedReader.Close() if err = tx.FileContentUpdate(path, convertedReader); err != nil { return err } return nil } func updateFolder(path string, backend IBackend, tx indexer.Manager) error { if err := tx.IndexTimeUpdate(path, time.Now().UTC()); err != nil { return err } // Fetch list of folders as in the remote filesystem currFiles, err := backend.Ls(path) if err != nil { tx.RemoveAll(path) return err } // Fetch FS as appear in our search cache rows, err := tx.FindParent(path) if err != nil { return err } defer rows.Close() previousFiles := make([]File, 0) for rows.Next() { r, err := rows.Value() if err != nil { return err } previousFiles = append(previousFiles, File{ FName: r.Name, FSize: r.Size, FPath: r.Path, }) } // Perform the DB operation to ensure previousFiles and currFiles are in sync // 1. Find the content that have been created and did not exist before for i := 0; i < len(currFiles); i++ { currFilenameAlreadyExist := false currFilename := currFiles[i].Name() for j := 0; j < len(previousFiles); j++ { if currFilename == previousFiles[j].Name() { if currFiles[i].Size() != previousFiles[j].Size() { if err = dbUpdate(path, currFiles[i], tx); err != nil { return err } break } currFilenameAlreadyExist = true break } } if currFilenameAlreadyExist == false { dbUpsert(path, currFiles[i], tx) } } // 2. Find the content that was existing before but got removed for i := 0; i < len(previousFiles); i++ { previousFilenameStillExist := false previousFilename := previousFiles[i].Name() for j := 0; j < len(currFiles); j++ { if previousFilename == currFiles[j].Name() { previousFilenameStillExist = true break } } if previousFilenameStillExist == false { p := filepath.Join(path, previousFiles[i].Name()) if previousFiles[i].IsDir() { p += "/" } tx.RemoveAll(p) } } return nil } func dbInsert(parent string, f os.FileInfo, tx indexer.Manager) error { return tx.FileCreate(f, parent, false) } func dbUpsert(parent string, f os.FileInfo, tx indexer.Manager) error { return tx.FileCreate(f, parent, true) } func dbUpdate(parent string, f fs.FileInfo, tx indexer.Manager) error { path := filepath.Join(parent, f.Name()) if f.IsDir() { path += "/" } return tx.FileMetaUpdate(path, f) } ================================================ FILE: server/plugin/plg_search_sqlitefts/crawler/types.go ================================================ package plg_search_sqlitefts import ( "strings" "time" ) const ( PHASE_EXPLORE = "PHASE_EXPLORE" PHASE_INDEXING = "PHASE_INDEXING" PHASE_MAINTAIN = "PHASE_MAINTAIN" PHASE_PAUSE = "PHASE_PAUSE" ) const MAX_HEAP_SIZE = 100000 type Document struct { Hash string `json:"-"` Type string `json:"type"` Name string `json:"name"` Path string `json:"path"` InitialPath string `json:"-"` Ext string `json:"ext"` ModTime time.Time `json:"time"` Size int64 `json:"size"` Content []byte `json:"content"` Priority int `json:"-"` } // https://golang.org/pkg/container/heap/ type HeapDoc []*Document func (h HeapDoc) Len() int { return len(h) } func (h HeapDoc) Less(i, j int) bool { if h[i].Priority != 0 || h[j].Priority != 0 { return h[i].Priority < h[j].Priority } scoreA := len(strings.Split(h[i].Path, "/")) / len(strings.Split(h[i].InitialPath, "/")) scoreB := len(strings.Split(h[j].Path, "/")) / len(strings.Split(h[j].InitialPath, "/")) return scoreA < scoreB } func (h HeapDoc) Swap(i, j int) { a := h[i] h[i] = h[j] h[j] = a } func (h *HeapDoc) Push(x interface{}) { if h.Len() < MAX_HEAP_SIZE { *h = append(*h, x.(*Document)) } } func (h *HeapDoc) Pop() interface{} { old := *h n := len(old) if n == 0 { return nil } x := old[n-1] *h = old[0 : n-1] return x } ================================================ FILE: server/plugin/plg_search_sqlitefts/index.go ================================================ package plg_search_sqlitefts import ( . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/crawler" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/workflow" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" ) func init() { Hooks.Register.SearchEngine(SearchEngine{}) Hooks.Register.WorkflowAction(StepIndexer{}) Hooks.Register.Onload(func() { if SEARCH_ENABLE() { Hooks.Register.AuthorisationMiddleware(FileHook{}) } }) } ================================================ FILE: server/plugin/plg_search_sqlitefts/indexer/error.go ================================================ package indexer import ( "database/sql" "fmt" "github.com/mickael-kerjean/filestash/server/pkg/sqlite" ) var ( ErrConstraint = fmt.Errorf("DB_CONSTRAINT_FAILED_ERROR") ErrNoRows = fmt.Errorf("NO_ROWS") ) func toErr(err error) error { if err == sql.ErrNoRows { return ErrNoRows } else if sqlite.IsConstraint(err) { return ErrConstraint } return err } ================================================ FILE: server/plugin/plg_search_sqlitefts/indexer/index.go ================================================ package indexer import ( "database/sql" "io" "io/fs" "path/filepath" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) type Index interface { Init() error Search(path string, q string) ([]IFile, error) Change() (Manager, error) Close() error } type Manager interface { FindParent(path string) (RowMapper, error) FindBefore(time time.Time) (RowMapper, error) FindNew(maxSize int, toOmit []string) (RowMapper, error) FileCreate(f fs.FileInfo, parent string, withUpsert bool) error FileContentUpdate(path string, f io.ReadCloser) error FileMetaUpdate(path string, f fs.FileInfo) error IndexTimeGet(path string) (time.Time, error) IndexTimeUpdate(path string, t time.Time) error IndexTimeClear(path string) error RemoveAll(path string) error Commit() error } func NewIndex(id string) Index { return &sqliteIndex{id, nil} } type sqliteIndex struct { id string db *sql.DB } func (this *sqliteIndex) Init() error { path := "fts_" + this.id + ".sql" if this.id == "" { path = "fts.sql" } db, err := sql.Open("sqlite3", GetAbsolutePath(FTS_PATH, path)+"?_journal_mode=wal") if err != nil { Log.Warning("search::init can't open database (%v)", err) return toErr(err) } this.db = db queryDB := func(sqlQuery string) error { stmt, err := db.Prepare(sqlQuery) if err != nil { Log.Warning("search::initschema prepare schema error(%v)", err) return toErr(err) } defer stmt.Close() _, err = stmt.Exec() if err != nil { Log.Warning("search::initschema execute error(%v)", err) return toErr(err) } return nil } if queryDB("CREATE TABLE IF NOT EXISTS file(path VARCHAR(1024) PRIMARY KEY, filename VARCHAR(64), filetype VARCHAR(16), type VARCHAR(16), parent VARCHAR(1024), size INTEGER, modTime timestamp, indexTime timestamp DEFAULT NULL);"); err != nil { return err } if queryDB("CREATE INDEX IF NOT EXISTS idx_file_index_time ON file(indexTime) WHERE indexTime IS NOT NULL;"); err != nil { return err } if queryDB("CREATE INDEX IF NOT EXISTS idx_file_parent ON file(parent);"); err != nil { return err } if queryDB("CREATE VIRTUAL TABLE IF NOT EXISTS file_index USING fts5(path UNINDEXED, filename, filetype, content, tokenize = 'porter');"); err != nil { return err } if queryDB("CREATE TRIGGER IF NOT EXISTS after_file_insert AFTER INSERT ON file BEGIN INSERT INTO file_index (path, filename, filetype) VALUES(new.path, new.filename, new.filetype); END;"); err != nil { return err } if queryDB("CREATE TRIGGER IF NOT EXISTS after_file_delete AFTER DELETE ON file BEGIN DELETE FROM file_index WHERE path = old.path; END;"); err != nil { return err } if queryDB("CREATE TRIGGER IF NOT EXISTS after_file_update_path UPDATE OF path ON file BEGIN UPDATE file_index SET path = new.path, filename = new.filename, filetype = new.filetype WHERE path = old.path; END;"); err != nil { return err } return nil } func (this sqliteIndex) Change() (Manager, error) { tx, err := this.db.Begin() if err != nil { return nil, toErr(err) } return sqliteQueries{tx}, nil } func (this sqliteIndex) Close() error { return this.db.Close() } type sqliteQueries struct { tx *sql.Tx } func (this sqliteQueries) Commit() error { return this.tx.Commit() } func (this sqliteQueries) IndexTimeGet(path string) (time.Time, error) { var t string if err := this.tx.QueryRow("SELECT indexTime FROM file WHERE path = ?", path).Scan(&t); err != nil { return time.Now(), toErr(err) } tm, err := time.Parse(time.RFC3339, t) if err != nil { return tm, toErr(err) } return tm, nil } func (this sqliteQueries) IndexTimeUpdate(path string, time time.Time) error { if _, err := this.tx.Exec("UPDATE file SET indexTime = ? WHERE path = ?", time, path); err != nil { return toErr(err) } return nil } func (this sqliteQueries) IndexTimeClear(path string) error { if _, err := this.tx.Exec("UPDATE file SET indexTime = NULL WHERE path = ?", path); err != nil { return toErr(err) } return nil } type Record struct { Name string Path string Size int64 CType string } type RowMapper struct { rows *sql.Rows } func (this *RowMapper) Next() bool { return this.rows.Next() } func (this *RowMapper) Value() (Record, error) { var r Record if err := this.rows.Scan(&r.Name, &r.CType, &r.Path, &r.Size); err != nil { return r, toErr(err) } return r, nil } func (this *RowMapper) Close() error { return this.rows.Close() } func (this sqliteQueries) FindNew(maxSize int, filetypes []string) (RowMapper, error) { for i := 0; i < len(filetypes); i++ { filetypes[i] = "'" + strings.TrimSpace(filetypes[i]) + "'" } rows, err := this.tx.Query( "SELECT filename, type, path, size FROM file WHERE ("+ " type = 'file' AND size < ? AND filetype IN ("+strings.Join(filetypes, ",")+") AND indexTime IS NULL "+ ") LIMIT 2", maxSize, ) if err != nil { return RowMapper{}, toErr(err) } return RowMapper{rows: rows}, nil } func (this sqliteQueries) FindBefore(t time.Time) (RowMapper, error) { rows, err := this.tx.Query( "SELECT filename, type, path, size FROM file WHERE indexTime < ? ORDER BY indexTime DESC LIMIT 5", t, ) if err != nil { return RowMapper{}, toErr(err) } return RowMapper{rows: rows}, nil } func (this sqliteQueries) FindParent(path string) (RowMapper, error) { rows, err := this.tx.Query("SELECT filename, type, path, size FROM file WHERE parent = ?", path) if err != nil { return RowMapper{}, err } return RowMapper{rows: rows}, nil } func (this sqliteQueries) FileMetaUpdate(path string, f fs.FileInfo) error { _, err := this.tx.Exec( "UPDATE file SET size = ?, modTime = ? indexTime = NULL WHERE path = ?", f.Size(), f.ModTime(), path, ) return toErr(err) } func (this sqliteQueries) FileContentUpdate(path string, reader io.ReadCloser) error { content, err := io.ReadAll(reader) if err != nil { return toErr(err) } if _, err := this.tx.Exec("UPDATE file_index SET content = ? WHERE path = ?", content, path); err != nil { return toErr(err) } return nil } func (this sqliteQueries) FileCreate(f fs.FileInfo, parentPath string, withUpsert bool) (err error) { name := f.Name() path := filepath.Join(parentPath, f.Name()) if f.IsDir() { sql := "INSERT INTO file(path, parent, filename, type, size, modTime, indexTime) VALUES(?, ?, ?, ?, ?, ?, ?)" if withUpsert { sql += " ON CONFLICT(path) DO UPDATE SET size=excluded.size, modTime=excluded.modTime" + " WHERE excluded.modTime != file.modTime OR excluded.size != file.size" } _, err = this.tx.Exec(sql, path+"/", parentPath, name, "directory", f.Size(), f.ModTime(), time.Now()) } else { sql := "INSERT INTO file(path, parent, filename, type, size, modTime, indexTime, filetype) VALUES(?, ?, ?, ?, ?, ?, ?, ?)" if withUpsert { sql += " ON CONFLICT(path) DO UPDATE SET size=excluded.size, modTime=excluded.modTime, indexTime=NULL" + " WHERE excluded.modTime != file.modTime OR excluded.size != file.size" } _, err = this.tx.Exec(sql, path, parentPath, name, "file", f.Size(), f.ModTime(), nil, strings.TrimPrefix(filepath.Ext(name), ".")) } return toErr(err) } func (this sqliteQueries) Remove(path string) error { if _, a := this.tx.Exec("DELETE FROM file WHERE path = ?", path); a != nil { return toErr(a) } return nil } func (this sqliteQueries) RemoveAll(path string) error { if _, a := this.tx.Exec("DELETE FROM file WHERE path >= ? AND path < ?", path, path+"~"); a != nil { return toErr(a) } return nil } ================================================ FILE: server/plugin/plg_search_sqlitefts/indexer/query.go ================================================ package indexer import ( "path/filepath" "regexp" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func (this sqliteIndex) Search(path string, q string) ([]IFile, error) { files := []IFile{} rows, err := this.db.Query( "SELECT type, path, size, modTime FROM file WHERE path IN ("+ " SELECT path FROM file_index WHERE file_index MATCH ? AND path > ? AND path < ?"+ " ORDER BY rank LIMIT 50000"+ ")", regexp.MustCompile(`(\.|\-)`).ReplaceAllString(q, "\"$1\""), path, path+"~", ) if err != nil { Log.Warning("search::query DBQuery (%s)", err.Error()) return files, ErrNotReachable } defer rows.Close() for rows.Next() { f := File{} var t string if err = rows.Scan(&f.FType, &f.FPath, &f.FSize, &t); err != nil { Log.Warning("search::query scan (%s)", err.Error()) return files, ErrNotReachable } if tm, err := time.Parse(time.RFC3339, t); err == nil { f.FTime = tm.Unix() * 1000 } f.FName = filepath.Base(f.FPath) files = append(files, f) } return files, nil } ================================================ FILE: server/plugin/plg_search_sqlitefts/query.go ================================================ package plg_search_sqlitefts import ( . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/crawler" ) type SearchEngine struct{} func (this SearchEngine) Query(app App, path string, keyword string) ([]IFile, error) { DaemonState.HintLs(&app, path) s := GetCrawler(&app) if s == nil { return nil, ErrNotReachable } if path == "" { path = "/" } return s.State.Search(path, keyword) } ================================================ FILE: server/plugin/plg_search_sqlitefts/workflow/index.go ================================================ package plg_search_sqlitefts import ( "context" "encoding/json" "sync" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/ctrl" . "github.com/mickael-kerjean/filestash/server/model" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/config" . "github.com/mickael-kerjean/filestash/server/plugin/plg_search_sqlitefts/crawler" ) type StepIndexer struct{} var runningIndexers sync.Map func (this StepIndexer) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: "tools/index", Title: "Refresh Search Index", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "token", Type: "password", }, { Name: "path", Type: "text", Placeholder: "default: /", }, }, }, } } func (this StepIndexer) Execute(params map[string]string, input map[string]string) (map[string]string, error) { str, err := DecryptString(SECRET_KEY_DERIVATE_FOR_USER, params["token"]) if err != nil { Log.Warning("plg_search_sqlitefts::workflow message=invalid_token err=%s", err.Error()) return input, err } session := map[string]string{} if err = json.Unmarshal([]byte(str), &session); err != nil { Log.Warning("plg_search_sqlitefts::workflow message=invalid_session err=%s", err.Error()) return input, err } id := GenerateID(session) if _, loaded := runningIndexers.LoadOrStore(id, struct{}{}); loaded { return input, nil } defer runningIndexers.Delete(id) app := &App{ Context: context.Background(), Session: session, } backend, err := NewBackend(app, app.Session) if err != nil { Log.Warning("plg_search_sqlitefts::workflow message=cannot_create_backend err=%s", err.Error()) return input, err } app.Backend = backend DaemonState.HintLs(app, EnforceDirectory(params["path"])) crwlr := GetCrawler(app) tr, err := crwlr.State.Change() if err != nil { Log.Warning("plg_search_sqlitefts::workflow message=cannot_begin_tx err=%s", err.Error()) return input, err } n := SEARCH_PROCESS_PAR() cond := sync.NewCond(&sync.Mutex{}) inflight := 0 var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go func() { defer wg.Done() cond.L.Lock() for crwlr.FoldersUnknown.Len() > 0 || inflight > 0 { for crwlr.FoldersUnknown.Len() == 0 && inflight > 0 { cond.Wait() } if crwlr.FoldersUnknown.Len() == 0 { break } doc := crwlr.DiscoverPop() inflight++ cond.L.Unlock() path, err := ctrl.PathBuilder(app, doc.Path) if err != nil { Log.Warning("plg_search_sqlitefts::workflow message=path_builder path=%s err=%s", doc.Path, err.Error()) break } files, err := crwlr.Backend.Ls(path) if err != nil { Log.Warning("plg_search_sqlitefts::workflow message=ls_error path=%s err=%s", doc.Path, err.Error()) } cond.L.Lock() if err == nil { crwlr.DiscoverPush(doc, files, tr) } inflight-- cond.Broadcast() } cond.L.Unlock() }() } wg.Wait() if err = tr.Commit(); err != nil { Log.Warning("plg_search_sqlitefts::workflow message=commit_error err=%s", err.Error()) } return input, nil } ================================================ FILE: server/plugin/plg_search_stateless/config.go ================================================ package plg_search_stateless import ( "fmt" . "github.com/mickael-kerjean/filestash/server/common" "time" ) var ( SEARCH_TIMEOUT func() time.Duration ) func init() { SEARCH_TIMEOUT = func() time.Duration { return time.Duration(Config.Get("features.search.explore_timeout").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "explore_timeout" f.Type = "number" f.Default = 1500 f.Description = `When full text search is disabled, the search engine recursively explore directories to find results. Exploration can't last longer than what is configured here` f.Placeholder = fmt.Sprintf("Default: %dms", f.Default) return f }).Int()) * time.Millisecond } Hooks.Register.Onload(func() { SEARCH_TIMEOUT() }) } ================================================ FILE: server/plugin/plg_search_stateless/index.go ================================================ package plg_search_stateless import ( "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.SearchEngine(StatelessSearch{}) } type PathQuandidate struct { Path string Score int } type StatelessSearch struct{} func (this StatelessSearch) Query(app App, path string, keyword string) ([]IFile, error) { files := make([]IFile, 0) toVisit := []PathQuandidate{PathQuandidate{path, 0}} MAX_SEARCH_TIME := SEARCH_TIMEOUT() for start := time.Now(); time.Since(start) < MAX_SEARCH_TIME; { if len(toVisit) == 0 { return files, nil } currentPath := toVisit[0] if len(toVisit) == 0 { toVisit = make([]PathQuandidate, 0) } else { toVisit = toVisit[1:] } // Ls on the directory f, err := app.Backend.Ls(currentPath.Path) if err != nil { continue } score1 := scoreBoostForFilesInDirectory(f) for i := 0; i < len(f); i++ { name := f[i].Name() if isAMatch := IsSearchQueryMatchingFilename( []rune(strings.ToLower(name)), []rune(strings.ToLower(keyword)), ); isAMatch { files = append(files, File{ FName: name, FType: func() string { if f[i].IsDir() { return "directory" } return "file" }(), FSize: f[i].Size(), FTime: f[i].ModTime().Unix() * 1000, FPath: func() string { p := JoinPath(currentPath.Path, name) if f[i].IsDir() { p = p + "/" } return p }(), }) } // follow directories fullpath := currentPath.Path + name + "/" relativePath := strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(fullpath, path), "/")) score2 := scoreBoostOnDepth(relativePath) * 2 if f[i].IsDir() { score := scoreBoostForPath(relativePath) if score < 0 { continue } score += score1 score += score2 score += currentPath.Score t := make([]PathQuandidate, len(toVisit)+1) k := 0 for k = 0; k < len(toVisit); k++ { if score > toVisit[k].Score { break } t[k] = toVisit[k] } t[k] = PathQuandidate{fullpath, score} for k = k + 1; k < len(toVisit)+1; k++ { t[k] = toVisit[k-1] } toVisit = t } } } return files, nil } ================================================ FILE: server/plugin/plg_search_stateless/scoring.go ================================================ package plg_search_stateless import ( "os" "path/filepath" "strings" ) func scoreBoostForPath(p string) int { b := strings.ToLower(filepath.Base(p)) // some path are garbage we don't want to explore unless there's nothing else to do if b == "node_modules" { return -100 } else if strings.HasPrefix(b, ".") { return -10 } // not all path are equally interesting, we bump the score of what we thing is interesting score := 0 if strings.Contains(b, "document") { score += 3 } else if strings.Contains(b, "project") { score += 3 } else if strings.Contains(b, "home") { score += 3 } else if strings.Contains(b, "note") { score += 3 } return score } func scoreBoostForFilesInDirectory(f []os.FileInfo) int { s := 0 for i := 0; i < len(f); i++ { name := f[i].Name() if f[i].IsDir() == false { if strings.HasSuffix(name, ".org") { s += 2 } else if strings.HasSuffix(name, ".pdf") { s += 1 } else if strings.HasSuffix(name, ".doc") || strings.HasSuffix(name, ".docx") { s += 1 } else if strings.HasSuffix(name, ".md") { s += 1 } else if strings.HasSuffix(name, ".pdf") { s += 1 } } if s > 4 { return 4 } } return s } func scoreBoostOnDepth(p string) int { return -strings.Count(p, "/") } func IsSearchQueryMatchingFilename(strRune []rune, patternRune []rune) bool { if len(patternRune) == 0 { return true } else if patternRune[0] == '*' { return IsSearchQueryMatchingFilename(strRune, patternRune[1:]) } dumbMatch := func(s []rune, p []rune) bool { currPattern := 0 moveCursor := false i := 0 for i = 0; i < len(s); i++ { if moveCursor { currPattern += 1 if currPattern >= len(p) { break } } if p[currPattern] == '*' { return IsSearchQueryMatchingFilename(s[i:], p[currPattern:]) } else if s[i] == p[currPattern] { moveCursor = true } else { return false } } currPattern += 1 if currPattern <= len(p)-1 { if p[currPattern] == '$' && i == len(s) { return true } return false } return true } for i := 0; i < len(strRune); i++ { if strRune[i] == patternRune[0] { if dumbMatch(strRune[i:], patternRune) == true { return true } } } return false } ================================================ FILE: server/plugin/plg_security_scanner/index.go ================================================ package plg_security_scanner import ( "bytes" "encoding/base64" "github.com/gorilla/mux" . "github.com/mickael-kerjean/filestash/server/common" "io" "math/rand" "net/http" "strings" ) var ( gzipBomb *bytes.Buffer billionsOfLol *bytes.Buffer plugin_enable func() bool ) func init() { plugin_enable = func() bool { return Config.Get("features.protection.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Default = true f.Name = "enable" f.Type = "boolean" f.Target = []string{} f.Description = "Enable/Disable active protection against scanners" f.Placeholder = "Default: true" return f }).Bool() } b, err := base64.StdEncoding.DecodeString("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") if err != nil { return } gzipBomb = bytes.NewBuffer(b) b, err = base64.StdEncoding.DecodeString("PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwhRE9DVFlQRSBsb2x6IFsKIDwhRU5USVRZIGxvbCAibG9sIj4KIDwhRUxFTUVOVCBsb2x6ICgjUENEQVRBKT4KIDwhRU5USVRZIGxvbDEgIiZsb2w7JmxvbDsmbG9sOyZsb2w7JmxvbDsmbG9sOyZsb2w7JmxvbDsmbG9sOyZsb2w7Ij4KIDwhRU5USVRZIGxvbDIgIiZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyZsb2wxOyI+CiA8IUVOVElUWSBsb2wzICImbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsmbG9sMjsiPgogPCFFTlRJVFkgbG9sNCAiJmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7JmxvbDM7Ij4KIDwhRU5USVRZIGxvbDUgIiZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyZsb2w0OyI+CiA8IUVOVElUWSBsb2w2ICImbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsmbG9sNTsiPgogPCFFTlRJVFkgbG9sNyAiJmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7JmxvbDY7Ij4KIDwhRU5USVRZIGxvbDggIiZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyZsb2w3OyI+CiA8IUVOVElUWSBsb2w5ICImbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsmbG9sODsiPgpdPgo8bG9sej4mbG9sOTs8L2xvbHo+") if err != nil { return } billionsOfLol = bytes.NewBuffer(b) Hooks.Register.HttpEndpoint(func(r *mux.Router) error { if plugin_enable() == false { return nil } // DEFAULT r.HandleFunc("/index.php", WelcomePackHandle) r.PathPrefix("/html/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/webdav/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/www/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/MAMP/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/xampp/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/web/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/scripts/").Handler(http.HandlerFunc(WelcomePackHandle)) // CMS r.PathPrefix("/blog/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/cms/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wordpress/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp-content/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/wp-config.php", WelcomePackHandle) r.HandleFunc("/wp-login.php", WelcomePackHandle) r.PathPrefix("/wp1/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp2/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp3/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp4/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp5/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp6/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp7/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/wp8/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/images/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/libraries/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/administrator/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/components/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/templates/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/includes/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/modules/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/plugins/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/drupal/", WelcomePackHandle) r.HandleFunc("/Drupal.php", WelcomePackHandle) r.PathPrefix("/core/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/web.config", WelcomePackHandle) r.HandleFunc("/server.php", WelcomePackHandle) r.HandleFunc("/composer.json", WelcomePackHandle) r.PathPrefix("/cacti/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/thinkphp/").Handler(http.HandlerFunc(WelcomePackHandle)) // SQL r.PathPrefix("/phpmyadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/pma/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/phpMyAdmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/mysqladmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/sql/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/myadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/mysql/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/db/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/phpmy/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/phppma/").Handler(http.HandlerFunc(WelcomePackHandle)) // OTHER r.HandleFunc("/echo.php", WelcomePackHandle) r.HandleFunc("/composer.php", WelcomePackHandle) r.HandleFunc("/uploader.php", WelcomePackHandle) r.HandleFunc("/shell.php", WelcomePackHandle) r.HandleFunc("/freenode-proxy-checker.txt", WelcomePackHandle) r.HandleFunc("/cmd.php", WelcomePackHandle) r.HandleFunc("/muhstiks.php", WelcomePackHandle) r.HandleFunc("/muhstik.php", WelcomePackHandle) r.HandleFunc("/jmx-console", WelcomePackHandle) r.HandleFunc("/status.php", WelcomePackHandle) r.PathPrefix("/TP/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/HNAP1/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/manager/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/program/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/shopdb/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/programs/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/jenkins/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/w00tw00t.at.blackhats.romanian.anti-sec:)", WelcomePackHandle) r.HandleFunc("/judge.php", WelcomePackHandle) r.HandleFunc("/muieblackcat", WelcomePackHandle) r.HandleFunc("/.env", WelcomePackHandle) r.HandleFunc("/log", WelcomePackHandle) r.HandleFunc("/configs", WelcomePackHandle) r.HandleFunc("/config", WelcomePackHandle) r.HandleFunc("/cfg", WelcomePackHandle) r.HandleFunc("/gs", WelcomePackHandle) r.HandleFunc("/gsProvision", WelcomePackHandle) r.HandleFunc("/overrides", WelcomePackHandle) r.HandleFunc("/polycom", WelcomePackHandle) r.HandleFunc("/spa.xml", WelcomePackHandle) r.HandleFunc("/yealink", WelcomePackHandle) r.HandleFunc("/help.php", WelcomePackHandle) r.HandleFunc("/java.php", WelcomePackHandle) r.HandleFunc("/_query.php", WelcomePackHandle) r.HandleFunc("/test.php", WelcomePackHandle) r.HandleFunc("/db_cts.php", WelcomePackHandle) r.HandleFunc("/db_pma.php", WelcomePackHandle) r.HandleFunc("/logon.php", WelcomePackHandle) r.HandleFunc("/help-e.php", WelcomePackHandle) r.HandleFunc("/license.php", WelcomePackHandle) r.HandleFunc("/log.php", WelcomePackHandle) r.HandleFunc("/hell.php", WelcomePackHandle) r.HandleFunc("/pmd_online.php", WelcomePackHandle) r.HandleFunc("/x.php", WelcomePackHandle) r.HandleFunc("/htdocs.php", WelcomePackHandle) r.HandleFunc("/b.php", WelcomePackHandle) r.HandleFunc("/desktop.ini.php", WelcomePackHandle) r.HandleFunc("/z.php", WelcomePackHandle) r.HandleFunc("/lala.php", WelcomePackHandle) r.HandleFunc("/lala-dpr.php", WelcomePackHandle) r.HandleFunc("/wpc.php", WelcomePackHandle) r.HandleFunc("/wpo.php", WelcomePackHandle) r.HandleFunc("/t6nv.php", WelcomePackHandle) r.HandleFunc("/text.php", WelcomePackHandle) r.HandleFunc("/muhstik2.php", WelcomePackHandle) r.HandleFunc("/muhstik-dpr.php", WelcomePackHandle) r.HandleFunc("/lol.php", WelcomePackHandle) r.HandleFunc("/cmv.php", WelcomePackHandle) r.HandleFunc("/cmdd.php", WelcomePackHandle) r.HandleFunc("/knal.php", WelcomePackHandle) r.HandleFunc("/appserv.php", WelcomePackHandle) r.HandleFunc("/d7.php", WelcomePackHandle) r.HandleFunc("/rxr.php", WelcomePackHandle) r.HandleFunc("/1x.php", WelcomePackHandle) r.HandleFunc("/home.php", WelcomePackHandle) r.HandleFunc("/undx.php", WelcomePackHandle) r.HandleFunc("/spider.php", WelcomePackHandle) r.HandleFunc("/payload.php", WelcomePackHandle) r.HandleFunc("/composers.php", WelcomePackHandle) r.HandleFunc("/izom.php", WelcomePackHandle) r.HandleFunc("/hue2.php", WelcomePackHandle) r.HandleFunc("/new_license.php", WelcomePackHandle) r.HandleFunc("/up.php", WelcomePackHandle) r.PathPrefix("/pmd/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/PMA/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/PMA2/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/pmamy/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/pmamy2/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/tools/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/phpma/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/php-my-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/websql/").Handler(http.HandlerFunc(WelcomePackHandle)) r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/xmlrpc.php", WelcomePackHandle) r.PathPrefix("/user/").Handler(http.HandlerFunc(WelcomePackHandle)) r.HandleFunc("/vuln.htm", WelcomePackHandle) r.HandleFunc("/webconfig.txt.php", WelcomePackHandle) return nil }) } func WelcomePackHandle(res http.ResponseWriter, req *http.Request) { Log.Info("Attack attempt %s %s %s", req.RemoteAddr, req.URL.String(), req.Header.Get("User-Agent")) r := rand.Intn(100) if r < 5 { res.Header().Set("Content-Length", "1000") res.Write([]byte("")) } else if r < 10 { res.Header().Set("Content-Length", "10000000000000") io.Copy(res, billionsOfLol) } else if r < 15 { res.Header().Set("Content-Encoding", "gzip") res.Header().Set("Content-Type", "text/html") res.Write([]byte("WAZAAAA")) } else if r < 20 { res.Header().Set("Content-Encoding", "gzip") res.Header().Set("Content-Type", "text/html") io.Copy(res, billionsOfLol) } else if r < 25 { res.Header().Set("Content-Language", "en-US\u000bContent‑Encoding: gzip") io.Copy(res, gzipBomb) } else if r < 30 { res.Header().Set("Content‑Encoding", "gzip") io.Copy(res, gzipBomb) } else if r < 35 { res.Header().Set("Content-Type", "application/json") res.Write([]byte(strings.Repeat("[", 10000) + `"WAZAAAAA"` + strings.Repeat("]", 10000))) } else if r < 40 { res.Header().Set("Content-Type", "application/json") res.Write([]byte(`{"response"꞉ "success"}`)) // not an ASCII colon -> not valid json } else if r < 45 { req.URL.Host = "127.0.0.1" if req.URL.Scheme == "" { req.URL.Scheme = "http" } http.Redirect(res, req, req.URL.String(), 301) } else if r < 50 { req.URL.Host = req.RemoteAddr if req.URL.Scheme == "" { req.URL.Scheme = "http" } http.Redirect(res, req, req.URL.String(), 301) } else if r < 55 { http.Redirect(res, req, "geo:37.786971,-122.399677", 301) } else if r < 60 { res.Header().Set("Content-Type", "text/html") res.Write([]byte(``)) } else if r < 65 { res.Header().Set("Content-Type", "text/html") res.Write([]byte(``)) } else if r < 85 { // xml bomb // https://en.wikipedia.org/wiki/Billion_laughs_attack if rand.Intn(100) < 50 { res.Header().Set("Content-Type", "text/xml") } else { res.Header().Set("Content-Type", "application/xml") } io.Copy(res, billionsOfLol) } else { res.Header().Set("Content-Encoding", "gzip") res.Header().Set("Content-Type", "text/html") io.Copy(res, gzipBomb) } } ================================================ FILE: server/plugin/plg_security_svg/index.go ================================================ package plg_security_svg import ( . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "regexp" ) var ( disable_svg func() bool ) func init() { disable_svg = func() bool { return Config.Get("features.protection.disable_svg").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Default = true f.Name = "disable_svg" f.Type = "boolean" f.Target = []string{} f.Description = "Disable the display of SVG documents" f.Placeholder = "Default: true" return f }).Bool() } Hooks.Register.Onload(func() { if disable_svg() == false { return } Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, bool, error) { if GetMimeType(req.URL.Query().Get("path")) != "image/svg+xml" { return reader, false, nil } else if disable_svg() == false { return reader, false, nil } // XSS (*res).Header().Set("Content-Security-Policy", "script-src 'none'; default-src 'none'; img-src 'self'") (*res).Header().Set("Content-Type", "text/plain") // XML bomb txt, _ := io.ReadAll(reader) if regexp.MustCompile("(?is)entity").Match(txt) { txt = []byte("") } return NewReadCloserFromBytes(txt), true, nil }) }) } ================================================ FILE: server/plugin/plg_starter_http/index.go ================================================ package plg_starter_http import ( "context" "fmt" "net/http" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/gorilla/mux" ) func init() { Hooks.Register.Starter(Start) } func Start(ctx context.Context, r *mux.Router) { Log.Info("[http] starting ...") port := Config.Get("general.port").Int() srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: r, } go func() { ensureAppHasBooted( fmt.Sprintf("http://127.0.0.1:%d%s", port, WithBase("/about")), fmt.Sprintf("[http] listening on :%d", port), ) <-ctx.Done() srv.Shutdown(context.Background()) }() if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { Log.Error("error: %v", err) } } func ensureAppHasBooted(address string, message string) { for i := 0; i < 10; i++ { if i > 10 { Log.Warning("[http] didn't boot") break } time.Sleep(250 * time.Millisecond) res, err := http.Get(address) if err != nil { continue } res.Body.Close() if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound { continue } Log.Info(message) break } } ================================================ FILE: server/plugin/plg_starter_http2/index.go ================================================ package plg_starter_http2 /* * In golang, HTTP2 server are written in the same way as HTTPS server, the only difference beeing * describe in the documentation: https://golang.org/src/net/http/doc.go#L81 * In our implementation, we use the 'TLSNextProto' trick */ import ( "context" "crypto/tls" "fmt" "net/http" "os" "path/filepath" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/common/ssl" "github.com/gorilla/mux" "golang.org/x/crypto/acme/autocert" ) var ( SSL_PATH string config_port func() int ) func init() { config_port = func() int { return Config.Get("general.port").Int() } Hooks.Register.Onload(func() { SSL_PATH = filepath.Join(GetAbsolutePath(CERT_PATH), "ssl") os.MkdirAll(SSL_PATH, os.ModePerm) }) Hooks.Register.Starter(func(ctx context.Context, r *mux.Router) { domain := Config.Get("general.host").String() Log.Info("[https] starting ...%s", domain) srv := &http.Server{ Addr: fmt.Sprintf(":%d", config_port()), Handler: r, TLSConfig: &DefaultTLSConfig, ErrorLog: NewNilLogger(), } switch domain { case "": TLSCert, roots, err := ssl.GenerateSelfSigned() if err != nil { return } srv.TLSConfig.Certificates = []tls.Certificate{TLSCert} HTTPClient.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{ RootCAs: roots, } HTTP.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{ RootCAs: roots, } default: mngr := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(domain), Cache: autocert.DirCache(SSL_PATH), } srv.TLSConfig.GetCertificate = mngr.GetCertificate } go func() { ensureAppHasBooted( fmt.Sprintf("https://127.0.0.1:%d/about", config_port()), fmt.Sprintf("[https] started"), ) <-ctx.Done() srv.Shutdown(context.Background()) }() if err := srv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { Log.Error("[https]: listen_serve %v", err) return } }) } func ensureAppHasBooted(address string, message string) { for i := 0; i < 10; i++ { if i > 10 { Log.Warning("[https] didn't boot") break } time.Sleep(250 * time.Millisecond) res, err := HTTPClient.Get(address) if err != nil { continue } res.Body.Close() if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound { continue } Log.Info(message) break } } ================================================ FILE: server/plugin/plg_starter_https/index.go ================================================ package plg_starter_https import ( "context" "crypto/tls" "fmt" "net/http" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/mickael-kerjean/filestash/server/common/ssl" "github.com/gorilla/mux" ) func init() { Hooks.Register.Starter(func(ctx context.Context, r *mux.Router) { domain := Config.Get("general.host").String() port := Config.Get("general.port").Int() Log.Info("[https] starting ...%s", domain) srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: r, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), TLSConfig: &DefaultTLSConfig, ErrorLog: NewNilLogger(), } TLSCert, roots, err := ssl.GenerateSelfSigned() if err != nil { return } srv.TLSConfig.Certificates = []tls.Certificate{TLSCert} HTTPClient.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{ RootCAs: roots, } HTTP.Transport.(*TransformedTransport).Orig.(*http.Transport).TLSClientConfig = &tls.Config{ RootCAs: roots, } go func() { ensureAppHasBooted( fmt.Sprintf("https://127.0.0.1:%d/about", port), fmt.Sprintf("[https] listening on :%d", port), ) <-ctx.Done() srv.Shutdown(context.Background()) }() if err := srv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { Log.Error("[https]: listen_serve %v", err) return } }) } func ensureAppHasBooted(address string, message string) { for i := 0; i < 10; i++ { if i > 10 { Log.Warning("[https] didn't boot") break } time.Sleep(250 * time.Millisecond) res, err := HTTPClient.Get(address) if err != nil { continue } res.Body.Close() if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound { continue } Log.Info(message) break } } ================================================ FILE: server/plugin/plg_starter_tor/index.go ================================================ package plg_starter_tor import ( "context" "net/http" "os" "time" . "github.com/mickael-kerjean/filestash/server/common" "github.com/cretz/bine/tor" "github.com/gorilla/mux" ) func init() { Hooks.Register.Starter(func(ctx context.Context, r *mux.Router) { torPath := GetAbsolutePath(CERT_PATH, "tor") os.MkdirAll(torPath, os.ModePerm) Log.Info("[tor] starting ...") t, err := tor.Start(nil, &tor.StartConf{ DataDir: torPath, }) if err != nil { Log.Error("[tor] Unable to start Tor: %v", err) return } defer t.Close() listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute) defer listenCancel() onion, err := t.Listen(listenCtx, &tor.ListenConf{Version3: true, RemotePorts: []int{80}}) if err != nil { Log.Error("[tor] Unable to create onion service: %v", err) return } defer onion.Close() srv := &http.Server{ Handler: r, } go func() { <-ctx.Done() srv.Shutdown(context.Background()) }() Log.Info("[tor] started http://%s.onion\n", onion.ID) Config.Get("features.server.tor_url").Set("http://" + onion.ID + ".onion") srv.Serve(onion) }) } ================================================ FILE: server/plugin/plg_video_thumbnail/index.go ================================================ package plg_video_thumbnail import ( "bytes" "encoding/base64" "fmt" "io" "net/http" "os" "os/exec" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) const ( VideoCachePath = "data/cache/video-thumbnail/" ) var plugin_enable = func() bool { return Config.Get("features.video.enable_thumbnail").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable_thumbnail" f.Type = "enable" f.Target = []string{} f.Description = "Enable/Disable on video thumbnail generation" f.Default = false return f }).Bool() } func init() { Hooks.Register.Onload(func() { if plugin_enable() == false { return } else if _, err := exec.LookPath("ffmpeg"); err != nil { Log.Warning("plg_video_thumbnail::init error=ffmpeg_not_installed") return } cachePath := GetAbsolutePath(VideoCachePath) os.RemoveAll(cachePath) os.MkdirAll(cachePath, os.ModePerm) Hooks.Register.Thumbnailer("video/mp4", &ffmpegThumbnail{}) Hooks.Register.Thumbnailer("video/x-matroska", &ffmpegThumbnail{}) Hooks.Register.Thumbnailer("video/x-msvideo", &ffmpegThumbnail{}) }) } type ffmpegThumbnail struct{} func (this *ffmpegThumbnail) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { var ( errBuff bytes.Buffer fullURL = strings.Replace( fmt.Sprintf("http://127.0.0.1:%d%s?%s", Config.Get("general.port").Int(), req.URL.Path, req.URL.RawQuery), "&thumbnail=true", "&origin=plg_video_thumbnail", 1, ) cacheName = "thumb_" + GenerateID(ctx.Session) + "_" + QuickHash(req.URL.Query().Get("path"), 10) + ".jpeg" cachePath = GetAbsolutePath(VideoCachePath, cacheName) ) reader.Close() thumbnail, err := os.OpenFile(cachePath, os.O_RDONLY, os.ModePerm) if err == nil { this.setHeader(res) return thumbnail, nil } cmd := exec.CommandContext(req.Context(), "ffmpeg", []string{ "-hide_banner", "-headers", "cookie: " + req.Header.Get("Cookie") + "\r\n", "-skip_frame", "nokey", "-i", fullURL, "-y", "-an", "-sn", "-vf", "thumbnail, scale=320:320: force_original_aspect_ratio=decrease", "-fps_mode", "passthrough", "-frames:v", "1", "-c:v", "mjpeg", cachePath, }...) cmd.Stderr = &errBuff if err := cmd.Run(); err != nil { if req.Context().Err() == nil { Log.Debug("plg_video_thumbnail::generate::run path=%s err=%s", req.URL.Query().Get("path"), base64.StdEncoding.EncodeToString(errBuff.Bytes())) return nil, err } return nil, err } cmd.Wait() thumbnail, err = os.OpenFile(cachePath, os.O_RDONLY, os.ModePerm) if err != nil { Log.Error("plg_video_thumbnail::generate::open path=%s err=%s", cachePath, err.Error()) return nil, err } this.setHeader(res) return thumbnail, nil } func (this *ffmpegThumbnail) setHeader(res *http.ResponseWriter) { (*res).Header().Set("Content-Type", "image/jpeg") (*res).Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12)) } ================================================ FILE: server/plugin/plg_video_transcoder/index.go ================================================ package plg_video_transcoder import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "math" "net/http" "os" "os/exec" "strconv" "strings" "time" "github.com/gorilla/mux" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/middleware" ) const ( HLS_SEGMENT_LENGTH = 5 FPS = 30 CLEAR_CACHE_AFTER = 12 VideoCachePath = "data/cache/video/" ) var ( plugin_enable func() bool blacklist_format func() string ) func init() { plugin_enable = func() bool { return Config.Get("features.video.enable_transcoder").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable_transcoder" f.Type = "enable" f.Target = []string{"transcoding_blacklist_format"} f.Description = "Enable/Disable on demand video transcoding. The transcoder" f.Default = true return f }).Bool() } blacklist_format = func() string { return Config.Get("features.video.blacklist_format").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "transcoding_blacklist_format" f.Name = "blacklist_format" f.Type = "text" f.Description = "Video format that won't be transcoded" f.Default = os.Getenv("FEATURE_TRANSCODING_VIDEO_BLACKLIST") if f.Default != "" { f.Placeholder = fmt.Sprintf("Default: '%s'", f.Default) } return f }).String() } Hooks.Register.Onload(func() { blacklist_format() if !plugin_enable() { return } else if _, err := exec.LookPath("ffmpeg"); err != nil { Log.Warning("plg_video_thumbnail::init error=ffmpeg_not_installed") return } else if _, err := exec.LookPath("ffprobe"); err != nil { Log.Warning("plg_video_thumbnail::init error=ffprobe_not_installed") return } cachePath := GetAbsolutePath(VideoCachePath) os.RemoveAll(cachePath) os.MkdirAll(cachePath, os.ModePerm) Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc(OverrideVideoSourceMapper, func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", GetMimeType(req.URL.String())) if plugin_enable() == false { return } res.Write([]byte(`window.overrides["video-map-sources"] = function(sources){`)) res.Write([]byte(` return sources.map(function(source){`)) blacklists := strings.Split(blacklist_format(), ",") for i := 0; i < len(blacklists); i++ { blacklists[i] = strings.TrimSpace(blacklists[i]) res.Write([]byte(fmt.Sprintf(`if(source.type == "%s"){ return source; } `, GetMimeType("."+blacklists[i])))) } res.Write([]byte(` source.src = source.src + "&transcode=hls";`)) res.Write([]byte(` source.type = "application/x-mpegURL";`)) res.Write([]byte(` return source;`)) res.Write([]byte(` })`)) res.Write([]byte(`}`)) }) r.PathPrefix("/hls/hls_{segment}.ts").Handler(NewMiddlewareChain( hlsTranscodeHandler, []Middleware{SecureHeaders}, )).Methods("GET") return nil }) Hooks.Register.ProcessFileContentBeforeSend(hlsPlaylistHandler) }) } func hlsPlaylistHandler(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, bool, error) { query := req.URL.Query() if query.Get("transcode") != "hls" { return reader, false, nil } path := query.Get("path") if strings.HasPrefix(GetMimeType(path), "video/") == false { return reader, false, nil } cacheName := "vid_" + GenerateID(ctx.Session) + "_" + QuickHash(path, 10) + ".dat" cachePath := GetAbsolutePath( VideoCachePath, cacheName, ) f, err := os.OpenFile(cachePath, os.O_CREATE|os.O_RDWR, os.ModePerm) if err != nil { return reader, false, err } io.Copy(f, reader) reader.Close() f.Close() time.AfterFunc(CLEAR_CACHE_AFTER*time.Hour, func() { os.Remove(cachePath) }) p, err := ffprobe(cachePath) if err != nil { return reader, false, err } var response string var i int response = "#EXTM3U\n" response += "#EXT-X-VERSION:3\n" response += "#EXT-X-MEDIA-SEQUENCE:0\n" response += "#EXT-X-ALLOW-CACHE:YES\n" response += fmt.Sprintf("#EXT-X-TARGETDURATION:%d\n", HLS_SEGMENT_LENGTH) for i = 0; i < int(p.Format.Duration)/HLS_SEGMENT_LENGTH; i++ { response += fmt.Sprintf("#EXTINF:%d.0000, nodesc\n", HLS_SEGMENT_LENGTH) response += fmt.Sprintf("/hls/hls_%d.ts?path=%s\n", i, cacheName) } if md := math.Mod(p.Format.Duration, HLS_SEGMENT_LENGTH); md > 0 { response += fmt.Sprintf("#EXTINF:%.4f, nodesc\n", md) response += fmt.Sprintf("/hls/hls_%d.ts?path=%s\n", i, cacheName) } response += "#EXT-X-ENDLIST\n" (*res).Header().Set("Content-Type", "application/x-mpegURL") return NewReadCloserFromBytes([]byte(response)), true, nil } func hlsTranscodeHandler(ctx *App, res http.ResponseWriter, req *http.Request) { if plugin_enable() == false { return } segmentNumber, err := strconv.Atoi(mux.Vars(req)["segment"]) if err != nil { Log.Info("[plugin hls] invalid segment request '%s'", mux.Vars(req)["segment"]) res.WriteHeader(http.StatusBadRequest) return } startTime := segmentNumber * HLS_SEGMENT_LENGTH cachePath := GetAbsolutePath( VideoCachePath, req.URL.Query().Get("path"), ) if _, err := os.Stat(cachePath); os.IsNotExist(err) { Log.Info("[plugin hls]: invalid video") res.WriteHeader(http.StatusServiceUnavailable) return } cmd := exec.CommandContext(req.Context(), "ffmpeg", []string{ "-timelimit", "30", "-ss", fmt.Sprintf("%d.00", startTime), "-i", cachePath, "-t", fmt.Sprintf("%d.00", HLS_SEGMENT_LENGTH), "-vf", fmt.Sprintf("scale=-2:%d", 720), "-vcodec", "libx264", "-preset", "veryfast", "-acodec", "aac", "-b:a", "128k", "-ac", "2", "-pix_fmt", "yuv420p", "-x264opts", strings.Join([]string{ "subme=0", "me_range=4", "rc_lookahead=10", "me=dia", "no_chroma_me", "8x8dct=0", "partitions=none", }, ":"), "-force_key_frames", fmt.Sprintf("expr:gte(t,n_forced*%d.000)", HLS_SEGMENT_LENGTH), "-f", "mpegts", "-output_ts_offset", fmt.Sprintf("%d.00", startTime), "-fps_mode", "cfr", "pipe:1", }...) var buffer bytes.Buffer cmd.Stdout = res cmd.Stderr = &buffer err = cmd.Run() if err != nil { Log.Error("plg_video_transcoder::ffmpeg::run '%s' - %s", err.Error(), base64.StdEncoding.EncodeToString(buffer.Bytes())) } } type FFProbeData struct { Format struct { Duration float64 `json:"duration,string"` BitRate int `json:"bit_rate,string"` } `json: "format"` Streams []struct { CodecType string `json:"codec_type"` CodecName string `json:"codec_name"` PixelFormat string `json:"pix_fmt"` } `json:"streams"` } func ffprobe(videoPath string) (FFProbeData, error) { var stream bytes.Buffer var probe FFProbeData cmd := exec.Command( "ffprobe", strings.Split(fmt.Sprintf( "-v quiet -print_format json -show_format -show_streams %s", videoPath, ), " ")..., ) cmd.Stdout = &stream if err := cmd.Run(); err != nil { return probe, nil } cmd.Run() if err := json.Unmarshal([]byte(stream.String()), &probe); err != nil { return probe, err } return probe, nil } ================================================ FILE: server/plugin/plg_widget_chat/assets/sidebar.diff ================================================ diff --git a/public/assets/components/sidebar.js b/public/assets/components/sidebar.js index 268aa4eb..e7b93142 100644 --- a/public/assets/components/sidebar.js +++ b/public/assets/components/sidebar.js @@ -52,1 +52,5 @@ export default async function ctrlSidebar(render, {}) { + qs($sidebar, ".component_sidebar > div").appendChild(createElement(`
    `)); + import(location.origin + "/plg_handler_chat/sidebar_chat.js") + .then((module) => module.default(createRender(qs($sidebar, `[data-bind="chat"]`)), { path })) + .catch((err) => console.log(err)); ================================================ FILE: server/plugin/plg_widget_chat/assets/sidebar_chat.js ================================================ import { createElement, createRender, nop } from "../lib/skeleton/index.js"; import { qs, safe } from "../lib/dom.js"; import rxjs, { effect, applyMutation, preventDefault } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { forwardURLParams } from "../../lib/path.js"; import t from "../locales/index.js"; import { createModal } from "../components/modal.js"; import { generateSkeleton } from "../components/skeleton.js"; export default async function(render, { path }) { const $page = createElement(`

    chat

      ${generateSkeleton(1)}
    `); render($page); const $dom = { compose: ($el) => qs($el, `[data-bind="compose"]`), messages: ($el) => qs($el, `[data-bind="messages"]`), mentions: ($el) => qs($el, `[data-bind="mentions"]`), input: ($el) => qs($el, "input"), }; const refresh$ = getMessages(path).pipe( rxjs.catchError(() => rxjs.EMPTY), ); renderMessages(createRender($dom.messages($page)), { $dom, refresh$ }); renderCompose(createRender($dom.compose($page)), { path, refresh$, $dom, onRefresh: () => renderMessages(createRender($dom.messages($page)), { $dom, refresh$ }), onLoad: () => renderMentions(createRender($dom.mentions($page)), { $input: $dom.input($page), }), }); } function renderMessages(render, { $dom, refresh$, sidebar = true }) { const onMessageClick = ({ path }) => { const loadingHTML = `
    `; const $modal = createElement(loadingHTML); createModal({})($modal); const trigger$ = new rxjs.Subject(); const refresh$ = trigger$.pipe( rxjs.startWith(null), rxjs.switchMap(() => getMessages(path)), rxjs.catchError(() => rxjs.EMPTY), ); effect(refresh$.pipe( rxjs.map((messages) => { const $page = createElement(`
      `); renderCompose(createRender($dom.compose($page)), { path, refresh$, $dom, onRefresh: () => trigger$.next(), onLoad: () => renderMentions(createRender($dom.mentions($page)), { $input: $dom.input($page), }), }); const $messages = document.createDocumentFragment(); for (const message of messages) { $messages.appendChild(renderMessage(message, { sidebar: false })); } $dom.messages($page).appendChild($messages); return $page; }), applyMutation($modal, "replaceChildren"), )); } effect(refresh$.pipe( rxjs.map((messages) => { if (messages.length === 0) return createElement(`
      ∅
      `); const $messages = document.createDocumentFragment(); for (const message of messages) { $messages.appendChild(renderMessage(message, { onClick: () => onMessageClick({ path: message.path }), sidebar, })); } return $messages; }), applyMutation(render(createElement(`
        `)), "replaceChildren"), )); } function renderCompose(render, { path, refresh$, $dom, onRefresh, onLoad }) { const $form = createElement(`
        `); render($form); onLoad(); const $input = $dom.input($form); effect(rxjs.fromEvent($form, "submit").pipe( preventDefault(), rxjs.mergeMap((e) => { const message = new FormData(e.target).get("message"); $input.disabled = true; return createMessage({ message, path }); }), rxjs.tap(() => { $input.value = ""; $input.disabled = false; onRefresh(); }), )); } function renderMessage(obj, { onClick = nop, sidebar = true }) { const $message = createElement(`
      • ${safe(obj.author)}: ${safe(obj.message)}
      • `); effect(rxjs.fromEvent($message, "click").pipe( rxjs.tap(() => onClick()), )); effect(rxjs.of(null).pipe( rxjs.filter(() => document.body.classList.contains("touch-no")), rxjs.tap(() => $message.onmouseenter = () => { const $things = document.querySelectorAll(".component_thing"); $things.forEach(($thing) => { const thingpath = $thing.getAttribute("data-path"); if (obj.path.indexOf(thingpath) !== -1) $thing.classList.add("hover"); }); $message.onmouseleave = () => $things.forEach(($thing) => $thing.classList.remove("hover")); }), )); return $message; } function renderMentions(render, { $input }) { const $list = createElement(`
          `); render($list); let active = -1; const hide = () => { $list.classList.add("hidden"); active = -1; }; const pick = (name) => { const before = $input.value.substring(0, $input.selectionStart); const after = $input.value.substring($input.selectionStart); const atIdx = before.lastIndexOf("@"); $input.value = before.substring(0, atIdx) + "@" + name + " " + after; const cursor = atIdx + name.length + 2; $input.setSelectionRange(cursor, cursor); $input.focus(); }; const getMentionQuery = () => { const text = $input.value.substring(0, $input.selectionStart); const atIdx = text.lastIndexOf("@"); if (atIdx === -1) return null; if (atIdx > 0 && text[atIdx - 1] !== " ") return null; const query = text.substring(atIdx + 1); if (query.indexOf(" ") !== -1) return null; return query; }; effect(rxjs.fromEvent($input, "input").pipe( rxjs.map(() => getMentionQuery()), rxjs.switchMap((q) => { if (q === null) { hide(); return rxjs.EMPTY; } return rxjs.of(q); }), rxjs.debounceTime(150), rxjs.switchMap((q) => searchUsers(q)), rxjs.map((users) => { if (users.length === 0) return document.createDocumentFragment(); const $messages = document.createDocumentFragment(); for (const user of users) { const handle = user.name.replace(/\s+/g, "."); const $li = createElement(`
        • ${safe(user.name)}
        • `); $li.onmousedown = (e) => { e.preventDefault(); pick(handle); hide(); }; $messages.appendChild($li); } return $messages; }), rxjs.tap(($messages) => { $messages.childNodes.length > 0 ? $list.classList.remove("hidden") : $list.classList.add("hidden"); active = -1; }), applyMutation($list, "replaceChildren"), )); effect(rxjs.fromEvent($input, "keydown").pipe( rxjs.filter(() => !$list.classList.contains("hidden")), rxjs.tap((e) => { const items = $list.children; if (e.key === "ArrowDown") { e.preventDefault(); if (active >= 0) items[active].classList.remove("active"); active = (active + 1) % items.length; items[active].classList.add("active"); } else if (e.key === "ArrowUp") { e.preventDefault(); if (active >= 0) items[active].classList.remove("active"); active = (active - 1 + items.length) % items.length; items[active].classList.add("active"); } else if (e.key === "Enter" && active >= 0) { e.preventDefault(); pick(items[active].getAttribute("data-handle")); hide(); } else if (e.key === "Escape") { hide(); } }), )); effect(rxjs.fromEvent($input, "blur").pipe( rxjs.tap(() => hide), )); } const searchUsers = (q) => ajax({ url: forwardURLParams("api/plg_widget_chat/lookup?q="+encodeURIComponent(q), ["share"]), responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.results || []), rxjs.catchError(() => rxjs.of([])), ); const getMessages = (path = "/") => ajax({ url: forwardURLParams("api/plg_widget_chat/messages?path="+encodeURIComponent(path), ["share"]), responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.results.reverse()), ); const createMessage = ({ path, ...body }) => ajax({ url: forwardURLParams("api/plg_widget_chat/messages?path="+encodeURIComponent(path), ["share"]), method: "POST", body, }); const CSS = ` /* MESSAGES */ [data-bind="messages"] { font-size: 0.9rem; } [data-bind="messages"] .message-author { font-weight: 500; opacity: 0.5; } /* SIDEBAR */ .component_filemanager_shell .component_sidebar [data-bind="chat"] h3 img { position: relative; top: 1px; } .component_filemanager_shell .component_sidebar [data-bind="chat"] a { cursor: pointer; } /* MODAL */ component-modal [data-bind="thread"] component-icon[name="loading"] { display: block; height: 30px; text-align: center; } component-modal [data-bind="thread"] form input { font-size: 1rem; border: 2px solid var(--border); border-radius: 5px; padding: 5px 10px; color: rgba(0, 0, 0, 0.75); width: 100%; display: block; box-sizing: border-box; } component-modal [data-bind="thread"] [data-bind="messages"] { list-style-type: none; margin: 10px 0 0 0; padding: 0; max-height: 200px; } component-modal [data-bind="thread"] [data-bind="messages"] > li { line-height: 1rem; margin: 5px 5px; text-transform: capitalize; } /* MENTIONS */ .plg_widget_chat ul.mentions { background: rgba(0, 0, 0, 0.65); border-radius: 3px; list-style: none; margin: 0; padding: 0; max-height: 120px; overflow-y: auto; color: var(--bg-color); } .plg_widget_chat ul.mentions li { padding: 3px 8px; cursor: pointer; font-size: 0.8rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .plg_widget_chat ul.mentions li:hover, .plg_widget_chat ul.mentions li.active { background: rgba(198, 200, 204, 0.25); border-radius: 3px; } `; ================================================ FILE: server/plugin/plg_widget_chat/config.go ================================================ package plg_widget_chat import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() }) } var PluginEnable = func() bool { return Config.Get("features.chat.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{} f.Default = false return f }).Bool() } ================================================ FILE: server/plugin/plg_widget_chat/db.go ================================================ package plg_widget_chat import ( "os" "database/sql" . "github.com/mickael-kerjean/filestash/server/common" ) var db *sql.DB func init() { Hooks.Register.Onload(func() { if err := initDB(); err != nil { Log.Error("plg_handler_chat::db err=cannot_init msg=%s", err.Error()) os.Exit(1) } }) } func initDB () error { var err error db, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH, "chat.db")) if err != nil { return err } _, err = db.Exec(` CREATE TABLE IF NOT EXISTS messages ( id TEXT PRIMARY KEY, path TEXT NOT NULL, author TEXT NOT NULL, message TEXT NOT NULL, creation_date INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_messages ON messages(path, creation_date); `) return err } ================================================ FILE: server/plugin/plg_widget_chat/handler.go ================================================ package plg_widget_chat import ( "net/http" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/ctrl" ) func listMessages(ctx *App, w http.ResponseWriter, r *http.Request) { path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } rows, err := db.QueryContext(r.Context(), ` SELECT path, author, message, creation_date FROM messages WHERE path GLOB ? ORDER BY creation_date ASC `, globAll(path)) if err != nil { SendErrorResult(w, err) return } defer rows.Close() out := []Message{} for rows.Next() { var m Message if err := rows.Scan( &m.Path, &m.Author, &m.Message, &m.CreatedAt, ); err != nil { SendErrorResult(w, err) return } if ctx.Session["path"] != "" { m.Path = strings.TrimPrefix(m.Path, strings.TrimSuffix(ctx.Session["path"], "/")) } out = append(out, m) } SendSuccessResults(w, out) } func createMessage(ctx *App, w http.ResponseWriter, r *http.Request) { path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } msg, ok := ctx.Body["message"].(string) if !ok { SendErrorResult(w, NewError("Invalid parameters", 400)) return } author := getUser(ctx.Session) _, err = db.ExecContext(ctx.Context, ` INSERT INTO messages(id, path, author, message, creation_date) VALUES(?,?,?,?,?) `, newID(), path, author, msg, time.Now().Unix()) if err != nil { SendErrorResult(w, err) return } extractMentions := func(message string) []string { matches := mention_re.FindAllStringSubmatch(message, -1) out := make([]string, 0, len(matches)) for _, m := range matches { name := strings.TrimSpace(m[1]) if name != "" { out = append(out, name) } } return out } for _, name := range extractMentions(msg) { go processMention(map[string]string{ "path": path, "author": author, "mention": name, "message": msg, }) } SendSuccessResult(w, nil) } func lookupUsers(ctx *App, w http.ResponseWriter, r *http.Request) { if ctx.Share.Id != "" { SendSuccessResults(w, []DirectoryUser{}) return } dir := Hooks.Get.DirectoryService() if dir == nil { SendSuccessResults(w, []DirectoryUser{}) return } results, err := dir.Search(r.URL.Query().Get("q")) if err != nil { SendErrorResult(w, err) return } SendSuccessResults(w, results) } ================================================ FILE: server/plugin/plg_widget_chat/index.go ================================================ package plg_widget_chat import ( _ "embed" "net/http" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/middleware" "github.com/gorilla/mux" ) //go:embed assets/sidebar_chat.js var CTRLJS []byte //go:embed assets/sidebar.diff var PATCH []byte func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc("/api/plg_widget_chat/messages", NewMiddlewareChain(createMessage, []Middleware{ApiHeaders, SecureHeaders, PluginGuard, SessionStart, LoggedInOnly, BodyParser})).Methods("POST") r.HandleFunc("/api/plg_widget_chat/messages", NewMiddlewareChain(listMessages, []Middleware{ApiHeaders, SecureHeaders, PluginGuard, SessionStart, LoggedInOnly})).Methods("GET") r.HandleFunc("/api/plg_widget_chat/lookup", NewMiddlewareChain(lookupUsers, []Middleware{ApiHeaders, SecureHeaders, PluginGuard, SessionStart, LoggedInOnly})).Methods("GET") r.HandleFunc(WithBase("/plg_handler_chat/sidebar_chat.js"), func(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, WithBase("/assets/"+BUILD_REF+"/components/sidebar_chat.js"), http.StatusSeeOther) }) r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/components/sidebar_chat.js"), func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/javascript") res.Write(CTRLJS) }).Methods("GET") return nil }) Hooks.Register.OnConfig(func() { if PluginEnable() { Hooks.Register.StaticPatch(PATCH, WithID("plg_widget_chat")) } else { Hooks.Register.StaticPatch([]byte(""), WithID("plg_widget_chat")) } }) } func PluginGuard(fn HandlerFunc) HandlerFunc { return func(ctx *App, res http.ResponseWriter, req *http.Request) { if !PluginEnable() { SendErrorResult(res, ErrNotAllowed) return } fn(ctx, res, req) } } ================================================ FILE: server/plugin/plg_widget_chat/type.go ================================================ package plg_widget_chat type Message struct { Path string `json:"path"` Author string `json:"author"` Message string `json:"message"` CreatedAt int64 `json:"created_at"` } ================================================ FILE: server/plugin/plg_widget_chat/utils.go ================================================ package plg_widget_chat import ( "crypto/rand" "encoding/hex" ) func newID() string { var b [16]byte _, _ = rand.Read(b[:]) return hex.EncodeToString(b[:]) } func getUser(session map[string]string) string { if session["username"] != "" { return session["username"] } else if session["user"] != "" { return session["user"] } return "unknown" } func globAll(path string) string { return path + "**" } ================================================ FILE: server/plugin/plg_widget_chat/workflow.go ================================================ package plg_widget_chat import ( "regexp" "strings" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/trigger" . "github.com/mickael-kerjean/filestash/server/pkg/workflow/model" ) var ( mention_name = "mention" mention_event = make(chan ITriggerEvent, 10) mention_re = regexp.MustCompile(`(?:^|\s)@([\w.]+)`) ) func init() { Hooks.Register.WorkflowTrigger(&MentionTrigger{}) } type MentionTrigger struct{} func (this *MentionTrigger) Manifest() WorkflowSpecs { return WorkflowSpecs{ Name: mention_name, Title: "When Someone is Mentioned", Icon: ``, Specs: Form{ Elmnts: []FormElement{ { Name: "path", Type: "text", Default: "/**", Placeholder: "Default: /**", }, }, }, Order: 4, } } func (this *MentionTrigger) Init() (chan ITriggerEvent, error) { return mention_event, nil } func processMention(params map[string]string) { if dir := Hooks.Get.DirectoryService(); dir != nil { query := strings.ReplaceAll(params["mention"], ".", " ") if users, err := dir.Search(query); err == nil { for _, u := range users { if strings.EqualFold(u.Name, query) { params["email"] = u.Email break } } } } if err := TriggerEvents(mention_event, mention_name, mentionCallback(params)); err != nil { Log.Error("[workflow] trigger=mention step=triggerEvents err=%s", err.Error()) } } func mentionCallback(out map[string]string) func(Workflow) (map[string]string, bool) { return func(w Workflow) (map[string]string, bool) { path := w.Trigger.Params["path"] if path != "" && !GlobMatch(path, out["path"]) { return out, false } return out, true } } ================================================ FILE: server/plugin/plg_widget_description/assets/sidebar.diff ================================================ diff --git a/public/assets/components/sidebar.js b/public/assets/components/sidebar.js index 268aa4eb..e7b93142 100644 --- a/public/assets/components/sidebar.js +++ b/public/assets/components/sidebar.js @@ -52,1 +52,5 @@ export default async function ctrlSidebar(render, {}) { + if (!$sidebar.querySelector(`[data-bind="description"]`)) qs($sidebar, ".component_sidebar > div").appendChild(createElement(`
          `)); + import(location.origin + "/plg_widget_description/sidebar_description.js") + .then((module) => module.default(createRender(qs($sidebar, `[data-bind="description"]`)), { path })) + .catch((err) => console.log(err)); ================================================ FILE: server/plugin/plg_widget_description/assets/sidebar_description.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import { qs } from "../lib/dom.js"; import rxjs, { effect, applyMutation, preventDefault } from "../../lib/rx.js"; import ajax from "../../lib/ajax.js"; import { forwardURLParams } from "../../lib/path.js"; import t from "../locales/index.js"; const ICONS = { DEFAULT: `description`, LOADING: ``, }; export default async function(render, { path }) { const $page = createElement(`

          ${ICONS.DEFAULT} ${t("Description")}

          `); render($page); const $DOM = { textarea: qs($page, "textarea"), h3: qs($page, "h3"), icon_default: () => createElement(ICONS.DEFAULT), icon_loading: () => createElement(ICONS.LOADING), }; effect(getDescription(path).pipe( rxjs.tap(({ text }) => $DOM.textarea.value = text), )); effect(rxjs.fromEvent($DOM.textarea, "input").pipe( rxjs.map((e) => e.target.value), rxjs.debounceTime(200), rxjs.tap(() => $DOM.h3.replaceChild($DOM.icon_loading(), $DOM.h3.firstElementChild)), rxjs.mergeMap((text) => updateDescription({ path, text })), rxjs.tap(() => $DOM.h3.replaceChild($DOM.icon_default(), $DOM.h3.firstElementChild)), )); } const getDescription = (path) => ajax({ url: forwardURLParams("api/plg_widget_description/description?path=" + encodeURIComponent(path), ["share"]), responseType: "json" }).pipe( rxjs.map(({ responseJSON }) => responseJSON.result || { path, text: "" }), ); const updateDescription = ({ path, ...body }) => ajax({ url: forwardURLParams("api/plg_widget_description/description?path=" + encodeURIComponent(path), ["share"]), method: "PUT", body, }); const CSS = ` [data-bind="description"] [data-bind="content"] { padding-left: 5px; padding-right: 2px; } [data-bind="description"] [data-bind="content"] textarea { max-width: 100%; min-width: 100%; font-size: 0.9rem; }`; ================================================ FILE: server/plugin/plg_widget_description/config.go ================================================ package plg_widget_description import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() }) } var PluginEnable = func() bool { return Config.Get("features.description.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{} f.Default = false return f }).Bool() } ================================================ FILE: server/plugin/plg_widget_description/db.go ================================================ package plg_widget_description import ( "os" "database/sql" . "github.com/mickael-kerjean/filestash/server/common" ) var db *sql.DB func init() { Hooks.Register.Onload(func() { if err := initDB(); err != nil { Log.Error("plg_widget_description::db err=cannot_init msg=%s", err.Error()) os.Exit(1) } }) } func initDB() error { var err error db, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH, "descriptions.db")) if err != nil { return err } _, err = db.Exec(` CREATE TABLE IF NOT EXISTS descriptions ( backend TEXT NOT NULL, path TEXT NOT NULL, author TEXT NOT NULL, text TEXT NOT NULL, updated_at INTEGER NOT NULL, PRIMARY KEY (backend, path) ); `) return err } ================================================ FILE: server/plugin/plg_widget_description/handler.go ================================================ package plg_widget_description import ( "database/sql" "net/http" "time" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/ctrl" ) func get(ctx *App, w http.ResponseWriter, r *http.Request) { path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } var d Description if err = db.QueryRowContext(ctx.Context, ` SELECT author, text, updated_at FROM descriptions WHERE backend = ? AND path = ? `, GenerateID(ctx.Session), path).Scan(&d.Author, &d.Text, &d.UpdatedAt); err != nil && err != sql.ErrNoRows { SendErrorResult(w, err) return } SendSuccessResult(w, d) } func update(ctx *App, w http.ResponseWriter, r *http.Request) { path, err := PathBuilder(ctx, r.URL.Query().Get("path")) if err != nil { SendErrorResult(w, err) return } text, ok := ctx.Body["text"].(string) if !ok { SendErrorResult(w, NewError("Invalid parameters", 400)) return } if text == "" { _, err = db.ExecContext(ctx.Context, ` DELETE FROM descriptions WHERE backend = ? AND path = ? `, GenerateID(ctx.Session), path) } else { _, err = db.ExecContext(ctx.Context, ` INSERT INTO descriptions(backend, path, author, text, updated_at) VALUES(?, ?, ?, ?, ?) ON CONFLICT(backend, path) DO UPDATE SET author = excluded.author, text = excluded.text, updated_at = excluded.updated_at `, GenerateID(ctx.Session), path, getUser(ctx.Session), text, time.Now().Unix()) } if err != nil { SendErrorResult(w, err) return } SendSuccessResult(w, nil) } ================================================ FILE: server/plugin/plg_widget_description/index.go ================================================ package plg_widget_description import ( _ "embed" "net/http" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/middleware" "github.com/gorilla/mux" ) //go:embed assets/sidebar_description.js var CTRLJS []byte //go:embed assets/sidebar.diff var PATCH []byte func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc("/api/plg_widget_description/description", NewMiddlewareChain(get, []Middleware{ApiHeaders, SecureHeaders, PluginGuard, SessionStart})).Methods("GET") r.HandleFunc("/api/plg_widget_description/description", NewMiddlewareChain(update, []Middleware{ApiHeaders, SecureHeaders, PluginGuard, SessionStart, BodyParser})).Methods("PUT") r.HandleFunc(WithBase("/plg_widget_description/sidebar_description.js"), func(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, WithBase("/assets/"+BUILD_REF+"/components/sidebar_description.js"), http.StatusSeeOther) }) r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/components/sidebar_description.js"), func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/javascript") res.Write(CTRLJS) }).Methods("GET") return nil }) Hooks.Register.OnConfig(func() { if PluginEnable() { Hooks.Register.StaticPatch(PATCH, WithID("plg_widget_description")) } else { Hooks.Register.StaticPatch([]byte(""), WithID("plg_widget_description")) } }) } func PluginGuard(fn HandlerFunc) HandlerFunc { return func(ctx *App, res http.ResponseWriter, req *http.Request) { if !PluginEnable() { SendErrorResult(res, ErrNotAllowed) return } fn(ctx, res, req) } } ================================================ FILE: server/plugin/plg_widget_description/type.go ================================================ package plg_widget_description type Description struct { Author string `json:"author"` Text string `json:"text"` UpdatedAt int64 `json:"updated_at"` } ================================================ FILE: server/plugin/plg_widget_description/utils.go ================================================ package plg_widget_description func getUser(session map[string]string) string { if session["user"] != "" { return session["user"] } return "unknown" } ================================================ FILE: server/plugin/plg_widget_favourite/assets/favourite.diff ================================================ diff --git a/public/assets/components/sidebar.js b/public/assets/components/sidebar.js index 268aa4eb..e7b93142 100644 --- a/public/assets/components/sidebar.js +++ b/public/assets/components/sidebar.js @@ -52,1 +52,5 @@ export default async function ctrlSidebar(render, {}) { + if (!$sidebar.querySelector(`[data-bind="favourites"]`)) qs($sidebar, `[data-bind="your-tags"]`).before(createElement(`
          `)); + import(new URL("./sidebar_favourite.js", import.meta.url).toString()) + .then((module) => module.default(createRender(qs($sidebar, `[data-bind="favourites"]`)), { path })) + .catch((err) => console.log(err)); diff --git a/public/assets/pages/filespage/ctrl_submenu.js b/public/assets/pages/filespage/ctrl_submenu.js index cdc4015c..5ec564b5 100644 --- a/public/assets/pages/filespage/ctrl_submenu.js +++ b/public/assets/pages/filespage/ctrl_submenu.js @@ -125,2 +125,12 @@ function componentLeft(render, { $scroll, getSelectionLength$ }) { `))), + rxjs.map(($buttons) => { + $buttons.appendChild(Object.assign(createElement(``), { + onclick: async () => { + const url = new URL("../../components/sidebar_favourite.js", import.meta.url).toString(); + const { toggleFavourite } = await import(url); + toggleFavourite(expandSelection()[0].path) + }, + })); + return $buttons; + }), rxjs.tap(($buttons) => animate($buttons, { time: 100, keyframes: slideYIn(5) })), ================================================ FILE: server/plugin/plg_widget_favourite/assets/sidebar_favourite.js ================================================ import { createElement } from "../lib/skeleton/index.js"; import { qs, safe } from "../lib/dom.js"; import rxjs, { effect, applyMutation } from "../../lib/rx.js"; import { basename } from "../lib/path.js"; import { getSession } from "../model/session.js"; import { onLogout } from "../pages/ctrl_logout.js"; import t from "../locales/index.js"; const refresh$ = new rxjs.Subject(); const currentShare = () => new window.URL(location.href).searchParams.get("share") || ""; let backendID = ""; const initBackendID = async () => { if (backendID) return; backendID = await getSession().toPromise().then(({ backendID }) => backendID); onLogout(() => backendID = ""); }; export default async function(render, { path }) { const $page = createElement(`

          ${t("Favourites")}

            `); render($page); effect(rxjs.merge(refresh$, rxjs.of(null)).pipe( rxjs.mergeMap(() => initBackendID()), rxjs.mergeMap(() => listFavourites(path)), rxjs.map((favourites) => { if (favourites.length === 0) return createElement(`
            ∅
            `); const $list = document.createDocumentFragment(); for (let i=0; i { e.preventDefault(); e.stopPropagation(); $el.closest("li").remove(); removeFavourite(path); }; return $el; }; $list.appendChild(withRemove(createElement(`
          • ${type} ${safe(basename(path.replace(new RegExp("/$"), "")))}
          • `))); } return $list; }), applyMutation(qs($page, `[data-bind="content"]`), "replaceChildren"), )); } export async function toggleFavourite(path) { return db().then((db) => new Promise((resolve, reject) => { const tx = db.transaction("favourites", "readonly"); const store = tx.objectStore("favourites"); const req = store.get([backendID, currentShare(), path]); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); })).then((existing) => existing ? removeFavourite(path) : addFavourite(path)).then(() => refresh$.next(null)); } function listFavourites(path) { return db().then((db) => new Promise((resolve, reject) => { const tx = db.transaction("favourites", "readonly"); const store = tx.objectStore("favourites"); const req = store.index("parent").getAll(IDBKeyRange.bound( [backendID, currentShare(), path], [backendID, currentShare(), path + "\uffff"], )); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); })); } function addFavourite(path) { return db().then((db) => new Promise((resolve, reject) => { const tx = db.transaction("favourites", "readwrite"); const store = tx.objectStore("favourites"); store.put({ backend: backendID, path, share: currentShare(), parent: path.replace(new RegExp("[^/]*\/?$"), "") }); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); })); } function removeFavourite(path) { return db().then((db) => new Promise((resolve, reject) => { const tx = db.transaction("favourites", "readwrite"); const store = tx.objectStore("favourites"); store.delete([backendID, currentShare(), path]); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); })); } const db = () => { let _db = null; return new Promise((resolve, reject) => { if (_db) return Promise.resolve(_db); const req = indexedDB.open("favourites", 1); req.onupgradeneeded = () => { const db = req.result; const store = db.createObjectStore("favourites", { keyPath: ["backend", "share", "path"] }); store.createIndex("parent", ["backend", "share", "parent"]); }; req.onsuccess = () => { _db = req.result; resolve(_db); }; req.onerror = () => reject(req.error); }); }; const CSS = ` #sidebar_favorite svg { position: relative; bottom: 3px; } #sidebar_favorite ul li a { justify-content: space-between; } #sidebar_favorite ul li a > div { margin-left: -5px; } #sidebar_favorite ul li a svg { display: none; background: rgba(255, 255, 255, 0.6); align-self: center; width: 13px; height: 13px; border-radius: 50%; padding: 4px; position: initial; } #sidebar_favorite ul li a:hover svg { display: block; } `; ================================================ FILE: server/plugin/plg_widget_favourite/config.go ================================================ package plg_widget_favourite import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() }) } var PluginEnable = func() bool { return Config.Get("features.favourite.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{} f.Default = false return f }).Bool() } ================================================ FILE: server/plugin/plg_widget_favourite/index.go ================================================ package plg_widget_favourite import ( _ "embed" "net/http" . "github.com/mickael-kerjean/filestash/server/common" "github.com/gorilla/mux" ) //go:embed assets/favourite.diff var PATCH []byte //go:embed assets/sidebar_favourite.js var JS []byte func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/components/sidebar_favourite.js"), func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/javascript") res.Write(JS) }).Methods("GET") return nil }) Hooks.Register.OnConfig(func() { if PluginEnable() { Hooks.Register.StaticPatch(PATCH, WithID("plg_widget_favourite")) } else { Hooks.Register.StaticPatch([]byte(""), WithID("plg_widget_favourite")) } }) } ================================================ FILE: server/plugin/plg_widget_pgp/assets/pgp.diff ================================================ diff --git a/public/assets/pages/viewerpage/mimetype.js b/public/assets/pages/viewerpage/mimetype.js index 0adb0934..d002a70e 100644 --- a/public/assets/pages/viewerpage/mimetype.js +++ b/public/assets/pages/viewerpage/mimetype.js @@ -3,1 +3,2 @@ import { get as getPlugin } from "../../model/plugin.js"; export function opener(file = "", mimes) { + file = file.replace(new RegExp(".gpg$"), ""); diff --git a/public/assets/pages/viewerpage/application_image.js b/public/assets/pages/viewerpage/application_image.js index 1227d426..b82330aa 100644 --- a/public/assets/pages/viewerpage/application_image.js +++ b/public/assets/pages/viewerpage/application_image.js @@ -65,1 +65,3 @@ export default function(render, { getFilename, getDownloadUrl, mime, hasMenubar let src = `${getDownloadUrl()}&size=${window.innerWidth}`; + const m = await import("/plg_widget_pgp/pgp.js"); + if (m.isPGP(getFilename())) src = await m.decode(src); diff --git a/public/assets/pages/viewerpage/model_files.js b/public/assets/pages/viewerpage/model_files.js index 743914ab..4f20546d 100644 --- a/public/assets/pages/viewerpage/model_files.js +++ b/public/assets/pages/viewerpage/model_files.js @@ -22,4 +22,4 @@ export const options = () => ajax({ -export const cat = (url) => ajax({ - url: forwardURLParams(url, ["share"]), - method: "GET", -}).pipe( +const { isPGP, decode, encode } = await import(location.origin + "/plg_widget_pgp/pgp.js"); +export const cat = (url) => rxjs.of(forwardURLParams(url, ["share"])).pipe( + rxjs.mergeMap((url) => isPGP(getCurrentPath()) ? decode(url) : rxjs.of(url)), + rxjs.mergeMap((url) => ajax({ url, method: "GET" })), @@ -29,5 +29,3 @@ export const cat = (url) => ajax({ -export const save = (content) => ajax({ - url: forwardURLParams("api/files/cat?path=" + encodeURIComponent(getCurrentPath()), ["share"]), - method: "POST", - body: content, -}); +export const save = (content) => (isPGP(getCurrentPath()) ? rxjs.from(encode(content)) : rxjs.of(content)).pipe( + rxjs.mergeMap((body) => ajax({ body, method: "POST", url: forwardURLParams("api/files/cat?path=" + encodeURIComponent(getCurrentPath()), ["share"]) })), +); ================================================ FILE: server/plugin/plg_widget_pgp/assets/pgp.js ================================================ import { createElement, onDestroy } from "../../lib/skeleton/index.js"; import { createModal, MODAL_RIGHT_BUTTON } from "../../components/modal.js"; import notification from "../../components/notification.js"; import { qs, safe } from "../../lib/dom.js"; import { getFilename } from "./common.js"; const openpgp = (window.crypto && window.crypto.subtle) ? await import("https://unpkg.com/openpgp@6.3.0/dist/openpgp.min.mjs") : null; let KEY = null; onDestroy(() => KEY = null); export function isPGP(path) { if (!openpgp) return false; else if (new RegExp(".gpg$").test(path)) return true; else if (new RegExp(".pgp$").test(path)) return true; return false; } export async function decode(src) { const $page = createElement(`
            `); const encrypted = await new Promise(async (done, error) => { try { const res = await fetch(src); done(await res.arrayBuffer()); } catch (err) { error(err); } }); if (encrypted.byteLength === 0) { return URL.createObjectURL(new Blob([encrypted])); } return new Promise((done) => createModal({ withButtonsRight: "decrypt", withButtonsLeft: "cancel" })($page, async (n) => { if (n !== MODAL_RIGHT_BUTTON) return done(src); try { const file = qs($page, "input").files?.[0]; if (!file) throw new Error("Missing Key"); const privateKey = await openpgp.readPrivateKey({ armoredKey: await file.text(), }); KEY = privateKey; const { data } = await openpgp.decrypt({ message: await openpgp.readMessage({ binaryMessage: new Uint8Array(encrypted), }), decryptionKeys: privateKey, format: "binary", }); done(URL.createObjectURL(new Blob([data]))); } catch (err) { notification.error(err); done(src); } finally { $page.remove(); } })); } export async function encode(data) { const $page = createElement(`
            `); if (KEY === null) KEY = await new Promise((done, error) => { createModal({ withButtonsRight: "encrypt", withButtonsLeft: "cancel" })($page, async (n) => { if (n !== MODAL_RIGHT_BUTTON) return error(new Error("missing key")); try { const file = qs($page, "input").files?.[0]; if (!file) return done(null); const text = await file.text(); done(await openpgp.readPrivateKey({ armoredKey: text, })); } catch (err) { if (err.message !== "Misformed armored text") { notification.error(err); error(err); return } done(null); } finally { $page.remove(); } }); }); if (KEY === null) try { const { privateKey, publicKey } = await openpgp.generateKey({ type: "rsa", rsaBits: 2048, userIDs: [{ name: "anonymous", email: "anomymous@local" }], format: "armored", }); KEY = await openpgp.readPrivateKey({ armoredKey: privateKey, }); const $link = document.body.appendChild(Object.assign(document.createElement("a"), { href: URL.createObjectURL(new Blob([privateKey], { type: "application/pgp-keys" })), download: getFilename() + ".key", })); $link.click(); $link.remove(); } catch (err) { return error(err); } const message = await openpgp.createMessage({ text: data, }); const encrypted = await openpgp.encrypt({ message, encryptionKeys: KEY.toPublic(), format: "binary", config: { minRSABits: 1024 }, }); return new Blob([encrypted]); } const CSS = ` .component_pgp textarea { width: 100%; border: none; height: 200px; font-size: 0.5rem; } `; ================================================ FILE: server/plugin/plg_widget_pgp/index.go ================================================ package plg_widget_chat import ( _ "embed" "net/http" . "github.com/mickael-kerjean/filestash/server/common" "github.com/gorilla/mux" ) //go:embed assets/pgp.js var CTRLJS []byte //go:embed assets/pgp.diff var PATCH []byte func init() { Hooks.Register.HttpEndpoint(func(r *mux.Router) error { r.HandleFunc(WithBase("/plg_widget_pgp/pgp.js"), func(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, WithBase("/assets/"+BUILD_REF+"/pages/viewerpage/pgp.js"), http.StatusSeeOther) }) r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/pages/viewerpage/pgp.js"), func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/javascript") res.Write(CTRLJS) }).Methods("GET") return nil }) Hooks.Register.StaticPatch(PATCH) } ================================================ FILE: server/plugin/plg_widget_recent/ai.go ================================================ package plg_widget_recent import ( "bytes" "context" "encoding/json" "fmt" "net/http" "os" "strconv" "strings" "time" . "github.com/mickael-kerjean/filestash/server/common" ) func aiFilterRecent(ctx context.Context, query string, files []os.FileInfo) ([]os.FileInfo, error) { endpoint := PluginEndpoint() model := PluginModel() if endpoint == "" || model == "" { return files, nil } entries := make([]string, 0, len(files)) for i, f := range files { file := f.(File) accessed := time.UnixMilli(file.FTime).Format("2006-01-02 15:04") entryType := "file" if strings.HasSuffix(file.FPath, "/") { entryType = "directory" } entries = append(entries, fmt.Sprintf("%d: path=%s type=%s size=%d last_accessed=%s", i, file.FPath, entryType, file.FSize, accessed)) } body, err := json.Marshal(map[string]any{ "model": model, "messages": []map[string]string{ { "role": "system", "content": fmt.Sprintf(`You filter recently accessed files and folders based on a search query. Today is %s. Each entry has: index, path, type (file or directory), size (bytes), last_accessed (YYYY-MM-DD HH:MM). Apply these rules strictly: - Temporal queries ("2 hours ago", "3 days ago", "last week", "today", "recent"): include ONLY entries whose last_accessed falls within that date range. Exclude everything else. - Type queries ("files", "folders", "images", "documents"): include ONLY entries matching that type or extension. - Name queries: match against path. - Size queries ("big", "small", "large"): filter by size. - Combined queries: apply all matching rules together. Output ONLY comma-separated indices, e.g.: 3,0,7,1 No brackets, no spaces, no explanation. Most relevant first. Omit irrelevant results entirely.`, time.Now().Format("Monday January 2, 2006")), }, { "role": "user", "content": fmt.Sprintf("Query: %s\n\nFiles:\n%s", query, strings.Join(entries, "\n")), }, }, "temperature": 0, "max_tokens": 500, }) if err != nil { return files, nil } req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { return files, nil } req.Header.Set("Content-Type", "application/json") if apiKey := PluginAPIKey(); apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } resp, err := http.DefaultClient.Do(req) if err != nil { return files, nil } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return files, nil } var result struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return files, nil } else if len(result.Choices) == 0 { return files, nil } filtered := make([]os.FileInfo, 0) for _, part := range strings.Split(result.Choices[0].Message.Content, ",") { idx, err := strconv.Atoi(strings.TrimSpace(part)) if err != nil || idx < 0 || idx >= len(files) { continue } filtered = append(filtered, files[idx]) } return filtered, nil } ================================================ FILE: server/plugin/plg_widget_recent/config.go ================================================ package plg_widget_recent import ( . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Onload(func() { PluginEnable() PluginFolderName() PluginEnableAI() PluginEndpoint() PluginModel() PluginAPIKey() }) } var PluginEnable = func() bool { return Config.Get("features.recent.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Name = "enable" f.Type = "enable" f.Target = []string{ "recent_folder_name", "recent_enable_ai", "recent_model_address", "recent_model_name", "recent_api_key", } f.Default = false return f }).Bool() } var PluginFolderName = func() string { return Config.Get("features.recent.folder_name").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "recent_folder_name" f.Name = "folder_name" f.Type = "text" f.Description = "Name of the virtual folder for recent files" f.Default = "Recent" f.Placeholder = "Recent" return f }).String() } var PluginEnableAI = func() bool { return Config.Get("features.recent.enable_ai").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "recent_enable_ai" f.Name = "enable_ai" f.Type = "boolean" f.Description = "Use AI to power search within recent files" f.Default = false return f }).Bool() } var PluginEndpoint = func() string { return Config.Get("features.recent.model_address").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "recent_model_address" f.Name = "model_address" f.Type = "text" f.Default = "https://api.openai.com/v1/chat/completions" f.Placeholder = "default: https://api.openai.com/v1/chat/completions" return f }).String() } var PluginModel = func() string { return Config.Get("features.recent.model_name").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "recent_model_name" f.Name = "model_name" f.Type = "text" f.Default = "gpt-4o-mini" f.Placeholder = "default: gpt-4o-mini" return f }).String() } var PluginAPIKey = func() string { return Config.Get("features.recent.api_key").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} } f.Id = "recent_api_key" f.Name = "api_key" f.Type = "password" f.Placeholder = "sk-..." return f }).String() } ================================================ FILE: server/plugin/plg_widget_recent/db.go ================================================ package plg_widget_recent import ( "os" "database/sql" . "github.com/mickael-kerjean/filestash/server/common" ) var db *sql.DB func init() { Hooks.Register.Onload(func() { if err := initDB(); err != nil { Log.Error("plg_widget_recent::db err=cannot_init msg=%s", err.Error()) os.Exit(1) } }) } func initDB() (err error) { db, err = sql.Open("sqlite3", GetAbsolutePath(DB_PATH, "recent.db")) if err != nil { return err } _, err = db.Exec(` CREATE TABLE IF NOT EXISTS recent ( backend TEXT NOT NULL, user TEXT NOT NULL, path TEXT NOT NULL, last_accessed INTEGER NOT NULL, size INTEGER DEFAULT 0, PRIMARY KEY (backend, user, path) ); CREATE INDEX IF NOT EXISTS idx_recent_lookup ON recent(backend, user, last_accessed DESC); `) return err } ================================================ FILE: server/plugin/plg_widget_recent/decorator.go ================================================ package plg_widget_recent import ( "strings" "os" "time" . "github.com/mickael-kerjean/filestash/server/common" ) type Decorator struct { IBackend chroot string } func NewRecentDecorator(app *App) Decorator { return Decorator{app.Backend, EnforceDirectory(app.Session["path"])} } func (this Decorator) Ls(path string) ([]os.FileInfo, error) { files, err := this.IBackend.Ls(path) if err != nil { return nil, err } if strings.TrimPrefix(path, this.chroot) == "" { if folderName := PluginFolderName(); folderName != "" { files = append(files, File{ FName: folderName, FType: "directory", FSize: 0, FTime: time.Now().Unix(), }) } } return files, nil } ================================================ FILE: server/plugin/plg_widget_recent/index.go ================================================ package plg_widget_recent import ( "net/http" "strings" . "github.com/mickael-kerjean/filestash/server/common" ) func init() { Hooks.Register.Middleware(func(next HandlerFunc) HandlerFunc { return HandlerFunc(func(ctx *App, res http.ResponseWriter, req *http.Request) { if ctx.Share.Id != "" || !PluginEnable() { next(ctx, res, req) return } ctx.Backend = NewRecentDecorator(ctx) path := req.URL.Query().Get("path") recentPathAdd := "" recentPathRemove := "" if strings.HasSuffix(req.URL.Path, "/api/files/search") && req.Method == http.MethodGet && strings.HasPrefix(path, "/"+PluginFolderName()+"/") { files, err := SearchRecent(req.Context(), GenerateID(ctx.Session), getUser(ctx.Session), req.URL.Query().Get("q")) if err != nil { SendErrorResult(res, err) return } SendSuccessResults(res, files) return } if strings.HasSuffix(req.URL.Path, "/api/files/ls") && req.Method == http.MethodGet { if path == "/"+PluginFolderName()+"/" { files, err := GetRecent(GenerateID(ctx.Session), getUser(ctx.Session)) if err != nil { SendErrorResult(res, err) return } SendSuccessResultsWithMetadata(res, files, Metadata{ CanCreateFile: NewBool(false), CanCreateDirectory: NewBool(false), CanRename: NewBool(false), CanMove: NewBool(false), CanUpload: NewBool(false), CanDelete: NewBool(false), }) return } recentPathAdd = EnforceDirectory(req.URL.Query().Get("path")) } if strings.HasSuffix(req.URL.Path, "/api/files/cat") && (req.Method == http.MethodGet || req.Method == http.MethodPost) { recentPathAdd = req.URL.Query().Get("path") } if strings.HasSuffix(req.URL.Path, "/api/files/save") && (req.Method == http.MethodPost) { recentPathAdd = req.URL.Query().Get("path") } if strings.HasSuffix(req.URL.Path, "/api/files/rm") && req.Method == http.MethodPost { recentPathRemove = req.URL.Query().Get("path") } if strings.HasSuffix(req.URL.Path, "/api/files/mkdir") && req.Method == http.MethodPost { recentPathAdd = EnforceDirectory(req.URL.Query().Get("path")) } if strings.HasSuffix(req.URL.Path, "/api/files/mv") && req.Method == http.MethodPost { recentPathRemove = req.URL.Query().Get("from") recentPathAdd = req.URL.Query().Get("to") } if recentPathAdd != "" { var size int64 = 0 if b, err := ctx.Backend.Init(ctx.Session, ctx); err == nil { if finfo, err := b.Stat(JoinPath(ctx.Session["path"], recentPathAdd)); err == nil { size = finfo.Size() } } go StoreRecent( GenerateID(ctx.Session), getUser(ctx.Session), recentPathAdd, size, ) } if recentPathRemove != "" { go RemoveRecent( GenerateID(ctx.Session), getUser(ctx.Session), recentPathRemove, ) } next(ctx, res, req) }) }) } func getUser(session map[string]string) string { if session["user"] != "" { return session["user"] } return "unknown" } ================================================ FILE: server/plugin/plg_widget_recent/service.go ================================================ package plg_widget_recent import ( "context" "path/filepath" "strings" "time" "os" . "github.com/mickael-kerjean/filestash/server/common" ) func GetRecent(backendID string, user string) ([]os.FileInfo, error) { rows, err := db.Query(` SELECT path, last_accessed, size FROM recent WHERE backend = ? AND user = ? ORDER BY last_accessed DESC LIMIT 100 `, backendID, user) if err != nil { return nil, err } defer rows.Close() files := make([]os.FileInfo, 0) for rows.Next() { file := File{ FType: "file", FTime: -1, FSize: -1, } if err := rows.Scan(&file.FPath, &file.FTime, &file.FSize); err != nil { Log.Warning("plg_widget_recent::get err=%s", err.Error()) continue } if strings.HasSuffix(file.FPath, "/") { file.FType = "directory" } file.FName = filepath.Base(strings.TrimSuffix(file.FPath, "/")) files = append(files, file) } return files, nil } func StoreRecent(backendID string, user string, path string, size int64) error { _, err := db.Exec(` INSERT INTO recent(backend, user, path, last_accessed, size) VALUES(?, ?, ?, ?, ?) ON CONFLICT(backend, user, path) DO UPDATE SET last_accessed = excluded.last_accessed `, backendID, user, path, time.Now().UnixMilli(), size) if err != nil { Log.Warning("plg_widget_recent::store path=%s err=%s", path, err.Error()) return err } return err } func SearchRecent(ctx context.Context, backendID string, user string, query string) ([]os.FileInfo, error) { rows, err := db.Query(` SELECT path, last_accessed, size FROM recent WHERE backend = ? AND user = ? ORDER BY last_accessed DESC LIMIT 500 `, backendID, user) if err != nil { return nil, err } defer rows.Close() files := make([]os.FileInfo, 0) for rows.Next() { file := File{ FType: "file", FTime: -1, FSize: -1, } if err := rows.Scan(&file.FPath, &file.FTime, &file.FSize); err != nil { continue } if strings.HasSuffix(file.FPath, "/") { file.FType = "directory" } file.FName = filepath.Base(strings.TrimSuffix(file.FPath, "/")) files = append(files, file) } if len(files) == 0 || !PluginEnableAI() { return files, nil } return aiFilterRecent(ctx, query, files) } func RemoveRecent(backendID string, user string, path string) error { _, err := db.Exec(` DELETE FROM recent WHERE backend = ? AND user = ? AND path > ? `, backendID, user, path) if err != nil { Log.Warning("plg_widget_recent::remove path=%s err=%s", path, err.Error()) return err } return nil } ================================================ FILE: server/routes.go ================================================ package server import ( "fmt" "net/http" "net/http/pprof" "runtime" "runtime/debug" "strconv" "github.com/gorilla/mux" . "github.com/mickael-kerjean/filestash/server/common" . "github.com/mickael-kerjean/filestash/server/ctrl" . "github.com/mickael-kerjean/filestash/server/middleware" . "github.com/mickael-kerjean/filestash/server/pkg/workflow" ) func Build(r *mux.Router) { var middlewares []Middleware // API for Session session := r.PathPrefix(WithBase("/api/session")).Subrouter() middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, PluginInjector} session.HandleFunc("", NewMiddlewareChain(SessionGet, middlewares)).Methods("GET") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, RateLimiter, BodyParser, PluginInjector} session.HandleFunc("", NewMiddlewareChain(SessionAuthenticate, middlewares)).Methods("POST") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, PluginInjector} session.HandleFunc("", NewMiddlewareChain(SessionLogout, middlewares)).Methods("DELETE") middlewares = []Middleware{ApiHeaders, SecureHeaders, PluginInjector} session.HandleFunc("/auth/{service}", NewMiddlewareChain(SessionOAuthBackend, middlewares)).Methods("GET") session.HandleFunc("/auth/", NewMiddlewareChain(SessionAuthMiddleware, middlewares)).Methods("GET", "POST") // API for Admin Console admin := r.PathPrefix(WithBase("/admin/api")).Subrouter() middlewares = []Middleware{ApiHeaders, SecureOrigin, PluginInjector} admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionGet, middlewares)).Methods("GET") middlewares = []Middleware{ApiHeaders, SecureOrigin, RateLimiter, PluginInjector} admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionAuthenticate, middlewares)).Methods("POST") middlewares = []Middleware{ApiHeaders, AdminOnly, SecureOrigin, PluginInjector} admin.HandleFunc("/config", NewMiddlewareChain(PrivateConfigHandler, middlewares)).Methods("GET") admin.HandleFunc("/config", NewMiddlewareChain(PrivateConfigUpdateHandler, middlewares)).Methods("POST") admin.HandleFunc("/workflow", NewMiddlewareChain(WorkflowAll, middlewares)).Methods("GET") admin.HandleFunc("/workflow/{workflowID}", NewMiddlewareChain(WorkflowGet, middlewares)).Methods("GET") admin.HandleFunc("/workflow", NewMiddlewareChain(WorkflowUpsert, middlewares)).Methods("POST") admin.HandleFunc("/workflow", NewMiddlewareChain(WorkflowDelete, middlewares)).Methods("DELETE") admin.HandleFunc("/middlewares/authentication", NewMiddlewareChain(AdminAuthenticationMiddleware, middlewares)).Methods("GET") admin.HandleFunc("/audit", NewMiddlewareChain(FetchAuditHandler, middlewares)).Methods("GET") middlewares = []Middleware{IndexHeaders, AdminOnly, PluginInjector} admin.HandleFunc("/logs", NewMiddlewareChain(FetchLogHandler, middlewares)).Methods("GET") // API for File management files := r.PathPrefix(WithBase("/api/files")).Subrouter() middlewares = []Middleware{ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly, PluginInjector} files.HandleFunc("/cat", NewMiddlewareChain(FileCat, middlewares)).Methods("GET", "HEAD") files.HandleFunc("/zip", NewMiddlewareChain(FileDownloader, middlewares)).Methods("GET") files.HandleFunc("/unzip", NewMiddlewareChain(FileExtract, middlewares)).Methods("POST") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, LoggedInOnly, PluginInjector} files.HandleFunc("/cat", NewMiddlewareChain(FileAccess, middlewares)).Methods("OPTIONS") files.HandleFunc("/cat", NewMiddlewareChain(FileSave, middlewares)).Methods("POST", "PATCH") files.HandleFunc("/save", NewMiddlewareChain(FileSave, middlewares)).Methods("POST", "PATCH", "HEAD", "OPTIONS") files.HandleFunc("/ls", NewMiddlewareChain(FileLs, middlewares)).Methods("GET") files.HandleFunc("/mv", NewMiddlewareChain(FileMv, middlewares)).Methods("POST") files.HandleFunc("/rm", NewMiddlewareChain(FileRm, middlewares)).Methods("POST") files.HandleFunc("/mkdir", NewMiddlewareChain(FileMkdir, middlewares)).Methods("POST") files.HandleFunc("/touch", NewMiddlewareChain(FileTouch, middlewares)).Methods("POST") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, LoggedInOnly, PluginInjector} files.HandleFunc("/search", NewMiddlewareChain(FileSearch, middlewares)).Methods("GET") // API for Shared link share := r.PathPrefix(WithBase("/api/share")).Subrouter() middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, LoggedInOnly, PluginInjector} share.HandleFunc("", NewMiddlewareChain(ShareList, middlewares)).Methods("GET") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, BodyParser, PluginInjector} share.HandleFunc("/{share}/proof", NewMiddlewareChain(ShareVerifyProof, middlewares)).Methods("POST") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, CanManageShare, PluginInjector} share.HandleFunc("/{share}", NewMiddlewareChain(ShareDelete, middlewares)).Methods("DELETE") middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, BodyParser, CanManageShare, PluginInjector} share.HandleFunc("/{share}", NewMiddlewareChain(ShareUpsert, middlewares)).Methods("POST") meta := r.PathPrefix(WithBase("/api/metadata")).Subrouter() middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, LoggedInOnly, PluginInjector} meta.HandleFunc("", NewMiddlewareChain(MetaGet, middlewares)).Methods("GET") meta.HandleFunc("", NewMiddlewareChain(MetaUpsert, middlewares)).Methods("POST") meta.HandleFunc("/search", NewMiddlewareChain(MetaSearch, middlewares)).Methods("POST") // Webdav server / Shared Link middlewares = []Middleware{IndexHeaders, SecureHeaders, PluginInjector} r.HandleFunc(WithBase("/s/{share}"), NewMiddlewareChain(ServeFrontofficeHandler, middlewares)).Methods("GET") middlewares = []Middleware{WebdavBlacklist, SessionStart, PluginInjector} r.PathPrefix(WithBase("/s/{share}")).Handler(NewMiddlewareChain(WebdavHandler, middlewares)) // Application Resources middlewares = []Middleware{ApiHeaders, SecureHeaders, PluginInjector} r.HandleFunc(WithBase("/api/backend"), NewMiddlewareChain(AdminBackend, middlewares)).Methods("GET") r.HandleFunc(WithBase("/api/plugin"), NewMiddlewareChain(PluginExportHandler, append(middlewares, PublicCORS))).Methods("GET", "OPTIONS") r.HandleFunc(WithBase("/api/config"), NewMiddlewareChain(PublicConfigHandler, append(middlewares, PublicCORS))).Methods("GET", "OPTIONS") middlewares = []Middleware{StaticHeaders, SecureHeaders, PublicCORS, PluginInjector} r.PathPrefix(WithBase("/assets/bundle.js")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeBundle(), middlewares))).Methods("GET", "OPTIONS") r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/plugin/{name}.zip/{path:.+}"), NewMiddlewareChain(PluginStaticHandler, middlewares)).Methods("GET", "OPTIONS", "HEAD") r.HandleFunc(WithBase("/assets/"+BUILD_REF+"/plugin/{name}.zip"), NewMiddlewareChain(PluginDownloadHandler, middlewares)).Methods("GET") r.HandleFunc(WithBase("/assets/plugin/{name}.zip"), NewMiddlewareChain(PluginDownloadHandler, middlewares)).Methods("GET") r.PathPrefix(WithBase("/assets/"+BUILD_REF)).Handler(http.HandlerFunc(NewMiddlewareChain(ServeFile("/"), middlewares))).Methods("GET", "OPTIONS") r.PathPrefix(WithBase("/assets/")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeFile("/"), middlewares))).Methods("GET", "OPTIONS") r.HandleFunc(WithBase("/sw.js"), http.HandlerFunc(NewMiddlewareChain(ServeFile("/assets/"), middlewares))).Methods("GET") r.HandleFunc(WithBase("/favicon.ico"), NewMiddlewareChain(ServeFavicon, middlewares)).Methods("GET") // Other endpoints middlewares = []Middleware{ApiHeaders, PluginInjector, PublicCORS} r.HandleFunc(WithBase("/report"), NewMiddlewareChain(ReportHandler, middlewares)).Methods("POST", "OPTIONS") middlewares = []Middleware{IndexHeaders, SecureHeaders, PluginInjector} r.HandleFunc(WithBase("/about"), NewMiddlewareChain(AboutHandler, middlewares)).Methods("GET") r.HandleFunc(WithBase("/robots.txt"), NewMiddlewareChain(RobotsHandler, []Middleware{})) r.HandleFunc(WithBase("/manifest.json"), NewMiddlewareChain(ManifestHandler, []Middleware{})).Methods("GET") r.HandleFunc(WithBase("/.well-known/security.txt"), NewMiddlewareChain(WellKnownSecurityHandler, []Middleware{})).Methods("GET") r.HandleFunc(WithBase("/healthz"), NewMiddlewareChain(HealthHandler, []Middleware{})).Methods("GET", "HEAD") r.HandleFunc(WithBase("/custom.css"), NewMiddlewareChain(CustomCssHandler, []Middleware{})).Methods("GET") } func CatchAll(r *mux.Router) { middlewares := []Middleware{SecureHeaders, PluginInjector} r.PathPrefix(WithBase("/admin")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeBackofficeHandler, middlewares))).Methods("GET") middlewares = []Middleware{IndexHeaders, SecureHeaders, PluginInjector} r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(ServeFrontofficeHandler, middlewares))).Methods("GET", "POST") } func DebugRoutes(r *mux.Router) { r.HandleFunc("/debug/pprof/", pprof.Index) r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) r.HandleFunc("/debug/pprof/profile", pprof.Profile) r.HandleFunc("/debug/pprof/symbol", pprof.Symbol) r.HandleFunc("/debug/pprof/trace", pprof.Trace) r.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) r.Handle("/debug/pprof/heap", pprof.Handler("heap")) r.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) r.Handle("/debug/pprof/block", pprof.Handler("block")) r.Handle("/debug/pprof/allocs", pprof.Handler("allocs")) r.Handle("/debug/pprof/mutex", pprof.Handler("mutex")) r.HandleFunc("/debug/free", func(w http.ResponseWriter, r *http.Request) { debug.FreeOSMemory() w.Write([]byte("DONE")) }) bToMb := func(b uint64) string { return strconv.Itoa(int(b / 1024 / 1024)) } r.HandleFunc("/debug/memory", func(w http.ResponseWriter, r *http.Request) { var m runtime.MemStats runtime.ReadMemStats(&m) w.Write([]byte("

            ")) w.Write([]byte("Alloc = " + bToMb(m.Alloc) + "MiB
            ")) w.Write([]byte("TotalAlloc = " + bToMb(m.TotalAlloc) + "MiB
            ")) w.Write([]byte("Sys = " + bToMb(m.Sys) + "MiB
            ")) w.Write([]byte("NumGC = " + strconv.Itoa(int(m.NumGC)))) w.Write([]byte("

            ")) }) } func PluginRoutes(r *mux.Router) { // frontoffice overrides: it is the mean by which plugin can interact with the frontoffice for _, obj := range Hooks.Get.FrontendOverrides() { r.HandleFunc(obj, func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", GetMimeType(req.URL.String())) res.Write([]byte(fmt.Sprintf("/* Default '%s' */", obj))) }) } // map file types to application handler r.HandleFunc(WithBase("/overrides/xdg-open.js"), func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", GetMimeType(req.URL.String())) res.Write([]byte(`window.overrides["xdg-open"] = function(mime){`)) openers := Hooks.Get.XDGOpen() for i := 0; i < len(openers); i++ { res.Write([]byte(openers[i])) } res.Write([]byte(`return null;}`)) }) }